1# (c) 2005 Clark C. Evans 2# This module is part of the Python Paste Project and is released under 3# the MIT License: http://www.opensource.org/licenses/mit-license.php 4# This code was written with funding by http://prometheusresearch.com 5""" 6CAS 1.0 Authentication 7 8The Central Authentication System is a straight-forward single sign-on 9mechanism developed by Yale University's ITS department. It has since 10enjoyed widespread success and is deployed at many major universities 11and some corporations. 12 13 https://clearinghouse.ja-sig.org/wiki/display/CAS/Home 14 http://www.yale.edu/tp/auth/usingcasatyale.html 15 16This implementation has the goal of maintaining current path arguments 17passed to the system so that it can be used as middleware at any stage 18of processing. It has the secondary goal of allowing for other 19authentication methods to be used concurrently. 20""" 21from six.moves.urllib.parse import urlencode 22from paste.request import construct_url 23from paste.httpexceptions import HTTPSeeOther, HTTPForbidden 24 25class CASLoginFailure(HTTPForbidden): 26 """ The exception raised if the authority returns 'no' """ 27 28class CASAuthenticate(HTTPSeeOther): 29 """ The exception raised to authenticate the user """ 30 31def AuthCASHandler(application, authority): 32 """ 33 middleware to implement CAS 1.0 authentication 34 35 There are several possible outcomes: 36 37 0. If the REMOTE_USER environment variable is already populated; 38 then this middleware is a no-op, and the request is passed along 39 to the application. 40 41 1. If a query argument 'ticket' is found, then an attempt to 42 validate said ticket /w the authentication service done. If the 43 ticket is not validated; an 403 'Forbidden' exception is raised. 44 Otherwise, the REMOTE_USER variable is set with the NetID that 45 was validated and AUTH_TYPE is set to "cas". 46 47 2. Otherwise, a 303 'See Other' is returned to the client directing 48 them to login using the CAS service. After logon, the service 49 will send them back to this same URL, only with a 'ticket' query 50 argument. 51 52 Parameters: 53 54 ``authority`` 55 56 This is a fully-qualified URL to a CAS 1.0 service. The URL 57 should end with a '/' and have the 'login' and 'validate' 58 sub-paths as described in the CAS 1.0 documentation. 59 60 """ 61 assert authority.endswith("/") and authority.startswith("http") 62 def cas_application(environ, start_response): 63 username = environ.get('REMOTE_USER','') 64 if username: 65 return application(environ, start_response) 66 qs = environ.get('QUERY_STRING','').split("&") 67 if qs and qs[-1].startswith("ticket="): 68 # assume a response from the authority 69 ticket = qs.pop().split("=", 1)[1] 70 environ['QUERY_STRING'] = "&".join(qs) 71 service = construct_url(environ) 72 args = urlencode( 73 {'service': service,'ticket': ticket}) 74 requrl = authority + "validate?" + args 75 result = urlopen(requrl).read().split("\n") 76 if 'yes' == result[0]: 77 environ['REMOTE_USER'] = result[1] 78 environ['AUTH_TYPE'] = 'cas' 79 return application(environ, start_response) 80 exce = CASLoginFailure() 81 else: 82 service = construct_url(environ) 83 args = urlencode({'service': service}) 84 location = authority + "login?" + args 85 exce = CASAuthenticate(location) 86 return exce.wsgi_application(environ, start_response) 87 return cas_application 88 89middleware = AuthCASHandler 90 91__all__ = ['CASLoginFailure', 'CASAuthenticate', 'AuthCASHandler' ] 92 93if '__main__' == __name__: 94 authority = "https://secure.its.yale.edu/cas/servlet/" 95 from paste.wsgilib import dump_environ 96 from paste.httpserver import serve 97 from paste.httpexceptions import * 98 serve(HTTPExceptionHandler( 99 AuthCASHandler(dump_environ, authority))) 100