import os
import re
import urllib
import httplib
import Cookie
import socket
import datetime

import lasso
if hasattr(lasso, 'ProfileService') and not hasattr(lasso, 'DataService'):
    lasso.DataService = lasso.ProfileService

from quixote import get_request, get_response, redirect, get_field, get_publisher
from quixote.directory import Directory, AccessControlled
from quixote.http_request import parse_header
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.publisher import sitecharset2utf8

import authentic
import authentic.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 common
import qommon.soap as soap

BENCH = False

class Artifact(StorableObject):
    _names = 'artifacts'
    _indexes = ['artifact']
    artifact = None
    session_id = None
    provider_id = 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)


class RootDirectory(common.LassoDirectory):
    _q_exports = ["", "sp", "singleSignOn", "failSingleSignOn", "soapEndpoint",
            'metadata', ('metadata.xml', 'metadata'), 'public_key',
            "singleLogout", "singleLogoutReturn", "federationTermination",
            "federationTerminationReturn", "registerNameIdentifier", "consent",
            "proxySoapEndpoint", "proxyAssertionConsumer", "proxySingleLogout",
            "proxySingleLogoutNext", 'proxySingleLogoutReturn']

    sp = SpDir()

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

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

    def failSingleSignOn(self):
        '''Handle an AuthnRequest, but always return
        RequestDenied'''
        return self.single_sign_on(request_denied = True)

    def single_sign_on(self, request_denied = False):
        server = get_lasso_server()
        if not server:
            return template.error_page(_('Liberty support not yet configured.'))
        login = lasso.Login(server)
        request = get_request()

        authn_request_msg = None
        if request.get_method() == 'GET':
            if lasso.isLibertyQuery(request.get_query()):
                authn_request_msg = request.get_query()
            if BENCH:
                authn_request_msg = 'RequestID=_CED73573A277216F1F1D046F5155D607&MajorVersion=1&MinorVersion=2&IssueInstant=2006-01-24T10%3A19%3A15Z&ProviderID=http%3A%2F%2Funwind%2Fliberty%2Fmetadata&ForceAuthn=false&IsPassive=false&NameIDPolicy=federated&consent=urn%3Aliberty%3Aconsent%3Aobtained&SigAlg=http%3A%2F%2Fwww.w3.org%2F2000%2F09%2Fxmldsig%23rsa-sha1&Signature=kfu2VYPIiSS%2FGGDdVKxiSd%2F%2Fe8XN6vAPZkf8TxXzNzuEqLKBGQsRQTv4waaDHBYMRmSMOHd81ZpDbc6lqOZLkUdv68QCjMIvF3L53S0V7Mab8PDs2%2B6lNEmuUOn%2F2hwoJ2ol6%2B%2BCKtpIq3xksh8juyyrZgMyjhopGTzmXCwFIhokUFuHD4SX8YC%2BYkFNyDnnD2acU3Id2ip2zmabO6WYh7VOvYSxBl4BtYRWLZ%2BmdZTM0LOUn%2FpSEUAwXi9bmCphdzgxQUd8T5BfRflvBmP%2BMUNSalmsvLwXk15eiVDGbxcmikVsNGpSy0nwQOoCMCgFJC60QeoClmpVjgRfILWBrg%3D%3D'
        else: # method is POST
            authn_request_msg = get_field('LAREQ')

        if not authn_request_msg:
            return template.error_page(_('No authentication request found.'))

        try:
            login.processAuthnRequestMsg(authn_request_msg)
        except lasso.Error, error:
            if error[0] == lasso.SERVER_ERROR_PROVIDER_NOT_FOUND:
                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.'))
            raise

        get_logger().info('SSO from %s' % login.remoteProviderId)

        request_id = login.request.requestId
        session = get_session()
        if BENCH:
            session.user = 'fred'
            session.id = 'SESSION-DE-TEST'
        if session and session.user and not session.lasso_session_dump:
            # user has a session but it was only created locally, so it has no
            # assertion.  However login.mustAuthenticate() will look for an
            # existing session to decide whether to ask for login or not.  So
            # we needs to feed an empty session to the login object.  Note that
            # Lasso doesn't do any namespace checking.
            login.setSessionFromDump('<Session xmlns="http://www.entrouvert.org/namespaces/lasso/0.0" Version="1"/>')
        if session and session.lasso_session_dump:
            login.setSessionFromDump(session.lasso_session_dump)
        if request_denied:
            user_authenticated = False
        elif login.mustAuthenticate() and not login_token.LoginToken.has_good_authentication(request_id):
            # We use a login token to verify ForceAuthn
            login_token.LoginToken(request_id).store()
            self.sso_before_login_redirect()
            login_url = request.environ['SCRIPT_NAME'] + '/login'
            return redirect_with_return_url(login_url, 
                    (('okURL', get_request().get_local_url()),
                     ('cancelURL', get_request().get_local_url().replace('singleSignOn','failSingleSignOn')),
                     ('LoginToken', request_id)))
        else:
            if session and session.user:
                user_authenticated = True
            else:
                user_authenticated = False

        return self.sso_after_authentication(login, user_authenticated)

    def sso_before_login_redirect(self):
        pass # to be subclassed

    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(), session.lasso_login_dump)
            session.lasso_login_dump = None
            return self.sso_after_consent(login, True, consent)

    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)

        consent_obtained = False
        if user_authenticated:
            if login.mustAskForConsent():
                session.lasso_login_dump = login.dump()
                return redirect('consent')
        return self.sso_after_consent(login, user_authenticated, True, proxied)

    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 sso_after_consent(self, login, user_authenticated, consent_obtained, proxied = False):
        # part 3 of SSO: if necessary, user has given its consent
        session, identity = self.restore_user_details(login)

        try:
            login.validateRequestMsg(user_authenticated, consent_obtained)
        except lasso.Error, error:
            if error[0] < 0:
                raise
            do_federation = False
        else:
            do_federation = True
            if not BENCH and lasso.WSF_SUPPORT:
                if identity:
                    if not identity.resource_id:
                        identity.resource_id = '%s/resources/identity/%s' % (
                                get_cfg('idp')['base_url'], identity.id)
                        authentic.identities.get_store().save(identity)
                    login.setResourceId(identity.resource_id)

            authn_methods = {
                'password': lasso.SAML_AUTHENTICATION_METHOD_PASSWORD,
                'password-on-https': lasso.SAML_AUTHENTICATION_METHOD_PASSWORD,
                'password-and-ip': 'urn:oasis:names:tc:SAML:1.0:am:InternetProtocolPassword',
                'client-certificate': lasso.SAML_AUTHENTICATION_METHOD_SOFTWARE_PKI
            }

            auth_method = authn_methods.get(session.authentication_method or 'password')

            if proxied:
                # XXX: if sso is proxied, build it with the same details as
                # the proxied assertion
                # (current condition looks ridiculous, I know)
                login.buildAssertion(auth_method, None, None, None, None)
            else:
                now = datetime.datetime.utcnow()
                notBefore = now-datetime.timedelta(0,60)
                notOnOrAfter = now+datetime.timedelta(0,60)
                login.buildAssertion(auth_method,
                        get_session().authentication_instant.isoformat()+'Z', 
                        None,
                        notBefore.isoformat()+'Z',
                        notOnOrAfter.isoformat()+'Z')


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

            login.assertion.authenticationStatement.sessionIndex = login.assertion.assertionId
            common.SessionIndex(login.assertion.assertionId).store()

            if identity and hasattr(identity, 'attributes') and identity.attributes:
                t = lasso.NodeList()
                if login.assertion.attributeStatement:
                    for at in login.assertion.attributeStatement.attribute:
                        t.append(at)

                login.assertion.attributeStatement = lasso.SamlAttributeStatement()

                for k, v in identity.attributes.items():
                    ta = lasso.SamlAttribute()
                    t.append(ta)
                    ta.attributeName = k
                    ta.attributeNamespace = 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic'

                    t2 = lasso.NodeList()
                    for vi in v:
                        tav = lasso.SamlAttributeValue()
                        t2.append(tav)

                        t3 = lasso.NodeList()

                        ttn = lasso.MiscTextNode()
                        if type(vi) is unicode:
                            ttn.content = vi.encode('utf-8')
                        else:
                            ttn.content = sitecharset2utf8(vi)
                        ttn.text_child = True
                        t3.append(ttn)
                        tav.any = t3

                    ta.attributeValue = t2

                login.assertion.attributeStatement.attribute = t

        if login.protocolProfile == lasso.LOGIN_PROTOCOL_PROFILE_BRWS_ART:
            login.buildArtifactMsg(lasso.HTTP_METHOD_REDIRECT)
        elif login.protocolProfile == lasso.LOGIN_PROTOCOL_PROFILE_BRWS_POST:
            login.buildAuthnResponseMsg()
        else:
            return template.error_page(_('Unknown liberty protocol profile'))

        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 = []
            session.name_identifiers.append(login.nameIdentifier.content)

        if login.isSessionDirty:
            session.lasso_session_dump = login.session.dump()

        self.handle_common_domain_cookie(login)

        if login.protocolProfile == lasso.LOGIN_PROTOCOL_PROFILE_BRWS_ART:
            artifact = Artifact(id = login.assertionArtifact)
            artifact.artifact = login.assertionArtifact
            artifact.session_id = session.id
            artifact.provider_id = login.remoteProviderId
            artifact.store()
            return redirect(login.msgUrl)
        else:
            return self.postTo(login.msgUrl, login.msgBody, 'LARES',
                    relay_state=login.relayState,
                    title=N_('Authentication Response'))

    def handle_common_domain_cookie(self, login):
        get_publisher().reload_cfg()
        try:
            common_domain = get_cfg('idp')['common_domain']
        except KeyError:
            common_domain = None
        if common_domain:
            request = get_request()
            try:
                intro_cookie = request.cookies['_liberty_idp']
            except KeyError:
                intro_cookie = ''
            splitted_cookie = [x.strip() for x in intro_cookie.split(' ') if x]
            succinct_id = login.server.getBase64SuccinctId()
            if succinct_id in splitted_cookie:
                splitted_cookie.remove(succinct_id)
            splitted_cookie.append(succinct_id)
            response = get_response()
            new_cookie = ' '.join(splitted_cookie)
            if new_cookie != intro_cookie:
                response.set_cookie('_liberty_idp', new_cookie,
                        domain = '.' + common_domain, path = '/',
                        expires = Cookie._getdate(3*365*86400))

    @common.soap_endpoint
    def soapEndpoint(self):
        soap_message = self.get_soap_message()
        authentic.identities.get_store().load_identities()
        request_type = lasso.getRequestTypeFromSoapMsg(soap_message) 
        if request_type == lasso.REQUEST_TYPE_LOGIN:
            login = lasso.Login(get_lasso_server())
            login.processRequestMsg(soap_message)
            artifact = None
            try:
                artifact = Artifact.get_by_artifact(login.assertionArtifact)
            except KeyError:
                provider_id = None
            else:
                artifact.remove_self()
                provider_id = artifact.provider_id
                session = get_session_manager().get(artifact.session_id)
                if session: # it should always be there but who knows...
                    login.setSessionFromDump(session.lasso_session_dump)
            try:
                login.buildResponseMsg(provider_id)
                get_logger().info('Artifact resolve from provider %s for artifact %s' % (provider_id, login.assertionArtifact))
            except lasso.Error, error:
                if error[0] == lasso.DS_ERROR_SIGNATURE_VERIFICATION_FAILED:
                    get_logger().warn('Signature verification failed on artifact resolve from %s)' % provider_id)
                elif error[0] == lasso.PROFILE_ERROR_SESSION_NOT_FOUND:
                    if not artifact:
                        get_logger().warn('Could not find the artifact on artifact resolve from %s, artifact %s)' % (provider_id, login.assertionArtifact))
                    else:
                        get_logger().warn('Could not find a session dump on artifact resolve from %s, artifact %s)' % (provider_id, login.assertionArtifact))
                else:
                    raise
            if login.isSessionDirty:
                if login.session:
                    session.lasso_session_dump = login.session.dump()
                else:
                    session.lasso_session_dump = None 
                get_session_manager().commit_changes(session)

            return login.msgBody

        if request_type == lasso.REQUEST_TYPE_LOGOUT:
            logout = lasso.Logout(get_lasso_server())
            logout.processRequestMsg(soap_message)
            name_identifier = logout.nameIdentifier.content
            try:
                session_index = common.SessionIndex.get(logout.request.sessionIndex)
                logout_session = get_session_manager().get(session_index.session_id)
            except KeyError:
                # no session, build straight failure answer
                logout.buildResponseMsg()
                return logout.msgBody
            authentic.identities.get_store().load_identities()
            try:
                identity = authentic.identities.get_store().get_identity(session.user)
            except KeyError:
                pass
            else:
                if identity.lasso_dump:
                    logout.setIdentityFromDump(identity.lasso_dump)

            if session.proxied_idp:
                try:
                    self.proxy_slo_soap(session, identity)
                except lasso.Error, error:
                    if error[0] == lasso.LOGOUT_ERROR_UNSUPPORTED_PROFILE:
                        pass
                    # too bad but we won't fallback to HTTP redirect for
                    # the sake of the IdP
                logout.setSessionFromDump(session.lasso_session_dump)

            try:
                logout.validateRequest()
            except lasso.Error, error:
                if error[0] == lasso.LOGOUT_ERROR_UNSUPPORTED_PROFILE:
                    pass
                elif error[0] == lasso.PROFILE_ERROR_SESSION_NOT_FOUND:
                    pass
                else:
                    raise
            else:
                session_manager = get_session_manager()
                del session_manager[session_index.session_id]
                remote_provider_id = logout.getNextProviderId()
                while remote_provider_id:
                    try:
                        logout.initRequest(remote_provider_id, lasso.HTTP_METHOD_SOAP)
                    except lasso.Error, error:
                        if error[0] == lasso.PROFILE_ERROR_IDENTITY_NOT_FOUND:
                            break # this is bad, abort.
                        if error[0] == lasso.LOGOUT_ERROR_UNSUPPORTED_PROFILE:
                            get_logger().warn('SLO not supported on %s' % remote_provider_id)
                            break # this doesn't happen with newer Lasso releases
                        if error[0] == lasso.PROFILE_ERROR_UNSUPPORTED_PROFILE:
                            get_logger().warn('SLO not supported on %s' % remote_provider_id)
                            break # this doesn't happen with newer Lasso releases
                        raise
                    logout.buildRequestMsg()
                    try:
                        soap_answer = self.soap_call(logout.msgUrl, logout.msgBody)
                        logout.processResponseMsg(soap_answer)
                    except soap.SOAPException:
                        pass # log ?
                    except lasso.Error, error:
                        pass # log ?
                    remote_provider_id = logout.getNextProviderId()

            logout.buildResponseMsg()
            return logout.msgBody

        if request_type == lasso.REQUEST_TYPE_DEFEDERATION:
            defederation = lasso.Defederation(get_lasso_server())
            defederation.processNotificationMsg(soap_message)
            name_identifier = defederation.nameIdentifier.content
            identity = authentic.identities.get_store().get_identity_for_name_identifier(name_identifier)
            for session in get_session_manager().values():
                if name_identifier in (session.name_identifiers or []):
                    get_logger().info('FedTerm/SOAP from %s' % logout.remoteProviderId)
                    break
            else:
                session = None
            if session and session.proxied_idp:
                pass # XXX: fedterm with IdP
            return self.fedterm(defederation, identity, session)

        if request_type == lasso.REQUEST_TYPE_NAME_REGISTRATION:
            registration = lasso.NameRegistration(get_lasso_server())
            registration.processRequestMsg(soap_message)
            oldNameIdentifier = registration.oldNameIdentifier.content
            identity = authentic.identities.get_store().get_identity_for_name_identifier(
                    oldNameIdentifier)
            for session in get_session_manager().values():
                if oldNameIdentifier in (session.name_identifiers or []):
                    break
            return self.rni(registration, identity, session)

        idsis_pp = get_cfg('idp').get('idsis_pp', False)
        if request_type == lasso.REQUEST_TYPE_DISCO_QUERY and lasso.WSF_SUPPORT:
            server = get_lasso_server()
            disco = lasso.Discovery(server)
            disco.processQueryMsg(soap_message)
            try:
                identity = self.get_identity_by_resource_id(disco.resourceId)
                disco.setIdentityFromDump(identity.lasso_dump)
            except:
                pass
            disco.buildResponseMsg()
            return disco.msgBody

        if request_type == lasso.REQUEST_TYPE_DISCO_MODIFY and lasso.WSF_SUPPORT:
            server = get_lasso_server()
            disco = lasso.Discovery(server)
            disco.processModifyMsg(soap_message, None)
            try:
                identity = self.get_identity_by_resource_id(disco.resourceId)
                disco.setIdentityFromDump(identity.lasso_dump)
            except:
                pass
            disco.buildModifyResponseMsg()
            if disco.isIdentityDirty:
                identity.lasso_dump = disco.identity.dump()
                authentic.identities.get_store().save(identity)
            return disco.msgBody

        if request_type == lasso.REQUEST_TYPE_DST_QUERY and lasso.WSF_SUPPORT:
            service = lasso.DataService(get_lasso_server())
            # XXX: lasso lacks a function to get the dst namespace
            try:
                service_prefix = re.findall('<([a-z]*?):Query>', soap_message)[0]
                service_href = re.findall('xmlns:%s="(.*?)"' % service_prefix, soap_message)[0]
            except IndexError: # too bad, this hacky thing didn't work.
                service_href = lasso.PP_HREF

            if service_href == lasso.PP_HREF and not idsis_pp:
                pass # XXX: build deny request
                return 'ERROR'

            service.processQueryMsg(soap_message)
            try:
                identity = self.get_identity_by_resource_id(service.resourceId)
            except:
                pass # XXX: build deny request ?

            resource = identity.get_dst_view(service_href)
            if resource:
                service.resourceData = identity.get_dst_view(service_href)
            else:
                return 'ERROR' # XXX: build deny request
            service.buildResponseMsg()
            return service.msgBody

    def get_identity_by_resource_id(self, resource_id):
        resource = resource_id.content
        prefix = '%s/resources/' % get_cfg('idp')['base_url']
        if not resource.startswith(prefix):
            raise "unknown resource"
        resource = resource[len(prefix):]
        type, value = resource.split('/')
        if type != 'identity': # we only know identities as resource
            raise "resource not identity"

        try:
            identity = authentic.identities.get_store().get_identity(value)
        except KeyError:
            raise "identity not found"

        if lasso.WSF_SUPPORT:
            idsis_pp = get_cfg('idp').get('idsis_pp', False)
            lasso_identity = lasso.Identity.newFromDump(identity.lasso_dump)
            server = authentic.misc.get_lasso_server()
            offerings = lasso_identity.getOfferings(lasso.PP_HREF)
            if offerings:
                lst = [x for x in lasso_identity.getOfferings(lasso.PP_HREF) if \
                      x.serviceInstance.providerId == server.providerId]
            else:
                lst = []
            if idsis_pp and len(lst) == 0:
                # adds authentic id-sis pp offering
                resource_offering = lasso.DiscoResourceOffering(self.get_pp_service())
                resource_offering.resourceId = lasso.DiscoResourceID(identity.resource_id)
                resource_offering.abstract = "Personal Profile with Authentic informations"
                lasso_identity.addResourceOffering(resource_offering)
                identity.lasso_dump = lasso_identity.dump()
                authentic.identities.get_store().save(identity)
            elif not idsis_pp and len(lst) > 0:
                # remove authentic id-sis pp offerings
                try:
                    for x in lst:
                        lasso_identity.removeResourceOffering(x.entryId)
                except:
                    pass # ignores everything since it would raise an exception even on success
                identity.lasso_dump = lasso_identity.dump()
                authentic.identities.get_store().save(identity)
        return identity

    def get_pp_service(self):
        server = authentic.misc.get_lasso_server()
        return lasso.DiscoServiceInstance(
                lasso.PP_HREF,
                server.providerId,
                lasso.DiscoDescription_newWithBriefSoapHttpDescription(
                    lasso.SECURITY_MECH_NULL,
                    '%s/soapEndpoint' % get_cfg('idp', {}).get('base_url', None)))

    def singleLogout(self):
        request = get_request()
        if lasso.isLibertyQuery(request.get_query()):
            return self.slo_sp()
        else:
            session = get_session()
            # TODO: settings for logout type (soap/get/redirect)
            return self.slo_idp(session)

    def slo_idp(self, session, method = None):
        # Single Logout initiated by IdP
        if not session or not session.user:
            raise errors.AccessError()
        # delog user
        user = session.user
        session.user = None
        session.store()
        if not session.lasso_session_dump:
            if method == lasso.HTTP_METHOD_SOAP:
                return
            return redirect(get_request().environ['SCRIPT_NAME'] + '/logout')

        logout = lasso.Logout(get_lasso_server())
        logout.setSessionFromDump(session.lasso_session_dump)

        authentic.identities.get_store().load_identities()
        try:
            identity = authentic.identities.get_store().get_identity(user)
        except KeyError:
            if method == lasso.HTTP_METHOD_SOAP:
                return
            return redirect(get_request().environ['SCRIPT_NAME'] + '/logout')
        else:
            if identity.lasso_dump:
                # keep a copy of the identity dump, for singleLogoutReturn
                session.lasso_identity_dump = identity.lasso_dump
                session.store()
                logout.setIdentityFromDump(identity.lasso_dump)

        if method == lasso.HTTP_METHOD_SOAP:
            return self.slo_idp_soap(logout)
        else:
            return self.slo_idp_get(logout)

    def slo_idp_get [html] (self, logout, url = None):
        # Single Logout initiated by IdP; GET profile
        session = get_session()
        session.in_slo_get = True
        if not url:
            url = '%s/logout' % get_request().environ['SCRIPT_NAME']

        template.html_top(onload = "window.location = '%s'" % url)
        '<div id="logout-top"><h1>%s</h1></div>' % _('Logout')
        """<div id="logout-sps">
        <p>%s</p>
        <ul>""" % _('Logging out from service providers:')

        remote_provider_id = logout.getNextProviderId()
        while remote_provider_id is not None:
            try:
                t = logout.initRequest(remote_provider_id, lasso.HTTP_METHOD_REDIRECT)
            except lasso.Error, error:
                if error[0] == lasso.PROFILE_ERROR_IDENTITY_NOT_FOUND:
                    get_logger().warn('SLO/GET failed: identity not found')
                    break
                elif error[0] == lasso.LOGIN_ERROR_FEDERATION_NOT_FOUND:
                    '<li>%s... %s</li>' % (remote_provider_id, _('federation not found'))
                elif error[0] == lasso.PROFILE_ERROR_UNSUPPORTED_PROFILE:
                    '<li>%s... %s</li>' % (remote_provider_id, _('logout not supported'))
                else:
                    raise
            else:
                t = logout.buildRequestMsg()
                session.lasso_logout_dump = logout.dump()
                organization_name = logout.server.getProvider(remote_provider_id).getOrganization() or remote_provider_id
                '<li>%s... <img src="%s" alt="" width="12" height="12" /></li>' % (
                        organization_name, logout.msgUrl)
            remote_provider_id = logout.getNextProviderId()
        '</ul></div>'
        '<p><a href="%s">%s</a></p>' % (url, _('Finish logging out'))

    def slo_idp_soap(self, logout):
        remote_provider_id = logout.getNextProviderId()
        session = get_session()
        finish_with_get = False
        while remote_provider_id:
            try:
                logout.initRequest(remote_provider_id, lasso.HTTP_METHOD_SOAP)
            except lasso.ProfileIdentityNotFoundError:
                # FIXME: this should not happen
                get_logger().warn('SLO/SOAP initRequest failed error: IdentityNotFound')
                break # this is bad, abort.
            except lasso.ProfileUnsupportedProfileError:
                # If one SP doesn't support SLO SOAP binding, switch to SLO GET
                # binding for all SP
                finish_with_get = True
            except lasso.Error, error:
                raise
            else:
                try:
                    logout.buildRequestMsg()
                    soap_answer = self.soap_call(logout.msgUrl, logout.msgBody)
                except soap.SOAPException:
                    pass
                except lasso.ServerProviderNotFoundError:
                    get_logger().warn('SLO/SOAP failed because provider was not found, providerID: %s' % remote_provider_id)
                except lasso.ProfileUnknownProfileUrlError:
                    get_logger().warn('SLO/SOAP failed because profile URL was empty, providerID: %s' % remote_provider_id)
                except lasso.ProfileMissingRequestError:
                    get_logger().warn('SLO/SOAP failed because of a missing request, providerID: %s' % remote_provider_id)
                except lasso.Error, error:
                    get_logger().warn('SLO/SOAP buildRequestMsg error, providerID: %s error: %s' % (remote_provider_id, error[1]))

                else:
                    try:
                        logout.processResponseMsg(soap_answer)
                    except lasso.ProfileInvalidMsgError:
                        get_logger().warn('SLO/SOAP response is invalid, providerID: %s' % remote_provider_id)
                    except lasso.ProfileStatusNotSuccessError:
                        get_logger().warn('SLO/SOAP response status is not success, providerID: %s status: %s' % (remote_provider_id, logout.response.status.statusCode.value))
                    except lasso.LogoutRequestDeniedError:
                        get_logger().warn('SLO/SOAP request denied, providerID: %s' % (remote_provider_id))
                    except lasso.Error, error:
                        get_logger().warn('SLO/SOAP error, providerID: %s error: %s' % (remote_provider_id, error[1]))
            remote_provider_id = logout.getNextProviderId()
        if finish_with_get:
            logout.resetProviderIdIndex()
            return self.slo_idp_get(logout)
        else:
            return redirect(get_request().environ['SCRIPT_NAME'] + '/logout')

    def singleLogoutReturn(self):
        session = get_session()
        if (not session or not session.user or not session.lasso_session_dump) and not hasattr(session, "lasso_identity_dump"):
            raise errors.AccessError()
        logout = lasso.Logout.newFromDump(get_lasso_server(), session.lasso_logout_dump)
        if not logout:
            # strange thing, should not happen, but handle it nicely
            response = get_response()
            response.set_content_type("image/png")
            return file(os.path.join(get_publisher().DATA_DIR, 'liberty/check_on.png')).read()

        logout.setSessionFromDump(session.lasso_session_dump)

        if hasattr(session, "lasso_identity_dump"):
            # FIXME: in later version of lasso this should be useless
            logout.setIdentityFromDump(session.lasso_identity_dump)
        else:
            authentic.identities.get_store().load_identities()
            try:
                identity = authentic.identities.get_store().get_identity(session.user)
            except KeyError:
                pass
            else:
                logout.setIdentityFromDump(identity.lasso_dump)

        request = get_request()
        try:
            logout.processResponseMsg(request.get_query())
        except lasso.Error, error:
            if error[0] == lasso.PROFILE_ERROR_INVALID_QUERY:
                raise errors.AccessError()
            raise
        if logout.session is None:
            session.lasso_session_dump = None
            session.lasso_logout_dump = None # it was the last one.
        else:
            session.lasso_session_dump = logout.session.dump()
        
        if session.in_slo_get:
            return redirect(get_publisher().get_root_url() + 'images/check_on.png')
        else:
            return self.slo_sp_continue_with_http(logout, session)


    def slo_sp(self):
        "Single Logout initiated by service provider"
        request = get_request()
        logout = lasso.Logout(get_lasso_server())
        try:
            logout.processRequestMsg(request.get_query())
        except lasso.Error, error:
            if error[0] == lasso.DS_ERROR_INVALID_SIGNATURE:
                return template.error_page(_('Failed to check single logout request signature.'))
            raise
        get_logger().info('SLO from %s' % logout.request.providerId)

        session = get_session()
        if not session.lasso_session_dump:
            pass # session got closed by an other mean
        elif logout.nameIdentifier.content not in (session.name_identifiers or []):
            pass # same
        else:
            logout.setSessionFromDump(session.lasso_session_dump)

        authentic.identities.get_store().load_identities()
        identity = None
        try:
            identity = authentic.identities.get_store().get_identity(session.user)
        except KeyError:
            pass
        else:
            if identity.lasso_dump:
                logout.setIdentityFromDump(identity.lasso_dump)

        try:
            logout.validateRequest()
        except lasso.Error, error:
            if error[0] != lasso.PROFILE_ERROR_SESSION_NOT_FOUND:
                raise
            try:
                logout.buildResponseMsg()
            except lasso.Error, error:
                if error[0] == lasso.PROFILE_ERROR_UNKNOWN_PROFILE_URL:
                    # metadata didn't contain logout return url, stay here.
                    return redirect(get_request().environ['SCRIPT_NAME'] + '/')
                raise
            return redirect(logout.msgUrl)

        if logout.session is None:
            session.lasso_session_dump = None
        else:
            session.lasso_session_dump = logout.session.dump()
        method = lasso.HTTP_METHOD_SOAP

        if method == lasso.HTTP_METHOD_SOAP:
            remote_provider_id = logout.getNextProviderId()
            while remote_provider_id:
                try:
                    logout.initRequest(remote_provider_id, method)
                except lasso.Error, error:
                    if error[0] in (lasso.PROFILE_ERROR_UNSUPPORTED_PROFILE,
                            lasso.PROFILE_ERROR_FEDERATION_NOT_FOUND):
                        remote_provider_id = logout.getNextProviderId()
                        continue
                    else:
                        raise

                logout.buildRequestMsg()
                try:
                    soap_answer = self.soap_call(logout.msgUrl, logout.msgBody)
                except soap.SOAPException:
                    remote_provider_id = logout.getNextProviderId()
                    continue

                try:
                    logout.processResponseMsg(soap_answer)
                except:
                    remote_provider_id = logout.getNextProviderId()
                    get_logger().warn('Invalid SOAP SLO answer from %s' % logout.msgUrl)
                    continue
                nameIdentifier = logout.nameIdentifier.content

                remote_provider_id = logout.getNextProviderId()

        return self.slo_sp_continue_with_http(logout, session)

    def slo_sp_continue_with_http(self, logout, session):
        logout.resetProviderIdIndex()
        while True: # to skip non-compliant providers
            remote_provider_id = logout.getNextProviderId()
            if remote_provider_id is None:
                logout.buildResponseMsg()
                if session.proxied_idp:
                    get_session().after_url = logout.msgUrl
                    return redirect(get_request().environ['SCRIPT_NAME'] + '/liberty/proxySingleLogout')
                get_session_manager().expire_session() 
                return redirect(logout.msgUrl)
            else:
                try:
                    logout.initRequest(remote_provider_id, lasso.HTTP_METHOD_REDIRECT)
                except lasso.Error, error:
                    if error[0] in (lasso.PROFILE_ERROR_UNSUPPORTED_PROFILE,
                            lasso.PROFILE_ERROR_FEDERATION_NOT_FOUND):
                        # it doesn't support nor SOAP nor Redirect ?
                        # And it calls itself Liberty compliant ?
                        continue # ignore it.
                    raise
                logout.buildRequestMsg()
                session.lasso_logout_dump = logout.dump()
                return redirect(logout.msgUrl)

    def federationTermination(self):
        request = get_request()
        if not lasso.isLibertyQuery(request.get_query()):
            return redirect('.')
        
        defederation = lasso.Defederation(get_lasso_server())
        defederation.processNotificationMsg(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
        return self.fedterm(defederation, identity, session)

    def fedterm(self, defederation, identity, session):
        if session and session.lasso_session_dump:
            defederation.setSessionFromDump(session.lasso_session_dump)

        if identity and identity.lasso_dump:
            defederation.setIdentityFromDump(identity.lasso_dump)
            get_logger().info('fedterm for %s from %s' % (identity.id, defederation.request.providerId))
        else:
            get_logger().info('fedterm from %s' % defederation.request.providerId)

        try:
            defederation.validateNotification()
        except lasso.Error, error:
            pass # ignore failure (?)
        else:
            if not defederation.identity:
                # if it was the last federation the whole identity dump collapsed
                identity.lasso_dump = None
            else:
                identity.lasso_dump = defederation.identity.dump()
            authentic.identities.get_store().save(identity)

        if session:
            name_identifier = defederation.nameIdentifier.content
            if name_identifier in (session.name_identifiers or []):
                session.name_identifiers.remove(name_identifier)
            if defederation.isSessionDirty:
                if defederation.session:
                    session.lasso_session_dump = defederation.session.dump()
                else:
                    session.lasso_session_dump = None

        if defederation.msgUrl:
            return redirect(defederation.msgUrl)
        else:
            get_session_manager().commit_changes(session)
            response = get_response()
            response.set_status(204)
            return ''

    def federationTerminationReturn(self):
        return redirect('..')

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

        registration = lasso.NameRegistration(get_lasso_server())
        registration.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
        return self.rni(registration, identity, session)

    def rni(self, registration, identity, session):
        if session and session.lasso_session_dump:
            registration.setSessionFromDump(session.lasso_session_dump)
        if identity:
            registration.setIdentityFromDump(identity.lasso_dump)
        registration.validateRequest()
        if registration.identity:
            identity.lasso_dump = registration.identity.dump()
            authentic.identities.get_store().save(identity)

        registration.buildResponseMsg()

        if registration.msgUrl:
            return redirect(registration.msgUrl)
        else:
            get_session_manager().commit_changes(session)
            return registration.msgBody


    def perform_proxy_login(self, idp = None, nameIdPolicy = None, extensions = None, relay_state = None):
        session = get_session()
        server = authentic.misc.get_lasso_server(lasso.PROVIDER_ROLE_SP)
        login = lasso.Login(server)
        login.initAuthnRequest(idp, lasso.HTTP_METHOD_REDIRECT)
        login.request.nameIdPolicy = 'federated'
        login.request.forceAuthn = False
        login.request.isPassive = False
        login.request.consent = "urn:liberty:consent:obtained"
        if session.lasso_login_dump:
            # replicates parameters from the request we proxy
            orig_login = lasso.Login.newFromDump(get_lasso_server(), session.lasso_login_dump)
            login.request.nameIdPolicy = orig_login.request.nameIdPolicy
            login.request.forceAuthn = orig_login.request.forceAuthn
            login.request.isPassive = orig_login.request.isPassive
            login.request.consent = orig_login.request.consent

        if nameIdPolicy: # forced value
            login.request.nameIdPolicy = nameIdPolicy
        if extensions:
            ext_list = lasso.StringList()
            for ext in extensions:
                ext_list.append('<lib:Extension xmlns:lib="urn:liberty:iff:2003-08"><%s>%s</%s></lib:Extension>' % (ext[0], ext[1], ext[0]))
            login.request.extension = ext_list
        if relay_state is not None:
            get_session().remember(login.request.iD, relay_state)

        login.buildAuthnRequestMsg()
        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 proxyAssertionConsumer(self):
        server = authentic.misc.get_lasso_server(lasso.PROVIDER_ROLE_SP)
        login = lasso.Login(server)
        request = get_request()
        if request.get_method() == 'GET' or get_field('LAREQ'):
            if request.get_method() == 'GET':
                try:
                    login.initRequest(request.get_query(), lasso.HTTP_METHOD_REDIRECT)
                except lasso.Error, error:
                    if hasattr(lasso, 'strError'):
                        return template.error_page(lasso.strError(error[0]))
                    return template.error_page(_('Liberty error (lasso code: %s)') % error[0])
            else:
                login.initRequest(get_field('LAREQ'), lasso.HTTP_METHOD_POST)

            login.buildRequestMsg()
            try:
                soap_answer = self.soap_call(login.msgUrl, login.msgBody)
            except soap.SOAPException:
                return template.error_page(_('Failed to get Assertion from identity provider'))
            try:
                login.processResponseMsg(soap_answer)
            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
                if error[0] != lasso.LOGIN_ERROR_UNKNOWN_PRINCIPAL:
                    raise
                t = self.proxy_auth_peer_cancelled(login)
                if t:
                    return t
                session = get_session()
                login = lasso.Login.newFromDump(get_lasso_server(), session.lasso_login_dump)
                session.lasso_login_dump = None
                return self.sso_after_authentication(login, False, proxied = True)
            else:
                self.proxy_auth_ok(login)
        else:
            login.processAuthnResponseMsg(get_field('LARES'))
        session = get_session()
        if session.lasso_proxy_session_dump:
            login.setSessionFromDump(session.lasso_proxy_session_dump)
        try:
            common.SessionIndex(login.response.assertion[0].authenticationStatement.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(), 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'] + '/')


    @common.soap_endpoint
    def proxySoapEndpoint(self):
        """handles SOAP message coming from an IdP we act as proxy for"""
        soap_message = self.get_soap_message()
        request_type = lasso.getRequestTypeFromSoapMsg(soap_message) 

        if request_type == lasso.REQUEST_TYPE_LOGOUT:
            return self.proxy_soap_logout(soap_message)
        if request_type == lasso.REQUEST_TYPE_DEFEDERATION:
            return self.proxy_soap_fedterm(soap_message)

    def proxy_soap_logout(self, soap_message):
        server = authentic.misc.get_lasso_server(lasso.PROVIDER_ROLE_SP)
        logout = lasso.Logout(server)
        logout.processRequestMsg(soap_message)
        name_identifier = logout.nameIdentifier.content
        authentic.identities.get_store().load_identities()
        try:
            identity = authentic.identities.get_store().get_identity_for_name_identifier(name_identifier)
            if identity and identity.lasso_proxy_dump:
                logout.setIdentityFromDump(identity.lasso_proxy_dump)
            if logout.request.sessionIndex:
                    session_index = common.SessionIndex.get(logout.request.sessionIndex)
                    sessions = [ get_session_manager().get(session_index.session_id) ]
            else:
                sessions = authentic.sessions.BasicSession.get_sessions_by_user(identity.id)
        except KeyError:
            # no session, build straight failure answer
            logout.buildResponseMsg()
            return logout.msgBody

        try:
            logout.validateRequest()
        except lasso.DsSignatureError:
            pass
        else:
            for session in sessions:
                self.slo_idp(session, method = lasso.HTTP_METHOD_SOAP)

                session_manager = get_session_manager()
                del session_manager[session.id]
        logout.buildResponseMsg()
        return logout.msgBody

    def proxy_soap_fedterm(self, soap_message):
        server = authentic.misc.get_lasso_server(lasso.PROVIDER_ROLE_SP)
        defederation = lasso.Defederation(server)
        defederation.processNotificationMsg(soap_message)
        name_identifier = defederation.nameIdentifier.content

        try:
            identity = authentic.identities.get_store().get_identity_for_name_identifier(name_identifier)
        except KeyError:
            identity = None # XXX: should not happen

        for session in get_session_manager().values():
            if name_identifier in (session.name_identifiers or []):
                get_logger().info('FedTerm/SOAP/Proxy from %s' % defederation.remoteProviderId)
                defederation.setSessionFromDump(session.lasso_proxy_session_dump)
                break
        else:
            pass

        if identity and identity.lasso_proxy_dump:
            defederation.setIdentityFromDump(identity.lasso_proxy_dump)

        try:
            defederation.validateNotification()
        except lasso.Error, error:
            pass # ignore failure (?)
        else:
            if not defederation.identity:
                # if it was the last federation the whole identity dump collapsed
                identity.lasso_proxy_dump = None
            else:
                identity.lasso_proxy_dump = defederation.identity.dump()
            authentic.identities.get_store().save(identity)

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

            if defederation.session:
                session.lasso_proxy_session_dump = defederation.session.dump()
            else:
                session.lasso_proxy_session_dump = None

        # and now, fedterm with SP, every SP.
        server = get_lasso_server()
        if identity and identity.lasso_dump:
            lasso_identity = lasso.Identity.newFromDump(identity.lasso_dump)
            provider_ids = lasso_identity.providerIds or []
        else:
            provider_ids = []
        for k in provider_ids:
            provider = server.getProvider(k)
            if not provider:
                continue
            defederation = lasso.Defederation(server)
            if identity.lasso_dump:
                defederation.setIdentityFromDump(identity.lasso_dump)
            if session.lasso_session_dump:
                defederation.setSessionFromDump(session.lasso_session_dump)
            try:
                defederation.initNotification(provider.providerId, lasso.HTTP_METHOD_SOAP)
            except lasso.Error, error:
                if error[0] == lasso.PROFILE_ERROR_FEDERATION_NOT_FOUND:
                    raise
                elif error[0] == lasso.PROFILE_ERROR_UNSUPPORTED_PROFILE:
                    continue # shit, ignore for now.
                else:
                    raise
    
            defederation.buildNotificationMsg()
            if not defederation.identity:
                identity.lasso_dump = None
            else:
                identity.lasso_dump = defederation.identity.dump()

            if session:
                if not defederation.session:
                    session.lasso_session_dump = None
                else:
                    session.lasso_session_dump = defederation.session.dump()
                name_identifier = defederation.nameIdentifier.content
                if name_identifier in (session.name_identifiers or []):
                    session.name_identifiers.remove(name_identifier)

            try:
                self.soap_call(defederation.msgUrl, defederation.msgBody)
            except soap.SOAPException:
                pass
        
        authentic.identities.get_store().save(identity)

        get_session_manager().commit_changes(session)
        response = get_response()
        response.set_status(204)
        return ''


    def proxySingleLogout(self, session = None):
        request = get_request()
        if not lasso.isLibertyQuery(request.get_query()):
            if not session:
                session = get_session()
            server = authentic.misc.get_lasso_server(lasso.PROVIDER_ROLE_SP)
            logout = lasso.Logout(server)
            if session and session.lasso_proxy_session_dump:
                logout.setSessionFromDump(session.lasso_proxy_session_dump)
            try:
                identity = authentic.identities.get_store().get_identity(session.user)
            except KeyError:
                pass
            else:
                if identity.lasso_proxy_dump:
                    logout.setIdentityFromDump(identity.lasso_proxy_dump)

            try:
                logout.initRequest(None, lasso.HTTP_METHOD_REDIRECT)
            except lasso.Error, error:
                session.lasso_proxy_session_dump = None
                if error[0] == lasso.PROFILE_ERROR_NAME_IDENTIFIER_NOT_FOUND:
                    return redirect('/logout')
                if error[0] == lasso.PROFILE_ERROR_SESSION_NOT_FOUND:
                    return redirect('/logout')
                raise
            logout.buildRequestMsg()
            return redirect(logout.msgUrl)
        else:
            server = authentic.misc.get_lasso_server(lasso.PROVIDER_ROLE_SP)
            logout = lasso.Logout(server)
            logout.processRequestMsg(request.get_query())
            return self.proxy_slo_redirect(logout, get_session())

    def proxySingleLogoutReturn(self):
        session = get_session()
        session.lasso_proxy_session_dump = None
        get_session_manager().expire_session() 
        if session.after_url:
            after_url = session.after_url
            session.after_url = None
            return redirect(after_url)
        return redirect(get_request().environ['SCRIPT_NAME'] + '/logout')


    def proxy_slo_soap(self, session, identity):
        # slo request from SP, continue with SOAP logout request to proxied-IdP
        server = authentic.misc.get_lasso_server(lasso.PROVIDER_ROLE_SP)
        logout = lasso.Logout(server)
        if session and session.lasso_proxy_session_dump:
            logout.setSessionFromDump(session.lasso_proxy_session_dump)
        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:
            return # ignore
        try:
            logout.processResponseMsg(soap_answer)
        except lasso.Error, error:
            if error[0] not in (lasso.LOGOUT_ERROR_UNSUPPORTED_PROFILE,
                        lasso.LOGOUT_ERROR_FEDERATION_NOT_FOUND):
                raise
            # XXX: it should actually answer back
            pass
        if logout.isSessionDirty:
            if logout.session:
                session.lasso_proxy_session_dump = logout.session.dump()
            else:
                session.lasso_proxy_session_dump = ''
            get_session_manager().commit_changes(session)


    def proxy_slo_redirect(self, logout, session):
        logout_idp = lasso.Logout(get_lasso_server())

        if session:
            if session.lasso_proxy_session_dump:
                logout.setSessionFromDump(session.lasso_proxy_session_dump)
            if session.lasso_session_dump:
                logout_idp.setSessionFromDump(session.lasso_session_dump)

        authentic.identities.get_store().load_identities()
        try:
            identity = authentic.identities.get_store().get_identity(session.user)
        except KeyError:
            pass
        else:
            if identity.lasso_proxy_dump:
                logout.setIdentityFromDump(identity.lasso_proxy_dump)
            if identity.lasso_dump:
                logout_idp.setIdentityFromDump(identity.lasso_dump)

        try:
            logout.validateRequest()
        except lasso.Error, error:
            if error[0] == lasso.PROFILE_ERROR_SESSION_NOT_FOUND:
                pass
            elif error[0] < 0:
                raise
            pass
        logout.buildResponseMsg()
        session.after_url = logout.msgUrl
        return self.slo_idp_get(logout_idp, url = 'proxySingleLogoutNext')

    def proxySingleLogoutNext(self):
        url = get_session().after_url
        get_session_manager().expire_session() 
        return redirect(url)


    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')['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



class SpUI(AccessControlled, Directory):
    _q_exports = ['terminate', 'login', 'proxy_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

    def init_provider(self, role = lasso.PROVIDER_ROLE_SP):
        try:
            lp = get_cfg('providers')[self.provider_key]
        except KeyError:
            raise errors.TraversalError()
        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)

    def login(self):
        self.init_provider()
        login = lasso.Login(get_lasso_server())
        login.initIdpInitiatedAuthnRequest(self.p.providerId)
        get_logger().info('SSO to %s' % self.p.providerId)
        login.request.protocolProfile = lasso.LIB_PROTOCOL_PROFILE_BRWS_ART
        login.processAuthnRequestMsg(None)
        rt = RootDirectory()
        return rt.sso_after_consent(login, True, True)

    def terminate(self):
        self.init_provider()
        defederation = lasso.Defederation(get_lasso_server())
        session = get_session()
        authentic.identities.get_store().load_identities()
        try:
            identity = authentic.identities.get_store().get_identity(session.user)
        except KeyError:
            identity = None
        if identity and identity.lasso_dump:
            defederation.setIdentityFromDump(identity.lasso_dump)
        if session and session.lasso_session_dump:
            defederation.setSessionFromDump(session.lasso_session_dump)

        get_logger().info('fedterm to %s' % self.p.providerId)
        try:
            defederation.initNotification(self.p.providerId, lasso.HTTP_METHOD_SOAP)
        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:
                try:
                    defederation.initNotification(self.p.providerId, lasso.HTTP_METHOD_REDIRECT)
                except lasso.Error, error:
                    if error[0] == lasso.PROFILE_ERROR_UNSUPPORTED_PROFILE:
                        # XXX: perhaps it could remove the federation locally,
                        # ignoring the remote provider?
                        return template.error_page(
                                _("This service doesn't support federation termination"))
                    raise
            else:
                raise

        defederation.buildNotificationMsg()
        name_identifier = defederation.nameIdentifier.content
        if not defederation.identity:
            # if it was the last federation the whole identity dump collapsed
            identity.lasso_dump = None
        else:
            identity.lasso_dump = defederation.identity.dump()
        authentic.identities.get_store().save(identity)
        if name_identifier in (session.name_identifiers or []):
            session.name_identifiers.remove(name_identifier)
        
        if defederation.msgBody:
            try:
                self.soap_call(defederation.msgUrl, defederation.msgBody)
            except soap.SOAPException:
                return template.error_page(_("Service provider failed to process request."))
        else:
            return redirect(defederation.msgUrl)

        return redirect('../../..')

    def proxy_terminate(self):
        self.init_provider(lasso.PROVIDER_ROLE_IDP)
        defederation = lasso.Defederation(get_lasso_server(lasso.PROVIDER_ROLE_SP))
        session = get_session()
        authentic.identities.get_store().load_identities()
        try:
            identity = authentic.identities.get_store().get_identity(session.user)
        except KeyError:
            identity = None
        if identity and identity.lasso_proxy_dump:
            defederation.setIdentityFromDump(identity.lasso_proxy_dump)
        if session and session.lasso_session_dump:
            defederation.setSessionFromDump(session.lasso_session_dump)

        get_logger().info('fedterm to %s' % self.p.providerId)
        try:
            defederation.initNotification(self.p.providerId, lasso.HTTP_METHOD_SOAP)
        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:
                try:
                    defederation.initNotification(self.p.providerId, lasso.HTTP_METHOD_REDIRECT)
                except lasso.Error, error:
                    if error[0] == lasso.PROFILE_ERROR_UNSUPPORTED_PROFILE:
                        # XXX: perhaps it could remove the federation locally,
                        # ignoring the remote provider?
                        return template.error_page(
                                _("This service doesn't support federation termination"))
                    raise
            else:
                raise

        defederation.buildNotificationMsg()
        name_identifier = defederation.nameIdentifier.content
        if not defederation.identity:
            # if it was the last federation the whole identity dump collapsed
            identity.lasso_proxy_dump = None
        else:
            identity.lasso_proxy_dump = defederation.identity.dump()
        authentic.identities.get_store().save(identity)
        if name_identifier in (session.name_identifiers or []):
            session.name_identifiers.remove(name_identifier)
        
        if defederation.msgBody:
            try:
                self.soap_call(defederation.msgUrl, defederation.msgBody)
            except soap.SOAPException:
                return template.error_page(_('Remote identity provider failed to process request.'))
        else:
            return redirect(defederation.msgUrl)

        return redirect('../../..')

