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