/* * 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.assertion.*; 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.metadata.SPSSODescriptorType; import oasis.names.tc.saml._2_0.protocol.AuthnRequestType; import oasis.names.tc.saml._2_0.protocol.ResponseType; import org.apache.commons.lang.StringUtils; 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.AbstractSSOMediator; import org.atricore.idbus.capabilities.sso.main.common.producers.SSOProducer; 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.SamlR2AuthnResponseToSPAuthnResponse; import org.atricore.idbus.capabilities.sso.support.SAMLR2Constants; import org.atricore.idbus.capabilities.sso.support.SSOConstants; import org.atricore.idbus.capabilities.sso.support.auth.AuthnCtxClass; import org.atricore.idbus.capabilities.sso.support.binding.SSOBinding; import org.atricore.idbus.capabilities.sso.support.core.SSOResponseException; import org.atricore.idbus.capabilities.sso.support.core.StatusCode; import org.atricore.idbus.capabilities.sso.support.core.StatusDetails; import org.atricore.idbus.capabilities.sso.support.core.encryption.SamlR2Encrypter; import org.atricore.idbus.capabilities.sso.support.core.encryption.SamlR2EncrypterException; import org.atricore.idbus.capabilities.sso.support.core.signature.SamlR2SignatureException; import org.atricore.idbus.capabilities.sso.support.core.signature.SamlR2SignatureValidationException; import org.atricore.idbus.capabilities.sso.support.core.signature.SamlR2Signer; import org.atricore.idbus.capabilities.sso.support.metadata.SSOMetadataConstants; import org.atricore.idbus.capabilities.sso.support.metadata.SSOService; import org.atricore.idbus.common.sso._1_0.protocol.CurrentEntityRequestType; import org.atricore.idbus.common.sso._1_0.protocol.SPAuthnResponseType; import org.atricore.idbus.common.sso._1_0.protocol.SPInitiatedAuthnRequestType; import org.atricore.idbus.common.sso._1_0.protocol.SSOResponseType; import org.atricore.idbus.kernel.auditing.core.ActionOutcome; import org.atricore.idbus.kernel.main.authn.SecurityToken; import org.atricore.idbus.kernel.main.authn.SecurityTokenImpl; import org.atricore.idbus.kernel.main.federation.*; import org.atricore.idbus.kernel.main.federation.metadata.*; import org.atricore.idbus.kernel.main.mediation.IdentityMediationException; import org.atricore.idbus.kernel.main.mediation.IdentityMediationFault; import org.atricore.idbus.kernel.main.mediation.MediationMessageImpl; import org.atricore.idbus.kernel.main.mediation.MediationState; 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.channel.IdPChannel; import org.atricore.idbus.kernel.main.mediation.endpoint.IdentityMediationEndpoint; import org.atricore.idbus.kernel.main.mediation.provider.FederatedProvider; import org.atricore.idbus.kernel.main.session.SSOSessionManager; import org.atricore.idbus.kernel.main.session.exceptions.NoSuchSessionException; import org.atricore.idbus.kernel.main.session.exceptions.SSOSessionException; import org.atricore.idbus.kernel.main.util.UUIDGenerator; import org.atricore.idbus.kernel.planning.*; import org.w3._2001._04.xmlenc_.EncryptedType; import org.w3c.dom.Element; import javax.security.auth.Subject; import javax.xml.bind.JAXBElement; import javax.xml.datatype.DatatypeConstants; import javax.xml.datatype.XMLGregorianCalendar; import javax.xml.namespace.QName; import java.util.*; /** * @author <a href="mailto:sgonzalez@atricore.org">Sebastian Gonzalez Oyuela</a> * @version $Id$ */ public class AssertionConsumerProducer extends SSOProducer { private static final Log logger = LogFactory.getLog(AssertionConsumerProducer.class); private UUIDGenerator uuidGenerator = new UUIDGenerator(); public AssertionConsumerProducer(AbstractCamelEndpoint endpoint) throws Exception { super(endpoint); } @Override protected void doProcess(CamelMediationExchange exchange) throws Exception { // Incomming message CamelMediationMessage in = (CamelMediationMessage) exchange.getIn(); if (in.getMessage().getContent() instanceof ResponseType) { doProcessSamlResponseType(exchange, (ResponseType) in.getMessage().getContent()); } else { throw new IdentityMediationException("Unknown message type : " + in.getMessage().getContent()); } } protected void doProcessSamlResponseType(CamelMediationExchange exchange, ResponseType response) throws Exception { // Incomming message CamelMediationMessage in = (CamelMediationMessage) exchange.getIn(); // Mediation state MediationState state = in.getMessage().getState(); // May be used later by HTTP-Redirect binding! AbstractSSOMediator mediator = (AbstractSSOMediator) channel.getIdentityMediator(); state.setAttribute("SAMLR2Signer", mediator.getSigner()); // Originally received Authn request from binding channel // When using IdP initiated SSO, this will be null! SPInitiatedAuthnRequestType ssoRequest = (SPInitiatedAuthnRequestType) state.getLocalVariable("urn:org:atricore:idbus:sso:protocol:SPInitiatedAuthnRequest"); state.removeLocalVariable("urn:org:atricore:idbus:sso:protocol:SPInitiatedAuthnRequest"); // Destination, where to respond the above referred request String destinationLocation = ((SSOSPMediator) channel.getIdentityMediator()).getSpBindingACS(); if (destinationLocation == null) throw new SSOException("SP Mediator does not have resource location!"); EndpointDescriptor destination = new EndpointDescriptorImpl("EmbeddedSPAcs", "AssertionConsumerService", SSOBinding.SSO_ARTIFACT.getValue(), destinationLocation, null); // Stored by SP Initiated producer AuthnRequestType authnRequest = (AuthnRequestType) state.getLocalVariable(SAMLR2Constants.SAML_PROTOCOL_NS + ":AuthnRequest"); state.removeLocalVariable(SAMLR2Constants.SAML_PROTOCOL_NS + ":AuthnRequest"); if (logger.isDebugEnabled()) logger.debug("Previous AuthnRequest " + (authnRequest != null ? authnRequest.getID() : "<NONE>")); if (ssoRequest != null && ssoRequest.getReplyTo() != null) { destination = new EndpointDescriptorImpl("EmbeddedSPAcs", "AssertionConsumerService", SSOBinding.SSO_ARTIFACT.getValue(), ssoRequest.getReplyTo(), null); } // ------------------------------------------------------ // Handle Proxy Mode // ------------------------------------------------------ validateResponse(authnRequest, response, in.getMessage().getRawContent(), state); // if (mediator.isVerifyUniqueIDs()) mediator.getIdRegistry().register(response.getID()); String issuerAlias = response.getIssuer().getValue(); // Response is valid, check received status! StatusCode status = StatusCode.asEnum(response.getStatus().getStatusCode().getValue()); StatusCode secStatus = response.getStatus().getStatusCode().getStatusCode() != null ? StatusCode.asEnum(response.getStatus().getStatusCode().getStatusCode().getValue()) : null; if (logger.isDebugEnabled()) logger.debug("Received status code " + status.getValue() + (secStatus != null ? "/" + secStatus.getValue() : "")); if (status.equals(StatusCode.TOP_RESPONDER) && secStatus != null && secStatus.equals(StatusCode.NO_PASSIVE)) { // Automatic Login failed if (logger.isDebugEnabled()) logger.debug("IDP Reports Passive login failed"); // This is SP initiated SSO or we did not requested passive authentication if (authnRequest == null || authnRequest.getForceAuthn()) { throw new SSOException("IDP Sent " + StatusCode.NO_PASSIVE + " but passive was not requested."); } Properties auditProps = new Properties(); auditProps.put("idpAlias", issuerAlias); auditProps.put("passive", "true"); recordInfoAuditTrail("SP-SSOR", ActionOutcome.FAILURE, null, exchange, auditProps); // Send a 'no-passive' status response SPAuthnResponseType ssoResponse = buildSPAuthnResponseType(exchange, ssoRequest, null, destination); CamelMediationMessage out = (CamelMediationMessage) exchange.getOut(); out.setMessage(new MediationMessageImpl(ssoResponse.getID(), ssoResponse, "SPAuthnResposne", null, destination, in.getMessage().getState())); exchange.setOut(out); return; } else if (!status.equals(StatusCode.TOP_SUCCESS)) { throw new SSOException("Unexpected IDP Status Code " + status.getValue() + (secStatus != null ? "/" + secStatus.getValue() : "")); } // check if there is an existing session for the user // if not, check if channel is federation-capable FederationChannel fChannel = (FederationChannel) channel; if (fChannel.getAccountLinkLifecycle() == null) { // cannot map subject to local account, terminate logger.error("No Account Lifecycle configured for Channel [" + fChannel.getName() + "] " + " Response [" + response.getID() + "]"); throw new SSOException("No Account Lifecycle configured for Channel [" + fChannel.getName() + "] " + " Response [" + response.getID() + "]"); } AccountLinkLifecycle accountLinkLifecycle = fChannel.getAccountLinkLifecycle(); // ------------------------------------------------------------------ // Build IDP Subject from response // ------------------------------------------------------------------ Subject idpSubject = buildSubjectFromResponse(response); AccountLink acctLink = null; AccountLinkEmitter accountLinkEmitter = fChannel.getAccountLinkEmitter(); if (logger.isTraceEnabled()) logger.trace("Account Link Emitter Found for Channel [" + fChannel.getName() + "]"); // Emit account link information acctLink = accountLinkEmitter.emit(idpSubject); if (logger.isDebugEnabled()) logger.debug("Emitted Account Link [" + (acctLink != null ? "[" + acctLink.getId() + "]" + acctLink.getLocalAccountNameIdentifier() : "null") + "] [" + fChannel.getName() + "] " + " for IDP Subject [" + idpSubject + "]" ); if (acctLink == null) { logger.error("No Account Link for Channel [" + fChannel.getName() + "] " + " Response [" + response.getID() + "]"); throw new IdentityMediationFault(StatusCode.TOP_REQUESTER.getValue(), null, StatusDetails.NO_ACCOUNT_LINK.getValue(), idpSubject.toString(), null); } // ------------------------------------------------------------------ // fetch local account for subject, if any // ------------------------------------------------------------------ Subject localAccountSubject = accountLinkLifecycle.resolve(acctLink); if (logger.isTraceEnabled()) logger.trace("Account Link [" + acctLink.getId() + "] resolved to " + "Local Subject [" + localAccountSubject + "] "); Subject federatedSubject = localAccountSubject; // if no identity mapping, the local account subject is used // having both remote and local accounts information, is now time to apply custom identity mapping rules if (fChannel.getIdentityMapper() != null) { IdentityMapper im = fChannel.getIdentityMapper(); if (logger.isTraceEnabled()) logger.trace("Using identity mapper : " + im.getClass().getName()); federatedSubject = im.map(idpSubject, localAccountSubject ); } // Add IDP Name to federated Subject if (logger.isDebugEnabled()) logger.debug("IDP Subject [" + idpSubject + "] mapped to Subject [" + federatedSubject + "] " + "through Account Link [" + acctLink.getId() + "]" ); // --------------------------------------------------- // Create SP Security context and session! // --------------------------------------------------- CircleOfTrustMemberDescriptor idp = resolveIdp(exchange); // We must have an assertion! SPSecurityContext spSecurityCtx = createSPSecurityContext(exchange, (ssoRequest != null && ssoRequest.getReplyTo() != null ? ssoRequest.getReplyTo() : null), idp, acctLink, federatedSubject, idpSubject, (AssertionType) response.getAssertionOrEncryptedAssertion().get(0)); Properties auditProps = new Properties(); auditProps.put("idpAlias", spSecurityCtx.getIdpAlias()); auditProps.put("idpSession", spSecurityCtx.getIdpSsoSession()); Set<SubjectNameID> principals = federatedSubject.getPrincipals(SubjectNameID.class); SubjectNameID principal = null; if (principals.size() == 1) { principal = principals.iterator().next(); } recordInfoAuditTrail("SP-SSOR", ActionOutcome.SUCCESS, principal != null ? principal.getName() : null, exchange, auditProps); Collection<CircleOfTrustMemberDescriptor> availableIdPs = getCotManager().lookupMembersForProvider(fChannel.getFederatedProvider(), SSOMetadataConstants.IDPSSODescriptor_QNAME.toString()); SPAuthnResponseType ssoResponse = buildSPAuthnResponseType(exchange, ssoRequest, spSecurityCtx, destination); CamelMediationMessage out = (CamelMediationMessage) exchange.getOut(); // --------------------------------------------------- // We must tell our Entity selector about the IdP we're using. It's considered a selection. // --------------------------------------------------- if (availableIdPs.size() > 1) { EndpointDescriptor idpSelectorCallbackEndpoint = resolveIdPSelectorCallbackEndpoint(exchange, fChannel); if (idpSelectorCallbackEndpoint != null) { if (logger.isDebugEnabled()) logger.debug("Sending Current Selected IdP request, callback location : " + idpSelectorCallbackEndpoint); // Store destination and response CurrentEntityRequestType entityRequest = new CurrentEntityRequestType(); entityRequest.setID(uuidGenerator.generateId()); entityRequest.setIssuer(getCotMemberDescriptor().getAlias()); entityRequest.setEntityId(idp.getAlias()); entityRequest.setReplyTo(idpSelectorCallbackEndpoint.getResponseLocation() != null ? idpSelectorCallbackEndpoint.getResponseLocation() : idpSelectorCallbackEndpoint.getLocation()); String idpSelectorLocation = ((SSOSPMediator) mediator).getIdpSelector(); EndpointDescriptor entitySelectorEndpoint = new EndpointDescriptorImpl( "IDPSelectorEndpoint", "EntitySelector", SSOBinding.SSO_ARTIFACT.toString(), idpSelectorLocation, null); out.setMessage(new MediationMessageImpl(entityRequest.getID(), entityRequest, "CurrentEntityRequest", null, entitySelectorEndpoint, in.getMessage().getState())); state.setLocalVariable(SSOConstants.SSO_RESPONSE_VAR_TMP, ssoResponse); state.setLocalVariable(SSOConstants.SSO_RESPONSE_ENDPOINT_VAR_TMP, destination); return; } else { if (logger.isDebugEnabled()) logger.debug("Multipel IdPs found, but no callback idp selection service is avaiable"); } } // --------------------------------------------------- // Send SPAuthnResponse // --------------------------------------------------- out.setMessage(new MediationMessageImpl(ssoResponse.getID(), ssoResponse, "SPAuthnResponse", null, destination, in.getMessage().getState())); exchange.setOut(out); } protected EndpointDescriptor resolveIdPSelectorCallbackEndpoint(CamelMediationExchange exchange, FederationChannel fChannel) throws SSOException { try { if(logger.isDebugEnabled()) logger.debug("Looking for " + SSOService.IdPSelectorCallbackService.toString() + " on channel " + fChannel.getName()); for (IdentityMediationEndpoint endpoint : fChannel.getEndpoints()) { if (logger.isDebugEnabled()) logger.debug("Processing endpoint : " + endpoint.getType() + "["+endpoint.getBinding()+"]"); if (endpoint.getType().equals(SSOService.IdPSelectorCallbackService.toString())) { if (endpoint.getBinding().equals(SSOBinding.SSO_ARTIFACT.getValue())) { // This is the endpoint we're looking for return fChannel.getIdentityMediator().resolveEndpoint(fChannel, endpoint); } } } } catch (IdentityMediationException e) { throw new SSOException(e); } return null; } /** * Build an AuthnResponse . */ protected SPAuthnResponseType buildSPAuthnResponseType(CamelMediationExchange exchange, SPInitiatedAuthnRequestType ssoAuthRequest, SPSecurityContext spSecurityContext, EndpointDescriptor ed ) throws IdentityPlanningException, SSOException { IdentityPlan identityPlan = findIdentityPlanOfType(SamlR2AuthnResponseToSPAuthnResponse.class); IdentityPlanExecutionExchange idPlanExchange = createIdentityPlanExecutionExchange(); // Publish IdP Metadata idPlanExchange.setProperty(VAR_DESTINATION_ENDPOINT_DESCRIPTOR, ed); idPlanExchange.setProperty(VAR_COT_MEMBER, ((IdPChannel)channel).getMember()); idPlanExchange.setProperty(VAR_SSO_AUTHN_REQUEST, ssoAuthRequest); if (spSecurityContext != null) idPlanExchange.setTransientProperty(VAR_SECURITY_CONTEXT, spSecurityContext); //idPlanExchange.setProperty(VAR_RESPONSE_CHANNEL, ((IdPChannel)channel)); // Get SPInitiated authn request, if any! ResponseType authnResponse = (ResponseType) ((CamelMediationMessage) exchange.getIn()).getMessage().getContent(); // Create in/out artifacts IdentityArtifact in = new IdentityArtifactImpl(new QName(SAMLR2Constants.SAML_PROTOCOL_NS, "Response"), authnResponse); idPlanExchange.setIn(in); IdentityArtifact<SPAuthnResponseType> out = new IdentityArtifactImpl<SPAuthnResponseType>(new QName(SSOConstants.SSO_PROTOCOL_NS, "AuthnResponse"), new SPAuthnResponseType()); idPlanExchange.setOut(out); // Prepare execution identityPlan.prepare(idPlanExchange); // Perform execution identityPlan.perform(idPlanExchange); if (!idPlanExchange.getStatus().equals(IdentityPlanExecutionStatus.SUCCESS)) { throw new SSOException("Identity plan returned : " + idPlanExchange.getStatus()); } if (idPlanExchange.getOut() == null) throw new SSOException("Plan Exchange OUT must not be null!"); SPAuthnResponseType ssoResponse = (SPAuthnResponseType) idPlanExchange.getOut().getContent(); ssoResponse.setIssuer(getProvider().getName()); return ssoResponse; } private Subject buildSubjectFromResponse(ResponseType response) { Subject outSubject = new Subject(); String issuerAlias = response.getIssuer().getValue(); FederatedProvider issuer = getCotManager().lookupFederatedProviderByAlias(issuerAlias); if (response.getAssertionOrEncryptedAssertion().size() > 0) { AssertionType assertion = null; if (response.getAssertionOrEncryptedAssertion().get(0) instanceof AssertionType) { assertion = (AssertionType) response.getAssertionOrEncryptedAssertion().get(0); } else { throw new RuntimeException("Response should be already decripted!"); } // store subject identification information if (assertion.getSubject() != null) { List subjectContentItems = assertion.getSubject().getContent(); for (Object o: subjectContentItems) { JAXBElement subjectContent = (JAXBElement) o; if (subjectContent.getValue() instanceof NameIDType ) { NameIDType nameId = (NameIDType) subjectContent.getValue(); // Create Subject ID Attribute if (logger.isDebugEnabled()) { logger.debug("Adding NameID to IDP Subject {"+nameId.getSPNameQualifier()+"}" + nameId.getValue() + ":" + nameId.getFormat()); } outSubject.getPrincipals().add( new SubjectNameID(nameId.getValue(), nameId.getFormat(), nameId.getNameQualifier(), nameId.getSPNameQualifier())); } else if (subjectContent.getValue() instanceof BaseIDAbstractType) { // TODO : Can we do something with this ? throw new IllegalArgumentException("Unsupported Subject BaseID type "+ subjectContent.getValue() .getClass().getName()); } else if (subjectContent.getValue() instanceof EncryptedType) { throw new IllegalArgumentException("Response should be already decripted!"); } else if (subjectContent.getValue() instanceof SubjectConfirmationType) { // TODO : Store subject confirmation data ? } else { logger.error("Unknown subject content type : " + subjectContent.getClass().getName()); } } } // store subject user attributes List<StatementAbstractType> stmts = assertion.getStatementOrAuthnStatementOrAuthzDecisionStatement(); if (logger.isDebugEnabled()) logger.debug("Found " + stmts.size() + " statements") ; for (StatementAbstractType stmt : stmts) { if (logger.isDebugEnabled()) logger.debug("Processing statement " + stmts) ; if (stmt instanceof AttributeStatementType) { AttributeStatementType attrStmt = (AttributeStatementType) stmt; List attrs = attrStmt.getAttributeOrEncryptedAttribute(); if (logger.isDebugEnabled()) logger.debug("Found " + attrs.size() + " attributes in attribute statement") ; for (Object attrOrEncAttr : attrs) { if (attrOrEncAttr instanceof AttributeType) { AttributeType attr = (AttributeType) attrOrEncAttr; List<Object> attributeValues = attr.getAttributeValue(); if (logger.isDebugEnabled()) logger.debug("Processing attribute " + attr.getName()) ; for (Object attributeValue : attributeValues) { if (logger.isDebugEnabled()) logger.debug("Processing attribute value " + attributeValue); if (attributeValue instanceof String) { if (logger.isDebugEnabled()) { logger.debug("Adding String Attribute Statement to IDP Subject " + attr.getName() + ":" + attr.getNameFormat() + "=" + attr.getAttributeValue()); } outSubject.getPrincipals().add( new SubjectAttribute( attr.getName(), (String) attributeValue ) ); } else if (attributeValue instanceof Integer) { if (logger.isDebugEnabled()) { logger.debug("Adding Integer Attribute Value to IDP Subject " + attr.getName() + ":" + attr.getNameFormat() + "=" + attr.getAttributeValue()); } outSubject.getPrincipals().add( new SubjectAttribute( attr.getName(), (Integer) attributeValue ) ); } else if (attributeValue instanceof Element) { Element e = (Element) attributeValue; if (logger.isDebugEnabled()) { logger.debug("Adding Attribute Statement to IDP Subject from DOM Element " + attr.getName() + ":" + attr.getNameFormat() + "=" + e.getTextContent()); } outSubject.getPrincipals().add( new SubjectAttribute( attr.getName(), e.getTextContent() ) ); } else if (attributeValue == null) { if (logger.isDebugEnabled()) { logger.debug("Adding String Attribute Statement to IDP Subject " + attr.getName() + ":" + attr.getNameFormat() + "=" + "null"); } outSubject.getPrincipals().add( new SubjectAttribute( attr.getName(), "" ) ); } else { logger.error("Unknown Attribute Value type " + attributeValue.getClass().getName()); } } } else { // TODO : Decrypt attribute using IDP's encryption key logger.debug("Unknown attribute type " + attrOrEncAttr); } } } // store subject authentication attributes if (stmt instanceof AuthnStatementType) { AuthnStatementType authnStmt = (AuthnStatementType) stmt; List<JAXBElement<?>> authnContextItems = authnStmt.getAuthnContext().getContent(); for (JAXBElement<?> authnContext : authnContextItems) { if (logger.isDebugEnabled()) { logger.debug("Adding Authentiation Context to IDP Subject " + authnContext.getValue() + ":" + SubjectAuthenticationAttribute.Name.AUTHENTICATION_CONTEXT) ; } outSubject.getPrincipals().add( new SubjectAuthenticationAttribute( SubjectAuthenticationAttribute.Name.AUTHENTICATION_CONTEXT, (String) authnContext.getValue() ) ); } if (authnStmt.getAuthnInstant() != null) { if (logger.isDebugEnabled()) { logger.debug("Adding Authentiation Attribute to IDP Subject " + authnStmt.getAuthnInstant().toString() + ":" + SubjectAuthenticationAttribute.Name.AUTHENTICATION_INSTANT) ; } outSubject.getPrincipals().add( new SubjectAuthenticationAttribute( SubjectAuthenticationAttribute.Name.AUTHENTICATION_INSTANT, authnStmt.getAuthnInstant().toString() ) ); } if (authnStmt.getSessionIndex() != null) { if (logger.isDebugEnabled()) { logger.debug("Adding Authentiation Attribute to IDP Subject " + authnStmt.getSessionIndex() + ":" + SubjectAuthenticationAttribute.Name.SESSION_INDEX) ; } outSubject.getPrincipals().add( new SubjectAuthenticationAttribute( SubjectAuthenticationAttribute.Name.SESSION_INDEX, authnStmt.getSessionIndex() ) ); } if (authnStmt.getSessionNotOnOrAfter() != null) { if (logger.isDebugEnabled()) { logger.debug("Adding Authentiation Attribute to IDP Subject " + authnStmt.getSessionNotOnOrAfter().toString() + ":" + SubjectAuthenticationAttribute.Name.SESSION_NOT_ON_OR_AFTER) ; } outSubject.getPrincipals().add( new SubjectAuthenticationAttribute( SubjectAuthenticationAttribute.Name.SESSION_NOT_ON_OR_AFTER, authnStmt.getSessionNotOnOrAfter().toString() ) ); } if (authnStmt.getSubjectLocality() != null && authnStmt.getSubjectLocality().getAddress() != null) { if (logger.isDebugEnabled()) { logger.debug("Adding Authentiation Attribute to IDP Subject " + authnStmt.getSubjectLocality().getAddress() + ":" + SubjectAuthenticationAttribute.Name.SUBJECT_LOCALITY_ADDRESS) ; } outSubject.getPrincipals().add( new SubjectAuthenticationAttribute( SubjectAuthenticationAttribute.Name.SUBJECT_LOCALITY_ADDRESS, authnStmt.getSubjectLocality().getAddress() ) ); } if (authnStmt.getSubjectLocality() != null && authnStmt.getSubjectLocality().getDNSName() != null) { if (logger.isDebugEnabled()) { logger.debug("Adding Authentiation Attribute to IDP Subject " + authnStmt.getSubjectLocality().getDNSName() + ":" + SubjectAuthenticationAttribute.Name.SUBJECT_LOCALITY_DNSNAME) ; } outSubject.getPrincipals().add( new SubjectAuthenticationAttribute( SubjectAuthenticationAttribute.Name.SUBJECT_LOCALITY_DNSNAME, authnStmt.getSubjectLocality().getDNSName() ) ); } } // Store subject authorization attributes if (stmt instanceof AuthzDecisionStatementType) { AuthzDecisionStatementType authzStmt = (AuthzDecisionStatementType) stmt; for (ActionType action : authzStmt.getAction()) { if (action.getNamespace() != null) { if (logger.isDebugEnabled()) { logger.debug("Adding Authz Decision Action NS to IDP Subject " + action.getNamespace() + ":" + SubjectAuthorizationAttribute.Name.ACTION_NAMESPACE) ; } outSubject.getPrincipals().add( new SubjectAuthorizationAttribute( SubjectAuthorizationAttribute.Name.ACTION_NAMESPACE, action.getNamespace() ) ); } if (action.getValue() != null) { if (logger.isDebugEnabled()) { logger.debug("Adding Authz Decision Action Value to IDP Subject " + action.getValue() + ":" + SubjectAuthorizationAttribute.Name.ACTION_VALUE) ; } outSubject.getPrincipals().add( new SubjectAuthorizationAttribute( SubjectAuthorizationAttribute.Name.ACTION_VALUE, action.getValue() ) ); } } if (authzStmt.getDecision() != null) { if (logger.isDebugEnabled()) { logger.debug("Adding Authz Decision Action to IDP Subject " + authzStmt.getDecision().value() + ":" + SubjectAuthorizationAttribute.Name.DECISION) ; } outSubject.getPrincipals().add( new SubjectAuthorizationAttribute( SubjectAuthorizationAttribute.Name.DECISION, authzStmt.getDecision().value() ) ); } if (authzStmt.getResource() != null) { if (logger.isDebugEnabled()) { logger.debug("Adding Authz Decision Action to IDP Subject " + authzStmt.getResource() + ":" + SubjectAuthorizationAttribute.Name.RESOURCE) ; } outSubject.getPrincipals().add( new SubjectAuthorizationAttribute( SubjectAuthorizationAttribute.Name.RESOURCE, authzStmt.getResource() ) ); } // TODO: store evidence } } } else { logger.warn("No Assertion present within Response [" + response.getID() + "]"); } SubjectAttribute idpAliasAttr = new SubjectAttribute("urn:org:atricore:idbus:sso:sp:idpAlias", issuerAlias); SubjectAttribute idpNameAttr = new SubjectAttribute("urn:org:atricore:idbus:sso:sp:idpName", issuer.getName()); outSubject.getPrincipals().add(idpNameAttr); outSubject.getPrincipals().add(idpAliasAttr); if (outSubject != null && logger.isDebugEnabled()) { logger.debug("IDP Subject:" + outSubject) ; } return outSubject; } // TODO : Reuse basic SAML request validations .... protected ResponseType validateResponse(AuthnRequestType request, ResponseType response, String originalResponse, MediationState state) throws SSOResponseException, SSOException { AbstractSSOMediator mediator = (AbstractSSOMediator) channel.getIdentityMediator(); SamlR2Signer signer = mediator.getSigner(); SamlR2Encrypter encrypter = mediator.getEncrypter(); // Metadata from the IDP String idpAlias = null; IDPSSODescriptorType idpMd = null; // Request can be null for IDP initiated SSO EndpointDescriptor endpointDesc; try { endpointDesc = channel.getIdentityMediator().resolveEndpoint(channel, endpoint); } catch (IdentityMediationException e1) { logger.error(e1.getMessage(), e1); throw new SSOResponseException(response, StatusCode.TOP_REQUESTER, StatusCode.RESOURCE_NOT_RECOGNIZED, StatusDetails.INTERNAL_ERROR, "Cannot resolve endpoint descriptor", e1); } try { idpAlias = response.getIssuer().getValue(); MetadataEntry md = getCotManager().findEntityMetadata(idpAlias); EntityDescriptorType saml2Md = (EntityDescriptorType) md.getEntry(); boolean found = false; for (RoleDescriptorType roleMd : saml2Md.getRoleDescriptorOrIDPSSODescriptorOrSPSSODescriptor()) { if (roleMd instanceof IDPSSODescriptorType) { idpMd = (IDPSSODescriptorType) roleMd; } } } catch (CircleOfTrustManagerException e) { logger.error(e.getMessage(), e); throw new SSOResponseException(response, StatusCode.TOP_RESPONDER, StatusCode.NO_SUPPORTED_IDP, null, response.getIssuer().getValue(), e); } if (idpMd == null) { logger.debug("No IDP Metadata found"); // Unknown IDP! throw new SSOResponseException(response, StatusCode.TOP_RESPONDER, StatusCode.NO_SUPPORTED_IDP, null, idpAlias); } // -------------------------------------------------------- // Validate response: // -------------------------------------------------------- // Destination //saml2 binding, sections 3.4.5.2 & 3.5.5.2 if(response.getDestination() != null) { //saml2 core, section 3.2.2 String location = endpointDesc.getResponseLocation(); if (location ==null) location = endpointDesc.getLocation(); if(!response.getDestination().equals(location)){ throw new SSOResponseException(response, StatusCode.TOP_REQUESTER, StatusCode.REQUEST_DENIED, StatusDetails.INVALID_DESTINATION); } } else if(response.getSignature() != null && (!endpointDesc.getBinding().equals(SSOBinding.SAMLR2_LOCAL.getValue()) && !endpointDesc.getBinding().equals(SSOBinding.SAMLR2_ARTIFACT.getValue()) && !endpointDesc.getBinding().equals(SSOBinding.SAMLR2_REDIRECT.getValue()) )) { // If message is signed, the destination is mandatory! //saml2 binding, sections 3.4.5.2 & 3.5.5.2 throw new SSOResponseException(response, StatusCode.TOP_REQUESTER, StatusCode.REQUEST_DENIED, StatusDetails.NO_DESTINATION); } else if(endpointDesc.getBinding().equals(SSOBinding.SAMLR2_REDIRECT.getValue()) && state.getTransientVariable("Signature") != null) { // If message is signed, the destination is mandatory! //saml2 binding, sections 3.4.5.2 & 3.5.5.2 throw new SSOResponseException(response, StatusCode.TOP_REQUESTER, StatusCode.REQUEST_DENIED, StatusDetails.NO_DESTINATION); } // IssueInstant /* - required - check that the response time is not before request time (use UTC) - check that time difference is not bigger than X */ if(response.getIssueInstant() == null){ throw new SSOResponseException(response, StatusCode.TOP_REQUESTER, StatusCode.INVALID_ATTR_NAME_OR_VALUE, StatusDetails.NO_ISSUE_INSTANT); } else if(request != null) { long responseIssueInstant = response.getIssueInstant().toGregorianCalendar().getTimeInMillis(); long requestIssueInstant = request.getIssueInstant().toGregorianCalendar().getTimeInMillis(); long tolerance = mediator.getTimestampValidationTolerance(); // You can't have a request emitted before 'tolerance' millisenconds if(responseIssueInstant - requestIssueInstant <= tolerance * -1) { throw new SSOResponseException(response, StatusCode.TOP_REQUESTER, StatusCode.INVALID_ATTR_NAME_OR_VALUE, StatusDetails.INVALID_ISSUE_INSTANT, response.getIssueInstant().toGregorianCalendar().toString() + " earlier than request issue instant."); } else { long ttl = mediator.getRequestTimeToLive(); long res = response.getIssueInstant().toGregorianCalendar().getTime().getTime(); long req = request.getIssueInstant().toGregorianCalendar().getTime().getTime(); if (logger.isDebugEnabled()) logger.debug("TTL : " + res + " - " + req + " = " + (res - req)); // If 0, response does not expires! if(ttl > 0 && response.getIssueInstant().toGregorianCalendar().getTime().getTime() - request.getIssueInstant().toGregorianCalendar().getTime().getTime() > ttl) { throw new SSOResponseException(response, StatusCode.TOP_REQUESTER, StatusCode.INVALID_ATTR_NAME_OR_VALUE, StatusDetails.INVALID_ISSUE_INSTANT, response.getIssueInstant().toGregorianCalendar().toString() + " expired after " + ttl + "ms"); } } } // Version, saml2 core, section 3.2.2 if(response.getVersion() == null) { throw new SSOResponseException(response, StatusCode.TOP_VERSION_MISSMATCH, null, StatusDetails.INVALID_VERSION); } if (!response.getVersion().equals(SAML_VERSION)){ throw new SSOResponseException(response, StatusCode.TOP_VERSION_MISSMATCH, null, // TODO : Check version! StatusDetails.UNSUPPORTED_VERSION, response.getVersion()); } // InResponseTo, saml2 core, section 3.2.2 // Request can be null for IDP initiated SSO if(request != null && response.getInResponseTo() != null) { // A second IDP-initiated request might have been triggered after an SP-initiated one. // The response for the IDP-initiated request should be honoured even if does not correspond with the // first authentication request. This condition is triggered in the identity confirmation usage scenario. // /* if (response.getInResponseTo() == null) { throw new SSOResponseException(response, StatusCode.TOP_REQUESTER, null, StatusDetails.NO_IN_RESPONSE_TO); } else */ if (!request.getID().equals(response.getInResponseTo())) { throw new SSOResponseException(response, StatusCode.TOP_REQUESTER, null, StatusDetails.INVALID_RESPONSE_ID, request.getID() + "/ " + response.getInResponseTo()); } } // Status.StatusDetails if(response.getStatus() != null) { if(response.getStatus().getStatusCode() != null) { if(StringUtils.isEmpty(response.getStatus().getStatusCode().getValue()) || !isStatusCodeValid(response.getStatus().getStatusCode().getValue())){ throw new SSOResponseException(response, StatusCode.TOP_REQUESTER, StatusCode.INVALID_ATTR_NAME_OR_VALUE, StatusDetails.INVALID_STATUS_CODE, response.getStatus().getStatusCode().getValue()); } } else { throw new SSOResponseException(response, StatusCode.TOP_REQUESTER, StatusCode.INVALID_ATTR_NAME_OR_VALUE, StatusDetails.NO_STATUS_CODE); } } else { throw new SSOResponseException(response, StatusCode.TOP_REQUESTER, StatusCode.INVALID_ATTR_NAME_OR_VALUE, StatusDetails.NO_STATUS); } // XML Signature, saml2 core, section 5 (always validate response signatures // HTTP-Redirect binding does not support embedded signatures if (!endpoint.getBinding().equals(SSOBinding.SAMLR2_REDIRECT.getValue())) { // If there are no assertions, response MUST be signed if (response.getSignature() == null && (response.getAssertionOrEncryptedAssertion() == null || response.getAssertionOrEncryptedAssertion().size() == 0)) { // Redirect binding does not have signature elements! throw new SSOResponseException(response, StatusCode.TOP_REQUESTER, StatusCode.REQUEST_DENIED, StatusDetails.INVALID_RESPONSE_SIGNATURE); } // If there's no signature, it means that there are assertions if (response.getSignature() != null) { try { // It's better to validate the original message, when available. if (originalResponse != null) signer.validateDom(idpMd, originalResponse); else signer.validate(idpMd, response, "Response"); } catch (SamlR2SignatureValidationException e) { logger.error(e.getMessage(), e); throw new SSOResponseException(response, StatusCode.TOP_REQUESTER, StatusCode.REQUEST_DENIED, StatusDetails.INVALID_RESPONSE_SIGNATURE, e); } catch (SamlR2SignatureException e) { //other exceptions like JAXB, xml parser... logger.error(e.getMessage(), e); throw new SSOResponseException(response, StatusCode.TOP_REQUESTER, StatusCode.REQUEST_DENIED, StatusDetails.INVALID_RESPONSE_SIGNATURE, e); } } } else { // HTTP-Redirect binding signature validation ! try { signer.validateQueryString(idpMd, state.getTransientVariable("SAMLResponse"), state.getTransientVariable("RelayState"), state.getTransientVariable("SigAlg"), state.getTransientVariable("Signature"), true); } catch (SamlR2SignatureValidationException e) { logger.error(e.getMessage(), e); throw new SSOResponseException(response, StatusCode.TOP_REQUESTER, StatusCode.REQUEST_DENIED, StatusDetails.INVALID_RESPONSE_SIGNATURE, e); } catch (SamlR2SignatureException e) { //other exceptions like JAXB, xml parser... logger.error(e.getMessage(), e); throw new SSOResponseException(response, StatusCode.TOP_REQUESTER, StatusCode.REQUEST_DENIED, StatusDetails.INVALID_RESPONSE_SIGNATURE, e); } } // -------------------------------------------------------- // Validate also assertion contained in response! // -------------------------------------------------------- AssertionType assertion = null; // Decrypt if encrypted List assertionObjects = response.getAssertionOrEncryptedAssertion(); for (Object assertionObject : assertionObjects) { if(assertionObject instanceof AssertionType){ assertion = (AssertionType) assertionObject; } else if(assertionObject instanceof EncryptedElementType){ try { assertion = encrypter.decryptAssertion((EncryptedElementType)assertionObject); } catch (SamlR2EncrypterException e) { logger.error(e.getMessage(), e); throw new SSOResponseException(response, StatusCode.TOP_REQUESTER, StatusCode.REQUEST_DENIED, StatusDetails.INVALID_ASSERTION_ENCRYPTION, e); } } // XML Signature, saml core, section 5 /* NOT WORKING OK ... */ SPSSODescriptorType saml2SpMd = null; try { MetadataEntry spMd = getCotManager().findEntityRoleMetadata(getCotMemberDescriptor().getAlias(), "urn:oasis:names:tc:SAML:2.0:metadata:SPSSODescriptor"); saml2SpMd = (SPSSODescriptorType) spMd.getEntry(); } catch (CircleOfTrustManagerException e) { //other exceptions like JAXB, xml parser... logger.error(e.getMessage(), e); throw new SSOResponseException(response, StatusCode.TOP_REQUESTER, StatusCode.REQUEST_DENIED, StatusDetails.INTERNAL_ERROR, e); } // If the response does not have a signature, assertions MUST be signed, otherwise relay on MD configuration to require a signature if( // Do we required signed assertions ? (saml2SpMd.getWantAssertionsSigned() != null && saml2SpMd.getWantAssertionsSigned()) || // Does response have a signature for bindings that require it ? (response.getSignature() == null && !endpointDesc.getBinding().equals(SSOBinding.SAMLR2_REDIRECT.getValue()) && !endpointDesc.getBinding().equals(SSOBinding.SAMLR2_LOCAL.getValue()) && !endpointDesc.getBinding().equals(SSOBinding.SAMLR2_ARTIFACT.getValue())) || // Does redirect binding have an outbound signature ? (state.getTransientVariable("Signature") == null && endpointDesc.getBinding().equals(SSOBinding.SAMLR2_REDIRECT.getValue())) ) { if (assertion.getSignature() == null) { throw new SSOResponseException(response, StatusCode.TOP_REQUESTER, StatusCode.REQUEST_DENIED, StatusDetails.INVALID_ASSERTION_SIGNATURE); } try { if (originalResponse != null) signer.validateDom(idpMd, originalResponse, assertion.getID()); else signer.validate(idpMd, assertion); } catch (SamlR2SignatureValidationException e) { logger.error(e.getMessage(), e); throw new SSOResponseException(response, StatusCode.TOP_REQUESTER, StatusCode.REQUEST_DENIED, StatusDetails.INVALID_ASSERTION_SIGNATURE, e); } catch (SamlR2SignatureException e) { logger.error(e.getMessage(), e); throw new SSOResponseException(response, StatusCode.TOP_REQUESTER, StatusCode.REQUEST_DENIED, StatusDetails.INVALID_ASSERTION_SIGNATURE, e); } } // Conditions // - optional validateAssertionConditions(response, assertion.getConditions()); // Subject, saml2 core, sections 2.3.3 & 2.7.2 if(assertion.getSubject() != null){ for (JAXBElement object : assertion.getSubject().getContent()) { Object subjectContent = object.getValue(); if(subjectContent instanceof SubjectConfirmationType){ SubjectConfirmationType subConf = (SubjectConfirmationType)subjectContent; if(subConf.getMethod() == null){ throw new SSOResponseException(response, StatusCode.TOP_REQUESTER, StatusCode.INVALID_ATTR_NAME_OR_VALUE, StatusDetails.NO_METHOD); } // saml2 core, section 2.4.1.2 if(subConf.getSubjectConfirmationData() != null){ SubjectConfirmationDataType scData = subConf.getSubjectConfirmationData(); if(assertion.getConditions() != null){ logger.debug("scData.getNotBefore(): " + scData.getNotBefore()); logger.debug("assertion.getConditions().getNotBefore()" + assertion.getConditions().getNotBefore()); if(scData.getNotBefore() != null && assertion.getConditions().getNotBefore() != null && scData.getNotBefore().normalize().compare(assertion.getConditions().getNotBefore().normalize()) < 0){ logger.warn("SubjectConfirmationData.NotBefore value SHOULD not be earlier than Conditions.NotBefore."); // TODO : Should be configurable : throw new SSOResponseException("SubjectConfirmationData.NotBefore value SHOULD not be earlier than Conditions.NotBefore."); } logger.debug("scData.getNotOnOrAfter(): " + scData.getNotOnOrAfter()); logger.debug("assertion.getConditions().getNotOnOrAfter()" + assertion.getConditions().getNotOnOrAfter()); if(scData.getNotOnOrAfter() != null && assertion.getConditions().getNotOnOrAfter() != null && scData.getNotOnOrAfter().normalize().compare(assertion.getConditions().getNotOnOrAfter().normalize()) > 0){ // TODO : Should be configurable : throw new SSOResponseException("SubjectConfirmationData.NotOnOrAfter value SHOULD not be later than Conditions.NotOnOrAfter."); logger.warn("SubjectConfirmationData.NotOnOrAfter value SHOULD not be later than Conditions.NotOnOrAfter."); } } if(scData.getNotBefore() != null && scData.getNotOnOrAfter() != null && scData.getNotOnOrAfter().normalize().compare(scData.getNotBefore().normalize()) < 0){ // TODO : Should be configurable : throw new SSOResponseException("SubjectConfirmationData.NotBefore value SHOULD be earlier than SubjectConfirmationData.NotOnOrAfter."); logger.warn("SubjectConfirmationData.NotBefore value SHOULD be earlier than SubjectConfirmationData.NotOnOrAfter."); } } } } } else if (assertion.getStatementOrAuthnStatementOrAuthzDecisionStatement() == null || assertion.getStatementOrAuthnStatementOrAuthzDecisionStatement().size() == 0){ throw new SSOResponseException(response, StatusCode.TOP_REQUESTER, StatusCode.INVALID_ATTR_NAME_OR_VALUE, StatusDetails.NO_SUBJECT); } else if (getAuthnStatements(assertion).size() != 0){ throw new SSOResponseException(response, StatusCode.TOP_REQUESTER, StatusCode.INVALID_ATTR_NAME_OR_VALUE, StatusDetails.NO_SUBJECT); } // AuthnStatement, saml2 core, section 2.7.2 List<AuthnStatementType> authnStatementList = getAuthnStatements(assertion); if(authnStatementList.size() != 0){ for (AuthnStatementType statement : authnStatementList) { if(statement.getAuthnInstant() == null){ throw new SSOResponseException(response, StatusCode.TOP_REQUESTER, StatusCode.INVALID_ATTR_NAME_OR_VALUE, StatusDetails.NO_AUTHN_INSTANT); } if(statement.getAuthnContext() == null){ throw new SSOResponseException(response, StatusCode.TOP_REQUESTER, StatusCode.INVALID_ATTR_NAME_OR_VALUE, StatusDetails.NO_AUTHN_CONTEXT); } } } } if (mediator.isVerifyUniqueIDs() && mediator.getIdRegistry().isUsed(response.getID())) { if (logger.isDebugEnabled()) logger.debug("Duplicated SAML ID " + response.getID()); throw new SSOResponseException(response, StatusCode.TOP_REQUESTER, StatusCode.REQUEST_DENIED, StatusDetails.DUPLICATED_ID ); } return response; } /* * Saml2 core, section 2.5.1 */ private void validateAssertionConditions(ResponseType response, ConditionsType conditions) throws SSOException, SSOResponseException { if (conditions == null) return; long tolerance = ((AbstractSSOMediator)channel.getIdentityMediator()).getTimestampValidationTolerance(); Calendar utcCalendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")); if(conditions.getConditionOrAudienceRestrictionOrOneTimeUse() == null && conditions.getNotBefore() == null && conditions.getNotOnOrAfter() == null){ return; } logger.debug("Current time (UTC): " + utcCalendar.toString()); XMLGregorianCalendar notBeforeUTC = null; XMLGregorianCalendar notOnOrAfterUTC = null; if(conditions.getNotBefore() != null){ //normalize to UTC logger.debug("Conditions.NotBefore: " + conditions.getNotBefore()); notBeforeUTC = conditions.getNotBefore().normalize(); logger.debug("Conditions.NotBefore normalized: " + notBeforeUTC.toString()); if(!notBeforeUTC.isValid()){ throw new SSOResponseException(response, StatusCode.TOP_REQUESTER, StatusCode.INVALID_ATTR_NAME_OR_VALUE, StatusDetails.INVALID_UTC_VALUE, notBeforeUTC.toString()); } else { Calendar notBefore = notBeforeUTC.toGregorianCalendar(); notBefore.add(Calendar.MILLISECOND, (int) tolerance * -1); if (utcCalendar.before(notBefore)) throw new SSOResponseException(response, StatusCode.TOP_REQUESTER, StatusCode.INVALID_ATTR_NAME_OR_VALUE, StatusDetails.NOT_BEFORE_VIOLATED, notBeforeUTC.toString()); } } // Make sure that the NOT ON OR AFTER is not violated, give a five minutes tolerance (should be configurable) if(conditions.getNotOnOrAfter() != null){ //normalize to UTC logger.debug("Conditions.NotOnOrAfter: " + conditions.getNotOnOrAfter().toString()); notOnOrAfterUTC = conditions.getNotOnOrAfter().normalize(); logger.debug("Conditions.NotOnOrAfter normalized: " + notOnOrAfterUTC.toString()); if(!notOnOrAfterUTC.isValid()){ throw new SSOResponseException(response, StatusCode.TOP_REQUESTER, StatusCode.INVALID_ATTR_NAME_OR_VALUE, StatusDetails.INVALID_UTC_VALUE, notOnOrAfterUTC.toString()); } else { // diff in millis Calendar notOnOrAfter = notOnOrAfterUTC.toGregorianCalendar(); notOnOrAfter.add(Calendar.MILLISECOND, (int) tolerance); if (utcCalendar.after(notOnOrAfter)) throw new SSOResponseException(response, StatusCode.TOP_REQUESTER, StatusCode.INVALID_ATTR_NAME_OR_VALUE, StatusDetails.NOT_ONORAFTER_VIOLATED, notOnOrAfterUTC.toString()); } } if(notBeforeUTC != null && notOnOrAfterUTC != null && notOnOrAfterUTC.compare(notBeforeUTC) <= 0) { throw new SSOResponseException(response, StatusCode.TOP_REQUESTER, StatusCode.INVALID_ATTR_NAME_OR_VALUE, StatusDetails.INVALID_CONDITION, "'Not On or After' earlier that 'Not Before'"); } // Our SAMLR2 Enityt ID should be part of the audience CircleOfTrustMemberDescriptor sp = this.getCotMemberDescriptor(); MetadataEntry spMd = sp.getMetadata(); if (spMd == null || spMd.getEntry() == null) throw new SSOException("No metadata descriptor found for SP " + sp); EntityDescriptorType md = null; if (spMd.getEntry() instanceof EntityDescriptorType) { md = (EntityDescriptorType) spMd.getEntry(); } else throw new SSOException("Unsupported Metadata type " + md + ", SAML 2 Metadata expected"); if(conditions.getConditionOrAudienceRestrictionOrOneTimeUse() != null){ boolean audienceRestrictionValid = false; boolean spInAllAudiences = false; boolean initState = true; for (ConditionAbstractType conditionAbs : conditions.getConditionOrAudienceRestrictionOrOneTimeUse()) { if(conditionAbs instanceof AudienceRestrictionType){ AudienceRestrictionType audienceRestriction = (AudienceRestrictionType) conditionAbs; if(audienceRestriction.getAudience() != null){ boolean spInAudience = false; for (String audience : audienceRestriction.getAudience()) { if(audience.equals(md.getEntityID())){ spInAudience = true; break; } } spInAllAudiences = (initState ? spInAudience : spInAllAudiences && spInAudience ); initState = false; } } audienceRestrictionValid = audienceRestrictionValid || spInAllAudiences; } if(!audienceRestrictionValid){ logger.error("SP is not in Audience list."); throw new SSOResponseException(response, StatusCode.TOP_REQUESTER, StatusCode.INVALID_ATTR_NAME_OR_VALUE, StatusDetails.NOT_IN_AUDIENCE); } } } private Calendar getSessionNotOnOrAfter(AssertionType assertion) { XMLGregorianCalendar sessionNotOnOrAfter = null; List<AuthnStatementType> authnStatements = getAuthnStatements(assertion); for (AuthnStatementType authnStatement : authnStatements) { if (authnStatement.getSessionNotOnOrAfter() != null) { if (sessionNotOnOrAfter == null) sessionNotOnOrAfter = authnStatement.getSessionNotOnOrAfter(); else if (sessionNotOnOrAfter.compare(authnStatement.getSessionNotOnOrAfter()) == DatatypeConstants.LESSER) { sessionNotOnOrAfter = authnStatement.getSessionNotOnOrAfter(); } } } if (sessionNotOnOrAfter != null) return sessionNotOnOrAfter.toGregorianCalendar(); return null; } private List<AuthnStatementType> getAuthnStatements(AssertionType assertion){ ArrayList<AuthnStatementType> statementsList = new ArrayList<AuthnStatementType>(); if (assertion.getStatementOrAuthnStatementOrAuthzDecisionStatement() != null && assertion.getStatementOrAuthnStatementOrAuthzDecisionStatement().size() != 0){ for (Object statement : assertion.getStatementOrAuthnStatementOrAuthzDecisionStatement()) { if(statement instanceof AuthnStatementType){ statementsList.add((AuthnStatementType)statement); } } } return statementsList; } protected CircleOfTrustMemberDescriptor resolveIdp(CamelMediationExchange exchange) throws SSOException { CamelMediationMessage in = (CamelMediationMessage) exchange.getIn(); ResponseType response = (ResponseType) in.getMessage().getContent(); String idpAlias = response.getIssuer().getValue(); if (logger.isDebugEnabled()) logger.debug("IdP alias received " + idpAlias); if (idpAlias == null) { throw new SSOException("No IDP available"); } CircleOfTrustMemberDescriptor idp = this.getCotManager().lookupMemberByAlias(idpAlias); if (idp == null) { throw new SSOException("No IDP Member descriptor available for " + idpAlias); } return idp; } protected SPSecurityContext createSPSecurityContext(CamelMediationExchange exchange, String requester, CircleOfTrustMemberDescriptor idp, AccountLink acctLink, Subject federatedSubject, Subject idpSubject, AssertionType assertion) throws SSOException { if (logger.isDebugEnabled()) logger.debug("Creating new SP Security Context for subject " + federatedSubject); IdPChannel idPChannel = (IdPChannel) channel; SSOSessionManager ssoSessionManager = idPChannel.getSessionManager(); CamelMediationMessage in = (CamelMediationMessage) exchange.getIn(); // Remove previous security context if any SPSecurityContext secCtx = (SPSecurityContext) in.getMessage().getState().getLocalVariable(getProvider().getName().toUpperCase() + "_SECURITY_CTX"); if (secCtx != null) { if (logger.isDebugEnabled()) logger.debug("Invalidating old sso session " + secCtx.getSessionIndex()); try { ssoSessionManager.invalidate(secCtx.getSessionIndex()); } catch (NoSuchSessionException e) { // Ignore this ... if (logger.isDebugEnabled()) logger.debug("Invalidating already expired sso session " + secCtx.getSessionIndex()); } catch (SSOSessionException e) { throw new SSOException(e); } } // Get Subject ID (username ?) SubjectNameID nameId = null; Set<SubjectNameID> nameIds = federatedSubject.getPrincipals(SubjectNameID.class); if (nameIds != null && nameIds.size() > 0) { nameId = nameIds.iterator().next(); if (logger.isTraceEnabled()) logger.trace("Using Subject ID " + nameId.getName() + "[" + nameId.getFormat() + "] "); /* Old logic, serched for UNSPECIFIED Subject Name ID: for (SubjectNameID i : nameIds) { if (logger.isTraceEnabled()) logger.trace("Checking Subject ID " + i.getName() + "["+i.getFormat()+"] "); // TODO : Support other name ID formats !!! if (i.getFormat() == null || i.getFormat().equals(NameIDFormat.UNSPECIFIED.getValue())) { nameId = i; break; } } */ } if (nameId == null) { logger.error("No suitable Subject Name Identifier (SubjectNameID) found"); throw new SSOException("No suitable Subject Name Identifier (SubjectNameID) found"); } String idpSessionIndex = null; Collection<SubjectAuthenticationAttribute> authnAttrs = idpSubject.getPrincipals(SubjectAuthenticationAttribute.class); for (SubjectAuthenticationAttribute authnAttr : authnAttrs) { if (authnAttr.getName().equals(SubjectAuthenticationAttribute.Name.SESSION_INDEX.name())) { idpSessionIndex = authnAttr.getValue(); break; } } AuthnCtxClass authnCtx = null; for (StatementAbstractType stmt : assertion.getStatementOrAuthnStatementOrAuthzDecisionStatement()) { if (stmt instanceof AuthnStatementType) { AuthnStatementType authnStmt = (AuthnStatementType) stmt; for (JAXBElement e : authnStmt.getAuthnContext().getContent()) { if (e.getName().getLocalPart().equals("AuthnContextClassRef")) { authnCtx = AuthnCtxClass.asEnum((String) e.getValue()); break; } } break; } } // Create a new Security Context secCtx = new SPSecurityContext(); secCtx.setIdpAlias(idp.getAlias()); secCtx.setIdpSsoSession(idpSessionIndex); secCtx.setSubject(federatedSubject); secCtx.setAccountLink(acctLink); secCtx.setRequester(requester); secCtx.setAuthnCtxClass(authnCtx); SecurityToken<SPSecurityContext> token = new SecurityTokenImpl<SPSecurityContext>(uuidGenerator.generateId(), secCtx); try { // Create new SSO Session // Take session timeout from the assertion, if available. Calendar sessionExpiration = getSessionNotOnOrAfter(assertion); long sessionTimeout = 0; if (sessionExpiration != null) { sessionTimeout = (sessionExpiration.getTimeInMillis() - System.currentTimeMillis()) / 1000L; } String ssoSessionId = (sessionTimeout > 0 ? ssoSessionManager.initiateSession(nameId.getName(), token, (int) sessionTimeout) : // Request session timeout ssoSessionManager.initiateSession(nameId.getName(), token)); // Use default session timeout if (logger.isTraceEnabled()) logger.trace("Created SP SSO Session with id " + ssoSessionId); // Update security context with SSO Session ID secCtx.setSessionIndex(ssoSessionId); Set<SubjectAuthenticationAttribute> attrs = idpSubject.getPrincipals(SubjectAuthenticationAttribute.class); String idpSsoSessionId = null; for (SubjectAuthenticationAttribute attr : attrs) { // Session index if (attr.getName().equals(SubjectAuthenticationAttribute.Name.SESSION_INDEX.name())) { idpSsoSessionId = attr.getValue(); break; } } // SubjectAuthenticationAttribute.Name.SESSION_NOT_ON_OR_AFTER if (logger.isDebugEnabled()) logger.debug("Created SP security context " + secCtx); in.getMessage().getState().setLocalVariable(getProvider().getName().toUpperCase() + "_SECURITY_CTX", secCtx); in.getMessage().getState().getLocalState().addAlternativeId("ssoSessionId", secCtx.getSessionIndex()); in.getMessage().getState().getLocalState().addAlternativeId("idpSsoSessionId", idpSsoSessionId); if (logger.isTraceEnabled()) logger.trace("Stored SP Security Context in " + getProvider().getName().toUpperCase() + "_SECURITY_CTX"); return secCtx; } catch (SSOSessionException e) { throw new SSOException(e); } } }