com.alfaariss.oa.authentication.remote.saml2.profile.sso.WebBrowserSSOProfile.java Source code

Java tutorial

Introduction

Here is the source code for com.alfaariss.oa.authentication.remote.saml2.profile.sso.WebBrowserSSOProfile.java

Source

/*
 * Asimba - Serious Open Source SSO
 * 
 * Copyright (C) 2012 Asimba
 * Copyright (C) 2007-2011 Alfa & Ariss B.V.
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program 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 Affero General Public License for more details.
 * 
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see www.gnu.org/licenses
 * 
 * Asimba - Serious Open Source SSO - More information on www.asimba.org
 * 
 */
package com.alfaariss.oa.authentication.remote.saml2.profile.sso;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Hashtable;
import java.util.List;
import java.util.Vector;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.asimba.util.saml2.assertion.SAML2TimestampWindow;
import org.joda.time.DateTime;
import org.opensaml.common.SAMLObject;
import org.opensaml.common.SignableSAMLObject;
import org.opensaml.common.binding.SAMLMessageContext;
import org.opensaml.common.binding.encoding.SAMLMessageEncoder;
import org.opensaml.common.impl.SecureRandomIdentifierGenerator;
import org.opensaml.common.xml.SAMLConstants;
import org.opensaml.saml2.core.ArtifactResponse;
import org.opensaml.saml2.core.Assertion;
import org.opensaml.saml2.core.Audience;
import org.opensaml.saml2.core.AudienceRestriction;
import org.opensaml.saml2.core.AuthenticatingAuthority;
import org.opensaml.saml2.core.AuthnContext;
import org.opensaml.saml2.core.AuthnContextClassRef;
import org.opensaml.saml2.core.AuthnContextComparisonTypeEnumeration;
import org.opensaml.saml2.core.AuthnRequest;
import org.opensaml.saml2.core.AuthnStatement;
import org.opensaml.saml2.core.Conditions;
import org.opensaml.saml2.core.EncryptedAssertion;
import org.opensaml.saml2.core.Issuer;
import org.opensaml.saml2.core.NameIDPolicy;
import org.opensaml.saml2.core.NameIDType;
import org.opensaml.saml2.core.RequestedAuthnContext;
import org.opensaml.saml2.core.Response;
import org.opensaml.saml2.core.Scoping;
import org.opensaml.saml2.core.StatusResponseType;
import org.opensaml.saml2.core.Subject;
import org.opensaml.saml2.core.impl.AuthnContextClassRefBuilder;
import org.opensaml.saml2.core.impl.AuthnRequestBuilder;
import org.opensaml.saml2.core.impl.RequestedAuthnContextBuilder;
import org.opensaml.saml2.metadata.AssertionConsumerService;
import org.opensaml.saml2.metadata.Endpoint;
import org.opensaml.saml2.metadata.EntityDescriptor;
import org.opensaml.saml2.metadata.IDPSSODescriptor;
import org.opensaml.saml2.metadata.SPSSODescriptor;
import org.opensaml.saml2.metadata.SingleSignOnService;
import org.opensaml.ws.message.encoder.MessageEncodingException;
import org.opensaml.xml.XMLObject;
import org.opensaml.xml.security.credential.Credential;
import org.w3c.dom.Element;

import com.alfaariss.oa.OAException;
import com.alfaariss.oa.SystemErrors;
import com.alfaariss.oa.UserEvent;
import com.alfaariss.oa.api.attribute.IAttributes;
import com.alfaariss.oa.api.attribute.ISessionAttributes;
import com.alfaariss.oa.api.configuration.IConfigurationManager;
import com.alfaariss.oa.api.idmapper.IIDMapper;
import com.alfaariss.oa.api.requestor.IRequestor;
import com.alfaariss.oa.api.session.ISession;
import com.alfaariss.oa.api.user.IUser;
import com.alfaariss.oa.authentication.remote.saml2.SAML2AuthNConstants;
import com.alfaariss.oa.authentication.remote.saml2.beans.SAMLRemoteUser;
import com.alfaariss.oa.authentication.remote.saml2.profile.AbstractAuthNMethodSAML2Profile;
import com.alfaariss.oa.authentication.remote.saml2.util.ResponseValidator;
import com.alfaariss.oa.engine.core.authentication.AuthenticationContext;
import com.alfaariss.oa.engine.core.authentication.AuthenticationContexts;
import com.alfaariss.oa.engine.core.idp.storage.IIDPStorage;
import com.alfaariss.oa.engine.user.provisioning.translator.standard.StandardProfile;
import com.alfaariss.oa.sso.SSOService;
import com.alfaariss.oa.sso.authentication.web.IWebAuthenticationMethod;
import com.alfaariss.oa.util.saml2.SAML2ConditionsWindow;
import com.alfaariss.oa.util.saml2.SAML2Exchange;
import com.alfaariss.oa.util.saml2.SAML2SecurityException;
import com.alfaariss.oa.util.saml2.binding.AbstractEncodingFactory;
import com.alfaariss.oa.util.saml2.crypto.SAML2CryptoUtils;
import com.alfaariss.oa.util.saml2.idp.SAML2IDP;
import com.alfaariss.oa.util.saml2.proxy.ProxyAttributes;

