Lots of code cleanup
authorlajoie <lajoie@ab3bd59b-922f-494d-bb5f-6f0a3c29deca>
Mon, 4 Jun 2007 02:00:40 +0000 (02:00 +0000)
committerlajoie <lajoie@ab3bd59b-922f-494d-bb5f-6f0a3c29deca>
Mon, 4 Jun 2007 02:00:40 +0000 (02:00 +0000)
Support for policy based name ID format selection

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

src/edu/internet2/middleware/shibboleth/idp/config/profile/SAML2AttributeQueryProfileHandlerBeanDefinitionParser.java
src/edu/internet2/middleware/shibboleth/idp/profile/AbstractSAMLProfileHandler.java
src/edu/internet2/middleware/shibboleth/idp/profile/saml1/AbstractSAML1ProfileHandler.java
src/edu/internet2/middleware/shibboleth/idp/profile/saml1/ShibbolethSSO.java
src/edu/internet2/middleware/shibboleth/idp/profile/saml2/AbstractAttributeQuery.java [deleted file]
src/edu/internet2/middleware/shibboleth/idp/profile/saml2/AbstractSAML2ProfileHandler.java
src/edu/internet2/middleware/shibboleth/idp/profile/saml2/AttributeQueryProfileHandler.java [new file with mode: 0644]
src/edu/internet2/middleware/shibboleth/idp/profile/saml2/HTTPSOAPAttributeQuery.java [deleted file]
tests/data/conf1/attribute-resolver.xml

index 90b6acb..ff81dc4 100644 (file)
@@ -20,7 +20,7 @@ import javax.xml.namespace.QName;
 
 import org.w3c.dom.Element;
 
-import edu.internet2.middleware.shibboleth.idp.profile.saml2.HTTPSOAPAttributeQuery;
+import edu.internet2.middleware.shibboleth.idp.profile.saml2.AttributeQueryProfileHandler;
 
 /**
  * Spring bean definition parser for {@link HTTPSOAPAttributeQuery} profile handlers.
@@ -34,6 +34,6 @@ public class SAML2AttributeQueryProfileHandlerBeanDefinitionParser extends
 
     /** {@inheritDoc} */
     protected Class getBeanClass(Element arg0) {
-        return HTTPSOAPAttributeQuery.class;
+        return AttributeQueryProfileHandler.class;
     }
 }
\ No newline at end of file
index f4f5de6..79c226a 100644 (file)
 package edu.internet2.middleware.shibboleth.idp.profile;
 
 import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
 import javax.servlet.http.HttpServletRequest;
+import javax.xml.namespace.QName;
 
 import org.apache.log4j.Logger;
 import org.opensaml.common.IdentifierGenerator;
 import org.opensaml.common.binding.decoding.MessageDecoderFactory;
 import org.opensaml.common.binding.encoding.MessageEncoderFactory;
-import org.opensaml.common.impl.SecureRandomIdentifierGenerator;
 import org.opensaml.saml2.metadata.provider.MetadataProvider;
 
 import edu.internet2.middleware.shibboleth.common.log.AuditLogEntry;
 import edu.internet2.middleware.shibboleth.common.profile.ProfileRequest;
+import edu.internet2.middleware.shibboleth.common.profile.ProfileResponse;
 import edu.internet2.middleware.shibboleth.common.profile.provider.AbstractShibbolethProfileHandler;
 import edu.internet2.middleware.shibboleth.common.relyingparty.provider.SAMLMDRelyingPartyConfigurationManager;
 import edu.internet2.middleware.shibboleth.idp.session.Session;
@@ -53,7 +55,6 @@ public abstract class AbstractSAMLProfileHandler extends
     /** Constructor. */
     protected AbstractSAMLProfileHandler() {
         super();
-        idGenerator = new SecureRandomIdentifierGenerator();
     }
 
     /**
@@ -64,6 +65,15 @@ public abstract class AbstractSAMLProfileHandler extends
     public IdentifierGenerator getIdGenerator() {
         return idGenerator;
     }
+    
+    /**
+     * Gets an ID generator which may be used for SAML assertions, requests, etc.
+     * 
+     * @param generator an ID generator which may be used for SAML assertions, requests, etc
+     */
+    public void setIdGenerator(IdentifierGenerator generator){
+        idGenerator = generator;
+    }
 
     /**
      * Gets the factory used to build new message decoders.
@@ -139,4 +149,63 @@ public abstract class AbstractSAMLProfileHandler extends
 
         return null;
     }
+    
+    /**
+     * Contextual object used to accumlate information as profile requests are being processed.
+     */
+    protected class SAMLProfileRequestContext extends ShibbolethProfileRequestContext {
+        
+        /** Role descriptor name that the asserting party is operating in. */
+        private QName assertingPartyRole;
+        
+        /** Role descriptor name that the relying party is operating in. */
+        private QName relyingPartyRole;
+        
+        /**
+         * Constructor.
+         * 
+         * @param request current profile request
+         * @param response current profile response
+         */
+        public SAMLProfileRequestContext(ProfileRequest<ServletRequest> request,
+                ProfileResponse<ServletResponse> response) {
+            super(request, response);
+        }
+
+        /**
+         * Gets the role descriptor name that the asserting party is operating in.
+         * 
+         * @return role descriptor name that the asserting party is operating in
+         */
+        public QName getAssertingPartyRole() {
+            return assertingPartyRole;
+        }
+
+        /**
+         * Sets the role descriptor name that the asserting party is operating in.
+         * 
+         * @param role role descriptor name that the asserting party is operating in
+         */
+        public void setAssertingPartyRole(QName role) {
+            assertingPartyRole = role;
+        }
+
+        /**
+         * Gets the role descriptor name that the relying party is operating in.
+         * 
+         * @return role descriptor name that the relying party is operating in
+         */
+        public QName getRelyingPartyRole() {
+            return relyingPartyRole;
+        }
+
+        /**
+         * Sets the role descriptor name that the relying party is operating in.
+         * 
+         * @param role role descriptor name that the relying party is operating in
+         */
+        public void setRelyingPartyRole(QName role) {
+            relyingPartyRole = role;
+        }
+    }
 }
\ No newline at end of file
index b92d6d1..68586c0 100644 (file)
 
 package edu.internet2.middleware.shibboleth.idp.profile.saml1;
 
+import java.security.NoSuchAlgorithmException;
+
 import javax.servlet.ServletResponse;
 
 import org.apache.log4j.Logger;
-import org.opensaml.Configuration;
 import org.opensaml.common.SAMLObjectBuilder;
 import org.opensaml.common.SAMLVersion;
 import org.opensaml.common.binding.BindingException;
 import org.opensaml.common.binding.encoding.MessageEncoder;
 import org.opensaml.common.impl.SAMLObjectContentReference;
-import org.opensaml.common.impl.SecureRandomIdentifierGenerator;
 import org.opensaml.saml1.core.Assertion;
+import org.opensaml.saml1.core.Response;
 import org.opensaml.saml1.core.Status;
 import org.opensaml.saml1.core.StatusCode;
 import org.opensaml.saml1.core.StatusMessage;
-import org.opensaml.saml1.core.Response;
-import org.opensaml.saml2.metadata.AssertionConsumerService;
 import org.opensaml.saml2.metadata.Endpoint;
 import org.opensaml.saml2.metadata.RoleDescriptor;
-import org.opensaml.saml2.metadata.provider.MetadataProvider;
 import org.opensaml.xml.XMLObjectBuilder;
