import os
import datetime
import urllib

import lasso
import common

from quixote import get_request, get_response, redirect, get_field, get_publisher
from quixote.http_request import parse_header
from quixote.directory import Directory, AccessControlled
from quixote import get_user, get_session, get_session_manager

from qommon import get_cfg, get_logger
from qommon import template
from qommon import errors
from qommon.storage import StorableObject
from qommon.tokens import Token
from qommon.publisher import sitecharset2utf8

import authentic
from authentic import identities
from authentic.misc import get_lasso_server, get_provider_key, redirect_with_return_url
from authentic.form import *
import authentic.login_token as login_token
import idwsf2
import qommon.soap as soap
import authentic.misc as misc
import qommon.misc as qmisc

authn_methods = {
    'password': lasso.SAML2_AUTHN_CONTEXT_PASSWORD,
    'password-on-https': lasso.SAML2_AUTHN_CONTEXT_PASSWORD_PROTECTED_TRANSPORT,
    'password-and-ip': lasso.SAML2_AUTHN_CONTEXT_INTERNET_PROTOCOL_PASSWORD,
    'client-certificate': lasso.SAML2_AUTHN_CONTEXT_TLS_CLIENT,
}

def get_saml2_authn_method(session):
    authn_method = 'password'
    if session and session.authentication_method:
        authn_method = session.authentication_method
    return authn_methods.get(authn_method)

class Saml2Artifact(StorableObject):
    _names = 'saml2_artifacts'
    artifact = None
    session_id = None
    provider_id = None
    message = None

    def get_by_artifact(cls, artifact):
        return cls.get(artifact)
    get_by_artifact = classmethod(get_by_artifact)


class SpDir(Directory):
    def _q_lookup(self, component):
        return SpUI(component)

def error_then_home(error):
    return_url = get_field('returnURL')
    if return_url:
        c_to = (return_url, _('Continue'))
    else:
        c_to = (get_publisher().get_root_url(), _('Home'))
    return template.error_page(error, continue_to = c_to)

def get_identity():
    authentic.identities.get_store().load_identities()
    session = get_session()
    if not session:
        return None
    user = session.user
    try:
        return authentic.identities.get_store().get_identity(user)
    except:
        return None

def save_session(profile, session = None):
    if not session:
        session = get_session()
    if profile.session:
        session.lasso_session_dump = profile.session.dump()
    else:
        session.lasso_session_dump = None

def load_session(profile, session):
    if not session:
        session = get_session()
    if session and session.lasso_session_dump:
        profile.setSessionFromDump(session.lasso_session_dump)

def load_identity(profile, identity = None, session = None):
    if not identity:
        identity = get_identity()
    if not identity:
        return
    if identity and identity.lasso_dump:
        profile.setIdentityFromDump(identity.lasso_dump)

def save_identity(profile, identity = None):
    if not profile.isIdentityDirty:
        return
    if not identity:
        identity = get_identity()
    if not identity:
        return
    if profile.identity:
        identity.lasso_dump = profile.identity.dump()
    else:
        identity.lasso_dump = None
        identity.name_identifiers = []
    authentic.identities.get_store().save(identity)