/**
 * Handler for the SAML2 Web browser SSO profile.
 *
 * @author jre
 * @author Alfa & Ariss
 */
public class WebBrowserSSOProfile extends AbstractAuthNMethodSAML2Profile {
    /** Logger */
    protected static Log _logger;
    /** SP SSO Descriptor */
    protected SPSSODescriptor _spSSODescriptor;
    /** Configured AuthnContext Comparison */
    protected String _sAuthnContextComparison;
    /** Configured AuthnContext ClassRefs */
    protected List<String> _listAuthnContextClassRefs;
    /** Secure Random ID Generator */
    protected SecureRandomIdentifierGenerator _idGenerator;

    /**
     * Default constructor. 
     */
    public WebBrowserSSOProfile() {
        _logger = LogFactory.getLog(this.getClass());
        _sAuthnContextComparison = null;
        _listAuthnContextClassRefs = new Vector<String>();
    }

    /**
     * @see com.alfaariss.oa.authentication.remote.saml2.profile.AbstractAuthNMethodSAML2Profile#init(com.alfaariss.oa.api.configuration.IConfigurationManager, org.w3c.dom.Element, org.opensaml.saml2.metadata.EntityDescriptor, com.alfaariss.oa.api.idmapper.IIDMapper, com.alfaariss.oa.engine.core.idp.storage.IIDPStorage, java.lang.String, com.alfaariss.oa.util.saml2.SAML2ConditionsWindow)
     */
    public void init(IConfigurationManager configurationManager, Element config, EntityDescriptor entityDescriptor,
            IIDMapper mapper, IIDPStorage orgStorage, String sMethodID, String sLinkedIDPProfile,
            SAML2ConditionsWindow conditionsWindow, SAML2TimestampWindow oAuthnInstant,
            StandardProfile oRemoteSAMLUserProvisioningProfile) throws OAException {
        super.init(configurationManager, config, entityDescriptor, mapper, orgStorage, sMethodID, sLinkedIDPProfile,
                conditionsWindow, oAuthnInstant, oRemoteSAMLUserProvisioningProfile);

        //check if OA Server 1.5 is used
        _bCompatible = isCompatible();
        _logger.info("Optional user attribute name format: " + (_bCompatible ? "supported" : "not supported"));

        _spSSODescriptor = _entityDescriptor.getSPSSODescriptor(SAMLConstants.SAML20P_NS);

        Element eAuthnContext = configurationManager.getSection(config, "AuthnContext");
        if (eAuthnContext == null) {
            _logger.info("No optional 'AuthnContext' section found in configuration");
            _sAuthnContextComparison = null;
            _listAuthnContextClassRefs = new Vector<String>();
        } else {
            readAuthnContextConfig(configurationManager, eAuthnContext);
        }
    }

    /**
     * Processes the event according to the implemented profile.
     *
     * @param request The HTTP request.
     * @param response The HTTP response.
     * @param session The Authentication Session.
     * @param organization The SAML organization.
     * @param attributeMapper The Table with attributes.
     * 
     * @return The resulting User event.
     * @throws OAException If an error occurs.
     */
    @SuppressWarnings("unchecked") // for retrieval of session attributes
    public UserEvent process(HttpServletRequest request, HttpServletResponse response, ISession session,
            SAML2IDP organization, Hashtable<String, String> attributeMapper) throws OAException {
        _logger.debug("Request recieved: " + request.getRequestURL().toString());

        Boolean boolResponse = (Boolean) request.getAttribute(SAML2AuthNConstants.RESPONSE_ENDPOINT_PARAM);
        if (boolResponse != null && boolResponse) {
            return handleResponse(request, session, organization, attributeMapper);
        }

        return createAuthNRequest(request, response, session, organization);
    }