-import org.opensaml.xml.XMLObjectBuilderFactory;
 import org.opensaml.xml.security.credential.Credential;
 import org.opensaml.xml.signature.Signature;
 import org.opensaml.xml.signature.Signer;
@@ -59,9 +56,6 @@ public abstract class AbstractSAML1ProfileHandler extends AbstractSAMLProfileHan
     /** Class logger. */
     private static Logger log = Logger.getLogger(AbstractSAML1ProfileHandler.class);
     
-    /** For generating random ids. */
-    private SecureRandomIdentifierGenerator idGenerator;
-    
     /** Builder for Status objects. */
     protected SAMLObjectBuilder<Status> statusBuilder;
     
@@ -77,9 +71,9 @@ public abstract class AbstractSAML1ProfileHandler extends AbstractSAMLProfileHan
     /**
      * Default constructor.
      */
-    public AbstractSAML1ProfileHandler() {
-        idGenerator = new SecureRandomIdentifierGenerator();
-        
+    @SuppressWarnings("unchecked")
+    public AbstractSAML1ProfileHandler(){
+        super();
         statusBuilder        = (SAMLObjectBuilder<Status>) getBuilderFactory().getBuilder(Status.DEFAULT_ELEMENT_NAME);
         statusCodeBuilder    = (SAMLObjectBuilder<StatusCode>) getBuilderFactory().getBuilder(StatusCode.DEFAULT_ELEMENT_NAME);
         statusMessageBuilder = (SAMLObjectBuilder<StatusMessage>) getBuilderFactory().getBuilder(StatusMessage.DEFAULT_ELEMENT_NAME);
@@ -87,15 +81,6 @@ public abstract class AbstractSAML1ProfileHandler extends AbstractSAMLProfileHan
     }
     
     /**
-     * Returns the id generator.
-     *
-     * @return Returns the idGenerator.
-     */
-    public SecureRandomIdentifierGenerator getIdGenerator() {
-        return idGenerator;
-    }
-    
-    /**
      * Build a SAML 1 Status element.
      *
      * @param statusCode The status code - see oasis-sstc-saml-core-1.1, section 3.4.3.1.
index a5a0de3..a17f2ff 100644 (file)
@@ -962,7 +962,7 @@ public class ShibbolethSSO extends AbstractSAML1ProfileHandler {
         Subject subject = subjectBuilder.buildObject();
         
         NameIdentifier nameID = nameIdentifierBuilder.buildObject();
-        nameID.setFormat(ssoConfig.getDefaultNameIDFormat());
+        nameID.setFormat(ssoConfig.getSubjectNameFormat());
         
         String username = loginContext.getUserID();
         
diff --git a/src/edu/internet2/middleware/shibboleth/idp/profile/saml2/AbstractAttributeQuery.java b/src/edu/internet2/middleware/shibboleth/idp/profile/saml2/AbstractAttributeQuery.java
deleted file mode 100644 (file)
index dd2690f..0000000
+++ /dev/null
@@ -1,398 +0,0 @@
-/*
- * Copyright [2006] [University Corporation for Advanced Internet Development, Inc.]
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package edu.internet2.middleware.shibboleth.idp.profile.saml2;
-
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-
-import org.apache.log4j.Logger;
-import org.joda.time.DateTime;
-import org.opensaml.common.binding.BindingException;
-import org.opensaml.common.binding.decoding.MessageDecoder;
-import org.opensaml.common.binding.encoding.MessageEncoder;
-import org.opensaml.log.Level;
-import org.opensaml.saml2.core.Assertion;
-import org.opensaml.saml2.core.AttributeQuery;
-import org.opensaml.saml2.core.AttributeStatement;
-import org.opensaml.saml2.core.RequestAbstractType;
-import org.opensaml.saml2.core.Response;
-import org.opensaml.saml2.core.Status;
-import org.opensaml.saml2.core.StatusCode;
-import org.opensaml.ws.security.SecurityPolicyException;
-
-import edu.internet2.middleware.shibboleth.common.attribute.AttributeRequestException;
-import edu.internet2.middleware.shibboleth.common.attribute.SAML2AttributeAuthority;
-import edu.internet2.middleware.shibboleth.common.attribute.provider.ShibbolethAttributeRequestContext;
-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.RelyingPartyConfiguration;
-import edu.internet2.middleware.shibboleth.common.relyingparty.provider.saml2.AttributeQueryConfiguration;
-import edu.internet2.middleware.shibboleth.idp.session.ServiceInformation;
-import edu.internet2.middleware.shibboleth.idp.session.Session;
-
-/**
- * SAML 2.0 Attribute Query profile handler.
- */
-public abstract class AbstractAttributeQuery extends AbstractSAML2ProfileHandler {
-
-    /** Class logger. */
-    private static Logger log = Logger.getLogger(AbstractAttributeQuery.class);
-
-    /** {@inheritDoc} */
-    public String getProfileId() {
-        return "urn:oasis:names:tc:SAML:2.0:profiles:query";
-    }
-
-    /** {@inheritDoc} */
-    public void processRequest(ProfileRequest<ServletRequest> request, ProfileResponse<ServletResponse> response)
-            throws ProfileException {
-
-        AttributeQueryContext queryContext = new AttributeQueryContext(request, response);
-
-        getMessageDecoder(queryContext);
-
-        try {
-            decodeRequest(queryContext);
-            buildAttributeRequestContext(queryContext);
-            buildResponse(queryContext);
-        } catch (SecurityPolicyException e) {
-            buildErrorResponse(queryContext, e);
-        } catch (AttributeRequestException e) {
-            buildErrorResponse(queryContext, e);
-        }
-
-        getMessageEncoder(queryContext);
-
-        try {
-            queryContext.getMessageEncoder().encode();
-            writeAuditLogEntry(queryContext);
-        } catch (BindingException e) {
-            log.error("Unable to encode response the relying party: "
-                    + queryContext.getAttributeRequestContext().getAttributeRequester(), e);
-            throw new ProfileException("Unable to encode response the relying party: "
-                    + queryContext.getAttributeRequestContext().getAttributeRequester(), e);
-        }
-    }
-
-    /**
-     * Gets a populated message decoder.
-     * 
-     * @param queryContext current request context
-     * 
-     * @throws ProfileException thrown if there is no message decoder that may be used to decoder the incoming request
-     */
-    protected abstract void getMessageDecoder(AttributeQueryContext queryContext) throws ProfileException;
-
-    /**
-     * Gets a populated message encoder.
-     * 
-     * @param queryContext current request context
-     * 
-     * @throws ProfileException thrown if there is no message encoder that may be used to encoder the outgoing response
-     */
-    protected abstract void getMessageEncoder(AttributeQueryContext queryContext) throws ProfileException;
-
-    /**
-     * Decodes the message in the request and adds it to the request context.
-     * 
-     * @param queryContext request context contianing the request to decode
-     * 
-     * @throws ProfileException throw if there is a problem decoding the request
-     * @throws SecurityPolicyException thrown if the message was decoded properly but did not meet the necessary
-     *             security policy requirements
-     */
-    protected void decodeRequest(AttributeQueryContext queryContext) throws ProfileException, SecurityPolicyException {
-
-        try {
-            queryContext.getMessageDecoder().decode();
-            if (log.isDebugEnabled()) {
-                log.debug("decoded http servlet request");
-            }
-        } catch (BindingException e) {
-            log.error("Error decoding attribute query message", e);
-            throw new ProfileException("Error decoding attribute query message");
-        }
-    }
-
-    /**
-     * Creates an attribute request context for this attribute query and places it in the query context.
-     * 
-     * @param queryContext current query context
-     */
-    protected void buildAttributeRequestContext(AttributeQueryContext queryContext) {
-        AttributeQuery attributeQuery = (AttributeQuery) queryContext.getMessageDecoder().getSAMLMessage();
-        RelyingPartyConfiguration rpConfig = getRelyingPartyConfiguration(attributeQuery.getIssuer().getValue());
-
-        ShibbolethAttributeRequestContext requestContext = new ShibbolethAttributeRequestContext(getMetadataProvider(),
-                rpConfig, attributeQuery);
-        Session userSession = getSessionManager().getSession(getUserSessionId(queryContext.getProfileRequest()));
-        if (userSession != null) {
-            requestContext.setUserSession(userSession);
-            ServiceInformation serviceInfo = userSession.getServiceInformation(attributeQuery.getIssuer().getValue());
-            if (serviceInfo != null) {
-                requestContext.setPrincipalAuthenticationMethod(serviceInfo.getAuthenticationMethod()
-                        .getAuthenticationMethod());
-            }
-        }
-
-        requestContext.setEffectiveProfileConfiguration((AttributeQueryConfiguration) rpConfig
-                .getProfileConfiguration(AttributeQueryConfiguration.PROFILE_ID));
-
-        requestContext.setRequest(queryContext.getProfileRequest().getRawRequest());
-        queryContext.setAttributeRequestContext(requestContext);
-    }
-
-    /**
-     * Builds a response to the attribute query within the request context.
-     * 
-     * @param queryContext current request context
-     * 
-     * @throws ProfileException thrown if there is a problem creating the SAML response
-     * @throws AttributeRequestException thrown if there is a problem resolving attributes
-     */
-    protected void buildResponse(AttributeQueryContext queryContext) throws ProfileException, AttributeRequestException {
-        AttributeQueryConfiguration profileConfiguration = (AttributeQueryConfiguration) queryContext
-                .getAttributeRequestContext().getEffectiveProfileConfiguration();
-        DateTime issueInstant = new DateTime();
-
-        // create the attribute statement
-        AttributeStatement attributeStatement = buildAttributeStatement(queryContext);
-
-        // create the assertion and add the attribute statement
-        Assertion assertion = buildAssertion(issueInstant, queryContext.getAttributeRequestContext()
-                .getRelyingPartyConfiguration(), profileConfiguration);
-        assertion.getAttributeStatements().add(attributeStatement);
-
-        // create the SAML response and add the assertion
-        Response samlResponse = getResponseBuilder().buildObject();
-        populateStatusResponse(samlResponse, issueInstant, (RequestAbstractType) queryContext
-                .getAttributeRequestContext().getAttributeQuery(), queryContext.getAttributeRequestContext()
-                .getRelyingPartyConfiguration());
-
-        // TODO handle subject
-        samlResponse.getAssertions().add(assertion);
-
-        // sign the assertion if it should be signed
-        signAssertion(assertion, queryContext.getAttributeRequestContext().getRelyingPartyConfiguration(),
-                profileConfiguration);
-
-        Status status = buildStatus(StatusCode.SUCCESS_URI, null, null);
-        samlResponse.setStatus(status);
-
-        queryContext.setAttributeQueryResponse(samlResponse);
-    }
-
-    /**
-     * Executes a query for attributes and builds a SAML attribute statement from the results.
-     * 
-     * @param queryContext current request context
-     * 
-     * @return attribute statement resulting from the query
-     * 
-     * @throws ProfileException thrown if there is a problem making the query
-     * @throws AttributeRequestException thrown if there is a problem resolving attributes
-     */
-    protected AttributeStatement buildAttributeStatement(AttributeQueryContext queryContext) throws ProfileException,
-            AttributeRequestException {
-
-        try {
-            AttributeQueryConfiguration profileConfiguration = (AttributeQueryConfiguration) queryContext
-                    .getAttributeRequestContext().getEffectiveProfileConfiguration();
-            if (profileConfiguration == null) {
-                log.error("No SAML 2 attribute query profile configuration is defined for relying party: "
-                        + queryContext.getAttributeRequestContext().getRelyingPartyConfiguration().getRelyingPartyId());
-                throw new AttributeRequestException(
-                        "SAML 2 attribute query is not configured for this relying party");
-            }
-
-            SAML2AttributeAuthority attributeAuthority = profileConfiguration.getAttributeAuthority();
-            return attributeAuthority.performAttributeQuery(queryContext.getAttributeRequestContext());
-        } catch (AttributeRequestException e) {
-            log.error("Error resolving attributes", e);
-            throw e;
-        }
-    }
-
-    /**
-     * Constructs an SAML response message carrying a request error.
-     * 
-     * @param queryContext current request context
-     * @param error the encountered error
-     */
-    protected void buildErrorResponse(AttributeQueryContext queryContext, Exception error) {
-        AttributeQuery attributeQuery = (AttributeQuery) queryContext.getAttributeRequestContext().getAttributeQuery();
-        RelyingPartyConfiguration rpConfig = queryContext.getAttributeRequestContext().getRelyingPartyConfiguration();
-
-        DateTime issueInstant = new DateTime();
-        Response samlResponse = getResponseBuilder().buildObject();
-        populateStatusResponse(samlResponse, issueInstant, attributeQuery, rpConfig);
-
-        Status status = buildStatus(StatusCode.REQUESTER_URI, StatusCode.REQUEST_DENIED_URI, error
-                .getLocalizedMessage());
-
-        samlResponse.setStatus(status);
-
-        queryContext.setAttributeQueryResponse(samlResponse);
-    }
-
-    /**
-     * Writes an aduit log entry indicating the successful response to the attribute request.
-     * 
-     * @param queryContext current request context
-     */
-    protected void writeAuditLogEntry(AttributeQueryContext queryContext) {
-        AuditLogEntry auditLogEntry = new AuditLogEntry();
-        auditLogEntry.setMessageProfile(getProfileId());
-        auditLogEntry.setPrincipalAuthenticationMethod(queryContext.getAttributeRequestContext()
-                .getPrincipalAuthenticationMethod());
-        auditLogEntry.setPrincipalId(queryContext.getAttributeRequestContext().getPrincipalName());
-        auditLogEntry.setProviderId(queryContext.getAttributeRequestContext().getRelyingPartyConfiguration()
-                .getProviderId());
-        auditLogEntry.setRelyingPartyId(queryContext.getAttributeRequestContext().getAttributeRequester());
-        auditLogEntry.setRequestBinding(queryContext.getMessageDecoder().getBindingURI());
-        auditLogEntry.setRequestId(((AttributeQuery) queryContext.getAttributeRequestContext().getAttributeQuery())
-                .getID());
-        auditLogEntry.setResponseBinding(queryContext.getMessageEncoder().getBindingURI());
-        auditLogEntry.setResponseId(queryContext.getAttributeQueryResponse().getID());
-        getAduitLog().log(Level.CRITICAL, auditLogEntry);
-    }
-
-    /** Basic data structure used to accumulate information as a request is being processed. */
-    protected class AttributeQueryContext {
-
-        /** Curent profile request. */
-        private ProfileRequest<ServletRequest> profileRequest;
-
-        /** Current profile response. */
-        private ProfileResponse<ServletResponse> profileResponse;
-
-        /** Decoder used to decode the incoming request. */
-        private MessageDecoder<ServletRequest> messageDecoder;
-
-        /** Attribute request context for this attribute query. */
-        private ShibbolethAttributeRequestContext attributeRequestContext;
-
-        /** Encoder used to encode the outgoing response. */
-        private MessageEncoder<ServletResponse> messageEncoder;
-
-        /** Attribute query response to the relying party. */
-        private Response attributeQueryResponse;
-
-        /**
-         * Constructor.
-         * 
-         * @param request current profile request
-         * @param response current profile response
-         */
-        public AttributeQueryContext(ProfileRequest<ServletRequest> request, ProfileResponse<ServletResponse> response) {
-            profileRequest = request;
-            profileResponse = response;
-        }
-
-        /**
-         * Gets the attribute request context for this query.
-         * 
-         * @return attribute request context for this query
-         */
-        public ShibbolethAttributeRequestContext getAttributeRequestContext() {
-            return attributeRequestContext;
-        }
-
-        /**
-         * Sets the attribute request context for this query.
-         * 
-         * @param context attribute request context for this query
-         */
-        public void setAttributeRequestContext(ShibbolethAttributeRequestContext context) {
-            attributeRequestContext = context;
-        }
-
-        /**
-         * Gets the attribute query response.
-         * 
-         * @return attribute query response
-         */
-        public Response getAttributeQueryResponse() {
-            return attributeQueryResponse;
-        }
-
-        /**
-         * Sets the attribute query response.
-         * 
-         * @param response attribute query response
-         */
-        public void setAttributeQueryResponse(Response response) {
-            attributeQueryResponse = response;
-        }
-
-        /**
-         * Gets the decoder used to decode the request.
-         * 
-         * @return decoder used to decode the request
-         */
-        public MessageDecoder<ServletRequest> getMessageDecoder() {
-            return messageDecoder;
-        }
-
-        /**
-         * Sets the decoder used to decode the request.
-         * 
-         * @param decoder decoder used to decode the request
-         */
-        public void setMessageDecoder(MessageDecoder<ServletRequest> decoder) {
-            messageDecoder = decoder;
-        }
-
-        /**
-         * Gets the encoder used to encoder the response.
-         * 
-         * @return encoder used to encoder the response
-         */
-        public MessageEncoder<ServletResponse> getMessageEncoder() {
-            return messageEncoder;
-        }
-
-        /**
-         * Sets the encoder used to encoder the response.
-         * 
-         * @param encoder encoder used to encoder the response
-         */
-        public void setMessageEncoder(MessageEncoder<ServletResponse> encoder) {
-            messageEncoder = encoder;
-        }
-
-        /**
-         * Gets the current profile request.
-         * 
-         * @return current profile request
-         */
-        public ProfileRequest<ServletRequest> getProfileRequest() {
-            return profileRequest;
-        }
-
-        /**
-         * Gets the current profile response.
-         * 
-         * @return current profile response
-         */
-        public ProfileResponse<ServletResponse> getProfileResponse() {
-            return profileResponse;
-        }
-    }
-}
\ No newline at end of file
index 846b85b..a30e1fd 100644 (file)
 
 package edu.internet2.middleware.shibboleth.idp.profile.saml2;
 
+import java.util.ArrayList;
 import java.util.Collection;
+import java.util.List;
 
-import org.joda.time.DateTime;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
 
+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.Audience;
 import org.opensaml.saml2.core.AudienceRestriction;
+import org.opensaml.saml2.core.AuthnRequest;
 import org.opensaml.saml2.core.Conditions;
 import org.opensaml.saml2.core.Issuer;
+import org.opensaml.saml2.core.NameID;
 import org.opensaml.saml2.core.ProxyRestriction;
 import org.opensaml.saml2.core.RequestAbstractType;
 import org.opensaml.saml2.core.Response;
+import org.opensaml.saml2.core.Statement;
 import org.opensaml.saml2.core.Status;
 import org.opensaml.saml2.core.StatusCode;
 import org.opensaml.saml2.core.StatusMessage;
 import org.opensaml.saml2.core.StatusResponseType;
 import org.opensaml.saml2.core.Subject;
-
+import org.opensaml.saml2.core.SubjectConfirmation;
+import org.opensaml.saml2.metadata.AttributeAuthorityDescriptor;
+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.SSODescriptor;
+import org.opensaml.saml2.metadata.provider.MetadataProviderException;
 import org.opensaml.xml.XMLObjectBuilder;
-import org.opensaml.xml.encryption.EncryptionException;
 import org.opensaml.xml.security.credential.Credential;
 import org.opensaml.xml.signature.Signature;
 import org.opensaml.xml.signature.Signer;
 import org.opensaml.xml.util.DatatypeHelper;
 
-import edu.internet2.middleware.shibboleth.common.relyingparty.RelyingPartyConfiguration;
+import edu.internet2.middleware.shibboleth.common.attribute.AttributeRequestException;
+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;
 
@@ -82,6 +100,9 @@ public abstract class AbstractSAML2ProfileHandler extends AbstractSAMLProfileHan
     /** For building subject. */
     private SAMLObjectBuilder<Subject> subjectBuilder;
 
+    /** For builder subject confirmation. */
+    private SAMLObjectBuilder<SubjectConfirmation> subjectConfirmationBuilder;
+
     /** For building conditions. */
     private SAMLObjectBuilder<Conditions> conditionsBuilder;
 
@@ -103,7 +124,6 @@ public abstract class AbstractSAML2ProfileHandler extends AbstractSAMLProfileHan
     /** Constructor. */
     @SuppressWarnings("unchecked")
     protected AbstractSAML2ProfileHandler() {
-
         super();
 
         responseBuilder = (SAMLObjectBuilder<Response>) getBuilderFactory().getBuilder(Response.DEFAULT_ELEMENT_NAME);
@@ -116,6 +136,8 @@ public abstract class AbstractSAML2ProfileHandler extends AbstractSAMLProfileHan
         assertionBuilder = (SAMLObjectBuilder<Assertion>) getBuilderFactory()
                 .getBuilder(Assertion.DEFAULT_ELEMENT_NAME);
         subjectBuilder = (SAMLObjectBuilder<Subject>) getBuilderFactory().getBuilder(Subject.DEFAULT_ELEMENT_NAME);
+        subjectConfirmationBuilder = (SAMLObjectBuilder<SubjectConfirmation>) getBuilderFactory().getBuilder(
+                SubjectConfirmation.DEFAULT_ELEMENT_NAME);
         conditionsBuilder = (SAMLObjectBuilder<Conditions>) getBuilderFactory().getBuilder(
                 Conditions.DEFAULT_ELEMENT_NAME);
         audienceRestrictionBuilder = (SAMLObjectBuilder<AudienceRestriction>) getBuilderFactory().getBuilder(
@@ -245,122 +267,103 @@ public abstract class AbstractSAML2ProfileHandler extends AbstractSAMLProfileHan
     }
 
     /**
-     * Populates the response's id, in response to, issue instant, version, and issuer properties.
+     * Convenience method for getting the SAML 2 subject confirmation builder.
      * 
-     * @param response the response to populate
-     * @param issueInstant timestamp to use as the issue instant for the response
-     * @param request the request that the response is for
-     * @param rpConfig the relying party configuration for the request
+     * @return SAML 2 subject confirmation builder
      */
-    protected void populateStatusResponse(StatusResponseType response, DateTime issueInstant,
-            RequestAbstractType request, RelyingPartyConfiguration rpConfig) {
-
-        response.setID(getIdGenerator().generateIdentifier());
-        response.setInResponseTo(request.getID());
-        response.setIssueInstant(issueInstant);
-        response.setVersion(SAMLVersion.VERSION_20);
-        response.setIssuer(buildEntityIssuer(rpConfig));
+    public SAMLObjectBuilder<SubjectConfirmation> getSubjectConfirmationBuilder() {
+        return subjectConfirmationBuilder;
     }
 
     /**
-     * Build a status message, with an optional second-level failure message.
+     * Builds a response to the attribute query within the 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.
+     * @param requestContext current request context
+     * @param assertionSubject subject of the assertion within the response
+     * @param statements the statements to include in the response
      * 
-     * @return a Status object.
+     * @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 Status buildStatus(String topLevelCode, String secondLevelCode, String secondLevelFailureMessage) {
+    protected Response buildResponse(SAML2ProfileRequestContext requestContext, Subject assertionSubject,
+            List<Statement> statements) throws ProfileException, AttributeRequestException {
 
-        Status status = statusBuilder.buildObject();
-        
-        StatusCode statusCode = statusCodeBuilder.buildObject();
-        statusCode.setValue(DatatypeHelper.safeTrimOrNullString(topLevelCode));
-        status.setStatusCode(statusCode);
-        
-        if (secondLevelCode != null) {
-            StatusCode secondLevelStatusCode = statusCodeBuilder.buildObject();
-            secondLevelStatusCode.setValue(DatatypeHelper.safeTrimOrNullString(secondLevelCode));
-            statusCode.setStatusCode(secondLevelStatusCode);
-        }
+        DateTime issueInstant = new DateTime();
 
-        if (secondLevelFailureMessage != null) {
-            StatusMessage msg = statusMessageBuilder.buildObject();
-            msg.setMessage(secondLevelFailureMessage);
-            status.setStatusMessage(msg);
+        // create the assertion and add the attribute statement
+        Assertion assertion = buildAssertion(requestContext, issueInstant);
+        assertion.setSubject(assertionSubject);
+        if (statements != null) {
+            assertion.getStatements().addAll(statements);
         }
 
-        return status;
+        // create the SAML response and add the assertion
+        Response samlResponse = getResponseBuilder().buildObject();
+        populateStatusResponse(requestContext, samlResponse);
+
+        samlResponse.getAssertions().add(assertion);
+
+        // sign the assertion if it should be signed
+        signAssertion(requestContext, assertion);
+
+        Status status = buildStatus(StatusCode.SUCCESS_URI, null, null);
+        samlResponse.setStatus(status);
+
+        return samlResponse;
     }
 
     /**
      * Builds a basic assertion with its id, issue instant, SAML version, issuer, subject, and conditions populated.
      * 
+     * @param requestContext current request context
      * @param issueInstant time to use as assertion issue instant
-     * @param rpConfig the relying party configuration
-     * @param profileConfig current profile configuration
      * 
      * @return the built assertion
      */
-    protected Assertion buildAssertion(final DateTime issueInstant, final RelyingPartyConfiguration rpConfig,
-            final AbstractSAML2ProfileConfiguration profileConfig) {
+    protected Assertion buildAssertion(SAML2ProfileRequestContext requestContext, DateTime issueInstant) {
 
-        Assertion assertion = assertionBuilder.buildObject();
+        Assertion assertion = getAssertionBuilder().buildObject();
         assertion.setID(getIdGenerator().generateIdentifier());
         assertion.setIssueInstant(issueInstant);
         assertion.setVersion(SAMLVersion.VERSION_20);
-        assertion.setIssuer(buildEntityIssuer(rpConfig));
-        // TODO assertion.setSubject(buildSubject());
+        assertion.setIssuer(buildEntityIssuer(requestContext));
 
-        Conditions conditions = buildConditions(issueInstant, profileConfig);
+        Conditions conditions = buildConditions(requestContext, issueInstant);
         assertion.setConditions(conditions);
 
         return assertion;
     }
 
     /**
-     * Builds an entity type Issuer populated with the correct provider Id for this relying party configuration.
+     * Creates an {@link Issuer} populated with information about the relying party.
      * 
-     * @param rpConfig the relying party configuration
+     * @param requestContext current request context
      * 
-     * @return the built Issuer
+     * @return the built issuer
      */
-    protected Issuer buildEntityIssuer(final RelyingPartyConfiguration rpConfig) {
-
+    protected Issuer buildEntityIssuer(SAML2ProfileRequestContext requestContext) {
         Issuer issuer = getIssuerBuilder().buildObject();
         issuer.setFormat(Issuer.ENTITY);
-        issuer.setValue(rpConfig.getProviderId());
+        issuer.setValue(requestContext.getRelyingPartyId());
 
         return issuer;
     }
 
     /**
-     * Builds the SAML subject for the user for the service provider.
-     * 
-     * @return SAML subject for the user for the service provider
-     * 
-     * @throws EncryptionException thrown if there is a problem encryption the subject's NameID
-     */
-    protected Subject buildSubject() throws EncryptionException {
-        // TODO
-        return null;
-    }
-
-    /**
      * Builds a SAML assertion condition set. The following fields are set; not before, not on or after, audience
      * restrictions, and proxy restrictions.
      * 
+     * @param requestContext current request context
      * @param issueInstant timestamp the assertion was created
-     * @param profileConfig current profile configuration
      * 
      * @return constructed conditions
      */
-    protected Conditions buildConditions(final DateTime issueInstant,
-            final AbstractSAML2ProfileConfiguration profileConfig) {
+    protected Conditions buildConditions(SAML2ProfileRequestContext requestContext, DateTime issueInstant) {
+        AbstractSAML2ProfileConfiguration profileConfig = requestContext.getProfileConfiguration();
 
-        Conditions conditions = conditionsBuilder.buildObject();
+        Conditions conditions = getConditionsBuilder().buildObject();
         conditions.setNotBefore(issueInstant);
         conditions.setNotOnOrAfter(issueInstant.plus(profileConfig.getAssertionLifetime()));
 
@@ -369,9 +372,9 @@ public abstract class AbstractSAML2ProfileHandler extends AbstractSAMLProfileHan
         // add audience restrictions
         audiences = profileConfig.getAssertionAudiences();
         if (audiences != null && audiences.size() > 0) {
-            AudienceRestriction audienceRestriction = audienceRestrictionBuilder.buildObject();
+            AudienceRestriction audienceRestriction = getAudienceRestrictionBuilder().buildObject();
             for (String audienceUri : audiences) {
-                Audience audience = audienceBuilder.buildObject();
+                Audience audience = getAudienceBuilder().buildObject();
                 audience.setAudienceURI(audienceUri);
                 audienceRestriction.getAudiences().add(audience);
             }
@@ -381,10 +384,10 @@ public abstract class AbstractSAML2ProfileHandler extends AbstractSAMLProfileHan
         // add proxy restrictions
         audiences = profileConfig.getProxyAudiences();
         if (audiences != null && audiences.size() > 0) {
-            ProxyRestriction proxyRestriction = proxyRestrictionBuilder.buildObject();
+            ProxyRestriction proxyRestriction = getProxyRestrictionBuilder().buildObject();
             Audience audience;
             for (String audienceUri : audiences) {
-                audience = audienceBuilder.buildObject();
+                audience = getAudienceBuilder().buildObject();
                 audience.setAudienceURI(audienceUri);
                 proxyRestriction.getAudiences().add(audience);
             }
@@ -397,22 +400,36 @@ public abstract class AbstractSAML2ProfileHandler extends AbstractSAMLProfileHan
     }
 
     /**
+     * Populates the response's id, in response to, issue instant, version, and issuer properties.
+     * 
+     * @param requestContext current request context
+     * @param response the response to populate
+     */
+    protected void populateStatusResponse(SAML2ProfileRequestContext requestContext, StatusResponseType response) {
+        response.setID(getIdGenerator().generateIdentifier());
+        response.setInResponseTo(requestContext.getSamlRequest().getID());
+        response.setIssueInstant(response.getIssueInstant());
+        response.setVersion(SAMLVersion.VERSION_20);
+        response.setIssuer(buildEntityIssuer(requestContext));
+    }
+
+    /**
      * 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
-     * @param rpConfig relying party configuration
-     * @param profileConfig current profile configuration
      */
-    protected void signAssertion(Assertion assertion, RelyingPartyConfiguration rpConfig,
-            AbstractSAML2ProfileConfiguration profileConfig) {
+    protected void signAssertion(SAML2ProfileRequestContext requestContext, Assertion assertion) {
+        AbstractSAML2ProfileConfiguration profileConfig = requestContext.getProfileConfiguration();
+
         if (!profileConfig.getSignAssertions()) {
             return;
         }
 
         Credential signatureCredential = profileConfig.getSigningCredential();
         if (signatureCredential == null) {
-            signatureCredential = rpConfig.getDefaultSigningCredential();
+            signatureCredential = requestContext.getRelyingPartyConfiguration().getDefaultSigningCredential();
         }
 
         if (signatureCredential == null) {
@@ -427,5 +444,287 @@ public abstract class AbstractSAML2ProfileHandler extends AbstractSAMLProfileHan
         Signer.signObject(signature);
     }
 
-    // TODO encryption support
+    /**
+     * Build a status message, with an optional second-level failure message.
+     * 
+     * @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 a Status object.
+     */
+    protected Status buildStatus(String topLevelCode, String secondLevelCode, String secondLevelFailureMessage) {
+
+        Status status = getStatusBuilder().buildObject();
+
+        StatusCode statusCode = getStatusCodeBuilder().buildObject();
+        statusCode.setValue(DatatypeHelper.safeTrimOrNullString(topLevelCode));
+        status.setStatusCode(statusCode);
+
+        if (secondLevelCode != null) {
+            StatusCode secondLevelStatusCode = getStatusCodeBuilder().buildObject();
+            secondLevelStatusCode.setValue(DatatypeHelper.safeTrimOrNullString(secondLevelCode));
+            statusCode.setStatusCode(secondLevelStatusCode);
+        }
+
+        if (secondLevelFailureMessage != null) {
+            StatusMessage msg = getStatusMessageBuilder().buildObject();
+            msg.setMessage(secondLevelFailureMessage);
+            status.setStatusMessage(msg);
+        }
+
+        return status;
+    }
+
+    /**
+     * Builds the SAML subject for the user for the service provider.
+     * 
+     * @param requestContext current request context
+     * @param confirmationMethod subject confirmation method used for the subject
+     * 
+     * @return SAML subject for the user for the service provider
+     */
+    protected Subject buildSubject(SAML2ProfileRequestContext requestContext, String confirmationMethod) {
+        NameID nameID = requestContext.getSubjectNameID();
+        // TODO handle encryption
+
+        SubjectConfirmation subjectConfirmation = getSubjectConfirmationBuilder().buildObject();
+        subjectConfirmation.setMethod(confirmationMethod);
+
+        Subject subject = getSubjectBuilder().buildObject();
+        subject.setNameID(nameID);
+        subject.getSubjectConfirmations().add(subjectConfirmation);
+
+        return subject;
+    }
+
+    /**
+     * Constructs an SAML response message carrying a request error.
+     * 
+     * @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
+     */
+    protected Response buildErrorResponse(SAML2ProfileRequestContext requestContext, String topLevelCode,
+            String secondLevelCode, String secondLevelFailureMessage) {
+        Response samlResponse = getResponseBuilder().buildObject();
+        samlResponse.setIssueInstant(new DateTime());
+        populateStatusResponse(requestContext, samlResponse);
+
+        Status status = buildStatus(topLevelCode, secondLevelCode, secondLevelFailureMessage);
+        samlResponse.setStatus(status);
+
+        return samlResponse;
+    }
+
+    /**
+     * Gets the NameID format to use when creating NameIDs for the relying party.
+     * 
+     * @param requestContext current request context
+     * 
+     * @return list of nameID formats that may be used with the relying party
+     * 
+     * @throws ProfileException thrown if there is a problem determing the NameID format to use
+     */
+    protected List<String> getNameIDFormat(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");
+                    }
+                }
+            }
+
+            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;
+
+        } catch (MetadataProviderException e) {
+            throw new ProfileException("Unable to determine lookup entity metadata", e);
+        }
+    }
+
+    /**
+     * Gets the list of NameID formats supported for a given role.
+     * 
+     * @param role the role to get the list of supported NameID formats
+     * 
+     * @return list of supported NameID formats
+     */
+    protected List<String> getEntitySupportedFormats(RoleDescriptor role) {
+        List<NameIDFormat> nameIDFormats = null;
+
+        if (role instanceof SSODescriptor) {
+            nameIDFormats = ((SSODescriptor) role).getNameIDFormats();
+        } else if (role instanceof AuthnAuthorityDescriptor) {
+            nameIDFormats = ((AuthnAuthorityDescriptor) role).getNameIDFormats();
+        } else if (role instanceof PDPDescriptor) {
+            nameIDFormats = ((PDPDescriptor) role).getNameIDFormats();
+        } else if (role instanceof AttributeAuthorityDescriptor) {
+            nameIDFormats = ((AttributeAuthorityDescriptor) role).getNameIDFormats();
+        }
+
+        ArrayList<String> supportedFormats = new ArrayList<String>();
+        if (nameIDFormats != null) {
+            for (NameIDFormat format : nameIDFormats) {
+                supportedFormats.add(format.getFormat());
+            }
+        }
+
+        return supportedFormats;
+    }
+
+    /**
+     * Writes an aduit log entry indicating the successful response to the attribute request.
+     * 
+     * @param context current request context
+     */
+    protected void writeAuditLogEntry(SAML2ProfileRequestContext context) {
+        AuditLogEntry auditLogEntry = new AuditLogEntry();
+        auditLogEntry.setMessageProfile(getProfileId());
+        auditLogEntry.setPrincipalAuthenticationMethod(context.getPrincipalAuthenticationMethod());
+        auditLogEntry.setPrincipalName(context.getPrincipalName());
+        auditLogEntry.setAssertingPartyId(context.getAssertingPartyId());
+        auditLogEntry.setRelyingPartyId(context.getRelyingPartyId());
+        auditLogEntry.setRequestBinding(context.getMessageDecoder().getBindingURI());
+        auditLogEntry.setRequestId(context.getSamlRequest().getID());
+        auditLogEntry.setResponseBinding(context.getMessageEncoder().getBindingURI());
+        auditLogEntry.setResponseId(context.getSamlResponse().getID());
+        getAduitLog().log(Level.CRITICAL, auditLogEntry);
+    }
+
+    /**
+     * Contextual object used to accumlate information as profile requests are being processed.
+     * 
+     * @param <RequestType> type of SAML 2 request
+     * @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>
+            extends SAMLProfileRequestContext {
+
+        /** SAML request message. */
+        private RequestType samlRequest;
+
+        /** SAML response message. */
+        private ResponseType samlResponse;
+
+        /** Request profile configuration. */
+        private ProfileConfigurationType profileConfiguration;
+
+        /** The NameID of the subject of this request. */
+        private NameID subjectNameID;
+
+        /**
+         * Constructor.
+         * 
+         * @param request current profile request
+         * @param response current profile response
+         */
+        public SAML2ProfileRequestContext(ProfileRequest<ServletRequest> request,
+                ProfileResponse<ServletResponse> response) {
+            super(request, response);
+        }
+
+        /**
+         * Gets the NameID of the subject of this request.
+         * 
+         * @return NameID of the subject of this request
+         */
+        public NameID getSubjectNameID() {
+            return subjectNameID;
+        }
+
+        /**
+         * Sets the NameID of the subject of this request.
+         * 
+         * @param nameID NameID of the subject of this request
+         */
+        public void setSubjectNameID(NameID nameID) {
+            subjectNameID = nameID;
+        }
+
+        /**
+         * Gets the profile configuration for this request.
+         * 
+         * @return profile configuration for this request
+         */
+        public ProfileConfigurationType getProfileConfiguration() {
+            return profileConfiguration;
+        }
+
+        /**
+         * Sets the profile configuration for this request.
+         * 
+         * @param configuration profile configuration for this request
+         */
+        public void setProfileConfiguration(ProfileConfigurationType configuration) {
+            profileConfiguration = configuration;
+        }
+
+        /**
+         * Gets the SAML request message.
+         * 
+         * @return SAML request message
+         */
+        public RequestType getSamlRequest() {
+            return samlRequest;
+        }
+
+        /**
+         * Sets the SAML request message.
+         * 
+         * @param request SAML request message
+         */
+        public void setSamlRequest(RequestType request) {
+            samlRequest = request;
+        }
+
+        /**
+         * Gets the SAML response message.
+         * 
+         * @return SAML response message
+         */
+        public ResponseType getSamlResponse() {
+            return samlResponse;
+        }
+
+        /**
+         * Sets the SAML response message.
+         * 
+         * @param response SAML response message
+         */
+        public void setSamlResponse(ResponseType response) {
+            samlResponse = response;
+        }
+    }
 }
\ No newline at end of file
diff --git a/src/edu/internet2/middleware/shibboleth/idp/profile/saml2/AttributeQueryProfileHandler.java b/src/edu/internet2/middleware/shibboleth/idp/profile/saml2/AttributeQueryProfileHandler.java
new file mode 100644 (file)
index 0000000..31bcebc
--- /dev/null
@@ -0,0 +1,245 @@
+/*
+ * Copyright [2006] [University Corporation for Advanced Internet Development, Inc.]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package edu.internet2.middleware.shibboleth.idp.profile.saml2;
+
+import java.util.ArrayList;
+import java.util.Map;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+import org.apache.log4j.Logger;
+import org.opensaml.common.binding.BindingException;
+import org.opensaml.common.binding.decoding.MessageDecoder;
+import org.opensaml.common.binding.encoding.MessageEncoder;
+import org.opensaml.common.binding.security.SAMLSecurityPolicy;
+import org.opensaml.saml2.core.AttributeQuery;
+import org.opensaml.saml2.core.AttributeStatement;
+import org.opensaml.saml2.core.NameID;
+import org.opensaml.saml2.core.Response;
+import org.opensaml.saml2.core.Statement;
+import org.opensaml.saml2.core.StatusCode;
+import org.opensaml.saml2.core.Subject;
+import org.opensaml.saml2.metadata.AttributeAuthorityDescriptor;
+import org.opensaml.saml2.metadata.SPSSODescriptor;
+import org.opensaml.ws.security.SecurityPolicyException;
+
+import edu.internet2.middleware.shibboleth.common.attribute.AttributeRequestException;
+import edu.internet2.middleware.shibboleth.common.attribute.BaseAttribute;
+import edu.internet2.middleware.shibboleth.common.attribute.provider.SAML2AttributeAuthority;
+import edu.internet2.middleware.shibboleth.common.attribute.provider.ShibbolethSAMLAttributeRequestContext;
+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.AttributeQueryConfiguration;
+import edu.internet2.middleware.shibboleth.idp.session.ServiceInformation;
+import edu.internet2.middleware.shibboleth.idp.session.Session;
+
+/**
+ * SAML 2.0 Attribute Query profile handler.
+ */
+public class AttributeQueryProfileHandler extends AbstractSAML2ProfileHandler {
+
+    /** Class logger. */
+    private static Logger log = Logger.getLogger(AttributeQueryProfileHandler.class);
+
+    /** SAML binding URI. */
+    private static final String BINDING = "urn:oasis:names:tc:SAML:2.0:bindings:SOAP";
+
+    /** {@inheritDoc} */
+    public String getProfileId() {
+        return "urn:mace:shibboleth:2.0:idp:profiles:saml2:query:attribute";
+    }
+
+    /** {@inheritDoc} */
+    public void processRequest(ProfileRequest<ServletRequest> request, ProfileResponse<ServletResponse> response)
+            throws ProfileException {
+
+        AttributeQueryContext requestContext = new AttributeQueryContext(request, response);
+
+        Response samlResponse;
+        try {
+            decodeRequest(requestContext);
+
+            // populate request context with information from decoded message
+            SAMLSecurityPolicy securityPolicy = requestContext.getMessageDecoder().getSecurityPolicy();
+            requestContext.setRelyingPartyId(securityPolicy.getIssuer());
+            requestContext
+                    .setRelyingPartyConfiguration(getRelyingPartyConfiguration(requestContext.getRelyingPartyId()));
+            requestContext.setRelyingPartyRole(SPSSODescriptor.DEFAULT_ELEMENT_NAME);
+            requestContext.setAssertingPartyId(requestContext.getRelyingPartyConfiguration().getProviderId());
+            requestContext.setAssertingPartyRole(AttributeAuthorityDescriptor.DEFAULT_ELEMENT_NAME);
+            requestContext.setProfileConfiguration((AttributeQueryConfiguration) getProfileConfiguration(requestContext
+                    .getRelyingPartyId(), AttributeQueryConfiguration.PROFILE_ID));
+            requestContext.setSamlRequest((AttributeQuery) requestContext.getMessageDecoder().getSAMLMessage());
+
+            // TODO principal
+
+            // create the SAML attribute statement
+            ArrayList<Statement> statements = new ArrayList<Statement>();
+            statements.add(buildAttributeStatement(requestContext));
+            
+            //TODO NameID
+            Subject assertionSubject = buildSubject(requestContext, "urn:oasis:names:tc:SAML:2.0:cm:sender-vouches");
+
+            // create the SAML response
+            samlResponse = buildResponse(requestContext, assertionSubject, statements);
+        } catch (SecurityPolicyException e) {
+            samlResponse = buildErrorResponse(requestContext, StatusCode.REQUESTER_URI, StatusCode.REQUEST_DENIED_URI,
+                    e.getMessage());
+        } catch (AttributeRequestException e) {
+            samlResponse = buildErrorResponse(requestContext, StatusCode.RESPONDER_URI,
+                    StatusCode.INVALID_ATTR_NAME_VALUE_URI, e.getMessage());
+        }
+
+        requestContext.setSamlResponse(samlResponse);
+
+        encodeResponse(requestContext);
+        writeAuditLogEntry(requestContext);
+    }
+
+    /**
+     * Decodes the message in the request and adds it to the request context.
+     * 
+     * @param requestContext request context contianing the request to decode
+     * 
+     * @throws ProfileException throw if there is a problem decoding the request
+     * @throws SecurityPolicyException thrown if the message was decoded properly but did not meet the necessary
+     *             security policy requirements
+     */
+    protected void decodeRequest(AttributeQueryContext requestContext) throws ProfileException, SecurityPolicyException {
+        MessageDecoder<ServletRequest> decoder = getMessageDecoderFactory().getMessageDecoder(BINDING);
+        if (decoder == null) {
+            throw new ProfileException("No request decoder was registered for binding type: " + BINDING);
+        }
+
+        super.populateMessageDecoder(decoder);
+        decoder.setRequest(requestContext.getProfileRequest().getRawRequest());
+        requestContext.setMessageDecoder(decoder);
+
+        try {
+            decoder.decode();
+            if (log.isDebugEnabled()) {
+                log.debug("decoded http servlet request");
+            }
+        } catch (BindingException e) {
+            log.error("Error decoding attribute query message", e);
+            throw new ProfileException("Error decoding attribute query message");
+        }
+    }
+
+    /**
+     * 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
+     * @throws AttributeRequestException thrown if there is a problem resolving attributes
+     */
+    protected AttributeStatement buildAttributeStatement(AttributeQueryContext requestContext) throws ProfileException,
+            AttributeRequestException {
+
+        try {
+            AttributeQueryConfiguration profileConfiguration = requestContext.getProfileConfiguration();
+            if (profileConfiguration == null) {
+                log.error("No SAML 2 attribute query profile configuration is defined for relying party: "
+                        + requestContext.getRelyingPartyId());
+                throw new AttributeRequestException("SAML 2 attribute query is not configured for this relying party");
+            }
+
+            SAML2AttributeAuthority attributeAuthority = profileConfiguration.getAttributeAuthority();
+
+            Map<String, BaseAttribute> principalAttributes = attributeAuthority
+                    .getAttributes(buildAttributeRequestContext(requestContext));
+
+            requestContext.setPrincipalAttributes(principalAttributes);
+
+            return attributeAuthority.buildAttributeStatement(requestContext.getSamlRequest(), principalAttributes
+                    .values());
+        } catch (AttributeRequestException e) {
+            log.error("Error resolving attributes", e);
+            throw 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(
+            AttributeQueryContext requestContext) {
+
+        ShibbolethSAMLAttributeRequestContext<NameID, AttributeQuery> queryContext = new ShibbolethSAMLAttributeRequestContext<NameID, AttributeQuery>(
+                getMetadataProvider(), requestContext.getRelyingPartyConfiguration(), requestContext.getSamlRequest());
+
+        Session userSession = getSessionManager().getSession(getUserSessionId(requestContext.getProfileRequest()));
+        if (userSession != null) {
+            queryContext.setUserSession(userSession);
+            ServiceInformation serviceInfo = userSession.getServiceInformation(requestContext.getRelyingPartyId());
+            if (serviceInfo != null) {
+                String principalAuthenticationMethod = serviceInfo.getAuthenticationMethod().getAuthenticationMethod();
+
+                requestContext.setPrincipalAuthenticationMethod(principalAuthenticationMethod);
+                queryContext.setPrincipalAuthenticationMethod(principalAuthenticationMethod);
+            }
+        }
+
+        queryContext.setProfileConfiguration(requestContext.getProfileConfiguration());
+        queryContext.setRequest(requestContext.getProfileRequest());
+
+        return queryContext;
+    }
+
+    /**
+     * Encodes the request's SAML response and writes it to the servlet response.
+     * 
+     * @param requestContext current request context
+     * 
+     * @throws ProfileException thrown if no message encoder is registered for this profiles binding
+     */
+    protected void encodeResponse(AttributeQueryContext requestContext) throws ProfileException {
+        MessageEncoder<ServletResponse> encoder = getMessageEncoderFactory().getMessageEncoder(BINDING);
+        if (encoder == null) {
+            throw new ProfileException("No response encoder was registered for binding type: " + BINDING);
+        }
+
+        super.populateMessageEncoder(encoder);
+        encoder.setResponse(requestContext.getProfileResponse().getRawResponse());
+        encoder.setSamlMessage(requestContext.getSamlResponse());
+        requestContext.setMessageEncoder(encoder);
+    }
+
+    /** Basic data structure used to accumulate information as a request is being processed. */
+    protected class AttributeQueryContext extends
+            SAML2ProfileRequestContext<AttributeQuery, Response, AttributeQueryConfiguration> {
+
+        /**
+         * Constructor.
+         * 
+         * @param request current profile request
+         * @param response current profile response
+         */
+        public AttributeQueryContext(ProfileRequest<ServletRequest> request, ProfileResponse<ServletResponse> response) {
+            super(request, response);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/edu/internet2/middleware/shibboleth/idp/profile/saml2/HTTPSOAPAttributeQuery.java b/src/edu/internet2/middleware/shibboleth/idp/profile/saml2/HTTPSOAPAttributeQuery.java
deleted file mode 100644 (file)
index f3ed1cb..0000000
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright [2007] [University Corporation for Advanced Internet Development, Inc.]
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package edu.internet2.middleware.shibboleth.idp.profile.saml2;
-
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-
-import org.opensaml.common.binding.decoding.MessageDecoder;
-import org.opensaml.common.binding.encoding.MessageEncoder;
-
-import edu.internet2.middleware.shibboleth.common.profile.ProfileException;
-
-/**
- * SAML 2.0 SOAP Attribute Query profile handler.
- */
-public class HTTPSOAPAttributeQuery extends AbstractAttributeQuery {
-
-    /** SAML binding URI. */
-    public static final String BINDING = "urn:oasis:names:tc:SAML:2.0:bindings:SOAP";
-
-    /** Constructor. */
-    public HTTPSOAPAttributeQuery() {
-        super();
-    }
-
-    /** {@inheritDoc} */
-    @SuppressWarnings("unchecked")
-    protected void getMessageDecoder(AttributeQueryContext requestContext) throws ProfileException {
-        MessageDecoder<ServletRequest> decoder = getMessageDecoderFactory().getMessageDecoder(BINDING);
-        if (decoder == null) {
-            throw new ProfileException("No request decoder was registered for binding type: " + BINDING);
-        }
-
-        super.populateMessageDecoder(decoder);
-        decoder.setRequest(requestContext.getProfileRequest().getRawRequest());
-        requestContext.setMessageDecoder(decoder);
-    }
-
-    /** {@inheritDoc} */
-    @SuppressWarnings("unchecked")
-    protected void getMessageEncoder(AttributeQueryContext requestContext) throws ProfileException {
-
-        MessageEncoder<ServletResponse> encoder = getMessageEncoderFactory().getMessageEncoder(BINDING);
-        if (encoder == null) {
-            throw new ProfileException("No response encoder was registered for binding type: " + BINDING);
-        }
-
-        super.populateMessageEncoder(encoder);
-        encoder.setResponse(requestContext.getProfileResponse().getRawResponse());
-        encoder.setSamlMessage(requestContext.getAttributeQueryResponse());
-        requestContext.setMessageEncoder(encoder);
-    }
-}
\ No newline at end of file
index 20ebc26..70d5125 100644 (file)
     <resolver:AttributeDefinition xsi:type="simple:Simple" id="cn">
         <resolver:DataConnectorDependency ref="static" />
     </resolver:AttributeDefinition>
+    
+    <resolver:AttributeDefinition xsi:type="simple:Simple" id="email">
+        <resolver:DataConnectorDependency ref="static" />
+    </resolver:AttributeDefinition>
                                        
     <resolver:DataConnector xsi:type="static:Static" id="static">
         <static:Attribute id="uid">
@@ -26,6 +30,9 @@
         <static:Attribute id="cn">
             <static:Value>Test User</static:Value>
         </static:Attribute>
+        <static:Attribute id="email">
+            <static:Value>t.user@example.org</static:Value>
+        </static:Attribute>
     </resolver:DataConnector>
     
     <resolver:PrincipalConnector xsi:type="pc:Direct"