/*
* 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.sp.plans.actions;
import oasis.names.tc.saml._2_0.metadata.EndpointType;
import oasis.names.tc.saml._2_0.metadata.EntityDescriptorType;
import oasis.names.tc.saml._2_0.metadata.IDPSSODescriptorType;
import oasis.names.tc.saml._2_0.metadata.RoleDescriptorType;
import oasis.names.tc.saml._2_0.protocol.AuthnRequestType;
import oasis.names.tc.saml._2_0.protocol.NameIDPolicyType;
import oasis.names.tc.saml._2_0.protocol.RequestedAuthnContextType;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.atricore.idbus.capabilities.sso.main.SSOException;
import org.atricore.idbus.capabilities.sso.main.common.plans.actions.AbstractSSOAction;
import org.atricore.idbus.capabilities.sso.main.sp.SSOSPMediator;
import org.atricore.idbus.capabilities.sso.support.binding.SSOBinding;
import org.atricore.idbus.capabilities.sso.support.core.NameIDFormat;
import org.atricore.idbus.capabilities.sso.support.metadata.SSOService;
import org.atricore.idbus.common.sso._1_0.protocol.SPInitiatedAuthnRequestType;
import org.atricore.idbus.common.sso._1_0.protocol.SPSessionHeartBeatRequestType;
import org.atricore.idbus.kernel.main.federation.metadata.CircleOfTrustMemberDescriptor;
import org.atricore.idbus.kernel.main.federation.metadata.MetadataEntry;
import org.atricore.idbus.kernel.main.mediation.binding.BindingChannel;
import org.atricore.idbus.kernel.main.mediation.channel.FederationChannel;
import org.atricore.idbus.kernel.main.mediation.endpoint.IdentityMediationEndpoint;
import org.atricore.idbus.kernel.planning.IdentityArtifact;
import org.jbpm.graph.exe.ExecutionContext;
/**
* @author <a href="mailto:sgonzalez@atricore.org">Sebastian Gonzalez Oyuela</a>
* @version $Id: InitializeAuthnRequestAction.java 1359 2009-07-19 16:57:57Z sgonzalez $
*/
public class InitializeAuthnRequestAction extends AbstractSSOAction {
private static final Log logger = LogFactory.getLog(InitializeAuthnRequestAction .class);
protected void doExecute(IdentityArtifact in, IdentityArtifact out, ExecutionContext executionContext) throws Exception {
if (in == null || out == null)
return;
boolean passive = false;
boolean forceAuthn = false;
RequestedAuthnContextType reqAuthnCtx = null;
if (in.getContent() instanceof SPInitiatedAuthnRequestType) {
SPInitiatedAuthnRequestType ssoAuthnReq = (SPInitiatedAuthnRequestType) in.getContent();
passive = ssoAuthnReq.isPassive();
// If credentials are present, request a special authnctx
if (ssoAuthnReq.getCredentials() != null &&
ssoAuthnReq.getCredentials().size() > 0) {
// TODO : Send SAML Subject, as stated in Saml 2 profiles : 4.1.4.1 <AuthnRequest> Usage
// Only set requested authn ctx. class when credentials are provided by the SP.
reqAuthnCtx = new RequestedAuthnContextType();
reqAuthnCtx.getAuthnContextClassRef().add(ssoAuthnReq.getAuthnCtxClass());
if (logger.isDebugEnabled() && passive)
logger.debug("Generating NON-PASSIVE Authn Request " +
"(SPInitiatedAuthnRequest w/credentials received for " + ssoAuthnReq.getAuthnCtxClass() + ")");
// Set this to non-passive, JOSSO Login will handle subsequent errors.
passive = ssoAuthnReq.isPassive();
forceAuthn = ssoAuthnReq.getForceAuthn() != null && ssoAuthnReq.getForceAuthn();
} else {
if (logger.isDebugEnabled() && passive)
logger.debug("Generating PASSIVE Authn Request (SPInitiatedAuthnRequest received)");
if (logger.isDebugEnabled() && !passive)
logger.debug("Generating NON-PASSIVE Authn Request (SPInitiatedAuthnRequest received)");
}
} else if (in.getContent() instanceof SPSessionHeartBeatRequestType) {
passive = true;
logger.debug("Generating PASSIVE Authn Request (SPSessionHeartBeat received)");
}
AuthnRequestType authn = (AuthnRequestType) out.getContent();
// The channel that recieved the request.
BindingChannel channel = (BindingChannel) executionContext.getContextInstance().getVariable(VAR_CHANNEL);
FederationChannel idpChannel = (FederationChannel) executionContext.getContextInstance().getVariable(VAR_RESPONSE_CHANNEL);
IdentityMediationEndpoint endpoint = (IdentityMediationEndpoint ) executionContext.getContextInstance().getVariable(VAR_ENDPOINT);
CircleOfTrustMemberDescriptor idp = (CircleOfTrustMemberDescriptor) executionContext.getContextInstance().getVariable(VAR_DESTINATION_COT_MEMBER);
// saml:Subject [optional]
// TODO : If credentials are present, Send SAML Subject, as stated in Saml 2 profiles : 4.1.4.1 <AuthnRequest> Usage
// NameIDPolicy [optional]
// TODO : This is deployment specific, every IDP and SP can provide / support different policies, check SAMLR2 MD
SSOSPMediator mediator = (SSOSPMediator) idpChannel.getIdentityMediator();
String nameIdPolicyFormat = resolveNameIdFormat(idp, mediator.getPreferredNameIdPolicy());
NameIDPolicyType nameIdPolicy = new NameIDPolicyType();
nameIdPolicy.setFormat(nameIdPolicyFormat);
nameIdPolicy.setAllowCreate(true);
authn.setNameIDPolicy(nameIdPolicy);
// saml:Conditions [optional]
// RequestedAuthnContext [optional]
authn.setRequestedAuthnContext(reqAuthnCtx);
// Scoping [optional]
// ForceAuthn [optional] --> re-establish identity
authn.setForceAuthn(forceAuthn);
// IsPassive [optional] --> automatic login!
authn.setIsPassive(passive);
// AssertionConsumerServiceIndex [optional] --> from our springmetadata/endponit
// AssertionConsumerServiceURL [optional] --> from our springmetadata/endpoint
IdentityMediationEndpoint acsEndpoint = resolveAcsEndpoint(idp, idpChannel, endpoint);
if (acsEndpoint != null) {
logger.debug("ACS Endpoint found " + acsEndpoint.getName());
EndpointType samlr2Endpoint = (EndpointType) acsEndpoint.getMetadata().getEntry();
if (samlr2Endpoint != null)
authn.setAssertionConsumerServiceURL(samlr2Endpoint.getLocation());
// ProtocolBinding [optional]
if (samlr2Endpoint != null)
authn.setProtocolBinding(samlr2Endpoint.getBinding());
else
authn.setProtocolBinding(SSOBinding.SAMLR2_SOAP.getValue());
} else {
logger.debug("No ACS Endpoint found, we're using back-channel messages.");
}
// AttributeConsumingServiceIndex [optional]
// ProviderName [optional]
}
/**
* This finds the ACS endpoint where we want responses, based on the destination IDP, the channel used to receive
* requests from that IdP and the endpoint where the incoming message was received, if any.
* @param idpChannel The channel we're mediating
* @param idp the identity provider metadata
* @return
*/
protected IdentityMediationEndpoint resolveAcsEndpoint(CircleOfTrustMemberDescriptor idp, FederationChannel idpChannel, IdentityMediationEndpoint incomingEndpoint) {
if (log.isDebugEnabled())
log.debug("Looking for ACS endpoint. Idp: " + idp.getAlias() + ", federation channel: " + idpChannel.getName());
SSOBinding incomingEndpointBinding = null;
IdentityMediationEndpoint acsEndpoint = null;
IdentityMediationEndpoint acsArtifactEndpoint = null;
IdentityMediationEndpoint acsPostEndpoint = null;
String acsEndpointType = SSOService.AssertionConsumerService.toString();
if (log.isDebugEnabled())
log.debug("Selected IdP channel " + idpChannel.getName());
if (incomingEndpoint != null) {
incomingEndpointBinding = SSOBinding.asEnum(incomingEndpoint.getBinding());
if (log.isTraceEnabled())
log.trace("Incomming endpoint " + incomingEndpoint + ". Is front-channel: " +
incomingEndpointBinding.isFrontChannel());
if (!incomingEndpointBinding.isFrontChannel()) {
// No need to resolve ACS endpoint for back-channel ...
return null;
}
}
// Look for the ACS endpoint configured in the IdP channel, redirect is out of the question
for (IdentityMediationEndpoint endpoint : idpChannel.getEndpoints()) {
if (endpoint.getType().equals(acsEndpointType)) {
if (endpoint.getBinding().equals(SSOBinding.SAMLR2_ARTIFACT.getValue()))
acsArtifactEndpoint = endpoint;
if (endpoint.getBinding().equals(SSOBinding.SAMLR2_POST.getValue()))
acsPostEndpoint = endpoint;
}
}
//POST, then artifact
acsEndpoint = acsPostEndpoint;
if (acsEndpoint == null)
acsEndpoint = acsArtifactEndpoint;
if (log.isDebugEnabled())
log.debug("Selected ACS endpoint " + (acsEndpoint != null ? acsEndpoint.getName() : "<Null>"));
return acsEndpoint;
}
/**
* This will select the name ID format from the IdP metadata descriptor as follows:<br>
* <br>
* 1. If <b>preferredNameIdFormat</b> is supported by the IdP, it will be selected.<br>
* 2. Else, if <b>transient</b> name id format is supported by the IdP, it will be selected.<br>
* 3. Else, the first format supported by the IdP will be selected.
*
*/
protected String resolveNameIdFormat(CircleOfTrustMemberDescriptor idp, String preferredNameIdFormat) throws SSOException {
MetadataEntry idpMd = idp.getMetadata();
String selectedNameIdFormat = null;
String defaultNameIdFormat = null;
if (idpMd.getEntry() instanceof EntityDescriptorType) {
EntityDescriptorType md = (EntityDescriptorType) idpMd.getEntry();
for (RoleDescriptorType role : md.getRoleDescriptorOrIDPSSODescriptorOrSPSSODescriptor()) {
if (role instanceof IDPSSODescriptorType) {
IDPSSODescriptorType idpSsoRole = (IDPSSODescriptorType) role;
for (String nameIdFormat : idpSsoRole.getNameIDFormat()) {
if (preferredNameIdFormat != null && nameIdFormat.equals(preferredNameIdFormat))
selectedNameIdFormat = nameIdFormat;
if (nameIdFormat.equals(NameIDFormat.TRANSIENT.toString()))
defaultNameIdFormat = nameIdFormat;
if (defaultNameIdFormat == null)
defaultNameIdFormat = nameIdFormat;
}
}
}
} else
throw new SSOException("Unsupported Metadata type " + idpMd.getEntry() + ", SAML 2 Metadata expected");
if (selectedNameIdFormat == null)
selectedNameIdFormat = defaultNameIdFormat;
if (logger.isDebugEnabled())
logger.debug("Selected NameIDFormat for " + idp.getAlias() + " is " + selectedNameIdFormat);
return selectedNameIdFormat;
}
}