Home | History | Annotate | Download | only in auth
      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