Checked for null format in wrong place
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / idp / profile / saml2 / AbstractSAML2ProfileHandler.java
index a30e1fd..bfdcfdc 100644 (file)
@@ -19,18 +19,20 @@ package edu.internet2.middleware.shibboleth.idp.profile.saml2;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
+import java.util.Map;
 
 import javax.servlet.ServletRequest;
 import javax.servlet.ServletResponse;
 
+import org.apache.log4j.Logger;
 import org.joda.time.DateTime;
 import org.opensaml.common.SAMLObjectBuilder;
 import org.opensaml.common.SAMLVersion;
 import org.opensaml.common.impl.SAMLObjectContentReference;
-import org.opensaml.common.xml.SAMLConstants;
 import org.opensaml.log.Level;
-import org.opensaml.saml2.core.Advice;
 import org.opensaml.saml2.core.Assertion;
+import org.opensaml.saml2.core.AttributeQuery;
+import org.opensaml.saml2.core.AttributeStatement;
 import org.opensaml.saml2.core.Audience;
 import org.opensaml.saml2.core.AudienceRestriction;
 import org.opensaml.saml2.core.AuthnRequest;
@@ -52,8 +54,8 @@ import org.opensaml.saml2.metadata.AuthnAuthorityDescriptor;
 import org.opensaml.saml2.metadata.NameIDFormat;
 import org.opensaml.saml2.metadata.PDPDescriptor;
 import org.opensaml.saml2.metadata.RoleDescriptor;
+import org.opensaml.saml2.metadata.SPSSODescriptor;
 import org.opensaml.saml2.metadata.SSODescriptor;
-import org.opensaml.saml2.metadata.provider.MetadataProviderException;
 import org.opensaml.xml.XMLObjectBuilder;
 import org.opensaml.xml.security.credential.Credential;
 import org.opensaml.xml.signature.Signature;
@@ -61,23 +63,29 @@ import org.opensaml.xml.signature.Signer;
 import org.opensaml.xml.util.DatatypeHelper;
 
 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.SAML2NameIDAttributeEncoder;
+import edu.internet2.middleware.shibboleth.common.attribute.provider.SAML2AttributeAuthority;
+import edu.internet2.middleware.shibboleth.common.attribute.provider.ShibbolethSAMLAttributeRequestContext;
 import edu.internet2.middleware.shibboleth.common.log.AuditLogEntry;
 import edu.internet2.middleware.shibboleth.common.profile.ProfileException;
 import edu.internet2.middleware.shibboleth.common.profile.ProfileRequest;
 import edu.internet2.middleware.shibboleth.common.profile.ProfileResponse;
 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.ServiceInformation;
+import edu.internet2.middleware.shibboleth.idp.session.Session;
 
