1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package com.linecorp.centraldogma.server.auth.saml;
17
18 import static com.google.common.base.MoreObjects.firstNonNull;
19 import static com.linecorp.centraldogma.server.CentralDogmaConfig.convertValue;
20 import static java.util.Objects.requireNonNull;
21
22 import java.util.Map;
23
24 import javax.annotation.Nullable;
25
26 import org.opensaml.xmlsec.signature.support.SignatureConstants;
27
28 import com.fasterxml.jackson.annotation.JsonCreator;
29 import com.fasterxml.jackson.annotation.JsonProperty;
30 import com.fasterxml.jackson.core.JsonProcessingException;
31 import com.google.common.collect.ImmutableMap;
32 import com.google.common.collect.ImmutableMap.Builder;
33
34 import com.linecorp.armeria.server.saml.SamlBindingProtocol;
35 import com.linecorp.armeria.server.saml.SamlEndpoint;
36 import com.linecorp.armeria.server.saml.SamlNameIdFormat;
37 import com.linecorp.centraldogma.internal.Jackson;
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74 final class SamlAuthProperties {
75
76
77
78 private static final String DEFAULT_SIGNING_KEY = "signing";
79
80
81
82
83 private static final String DEFAULT_ENCRYPTION_KEY = "encryption";
84
85
86
87
88 private final String entityId;
89
90
91
92
93 private final String hostname;
94
95
96
97
98 private final String signingKey;
99
100
101
102
103 private final String encryptionKey;
104
105
106
107
108 private final KeyStore keyStore;
109
110
111
112
113 private final Idp idp;
114
115 @JsonCreator
116 SamlAuthProperties(
117 @JsonProperty("entityId") String entityId,
118 @JsonProperty("hostname") String hostname,
119 @JsonProperty("signingKey") @Nullable String signingKey,
120 @JsonProperty("encryptionKey") @Nullable String encryptionKey,
121 @JsonProperty("keyStore") KeyStore keyStore,
122 @JsonProperty("idp") Idp idp) {
123 this.entityId = requireNonNull(entityId, "entityId");
124 this.hostname = requireNonNull(hostname, "hostname");
125 this.signingKey = firstNonNull(signingKey, DEFAULT_SIGNING_KEY);
126 this.encryptionKey = firstNonNull(encryptionKey, DEFAULT_ENCRYPTION_KEY);
127 this.keyStore = requireNonNull(keyStore, "keyStore");
128 this.idp = requireNonNull(idp, "idp");
129 }
130
131 @JsonProperty
132 public String entityId() {
133 return entityId;
134 }
135
136 @JsonProperty
137 public String hostname() {
138 return hostname;
139 }
140
141 @JsonProperty
142 public String signingKey() {
143 return signingKey;
144 }
145
146 @JsonProperty
147 public String encryptionKey() {
148 return encryptionKey;
149 }
150
151 @JsonProperty
152 public KeyStore keyStore() {
153 return keyStore;
154 }
155
156 @JsonProperty
157 public Idp idp() {
158 return idp;
159 }
160
161 @Override
162 public String toString() {
163 try {
164 return Jackson.writeValueAsPrettyString(this);
165 } catch (JsonProcessingException e) {
166 throw new IllegalStateException(e);
167 }
168 }
169
170 static class KeyStore {
171
172
173
174 private static final String DEFAULT_SIGNATURE_ALGORITHM = SignatureConstants.ALGO_ID_SIGNATURE_RSA;
175
176
177
178
179
180 private final String type;
181
182
183
184
185 private final String path;
186
187
188
189
190 @Nullable
191 private final String password;
192
193
194
195
196 @Nullable
197 private final Map<String, String> keyPasswords;
198
199
200
201
202
203
204
205 private final String signatureAlgorithm;
206
207 @JsonCreator
208 KeyStore(@JsonProperty("type") @Nullable String type,
209 @JsonProperty("path") String path,
210 @JsonProperty("password") @Nullable String password,
211 @JsonProperty("keyPasswords") @Nullable Map<String, String> keyPasswords,
212 @JsonProperty("signatureAlgorithm") @Nullable String signatureAlgorithm) {
213 this.type = firstNonNull(type, java.security.KeyStore.getDefaultType());
214 this.path = requireNonNull(path, "path");
215 this.password = password;
216 this.keyPasswords = keyPasswords;
217 this.signatureAlgorithm = firstNonNull(signatureAlgorithm, DEFAULT_SIGNATURE_ALGORITHM);
218 }
219
220 @JsonProperty
221 public String type() {
222 return type;
223 }
224
225 @JsonProperty
226 public String path() {
227 return path;
228 }
229
230 @Nullable
231 @JsonProperty
232 public String password() {
233 return convertValue(password, "keyStore.password");
234 }
235
236 @JsonProperty
237 public Map<String, String> keyPasswords() {
238 return sanitizePasswords(keyPasswords);
239 }
240
241 private static Map<String, String> sanitizePasswords(@Nullable Map<String, String> keyPasswords) {
242 if (keyPasswords == null) {
243 return ImmutableMap.of();
244 }
245 final ImmutableMap.Builder<String, String> builder = new Builder<>();
246 keyPasswords.forEach((key, password) -> builder.put(key, firstNonNull(
247 convertValue(password, "keyStore.keyPasswords"), "")));
248 return builder.build();
249 }
250
251 @JsonProperty
252 public String signatureAlgorithm() {
253 return signatureAlgorithm;
254 }
255 }
256
257 static class Idp {
258
259
260
261 private final String entityId;
262
263
264
265
266 private final String uri;
267
268
269
270
271
272 private final SamlBindingProtocol binding;
273
274
275
276
277 private final String signingKey;
278
279
280
281
282 private final String encryptionKey;
283
284
285
286
287
288
289 @Nullable
290 private final String subjectLoginNameIdFormat;
291
292
293
294
295 @Nullable
296 private final String attributeLoginName;
297
298 @JsonCreator
299 Idp(@JsonProperty("entityId") String entityId,
300 @JsonProperty("uri") String uri,
301 @JsonProperty("binding") @Nullable String binding,
302 @JsonProperty("signingKey") @Nullable String signingKey,
303 @JsonProperty("encryptionKey") @Nullable String encryptionKey,
304 @JsonProperty("subjectLoginNameIdFormat") @Nullable String subjectLoginNameIdFormat,
305 @JsonProperty("attributeLoginName") @Nullable String attributeLoginName) {
306 this.entityId = requireNonNull(entityId, "entityId");
307 this.uri = requireNonNull(uri, "uri");
308 this.binding = binding != null ? SamlBindingProtocol.valueOf(binding)
309 : SamlBindingProtocol.HTTP_POST;
310 this.signingKey = firstNonNull(signingKey, entityId);
311 this.encryptionKey = firstNonNull(encryptionKey, entityId);
312
313 if (subjectLoginNameIdFormat == null && attributeLoginName == null) {
314 this.subjectLoginNameIdFormat = SamlNameIdFormat.EMAIL.urn();
315 this.attributeLoginName = null;
316 } else {
317 this.subjectLoginNameIdFormat = subjectLoginNameIdFormat;
318 this.attributeLoginName = attributeLoginName;
319 }
320 }
321
322 @JsonProperty
323 public String entityId() {
324 return entityId;
325 }
326
327 @JsonProperty
328 public String uri() {
329 return uri;
330 }
331
332 @JsonProperty
333 public String binding() {
334 return binding.name();
335 }
336
337 @JsonProperty
338 public String signingKey() {
339 return signingKey;
340 }
341
342 @JsonProperty
343 public String encryptionKey() {
344 return encryptionKey;
345 }
346
347 @Nullable
348 @JsonProperty
349 public String subjectLoginNameIdFormat() {
350 return subjectLoginNameIdFormat;
351 }
352
353 @Nullable
354 @JsonProperty
355 public String attributeLoginName() {
356 return attributeLoginName;
357 }
358
359 public SamlEndpoint endpoint() {
360 switch (binding) {
361 case HTTP_POST:
362 return SamlEndpoint.ofHttpPost(uri);
363 case HTTP_REDIRECT:
364 return SamlEndpoint.ofHttpRedirect(uri);
365 default:
366 throw new IllegalStateException("Failed to get an endpoint of the IdP: " + entityId);
367 }
368 }
369 }
370 }