sourceAttributeID="jpegPhoto">
<resolver:Dependency ref="myLDAP" />
- <resolver:AttributeEncoder xsi:type="SAML1Base64" xmlns="urn:mace:shibboleth:2.0:attribute:encoder"
+ <resolver:AttributeEncoder xsi:type="SAML1String" xmlns="urn:mace:shibboleth:2.0:attribute:encoder"
name="urn:mace:dir:attribute-def:jpegPhoto" />
- <resolver:AttributeEncoder xsi:type="SAML2Base64" xmlns="urn:mace:shibboleth:2.0:attribute:encoder"
+ <resolver:AttributeEncoder xsi:type="SAML2String" xmlns="urn:mace:shibboleth:2.0:attribute:encoder"
name="urn:oid:0.9.2342.19200300.100.1.60" friendlyName="jpegPhoto" />
</resolver:AttributeDefinition>
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.0.xsd" >
- <bean id="shibboleth.CacheMaager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" />
+ <bean id="shibboleth.CacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" />
<bean id="shibboleth.TaskTimer" class="java.util.Timer" destroy-method="cancel">
<constructor-arg value="true" type="boolean" />
import javax.servlet.http.HttpServletRequest;
import org.opensaml.common.IdentifierGenerator;
-import org.opensaml.common.binding.SAMLMessageContext;
import org.opensaml.common.binding.decoding.SAMLMessageDecoder;
import org.opensaml.common.binding.encoding.SAMLMessageEncoder;
import org.opensaml.saml1.core.NameIdentifier;
import org.opensaml.ws.transport.InTransport;
import org.opensaml.ws.transport.http.HttpServletRequestAdapter;
import org.opensaml.xml.security.credential.Credential;
+import org.opensaml.xml.util.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.slf4j.helpers.MessageFormatter;
+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.SAMLNameIdentifierEncoder;
import edu.internet2.middleware.shibboleth.common.log.AuditLogEntry;
import edu.internet2.middleware.shibboleth.common.profile.ProfileException;
import edu.internet2.middleware.shibboleth.common.profile.provider.AbstractShibbolethProfileHandler;
}
/**
+ * Attempts to select the most fitting name identifier attribute, and associated encoder, for a request. If no
+ * attributes for the request subject are available no name identifier is constructed. If a specific name format is
+ * required, as returned by {@link #getRequiredNameIDFormat(BaseSAMLProfileRequestContext)}, then either an
+ * attribute with an encoder supporting that format is selected or an exception is thrown. If no specific format is
+ * required then an attribute supporting a format listed as supported by the relying party is used. If the relying
+ * party does not list any supported formats then any attribute supporting the correct name identifier type is used.
+ *
+ * @param <T> type of name identifier encoder the attribute must support
+ * @param nameIdEncoderType type of name identifier encoder the attribute must support
+ * @param requestContext the current request context
+ *
+ * @return the select attribute, and its encoder, to be used to build the name identifier
+ *
+ * @throws ProfileException thrown if a specific name identifier format was required but not supported
+ */
+ protected <T extends SAMLNameIdentifierEncoder> Pair<BaseAttribute, T> selectNameIDAttributeAndEncoder(
+ Class<T> nameIdEncoderType, BaseSAMLProfileRequestContext requestContext) throws ProfileException {
+ String requiredNameFormat = getRequiredNameIDFormat(requestContext);
+ String requiredNameFormatErr = MessageFormatter.format(
+ "No attribute of principal '{}' can be encoded in to a NameIdentifier of "
+ + "required format '{}' for relying party '{}'", new Object[] {
+ requestContext.getPrincipalName(), requiredNameFormat,
+ requestContext.getInboundMessageIssuer(), });
+
+ Map<String, BaseAttribute> principalAttributes = requestContext.getAttributes();
+ if (principalAttributes == null || principalAttributes.isEmpty()) {
+ log.debug("No attributes for principal '{}', no name identifier will be created for relying party '{}'",
+ requestContext.getPrincipalName(), requestContext.getInboundMessageIssuer());
+ if (requiredNameFormat == null) {
+ return null;
+ } else {
+ log.warn(requiredNameFormatErr);
+ throw new ProfileException(requiredNameFormatErr);
+ }
+ }
+
+ List<String> supportedNameFormats = getNameFormats(requestContext);
+ if (!supportedNameFormats.isEmpty()) {
+ log.debug("Relying party '{}' supports the name formats: {}", requestContext.getInboundMessageIssuer(),
+ supportedNameFormats);
+ } else {
+ log.debug("Relying party '{}' indicated no preferred name formats", requestContext
+ .getInboundMessageIssuer());
+ }
+
+ Pair<BaseAttribute, T> nameIdAttributeAndEncoder = null;
+ if (requiredNameFormat == null) {
+ nameIdAttributeAndEncoder = selectNameIDAttributeAndEncoder(nameIdEncoderType, principalAttributes,
+ supportedNameFormats);
+ if (nameIdAttributeAndEncoder == null) {
+ log.debug("No attributes for principal '{}' supports encoding into a supported NameIdentifier format for relying party '{}'",
+ requestContext.getPrincipalName(), requestContext.getInboundMessageIssuer());
+ return null;
+ }
+ } else {
+ nameIdAttributeAndEncoder = selectNameIDAttributeAndEncoder(nameIdEncoderType, principalAttributes,
+ requiredNameFormat);
+ if (nameIdAttributeAndEncoder == null) {
+ log.warn(requiredNameFormatErr);
+ throw new ProfileException(requiredNameFormatErr);
+ }
+ }
+
+ return nameIdAttributeAndEncoder;
+ }
+
+ /**
+ * Gets the name identifier format required to be sent back to the relying party.
+ *
+ * This implementation of this method returns null. Profile handler implementations should override this method if
+ * an incoming request is capable of requiring a specific format.
+ *
+ * @param requestContext current request context
+ *
+ * @return the required name ID format or null if no specific format is required
+ */
+ protected String getRequiredNameIDFormat(BaseSAMLProfileRequestContext requestContext) {
+ return null;
+ }
+
+ /**
* Gets the name identifier formats to use when creating identifiers for the relying party.
*
* @param requestContext current request context
nameFormats.clear();
}
+ if (!nameFormats.isEmpty()) {
+ log.debug("Relying party '{}' supports the name formats: {}", requestContext.getInboundMessageIssuer(),
+ nameFormats);
+ } else {
+ log.debug("Relying party '{}' indicated no preferred name formats", requestContext
+ .getInboundMessageIssuer());
+ }
+
return nameFormats;
}
}
/**
+ * Selects an attribute, resolved previously and of the required format, to encode as a NameID.
+ *
+ * @param <T> type of name identifier encoder the attribute must support
+ * @param nameIdEncoderType type of name identifier encoder the attribute must support
+ * @param principalAttributes resolved attributes
+ * @param requiredNameFormat required NameID format
+ *
+ * @return the attribute and its associated NameID encoder
+ *
+ * @throws ProfileException thrown if no attribute can be encoded in to a NameID of the required type
+ */
+ protected <T extends SAMLNameIdentifierEncoder> Pair<BaseAttribute, T> selectNameIDAttributeAndEncoder(
+ Class<T> nameIdEncoderType, Map<String, BaseAttribute> principalAttributes, String requiredNameFormat)
+ throws ProfileException {
+
+ T nameIdEncoder = null;
+
+ if (principalAttributes != null) {
+ for (BaseAttribute<?> attribute : principalAttributes.values()) {
+ if (attribute == null) {
+ continue;
+ }
+
+ for (AttributeEncoder encoder : attribute.getEncoders()) {
+ if (encoder == null) {
+ continue;
+ }
+
+ if (nameIdEncoderType.isInstance(encoder)) {
+ nameIdEncoder = nameIdEncoderType.cast(encoder);
+ if (nameIdEncoder.getNameFormat().equals(requiredNameFormat)) {
+ return new Pair<BaseAttribute, T>(attribute, nameIdEncoder);
+ }
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Selects an attribute, resolved previously, to encode as a NameID.
+ *
+ * @param <T> type of name identifier encoder the attribute must support
+ * @param nameIdEncoderType type of name identifier encoder the attribute must support
+ * @param principalAttributes resolved attributes
+ * @param supportedNameFormats NameID formats supported by the relying party or an empty list if all formats are
+ * acceptable
+ *
+ * @return the attribute and its associated NameID encoder
+ *
+ * @throws ProfileException thrown if no attribute can be encoded in to a NameID of the required type
+ */
+ protected <T extends SAMLNameIdentifierEncoder> Pair<BaseAttribute, T> selectNameIDAttributeAndEncoder(
+ Class<T> nameIdEncoderType, Map<String, BaseAttribute> principalAttributes,
+ List<String> supportedNameFormats) throws ProfileException {
+
+ T nameIdEncoder = null;
+
+ if (principalAttributes != null) {
+ for (BaseAttribute<?> attribute : principalAttributes.values()) {
+ if (attribute == null) {
+ continue;
+ }
+
+ for (AttributeEncoder encoder : attribute.getEncoders()) {
+ if (encoder == null) {
+ continue;
+ }
+
+ if (nameIdEncoderType.isInstance(encoder)) {
+ nameIdEncoder = nameIdEncoderType.cast(encoder);
+ if (supportedNameFormats.isEmpty()
+ || supportedNameFormats.contains(nameIdEncoder.getNameFormat())) {
+ return new Pair<BaseAttribute, T>(attribute, nameIdEncoder);
+ }
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
* Populates the request context with the information about the user if they have an existing session. Unless
* overridden, {@link #populateRequestContext(BaseSAMLProfileRequestContext)} has already invoked
* {@link #populateRelyingPartyInformation(BaseSAMLProfileRequestContext)},
* @throws ProfileException if there is a problem determining whether responses should be signed
*/
protected boolean isSignResponse(BaseSAMLProfileRequestContext requestContext) throws ProfileException {
-
+
SAMLMessageEncoder encoder = getOutboundMessageEncoder(requestContext);
-
- AbstractSAMLProfileConfiguration profileConfig =
- (AbstractSAMLProfileConfiguration) requestContext.getProfileConfiguration();
-
+
+ AbstractSAMLProfileConfiguration profileConfig = (AbstractSAMLProfileConfiguration) requestContext
+ .getProfileConfiguration();
+
if (profileConfig != null) {
try {
return profileConfig.getSignResponses() == CryptoOperationRequirementLevel.always
- || (profileConfig.getSignResponses() == CryptoOperationRequirementLevel.conditional
- && !encoder.providesMessageIntegrity(requestContext));
+ || (profileConfig.getSignResponses() == CryptoOperationRequirementLevel.conditional && !encoder
+ .providesMessageIntegrity(requestContext));
} catch (MessageEncodingException e) {
log.error("Unable to determine if outbound encoding '{}' provides message integrity protection",
encoder.getBindingURI());
} else {
return false;
}
-
+
}
/**
* Get the outbound message encoder to use.
*
- * <p>The default implementation uses the binding URI from the
- * {@link SAMLMessageContext#getPeerEntityEndpoint()} to lookup
- * the encoder from the supported message encoders defined in {@link #getMessageEncoders()}.
+ * <p>
+ * The default implementation uses the binding URI from the
+ * {@link org.opensaml.common.binding.SAMLMessageContext#getPeerEntityEndpoint()} to lookup the encoder from the
+ * supported message encoders defined in {@link #getMessageEncoders()}.
* </p>
*
* <p>
- * Subclasses may override to implement a different mechanism to determine the
- * encoder to use, such as for example cases where an active intermediary actor
- * sits between this provider and the peer entity endpoint (e.g. the SAML 2 ECP case).
+ * Subclasses may override to implement a different mechanism to determine the encoder to use, such as for example
+ * cases where an active intermediary actor sits between this provider and the peer entity endpoint (e.g. the SAML 2
+ * ECP case).
* </p>
*
* @param requestContext current request context
}
return encoder;
}
-
+
/**
* Get the inbound message decoder to use.
*
- * <p>The default implementation uses the binding URI from
- * {@link #getInboundBinding()} to lookup the decoder from the supported message decoders
- * defined in {@link #getMessageDecoders()}.
+ * <p>
+ * The default implementation uses the binding URI from {@link #getInboundBinding()} to lookup the decoder from the
+ * supported message decoders defined in {@link #getMessageDecoders()}.
* </p>
*
* <p>
- * Subclasses may override to implement a different mechanism to determine the
- * decoder to use.
+ * Subclasses may override to implement a different mechanism to determine the decoder to use.
* </p>
*
* @param requestContext current request context
import org.opensaml.xml.signature.Signature;
import org.opensaml.xml.signature.SignatureException;
import org.opensaml.xml.signature.Signer;
+import org.opensaml.xml.util.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.helpers.MessageFormatter;
import edu.internet2.middleware.shibboleth.common.attribute.AttributeRequestException;
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.SAML1NameIdentifierEncoder;
import edu.internet2.middleware.shibboleth.common.attribute.provider.SAML1AttributeAuthority;
*/
protected NameIdentifier buildNameId(BaseSAML1ProfileRequestContext<?, ?, ?> requestContext)
throws ProfileException {
- if(requestContext.getAttributes() == null){
- return null;
- }
-
log.debug("Attemping to build NameIdentifier for principal '{}' in response to request from relying party '{}",
requestContext.getPrincipalName(), requestContext.getInboundMessageIssuer());
-
- List<String> supportedNameFormats = getNameFormats(requestContext);
- if (!supportedNameFormats.isEmpty()) {
- log.debug("Relying party '{}' supports the name formats: {}", requestContext.getInboundMessageIssuer(),
- supportedNameFormats);
- } else {
- log.debug("Relying party '{}' indicated no preferred name formats", requestContext
- .getInboundMessageIssuer());
- }
-
- Map<String, BaseAttribute> principalAttributes = requestContext.getAttributes();
- if (principalAttributes == null || principalAttributes.isEmpty()) {
- log.debug("No attributes for principal '{}', no NameIdentifier will be created for relying party '{}'",
- requestContext.getPrincipalName(), requestContext.getInboundMessageIssuer());
- return null;
- }
-
- BaseAttribute<?> nameIdAttribute = null;
- SAML1NameIdentifierEncoder nameIdEncoder = null;
- ATTRIBUTESELECT: for (BaseAttribute<?> attribute : principalAttributes.values()) {
- if (attribute == null) {
- continue;
- }
- for (AttributeEncoder encoder : attribute.getEncoders()) {
- if (encoder == null) {
- continue;
- }
- if (encoder instanceof SAML1NameIdentifierEncoder) {
- nameIdEncoder = (SAML1NameIdentifierEncoder) encoder;
- if (supportedNameFormats.isEmpty() || supportedNameFormats.contains(nameIdEncoder.getNameFormat())) {
- nameIdAttribute = attribute;
- break ATTRIBUTESELECT;
- }
- }
- }
+
+ Pair<BaseAttribute, SAML1NameIdentifierEncoder> nameIdAttributeAndEncoder = null;
+ try {
+ nameIdAttributeAndEncoder = selectNameIDAttributeAndEncoder(SAML1NameIdentifierEncoder.class,
+ requestContext);
+ } catch (ProfileException e) {
+ requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER, null,
+ "Required NameIdentifier format not supported"));
+ throw e;
}
- if (nameIdAttribute == null || nameIdEncoder == null) {
- log.debug("No attributes for principal '{}' supports encoding into a supported NameIdentifier format for relying party '{}'",
- requestContext.getPrincipalName(), requestContext.getInboundMessageIssuer());
- return null;
- }
+ BaseAttribute<?> nameIdAttribute = nameIdAttributeAndEncoder.getFirst();
+ SAML1NameIdentifierEncoder nameIdEncoder = nameIdAttributeAndEncoder.getSecond();
try {
log.debug("Using attribute '{}' supporting name format '{}' to create the NameIdentifier for relying party '{}'",
- new Object[] { nameIdAttribute.getId(), nameIdEncoder.getNameFormat(), requestContext.getInboundMessageIssuer(), });
- return nameIdEncoder.encode(nameIdAttribute);
+ new Object[] { nameIdAttribute.getId(), nameIdEncoder.getNameFormat(),
+ requestContext.getInboundMessageIssuer(), });
+ NameIdentifier nameId = nameIdEncoder.encode(nameIdAttribute);
+ nameId.setNameQualifier(requestContext.getRelyingPartyConfiguration().getProviderId());
+ return nameId;
} catch (AttributeEncodingException e) {
requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER, null, "Unable to encode NameIdentifier"));
String msg = MessageFormatter.format("Unable to encode NameIdentifier for relying party '{}'",
* Resolved the attributes for the principal.
*
* @param requestContext current request context
+ *
+ * @throws ProfileException thrown if there is a problem resolving the attributes for the subject.
*/
protected void resolveAttributes(BaseSAML1ProfileRequestContext<?, ?, ?> requestContext) throws ProfileException {
AbstractSAML1ProfileConfiguration profileConfiguration = requestContext.getProfileConfiguration();
requestContext.setAttributes(principalAttributes);
} catch (AttributeRequestException e) {
- log.warn("Error resolving attributes for principal '{}'. No name identifier or attribute statement will be included in response",
- requestContext.getPrincipalName());
+ log
+ .warn(
+ "Error resolving attributes for principal '{}'. No name identifier or attribute statement will be included in response",
+ requestContext.getPrincipalName());
}
}
protected AttributeStatement buildAttributeStatement(BaseSAML1ProfileRequestContext<?, ?, ?> requestContext,
String subjectConfMethod) throws ProfileException {
- if(requestContext.getAttributes() == null){
+ if (requestContext.getAttributes() == null) {
return null;
}
-
+
log.debug(
"Creating attribute statement about principal '{}'in response to SAML request from relying party '{}'",
requestContext.getPrincipalName(), requestContext.getInboundMessageIssuer());
throw new ProfileException(msg, e);
}
}
-
+
/**
* Determine whether issued assertions should be signed.
*
* @throws ProfileException if there is a problem determining whether assertions should be signed
*/
protected boolean isSignAssertion(BaseSAML1ProfileRequestContext<?, ?, ?> requestContext) throws ProfileException {
-
+
SAMLMessageEncoder encoder = getOutboundMessageEncoder(requestContext);
AbstractSAML1ProfileConfiguration profileConfig = requestContext.getProfileConfiguration();
-
+
try {
boolean signAssertion = profileConfig.getSignAssertions() == CryptoOperationRequirementLevel.always
- || (profileConfig.getSignAssertions() == CryptoOperationRequirementLevel.conditional
- && !encoder.providesMessageIntegrity(requestContext));
-
+ || (profileConfig.getSignAssertions() == CryptoOperationRequirementLevel.conditional && !encoder
+ .providesMessageIntegrity(requestContext));
+
log.debug("IdP relying party configuration '{}' indicates to sign assertions: {}", requestContext
.getRelyingPartyConfiguration().getRelyingPartyId(), signAssertion);
-
+
if (!signAssertion && requestContext.getPeerEntityRoleMetadata() instanceof SPSSODescriptor) {
SPSSODescriptor ssoDescriptor = (SPSSODescriptor) requestContext.getPeerEntityRoleMetadata();
if (ssoDescriptor.getWantAssertionsSigned() != null) {
.getInboundMessageIssuer(), signAssertion);
}
}
-
+
return signAssertion;
} catch (MessageEncodingException e) {
- log.error("Unable to determine if outbound encoding '{}' provides message integrity protection",
- encoder.getBindingURI());
+ log.error("Unable to determine if outbound encoding '{}' provides message integrity protection", encoder
+ .getBindingURI());
throw new ProfileException("Unable to determine if outbound assertion should be signed");
}
}
import org.opensaml.xml.signature.SignatureException;
import org.opensaml.xml.signature.Signer;
import org.opensaml.xml.util.DatatypeHelper;
+import org.opensaml.xml.util.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.helpers.MessageFormatter;
import edu.internet2.middleware.shibboleth.common.attribute.AttributeRequestException;
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.SAML2NameIDEncoder;
import edu.internet2.middleware.shibboleth.common.attribute.provider.SAML2AttributeAuthority;
assertion = buildAssertion(requestContext, issueInstant);
assertion.getStatements().addAll(statements);
assertion.setSubject(buildSubject(requestContext, subjectConfirmationMethod, issueInstant));
-
+
postProcessAssertion(requestContext, assertion);
signAssertion(requestContext, assertion);
} catch (SecurityException e) {
log.error("Unable to construct encrypter", e);
requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null,
- "Unable to encrypt assertion"));
+ "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"));
+ "Unable to encrypt assertion"));
throw new ProfileException("Unable to encrypt assertion", e);
}
} else {
Status status = buildStatus(StatusCode.SUCCESS_URI, null, null);
samlResponse.setStatus(status);
-
+
postProcessResponse(requestContext, samlResponse);
return samlResponse;
* @return true if assertions should be encrypted, false otherwise
* @throws ProfileException if there is a problem determining whether assertions should be encrypted
*/
- protected boolean isEncryptAssertion(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext)
+ protected boolean isEncryptAssertion(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext)
throws ProfileException {
-
+
SAMLMessageEncoder encoder = getOutboundMessageEncoder(requestContext);
try {
return requestContext.getProfileConfiguration().getEncryptAssertion() == CryptoOperationRequirementLevel.always
- || (requestContext.getProfileConfiguration().getEncryptAssertion() == CryptoOperationRequirementLevel.conditional
- && !encoder.providesMessageConfidentiality(requestContext));
+ || (requestContext.getProfileConfiguration().getEncryptAssertion() == CryptoOperationRequirementLevel.conditional && !encoder
+ .providesMessageConfidentiality(requestContext));
} 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");
+ throw new ProfileException("Unable to determine if assertions should be encrypted");
}
}
*
* @throws ProfileException if there was an error processing the response
*/
- protected void postProcessResponse(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext, Response samlResponse)
+ protected void postProcessResponse(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext, Response samlResponse)
throws ProfileException {
}
*
* @throws ProfileException if there is an error processing the assertion
*/
- protected void postProcessAssertion(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext, Assertion assertion)
+ protected void postProcessAssertion(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext, Assertion assertion)
throws ProfileException {
}
requestContext.setAttributes(principalAttributes);
} catch (AttributeRequestException e) {
- log.warn("Error resolving attributes for principal '{}'. No name identifier or attribute statement will be included in response",
- requestContext.getPrincipalName());
+ log
+ .warn(
+ "Error resolving attributes for principal '{}'. No name identifier or attribute statement will be included in response",
+ requestContext.getPrincipalName());
}
}
*/
protected AttributeStatement buildAttributeStatement(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext)
throws ProfileException {
- if(requestContext.getAttributes() == null){
+ if (requestContext.getAttributes() == null) {
return null;
}
-
+
log.debug("Creating attribute statement in response to SAML request '{}' from relying party '{}'",
requestContext.getInboundSAMLMessageId(), requestContext.getInboundMessageIssuer());
if (!signAssertion) {
return;
}
-
+
AbstractSAML2ProfileConfiguration profileConfig = requestContext.getProfileConfiguration();
log.debug("Determining signing credntial for assertion to relying party '{}'", requestContext
* @throws ProfileException if there is a problem determining whether assertions should be signed
*/
protected boolean isSignAssertion(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext) throws ProfileException {
-
+
SAMLMessageEncoder encoder = getOutboundMessageEncoder(requestContext);
AbstractSAML2ProfileConfiguration profileConfig = requestContext.getProfileConfiguration();
-
+
try {
boolean signAssertion = profileConfig.getSignAssertions() == CryptoOperationRequirementLevel.always
- || (profileConfig.getSignAssertions() == CryptoOperationRequirementLevel.conditional
- && !encoder.providesMessageIntegrity(requestContext));
-
+ || (profileConfig.getSignAssertions() == CryptoOperationRequirementLevel.conditional && !encoder
+ .providesMessageIntegrity(requestContext));
+
log.debug("IdP relying party configuration '{}' indicates to sign assertions: {}", requestContext
.getRelyingPartyConfiguration().getRelyingPartyId(), signAssertion);
-
+
if (!signAssertion && requestContext.getPeerEntityRoleMetadata() instanceof SPSSODescriptor) {
SPSSODescriptor ssoDescriptor = (SPSSODescriptor) requestContext.getPeerEntityRoleMetadata();
if (ssoDescriptor.getWantAssertionsSigned() != null) {
.getInboundMessageIssuer(), signAssertion);
}
}
-
+
return signAssertion;
} catch (MessageEncodingException e) {
log.error("Unable to determine if outbound encoding '{}' provides message integrity protection", encoder
}
requestContext.setSubjectNameIdentifier(nameID);
-
+
if (isEncryptNameID(requestContext)) {
- log.debug("Attempting to encrypt NameID to relying party '{}'", requestContext
- .getInboundMessageIssuer());
+ 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 encrypt NameID"));
+ requestContext
+ .setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null, "Unable to encrypt 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 encrypt NameID"));
+ requestContext
+ .setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null, "Unable to encrypt NameID"));
throw new ProfileException("Unable to encrypt NameID", e);
}
} else {
subject.setNameID(nameID);
}
-
return subject;
}
-
/**
* Determine whether NameID's should be encrypted.
* @return true if NameID's should be encrypted, false otherwise
* @throws ProfileException if there is a problem determining whether NameID's should be encrypted
*/
- protected boolean isEncryptNameID(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext)
- throws ProfileException {
-
+ protected boolean isEncryptNameID(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext) throws ProfileException {
+
boolean nameIdEncRequiredByAuthnRequest = isRequestRequiresEncryptNameID(requestContext);
-
+
SAMLMessageEncoder encoder = getOutboundMessageEncoder(requestContext);
boolean nameIdEncRequiredByConfig = false;
try {
- nameIdEncRequiredByConfig =
- requestContext.getProfileConfiguration().getEncryptNameID() == CryptoOperationRequirementLevel.always
- || (requestContext.getProfileConfiguration().getEncryptNameID() == CryptoOperationRequirementLevel.conditional
- && !encoder.providesMessageConfidentiality(requestContext));
+ nameIdEncRequiredByConfig = requestContext.getProfileConfiguration().getEncryptNameID() == CryptoOperationRequirementLevel.always
+ || (requestContext.getProfileConfiguration().getEncryptNameID() == CryptoOperationRequirementLevel.conditional && !encoder
+ .providesMessageConfidentiality(requestContext));
} catch (MessageEncodingException e) {
String msg = MessageFormatter.format(
"Unable to determine if outbound encoding '{}' provides message confidentiality protection",
log.error(msg);
throw new ProfileException(msg);
}
-
+
return nameIdEncRequiredByAuthnRequest || nameIdEncRequiredByConfig;
}
/**
- * Determine whether information in the SAML request requires the issued NameID to
- * be encrypted.
+ * Determine whether information in the SAML request requires the issued NameID to be encrypted.
*
* @param requestContext the current request context
* @return true if the request indicates NameID encryption is required, false otherwise
* name ID attribute or because there are no supported name formats
*/
protected NameID buildNameId(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext) throws ProfileException {
- if(requestContext.getAttributes() == null){
- return null;
- }
-
- log.debug("Building assertion NameID for principal/relying party:{}/{}", requestContext.getPrincipalName(),
- requestContext.getInboundMessageIssuer());
+ log.debug("Attemping to build NameID for principal '{}' in response to request from relying party '{}",
+ requestContext.getPrincipalName(), requestContext.getInboundMessageIssuer());
- // Check if AuthnRequest includes an explicit NameIDPolicy Format.
- String requiredNameFormat = null;
- if (requestContext.getInboundSAMLMessage() instanceof AuthnRequest) {
- AuthnRequest authnRequest = (AuthnRequest) requestContext.getInboundSAMLMessage();
- if (authnRequest.getNameIDPolicy() != null) {
- requiredNameFormat = DatatypeHelper.safeTrimOrNullString(authnRequest.getNameIDPolicy().getFormat());
- // Check for unspec'd or encryption formats, which aren't relevant for this section of code.
- if (requiredNameFormat != null
- && (requiredNameFormat.equals("urn:oasis:names:tc:SAML:2.0:nameid-format:encrypted") || requiredNameFormat
- .equals(NameID.UNSPECIFIED))) {
- requiredNameFormat = null;
- }
- }
- }
-
- // Get the SP's list, and filter it down by the AuthnRequest if need be.
- List<String> supportedNameFormats = getNameFormats(requestContext);
- if (requiredNameFormat != null) {
- supportedNameFormats.clear();
- supportedNameFormats.add(requiredNameFormat);
- }
- if (!supportedNameFormats.isEmpty()) {
- log.debug("Relying party '{}' supports the name formats: {}", requestContext.getInboundMessageIssuer(),
- supportedNameFormats);
- } else {
- log.debug("Relying party '{}' indicated no preferred name formats", requestContext
- .getInboundMessageIssuer());
- }
-
- BaseAttribute<?> nameIdAttribute = null;
- SAML2NameIDEncoder nameIdEncoder = null;
-
- Map<String, BaseAttribute> principalAttributes = requestContext.getAttributes();
- if (principalAttributes != null) {
- ATTRIBUTESELECT: for (BaseAttribute<?> attribute : principalAttributes.values()) {
- if (attribute == null) {
- continue;
- }
-
- for (AttributeEncoder encoder : attribute.getEncoders()) {
- if (encoder == null) {
- continue;
- }
-
- if (encoder instanceof SAML2NameIDEncoder) {
- nameIdEncoder = (SAML2NameIDEncoder)encoder;
-
- if (requiredNameFormat != null) {
- if (nameIdEncoder.getNameFormat().equals(requiredNameFormat)) {
- nameIdAttribute = attribute;
- nameIdEncoder = (SAML2NameIDEncoder) encoder;
- break ATTRIBUTESELECT;
- }
- } else {
- if (supportedNameFormats.isEmpty()
- || supportedNameFormats.contains(nameIdEncoder.getNameFormat())) {
- nameIdAttribute = attribute;
- nameIdEncoder = (SAML2NameIDEncoder) encoder;
- break ATTRIBUTESELECT;
- }
- }
- }
- }
- }
+ Pair<BaseAttribute, SAML2NameIDEncoder> nameIdAttributeAndEncoder = null;
+ try {
+ nameIdAttributeAndEncoder = selectNameIDAttributeAndEncoder(SAML2NameIDEncoder.class, requestContext);
+ } catch (ProfileException e) {
+ requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, StatusCode.INVALID_NAMEID_POLICY_URI,
+ "Required NameID format not supported"));
+ throw e;
}
- if (nameIdAttribute == null || nameIdEncoder == null) {
- if (requiredNameFormat != null) {
- requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI,
- StatusCode.INVALID_NAMEID_POLICY_URI, "NameID Format not supported: " + requiredNameFormat));
- String msg = MessageFormatter
- .format(
- "No attribute of principal '{}' can be encoded in to a NameID of required format '{}' for relying party '{}'",
- new Object[] { requestContext.getPrincipalName(), requiredNameFormat,
- requestContext.getInboundMessageIssuer() });
- log.warn(msg);
- throw new ProfileException(msg);
- } else {
- return null;
- }
- }
+ BaseAttribute<?> nameIdAttribute = nameIdAttributeAndEncoder.getFirst();
+ SAML2NameIDEncoder nameIdEncoder = nameIdAttributeAndEncoder.getSecond();
log.debug("Using attribute '{}' supporting NameID format '{}' to create the NameID for relying party '{}'",
new Object[] { nameIdAttribute.getId(), nameIdEncoder.getNameFormat(),
- requestContext.getInboundMessageIssuer() });
+ requestContext.getInboundMessageIssuer(), });
try {
- return nameIdEncoder.encode(nameIdAttribute);
+ // build the actual NameID
+ NameID nameId = nameIdEncoder.encode(nameIdAttribute);
+ nameId.setNameQualifier(requestContext.getRelyingPartyConfiguration().getProviderId());
+ return nameId;
} catch (AttributeEncodingException e) {
log.error("Unable to encode NameID attribute", e);
requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null, "Unable to construct NameID"));
}
/** {@inheritDoc} */
- protected NameID buildNameId(BaseSAML2ProfileRequestContext requestContext) throws ProfileException {
- NameID nameId = super.buildNameId(requestContext);
+ protected String getRequiredNameIDFormat(BaseSAMLProfileRequestContext requestContext) {
+ String requiredNameFormat = null;
+ AuthnRequest authnRequest = (AuthnRequest) requestContext.getInboundSAMLMessage();
+ NameIDPolicy nameIdPolicy = authnRequest.getNameIDPolicy();
+ if(nameIdPolicy != null){
+ requiredNameFormat = DatatypeHelper.safeTrimOrNullString(nameIdPolicy.getFormat());
+ // Check for unspec'd or encryption formats, which aren't relevant for this section of code.
+ if (requiredNameFormat != null
+ && (NameID.ENCRYPTED.equals(requiredNameFormat) || NameID.UNSPECIFIED.equals(requiredNameFormat))) {
+ requiredNameFormat = null;
+ }
+ }
- AuthnRequest authnRequest = (AuthnRequest) requestContext.getInboundMessage();
+ return requiredNameFormat;
+ }
+
+ /** {@inheritDoc} */
+ protected NameID buildNameId(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext) throws ProfileException {
+ NameID nameId = super.buildNameId(requestContext);
+ AuthnRequest authnRequest = (AuthnRequest) requestContext.getInboundSAMLMessage();
NameIDPolicy nameIdPolicy = authnRequest.getNameIDPolicy();
if(nameIdPolicy != null){
- if(!DatatypeHelper.isEmpty(nameIdPolicy.getSPNameQualifier())){
- nameId.setSPNameQualifier(nameIdPolicy.getSPNameQualifier());
+ String spNameQualifier = DatatypeHelper.safeTrimOrNullString(nameIdPolicy.getSPNameQualifier());
+ if(spNameQualifier != null){
+ nameId.setSPNameQualifier(spNameQualifier);
+ }else{
+ nameId.setSPNameQualifier(requestContext.getInboundMessageIssuer());
}
}
return nameId;
}
-
+
/**
* Selects the appropriate endpoint for the relying party and stores it in the request context.
*