100 lines
3.9 KiB
Python
100 lines
3.9 KiB
Python
|
# (c) 2005 Clark C. Evans
|
||
|
# This module is part of the Python Paste Project and is released under
|
||
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||
|
# This code was written with funding by http://prometheusresearch.com
|
||
|
"""
|
||
|
CAS 1.0 Authentication
|
||
|
|
||
|
The Central Authentication System is a straight-forward single sign-on
|
||
|
mechanism developed by Yale University's ITS department. It has since
|
||
|
enjoyed widespread success and is deployed at many major universities
|
||
|
and some corporations.
|
||
|
|
||
|
https://clearinghouse.ja-sig.org/wiki/display/CAS/Home
|
||
|
http://www.yale.edu/tp/auth/usingcasatyale.html
|
||
|
|
||
|
This implementation has the goal of maintaining current path arguments
|
||
|
passed to the system so that it can be used as middleware at any stage
|
||
|
of processing. It has the secondary goal of allowing for other
|
||
|
authentication methods to be used concurrently.
|
||
|
"""
|
||
|
from six.moves.urllib.parse import urlencode
|
||
|
from paste.request import construct_url
|
||
|
from paste.httpexceptions import HTTPSeeOther, HTTPForbidden
|
||
|
|
||
|
class CASLoginFailure(HTTPForbidden):
|
||
|
""" The exception raised if the authority returns 'no' """
|
||
|
|
||
|
class CASAuthenticate(HTTPSeeOther):
|
||
|
""" The exception raised to authenticate the user """
|
||
|
|
||
|
def AuthCASHandler(application, authority):
|
||
|
"""
|
||
|
middleware to implement CAS 1.0 authentication
|
||
|
|
||
|
There are several possible outcomes:
|
||
|
|
||
|
0. If the REMOTE_USER environment variable is already populated;
|
||
|
then this middleware is a no-op, and the request is passed along
|
||
|
to the application.
|
||
|
|
||
|
1. If a query argument 'ticket' is found, then an attempt to
|
||
|
validate said ticket /w the authentication service done. If the
|
||
|
ticket is not validated; an 403 'Forbidden' exception is raised.
|
||
|
Otherwise, the REMOTE_USER variable is set with the NetID that
|
||
|
was validated and AUTH_TYPE is set to "cas".
|
||
|
|
||
|
2. Otherwise, a 303 'See Other' is returned to the client directing
|
||
|
them to login using the CAS service. After logon, the service
|
||
|
will send them back to this same URL, only with a 'ticket' query
|
||
|
argument.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
``authority``
|
||
|
|
||
|
This is a fully-qualified URL to a CAS 1.0 service. The URL
|
||
|
should end with a '/' and have the 'login' and 'validate'
|
||
|
sub-paths as described in the CAS 1.0 documentation.
|
||
|
|
||
|
"""
|
||
|
assert authority.endswith("/") and authority.startswith("http")
|
||
|
def cas_application(environ, start_response):
|
||
|
username = environ.get('REMOTE_USER','')
|
||
|
if username:
|
||
|
return application(environ, start_response)
|
||
|
qs = environ.get('QUERY_STRING','').split("&")
|
||
|
if qs and qs[-1].startswith("ticket="):
|
||
|
# assume a response from the authority
|
||
|
ticket = qs.pop().split("=", 1)[1]
|
||
|
environ['QUERY_STRING'] = "&".join(qs)
|
||
|
service = construct_url(environ)
|
||
|
args = urlencode(
|
||
|
{'service': service,'ticket': ticket})
|
||
|
requrl = authority + "validate?" + args
|
||
|
result = urlopen(requrl).read().split("\n")
|
||
|
if 'yes' == result[0]:
|
||
|
environ['REMOTE_USER'] = result[1]
|
||
|
environ['AUTH_TYPE'] = 'cas'
|
||
|
return application(environ, start_response)
|
||
|
exce = CASLoginFailure()
|
||
|
else:
|
||
|
service = construct_url(environ)
|
||
|
args = urlencode({'service': service})
|
||
|
location = authority + "login?" + args
|
||
|
exce = CASAuthenticate(location)
|
||
|
return exce.wsgi_application(environ, start_response)
|
||
|
return cas_application
|
||
|
|
||
|
middleware = AuthCASHandler
|
||
|
|
||
|
__all__ = ['CASLoginFailure', 'CASAuthenticate', 'AuthCASHandler' ]
|
||
|
|
||
|
if '__main__' == __name__:
|
||
|
authority = "https://secure.its.yale.edu/cas/servlet/"
|
||
|
from paste.wsgilib import dump_environ
|
||
|
from paste.httpserver import serve
|
||
|
from paste.httpexceptions import *
|
||
|
serve(HTTPExceptionHandler(
|
||
|
AuthCASHandler(dump_environ, authority)))
|