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.PostConstruct;
12 import java.lang.invoke.MethodHandles;
13 import java.net.URI;
14 import java.net.http.HttpClient;
15 import java.net.http.HttpRequest;
16 import java.net.http.HttpResponse;
17 import java.util.HashMap;
18 import java.util.Iterator;
19 import java.util.Map;
20 import java.util.UUID;
21 import org.jspecify.annotations.NonNull;
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 public Iterator<ClientRegistration> iterator() {
82 return registrations.values().iterator();
83 }
84
85 public OIDCRegistrationMetadata getMetadataForRegistrationId(String id) {
86 OIDCRegistrationMetadata metadata = new OIDCRegistrationMetadata();
87 if ("static".equals(id)) {
88 metadata.showForViewer = oidcShowForViewer;
89 } else {
90 metadata.showForViewer = true;
91 metadata.image = oidcConfigurationRepository
92 .findById(Long.valueOf(id))
93 .map(OIDCConfiguration::getImage)
94 .orElse(null);
95 }
96
97 return metadata;
98 }
99
100 @PostConstruct
101 public void synchronize() {
102 Map<String, ClientRegistration> newMap = new HashMap<>();
103
104 for (OIDCConfiguration configuration : oidcConfigurationRepository.findAll()) {
105 String id = "%d".formatted(configuration.getId());
106 try (HttpClient httpClient = HttpClient.newBuilder().build()) {
107 HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
108 .uri(new URI(configuration.getIssuerUrl() + "/.well-known/openid-configuration"));
109 HttpResponse<String> response =
110 httpClient.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString());
111
112 OIDCProviderMetadata metadata = OIDCProviderMetadata.parse(response.body());
113
114 newMap.put(
115 id,
116 ClientRegistration.withRegistrationId(id)
117 .clientId(configuration.getClientId())
118 .clientSecret(configuration.getClientSecret())
119 .clientName(configuration.getName())
120 .scope("openid")
121 .issuerUri(metadata.getIssuer().toString())
122 .clientAuthenticationMethod(
123 ClientAuthenticationMethod
124 .CLIENT_SECRET_BASIC)
125 .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
126 .authorizationUri(
127 metadata.getAuthorizationEndpointURI().toASCIIString())
128 .tokenUri(metadata.getTokenEndpointURI().toASCIIString())
129 .userInfoUri(metadata.getUserInfoEndpointURI().toASCIIString())
130 .providerConfigurationMetadata(metadata.toJSONObject())
131 .jwkSetUri(metadata.getJWKSetURI().toASCIIString())
132 .userNameAttributeName(configuration.getUserNameAttribute())
133 .redirectUri("{baseUrl}/api/oauth2/callback")
134 .build());
135 if (configuration.getStatus() != null) {
136 configuration.setStatus(null);
137 oidcConfigurationRepository.save(configuration);
138 }
139 } catch (Exception e) {
140 logger.error("Failed to create OIDC client registration for ID {}", id, e);
141 configuration.setStatus(e.toString());
142 oidcConfigurationRepository.save(configuration);
143 }
144 }
145
146 if (isNotBlank(oidcName) && isNotBlank(oidcIssuerUri) && isNotBlank(oidcClientId)) {
147 try (HttpClient httpClient = HttpClient.newBuilder().build()) {
148
149
150 if (!oidcIssuerUri.endsWith("/.well-known/openid-configuration")) {
151 oidcIssuerUri = oidcIssuerUri + "/.well-known/openid-configuration";
152 }
153 HttpRequest.Builder requestBuilder = HttpRequest.newBuilder().uri(new URI(oidcIssuerUri));
154 HttpResponse<String> response =
155 httpClient.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString());
156
157 OIDCProviderMetadata metadata = OIDCProviderMetadata.parse(response.body());
158 String id = "static";
159
160 newMap.put(
161 id,
162 ClientRegistration.withRegistrationId(id)
163 .clientId(oidcClientId)
164 .clientSecret(oidcClientSecret)
165 .clientName(oidcName)
166 .scope("openid")
167 .issuerUri(metadata.getIssuer().toString())
168 .clientAuthenticationMethod(
169 ClientAuthenticationMethod
170 .CLIENT_SECRET_BASIC)
171 .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
172 .authorizationUri(
173 metadata.getAuthorizationEndpointURI().toASCIIString())
174 .tokenUri(metadata.getTokenEndpointURI().toASCIIString())
175 .userInfoUri(metadata.getUserInfoEndpointURI().toASCIIString())
176 .providerConfigurationMetadata(metadata.toJSONObject())
177 .jwkSetUri(metadata.getJWKSetURI().toASCIIString())
178 .userNameAttributeName(oidcUserNameAttribute)
179 .redirectUri("{baseUrl}/api/oauth2/callback")
180 .build());
181 } catch (Exception e) {
182 logger.error("Failed to create static OIDC client registration", e);
183 }
184 }
185
186 registrations.clear();
187 registrations.putAll(newMap);
188 }
189 }