    /**
     * Creates and sends the SAML2 AuthnRequest to the supplied IdP.
     *  
     * @param servletRequest Servlet Request
     * @param servletResponse Selvet Response
     * @param session AuthN session
     * @param organization Target IdP
     * @return User Event
     * @throws OAException If authnrequest could not be send
     */
    protected UserEvent createAuthNRequest(HttpServletRequest servletRequest, HttpServletResponse servletResponse,
            ISession session, SAML2IDP organization) throws OAException {
        try {
            IDPSSODescriptor descriptor = getIdPDescriptor(organization);
            String sSupportedBinding = getSupportedBinding(descriptor);
            if (sSupportedBinding == null) {
                _logger.error("Authentication request could not be formed, since no suitable binding can be found");
                throw new OAException(SystemErrors.ERROR_INTERNAL);
            }
            _logger.debug("Using binding: " + sSupportedBinding);

            String sDestination = null;

            for (SingleSignOnService service : descriptor.getSingleSignOnServices()) {
                if (service.getBinding().equals(sSupportedBinding)) {
                    sDestination = service.getLocation();
                }
            }

            AuthnRequest request = buildAuthnRequest();

            ISessionAttributes sessionAttributes = session.getAttributes();
            String requestID = generateRequestID(session.getId(), sessionAttributes);
            request.setID(requestID);

            //Add AssertionConsumerService

            if (_spSSODescriptor != null) {
                AssertionConsumerService acs = _spSSODescriptor.getDefaultAssertionConsumerService();
                if (acs != null) {
                    Integer intIndex = acs.getIndex();
                    String sLocation = acs.getLocation();
                    String sBinding = acs.getBinding();
                    if (intIndex != null && organization.useACSIndex() != null && organization.useACSIndex()) {
                        request.setAssertionConsumerServiceIndex(intIndex);
                    } else if (sLocation != null && sBinding != null) {//If the AssertionConsumerServiceIndex can't be set, the following info should be set:
                        request.setAssertionConsumerServiceURL(sLocation);
                        request.setProtocolBinding(sBinding);
                    }
                }
            }

            request.setDestination(sDestination);
            request.setIssueInstant(new DateTime());

            Issuer issuer = buildIssuer();
            request.setIssuer(issuer);

            //NameIDPolicy
            if (organization.useNameIDPolicy() != null && organization.useNameIDPolicy()) {
                NameIDPolicy nidp = buildNameIDPolicy(session, descriptor, organization.useAllowCreate(),
                        organization.getNameIDFormat());
                if (nidp != null)
                    request.setNameIDPolicy(nidp);
            }

            IUser user = session.getUser();
            String sRequestUID = session.getForcedUserID();
            if (user != null)
                sRequestUID = user.getID();

            if (sRequestUID != null) {
                String sNameQualifier = _entityDescriptor.getEntityID();
                String sNameIDFormat = NameIDType.UNSPECIFIED;
                if (user instanceof SAMLRemoteUser) {
                    SAMLRemoteUser samlUser = ((SAMLRemoteUser) user);
                    sNameIDFormat = samlUser.getFormat();

                    //the namequalifier that was returned by the remote SAML 
                    //organization is set as the organization of the remote 
                    //SAML user; this way the organization is set as name qualifier
                    sNameQualifier = samlUser.getOrganization();
                } else {
                    String sProxyNameID = (String) sessionAttributes.get(ProxyAttributes.class,
                            ProxyAttributes.SUBJECT_NAMEID);
                    if (sProxyNameID != null && sProxyNameID.equals(session.getForcedUserID())) {//Check if the force user id is supplied by the requestor (SAML2) 

                        String sProxyNameIDFormat = (String) sessionAttributes.get(ProxyAttributes.class,
                                ProxyAttributes.SUBJECT_NAME_FORMAT);
                        if (sProxyNameIDFormat != null)
                            sNameIDFormat = sProxyNameIDFormat;

                        String sProxyNameQualifier = (String) sessionAttributes.get(ProxyAttributes.class,
                                ProxyAttributes.SUBJECT_NAME_QUALIFIER);
                        if (sProxyNameQualifier != null)
                            sNameQualifier = sProxyNameQualifier;
                    }
                }

                Subject subject = buildSubject(sRequestUID, sNameIDFormat, sNameQualifier,
                        organization.avoidSubjectConfirmations());
                if (subject != null)
                    request.setSubject(subject);
            }

            //Scoping
            if (organization.useScoping() != null && organization.useScoping()) {
                Scoping scop = buildScoping(sessionAttributes, session.getRequestorId());
                if (scop != null)
                    request.setScoping(scop);
            }

            //TODO is the forceAuthN parameter for the session also valid for remote authNs? 
            request.setForceAuthn(session.isForcedAuthentication());

            String sProviderName = (String) sessionAttributes.get(ProxyAttributes.class,
                    ProxyAttributes.PROVIDERNAME);
            if (sProviderName != null) {
                request.setProviderName(sProviderName);
            } else {//DD set ProviderName with requestor name if not supplied in AuthnRequest
                IRequestor requestor = _requestorPoolFactory.getRequestor(session.getRequestorId());
                if (requestor != null) {
                    String sFriendlyName = requestor.getFriendlyName();
                    if (sFriendlyName != null && sFriendlyName.length() > 0)
                        request.setProviderName(sFriendlyName);
                }
            }

            //DD proxy the optionally available authncontext
            RequestedAuthnContext requestedAuthnContext = buildRequestedAuthnContext(sessionAttributes);
            if (requestedAuthnContext != null)
                request.setRequestedAuthnContext(requestedAuthnContext);

            AbstractEncodingFactory encFactory = AbstractEncodingFactory.createInstance(servletRequest,
                    servletResponse, sSupportedBinding,
                    SAML2Exchange.getSPSSOBindingProperties(_sLinkedIDPProfile));

            if (encFactory == null) {
                _logger.error("No encoding factory available for request");
                throw new OAException(SystemErrors.ERROR_INTERNAL);
            }

            SAMLMessageContext<SignableSAMLObject, SignableSAMLObject, SAMLObject> context = createEncodingContext(
                    servletRequest, servletResponse);

            context.setInboundMessageIssuer(organization.getID());
            context.setOutboundMessageIssuer(_entityDescriptor.getEntityID());
            context.setLocalEntityId(_entityDescriptor.getEntityID());
            context.setLocalEntityMetadata(_entityDescriptor);
            context.setLocalEntityRoleMetadata(_entityDescriptor.getSPSSODescriptor(SAMLConstants.SAML20P_NS));
            context.setMetadataProvider(organization.getMetadataProvider());

            context.setOutboundSAMLMessage(request);

            Endpoint endPoint = buildMetadataEndpoint(AssertionConsumerService.DEFAULT_ELEMENT_NAME,
                    sSupportedBinding, sDestination, null);
            context.setPeerEntityEndpoint(endPoint);

            if (_signingEnabled) {
                Credential credentials = SAML2CryptoUtils.retrieveMySigningCredentials(_crypto,
                        _entityDescriptor.getEntityID());
                context.setOutboundSAMLMessageSigningCredential(credentials);
            } else if (_spSSODescriptor.isAuthnRequestsSigned() || descriptor.getWantAuthnRequestsSigned()) {
                _logger.warn("Could not sign AuthnRequest: no private key available");
            }

            SAMLMessageEncoder encoder = encFactory.getEncoder();

            //session must be persisted before sending the request.
            session.persist();

            encoder.encode(context);

            if (_logger.isDebugEnabled()) {
                XMLObject xmlObject = context.getOutboundSAMLMessage();
                if (xmlObject != null)
                    logXML(xmlObject);
            }

            return UserEvent.AUTHN_METHOD_IN_PROGRESS;
        } catch (OAException e) {
            throw e;
        } catch (MessageEncodingException e) {
            _logger.error("Encoding of authentication request failed", e);
            throw new OAException(SystemErrors.ERROR_INTERNAL);
        }
    }

