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