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.List;
23 import java.util.Map;
24
25 import javax.annotation.Nullable;
26
27 import org.opensaml.xmlsec.signature.support.SignatureConstants;
28
29 import com.fasterxml.jackson.annotation.JsonCreator;
30 import com.fasterxml.jackson.annotation.JsonProperty;
31 import com.fasterxml.jackson.core.JsonProcessingException;
32 import com.google.common.collect.ImmutableMap;
33 import com.google.common.collect.ImmutableMap.Builder;
34
35 import com.linecorp.armeria.server.saml.SamlBindingProtocol;
36 import com.linecorp.armeria.server.saml.SamlEndpoint;
37 import com.linecorp.armeria.server.saml.SamlNameIdFormat;
38 import com.linecorp.centraldogma.internal.Jackson;
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
75
76
77
78
79
80
81
82
83
84
85
86
87 final class SamlAuthProperties {
88
89
90
91 private static final String DEFAULT_SIGNING_KEY = "signing";
92
93
94
95
96 private static final String DEFAULT_ENCRYPTION_KEY = "encryption";
97
98
99
100
101 private final String entityId;
102
103
104
105
106 private final String hostname;
107
108
109
110
111 private final String signingKey;
112
113
114
115
116 private final String encryptionKey;
117
118
119
120
121 private final KeyStore keyStore;
122
123
124
125
126 @Nullable
127 private final Acs acs;
128
129
130
131
132 private final Idp idp;
133
134 @JsonCreator
135 SamlAuthProperties(
136 @JsonProperty("entityId") String entityId,
137 @JsonProperty("hostname") String hostname,
138 @JsonProperty("signingKey") @Nullable String signingKey,
139 @JsonProperty("encryptionKey") @Nullable String encryptionKey,
140 @JsonProperty("keyStore") KeyStore keyStore,
141 @JsonProperty("acs") @Nullable Acs acs,
142 @JsonProperty("idp") Idp idp) {
143 this.entityId = requireNonNull(entityId, "entityId");
144 this.hostname = requireNonNull(hostname, "hostname");
145 this.signingKey = firstNonNull(signingKey, DEFAULT_SIGNING_KEY);
146 this.encryptionKey = firstNonNull(encryptionKey, DEFAULT_ENCRYPTION_KEY);
147 this.keyStore = requireNonNull(keyStore, "keyStore");
148 this.acs = acs;
149 this.idp = requireNonNull(idp, "idp");
150 }
151
152 @JsonProperty
153 String entityId() {
154 return entityId;
155 }
156
157 @JsonProperty
158 String hostname() {
159 return hostname;
160 }
161
162 @JsonProperty
163 String signingKey() {
164 return signingKey;
165 }
166
167 @JsonProperty
168 String encryptionKey() {
169 return encryptionKey;
170 }
171
172 @JsonProperty
173 KeyStore keyStore() {
174 return keyStore;
175 }
176
177 @Nullable
178 @JsonProperty
179 Acs acs() {
180 return acs;
181 }
182
183 @JsonProperty
184 Idp idp() {
185 return idp;
186 }
187
188 @Override
189 public String toString() {
190 try {
191 return Jackson.writeValueAsPrettyString(this);
192 } catch (JsonProcessingException e) {
193 throw new IllegalStateException(e);
194 }
195 }
196
197 static class KeyStore {
198
199
200
201 private static final String DEFAULT_SIGNATURE_ALGORITHM = SignatureConstants.ALGO_ID_SIGNATURE_RSA;
202
203
204
205
206
207 private final String type;
208
209
210
211
212 private final String path;
213
214
215
216
217 @Nullable
218 private final String password;
219
220
221
222
223 @Nullable
224 private final Map<String, String> keyPasswords;
225
226
227
228
229
230
231
232 private final String signatureAlgorithm;
233
234 @JsonCreator
235 KeyStore(@JsonProperty("type") @Nullable String type,
236 @JsonProperty("path") String path,
237 @JsonProperty("password") @Nullable String password,
238 @JsonProperty("keyPasswords") @Nullable Map<String, String> keyPasswords,
239 @JsonProperty("signatureAlgorithm") @Nullable String signatureAlgorithm) {
240 this.type = firstNonNull(type, java.security.KeyStore.getDefaultType());
241 this.path = requireNonNull(path, "path");
242 this.password = password;
243 this.keyPasswords = keyPasswords;
244 this.signatureAlgorithm = firstNonNull(signatureAlgorithm, DEFAULT_SIGNATURE_ALGORITHM);
245 }
246
247 @JsonProperty
248 String type() {
249 return type;
250 }
251
252 @JsonProperty
253 String path() {
254 return path;
255 }
256
257 @Nullable
258 @JsonProperty
259 String password() {
260 return convertValue(password, "keyStore.password");
261 }
262
263 @JsonProperty
264 Map<String, String> keyPasswords() {
265 return sanitizePasswords(keyPasswords);
266 }
267
268 private static Map<String, String> sanitizePasswords(@Nullable Map<String, String> keyPasswords) {
269 if (keyPasswords == null) {
270 return ImmutableMap.of();
271 }
272 final ImmutableMap.Builder<String, String> builder = new Builder<>();
273 keyPasswords.forEach((key, password) -> builder.put(key, firstNonNull(
274 convertValue(password, "keyStore.keyPasswords"), "")));
275 return builder.build();
276 }
277
278 @JsonProperty
279 String signatureAlgorithm() {
280 return signatureAlgorithm;
281 }
282 }
283
284 static class Acs {
285
286 private final List<SamlEndpoint> endpoints;
287
288 @JsonCreator
289 Acs(@JsonProperty("endpoints") List<SamlEndpoint> endpoints) {
290 this.endpoints = requireNonNull(endpoints, "endpoints");
291 }
292
293 @JsonProperty
294 List<SamlEndpoint> endpoints() {
295 return endpoints;
296 }
297 }
298
299 static class Idp {
300
301
302
303 private final String entityId;
304
305
306
307
308 private final String uri;
309
310
311
312
313
314 private final SamlBindingProtocol binding;
315
316
317
318
319 private final String signingKey;
320
321
322
323
324 private final String encryptionKey;
325
326
327
328
329
330
331 @Nullable
332 private final String subjectLoginNameIdFormat;
333
334
335
336
337 @Nullable
338 private final String attributeLoginName;
339
340 @JsonCreator
341 Idp(@JsonProperty("entityId") String entityId,
342 @JsonProperty("uri") String uri,
343 @JsonProperty("binding") @Nullable String binding,
344 @JsonProperty("signingKey") @Nullable String signingKey,
345 @JsonProperty("encryptionKey") @Nullable String encryptionKey,
346 @JsonProperty("subjectLoginNameIdFormat") @Nullable String subjectLoginNameIdFormat,
347 @JsonProperty("attributeLoginName") @Nullable String attributeLoginName) {
348 this.entityId = requireNonNull(entityId, "entityId");
349 this.uri = requireNonNull(uri, "uri");
350 this.binding = binding != null ? SamlBindingProtocol.valueOf(binding)
351 : SamlBindingProtocol.HTTP_POST;
352 this.signingKey = firstNonNull(signingKey, entityId);
353 this.encryptionKey = firstNonNull(encryptionKey, entityId);
354
355 if (subjectLoginNameIdFormat == null && attributeLoginName == null) {
356 this.subjectLoginNameIdFormat = SamlNameIdFormat.EMAIL.urn();
357 this.attributeLoginName = null;
358 } else {
359 this.subjectLoginNameIdFormat = subjectLoginNameIdFormat;
360 this.attributeLoginName = attributeLoginName;
361 }
362 }
363
364 @JsonProperty
365 String entityId() {
366 return entityId;
367 }
368
369 @JsonProperty
370 String uri() {
371 return uri;
372 }
373
374 @JsonProperty
375 String binding() {
376 return binding.name();
377 }
378
379 @JsonProperty
380 String signingKey() {
381 return signingKey;
382 }
383
384 @JsonProperty
385 String encryptionKey() {
386 return encryptionKey;
387 }
388
389 @Nullable
390 @JsonProperty
391 String subjectLoginNameIdFormat() {
392 return subjectLoginNameIdFormat;
393 }
394
395 @Nullable
396 @JsonProperty
397 String attributeLoginName() {
398 return attributeLoginName;
399 }
400
401 SamlEndpoint endpoint() {
402 switch (binding) {
403 case HTTP_POST:
404 return SamlEndpoint.ofHttpPost(uri);
405 case HTTP_REDIRECT:
406 return SamlEndpoint.ofHttpRedirect(uri);
407 default:
408 throw new IllegalStateException("Failed to get an endpoint of the IdP: " + entityId);
409 }
410 }
411 }
412 }