    /**
     * Processes the SAML2 AuthnResponse.
     *  
     * @param request Servlet request object
     * @param session AuthN session
     * @param authnSessionOrganization IdP who was selected to provide the response
     * @param attributeMapper Optional attribute mapper object
     * @return User Event
     * @throws OAException If response handling results in an internal error.
     */
    @SuppressWarnings({ "unchecked" })
    protected UserEvent handleResponse(HttpServletRequest request, ISession session,
            SAML2IDP authnSessionOrganization, Hashtable<String, String> attributeMapper) throws OAException {
        //SAML Response handling.
        AuthenticationContext oCurrentAuthenticationContext = new AuthenticationContext();

        StatusResponseType respMsg = null;
        SAML2IDP samlResponseOrganization = null;

        //Initialize validator for responses
        ResponseValidator validator = null;

        SAMLMessageContext<SignableSAMLObject, SignableSAMLObject, SAMLObject> context = (SAMLMessageContext<SignableSAMLObject, SignableSAMLObject, SAMLObject>) request
                .getAttribute(SAML2AuthNConstants.SESSION_ATTRIBUTE_NAME);
        try {

            if (context == null) {
                _logger.debug("No context available in request as attribute with name: "
                        + SAML2AuthNConstants.SESSION_ATTRIBUTE_NAME);
                return UserEvent.AUTHN_METHOD_FAILED;
            }

            respMsg = (Response) context.getInboundSAMLMessage();

            if (_logger.isDebugEnabled()) {
                XMLObject xmlObject = context.getInboundSAMLMessage();
                if (xmlObject != null)
                    logXML(xmlObject);
            }

            //Resolve Issuer
            String sOrgID = context.getInboundMessageIssuer();
            if (authnSessionOrganization.getID().equals(sOrgID)) {
                samlResponseOrganization = authnSessionOrganization;
            } else {
                _logger.debug("Response issuer was not the same as who the AuthnRequest was sent to.");
                return UserEvent.AUTHN_METHOD_FAILED;
            }

            //Response signing is not mandatory
            validator = new ResponseValidator(_entityDescriptor.getEntityID(), samlResponseOrganization, false);
            validator.validateResponse(context);
        } catch (ClassCastException e) {
            _logger.debug("Illegally typed object retrieved from session", e);
            throw new OAException(SystemErrors.ERROR_INTERNAL);
        } catch (SAML2SecurityException e) {
            _logger.debug("Validation of incoming SAML message failed", e);
            return UserEvent.AUTHN_METHOD_FAILED;
        }

        if (respMsg == null) {
            _logger.debug("Could not fetch response from session");
            return UserEvent.AUTHN_METHOD_FAILED;
        }

        //the status
        UserEvent evt = getStatus(respMsg.getStatus(), authnSessionOrganization);
        if (evt != UserEvent.AUTHN_METHOD_SUCCESSFUL) {
            _logger.debug("Message indicated that the authentication was not successful: " + evt);
            return evt;
        }

        //Response handling:
        //DD: Artifact response type should not be known here, but until libraries provide a good implementation we leave it here.
        Response resp = null;
        if (respMsg instanceof Response) {
            resp = (Response) respMsg;
        } else if (respMsg instanceof ArtifactResponse) {
            SAMLObject msg = ((ArtifactResponse) respMsg).getMessage();
            if (msg instanceof Response) {
                resp = (Response) msg;
                if (!validator.validateMessage(context, resp)) //Extra validation
                {
                    _logger.debug("Response in ArtifactResponse signature validation failure");
                    return UserEvent.AUTHN_METHOD_FAILED;
                }
                UserEvent event = getStatus(resp.getStatus(), authnSessionOrganization);
                if (event != UserEvent.AUTHN_METHOD_SUCCESSFUL) {
                    _logger.debug(
                            "Message (in artifact response) indicated that the authentication was not successful: "
                                    + event);
                    return event;
                }
            } else {
                _logger.debug(
                        "Artifact response did not contain a Response message: received " + msg.getElementQName());
            }
        }

        if (resp == null) {
            _logger.debug("Response message did not contain 'Response' or 'ArtifactResponse' XML object");
            return UserEvent.AUTHN_METHOD_FAILED;
        }

        //handle assertions
        List<Assertion> assertions = resp.getAssertions();

        if (assertions.isEmpty()) {
            _logger.debug("Response contains no (unencrypted) assertions");
            return UserEvent.AUTHN_METHOD_FAILED;
        }

        Assertion assertion = null;

        Collection<String> cForcedOrgs = (Collection<String>) session.getAttributes().get(SAML2AuthNConstants.class,
                SAML2AuthNConstants.FORCED_ORGANIZATIONS);

        //DD The first assertion that does not have an issuer, or known issuer will be processed
        for (Assertion as : assertions) {
            SAML2IDP tmpReq = null;
            Issuer asIssuer = as.getIssuer();
            String sIssuer = (asIssuer != null ? asIssuer.getValue() : null);
            if (sIssuer != null && !sIssuer.equals(authnSessionOrganization.getID())) {
                //Proxied assertion, check organizations
                SAML2IDP forcedOrg = (SAML2IDP) _organizationStorage.getIDP(sIssuer);
                if (forcedOrg != null) //org found locally
                {
                    tmpReq = forcedOrg;
                } else if (cForcedOrgs != null && cForcedOrgs.contains(sIssuer)) {
                    //requestor is unknown, but was forced initially. Use
                    //sending party
                    _logger.debug("Assertion found with unknown forced issuer: " + sIssuer);
                    tmpReq = authnSessionOrganization;
                } else {
                    _logger.debug("Assertion found with unknown issuer: " + sIssuer);
                }
            } else {
                tmpReq = authnSessionOrganization;
            }

            //validate assertion
            if (tmpReq != null) //requestor found
            {
                if (tmpReq == authnSessionOrganization) {
                    //validator for assertions
                    ResponseValidator newValidator = new ResponseValidator(_entityDescriptor.getEntityID(),
                            samlResponseOrganization, _spSSODescriptor.getWantAssertionsSigned());
                    if (!newValidator.validateMessage(context, as)) {
                        _logger.warn("Assertion signature validation failure");
                        return UserEvent.AUTHN_METHOD_FAILED;
                    }
                } else {
                    //'Foreign' requestor, new validator necessary.
                    ResponseValidator newValidator = new ResponseValidator(_entityDescriptor.getEntityID(), tmpReq,
                            _spSSODescriptor.getWantAssertionsSigned());
                    if (!newValidator.validateMessage(context, as)) {
                        _logger.warn("Foreign Assertion signature validation failure");
                        return UserEvent.AUTHN_METHOD_FAILED;
                    }
                }

                //Everything ok, set assertion
                assertion = as;
                samlResponseOrganization = tmpReq;
                break;
            }
        }

        if (assertion == null) {
            _logger.debug("No (valid) assertions found");
            return UserEvent.AUTHN_METHOD_FAILED;
        }

        //DD: we don't support encrypted assertions
        List<EncryptedAssertion> encAssertions = resp.getEncryptedAssertions();
        if (encAssertions != null && !encAssertions.isEmpty()) {
            _logger.debug(
                    "One or more encrypted assertions received and ignored. This feature is not implemented yet.");
        }

        String sAssertionIssuer = (assertion.getIssuer() == null ? null : assertion.getIssuer().getValue());
        if (!samlResponseOrganization.getID().equals(sAssertionIssuer)) {
            _logger.debug("Assertion issuer not found or correct");
            return UserEvent.AUTHN_METHOD_FAILED;
        }
        oCurrentAuthenticationContext.set(AuthenticationContext.ATTR_ISSUER, sAssertionIssuer);

        Conditions conditions = assertion.getConditions();
        if (conditions != null) {//DD if conditions are available, then they must be evaluated (saml-core-2.0-os r569)
            if (!doConditions(conditions)) {
                _logger.debug("Response conditions not met");
                return UserEvent.AUTHN_METHOD_FAILED;
            }

            setAudienceInAuthnContext(conditions, oCurrentAuthenticationContext);
        }

        Subject subject = assertion.getSubject();
        if (subject == null) {
            _logger.debug("Missing required subject");
            return UserEvent.AUTHN_METHOD_FAILED;
        }

        IUser oAssertionUser = createUserFromAssertion(assertion, _sMethodID, samlResponseOrganization.getID());

        IUser oSessionUser = session.getUser();
        if (oSessionUser == null && oAssertionUser == null) {
            //No user found: error
            _logger.error("Response user conditions not met (no user found)");
            return UserEvent.AUTHN_METHOD_FAILED;
        } else if (oSessionUser != null && oAssertionUser != null) {
            //verify UID
            if (!oSessionUser.getID().equals(oAssertionUser.getID())) {
                _logger.error("Response user conditions not met (UID has changed from " + oSessionUser.getID()
                        + " to " + oAssertionUser.getID() + "during remote authN)");
                return UserEvent.AUTHN_METHOD_FAILED;
            }
        } else if (oSessionUser == null) {
            oSessionUser = oAssertionUser;
        }

        if (assertion.getAuthnStatements().size() < 1) {
            _logger.debug("No AuthN statement found");
            return UserEvent.AUTHN_METHOD_FAILED;
        }

        SAMLRemoteUser samlUser = null;
        if (oSessionUser instanceof SAMLRemoteUser)
            samlUser = (SAMLRemoteUser) oSessionUser;

        //authenticating authorities must be proxied back to the profile 
        List<String> listAuthenticatingAuthorities = new Vector<String>();

        for (AuthnStatement stmt : assertion.getAuthnStatements()) {
            if (!checkAuthNStatement(stmt)) {
                _logger.debug("Response conditions not met");
                return UserEvent.AUTHN_METHOD_FAILED;
            }

            //DD Session Index must first be stored in the user object instead of in the IDP alias store, because the TGT isn't available yet
            String sessionIndex = stmt.getSessionIndex();
            if (samlUser != null && sessionIndex != null)
                samlUser.addSessionIndex(sessionIndex);

            //DD proxy the authncontext classref back to the profile
            AuthnContext authnContext = stmt.getAuthnContext();
            if (authnContext != null) {
                AuthnContextClassRef classRef = authnContext.getAuthnContextClassRef();
                if (classRef != null) {
                    // NOTE: When multiple AuthnStatements are returned, this will only store the last one!
                    session.getAttributes().put(ProxyAttributes.class, ProxyAttributes.AUTHNCONTEXT_CLASS_REF,
                            classRef.getAuthnContextClassRef());
                    oCurrentAuthenticationContext.set(AuthenticationContext.ATTR_AUTHNCONTEXT_CLASSREF,
                            classRef.getAuthnContextClassRef());
                }

                List<AuthenticatingAuthority> listAuthorities = authnContext.getAuthenticatingAuthorities();
                if (listAuthorities != null) {
                    for (AuthenticatingAuthority authority : listAuthorities) {
                        String sURI = authority.getURI();
                        if (sURI != null)
                            listAuthenticatingAuthorities.add(sURI);
                    }
                }
            }

            if (stmt.getAuthnInstant() != null) {
                // NOTE: When multiple AuthnStatements are returned, this will only store the last one!
                oCurrentAuthenticationContext.set(AuthenticationContext.ATTR_AUTHENTICATION_TIME,
                        stmt.getAuthnInstant().toString());
            }
        }

        if (!listAuthenticatingAuthorities.contains(authnSessionOrganization.getID()))
            listAuthenticatingAuthorities.add(authnSessionOrganization.getID());

        session.getAttributes().put(ProxyAttributes.class, ProxyAttributes.AUTHNCONTEXT_AUTHENTICATING_AUTHORITIES,
                listAuthenticatingAuthorities);

        //Attribute handling
        IAttributes oAttributes = null;
        IAttributes oRemoteAttributes = getAttributeMap(assertion.getAttributeStatements());
        if (oRemoteAttributes != null) {
            oAttributes = mapAttributes(oRemoteAttributes, oSessionUser.getAttributes(), attributeMapper);
        } else
            oAttributes = oSessionUser.getAttributes();

        oSessionUser.setAttributes(oAttributes);

        session.setUser(oSessionUser);

        //reset session so not to confuse other SAML components in the chain.
        request.setAttribute(SAML2AuthNConstants.RESPONSE_ENDPOINT_PARAM, new Boolean(false));

        // set attribute in session context to reflecft whether SSO should be used for this IDP
        if (samlResponseOrganization.disableSSO()) {
            session.getAttributes().put(SSOService.class, _sMethodID, IWebAuthenticationMethod.DISABLE_SSO, "true");
        }

        // Register the current Authentication Context in the Session
        AuthenticationContexts oAuthenticationContexts = (AuthenticationContexts) session.getAttributes()
                .get(AuthenticationContexts.class, AuthenticationContexts.ATTR_AUTHCONTEXTS);
        if (oAuthenticationContexts == null) {
            oAuthenticationContexts = new AuthenticationContexts();
            session.getAttributes().put(AuthenticationContexts.class, AuthenticationContexts.ATTR_AUTHCONTEXTS,
                    oAuthenticationContexts);
        }

        oAuthenticationContexts.setAuthenticationContext(_sMethodID, oCurrentAuthenticationContext);
        session.persist();

        return UserEvent.AUTHN_METHOD_SUCCESSFUL;
    }

