1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package edu.internet2.middleware.shibboleth.idp.profile;
18
19 import java.util.ArrayList;
20 import java.util.List;
21 import java.util.Map;
22
23 import javax.servlet.http.HttpServletRequest;
24
25 import org.opensaml.common.IdentifierGenerator;
26 import org.opensaml.common.binding.decoding.SAMLMessageDecoder;
27 import org.opensaml.common.binding.encoding.SAMLMessageEncoder;
28 import org.opensaml.saml1.core.NameIdentifier;
29 import org.opensaml.saml2.metadata.AttributeAuthorityDescriptor;
30 import org.opensaml.saml2.metadata.AuthnAuthorityDescriptor;
31 import org.opensaml.saml2.metadata.Endpoint;
32 import org.opensaml.saml2.metadata.EntityDescriptor;
33 import org.opensaml.saml2.metadata.NameIDFormat;
34 import org.opensaml.saml2.metadata.PDPDescriptor;
35 import org.opensaml.saml2.metadata.RoleDescriptor;
36 import org.opensaml.saml2.metadata.SSODescriptor;
37 import org.opensaml.saml2.metadata.provider.MetadataProvider;
38 import org.opensaml.saml2.metadata.provider.MetadataProviderException;
39 import org.opensaml.security.MetadataCredentialResolver;
40 import org.opensaml.security.MetadataCredentialResolverFactory;
41 import org.opensaml.ws.message.encoder.MessageEncodingException;
42 import org.opensaml.ws.security.SecurityPolicyResolver;
43 import org.opensaml.ws.transport.InTransport;
44 import org.opensaml.ws.transport.http.HttpServletRequestAdapter;
45 import org.opensaml.xml.security.credential.Credential;
46 import org.opensaml.xml.util.DatatypeHelper;
47 import org.opensaml.xml.util.Pair;
48 import org.slf4j.Logger;
49 import org.slf4j.LoggerFactory;
50 import org.slf4j.helpers.MessageFormatter;
51
52 import edu.internet2.middleware.shibboleth.common.attribute.BaseAttribute;
53 import edu.internet2.middleware.shibboleth.common.attribute.encoding.AttributeEncoder;
54 import edu.internet2.middleware.shibboleth.common.attribute.encoding.SAMLNameIdentifierEncoder;
55 import edu.internet2.middleware.shibboleth.common.log.AuditLogEntry;
56 import edu.internet2.middleware.shibboleth.common.profile.ProfileException;
57 import edu.internet2.middleware.shibboleth.common.profile.provider.AbstractShibbolethProfileHandler;
58 import edu.internet2.middleware.shibboleth.common.profile.provider.BaseSAMLProfileRequestContext;
59 import edu.internet2.middleware.shibboleth.common.relyingparty.RelyingPartyConfiguration;
60 import edu.internet2.middleware.shibboleth.common.relyingparty.RelyingPartySecurityPolicyResolver;
61 import edu.internet2.middleware.shibboleth.common.relyingparty.provider.AbstractSAMLProfileConfiguration;
62 import edu.internet2.middleware.shibboleth.common.relyingparty.provider.CryptoOperationRequirementLevel;
63 import edu.internet2.middleware.shibboleth.common.relyingparty.provider.SAMLMDRelyingPartyConfigurationManager;
64 import edu.internet2.middleware.shibboleth.idp.session.Session;
65
66
67
68
69 public abstract class AbstractSAMLProfileHandler extends
70 AbstractShibbolethProfileHandler<SAMLMDRelyingPartyConfigurationManager, Session> {
71
72
73 private final Logger auditLog = LoggerFactory.getLogger(AuditLogEntry.AUDIT_LOGGER_NAME);
74
75
76 private final Logger log = LoggerFactory.getLogger(AbstractSAMLProfileHandler.class);
77
78
79 private IdentifierGenerator idGenerator;
80
81
82 private Map<String, SAMLMessageDecoder> messageDecoders;
83
84
85 private Map<String, SAMLMessageEncoder> messageEncoders;
86
87
88 private String inboundBinding;
89
90
91 private List<String> supportedOutboundBindings;
92
93
94 private SecurityPolicyResolver securityPolicyResolver;
95
96
97 protected AbstractSAMLProfileHandler() {
98 super();
99 }
100
101
102
103
104
105
106 public SecurityPolicyResolver getSecurityPolicyResolver() {
107 if (securityPolicyResolver == null) {
108 setSecurityPolicyResolver(new RelyingPartySecurityPolicyResolver(getRelyingPartyConfigurationManager()));
109 }
110
111 return securityPolicyResolver;
112 }
113
114
115
116
117
118
119 public void setSecurityPolicyResolver(SecurityPolicyResolver resolver) {
120 securityPolicyResolver = resolver;
121 }
122
123
124
125
126
127
128 protected Logger getAduitLog() {
129 return auditLog;
130 }
131
132
133
134
135
136
137 public IdentifierGenerator getIdGenerator() {
138 return idGenerator;
139 }
140
141
142
143
144
145
146 public String getInboundBinding() {
147 return inboundBinding;
148 }
149
150
151
152
153
154
155 public Map<String, SAMLMessageDecoder> getMessageDecoders() {
156 return messageDecoders;
157 }
158
159
160
161
162
163
164 public Map<String, SAMLMessageEncoder> getMessageEncoders() {
165 return messageEncoders;
166 }
167
168
169
170
171
172
173 public MetadataProvider getMetadataProvider() {
174 SAMLMDRelyingPartyConfigurationManager rpcManager = getRelyingPartyConfigurationManager();
175 if (rpcManager != null) {
176 return rpcManager.getMetadataProvider();
177 }
178
179 return null;
180 }
181
182
183
184
185
186
187 public MetadataCredentialResolver getMetadataCredentialResolver() {
188 MetadataCredentialResolverFactory mcrFactory = MetadataCredentialResolverFactory.getFactory();
189 MetadataProvider metadataProvider = getMetadataProvider();
190 return mcrFactory.getInstance(metadataProvider);
191 }
192
193
194
195
196
197
198 public List<String> getSupportedOutboundBindings() {
199 return supportedOutboundBindings;
200 }
201
202
203
204
205
206
207
208
209 protected Session getUserSession(InTransport inTransport) {
210 HttpServletRequest rawRequest = ((HttpServletRequestAdapter) inTransport).getWrappedRequest();
211 return (Session) rawRequest.getAttribute(Session.HTTP_SESSION_BINDING_ATTRIBUTE);
212 }
213
214
215
216
217
218
219
220
221 protected Session getUserSession(String principalName) {
222 return getSessionManager().getSession(principalName);
223 }
224
225
226
227
228
229
230 public void setIdGenerator(IdentifierGenerator generator) {
231 idGenerator = generator;
232 }
233
234
235
236
237
238
239 public void setInboundBinding(String binding) {
240 inboundBinding = binding;
241 }
242
243
244
245
246
247
248 public void setMessageDecoders(Map<String, SAMLMessageDecoder> decoders) {
249 messageDecoders = decoders;
250 }
251
252
253
254
255
256
257 public void setMessageEncoders(Map<String, SAMLMessageEncoder> encoders) {
258 messageEncoders = encoders;
259 }
260
261
262
263
264
265
266 public void setSupportedOutboundBindings(List<String> bindings) {
267 supportedOutboundBindings = bindings;
268 }
269
270
271 public RelyingPartyConfiguration getRelyingPartyConfiguration(String relyingPartyId) {
272 try {
273 if (getMetadataProvider().getEntityDescriptor(relyingPartyId) == null) {
274 log.warn("No metadata for relying party {}, treating party as anonymous", relyingPartyId);
275 return getRelyingPartyConfigurationManager().getAnonymousRelyingConfiguration();
276 }
277 } catch (MetadataProviderException e) {
278 log.error("Unable to look up relying party metadata", e);
279 return null;
280 }
281
282 return super.getRelyingPartyConfiguration(relyingPartyId);
283 }
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298 protected void populateRequestContext(BaseSAMLProfileRequestContext requestContext) throws ProfileException {
299 populateRelyingPartyInformation(requestContext);
300 populateAssertingPartyInformation(requestContext);
301 populateSAMLMessageInformation(requestContext);
302 populateProfileInformation(requestContext);
303 populateUserInformation(requestContext);
304 }
305
306
307
308
309
310
311
312
313
314
315
316
317 protected void populateRelyingPartyInformation(BaseSAMLProfileRequestContext requestContext)
318 throws ProfileException {
319 MetadataProvider metadataProvider = requestContext.getMetadataProvider();
320 String relyingPartyId = requestContext.getInboundMessageIssuer();
321
322 EntityDescriptor relyingPartyMetadata;
323 try {
324 relyingPartyMetadata = metadataProvider.getEntityDescriptor(relyingPartyId);
325 requestContext.setPeerEntityMetadata(relyingPartyMetadata);
326 } catch (MetadataProviderException e) {
327 log.error("Error looking up metadata for relying party " + relyingPartyId, e);
328 throw new ProfileException("Error looking up metadata for relying party " + relyingPartyId);
329 }
330
331 RelyingPartyConfiguration rpConfig = getRelyingPartyConfiguration(relyingPartyId);
332 if (rpConfig == null) {
333 log.error("Unable to retrieve relying party configuration data for entity with ID {}", relyingPartyId);
334 throw new ProfileException("Unable to retrieve relying party configuration data for entity with ID "
335 + relyingPartyId);
336 }
337 requestContext.setRelyingPartyConfiguration(rpConfig);
338 }
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355 protected void populateAssertingPartyInformation(BaseSAMLProfileRequestContext requestContext)
356 throws ProfileException {
357 String assertingPartyId = requestContext.getRelyingPartyConfiguration().getProviderId();
358 requestContext.setLocalEntityId(assertingPartyId);
359 requestContext.setOutboundMessageIssuer(assertingPartyId);
360
361 try {
362 EntityDescriptor localEntityDescriptor = requestContext.getMetadataProvider().getEntityDescriptor(
363 assertingPartyId);
364 if (localEntityDescriptor != null) {
365 requestContext.setLocalEntityMetadata(localEntityDescriptor);
366 }
367 } catch (MetadataProviderException e) {
368 log.error("Error looking up metadata for asserting party " + assertingPartyId, e);
369 throw new ProfileException("Error looking up metadata for asserting party " + assertingPartyId);
370 }
371 }
372
373
374
375
376
377
378
379
380
381
382
383
384
385 protected abstract void populateSAMLMessageInformation(BaseSAMLProfileRequestContext requestContext)
386 throws ProfileException;
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405 protected void populateProfileInformation(BaseSAMLProfileRequestContext requestContext) throws ProfileException {
406 AbstractSAMLProfileConfiguration profileConfig = (AbstractSAMLProfileConfiguration) requestContext
407 .getRelyingPartyConfiguration().getProfileConfiguration(getProfileId());
408 if (profileConfig != null) {
409 requestContext.setProfileConfiguration(profileConfig);
410 requestContext.setOutboundMessageArtifactType(profileConfig.getOutboundArtifactType());
411 }
412
413 Endpoint endpoint = selectEndpoint(requestContext);
414 if (endpoint == null) {
415 log.error("No return endpoint available for relying party {}", requestContext.getInboundMessageIssuer());
416 throw new ProfileException("No peer endpoint available to which to send SAML response");
417 }
418 requestContext.setPeerEntityEndpoint(endpoint);
419 }
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437 protected <T extends SAMLNameIdentifierEncoder> Pair<BaseAttribute, T> selectNameIDAttributeAndEncoder(
438 Class<T> nameIdEncoderType, BaseSAMLProfileRequestContext requestContext) throws ProfileException {
439
440 String requiredNameFormat = DatatypeHelper.safeTrimOrNullString(getRequiredNameIDFormat(requestContext));
441 if (requiredNameFormat != null) {
442 log.debug("Attempting to build name identifier for relying party'{}' that requires format '{}'",
443 requestContext.getInboundMessageIssuer(), requiredNameFormat);
444 return selectNameIDAttributeAndEncoderByRequiredFormat(requiredNameFormat, nameIdEncoderType,
445 requestContext);
446 }
447
448 List<String> supportedNameFormats = getNameFormats(requestContext);
449 if (supportedNameFormats.isEmpty()) {
450 log.debug("Attempting to build name identifier for relying party '{}' that supports any format",
451 requestContext.getInboundMessageIssuer());
452 } else {
453 log.debug("Attempting to build name identifier for relying party '{}' that supports the formats: {}",
454 requestContext.getInboundMessageIssuer(), supportedNameFormats);
455 }
456 return selectNameIDAttributeAndEncoderBySupportedFormats(supportedNameFormats, nameIdEncoderType,
457 requestContext);
458 }
459
460
461
462
463
464
465
466
467
468
469
470 protected String getRequiredNameIDFormat(BaseSAMLProfileRequestContext requestContext) {
471 return null;
472 }
473
474
475
476
477
478
479
480
481
482
483
484
485
486 protected <T extends SAMLNameIdentifierEncoder> Pair<BaseAttribute, T> selectNameIDAttributeAndEncoderByRequiredFormat(
487 String requiredNameFormat, Class<T> nameIdEncoderType, BaseSAMLProfileRequestContext requestContext)
488 throws ProfileException {
489 String requiredNameFormatErr = "No attribute of principal '" + requestContext.getPrincipalName()
490 + "' can be encoded in to a NameIdentifier of " + "required format '" + requiredNameFormat
491 + "' for relying party '" + requestContext.getInboundMessageIssuer() + "'";
492
493 Map<String, BaseAttribute> principalAttributes = requestContext.getAttributes();
494 if (principalAttributes == null || principalAttributes.isEmpty()) {
495 log.debug("No attributes for principal '{}', no name identifier will be created for relying party '{}'",
496 requestContext.getPrincipalName(), requestContext.getInboundMessageIssuer());
497 log.warn(requiredNameFormatErr);
498 throw new ProfileException(requiredNameFormatErr);
499 }
500
501 Pair<BaseAttribute, T> nameIdAttributeAndEncoder = selectNameIDAttributeAndEncoder(nameIdEncoderType,
502 principalAttributes, java.util.Collections.singletonList(requiredNameFormat));
503 if (nameIdAttributeAndEncoder == null) {
504 log.warn(requiredNameFormatErr);
505 throw new ProfileException(requiredNameFormatErr);
506 }
507
508 return nameIdAttributeAndEncoder;
509 }
510
511
512
513
514
515
516
517
518
519
520 protected List<String> getNameFormats(BaseSAMLProfileRequestContext requestContext) throws ProfileException {
521 ArrayList<String> nameFormats = new ArrayList<String>();
522
523 RoleDescriptor relyingPartyRole = requestContext.getPeerEntityRoleMetadata();
524 if (relyingPartyRole != null) {
525 List<String> relyingPartySupportedFormats = getEntitySupportedFormats(relyingPartyRole);
526 if (relyingPartySupportedFormats != null && !relyingPartySupportedFormats.isEmpty()) {
527 nameFormats.addAll(relyingPartySupportedFormats);
528 }
529 }
530
531
532 if (nameFormats.contains(NameIdentifier.UNSPECIFIED)) {
533 nameFormats.clear();
534 }
535
536 return nameFormats;
537 }
538
539
540
541
542
543
544
545
546 protected List<String> getEntitySupportedFormats(RoleDescriptor role) {
547 List<NameIDFormat> nameIDFormats = null;
548
549 if (role instanceof SSODescriptor) {
550 nameIDFormats = ((SSODescriptor) role).getNameIDFormats();
551 } else if (role instanceof AuthnAuthorityDescriptor) {
552 nameIDFormats = ((AuthnAuthorityDescriptor) role).getNameIDFormats();
553 } else if (role instanceof PDPDescriptor) {
554 nameIDFormats = ((PDPDescriptor) role).getNameIDFormats();
555 } else if (role instanceof AttributeAuthorityDescriptor) {
556 nameIDFormats = ((AttributeAuthorityDescriptor) role).getNameIDFormats();
557 }
558
559 ArrayList<String> supportedFormats = new ArrayList<String>();
560 if (nameIDFormats != null) {
561 for (NameIDFormat format : nameIDFormats) {
562 supportedFormats.add(format.getFormat());
563 }
564 }
565
566 return supportedFormats;
567 }
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582 protected <T extends SAMLNameIdentifierEncoder> Pair<BaseAttribute, T> selectNameIDAttributeAndEncoderBySupportedFormats(
583 List<String> supportedNameFormats, Class<T> nameIdEncoderType, BaseSAMLProfileRequestContext requestContext)
584 throws ProfileException {
585 Map<String, BaseAttribute> principalAttributes = requestContext.getAttributes();
586 if (principalAttributes == null || principalAttributes.isEmpty()) {
587 log.debug("No attributes for principal '{}', no name identifier will be created for relying party '{}'",
588 requestContext.getPrincipalName(), requestContext.getInboundMessageIssuer());
589 return null;
590 }
591
592 Pair<BaseAttribute, T> nameIdAttributeAndEncoder = null;
593 nameIdAttributeAndEncoder = selectNameIDAttributeAndEncoder(nameIdEncoderType, principalAttributes,
594 supportedNameFormats);
595 if (nameIdAttributeAndEncoder == null) {
596 log
597 .debug(
598 "No attributes for principal '{}' support encoding into a supported name identifier format for relying party '{}'",
599 requestContext.getPrincipalName(), requestContext.getInboundMessageIssuer());
600 }
601
602 return nameIdAttributeAndEncoder;
603 }
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618 protected <T extends SAMLNameIdentifierEncoder> Pair<BaseAttribute, T> selectNameIDAttributeAndEncoder(
619 Class<T> nameIdEncoderType, Map<String, BaseAttribute> principalAttributes,
620 List<String> supportedNameFormats) throws ProfileException {
621
622 T nameIdEncoder = null;
623
624 if (principalAttributes != null) {
625 for (BaseAttribute<?> attribute : principalAttributes.values()) {
626 if (attribute == null) {
627 continue;
628 }
629
630 for (AttributeEncoder encoder : attribute.getEncoders()) {
631 if (encoder == null) {
632 continue;
633 }
634
635 if (nameIdEncoderType.isInstance(encoder)) {
636 nameIdEncoder = nameIdEncoderType.cast(encoder);
637 if (supportedNameFormats.isEmpty()
638 || supportedNameFormats.contains(nameIdEncoder.getNameFormat())) {
639 return new Pair<BaseAttribute, T>(attribute, nameIdEncoder);
640 }
641 }
642 }
643 }
644 }
645
646 return null;
647 }
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664 protected abstract void populateUserInformation(BaseSAMLProfileRequestContext requestContext)
665 throws ProfileException;
666
667
668
669
670
671
672
673
674
675
676 protected abstract Endpoint selectEndpoint(BaseSAMLProfileRequestContext requestContext) throws ProfileException;
677
678
679
680
681
682
683
684
685 protected void encodeResponse(BaseSAMLProfileRequestContext requestContext) throws ProfileException {
686 try {
687 SAMLMessageEncoder encoder = getOutboundMessageEncoder(requestContext);
688
689 AbstractSAMLProfileConfiguration profileConfig = (AbstractSAMLProfileConfiguration) requestContext
690 .getProfileConfiguration();
691 if (profileConfig != null) {
692 if (isSignResponse(requestContext)) {
693 Credential signingCredential = profileConfig.getSigningCredential();
694 if (signingCredential == null) {
695 signingCredential = requestContext.getRelyingPartyConfiguration().getDefaultSigningCredential();
696 }
697
698 if (signingCredential == null) {
699 throw new ProfileException(
700 "Signing of responses is required but no signing credential is available");
701 }
702
703 if (signingCredential.getPrivateKey() == null) {
704 throw new ProfileException(
705 "Signing of response is required but signing credential does not have a private key");
706 }
707
708 requestContext.setOutboundSAMLMessageSigningCredential(signingCredential);
709 }
710 }
711
712 log.debug("Encoding response to SAML request {} from relying party {}", requestContext
713 .getInboundSAMLMessageId(), requestContext.getInboundMessageIssuer());
714
715 requestContext.setMessageEncoder(encoder);
716 encoder.encode(requestContext);
717 } catch (MessageEncodingException e) {
718 throw new ProfileException("Unable to encode response to relying party: "
719 + requestContext.getInboundMessageIssuer(), e);
720 }
721 }
722
723
724
725
726
727
728
729
730 protected boolean isSignResponse(BaseSAMLProfileRequestContext requestContext) throws ProfileException {
731
732 SAMLMessageEncoder encoder = getOutboundMessageEncoder(requestContext);
733
734 AbstractSAMLProfileConfiguration profileConfig = (AbstractSAMLProfileConfiguration) requestContext
735 .getProfileConfiguration();
736
737 if (profileConfig != null) {
738 try {
739 return profileConfig.getSignResponses() == CryptoOperationRequirementLevel.always
740 || (profileConfig.getSignResponses() == CryptoOperationRequirementLevel.conditional && !encoder
741 .providesMessageIntegrity(requestContext));
742 } catch (MessageEncodingException e) {
743 log.error("Unable to determine if outbound encoding '{}' provides message integrity protection",
744 encoder.getBindingURI());
745 throw new ProfileException("Unable to determine if outbound response should be signed");
746 }
747 } else {
748 return false;
749 }
750
751 }
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772 protected SAMLMessageEncoder getOutboundMessageEncoder(BaseSAMLProfileRequestContext requestContext)
773 throws ProfileException {
774 SAMLMessageEncoder encoder = null;
775
776 Endpoint endpoint = requestContext.getPeerEntityEndpoint();
777 if (endpoint == null) {
778 log.warn("No peer endpoint available for peer. Unable to send response.");
779 throw new ProfileException("No peer endpoint available for peer. Unable to send response.");
780 }
781
782 if (endpoint != null) {
783 encoder = getMessageEncoders().get(endpoint.getBinding());
784 if (encoder == null) {
785 log.error("No outbound message encoder configured for binding: {}", requestContext
786 .getPeerEntityEndpoint().getBinding());
787 throw new ProfileException("No outbound message encoder configured for binding: "
788 + requestContext.getPeerEntityEndpoint().getBinding());
789 }
790 }
791 return encoder;
792 }
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810 protected SAMLMessageDecoder getInboundMessageDecoder(BaseSAMLProfileRequestContext requestContext)
811 throws ProfileException {
812 SAMLMessageDecoder decoder = null;
813
814 decoder = getMessageDecoders().get(getInboundBinding());
815 if (decoder == null) {
816 log.error("No inbound message decoder configured for binding: {}", getInboundBinding());
817 throw new ProfileException("No inbound message decoder configured for binding: " + getInboundBinding());
818 }
819 return decoder;
820 }
821
822
823
824
825
826
827 protected void writeAuditLogEntry(BaseSAMLProfileRequestContext context) {
828 AuditLogEntry auditLogEntry = new AuditLogEntry();
829 auditLogEntry.setMessageProfile(getProfileId());
830 auditLogEntry.setPrincipalAuthenticationMethod(context.getPrincipalAuthenticationMethod());
831 auditLogEntry.setPrincipalName(context.getPrincipalName());
832 auditLogEntry.setAssertingPartyId(context.getLocalEntityId());
833 auditLogEntry.setRelyingPartyId(context.getInboundMessageIssuer());
834 auditLogEntry.setRequestBinding(context.getMessageDecoder().getBindingURI());
835 auditLogEntry.setRequestId(context.getInboundSAMLMessageId());
836 auditLogEntry.setResponseBinding(context.getMessageEncoder().getBindingURI());
837 auditLogEntry.setResponseId(context.getOutboundSAMLMessageId());
838 if (context.getReleasedAttributes() != null) {
839 auditLogEntry.getReleasedAttributes().addAll(context.getReleasedAttributes());
840 }
841
842 getAduitLog().info(auditLogEntry.toString());
843 }
844 }