Refactor name identifier code, pulling all of the selection process up in to Abstract...
authorlajoie <lajoie@ab3bd59b-922f-494d-bb5f-6f0a3c29deca>
Thu, 4 Mar 2010 14:25:55 +0000 (14:25 +0000)
committerlajoie <lajoie@ab3bd59b-922f-494d-bb5f-6f0a3c29deca>
Thu, 4 Mar 2010 14:25:55 +0000 (14:25 +0000)
Always populate name qualifiers for name identifiers
Fix type in cache manager ID in internal.xml
Fix encoder type of jpegPhoto attribute in attribute-resolver.xml

git-svn-id: https://subversion.switch.ch/svn/shibboleth/java-idp/branches/REL_2@2921 ab3bd59b-922f-494d-bb5f-6f0a3c29deca

src/installer/resources/conf-tmpl/attribute-resolver.xml
src/installer/resources/conf-tmpl/internal.xml
src/main/java/edu/internet2/middleware/shibboleth/idp/profile/AbstractSAMLProfileHandler.java
src/main/java/edu/internet2/middleware/shibboleth/idp/profile/saml1/AbstractSAML1ProfileHandler.java
src/main/java/edu/internet2/middleware/shibboleth/idp/profile/saml2/AbstractSAML2ProfileHandler.java
src/main/java/edu/internet2/middleware/shibboleth/idp/profile/saml2/SSOProfileHandler.java

index 48b9209..1adec81 100644 (file)
         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>
 
index 849718a..176f473 100644 (file)
@@ -5,7 +5,7 @@
      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" />
index 3010232..06b263d 100644 (file)
@@ -23,7 +23,6 @@ import java.util.Map;
 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;
@@ -42,9 +41,14 @@ import org.opensaml.ws.security.SecurityPolicyResolver;
 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;
@@ -401,6 +405,87 @@ public abstract class AbstractSAMLProfileHandler extends
     }
 
     /**
+     * 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
@@ -425,6 +510,14 @@ public abstract class AbstractSAMLProfileHandler extends
             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;
     }
 
@@ -459,6 +552,92 @@ public abstract class AbstractSAMLProfileHandler extends
     }
 
     /**
+     * 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)},
@@ -540,17 +719,17 @@ public abstract class AbstractSAMLProfileHandler extends
      * @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());
@@ -559,21 +738,22 @@ public abstract class AbstractSAMLProfileHandler extends
         } 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
@@ -601,18 +781,17 @@ public abstract class AbstractSAMLProfileHandler extends
         }
         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
index 89685cc..c536a3c 100644 (file)
@@ -57,13 +57,13 @@ import org.opensaml.xml.security.credential.Credential;
 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;
@@ -360,59 +360,29 @@ public abstract class AbstractSAML1ProfileHandler extends AbstractSAMLProfileHan
      */
     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 '{}'",
@@ -492,6 +462,8 @@ public abstract class AbstractSAML1ProfileHandler extends AbstractSAMLProfileHan
      * 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();
@@ -504,8 +476,10 @@ public abstract class AbstractSAML1ProfileHandler extends AbstractSAMLProfileHan
 
             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());
         }
     }
 
@@ -522,10 +496,10 @@ public abstract class AbstractSAML1ProfileHandler extends AbstractSAMLProfileHan
     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());
@@ -652,7 +626,7 @@ public abstract class AbstractSAML1ProfileHandler extends AbstractSAMLProfileHan
             throw new ProfileException(msg, e);
         }
     }
-    
+
     /**
      * Determine whether issued assertions should be signed.
      * 
@@ -661,18 +635,18 @@ public abstract class AbstractSAML1ProfileHandler extends AbstractSAMLProfileHan
      * @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) {
@@ -681,11 +655,11 @@ public abstract class AbstractSAML1ProfileHandler extends AbstractSAMLProfileHan
                             .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");
         }
     }
index 60de211..ecd9e9b 100644 (file)
@@ -72,13 +72,13 @@ 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.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;
@@ -261,7 +261,7 @@ public abstract class AbstractSAML2ProfileHandler extends AbstractSAMLProfileHan
             assertion = buildAssertion(requestContext, issueInstant);
             assertion.getStatements().addAll(statements);
             assertion.setSubject(buildSubject(requestContext, subjectConfirmationMethod, issueInstant));
-            
+
             postProcessAssertion(requestContext, assertion);
 
             signAssertion(requestContext, assertion);
@@ -275,12 +275,12 @@ public abstract class AbstractSAML2ProfileHandler extends AbstractSAMLProfileHan
                 } 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 {
@@ -290,7 +290,7 @@ public abstract class AbstractSAML2ProfileHandler extends AbstractSAMLProfileHan
 
         Status status = buildStatus(StatusCode.SUCCESS_URI, null, null);
         samlResponse.setStatus(status);
-        
+
         postProcessResponse(requestContext, samlResponse);
 
         return samlResponse;
@@ -303,18 +303,18 @@ public abstract class AbstractSAML2ProfileHandler extends AbstractSAMLProfileHan
      * @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");
         }
     }
 
@@ -326,7 +326,7 @@ public abstract class AbstractSAML2ProfileHandler extends AbstractSAMLProfileHan
      * 
      * @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 {
     }
 
@@ -338,7 +338,7 @@ public abstract class AbstractSAML2ProfileHandler extends AbstractSAMLProfileHan
      * 
      * @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 {
     }
 
@@ -462,8 +462,10 @@ public abstract class AbstractSAML2ProfileHandler extends AbstractSAMLProfileHan
 
             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());
         }
     }
 
@@ -478,10 +480,10 @@ public abstract class AbstractSAML2ProfileHandler extends AbstractSAMLProfileHan
      */
     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());
 
@@ -559,7 +561,7 @@ public abstract class AbstractSAML2ProfileHandler extends AbstractSAMLProfileHan
         if (!signAssertion) {
             return;
         }
-        
+
         AbstractSAML2ProfileConfiguration profileConfig = requestContext.getProfileConfiguration();
 
         log.debug("Determining signing credntial for assertion to relying party '{}'", requestContext
@@ -616,18 +618,18 @@ public abstract class AbstractSAML2ProfileHandler extends AbstractSAMLProfileHan
      * @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) {
@@ -636,7 +638,7 @@ public abstract class AbstractSAML2ProfileHandler extends AbstractSAMLProfileHan
                             .getInboundMessageIssuer(), signAssertion);
                 }
             }
-            
+
             return signAssertion;
         } catch (MessageEncodingException e) {
             log.error("Unable to determine if outbound encoding '{}' provides message integrity protection", encoder
@@ -701,32 +703,29 @@ public abstract class AbstractSAML2ProfileHandler extends AbstractSAMLProfileHan
         }
 
         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.
@@ -735,18 +734,16 @@ public abstract class AbstractSAML2ProfileHandler extends AbstractSAMLProfileHan
      * @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",
@@ -754,13 +751,12 @@ public abstract class AbstractSAML2ProfileHandler extends AbstractSAMLProfileHan
             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
@@ -826,100 +822,29 @@ public abstract class AbstractSAML2ProfileHandler extends AbstractSAMLProfileHan
      *             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"));
index 5c84da8..4d0f435 100644 (file)
@@ -563,20 +563,39 @@ public class SSOProfileHandler extends AbstractSAML2ProfileHandler {
     }
     
     /** {@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.
      *