    /**
     * Returns the first supported binding.
     * 
     * @param idpSSODescriptor IDP SSO Descriptor where to look for the binding.
     * @return The first SSO Service binding as String 
     */
    protected String getSupportedBinding(IDPSSODescriptor idpSSODescriptor) {
        if (idpSSODescriptor != null) {
            List<SingleSignOnService> ssoServices = idpSSODescriptor.getSingleSignOnServices();

            if (ssoServices.size() > 0) {
                // Return the first binding that we can support:
                for (SingleSignOnService ssos : ssoServices) {
                    if (AbstractEncodingFactory.getSupportedBindings().contains(ssos.getBinding())) {
                        return ssos.getBinding();
                    }
                }

                _logger.error("Could not find a binding that we support in IDP's metadata; supported: "
                        + AbstractEncodingFactory.getSupportedBindings());
            }
        } else {
            _logger.debug("Could not determine binding, no IDP role descriptor found");
        }

        return null;
    }

    /**
     * Creates the SAML2 AuthnRequest object. 
     * @return SAML2 AuthnRequest object
     */
    protected AuthnRequest buildAuthnRequest() {
        AuthnRequestBuilder builder = (AuthnRequestBuilder) _builderFactory
                .getBuilder(AuthnRequest.DEFAULT_ELEMENT_NAME);

        AuthnRequest request = builder.buildObject();

        return request;
    }

