1# -*- coding: iso-8859-1 -*- 2""" 3 MoinMoin - CAS authentication 4 5 Jasig CAS (see http://www.jasig.org/cas) authentication module. 6 7 @copyright: 2009 MoinMoin:RichardLiao 8 @license: GNU GPL, see COPYING for details. 9""" 10 11import time, re 12import urlparse 13import urllib, urllib2 14 15from MoinMoin import log 16logging = log.getLogger(__name__) 17 18from MoinMoin.auth import BaseAuth 19from MoinMoin import user, wikiutil 20 21 22class PyCAS(object): 23 """A class for working with a CAS server.""" 24 25 def __init__(self, server_url, renew=False, login_path='/login', logout_path='/logout', 26 validate_path='/validate', coding='utf-8'): 27 self.server_url = server_url 28 self.renew = renew 29 self.login_path = login_path 30 self.logout_path = logout_path 31 self.validate_path = validate_path 32 self.coding = coding 33 34 def login_url(self, service): 35 """Return the login URL for the given service.""" 36 url = self.server_url + self.login_path + '?service=' + urllib.quote_plus(service) 37 if self.renew: 38 url += "&renew=true" 39 return url 40 41 def logout_url(self, redirect_url=None): 42 """Return the logout URL.""" 43 url = self.server_url + self.logout_path 44 if redirect_url: 45 url += '?url=' + urllib.quote_plus(redirect_url) 46 return url 47 48 def validate_url(self, service, ticket): 49 """Return the validation URL for the given service. (For CAS 1.0)""" 50 url = self.server_url + self.validate_path + '?service=' + urllib.quote_plus(service) + '&ticket=' + urllib.quote_plus(ticket) 51 if self.renew: 52 url += "&renew=true" 53 return url 54 55 def validate_ticket(self, service, ticket): 56 """Validate the given ticket against the given service.""" 57 f = urllib2.urlopen(self.validate_url(service, ticket)) 58 valid = f.readline() 59 valid = valid.strip() == 'yes' 60 user = f.readline().strip() 61 user = user.decode(self.coding) 62 return valid, user 63 64 65class CASAuth(BaseAuth): 66 """ handle login from CAS """ 67 name = 'CAS' 68 login_inputs = ['username', 'password'] 69 logout_possible = True 70 71 def __init__(self, auth_server, login_path="/login", logout_path="/logout", validate_path="/validate"): 72 BaseAuth.__init__(self) 73 self.cas = PyCAS(auth_server, login_path=login_path, 74 validate_path=validate_path, logout_path=logout_path) 75 76 def request(self, request, user_obj, **kw): 77 ticket = request.args.get('ticket') 78 action = request.args.get("action", []) 79 logoutRequest = request.args.get('logoutRequest', []) 80 url = request.url_root + urllib.quote_plus(request.path.encode('utf-8')) 81 82 # # handle logout request from CAS 83 # if logoutRequest: 84 # logoutRequestMatch = re.search("<samlp:SessionIndex>(.*)</samlp:SessionIndex>", logoutRequest[0]) 85 # service_ticket = logoutRequestMatch.group(1) 86 # if service_ticket: 87 # # TODO: logout 88 # return self.logout(request, user_obj) 89 90 # authenticated user 91 if user_obj and user_obj.valid: 92 return user_obj, True 93 94 # anonymous 95 if not ticket and not "login" in action: 96 return user_obj, True 97 98 # valid ticket on CAS 99 if ticket: 100 valid, username = self.cas.validate_ticket(url, ticket[0]) 101 if valid: 102 u = user.User(request, auth_username=username, auth_method=self.name) 103 u.valid = valid 104 # auto create user 105 u.create_or_update(True) 106 return u, True 107 108 # login 109 request.http_redirect(self.cas.login_url(url)) 110 111 return user_obj, True 112 113 def logout(self, request, user_obj, **kw): 114 if self.name and user_obj and user_obj.auth_method == self.name: 115 url = request.url_root + urllib.quote_plus(request.path.encode('utf-8')) 116 request.http_redirect(self.cas.logout_url(url)) 117 118 user_obj.valid = False 119 120 return user_obj, True 121 122