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