-/**
- * Common implementation details for profile handlers.
- */
+/** Common implementation details for profile handlers. */
 public abstract class AbstractSAML2ProfileHandler extends AbstractSAMLProfileHandler {
 
     /** SAML Version for this profile handler. */
     public static final SAMLVersion SAML_VERSION = SAMLVersion.VERSION_20;
 
-    /** URI for the SAML 2 protocol. */
-    public static final String SAML20_PROTOCOL_URI = "urn:oasis:names:tc:SAML:2.0:protocol";
+    /** Class logger. */
+    private Logger log = Logger.getLogger(AbstractSAML2ProfileHandler.class);
 
     /** For building response. */
     private SAMLObjectBuilder<Response> responseBuilder;
@@ -115,9 +123,6 @@ public abstract class AbstractSAML2ProfileHandler extends AbstractSAMLProfileHan
     /** For building audience. */
     private SAMLObjectBuilder<Audience> audienceBuilder;
 
-    /** For building advice. */
-    private SAMLObjectBuilder<Advice> adviceBuilder;
-
     /** For building signature. */
     private XMLObjectBuilder<Signature> signatureBuilder;
 
@@ -145,134 +150,27 @@ public abstract class AbstractSAML2ProfileHandler extends AbstractSAMLProfileHan
         proxyRestrictionBuilder = (SAMLObjectBuilder<ProxyRestriction>) getBuilderFactory().getBuilder(
                 ProxyRestriction.DEFAULT_ELEMENT_NAME);
         audienceBuilder = (SAMLObjectBuilder<Audience>) getBuilderFactory().getBuilder(Audience.DEFAULT_ELEMENT_NAME);
-        adviceBuilder = (SAMLObjectBuilder<Advice>) getBuilderFactory().getBuilder(Advice.DEFAULT_ELEMENT_NAME);
         signatureBuilder = (XMLObjectBuilder<Signature>) getBuilderFactory().getBuilder(Signature.DEFAULT_ELEMENT_NAME);
     }
 
     /**
-     * Convenience method for getting the SAML 2 advice builder.
-     * 
-     * @return SAML 2 advice builder
-     */
-    public SAMLObjectBuilder<Advice> getAdviceBuilder() {
-        return adviceBuilder;
-    }
-
-    /**
-     * Convenience method for getting the SAML 2 assertion builder.
-     * 
-     * @return SAML 2 assertion builder
-     */
-    public SAMLObjectBuilder<Assertion> getAssertionBuilder() {
-        return assertionBuilder;
-    }
-
-    /**
-     * Convenience method for getting the SAML 2 audience builder.
-     * 
-     * @return SAML 2 audience builder
-     */
-    public SAMLObjectBuilder<Audience> getAudienceBuilder() {
-        return audienceBuilder;
-    }
-
-    /**
-     * Convenience method for getting the SAML 2 audience restriction builder.
-     * 
-     * @return SAML 2 audience restriction builder
-     */
-    public SAMLObjectBuilder<AudienceRestriction> getAudienceRestrictionBuilder() {
-        return audienceRestrictionBuilder;
-    }
-
-    /**
-     * Convenience method for getting the SAML 2 conditions builder.
-     * 
-     * @return SAML 2 conditions builder
-     */
-    public SAMLObjectBuilder<Conditions> getConditionsBuilder() {
-        return conditionsBuilder;
-    }
-
-    /**
-     * Convenience method for getting the SAML 2 Issuer builder.
-     * 
-     * @return SAML 2 Issuer builder
-     */
-    public SAMLObjectBuilder<Issuer> getIssuerBuilder() {
-        return issuerBuilder;
-    }
-
-    /**
-     * Convenience method for getting the SAML 2 proxy restriction builder.
-     * 
-     * @return SAML 2 proxy restriction builder
-     */
-    public SAMLObjectBuilder<ProxyRestriction> getProxyRestrictionBuilder() {
-        return proxyRestrictionBuilder;
-    }
-
-    /**
-     * Convenience method for getting the SAML 2 response builder.
-     * 
-     * @return SAML 2 response builder
-     */
-    public SAMLObjectBuilder<Response> getResponseBuilder() {
-        return responseBuilder;
-    }
-
-    /**
-     * Convenience method for getting the Signature builder.
-     * 
-     * @return signature builder
-     */
-    public XMLObjectBuilder<Signature> getSignatureBuilder() {
-        return signatureBuilder;
-    }
-
-    /**
-     * Convenience method for getting the SAML 2 status builder.
-     * 
-     * @return SAML 2 status builder
-     */
-    public SAMLObjectBuilder<Status> getStatusBuilder() {
-        return statusBuilder;
-    }
-
-    /**
-     * Convenience method for getting the SAML 2 status code builder.
+     * Checks that the SAML major version for a request is 2.
      * 
-     * @return SAML 2 status code builder
-     */
-    public SAMLObjectBuilder<StatusCode> getStatusCodeBuilder() {
-        return statusCodeBuilder;
-    }
-
-    /**
-     * Convenience method for getting the SAML 2 status message builder.
+     * @param requestContext current request context containing the SAML message
      * 
-     * @return SAML 2 status message builder
+     * @throws ProfileException thrown if the major version of the SAML request is not 2
      */
