diff --git a/wiki/auth/cas.py b/wiki/auth/cas.py index 12f50580..6a57db06 100644 --- a/wiki/auth/cas.py +++ b/wiki/auth/cas.py @@ -9,9 +9,12 @@ """ import sys +import os import time, re import urlparse import urllib, urllib2 +from lxml import etree +from lxml.etree import XMLSyntaxError from MoinMoin import log logging = log.getLogger(__name__) @@ -19,7 +22,6 @@ logging = log.getLogger(__name__) from MoinMoin.auth import BaseAuth from MoinMoin import user, wikiutil - class PyCAS(object): """A class for working with a CAS server.""" @@ -53,6 +55,14 @@ class PyCAS(object): url += "&renew=true" return url + def singlesignout(self, callback, body): + try: + nodes = etree.fromstring(body).xpath("/samlp:LogoutRequest/samlp:SessionIndex", namespaces={'samlp' : 'urn:oasis:names:tc:SAML:2.0:protocol'}) + for node in nodes: + callback(node.text) + except XMLSyntaxError: + pass + def validate_ticket(self, service, ticket): """Validate the given ticket against the given service.""" f = urllib2.urlopen(self.validate_url(service, ticket)) @@ -68,13 +78,14 @@ class CASAuth(BaseAuth): login_inputs = ['username', 'password'] logout_possible = True - def __init__(self, auth_server, login_path="/login", logout_path="/logout", validate_path="/validate", action="login_cas", create_user=False, fallback_url=None): + def __init__(self, auth_server, login_path="/login", logout_path="/logout", validate_path="/validate", action="login_cas", create_user=False, fallback_url=None, ticket_path=None): BaseAuth.__init__(self) self.cas = PyCAS(auth_server, login_path=login_path, validate_path=validate_path, logout_path=logout_path) self.action = action self.create_user = create_user self.fallback_url = fallback_url + self.ticket_path = ticket_path def request(self, request, user_obj, **kw): ticket = request.args.get("ticket", "") @@ -84,10 +95,46 @@ class CASAuth(BaseAuth): p = urlparse.urlparse(request.url) url = urlparse.urlunparse(('https', p.netloc, p.path, "", "", "")) + def store_ticket(ticket, username): + with open(self.ticket_path + ticket, 'w') as f: + f.write(username) + + def username_of_ticket(ticket): + try: + with open(self.ticket_path + ticket) as f: + username = f.read() + os.remove(self.ticket_path + ticket) + return username + except IOError: + return None + + def logout_user(ticket): + username = username_of_ticket(ticket) + if username: + u = user.User(request, None, username) + checks = [] + if u.exists(): + def user_matches(session): + try: + return session['user.id'] == u.id + except KeyError: + return False + session_service = request.cfg.session_service + for sid in session_service.get_all_session_ids(request): + session = session_service.get_session(request, sid) + + if user_matches(session): + session_service.destroy_session(request, session) + # authenticated user if not force and user_obj and user_obj.valid: return user_obj, True + if self.ticket_path and request.method == 'POST': + logoutRequest=request.form.get('logoutRequest', None) + if logoutRequest is not None: + self.cas.singlesignout(logout_user, logoutRequest) + # anonymous if not ticket and not self.action == action: return user_obj, True @@ -105,6 +152,8 @@ class CASAuth(BaseAuth): u.valid = u.exists() if self.fallback_url and not u.valid: request.http_redirect("%s?action=%s&wiki_url=%s" % (self.fallback_url, self.action, url)) + if u.valid: + store_ticket(ticket, username) return u, True # login