    /**
     * Creates the SAML2 RequestedAuthnContext object. 
     * @param sessionAttributes Session Attributes
     * @return RequestedAuthnContext object
     */
    @SuppressWarnings("unchecked") //because of List<String> retrieval from session attributes
    protected RequestedAuthnContext buildRequestedAuthnContext(ISessionAttributes sessionAttributes) {
        //add optional configurable AuthnContext parameters
        List<String> listClassRefs = (List<String>) sessionAttributes.get(ProxyAttributes.class,
                ProxyAttributes.AUTHNCONTEXT_CLASS_REFS);
        if (listClassRefs == null && !_listAuthnContextClassRefs.isEmpty()) {
            _logger.debug("Using configured ClassRefs: " + _listAuthnContextClassRefs);
            listClassRefs = new Vector<String>();
            listClassRefs.addAll(_listAuthnContextClassRefs);
        }

        String sComparison = (String) sessionAttributes.get(ProxyAttributes.class,
                ProxyAttributes.AUTHNCONTEXT_COMPARISON_TYPE);
        if (sComparison == null && _sAuthnContextComparison != null) {
            _logger.debug("Using configured Comparison: " + _sAuthnContextComparison);
            sComparison = _sAuthnContextComparison;
        }

        RequestedAuthnContext requestedAuthnContext = null;
        if (listClassRefs != null) {
            _logger.debug("Using session attribute: " + ProxyAttributes.AUTHNCONTEXT_CLASS_REFS);
            RequestedAuthnContextBuilder racBuilder = (RequestedAuthnContextBuilder) _builderFactory
                    .getBuilder(RequestedAuthnContext.DEFAULT_ELEMENT_NAME);

            requestedAuthnContext = racBuilder.buildObject();

            for (String classRef : listClassRefs) {
                AuthnContextClassRefBuilder classRefBuilder = (AuthnContextClassRefBuilder) _builderFactory
                        .getBuilder(AuthnContextClassRef.DEFAULT_ELEMENT_NAME);

                AuthnContextClassRef authnContextClassRef = classRefBuilder.buildObject();
                authnContextClassRef.setAuthnContextClassRef(classRef);

                requestedAuthnContext.getAuthnContextClassRefs().add(authnContextClassRef);
            }

            if (sComparison != null) {
                AuthnContextComparisonTypeEnumeration comparisonType = null;
                if (sComparison.equalsIgnoreCase(AuthnContextComparisonTypeEnumeration.MINIMUM.toString()))
                    comparisonType = AuthnContextComparisonTypeEnumeration.MINIMUM;
                else if (sComparison.equalsIgnoreCase(AuthnContextComparisonTypeEnumeration.BETTER.toString()))
                    comparisonType = AuthnContextComparisonTypeEnumeration.BETTER;
                else if (sComparison.equalsIgnoreCase(AuthnContextComparisonTypeEnumeration.EXACT.toString()))
                    comparisonType = AuthnContextComparisonTypeEnumeration.EXACT;
                else if (sComparison.equalsIgnoreCase(AuthnContextComparisonTypeEnumeration.MAXIMUM.toString()))
                    comparisonType = AuthnContextComparisonTypeEnumeration.MAXIMUM;
                else
                    _logger.debug("Unknown comparison type available as session attribute: " + sComparison);

                if (comparisonType != null) {
                    _logger.debug("Using comparison type session attribute: "
                            + ProxyAttributes.AUTHNCONTEXT_COMPARISON_TYPE);
                    requestedAuthnContext.setComparison(comparisonType);
                }
            }
        }

        return requestedAuthnContext;
    }

