This repository has been archived on 2023-02-21. You can view files and clone it, but cannot push or open issues or pull requests.
expression/src/modules/libertyalliance.py

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("'", "&apos;"))
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)