-    public SAMLObjectBuilder<StatusMessage> getStatusMessageBuilder() {
-        return statusMessageBuilder;
-    }
-
-    /**
-     * Convenience method for getting the SAML 2 subject builder.
-     * 
-     * @return SAML 2 subject builder
-     */
-    public SAMLObjectBuilder<Subject> getSubjectBuilder() {
-        return subjectBuilder;
-    }
-
-    /**
-     * Convenience method for getting the SAML 2 subject confirmation builder.
-     * 
-     * @return SAML 2 subject confirmation builder
-     */
-    public SAMLObjectBuilder<SubjectConfirmation> getSubjectConfirmationBuilder() {
-        return subjectConfirmationBuilder;
+    protected void checkSamlVersion(SAML2ProfileRequestContext requestContext) throws ProfileException {
+        SAMLVersion version = requestContext.getSamlRequest().getVersion();
+        if (version.getMajorVersion() < 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) {
+            requestContext.setFailureStatus(buildStatus(StatusCode.VERSION_MISMATCH_URI,
+                    StatusCode.REQUEST_VERSION_TOO_HIGH_URI, null));
+            throw new ProfileException("SAML request version too high");
+        }
     }
 
     /**
@@ -285,10 +183,9 @@ public abstract class AbstractSAML2ProfileHandler extends AbstractSAMLProfileHan
      * @return the built response
      * 
      * @throws ProfileException thrown if there is a problem creating the SAML response
-     * @throws AttributeRequestException thrown if there is a problem resolving attributes
      */
     protected Response buildResponse(SAML2ProfileRequestContext requestContext, Subject assertionSubject,
-            List<Statement> statements) throws ProfileException, AttributeRequestException {
+            List<Statement> statements) throws ProfileException {
 
         DateTime issueInstant = new DateTime();
 
@@ -300,7 +197,8 @@ public abstract class AbstractSAML2ProfileHandler extends AbstractSAMLProfileHan
         }
 
         // create the SAML response and add the assertion
-        Response samlResponse = getResponseBuilder().buildObject();
+        Response samlResponse = responseBuilder.buildObject();
+        samlResponse.setIssueInstant(issueInstant);
         populateStatusResponse(requestContext, samlResponse);
 
         samlResponse.getAssertions().add(assertion);
@@ -323,8 +221,7 @@ public abstract class AbstractSAML2ProfileHandler extends AbstractSAMLProfileHan
      * @return the built assertion
      */
     protected Assertion buildAssertion(SAML2ProfileRequestContext requestContext, DateTime issueInstant) {
-
-        Assertion assertion = getAssertionBuilder().buildObject();
+        Assertion assertion = assertionBuilder.buildObject();
         assertion.setID(getIdGenerator().generateIdentifier());
         assertion.setIssueInstant(issueInstant);
         assertion.setVersion(SAMLVersion.VERSION_20);
@@ -344,9 +241,9 @@ public abstract class AbstractSAML2ProfileHandler extends AbstractSAMLProfileHan
      * @return the built issuer
      */
     protected Issuer buildEntityIssuer(SAML2ProfileRequestContext requestContext) {
-        Issuer issuer = getIssuerBuilder().buildObject();
+        Issuer issuer = issuerBuilder.buildObject();
         issuer.setFormat(Issuer.ENTITY);
-        issuer.setValue(requestContext.getRelyingPartyId());
+        issuer.setValue(requestContext.getAssertingPartyId());
 
         return issuer;
     }
@@ -363,7 +260,7 @@ public abstract class AbstractSAML2ProfileHandler extends AbstractSAMLProfileHan
     protected Conditions buildConditions(SAML2ProfileRequestContext requestContext, DateTime issueInstant) {
         AbstractSAML2ProfileConfiguration profileConfig = requestContext.getProfileConfiguration();
 
-        Conditions conditions = getConditionsBuilder().buildObject();
+        Conditions conditions = conditionsBuilder.buildObject();
         conditions.setNotBefore(issueInstant);
         conditions.setNotOnOrAfter(issueInstant.plus(profileConfig.getAssertionLifetime()));
 
@@ -372,9 +269,9 @@ public abstract class AbstractSAML2ProfileHandler extends AbstractSAMLProfileHan
         // add audience restrictions
         audiences = profileConfig.getAssertionAudiences();
         if (audiences != null && audiences.size() > 0) {
-            AudienceRestriction audienceRestriction = getAudienceRestrictionBuilder().buildObject();
+            AudienceRestriction audienceRestriction = audienceRestrictionBuilder.buildObject();
             for (String audienceUri : audiences) {
-                Audience audience = getAudienceBuilder().buildObject();
+                Audience audience = audienceBuilder.buildObject();
                 audience.setAudienceURI(audienceUri);
                 audienceRestriction.getAudiences().add(audience);
             }
@@ -384,10 +281,10 @@ public abstract class AbstractSAML2ProfileHandler extends AbstractSAMLProfileHan
         // add proxy restrictions
         audiences = profileConfig.getProxyAudiences();
         if (audiences != null && audiences.size() > 0) {
-            ProxyRestriction proxyRestriction = getProxyRestrictionBuilder().buildObject();
+            ProxyRestriction proxyRestriction = proxyRestrictionBuilder.buildObject();
             Audience audience;
             for (String audienceUri : audiences) {
-                audience = getAudienceBuilder().buildObject();
+                audience = audienceBuilder.buildObject();
                 audience.setAudienceURI(audienceUri);
                 proxyRestriction.getAudiences().add(audience);
             }
@@ -407,35 +304,199 @@ public abstract class AbstractSAML2ProfileHandler extends AbstractSAMLProfileHan
      */
     protected void populateStatusResponse(SAML2ProfileRequestContext requestContext, StatusResponseType response) {
         response.setID(getIdGenerator().generateIdentifier());
-        response.setInResponseTo(requestContext.getSamlRequest().getID());
-        response.setIssueInstant(response.getIssueInstant());
+        if (requestContext.getSamlRequest() != null) {
+            response.setInResponseTo(requestContext.getSamlRequest().getID());
+        }
         response.setVersion(SAMLVersion.VERSION_20);
         response.setIssuer(buildEntityIssuer(requestContext));
     }
 
     /**
+     * Executes a query for attributes and builds a SAML attribute statement from the results.
+     * 
+     * @param requestContext current request context
+     * 
+     * @return attribute statement resulting from the query
+     * 
+     * @throws ProfileException thrown if there is a problem making the query
+     */
+    protected AttributeStatement buildAttributeStatement(SAML2ProfileRequestContext requestContext)
+            throws ProfileException {
+
+        if (log.isDebugEnabled()) {
+            log.debug("Creating attribute statement in response to SAML request "
+                    + requestContext.getSamlRequest().getID() + " from relying party "
+                    + requestContext.getRelyingPartyId());
+        }
+
+        AbstractSAML2ProfileConfiguration profileConfiguration = requestContext.getProfileConfiguration();
+        SAML2AttributeAuthority attributeAuthority = profileConfiguration.getAttributeAuthority();
+
+        try {
+            if (log.isDebugEnabled()) {
+                log.debug("Resolving attributes for principal " + requestContext.getPrincipalName()
+                        + " of SAML request " + requestContext.getSamlRequest().getID() + " from relying party "
+                        + requestContext.getRelyingPartyId());
+            }
+            Map<String, BaseAttribute> principalAttributes = attributeAuthority
+                    .getAttributes(buildAttributeRequestContext(requestContext));
+
+            requestContext.setPrincipalAttributes(principalAttributes);
+
+            if (requestContext.getSamlRequest() instanceof AttributeQuery) {
+                return attributeAuthority.buildAttributeStatement((AttributeQuery) requestContext.getSamlRequest(),
+                        principalAttributes.values());
+            } else {
+                return attributeAuthority.buildAttributeStatement(null, principalAttributes.values());
+            }
+        } catch (AttributeRequestException e) {
+            log.error("Error resolving attributes for SAML request " + requestContext.getSamlRequest().getID()
+                    + " from relying party " + requestContext.getRelyingPartyId(), e);
+            requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null, "Error resolving attributes"));
+            throw new ProfileException("Error resolving attributes for SAML request "
+                    + requestContext.getSamlRequest().getID() + " from relying party "
+                    + requestContext.getRelyingPartyId(), e);
+        }
+    }
+
+    /**
+     * Resolves the principal name of the subject of the request.
+     * 
+     * @param requestContext current request context
+     * 
+     * @throws ProfileException thrown if the principal name can not be resolved
+     */
+    protected void resolvePrincipal(SAML2ProfileRequestContext requestContext) throws ProfileException {
+        AbstractSAML2ProfileConfiguration profileConfiguration = requestContext.getProfileConfiguration();
+        if (profileConfiguration == null) {
+            log.error("Unable to resolve principal, no SAML 2 profile configuration for relying party "
+                    + requestContext.getRelyingPartyId());
+            requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, StatusCode.REQUEST_DENIED_URI,
+                    "Error resolving principal"));
+            throw new ProfileException(
+                    "Unable to resolve principal, no SAML 2 profile configuration for relying party "
+                            + requestContext.getRelyingPartyId());
+        }
+        SAML2AttributeAuthority attributeAuthority = profileConfiguration.getAttributeAuthority();
+
+        if (log.isDebugEnabled()) {
+            log.debug("Resolving principal name for subject of SAML request " + requestContext.getSamlRequest().getID()
+                    + " from relying party " + requestContext.getRelyingPartyId());
+        }
+
+        try {
+            String principal = attributeAuthority.getPrincipal(buildAttributeRequestContext(requestContext));
+            requestContext.setPrincipalName(principal);
+        } catch (AttributeRequestException e) {
+            log.error("Error resolving attributes for SAML request " + requestContext.getSamlRequest().getID()
+                    + " from relying party " + requestContext.getRelyingPartyId(), e);
+            requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, StatusCode.UNKNOWN_PRINCIPAL_URI,
+                    "Error resolving principal"));
+            throw new ProfileException("Error resolving attributes for SAML request "
+                    + requestContext.getSamlRequest().getID() + " from relying party "
+                    + requestContext.getRelyingPartyId(), e);
+        }
+    }
+
+    /**
+     * Creates an attribute query context from the current profile request context.
+     * 
+     * @param requestContext current profile request
+     * 
+     * @return created query context
+     */
+    protected ShibbolethSAMLAttributeRequestContext<NameID, AttributeQuery> buildAttributeRequestContext(
+            SAML2ProfileRequestContext requestContext) {
+
+        ShibbolethSAMLAttributeRequestContext<NameID, AttributeQuery> queryContext;
+
+        if(requestContext.getSamlRequest() instanceof AttributeQuery){
+        queryContext = new ShibbolethSAMLAttributeRequestContext<NameID, AttributeQuery>(getMetadataProvider(),
+                requestContext.getRelyingPartyConfiguration(), (AttributeQuery) requestContext.getSamlRequest());
+        }else{
+            queryContext = new ShibbolethSAMLAttributeRequestContext<NameID, AttributeQuery>(getMetadataProvider(),
+                    requestContext.getRelyingPartyConfiguration(), null);
+        }
+        queryContext.setAttributeRequester(requestContext.getAssertingPartyId());
+        queryContext.setPrincipalName(requestContext.getPrincipalName());
+        queryContext.setProfileConfiguration(requestContext.getProfileConfiguration());
+        queryContext.setRequest(requestContext.getProfileRequest());
+
+        Session userSession = getSessionManager().getSession(getUserSessionId(requestContext.getProfileRequest()));
+        if (userSession != null) {
+            queryContext.setUserSession(userSession);
+            ServiceInformation serviceInfo = userSession.getServicesInformation().get(
+                    requestContext.getRelyingPartyId());
+            if (serviceInfo != null) {
+                String principalAuthenticationMethod = serviceInfo.getAuthenticationMethod().getAuthenticationMethod();
+
+                requestContext.setPrincipalAuthenticationMethod(principalAuthenticationMethod);
+                queryContext.setPrincipalAuthenticationMethod(principalAuthenticationMethod);
+            }
+        }
+
+        return queryContext;
+    }
+
+    /**
      * Signs the given assertion if either the current profile configuration or the relying party configuration contains
      * signing credentials.
      * 
      * @param requestContext current request context
      * @param assertion assertion to sign
+     * 
+     * @throws ProfileException thrown if the metadata can not be located for the relying party or, if signing is
+     *             required, if a signing credential is not configured
      */
