/*
* 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.producers;
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 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.producers.SSOProducer;
import org.atricore.idbus.capabilities.sso.main.select.spi.EntitySelectorConstants;
import org.atricore.idbus.capabilities.sso.main.sp.SPSecurityContext;
import org.atricore.idbus.capabilities.sso.main.sp.SSOSPMediator;
import org.atricore.idbus.capabilities.sso.main.sp.plans.SPInitiatedAuthnReqToSamlR2AuthnReqPlan;
import org.atricore.idbus.capabilities.sso.support.SAMLR2Constants;
import org.atricore.idbus.capabilities.sso.support.binding.SSOBinding;
import org.atricore.idbus.capabilities.sso.support.metadata.SSOMetadataConstants;
import org.atricore.idbus.capabilities.sts.main.SecurityTokenEmissionException;
import org.atricore.idbus.common.sso._1_0.protocol.*;
import org.atricore.idbus.kernel.auditing.core.ActionOutcome;
import org.atricore.idbus.kernel.main.federation.SubjectNameID;
import org.atricore.idbus.kernel.main.federation.metadata.CircleOfTrustMemberDescriptor;
import org.atricore.idbus.kernel.main.federation.metadata.EndpointDescriptor;
import org.atricore.idbus.kernel.main.federation.metadata.EndpointDescriptorImpl;
import org.atricore.idbus.kernel.main.federation.metadata.MetadataEntry;
import org.atricore.idbus.kernel.main.mediation.MediationMessageImpl;
import org.atricore.idbus.kernel.main.mediation.binding.BindingChannel;
import org.atricore.idbus.kernel.main.mediation.camel.AbstractCamelEndpoint;
import org.atricore.idbus.kernel.main.mediation.camel.component.binding.CamelMediationExchange;
import org.atricore.idbus.kernel.main.mediation.camel.component.binding.CamelMediationMessage;
import org.atricore.idbus.kernel.main.mediation.channel.FederationChannel;
import org.atricore.idbus.kernel.main.mediation.endpoint.IdentityMediationEndpoint;
import org.atricore.idbus.kernel.main.mediation.provider.FederatedLocalProvider;
import org.atricore.idbus.kernel.main.mediation.provider.FederatedProvider;
import org.atricore.idbus.kernel.main.util.UUIDGenerator;
import org.atricore.idbus.kernel.planning.*;
import javax.xml.namespace.QName;
import java.util.Collection;
import java.util.Properties;
import java.util.Set;
/**
*
*/
public class SPInitiatedSingleSignOnProducer extends SSOProducer {
private static final Log logger = LogFactory.getLog( SPInitiatedSingleSignOnProducer.class );
protected UUIDGenerator uuidGenerator = new UUIDGenerator();
public SPInitiatedSingleSignOnProducer( AbstractCamelEndpoint<CamelMediationExchange> endpoint ) throws Exception {
super(endpoint);
}
@Override
protected void doProcess(CamelMediationExchange exchange) throws SSOException {
logger.debug("Processing SP Initiated Single Sign-On on HTTP Redirect");
// ------------------------------------------------------------------------------------------
// We have to check if identity has been provided, if so. No need to go to IDP.
// ------------------------------------------------------------------------------------------
CamelMediationMessage in = (CamelMediationMessage) exchange.getIn();
// May be used later by HTTP-Redirect binding!
SSOSPMediator mediator = (SSOSPMediator) channel.getIdentityMediator();
in.getMessage().getState().setAttribute("SAMLR2Signer", mediator.getSigner());
Object content = in.getMessage().getContent();
if (content == null || content instanceof SPInitiatedAuthnRequestType ) {
doProcessSPInitiatedAuthnRequest(exchange);
} else if (content instanceof SelectEntityResponseType) {
doProcessSelectEntityResponse(exchange);
} else {
throw new SSOException("Unknown SSO message type " + content);
}
}
protected void doProcessSPInitiatedAuthnRequest(CamelMediationExchange exchange) throws SSOException {
logger.debug("Processing SP Initiated Single Sign-On on HTTP Redirect");
try {
CamelMediationMessage in = (CamelMediationMessage) exchange.getIn();
// May be used later by HTTP-Redirect binding!
SSOSPMediator mediator = (SSOSPMediator) channel.getIdentityMediator();
in.getMessage().getState().setAttribute("SAMLR2Signer", mediator.getSigner());
SPInitiatedAuthnRequestType ssoAuthnReq =
(SPInitiatedAuthnRequestType) in.getMessage().getContent();
// Use local state ID as our relay state,
String relayState = in.getMessage().getState().getLocalState().getId();
SPSecurityContext secCtx =
(SPSecurityContext) in.getMessage().getState().getLocalVariable(getProvider().getName().toUpperCase() + "_SECURITY_CTX");
if (secCtx != null && secCtx.getSessionIndex() != null) {
// Support no authentication request !
if (ssoAuthnReq != null && ssoAuthnReq.getForceAuthn() != null && !ssoAuthnReq.getForceAuthn()) {
// TODO ! Check that the session belongs to the IdP associated with this request
logger.debug("SSO Session found " + secCtx.getSessionIndex());
SPAuthnResponseType ssoResponse = new SPAuthnResponseType ();
ssoResponse.setID(uuidGenerator.generateId());
ssoResponse.setIssuer(getProvider().getName());
SPInitiatedAuthnRequestType ssoRequest =
(SPInitiatedAuthnRequestType) in.getMessage().getState().
getLocalVariable("urn:org:atricore:idbus:sso:protocol:SPInitiatedAuthnRequest");
if (ssoRequest != null) {
ssoResponse.setInReplayTo(ssoRequest.getID());
}
SubjectType subjectType = toSubjectType(secCtx.getSubject());
ssoResponse.setSessionIndex(secCtx.getSessionIndex());
ssoResponse.setSubject(subjectType);
String destinationLocation = resolveSpBindingACS();
EndpointDescriptor destination =
new EndpointDescriptorImpl("EmbeddedSPAcs",
"AssertionConsumerService",
SSOBinding.SSO_ARTIFACT.getValue(),
destinationLocation, null);
CamelMediationMessage out = (CamelMediationMessage) exchange.getOut();
out.setMessage(new MediationMessageImpl(ssoResponse.getID(),
ssoResponse, "SPAuthnResposne", relayState, destination, in.getMessage().getState()));
exchange.setOut(out);
return;
} else {
logger.debug("SSO Session found " + secCtx.getSessionIndex() + ", but SP requested 'forceAuthn'. Destroying security context");
// Destroy current session/secCtx !
destroySPSecurityContext(exchange, secCtx);
}
}
// ------------------------------------------------------
// Resolve IDP configuration!
// ------------------------------------------------------
// TODO : Check select options ... do we have a select endpoint and multiple IdPs ?!
BindingChannel bChannel = (BindingChannel) channel;
Collection<CircleOfTrustMemberDescriptor> availableIdPs = getCotManager().lookupMembersForProvider(bChannel.getFederatedProvider(),
SSOMetadataConstants.IDPSSODescriptor_QNAME.toString());
// Do we have to select an IdP
if (availableIdPs.size() > 1) {
// Use IDP Selector, build a context with enough information (provider state ?!) and send a request
if (logger.isDebugEnabled())
logger.debug("Selecting from " + availableIdPs.size() + " IDps" );
SelectEntityRequestType selectIdPRequest = buildSelectEntityRequest(bChannel, ssoAuthnReq, availableIdPs);
// Look up for appliance entity select endpoint for IdPs
String idpSelectorLocation = mediator.getIdpSelector();
EndpointDescriptor ed = new EndpointDescriptorImpl(
"IDPSelectorEndpoint",
"EntitySelector",
SSOBinding.SSO_ARTIFACT.toString(),
idpSelectorLocation,
null);
if (ssoAuthnReq != null)
in.getMessage().getState().setLocalVariable(
"urn:org:atricore:idbus:sso:protocol:SPInitiatedAuthnRequest", ssoAuthnReq);
else
in.getMessage().getState().removeLocalVariable("urn:org:atricore:idbus:sso:protocol:SPInitiatedAuthnRequest");
// Send SAMLR2 Message back
CamelMediationMessage out = (CamelMediationMessage) exchange.getOut();
out.setMessage(new MediationMessageImpl(uuidGenerator.generateId(),
selectIdPRequest,
"SelectEntityRequest",
relayState,
ed,
in.getMessage().getState()));
// Send request
exchange.setOut(out);
return;
}
if (availableIdPs.size() < 1)
throw new SSOException("No Identity Providers available for " + channel.getName());
CircleOfTrustMemberDescriptor idp = availableIdPs.iterator().next();
if (logger.isDebugEnabled())
logger.debug("Using IdP " + idp.getAlias());
// Select endpoint, must be a SingleSingOnService endpoint from a IDPSSORoleD
EndpointType idpSsoEndpoint = resolveIdpSsoEndpoint(idp);
EndpointDescriptor ed = new EndpointDescriptorImpl(
"IDPSSOEndpoint",
"SingleSignOnService",
idpSsoEndpoint.getBinding(),
idpSsoEndpoint.getLocation(),
idpSsoEndpoint.getResponseLocation());
// ------------------------------------------------------
// Create AuthnRequest using identity plan
// ------------------------------------------------------
FederationChannel idpChannel = resolveIdpChannel(idp);
if (logger.isDebugEnabled())
logger.debug("Using IdP channel " + idpChannel.getName());
AuthnRequestType authnRequest = buildAuthnRequest(exchange, idp, ed, idpChannel, ssoAuthnReq);
Properties auditProps = new Properties();
auditProps.put("idpAlias", idp.getAlias());
recordInfoAuditTrail("SP-SSO", ActionOutcome.SUCCESS, null, exchange, auditProps);
// ------------------------------------------------------
// Send Authn Request to IDP
// ------------------------------------------------------
in.getMessage().getState().setLocalVariable(
"urn:org:atricore:idbus:sso:protocol:SPInitiatedAuthnRequest", ssoAuthnReq);
in.getMessage().getState().setLocalVariable(
SAMLR2Constants.SAML_PROTOCOL_NS + ":AuthnRequest", authnRequest);
// Send SAMLR2 Message back
CamelMediationMessage out = (CamelMediationMessage) exchange.getOut();
out.setMessage(new MediationMessageImpl(uuidGenerator.generateId(),
authnRequest,
"AuthnRequest",
relayState,
ed,
in.getMessage().getState()));
exchange.setOut(out);
} catch (Exception e) {
throw new SSOException(e);
}
}
protected void doProcessSelectEntityResponse(CamelMediationExchange exchange) throws SSOException {
try {
CamelMediationMessage in = (CamelMediationMessage) exchange.getIn();
// May be used later by HTTP-Redirect binding!
SSOSPMediator mediator = (SSOSPMediator) channel.getIdentityMediator();
in.getMessage().getState().setAttribute("SAMLR2Signer", mediator.getSigner());
SPInitiatedAuthnRequestType ssoAuthnReq =
(SPInitiatedAuthnRequestType) in.getMessage().getState().getLocalVariable(
"urn:org:atricore:idbus:sso:protocol:SPInitiatedAuthnRequest");
String relayState = in.getMessage().getState().getLocalState().getId();
SelectEntityResponseType response = (SelectEntityResponseType) in.getMessage().getContent();
CircleOfTrustMemberDescriptor idp = getCotManager().lookupMemberById(response.getEntityId());
if (logger.isDebugEnabled())
logger.debug("Using IdP " + idp.getAlias());
// Select endpoint, must be a SingleSingOnService endpoint from a IDPSSORoleD
EndpointType idpSsoEndpoint = resolveIdpSsoEndpoint(idp);
EndpointDescriptor ed = new EndpointDescriptorImpl(
"IDPSSOEndpoint",
"SingleSignOnService",
idpSsoEndpoint.getBinding(),
idpSsoEndpoint.getLocation(),
idpSsoEndpoint.getResponseLocation());
// ------------------------------------------------------
// Create AuthnRequest using identity plan
// ------------------------------------------------------
FederationChannel idpChannel = resolveIdpChannel(idp);
if (logger.isDebugEnabled())
logger.debug("Using IdP channel " + idpChannel.getName());
AuthnRequestType authnRequest = buildAuthnRequest(exchange, idp, ed, idpChannel, ssoAuthnReq);
// ------------------------------------------------------
// Send Authn Request to IDP
// ------------------------------------------------------
in.getMessage().getState().setLocalVariable(
SAMLR2Constants.SAML_PROTOCOL_NS + ":AuthnRequest", authnRequest);
// Send SAMLR2 Message back
CamelMediationMessage out = (CamelMediationMessage) exchange.getOut();
out.setMessage(new MediationMessageImpl(uuidGenerator.generateId(),
authnRequest,
"AuthnRequest",
relayState,
ed,
in.getMessage().getState()));
exchange.setOut(out);
} catch (Exception e) {
throw new SSOException(e);
}
}
protected AuthnRequestType buildAuthnRequest(CamelMediationExchange exchange,
CircleOfTrustMemberDescriptor idp,
EndpointDescriptor ed,
FederationChannel idpChannel,
SPInitiatedAuthnRequestType ssoAuthnRequest
) throws IdentityPlanningException, SSOException {
IdentityPlan identityPlan = findIdentityPlanOfType(SPInitiatedAuthnReqToSamlR2AuthnReqPlan.class);
IdentityPlanExecutionExchange idPlanExchange = createIdentityPlanExecutionExchange();
// Publish IdP Metadata
idPlanExchange.setProperty(VAR_DESTINATION_COT_MEMBER, idp);
idPlanExchange.setProperty(VAR_DESTINATION_ENDPOINT_DESCRIPTOR, ed);
idPlanExchange.setProperty(VAR_COT_MEMBER, idpChannel.getMember());
idPlanExchange.setProperty(VAR_RESPONSE_CHANNEL, idpChannel);
// Get SPInitiated authn request, if any!
// Create in/out artifacts
IdentityArtifact in =
new IdentityArtifactImpl(new QName("urn:org:atricore:idbus:sso:protocol", "SPInitiatedAuthnRequest"), ssoAuthnRequest );
idPlanExchange.setIn(in);
IdentityArtifact<AuthnRequestType> out =
new IdentityArtifactImpl<AuthnRequestType>(new QName(SAMLR2Constants.SAML_PROTOCOL_NS, "AuthnRequest"),
new AuthnRequestType());
idPlanExchange.setOut(out);
// Prepare execution
identityPlan.prepare(idPlanExchange);
// Perform execution
identityPlan.perform(idPlanExchange);
if (!idPlanExchange.getStatus().equals(IdentityPlanExecutionStatus.SUCCESS)) {
throw new SecurityTokenEmissionException("Identity plan returned : " + idPlanExchange.getStatus());
}
if (idPlanExchange.getOut() == null)
throw new SecurityTokenEmissionException("Plan Exchange OUT must not be null!");
return (AuthnRequestType) idPlanExchange.getOut().getContent();
}
protected SelectEntityRequestType buildSelectEntityRequest(BindingChannel bChannel,
SPInitiatedAuthnRequestType ssoAuthnReq,
Collection<CircleOfTrustMemberDescriptor> availableIdPs) {
SSOSPMediator mediator = (SSOSPMediator) bChannel.getIdentityMediator();
SelectEntityRequestType selectIdPRequest = new SelectEntityRequestType();
selectIdPRequest.setID(uuidGenerator.generateId());
selectIdPRequest.setIssuer(bChannel.getFederatedProvider().getName());
for (IdentityMediationEndpoint ed : channel.getEndpoints()) {
if (ed.getBinding().equals(SSOBinding.SSO_ARTIFACT.getValue()) &&
ed.getType().equals(endpoint.getType())) {
selectIdPRequest.setReplyTo(channel.getLocation() + ed.getLocation());
break;
}
}
for (CircleOfTrustMemberDescriptor idp : availableIdPs) {
RequestAttributeType idpAttr = new RequestAttributeType();
idpAttr.setName(SSOMetadataConstants.IDPSSODescriptor_QNAME.toString());
idpAttr.setValue(idp.getAlias());
selectIdPRequest.getRequestAttribute().add(idpAttr);
}
// Send the name of the SP asking for the selection
RequestAttributeType spAttr = new RequestAttributeType();
spAttr.setName(EntitySelectorConstants.ISSUER_SP_ATTR);
spAttr.setValue(((BindingChannel)channel).getProvider().getName());
selectIdPRequest.getRequestAttribute().add(spAttr);
// Send the preferred IDP alias
RequestAttributeType preferredIdp = new RequestAttributeType();
preferredIdp.setName(EntitySelectorConstants.PREFERRED_IDP_ATTR);
preferredIdp.setValue(mediator.getPreferredIdpAlias());
selectIdPRequest.getRequestAttribute().add(preferredIdp);
if (ssoAuthnReq != null) {
// Add additional information about the environment ...
RequestAttributeType authnCtx = new RequestAttributeType();
authnCtx.setName(EntitySelectorConstants.AUTHN_CTX_ATTR);
authnCtx.setValue(ssoAuthnReq.getAuthnCtxClass());
selectIdPRequest.getRequestAttribute().add(authnCtx);
// All request attributes
if (ssoAuthnReq.getRequestAttribute() != null) {
for (int i = 0; i < ssoAuthnReq.getRequestAttribute().size(); i++) {
RequestAttributeType a =
ssoAuthnReq.getRequestAttribute().get(i);
RequestAttributeType a1 = new RequestAttributeType();
a1.setName(a.getName());
a1.setValue(a.getValue());
selectIdPRequest.getRequestAttribute().add(a1);
}
}
}
return selectIdPRequest;
}
/**
* @return
*/
protected FederationChannel resolveIdpChannel(CircleOfTrustMemberDescriptor idpDescriptor) {
// Resolve IdP channel, then look for the ACS endpoint
BindingChannel bChannel = (BindingChannel) channel;
FederatedLocalProvider sp = bChannel.getFederatedProvider();
FederationChannel idpChannel = sp.getChannel();
for (FederationChannel fChannel : sp.getChannels()) {
FederatedProvider idp = fChannel.getTargetProvider();
for (CircleOfTrustMemberDescriptor member : idp.getMembers()) {
if (member.getAlias().equals(idpDescriptor.getAlias())) {
if (logger.isDebugEnabled())
logger.debug("Selected IdP channel " + fChannel.getName() + " for provider " + idp.getName());
idpChannel = fChannel;
break;
}
}
}
return idpChannel;
}
protected EndpointType resolveIdpSsoEndpoint(CircleOfTrustMemberDescriptor idp) throws SSOException {
SSOSPMediator mediator = (SSOSPMediator) channel.getIdentityMediator();
SSOBinding preferredBinding = mediator.getPreferredIdpSSOBindingValue();
MetadataEntry idpMd = idp.getMetadata();
if (idpMd == null || idpMd.getEntry() == null)
throw new SSOException("No metadata descriptor found for IDP " + idp);
if (idpMd.getEntry() instanceof EntityDescriptorType ) {
EntityDescriptorType md = (EntityDescriptorType) idpMd.getEntry();
for (RoleDescriptorType role : md.getRoleDescriptorOrIDPSSODescriptorOrSPSSODescriptor()) {
if (role instanceof IDPSSODescriptorType) {
IDPSSODescriptorType idpSsoRole = (IDPSSODescriptorType) role;
EndpointType defaultEndpoint = null;
for (EndpointType idpSsoEndpoint : idpSsoRole.getSingleSignOnService()) {
SSOBinding b = SSOBinding.asEnum(idpSsoEndpoint.getBinding());
if (b.equals(preferredBinding))
return idpSsoEndpoint;
if (b.equals(SSOBinding.SAMLR2_ARTIFACT))
defaultEndpoint = idpSsoEndpoint;
if (defaultEndpoint == null)
defaultEndpoint = idpSsoEndpoint;
}
return defaultEndpoint;
}
}
} else {
throw new SSOException("Unknown metadata descriptor type " + idpMd.getEntry().getClass().getName());
}
logger.debug("No IDP Endpoint supporting binding : " + preferredBinding);
throw new SSOException("IDP does not support preferred binding " + preferredBinding);
}
protected String resolveSpBindingACS() {
return ((SSOSPMediator)channel.getIdentityMediator()).getSpBindingACS();
}
}