1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 package dk.sosi.seal.model;
31
32 import dk.sosi.seal.model.constants.*;
33 import dk.sosi.seal.model.dombuilders.AbstractDOMBuilder;
34 import dk.sosi.seal.pki.CredentialVaultSignatureProvider;
35 import dk.sosi.seal.pki.Federation;
36 import dk.sosi.seal.vault.CredentialVault;
37 import dk.sosi.seal.xml.XmlUtil;
38 import org.apache.commons.logging.Log;
39 import org.apache.commons.logging.LogFactory;
40 import org.w3c.dom.Document;
41 import org.w3c.dom.Element;
42 import org.w3c.dom.Node;
43
44 import java.util.Date;
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62 public final class IdentityTokenBuilder extends AbstractDOMBuilder<IdentityToken> {
63
64 private static final Log LOG = LogFactory.getLog(IdentityTokenBuilder.class);
65
66 private static final String ASSURANCELEVEL = "3";
67 private static final String AUTHN_CONTEXT_CLASS_REF = "urn:oasis:names:tc:SAML:2.0:ac:classes:X509";
68 private static final String AUTHN_INSTANT_ATTRIBUTE = "AuthnInstant";
69 private static final String CONFIRMATION_METHOD = "urn:oasis:names:tc:SAML:2.0:cm:holder-of-key";
70 private static final String FORMAT_ATTRIBUTE = "Format";
71 private static final String ISSUE_INSTANT_ATTRIBUTE = "IssueInstant";
72 private static final String KEY_ID = "SigningKey";
73 private static final String NOT_BEFORE_ATTRIBUTE = "NotBefore";
74 private static final String NOT_ON_OR_AFTER_ATTRIBUTE = "NotOnOrAfter";
75 private static final String SAML_ID_ATTRIBUTE = IDValues.ID;
76 private static final String SAML_VERSION = "2.0";
77 private static final String SPECVERSION = "DK-SAML-2.0";
78 private static final String VERSION_ATTRIBUTE = "Version";
79
80
81
82
83
84
85
86
87
88
89 public static IdentityToken constructFromURLString(String urlParameterValue, Federation federation) {
90 return new IdentityToken(urlParameterValue, federation);
91 }
92
93 private String audienceRestriction;
94 private boolean certAsReference;
95 private final CredentialVault credentialVault;
96 private boolean extractCprNumber;
97 private boolean extractCvrNumberIdentifier;
98 private boolean extractOrganizationName;
99 private boolean extractITSystemName;
100 private boolean extractUserAuthorizationCode;
101 private boolean extractUserEducationCode;
102 private String issuer;
103 private Date notBefore;
104 private Date notOnOrAfter;
105 private Node subjectNode;
106 private UserIDCard userIdCard;
107 private String assertionID;
108
109
110
111
112
113
114
115 public IdentityTokenBuilder(CredentialVault credentialVault) {
116 if(credentialVault == null) {
117 throw new IllegalArgumentException("credentialVault cannot be null");
118 }
119 this.credentialVault = credentialVault;
120 }
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140 public final IdentityToken build() throws ModelException {
141 Document document = createDocument();
142
143 SignatureConfiguration signatureConfiguration = new SignatureConfiguration(new String[] { assertionID }, assertionID, IDValues.Id);
144 signatureConfiguration.setSignatureSiblingNode(subjectNode);
145 signatureConfiguration.setAddCertificateAsReference(certAsReference);
146 signatureConfiguration.setKeyInfoId(KEY_ID);
147
148 SignatureUtil.sign(new CredentialVaultSignatureProvider(credentialVault), document, signatureConfiguration);
149
150 return new IdentityToken((Element)document.getFirstChild());
151 }
152
153
154
155
156
157
158
159
160
161
162
163
164
165 public IdentityTokenBuilder requireCertificateAsReference() {
166 this.certAsReference = true;
167 return this;
168 }
169
170
171
172
173
174
175
176
177
178
179
180
181
182 public IdentityTokenBuilder requireCprNumber() {
183 this.extractCprNumber = true;
184 return this;
185 }
186
187
188
189
190
191
192
193
194
195
196
197
198
199 public IdentityTokenBuilder requireCvrNumberIdentifier() {
200 this.extractCvrNumberIdentifier = true;
201 return this;
202 }
203
204
205
206
207
208
209
210
211
212
213
214
215
216 public IdentityTokenBuilder requireOrganizationName() {
217 this.extractOrganizationName = true;
218 return this;
219 }
220
221
222
223
224
225
226
227
228
229
230
231
232
233 public IdentityTokenBuilder requireITSystemName() {
234 this.extractITSystemName = true;
235 return this;
236 }
237
238
239
240
241
242
243
244
245
246
247
248
249
250 public IdentityTokenBuilder requireUserAuthorizationCode() {
251 this.extractUserAuthorizationCode = true;
252 return this;
253 }
254
255
256
257
258
259
260
261
262
263
264
265
266
267 public IdentityTokenBuilder requireUserEducationCode() {
268 this.extractUserEducationCode = true;
269 return this;
270 }
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286 public IdentityTokenBuilder setAudienceRestriction(String audienceRestriction) {
287 this.audienceRestriction = audienceRestriction;
288 return this;
289 }
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306 public IdentityTokenBuilder setIssuer(String issuer) {
307 this.issuer = issuer;
308 return this;
309 }
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325 public IdentityTokenBuilder setNotBefore(Date notBefore) {
326 this.notBefore = notBefore;
327 return this;
328 }
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344 public IdentityTokenBuilder setNotOnOrAfter(Date notOnOrAfter) {
345 this.notOnOrAfter = notOnOrAfter;
346 return this;
347 }
348
349
350
351
352
353
354
355
356 public IdentityTokenBuilder setUserIdCard(UserIDCard userIdCard) {
357 this.userIdCard = userIdCard;
358 return this;
359 }
360
361 @Override
362 protected final void addBodyContent(Document doc, Element body) {
363 body.appendChild(createIssuer());
364 subjectNode = createSubject(doc);
365 body.appendChild(subjectNode);
366 body.appendChild(createConditions());
367 body.appendChild(createAuthStatement());
368 body.appendChild(createAttributeStatementElement(doc));
369 }
370
371 @Override
372 protected final void addHeaderContent(Document doc, Element header) {
373
374 }
375
376 @Override
377 protected void addRootAttributes(Element root) {
378 root.setAttribute(ISSUE_INSTANT_ATTRIBUTE, XmlUtil.getDateFormat(true).format(new Date()));
379 assertionID = XmlUtil.generateRandomNCName();
380 root.setAttribute(SAML_ID_ATTRIBUTE, assertionID);
381 root.setAttribute(VERSION_ATTRIBUTE, SAML_VERSION);
382
383 addNS(root, NameSpaces.NS_SAML, NameSpaces.SAML2ASSERTION_SCHEMA);
384 addNS(root, NameSpaces.NS_DS, NameSpaces.DSIG_SCHEMA);
385 addNS(root, NameSpaces.NS_XSI, NameSpaces.XMLSCHEMAINSTANCE_SCHEMA);
386 addNS(root, NameSpaces.NS_XS, NameSpaces.XSD_SCHEMA);
387 }
388
389
390
391
392 @Override
393 protected final void appendBody(Document doc, Element envelope) {
394 addBodyContent(doc, envelope);
395 }
396
397
398
399
400 @Override
401 protected final void appendHeader(Document doc, Element envelope) {
402
403 }
404
405 @Override
406 protected Element appendRoot(Document doc) {
407 Element envelope = createElement(SAMLTags.assertion);
408 addRootAttributes(envelope);
409 doc.appendChild(envelope);
410 return envelope;
411 }
412
413 @Override
414 protected void validateBeforeBuild() {
415 validate("userIdCard", userIdCard);
416 validate("notBefore", notBefore);
417 validate("audienceRestriction", audienceRestriction);
418 validate("notOnOrAfter", notOnOrAfter);
419 validate("issuer", issuer);
420
421 if(!notBefore.before(notOnOrAfter)) {
422 throw new ModelException("notBefore is after notOnOrAfter");
423 }
424 }
425
426 private Node createAttributeElement(Document doc, String name, String friendlyName, String value) {
427 Element attributeElm = createElement(SAMLTags.attribute);
428 attributeElm.setAttribute(SAMLAttributes.NAME_FORMAT, "urn:oasis:names:tc:SAML:2.0:attrname-format:basic");
429 attributeElm.setAttribute(SAMLAttributes.NAME, name);
430 if(friendlyName != null) {
431 attributeElm.setAttribute(SAMLAttributes.FRIENDLY_NAME, friendlyName);
432 }
433
434 Element attributeValue = createElement(SAMLTags.attributeValue);
435 attributeValue.setAttributeNS(NameSpaces.XMLSCHEMAINSTANCE_SCHEMA, XSIAttributes.TYPE_PREFIXED, NameSpaces.NS_XS + ":string");
436 attributeValue.setTextContent(value);
437 attributeElm.appendChild(attributeValue);
438
439 return attributeElm;
440 }
441
442 private Element createAttributeStatementElement(Document doc) {
443 Element attributeStatementElm = createElement(SAMLTags.attributeStatement);
444 attributeStatementElm.appendChild(createAttributeElement(doc, "urn:oid:2.5.4.4", "surName", userIdCard.getUserInfo().getSurName()));
445 attributeStatementElm.appendChild(createAttributeElement(doc, "urn:oid:2.5.4.3", "CommonName", userIdCard.getUserInfo().getGivenName() + " " + userIdCard.getUserInfo().getSurName()));
446 attributeStatementElm.appendChild(createAttributeElement(doc, "urn:oid:0.9.2342.19200300.100.1.3", "email", userIdCard.getUserInfo().getEmail()));
447 attributeStatementElm.appendChild(createAttributeElement(doc, "dk:gov:saml:attribute:AssuranceLevel", null, ASSURANCELEVEL));
448 attributeStatementElm.appendChild(createAttributeElement(doc, "dk:gov:saml:attribute:SpecVer", null, SPECVERSION));
449 if(extractCvrNumberIdentifier) {
450 if(!SubjectIdentifierTypeValues.CVR_NUMBER.equals(userIdCard.getSystemInfo().getCareProvider().getType())) {
451 throw new IllegalArgumentException("CVR no. not provided in CareProvider - was " + userIdCard.getSystemInfo().getCareProvider().getType());
452 }
453 attributeStatementElm.appendChild(createAttributeElement(doc, "dk:gov:saml:attribute:CvrNumberIdentifier", null, userIdCard.getSystemInfo().getCareProvider().getID()));
454 }
455 if(extractOrganizationName) {
456 attributeStatementElm.appendChild(createAttributeElement(doc, "urn:oid:2.5.4.10", "organizationName", userIdCard.getSystemInfo().getCareProvider().getOrgName()));
457 }
458 if(extractCprNumber) {
459 attributeStatementElm.appendChild(createAttributeElement(doc, "dk:gov:saml:attribute:CprNumberIdentifier", null, userIdCard.getUserInfo().getCPR()));
460 }
461 if(extractUserAuthorizationCode) {
462 if(userIdCard.getUserInfo().getAuthorizationCode() == null) {
463 throw new IllegalArgumentException("UserAuthorizationCode missing in UserIdCard - cannot create IdentityToken with UserAuthorizationCode");
464 }
465 attributeStatementElm.appendChild(createAttributeElement(doc, "dk:healthcare:saml:attribute:UserAuthorizationCode", null, userIdCard.getUserInfo().getAuthorizationCode()));
466 }
467 if(extractITSystemName) {
468 attributeStatementElm.appendChild(createAttributeElement(doc, "dk:healthcare:saml:attribute:ITSystemName", null, userIdCard.getSystemInfo().getITSystemName()));
469 }
470 if(extractUserEducationCode) {
471 if(userIdCard.getUserInfo().getAuthorizationCode() == null) {
472 throw new IllegalArgumentException("UserAuthorizationCode must also be set on UserIdCard in order to treat contents of UserRole as UserEducationCode");
473 }
474 attributeStatementElm.appendChild(createAttributeElement(doc, "dk:healthcare:saml:attribute:UserEducationCode", null, userIdCard.getUserInfo().getRole()));
475 }
476 return attributeStatementElm;
477 }
478
479 private Node createAuthStatement() {
480 Element authnStatementElm = createElement(SAMLTags.authnStatement);
481 authnStatementElm.setAttribute(AUTHN_INSTANT_ATTRIBUTE, XmlUtil.getDateFormat(true).format(userIdCard.getCreatedDate()));
482
483 Element authnContextElm = createElement(SAMLTags.authnContext);
484 Element authContextClassRefElm = createElement(SAMLTags.authnContextClassRef);
485 authContextClassRefElm.setTextContent(AUTHN_CONTEXT_CLASS_REF);
486 authnContextElm.appendChild(authContextClassRefElm);
487 authnStatementElm.appendChild(authnContextElm);
488 return authnStatementElm;
489 }
490
491 private Node createConditions() {
492 Element conditionsElm = createElement(SAMLTags.conditions);
493 conditionsElm.setAttribute(NOT_BEFORE_ATTRIBUTE, XmlUtil.getDateFormat(true).format(notBefore));
494 conditionsElm.setAttribute(NOT_ON_OR_AFTER_ATTRIBUTE, XmlUtil.getDateFormat(true).format(notOnOrAfter));
495
496 Element audienceRestrictionElm = createElement(SAMLTags.audienceRestriction);
497 conditionsElm.appendChild(audienceRestrictionElm);
498
499 Element audienceElm = createElement(SAMLTags.audience);
500 audienceElm.setTextContent(audienceRestriction);
501 audienceRestrictionElm.appendChild(audienceElm);
502
503 return conditionsElm;
504 }
505
506 private Node createIssuer() {
507 Element issuerElm = createElement(SAMLTags.issuer);
508 issuerElm.setTextContent(issuer);
509 return issuerElm;
510 }
511
512 private Node createSubject(Document doc) {
513 Element subjectElm = createElement(SAMLTags.subject);
514
515 Element nameIdElm = createElement(SAMLTags.nameID);
516 nameIdElm.setAttribute(FORMAT_ATTRIBUTE, "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified");
517 if(userIdCard.getAlternativeIdentifier() != null) {
518 nameIdElm.setTextContent(userIdCard.getAlternativeIdentifier());
519 } else {
520 nameIdElm.setTextContent(userIdCard.getUserInfo().getCPR());
521 }
522 subjectElm.appendChild(nameIdElm);
523
524 Element subjectConfirmationElm = createElement(SAMLTags.subjectConfirmation);
525 subjectConfirmationElm.setAttribute(SAMLAttributes.METHOD, CONFIRMATION_METHOD);
526
527 Element subjectConfirmationDataElm = createElement(SAMLTags.subjectConfirmationData);
528 Element keyInfoElm = createElement(DSTags.keyInfo);
529 Element keyNameElm = createElement(DSTags.keyName);
530 keyNameElm.setTextContent(KEY_ID);
531
532 keyInfoElm.appendChild(keyNameElm);
533 subjectConfirmationDataElm.appendChild(keyInfoElm);
534 subjectConfirmationElm.appendChild(subjectConfirmationDataElm);
535
536 subjectElm.appendChild(subjectConfirmationElm);
537 return subjectElm;
538 }
539 }