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 """ 6 CAS 1.0 Authentication 7 8 The Central Authentication System is a straight-forward single sign-on 9 mechanism developed by Yale University's ITS department. It has since 10 enjoyed widespread success and is deployed at many major universities 11 and some corporations. 12 13 https://clearinghouse.ja-sig.org/wiki/display/CAS/Home 14 http://www.yale.edu/tp/auth/usingcasatyale.html 15 16 This implementation has the goal of maintaining current path arguments 17 passed to the system so that it can be used as middleware at any stage 18 of processing. It has the secondary goal of allowing for other 19 authentication methods to be used concurrently. 20 """ 21 from six.moves.urllib.parse import urlencode 22 from paste.request import construct_url 23 from paste.httpexceptions import HTTPSeeOther, HTTPForbidden 24 25 class CASLoginFailure(HTTPForbidden): 26 """ The exception raised if the authority returns 'no' """ 27 28 class CASAuthenticate(HTTPSeeOther): 29 """ The exception raised to authenticate the user """ 30 31 def 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 89 middleware = AuthCASHandler 90 91 __all__ = ['CASLoginFailure', 'CASAuthenticate', 'AuthCASHandler' ] 92 93 if '__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