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 final HttpClient httpClient = HttpClient.newBuilder()
106 .followRedirects(HttpClient.Redirect.NORMAL)
107 .build();
108 for (OIDCConfiguration configuration : oidcConfigurationRepository.findAll()) {
109 String id = String.format("%d", configuration.getId());
110 try {
111 HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
112 .uri(new URI(configuration.getIssuerUrl() + "/.well-known/openid-configuration"));
113 HttpResponse<String> response =
114 httpClient.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString());
115
116 OIDCProviderMetadata metadata = OIDCProviderMetadata.parse(response.body());
117
118 newMap.put(
119 id,
120 ClientRegistration.withRegistrationId(id)
121 .clientId(configuration.getClientId())
122 .clientSecret(configuration.getClientSecret())
123 .clientName(configuration.getName())
124 .scope("openid")
125 .issuerUri(metadata.getIssuer().toString())
126 .clientAuthenticationMethod(
127 ClientAuthenticationMethod
128 .CLIENT_SECRET_BASIC)
129 .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
130 .authorizationUri(
131 metadata.getAuthorizationEndpointURI().toASCIIString())
132 .tokenUri(metadata.getTokenEndpointURI().toASCIIString())
133 .userInfoUri(metadata.getUserInfoEndpointURI().toASCIIString())
134 .providerConfigurationMetadata(metadata.toJSONObject())
135 .jwkSetUri(metadata.getJWKSetURI().toASCIIString())
136 .userNameAttributeName(configuration.getUserNameAttribute())
137 .redirectUri("{baseUrl}/api/oauth2/callback")
138 .build());
139 if (configuration.getStatus() != null) {
140 configuration.setStatus(null);
141 oidcConfigurationRepository.save(configuration);
142 }
143 } catch (Exception e) {
144 logger.error("Failed to create OIDC client registration for ID {}", id, e);
145 configuration.setStatus(e.toString());
146 oidcConfigurationRepository.save(configuration);
147 }
148 }
149
150 if (isNotBlank(oidcName) && isNotBlank(oidcIssuerUri) && isNotBlank(oidcClientId)) {
151 try {
152
153
154 if (!oidcIssuerUri.endsWith("/.well-known/openid-configuration")) {
155 oidcIssuerUri = oidcIssuerUri + "/.well-known/openid-configuration";
156 }
157 HttpRequest.Builder requestBuilder = HttpRequest.newBuilder().uri(new URI(oidcIssuerUri));
158 HttpResponse<String> response =
159 httpClient.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString());
160
161 OIDCProviderMetadata metadata = OIDCProviderMetadata.parse(response.body());
162 String id = "static";
163
164 newMap.put(
165 id,
166 ClientRegistration.withRegistrationId(id)
167 .clientId(oidcClientId)
168 .clientSecret(oidcClientSecret)
169 .clientName(oidcName)
170 .scope("openid")
171 .issuerUri(metadata.getIssuer().toString())
172 .clientAuthenticationMethod(
173 ClientAuthenticationMethod
174 .CLIENT_SECRET_BASIC)
175 .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
176 .authorizationUri(
177 metadata.getAuthorizationEndpointURI().toASCIIString())
178 .tokenUri(metadata.getTokenEndpointURI().toASCIIString())
179 .userInfoUri(metadata.getUserInfoEndpointURI().toASCIIString())
180 .providerConfigurationMetadata(metadata.toJSONObject())
181 .jwkSetUri(metadata.getJWKSetURI().toASCIIString())
182 .userNameAttributeName(oidcUserNameAttribute)
183 .redirectUri("{baseUrl}/api/oauth2/callback")
184 .build());
185 } catch (Exception e) {
186 logger.error("Failed to create static OIDC client registration", e);
187 }
188 }
189
190 registrations.clear();
191 registrations.putAll(newMap);
192 }
193 }