class RootDirectory(common.LassoDirectory):
    _q_exports = ['singleSignOn', 'failSingleSignOn', 'singleSignOnSOAP', 'artifact', 'consent',
            'singleLogoutSOAP', 'singleLogoutPOST', 'singleLogoutFinish',
            'singleLogout', 'singleLogoutReturn',
            'manageNameIdSOAP', 'manageNameId', 'manageNameIdReturn',
            'metadata', ('metadata.xml', 'metadata'), 'public_key',
            'sp', 'intro', 'disco',
            'proxySingleSignOnArtifact', 'proxySingleSignOnPost',
            'proxySingleSignOnRedirect','proxySingleLogoutSOAP','proxySingleLogout','proxySingleLogoutReturn',
            'proxyAssertionConsumerArtifact', 'proxyAssertionConsumerPost',
            'proxyAssertionConsumerRedirect', 'continueSSO', 'failSSO']

    sp = SpDir()

    def _q_index(self):
        raise errors.AccessError()

    def check_on(self):
        return redirect(get_publisher().get_root_url() + 'images/check_on.png')

    def check_off(self):
        return redirect(get_publisher().get_root_url() + 'images/check_off.png')

    def postRequestTo(self, url, body, relayState='', title=''):
        return self.postTo(url, body, "SAMLRequest", relay_state=relay_state,
                title=title)

    def postResponseTo(self, url, body, relay_state='', title=''):
        return self.postTo(url, body, "SAMLResponse", relay_state=relay_state,
                title=title)

    def singleSignOn(self):
        '''Handle an AuthnRequest, if not logged and
        interaction is authorized, ask for login'''
        return self.single_sign_on()

    def get_query(self):
        '''Retrieve the query from the given login token'''
        id = get_field('id')
        if not id:
            return None
        token = login_token.LoginToken.get(id, ignore_errors=True)
        if token:
            return getattr(token, 'query', None)
        return None

    def invoke_login(self, login, query):
        request_id = login.request.iD
        login_url = get_request().environ['SCRIPT_NAME'] + '/login'
        token = login_token.LoginToken(request_id)
        token.query = query
        token.store()
        request_id = urllib.quote(request_id)
        return redirect_with_return_url(login_url,
                (('okURL', '/saml/continueSSO?id=%s' % request_id),
                 ('cancelURL', '/saml/failSSO?id=%s' % request_id),
                 ('LoginToken', request_id),
                 ('service',qmisc.get_provider_key(login.remoteProviderId))))

    def continueSSO(self):
        query = self.get_query()
        return self.single_sign_on(False, query)

    def failSSO(self):
        query = self.get_query()
        return self.single_sign_on(True, query)

    def single_sign_on(self, request_denied = False, query = None):
        server = get_lasso_server(protocol = 'saml2')
        if not server:
            return template.error_page(_('SAML 2.0 support not yet configured.'))

        login = lasso.Login(server)
        request = get_request()
        if query is None:
            if request.get_method() == 'GET':
                query = request.get_query()
            elif request.get_method() == 'POST':
                query = request.form.get('SAMLRequest')
            else:
                return template.error_page(_('Invalid request message'))
        try:
            login.processAuthnRequestMsg(query)
        except lasso.DsError, e:
            return template.error_page('Cryptographic error: %s' % lasso.strError(e.code))
        except lasso.Error, error:
            self.logProfileError(login, error, 'singleSignOn login.processAuthnRequestMsg')
            get_logger().debug('singleSignOn query: %r' % query)
            if error[0] == lasso.SERVER_ERROR_PROVIDER_NOT_FOUND or \
                    error[0] == lasso.PROFILE_ERROR_UNKNOWN_PROVIDER:
                return template.error_page(_('Authentication request initiated by an unaffiliated provider.'))
            elif error[0] == lasso.PROFILE_ERROR_INVALID_MSG or \
                    error[0] == lasso.PROFILE_ERROR_MISSING_REMOTE_PROVIDERID:
                return template.error_page(_('Invalid request message'))
            else:
                return error_then_home(_('Internal Server Error'))
        # Loggin informations on the request
        issuer = login.remoteProviderId
        authn_request = login.request
        nameIDPolicy = login.request.nameIdPolicy
        if nameIDPolicy:
            if nameIDPolicy.allowCreate:
                allow_create = 'allowing'
            else:
                allow_create = 'disallowing'
            policy = 'with format "%(format)s", SP name qualifier "%(name_qualifier)s" and %(allow_create)s creation' % {
                    'format': nameIDPolicy.format or '',
                    'name_qualifier': nameIDPolicy.spNameQualifier or '',
                    'allow_create': allow_create }
        else:
            policy = 'with default name id policy'
        assertion_consumer_flags = []
        if authn_request.protocolBinding:
            assertion_consumer_flags.append('with binding "%s"' % \
                    login.request.protocolBinding)
        if authn_request.assertionConsumerServiceUrl:
            assertion_consumer_flags.append('with URL "%s"' % \
                    authn_request.assertionConsumerServiceUrl)
        if authn_request.assertionConsumerServiceIndex != -1:
            assertion_consumer_flags.append('with idx "%s"' %
                    authn_request.assertionConsumerServiceIndex)
        default = 'default '
        if assertion_consumer_flags:
            default = ''
        assertion_consumer_flags = ','.join(assertion_consumer_flags)
        request_flags = []
        if login.request.forceAuthn:
            request_flags.append('forcing authentication')
        if login.request.isPassive:
            request_flags.append('without interaction')
        get_logger().info('Received AuthnRequest from %(remote_provider)s \
%(policy)s, %(request_flags)s targetting %(default)sassertion consumer service\
%(assertion_consumer_flags)s'
                % { 'remote_provider' : issuer,
                    'policy': policy,
                    'default': default,
                    'assertion_consumer_flags': assertion_consumer_flags,
                    'protocol_binding': login.request.protocolBinding,
                    'request_flags': ', '.join(request_flags)})

        session = get_session()
        session.saml2 = True
        request_id = login.request.iD

        if session and session.lasso_session_dump:
            login.setSessionFromDump(session.lasso_session_dump)
        user_authenticated = False
        if not request_denied:
            if session and session.user:
                user_authenticated = True
            auth_ok = login_token.LoginToken.has_good_authentication(request_id)
            if authn_request.forceAuthn and not auth_ok:
                user_authenticated = False
            if not user_authenticated and not authn_request.isPassive:
                return self.invoke_login(login, query)
        return self.sso_after_authentication(login, user_authenticated)

    def logLoginValidateRequestError(self, login, error):
        self.logProfileError(login, error, 'login.validateRequestMsg')

    def logLoginProcessResponseMsgError(self, login, error):
        self.logProfileError(login, error, 'login.processResponseMsg')

    def logProfileError(self, login, error, what):
        if isinstance(error, lasso.Error):
            message = error[1]
        else:
            message = str(error)
        get_logger().warn('%(what)s from %(provider)s failed: %(message)s' %
                { 'provider': login.remoteProviderId, 'message': message, \
                        'what': what })

    def check_basic_authentication(self, request):
        authorization=request.get_header('Authorization')
        if authorization and authorization.startswith('Basic '):
            account = identities.PasswordAccount()
            account.login, account.password = \
                    base64.b64decode.authorization[6:].split(':', 1)
            return identities.get_store().get_identity_for_account(account)
        return None

    @common.soap_endpoint
    def singleSignOnSOAP(self):
        server = get_lasso_server(protocol = 'saml2')
        if not server:
            return template.error_page(_('SAML 2.0 support not yet configured.'))
        login = lasso.Login(server)
        request = get_request()
        soap_request_msg = self.get_soap_message()
        try:
            login.processAuthnRequestMsg(soap_request_msg)
        except lasso.Error, error:
            self.logProfileError(logint, error, 'singleSignOnSOAP login.processAuthnRequestMsg')
            if error[0] == lasso.PROFILE_ERROR_MISSING_REMOTE_PROVIDERID:
                return template.error_page(_('Request initiated by an unaffiliated provider.'))
            if error[0] == lasso.DS_ERROR_INVALID_SIGNATURE:
                return template.error_page(_('Failed to check authentication request signature.'))
            if error[0] == lasso.DS_ERROR_SIGNATURE_NOT_FOUND:
                return template.error_page(_('Authentication request is not signed.'))
            return error_then_home(_('Internal Server Error'))

        user_authenticated = False
        user = self.check_basic_authentication(request)
        if user:
            user_authenticated = True

        # Consent must be obtained from service provider because
        # identity provider is unable to interact with user
        consent_obtained = False
        if user_authenticated:
            if not login.mustAskForConsent():
                consent_obtained = True

        try:
            login.validateRequestMsg(user_authenticated, consent_obtained)
        except lasso.Error, error:
            self.logLoginValidateRequestError(login, error)
            return self.finishSingleSignOnSOAP(login)
        else:
            now = datetime.datetime.utcnow()
            notBefore = now-datetime.timedelta(0,60)
            notOnOrAfter = now+datetime.timedelta(0,60)
            login.buildAssertion(get_saml2_authn_method(session),
                    now.isoformat()+'Z',
                    'unused', # reauthenticateOnOrAfter is only for ID-FF 1.2
                    notBefore.isoformat()+'Z',
                    notOnOrAfter.isoformat()+'Z')
            login.assertion.authnStatement[0].sessionIndex = login.assertion.id
            common.SessionIndex(login.assertion.id).store()
            save_identity(login, user)

        if login.protocolProfile == lasso.LOGIN_PROTOCOL_PROFILE_BRWS_LECP:
            login.buildResponseMsg(None)
        else:
            return template.error_page(_('Unknown SAML2 protocol binding'))

        response = get_response()
        response.set_content_type('application/vnd.paos+xml', 'UTF-8')
        response.set_header('PAOS', 'ver=urn:liberty:paos:2003-08;urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp')
        return login.msgBody

    @common.soap_endpoint
    def finishSingleSignOnSOAP(self, login):
        login.buildResponseMsg(None)
        response = get_response()
        response.set_content_type('application/vnd.paos+xml', 'UTF-8')
        response.set_header('PAOS', 'ver=urn:liberty:paos:2003-08;urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp')
        return login.msgBody

    def check_access_authorizations(cls, login):
        return True
    check_access_authorizations = classmethod(check_access_authorizations)

    def sso_after_authentication(self, login, user_authenticated, proxied = False):
        # part 2 of SSO: user has been authenticated (or not)
        session, identity = self.restore_user_details(login)

        if user_authenticated:
            if not self.check_access_authorizations(login):
                # XXX: notify the user here and now?
                get_logger().warn('user refused because of authorizations')
                user_authenticated = False

        consent_obtained = False
        if user_authenticated:
            if login.mustAskForConsent():
                session.lasso_login_dump = login.dump()
                return redirect(get_request().environ['SCRIPT_NAME'] + '/saml/consent')
        return self.sso_after_consent(login, user_authenticated, True)

    def restore_user_details(self, login):
        authentic.identities.get_store().load_identities()
        session = get_session()
        try:
            identity = authentic.identities.get_store().get_identity(session.user)
        except KeyError:
            identity = None
        if identity and identity.lasso_dump:
            login.setIdentityFromDump(identity.lasso_dump)
        if session and session.lasso_session_dump:
            login.setSessionFromDump(session.lasso_session_dump)
        return (session, identity)

    def disco_service_url(self):
        request = get_request()
        url = '%s://%s/disco/soapEndpoint' % (request.get_scheme(), request.get_server())
        return url

    def saml2_add_attribute_values(self, assertion, name, values):
        if not assertion.attributeStatement:
            assertion.attributeStatement = [ lasso.Saml2AttributeStatement() ]
        attribute_statement = assertion.attributeStatement[0]
        for attribute in attribute_statement.attribute:
            if attribute.name == name and attribute.nameFormat == lasso.SAML2_ATTRIBUTE_NAME_FORMAT_BASIC:
                break
        else:
            attribute = lasso.Saml2Attribute()
            attribute.name = name
            attribute.nameFormat = lasso.SAML2_ATTRIBUTE_NAME_FORMAT_BASIC
            attribute_statement.attribute = list(attribute_statement.attribute) + [ attribute ]
        attribute_value_list = list(attribute.attributeValue)
        for value in values:
            if value is True:
                value = 'true'
            elif value is False:
                value = 'false'
            else:
                value = str(value)
            if type(value) is unicode:
                value = value.encode('utf-8')
            else:
                value = sitecharset2utf8(value)
            text_node = lasso.MiscTextNode.newWithString(value)
            text_node.textChild = True
            attribute_value = lasso.Saml2AttributeValue()
            attribute_value.any = [ text_node ]
            attribute_value_list.append(attribute_value)
        attribute.attributeValue = attribute_value_list

    def build_assertion(self, login, session, identity):
        now = datetime.datetime.utcnow()
        notBefore = now-datetime.timedelta(0,60)
        notOnOrAfter = now+datetime.timedelta(0,60)
        login.buildAssertion(get_saml2_authn_method(session),
                session.authentication_instant.isoformat()+'Z',
                'unused', # reauthenticateOnOrAfter is only for ID-FF 1.2
                notBefore.isoformat()+'Z',
                notOnOrAfter.isoformat()+'Z')
        if lasso.WSF_ENABLED:
            idwsf2.addDiscoveryBootStrapToLogin(login,
                    self.disco_service_url(),
                    login.server.providerId, (lasso.SECURITY_MECH_BEARER,))
        login.assertion.authnStatement[0].sessionIndex = login.assertion.id
        common.SessionIndex(login.assertion.id).store()

        name_id_format = None
        if login.request.nameIDPolicy:
            name_id_format = login.request.nameIDPolicy.format

        if name_id_format == lasso.SAML2_NAME_IDENTIFIER_FORMAT_EMAIL:
            if not identity.email:
                return template.error_page(_('The service asked for NameIDFormat email, \
but the current does not have one.'))
            login.response.assertion[0].subject.nameID.content = identity.email
        elif name_id_format == lasso.SAML2_NAME_IDENTIFIER_FORMAT_UNSPECIFIED:
            if not session.name_identifiers or not session.name_identifiers[-1]:
                # Final IdP : must create the nameId if it doesn't exist
                # XXX: This is mainly for testing. If it should be use in real world, the NameID
                # generation should be configurable somehow
                nameID = 'test'
            else:
                # Proxying IdP : a name identifier is already set in the session, copy it to the
                # new assertion
                nameID = session.name_identifiers[-1]
            login.nameIdentifier.content = nameID
            login.response.assertion[0].subject.nameID.content = nameID
        provider_key = misc.get_provider_key(login.remoteProviderId)
        self.add_attributes(provider_key, login.response.assertion[0],
                identity, self.saml2_add_attribute_values)

    def set_default_name_id(self, login):
        '''If no NameID format is asked by the service provider or if it asked
           for the format 'unspecified', use the default from the settings.
        '''
        try:
            lp = get_cfg('providers')[misc.get_provider_key(
                login.remoteProviderId)]
        except KeyError:
            raise errors.TraversalError()
        if not login.request.nameIDPolicy:
            login.request.nameIDPolicy = lasso.Samlp2NameIDPolicy()
        if not login.request.nameIDPolicy.format or \
            login.request.nameIDPolicy.format == \
                lasso.SAML2_NAME_IDENTIFIER_FORMAT_UNSPECIFIED:
            nid_format = lp.get('default_name_id_format', 'persistent')
            if nid_format == 'persistent':
                login.request.nameIDPolicy.format = lasso.SAML2_NAME_IDENTIFIER_FORMAT_PERSISTENT
            elif nid_format == 'transient':
                login.request.nameIDPolicy.format = lasso.SAML2_NAME_IDENTIFIER_FORMAT_TRANSIENT
            elif nid_format == 'encrypted':
                login.request.nameIDPolicy.format = lasso.SAML2_NAME_IDENTIFIER_FORMAT_ENCRYPTED
            elif nid_format == 'email':
                login.request.nameIDPolicy.format = lasso.SAML2_NAME_IDENTIFIER_FORMAT_EMAIL
            elif nid_format == 'none':
                login.request.nameIDPolicy.format = lasso.SAML2_NAME_IDENTIFIER_FORMAT_UNSPECIFIED

    def sso_after_consent(self, login, user_authenticated, consent_obtained, intro_cookie = True):
        # part 3 of SSO: if necessary, user has given its consent
        session, identity = self.restore_user_details(login)
        get_logger().debug('sso_after_consent user_authenticated:%s \
consent_obtained: %s intro_cookie: %s' % (user_authenticated, consent_obtained,
    intro_cookie))

        self.set_default_name_id(login)

        try:
            login.validateRequestMsg(user_authenticated, consent_obtained)
        except lasso.Error, error:
            self.logLoginValidateRequestError(login, error)
            do_federation = False
        else:
            do_federation = True
            self.build_assertion(login, session, identity)

        if login.protocolProfile == lasso.LOGIN_PROTOCOL_PROFILE_BRWS_ART:
            login.buildArtifactMsg(lasso.HTTP_METHOD_ARTIFACT_GET)
        elif login.protocolProfile in (lasso.LOGIN_PROTOCOL_PROFILE_BRWS_POST,
                    lasso.LOGIN_PROTOCOL_PROFILE_REDIRECT):
            login.buildAuthnResponseMsg()
        elif login.protocolProfile == lasso.LOGIN_PROTOCOL_PROFILE_BRWS_LECP:
            login.buildResponseMsg(None)
        else:
            return template.error_page(_('Unknown SAML2 protocol binding'))

        if do_federation and identity:
            if login.isIdentityDirty:
                identity.lasso_dump = login.identity.dump()
            authentic.identities.get_store().save(identity)

        if login.nameIdentifier:
            # not there if not authenticated or consent not granted
            if not session.name_identifiers:
                session.name_identifiers = []
            if not login.nameIdentifier.content in session.name_identifiers:
                session.name_identifiers.append(login.nameIdentifier.content)

        if login.isSessionDirty:
            save_session(login)
            get_session_manager().maintain_session(session)

        common_domain = get_cfg('idp').get('common_domain')
        common_domain_setter_url = get_cfg('idp').get('common_domain_setter_url')
        if common_domain_setter_url and common_domain and intro_cookie and False:
            token = Token(expiration_delay = 600) # ten minutes
            token.session_id = session.id
            token.protocol = 'saml2'
            token.provider_id = login.server.providerId
            token.common_domain = common_domain
            token.next_url = get_request().get_url(1) + '/intro'
            token.store()
            session.lasso_login_dump = login.dump()
            return redirect(common_domain_setter_url + '?tok=%s' % token.id)

        return self.sso_after_intro(login, session)


    def sso_after_intro(self, login, session):
        if login.protocolProfile == lasso.LOGIN_PROTOCOL_PROFILE_BRWS_ART:
            artifact = Saml2Artifact(id = login.artifact)
            artifact.artifact = login.artifact
            artifact.message = login.artifactMessage
            artifact.session_id = session.id
            artifact.provider_id = login.remoteProviderId
            artifact.store()
            return redirect(login.msgUrl)
        elif login.protocolProfile == lasso.LOGIN_PROTOCOL_PROFILE_BRWS_LECP:
            response = get_response()
            response.set_content_type('text/xml')
            return login.msgBody
        else:
            if login.msgBody:
                return self.postResponseTo(login.msgUrl, login.msgBody,
                        relay_state = login.msgRelayState,
                        title = N_('Authentication Response'))
            else:
                return redirect(login.msgUrl)


    def intro(self):
        session = get_session()
        login = lasso.Login.newFromDump(
                get_lasso_server(protocol = 'saml2'), session.lasso_login_dump)
        return self.sso_after_intro(login, get_session())


    def consent [html] (self):
        form = Form(enctype="multipart/form-data", id = "login")
        form.add_submit("consent", _("Consent to Federation"))
        form.add_submit("refuse", _("Refuse Federation"))
        if not form.is_submitted() or form.has_errors():
            template.html_top()
            """<div id="login-top"><h1>%s</h1></div>
            <div id="login-form">""" % _('Login')
            # XXX: get site name from provider/metadata/organization to have
            # an appropriate question.
            "<p>%s</p>" % _('Do you consent to federate your account with the other site?')
            form.render()
            '</div>'
        else:
            if form.get_widget('consent').parse():
                consent = True
            else:
                consent = False
            session = get_session()
            login = lasso.Login.newFromDump(
                    get_lasso_server(protocol = 'saml2'), session.lasso_login_dump)
            session.lasso_login_dump = None
            return self.sso_after_consent(login, True, consent)


    @common.soap_endpoint
    def artifact(self):
        #try:
        soap_message = self.get_soap_message()
        #except:
        #    return

        response = get_response()
        response.set_content_type('text/xml')

        authentic.identities.get_store().load_identities()
        login = lasso.Login(get_lasso_server(protocol = 'saml2'))
        try:
            login.processRequestMsg(soap_message)
        except Exception, e:
            get_logger().error('artifact resolve error %s' % e)
            raise # leverage common.soap_endpoint
        try:
            artifact = Saml2Artifact.get_by_artifact(login.artifact)
        except KeyError:
            login.buildResponseMsg(None)
            return login.msgBody
        login.artifactMessage = artifact.message
        artifact.remove_self()
        session = get_session_manager().get(artifact.session_id)
        if session and session.lasso_session_dump:
            login.setSessionFromDump(session.lasso_session_dump)
        login.buildResponseMsg(None)

        if session and login.isSessionDirty:
            save_session(login)
            get_session_manager().commit_changes(session)

        return login.msgBody

    def setup_logout_for_validation(self):
        pass

    def check_logout_request(self, logout_profile):
        '''Make any needed check on the logout request,
           must return True if the request if accepted, False otherwise
        '''
        return True

    # SLO support
    def singleLogout(self):
        'Single Logout initiated by service provider with Redirect binding'
        return self.single_logout_sp(get_request().get_query(),
                lasso.HTTP_METHOD_REDIRECT)

    @common.soap_endpoint
    def singleLogoutSOAP(self):
        'Single Logout initiated by service provider with SOAP binding'
        try:
            soap_message = self.get_soap_message()
        except:
            return
        return self.single_logout_sp(soap_message, lasso.HTTP_METHOD_SOAP)

    def singleLogoutPOST(self):
        'Single Logout initiated by service provider with POST binding'
        return self.single_logout_sp(get_field('SAMLRequest'),
                lasso.HTTP_METHOD_POST, relay_state = get_field('RelayState'))

    def singleLogoutFinish(self):
        'Single Logout Finish synchronous sequence'
        session = get_session()
        logout = None
        try:
            logout = lasso.Logout.newFromDump(
                    get_lasso_server(protocol = 'saml2'),
                    session.lasso_logout_dump)
            return self.slo_idp_finish(logout, session, method=lasso.HTTP_METHOD_REDIRECT)
        except:
            pass
        # Catch All case
        if get_session():
            get_session_manager().expire_session()
        return redirect(session.after_url or get_publisher().get_root_url())

    def single_logout_sp(self, message, method, relay_state = None):
        assert(method is not None)
        logout = lasso.Logout(get_lasso_server(protocol = 'saml2'))
        try:
            logout.processRequestMsg(message)
        except lasso.Error, error:
            return self.slo_sp_finish(logout, method, error = error)

        if relay_state:
            logout.relayState = relay_state

        get_logger().info('received SLO request from %s' % logout.remoteProviderId)
        get_session().service = get_provider_key(logout.remoteProviderId)
        # Look for a session index
        try:
            session_index = common.SessionIndex.get(logout.request.sessionIndex)
            name_identifier = logout.nameIdentifier.content
            identity = authentic.identities.get_store().get_identity_for_name_identifier(name_identifier)
            session = get_session_manager().get(session_index.session_id)
            if get_session() and get_session().id == session.id:
                session = get_session()
            load_session(logout, session)
        except Exception, error:
            # No session index: request is refused
            return self.slo_sp_finish(logout, method, error = error)

        # Tag current session as in_slo
        session.in_slo_sp = True
        # identity loading is useless since Lasso >= 2.2.90
        load_identity(logout, identity = None, session = session)

        self.setup_logout_for_validation()

        try:
            logout.validateRequest()
        except lasso.Error, error:
            return self.slo_sp_finish(logout, method, error = error)

        if not self.check_logout_request(logout):
            return self.slo_sp_finish(logout, method, error = error)

        # logout user now! but remember who was logged.
        session.previous_user = session.user
        session.user = None
        if method == lasso.HTTP_METHOD_SOAP:
            get_session_manager().expire_session()
            session.remove_self()

        # FIXME: support redirect to proxy in slo_idp
        if session.proxied_idp and identity:
            self.proxy_slo_soap(session, identity)

        if method != lasso.HTTP_METHOD_SOAP:
            save_session(logout)
        # Eventually start a provider logout
        return self.slo_idp(session, method = method, logout = logout)

    def slo_sp_finish(self, logout, method, error = None):
        assert(logout is not None)
        if error:
            self.logProfileError(logout, error, 'SLO Request')
        else:
            if method != lasso.HTTP_METHOD_SOAP:
                get_session_manager().expire_session()
        if not method:
            method = logout.server.getFirstHttpMethod(
                    logout.server.getProvider(logout.remoteProviderId),

                    lasso.MD_PROTOCOL_TYPE_SINGLE_LOGOUT)
            logout.http_request_method = method
        # FIXME: use method if supported or find first supported HTTP
        # method

        # Handle partial logout
        if getattr(get_session(), 'partial_logout', False):
            # FIXME: set status code to PartialLogout
            pass
        try:
            logout.buildResponseMsg()
        except lasso.Error, error:
            # Log and display an error
            self.logProfileError(logout, error, 'slo_sp_finish')
            if method == lasso.HTTP_METHOD_SOAP:
                # FIXME: return SOAP Fault
                pass
            return error_then_home("%s: %s" % (_('Internal Error'),
                _('Logout cannot be finished')))
        # FIXME: if we support other binding than SOAP or Redirect, this must
        # be extended
        if method == lasso.HTTP_METHOD_SOAP:
            response = get_response()
            response.set_content_type('text/xml')
            return logout.msgBody
        else:
            if method == lasso.HTTP_METHOD_POST:
                return self.postResponseTo(logout.msgBody,
                        logout.msgUrl, relay_state = logout.relayState,
                        title = _('Logout Response'))
            elif method == lasso.HTTP_METHOD_REDIRECT:
                return redirect(logout.msgUrl)

    def slo_idp(self, session = None, method = None, logout = None):
        # Single Logout initiated by IdP
        if not session:
            session = get_session()
        if not session:
            raise errors.AccessError()
        # shortcut
        if not session.lasso_session_dump:
            return self.slo_idp_finish(logout, session, method)
        if not logout:
            logout = lasso.Logout(get_lasso_server(protocol = 'saml2'))
            load_session(logout, session)
        # delog user
        # Identity loading is useless since lasso >= 2.2.90
        load_identity(logout, identity = None, session = session)
        if logout.identity:
            session.lasso_identity_dump = logout.identity.dump()
        # remember who was logged
        session.previous_user = session.user
        session.user = None
        logout.resetProviderIdIndex()
        if method == lasso.HTTP_METHOD_SOAP:
            return self.slo_idp_soap(logout, session)
        else:
            return self.slo_idp_multi_binding(logout, session)

    def slo_idp_multi_binding [html] (self, logout, session, url = None):
        remote_provider_id = logout.getNextProviderId()
        # Single Logout initiated by IdP; GET profile
        if not remote_provider_id:
            # Early exit
            return self.slo_idp_finish(logout, session)
        session.store()
        url = get_publisher().get_root_url() + 'saml/singleLogoutFinish'
        template.html_top(onload = "setTimeout(function () { window.location = '%s' }, 1000)" % url)
        '<div id="logout-top"><h1>%s</h1></div>' % _('Logout')
        """<div id="logout-sps">
        <p>%s</p>
        <ul>""" % _('Logging out from service providers:')

        while remote_provider_id is not None:
            method = logout.server.getFirstHttpMethod(
                        logout.server.getProvider(remote_provider_id),
                        lasso.MD_PROTOCOL_TYPE_SINGLE_LOGOUT)
            '<li>%s... ' % remote_provider_id
            if method == lasso.HTTP_METHOD_REDIRECT:
                try:
                    # without t=, the result would be printed in the HTML page
                    t = logout.initRequest(remote_provider_id, lasso.HTTP_METHOD_REDIRECT)
                except lasso.Error, error:
                    # Cannot happen with lasso > 2.2.90
                    if error[0] == lasso.PROFILE_ERROR_IDENTITY_NOT_FOUND:
                        break # this is bad, abort.
                    # Cannot happen with lasso > 2.2.90
                    elif error[0] == lasso.PROFILE_ERROR_FEDERATION_NOT_FOUND:
                        message = _('missing federation')
                    elif error[0] == lasso.PROFILE_ERROR_UNSUPPORTED_PROFILE:
                        message = _('logout not supported')
                    else:
                        message = _('internal error')
                    get_logger().error('SLO/GET: logout.initRequest failed for %s: %s' % (remote_provider_id, error[1]))
                    '%s' % message
                else:
                    try:
                        t = logout.buildRequestMsg()
                    except lasso.Error, error:
                        '%s' % _('internal error')
                        get_logger().error('SLO/GET: logout.initRequest failed for %s: %s' % (remote_provider_id, error[1]))
                    else:
                        # TODO: use organization name instead of provider_id
                        '''<iframe src="%(url)s" alt="" marginwidth="0" marginheight="0" scrolling="no" style="border: none" width="12" height="12">
                <img src="%(url)s" width="12" height="12"></img>
                        </iframe>''' % { 'url': logout.msgUrl }
            elif method == lasso.HTTP_METHOD_SOAP:
                if self.slo_soap_one_provider(logout, remote_provider_id):
                    '<img src="%s" alt="OK"/>' % (get_publisher().get_root_url() + 'images/check_on.png')
                else:
                    '<img src="%s" alt="KO"/>' % (get_publisher().get_root_url() + 'images/check_off.png')
            else:
                get_logger().error('SLO: unsupported logout profile for %s: %s' % (remote_provider_id, method))
                'Cannot logout unsupported profile'
            '</li>'
            remote_provider_id = logout.getNextProviderId()
         # FIXME: send proxy logout
        '</ul></div>'
        '<p><a href="%s">%s</a></p>' % (url, _('Finish logging out'))
        session.lasso_logout_dump = logout.dump()
        save_session(logout, session)

    def slo_soap_one_provider(self, logout, remote_provider_id):
        '''Could raise lasso.ProfileIdentityNotFoundError with lasso < 2.2.90'''
        assert(remote_provider_id is not None)
        try:
            logout.initRequest(remote_provider_id, lasso.HTTP_METHOD_SOAP)
            logout.buildRequestMsg()
            soap_answer = self.soap_call(logout.msgUrl, logout.msgBody)
            logout.processResponseMsg(soap_answer)
        except soap.SOAPException, exception:
            self.logProfileError(logout, exception, 'slo_idp_soap: HTTP transport failed')
        except Exception, error:
            self.logProfileError(logout, error, 'slo_idp_soap')
        else:
            return True
        return False

    def slo_idp_soap(self, logout, session):
        '''SLO IDP Special case for SOAP only'''
        remote_provider_id = logout.getNextProviderId()
        if not remote_provider_id:
            # FIXME: send proxy soap logout
            return self.slo_idp_finish(logout, session, method = lasso.HTTP_METHOD_SOAP)
        self.slo_soap_one_provider(logout, remote_provider_id)
        return self.slo_idp_soap(logout, session)

    def slo_idp_finish(self, logout, session, method = None):
        '''End of the SLO IdP sequence'''
        if getattr(session, 'in_slo_sp', False):
            session.in_slo_sp = False
            return self.slo_sp_finish(logout, method)
        else:
            get_session_manager().expire_session()
            return misc.redirect_to_after_url() or misc.redirect_home()

    def singleLogoutReturn(self):
        session = get_session()
        ok = True
        # return must not need a logged user
        if not session:
            raise errors.AccessError()
        logout = lasso.Logout(get_lasso_server(protocol = 'saml2'))
        load_session(logout, session)
        if getattr(session, "lasso_identity_dump", None):
            # FIXME: in later version of lasso this should be useless
            logout.setIdentityFromDump(session.lasso_identity_dump)
        else:
            load_identity(logout, identity = None, session = session)
        request = get_request()
        try:
            logout.processResponseMsg(request.get_query())
        except lasso.Error, error:
            self.logProfileError(logout, error, 'singleLogoutReturn')
            ok = False

        if ok:
            get_request().session = None
            return self.check_on()
        else:
            session.partial_logout = True
            session.store()
            return self.check_off()

    # Defederation

    def manageNameId(self):
        request = get_request()
        if not lasso.isSamlQuery(request.get_query()):
            return redirect('.')

        manage = lasso.NameIdManagement(get_lasso_server(protocol = 'saml2'))
        manage.processRequestMsg(request.get_query())

        session = get_session()
        authentic.identities.get_store().load_identities()
        try:
            identity = authentic.identities.get_store().get_identity(session.user)
        except KeyError:
            identity = None

        self.manage_name_id(manage, identity)
        return redirect(manage.msgUrl)

    def manageNameIdReturn(self):
        # TODO: Implement this function using Lasso
        return redirect(get_publisher().get_root_url())

    @common.soap_endpoint
    def manageNameIdSOAP(self):
        soap_message = self.get_soap_message()

        response = get_response()
        response.set_content_type('text/xml')

        request_type = lasso.getRequestTypeFromSoapMsg(soap_message) 

        if request_type != lasso.REQUEST_TYPE_NAME_ID_MANAGEMENT:
            get_logger().warn('SOAP message on name id management url of not appropriate type')
            return

        manage = lasso.NameIdManagement(get_lasso_server(protocol = 'saml2'))
        manage.processRequestMsg(soap_message)

        name_identifier = manage.nameIdentifier.content
        identity = authentic.identities.get_store().get_identity_for_name_identifier(name_identifier)
        self.manage_name_id(manage, identity)
        return manage.msgBody


    def manage_name_id(self, manage, identity):
        load_identity(manage, identity = identity)

        try:
            manage.validateRequest()
        except lasso.Error, error:
            self.logProfileError(manage, error, 'manage.validateRequest')
        else:
            save_identity(manage, identity = identity)
        manage.buildResponseMsg()

    def metadata(self):
        get_publisher().reload_cfg()
        response = get_response()
        response.set_content_type('text/xml', 'utf-8')
        metadata = unicode(open(authentic.misc.get_abs_path(
                        get_cfg('idp')['saml2_metadata'])).read(), 'utf-8')
        return metadata

    def public_key(self):
        get_publisher().reload_cfg()
        response = get_response()
        response.set_content_type('application/octet-stream')
        publickey = file(authentic.misc.get_abs_path(get_cfg('idp')['publickey'])).read()
        return publickey

    def proxy_login_fill_authn_request(self, login, nameIdPolicy = None):
        session = get_session()
        login.request.nameIDPolicy.format = lasso.SAML2_NAME_IDENTIFIER_FORMAT_PERSISTENT
        login.request.nameIDPolicy.allowCreate = True
        login.request.forceAuthn = False
        login.request.isPassive = False
        login.request.consent = 'urn:oasis:names:tc:SAML:2.0:consent:current-implicit'
        if session.lasso_login_dump:
            login_proxy = lasso.Login.newFromDump(login.server, session.lasso_login_dump)
            if (hasattr(lasso, 'SAML2_NAME_IDENTIFIER_FORMAT_EMAIL') and \
                    login_proxy.request.nameIDPolicy.format == lasso.SAML2_NAME_IDENTIFIER_FORMAT_EMAIL) or \
                    (hasattr(lasso, 'SAML2_NAME_IDENTIFIER_FORMAT_UNSPECIFIED') and \
                    login_proxy.request.nameIDPolicy.format == lasso.SAML2_NAME_IDENTIFIER_FORMAT_UNSPECIFIED):
                login.request.nameIDPolicy.format = login_proxy.request.nameIDPolicy.format
            login.request.protocolBinding = login_proxy.request.protocolBinding
            # XXX: replicate other parameters from the request we proxy
        if nameIdPolicy: # forced value
            login.request.nameIDPolicy.format = lasso.SAML2_NAME_IDENTIFIER_FORMAT_PERSISTENT

    def perform_proxy_login(self, idp = None, nameIdPolicy = None, extensions = None, relay_state = None):
        server = get_lasso_server(lasso.PROVIDER_ROLE_SP, protocol = 'saml2')
        login = lasso.Login(server)
        login.initAuthnRequest(idp, lasso.HTTP_METHOD_REDIRECT)
        self.proxy_login_fill_authn_request(login, nameIdPolicy)

        if relay_state is not None:
            get_session().remember(login.request.requestId, relay_state)

        try:
            try:
                login.buildAuthnRequestMsg()
            except lasso.Error, error:
                self.logProfileError(login, error, 'perform_proxy_login HTTP-Redirect login.buildAuthnRequestMsg')
                login.initAuthnRequest(idp, lasso.HTTP_METHOD_POST)
                self.proxy_login_fill_authn_request(login, nameIdPolicy)
                login.buildAuthnRequestMsg()
        except lasso.Error, error:
            self.logProfileError(login, error, 'perform_proxy_login HTTP-POST login.buildAuthnRequestMsg')
            return error_then_home("%s: %s" % (_('Internal Error'), _('Proxy Login failed')))

        if login.msgBody:
            return self.postRequestTo(login.msgUrl, login.msgBody,
                    relay_state = login.relayState, title = N_('Authentication Request'))
        else:
            return redirect(login.msgUrl)

    def proxy_auth_ok(self, login):
        pass # to override

    def proxy_auth_peer_cancelled(self, login):
        pass

    def proxy_auth_federation_not_found(self, login):
        pass

    def proxyAssertionConsumerArtifact(self):
        server = get_lasso_server(lasso.PROVIDER_ROLE_SP, protocol = 'saml2')
        if not server:
            return template.error_page(_('SAML 2.0 support not yet configured.'))
        login = lasso.Login(server)
        request = get_request()
        session = get_session()

        if request.get_method() == 'GET':
            artifact = request.get_query()
            response_method = lasso.HTTP_METHOD_ARTIFACT_GET
        else:
            artifact = request.form.get('SAMLart')
            response_method = lasso.HTTP_METHOD_ARTIFACT_POST

        try:
            login.initRequest(artifact, response_method)
        except lasso.Error, error:
            if error[0] == lasso.PROFILE_ERROR_MISSING_ARTIFACT:
                get_logger().error(_('during proxy single sign on received a query without an artifact: %s') % request.get_query())
                return template.error_page(_('Missing SAML Artifact'))
            else:
                self.logProfileError(login, error, 'proxySingleSignOnArtifact login.initRequest')
                return error_then_home(_('Internal Server Error'))

        login.buildRequestMsg()

        try:
            soap_answer = self.soap_call(login.msgUrl, login.msgBody)
        except SOAPException:
            return template.error_page(_('Failure to communicate with identity provider'))

        try:
            login.processResponseMsg(soap_answer)
        except lasso.Error, error:
            self.logLoginProcessResponseMsgError(login, error)
            if error[0] == lasso.LOGIN_ERROR_FEDERATION_NOT_FOUND:
                t = self.proxy_auth_federation_not_found(login)
                if t:
                    return t
                return template.error_page('there was no federation')
            t = self.proxy_auth_peer_cancelled(login)
            if t:
                return t
            login = lasso.Login.newFromDump(get_lasso_server(protocol = 'saml2'),
                    session.lasso_login_dump)
            session.lasso_login_dump = None
            return self.sso_after_authentication(login, False, proxied = True)
        else:
            self.proxy_auth_ok(login)

        return self.proxy_sso_ok(login)

    def proxyAssertionConsumerPost(self):
        server = get_lasso_server(lasso.PROVIDER_ROLE_SP, protocol = 'saml2')
        if not server:
            return template.error_page(_('SAML 2.0 support not yet configured.'))
        login = lasso.Login(server)
        request = get_request()
        session = get_session()

        try:
            login.processAuthnResponseMsg(get_field('SAMLResponse'))
        except lasso.Error, error:
            if error[0] == lasso.LOGIN_ERROR_FEDERATION_NOT_FOUND:
                t = self.proxy_auth_federation_not_found(login)
                if t:
                    return t
                return template.error_page('there was no federation')
            t = self.proxy_auth_peer_cancelled(login)
            if t:
                return t
            login = lasso.Login.newFromDump(get_lasso_server(protocol = 'saml2'),
                    session.lasso_login_dump)
            session.lasso_login_dump = None
            return self.sso_after_authentication(login, False, proxied = True)
        else:
            self.proxy_auth_ok(login)

        return self.proxy_sso_ok(login)

    def proxyAssertionConsumerRedirect(self):
        server = get_lasso_server(lasso.PROVIDER_ROLE_SP, protocol = 'saml2')
        if not server:
            return template.error_page(_('SAML 2.0 support not yet configured.'))
        login = lasso.Login(server)
        request = get_request()
        session = get_session()

        try:
            login.processAuthnResponseMsg(request.get_query())
        except lasso.Error, error:
            if error[0] == lasso.LOGIN_ERROR_FEDERATION_NOT_FOUND:
                t = self.proxy_auth_federation_not_found(login)
                if t:
                    return t
                return template.error_page('there was no federation')
            t = self.proxy_auth_peer_cancelled(login)
            if t:
                return t
            login = lasso.Login.newFromDump(get_lasso_server(protocol = 'saml2'),
                    session.lasso_login_dump)
            session.lasso_login_dump = None
            return self.sso_after_authentication(login, False, proxied = True)
        else:
            self.proxy_auth_ok(login)

        return self.proxy_sso_ok(login)

    def proxy_sso_ok(self, login):
        session = get_session()
        if session.lasso_proxy_session_dump:
            login.setSessionFromDump(session.lasso_proxy_session_dump)
        try:
            common.SessionIndex(login.response.assertion[0].authnStatement[0].sessionIndex).store()
        except:
            pass
        ni = login.nameIdentifier.content
        authentic.identities.get_store().load_identities()
        identity = authentic.identities.get_store().get_identity_for_name_identifier(ni)

        if identity is None and get_session().user:
            identity = authentic.identities.get_store().get_identity(get_session().user)

        if identity:
            if identity.lasso_proxy_dump:
                login.setIdentityFromDump(identity.lasso_proxy_dump)
        else:
            # XXX: only create identity when "federated" ?
            identity = authentic.identities.Identity()
            identity_keys = authentic.identities.get_store().keys()
            base_key = get_provider_key(login.remoteProviderId)
            i = 1
            while ('%s-%d' % (base_key, i)) in identity_keys:
                i += 1
            identity.id = '%s-%d' % (base_key, i)
            identity.name = ''
            identity.email = ''
            identity.proxied_identity_origin = login.remoteProviderId
            authentic.identities.get_store().add(identity)
        session.set_user(identity.id)
        session.authentication_instant = datetime.datetime.utcnow()
        session.authentication_method = 'saml'
        session.proxied_idp = login.remoteProviderId
        if not session.name_identifiers:
            session.name_identifiers = []
        session.name_identifiers.append(login.nameIdentifier.content)
        
        login.acceptSso()
        if login.isIdentityDirty:
            identity.lasso_proxy_dump = login.identity.dump()
        if login.isSessionDirty:
            session.lasso_proxy_session_dump = login.session.dump()

        authentic.identities.get_store().save(identity)
            
        if session.lasso_login_dump:
            login = lasso.Login.newFromDump(get_lasso_server(protocol = 'saml2'),
                    session.lasso_login_dump)
            session.lasso_login_dump = None
            return self.sso_after_authentication(login, True, proxied = True)

        if session.after_url:
            after_url = session.after_url
            session.after_url = None
            return redirect(after_url)
        return redirect(get_request().environ['SCRIPT_NAME'] + '/')


    def proxy_slo_soap(self, session, identity):
        # slo request from SP, continue with SOAP logout request to proxied-IdP
        server = get_lasso_server(lasso.PROVIDER_ROLE_SP, protocol = 'saml2')
        logout = lasso.Logout(server)
        load_session(logout, session)
        if identity and identity.lasso_proxy_dump:
            logout.setIdentityFromDump(identity.lasso_proxy_dump)
        logout.initRequest(session.proxied_idp, lasso.HTTP_METHOD_SOAP)
        logout.buildRequestMsg()
        try:
            soap_answer = self.soap_call(logout.msgUrl, logout.msgBody)
        except soap.SOAPException:
            pass
        else:
            try:
                logout.processResponseMsg(soap_answer)
            except lasso.Error, error:
                pass
            else:
                save_session(logout, session)

    # Keep compatibility with old metadatas files
    proxySingleSignOnArtifact = proxyAssertionConsumerArtifact
    proxySingleSignOnPost = proxyAssertionConsumerPost
    proxySingleSignOnRedirect = proxyAssertionConsumerRedirect

