774 lines
35 KiB
Python
774 lines
35 KiB
Python
# -*- coding: UTF-8 -*-
|
|
|
|
|
|
# Expression
|
|
# By: Frederic Peters <fpeters@entrouvert.com>
|
|
# Emmanuel Raviart <eraviart@entrouvert.com>
|
|
#
|
|
# Copyright (C) 2004 Entr'ouvert, Frederic Peters & Emmanuel Raviart
|
|
#
|
|
# 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.
|
|
|
|
|
|
"""Liberty Alliance Module"""
|
|
|
|
|
|
import httplib
|
|
import os
|
|
import urllib
|
|
import urlparse
|
|
|
|
import lasso
|
|
lasso.init()
|
|
import libxml2
|
|
|
|
import expression.core.directories as directories
|
|
import expression.core.documents as documents
|
|
import expression.core.elements as elements
|
|
import expression.core.environs as environs
|
|
import expression.core.faults as faults
|
|
import expression.core.filesystems as filesystems
|
|
import expression.core.html as html
|
|
import expression.core.http as http
|
|
import expression.core.locations as locations
|
|
import expression.core.logs as logs
|
|
import expression.core.modules as modules
|
|
import expression.core.namespaces as namespaces
|
|
import expression.core.sessions as sessions
|
|
import expression.core.stations as stations
|
|
import expression.core.strings as strings
|
|
import expression.core.things as things
|
|
import identities
|
|
import libertyallianceaccounts
|
|
import libertyallianceresponses
|
|
|
|
|
|
deleteSessionOnLogout = True
|
|
lassoServerContext = None
|
|
#symmetricKey = "Expression rules!" # FIXME
|
|
|
|
|
|
class EntityDescriptor(elements.Element):
|
|
def getAssertionConsumerServiceUrl(self, id):
|
|
if id:
|
|
nodes = self.evaluateXpath("md:SPDescriptor/md:AssertionConsumerServiceURL[@id = '%s']"
|
|
% id.replace("'", "'"))
|
|
else:
|
|
nodes = None
|
|
if not nodes:
|
|
nodes = self.evaluateXpath(
|
|
"md:SPDescriptor/md:AssertionConsumerServiceURL[@isDefault = 'true']")
|
|
if not nodes:
|
|
return None
|
|
return nodes[0].content
|
|
|
|
def getProviderId(self):
|
|
nodes = self.evaluateXpath("@ProviderID")
|
|
if not nodes:
|
|
return None
|
|
return nodes[0].content
|
|
|
|
def getSingleSignOnProtocolProfiles(self):
|
|
nodes = self.evaluateXpath("md:IDPDescriptor/md:SingleSignOnProtocolProfile")
|
|
return [node.content for node in nodes]
|
|
|
|
def getSingleSignOnServiceUrl(self):
|
|
nodes = self.evaluateXpath("md:IDPDescriptor/md:SingleSignOnServiceURL")
|
|
if not nodes:
|
|
return None
|
|
return nodes[0].content
|
|
|
|
def getSoapEndpoint(self):
|
|
nodes = self.evaluateXpath("md:IDPDescriptor/md:SoapEndpoint")
|
|
if not nodes:
|
|
return None
|
|
return nodes[0].content
|
|
|
|
providerId = property(getProviderId)
|
|
singleSignOnProtocolProfiles = property(getSingleSignOnProtocolProfiles)
|
|
singleSignOnServiceUrl = property(getSingleSignOnServiceUrl)
|
|
soapEndpoint = property(getSoapEndpoint)
|
|
|
|
|
|
class LibertyAlliance(things.Thing):
|
|
_serverContextDump = None
|
|
|
|
def afterAuthentication(self, action = "afterAuthentication", authnResponse = None):
|
|
"""Liberty Alliance Identity Provider Method which is called once the user authentication
|
|
requested by singleSignOn has been attempted.
|
|
|
|
Handles HTTP GET.
|
|
"""
|
|
|
|
command = environs.getVar("httpCommand")
|
|
if command != "GET":
|
|
raise faults.PathNotFound("")
|
|
success = True
|
|
session = environs.getVar("session")
|
|
if session is None:
|
|
success = False
|
|
if success:
|
|
if authnResponse is None:
|
|
authnResponse = lasso.AuthnResponse.new_from_dump(
|
|
session.libertyAllianceAuthenticationResponse)
|
|
user = environs.getVar("user")
|
|
if user is None:
|
|
success = False
|
|
if success:
|
|
authenticationRequest = authnResponse.request
|
|
serviceProviderId = authenticationRequest.get_child(
|
|
"ProviderID").get_content()
|
|
serviceProviderMetadata = self.getServiceProviderMetadata(
|
|
serviceProviderId)
|
|
if serviceProviderMetadata is None:
|
|
success = False
|
|
authnResponse.process_authentication_result(success)
|
|
if success:
|
|
assertion = lasso.Assertion(
|
|
"issuer",
|
|
authnResponse.get_attr_value("InResponseTo"))
|
|
|
|
authenticationMethod = session.getAuthenticationMethod()
|
|
if authenticationMethod == "password":
|
|
lassoAuthenticationMethod = lasso.samlAuthenticationMethodPassword
|
|
else:
|
|
raise Exception("Unknown authentication method %s" % authenticationMethod)
|
|
|
|
identification = user.getIdentification(serviceProviderId)
|
|
# No service identification found, create it.
|
|
if identification is None:
|
|
if user.identifications is None:
|
|
user.node.newTextChild(None, "identifications", None)
|
|
identifications = user.identifications
|
|
identificationNode = identifications.node.newTextChild(
|
|
None, "identification", None)
|
|
identification = identifications.newElement(identificationNode)
|
|
identification.providerId = serviceProviderId
|
|
identification.localNameIdentifier \
|
|
= 'CDSICDSC7SD89CDSDCDCDCDISCDUIDSI344343'#lassoTools.generateNameIdentifier(serviceProviderId)
|
|
user.identifications.append(identificationNode)
|
|
user.getDocument().save()
|
|
|
|
# Process the NameIDPolicy.
|
|
# Remove the next lines.
|
|
nameIdentifierPolicy = "federated"
|
|
nameIdentifier = identification.peerNameIdentifier
|
|
if not nameIdentifier:
|
|
nameIdentifier = identification.localNameIdentifier
|
|
assert nameIdentifier
|
|
|
|
## nameIdentifierPolicy = authenticationRequest.getNameIDPolicy()
|
|
## nameIdentifier = None
|
|
## if nameIdentifierPolicy == "none":
|
|
## # No federation, just return the peer/local name identifier.
|
|
## nameIdentifier = identification.peerNameIdentifier
|
|
## if not nameIdentifier:
|
|
## nameIdentifier = identification.localNameIdentifier
|
|
## assert nameIdentifier
|
|
## elif nameIdentifierPolicy == "onetime":
|
|
## # One time federation.
|
|
## nameIdentifier = lassoTools.generateNameIdentifier(
|
|
## peerProviderId)
|
|
## raise "FIXME: Store new nameIdentifier in identification?"
|
|
## elif nameIdentifierPolicy == "federated":
|
|
## # Federation. Return peer/local/new name identifier
|
|
## nameIdentifier = identification.peerNameIdentifier
|
|
## if not nameIdentifier:
|
|
## nameIdentifier = identification.localNameIdentifier
|
|
## assert nameIdentifier
|
|
## elif nameIdentifierPolicy == "any":
|
|
## # Federated else none.
|
|
## consent = authenticationRequest.getConsent()
|
|
## if consent:
|
|
## nameIdentifier = identification.peerNameIdentifier
|
|
## if not nameIdentifier:
|
|
## nameIdentifier = identification.localNameIdentifier
|
|
## assert nameIdentifier
|
|
## else:
|
|
## raise "FIXME: No consent given by the principal"
|
|
|
|
# Get nameIdentifiers formats.
|
|
nameIdentifierFormat = "federated"
|
|
idpNameIdentifierFormat = "federated"
|
|
if nameIdentifierPolicy == "onetime":
|
|
nameIdentifierFormat = "onetime"
|
|
idpNameIdentifierFormat = "onetime"
|
|
|
|
authenticationStatement = lasso.AuthenticationStatement(
|
|
lassoAuthenticationMethod,
|
|
"2005-05-03T16:12:00Z", # reauthenticateOnOrAfter
|
|
nameIdentifier = nameIdentifier,
|
|
nameQualifier = serviceProviderId,
|
|
format = nameIdentifierFormat,
|
|
idp_nameIdentifier = identification.localNameIdentifier,
|
|
idp_nameQualifier = self.getMetadata().providerId,
|
|
idp_format = idpNameIdentifierFormat)
|
|
assertion.add_authenticationStatement(authenticationStatement)
|
|
assertion.set_signature(1, self.privateKeyFilePath, self.certificateFilePath)
|
|
authnResponse.add_assertion(assertion)
|
|
|
|
protocolProfileLassoNode = authenticationRequest.get_child(
|
|
"ProtocolProfile")
|
|
if protocolProfileLassoNode is None:
|
|
protocolProfile = "artifact"
|
|
else:
|
|
protocolProfile = protocolProfileLassoNode.get_content()
|
|
|
|
# FIXME: To remove.
|
|
if protocolProfile != lasso.libProtocolProfilePost:
|
|
logs.debug(
|
|
"Using POST protocol profile instead of %s." % protocolProfile)
|
|
protocolProfile = "post"
|
|
|
|
## if protocolProfile == lasso.libProtocolProfileArtifact:
|
|
## # Send an artifact, keep the assertion for later retrieval.
|
|
## raise "BOUM"
|
|
## assertionsProxy = getProxyForServerRole("assertions")
|
|
## artifact = assertionsProxy.addAssertion(assertion.exportToString())
|
|
## url = sso.getServiceProviderAssertionArtifactHandlerUrl(
|
|
## serviceProviderMetadata.assertionConsumerServiceUrl, # FIXME
|
|
## artifact,
|
|
## authenticationRequest.getRelayState())
|
|
## return redirect(url)
|
|
if protocolProfile == lasso.libProtocolProfilePost:
|
|
import base64
|
|
lares = authnResponse.export_to_base64()
|
|
|
|
# Remove this line and uncomment the next ones.
|
|
assertionConsumerServiceId = None
|
|
## assertionConsumerServiceIdLassoNode \
|
|
## = authenticationRequest.get_child("AssertionConsumerServiceID")
|
|
## if assertionConsumerServiceIdLassoNode is None:
|
|
## assertionConsumerServiceId = None
|
|
## else:
|
|
## assertionConsumerServiceId \
|
|
## = assertionConsumerServiceIdLassoNode.get_content()
|
|
actionUrl = serviceProviderMetadata.getAssertionConsumerServiceUrl(
|
|
assertionConsumerServiceId)
|
|
layout = html.html(
|
|
html.head(html.title(_("Authentication Succeeded"))),
|
|
html.body(
|
|
html.form(
|
|
html.input(
|
|
name = "LARES", type = "hidden", value = lares),
|
|
html.p(_("""\
|
|
You have been succesfully authenticated; click ok to go back to the service provider.\
|
|
""")),
|
|
html.div(
|
|
html.span(
|
|
html.input(
|
|
class_ = "button", name = "submit", type = "submit",
|
|
value = _("OK")),
|
|
class_ = "action-buttons-bar"),
|
|
class_ = "buttons-bar"),
|
|
action = actionUrl,
|
|
enctype = "multipart/form-data", method = "post")))
|
|
htmlDocument = documents.newTemporaryHtmlDocument()
|
|
htmlDocument.append(layout)
|
|
stylesheet = self.getDataHolder().getSiteXslt()
|
|
self.outputHttpHtmlDocument(htmlDocument, stylesheet)
|
|
else:
|
|
# Unknown protocol profile. We dont know what to do :
|
|
raise "FIXME: Unknow protocol profile"
|
|
|
|
def assertionConsumer(self):
|
|
"""Liberty Alliance service provider method which processes an authentication response
|
|
coming (indirectly) from identity provider.
|
|
|
|
Handles HTTP GET & POST.
|
|
"""
|
|
|
|
command = environs.getVar("httpCommand")
|
|
if command not in ("GET", "POST"):
|
|
raise faults.PathNotFound("")
|
|
|
|
serverContextDump = self.getServerContextDump()
|
|
serverContext = lasso.Server.new_from_dump(serverContextDump)
|
|
loginContext = lasso.Login.new(serverContext)
|
|
submission = environs.getVar("submission")
|
|
if command == "GET":
|
|
responseMsg = environs.getVar("httpQuery")
|
|
method = lasso.httpMethodRedirect
|
|
else: # command == "POST"
|
|
responseMsg = submission.getField("LAREQ")
|
|
method = lasso.httpMethodPost
|
|
relayState = submission.getField("RelayState", default = None)
|
|
errorCode = loginContext.init_request(responseMsg, method)
|
|
if errorCode:
|
|
raise Exception("Lasso login error %s" % errorCode)
|
|
errorCode = loginContext.build_request_msg()
|
|
if errorCode:
|
|
raise Exception("Lasso login error %s" % errorCode)
|
|
idpSoapEndpoint = loginContext.msg_url
|
|
soapRequestMsg = loginContext.msg_body
|
|
logs.debug("idpSoapEndpoint = %s; soapRequestMsg = %s" % (idpSoapEndpoint, soapRequestMsg))
|
|
idpParsedSoapEndpoint = urlparse.urlparse(idpSoapEndpoint)
|
|
idpAddressingScheme, idpHostName, idpPath = idpParsedSoapEndpoint[:3]
|
|
if idpAddressingScheme == "https":
|
|
privateKeyAbsolutePath = self.getConfigAbsolutePath("yep:sslCertificateKeyFile")
|
|
certificateAbsolutePath = self.getConfigAbsolutePath("yep:sslCertificateFile")
|
|
peerCaCertificateAbsolutePath = self.getConfigAbsolutePath(
|
|
"yep:sslCACertificateFile", None)
|
|
connection = http.HttpsConnection(
|
|
idpHostName, None, privateKeyAbsolutePath, certificateAbsolutePath,
|
|
peerCaCertificateAbsolutePath)
|
|
else:
|
|
connection = httplib.HTTPConnection(idpHostName)
|
|
connection.request("POST", idpPath, soapRequestMsg, {"Content-type": "text/xml"})
|
|
soapResponseFile = connection.getresponse()
|
|
soapResponseMsg = soapResponseFile.read()
|
|
logs.debug("soapResponseMsg = %s" % soapResponseMsg)
|
|
errorCode = loginContext.process_response_msg(soapResponseMsg)
|
|
if errorCode:
|
|
raise Exception("Lasso login error %s" % errorCode)
|
|
nameIdentifier = loginContext.nameIdentifier
|
|
|
|
# Retrieve account using name identifier.
|
|
accountsPath = self.getAccountsDirectoryAbsolutePath()
|
|
accountsHolder = directories.DirectoryHolder(
|
|
pathFragment = accountsPath, previous = environs.getVar("rootStation"),
|
|
isRootElder = True, containedFileSystem = filesystems.PartialFileSystem(accountsPath))
|
|
accountLocalId = accountsHolder.getItemLocalId(nameIdentifier)
|
|
if accountLocalId is None:
|
|
accountHolder = None
|
|
else:
|
|
accountHolder = accountsHolder.getItem(accountLocalId)
|
|
if accountHolder is None:
|
|
# There is no account indexed by this name identifier. Create it and its user.
|
|
# FIXME: Call account creation wizard and user creation or retrieval wizard.
|
|
|
|
# Create user.
|
|
usersPath = self.getConfigAbsolutePath("yep:usersPath")
|
|
usersHolder = directories.DirectoryHolder(
|
|
pathFragment = usersPath, previous = environs.getVar("rootStation"),
|
|
isRootElder = True,
|
|
containedFileSystem = filesystems.PartialFileSystem(usersPath))
|
|
userFeature = modules.getElementFeature(namespaces.yep.uri, "user")
|
|
userHolder = userFeature.newXmlHolder(usersHolder)
|
|
user = userHolder.getRootElement()
|
|
# FIXME: Initialize user attributes.
|
|
# userHolder.save() is done below.
|
|
|
|
# Create account.
|
|
accountFeature = modules.getElementFeature(
|
|
namespaces.yep.uri, "libertyAllianceAccount")
|
|
accountHolder = accountFeature.newXmlHolder(
|
|
accountsHolder, pathFragment = nameIdentifier + ".xml")
|
|
account = accountHolder.getRootElement()
|
|
account.setNameIdentifier(nameIdentifier)
|
|
account.setUserAbsolutePath(user.getAbsolutePath())
|
|
user.setLibertyAllianceAccountAbsolutePath(account.getAbsolutePath())
|
|
userContextDump = None
|
|
else:
|
|
account = accountHolder.getRootElement()
|
|
user = account.getUser()
|
|
userContextDump = account.getUserContextDump()
|
|
loginContext.create_user(userContextDump)
|
|
# Save the new or updated userContext into account.
|
|
account.setUserContextDump(loginContext.user.dump())
|
|
# Retrieve the authentication method and store it in account.
|
|
lassoAuthenticationMethod = loginContext.response.get_child(
|
|
"AuthenticationStatement").get_attr_value("AuthenticationMethod")
|
|
if lassoAuthenticationMethod == lasso.samlAuthenticationMethodPassword:
|
|
authenticationMethod = "password"
|
|
else:
|
|
logs.debug('Unknown authentication method = "%s"' % lassoAuthenticationMethod)
|
|
account.setAuthenticationMethod(authenticationMethod)
|
|
account.getDocument().save()
|
|
# User is now authenticated => create session, cookie...
|
|
session = sessions.getOrCreateSession()
|
|
session.publishToken = True
|
|
session.setAccountAbsolutePath(account.getAbsolutePath())
|
|
user.setSessionToken(session.getToken())
|
|
user.getDocument().save()
|
|
baseEnviron = environs.get(_level = "handleHttpCommand")
|
|
baseEnviron.setVar("user", user)
|
|
logs.debug('Setting session user = "%s" at "%s".' % (
|
|
user.getSimpleLabel(), session.getAbsolutePath()))
|
|
raise "On avance"
|
|
|
|
def getAccountsDirectoryAbsolutePath(self):
|
|
nodes = self.evaluateXpath("yep:accountsDirectory")
|
|
if nodes:
|
|
location = nodes[0].content.strip()
|
|
return self.convertRelativeLocationToAbsolutePath(location)
|
|
else:
|
|
return None
|
|
|
|
def getCertificateFilePath(self):
|
|
nodes = self.evaluateXpath("yep:certificateFile")
|
|
if nodes:
|
|
location = nodes[0].content.strip()
|
|
return self.convertRelativeLocationToAbsolutePath(location)
|
|
else:
|
|
return None
|
|
|
|
def getMetadata(self):
|
|
directoryHolder = self.getDataHolder().getUriPrevious()
|
|
try:
|
|
metadataHolder = directoryHolder.walkToLocation(
|
|
directoryHolder.getSubPathInternUri("metadata.xml"))
|
|
except faults.PathNotFound:
|
|
return None
|
|
return metadataHolder.getRootElement()
|
|
|
|
def getMetadataFilePath(self):
|
|
location = "metadata.xml"
|
|
return self.convertRelativeLocationToAbsolutePath(location)
|
|
|
|
def getPeerCertificationAuthorityCertificateFilePath(self):
|
|
nodes = self.evaluateXpath("yep:peerCACertificateFile")
|
|
if nodes:
|
|
location = nodes[0].content.strip()
|
|
return self.convertRelativeLocationToAbsolutePath(location)
|
|
else:
|
|
return None
|
|
|
|
def getPeerMetadata(self):
|
|
# FIXME: Temporary element name: yep:peerMetadata
|
|
nodes = self.evaluateXpath("yep:peerMetadata/@src")
|
|
if not nodes:
|
|
return None
|
|
location = nodes[0].content.strip()
|
|
directoryHolder = self.getDataHolder().getUriPrevious()
|
|
try:
|
|
metadataHolder = directoryHolder.walkToLocation(
|
|
directoryHolder.getSubPathInternUri(location))
|
|
except faults.PathNotFound:
|
|
return None
|
|
return metadataHolder.getRootElement()
|
|
|
|
def getPeerMetadataFilePath(self):
|
|
nodes = self.evaluateXpath("yep:peerMetadata/@src")
|
|
if nodes:
|
|
location = nodes[0].content.strip()
|
|
return self.convertRelativeLocationToAbsolutePath(location)
|
|
else:
|
|
return None
|
|
|
|
def getPeerPublicKeyFilePath(self):
|
|
nodes = self.evaluateXpath("yep:peerPublicKeyFile")
|
|
if nodes:
|
|
location = nodes[0].content.strip()
|
|
return self.convertRelativeLocationToAbsolutePath(location)
|
|
else:
|
|
return None
|
|
|
|
def getPrivateKeyFilePath(self):
|
|
nodes = self.evaluateXpath("yep:privateKeyFile")
|
|
if nodes:
|
|
location = nodes[0].content.strip()
|
|
return self.convertRelativeLocationToAbsolutePath(location)
|
|
else:
|
|
return None
|
|
|
|
def getPublicKeyFilePath(self):
|
|
nodes = self.evaluateXpath("yep:publicKeyFile")
|
|
if nodes:
|
|
location = nodes[0].content.strip()
|
|
return self.convertRelativeLocationToAbsolutePath(location)
|
|
else:
|
|
return None
|
|
|
|
def getResponsesDirectoryAbsolutePath(self):
|
|
nodes = self.evaluateXpath("yep:responsesDirectory")
|
|
if nodes:
|
|
location = nodes[0].content.strip()
|
|
return self.convertRelativeLocationToAbsolutePath(location)
|
|
else:
|
|
return None
|
|
|
|
def getServerContextDump(self):
|
|
if self._serverContextDump is None:
|
|
serverContext = lasso.Server.new(
|
|
self.getMetadataFilePath(), self.getPublicKeyFilePath(),
|
|
self.getPrivateKeyFilePath(), self.getCertificateFilePath(),
|
|
lasso.signatureMethodRsaSha1)
|
|
serverContext.add_provider(
|
|
self.getPeerMetadataFilePath(), self.getPeerPublicKeyFilePath(),
|
|
self.getPeerCertificationAuthorityCertificateFilePath())
|
|
self._serverContextDump = serverContext.dump()
|
|
return self._serverContextDump
|
|
|
|
def getServiceProviderMetadata(self, providerId):
|
|
directoryHolder = self.getDataHolder().getUriPrevious()
|
|
try:
|
|
metadataHolder = directoryHolder.walkToLocation(directoryHolder.getSubPathInternUri(
|
|
"service-providers/%s" % strings.simplify(providerId)))
|
|
except faults.PathNotFound:
|
|
logs.debug("Service provider not found: %s" % providerId)
|
|
return None
|
|
return metadataHolder.getRootElement()
|
|
|
|
def login(self):
|
|
"""Liberty Alliance service provider method that builds an authentication requests and then
|
|
redirects to the identity provider login page.
|
|
|
|
Handles HTTP GET.
|
|
"""
|
|
|
|
command = environs.getVar("httpCommand")
|
|
if command != "GET":
|
|
raise faults.PathNotFound("")
|
|
|
|
serverContextDump = self.getServerContextDump()
|
|
serverContext = lasso.Server.new_from_dump(serverContextDump)
|
|
loginContext = lasso.Login.new(serverContext)
|
|
if loginContext.init_authn_request(self.getPeerMetadata().getProviderId()):
|
|
raise Exception('Login error')
|
|
|
|
# Identity provider will ask user to authenticate himself.
|
|
loginContext.request.set_isPassive(False)
|
|
|
|
# Identity provider will not ask user to authenticate himself if he has already done it
|
|
# recently.
|
|
# loginContext.request.set_forceAuthn(False)
|
|
|
|
loginContext.request.set_nameIDPolicy(lasso.libNameIDPolicyTypeFederated)
|
|
# loginContext.request.set_relayState("fake")
|
|
# loginContext.request.set_protocolProfile(lasso.libProtocolProfileBrwsArt)
|
|
|
|
if loginContext.build_authn_request_msg():
|
|
raise Exception('Login error')
|
|
|
|
return environs.getVar("httpRequestHandler").outputRedirect(loginContext.msg_url)
|
|
|
|
def soapEndpoint(self):
|
|
"""Liberty Alliance identity & service provider SOAP endpoint.
|
|
|
|
Handles HTTP POST.
|
|
"""
|
|
|
|
command = environs.getVar("httpCommand")
|
|
if command != "POST":
|
|
raise faults.PathNotFound("")
|
|
|
|
submission = environs.getVar("submission")
|
|
requestMsg = submission.readFile()
|
|
serverContextDump = self.getServerContextDump()
|
|
serverContext = lasso.Server.new_from_dump(serverContextDump)
|
|
loginContext = lasso.Login.new(serverContext)
|
|
errorCode = loginContext.process_request_msg(requestMsg)
|
|
if errorCode:
|
|
raise Exception("Lasso login error %s" % errorCode)
|
|
|
|
# Retrieve response with artifact. Reply to SOAP request using the response dump, then
|
|
# destroy response.
|
|
responsesPath = self.getResponsesDirectoryAbsolutePath()
|
|
responsesHolder = directories.DirectoryHolder(
|
|
pathFragment = responsesPath, previous = environs.getVar("rootStation"),
|
|
isRootElder = True, containedFileSystem = filesystems.PartialFileSystem(responsesPath))
|
|
responseLocalId = responsesHolder.getItemLocalId(loginContext.assertionArtifact)
|
|
if responseLocalId is None:
|
|
responseHolder = None
|
|
else:
|
|
responseHolder = responsesHolder.getItem(responseLocalId)
|
|
if responseHolder is None:
|
|
raise "FIXME: What should be done when artifact %s is unknown?" % loginContext.assertionArtifact
|
|
response = responseHolder.getRootElement()
|
|
responseDump = response.getResponseDump()
|
|
responseHolder.destroy()
|
|
# FIXME: I presume that responseDump should be encapsulated inside a SOAP enveloppe?
|
|
environs.getVar("httpRequestHandler").outputData(responseDump, mimeType = "text/xml")
|
|
|
|
def singleSignOn(self):
|
|
"""Liberty Alliance identity provider method that processes an authentication request
|
|
coming (indirectly) from a service provider.
|
|
|
|
Handles HTTP GET & POST.
|
|
"""
|
|
|
|
command = environs.getVar("httpCommand")
|
|
if command not in ("GET", "POST"):
|
|
raise faults.PathNotFound("")
|
|
|
|
serverContextDump = self.getServerContextDump()
|
|
serverContext = lasso.Server.new_from_dump(serverContextDump)
|
|
userContext = None
|
|
account = None
|
|
user = environs.getVar("user")
|
|
if user is not None:
|
|
account = user.getLibertyAllianceAccount()
|
|
if account is not None:
|
|
userContextDump = account.getUserContextDump()
|
|
if userContextDump is not None:
|
|
userContext = lasso.User.new_from_dump(userContextDump)
|
|
loginContext = lasso.Login.new(serverContext, userContext)
|
|
if command == "GET":
|
|
authnRequestMsg = environs.getVar("httpQuery")
|
|
method = lasso.httpMethodRedirect
|
|
else: # command == "POST"
|
|
submission = environs.getVar("submission")
|
|
authnRequestMsg = submission.getField("LAREQ")
|
|
method = lasso.httpMethodPost
|
|
if loginContext.init_from_authn_request_msg(authnRequestMsg, method):
|
|
raise Exception('Login error')
|
|
logs.debug("ProtocolProfile = %s" % loginContext.protocolProfile)
|
|
mustAuthenticate = loginContext.must_authenticate()
|
|
logs.debug("User must be authenticated = %s" % mustAuthenticate)
|
|
if mustAuthenticate:
|
|
if self.getConfigBoolean("yep:useHttpAuthentication", default = False):
|
|
# HTTP authentication.
|
|
if user is None:
|
|
return environs.getVar("httpRequestHandler").outputErrorUnauthorized(
|
|
self.getUriAbsolutePath())
|
|
else:
|
|
# The user is already authenticated using HTTP authentication.
|
|
userAuthenticated = True
|
|
authenticationMethod = "password"
|
|
else:
|
|
# FIXME: Authenticate the user and set the right authenticationMethod.
|
|
raise "TODO"
|
|
else:
|
|
userAuthenticated = True
|
|
authenticationMethod = "password" # FIXME
|
|
if authenticationMethod == "password":
|
|
lassoAuthenticationMethod = lasso.samlAuthenticationMethodPassword
|
|
else:
|
|
logs.debug('Unknown authentication method = "%s"' % authenticationMethod)
|
|
if loginContext.protocolProfile == lasso.loginProtocolProfileBrwsArt:
|
|
# We can send the artifact either using a redirect or a POST. It is an IDP choice,
|
|
# but redirect is better than POST (no need for a page with a form).
|
|
useRedirectForArtifact = True
|
|
if useRedirectForArtifact:
|
|
errorCode = loginContext.build_artifact_msg(
|
|
userAuthenticated, lassoAuthenticationMethod,
|
|
"FIXME: reauthenticateOnOrAfter", lasso.httpMethodRedirect)
|
|
if errorCode:
|
|
raise Exception("Lasso login error %s" % errorCode)
|
|
logs.debug("msg_url = %s; assertionArtifact = %s; response_dump = %s" % (
|
|
loginContext.msg_url, loginContext.assertionArtifact,
|
|
loginContext.response_dump))
|
|
nameIdentifier = loginContext.nameIdentifier
|
|
|
|
# Save user dump and name identifier.
|
|
if account is None:
|
|
accountsPath = self.getAccountsDirectoryAbsolutePath()
|
|
accountsHolder = directories.DirectoryHolder(
|
|
pathFragment = accountsPath, previous = environs.getVar("rootStation"),
|
|
isRootElder = True,
|
|
containedFileSystem = filesystems.PartialFileSystem(accountsPath))
|
|
accountLocalId = accountsHolder.getItemLocalId(nameIdentifier)
|
|
if accountLocalId is None:
|
|
accountHolder = None
|
|
else:
|
|
accountHolder = accountsHolder.getItem(accountLocalId)
|
|
if accountHolder is None:
|
|
# There is no account indexed by this name identifier. Create it and
|
|
# link it to the user.
|
|
accountFeature = modules.getElementFeature(
|
|
namespaces.yep.uri, "libertyAllianceAccount")
|
|
accountHolder = accountFeature.newXmlHolder(
|
|
accountsHolder, pathFragment = nameIdentifier + ".xml")
|
|
account = accountHolder.getRootElement()
|
|
account.setNameIdentifier(nameIdentifier)
|
|
else:
|
|
account = accountHolder.getRootElement()
|
|
logs.warning(
|
|
'Liberty Alliance account "%s" was not referenced by user %s'
|
|
% (account.getAbsolutePath(), user.getAbsolutePath()))
|
|
account.setUserAbsolutePath(user.getAbsolutePath())
|
|
user.setLibertyAllianceAccountAbsolutePath(account.getAbsolutePath())
|
|
user.getDocument().save()
|
|
# Save the new or updated userContext into account.
|
|
account.setUserContextDump(loginContext.user.dump())
|
|
account.setAuthenticationMethod(authenticationMethod)
|
|
account.getDocument().save()
|
|
|
|
# Save response dump, to retrieve it later, when receiving a request with the
|
|
# same artifact.
|
|
responsesPath = self.getResponsesDirectoryAbsolutePath()
|
|
responsesHolder = directories.DirectoryHolder(
|
|
pathFragment = responsesPath, previous = environs.getVar("rootStation"),
|
|
isRootElder = True,
|
|
containedFileSystem = filesystems.PartialFileSystem(responsesPath))
|
|
responseFeature = modules.getElementFeature(
|
|
namespaces.yep.uri, "libertyAllianceResponse")
|
|
responseHolder = responseFeature.newXmlHolder(
|
|
responsesHolder, pathFragment = loginContext.assertionArtifact + ".xml")
|
|
response = responseHolder.getRootElement()
|
|
response.setArtifact(loginContext.assertionArtifact)
|
|
response.setResponseDump(loginContext.response_dump)
|
|
response.getDocument().save()
|
|
|
|
return environs.getVar("httpRequestHandler").outputRedirect(
|
|
loginContext.msg_url)
|
|
else:
|
|
errorCode = loginContext.build_artifact_msg(
|
|
userAuthenticated, lassoAuthenticationMethod,
|
|
"FIXME: reauthenticateOnOrAfter", lasso.httpMethodPost)
|
|
if errorCode:
|
|
raise Exception("Lasso login error %s" % errorCode)
|
|
logs.debug("msg_url = %s; msg_body = %s; msg_relayState = %s; "
|
|
"assertionArtifact = %s; response_dump = %s" % (
|
|
loginContext.msg_url, loginContext.msg_body, loginContext.msg_relayState,
|
|
loginContext.assertionArtifact, loginContext.response_dump))
|
|
raise "FIXME"
|
|
else: # loginContext.protocolProfile == lasso.loginProtocolProfileBrwsPost:
|
|
errorCode = loginContext.build_authn_response_msg(
|
|
userAuthenticated, lassoAuthenticationMethod, "FIXME: reauthenticateOnOrAfter")
|
|
if errorCode:
|
|
raise Exception("Lasso login error %s" % errorCode)
|
|
logs.debug("msg_url = %s; msg_body = %s; msg_relayState = %s" % (
|
|
loginContext.msg_url, loginContext.msg_body, loginContext.msg_relayState))
|
|
raise "FIXME"
|
|
raise "TODO"
|
|
|
|
|
|
# FIXME: To remove.
|
|
if loginContext.protocolProfile == lasso.loginProtocolProfileBrwsArt:
|
|
if loginContext.build_artifact_msg(1, lasso.samlAuthenticationMethodPassword,
|
|
"",
|
|
lasso.httpMethodRedirect):
|
|
raise Exception('Login error')
|
|
logs.debug("msg_url = %s" % loginContext.msg_url)
|
|
|
|
|
|
protocolProfile = lasso.authn_request_get_protocolProfile(ssoQuery)
|
|
if protocolProfile == lasso.libProtocolProfilePost:
|
|
authnResponse = lasso.AuthnResponse.new_from_request_query(
|
|
ssoQuery, self.getMetadata().providerId)
|
|
if authnResponse is None:
|
|
raise faults.PathForbidden("")
|
|
if not authnResponse.verify_signature(
|
|
self.peerPublicKeyFilePath, # FIXME: Use a certificate instead & How to know which SP?
|
|
self.privateKeyFilePath # Should be removed. Used only to generate a fake signature.
|
|
): # Change this test to compare to 0.
|
|
# FIXME: Retourner le post d'erreur.
|
|
raise "BOUM"
|
|
user = environs.getVar("user")
|
|
if authnResponse.must_authenticate(
|
|
is_authenticated = user is not None):
|
|
# Save the authentication response. It will be used after
|
|
# authentication.
|
|
session = sessions.getOrCreateSession()
|
|
session.publishToken = True
|
|
session.libertyAllianceAuthenticationResponse \
|
|
= authnResponse.dump()
|
|
return self.walkToLocation(
|
|
"/accounts/passwords/login", "GET",
|
|
nextUrl = self.getActionUri("afterAuthentication"))
|
|
else:
|
|
return self.afterAuthentication("singleSignOn", authnResponse)
|
|
else:
|
|
# FIXME: Gérer l'artifact.
|
|
raise "BOUM"
|
|
|
|
|
|
elements.registerElement(
|
|
namespaces.md.uri, "EntityDescriptor", EntityDescriptor,
|
|
"http://www.entrouvert.org/expression/schemas/liberty-metadata-v1.0.xsd")
|
|
elements.registerElement(namespaces.yep.uri, "libertyAlliance", LibertyAlliance)
|