/*
* Atricore IDBus
*
* Copyright (c) 2009, Atricore Inc.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.atricore.idbus.capabilities.sso.main.emitter.plans.actions;
import oasis.names.tc.saml._2_0.assertion.*;
import oasis.names.tc.saml._2_0.protocol.AuthnRequestType;
import oasis.names.tc.saml._2_0.protocol.NameIDPolicyType;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.atricore.idbus.capabilities.sso.main.emitter.SamlR2SecurityTokenEmissionContext;
import org.atricore.idbus.capabilities.sso.main.emitter.plans.SubjectNameIDBuilder;
import org.atricore.idbus.capabilities.sso.support.core.NameIDFormat;
import org.atricore.idbus.capabilities.sso.support.core.util.DateUtils;
import org.atricore.idbus.capabilities.sso.support.profiles.SubjectConfirmationMethod;
import org.atricore.idbus.capabilities.sts.main.WSTConstants;
import org.atricore.idbus.kernel.main.federation.metadata.EndpointDescriptor;
import org.atricore.idbus.kernel.planning.IdentityArtifact;
import org.jbpm.graph.exe.ExecutionContext;
import javax.security.auth.Subject;
import java.util.Collection;
import java.util.Date;
/**
* <h2>Standards Reference</h2>
* <p>This action will populate the SAML2 Assertion Subject as described in:
* <ul>
* <li>SAML 2.0 Core, Processing Rules (section 3.4.1.4)</li>
* <li>SAML 2.0 Web Browser SSO Profile, <Response> usage (section 4.1.4.2)</li>
* <li>WS-Trust 1.3 Interoperability Profile: SAML 2.0 Token Profile, SAML 2 Token Creation (section 2.3)</li>
* </ul>
* </p>
* <h3>SAML 2.0 Core, Processing Rules (section 3.4.1.4)</h3>
* <p>
* If the <saml:Subject> element in the request is present, then the resulting assertions'
* <saml:Subject> MUST strongly match the request <saml:Subject>, as described in Section 3.3.4,
* except that the identifier MAY be in a different format if specified by <NameIDPolicy>. In such a case,
* the identifier's physical content MAY be different, but it MUST refer to the same principal.
* <br>
* All of the content defined specifically within <AuthnRequest> is optional, although some may be required
* by certain profiles. In the absence of any specific content at all, the following behavior is implied:
* <ul>
* <li>The assertion(s) returned MUST contain a <saml:Subject> element that represents the presenter.
* The identifier type and format are determined by the identity provider. At least one
* statement in at least one assertion MUST be a <saml:AuthnStatement> that describes the
* authentication performed by the responder or authentication service associated with it.
* </li>
* <li>The request presenter should, to the extent possible, be the only attesting entity able to satisfy the
* <saml:SubjectConfirmation> of the assertion(s). In the case of weaker confirmation
* methods, binding-specific or other mechanisms will be used to help satisfy this requirement.
* </li>
* </ul>
* </p>
* <h3>SAML 2.0 Web Browser SSO Profile, <Response> usage (section 4.1.4.2)</h3>
* <p>TBD</p>
* <h3>WS-Trust 1.3 Interoperability Profile: SAML 2.0 Token Profile, SAML 2 Token Creation (section 2.3)</h3>
* <p>
* When a token service issues a SAML 2 token it MUST produce a token with the following criteria:
* <ul>
* <li>The assertion MUST contain a <saml2:Subject>.</li>
* </ul>
* </p>
*
* @author <a href="mailto:sgonzalez@atricore.org">Sebastian Gonzalez Oyuela</a>
* @version $Id: BuildAuthnAssertionSubjectAction.java 1335 2009-06-24 16:34:38Z sgonzalez $
*/
public class BuildAuthnAssertionSubjectAction extends AbstractSSOAssertionAction {
private static final Log logger = LogFactory.getLog(BuildAuthnAssertionSubjectAction.class);
/**
* <h3>SAML 2.0 Web Browser SSO Profile, <AuthnRequest> usage (section 4.1.4.1) </h3>
* <p>
* Note that if the <AuthnRequest> is not authenticated and/or integrity protected, the information in it
* MUST NOT be trusted except as advisory. Whether the request is signed or not, the identity provider
* MUST ensure that any <AssertionConsumerServiceURL> or
* <AssertionConsumerServiceIndex> elements in the request are verified as belonging to the service
* provider to whom the response will be sent. Failure to do so can result in a man-in-the-middle attack.
* </p>
* <h3>SAML 2.0 Web Browser SSO Profile, Response Usage (section 4.1.4.2)
* <p>
* At least one assertion containing an <AuthnStatement> MUST contain a <Subject> element with
* at least one <SubjectConfirmation> element containing a Method of
* urn:oasis:names:tc:SAML:2.0:cm:bearer. If the identity provider supports the Single Logout
* profile, defined in Section 4.4, any such authentication statements MUST include a SessionIndex
* attribute to enable per-session logout requests by the service provider.
* </p>
* <p>
* The bearer <SubjectConfirmation> element described above MUST contain a
* <SubjectConfirmationData> element that contains a Recipient attribute containing the service
* provider's assertion consumer service URL and a NotOnOrAfter attribute that limits the window
* during which the assertion can be delivered. It MAY contain an Address attribute limiting the client
* address from which the assertion can be delivered. It MUST NOT contain a NotBefore attribute. If
* the containing message is in response to an <AuthnRequest>, then the InResponseTo attribute
* MUST match the request's ID.
* </p>
*
* @param executionContext
*/
@Override
protected void doExecute(IdentityArtifact in, IdentityArtifact out, ExecutionContext executionContext) {
logger.debug("starting action");
if (!(out.getContent() instanceof AssertionType))
throw new IllegalArgumentException("Output Identity Artifact MUST be contain SAMLR2 Assertion");
AssertionType assertion = (AssertionType) out.getContent();
// Do we have a SSOUser ?
Subject s = (Subject) executionContext.getContextInstance().getVariable(WSTConstants.SUBJECT_PROP);
if (s == null)
throw new IllegalArgumentException("Subject not found as process variable : " + WSTConstants.SUBJECT_PROP);
oasis.names.tc.saml._2_0.assertion.ObjectFactory samlObjectFactory;
samlObjectFactory = new oasis.names.tc.saml._2_0.assertion.ObjectFactory();
// Subject Confirmation Data
SubjectConfirmationDataType subjectConfirmationData = samlObjectFactory.createSubjectConfirmationDataType();
SamlR2SecurityTokenEmissionContext ctx =
(SamlR2SecurityTokenEmissionContext) executionContext.getContextInstance().getVariable(RST_CTX);
if (ctx != null && ctx.getRequest() != null) {
AuthnRequestType authnReq = (AuthnRequestType)ctx.getRequest();
// This should be the ACS endpoint we're using
EndpointDescriptor ed = ctx.getSpAcs();
if (ed != null) {
subjectConfirmationData.setRecipient(
ed.getResponseLocation() != null ? ed.getResponseLocation() : ed.getLocation());
}
if (ctx.getAuthnState() == null ||
ctx.getAuthnState().getResponseMode() == null ||
!ctx.getAuthnState().getResponseMode().equals("unsolicited")) {
// This is NOT idp initiated, send in response to.
subjectConfirmationData.setInResponseTo(authnReq.getID());
}
}
// Subject Confirmation : NotOnOrAfter (required)
Date dateNow = new java.util.Date();
subjectConfirmationData.setNotOnOrAfter(DateUtils.toXMLGregorianCalendar(dateNow.getTime() + (1000L * 60L * 5)));
// TODO : Check when we need to set SubjectConfirmation notBefore
// subjectConfirmationData.setNotBefore(DateUtils.toXMLGregorianCalendar(dateNow.getTime() - (1000L * 60L * 5)));
// Subject Confirmation, Confirmation Method Bearer is required.
SubjectConfirmationType subjectConfirmation = samlObjectFactory.createSubjectConfirmationType();
subjectConfirmation.setMethod(SubjectConfirmationMethod.BEARER.getValue());
subjectConfirmation.setSubjectConfirmationData(subjectConfirmationData);
// Subject
// TODO : Check AuthnRequest to see if a Subject element is present, check also SP SAML2 Metadata ?
SubjectType subject = samlObjectFactory.createSubjectType();
// TODO : Set SPNameQualifier
//subjectNameID.setSPNameQualifier("google.com/a/atricore.com");
NameIDType subjectNameID = null;
NameIDPolicyType nameIDPolicy = resolveNameIDPolicy(ctx);
SubjectNameIDBuilder nameIDBuilder = resolveNameIDBuiler(executionContext, nameIDPolicy);
subjectNameID = nameIDBuilder.buildNameID(nameIDPolicy, s);
if (subjectNameID == null)
throw new RuntimeException("No NameID builder found for " + nameIDPolicy.getFormat());
// Previously built parts
subject.getContent().add(samlObjectFactory.createNameID(subjectNameID));
subject.getContent().add(samlObjectFactory.createSubjectConfirmation(subjectConfirmation));
// Add the subject to the assertion
assertion.setSubject(subject);
logger.debug("ending-action");
}
protected NameIDPolicyType resolveNameIDPolicy(SamlR2SecurityTokenEmissionContext ctx) {
// Take NameID policy from request
NameIDPolicyType nameIDPolicy = null;
if (ctx.getRequest() != null) {
if (ctx.getRequest() instanceof AuthnRequestType) {
AuthnRequestType authnRequest = (AuthnRequestType) ctx.getRequest();
nameIDPolicy = authnRequest.getNameIDPolicy();
}
}
if (nameIDPolicy == null) {
// TODO : Take NameIDFormat from Provider Metadata
// TODO : Consider SAML R2 Metadata, it can also specify the required Name ID policy as <md:NameIDFormat> in SSO Descriptor!
}
if (nameIDPolicy == null) {
if (logger.isDebugEnabled())
logger.debug("Using default NameIDPolicy");
// Default name id policy : unspecified
nameIDPolicy = new NameIDPolicyType();
nameIDPolicy.setFormat(NameIDFormat.UNSPECIFIED.getValue());
} else {
if (logger.isDebugEnabled())
logger.debug("Using request NameIDPolicy " + nameIDPolicy.getFormat());
}
return nameIDPolicy;
}
protected SubjectNameIDBuilder resolveNameIDBuiler(ExecutionContext executionContext, NameIDPolicyType nameIDPolicy) {
Boolean ignoreRequestedNameIDPolicy = (Boolean) executionContext.getContextInstance().getTransientVariable(VAR_IGNORE_REQUESTED_NAMEID_POLICY);
SubjectNameIDBuilder defaultNameIDBuilder = (SubjectNameIDBuilder) executionContext.getContextInstance().getTransientVariable(VAR_DEFAULT_NAMEID_BUILDER);
if (ignoreRequestedNameIDPolicy != null && ignoreRequestedNameIDPolicy) {
if (logger.isDebugEnabled())
logger.debug("Ignoring requested NameIDPolicy, using DefaultNameIDBuilder : " + defaultNameIDBuilder);
return defaultNameIDBuilder;
}
Collection<SubjectNameIDBuilder> nameIdBuilders =
(Collection<SubjectNameIDBuilder>) executionContext.getContextInstance().getTransientVariable(VAR_NAMEID_BUILDERS);
if (nameIdBuilders == null || nameIdBuilders.size() == 0)
throw new RuntimeException("No NameIDBuilders configured for plan!");
for (SubjectNameIDBuilder nameIDBuilder : nameIdBuilders) {
if (nameIDBuilder.supportsPolicy(nameIDPolicy)) {
if (logger.isDebugEnabled())
logger.debug("Using NameIDBuilder : " + nameIDBuilder);
return nameIDBuilder;
}
}
if (logger.isDebugEnabled())
logger.debug("Using DefaultNameIDBuilder : " + defaultNameIDBuilder);
return defaultNameIDBuilder;
}
}