1510 lines
65 KiB
Plaintext
1510 lines
65 KiB
Plaintext
import os
|
|
import re
|
|
import urllib
|
|
import httplib
|
|
import Cookie
|
|
import socket
|
|
import datetime
|
|
|
|
import lasso
|
|
if hasattr(lasso, 'ProfileService') and not hasattr(lasso, 'DataService'):
|
|
lasso.DataService = lasso.ProfileService
|
|
|
|
from quixote import get_request, get_response, redirect, get_field, get_publisher
|
|
from quixote.directory import Directory, AccessControlled
|
|
from quixote.http_request import parse_header
|
|
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.publisher import sitecharset2utf8
|
|
|
|
import authentic
|
|
import authentic.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 common
|
|
import qommon.soap as soap
|
|
|
|
BENCH = False
|
|
|
|
class Artifact(StorableObject):
|
|
_names = 'artifacts'
|
|
_indexes = ['artifact']
|
|
artifact = None
|
|
session_id = None
|
|
provider_id = 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)
|
|
|
|
|
|
class RootDirectory(common.LassoDirectory):
|
|
_q_exports = ["", "sp", "singleSignOn", "failSingleSignOn", "soapEndpoint",
|
|
'metadata', ('metadata.xml', 'metadata'), 'public_key',
|
|
"singleLogout", "singleLogoutReturn", "federationTermination",
|
|
"federationTerminationReturn", "registerNameIdentifier", "consent",
|
|
"proxySoapEndpoint", "proxyAssertionConsumer", "proxySingleLogout",
|
|
"proxySingleLogoutNext", 'proxySingleLogoutReturn']
|
|
|
|
sp = SpDir()
|
|
|
|
def _q_index(self):
|
|
raise errors.AccessError()
|
|
|
|
def singleSignOn(self):
|
|
'''Handle an AuthnRequest, if not logged and
|
|
interaction is authorized, ask for login'''
|
|
return self.single_sign_on()
|
|
|
|
def failSingleSignOn(self):
|
|
'''Handle an AuthnRequest, but always return
|
|
RequestDenied'''
|
|
return self.single_sign_on(request_denied = True)
|
|
|
|
def single_sign_on(self, request_denied = False):
|
|
server = get_lasso_server()
|
|
if not server:
|
|
return template.error_page(_('Liberty support not yet configured.'))
|
|
login = lasso.Login(server)
|
|
request = get_request()
|
|
|
|
authn_request_msg = None
|
|
if request.get_method() == 'GET':
|
|
if lasso.isLibertyQuery(request.get_query()):
|
|
authn_request_msg = request.get_query()
|
|
if BENCH:
|
|
authn_request_msg = 'RequestID=_CED73573A277216F1F1D046F5155D607&MajorVersion=1&MinorVersion=2&IssueInstant=2006-01-24T10%3A19%3A15Z&ProviderID=http%3A%2F%2Funwind%2Fliberty%2Fmetadata&ForceAuthn=false&IsPassive=false&NameIDPolicy=federated&consent=urn%3Aliberty%3Aconsent%3Aobtained&SigAlg=http%3A%2F%2Fwww.w3.org%2F2000%2F09%2Fxmldsig%23rsa-sha1&Signature=kfu2VYPIiSS%2FGGDdVKxiSd%2F%2Fe8XN6vAPZkf8TxXzNzuEqLKBGQsRQTv4waaDHBYMRmSMOHd81ZpDbc6lqOZLkUdv68QCjMIvF3L53S0V7Mab8PDs2%2B6lNEmuUOn%2F2hwoJ2ol6%2B%2BCKtpIq3xksh8juyyrZgMyjhopGTzmXCwFIhokUFuHD4SX8YC%2BYkFNyDnnD2acU3Id2ip2zmabO6WYh7VOvYSxBl4BtYRWLZ%2BmdZTM0LOUn%2FpSEUAwXi9bmCphdzgxQUd8T5BfRflvBmP%2BMUNSalmsvLwXk15eiVDGbxcmikVsNGpSy0nwQOoCMCgFJC60QeoClmpVjgRfILWBrg%3D%3D'
|
|
else: # method is POST
|
|
authn_request_msg = get_field('LAREQ')
|
|
|
|
if not authn_request_msg:
|
|
return template.error_page(_('No authentication request found.'))
|
|
|
|
try:
|
|
login.processAuthnRequestMsg(authn_request_msg)
|
|
except lasso.Error, error:
|
|
if error[0] == lasso.SERVER_ERROR_PROVIDER_NOT_FOUND:
|
|
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.'))
|
|
raise
|
|
|
|
get_logger().info('SSO from %s' % login.remoteProviderId)
|
|
|
|
request_id = login.request.requestId
|
|
session = get_session()
|
|
if BENCH:
|
|
session.user = 'fred'
|
|
session.id = 'SESSION-DE-TEST'
|
|
if session and session.user and not session.lasso_session_dump:
|
|
# user has a session but it was only created locally, so it has no
|
|
# assertion. However login.mustAuthenticate() will look for an
|
|
# existing session to decide whether to ask for login or not. So
|
|
# we needs to feed an empty session to the login object. Note that
|
|
# Lasso doesn't do any namespace checking.
|
|
login.setSessionFromDump('<Session xmlns="http://www.entrouvert.org/namespaces/lasso/0.0" Version="1"/>')
|
|
if session and session.lasso_session_dump:
|
|
login.setSessionFromDump(session.lasso_session_dump)
|
|
if request_denied:
|
|
user_authenticated = False
|
|
elif login.mustAuthenticate() and not login_token.LoginToken.has_good_authentication(request_id):
|
|
# We use a login token to verify ForceAuthn
|
|
login_token.LoginToken(request_id).store()
|
|
self.sso_before_login_redirect()
|
|
login_url = request.environ['SCRIPT_NAME'] + '/login'
|
|
return redirect_with_return_url(login_url,
|
|
(('okURL', get_request().get_local_url()),
|
|
('cancelURL', get_request().get_local_url().replace('singleSignOn','failSingleSignOn')),
|
|
('LoginToken', request_id)))
|
|
else:
|
|
if session and session.user:
|
|
user_authenticated = True
|
|
else:
|
|
user_authenticated = False
|
|
|
|
return self.sso_after_authentication(login, user_authenticated)
|
|
|
|
def sso_before_login_redirect(self):
|
|
pass # to be subclassed
|
|
|
|
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(), session.lasso_login_dump)
|
|
session.lasso_login_dump = None
|
|
return self.sso_after_consent(login, True, consent)
|
|
|
|
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)
|
|
|
|
consent_obtained = False
|
|
if user_authenticated:
|
|
if login.mustAskForConsent():
|
|
session.lasso_login_dump = login.dump()
|
|
return redirect('consent')
|
|
return self.sso_after_consent(login, user_authenticated, True, proxied)
|
|
|
|
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 sso_after_consent(self, login, user_authenticated, consent_obtained, proxied = False):
|
|
# part 3 of SSO: if necessary, user has given its consent
|
|
session, identity = self.restore_user_details(login)
|
|
|
|
try:
|
|
login.validateRequestMsg(user_authenticated, consent_obtained)
|
|
except lasso.Error, error:
|
|
if error[0] < 0:
|
|
raise
|
|
do_federation = False
|
|
else:
|
|
do_federation = True
|
|
if not BENCH and lasso.WSF_SUPPORT:
|
|
if identity:
|
|
if not identity.resource_id:
|
|
identity.resource_id = '%s/resources/identity/%s' % (
|
|
get_cfg('idp')['base_url'], identity.id)
|
|
authentic.identities.get_store().save(identity)
|
|
login.setResourceId(identity.resource_id)
|
|
|
|
authn_methods = {
|
|
'password': lasso.SAML_AUTHENTICATION_METHOD_PASSWORD,
|
|
'password-on-https': lasso.SAML_AUTHENTICATION_METHOD_PASSWORD,
|
|
'password-and-ip': 'urn:oasis:names:tc:SAML:1.0:am:InternetProtocolPassword',
|
|
'client-certificate': lasso.SAML_AUTHENTICATION_METHOD_SOFTWARE_PKI
|
|
}
|
|
|
|
auth_method = authn_methods.get(session.authentication_method or 'password')
|
|
|
|
if proxied:
|
|
# XXX: if sso is proxied, build it with the same details as
|
|
# the proxied assertion
|
|
# (current condition looks ridiculous, I know)
|
|
login.buildAssertion(auth_method, None, None, None, None)
|
|
else:
|
|
now = datetime.datetime.utcnow()
|
|
notBefore = now-datetime.timedelta(0,60)
|
|
notOnOrAfter = now+datetime.timedelta(0,60)
|
|
login.buildAssertion(auth_method,
|
|
get_session().authentication_instant.isoformat()+'Z',
|
|
None,
|
|
notBefore.isoformat()+'Z',
|
|
notOnOrAfter.isoformat()+'Z')
|
|
|
|
|
|
try:
|
|
identity = authentic.identities.get_store().get_identity(get_session().user)
|
|
except KeyError:
|
|
identity = None
|
|
|
|
login.assertion.authenticationStatement.sessionIndex = login.assertion.assertionId
|
|
common.SessionIndex(login.assertion.assertionId).store()
|
|
|
|
if identity and hasattr(identity, 'attributes') and identity.attributes:
|
|
t = lasso.NodeList()
|
|
if login.assertion.attributeStatement:
|
|
for at in login.assertion.attributeStatement.attribute:
|
|
t.append(at)
|
|
|
|
login.assertion.attributeStatement = lasso.SamlAttributeStatement()
|
|
|
|
for k, v in identity.attributes.items():
|
|
ta = lasso.SamlAttribute()
|
|
t.append(ta)
|
|
ta.attributeName = k
|
|
ta.attributeNamespace = 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic'
|
|
|
|
t2 = lasso.NodeList()
|
|
for vi in v:
|
|
tav = lasso.SamlAttributeValue()
|
|
t2.append(tav)
|
|
|
|
t3 = lasso.NodeList()
|
|
|
|
ttn = lasso.MiscTextNode()
|
|
if type(vi) is unicode:
|
|
ttn.content = vi.encode('utf-8')
|
|
else:
|
|
ttn.content = sitecharset2utf8(vi)
|
|
ttn.text_child = True
|
|
t3.append(ttn)
|
|
tav.any = t3
|
|
|
|
ta.attributeValue = t2
|
|
|
|
login.assertion.attributeStatement.attribute = t
|
|
|
|
if login.protocolProfile == lasso.LOGIN_PROTOCOL_PROFILE_BRWS_ART:
|
|
login.buildArtifactMsg(lasso.HTTP_METHOD_REDIRECT)
|
|
elif login.protocolProfile == lasso.LOGIN_PROTOCOL_PROFILE_BRWS_POST:
|
|
login.buildAuthnResponseMsg()
|
|
else:
|
|
return template.error_page(_('Unknown liberty protocol profile'))
|
|
|
|
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 = []
|
|
session.name_identifiers.append(login.nameIdentifier.content)
|
|
|
|
if login.isSessionDirty:
|
|
session.lasso_session_dump = login.session.dump()
|
|
|
|
self.handle_common_domain_cookie(login)
|
|
|
|
if login.protocolProfile == lasso.LOGIN_PROTOCOL_PROFILE_BRWS_ART:
|
|
artifact = Artifact(id = login.assertionArtifact)
|
|
artifact.artifact = login.assertionArtifact
|
|
artifact.session_id = session.id
|
|
artifact.provider_id = login.remoteProviderId
|
|
artifact.store()
|
|
return redirect(login.msgUrl)
|
|
else:
|
|
return self.postTo(login.msgUrl, login.msgBody, 'LARES',
|
|
relay_state=login.relayState,
|
|
title=N_('Authentication Response'))
|
|
|
|
def handle_common_domain_cookie(self, login):
|
|
get_publisher().reload_cfg()
|
|
try:
|
|
common_domain = get_cfg('idp')['common_domain']
|
|
except KeyError:
|
|
common_domain = None
|
|
if common_domain:
|
|
request = get_request()
|
|
try:
|
|
intro_cookie = request.cookies['_liberty_idp']
|
|
except KeyError:
|
|
intro_cookie = ''
|
|
splitted_cookie = [x.strip() for x in intro_cookie.split(' ') if x]
|
|
succinct_id = login.server.getBase64SuccinctId()
|
|
if succinct_id in splitted_cookie:
|
|
splitted_cookie.remove(succinct_id)
|
|
splitted_cookie.append(succinct_id)
|
|
response = get_response()
|
|
new_cookie = ' '.join(splitted_cookie)
|
|
if new_cookie != intro_cookie:
|
|
response.set_cookie('_liberty_idp', new_cookie,
|
|
domain = '.' + common_domain, path = '/',
|
|
expires = Cookie._getdate(3*365*86400))
|
|
|
|
@common.soap_endpoint
|
|
def soapEndpoint(self):
|
|
soap_message = self.get_soap_message()
|
|
authentic.identities.get_store().load_identities()
|
|
request_type = lasso.getRequestTypeFromSoapMsg(soap_message)
|
|
if request_type == lasso.REQUEST_TYPE_LOGIN:
|
|
login = lasso.Login(get_lasso_server())
|
|
login.processRequestMsg(soap_message)
|
|
artifact = None
|
|
try:
|
|
artifact = Artifact.get_by_artifact(login.assertionArtifact)
|
|
except KeyError:
|
|
provider_id = None
|
|
else:
|
|
artifact.remove_self()
|
|
provider_id = artifact.provider_id
|
|
session = get_session_manager().get(artifact.session_id)
|
|
if session: # it should always be there but who knows...
|
|
login.setSessionFromDump(session.lasso_session_dump)
|
|
try:
|
|
login.buildResponseMsg(provider_id)
|
|
get_logger().info('Artifact resolve from provider %s for artifact %s' % (provider_id, login.assertionArtifact))
|
|
except lasso.Error, error:
|
|
if error[0] == lasso.DS_ERROR_SIGNATURE_VERIFICATION_FAILED:
|
|
get_logger().warn('Signature verification failed on artifact resolve from %s)' % provider_id)
|
|
elif error[0] == lasso.PROFILE_ERROR_SESSION_NOT_FOUND:
|
|
if not artifact:
|
|
get_logger().warn('Could not find the artifact on artifact resolve from %s, artifact %s)' % (provider_id, login.assertionArtifact))
|
|
else:
|
|
get_logger().warn('Could not find a session dump on artifact resolve from %s, artifact %s)' % (provider_id, login.assertionArtifact))
|
|
else:
|
|
raise
|
|
if login.isSessionDirty:
|
|
if login.session:
|
|
session.lasso_session_dump = login.session.dump()
|
|
else:
|
|
session.lasso_session_dump = None
|
|
get_session_manager().commit_changes(session)
|
|
|
|
return login.msgBody
|
|
|
|
if request_type == lasso.REQUEST_TYPE_LOGOUT:
|
|
logout = lasso.Logout(get_lasso_server())
|
|
logout.processRequestMsg(soap_message)
|
|
name_identifier = logout.nameIdentifier.content
|
|
try:
|
|
session_index = common.SessionIndex.get(logout.request.sessionIndex)
|
|
logout_session = get_session_manager().get(session_index.session_id)
|
|
except KeyError:
|
|
# no session, build straight failure answer
|
|
logout.buildResponseMsg()
|
|
return logout.msgBody
|
|
authentic.identities.get_store().load_identities()
|
|
try:
|
|
identity = authentic.identities.get_store().get_identity(session.user)
|
|
except KeyError:
|
|
pass
|
|
else:
|
|
if identity.lasso_dump:
|
|
logout.setIdentityFromDump(identity.lasso_dump)
|
|
|
|
if session.proxied_idp:
|
|
try:
|
|
self.proxy_slo_soap(session, identity)
|
|
except lasso.Error, error:
|
|
if error[0] == lasso.LOGOUT_ERROR_UNSUPPORTED_PROFILE:
|
|
pass
|
|
# too bad but we won't fallback to HTTP redirect for
|
|
# the sake of the IdP
|
|
logout.setSessionFromDump(session.lasso_session_dump)
|
|
|
|
try:
|
|
logout.validateRequest()
|
|
except lasso.Error, error:
|
|
if error[0] == lasso.LOGOUT_ERROR_UNSUPPORTED_PROFILE:
|
|
pass
|
|
elif error[0] == lasso.PROFILE_ERROR_SESSION_NOT_FOUND:
|
|
pass
|
|
else:
|
|
raise
|
|
else:
|
|
session_manager = get_session_manager()
|
|
del session_manager[session_index.session_id]
|
|
remote_provider_id = logout.getNextProviderId()
|
|
while remote_provider_id:
|
|
try:
|
|
logout.initRequest(remote_provider_id, lasso.HTTP_METHOD_SOAP)
|
|
except lasso.Error, error:
|
|
if error[0] == lasso.PROFILE_ERROR_IDENTITY_NOT_FOUND:
|
|
break # this is bad, abort.
|
|
if error[0] == lasso.LOGOUT_ERROR_UNSUPPORTED_PROFILE:
|
|
get_logger().warn('SLO not supported on %s' % remote_provider_id)
|
|
break # this doesn't happen with newer Lasso releases
|
|
if error[0] == lasso.PROFILE_ERROR_UNSUPPORTED_PROFILE:
|
|
get_logger().warn('SLO not supported on %s' % remote_provider_id)
|
|
break # this doesn't happen with newer Lasso releases
|
|
raise
|
|
logout.buildRequestMsg()
|
|
try:
|
|
soap_answer = self.soap_call(logout.msgUrl, logout.msgBody)
|
|
logout.processResponseMsg(soap_answer)
|
|
except soap.SOAPException:
|
|
pass # log ?
|
|
except lasso.Error, error:
|
|
pass # log ?
|
|
remote_provider_id = logout.getNextProviderId()
|
|
|
|
logout.buildResponseMsg()
|
|
return logout.msgBody
|
|
|
|
if request_type == lasso.REQUEST_TYPE_DEFEDERATION:
|
|
defederation = lasso.Defederation(get_lasso_server())
|
|
defederation.processNotificationMsg(soap_message)
|
|
name_identifier = defederation.nameIdentifier.content
|
|
identity = authentic.identities.get_store().get_identity_for_name_identifier(name_identifier)
|
|
for session in get_session_manager().values():
|
|
if name_identifier in (session.name_identifiers or []):
|
|
get_logger().info('FedTerm/SOAP from %s' % logout.remoteProviderId)
|
|
break
|
|
else:
|
|
session = None
|
|
if session and session.proxied_idp:
|
|
pass # XXX: fedterm with IdP
|
|
return self.fedterm(defederation, identity, session)
|
|
|
|
if request_type == lasso.REQUEST_TYPE_NAME_REGISTRATION:
|
|
registration = lasso.NameRegistration(get_lasso_server())
|
|
registration.processRequestMsg(soap_message)
|
|
oldNameIdentifier = registration.oldNameIdentifier.content
|
|
identity = authentic.identities.get_store().get_identity_for_name_identifier(
|
|
oldNameIdentifier)
|
|
for session in get_session_manager().values():
|
|
if oldNameIdentifier in (session.name_identifiers or []):
|
|
break
|
|
return self.rni(registration, identity, session)
|
|
|
|
idsis_pp = get_cfg('idp').get('idsis_pp', False)
|
|
if request_type == lasso.REQUEST_TYPE_DISCO_QUERY and lasso.WSF_SUPPORT:
|
|
server = get_lasso_server()
|
|
disco = lasso.Discovery(server)
|
|
disco.processQueryMsg(soap_message)
|
|
try:
|
|
identity = self.get_identity_by_resource_id(disco.resourceId)
|
|
disco.setIdentityFromDump(identity.lasso_dump)
|
|
except:
|
|
pass
|
|
disco.buildResponseMsg()
|
|
return disco.msgBody
|
|
|
|
if request_type == lasso.REQUEST_TYPE_DISCO_MODIFY and lasso.WSF_SUPPORT:
|
|
server = get_lasso_server()
|
|
disco = lasso.Discovery(server)
|
|
disco.processModifyMsg(soap_message, None)
|
|
try:
|
|
identity = self.get_identity_by_resource_id(disco.resourceId)
|
|
disco.setIdentityFromDump(identity.lasso_dump)
|
|
except:
|
|
pass
|
|
disco.buildModifyResponseMsg()
|
|
if disco.isIdentityDirty:
|
|
identity.lasso_dump = disco.identity.dump()
|
|
authentic.identities.get_store().save(identity)
|
|
return disco.msgBody
|
|
|
|
if request_type == lasso.REQUEST_TYPE_DST_QUERY and lasso.WSF_SUPPORT:
|
|
service = lasso.DataService(get_lasso_server())
|
|
# XXX: lasso lacks a function to get the dst namespace
|
|
try:
|
|
service_prefix = re.findall('<([a-z]*?):Query>', soap_message)[0]
|
|
service_href = re.findall('xmlns:%s="(.*?)"' % service_prefix, soap_message)[0]
|
|
except IndexError: # too bad, this hacky thing didn't work.
|
|
service_href = lasso.PP_HREF
|
|
|
|
if service_href == lasso.PP_HREF and not idsis_pp:
|
|
pass # XXX: build deny request
|
|
return 'ERROR'
|
|
|
|
service.processQueryMsg(soap_message)
|
|
try:
|
|
identity = self.get_identity_by_resource_id(service.resourceId)
|
|
except:
|
|
pass # XXX: build deny request ?
|
|
|
|
resource = identity.get_dst_view(service_href)
|
|
if resource:
|
|
service.resourceData = identity.get_dst_view(service_href)
|
|
else:
|
|
return 'ERROR' # XXX: build deny request
|
|
service.buildResponseMsg()
|
|
return service.msgBody
|
|
|
|
def get_identity_by_resource_id(self, resource_id):
|
|
resource = resource_id.content
|
|
prefix = '%s/resources/' % get_cfg('idp')['base_url']
|
|
if not resource.startswith(prefix):
|
|
raise "unknown resource"
|
|
resource = resource[len(prefix):]
|
|
type, value = resource.split('/')
|
|
if type != 'identity': # we only know identities as resource
|
|
raise "resource not identity"
|
|
|
|
try:
|
|
identity = authentic.identities.get_store().get_identity(value)
|
|
except KeyError:
|
|
raise "identity not found"
|
|
|
|
if lasso.WSF_SUPPORT:
|
|
idsis_pp = get_cfg('idp').get('idsis_pp', False)
|
|
lasso_identity = lasso.Identity.newFromDump(identity.lasso_dump)
|
|
server = authentic.misc.get_lasso_server()
|
|
offerings = lasso_identity.getOfferings(lasso.PP_HREF)
|
|
if offerings:
|
|
lst = [x for x in lasso_identity.getOfferings(lasso.PP_HREF) if \
|
|
x.serviceInstance.providerId == server.providerId]
|
|
else:
|
|
lst = []
|
|
if idsis_pp and len(lst) == 0:
|
|
# adds authentic id-sis pp offering
|
|
resource_offering = lasso.DiscoResourceOffering(self.get_pp_service())
|
|
resource_offering.resourceId = lasso.DiscoResourceID(identity.resource_id)
|
|
resource_offering.abstract = "Personal Profile with Authentic informations"
|
|
lasso_identity.addResourceOffering(resource_offering)
|
|
identity.lasso_dump = lasso_identity.dump()
|
|
authentic.identities.get_store().save(identity)
|
|
elif not idsis_pp and len(lst) > 0:
|
|
# remove authentic id-sis pp offerings
|
|
try:
|
|
for x in lst:
|
|
lasso_identity.removeResourceOffering(x.entryId)
|
|
except:
|
|
pass # ignores everything since it would raise an exception even on success
|
|
identity.lasso_dump = lasso_identity.dump()
|
|
authentic.identities.get_store().save(identity)
|
|
return identity
|
|
|
|
def get_pp_service(self):
|
|
server = authentic.misc.get_lasso_server()
|
|
return lasso.DiscoServiceInstance(
|
|
lasso.PP_HREF,
|
|
server.providerId,
|
|
lasso.DiscoDescription_newWithBriefSoapHttpDescription(
|
|
lasso.SECURITY_MECH_NULL,
|
|
'%s/soapEndpoint' % get_cfg('idp', {}).get('base_url', None)))
|
|
|
|
def singleLogout(self):
|
|
request = get_request()
|
|
if lasso.isLibertyQuery(request.get_query()):
|
|
return self.slo_sp()
|
|
else:
|
|
session = get_session()
|
|
# TODO: settings for logout type (soap/get/redirect)
|
|
return self.slo_idp(session)
|
|
|
|
def slo_idp(self, session, method = None):
|
|
# Single Logout initiated by IdP
|
|
if not session or not session.user:
|
|
raise errors.AccessError()
|
|
# delog user
|
|
user = session.user
|
|
session.user = None
|
|
session.store()
|
|
if not session.lasso_session_dump:
|
|
if method == lasso.HTTP_METHOD_SOAP:
|
|
return
|
|
return redirect(get_request().environ['SCRIPT_NAME'] + '/logout')
|
|
|
|
logout = lasso.Logout(get_lasso_server())
|
|
logout.setSessionFromDump(session.lasso_session_dump)
|
|
|
|
authentic.identities.get_store().load_identities()
|
|
try:
|
|
identity = authentic.identities.get_store().get_identity(user)
|
|
except KeyError:
|
|
if method == lasso.HTTP_METHOD_SOAP:
|
|
return
|
|
return redirect(get_request().environ['SCRIPT_NAME'] + '/logout')
|
|
else:
|
|
if identity.lasso_dump:
|
|
# keep a copy of the identity dump, for singleLogoutReturn
|
|
session.lasso_identity_dump = identity.lasso_dump
|
|
session.store()
|
|
logout.setIdentityFromDump(identity.lasso_dump)
|
|
|
|
if method == lasso.HTTP_METHOD_SOAP:
|
|
return self.slo_idp_soap(logout)
|
|
else:
|
|
return self.slo_idp_get(logout)
|
|
|
|
def slo_idp_get [html] (self, logout, url = None):
|
|
# Single Logout initiated by IdP; GET profile
|
|
session = get_session()
|
|
session.in_slo_get = True
|
|
if not url:
|
|
url = '%s/logout' % get_request().environ['SCRIPT_NAME']
|
|
|
|
template.html_top(onload = "window.location = '%s'" % url)
|
|
'<div id="logout-top"><h1>%s</h1></div>' % _('Logout')
|
|
"""<div id="logout-sps">
|
|
<p>%s</p>
|
|
<ul>""" % _('Logging out from service providers:')
|
|
|
|
remote_provider_id = logout.getNextProviderId()
|
|
while remote_provider_id is not None:
|
|
try:
|
|
t = logout.initRequest(remote_provider_id, lasso.HTTP_METHOD_REDIRECT)
|
|
except lasso.Error, error:
|
|
if error[0] == lasso.PROFILE_ERROR_IDENTITY_NOT_FOUND:
|
|
get_logger().warn('SLO/GET failed: identity not found')
|
|
break
|
|
elif error[0] == lasso.LOGIN_ERROR_FEDERATION_NOT_FOUND:
|
|
'<li>%s... %s</li>' % (remote_provider_id, _('federation not found'))
|
|
elif error[0] == lasso.PROFILE_ERROR_UNSUPPORTED_PROFILE:
|
|
'<li>%s... %s</li>' % (remote_provider_id, _('logout not supported'))
|
|
else:
|
|
raise
|
|
else:
|
|
t = logout.buildRequestMsg()
|
|
session.lasso_logout_dump = logout.dump()
|
|
organization_name = logout.server.getProvider(remote_provider_id).getOrganization() or remote_provider_id
|
|
'<li>%s... <img src="%s" alt="" width="12" height="12" /></li>' % (
|
|
organization_name, logout.msgUrl)
|
|
remote_provider_id = logout.getNextProviderId()
|
|
'</ul></div>'
|
|
'<p><a href="%s">%s</a></p>' % (url, _('Finish logging out'))
|
|
|
|
def slo_idp_soap(self, logout):
|
|
remote_provider_id = logout.getNextProviderId()
|
|
session = get_session()
|
|
finish_with_get = False
|
|
while remote_provider_id:
|
|
try:
|
|
logout.initRequest(remote_provider_id, lasso.HTTP_METHOD_SOAP)
|
|
except lasso.ProfileIdentityNotFoundError:
|
|
# FIXME: this should not happen
|
|
get_logger().warn('SLO/SOAP initRequest failed error: IdentityNotFound')
|
|
break # this is bad, abort.
|
|
except lasso.ProfileUnsupportedProfileError:
|
|
# If one SP doesn't support SLO SOAP binding, switch to SLO GET
|
|
# binding for all SP
|
|
finish_with_get = True
|
|
except lasso.Error, error:
|
|
raise
|
|
else:
|
|
try:
|
|
logout.buildRequestMsg()
|
|
soap_answer = self.soap_call(logout.msgUrl, logout.msgBody)
|
|
except soap.SOAPException:
|
|
pass
|
|
except lasso.ServerProviderNotFoundError:
|
|
get_logger().warn('SLO/SOAP failed because provider was not found, providerID: %s' % remote_provider_id)
|
|
except lasso.ProfileUnknownProfileUrlError:
|
|
get_logger().warn('SLO/SOAP failed because profile URL was empty, providerID: %s' % remote_provider_id)
|
|
except lasso.ProfileMissingRequestError:
|
|
get_logger().warn('SLO/SOAP failed because of a missing request, providerID: %s' % remote_provider_id)
|
|
except lasso.Error, error:
|
|
get_logger().warn('SLO/SOAP buildRequestMsg error, providerID: %s error: %s' % (remote_provider_id, error[1]))
|
|
|
|
else:
|
|
try:
|
|
logout.processResponseMsg(soap_answer)
|
|
except lasso.ProfileInvalidMsgError:
|
|
get_logger().warn('SLO/SOAP response is invalid, providerID: %s' % remote_provider_id)
|
|
except lasso.ProfileStatusNotSuccessError:
|
|
get_logger().warn('SLO/SOAP response status is not success, providerID: %s status: %s' % (remote_provider_id, logout.response.status.statusCode.value))
|
|
except lasso.LogoutRequestDeniedError:
|
|
get_logger().warn('SLO/SOAP request denied, providerID: %s' % (remote_provider_id))
|
|
except lasso.Error, error:
|
|
get_logger().warn('SLO/SOAP error, providerID: %s error: %s' % (remote_provider_id, error[1]))
|
|
remote_provider_id = logout.getNextProviderId()
|
|
if finish_with_get:
|
|
logout.resetProviderIdIndex()
|
|
return self.slo_idp_get(logout)
|
|
else:
|
|
return redirect(get_request().environ['SCRIPT_NAME'] + '/logout')
|
|
|
|
def singleLogoutReturn(self):
|
|
session = get_session()
|
|
if (not session or not session.user or not session.lasso_session_dump) and not hasattr(session, "lasso_identity_dump"):
|
|
raise errors.AccessError()
|
|
logout = lasso.Logout.newFromDump(get_lasso_server(), session.lasso_logout_dump)
|
|
if not logout:
|
|
# strange thing, should not happen, but handle it nicely
|
|
response = get_response()
|
|
response.set_content_type("image/png")
|
|
return file(os.path.join(get_publisher().DATA_DIR, 'liberty/check_on.png')).read()
|
|
|
|
logout.setSessionFromDump(session.lasso_session_dump)
|
|
|
|
if hasattr(session, "lasso_identity_dump"):
|
|
# FIXME: in later version of lasso this should be useless
|
|
logout.setIdentityFromDump(session.lasso_identity_dump)
|
|
else:
|
|
authentic.identities.get_store().load_identities()
|
|
try:
|
|
identity = authentic.identities.get_store().get_identity(session.user)
|
|
except KeyError:
|
|
pass
|
|
else:
|
|
logout.setIdentityFromDump(identity.lasso_dump)
|
|
|
|
request = get_request()
|
|
try:
|
|
logout.processResponseMsg(request.get_query())
|
|
except lasso.Error, error:
|
|
if error[0] == lasso.PROFILE_ERROR_INVALID_QUERY:
|
|
raise errors.AccessError()
|
|
raise
|
|
if logout.session is None:
|
|
session.lasso_session_dump = None
|
|
session.lasso_logout_dump = None # it was the last one.
|
|
else:
|
|
session.lasso_session_dump = logout.session.dump()
|
|
|
|
if session.in_slo_get:
|
|
return redirect(get_publisher().get_root_url() + 'images/check_on.png')
|
|
else:
|
|
return self.slo_sp_continue_with_http(logout, session)
|
|
|
|
|
|
def slo_sp(self):
|
|
"Single Logout initiated by service provider"
|
|
request = get_request()
|
|
logout = lasso.Logout(get_lasso_server())
|
|
try:
|
|
logout.processRequestMsg(request.get_query())
|
|
except lasso.Error, error:
|
|
if error[0] == lasso.DS_ERROR_INVALID_SIGNATURE:
|
|
return template.error_page(_('Failed to check single logout request signature.'))
|
|
raise
|
|
get_logger().info('SLO from %s' % logout.request.providerId)
|
|
|
|
session = get_session()
|
|
if not session.lasso_session_dump:
|
|
pass # session got closed by an other mean
|
|
elif logout.nameIdentifier.content not in (session.name_identifiers or []):
|
|
pass # same
|
|
else:
|
|
logout.setSessionFromDump(session.lasso_session_dump)
|
|
|
|
authentic.identities.get_store().load_identities()
|
|
identity = None
|
|
try:
|
|
identity = authentic.identities.get_store().get_identity(session.user)
|
|
except KeyError:
|
|
pass
|
|
else:
|
|
if identity.lasso_dump:
|
|
logout.setIdentityFromDump(identity.lasso_dump)
|
|
|
|
try:
|
|
logout.validateRequest()
|
|
except lasso.Error, error:
|
|
if error[0] != lasso.PROFILE_ERROR_SESSION_NOT_FOUND:
|
|
raise
|
|
try:
|
|
logout.buildResponseMsg()
|
|
except lasso.Error, error:
|
|
if error[0] == lasso.PROFILE_ERROR_UNKNOWN_PROFILE_URL:
|
|
# metadata didn't contain logout return url, stay here.
|
|
return redirect(get_request().environ['SCRIPT_NAME'] + '/')
|
|
raise
|
|
return redirect(logout.msgUrl)
|
|
|
|
if logout.session is None:
|
|
session.lasso_session_dump = None
|
|
else:
|
|
session.lasso_session_dump = logout.session.dump()
|
|
method = lasso.HTTP_METHOD_SOAP
|
|
|
|
if method == lasso.HTTP_METHOD_SOAP:
|
|
remote_provider_id = logout.getNextProviderId()
|
|
while remote_provider_id:
|
|
try:
|
|
logout.initRequest(remote_provider_id, method)
|
|
except lasso.Error, error:
|
|
if error[0] in (lasso.PROFILE_ERROR_UNSUPPORTED_PROFILE,
|
|
lasso.PROFILE_ERROR_FEDERATION_NOT_FOUND):
|
|
remote_provider_id = logout.getNextProviderId()
|
|
continue
|
|
else:
|
|
raise
|
|
|
|
logout.buildRequestMsg()
|
|
try:
|
|
soap_answer = self.soap_call(logout.msgUrl, logout.msgBody)
|
|
except soap.SOAPException:
|
|
remote_provider_id = logout.getNextProviderId()
|
|
continue
|
|
|
|
try:
|
|
logout.processResponseMsg(soap_answer)
|
|
except:
|
|
remote_provider_id = logout.getNextProviderId()
|
|
get_logger().warn('Invalid SOAP SLO answer from %s' % logout.msgUrl)
|
|
continue
|
|
nameIdentifier = logout.nameIdentifier.content
|
|
|
|
remote_provider_id = logout.getNextProviderId()
|
|
|
|
return self.slo_sp_continue_with_http(logout, session)
|
|
|
|
def slo_sp_continue_with_http(self, logout, session):
|
|
logout.resetProviderIdIndex()
|
|
while True: # to skip non-compliant providers
|
|
remote_provider_id = logout.getNextProviderId()
|
|
if remote_provider_id is None:
|
|
logout.buildResponseMsg()
|
|
if session.proxied_idp:
|
|
get_session().after_url = logout.msgUrl
|
|
return redirect(get_request().environ['SCRIPT_NAME'] + '/liberty/proxySingleLogout')
|
|
get_session_manager().expire_session()
|
|
return redirect(logout.msgUrl)
|
|
else:
|
|
try:
|
|
logout.initRequest(remote_provider_id, lasso.HTTP_METHOD_REDIRECT)
|
|
except lasso.Error, error:
|
|
if error[0] in (lasso.PROFILE_ERROR_UNSUPPORTED_PROFILE,
|
|
lasso.PROFILE_ERROR_FEDERATION_NOT_FOUND):
|
|
# it doesn't support nor SOAP nor Redirect ?
|
|
# And it calls itself Liberty compliant ?
|
|
continue # ignore it.
|
|
raise
|
|
logout.buildRequestMsg()
|
|
session.lasso_logout_dump = logout.dump()
|
|
return redirect(logout.msgUrl)
|
|
|
|
def federationTermination(self):
|
|
request = get_request()
|
|
if not lasso.isLibertyQuery(request.get_query()):
|
|
return redirect('.')
|
|
|
|
defederation = lasso.Defederation(get_lasso_server())
|
|
defederation.processNotificationMsg(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
|
|
return self.fedterm(defederation, identity, session)
|
|
|
|
def fedterm(self, defederation, identity, session):
|
|
if session and session.lasso_session_dump:
|
|
defederation.setSessionFromDump(session.lasso_session_dump)
|
|
|
|
if identity and identity.lasso_dump:
|
|
defederation.setIdentityFromDump(identity.lasso_dump)
|
|
get_logger().info('fedterm for %s from %s' % (identity.id, defederation.request.providerId))
|
|
else:
|
|
get_logger().info('fedterm from %s' % defederation.request.providerId)
|
|
|
|
try:
|
|
defederation.validateNotification()
|
|
except lasso.Error, error:
|
|
pass # ignore failure (?)
|
|
else:
|
|
if not defederation.identity:
|
|
# if it was the last federation the whole identity dump collapsed
|
|
identity.lasso_dump = None
|
|
else:
|
|
identity.lasso_dump = defederation.identity.dump()
|
|
authentic.identities.get_store().save(identity)
|
|
|
|
if session:
|
|
name_identifier = defederation.nameIdentifier.content
|
|
if name_identifier in (session.name_identifiers or []):
|
|
session.name_identifiers.remove(name_identifier)
|
|
if defederation.isSessionDirty:
|
|
if defederation.session:
|
|
session.lasso_session_dump = defederation.session.dump()
|
|
else:
|
|
session.lasso_session_dump = None
|
|
|
|
if defederation.msgUrl:
|
|
return redirect(defederation.msgUrl)
|
|
else:
|
|
get_session_manager().commit_changes(session)
|
|
response = get_response()
|
|
response.set_status(204)
|
|
return ''
|
|
|
|
def federationTerminationReturn(self):
|
|
return redirect('..')
|
|
|
|
def registerNameIdentifier(self):
|
|
request = get_request()
|
|
if not lasso.isLibertyQuery(request.get_query()):
|
|
return redirect('.')
|
|
|
|
registration = lasso.NameRegistration(get_lasso_server())
|
|
registration.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
|
|
return self.rni(registration, identity, session)
|
|
|
|
def rni(self, registration, identity, session):
|
|
if session and session.lasso_session_dump:
|
|
registration.setSessionFromDump(session.lasso_session_dump)
|
|
if identity:
|
|
registration.setIdentityFromDump(identity.lasso_dump)
|
|
registration.validateRequest()
|
|
if registration.identity:
|
|
identity.lasso_dump = registration.identity.dump()
|
|
authentic.identities.get_store().save(identity)
|
|
|
|
registration.buildResponseMsg()
|
|
|
|
if registration.msgUrl:
|
|
return redirect(registration.msgUrl)
|
|
else:
|
|
get_session_manager().commit_changes(session)
|
|
return registration.msgBody
|
|
|
|
|
|
def perform_proxy_login(self, idp = None, nameIdPolicy = None, extensions = None, relay_state = None):
|
|
session = get_session()
|
|
server = authentic.misc.get_lasso_server(lasso.PROVIDER_ROLE_SP)
|
|
login = lasso.Login(server)
|
|
login.initAuthnRequest(idp, lasso.HTTP_METHOD_REDIRECT)
|
|
login.request.nameIdPolicy = 'federated'
|
|
login.request.forceAuthn = False
|
|
login.request.isPassive = False
|
|
login.request.consent = "urn:liberty:consent:obtained"
|
|
if session.lasso_login_dump:
|
|
# replicates parameters from the request we proxy
|
|
orig_login = lasso.Login.newFromDump(get_lasso_server(), session.lasso_login_dump)
|
|
login.request.nameIdPolicy = orig_login.request.nameIdPolicy
|
|
login.request.forceAuthn = orig_login.request.forceAuthn
|
|
login.request.isPassive = orig_login.request.isPassive
|
|
login.request.consent = orig_login.request.consent
|
|
|
|
if nameIdPolicy: # forced value
|
|
login.request.nameIdPolicy = nameIdPolicy
|
|
if extensions:
|
|
ext_list = lasso.StringList()
|
|
for ext in extensions:
|
|
ext_list.append('<lib:Extension xmlns:lib="urn:liberty:iff:2003-08"><%s>%s</%s></lib:Extension>' % (ext[0], ext[1], ext[0]))
|
|
login.request.extension = ext_list
|
|
if relay_state is not None:
|
|
get_session().remember(login.request.iD, relay_state)
|
|
|
|
login.buildAuthnRequestMsg()
|
|
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 proxyAssertionConsumer(self):
|
|
server = authentic.misc.get_lasso_server(lasso.PROVIDER_ROLE_SP)
|
|
login = lasso.Login(server)
|
|
request = get_request()
|
|
if request.get_method() == 'GET' or get_field('LAREQ'):
|
|
if request.get_method() == 'GET':
|
|
try:
|
|
login.initRequest(request.get_query(), lasso.HTTP_METHOD_REDIRECT)
|
|
except lasso.Error, error:
|
|
if hasattr(lasso, 'strError'):
|
|
return template.error_page(lasso.strError(error[0]))
|
|
return template.error_page(_('Liberty error (lasso code: %s)') % error[0])
|
|
else:
|
|
login.initRequest(get_field('LAREQ'), lasso.HTTP_METHOD_POST)
|
|
|
|
login.buildRequestMsg()
|
|
try:
|
|
soap_answer = self.soap_call(login.msgUrl, login.msgBody)
|
|
except soap.SOAPException:
|
|
return template.error_page(_('Failed to get Assertion from identity provider'))
|
|
try:
|
|
login.processResponseMsg(soap_answer)
|
|
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
|
|
if error[0] != lasso.LOGIN_ERROR_UNKNOWN_PRINCIPAL:
|
|
raise
|
|
t = self.proxy_auth_peer_cancelled(login)
|
|
if t:
|
|
return t
|
|
session = get_session()
|
|
login = lasso.Login.newFromDump(get_lasso_server(), session.lasso_login_dump)
|
|
session.lasso_login_dump = None
|
|
return self.sso_after_authentication(login, False, proxied = True)
|
|
else:
|
|
self.proxy_auth_ok(login)
|
|
else:
|
|
login.processAuthnResponseMsg(get_field('LARES'))
|
|
session = get_session()
|
|
if session.lasso_proxy_session_dump:
|
|
login.setSessionFromDump(session.lasso_proxy_session_dump)
|
|
try:
|
|
common.SessionIndex(login.response.assertion[0].authenticationStatement.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(), 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'] + '/')
|
|
|
|
|
|
@common.soap_endpoint
|
|
def proxySoapEndpoint(self):
|
|
"""handles SOAP message coming from an IdP we act as proxy for"""
|
|
soap_message = self.get_soap_message()
|
|
request_type = lasso.getRequestTypeFromSoapMsg(soap_message)
|
|
|
|
if request_type == lasso.REQUEST_TYPE_LOGOUT:
|
|
return self.proxy_soap_logout(soap_message)
|
|
if request_type == lasso.REQUEST_TYPE_DEFEDERATION:
|
|
return self.proxy_soap_fedterm(soap_message)
|
|
|
|
def proxy_soap_logout(self, soap_message):
|
|
server = authentic.misc.get_lasso_server(lasso.PROVIDER_ROLE_SP)
|
|
logout = lasso.Logout(server)
|
|
logout.processRequestMsg(soap_message)
|
|
name_identifier = logout.nameIdentifier.content
|
|
authentic.identities.get_store().load_identities()
|
|
try:
|
|
identity = authentic.identities.get_store().get_identity_for_name_identifier(name_identifier)
|
|
if identity and identity.lasso_proxy_dump:
|
|
logout.setIdentityFromDump(identity.lasso_proxy_dump)
|
|
if logout.request.sessionIndex:
|
|
session_index = common.SessionIndex.get(logout.request.sessionIndex)
|
|
sessions = [ get_session_manager().get(session_index.session_id) ]
|
|
else:
|
|
sessions = authentic.sessions.BasicSession.get_sessions_by_user(identity.id)
|
|
except KeyError:
|
|
# no session, build straight failure answer
|
|
logout.buildResponseMsg()
|
|
return logout.msgBody
|
|
|
|
try:
|
|
logout.validateRequest()
|
|
except lasso.DsSignatureError:
|
|
pass
|
|
else:
|
|
for session in sessions:
|
|
self.slo_idp(session, method = lasso.HTTP_METHOD_SOAP)
|
|
|
|
session_manager = get_session_manager()
|
|
del session_manager[session.id]
|
|
logout.buildResponseMsg()
|
|
return logout.msgBody
|
|
|
|
def proxy_soap_fedterm(self, soap_message):
|
|
server = authentic.misc.get_lasso_server(lasso.PROVIDER_ROLE_SP)
|
|
defederation = lasso.Defederation(server)
|
|
defederation.processNotificationMsg(soap_message)
|
|
name_identifier = defederation.nameIdentifier.content
|
|
|
|
try:
|
|
identity = authentic.identities.get_store().get_identity_for_name_identifier(name_identifier)
|
|
except KeyError:
|
|
identity = None # XXX: should not happen
|
|
|
|
for session in get_session_manager().values():
|
|
if name_identifier in (session.name_identifiers or []):
|
|
get_logger().info('FedTerm/SOAP/Proxy from %s' % defederation.remoteProviderId)
|
|
defederation.setSessionFromDump(session.lasso_proxy_session_dump)
|
|
break
|
|
else:
|
|
pass
|
|
|
|
if identity and identity.lasso_proxy_dump:
|
|
defederation.setIdentityFromDump(identity.lasso_proxy_dump)
|
|
|
|
try:
|
|
defederation.validateNotification()
|
|
except lasso.Error, error:
|
|
pass # ignore failure (?)
|
|
else:
|
|
if not defederation.identity:
|
|
# if it was the last federation the whole identity dump collapsed
|
|
identity.lasso_proxy_dump = None
|
|
else:
|
|
identity.lasso_proxy_dump = defederation.identity.dump()
|
|
authentic.identities.get_store().save(identity)
|
|
|
|
if defederation.isSessionDirty:
|
|
if session:
|
|
name_identifier = defederation.nameIdentifier.content
|
|
if name_identifier in (session.name_identifiers or []):
|
|
session.name_identifiers.remove(name_identifier)
|
|
|
|
if defederation.session:
|
|
session.lasso_proxy_session_dump = defederation.session.dump()
|
|
else:
|
|
session.lasso_proxy_session_dump = None
|
|
|
|
# and now, fedterm with SP, every SP.
|
|
server = get_lasso_server()
|
|
if identity and identity.lasso_dump:
|
|
lasso_identity = lasso.Identity.newFromDump(identity.lasso_dump)
|
|
provider_ids = lasso_identity.providerIds or []
|
|
else:
|
|
provider_ids = []
|
|
for k in provider_ids:
|
|
provider = server.getProvider(k)
|
|
if not provider:
|
|
continue
|
|
defederation = lasso.Defederation(server)
|
|
if identity.lasso_dump:
|
|
defederation.setIdentityFromDump(identity.lasso_dump)
|
|
if session.lasso_session_dump:
|
|
defederation.setSessionFromDump(session.lasso_session_dump)
|
|
try:
|
|
defederation.initNotification(provider.providerId, lasso.HTTP_METHOD_SOAP)
|
|
except lasso.Error, error:
|
|
if error[0] == lasso.PROFILE_ERROR_FEDERATION_NOT_FOUND:
|
|
raise
|
|
elif error[0] == lasso.PROFILE_ERROR_UNSUPPORTED_PROFILE:
|
|
continue # shit, ignore for now.
|
|
else:
|
|
raise
|
|
|
|
defederation.buildNotificationMsg()
|
|
if not defederation.identity:
|
|
identity.lasso_dump = None
|
|
else:
|
|
identity.lasso_dump = defederation.identity.dump()
|
|
|
|
if session:
|
|
if not defederation.session:
|
|
session.lasso_session_dump = None
|
|
else:
|
|
session.lasso_session_dump = defederation.session.dump()
|
|
name_identifier = defederation.nameIdentifier.content
|
|
if name_identifier in (session.name_identifiers or []):
|
|
session.name_identifiers.remove(name_identifier)
|
|
|
|
try:
|
|
self.soap_call(defederation.msgUrl, defederation.msgBody)
|
|
except soap.SOAPException:
|
|
pass
|
|
|
|
authentic.identities.get_store().save(identity)
|
|
|
|
get_session_manager().commit_changes(session)
|
|
response = get_response()
|
|
response.set_status(204)
|
|
return ''
|
|
|
|
|
|
def proxySingleLogout(self, session = None):
|
|
request = get_request()
|
|
if not lasso.isLibertyQuery(request.get_query()):
|
|
if not session:
|
|
session = get_session()
|
|
server = authentic.misc.get_lasso_server(lasso.PROVIDER_ROLE_SP)
|
|
logout = lasso.Logout(server)
|
|
if session and session.lasso_proxy_session_dump:
|
|
logout.setSessionFromDump(session.lasso_proxy_session_dump)
|
|
try:
|
|
identity = authentic.identities.get_store().get_identity(session.user)
|
|
except KeyError:
|
|
pass
|
|
else:
|
|
if identity.lasso_proxy_dump:
|
|
logout.setIdentityFromDump(identity.lasso_proxy_dump)
|
|
|
|
try:
|
|
logout.initRequest(None, lasso.HTTP_METHOD_REDIRECT)
|
|
except lasso.Error, error:
|
|
session.lasso_proxy_session_dump = None
|
|
if error[0] == lasso.PROFILE_ERROR_NAME_IDENTIFIER_NOT_FOUND:
|
|
return redirect('/logout')
|
|
if error[0] == lasso.PROFILE_ERROR_SESSION_NOT_FOUND:
|
|
return redirect('/logout')
|
|
raise
|
|
logout.buildRequestMsg()
|
|
return redirect(logout.msgUrl)
|
|
else:
|
|
server = authentic.misc.get_lasso_server(lasso.PROVIDER_ROLE_SP)
|
|
logout = lasso.Logout(server)
|
|
logout.processRequestMsg(request.get_query())
|
|
return self.proxy_slo_redirect(logout, get_session())
|
|
|
|
def proxySingleLogoutReturn(self):
|
|
session = get_session()
|
|
session.lasso_proxy_session_dump = None
|
|
get_session_manager().expire_session()
|
|
if session.after_url:
|
|
after_url = session.after_url
|
|
session.after_url = None
|
|
return redirect(after_url)
|
|
return redirect(get_request().environ['SCRIPT_NAME'] + '/logout')
|
|
|
|
|
|
def proxy_slo_soap(self, session, identity):
|
|
# slo request from SP, continue with SOAP logout request to proxied-IdP
|
|
server = authentic.misc.get_lasso_server(lasso.PROVIDER_ROLE_SP)
|
|
logout = lasso.Logout(server)
|
|
if session and session.lasso_proxy_session_dump:
|
|
logout.setSessionFromDump(session.lasso_proxy_session_dump)
|
|
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:
|
|
return # ignore
|
|
try:
|
|
logout.processResponseMsg(soap_answer)
|
|
except lasso.Error, error:
|
|
if error[0] not in (lasso.LOGOUT_ERROR_UNSUPPORTED_PROFILE,
|
|
lasso.LOGOUT_ERROR_FEDERATION_NOT_FOUND):
|
|
raise
|
|
# XXX: it should actually answer back
|
|
pass
|
|
if logout.isSessionDirty:
|
|
if logout.session:
|
|
session.lasso_proxy_session_dump = logout.session.dump()
|
|
else:
|
|
session.lasso_proxy_session_dump = ''
|
|
get_session_manager().commit_changes(session)
|
|
|
|
|
|
def proxy_slo_redirect(self, logout, session):
|
|
logout_idp = lasso.Logout(get_lasso_server())
|
|
|
|
if session:
|
|
if session.lasso_proxy_session_dump:
|
|
logout.setSessionFromDump(session.lasso_proxy_session_dump)
|
|
if session.lasso_session_dump:
|
|
logout_idp.setSessionFromDump(session.lasso_session_dump)
|
|
|
|
authentic.identities.get_store().load_identities()
|
|
try:
|
|
identity = authentic.identities.get_store().get_identity(session.user)
|
|
except KeyError:
|
|
pass
|
|
else:
|
|
if identity.lasso_proxy_dump:
|
|
logout.setIdentityFromDump(identity.lasso_proxy_dump)
|
|
if identity.lasso_dump:
|
|
logout_idp.setIdentityFromDump(identity.lasso_dump)
|
|
|
|
try:
|
|
logout.validateRequest()
|
|
except lasso.Error, error:
|
|
if error[0] == lasso.PROFILE_ERROR_SESSION_NOT_FOUND:
|
|
pass
|
|
elif error[0] < 0:
|
|
raise
|
|
pass
|
|
logout.buildResponseMsg()
|
|
session.after_url = logout.msgUrl
|
|
return self.slo_idp_get(logout_idp, url = 'proxySingleLogoutNext')
|
|
|
|
def proxySingleLogoutNext(self):
|
|
url = get_session().after_url
|
|
get_session_manager().expire_session()
|
|
return redirect(url)
|
|
|
|
|
|
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')['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
|
|
|
|
|
|
|
|
class SpUI(AccessControlled, Directory):
|
|
_q_exports = ['terminate', 'login', 'proxy_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
|
|
|
|
def init_provider(self, role = lasso.PROVIDER_ROLE_SP):
|
|
try:
|
|
lp = get_cfg('providers')[self.provider_key]
|
|
except KeyError:
|
|
raise errors.TraversalError()
|
|
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)
|
|
|
|
def login(self, relay_state=None):
|
|
self.init_provider()
|
|
login = lasso.Login(get_lasso_server())
|
|
login.initIdpInitiatedAuthnRequest(self.p.providerId)
|
|
if relay_state:
|
|
login.msgRelayState = relay_state
|
|
get_logger().info('SSO to %s' % self.p.providerId)
|
|
login.request.protocolProfile = lasso.LIB_PROTOCOL_PROFILE_BRWS_ART
|
|
login.processAuthnRequestMsg(None)
|
|
rt = RootDirectory()
|
|
return rt.sso_after_consent(login, True, True)
|
|
|
|
def terminate(self):
|
|
self.init_provider()
|
|
defederation = lasso.Defederation(get_lasso_server())
|
|
session = get_session()
|
|
authentic.identities.get_store().load_identities()
|
|
try:
|
|
identity = authentic.identities.get_store().get_identity(session.user)
|
|
except KeyError:
|
|
identity = None
|
|
if identity and identity.lasso_dump:
|
|
defederation.setIdentityFromDump(identity.lasso_dump)
|
|
if session and session.lasso_session_dump:
|
|
defederation.setSessionFromDump(session.lasso_session_dump)
|
|
|
|
get_logger().info('fedterm to %s' % self.p.providerId)
|
|
try:
|
|
defederation.initNotification(self.p.providerId, lasso.HTTP_METHOD_SOAP)
|
|
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:
|
|
try:
|
|
defederation.initNotification(self.p.providerId, lasso.HTTP_METHOD_REDIRECT)
|
|
except lasso.Error, error:
|
|
if error[0] == lasso.PROFILE_ERROR_UNSUPPORTED_PROFILE:
|
|
# XXX: perhaps it could remove the federation locally,
|
|
# ignoring the remote provider?
|
|
return template.error_page(
|
|
_("This service doesn't support federation termination"))
|
|
raise
|
|
else:
|
|
raise
|
|
|
|
defederation.buildNotificationMsg()
|
|
name_identifier = defederation.nameIdentifier.content
|
|
if not defederation.identity:
|
|
# if it was the last federation the whole identity dump collapsed
|
|
identity.lasso_dump = None
|
|
else:
|
|
identity.lasso_dump = defederation.identity.dump()
|
|
authentic.identities.get_store().save(identity)
|
|
if name_identifier in (session.name_identifiers or []):
|
|
session.name_identifiers.remove(name_identifier)
|
|
|
|
if defederation.msgBody:
|
|
try:
|
|
self.soap_call(defederation.msgUrl, defederation.msgBody)
|
|
except soap.SOAPException:
|
|
return template.error_page(_("Service provider failed to process request."))
|
|
else:
|
|
return redirect(defederation.msgUrl)
|
|
|
|
return redirect('../../..')
|
|
|
|
def proxy_terminate(self):
|
|
self.init_provider(lasso.PROVIDER_ROLE_IDP)
|
|
defederation = lasso.Defederation(get_lasso_server(lasso.PROVIDER_ROLE_SP))
|
|
session = get_session()
|
|
authentic.identities.get_store().load_identities()
|
|
try:
|
|
identity = authentic.identities.get_store().get_identity(session.user)
|
|
except KeyError:
|
|
identity = None
|
|
if identity and identity.lasso_proxy_dump:
|
|
defederation.setIdentityFromDump(identity.lasso_proxy_dump)
|
|
if session and session.lasso_session_dump:
|
|
defederation.setSessionFromDump(session.lasso_session_dump)
|
|
|
|
get_logger().info('fedterm to %s' % self.p.providerId)
|
|
try:
|
|
defederation.initNotification(self.p.providerId, lasso.HTTP_METHOD_SOAP)
|
|
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:
|
|
try:
|
|
defederation.initNotification(self.p.providerId, lasso.HTTP_METHOD_REDIRECT)
|
|
except lasso.Error, error:
|
|
if error[0] == lasso.PROFILE_ERROR_UNSUPPORTED_PROFILE:
|
|
# XXX: perhaps it could remove the federation locally,
|
|
# ignoring the remote provider?
|
|
return template.error_page(
|
|
_("This service doesn't support federation termination"))
|
|
raise
|
|
else:
|
|
raise
|
|
|
|
defederation.buildNotificationMsg()
|
|
name_identifier = defederation.nameIdentifier.content
|
|
if not defederation.identity:
|
|
# if it was the last federation the whole identity dump collapsed
|
|
identity.lasso_proxy_dump = None
|
|
else:
|
|
identity.lasso_proxy_dump = defederation.identity.dump()
|
|
authentic.identities.get_store().save(identity)
|
|
if name_identifier in (session.name_identifiers or []):
|
|
session.name_identifiers.remove(name_identifier)
|
|
|
|
if defederation.msgBody:
|
|
try:
|
|
self.soap_call(defederation.msgUrl, defederation.msgBody)
|
|
except soap.SOAPException:
|
|
return template.error_page(_('Remote identity provider failed to process request.'))
|
|
else:
|
|
return redirect(defederation.msgUrl)
|
|
|
|
return redirect('../../..')
|
|
|