1650 lines
69 KiB
Python
1650 lines
69 KiB
Python
# -*- 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
|
|
|