1159 lines
54 KiB
Python
1159 lines
54 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 errno
|
|
import httplib
|
|
import os
|
|
import urllib
|
|
import urlparse
|
|
|
|
import lasso
|
|
import libxml2
|
|
|
|
from OpenSSL import SSL
|
|
|
|
import expression.core.directories as directories
|
|
import expression.core.dataholders as dataholders
|
|
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 libertyallianceresponses
|
|
|
|
|
|
deleteSessionOnLogout = True
|
|
#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):
|
|
_lassoServerDump = 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
|
|
elif lassoAuthenticationMethod == "certificate":
|
|
lassoAuthenticationMethod = lasso.samlAuthenticationMethodSoftwarePki
|
|
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", # FIXME: 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("")
|
|
|
|
lassoServerDump = self.getLassoServerDump()
|
|
lassoServer = lasso.Server.new_from_dump(lassoServerDump)
|
|
lassoLogin = lasso.Login.new(lassoServer)
|
|
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)
|
|
try:
|
|
lassoLogin.init_request(responseMsg, method)
|
|
except lasso.Error, error:
|
|
raise Exception("Lasso login error %s" % error.code)
|
|
try:
|
|
lassoLogin.build_request_msg()
|
|
except lasso.Error, error:
|
|
raise Exception("Lasso login error %s" % error.code)
|
|
logs.debug("idpSoapEndpoint = %s; soapRequestMsg = %s" % (
|
|
lassoLogin.msg_url, lassoLogin.msg_body))
|
|
soapResponseMsg = self.callSoap(lassoLogin.msg_url, lassoLogin.msg_body)
|
|
logs.debug("soapResponseMsg = %s" % soapResponseMsg)
|
|
try:
|
|
lassoLogin.process_response_msg(soapResponseMsg)
|
|
except lasso.Error, error:
|
|
raise Exception("Lasso login error %s" % error.code)
|
|
nameIdentifier = lassoLogin.nameIdentifier
|
|
|
|
# Retrieve user, using name identifier or else use the current user.
|
|
user = environs.getVar("user")
|
|
userSymlinksAbsolutePath = self.getUsersDirectoryAbsolutePath()
|
|
userSymlinkName = strings.simplify(nameIdentifier) + ".xml"
|
|
userSymlinkAbsolutePath = os.path.join(userSymlinksAbsolutePath, userSymlinkName)
|
|
try:
|
|
userPath = os.readlink(userSymlinkAbsolutePath)
|
|
except OSError, error:
|
|
if error.errno not in (errno.ENOENT, errno.EACCES):
|
|
raise
|
|
userAbsolutePath = None
|
|
else:
|
|
userAbsolutePath = os.path.join(userSymlinksAbsolutePath, userPath)
|
|
if user is None or user.getAbsolutePath() != userAbsolutePath:
|
|
try:
|
|
userHolder = dataholders.DataHolder(
|
|
pathFragment = userAbsolutePath, mimeType = "text/xml", isRootElder = True,
|
|
containedFileSystem = filesystems.PartialFileSystem(userAbsolutePath))
|
|
except IOError, error:
|
|
if error.errno == errno.ENOENT:
|
|
logs.debug("""User at path "%s" doesn't exist.""" % userAbsolutePath)
|
|
else:
|
|
raise
|
|
else:
|
|
# Some times, users are stored in independant files; some times they are
|
|
# embedded inside accounts.
|
|
accountOrUser = userHolder.getRootElement()
|
|
user = accountOrUser.getUser()
|
|
if user is not None:
|
|
lassoIdentityDump = user.getLassoIdentityDump()
|
|
if lassoIdentityDump is not None:
|
|
lassoLogin.set_identity_from_dump(lassoIdentityDump)
|
|
|
|
# Retrieve session, using name identifier or else use the current session.
|
|
session = environs.getVar("session")
|
|
sessionSymlinksAbsolutePath = self.getSessionsDirectoryAbsolutePath()
|
|
sessionSymlinkName = strings.simplify(nameIdentifier) + ".xml"
|
|
sessionSymlinkAbsolutePath = os.path.join(sessionSymlinksAbsolutePath, sessionSymlinkName)
|
|
try:
|
|
sessionPath = os.readlink(sessionSymlinkAbsolutePath)
|
|
except OSError, error:
|
|
if error.errno not in (errno.ENOENT, errno.EACCES):
|
|
raise
|
|
sessionAbsolutePath = None
|
|
else:
|
|
sessionAbsolutePath = os.path.join(sessionSymlinksAbsolutePath, sessionPath)
|
|
if session is None or session.getAbsolutePath() != sessionAbsolutePath:
|
|
try:
|
|
sessionHolder = dataholders.DataHolder(
|
|
pathFragment = sessionAbsolutePath, mimeType = "text/xml",
|
|
isRootElder = True,
|
|
containedFileSystem = filesystems.PartialFileSystem(sessionAbsolutePath))
|
|
except IOError, error:
|
|
if error.errno == errno.ENOENT:
|
|
logs.debug("""Session at path "%s" doesn't exist.""" % sessionAbsolutePath)
|
|
else:
|
|
raise
|
|
else:
|
|
session = sessionHolder.getRootElement()
|
|
if session is not None:
|
|
lassoSessionDump = session.getLassoSessionDump()
|
|
if lassoSessionDump is not None:
|
|
lassoLogin.set_session_from_dump(lassoSessionDump)
|
|
|
|
try:
|
|
lassoLogin.accept_sso()
|
|
except lasso.Error, error:
|
|
raise Exception("Lasso login error %s" % error.code)
|
|
|
|
# User is now authenticated => Create user, session, cookie...
|
|
if user is None:
|
|
# FIXME: Call 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.
|
|
if session is None:
|
|
session = sessions.createSession()
|
|
# Save the new or updated Lasso identity into user.
|
|
if lassoLogin.is_identity_dirty():
|
|
user.setLassoIdentityDump(lassoLogin.get_identity().dump()) # FIXME: Use get_identity_dump()
|
|
user.setSessionToken(session.getToken())
|
|
user.getDocument().save()
|
|
if user != environs.getVar("user"):
|
|
user.setEnviron()
|
|
logs.debug('Setting user = "%s" at "%s".' % (
|
|
user.getSimpleLabel(), user.getAbsolutePath()))
|
|
# Create a symbolic link to user, to allow to retrieve it using name identifier.
|
|
if userAbsolutePath != user.getAbsolutePath():
|
|
if not os.access(userSymlinksAbsolutePath, os.F_OK):
|
|
os.makedirs(userSymlinksAbsolutePath)
|
|
# os.access(userSymlinkAbsolutePath, os.F_OK) returns False for broken
|
|
# symlinks, so we can't use it to test existence of symlink.
|
|
try:
|
|
os.remove(userSymlinkAbsolutePath)
|
|
except OSError, error:
|
|
if error.errno != errno.ENOENT:
|
|
raise
|
|
# FIXME: Use a relative path instead of an absolute one for symbolic link.
|
|
os.symlink(user.getAbsolutePath(), userSymlinkAbsolutePath)
|
|
# Save the new or updated Lasso session into user.
|
|
session.publishToken = True
|
|
if lassoLogin.is_session_dirty():
|
|
session.setLassoSessionDump(lassoLogin.get_session().dump()) # FIXME: Use get_session_dump()
|
|
session.setAccountAbsolutePath(user.getAbsolutePath())
|
|
if session != environs.getVar("session"):
|
|
session.setEnviron()
|
|
logs.debug('Setting session at "%s".' % session.getAbsolutePath())
|
|
# Create a symbolic link to session, to allow to retrieve it using name identifier.
|
|
if sessionAbsolutePath != session.getAbsolutePath():
|
|
if not os.access(sessionSymlinksAbsolutePath, os.F_OK):
|
|
os.makedirs(sessionSymlinksAbsolutePath)
|
|
# os.access(sessionSymlinkAbsolutePath, os.F_OK) returns False for broken
|
|
# symlinks, so we can't use it to test existence of symlink.
|
|
try:
|
|
os.remove(sessionSymlinkAbsolutePath)
|
|
except OSError, error:
|
|
if error.errno != errno.ENOENT:
|
|
raise
|
|
# FIXME: Use a relative path instead of an absolute one for symbolic link.
|
|
os.symlink(session.getAbsolutePath(), sessionSymlinkAbsolutePath)
|
|
|
|
return self.walkToLocation("/", "GET")
|
|
|
|
def callSoap(self, url, soapRequestMessage):
|
|
parsedUrl = urlparse.urlparse(url)
|
|
addressingScheme, hostName, path = parsedUrl[:3]
|
|
if addressingScheme == "https":
|
|
privateKeyAbsolutePath = self.getConfigAbsolutePath("yep:sslCertificateKeyFile")
|
|
certificateAbsolutePath = self.getConfigAbsolutePath("yep:sslCertificateFile")
|
|
peerCaCertificateAbsolutePath = self.getConfigAbsolutePath(
|
|
"yep:sslCACertificateFile", None)
|
|
if self.getConfigBoolean("yep:libertyAllianceNoCert", default = False):
|
|
connection = http.HttpsConnection(hostName)
|
|
else:
|
|
connection = http.HttpsConnection(
|
|
hostName, None, privateKeyAbsolutePath, certificateAbsolutePath,
|
|
peerCaCertificateAbsolutePath)
|
|
else:
|
|
connection = httplib.HTTPConnection(hostName)
|
|
try:
|
|
connection.request("POST", path, soapRequestMessage, {"Content-Type": "text/xml"})
|
|
except SSL.Error, e:
|
|
if e.args and e.args[0] and e.args[0][0] and e.args[0][0][0] == 'SSL routines':
|
|
logs.debug(repr(e))
|
|
return environs.getVar("httpRequestHandler").outputAlert(e.args[0][0][2])
|
|
raise
|
|
soapResponseFile = connection.getresponse()
|
|
try:
|
|
soapResponseMsg = soapResponseFile.read()
|
|
except SSL.SysCallError, e:
|
|
return environs.getVar("httpRequestHandler").outputAlert(
|
|
"No SOAP answer: SSL.SysCallError%s" % str(e))
|
|
return soapResponseMsg
|
|
|
|
def getCertificateFilePath(self):
|
|
nodes = self.evaluateXpath("yep:certificateFile")
|
|
if nodes:
|
|
location = nodes[0].content.strip()
|
|
return self.convertRelativeLocationToAbsolutePath(location)
|
|
else:
|
|
return None
|
|
|
|
def getLassoServerDump(self):
|
|
if self._lassoServerDump is None:
|
|
lassoServer = lasso.Server.new(
|
|
self.getMetadataFilePath(), self.getPublicKeyFilePath(),
|
|
self.getPrivateKeyFilePath(), self.getCertificateFilePath(),
|
|
lasso.signatureMethodRsaSha1)
|
|
lassoServer.add_provider(
|
|
self.getPeerMetadataFilePath(), self.getPeerPublicKeyFilePath(),
|
|
self.getPeerCertificationAuthorityCertificateFilePath())
|
|
self._lassoServerDump = lassoServer.dump()
|
|
return self._lassoServerDump
|
|
|
|
def getMetadata(self):
|
|
nodes = self.evaluateXpath("yep:metadata/@src")
|
|
if not nodes:
|
|
return None
|
|
location = nodes[0].content.strip()
|
|
metadataAbsolutePath = self.convertRelativeLocationToAbsolutePath(location)
|
|
metadataHolder = dataholders.DataHolder(
|
|
pathFragment = metadataAbsolutePath, mimeType = "text/xml", isRootElder = True,
|
|
containedFileSystem = filesystems.PartialFileSystem(metadataAbsolutePath))
|
|
return metadataHolder.getRootElement()
|
|
|
|
def getMetadataFilePath(self):
|
|
nodes = self.evaluateXpath("yep:metadata/@src")
|
|
if nodes:
|
|
location = nodes[0].content.strip()
|
|
return self.convertRelativeLocationToAbsolutePath(location)
|
|
else:
|
|
return None
|
|
|
|
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()
|
|
metadataAbsolutePath = self.convertRelativeLocationToAbsolutePath(location)
|
|
metadataHolder = dataholders.DataHolder(
|
|
pathFragment = metadataAbsolutePath, mimeType = "text/xml", isRootElder = True,
|
|
containedFileSystem = filesystems.PartialFileSystem(metadataAbsolutePath))
|
|
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 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 getUsersDirectoryAbsolutePath(self):
|
|
nodes = self.evaluateXpath("yep:usersDirectory")
|
|
if nodes:
|
|
location = nodes[0].content.strip()
|
|
return self.convertRelativeLocationToAbsolutePath(location)
|
|
else:
|
|
return None
|
|
|
|
def getSessionsDirectoryAbsolutePath(self):
|
|
nodes = self.evaluateXpath("yep:sessionsDirectory")
|
|
if nodes:
|
|
location = nodes[0].content.strip()
|
|
return self.convertRelativeLocationToAbsolutePath(location)
|
|
else:
|
|
return None
|
|
|
|
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("")
|
|
|
|
lassoServerDump = self.getLassoServerDump()
|
|
lassoServer = lasso.Server.new_from_dump(lassoServerDump)
|
|
lassoLogin = lasso.Login.new(lassoServer)
|
|
try:
|
|
lassoLogin.init_authn_request()
|
|
except lasso.Error, error:
|
|
raise Exception("Lasso login error %s" % error.code)
|
|
|
|
# Identity provider will ask user to authenticate himself.
|
|
lassoLogin.request.set_isPassive(False)
|
|
|
|
# Identity provider will not ask user to authenticate himself if he has already done it
|
|
# recently.
|
|
# lassoLogin.request.set_forceAuthn(False)
|
|
|
|
lassoLogin.request.set_nameIDPolicy(lasso.libNameIDPolicyTypeFederated)
|
|
lassoLogin.request.set_consent(lasso.libConsentObtained)
|
|
# lassoLogin.request.set_relayState("fake")
|
|
# lassoLogin.request.set_protocolProfile(lasso.libProtocolProfileBrwsArt)
|
|
|
|
try:
|
|
lassoLogin.build_authn_request_msg(self.getPeerMetadata().getProviderId())
|
|
except lasso.Error, error:
|
|
raise Exception("Lasso login error %s" % error.code)
|
|
|
|
return environs.getVar("httpRequestHandler").outputRedirect(lassoLogin.msg_url)
|
|
|
|
def logout(self):
|
|
"""Liberty Alliance service provider method that builds a logout request and sends it to
|
|
the identity provider.
|
|
|
|
Handles HTTP GET.
|
|
"""
|
|
|
|
command = environs.getVar("httpCommand")
|
|
if command != "GET":
|
|
raise faults.PathNotFound("")
|
|
|
|
user = environs.getVar("user")
|
|
if user is None:
|
|
raise Exception("FIXME: already logged out")
|
|
lassoIdentityDump = user.getLassoIdentityDump()
|
|
if lassoIdentityDump is None:
|
|
raise Exception("FIXME: Identity has no federation => non Liberty logout")
|
|
session = environs.getVar("session")
|
|
if session is None:
|
|
raise Exception("FIXME: already logged out")
|
|
lassoSessionDump = session.getLassoSessionDump()
|
|
if lassoSessionDump is None:
|
|
raise Exception("FIXME: Session has no authentication assertion => non Liberty logout")
|
|
lassoServerDump = self.getLassoServerDump()
|
|
lassoServer = lasso.Server.new_from_dump(lassoServerDump)
|
|
lassoLogout = lasso.Logout.new(lassoServer, lasso.providerTypeSp)
|
|
lassoLogout.set_identity_from_dump(lassoIdentityDump)
|
|
lassoLogout.set_session_from_dump(lassoSessionDump)
|
|
try:
|
|
lassoLogout.init_request()
|
|
except lasso.Error, error:
|
|
raise Exception("Lasso logout error %s" % error.code)
|
|
try:
|
|
lassoLogout.build_request_msg()
|
|
except lasso.Error, error:
|
|
raise Exception("Lasso logout error %s" % error.code)
|
|
nameIdentifier = lassoLogout.nameIdentifier
|
|
soapResponseMessage = self.callSoap(lassoLogout.msg_url, lassoLogout.msg_body)
|
|
lassoLogout.process_response_msg(soapResponseMessage, lasso.httpMethodSoap)
|
|
# Logout doesn't change identity federation => no change to user.
|
|
assert not lassoLogout.is_identity_dirty()
|
|
# Logout removes authentication assertion from session
|
|
assert lassoLogout.is_session_dirty()
|
|
lassoSession = lassoLogout.get_session()
|
|
if lassoSession is None:
|
|
user.deleteSessionToken()
|
|
user.getDocument().save()
|
|
baseEnviron = environs.get(_level = "handleHttpCommand")
|
|
baseEnviron.setVar("user", None)
|
|
session = environs.getVar("session")
|
|
session.getDocument().destroy()
|
|
baseEnviron.setVar("session", None)
|
|
else:
|
|
lassoSessionDump = lassoSession.dump()
|
|
session.setLassoSessionDump(lassoSessionDump)
|
|
# Remove the symbolic link from name identifier to session.
|
|
sessionSymlinksAbsolutePath = self.getSessionsDirectoryAbsolutePath()
|
|
sessionSymlinkName = strings.simplify(nameIdentifier) + ".xml"
|
|
sessionSymlinkAbsolutePath = os.path.join(sessionSymlinksAbsolutePath, sessionSymlinkName)
|
|
try:
|
|
os.remove(sessionSymlinkAbsolutePath)
|
|
except OSError, error:
|
|
logs.warning("Unable to remove symlink %s to session (errno = %s)" % (
|
|
sessionSymlinkAbsolutePath, error.errno))
|
|
# Leave HTTPS.
|
|
nextUrl = locations.removeSessionFromUrl("/")
|
|
# The following instruction doesn't work when going from HTTPS to HTTP.
|
|
# return self.walkToLocation(nextUrl, "GET")
|
|
return environs.getVar("httpRequestHandler").outputRedirect(nextUrl)
|
|
|
|
## def singleLogout(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("")
|
|
|
|
## lassoServerDump = self.getLassoServerDump()
|
|
## lassoServer = lasso.Server.new_from_dump(lassoServerDump)
|
|
## lassoIdentity = None
|
|
## account = None
|
|
## user = environs.getVar("user")
|
|
## if user is None:
|
|
## raise "FIXME"
|
|
## account = user.getLibertyAllianceAccount()
|
|
## if account is None:
|
|
## raise "FIXME"
|
|
## lassoIdentityDump = account.getLassoIdentityDump()
|
|
## if lassoIdentityDump is None:
|
|
## raise "FIXME"
|
|
## lassoIdentity = lasso.User.new_from_dump(lassoIdentityDump)
|
|
## lassoLogout = lasso.Logout.new(lassoServer, lassoIdentity, lasso.providerTypeIdp)
|
|
## lassoLogout.process_request_msg(request_msg, lasso.httpMethodSoap)
|
|
## #idplogout.build_response_msg()
|
|
|
|
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("")
|
|
|
|
lassoServerDump = self.getLassoServerDump()
|
|
lassoServer = lasso.Server.new_from_dump(lassoServerDump)
|
|
lassoLogin = lasso.Login.new(lassoServer)
|
|
user = environs.getVar("user")
|
|
if user is not None:
|
|
lassoIdentityDump = user.getLassoIdentityDump()
|
|
if lassoIdentityDump is not None:
|
|
lassoLogin.set_identity_from_dump(lassoIdentityDump)
|
|
session = environs.getVar("session")
|
|
if session is not None:
|
|
lassoSessionDump = session.getLassoSessionDump()
|
|
if lassoSessionDump is not None:
|
|
lassoLogin.set_session_from_dump(lassoSessionDump)
|
|
if command == "GET":
|
|
authnRequestMsg = environs.getVar("httpQuery")
|
|
method = lasso.httpMethodRedirect
|
|
else: # command == "POST"
|
|
# FIXME: We should check if the POST is SOAP. In this case this is a LECP AuthRequest.
|
|
submission = environs.getVar("submission")
|
|
authnRequestMsg = submission.getField("LAREQ")
|
|
method = lasso.httpMethodPost
|
|
try:
|
|
lassoLogin.init_from_authn_request_msg(authnRequestMsg, method)
|
|
except lasso.Error, error:
|
|
raise Exception("Lasso login error %s" % error.code)
|
|
logs.debug("ProtocolProfile = %s" % lassoLogin.protocolProfile)
|
|
mustAuthenticate = lassoLogin.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"
|
|
elif self.getConfigBoolean("yep:useCertificateAuthentication", default = False):
|
|
if user is None:
|
|
return environs.getVar("httpRequestHandler").outputErrorUnauthorized(
|
|
self.getUriAbsolutePath())
|
|
else:
|
|
userAuthenticated = True
|
|
authenticationMethod = "certificate"
|
|
elif self.getConfigString("yep:useCustomAuthentication", default = None):
|
|
# FIXME: Authenticate the user and set the right authenticationMethod.
|
|
#
|
|
# it should probably create a session with lassoServerDump
|
|
# and authnRequestMsg then redirect to a login page; either
|
|
# login/password or "please enter smartcard" page; then it
|
|
# would call back this method on submit.
|
|
#
|
|
# TODO: this could be refactored in core/
|
|
|
|
nodes, nodesOwner = self.getConfigNodesAndOwner("yep:useCustomAuthentication")
|
|
|
|
nsUri = nodes[0].prop("ns") or namespaces.yep.uri
|
|
elementName = nodes[0].content.strip()
|
|
klass = modules.getElementClass(nsUri, elementName)
|
|
auth = klass()
|
|
try:
|
|
userAuthenticated, authenticationMethod = auth.singleSignOn(
|
|
self.getDataHolder(), lassoServerDump, authnRequestMsg)
|
|
except faults.AbortFault:
|
|
return
|
|
else:
|
|
raise "No authentication method defined!"
|
|
else:
|
|
userAuthenticated = True
|
|
authenticationMethod = "password" # FIXME
|
|
|
|
return self.singleSignOnPart2(userAuthenticated, authenticationMethod, lassoLogin)
|
|
|
|
def singleSignOnPart2(self, userAuthenticated, authenticationMethod, lassoLogin):
|
|
if authenticationMethod == "password":
|
|
lassoAuthenticationMethod = lasso.samlAuthenticationMethodPassword
|
|
elif authenticationMethod == "certificate":
|
|
lassoAuthenticationMethod = lasso.samlAuthenticationMethodSoftwarePki
|
|
else:
|
|
logs.debug('Unknown authentication method = "%s"' % authenticationMethod)
|
|
if lassoLogin.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:
|
|
try:
|
|
lassoLogin.build_artifact_msg(
|
|
userAuthenticated, lassoAuthenticationMethod,
|
|
"2005-05-03T16:12:00Z", # FIXME: reauthenticateOnOrAfter
|
|
lasso.httpMethodRedirect)
|
|
except lasso.Error, error:
|
|
raise Exception("Lasso login error %s" % error.code)
|
|
logs.debug("msg_url = %s; assertionArtifact = %s; response_dump = %s" % (
|
|
lassoLogin.msg_url, lassoLogin.assertionArtifact,
|
|
lassoLogin.response_dump))
|
|
nameIdentifier = lassoLogin.nameIdentifier
|
|
|
|
# Save the new or updated Lasso identity into user.
|
|
user = environs.getVar("user")
|
|
if lassoLogin.is_identity_dirty():
|
|
user.setLassoIdentityDump(lassoLogin.get_identity().dump())
|
|
user.getDocument().save()
|
|
|
|
# Create a symbolic link to user, to allow to retrieve it using name identifier.
|
|
userSymlinksAbsolutePath = self.getUsersDirectoryAbsolutePath()
|
|
userSymlinkName = strings.simplify(nameIdentifier) + ".xml"
|
|
userSymlinkAbsolutePath = os.path.join(userSymlinksAbsolutePath, userSymlinkName)
|
|
try:
|
|
userPath = os.readlink(userSymlinkAbsolutePath)
|
|
except OSError, error:
|
|
if error.errno not in (errno.ENOENT, errno.EACCES):
|
|
raise
|
|
userAbsolutePath = None
|
|
else:
|
|
userAbsolutePath = os.path.join(userSymlinksAbsolutePath, userPath)
|
|
if userAbsolutePath != user.getAbsolutePath():
|
|
if not os.access(userSymlinksAbsolutePath, os.F_OK):
|
|
os.makedirs(userSymlinksAbsolutePath)
|
|
# os.access(userSymlinkAbsolutePath, os.F_OK) returns False for broken
|
|
# symlinks, so we can't use it to test existence of symlink.
|
|
try:
|
|
os.remove(userSymlinkAbsolutePath)
|
|
except OSError, error:
|
|
if error.errno != errno.ENOENT:
|
|
raise
|
|
# FIXME: Use a relative path instead of an absolute one for symbolic link.
|
|
os.symlink(user.getAbsolutePath(), userSymlinkAbsolutePath)
|
|
|
|
# Save the new or updated Lasso session into session.
|
|
session = environs.getVar("session")
|
|
if lassoLogin.is_session_dirty():
|
|
session.setLassoSessionDump(lassoLogin.get_session().dump())
|
|
session.getDocument().save()
|
|
|
|
# Create a symbolic link to session, to allow to retrieve it using name identifier.
|
|
sessionSymlinksAbsolutePath = self.getSessionsDirectoryAbsolutePath()
|
|
sessionSymlinkName = strings.simplify(nameIdentifier) + ".xml"
|
|
sessionSymlinkAbsolutePath = os.path.join(
|
|
sessionSymlinksAbsolutePath, sessionSymlinkName)
|
|
try:
|
|
sessionPath = os.readlink(sessionSymlinkAbsolutePath)
|
|
except OSError, error:
|
|
if error.errno not in (errno.ENOENT, errno.EACCES):
|
|
raise
|
|
sessionAbsolutePath = None
|
|
else:
|
|
sessionAbsolutePath = os.path.join(sessionSymlinksAbsolutePath, sessionPath)
|
|
if sessionAbsolutePath != session.getAbsolutePath():
|
|
if not os.access(sessionSymlinksAbsolutePath, os.F_OK):
|
|
os.makedirs(sessionSymlinksAbsolutePath)
|
|
# os.access(sessionSymlinkAbsolutePath, os.F_OK) returns False for broken
|
|
# symlinks, so we can't use it to test existence of symlink.
|
|
try:
|
|
os.remove(sessionSymlinkAbsolutePath)
|
|
except OSError, error:
|
|
if error.errno != errno.ENOENT:
|
|
raise
|
|
# FIXME: Use a relative path instead of an absolute one for symbolic link.
|
|
os.symlink(session.getAbsolutePath(), sessionSymlinkAbsolutePath)
|
|
|
|
# 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 = strings.simplify(lassoLogin.assertionArtifact) + ".xml")
|
|
response = responseHolder.getRootElement()
|
|
response.setArtifact(lassoLogin.assertionArtifact)
|
|
response.setResponseDump(lassoLogin.response_dump)
|
|
response.getDocument().save()
|
|
|
|
return environs.getVar("httpRequestHandler").outputRedirect(
|
|
lassoLogin.msg_url)
|
|
else:
|
|
# Send artifact using HTTP POST.
|
|
try:
|
|
lassoLogin.build_artifact_msg(
|
|
userAuthenticated, lassoAuthenticationMethod,
|
|
"2005-05-03T16:12:00Z", # FIXME: reauthenticateOnOrAfter
|
|
lasso.httpMethodPost)
|
|
except lasso.Error, error:
|
|
raise Exception("Lasso login error %s" % error.code)
|
|
logs.debug("msg_url = %s; msg_body = %s; msg_relayState = %s; "
|
|
"assertionArtifact = %s; response_dump = %s" % (
|
|
lassoLogin.msg_url, lassoLogin.msg_body, lassoLogin.msg_relayState,
|
|
lassoLogin.assertionArtifact, lassoLogin.response_dump))
|
|
raise "FIXME"
|
|
else: # lassoLogin.protocolProfile == lasso.loginProtocolProfileBrwsPost:
|
|
try:
|
|
lassoLogin.build_authn_response_msg(
|
|
userAuthenticated, lassoAuthenticationMethod,
|
|
"2005-05-03T16:12:00Z", # FIXME: reauthenticateOnOrAfter
|
|
)
|
|
except lasso.Error, error:
|
|
raise Exception("Lasso login error %s" % error.code)
|
|
logs.debug("msg_url = %s; msg_body = %s; msg_relayState = %s" % (
|
|
lassoLogin.msg_url, lassoLogin.msg_body, lassoLogin.msg_relayState))
|
|
raise "FIXME"
|
|
raise "TODO"
|
|
|
|
def soapEndpoint(self):
|
|
"""Liberty Alliance identity & service provider SOAP endpoint.
|
|
|
|
Handles HTTP POST.
|
|
"""
|
|
|
|
command = environs.getVar("httpCommand")
|
|
if command != "POST":
|
|
raise faults.PathNotFound("")
|
|
|
|
httpRequestHandler = environs.getVar("httpRequestHandler")
|
|
submission = environs.getVar("submission")
|
|
soapRequestMessage = submission.readFile()
|
|
lassoServerDump = self.getLassoServerDump()
|
|
lassoServer = lasso.Server.new_from_dump(lassoServerDump)
|
|
|
|
requestType = lasso.get_request_type_from_soap_msg(soapRequestMessage)
|
|
if requestType == lasso.requestTypeLogin:
|
|
lassoLogin = lasso.Login.new(lassoServer)
|
|
try:
|
|
lassoLogin.process_request_msg(soapRequestMessage)
|
|
except lasso.Error, error:
|
|
raise Exception("Lasso login error %s" % error.code)
|
|
|
|
# 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(strings.simplify(
|
|
lassoLogin.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?" % lassoLogin.assertionArtifact
|
|
response = responseHolder.getRootElement()
|
|
soapResponseMsg = response.getResponseDump()
|
|
responseHolder.destroy()
|
|
elif requestType == lasso.requestTypeLogout:
|
|
lassoLogout = lasso.Logout.new(lassoServer, lasso.providerTypeIdp)
|
|
try:
|
|
lassoLogout.process_request_msg(soapRequestMessage, lasso.httpMethodSoap)
|
|
except lasso.Error, error:
|
|
raise Exception("Lasso logout error %s" % error.code)
|
|
nameIdentifier = lassoLogout.nameIdentifier
|
|
|
|
# Retrieve identity using name identifier.
|
|
user = None
|
|
userSymlinksAbsolutePath = self.getUsersDirectoryAbsolutePath()
|
|
userSymlinkName = strings.simplify(nameIdentifier) + ".xml"
|
|
userSymlinkAbsolutePath = os.path.join(userSymlinksAbsolutePath, userSymlinkName)
|
|
try:
|
|
userPath = os.readlink(userSymlinkAbsolutePath)
|
|
except OSError, error:
|
|
logs.error("Unable to read symlink %s to session (errno = %s)" % (
|
|
sessionSymlinkAbsolutePath, error.errno))
|
|
raise "FIXME: Do what? Return what?"
|
|
userAbsolutePath = os.path.join(userSymlinksAbsolutePath, userPath)
|
|
try:
|
|
userHolder = dataholders.DataHolder(
|
|
pathFragment = userAbsolutePath, mimeType = "text/xml", isRootElder = True,
|
|
containedFileSystem = filesystems.PartialFileSystem(userAbsolutePath))
|
|
except IOError, error:
|
|
logs.error('Unable to read user at path "%s" (errno = %s)' % (
|
|
userAbsolutePath, error.errno))
|
|
raise "FIXME: Do what? Return what?"
|
|
# Some times, users are stored in independant files; some times they are
|
|
# embedded inside accounts.
|
|
accountOrUser = userHolder.getRootElement()
|
|
user = accountOrUser.getUser()
|
|
if user is None:
|
|
logs.error("Empty user")
|
|
raise "FIXME: Do what? Return what?"
|
|
lassoIdentityDump = user.getLassoIdentityDump()
|
|
if lassoIdentityDump is None:
|
|
logs.error("Empty identity dump in user")
|
|
raise "FIXME: Do what? Return what?"
|
|
lassoLogout.set_identity_from_dump(lassoIdentityDump)
|
|
|
|
# Retrieve session using name identifier.
|
|
session = None
|
|
sessionSymlinksAbsolutePath = self.getSessionsDirectoryAbsolutePath()
|
|
sessionSymlinkName = strings.simplify(nameIdentifier) + ".xml"
|
|
sessionSymlinkAbsolutePath = os.path.join(
|
|
sessionSymlinksAbsolutePath, sessionSymlinkName)
|
|
try:
|
|
sessionPath = os.readlink(sessionSymlinkAbsolutePath)
|
|
except OSError, error:
|
|
logs.error("Unable to read symlink %s to session (errno = %s)" % (
|
|
sessionSymlinkAbsolutePath, error.errno))
|
|
raise "FIXME: Do what? Return what?"
|
|
sessionAbsolutePath = os.path.join(sessionSymlinksAbsolutePath, sessionPath)
|
|
try:
|
|
sessionHolder = dataholders.DataHolder(
|
|
pathFragment = sessionAbsolutePath, mimeType = "text/xml", isRootElder = True,
|
|
containedFileSystem = filesystems.PartialFileSystem(sessionAbsolutePath))
|
|
except IOError, error:
|
|
logs.error('Unable to read session at path "%s" (errno = %s)' % (
|
|
sessionAbsolutePath, error.errno))
|
|
raise "FIXME: Do what? Return what?"
|
|
session = sessionHolder.getRootElement()
|
|
if session is None:
|
|
logs.error("Empty session")
|
|
raise "FIXME: Do what? Return what?"
|
|
lassoSessionDump = session.getLassoSessionDump()
|
|
if lassoSessionDump is None:
|
|
logs.error("Empty session dump")
|
|
raise "FIXME: Do what? Return what?"
|
|
lassoLogout.set_session_from_dump(lassoSessionDump)
|
|
|
|
try:
|
|
lassoLogout.validate_request()
|
|
except lasso.Error, error:
|
|
raise Exception("Lasso logout error %s" % error.code)
|
|
|
|
# Logout doesn't change identity federation => no change to user.
|
|
assert not lassoLogout.is_identity_dirty()
|
|
# Logout removes authentication assertion from session
|
|
assert lassoLogout.is_session_dirty()
|
|
lassoSession = lassoLogout.get_session()
|
|
if lassoSession is None:
|
|
user.deleteSessionToken()
|
|
user.getDocument().save()
|
|
assert environs.getVar("user") is None
|
|
#baseEnviron = environs.get(_level = "handleHttpCommand")
|
|
#baseEnviron.setVar("user", None)
|
|
session.getDocument().destroy()
|
|
assert environs.getVar("session") is None
|
|
#baseEnviron.setVar("session", None)
|
|
else:
|
|
lassoSessionDump = lassoSession.dump()
|
|
session.setLassoSessionDump(lassoSessionDump)
|
|
# Remove the symbolic link from name identifier to session.
|
|
sessionSymlinksAbsolutePath = self.getSessionsDirectoryAbsolutePath()
|
|
sessionSymlinkName = strings.simplify(nameIdentifier) + ".xml"
|
|
sessionSymlinkAbsolutePath = os.path.join(
|
|
sessionSymlinksAbsolutePath, sessionSymlinkName)
|
|
try:
|
|
os.remove(sessionSymlinkAbsolutePath)
|
|
except OSError, error:
|
|
logs.warning("Unable to remove symlink %s to session (errno = %s)" % (
|
|
sessionSymlinkAbsolutePath, error.errno))
|
|
|
|
authorization = httpRequestHandler.headers.get("authorization")
|
|
if self.getConfigBoolean("yep:useHttpAuthentication", default = False) \
|
|
and authorization:
|
|
# Since HTTP authentication provides no way to logout, we send a status
|
|
# Unauthorized to force the user to press the cancel button. But instead of
|
|
# sending an error page immediately, we send the real page, so the user will see
|
|
# the page instead of an error message.
|
|
environs.setVar("httpAuthenticationLogoutTrick", True)
|
|
# Tell each other service provider to logout the user.
|
|
otherProviderId = lassoLogout.get_next_providerID()
|
|
while otherProviderId is not None:
|
|
lassoLogout.init_request(otherProviderId)
|
|
lassoLogout.build_request_msg()
|
|
logs.debug("IDP logout: SOAP endpoint = %s; soapRequestMsg = %s" % (
|
|
lassoLogout.msg_url, lassoLogout.msg_body))
|
|
soapResponseMessage = self.callSoap(lassoLogout.msg_url, lassoLogout.msg_body)
|
|
try:
|
|
lassoLogout.process_response_msg(
|
|
soapResponseMessage, lasso.httpMethodSoap)
|
|
except lasso.Error, error:
|
|
raise Exception("Lasso logout error %s" % error.code)
|
|
otherProviderId = lassoLogout.get_next_providerID()
|
|
try:
|
|
lassoLogout.build_response_msg()
|
|
except lasso.Error, error:
|
|
raise Exception("Lasso logout error %s" % error.code)
|
|
soapResponseMsg = lassoLogout.msg_body
|
|
elif requestType == lasso.requestTypeFederationTermination:
|
|
raise "FIXME: To do."
|
|
elif requestType == lasso.requestTypeRegisterNameIdentifier:
|
|
raise "FIXME: To do."
|
|
elif requestType == lasso.requestTypeNameIdentifierMapping:
|
|
raise "FIXME: To do."
|
|
else:
|
|
raise Exception("Unknown request type = %s" % requestType)
|
|
httpRequestHandler.outputData(soapResponseMsg, mimeType = "text/xml")
|
|
|
|
|
|
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)
|