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.
authentic-old/authentic/liberty/saml2.ptl

1394 lines
58 KiB
Plaintext

import os
import datetime
import urllib
import lasso
import common
from quixote import get_request, get_response, redirect, get_field, get_publisher
from quixote.http_request import parse_header
from quixote.directory import Directory, AccessControlled
from quixote import get_user, get_session, get_session_manager
from qommon import get_cfg, get_logger
from qommon import template
from qommon import errors
from qommon.storage import StorableObject
from qommon.tokens import Token
from qommon.publisher import sitecharset2utf8
import authentic
from authentic import identities
from authentic.misc import get_lasso_server, get_provider_key, redirect_with_return_url
from authentic.form import *
import authentic.login_token as login_token
import idwsf2
import qommon.soap as soap
import authentic.misc as misc
import qommon.misc as qmisc
authn_methods = {
'password': lasso.SAML2_AUTHN_CONTEXT_PASSWORD,
'password-on-https': lasso.SAML2_AUTHN_CONTEXT_PASSWORD_PROTECTED_TRANSPORT,
'password-and-ip': lasso.SAML2_AUTHN_CONTEXT_INTERNET_PROTOCOL_PASSWORD,
'client-certificate': lasso.SAML2_AUTHN_CONTEXT_TLS_CLIENT,
}
def get_saml2_authn_method(session):
authn_method = 'password'
if session and session.authentication_method:
authn_method = session.authentication_method
return authn_methods.get(authn_method)
class Saml2Artifact(StorableObject):
_names = 'saml2_artifacts'
artifact = None
session_id = None
provider_id = None
message = None
def get_by_artifact(cls, artifact):
return cls.get(artifact)
get_by_artifact = classmethod(get_by_artifact)
class SpDir(Directory):
def _q_lookup(self, component):
return SpUI(component)
def error_then_home(error):
return_url = get_field('returnURL')
if return_url:
c_to = (return_url, _('Continue'))
else:
c_to = (get_publisher().get_root_url(), _('Home'))
return template.error_page(error, continue_to = c_to)
def get_identity():
authentic.identities.get_store().load_identities()
session = get_session()
if not session:
return None
user = session.user
try:
return authentic.identities.get_store().get_identity(user)
except:
return None
def save_session(profile, session = None):
if not session:
session = get_session()
if profile.session:
session.lasso_session_dump = profile.session.dump()
else:
session.lasso_session_dump = None
def load_session(profile, session):
if not session:
session = get_session()
if session and session.lasso_session_dump:
profile.setSessionFromDump(session.lasso_session_dump)
def load_identity(profile, identity = None, session = None):
if not identity:
identity = get_identity()
if not identity:
return
if identity and identity.lasso_dump:
profile.setIdentityFromDump(identity.lasso_dump)
def save_identity(profile, identity = None):
if not profile.isIdentityDirty:
return
if not identity:
identity = get_identity()
if not identity:
return
if profile.identity:
identity.lasso_dump = profile.identity.dump()
else:
identity.lasso_dump = None
identity.name_identifiers = []
authentic.identities.get_store().save(identity)
class RootDirectory(common.LassoDirectory):
_q_exports = ['singleSignOn', 'failSingleSignOn', 'singleSignOnSOAP', 'artifact', 'consent',
'singleLogoutSOAP', 'singleLogoutPOST', 'singleLogoutFinish',
'singleLogout', 'singleLogoutReturn',
'manageNameIdSOAP', 'manageNameId', 'manageNameIdReturn',
'metadata', ('metadata.xml', 'metadata'), 'public_key',
'sp', 'intro', 'disco',
'proxySingleSignOnArtifact', 'proxySingleSignOnPost',
'proxySingleSignOnRedirect','proxySingleLogoutSOAP','proxySingleLogout','proxySingleLogoutReturn',
'proxyAssertionConsumerArtifact', 'proxyAssertionConsumerPost',
'proxyAssertionConsumerRedirect', 'continueSSO', 'failSSO']
sp = SpDir()
def _q_index(self):
raise errors.AccessError()
def check_on(self):
return redirect(get_publisher().get_root_url() + 'images/check_on.png')
def check_off(self):
return redirect(get_publisher().get_root_url() + 'images/check_off.png')
def postRequestTo(self, url, body, relayState='', title=''):
return self.postTo(url, body, "SAMLRequest", relay_state=relay_state,
title=title)
def postResponseTo(self, url, body, relay_state='', title=''):
return self.postTo(url, body, "SAMLResponse", relay_state=relay_state,
title=title)
def singleSignOn(self):
'''Handle an AuthnRequest, if not logged and
interaction is authorized, ask for login'''
return self.single_sign_on()
def get_query(self):
'''Retrieve the query from the given login token'''
id = get_field('id')
if not id:
return None
token = login_token.LoginToken.get(id, ignore_errors=True)
if token:
return getattr(token, 'query', None)
return None
def invoke_login(self, login, query):
request_id = login.request.iD
login_url = get_request().environ['SCRIPT_NAME'] + '/login'
token = login_token.LoginToken(request_id)
token.query = query
token.store()
request_id = urllib.quote(request_id)
return redirect_with_return_url(login_url,
(('okURL', '/saml/continueSSO?id=%s' % request_id),
('cancelURL', '/saml/failSSO?id=%s' % request_id),
('LoginToken', request_id),
('service',qmisc.get_provider_key(login.remoteProviderId))))
def continueSSO(self):
query = self.get_query()
return self.single_sign_on(False, query)
def failSSO(self):
query = self.get_query()
return self.single_sign_on(True, query)
def single_sign_on(self, request_denied = False, query = None):
server = get_lasso_server(protocol = 'saml2')
if not server:
return template.error_page(_('SAML 2.0 support not yet configured.'))
login = lasso.Login(server)
request = get_request()
if query is None:
if request.get_method() == 'GET':
query = request.get_query()
elif request.get_method() == 'POST':
query = request.form.get('SAMLRequest')
else:
return template.error_page(_('Invalid request message'))
try:
login.processAuthnRequestMsg(query)
except lasso.DsError, e:
return template.error_page('Cryptographic error: %s' % lasso.strError(e.code))
except lasso.Error, error:
self.logProfileError(login, error, 'singleSignOn login.processAuthnRequestMsg')
get_logger().debug('singleSignOn query: %r' % query)
if error[0] == lasso.SERVER_ERROR_PROVIDER_NOT_FOUND or \
error[0] == lasso.PROFILE_ERROR_UNKNOWN_PROVIDER:
return template.error_page(_('Authentication request initiated by an unaffiliated provider.'))
elif error[0] == lasso.PROFILE_ERROR_INVALID_MSG or \
error[0] == lasso.PROFILE_ERROR_MISSING_REMOTE_PROVIDERID:
return template.error_page(_('Invalid request message'))
else:
return error_then_home(_('Internal Server Error'))
# Loggin informations on the request
issuer = login.remoteProviderId
authn_request = login.request
nameIDPolicy = login.request.nameIdPolicy
if nameIDPolicy:
if nameIDPolicy.allowCreate:
allow_create = 'allowing'
else:
allow_create = 'disallowing'
policy = 'with format "%(format)s", SP name qualifier "%(name_qualifier)s" and %(allow_create)s creation' % {
'format': nameIDPolicy.format or '',
'name_qualifier': nameIDPolicy.spNameQualifier or '',
'allow_create': allow_create }
else:
policy = 'with default name id policy'
assertion_consumer_flags = []
if authn_request.protocolBinding:
assertion_consumer_flags.append('with binding "%s"' % \
login.request.protocolBinding)
if authn_request.assertionConsumerServiceUrl:
assertion_consumer_flags.append('with URL "%s"' % \
authn_request.assertionConsumerServiceUrl)
if authn_request.assertionConsumerServiceIndex != -1:
assertion_consumer_flags.append('with idx "%s"' %
authn_request.assertionConsumerServiceIndex)
default = 'default '
if assertion_consumer_flags:
default = ''
assertion_consumer_flags = ','.join(assertion_consumer_flags)
request_flags = []
if login.request.forceAuthn:
request_flags.append('forcing authentication')
if login.request.isPassive:
request_flags.append('without interaction')
get_logger().info('Received AuthnRequest from %(remote_provider)s \
%(policy)s, %(request_flags)s targetting %(default)sassertion consumer service\
%(assertion_consumer_flags)s'
% { 'remote_provider' : issuer,
'policy': policy,
'default': default,
'assertion_consumer_flags': assertion_consumer_flags,
'protocol_binding': login.request.protocolBinding,
'request_flags': ', '.join(request_flags)})
session = get_session()
session.saml2 = True
request_id = login.request.iD
if session and session.lasso_session_dump:
login.setSessionFromDump(session.lasso_session_dump)
user_authenticated = False
if not request_denied:
if session and session.user:
user_authenticated = True
auth_ok = login_token.LoginToken.has_good_authentication(request_id)
if authn_request.forceAuthn and not auth_ok:
user_authenticated = False
if not user_authenticated and not authn_request.isPassive:
return self.invoke_login(login, query)
return self.sso_after_authentication(login, user_authenticated)
def logLoginValidateRequestError(self, login, error):
self.logProfileError(login, error, 'login.validateRequestMsg')
def logLoginProcessResponseMsgError(self, login, error):
self.logProfileError(login, error, 'login.processResponseMsg')
def logProfileError(self, login, error, what):
if isinstance(error, lasso.Error):
message = error[1]
else:
message = str(error)
get_logger().warn('%(what)s from %(provider)s failed: %(message)s' %
{ 'provider': login.remoteProviderId, 'message': message, \
'what': what })
def check_basic_authentication(self, request):
authorization=request.get_header('Authorization')
if authorization and authorization.startswith('Basic '):
account = identities.PasswordAccount()
account.login, account.password = \
base64.b64decode.authorization[6:].split(':', 1)
return identities.get_store().get_identity_for_account(account)
return None
@common.soap_endpoint
def singleSignOnSOAP(self):
server = get_lasso_server(protocol = 'saml2')
if not server:
return template.error_page(_('SAML 2.0 support not yet configured.'))
login = lasso.Login(server)
request = get_request()
soap_request_msg = self.get_soap_message()
try:
login.processAuthnRequestMsg(soap_request_msg)
except lasso.Error, error:
self.logProfileError(logint, error, 'singleSignOnSOAP login.processAuthnRequestMsg')
if error[0] == lasso.PROFILE_ERROR_MISSING_REMOTE_PROVIDERID:
return template.error_page(_('Request initiated by an unaffiliated provider.'))
if error[0] == lasso.DS_ERROR_INVALID_SIGNATURE:
return template.error_page(_('Failed to check authentication request signature.'))
if error[0] == lasso.DS_ERROR_SIGNATURE_NOT_FOUND:
return template.error_page(_('Authentication request is not signed.'))
return error_then_home(_('Internal Server Error'))
user_authenticated = False
user = self.check_basic_authentication(request)
if user:
user_authenticated = True
# Consent must be obtained from service provider because
# identity provider is unable to interact with user
consent_obtained = False
if user_authenticated:
if not login.mustAskForConsent():
consent_obtained = True
try:
login.validateRequestMsg(user_authenticated, consent_obtained)
except lasso.Error, error:
self.logLoginValidateRequestError(login, error)
return self.finishSingleSignOnSOAP(login)
else:
now = datetime.datetime.utcnow()
notBefore = now-datetime.timedelta(0,60)
notOnOrAfter = now+datetime.timedelta(0,60)
login.buildAssertion(get_saml2_authn_method(session),
now.isoformat()+'Z',
'unused', # reauthenticateOnOrAfter is only for ID-FF 1.2
notBefore.isoformat()+'Z',
notOnOrAfter.isoformat()+'Z')
login.assertion.authnStatement[0].sessionIndex = login.assertion.id
common.SessionIndex(login.assertion.id).store()
save_identity(login, user)
if login.protocolProfile == lasso.LOGIN_PROTOCOL_PROFILE_BRWS_LECP:
login.buildResponseMsg(None)
else:
return template.error_page(_('Unknown SAML2 protocol binding'))
response = get_response()
response.set_content_type('application/vnd.paos+xml', 'UTF-8')
response.set_header('PAOS', 'ver=urn:liberty:paos:2003-08;urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp')
return login.msgBody
@common.soap_endpoint
def finishSingleSignOnSOAP(self, login):
login.buildResponseMsg(None)
response = get_response()
response.set_content_type('application/vnd.paos+xml', 'UTF-8')
response.set_header('PAOS', 'ver=urn:liberty:paos:2003-08;urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp')
return login.msgBody
def check_access_authorizations(cls, login):
return True
check_access_authorizations = classmethod(check_access_authorizations)
def sso_after_authentication(self, login, user_authenticated, proxied = False):
# part 2 of SSO: user has been authenticated (or not)
session, identity = self.restore_user_details(login)
if user_authenticated:
if not self.check_access_authorizations(login):
# XXX: notify the user here and now?
get_logger().warn('user refused because of authorizations')
user_authenticated = False
consent_obtained = False
if user_authenticated:
if login.mustAskForConsent():
session.lasso_login_dump = login.dump()
return redirect(get_request().environ['SCRIPT_NAME'] + '/saml/consent')
return self.sso_after_consent(login, user_authenticated, True)
def restore_user_details(self, login):
authentic.identities.get_store().load_identities()
session = get_session()
try:
identity = authentic.identities.get_store().get_identity(session.user)
except KeyError:
identity = None
if identity and identity.lasso_dump:
login.setIdentityFromDump(identity.lasso_dump)
if session and session.lasso_session_dump:
login.setSessionFromDump(session.lasso_session_dump)
return (session, identity)
def disco_service_url(self):
request = get_request()
url = '%s://%s/disco/soapEndpoint' % (request.get_scheme(), request.get_server())
return url
def saml2_add_attribute_values(self, assertion, name, values):
if not assertion.attributeStatement:
assertion.attributeStatement = [ lasso.Saml2AttributeStatement() ]
attribute_statement = assertion.attributeStatement[0]
for attribute in attribute_statement.attribute:
if attribute.name == name and attribute.nameFormat == lasso.SAML2_ATTRIBUTE_NAME_FORMAT_BASIC:
break
else:
attribute = lasso.Saml2Attribute()
attribute.name = name
attribute.nameFormat = lasso.SAML2_ATTRIBUTE_NAME_FORMAT_BASIC
attribute_statement.attribute = list(attribute_statement.attribute) + [ attribute ]
attribute_value_list = list(attribute.attributeValue)
for value in values:
if value is True:
value = 'true'
elif value is False:
value = 'false'
else:
value = str(value)
if type(value) is unicode:
value = value.encode('utf-8')
else:
value = sitecharset2utf8(value)
text_node = lasso.MiscTextNode.newWithString(value)
text_node.textChild = True
attribute_value = lasso.Saml2AttributeValue()
attribute_value.any = [ text_node ]
attribute_value_list.append(attribute_value)
attribute.attributeValue = attribute_value_list
def build_assertion(self, login, session, identity):
now = datetime.datetime.utcnow()
notBefore = now-datetime.timedelta(0,60)
notOnOrAfter = now+datetime.timedelta(0,60)
login.buildAssertion(get_saml2_authn_method(session),
session.authentication_instant.isoformat()+'Z',
'unused', # reauthenticateOnOrAfter is only for ID-FF 1.2
notBefore.isoformat()+'Z',
notOnOrAfter.isoformat()+'Z')
if lasso.WSF_ENABLED:
idwsf2.addDiscoveryBootStrapToLogin(login,
self.disco_service_url(),
login.server.providerId, (lasso.SECURITY_MECH_BEARER,))
login.assertion.authnStatement[0].sessionIndex = login.assertion.id
common.SessionIndex(login.assertion.id).store()
name_id_format = None
if login.request.nameIDPolicy:
name_id_format = login.request.nameIDPolicy.format
if name_id_format == lasso.SAML2_NAME_IDENTIFIER_FORMAT_EMAIL:
if not identity.email:
return template.error_page(_('The service asked for NameIDFormat email, \
but the current does not have one.'))
login.response.assertion[0].subject.nameID.content = identity.email
elif name_id_format == lasso.SAML2_NAME_IDENTIFIER_FORMAT_UNSPECIFIED:
if not session.name_identifiers or not session.name_identifiers[-1]:
# Final IdP : must create the nameId if it doesn't exist
# XXX: This is mainly for testing. If it should be use in real world, the NameID
# generation should be configurable somehow
nameID = 'test'
else:
# Proxying IdP : a name identifier is already set in the session, copy it to the
# new assertion
nameID = session.name_identifiers[-1]
login.nameIdentifier.content = nameID
login.response.assertion[0].subject.nameID.content = nameID
provider_key = misc.get_provider_key(login.remoteProviderId)
self.add_attributes(provider_key, login.response.assertion[0],
identity, self.saml2_add_attribute_values)
def set_default_name_id(self, login):
'''If no NameID format is asked by the service provider or if it asked
for the format 'unspecified', use the default from the settings.
'''
try:
lp = get_cfg('providers')[misc.get_provider_key(
login.remoteProviderId)]
except KeyError:
raise errors.TraversalError()
if not login.request.nameIDPolicy:
login.request.nameIDPolicy = lasso.Samlp2NameIDPolicy()
if not login.request.nameIDPolicy.format or \
login.request.nameIDPolicy.format == \
lasso.SAML2_NAME_IDENTIFIER_FORMAT_UNSPECIFIED:
nid_format = lp.get('default_name_id_format', 'persistent')
if nid_format == 'persistent':
login.request.nameIDPolicy.format = lasso.SAML2_NAME_IDENTIFIER_FORMAT_PERSISTENT
elif nid_format == 'transient':
login.request.nameIDPolicy.format = lasso.SAML2_NAME_IDENTIFIER_FORMAT_TRANSIENT
elif nid_format == 'encrypted':
login.request.nameIDPolicy.format = lasso.SAML2_NAME_IDENTIFIER_FORMAT_ENCRYPTED
elif nid_format == 'email':
login.request.nameIDPolicy.format = lasso.SAML2_NAME_IDENTIFIER_FORMAT_EMAIL
elif nid_format == 'none':
login.request.nameIDPolicy.format = lasso.SAML2_NAME_IDENTIFIER_FORMAT_UNSPECIFIED
def sso_after_consent(self, login, user_authenticated, consent_obtained, intro_cookie = True):
# part 3 of SSO: if necessary, user has given its consent
session, identity = self.restore_user_details(login)
get_logger().debug('sso_after_consent user_authenticated:%s \
consent_obtained: %s intro_cookie: %s' % (user_authenticated, consent_obtained,
intro_cookie))
self.set_default_name_id(login)
try:
login.validateRequestMsg(user_authenticated, consent_obtained)
except lasso.Error, error:
self.logLoginValidateRequestError(login, error)
do_federation = False
else:
do_federation = True
self.build_assertion(login, session, identity)
if login.protocolProfile == lasso.LOGIN_PROTOCOL_PROFILE_BRWS_ART:
login.buildArtifactMsg(lasso.HTTP_METHOD_ARTIFACT_GET)
elif login.protocolProfile in (lasso.LOGIN_PROTOCOL_PROFILE_BRWS_POST,
lasso.LOGIN_PROTOCOL_PROFILE_REDIRECT):
login.buildAuthnResponseMsg()
elif login.protocolProfile == lasso.LOGIN_PROTOCOL_PROFILE_BRWS_LECP:
login.buildResponseMsg(None)
else:
return template.error_page(_('Unknown SAML2 protocol binding'))
if do_federation and identity:
if login.isIdentityDirty:
identity.lasso_dump = login.identity.dump()
authentic.identities.get_store().save(identity)
if login.nameIdentifier:
# not there if not authenticated or consent not granted
if not session.name_identifiers:
session.name_identifiers = []
if not login.nameIdentifier.content in session.name_identifiers:
session.name_identifiers.append(login.nameIdentifier.content)
if login.isSessionDirty:
save_session(login)
get_session_manager().maintain_session(session)
common_domain = get_cfg('idp').get('common_domain')
common_domain_setter_url = get_cfg('idp').get('common_domain_setter_url')
if common_domain_setter_url and common_domain and intro_cookie and False:
token = Token(expiration_delay = 600) # ten minutes
token.session_id = session.id
token.protocol = 'saml2'
token.provider_id = login.server.providerId
token.common_domain = common_domain
token.next_url = get_request().get_url(1) + '/intro'
token.store()
session.lasso_login_dump = login.dump()
return redirect(common_domain_setter_url + '?tok=%s' % token.id)
return self.sso_after_intro(login, session)
def sso_after_intro(self, login, session):
if login.protocolProfile == lasso.LOGIN_PROTOCOL_PROFILE_BRWS_ART:
artifact = Saml2Artifact(id = login.artifact)
artifact.artifact = login.artifact
artifact.message = login.artifactMessage
artifact.session_id = session.id
artifact.provider_id = login.remoteProviderId
artifact.store()
return redirect(login.msgUrl)
elif login.protocolProfile == lasso.LOGIN_PROTOCOL_PROFILE_BRWS_LECP:
response = get_response()
response.set_content_type('text/xml')
return login.msgBody
else:
if login.msgBody:
return self.postResponseTo(login.msgUrl, login.msgBody,
relay_state = login.msgRelayState,
title = N_('Authentication Response'))
else:
return redirect(login.msgUrl)
def intro(self):
session = get_session()
login = lasso.Login.newFromDump(
get_lasso_server(protocol = 'saml2'), session.lasso_login_dump)
return self.sso_after_intro(login, get_session())
def consent [html] (self):
form = Form(enctype="multipart/form-data", id = "login")
form.add_submit("consent", _("Consent to Federation"))
form.add_submit("refuse", _("Refuse Federation"))
if not form.is_submitted() or form.has_errors():
template.html_top()
"""<div id="login-top"><h1>%s</h1></div>
<div id="login-form">""" % _('Login')
# XXX: get site name from provider/metadata/organization to have
# an appropriate question.
"<p>%s</p>" % _('Do you consent to federate your account with the other site?')
form.render()
'</div>'
else:
if form.get_widget('consent').parse():
consent = True
else:
consent = False
session = get_session()
login = lasso.Login.newFromDump(
get_lasso_server(protocol = 'saml2'), session.lasso_login_dump)
session.lasso_login_dump = None
return self.sso_after_consent(login, True, consent)
@common.soap_endpoint
def artifact(self):
#try:
soap_message = self.get_soap_message()
#except:
# return
response = get_response()
response.set_content_type('text/xml')
authentic.identities.get_store().load_identities()
login = lasso.Login(get_lasso_server(protocol = 'saml2'))
try:
login.processRequestMsg(soap_message)
except Exception, e:
get_logger().error('artifact resolve error %s' % e)
raise # leverage common.soap_endpoint
try:
artifact = Saml2Artifact.get_by_artifact(login.artifact)
except KeyError:
login.buildResponseMsg(None)
return login.msgBody
login.artifactMessage = artifact.message
artifact.remove_self()
session = get_session_manager().get(artifact.session_id)
if session and session.lasso_session_dump:
login.setSessionFromDump(session.lasso_session_dump)
login.buildResponseMsg(None)
if session and login.isSessionDirty:
save_session(login)
get_session_manager().commit_changes(session)
return login.msgBody
def setup_logout_for_validation(self):
pass
def check_logout_request(self, logout_profile):
'''Make any needed check on the logout request,
must return True if the request if accepted, False otherwise
'''
return True
# SLO support
def singleLogout(self):
'Single Logout initiated by service provider with Redirect binding'
return self.single_logout_sp(get_request().get_query(),
lasso.HTTP_METHOD_REDIRECT)
@common.soap_endpoint
def singleLogoutSOAP(self):
'Single Logout initiated by service provider with SOAP binding'
try:
soap_message = self.get_soap_message()
except:
return
return self.single_logout_sp(soap_message, lasso.HTTP_METHOD_SOAP)
def singleLogoutPOST(self):
'Single Logout initiated by service provider with POST binding'
return self.single_logout_sp(get_field('SAMLRequest'),
lasso.HTTP_METHOD_POST, relay_state = get_field('RelayState'))
def singleLogoutFinish(self):
'Single Logout Finish synchronous sequence'
session = get_session()
logout = None
try:
logout = lasso.Logout.newFromDump(
get_lasso_server(protocol = 'saml2'),
session.lasso_logout_dump)
return self.slo_idp_finish(logout, session, method=lasso.HTTP_METHOD_REDIRECT)
except:
pass
# Catch All case
if get_session():
get_session_manager().expire_session()
return redirect(session.after_url or get_publisher().get_root_url())
def single_logout_sp(self, message, method, relay_state = None):
assert(method is not None)
logout = lasso.Logout(get_lasso_server(protocol = 'saml2'))
try:
logout.processRequestMsg(message)
except lasso.Error, error:
return self.slo_sp_finish(logout, method, error = error)
if relay_state:
logout.relayState = relay_state
get_logger().info('received SLO request from %s' % logout.remoteProviderId)
get_session().service = get_provider_key(logout.remoteProviderId)
# Look for a session index
try:
session_index = common.SessionIndex.get(logout.request.sessionIndex)
name_identifier = logout.nameIdentifier.content
identity = authentic.identities.get_store().get_identity_for_name_identifier(name_identifier)
session = get_session_manager().get(session_index.session_id)
if get_session() and get_session().id == session.id:
session = get_session()
load_session(logout, session)
except Exception, error:
# No session index: request is refused
return self.slo_sp_finish(logout, method, error = error)
# Tag current session as in_slo
session.in_slo_sp = True
# identity loading is useless since Lasso >= 2.2.90
load_identity(logout, identity = None, session = session)
self.setup_logout_for_validation()
try:
logout.validateRequest()
except lasso.Error, error:
return self.slo_sp_finish(logout, method, error = error)
if not self.check_logout_request(logout):
return self.slo_sp_finish(logout, method, error = error)
# logout user now!
session.user = None
if method == lasso.HTTP_METHOD_SOAP:
get_session_manager().expire_session()
session.remove_self()
# FIXME: support redirect to proxy in slo_idp
if session.proxied_idp and identity:
self.proxy_slo_soap(session, identity)
if method != lasso.HTTP_METHOD_SOAP:
save_session(logout)
# Eventually start a provider logout
return self.slo_idp(session, method = method, logout = logout)
def slo_sp_finish(self, logout, method, error = None):
assert(logout is not None)
if error:
self.logProfileError(logout, error, 'SLO Request')
else:
if method != lasso.HTTP_METHOD_SOAP:
get_session_manager().expire_session()
if not method:
method = logout.server.getFirstHttpMethod(
logout.server.getProvider(logout.remoteProviderId),
lasso.MD_PROTOCOL_TYPE_SINGLE_LOGOUT)
logout.http_request_method = method
# FIXME: use method if supported or find first supported HTTP
# method
# Handle partial logout
if getattr(get_session(), 'partial_logout', False):
# FIXME: set status code to PartialLogout
pass
try:
logout.buildResponseMsg()
except lasso.Error, error:
# Log and display an error
self.logProfileError(logout, error, 'slo_sp_finish')
if method == lasso.HTTP_METHOD_SOAP:
# FIXME: return SOAP Fault
pass
return error_then_home("%s: %s" % (_('Internal Error'),
_('Logout cannot be finished')))
# FIXME: if we support other binding than SOAP or Redirect, this must
# be extended
if method == lasso.HTTP_METHOD_SOAP:
response = get_response()
response.set_content_type('text/xml')
return logout.msgBody
else:
if method == lasso.HTTP_METHOD_POST:
return self.postResponseTo(logout.msgBody,
logout.msgUrl, relay_state = logout.relayState,
title = _('Logout Response'))
elif method == lasso.HTTP_METHOD_REDIRECT:
return redirect(logout.msgUrl)
def slo_idp(self, session = None, method = None, logout = None):
# Single Logout initiated by IdP
if not session:
session = get_session()
if not session:
raise errors.AccessError()
# shortcut
if not session.lasso_session_dump:
return self.slo_idp_finish(logout, session, method)
if not logout:
logout = lasso.Logout(get_lasso_server(protocol = 'saml2'))
load_session(logout, session)
# delog user
# Identity loading is useless since lasso >= 2.2.90
load_identity(logout, identity = None, session = session)
if logout.identity:
session.lasso_identity_dump = logout.identity.dump()
session.user = None
logout.resetProviderIdIndex()
if method == lasso.HTTP_METHOD_SOAP:
return self.slo_idp_soap(logout, session)
else:
return self.slo_idp_multi_binding(logout, session)
def slo_idp_multi_binding [html] (self, logout, session, url = None):
remote_provider_id = logout.getNextProviderId()
# Single Logout initiated by IdP; GET profile
if not remote_provider_id:
# Early exit
return self.slo_idp_finish(logout, session)
session.store()
url = get_publisher().get_root_url() + 'saml/singleLogoutFinish'
template.html_top(onload = "setTimeout(function () { window.location = '%s' }, 1000)" % url)
'<div id="logout-top"><h1>%s</h1></div>' % _('Logout')
"""<div id="logout-sps">
<p>%s</p>
<ul>""" % _('Logging out from service providers:')
while remote_provider_id is not None:
method = logout.server.getFirstHttpMethod(
logout.server.getProvider(remote_provider_id),
lasso.MD_PROTOCOL_TYPE_SINGLE_LOGOUT)
'<li>%s... ' % remote_provider_id
if method == lasso.HTTP_METHOD_REDIRECT:
try:
# without t=, the result would be printed in the HTML page
t = logout.initRequest(remote_provider_id, lasso.HTTP_METHOD_REDIRECT)
except lasso.Error, error:
# Cannot happen with lasso > 2.2.90
if error[0] == lasso.PROFILE_ERROR_IDENTITY_NOT_FOUND:
break # this is bad, abort.
# Cannot happen with lasso > 2.2.90
elif error[0] == lasso.PROFILE_ERROR_FEDERATION_NOT_FOUND:
message = _('missing federation')
elif error[0] == lasso.PROFILE_ERROR_UNSUPPORTED_PROFILE:
message = _('logout not supported')
else:
message = _('internal error')
get_logger().error('SLO/GET: logout.initRequest failed for %s: %s' % (remote_provider_id, error[1]))
'%s' % message
else:
try:
t = logout.buildRequestMsg()
except lasso.Error, error:
'%s' % _('internal error')
get_logger().error('SLO/GET: logout.initRequest failed for %s: %s' % (remote_provider_id, error[1]))
else:
# TODO: use organization name instead of provider_id
'''<iframe src="%(url)s" alt="" marginwidth="0" marginheight="0" scrolling="no" style="border: none" width="12" height="12">
<img src="%(url)s" width="12" height="12"></img>
</iframe>''' % { 'url': logout.msgUrl }
elif method == lasso.HTTP_METHOD_SOAP:
if self.slo_soap_one_provider(logout, remote_provider_id):
'<img src="%s" alt="OK"/>' % (get_publisher().get_root_url() + 'images/check_on.png')
else:
'<img src="%s" alt="KO"/>' % (get_publisher().get_root_url() + 'images/check_off.png')
else:
get_logger().error('SLO: unsupported logout profile for %s: %s' % (remote_provider_id, method))
'Cannot logout unsupported profile'
'</li>'
remote_provider_id = logout.getNextProviderId()
# FIXME: send proxy logout
'</ul></div>'
'<p><a href="%s">%s</a></p>' % (url, _('Finish logging out'))
session.lasso_logout_dump = logout.dump()
save_session(logout, session)
def slo_soap_one_provider(self, logout, remote_provider_id):
'''Could raise lasso.ProfileIdentityNotFoundError with lasso < 2.2.90'''
assert(remote_provider_id is not None)
try:
logout.initRequest(remote_provider_id, lasso.HTTP_METHOD_SOAP)
logout.buildRequestMsg()
soap_answer = self.soap_call(logout.msgUrl, logout.msgBody)
logout.processResponseMsg(soap_answer)
except soap.SOAPException, exception:
self.logProfileError(logout, exception, 'slo_idp_soap: HTTP transport failed')
except Exception, error:
self.logProfileError(logout, error, 'slo_idp_soap')
else:
return True
return False
def slo_idp_soap(self, logout, session):
'''SLO IDP Special case for SOAP only'''
remote_provider_id = logout.getNextProviderId()
if not remote_provider_id:
# FIXME: send proxy soap logout
return self.slo_idp_finish(logout, session, method = lasso.HTTP_METHOD_SOAP)
self.slo_soap_one_provider(logout, remote_provider_id)
return self.slo_idp_soap(logout, session)
def slo_idp_finish(self, logout, session, method = None):
'''End of the SLO IdP sequence'''
if getattr(session, 'in_slo_sp', False):
session.in_slo_sp = False
return self.slo_sp_finish(logout, method)
else:
get_session_manager().expire_session()
return misc.redirect_to_after_url() or misc.redirect_home()
def singleLogoutReturn(self):
session = get_session()
ok = True
# return must not need a logged user
if not session:
raise errors.AccessError()
logout = lasso.Logout(get_lasso_server(protocol = 'saml2'))
load_session(logout, session)
if getattr(session, "lasso_identity_dump", None):
# FIXME: in later version of lasso this should be useless
logout.setIdentityFromDump(session.lasso_identity_dump)
else:
load_identity(logout, identity = None, session = session)
request = get_request()
try:
logout.processResponseMsg(request.get_query())
except lasso.Error, error:
self.logProfileError(logout, error, 'singleLogoutReturn')
ok = False
if ok:
get_request().session = None
return self.check_on()
else:
session.partial_logout = True
session.store()
return self.check_off()
# Defederation
def manageNameId(self):
request = get_request()
if not lasso.isSamlQuery(request.get_query()):
return redirect('.')
manage = lasso.NameIdManagement(get_lasso_server(protocol = 'saml2'))
manage.processRequestMsg(request.get_query())
session = get_session()
authentic.identities.get_store().load_identities()
try:
identity = authentic.identities.get_store().get_identity(session.user)
except KeyError:
identity = None
self.manage_name_id(manage, identity)
return redirect(manage.msgUrl)
def manageNameIdReturn(self):
# TODO: Implement this function using Lasso
return redirect(get_publisher().get_root_url())
@common.soap_endpoint
def manageNameIdSOAP(self):
soap_message = self.get_soap_message()
response = get_response()
response.set_content_type('text/xml')
request_type = lasso.getRequestTypeFromSoapMsg(soap_message)
if request_type != lasso.REQUEST_TYPE_NAME_ID_MANAGEMENT:
get_logger().warn('SOAP message on name id management url of not appropriate type')
return
manage = lasso.NameIdManagement(get_lasso_server(protocol = 'saml2'))
manage.processRequestMsg(soap_message)
name_identifier = manage.nameIdentifier.content
identity = authentic.identities.get_store().get_identity_for_name_identifier(name_identifier)
self.manage_name_id(manage, identity)
return manage.msgBody
def manage_name_id(self, manage, identity):
load_identity(manage, identity = identity)
try:
manage.validateRequest()
except lasso.Error, error:
self.logProfileError(manage, error, 'manage.validateRequest')
else:
save_identity(manage, identity = identity)
manage.buildResponseMsg()
def metadata(self):
get_publisher().reload_cfg()
response = get_response()
response.set_content_type('text/xml', 'utf-8')
metadata = unicode(open(authentic.misc.get_abs_path(
get_cfg('idp')['saml2_metadata'])).read(), 'utf-8')
return metadata
def public_key(self):
get_publisher().reload_cfg()
response = get_response()
response.set_content_type('application/octet-stream')
publickey = file(authentic.misc.get_abs_path(get_cfg('idp')['publickey'])).read()
return publickey
def proxy_login_fill_authn_request(self, login, nameIdPolicy = None):
session = get_session()
login.request.nameIDPolicy.format = lasso.SAML2_NAME_IDENTIFIER_FORMAT_PERSISTENT
login.request.nameIDPolicy.allowCreate = True
login.request.forceAuthn = False
login.request.isPassive = False
login.request.consent = 'urn:oasis:names:tc:SAML:2.0:consent:current-implicit'
if session.lasso_login_dump:
login_proxy = lasso.Login.newFromDump(login.server, session.lasso_login_dump)
if (hasattr(lasso, 'SAML2_NAME_IDENTIFIER_FORMAT_EMAIL') and \
login_proxy.request.nameIDPolicy.format == lasso.SAML2_NAME_IDENTIFIER_FORMAT_EMAIL) or \
(hasattr(lasso, 'SAML2_NAME_IDENTIFIER_FORMAT_UNSPECIFIED') and \
login_proxy.request.nameIDPolicy.format == lasso.SAML2_NAME_IDENTIFIER_FORMAT_UNSPECIFIED):
login.request.nameIDPolicy.format = login_proxy.request.nameIDPolicy.format
login.request.protocolBinding = login_proxy.request.protocolBinding
# XXX: replicate other parameters from the request we proxy
if nameIdPolicy: # forced value
login.request.nameIDPolicy.format = lasso.SAML2_NAME_IDENTIFIER_FORMAT_PERSISTENT
def perform_proxy_login(self, idp = None, nameIdPolicy = None, extensions = None, relay_state = None):
server = get_lasso_server(lasso.PROVIDER_ROLE_SP, protocol = 'saml2')
login = lasso.Login(server)
login.initAuthnRequest(idp, lasso.HTTP_METHOD_REDIRECT)
self.proxy_login_fill_authn_request(login, nameIdPolicy)
if relay_state is not None:
get_session().remember(login.request.requestId, relay_state)
try:
try:
login.buildAuthnRequestMsg()
except lasso.Error, error:
self.logProfileError(login, error, 'perform_proxy_login HTTP-Redirect login.buildAuthnRequestMsg')
login.initAuthnRequest(idp, lasso.HTTP_METHOD_POST)
self.proxy_login_fill_authn_request(login, nameIdPolicy)
login.buildAuthnRequestMsg()
except lasso.Error, error:
self.logProfileError(login, error, 'perform_proxy_login HTTP-POST login.buildAuthnRequestMsg')
return error_then_home("%s: %s" % (_('Internal Error'), _('Proxy Login failed')))
if login.msgBody:
return self.postRequestTo(login.msgUrl, login.msgBody,
relay_state = login.relayState, title = N_('Authentication Request'))
else:
return redirect(login.msgUrl)
def proxy_auth_ok(self, login):
pass # to override
def proxy_auth_peer_cancelled(self, login):
pass
def proxy_auth_federation_not_found(self, login):
pass
def proxyAssertionConsumerArtifact(self):
server = get_lasso_server(lasso.PROVIDER_ROLE_SP, protocol = 'saml2')
if not server:
return template.error_page(_('SAML 2.0 support not yet configured.'))
login = lasso.Login(server)
request = get_request()
session = get_session()
if request.get_method() == 'GET':
artifact = request.get_query()
response_method = lasso.HTTP_METHOD_ARTIFACT_GET
else:
artifact = request.form.get('SAMLart')
response_method = lasso.HTTP_METHOD_ARTIFACT_POST
try:
login.initRequest(artifact, response_method)
except lasso.Error, error:
if error[0] == lasso.PROFILE_ERROR_MISSING_ARTIFACT:
get_logger().error(_('during proxy single sign on received a query without an artifact: %s') % request.get_query())
return template.error_page(_('Missing SAML Artifact'))
else:
self.logProfileError(login, error, 'proxySingleSignOnArtifact login.initRequest')
return error_then_home(_('Internal Server Error'))
login.buildRequestMsg()
try:
soap_answer = self.soap_call(login.msgUrl, login.msgBody)
except SOAPException:
return template.error_page(_('Failure to communicate with identity provider'))
try:
login.processResponseMsg(soap_answer)
except lasso.Error, error:
self.logLoginProcessResponseMsgError(login, error)
if error[0] == lasso.LOGIN_ERROR_FEDERATION_NOT_FOUND:
t = self.proxy_auth_federation_not_found(login)
if t:
return t
return template.error_page('there was no federation')
t = self.proxy_auth_peer_cancelled(login)
if t:
return t
login = lasso.Login.newFromDump(get_lasso_server(protocol = 'saml2'),
session.lasso_login_dump)
session.lasso_login_dump = None
return self.sso_after_authentication(login, False, proxied = True)
else:
self.proxy_auth_ok(login)
return self.proxy_sso_ok(login)
def proxyAssertionConsumerPost(self):
server = get_lasso_server(lasso.PROVIDER_ROLE_SP, protocol = 'saml2')
if not server:
return template.error_page(_('SAML 2.0 support not yet configured.'))
login = lasso.Login(server)
request = get_request()
session = get_session()
try:
login.processAuthnResponseMsg(get_field('SAMLResponse'))
except lasso.Error, error:
if error[0] == lasso.LOGIN_ERROR_FEDERATION_NOT_FOUND:
t = self.proxy_auth_federation_not_found(login)
if t:
return t
return template.error_page('there was no federation')
t = self.proxy_auth_peer_cancelled(login)
if t:
return t
login = lasso.Login.newFromDump(get_lasso_server(protocol = 'saml2'),
session.lasso_login_dump)
session.lasso_login_dump = None
return self.sso_after_authentication(login, False, proxied = True)
else:
self.proxy_auth_ok(login)
return self.proxy_sso_ok(login)
def proxyAssertionConsumerRedirect(self):
server = get_lasso_server(lasso.PROVIDER_ROLE_SP, protocol = 'saml2')
if not server:
return template.error_page(_('SAML 2.0 support not yet configured.'))
login = lasso.Login(server)
request = get_request()
session = get_session()
try:
login.processAuthnResponseMsg(request.get_query())
except lasso.Error, error:
if error[0] == lasso.LOGIN_ERROR_FEDERATION_NOT_FOUND:
t = self.proxy_auth_federation_not_found(login)
if t:
return t
return template.error_page('there was no federation')
t = self.proxy_auth_peer_cancelled(login)
if t:
return t
login = lasso.Login.newFromDump(get_lasso_server(protocol = 'saml2'),
session.lasso_login_dump)
session.lasso_login_dump = None
return self.sso_after_authentication(login, False, proxied = True)
else:
self.proxy_auth_ok(login)
return self.proxy_sso_ok(login)
def proxy_sso_ok(self, login):
session = get_session()
if session.lasso_proxy_session_dump:
login.setSessionFromDump(session.lasso_proxy_session_dump)
try:
common.SessionIndex(login.response.assertion[0].authnStatement[0].sessionIndex).store()
except:
pass
ni = login.nameIdentifier.content
authentic.identities.get_store().load_identities()
identity = authentic.identities.get_store().get_identity_for_name_identifier(ni)
if identity is None and get_session().user:
identity = authentic.identities.get_store().get_identity(get_session().user)
if identity:
if identity.lasso_proxy_dump:
login.setIdentityFromDump(identity.lasso_proxy_dump)
else:
# XXX: only create identity when "federated" ?
identity = authentic.identities.Identity()
identity_keys = authentic.identities.get_store().keys()
base_key = get_provider_key(login.remoteProviderId)
i = 1
while ('%s-%d' % (base_key, i)) in identity_keys:
i += 1
identity.id = '%s-%d' % (base_key, i)
identity.name = ''
identity.email = ''
identity.proxied_identity_origin = login.remoteProviderId
authentic.identities.get_store().add(identity)
session.set_user(identity.id)
session.authentication_instant = datetime.datetime.utcnow()
session.authentication_method = 'saml'
session.proxied_idp = login.remoteProviderId
if not session.name_identifiers:
session.name_identifiers = []
session.name_identifiers.append(login.nameIdentifier.content)
login.acceptSso()
if login.isIdentityDirty:
identity.lasso_proxy_dump = login.identity.dump()
if login.isSessionDirty:
session.lasso_proxy_session_dump = login.session.dump()
authentic.identities.get_store().save(identity)
if session.lasso_login_dump:
login = lasso.Login.newFromDump(get_lasso_server(protocol = 'saml2'),
session.lasso_login_dump)
session.lasso_login_dump = None
return self.sso_after_authentication(login, True, proxied = True)
if session.after_url:
after_url = session.after_url
session.after_url = None
return redirect(after_url)
return redirect(get_request().environ['SCRIPT_NAME'] + '/')
def proxy_slo_soap(self, session, identity):
# slo request from SP, continue with SOAP logout request to proxied-IdP
server = get_lasso_server(lasso.PROVIDER_ROLE_SP, protocol = 'saml2')
logout = lasso.Logout(server)
load_session(logout, session)
if identity and identity.lasso_proxy_dump:
logout.setIdentityFromDump(identity.lasso_proxy_dump)
logout.initRequest(session.proxied_idp, lasso.HTTP_METHOD_SOAP)
logout.buildRequestMsg()
try:
soap_answer = self.soap_call(logout.msgUrl, logout.msgBody)
except soap.SOAPException:
pass
else:
try:
logout.processResponseMsg(soap_answer)
except lasso.Error, error:
pass
else:
save_session(logout, session)
# Keep compatibility with old metadatas files
proxySingleSignOnArtifact = proxyAssertionConsumerArtifact
proxySingleSignOnPost = proxyAssertionConsumerPost
proxySingleSignOnRedirect = proxyAssertionConsumerRedirect
class SpUI(AccessControlled, common.LassoDirectory):
_q_exports = ['login', 'terminate']
def _q_access(self):
session = get_session()
if not session or session.user is None:
raise errors.AccessUnauthorizedError()
def __init__(self, component):
self.provider_key = component
try:
self.lp = get_cfg('providers')[component]
except KeyError:
raise errors.TraversalError()
def init_provider(self, role = lasso.PROVIDER_ROLE_SP, encryption_mode = None):
lp = self.lp
if lp['role'] != role and lp['role'] != lasso.PROVIDER_ROLE_NONE:
raise errors.TraversalError()
self.p = lasso.Provider(role,
authentic.misc.get_abs_path(lp['metadata']),
authentic.misc.get_abs_path(lp.get('publickey')), None)
if self.p is not None:
if encryption_mode is None:
# take config from settings
encryption_mode = lasso.ENCRYPTION_MODE_NONE
if get_cfg('providers')[self.provider_key].get('encrypt_nameid'):
encryption_mode |= lasso.ENCRYPTION_MODE_NAMEID
if get_cfg('providers')[self.provider_key].get('encrypt_assertion'):
encryption_mode |= lasso.ENCRYPTION_MODE_ASSERTION
if encryption_mode:
self.p.setEncryptionMode(encryption_mode);
def login(self, encryption_mode = None, method = None, nid_format = 'none',
relay_state = None):
self.init_provider(encryption_mode = encryption_mode)
login = lasso.Login(get_lasso_server(protocol = 'saml2'))
login.initIdpInitiatedAuthnRequest(self.p.providerId)
if method:
login.request.protocolBinding = method
if nid_format == 'persistent':
login.request.nameIDPolicy.format = lasso.SAML2_NAME_IDENTIFIER_FORMAT_PERSISTENT
elif nid_format == 'transient':
login.request.nameIDPolicy.format = lasso.SAML2_NAME_IDENTIFIER_FORMAT_TRANSIENT
elif nid_format == 'encrypted':
login.request.nameIDPolicy.format = lasso.SAML2_NAME_IDENTIFIER_FORMAT_ENCRYPTED
elif nid_format == 'none':
login.request.nameIDPolicy.format = lasso.SAML2_NAME_IDENTIFIER_FORMAT_UNSPECIFIED
if relay_state:
login.request.relayState = relay_state
login.request.nameIDPolicy.allowCreate = True
get_logger().info('SSO to %s' % self.p.providerId)
try:
login.processAuthnRequestMsg(None)
except lasso.Error, error:
if error[0] == lasso.LOGIN_ERROR_NO_DEFAULT_ENDPOINT:
return template.error_page(
_('No default endpoint for this service provider'))
self.logProfileError(login, error, "login.processAuthnRequestMsg")
return template.error_page(
_('No default endpoint for this service provider'))
session = get_session()
session.saml2 = True
rt = RootDirectory()
return rt.sso_after_consent(login, True, True, intro_cookie = False)
def terminate(self, method = lasso.HTTP_METHOD_SOAP):
self.init_provider()
manage = lasso.NameIdManagement(get_lasso_server(protocol = 'saml2'))
session = get_session()
# No more needed for Lasso > 2.2.90
load_session(manage, session)
load_identity(manage, identity = None, session = session)
get_logger().info('fedterm to %s' % self.p.providerId)
try:
manage.initRequest(self.p.providerId, None, method)
except lasso.Error, error:
if error[0] == lasso.PROFILE_ERROR_FEDERATION_NOT_FOUND:
raise errors.TraversalError() # no such federation
if error[0] == lasso.PROFILE_ERROR_IDENTITY_NOT_FOUND:
raise errors.TraversalError() # no such federation
elif error[0] == lasso.PROFILE_ERROR_UNSUPPORTED_PROFILE:
if method != lasso.HTTP_METHOD_REDIRECT:
self.terminate(lasso.HTTP_METHOD_REDIRECT)
else:
get_logger().error('FTN initRequest failed for %s: no supported profile' % self.p.providerId)
return error_then_home(_("This service doesn't support federation termination"))
get_logger().error('FTN initRequest failed for %s: %s' % (self.p.providerId, error[1]))
return error_then_home(_('Federation termination failed for %s') % self.p.providerId)
try:
manage.buildRequestMsg()
except lasso.Error, error:
get_logger().error('FTN failed for %s: %s' % (self.p.providerId, error[1]))
return error_then_home(_('Federation termination failed for %s') % self.p.providerId)
name_identifier = manage.nameIdentifier.content
save_identity(manage)
if name_identifier in (session.name_identifiers or []):
session.name_identifiers.remove(name_identifier)
if not manage.msgUrl:
return error_then_home(_("Service provider doesn't support federation termination profile."))
if manage.msgBody:
try:
response_msg = self.soap_call(manage.msgUrl, manage.msgBody)
except soap.SOAPException:
return error_then_home(_('Service provider failed to process request.'))
else:
return redirect(manage.msgUrl)
try:
manage.processResponseMsg(response_msg)
except lasso.Error, error:
return template.error_page(_('Service provider response could not be processed.'))
save_identity(manage)
return misc.redirect_to_return_url() or misc.redirect_home()