1
2
3
4
5
6 package org.tailormap.api.security;
7
8 import static org.apache.commons.lang3.StringUtils.isNotBlank;
9
10 import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
11 import jakarta.annotation.Nonnull;
12 import jakarta.annotation.PostConstruct;
13 import java.lang.invoke.MethodHandles;
14 import java.net.URI;
15 import java.net.http.HttpClient;
16 import java.net.http.HttpRequest;
17 import java.net.http.HttpResponse;
18 import java.util.HashMap;
19 import java.util.Iterator;
20 import java.util.Map;
21 import java.util.UUID;
22 import org.slf4j.Logger;
23 import org.slf4j.LoggerFactory;
24 import org.springframework.beans.factory.annotation.Value;
25 import org.springframework.security.oauth2.client.registration.ClientRegistration;
26 import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
27 import org.springframework.security.oauth2.core.AuthorizationGrantType;
28 import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
29 import org.tailormap.api.persistence.OIDCConfiguration;
30 import org.tailormap.api.repository.OIDCConfigurationRepository;
31
32 public class OIDCRepository implements ClientRegistrationRepository, Iterable<ClientRegistration> {
33 public static class OIDCRegistrationMetadata {
34 private boolean showForViewer;
35 private UUID image;
36
37 public boolean getShowForViewer() {
38 return showForViewer;
39 }
40
41 public UUID getImage() {
42 return image;
43 }
44 }
45
46 private static final Logger logger =
47 LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
48 private final OIDCConfigurationRepository oidcConfigurationRepository;
49
50 @Value("${tailormap-api.oidc.name:#{null}}")
51 private String oidcName;
52
53 @Value("${tailormap-api.oidc.issuer-uri:#{null}}")
54 private String oidcIssuerUri;
55
56 @Value("${tailormap-api.oidc.client-id:#{null}}")
57 private String oidcClientId;
58
59 @Value("${tailormap-api.oidc.client-secret:#{null}}")
60 private String oidcClientSecret;
61
62 @Value("${tailormap-api.oidc.user-name-attribute:#{null}}")
63 private String oidcUserNameAttribute;
64
65 @Value("${tailormap-api.oidc.show-for-viewer:false}")
66 private boolean oidcShowForViewer;
67
68 private final Map<String, ClientRegistration> registrations;
69
70 public OIDCRepository(OIDCConfigurationRepository repository) {
71 oidcConfigurationRepository = repository;
72 registrations = new HashMap<>();
73 }
74
75 @Override
76 public ClientRegistration findByRegistrationId(String registrationId) {
77 return registrations.get(registrationId);
78 }
79
80 @Override
81 @Nonnull
82 public Iterator<ClientRegistration> iterator() {
83 return registrations.values().iterator();
84 }
85
86 public OIDCRegistrationMetadata getMetadataForRegistrationId(String id) {
87 OIDCRegistrationMetadata metadata = new OIDCRegistrationMetadata();
88 if ("static".equals(id)) {
89 metadata.showForViewer = oidcShowForViewer;
90 } else {
91 metadata.showForViewer = true;
92 metadata.image = oidcConfigurationRepository
93 .findById(Long.valueOf(id))
94 .map(OIDCConfiguration::getImage)
95 .orElse(null);
96 }
97
98 return metadata;
99 }
100
101 @PostConstruct
102 public void synchronize() {
103 Map<String, ClientRegistration> newMap = new HashMap<>();
104
105 for (OIDCConfiguration configuration : oidcConfigurationRepository.findAll()) {
106 String id = "%d".formatted(configuration.getId());
107 try (HttpClient httpClient = HttpClient.newBuilder().build()) {
108 HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
109 .uri(new URI(configuration.getIssuerUrl() + "/.well-known/openid-configuration"));
110 HttpResponse<String> response =
111 httpClient.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString());
112
113 OIDCProviderMetadata metadata = OIDCProviderMetadata.parse(response.body());
114
115 newMap.put(
116 id,
117 ClientRegistration.withRegistrationId(id)
118 .clientId(configuration.getClientId())
119 .clientSecret(configuration.getClientSecret())
120 .clientName(configuration.getName())
121 .scope("openid")
122 .issuerUri(metadata.getIssuer().toString())
123 .clientAuthenticationMethod(
124 ClientAuthenticationMethod
125 .CLIENT_SECRET_BASIC)
126 .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
127 .authorizationUri(
128 metadata.getAuthorizationEndpointURI().toASCIIString())
129 .tokenUri(metadata.getTokenEndpointURI().toASCIIString())
130 .userInfoUri(metadata.getUserInfoEndpointURI().toASCIIString())
131 .providerConfigurationMetadata(metadata.toJSONObject())
132 .jwkSetUri(metadata.getJWKSetURI().toASCIIString())
133 .userNameAttributeName(configuration.getUserNameAttribute())
134 .redirectUri("{baseUrl}/api/oauth2/callback")
135 .build());
136 if (configuration.getStatus() != null) {
137 configuration.setStatus(null);
138 oidcConfigurationRepository.save(configuration);
139 }
140 } catch (Exception e) {
141 logger.error("Failed to create OIDC client registration for ID {}", id, e);
142 configuration.setStatus(e.toString());
143 oidcConfigurationRepository.save(configuration);
144 }
145 }
146
147 if (isNotBlank(oidcName) && isNotBlank(oidcIssuerUri) && isNotBlank(oidcClientId)) {
148 try (HttpClient httpClient = HttpClient.newBuilder().build()) {
149
150
151 if (!oidcIssuerUri.endsWith("/.well-known/openid-configuration")) {
152 oidcIssuerUri = oidcIssuerUri + "/.well-known/openid-configuration";
153 }
154 HttpRequest.Builder requestBuilder = HttpRequest.newBuilder().uri(new URI(oidcIssuerUri));
155 HttpResponse<String> response =
156 httpClient.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString());
157
158 OIDCProviderMetadata metadata = OIDCProviderMetadata.parse(response.body());
159 String id = "static";
160
161 newMap.put(
162 id,
163 ClientRegistration.withRegistrationId(id)
164 .clientId(oidcClientId)
165 .clientSecret(oidcClientSecret)
166 .clientName(oidcName)
167 .scope("openid")
168 .issuerUri(metadata.getIssuer().toString())
169 .clientAuthenticationMethod(
170 ClientAuthenticationMethod
171 .CLIENT_SECRET_BASIC)
172 .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
173 .authorizationUri(
174 metadata.getAuthorizationEndpointURI().toASCIIString())
175 .tokenUri(metadata.getTokenEndpointURI().toASCIIString())
176 .userInfoUri(metadata.getUserInfoEndpointURI().toASCIIString())
177 .providerConfigurationMetadata(metadata.toJSONObject())
178 .jwkSetUri(metadata.getJWKSetURI().toASCIIString())
179 .userNameAttributeName(oidcUserNameAttribute)
180 .redirectUri("{baseUrl}/api/oauth2/callback")
181 .build());
182 } catch (Exception e) {
183 logger.error("Failed to create static OIDC client registration", e);
184 }
185 }
186
187 registrations.clear();
188 registrations.putAll(newMap);
189 }
190 }