import org.opensaml.Configuration;
import org.opensaml.common.SAMLObjectBuilder;
import org.opensaml.common.SAMLVersion;
+import org.opensaml.common.binding.encoding.SAMLMessageEncoder;
import org.opensaml.common.xml.SAMLConstants;
import org.opensaml.saml2.core.Assertion;
import org.opensaml.saml2.core.AttributeQuery;
import org.opensaml.saml2.metadata.SSODescriptor;
import org.opensaml.security.MetadataCredentialResolver;
import org.opensaml.security.MetadataCriteria;
+import org.opensaml.ws.message.encoder.MessageEncodingException;
import org.opensaml.ws.transport.http.HTTPInTransport;
import org.opensaml.xml.XMLObjectBuilder;
import org.opensaml.xml.encryption.EncryptionException;
import org.opensaml.xml.security.criteria.EntityIDCriteria;
import org.opensaml.xml.security.criteria.UsageCriteria;
import org.opensaml.xml.signature.Signature;
+import org.opensaml.xml.signature.SignatureException;
import org.opensaml.xml.signature.Signer;
import org.opensaml.xml.util.DatatypeHelper;
import org.slf4j.Logger;
import edu.internet2.middleware.shibboleth.common.attribute.BaseAttribute;
import edu.internet2.middleware.shibboleth.common.attribute.encoding.AttributeEncoder;
import edu.internet2.middleware.shibboleth.common.attribute.encoding.AttributeEncodingException;
-import edu.internet2.middleware.shibboleth.common.attribute.encoding.SAML2NameIDAttributeEncoder;
+import edu.internet2.middleware.shibboleth.common.attribute.encoding.SAML2NameIDEncoder;
import edu.internet2.middleware.shibboleth.common.attribute.provider.SAML2AttributeAuthority;
import edu.internet2.middleware.shibboleth.common.profile.ProfileException;
+import edu.internet2.middleware.shibboleth.common.profile.provider.BaseSAMLProfileRequestContext;
+import edu.internet2.middleware.shibboleth.common.relyingparty.provider.CryptoOperationRequirementLevel;
import edu.internet2.middleware.shibboleth.common.relyingparty.provider.saml2.AbstractSAML2ProfileConfiguration;
import edu.internet2.middleware.shibboleth.idp.profile.AbstractSAMLProfileHandler;
+import edu.internet2.middleware.shibboleth.idp.session.Session;
/** Common implementation details for profile handlers. */
public abstract class AbstractSAML2ProfileHandler extends AbstractSAMLProfileHandler {
signatureBuilder = (XMLObjectBuilder<Signature>) getBuilderFactory().getBuilder(Signature.DEFAULT_ELEMENT_NAME);
}
+ /** {@inheritDoc} */
+ protected void populateRequestContext(BaseSAMLProfileRequestContext requestContext) throws ProfileException {
+ BaseSAML2ProfileRequestContext saml2Request = (BaseSAML2ProfileRequestContext) requestContext;
+ try {
+ super.populateRequestContext(requestContext);
+ } catch (ProfileException e) {
+ if (saml2Request.getFailureStatus() == null) {
+ saml2Request.setFailureStatus(buildStatus(StatusCode.REQUESTER_URI, null, e.getMessage()));
+ }
+ throw e;
+ }
+ }
+
+ /**
+ * Populates the request context with the information about the user.
+ *
+ * This method requires the the following request context properties to be populated: inbound message transport,
+ * relying party ID
+ *
+ * This methods populates the following request context properties: user's session, user's principal name, and
+ * service authentication method
+ *
+ * @param requestContext current request context
+ */
+ protected void populateUserInformation(BaseSAMLProfileRequestContext requestContext) {
+ Session userSession = getUserSession(requestContext.getInboundMessageTransport());
+ if (userSession == null) {
+ NameID subject = (NameID) requestContext.getSubjectNameIdentifier();
+ if (subject != null && subject.getValue() != null) {
+ userSession = getUserSession(subject.getValue());
+ }
+ }
+
+ if (userSession != null) {
+ requestContext.setUserSession(userSession);
+ requestContext.setPrincipalName(userSession.getPrincipalName());
+ requestContext.setPrincipalAuthenticationMethod(userSession.getServicesInformation().get(
+ requestContext.getPeerEntityId()).getAuthenticationMethod().getAuthenticationMethod());
+ }
+ }
+
/**
* Checks that the SAML major version for a request is 2.
*
requestContext.setFailureStatus(buildStatus(StatusCode.VERSION_MISMATCH_URI,
StatusCode.REQUEST_VERSION_TOO_LOW_URI, null));
throw new ProfileException("SAML request version too low");
- } else if (version.getMajorVersion() > 2) {
+ } else if (version.getMajorVersion() > 2 || version.getMinorVersion() > 0) {
requestContext.setFailureStatus(buildStatus(StatusCode.VERSION_MISMATCH_URI,
StatusCode.REQUEST_VERSION_TOO_HIGH_URI, null));
throw new ProfileException("SAML request version too high");
samlResponse.setIssueInstant(issueInstant);
populateStatusResponse(requestContext, samlResponse);
- samlResponse.getAssertions().add(assertion);
-
// sign the assertion if it should be signed
signAssertion(requestContext, assertion);
+ SAMLMessageEncoder encoder = getMessageEncoders().get(requestContext.getPeerEntityEndpoint().getBinding());
+ try {
+ if (requestContext.getProfileConfiguration().getEncryptAssertion() == CryptoOperationRequirementLevel.always
+ || (requestContext.getProfileConfiguration().getEncryptAssertion() == CryptoOperationRequirementLevel.conditional && !encoder
+ .providesMessageConfidentiality(requestContext))) {
+ log.debug("Attempting to encrypt assertion to relying party {}", requestContext
+ .getInboundMessageIssuer());
+ try {
+ Encrypter encrypter = getEncrypter(requestContext.getInboundMessageIssuer());
+ samlResponse.getEncryptedAssertions().add(encrypter.encrypt(assertion));
+ } catch (SecurityException e) {
+ log.error("Unable to construct encrypter", e);
+ requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null,
+ "Unable to encrypt assertion"));
+ throw new ProfileException("Unable to construct encrypter", e);
+ } catch (EncryptionException e) {
+ log.error("Unable to encrypt assertion", e);
+ requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null,
+ "Unable to encrypt assertion"));
+ throw new ProfileException("Unable to encrypt assertion", e);
+ }
+ } else {
+ samlResponse.getAssertions().add(assertion);
+ }
+ } catch (MessageEncodingException e) {
+ log.error("Unable to determine if outbound encoding {} can provide confidentiality", encoder
+ .getBindingURI());
+ throw new ProfileException("Unable to determine if assertions should be encrypted");
+ }
+
Status status = buildStatus(StatusCode.SUCCESS_URI, null, null);
samlResponse.setStatus(status);
Collection<String> audiences;
// add audience restrictions
+ AudienceRestriction audienceRestriction = audienceRestrictionBuilder.buildObject();
+ // TODO we should only do this for certain outgoing bindings, not globally
+ Audience audience = audienceBuilder.buildObject();
+ audience.setAudienceURI(requestContext.getInboundMessageIssuer());
+ audienceRestriction.getAudiences().add(audience);
audiences = profileConfig.getAssertionAudiences();
if (audiences != null && audiences.size() > 0) {
- AudienceRestriction audienceRestriction = audienceRestrictionBuilder.buildObject();
for (String audienceUri : audiences) {
- Audience audience = audienceBuilder.buildObject();
+ audience = audienceBuilder.buildObject();
audience.setAudienceURI(audienceUri);
audienceRestriction.getAudiences().add(audience);
}
- conditions.getAudienceRestrictions().add(audienceRestriction);
}
+ conditions.getAudienceRestrictions().add(audienceRestriction);
// add proxy restrictions
audiences = profileConfig.getProxyAudiences();
if (audiences != null && audiences.size() > 0) {
ProxyRestriction proxyRestriction = proxyRestrictionBuilder.buildObject();
- Audience audience;
for (String audienceUri : audiences) {
audience = audienceBuilder.buildObject();
audience.setAudienceURI(audienceUri);
try {
if (requestContext.getInboundSAMLMessage() instanceof AttributeQuery) {
return attributeAuthority.buildAttributeStatement((AttributeQuery) requestContext
- .getInboundSAMLMessage(), requestContext.getPrincipalAttributes().values());
+ .getInboundSAMLMessage(), requestContext.getAttributes().values());
} else {
- return attributeAuthority.buildAttributeStatement(null, requestContext.getPrincipalAttributes()
- .values());
+ return attributeAuthority.buildAttributeStatement(null, requestContext.getAttributes().values());
}
} catch (AttributeRequestException e) {
log.error("Error encoding attributes for principal " + requestContext.getPrincipalName(), e);
boolean signAssertion = false;
+ SAMLMessageEncoder encoder = getMessageEncoders().get(requestContext.getPeerEntityEndpoint().getBinding());
AbstractSAML2ProfileConfiguration profileConfig = requestContext.getProfileConfiguration();
- if (profileConfig.getSignAssertions()) {
- signAssertion = true;
- log.debug("IdP relying party configuration {} indicates to sign assertions: {}", requestContext
- .getRelyingPartyConfiguration().getRelyingPartyId(), signAssertion);
+ try {
+ if (profileConfig.getSignAssertions() == CryptoOperationRequirementLevel.always
+ || (profileConfig.getSignAssertions() == CryptoOperationRequirementLevel.conditional && !encoder
+ .providesMessageIntegrity(requestContext))) {
+ signAssertion = true;
+ log.debug("IdP relying party configuration {} indicates to sign assertions: {}", requestContext
+ .getRelyingPartyConfiguration().getRelyingPartyId(), signAssertion);
+ }
+ } catch (MessageEncodingException e) {
+ log.error("Unable to determine if outbound encoding {} can provide integrity protection", encoder
+ .getBindingURI());
+ throw new ProfileException("Unable to determine if outbound message should be signed");
}
if (!signAssertion && requestContext.getPeerEntityRoleMetadata() instanceof SPSSODescriptor) {
} catch (MarshallingException e) {
log.error("Unable to marshall assertion for signing", e);
throw new ProfileException("Unable to marshall assertion for signing", e);
+ } catch (SignatureException e) {
+ log.error("Unable to sign assertion", e);
+ throw new ProfileException("Unable to sign assertion", e);
}
}
Subject subject = subjectBuilder.buildObject();
subject.getSubjectConfirmations().add(subjectConfirmation);
-
- if (requestContext.getProfileConfiguration().getEncryptNameID()) {
- try {
- Encrypter encrypter = getEncrypter(requestContext.getPeerEntityId());
- subject.setEncryptedID(encrypter.encrypt(nameID));
- } catch (SecurityException e) {
- log.error("Unable to construct encrypter", e);
- requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null,
- "Unable to construct NameID"));
- throw new ProfileException("Unable to construct encrypter", e);
- } catch (EncryptionException e) {
- log.error("Unable to encrypt NameID", e);
- requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null,
- "Unable to construct NameID"));
- throw new ProfileException("Unable to encrypt NameID", e);
+ SAMLMessageEncoder encoder = getMessageEncoders().get(requestContext.getPeerEntityEndpoint().getBinding());
+ try {
+ if (requestContext.getProfileConfiguration().getEncryptNameID() == CryptoOperationRequirementLevel.always
+ || (requestContext.getProfileConfiguration().getEncryptNameID() == CryptoOperationRequirementLevel.conditional && !encoder
+ .providesMessageConfidentiality(requestContext))) {
+ log.debug("Attempting to encrypt NameID to relying party {}", requestContext.getInboundMessageIssuer());
+ try {
+ Encrypter encrypter = getEncrypter(requestContext.getInboundMessageIssuer());
+ subject.setEncryptedID(encrypter.encrypt(nameID));
+ } catch (SecurityException e) {
+ log.error("Unable to construct encrypter", e);
+ requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null,
+ "Unable to construct NameID"));
+ throw new ProfileException("Unable to construct encrypter", e);
+ } catch (EncryptionException e) {
+ log.error("Unable to encrypt NameID", e);
+ requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null,
+ "Unable to construct NameID"));
+ throw new ProfileException("Unable to encrypt NameID", e);
+ }
+ } else {
+ subject.setNameID(nameID);
}
- } else {
- subject.setNameID(nameID);
+ } catch (MessageEncodingException e) {
+ log.error("Unable to determine if outbound encoding {} can provide confidentiality", encoder
+ .getBindingURI());
+ throw new ProfileException("Unable to determine if assertions should be encrypted");
}
return subject;
protected NameID buildNameId(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext) throws ProfileException {
log.debug("Building assertion NameID for principal/relying party:{}/{}", requestContext.getPrincipalName(),
requestContext.getInboundMessageIssuer());
- Map<String, BaseAttribute> principalAttributes = requestContext.getPrincipalAttributes();
- List<String> supportedNameFormats = getNameFormats(requestContext);
- log.debug("Supported NameID formats: {}", supportedNameFormats);
+ Map<String, BaseAttribute> principalAttributes = requestContext.getAttributes();
+ if (principalAttributes == null || principalAttributes.isEmpty()) {
+ log.error("No attributes for principal {}, unable to construct of NameID", requestContext
+ .getPrincipalName());
+ requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, StatusCode.INVALID_NAMEID_POLICY_URI,
+ "Unable to construct NameID"));
+ throw new ProfileException("No principal attributes support NameID construction");
+ }
- if (principalAttributes == null || supportedNameFormats == null) {
- log.error("No attributes for principal " + requestContext.getPrincipalName()
- + " support constructions of NameID");
+ List<String> supportedNameFormats = getNameFormats(requestContext);
+ if (supportedNameFormats == null || supportedNameFormats.isEmpty()) {
+ log.error("No common NameID formats supported by SP {} and IdP", requestContext.getInboundMessageIssuer());
requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, StatusCode.INVALID_NAMEID_POLICY_URI,
"Unable to construct NameID"));
throw new ProfileException("No principal attributes support NameID construction");
}
+ log.debug("Supported NameID formats: {}", supportedNameFormats);
try {
- SAML2NameIDAttributeEncoder nameIdEncoder;
+ SAML2NameIDEncoder nameIdEncoder;
for (BaseAttribute<?> attribute : principalAttributes.values()) {
for (AttributeEncoder encoder : attribute.getEncoders()) {
- if (encoder instanceof SAML2NameIDAttributeEncoder) {
- nameIdEncoder = (SAML2NameIDAttributeEncoder) encoder;
+ if (encoder instanceof SAML2NameIDEncoder) {
+ nameIdEncoder = (SAML2NameIDEncoder) encoder;
if (supportedNameFormats.contains(nameIdEncoder.getNameFormat())) {
log.debug("Using attribute {} suppoting NameID format {} to create the NameID.", attribute
.getId(), nameIdEncoder.getNameFormat());
}
if (nameFormats.isEmpty()) {
- nameFormats.add("urn:oasis:names:tc:SAML:2.0:nameid-format:unspecified");
+ nameFormats.add("urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified");
}
// If authn request and name ID policy format specified, make sure it's in the list of supported formats