class SpUI(AccessControlled, common.LassoDirectory):
    _q_exports = ['login', 'terminate']

    def _q_access(self):
        session = get_session()
        if not session or session.user is None:
            raise errors.AccessUnauthorizedError()

    def __init__(self, component):
        self.provider_key = component
        try:
            self.lp = get_cfg('providers')[component]
        except KeyError:
            raise errors.TraversalError()

    def init_provider(self, role = lasso.PROVIDER_ROLE_SP, encryption_mode = None):
        lp = self.lp
        if lp['role'] != role and lp['role'] != lasso.PROVIDER_ROLE_NONE:
            raise errors.TraversalError()
        self.p = lasso.Provider(role,
                authentic.misc.get_abs_path(lp['metadata']),
                authentic.misc.get_abs_path(lp.get('publickey')), None)
        if self.p is not None:
            if encryption_mode is None:
                # take config from settings
                encryption_mode = lasso.ENCRYPTION_MODE_NONE
                if get_cfg('providers')[self.provider_key].get('encrypt_nameid'):
                    encryption_mode |= lasso.ENCRYPTION_MODE_NAMEID
                if get_cfg('providers')[self.provider_key].get('encrypt_assertion'):
                    encryption_mode |= lasso.ENCRYPTION_MODE_ASSERTION

            if encryption_mode:
                self.p.setEncryptionMode(encryption_mode);


    def login(self, encryption_mode = None, method = None, nid_format = 'none',
            relay_state = None):
        self.init_provider(encryption_mode = encryption_mode)
        login = lasso.Login(get_lasso_server(protocol = 'saml2'))
        login.initIdpInitiatedAuthnRequest(self.p.providerId)

        if method:
            login.request.protocolBinding = method

        if nid_format == 'persistent':
            login.request.nameIDPolicy.format = lasso.SAML2_NAME_IDENTIFIER_FORMAT_PERSISTENT
        elif nid_format == 'transient':
            login.request.nameIDPolicy.format = lasso.SAML2_NAME_IDENTIFIER_FORMAT_TRANSIENT
        elif nid_format == 'encrypted':
            login.request.nameIDPolicy.format = lasso.SAML2_NAME_IDENTIFIER_FORMAT_ENCRYPTED
        elif nid_format == 'none':
            login.request.nameIDPolicy.format = lasso.SAML2_NAME_IDENTIFIER_FORMAT_UNSPECIFIED

        if relay_state:
            login.request.relayState = relay_state

        login.request.nameIDPolicy.allowCreate = True

        get_logger().info('SSO to %s' % self.p.providerId)
        try:
            login.processAuthnRequestMsg(None)
        except lasso.Error, error:
            if error[0] == lasso.LOGIN_ERROR_NO_DEFAULT_ENDPOINT:
                return template.error_page(
                        _('No default endpoint for this service provider'))
            self.logProfileError(login, error, "login.processAuthnRequestMsg")
            return template.error_page(
                    _('No default endpoint for this service provider'))

        session = get_session()
        session.saml2 = True

        rt = RootDirectory()
        return rt.sso_after_consent(login, True, True, intro_cookie = False)


    def terminate(self, method = lasso.HTTP_METHOD_SOAP):
        self.init_provider()
        manage = lasso.NameIdManagement(get_lasso_server(protocol = 'saml2'))
        session = get_session()
        # No more needed for Lasso > 2.2.90
        load_session(manage, session)
        load_identity(manage, identity = None, session = session)

        get_logger().info('fedterm to %s' % self.p.providerId)
        try:
            manage.initRequest(self.p.providerId, None, method)
        except lasso.Error, error:
            if error[0] == lasso.PROFILE_ERROR_FEDERATION_NOT_FOUND:
                raise errors.TraversalError() # no such federation
            if error[0] == lasso.PROFILE_ERROR_IDENTITY_NOT_FOUND:
                raise errors.TraversalError() # no such federation
            elif error[0] == lasso.PROFILE_ERROR_UNSUPPORTED_PROFILE:
                if method != lasso.HTTP_METHOD_REDIRECT:
                    self.terminate(lasso.HTTP_METHOD_REDIRECT)
                else:
                    get_logger().error('FTN initRequest failed for %s: no supported profile' % self.p.providerId)
                    return error_then_home(_("This service doesn't support federation termination"))
            get_logger().error('FTN initRequest failed for %s: %s' % (self.p.providerId, error[1]))
            return error_then_home(_('Federation termination failed for %s') % self.p.providerId)

        try:
            manage.buildRequestMsg()
        except lasso.Error, error:
            get_logger().error('FTN failed for %s: %s' % (self.p.providerId, error[1]))
            return error_then_home(_('Federation termination failed for %s') % self.p.providerId)

        name_identifier = manage.nameIdentifier.content
        save_identity(manage)
        if name_identifier in (session.name_identifiers or []):
            session.name_identifiers.remove(name_identifier)

        if not manage.msgUrl:
            return error_then_home(_("Service provider doesn't support federation termination profile."))

        if manage.msgBody:
            try:
                response_msg = self.soap_call(manage.msgUrl, manage.msgBody)
            except soap.SOAPException:
                return error_then_home(_('Service provider failed to process request.'))
        else:
            return redirect(manage.msgUrl)
        try:
            manage.processResponseMsg(response_msg)
        except lasso.Error, error:
            return template.error_page(_('Service provider response could not be processed.'))

        save_identity(manage)
        return misc.redirect_to_return_url() or misc.redirect_home()
