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.
larpe/larpe/branches/idwsf/larpe/liberty.ptl

431 lines
18 KiB
Plaintext

import libxml2
import urllib
import urlparse
import httplib
import re
import os
from quixote import get_field, get_request, get_response, get_session, get_session_manager, redirect
from quixote.directory import Directory
from quixote.http_request import parse_header
import lasso
from qommon import get_logger
from qommon.form import *
from qommon.template import *
import misc
import storage
from users import User
from hosts import Host
from federations import Federation
import site_authentication
class Liberty(Directory):
_q_exports = ['', 'login', 'assertionConsumer', 'soapEndpoint',
'singleLogout', 'singleLogoutReturn',
'federationTermination', 'federationTerminationReturn',
('metadata.xml', 'metadata'), 'public_key', 'local_auth']
def perform_login(self, idp = None):
server = misc.get_lasso_server()
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'
login.buildAuthnRequestMsg()
return redirect(login.msgUrl)
def assertionConsumer(self):
server = misc.get_lasso_server()
if not server:
return error_page(_('Liberty support is not yet configured'))
login = lasso.Login(server)
request = get_request()
if request.get_method() == 'GET' or get_field('LAREQ'):
if request.get_method() == 'GET':
login.initRequest(request.get_query(), lasso.HTTP_METHOD_REDIRECT)
else:
login.initRequest(get_field('LAREQ'), lasso.HTTP_METHOD_POST)
login.buildRequestMsg()
try:
soap_answer = soap_call(login.msgUrl, login.msgBody)
except SOAPException:
return error_page(_('Failure to communicate with identity provider'))
try:
login.processResponseMsg(soap_answer)
except lasso.Error, error:
if error[0] == lasso.LOGIN_ERROR_STATUS_NOT_SUCCESS:
return error_page(_('Unknown authentication failure'))
if hasattr(lasso, 'LOGIN_ERROR_UNKNOWN_PRINCIPAL'):
if error[0] == lasso.LOGIN_ERROR_UNKNOWN_PRINCIPAL:
return error_page(_('Authentication failure; unknown principal'))
return error_page(_("Identity Provider didn't accept artifact transaction."))
else:
login.processAuthnResponseMsg(get_field('LARES'))
login.acceptSso()
session = get_session()
if login.isSessionDirty:
if login.session:
session.lasso_session_dumps[server.providerId] = login.session.dump()
else:
session.lasso_session_dumps[server.providerId] = None
# Look for an existing user
user = self.lookup_user(session, login)
# Check if it is for Larpe administration or token
host = Host.get_host_from_url()
if host is None:
return redirect('%s/' % get_request().environ['SCRIPT_NAME'])
if host.name == 'larpe':
session.name_identifier = login.nameIdentifier.content
session.lasso_dump = login.identity.dump()
session.provider_id = server.providerId
if user:
session.set_user(user.id, server.providerId)
if hasattr(session, 'after_url') and session.after_url is not None and \
session.after_url.find('admin') != -1:
return redirect(session.after_url)
else:
if not user or not user.is_admin:
if hasattr(session, 'after_url') and session.after_url is not None \
and session.after_url.find('token') != -1:
return redirect(session.after_url)
return redirect('%s/token' % get_request().environ['SCRIPT_NAME'])
return redirect('%s/admin/' % get_request().environ['SCRIPT_NAME'])
# Set session user
if not user:
user = User()
user.name_identifiers = [ login.nameIdentifier.content ]
user.lasso_dumps = [ login.identity.dump() ]
user.store()
session.set_user(user.id, server.providerId)
# elif request.user is not None:
# user = request.user
# session.set_user(user.id)
# else:
# session.set_user('anonymous-%s' % login.nameIdentifier.content)
# user = session.get_user(login.nameIdentifier.content)
# if not user.name_identifiers.has_key(server.providerId):
# user.name_identifiers[server.providerId] = [ login.nameIdentifier.content ]
# elif not login.nameIdentifier.content in user.name_identifiers[server.providerId]:
# user.name_identifiers[server.providerId].append(login.nameIdentifier.content)
# user.lasso_dumps[server.providerId] = login.identity.dump()
# user.store()
# request.user = user
federations = Federation.select(lambda x: host.id == x.host_id \
and user.name_identifiers[0] in x.name_identifiers)
if federations:
return site_authentication.get_site_authentication(host).sso_local_login(federations[0])
else:
if hasattr(session, 'token'):
# FIXME : this method doesn't exist anymore
self.federate_token(session.token)
else:
response = get_response()
if session.after_url:
after_url = session.after_url
session.after_url = None
return redirect(after_url)
response.set_status(303)
response.headers['location'] = urlparse.urljoin(request.get_url(), str('local_auth'))
response.content_type = 'text/plain'
return 'Your browser should redirect you'
def lookup_user(self, session, login):
found_users = list(User.select(lambda x: login.nameIdentifier.content in x.name_identifiers))
if found_users:
return found_users[0]
return None
def singleLogout(self):
request = get_request()
logout = lasso.Logout(misc.get_lasso_server())
if lasso.isLibertyQuery(request.get_query()):
try:
logout.processRequestMsg(request.get_query())
except lasso.Error, error:
if error[0] == lasso.DS_ERROR_INVALID_SIGNATURE:
return error_page(_('Failed to check single logout request signature.'))
raise
session = get_session()
if not session.id:
# session has not been found, this may be because the user has
# its browser configured so that cookies are not sent for
# remote queries and IdP is using image-based SLO.
# so we look up a session with the appropriate name identifier
for session in get_session_manager().values():
# This block differs from qommon
user = session.get_user(logout.server.providerId)
if user and logout.nameIdentifier.content in user.name_identifiers:
break
else:
session = get_session()
return self.slo_idp(logout, session)
else:
return self.slo_sp(logout, get_session())
def singleLogoutReturn(self):
logout = lasso.Logout(misc.get_lasso_server())
host = Host.get_host_from_url()
if host is None:
return redirect('%s/' % get_request().environ['SCRIPT_NAME'])
try:
logout.processResponseMsg(get_request().get_query())
except lasso.Error, error:
if error[0] == lasso.PROFILE_ERROR_INVALID_QUERY:
raise AccessError()
if error[0] == lasso.DS_ERROR_INVALID_SIGNATURE:
return error_page(_('Failed to check single logout request signature.'))
if hasattr(lasso, 'LOGOUT_ERROR_REQUEST_DENIED') and \
error[0] == lasso.LOGOUT_ERROR_REQUEST_DENIED:
return redirect(host.get_root_url()) # ignore silently
elif error[0] == lasso.ERROR_UNDEFINED:
# XXX: unknown status; ignoring for now.
return redirect(host.get_root_url()) # ignore silently
raise
return redirect(host.get_root_url())
def slo_idp(self, logout, session):
'''Single Logout initiated by IdP'''
# This block differs from qommon
if session.lasso_session_dumps.has_key(logout.server.providerId):
logout.setSessionFromDump(session.lasso_session_dumps[logout.server.providerId])
user = session.get_user(logout.server.providerId)
if user and user.lasso_dumps:
logout.setIdentityFromDump(user.lasso_dumps[0])
if user and logout.nameIdentifier.content not in user.name_identifiers:
raise 'No appropriate name identifier in user (%s and %s)' % (
logout.nameIdentifier.content, user.name_identifiers)
host = Host.get_host_with_provider_id(logout.server.providerId)
if host is not None:
site_authentication.get_site_authentication(host).local_logout(user=user)
try:
logout.validateRequest()
except lasso.Error, error:
if error[0] != lasso.PROFILE_ERROR_SESSION_NOT_FOUND:
raise
else:
get_session_manager().expire_session(logout.server.providerId)
logout.buildResponseMsg()
if logout.msgBody: # soap answer
return logout.msgBody
else:
return redirect(logout.msgUrl)
def slo_sp(self, logout, session):
host = Host.get_host_from_url()
if host is None:
return redirect('%s/' % get_request().environ['SCRIPT_NAME'])
if not session.id or not session.users.has_key(logout.server.providerId) \
or not session.lasso_session_dumps.has_key(logout.server.providerId):
get_session_manager().expire_session(logout.server.providerId)
return redirect(host.get_root_url())
logout.setSessionFromDump(session.lasso_session_dumps[logout.server.providerId])
user = session.get_user(logout.server.providerId)
site_authentication.get_site_authentication(host).local_logout(user=user)
if user and user.lasso_dumps:
logout.setIdentityFromDump(user.lasso_dumps[0])
return self.slo_sp_redirect(logout, host)
def slo_sp_redirect(self, logout, host):
try:
logout.initRequest(None, lasso.HTTP_METHOD_REDIRECT)
except lasso.Error, error:
if error[0] == lasso.PROFILE_ERROR_NAME_IDENTIFIER_NOT_FOUND:
get_session_manager().expire_session()
return redirect(host.get_root_url())
raise
logout.buildRequestMsg()
get_session_manager().expire_session(logout.server.providerId)
return redirect(logout.msgUrl)
def soapEndpoint(self):
request = get_request()
ctype = request.environ.get('CONTENT_TYPE')
if not ctype:
return
ctype, ctype_params = parse_header(ctype)
if ctype != 'text/xml':
return
response = get_response()
response.set_content_type('text/xml')
length = int(request.environ.get('CONTENT_LENGTH'))
soap_message = request.stdin.read(length)
request_type = lasso.getRequestTypeFromSoapMsg(soap_message)
if request_type == lasso.REQUEST_TYPE_LOGOUT:
logout = lasso.Logout(misc.get_lasso_server())
logout.processRequestMsg(soap_message)
name_identifier = logout.nameIdentifier.content
for session in get_session_manager().values():
user = session.get_user(logout.server.providerId)
if user and logout.nameIdentifier.content in user.name_identifiers:
break
else:
session = None
return self.slo_idp(logout, session)
if request_type == lasso.REQUEST_TYPE_DEFEDERATION:
defederation = lasso.Defederation(misc.get_lasso_server())
defederation.processNotificationMsg(soap_message)
for session in get_session_manager().values():
user = session.get_user(defederation.server.providerId)
if user and defederation.nameIdentifier.content in user.name_identifiers:
break
else:
session = None
return self.fedterm(defederation, session)
def federationTermination(self):
request = get_request()
if not lasso.isLibertyQuery(request.get_query()):
return redirect('%s/' % get_request().environ['SCRIPT_NAME'])
defederation = lasso.Defederation(misc.get_lasso_server())
defederation.processNotificationMsg(request.get_query())
return self.fedterm(defederation, get_session())
def fedterm(self, defederation, session):
if session is not None:
host = Host.get_host_with_provider_id(defederation.server.providerId)
if host is not None:
site_authentication.get_site_authentication(host).local_defederate(session, defederation.server.providerId)
if session.lasso_session_dumps.has_key(defederation.server.providerId):
defederation.setSessionFromDump(session.lasso_session_dumps[defederation.server.providerId])
user = session.get_user(defederation.server.providerId)
if user and user.lasso_dumps:
defederation.setIdentityFromDump(user.lasso_dumps[0])
else:
user = None
try:
defederation.validateNotification()
except lasso.Error, error:
pass # ignore failure (?)
else:
if user:
if not defederation.identity:
# if it was the last federation the whole identity dump collapsed
del user.lasso_dumps[0]
else:
user.lasso_dumps[0] = defederation.identity.dump()
user.store()
if user and defederation.nameIdentifier.content:
user.remove_name_identifier(defederation.server.providerId, defederation.nameIdentifier.content)
user.store()
if defederation.isSessionDirty and session is not None:
if not defederation.session:
del session.lasso_session_dumps[defederation.server.providerId]
else:
session.lasso_session_dumps[defederation.server.providerId] = defederation.session.dump()
session.store()
get_session_manager().expire_session(defederation.server.providerId)
if defederation.msgUrl:
return redirect(defederation.msgUrl)
else:
response = get_response()
response.set_status(204)
return ''
def federationTerminationReturn(self):
host = Host.get_host_from_url()
if host is None:
return redirect('%s/' % get_request().environ['SCRIPT_NAME'])
return redirect(host.get_return_url())
# def defederate(self, idp = None):
# session = get_session()
# user = get_request().user
# server = misc.get_lasso_server()
# self.local_defederate(session, server.providerId)
#
# defederation = lasso.Defederation(server)
# defederation.setSessionFromDump(session.lasso_session_dumps[defederation.server.providerId])
# if user and user.lasso_dumps.has_key(defederation.server.providerId):
# defederation.setIdentityFromDump(user.lasso_dumps[defederation.server.providerId])
# defederation.initNotification(idp, lasso.HTTP_METHOD_SOAP);
# defederation.buildNotificationMsg();
# try:
# soap_call(defederation.msgUrl, defederation.msgBody);
# except SOAPException:
# return error_page(_('Failure to communicate with identity provider'))
# rootUrl = '/' + misc.get_proxied_site_name() + '/'
# return redirect(rootUrl)
def local_auth(self):
host = Host.get_host_from_url()
if host is None:
return redirect('%s/' % get_request().environ['SCRIPT_NAME'])
return site_authentication.get_site_authentication(host).local_auth
local_auth = property(local_auth)
def metadata(self):
host = Host.get_host_from_url()
if host is None:
return redirect('%s/' % get_request().environ['SCRIPT_NAME'])
get_response().set_content_type('text/xml', 'utf-8')
metadata = unicode(open(host.metadata).read(), 'utf-8')
return metadata
def public_key(self):
host = Host.get_host_from_url()
if host is None:
return redirect('%s/' % get_request().environ['SCRIPT_NAME'])
get_response().set_content_type('text/plain')
public_key = open(host.public_key).read()
return public_key
class SOAPException(Exception):
pass
def soap_call(url, msg):
if url.startswith('http://'):
host, query = urllib.splithost(url[5:])
conn = httplib.HTTPConnection(host)
else:
host, query = urllib.splithost(url[6:])
conn = httplib.HTTPSConnection(host)
conn.request('POST', query, msg, {'Content-Type': 'text/xml'})
response = conn.getresponse()
data = response.read()
conn.close()
if response.status not in (200, 204): # 204 ok for federation termination
get_logger().warn('SOAP error (%s) (on %s)' % (response.status, url))
raise SOAPException()
return data