    private void readAuthnContextConfig(IConfigurationManager configurationManager, Element config)
            throws OAException {
        _sAuthnContextComparison = configurationManager.getParam(config, "Comparison");
        if (_sAuthnContextComparison == null) {
            _logger.info("No optional 'Comparison' parameter in 'AuthnContext' section found in configuration");
            _sAuthnContextComparison = null;
        } else {
            _logger.info("Using configured AuthnContext Comparison value: " + _sAuthnContextComparison);
        }

        Element eClassRefs = configurationManager.getSection(config, "ClassRefs");
        if (eClassRefs == null) {
            _logger.info("No optional 'ClassRefs' section in 'AuthnContext' section found in configuration");
            _listAuthnContextClassRefs = new Vector<String>();
        } else {
            Element eClassRef = configurationManager.getSection(eClassRefs, "ClassRef");
            if (eClassRef == null) {
                _logger.error("No 'ClassRef' section in 'ClassRefs' section found in configuration");
                throw new OAException(SystemErrors.ERROR_CONFIG_READ);
            }

            while (eClassRef != null) {
                String sClassRefURI = configurationManager.getParam(eClassRef, "uri");
                if (sClassRefURI == null) {
                    _logger.error("No 'uri' parameter in 'ClassRef' section found in configuration");
                    throw new OAException(SystemErrors.ERROR_CONFIG_READ);
                }

                if (_listAuthnContextClassRefs.contains(sClassRefURI)) {
                    _logger.error(
                            "Configured 'uri' parameter in 'ClassRef' section is not unique: " + sClassRefURI);
                    throw new OAException(SystemErrors.ERROR_INIT);
                }

                _listAuthnContextClassRefs.add(sClassRefURI);

                _logger.info("Using configured AuthnContext ClassRef uri: " + sClassRefURI);

                eClassRef = configurationManager.getNextSection(eClassRef);
            }
        }
    }

    private boolean isCompatible() {
        try {
            IAttributes.class.getDeclaredMethod("getFormat", String.class);
            return true;
        } catch (java.lang.SecurityException e) {
            //false
        } catch (NoSuchMethodException e) {
            //false
        }
        return false;
    }

    private void setAudienceInAuthnContext(Conditions oConditions, AuthenticationContext oAuthnContext) {
        List<AudienceRestriction> lAudienceRestrictions = oConditions.getAudienceRestrictions();
        if (lAudienceRestrictions == null)
            return;

        List<String> lAudienceList = new ArrayList<String>();

        for (AudienceRestriction oAudienceRestriction : lAudienceRestrictions) {
            List<Audience> lAudiences = oAudienceRestriction.getAudiences();
            if (lAudiences == null)
                continue;

            for (Audience oAudience : lAudiences) {
                String sAudience = oAudience.getAudienceURI();
                if (sAudience != null)
                    lAudienceList.add(sAudience);
            }
        }

        if (lAudienceList.size() > 0) {
            String value = StringUtils.join(lAudienceList.iterator(), " ");
            oAuthnContext.set(AuthenticationContext.ATTR_AUDIENCE, value);

            _logger.debug("Audience set in local AuthenticationContext (" + value + ")");
        }
    }
}