# -*- coding: UTF-8 -*-


# Glasnost
# By: Odile Bénassy <obenassy@entrouvert.com>
#     Romain Chantereau <rchantereau@entrouvert.com>
#     Nicolas Clapiès <nclapies@easter-eggs.org>
#     Pierre-Antoine Dejace <padejace@entrouvert.be>
#     Thierry Dulieu <tdulieu@easter-eggs.com>
#     Florent Monnier <monnier@codelutin.com>
#     Cédric Musso <cmusso@easter-eggs.org>
#     Frédéric Péters <fpeters@entrouvert.be>
#     Benjamin Poussin <poussin@codelutin.com>
#     Emmanuel Raviart <eraviart@entrouvert.com>
#     Sébastien Régnier <regnier@codelutin.com>
#     Emmanuel Saracco <esaracco@easter-eggs.com>
#
# Copyright (C) 2000, 2001 Easter-eggs & Emmanuel Raviart
# Copyright (C) 2002 Odile Bénassy, Code Lutin, Thierry Dulieu, Easter-eggs,
#     Entr'ouvert, Frédéric Péters, Benjamin Poussin, Emmanuel Raviart,
#     Emmanuel Saracco & Théridion
# Copyright (C) 2003 Odile Bénassy, Romain Chantereau, Nicolas Clapiès,
#     Code Lutin, Pierre-Antoine Dejace, Thierry Dulieu, Easter-eggs,
#     Entr'ouvert, Florent Monnier, Cédric Musso, Ouvaton, Frédéric Péters,
#     Benjamin Poussin, Rodolphe Quiédeville, Emmanuel Raviart, Sébastien
#     Régnier, Emmanuel Saracco, Théridion & Vecam
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.


__doc__ = """Glasnost Identities Web"""

__version__ = '$Revision$'[11:-2]


import httplib
import md5
try:
    from OpenSSL import SSL
except ImportError:
    SSL = None
import socket
import urllib
import urlparse
import whrandom
from xml.dom.minidom import parseString 

try:
    import lasso.Protocols.SingleSignOnAndFederation as sso
    import lasso.Protocols.NameRegistration as NameRegistration
    import lasso.Protocols.SingleLogout as SingleLogout
    import lasso.Protocols.FederationTerminationNotification as Defederation

    import lasso.Schemas.SchemaDom as SchemaDom
    import lasso.Soap as Soap
    import lasso.Tools as lassoTools
    import lasso.Constants as Constants
except ImportError:
    sso = None

try:
    import libxml2
except ImportError:
    libxml2 = None

import glasnost.common.context as context
import glasnost.common.faults as faults
import glasnost.common.slots as slots
import glasnost.common.xhtmlgenerator as X

import glasnost.proxy.IdentitiesProxy as proxyIdentities
from glasnost.proxy.tools import getProxyForServerRole

import ObjectsWeb as objects
import things
from tools import *


class AdminIdentities(objects.AdminMixin, proxyIdentities.AdminIdentities):
    pass
objects.register(AdminIdentities)


class HTTPSConnection(httplib.HTTPConnection):
    certificateFile = None
    default_port = httplib.HTTPS_PORT
    peerCaCertificateFile = None
    privateKeyFile = None

    def __init__(self, host, port = None, privateKeyFile = None,
                 certificateFile = None, peerCaCertificateFile = None,
                 strict = None):
        httplib.HTTPConnection.__init__(self, host, port, strict)
        self.privateKeyFile = privateKeyFile
        self.certificateFile = certificateFile
        self.peerCaCertificateFile = peerCaCertificateFile

    def connect(self):
        "Connect to a host on a given (SSL) port."

        context = SSL.Context(SSL.SSLv23_METHOD)
        # Demand a certificate.
        context.set_verify(SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT,
                           self.verifyCallback)
        context.use_privatekey_file(self.privateKeyFile)
        context.use_certificate_file(self.certificateFile)
        context.load_verify_locations(self.peerCaCertificateFile)

        # Strange hack, that is derivated from httplib.HTTPSConnection, but
        # that I (Emmanuel) don't really understand...
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sslSocket = SSL.Connection(context, sock)
        sslSocket.connect((self.host, self.port))
        self.sock = httplib.FakeSocket(sslSocket, sslSocket)

    def verifyCallback(self, connection, x509Object, errorNumber, errorDepth,
                       returnCode):
        # FIXME: What should be done?
        return returnCode


class Identification(things.ThingMixin, proxyIdentities.Identification):
    pass
things.register(Identification)


class Identity(objects.ObjectWebMixin, proxyIdentities.Identity):
    pass
objects.register(Identity)


