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.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.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.log.AuditLogEntry;
import edu.internet2.middleware.shibboleth.common.profile.ProfileException;
import edu.internet2.middleware.shibboleth.common.profile.ProfileRequest;
/** 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;
// create the SAML response and add the assertion
Response samlResponse = getResponseBuilder().buildObject();
+ samlResponse.setIssueInstant(issueInstant);
populateStatusResponse(requestContext, samlResponse);
samlResponse.getAssertions().add(assertion);
* @return the built assertion
*/
protected Assertion buildAssertion(SAML2ProfileRequestContext requestContext, DateTime issueInstant) {
-
Assertion assertion = getAssertionBuilder().buildObject();
assertion.setID(getIdGenerator().generateIdentifier());
assertion.setIssueInstant(issueInstant);
*/
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));
}
*
* @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;
+
+ RoleDescriptor relyingPartyRole;
+ try {
+ relyingPartyRole = getMetadataProvider().getRole(requestContext.getRelyingPartyId(),
+ requestContext.getRelyingPartyRole(), SAML20_PROTOCOL_URI);
+ } catch (MetadataProviderException e) {
+ throw new ProfileException("Unable to lookup entity metadata for relying party "
+ + requestContext.getRelyingPartyId());
+ }
AbstractSAML2ProfileConfiguration profileConfig = requestContext.getProfileConfiguration();
- if (!profileConfig.getSignAssertions()) {
+ if (relyingPartyRole instanceof SPSSODescriptor) {
+ SPSSODescriptor ssoDescriptor = (SPSSODescriptor) relyingPartyRole;
+ 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);
* @return a Status object.
*/
protected Status buildStatus(String topLevelCode, String secondLevelCode, String secondLevelFailureMessage) {
-
Status status = getStatusBuilder().buildObject();
StatusCode statusCode = getStatusCodeBuilder().buildObject();
* @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();
}
/**
- * 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 attribtue 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 = getNameIDFormat(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) {
+ try {
+ AttributeEncoder<NameID> nameIdEncoder = null;
+ for (BaseAttribute attribute : principalAttributes.values()) {
+ for (String nameFormat : supportedNameFormats) {
+ nameIdEncoder = attribute.getEncoderByCategory(nameFormat);
+ if (nameIdEncoder != null) {
+ if (log.isDebugEnabled()) {
+ log.debug("Using attribute " + attribute.getId() + " suppoting NameID format "
+ + nameFormat + " to create the NameID for principal "
+ + requestContext.getPrincipalName());
+ }
+ return nameIdEncoder.encode(attribute);
+ }
+ }
+ }
+ } catch (AttributeEncodingException e) {
+ throw new ProfileException("Unable to encode NameID attribute", e);
+ }
+ }
+
+ throw new ProfileException("No principal attributes support NameID construction");
}
/**
try {
RoleDescriptor assertingPartyRole = getMetadataProvider().getRole(requestContext.getAssertingPartyId(),
- requestContext.getAssertingPartyRole(), SAMLConstants.SAML20P_NS);
+ requestContext.getAssertingPartyRole(), SAML20_PROTOCOL_URI);
List<String> assertingPartySupportedFormats = getEntitySupportedFormats(assertingPartyRole);
String nameFormat = null;
if (nameFormats.isEmpty()) {
RoleDescriptor relyingPartyRole = getMetadataProvider().getRole(requestContext.getRelyingPartyId(),
- requestContext.getRelyingPartyRole(), SAMLConstants.SAML20P_NS);
+ requestContext.getRelyingPartyRole(), SAML20_PROTOCOL_URI);
List<String> relyingPartySupportedFormats = getEntitySupportedFormats(relyingPartyRole);
assertingPartySupportedFormats.retainAll(relyingPartySupportedFormats);
}
/**
+ * 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;
+ }
+
+ /**
* Writes an aduit log entry indicating the successful response to the attribute request.
*
* @param context current request context
* @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. */
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;
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
+ // Lookup principal name and attributes, create attribute statement from information
ArrayList<Statement> statements = new ArrayList<Statement>();
statements.add(buildAttributeStatement(requestContext));
-
- //TODO NameID
+
+ // create the assertion subject
Subject assertionSubject = buildSubject(requestContext, "urn:oasis:names:tc:SAML:2.0:cm:sender-vouches");
// create the SAML response
} catch (AttributeRequestException e) {
samlResponse = buildErrorResponse(requestContext, StatusCode.RESPONDER_URI,
StatusCode.INVALID_ATTR_NAME_VALUE_URI, e.getMessage());
+ } catch (ProfileException e) {
+ samlResponse = buildErrorResponse(requestContext, StatusCode.RESPONDER_URI, StatusCode.REQUEST_DENIED_URI,
+ e.getMessage());
}
requestContext.setSamlResponse(samlResponse);
* security policy requirements
*/
protected void decodeRequest(AttributeQueryContext requestContext) throws ProfileException, SecurityPolicyException {
+ if (log.isDebugEnabled()) {
+ log.debug("Decoding incomming request");
+ }
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");
+ log.debug("Decoded request");
}
} catch (BindingException e) {
log.error("Error decoding attribute query message", e);
throw new ProfileException("Error decoding attribute query message");
+ } finally {
+ // Set as much information as can be retrieved from the decoded message
+ SAMLSecurityPolicy securityPolicy = requestContext.getMessageDecoder().getSecurityPolicy();
+ requestContext.setRelyingPartyId(securityPolicy.getIssuer());
+
+ RelyingPartyConfiguration rpConfig = getRelyingPartyConfiguration(requestContext.getRelyingPartyId());
+ requestContext.setRelyingPartyConfiguration(rpConfig);
+
+ requestContext.setRelyingPartyRole(SPSSODescriptor.DEFAULT_ELEMENT_NAME);
+
+ requestContext.setAssertingPartyId(requestContext.getRelyingPartyConfiguration().getProviderId());
+
+ requestContext.setAssertingPartyRole(AttributeAuthorityDescriptor.DEFAULT_ELEMENT_NAME);
+
+ requestContext.setProfileConfiguration((AttributeQueryConfiguration) rpConfig
+ .getProfileConfiguration(AttributeQueryConfiguration.PROFILE_ID));
+
+ requestContext.setSamlRequest((AttributeQuery) requestContext.getMessageDecoder().getSAMLMessage());
}
}
protected AttributeStatement buildAttributeStatement(AttributeQueryContext requestContext) throws ProfileException,
AttributeRequestException {
+ if (log.isDebugEnabled()) {
+ log.debug("Creating attribute statement in response to SAML request "
+ + requestContext.getSamlRequest().getID() + " from relying party "
+ + requestContext.getRelyingPartyId());
+ }
+
try {
AttributeQueryConfiguration profileConfiguration = requestContext.getProfileConfiguration();
if (profileConfiguration == null) {
SAML2AttributeAuthority attributeAuthority = profileConfiguration.getAttributeAuthority();
+ ShibbolethSAMLAttributeRequestContext<NameID, AttributeQuery> attributeRequestContext = buildAttributeRequestContext(requestContext);
+
+ if (log.isDebugEnabled()) {
+ log.debug("Resolving principal name for subject of SAML request "
+ + requestContext.getSamlRequest().getID() + " from relying party "
+ + requestContext.getRelyingPartyId());
+ }
+ String principal = attributeAuthority.getPrincipal(attributeRequestContext);
+ requestContext.setPrincipalName(principal);
+
+ if (log.isDebugEnabled()) {
+ log.debug("Resolving attributes for principal " + principal + " of SAML request "
+ + requestContext.getSamlRequest().getID() + " from relying party "
+ + requestContext.getRelyingPartyId());
+ }
Map<String, BaseAttribute> principalAttributes = attributeAuthority
.getAttributes(buildAttributeRequestContext(requestContext));
return attributeAuthority.buildAttributeStatement(requestContext.getSamlRequest(), principalAttributes
.values());
} catch (AttributeRequestException e) {
- log.error("Error resolving attributes", e);
+ log.error("Error resolving attributes for SAML request " + requestContext.getSamlRequest().getID()
+ + " from relying party " + requestContext.getRelyingPartyId(), e);
throw e;
}
}
ShibbolethSAMLAttributeRequestContext<NameID, AttributeQuery> queryContext = new ShibbolethSAMLAttributeRequestContext<NameID, AttributeQuery>(
getMetadataProvider(), requestContext.getRelyingPartyConfiguration(), requestContext.getSamlRequest());
+ 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);
}
}
- queryContext.setProfileConfiguration(requestContext.getProfileConfiguration());
- queryContext.setRequest(requestContext.getProfileRequest());
-
return queryContext;
}
* @throws ProfileException thrown if no message encoder is registered for this profiles binding
*/
protected void encodeResponse(AttributeQueryContext requestContext) throws ProfileException {
+ if (log.isDebugEnabled()) {
+ log.debug("Encoding response to SAML request " + requestContext.getSamlRequest().getID()
+ + " from relying party " + requestContext.getRelyingPartyId());
+ }
MessageEncoder<ServletResponse> encoder = getMessageEncoderFactory().getMessageEncoder(BINDING);
if (encoder == null) {
throw new ProfileException("No response encoder was registered for binding type: " + BINDING);
encoder.setResponse(requestContext.getProfileResponse().getRawResponse());
encoder.setSamlMessage(requestContext.getSamlResponse());
requestContext.setMessageEncoder(encoder);
+
+ try {
+ encoder.encode();
+ } catch (BindingException e) {
+ throw new ProfileException("Unable to encode response to relying party: "
+ + requestContext.getRelyingPartyId(), e);
+ }
}
/** Basic data structure used to accumulate information as a request is being processed. */