-    protected void signAssertion(SAML2ProfileRequestContext requestContext, Assertion assertion) {
+    protected void signAssertion(SAML2ProfileRequestContext requestContext, Assertion assertion)
+            throws ProfileException {
+        if (log.isDebugEnabled()) {
+            log.debug("Determining if SAML assertion to relying party " + requestContext.getRelyingPartyId()
+                    + " should be signed");
+        }
+
+        boolean signAssertion = false;
+
         AbstractSAML2ProfileConfiguration profileConfig = requestContext.getProfileConfiguration();
 
-        if (!profileConfig.getSignAssertions()) {
+        if (requestContext.getRelyingPartyRoleMetadata() instanceof SPSSODescriptor) {
+            SPSSODescriptor ssoDescriptor = (SPSSODescriptor) requestContext.getRelyingPartyRoleMetadata();
+            if (ssoDescriptor.getWantAssertionsSigned() != null) {
+                signAssertion = ssoDescriptor.getWantAssertionsSigned().booleanValue();
+                if (log.isDebugEnabled()) {
+                    log.debug("Entity metadata for relying party " + requestContext.getRelyingPartyId()
+                            + " indicates to sign assertions: " + signAssertion);
+                }
+            }
+        } else if (profileConfig.getSignAssertions()) {
+            signAssertion = true;
+            log.debug("IdP relying party configuration "
+                    + requestContext.getRelyingPartyConfiguration().getRelyingPartyId()
+                    + " indicates to sign assertions: " + signAssertion);
+        }
+
+        if (!signAssertion) {
             return;
         }
 
+        if (log.isDebugEnabled()) {
+            log.debug("Determining signing credntial for assertion to relying party "
+                    + requestContext.getRelyingPartyId());
+        }
         Credential signatureCredential = profileConfig.getSigningCredential();
         if (signatureCredential == null) {
             signatureCredential = requestContext.getRelyingPartyConfiguration().getDefaultSigningCredential();
         }
 
         if (signatureCredential == null) {
-            return;
+            throw new ProfileException("No signing credential is specified for relying party configuration "
+                    + requestContext.getRelyingPartyConfiguration().getProviderId()
+                    + " or it's SAML2 attribute query profile configuration");
         }
 
+        if (log.isDebugEnabled()) {
+            log.debug("Signing assertion to relying party " + requestContext.getRelyingPartyId());
+        }
         SAMLObjectContentReference contentRef = new SAMLObjectContentReference(assertion);
         Signature signature = signatureBuilder.buildObject(Signature.DEFAULT_ELEMENT_NAME);
         signature.getContentReferences().add(contentRef);
@@ -450,27 +511,26 @@ public abstract class AbstractSAML2ProfileHandler extends AbstractSAMLProfileHan
      * @param topLevelCode The top-level status code. Should be from saml-core-2.0-os, sec. 3.2.2.2
      * @param secondLevelCode An optional second-level failure code. Should be from saml-core-2.0-is, sec 3.2.2.2. If
      *            null, no second-level Status element will be set.
-     * @param secondLevelFailureMessage An optional second-level failure message
+     * @param failureMessage An optional second-level failure message
      * 
      * @return a Status object.
      */
-    protected Status buildStatus(String topLevelCode, String secondLevelCode, String secondLevelFailureMessage) {
-
-        Status status = getStatusBuilder().buildObject();
+    protected Status buildStatus(String topLevelCode, String secondLevelCode, String failureMessage) {
+        Status status = statusBuilder.buildObject();
 
-        StatusCode statusCode = getStatusCodeBuilder().buildObject();
+        StatusCode statusCode = statusCodeBuilder.buildObject();
         statusCode.setValue(DatatypeHelper.safeTrimOrNullString(topLevelCode));
         status.setStatusCode(statusCode);
 
         if (secondLevelCode != null) {
-            StatusCode secondLevelStatusCode = getStatusCodeBuilder().buildObject();
+            StatusCode secondLevelStatusCode = statusCodeBuilder.buildObject();
             secondLevelStatusCode.setValue(DatatypeHelper.safeTrimOrNullString(secondLevelCode));
             statusCode.setStatusCode(secondLevelStatusCode);
         }
 
-        if (secondLevelFailureMessage != null) {
-            StatusMessage msg = getStatusMessageBuilder().buildObject();
-            msg.setMessage(secondLevelFailureMessage);
+        if (failureMessage != null) {
+            StatusMessage msg = statusMessageBuilder.buildObject();
+            msg.setMessage(failureMessage);
             status.setStatusMessage(msg);
         }
 
@@ -484,15 +544,20 @@ public abstract class AbstractSAML2ProfileHandler extends AbstractSAMLProfileHan
      * @param confirmationMethod subject confirmation method used for the subject
      * 
      * @return SAML subject for the user for the service provider
+     * 
+     * @throws ProfileException thrown if a NameID can not be created either because there was a problem encoding the
+     *             name ID attribute or because there are no supported name formats
      */
-    protected Subject buildSubject(SAML2ProfileRequestContext requestContext, String confirmationMethod) {
-        NameID nameID = requestContext.getSubjectNameID();
+    protected Subject buildSubject(SAML2ProfileRequestContext requestContext, String confirmationMethod)
+            throws ProfileException {
+        NameID nameID = buildNameId(requestContext);
+        requestContext.setSubjectNameID(nameID);
         // TODO handle encryption
 
-        SubjectConfirmation subjectConfirmation = getSubjectConfirmationBuilder().buildObject();
+        SubjectConfirmation subjectConfirmation = subjectConfirmationBuilder.buildObject();
         subjectConfirmation.setMethod(confirmationMethod);
 
-        Subject subject = getSubjectBuilder().buildObject();
+        Subject subject = subjectBuilder.buildObject();
         subject.setNameID(nameID);
         subject.getSubjectConfirmations().add(subjectConfirmation);
 
@@ -500,26 +565,62 @@ public abstract class AbstractSAML2ProfileHandler extends AbstractSAMLProfileHan
     }
 
     /**
-     * Constructs an SAML response message carrying a request error.
+     * Builds a NameID appropriate for this request. NameIDs are built by inspecting the SAML request and metadata,
+     * picking a name format that was requested by the relying party or is mutually supported by both the relying party
+     * and asserting party as described in their metadata entries. Once a set of supported name formats is determined
+     * the principals attributes are inspected for an attribute supported an attribute encoder whose category is one of
+     * the supported name formats.
      * 
      * @param requestContext current request context
-     * @param topLevelCode The top-level status code. Should be from saml-core-2.0-os, sec. 3.2.2.2
-     * @param secondLevelCode An optional second-level failure code. Should be from saml-core-2.0-is, sec 3.2.2.2. If
-     *            null, no second-level Status element will be set.
-     * @param secondLevelFailureMessage An optional second-level failure message
      * 
-     * @return the constructed error response
+     * @return the NameID appropriate for this request
+     * 
+     * @throws ProfileException thrown if a NameID can not be created either because there was a problem encoding the
+     *             name ID attribute or because there are no supported name formats
      */
-    protected Response buildErrorResponse(SAML2ProfileRequestContext requestContext, String topLevelCode,
-            String secondLevelCode, String secondLevelFailureMessage) {
-        Response samlResponse = getResponseBuilder().buildObject();
-        samlResponse.setIssueInstant(new DateTime());
-        populateStatusResponse(requestContext, samlResponse);
+    protected NameID buildNameId(SAML2ProfileRequestContext requestContext) throws ProfileException {
+        if (log.isDebugEnabled()) {
+            log.debug("Building assertion NameID for principal/relying party:" + requestContext.getPrincipalName()
+                    + "/" + requestContext.getRelyingPartyId());
+        }
+        Map<String, BaseAttribute> principalAttributes = requestContext.getPrincipalAttributes();
+        List<String> supportedNameFormats = getNameFormats(requestContext);
 
-        Status status = buildStatus(topLevelCode, secondLevelCode, secondLevelFailureMessage);
-        samlResponse.setStatus(status);
+        if (log.isDebugEnabled()) {
+            log.debug("Supported NameID formats: " + supportedNameFormats);
+        }
 
-        return samlResponse;
+        if (principalAttributes == null || supportedNameFormats == null) {
+            log.error("No attributes for principal " + requestContext.getPrincipalName() 
+                    + " support constructions of NameID");
+            requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, StatusCode.INVALID_NAMEID_POLICY_URI,
+                    "Unable to construct NameID"));
+            throw new ProfileException("No principal attributes support NameID construction");
+        }
+
+        try {
+            SAML2NameIDAttributeEncoder nameIdEncoder;
+            for (BaseAttribute<?> attribute : principalAttributes.values()) {
+                for (AttributeEncoder encoder : attribute.getEncoders()) {
+                    if (encoder instanceof SAML2NameIDAttributeEncoder) {
+                        nameIdEncoder = (SAML2NameIDAttributeEncoder) encoder;
+                        if (supportedNameFormats.contains(nameIdEncoder.getNameFormat())) {
+                            if (log.isDebugEnabled()) {
+                                log.debug("Using attribute " + attribute.getId() + " suppoting NameID format "
+                                        + nameIdEncoder.getNameFormat() + " to create the NameID for principal "
+                                        + requestContext.getPrincipalName());
+                            }
+                            return nameIdEncoder.encode(attribute);
+                        }
+                    }
+                }
+            }
+            requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null, "Unable to construct NameID"));
+            throw new ProfileException("No principal attribute supported encoding into the a supported name ID format.");
+        } catch (AttributeEncodingException e) {
+            requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null, "Unable to construct NameID"));
+            throw new ProfileException("Unable to encode NameID attribute", e);
+        }
     }
 
     /**
@@ -531,44 +632,39 @@ public abstract class AbstractSAML2ProfileHandler extends AbstractSAMLProfileHan
      * 
      * @throws ProfileException thrown if there is a problem determing the NameID format to use
      */
-    protected List<String> getNameIDFormat(SAML2ProfileRequestContext requestContext) throws ProfileException {
+    protected List<String> getNameFormats(SAML2ProfileRequestContext requestContext) throws ProfileException {
         ArrayList<String> nameFormats = new ArrayList<String>();
 
-        try {
-            RoleDescriptor assertingPartyRole = getMetadataProvider().getRole(requestContext.getAssertingPartyId(),
-                    requestContext.getAssertingPartyRole(), SAMLConstants.SAML20P_NS);
-            List<String> assertingPartySupportedFormats = getEntitySupportedFormats(assertingPartyRole);
-
-            String nameFormat = null;
-            if (requestContext.getSamlRequest() instanceof AuthnRequest) {
-                AuthnRequest authnRequest = (AuthnRequest) requestContext.getSamlRequest();
-                if (authnRequest.getNameIDPolicy() != null) {
-                    nameFormat = authnRequest.getNameIDPolicy().getFormat();
-                    if (assertingPartySupportedFormats.contains(nameFormat)) {
-                        nameFormats.add(nameFormat);
-                    } else {
-                        throw new ProfileException("NameID format required by relying party is not supported");
-                    }
+        List<String> assertingPartySupportedFormats = getEntitySupportedFormats(requestContext
+                .getAssertingPartyRoleMetadata());
+
+        String nameFormat = null;
+        if (requestContext.getSamlRequest() instanceof AuthnRequest) {
+            AuthnRequest authnRequest = (AuthnRequest) requestContext.getSamlRequest();
+            if (authnRequest.getNameIDPolicy() != null && !DatatypeHelper.isEmpty(nameFormat)) {
+                nameFormat = authnRequest.getNameIDPolicy().getFormat();
+                if (assertingPartySupportedFormats.contains(nameFormat)) {
+                    nameFormats.add(nameFormat);
+                } else {
+                    requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI,
+                            StatusCode.INVALID_NAMEID_POLICY_URI, "Format not supported: " + nameFormat));
+                    throw new ProfileException("NameID format required by relying party is not supported");
                 }
             }
+        }
 
-            if (nameFormats.isEmpty()) {
-                RoleDescriptor relyingPartyRole = getMetadataProvider().getRole(requestContext.getRelyingPartyId(),
-                        requestContext.getRelyingPartyRole(), SAMLConstants.SAML20P_NS);
-                List<String> relyingPartySupportedFormats = getEntitySupportedFormats(relyingPartyRole);
-
-                assertingPartySupportedFormats.retainAll(relyingPartySupportedFormats);
-                nameFormats.addAll(assertingPartySupportedFormats);
-            }
-            if (nameFormats.isEmpty()) {
-                nameFormats.add("urn:oasis:names:tc:SAML:2.0:nameid-format:unspecified");
-            }
-
-            return nameFormats;
+        if (nameFormats.isEmpty()) {
+            List<String> relyingPartySupportedFormats = getEntitySupportedFormats(requestContext
+                    .getRelyingPartyRoleMetadata());
 
-        } catch (MetadataProviderException e) {
-            throw new ProfileException("Unable to determine lookup entity metadata", e);
+            assertingPartySupportedFormats.retainAll(relyingPartySupportedFormats);
+            nameFormats.addAll(assertingPartySupportedFormats);
+        }
+        if (nameFormats.isEmpty()) {
+            nameFormats.add("urn:oasis:names:tc:SAML:2.0:nameid-format:unspecified");
         }
+
+        return nameFormats;
     }
 
     /**
@@ -602,6 +698,23 @@ public abstract class AbstractSAML2ProfileHandler extends AbstractSAMLProfileHan
     }
 
     /**
+     * Constructs an SAML response message carrying a request error.
+     * 
+     * @param requestContext current request context
+     * 
+     * @return the constructed error response
+     */
+    protected Response buildErrorResponse(SAML2ProfileRequestContext requestContext) {
+        Response samlResponse = responseBuilder.buildObject();
+        samlResponse.setIssueInstant(new DateTime());
+        populateStatusResponse(requestContext, samlResponse);
+
+        samlResponse.setStatus(requestContext.getFailureStatus());
+
+        return samlResponse;
+    }
+
+    /**
      * Writes an aduit log entry indicating the successful response to the attribute request.
      * 
      * @param context current request context
@@ -617,6 +730,9 @@ public abstract class AbstractSAML2ProfileHandler extends AbstractSAMLProfileHan
         auditLogEntry.setRequestId(context.getSamlRequest().getID());
         auditLogEntry.setResponseBinding(context.getMessageEncoder().getBindingURI());
         auditLogEntry.setResponseId(context.getSamlResponse().getID());
+        if(context.getPrincipalAttributes() != null){
+            auditLogEntry.getReleasedAttributes().addAll(context.getPrincipalAttributes().keySet());
+        }
         getAduitLog().log(Level.CRITICAL, auditLogEntry);
     }
 
@@ -627,9 +743,7 @@ public abstract class AbstractSAML2ProfileHandler extends AbstractSAMLProfileHan
      * @param <ResponseType> type of SAML 2 response
      * @param <ProfileConfigurationType> configuration type for this profile
      */
-    protected class SAML2ProfileRequestContext<RequestType extends RequestAbstractType, 
-                                               ResponseType extends StatusResponseType, 
-                                               ProfileConfigurationType extends AbstractSAML2ProfileConfiguration>
+    protected class SAML2ProfileRequestContext<RequestType extends RequestAbstractType, ResponseType extends StatusResponseType, ProfileConfigurationType extends AbstractSAML2ProfileConfiguration>
             extends SAMLProfileRequestContext {
 
         /** SAML request message. */
@@ -644,6 +758,9 @@ public abstract class AbstractSAML2ProfileHandler extends AbstractSAMLProfileHan
         /** The NameID of the subject of this request. */
         private NameID subjectNameID;
 
+        /** The request failure status. */
+        private Status failureStatus;
+
         /**
          * Constructor.
          * 
@@ -726,5 +843,23 @@ public abstract class AbstractSAML2ProfileHandler extends AbstractSAMLProfileHan
         public void setSamlResponse(ResponseType response) {
             samlResponse = response;
         }
+
+        /**
+         * Gets the status reflecting a request failure.
+         * 
+         * @return status reflecting a request failure
+         */
+        public Status getFailureStatus() {
+            return failureStatus;
+        }
+
+        /**
+         * Sets the status reflecting a request failure.
+         * 
+         * @param status status reflecting a request failure
+         */
+        public void setFailureStatus(Status status) {
+            failureStatus = status;
+        }
     }
 }
\ No newline at end of file