class IdentitiesWeb(objects.ObjectsWebMixin, proxyIdentities.IdentitiesProxy):
    def createSessionIfNeeded(self, nextUri):
        """Create a session, if it doesn't exist yet.

        Return either None (if the session already existed or has been created
        immediately) or a redirect page which must be returned to the user
        agent, in order to create the session.

        """

        session = context.getVar('session')
        if session is None:
            req = context.getVar('req')
            sessionsProxy = getProxyForServerRole('sessions')
            virtualHost = context.getVar('virtualHost')
            try:
                session = sessionsProxy.newSession(
                        req.connection.remote_ip,
                        serverId = virtualHost.defaultDispatcherId)
            except: # Do a tighter check?
                if context.getVar('debug'):
                    raise
                return failure(_('Failed to initialize a session.'),
                               X.roleUrl('login'))
            sessionToken = session['sessionToken']
            context.setVar('sessionToken', sessionToken)
            context.setVar('session', session)
            canUseCookie = context.getVar('canUseCookie', default = 0)
            if canUseCookie:
                url = X.roleUrl('define', action = 'testCookie')
                url.add('nextUri', nextUri)
                url.add('sessionToken', sessionToken)
                context.setVar('canUseCookie', 0)
                url = url.getAsUrl()
                context.setVar('canUseCookie', 1)
                return redirect(url)
        return None

    def deleteSession(self):
        self.deleteUserToken()
        session = context.getVar('session')
        # get the host name to find the Name Identity entry
        # of the current session :
        if session is not None:
            # Don't delete the session, just remove userToken from it.
            context.setVar('userId', None)
            context.setVar('user', None)
            if session.has_key('userToken'):
                del session['userToken']
                session['isDirty'] = 1
            context.setVar('userToken', '')
        # if not context.getVar('sessionTokenInCookie', default = 0):
        #     # The sessionToken was not stored in a cookie, so don't try to
        #     # use
        #     # the cookie when loging out.
        #     context.setVar('canUseCookie', 0)
        nextUri = context.getVar('nextUri') or ''
        if not nextUri:
            nextUri = '/'
        else:
            nextUri = cleanUpUri(nextUri, ['sessionToken'])
        canUseCookie = context.getVar('canUseCookie', default = 0)
        if not canUseCookie:
            nextUri = appendToUri(nextUri,
                    'sessionToken=' + context.getVar('sessionToken'))

    def federationTerminationRequestUrl(self):
        # FIXME : process the federation termination notification :
        session = context.getVar('session')
        if not session:
            return accessForbidden()
        peerHostName = session['peerHostName']

        # get the name identifier :
        nameIdentifier = None
        remoteProvider = self.isServiceProvider()
        if remoteProvider:
            nameIdentifier = self.getLocalNameIdentifierServiceProvider(
                    peerHostName)

        else:
            remoteProvider = self.isIdentityProvider(peerHostName)
            nameIdentifier = self.getPeerNameIdentifierIdentityProvider(
                    peerHostName)
            if not nameIdentifier:
                nameIdentifier = self.getLocalNameIdentifierIdentityProvider(
                        peerHostName)

        handlerUrl = remoteProvider.federationTerminationServiceUrl
        providerId = context.getVar('httpHostName')

        # get the protocole profile :
        protocolProfiles = remoteProvider.federationTerminationProtocolProfiles
        if not protocolProfiles:
            raise 'no protocol profile supported'
        profile = protocolProfiles[0]

        # HTTP with redirect 302 profile :
        if profile == Constants.protocolProfiles['fedTermIdpHttp'] or \
               profile == Constants.protocolProfiles['fedTermSpHttp']:
            url = Defederation.getFederationTerminationNotificationHandlerUrl(
                    handlerUrl, providerId, nameIdentifier)
            return redirect(url)

        # HTTP SOAP profile :
        elif profile == Constants.protocolProfiles['fedTermIdpSoap'] or \
                 profile == Constants.protocolProfiles['fedTermSpSoap']:
            request = Defederation.buildFederationTerminationNotificationRequest(
                    providerId, nameIdentifier)
            response = self.sendSoapRequest(request)
            return OK

        else:
            raise 'no profile defined'
        
    federationTerminationRequestUrl.isPublicForWeb = 1

    def federationTerminationNotificationServiceReturnUrl(self, **keywords):
        return redirect('http://localhost') # FIXME : where to redirect ?
    federationTerminationNotificationServiceReturnUrl.isPublicForWeb = 1

    def federationTerminationServiceUrl(self, **keywords):
        # rebuild the request :
        request = Defederation.buildFederationTerminationNotificationFromKeywords(
                        keywords)

        # build the response :
        response = self.processFederationTerminationNotification(request)
        remoteProvider = self.isServiceProvider()
        if not remoteProvider:
            peerHostName = request.getProviderID()
            remoteProvider = self.isIdentityProvider(peerHostName)
        handlerUrl = remoteProvider.federationTerminationServiceReturnUrl
        return redirect(handlerUrl)
    federationTerminationServiceUrl.isPublicForWeb = 1

    def getViewAllActionButtonsBarLayout(self):
        layout = X.array()
        if self.canAddObject():
            layout += X.buttonStandalone(_('New account'),
                    X.actionUrl('newAccount'))
            layout += X.buttonStandalone('new', X.actionUrl('edit'))
        return layout

    def getViewAboveButtonsBarLayout(self, object, fields):
        ballotsWeb = getWebForServerRole('ballots')
        electionsWeb = getWebForServerRole('elections')
        layout = X.array()
        layout += objects.ObjectsWebMixin.getViewAboveButtonsBarLayout(
                self, object, fields)

        def getVotesSectionLayout(person, votes, elections, intertitle):
            layout = None
            if len(elections) > 0:
                layout = X.array( X.h3()(intertitle) )
                layout += X.br()
                table = X.table()
                layout += table
                for election in elections:
                    if not person.id in election.getVoterIds():
                        continue
                    tr = X.tr(X.th(scope = 'row')(
                            X.a(href = X.idUrl(election.id))(
                                election.getLabelTranslated(
                                    context.getVar('readLanguages')))))
                    table += tr
                    if not votes.has_key(election.id):
                        tr += X.td(_('Abstention'))
                    else:
                        vote = votes[election.id]
                        if vote == 'secret':
                            tr += X.td(_('Secret Ballot'))
                        else:
                            tr += X.td(X.a(href = X.idUrl(vote.id))(
                                    X.asIs(vote.getLabelLine(election))))
            return layout

        userId = context.getVar('userId')
        knownRoles = context.getVar('knownRoles')
        if userId and 'ballots' in knownRoles and 'elections' in knownRoles:
            # Only an identified user can see someone's votes.
            votes = ballotsWeb.getVotesFromTokens(object.voteTokens)
            try:
                lastElections = electionsWeb.getLastObjectsWithVoter(
                        50, object.id, ['running'])
            except faults.UserAccessDenied:
                lastElections = []
            layout += getVotesSectionLayout(
                object, votes, lastElections,
                _("""The votes for the elections in progress"""))

            try:
                lastElections = electionsWeb.getLastObjectsWithVoter(
                        10, object.id, ['closed'])
            except faults.UserAccessDenied:
                lastElections = []
            layout += getVotesSectionLayout(
                    object, votes, lastElections,
                    _("""The votes for the closed elections"""))
        return layout

    def isServiceProvider(self):
        """ return None if is an identity provider, else return the remote
        identity provider """
        identityProviderId = None
        providersWeb = getWebForServerRole('providers')
        if providersWeb is not None:
            try:
                identityProviderId = providersWeb.getRemoteIdentityProviderId()
            except faults.MissingItem:
                pass
        identityProvider = None
        if identityProviderId:
            identityProvider = providersWeb.getObject(identityProviderId)
        return identityProvider

    def isIdentityProvider(self, serviceProviderHostName):
        """ return None if not an identity provider (serviceProviderHostName
        is not a remote service provider), else return the remote service
        provider """
        providersWeb = getWebForServerRole('providers')
        serviceProviderId = None
        serviceProviderId = providersWeb.getServiceProviderId(
            serviceProviderHostName)
        serviceProvider = None
        if serviceProviderId:
            serviceProvider = providersWeb.getObject(serviceProviderId)
        return serviceProvider

    def login(self): # FIXME: Rename to signOn or authentication or ...,
        providerId = context.getVar('httpHostName')
        loginUrl = self.getSingleSignOnRequestUrl(
                providerId)
        return redirect(loginUrl)
    login.isPublicForWeb = 1

    def getSingleSignOnRequestUrl(self, providerId, nameIdPolicy = 'none'):
        """ forge the url to redirect the user and negociate a sso """

        # decide which type of authentication to do.
        # (local authentication or liberty alliance authentication ) :
        identityProviderId = None
        providersWeb = getWebForServerRole('providers')
        if providersWeb is not None:
            try:
                identityProviderId = providersWeb.getRemoteIdentityProviderId()
            except faults.MissingItem:
                pass
        # Don't use Liberty Alliance protocol to authenticate user.
        if identityProviderId is None:
            return self.loginLocal(context.getVar('nextUri'))

        identityProvider = providersWeb.getObject(identityProviderId)

        # choose a protocole profile :
        profile = 'artifact'
        remoteProvider = self.isServiceProvider()
        if remoteProvider:
            protocolProfiles = remoteProvider.singleSignOnProtocolProfiles
            if not protocolProfiles:
                raise 'Missing SingleSignOnProtocolProfile in IDP metadata'
            profile = lassoTools.getProfileKey(protocolProfiles[0])

        # get and crypt the RelayState :
        # FIXME : choose a different way to store the password for ciphering
        relayState = 'http://' + context.getVar('httpHostName')
        if 0: # FIXME: encryption doesn't work
            symmetricKey = self.getSymmetricKey()
            relayState = lassoTools.encryptRelayState(relayState, symmetricKey)
        # choose the authentication methods needed :
        authenticationMethods = ['password']

        # FIXME : implement the assertion consumer service id

        # verify the Scheme of the single sign on service url (HTTPS) :
        singleSignOnServiceUrl = identityProvider.singleSignOnServiceUrl
        #if singleSignOnServiceUrl.startswith('http://'):
        #    raise 'HTTP for sso is not secure'

        # forge the url and redirect :
        ssoUrl = sso.getIdentityProviderAuthenticationUrl(
                authenticationMethods = authenticationMethods,
                profile = profile,
                nameIdPolicy = nameIdPolicy,
                serviceProviderId = providerId,
                singleSignOnServiceUrl = singleSignOnServiceUrl)
        return ssoUrl

    def singleSignOnServiceUrl(self, **authenticationRequestKeywords):
        """Liberty Alliance Identity Provider Login Method."""
        # init the nextUri :
        nextUri = X.actionUrl('singleSignOnServiceUrl')
        for key, value in authenticationRequestKeywords.items():
            nextUri.add(key, value)
        redirectPage = self.createSessionIfNeeded(nextUri)
        if redirectPage is not None:
            return redirectPage

        # udpate the state of the session :
        session = context.getVar('session')
        session['authenticationRequestKeywords'] \
                = authenticationRequestKeywords
        session['isDirty'] = 1

        # build the Authentication Request :
        authenticationRequest = sso.buildAuthenticationRequestFromKeywords(
                authenticationRequestKeywords)

        # verify the providerId :
        providerId = authenticationRequest.getProviderID()

        # process the AssertionConsumerServiceID if exists :
        # FIXME : add support of multi assertion consumer service urls.
        serviceProvider = self.isIdentityProvider(providerId)

        # FIXME : verifying the signature of the AuthnRequest if needed :
        authnRequestSigned = serviceProvider.authnRequestSigned
        if authnRequestSigned:
            pass

        # process the authentication method:
        authenticationMethods = authenticationRequest.getAuthenticationMethods(
                )
        strongerMethods = ['smartcardPki', 'softwarePki', 'password']
        chosenAuthenticationMethod = 'smartcardPki'
        if len(authenticationMethods) > 0:
            # choose the default authentication of the IDP :
            authenticationComparison = \
                              authenticationRequest.getAuthnContextComparison()
            if authenticationComparison == 'exact':
                # choose the exactly requested method :
                for method in authenticationMethods:
                    if method in strongerMethods:
                        chosenAuthenticationMethod = method
                        break
            elif authenticationComparison == 'minimum':
                # choose a method as strong as the one requested:
                for method in strongerMethods:
                    if method in authenticationMethods:
                        chosenAuthenticationMethod = method
                        break

            elif authenticationComparison == 'better':
                # FIXME : ...
                pass

        # FIXME : define a way to know if the authentication process
        # can be passive or not.

        # process isPassive and forceAuthn :
        isPassive, forceAuthentication = sso.processAuthenticationRequest(
                authenticationRequest)
        userId = context.getVar('userId')
        if isPassive:
            # do not interact with the principal :
            if not userId:
                url = sso.getServiceProviderAssertionArtifactHandlerUrl(
                        serviceProvider.assertionConsumerServiceUrl, # FIXME
                        sso.buildStatus('noPassive'))
                return redirect(url)
            return self.loginSucceeded2(session['authenticationMethod'],
                                        authenticationRequest)
        elif forceAuthentication or not userId:
            # force authentication of the user even if already authenticated :
            accountsServerRoles = {
                    'password': 'passwordaccounts',
                    'smartcardPki':'x509accounts',
                    'softwarePki': 'x509accounts',
                    'token': 'tokenaccounts',
                    }
            accountsWeb = getWebForServerRole(
                accountsServerRoles[chosenAuthenticationMethod]) # FIXME
            if accountsWeb is None:
                return failure(
                    _('Requested login method (%s) is not supported.') % (
                            chosenAuthenticationMethod), X.rootUrl('/'))
            return accountsWeb.login()
        else:
            return self.loginSucceeded2(session['authenticationMethod'],
                                        authenticationRequest)
    singleSignOnServiceUrl.isPublicForWeb = 1

    def loginLocal(self, authMeth = ''):
        """Non Liberty Alliance local login."""
        
        # FIXME: this should be dynamic
        authenticationMethods = ['password', 'softwarePki', 'token']
        accountsServerRoles = {
                'password': 'passwordaccounts',
                'softwarePki': 'x509accounts',
                'token': 'tokenaccounts',
                }
        if not authMeth or authMeth not in accountsServerRoles.keys():
            authMeth = authenticationMethods[0]
        accountsWeb = getWebForServerRole(accountsServerRoles[authMeth])
        if not accountsWeb:
            if context.getVar('debug'):
                raise 'accountsWeb is None'
            return pageNotFound()
            
        return accountsWeb.login()
    loginLocal.isPublicForWeb = 1

    def loginSucceeded(self, userToken, authenticationMethod):
        """Liberty Alliance Identity Provider Login Succeeded Method."""

        session = context.getVar('session')
        newSession = 0
        if not session:
            newSession = 1
            req = context.getVar('req')
            sessionsProxy = getProxyForServerRole('sessions')
            virtualHost = context.getVar('virtualHost')
            try:
                session = sessionsProxy.newSession(
                        req.connection.remote_ip,
                        serverId = virtualHost.defaultDispatcherId)
            except: # Do a tighter check?
                if context.getVar('debug'):
                    raise
                return failure(_('Failed to initialize a session.'),
                               X.roleUrl('login'))
            sessionToken = session['sessionToken']
            context.setVar('session', session)
            context.setVar('sessionToken', sessionToken)

        session['authenticationMethod'] = authenticationMethod

        context.setVar('userToken', userToken)
        userId = self.getUserId()
        user = None
        if userId:
            try:
                user = self.getObject(userId)
            except faults.MissingItem:
                pass
        if user is None:
            userToken = ''
            userId = None
            if session.has_key('userToken'):
                del session['userToken']
        else:
            session['userToken'] = userToken
        session['isDirty'] = 1
        context.setVar('userToken', userToken)
        context.setVar('userId', userId)
        context.setVar('user', user)

        if not session.has_key('authenticationRequestKeywords'):
            # Non Liberty Alliance local login.
            if context.getVar('nextUri'):
                nextUri = context.getVar('nextUri')
            else:
                nextUri = X.rootUrl()
            if newSession and context.getVar('canUseCookie'):
                url = X.roleUrl('define', action = 'testCookie')
                url.add('nextUri', nextUri)
                url.add('sessionToken', sessionToken)
                context.setVar('canUseCookie', 0)
                url = url.getAsUrl()
                context.setVar('canUseCookie', 1)
                return redirect(url)
            else:
                return redirect(nextUri)
        
        # Liberty Alliance login.
        authenticationRequestKeywords \
                = session['authenticationRequestKeywords']
        authenticationRequest = sso.buildAuthenticationRequestFromKeywords(
                authenticationRequestKeywords)
        return self.loginSucceeded2(authenticationMethod,
                                    authenticationRequest)

    def loginSucceeded2(self, authenticationMethod, authenticationRequest):
        # function to return an AuthnResponse without an Assertion :
        def authenticationResponseWithoutAssertion(
            assertionConsumerServiceUrl,
            statusKey,
            authnRequest):
            authnResponse = sso.buildAuthenticationResponseFromAuthnRequest(
                context.getVar('httpHostName'),
                statusKey,
                None,
                authnRequest)
            url = '%s?%s' % (assertionConsumerServiceUrl,
                             authnResponse.exportToEncodedUrlEmbeddedMessage())
            return redirect(url)

        # check if a service identification exists for the user :
        user = context.getVar('user')
        serviceProviderHostName = authenticationRequest.getProviderID()
        providersWeb = getWebForServerRole('providers')
        try:
            serviceProviderId = providersWeb.getServiceProviderId(
                serviceProviderHostName)
            serviceProvider = providersWeb.getObject(serviceProviderId)
        except faults.MissingItem:
            session = context.getVar('session')
            session['isDirty'] = 1
            del session['authenticationRequestKeywords']
            return failure(_('Unknown service provider'))
            
        serviceIdentification = None
        if user.serviceIdentifications is not None:
            for service in user.serviceIdentifications:
                if service.peerHostName \
                       == serviceProviderHostName:
                    serviceIdentification = service
                    break
        # no service identification found for the user, create an identity.
        if serviceIdentification is None:
            serviceIdentification = Identification()
            serviceIdentification.peerHostName = serviceProviderHostName
            digest = md5.new(serviceIdentification.peerHostName)
            randomGenerator = whrandom.whrandom()
            randomSalt = str(randomGenerator.uniform(0.1, 1))[2:]
            digest.update(randomSalt)
            serviceIdentification.localNameIdentifier = digest.hexdigest()
            if user.serviceIdentifications is None:
                user.serviceIdentifications = []
            user.serviceIdentifications.append(serviceIdentification)
            self.modifyPartialObject(user, ['serviceIdentifications'])

        # process the NameIDPolicy :
        statusCode = 'Success'
        peerHostName = serviceProviderHostName
        nameIdentifierPolicy = authenticationRequest.getNameIDPolicy()
        nameIdentifier = None
        # no federation, just return the peer/local name identifier :
        if nameIdentifierPolicy == 'none':
            nameIdentifier = self.getPeerNameIdentifierIdentityProvider(
                    peerHostName)
            if not nameIdentifier:
                nameIdentifier = self.getLocalNameIdentifierIdentityProvider(
                        peerHostName)
            if not nameIdentifier:
                return authenticationResponseWithoutAssertion(
                        serviceProvider.assertionConsumerServiceUrl,
                        'FederationDoesNotExist', authenticationRequest)

        # do one time federation :
        elif nameIdentifierPolicy == 'onetime':
            nameIdentifier = lassoTools.generateNameIdentifier(peerHostName)
            self.setLocalNameIdentifierIdentityProvider(
                    peerHostName, nameIdentifier)

        # do a federation, return peer/local/new name identifier
        elif nameIdentifierPolicy == 'federated':
            nameIdentifier = self.getPeerNameIdentifierIdentityProvider(
                    peerHostName)
            if not nameIdentifier:
                nameIdentifier = self.getLocalNameIdentifierIdentityProvider(
                        peerHostName)
                if not nameIdentifier:
                    nameIdentifier = lassoTools.generateNameIdentifier(
                            peerHostName)
                    self.setLocalNameIdentifierIdentityProvider(
                            peerHostName, nameIdentifier)

        # federated else a none :
        elif nameIdentifierPolicy == 'any':
            consent = authenticationRequest.getConsent()
            if consent:
                nameIdentifier = self.getPeerNameIdentifierIdentityProvider(
                        peerHostName)
                if not nameIdentifier:
                    nameIdentifier = self.getLocalNameIdentifierIdentityProvider(
                            peerHostName)
                    if not nameIdentifier:
                        nameIdentifier = lassoTools.generateNameIdentifier(
                            peerHostName)
                        self.setLocalNameIdentifierIdentityProvider(
                                peerHostName, nameIdentifier)
            else:
                return failure(_('No consent given by the principal'))

        # get the format of the NameIdentifiers :
        nameIdentifierFormat = 'federated'
        idpNameIdentifierFormat = 'federated'
        if nameIdentifierPolicy == 'onetime':
            nameIdentifierFormat = 'onetime'
            idpNameIdentifierFormat = 'onetime'

        # build the assertion :
        peerNameIdentifier = nameIdentifier
        assertion = sso.buildAuthenticationAssertion(
                authnRequest = authenticationRequest,
                issuer = context.getVar('httpHostName'),
                authenticationMethod = authenticationMethod,
                nameIdentifier = peerNameIdentifier,
                nameQualifier = peerHostName,
                nameIdentifierFormat = nameIdentifierFormat,
                idpNameIdentifier = serviceIdentification.localNameIdentifier,
                idpNameQualifier = peerHostName,
                idpNameIdentifierFormat = idpNameIdentifierFormat)

        # save the peerHostName :
        session = context.getVar('session')
        if session:
            session['peerHostName'] = serviceProviderHostName

        # respond to the Service Provider :
        profile = authenticationRequest.getProtocolProfile()
        if profile == 'artifact':
            # send an artifact, keep the assertion to be dereferenced :
            assertionsProxy = getProxyForServerRole('assertions')
            artifact = assertionsProxy.addAssertion(assertion.exportToString())
            url = sso.getServiceProviderAssertionArtifactHandlerUrl(
                    serviceProvider.assertionConsumerServiceUrl, # FIXME
                    artifact,
                    authenticationRequest.getRelayState())
            return redirect(url)
        elif profile == 'post':
            # send an AuthnResponse
            authenticationResponse \
                    = sso.buildAuthenticationResponseFromAuthnRequest(
                    providerId = context.getVar('httpHostName'),
                    statusCode = 'Success',
                    assertion = assertion,
                    authnRequest = authenticationRequest)

            authenticationResponseEmbedded \
                  = authenticationResponse.exportToEncodedUrlEmbeddedMessage()

            # process with the POST format :
            context.push(_level = 'loginSucceeded', layoutMode = 'edit')
            try:
                layout = X.array()

                submitUrl = serviceProvider.assertionConsumerServiceUrl # FIXME
                form = X.form(action = submitUrl,
                              enctype = 'multipart/form-data', method = 'post')
                layout += form

                form += X.input(name = 'LARES', type = 'hidden',
                                value = authenticationResponseEmbedded)
                form += X.p(
                        _('You have been succesfully authenticated; click ok '\
                          'to go back to the service provider.'))
                buttonsBar = X.div(_class = 'buttons-bar')
                form += buttonsBar
                buttonsBar += X.buttonInForm('ok', 'okButton')
                return writePageLayout(layout, _('Authentication Succeeded'))
            finally:
                context.pull(_level = 'loginSucceeded')

        else:
            # unknown profile, dont know what to do :
            raise 'Unknow protocol profile'


    def logout(self):
        self.deleteUserToken()
        session = context.getVar('session')

        if webTools.getConfig('DeleteSessionOnLogout'):
            if session:
                try:
                    getProxyForServerRole('sessions').deleteSession(
                        context.getVar('sessionToken'))
                except UnknownSessionToken:
                    # uh ?
                    pass
            nextUri = context.getVar('nextUri') or ''
            if not nextUri:
                nextUri = '/'
            else:
                nextUri = cleanUpUri(nextUri, ['sessionToken'])
            return redirect(nextUri)
        

        # Don't delete the session, just remove userToken from it.
        if session:
            context.setVar('userId', None)
            context.setVar('user', None)
            if session.has_key('userToken'):
                del session['userToken']
                session['isDirty'] = 1
            context.setVar('userToken', '')

        # if not context.getVar('sessionTokenInCookie', default = 0):
        #     # The sessionToken was not stored in a cookie, so don't try to
        #     # use
        #     # the cookie when loging out.
        #     context.setVar('canUseCookie', 0)
        nextUri = context.getVar('nextUri') or ''
        if not nextUri:
            nextUri = '/'
        else:
            nextUri = cleanUpUri(nextUri, ['sessionToken'])
        canUseCookie = context.getVar('canUseCookie', default = 0)
        if not canUseCookie:
            nextUri = appendToUri(nextUri,
                    'sessionToken=' + context.getVar('sessionToken'))
        return redirect(nextUri)
    logout.isPublicForWeb = 1


    def newAccountNeededObjects(self):
        roles = self.getRoles()
        peopleWeb = getWebForServerRole(roles[0])
        if not peopleWeb.canAddObject():
            return accessForbidden()

        person = peopleWeb.newObject()

        knownRoles = context.getVar('knownRoles')
        accountRole = 'passwordaccounts'
        if 'passwordaccounts' in knownRoles and \
                'x509accounts' in knownRoles:
            # user should now decide account type
            pass
        
        accountsWeb = getWebForServerRole(accountRole)
        if not accountsWeb.canAddObject():
            return accessForbidden()
        account = accountsWeb.newObject()

        return (person, account)

    def newAccount(self):
        person, account = self.newAccountNeededObjects()
        person.fillWithDefaultValues()
        return self.newAccountObjects(person, account)
    newAccount.isPublicForWeb = 1

    def newAccountObjects(self, person, account):
        context.push(_level = 'newAccountObjects', layoutMode = 'edit')
        try:
            layout = X.array()
            if context.getVar('error'):
                layout += person.getErrorLayout()
                # or account.getErrorLayout() ?

            form = X.form(action = X.actionUrl('newAccountSubmit'),
                          enctype = 'multipart/form-data', method = 'post')
            layout += form

            if context.getVar('nextUri'):
                form += X.input(name = 'nextUri', type = 'hidden',
                                value = context.getVar('nextUri'))

            slot = slots.Root(account, name = 'account')
            identitySlot = account.getSlot('identityId')
            identitySlot.getKind().stateInEditMode = 'hidden'
            widget = slot.getWidget()
            form += widget.getModelPageBodyLayout(slot, fields = None)

            slot = slots.Root(person, name = 'person')
            widget = slot.getWidget()
            if person.id == None:
                form += widget.getModelPageBodyLayout(slot, fields = None)
            else:
                personSlot = person.getSlot('id', parentSlot = slot)
                widget = personSlot.getWidget()
                form += widget.getModelHiddenLayout(personSlot, fields = None)
                form += person.getViewLayout(None, parentSlot = slot)

            form += X.div(_class = 'buttons-bar')(
                    X.span(_class = 'action-buttons-bar')(
                            X.buttonInForm('create', 'createButton')))
            return writePageLayout(layout, _('New Account'))
        finally:
            context.pull(_level = 'newAccountObjects')

    def newAccountSubmit(self, **keywords):
        person, account = self.newAccountNeededObjects()
        peopleWeb = person.getWeb()
        accountsWeb = account.getWeb()

        slot = slots.Root(account, name = 'account')
        identitySlot = account.getSlot('identityId')
        identitySlot.getKind().isRequired = 0
        account.submitFields(keywords, parentSlot = slot)

        slot = slots.Root(person, name = 'person')
        personIdSlot = person.getSlot('id', parentSlot = slot)
        person.id = personIdSlot.getWidget().submit(personIdSlot, keywords)
        if person.id:
            person = peopleWeb.getObject(person.id)
        else:
            person.submitFields(keywords, parentSlot = slot)

        if context.getVar('again'):
            return self.newAccountObjects(person, account)

        if not account.identityId:
            identity = self.newObject()
            try:
                identity.personId = peopleWeb.addObject(person)
                person.id = identity.personId
            except: # ?
                return self.newAccountObjects(person, account)
            account.identityId = self.addObject(identity)
        
        try:
            accountsWeb.addObject(account)
        except faults.DuplicateLogin, f:
            account.setError('self.login', f)
            context.setVar('again', 1)
            context.setVar('error', 1)
            return self.newAccountObjects(person, account)
        except faults.DuplicateSerial, f:
            context.setVar('again', 1)
            context.setVar('error', 1)
            account.setError('self.serial', f)
            return self.newAccountObjects(person, account)


        if context.getVar('nextUri'):
            return redirect(context.getVar('nextUri'))
        return redirect(X.rootUrl())
    newAccountSubmit.isPublicForWeb = 1

    def newPerson(self):
        # used when the user comes back from idp for the first time
        peopleWeb = getWebForServerRole('people')
        if not peopleWeb:
            return redirect(X.rootUrl())
        if not peopleWeb.canAddObject():
            return accessForbidden()
        
        person = peopleWeb.newObject()
        return self.newPersonObject(person)
    newPerson.isPublicForWeb = 0 # not a public url!

    def newPersonObject(self, object):
        context.push(_level = 'newPersonObject', layoutMode = 'edit')
        try:
            layout = X.array()
            if context.getVar('error'):
                layout += object.getErrorLayout()
            form = X.form(action = X.actionUrl('newPersonSubmit'),
                          enctype = 'multipart/form-data', method = 'post')
            layout += form

            if context.getVar('nextUri'):
                form += X.input(name = 'nextUri', type = 'hidden',
                                value = context.getVar('nextUri'))

            slot = slots.Root(object)
            widget = slot.getWidget()
            form += widget.getModelPageBodyLayout(slot, fields = None)

            form += X.div(_class = 'buttons-bar')(
                    X.span(_class = 'action-buttons-bar')(
                            X.buttonInForm('create', 'createButton')))
            return writePageLayout(layout, _('New Account'))
        finally:
            context.pull(_level = 'newPersonObject')

    def newPersonSubmit(self, **keywords):
        if keywords is None:
            keywords = {}

        peopleWeb = getWebForServerRole('people')

        person = peopleWeb.newObject(keywords)
        person.submitFields(keywords)
        
        if context.getVar('again'):
            return self.newPersonObject(person)

        try:
            result = peopleWeb.submitAddObject(person)
        except:
            if context.getVar('debug'):
                raise
            return accessForbidden() # TODO: return failure ?
        if result:
            return result

        user = context.getVar('user')
        if user is not None:
            user.personId = person.id
            self.modifyPartialObject(user, ['personId'])

        if context.getVar('nextUri'):
            return redirect(context.getVar('nextUri'))
        return redirect(X.idUrl(person.id))
    newPersonSubmit.isPublicForWeb = 1

    def processAuthenticationResponse(self, **keywords):
        # Create the service provider side session.
        nextUri = X.actionUrl('processAuthenticationResponse')
        for key, value in keywords.items():
            nextUri.add(key, value)

        # create a session if needed :
        redirectPage = self.createSessionIfNeeded(nextUri)
        if redirectPage is not None:
            return redirectPage

        relayState = None

        # process the response :
        if keywords.has_key('LARES'):
            # POST profile :
            # get keyword from LARES dict :
            authenticationResponseEmbedded = keywords['LARES']

            # Remove base64 url encoding.
            import lasso.Schemas.Schema as schema
            authenticationResponseEmbeddedDecoded = \
                schema.decodeEncodedUrlEmbeddedMessage(
                    authenticationResponseEmbedded)

            # build the keyword :
            authenticationResponseKeywords = {}
            for segment in authenticationResponseEmbeddedDecoded.split('&'):
                key, value = segment.split('=')
                authenticationResponseKeywords[key] = value

            # build the response from keywords :
            response = sso.buildAuthenticationResponseFromKeywords(
                    authenticationResponseKeywords)

            # decrypt the relay state :
            relayState = response.getRelayState()
            if 0: # FIXME: was if relayState: but encryption doesn't work
                symmetricKey = self.getSymmetricKey()
                relayState = lassoTools.decryptRelayState(
                        relayState, symmetricKey)

            # get the host name of the provider (IDP) :
            identityProviderHostName = response.getProviderID()

        elif keywords.has_key('SAMLArt'):
            # Artifact profile :
            if keywords.has_key('RelayState'):
                relayState = keywords['RelayState']
                if 0: # FIXME: encryption doesn't work
                    symmetricKey = self.getSymmetricKey()
                    relayState = lassoTools.decryptRelayState(
                            relayState, symmetricKey)
                del keywords['RelayState']

            # build assertion artifact (in fact only get the value from
            # SAMArt key ) :
            assertionArtifact = sso.buildAssertionArtifactFromKeywords(
                    keywords)

            # build the request, send it ,and get the response (the assertion)
            # FIXME : get the providerId from the succintId if artifact
            request = sso.buildRequest(assertionArtifact)
            response = self.sendSoapRequest(request)

        else:
            # error, unknown profile :
            response = sso.buildAuthenticationResponseFromKeywords(keywords)
            relayState = response.getRelayState()
            if 0: # was relayState:  FIXME: encryption doesn't work
                symmetricKey = self.getSymmetricKey()
                relayState = lassoTools.decryptRelayState(
                        relayState, symmetricKey)

        # build the response :
        statusCode = response.getStatusCode()

        # error, stop now ! :
        if statusCode != 'Success':
            layout = X.array()
            layout += X.p(_class = 'alert')(
                   _('Liberty Alliance authentication assertion request '
                     'failed (reason = %s)') % statusCode)
            return writePageLayout(layout, _('Failure'), canCache = 0)

        # get the assertion :
        assertion = response.getAssertion()

        # get peer name identifier (idp name identifier)
        # local name identifier  (sp name identifier if exists)
        idpNameIdentifier = assertion.getIDPProvidedNameIdentifier()
        nameIdentifier = assertion.getNameIdentifier()

        # get the token of the user from the name identifiers.
        # if no user token, no identities, so create one (user profile).
        providersWeb = getWebForServerRole('providers')
        identityProviderId = providersWeb.getRemoteIdentityProviderId()
        identityProvider = providersWeb.getObject(identityProviderId)

        identityProviderHostName = identityProvider.providerId
        identityProvider = self.isServiceProvider()
        if nameIdentifier and nameIdentifier != idpNameIdentifier:
            try:
                userToken = self.checkIdentityLocalNameIdentifierSP(
                        identityProviderHostName, nameIdentifier)
            except faults.WrongNameIdentifier:
                userToken = None
        else:
            try:
                userToken = self.checkIdentityPeerNameIdentifierSP(
                        identityProviderHostName, idpNameIdentifier)
            except faults.WrongNameIdentifier:
                userToken = None

        # get the RelayState, set the nextUri
        nextUri = relayState
        if nextUri:
            canUseCookie = context.getVar('canUseCookie', default = 0)
            if not canUseCookie:
                sessionToken = context.getVar('sessionToken')
                nextUri = appendToUri(nextUri,
                                      'sessionToken=' + sessionToken)
        else:
            nextUri = X.rootUrl()

        # set the session :
        session = context.getVar('session')
        session['peerHostName'] = identityProviderHostName
        session['authenticationMethod'] = assertion.getAuthenticationMethod()
        session['isDirty'] = 1

        # if no token, set a new profile for the user :
        if userToken is None and self.canAddObject():
            user = Identity()
            user.language = context.getVar('readLanguages')[0]
            identification = Identification()
            if nameIdentifier and nameIdentifier != idpNameIdentifier:
                identification.localNameIdentifier = nameIdentifier
            identification.peerHostName = identityProviderHostName
            identification.peerNameIdentifier = idpNameIdentifier
            user.identityIdentifications = [identification]
            userId = self.addObject(user)
            if nameIdentifier and nameIdentifier != idpNameIdentifier:
                userToken = self.checkIdentityLocalNameIdentifierSP(
                        identityProviderHostName, nameIdentifier)
            else:
                userToken = self.checkIdentityPeerNameIdentifierSP(
                        identityProviderHostName, idpNameIdentifier)
            session['userToken'] = userToken
            context.setVar('userToken', userToken)
            context.setVar('userId', userId)
            context.setVar('user', user)
            context.push(_level = 'processAuthenticationResponse',
                         nextUri = nextUri)
            try:
                return self.newPerson()
            finally:
                context.pull(_level = 'processAuthenticationResponse')

        # complete the setting of the session :
        context.setVar('userToken', userToken)
        userId = self.getUserId()
        user = None
        if userId:
            try:
                user = self.getObject(userId)
            except faults.MissingItem:
                pass
        if user is None:
            userToken = ''
            userId = None
            if session.has_key('userToken'):
                del session['userToken']
        else:
            session['userToken'] = userToken
        session['isDirty'] = 1
        context.setVar('userToken', userToken)
        context.setVar('userId', userId)
        context.setVar('user', user)

        session = context.getVar('session')
        session['peerHostName'] = identityProviderHostName
        session['isDirty'] = 1

        return redirect(nextUri)
    processAuthenticationResponse.isPublicForWeb = 1

    def processFederationTerminationNotification(self, request):
        # FIXME : process request object
        nameIdentifier = request.getNameIdentifier()
        peerHostName = request.getProviderID()
        remoteProvider = self.isServiceProvider()

        if remoteProvider:
            # the idp is stating the sp that it will no longer send assertions
            #raise 'idp notify the sp it will no longer send assertions'
            pass

        else:
            # the sp isstating the idp that it will no longer accept assertions
            remoteProvider = self.isIdentityProvider(peerHostName)
            #raise 'sp notify the idp it will not accept assertions'
        response = HTTP_NO_CONTENT
        return response

    def processRegisterNameIdentifierRequest(self, request):
        # validate the signature if present :
        # Process a register name identifier registration request :
        idpProvidedNameIdentifier = request.IDPProvidedNameIdentifier[0].PCDATA
        spProvidedNameIdentifier = request.SPProvidedNameIdentifier[0].PCDATA
        oldProvidedNameIdentifier = request.OldProvidedNameIdentifier[0].PCDATA
        statusCode = Constants.statusCodes['Success']
        peerHostName = request.getProviderID()
        remoteProvider = self.isServiceProvider()
        if remoteProvider:
            # find the peer name identifier of the provider :
            peerNameIdentifier = idpProvidedNameIdentifier
            res = self.setPeerNameIdentifierServiceProvider(
                peerHostName, peerNameIdentifier, oldProvidedNameIdentifier)
            if not res:
                statusCode = Constants.statusCodes['FederationDoesNotExist']
            
        else:
            remoteProvider = self.isIdentityProvider(peerHostName)
            peerNameIdentifier = spProvidedNameIdentifier
            res = self.setPeerNameIdentifierIdentityProvider(
                peerHostName,
                peerNameIdentifier,
                oldProvidedNameIdentifier, idpProvidedNameIdentifier)
            if not res:
                statusCode = Constants.statusCodes['FederationDoesNotExist']
                
        providerId = context.getVar('httpHostName')
        relayState = request.getRelayState()
        response = NameRegistration.buildRegisterNameIdentifierResponse(
                providerId, statusCode, relayState = relayState)
        return response

    def processRequest(self, request):
        # Process a single sign on request :
        artifact = request.samlp_AssertionArtifact[0].PCDATA
        assertionsProxy = getProxyForServerRole('assertions')
        try:
            assertionXml = assertionsProxy.getAssertion(artifact)
        except faults.WrongArtifact:
            # FIXME: Is it the proper way ton signal an error?
            # I believe the good solution is to call buildResponse
            # without any
            # assertion => buildStatus should be modified.
            response = sso.buildStatus('RequestDenied') # FIXME
        except:
            # FIXME: Is it the proper way ton signal an error?
            # I believe the good solution is to call buildResponse
            # without any
            # assertion => buildStatus should be modified.
            response = sso.buildStatus('RequestDenied') # FIXME
        else:
            assertionDom = libxml2.parseMemory(assertionXml, len(assertionXml))
            assertion = SchemaDom.importFromNode(assertionDom, 1)
            response = sso.buildResponse('Success', assertion)

        return response

    def processSingleLogoutRequest(self, request):
        # FIXME : propagation to other idp
        # FIXME : support of SessionIndex
        peerHostName = request.getProviderID()
        listIdentificationsToLogout = []
        if not self.isServiceProvider():
            # get the federation of the SP except its and send a logout :
            user = context.getVar('user')
            if user.serviceIdentifications is not None:
                for identification in user.serviceIdentifications:
                    if identification.peerHostName != peerHostName:
                        listIdentificationsToLogout.append(identification)

            # propagate the logout to the other provider :
            # FIXME : only SOAP supported, because it's the easiest way
            for identification in listIdentificationsToLogout:
                nameIdentifier = identification.peerNameIdentifier
                if not nameIdentifier:
                    nameIdentifier = identification.localNameIdentifier
                    logoutRequest = SingleLogout.buildSingleLogoutRequest(
                            context.getVar('httpHostName'), nameIdentifier)
                    response = self.sendSoapRequest(
                            logoutRequest, identification.peerHostName)
        else:
            nameIdentifier = request.getNameIdentifier()
            
        self.deleteSession()
        providerId = context.getVar('httpHostName')
        statusCode = Constants.statusCodes['Success']
        relayState = request.getRelayState()
        response = SingleLogout.buildLogoutResponse(providerId,
                statusCode, relayState = relayState)
        return response

    def registerNameIdentifierRequestUrl(self):
        # FIXME : IDP must request a name registration through another profile
        # test if a effective session exists :
        session = context.getVar('session')
        if session is None:
            return accessForbidden()

        # session is ok, so process the name identifiers :
        peerHostName = session['peerHostName']
        remoteProvider = self.isServiceProvider()
        if remoteProvider:
            # at service provider, change his name identifier :
            idpProvidedNameIdentifier = self.getPeerNameIdentifierServiceProvider(peerHostName)
            oldProvidedNameIdentifier = self.getLocalNameIdentifierServiceProvider(peerHostName)
            if oldProvidedNameIdentifier == 0:
                oldProvidedNameIdentifier = idpProvidedNameIdentifier
            spProvidedNameIdentifier = lassoTools.generateNameIdentifier(
                    peerHostName)

            self.setLocalNameIdentifierServiceProvider(
                    peerHostName, spProvidedNameIdentifier)
        else:
            # at identity provider, changer his name identifier :
            remoteProvider = self.isIdentityProvider(peerHostName)
            spProvidedNameIdentifier = self.getPeerNameIdentifierIdentityProvider(
                    peerHostName)
            oldProvidedNameIdentifier = self.getLocalNameIdentifierIdentityProvider(
                    peerHostName)
            idpProvidedNameIdentifier = lassoTools.generateNameIdentifier(
                    peerHostName)
            self.setLocalNameIdentifierIdentityProvider(peerHostName,
                    idpProvidedNameIdentifier)
        serviceUrl = remoteProvider.registerNameIdentifierServiceUrl

        # get a profile :
        profiles = remoteProvider.registerNameIdentifierProtocolProfiles
        if not profiles:
            raise 'no profile found at the provider'
        protocolProfile = profiles[0] # FIXME : choose in local profiles too.

        # get the provider identifier :
        providerId = context.getVar('httpHostName')

        # build the relay state : (FIXME: should not hardcode http://)
        relayState = context.getVar('nextUri') or 'http://' + providerId
        if not relayState:
            relayState = context.getVar('httpHostName')
        if relayState:
            symmetricKey = self.getSymmetricKey()
            relayState = lassoTools.encryptRelayState(relayState, symmetricKey)

        # send the request depending on the found profile :
        if protocolProfile == Constants.protocolProfiles['rniIdpHttp'] or \
               protocolProfile == Constants.protocolProfiles['rniSpHttp']:
            # test if the service url is HTTPS :
            if not serviceUrl.startswith('https://'):
                return accessforbidden

            # HTTP with redirect 302 profile :
            url = NameRegistration.getRegisterNameIdentifierHandlerUrl(
                    serviceUrl, providerId, idpProvidedNameIdentifier,
                    spProvidedNameIdentifier, oldProvidedNameIdentifier,
                    relayState = relayState)
            return redirect(url)

        elif protocolProfile == Constants.protocolProfiles['rniIdpSoap'] or \
                 protocolProfile == Constants.protocolProfiles['rniSpSoap']:
            # HTTP SOAP profile :
            request = NameRegistration.buildRegisterNameIdentifierRequest(
                providerId, idpProvidedNameIdentifier,
                spProvidedNameIdentifier, oldProvidedNameIdentifier,
                relayState)
            response = self.sendSoapRequest(request)
            if response.getStatusCode() == Constants['Success']:
                relayState = response.getRelayState()
                if relayState:
                    symmetricKey = self.getSymmetricKey()
                    relayState = lassoTools.decrypt(relayState, symmetricKey)
                    return redirect(nextUri)
            raise response.exportToString()

        return accessForbidden() # FIXME : return 500 or another code ?
    registerNameIdentifierRequestUrl.isPublicForWeb = 1

    def registerNameIdentifierServiceReturnUrl(self, **keywords):
        response = NameRegistration.buildRegisterNameIdentifierResponseFromKeywords(
                keywords)
        relayState = response.getRelayState()
        if relayState:
            symmetricKey = self.getSymmetricKey()
            relayState = lassoTools.decryptRelayState(relayState, symmetricKey)
            return redirect(relayState)
        else:
            return OK
    registerNameIdentifierServiceReturnUrl.isPublicForWeb = 1

    def registerNameIdentifierServiceUrl(self, **keywords):
        session = context.getVar('session')
        peerHostName = session['peerHostName']

        # process the request :
        request = NameRegisration.buildRegisterNameIdentifierFromKeywords(
                      keywords)
        response = self.processRegisterNameIdentifierRequest(request)
        # return a response, get the provider handler url :
        remoteProvider = self.isServiceProvider()
        if not remoteProvider:
            remoteProvider = self.isIdentityProvider(peerHostName)
        handlerUrl = remoteProvider.registerNameIdentifierServiceReturnUrl
        providerId = context.getVar('httpHostName')
        url = NameRegistration.getRegisterNameIdentifierResponseHandlerUrl(
                handlerUrl, providerId, response)
        return redirect(url)
    registerNameIdentifierServiceUrl.isPublicForWeb = 1

    def sendSoapRequest(self, request, remoteProviderId = None):
        """ remoteProviderId is used to send the request to a specific
        provider """
        providersWeb = getWebForServerRole('providers')
        if self.isServiceProvider():
            identityProviderId = providersWeb.getRemoteIdentityProviderId()
        else:
            if not remoteProviderId:
                session = context.getVar('session')
                peerHostName = session['peerHostName']
            else:
                peerHostName = remoteProviderId
            identityProviderId = providersWeb.getServiceProviderId(
                    peerHostName)

        identityProvider = providersWeb.getObject(identityProviderId)
        identityProviderHostName = identityProvider.providerId
        idpParsedSoapEndpoint =  urlparse.urlparse(
                identityProvider.soapEndpoint)
        idpAddressingScheme, idpHostName, idpPath = idpParsedSoapEndpoint[:3]
        requestSoapMessage = Soap.buildMessage(request.exportToString())
        requestSoapMessage = urllib.quote_plus(requestSoapMessage, '=&')
        headers = {'Content-type': 'text/xml'}

        if idpAddressingScheme == 'https':
            # FIXME : only allow secure http
            # FIXME : Use PEM stored in glasnost attributes.

            import tempfile, os
            # private key :
            identitiesWeb = getWebForServerRole('identities')
            privateFd, privateKeyFile = tempfile.mkstemp()
            os.write(privateFd, identitiesWeb.getPrivateKeySSL())

            # cert and ca :
            remoteProvider = self.isServiceProvider()
            if not remoteProvider:
                remoteProvider = self.isIdentityProvider(peerHostName)
            certFd, certificateFile = tempfile.mkstemp()
            os.write(certFd, remoteProvider.certificateSSL)
            peerFd, peerCaCertificateFile = tempfile.mkstemp()
            os.write(peerFd, remoteProvider.caCertificateSSL)
            shortHostName = context.getVar('httpHostName').split('.')[0]

            connection = HTTPSConnection(
                    idpHostName, privateKeyFile, certificateFile,
                    peerCaCertificateFile)
        else:
            # Use HTTP protocol.
            connection = httplib.HTTPConnection(idpHostName)
        connection.request('POST', idpPath, requestSoapMessage, headers)
        responseSoapMessageFile = connection.getresponse()
        responseSoapMessage = responseSoapMessageFile.read()
        response = Soap.extractFromMessage(responseSoapMessage)
        responseDom = libxml2.parseMemory(response, len(response))
        element = SchemaDom.importFromNode(responseDom, 1)
        return element

    def singleLogoutRequestUrl(self):
        session = context.getVar('session')
        if not session:
            return accessForbidden()
        peerHostName = session['peerHostName']
        providerId = context.getVar('httpHostName')

        # set the name identifier :
        nameIdentifier = None
        remoteProvider = self.isServiceProvider()
        if remoteProvider:
            # at service provider :
            nameIdentifier = self.getLocalNameIdentifierServiceProvider(
                    peerHostName)
        else:
            # at identity provider :
            remoteProvider = self.isIdentityProvider(peerHostName)
            nameIdentifier = self.getPeerNameIdentifierIdentityProvider(
                    peerHostName)
            if not nameIdentifier:
                nameIdentifier = self.getLocalNameIdentifierIdentityProvider(
                        peerHostName)

        # set the relay state :
        relayState = 'http://' + context.getVar('httpHostName')
        symmetricKey = self.getSymmetricKey()
        relayState = lassoTools.encryptRelayState(relayState, symmetricKey)

        # Choose a profile supported by the peer provider :
        supportedProfiles = remoteProvider.singleLogoutProtocolProfiles
        if not supportedProfiles:
            raise 'no supprted profiles - is none'
        if len(supportedProfiles) == 0:
            raise 'no supported profiles'

        # FIXME : supported protocol profiles
        chosenProfile = None
        for supportedProfile in supportedProfiles:
            if supportedProfile in Constants.protocolProfiles.keys():
                chosenProfile = supportedProfile
                break

        if not chosenProfile:
            raise 'no protocol profile supported'

        if chosenProfile in ('sloIdpHttp', 'sloSpHttp'):
            # HTTP profile request :
            handlerUrl = remoteProvider.singleLogoutServiceUrl
            # must verify that handlerUrl starts with https:// :
            if not handlerUrl.startswith('https://'):
                raise 'HTTP unsecured is not allowed'
            url = SingleLogout.getSingleLogoutHandlerUrl(
                    handlerUrl, providerId, nameIdentifier,
                    relayState = relayState)
            return redirect(url)

        if chosenProfile in ('sloIdpSoap', 'sloSpSoap'):
            # SOAP profile request :
            request = SingleLogout.buildSingleLogoutRequest(
                    providerId, nameIdentifier, relayState = relayState)
            response = self.sendSoapRequest(request)
            statusKey = response.getStatusCode()
            if statusKey == 'Success':
                # everything is ok, so delete the session
                self.deleteSession()
                relayState = response.getRelayState()
                if 0: # was relayState:  FIXME: crypt doesn't work
                    symmetricKey = self.getSymmetricKey()
                    relayState = lassoTools.decryptRelayState(
                            relayState, symmetricKey)
                    return redirect(relayState)
            # FIXME : redirect to relaystate
            return OK

        raise 'no profile found'
    singleLogoutRequestUrl.isPublicForWeb = 1

    def singleLogoutServiceReturnUrl(self, **keywords):
        singleLogoutResponse = SingleLogout.buildSingleLogoutResponseFromKeywords(
                keywords)
        statusKey = singleLogoutResponse.getStatusCode()
        if statusKey == 'Success':
            # everything is ok, so delete the session
            self.deleteSession()
            relayState = singleLogoutResponse.getRelayState()
            if 0: # was relayState:  FIXME: crypt doesn't work
                symmetricKey = self.getSymmetricKey()
                relayState = lassoTools.decryptRelayState(
                        relayState, symmetricKey)
                return redirect(relayState)
        return OK
    singleLogoutServiceReturnUrl.isPublicForWeb = 1

    def singleLogoutServiceUrl(self, **keywords):
        # process the request :
        request = SingleLogout.buildSingleLogoutRequestFromKeywords(keywords)
        response = self.processSingleLogoutRequest(request)
        remoteProvider = self.isServiceProvider()
        if not remoteProvider:
            peerHostName = request.getProviderID()
            remoteProvider = self.isIdentityProvider(peerHostName)
        handlerUrl = remoteProvider.singleLogoutServiceReturnUrl
        providerId = context.getVar('httpHostName')
        url = SingleLogout.getSingleLogoutReturnHandlerUrl(
                handlerUrl, providerId, response)
        return redirect(url)
    singleLogoutServiceUrl.isPublicForWeb = 1

    def soapEndPoint(self):
        """Liberty Alliance SOAP Request Processing Method. """
        requestSoapMessage = context.getVar('xmlPost')
        import urllib
        requestSoapMessage = urllib.unquote_plus(requestSoapMessage)
        requestXml = Soap.extractFromMessage(requestSoapMessage)
        requestDom = libxml2.parseMemory(requestXml, len(requestXml))
        element = SchemaDom.extractElement(requestDom)
        request = SchemaDom.importFromNode(requestDom, 1)

        if element.name == 'Request':
            response = self.processRequest(request)
        elif element.name == 'RegisterNameIdentifierRequest':
            response = self.processRegisterNameIdentifierRequest(request)
        elif element.name == 'LogoutRequest':
            response = self.processSingleLogoutRequest(request)
        elif element.name ==' FederationTerminationNotification':
            response = self.processFederationTerminationNotification(request)

        if type(response) == type(HTTP_NO_CONTENT):
            responseSoapMessage = 'http_no_content'
            req = context.getVar('req')
            req.content_type = 'text/xml'
            req.send_http_header()
            req.write(responseSoapMessage)
            return HTTP_NO_CONTENT

        responseSoapMessage = Soap.buildMessage(response.exportToString())
        req = context.getVar('req')
        req.content_type = 'text/xml'
        req.send_http_header()
        req.write(responseSoapMessage)
        return OK
    soapEndPoint.isPublicForWeb = 1

    def status(self):
        userId = context.getVar('userId')
        if not userId:
            return writePageLayout(X.p(_("You are not currently logged in.")),
                    _('Logon Status'))
        
        return writePageLayout(X.p(X.asIs(
                _('You are currently logged in as %s.') % \
                    X.objectHypertextLabel(userId))), _('Login Status'))
    status.isPublicForWeb = 1

