Compare commits

...
Sign in to create a new pull request.

11 commits

Author SHA1 Message Date
Cameron Brandon White
1cf4731653 Replaced version selection with validator selection
Previously the CAS version was specified in a configuration variable,
`CAS_VERSION`. This commit makes it so that the validation type is
selected as either `validate` or `serviceValidate` in `CAS_VALIDATOR`.

This commit also corrects the behavior previous coded. `/serviceValidate`
should be used instead of `/validate` in versions 2 and 3.
2014-09-12 15:26:20 -07:00
Cameron Brandon White
752beb1e12 Hard codes endpoints in create_cas_* functions
** NOT BACKWARDS COMPATIBLE **

The previous technique was to have a configuration entry for every
CAS endpoint; CAS_LOGIN_ROUTE, CAS_LOGOUT_ROUTE, CAS_VALIDATE_ROUTE,
... This worked fine but with the inclusion of more CAS routes it
would be necessary to have even more configuration entries.

To simplify the configuration this commit hard codes the endpoint;
'/login', '/logout', '/validate', ... and provides one configuration
entry CAS_ROUTE_PREFIX that allows you to modify them all.

This change is not backwards compatible therefore the version is
being bumped to 1.0.0
2014-09-12 13:40:42 -07:00
Cameron Brandon White
9d7107af1c Added create functions for missing version 2/3 urls 2014-09-12 11:59:17 -07:00
Cameron Brandon White
5bc72d6940 Merge pull request #12 from xcompass/master
Add return URL after logout
2014-09-11 18:14:00 -07:00
Pan Luo
fcf2093004 Add return URL after logout
The logout URL accepts a parameter (url or service) to redirect user
back after logging out from CAS server.
https://github.com/Jasig/cas/blob/master/cas-server-protocol/3.0/cas_protocol_3_0.md#231-parameters
2014-09-11 12:24:26 -07:00
Cameron Brandon White
afc2d6b846 Merge pull request #11 from xcompass/master
Fix incorrect close statement
2014-09-09 10:19:03 -07:00
Pan Luo
3a0b40a592 Fix incorrect close statement 2014-09-09 01:41:32 -07:00
Cameron Brandon White
820b4ae5de Updated README with new configuration options 2014-09-08 17:08:59 -07:00
Cameron Brandon White
f134e61261 Refactored test_routing tests
* Moved tests into three classes one for each CAS version
* Minor formating changes
2014-09-08 17:06:33 -07:00
Cameron Brandon White
cc0bd5006f Merge pull request #10 from xcompass/master
Add basic support for CAS 2.0 and CAS 3.0
2014-09-08 16:39:26 -07:00
Pan Luo
286a275538 Add basic support for CAS 2.0 and CAS 3.0
/proxyValidate is not supported yet
2014-09-05 00:10:00 -07:00
8 changed files with 402 additions and 77 deletions

View file

@ -97,13 +97,14 @@ the `username` will be removed from the session.
#### Optional Configs ####
|Key | Default |
|-------------------------|----------------|
|CAS_TOKEN_SESSION_KEY | _CAS_TOKEN |
|CAS_USERNAME_SESSION_KEY | CAS_USERNAME |
|CAS_LOGIN_ROUTE | '/cas' |
|CAS_LOGOUT_ROUTE | '/cas/logout' |
|CAS_VALIDATE_ROUTE | '/cas/validate'|
|Key | Default |
|---------------------------|----------------|
|CAS_TOKEN_SESSION_KEY | _CAS_TOKEN |
|CAS_USERNAME_SESSION_KEY | CAS_USERNAME |
|CAS_ATTRIBUTES_SESSION_KEY | CAS_ATTRIBUTES |
|CAS_ROUTE_PREFIX | 'cas' |
|CAS_LOGOUT_RETURN_URL | None |
|CAS_VALIDATOR | 'validate' |
## Example ##

View file

@ -27,13 +27,14 @@ class CAS(object):
Optional Configs:
|Key | Default |
|-------------------------|----------------|
|CAS_TOKEN_SESSION_KEY | _CAS_TOKEN |
|CAS_USERNAME_SESSION_KEY | CAS_USERNAME |
|CAS_LOGIN_ROUTE | '/cas' |
|CAS_LOGOUT_ROUTE | '/cas/logout' |
|CAS_VALIDATE_ROUTE | '/cas/validate'|
|Key | Default |
|---------------------------|----------------|
|CAS_TOKEN_SESSION_KEY | _CAS_TOKEN |
|CAS_USERNAME_SESSION_KEY | CAS_USERNAME |
|CAS_ATTRIBUTES_SESSION_KEY | CAS_ATTRIBUTES |
|CAS_ROUTE_PREFIX | '/cas' |
|CAS_LOGOUT_RETURN_URL | None |
|CAS_VALIDATOR | 'validate' |
"""
def __init__(self, app=None, url_prefix=None):
@ -45,9 +46,10 @@ class CAS(object):
# Configuration defaults
app.config.setdefault('CAS_TOKEN_SESSION_KEY', '_CAS_TOKEN')
app.config.setdefault('CAS_USERNAME_SESSION_KEY', 'CAS_USERNAME')
app.config.setdefault('CAS_LOGIN_ROUTE', '/cas')
app.config.setdefault('CAS_LOGOUT_ROUTE', '/cas/logout')
app.config.setdefault('CAS_VALIDATE_ROUTE', '/cas/validate')
app.config.setdefault('CAS_ATTRIBUTES_SESSION_KEY', 'CAS_ATTRIBUTES')
app.config.setdefault('CAS_ROUTE_PREFIX', 'cas')
app.config.setdefault('CAS_LOGOUT_RETURN_URL', None)
app.config.setdefault('CAS_VALIDATOR', 'validate')
# Register Blueprint
app.register_blueprint(routing.blueprint, url_prefix=url_prefix)
@ -60,7 +62,7 @@ class CAS(object):
def teardown(self, exception):
ctx = stack.top
@property
def app(self):
return self._app or current_app
@ -70,8 +72,12 @@ class CAS(object):
return flask.session.get(
self.app.config['CAS_USERNAME_SESSION_KEY'], None)
@property
def token(self):
return flask.session.get(
self.app.config['CAS_TOKEN_SESSION_KEY'], None)
@property
def attributes(self):
return flask.session.get(
self.app.config['CAS_ATTRIBUTES_SESSION_KEY'], {})

View file

@ -4,6 +4,8 @@ flask_cas.cas_urls
Functions for creating urls to access CAS.
"""
from functools import reduce
try:
from urllib import quote
from urllib import urlencode
@ -13,7 +15,6 @@ except ImportError:
from urllib.parse import urljoin
from urllib.parse import urlencode
def create_url(base, path=None, *query):
""" Create a url.
@ -23,7 +24,7 @@ def create_url(base, path=None, *query):
Keyword arguments:
base -- The left most part of the url (ex. http://localhost:5000).
path -- The path after the base (ex. /foo/bar).
path -- The path after the base (ex. /foo/bar or ['foo', 'bar']).
query -- A list of key value pairs (ex. [('key', 'value')]).
Example usage:
@ -35,10 +36,25 @@ def create_url(base, path=None, *query):
... ('url', 'http://example.com'),
... )
'http://localhost:5000/foo/bar?key1=value&url=http%3A%2F%2Fexample.com'
>>> create_url('http://localhost:5000/', ['/foo/', '/bar/'])
'http://localhost:5000/foo/bar/'
>>> create_url('http://localhost:5000', ['foo', 'bar'])
'http://localhost:5000/foo/bar'
>>> create_url('http://localhost:5000', ['/foo/', '/bar/'])
'http://localhost:5000/foo/bar/'
>>> create_url('http://localhost:5000/', ['foo/', '/bar/'])
'http://localhost:5000/foo/bar/'
>>> create_url('http://localhost:5000/', ['foo/', None, '/bar/'])
'http://localhost:5000/foo/bar/'
"""
url = base
# Add the path to the url if it's not None.
if path is not None:
# If path is a list remove all None values and reduce to a '/'
# seperated string.
if isinstance(path, list):
path = filter(lambda x: bool(x), path)
path = reduce(lambda l, r: '{}/{}'.format(l.rstrip('/'), r.lstrip('/')), path)
# Add the path to the url if there is something to add.
if path:
url = urljoin(url, quote(path))
# Remove key/value pairs with None values.
query = filter(lambda pair: pair[1] is not None, query)
@ -47,12 +63,13 @@ def create_url(base, path=None, *query):
return url
def create_cas_login_url(cas_url, cas_route, service, renew=None, gateway=None):
def create_cas_login_url(cas_url, cas_route_prefix, service, renew=None,
gateway=None, method=None):
""" Create a CAS login URL .
Keyword arguments:
cas_url -- The url to the CAS (ex. http://sso.pdx.edu)
cas_route -- The route where the CAS lives on server (ex. /cas)
cas_route_prefix -- The prefix of the CAS endpoint (ex. /cas/)
service -- (ex. http://localhost:5000/login)
renew -- "true" or "false"
gateway -- "true" or "false"
@ -60,50 +77,51 @@ def create_cas_login_url(cas_url, cas_route, service, renew=None, gateway=None):
Example usage:
>>> create_cas_login_url(
... 'http://sso.pdx.edu',
... '/cas',
... 'cas',
... 'http://localhost:5000',
... )
'http://sso.pdx.edu/cas?service=http%3A%2F%2Flocalhost%3A5000'
'http://sso.pdx.edu/cas/login?service=http%3A%2F%2Flocalhost%3A5000'
"""
return create_url(
cas_url,
cas_route,
[cas_route_prefix, 'login'],
('service', service),
('renew', renew),
('gateway', gateway),
('method', method),
)
def create_cas_logout_url(cas_url, cas_route, url=None):
def create_cas_logout_url(cas_url, cas_route_prefix, service=None):
""" Create a CAS logout URL.
Keyword arguments:
cas_url -- The url to the CAS (ex. http://sso.pdx.edu)
cas_route -- The route where the CAS lives on server (ex. /cas/logout)
url -- (ex. http://localhost:5000/login)
cas_route_prefix -- The prefix of the CAS endpoint (ex. /cas/)
service -- (ex. http://localhost:5000/login)
Example usage:
>>> create_cas_logout_url(
... 'http://sso.pdx.edu',
... '/cas/logout',
... 'cas',
... 'http://localhost:5000',
... )
'http://sso.pdx.edu/cas/logout?url=http%3A%2F%2Flocalhost%3A5000'
'http://sso.pdx.edu/cas/logout?service=http%3A%2F%2Flocalhost%3A5000'
"""
return create_url(
cas_url,
cas_route,
('url', url),
[cas_route_prefix, 'logout'],
('service', service),
)
def create_cas_validate_url(cas_url, cas_route, service, ticket,
def create_cas_validate_url(cas_url, cas_route_prefix, service, ticket,
renew=None):
""" Create a CAS validate URL.
Keyword arguments:
cas_url -- The url to the CAS (ex. http://sso.pdx.edu)
cas_route -- The route where the CAS lives on server (ex. /cas/validate)
cas_route_prefix -- The prefix of the CAS endpoint (ex. /cas/)
service -- (ex. http://localhost:5000/login)
ticket -- (ex. 'ST-58274-x839euFek492ou832Eena7ee-cas')
renew -- "true" or "false"
@ -111,7 +129,7 @@ def create_cas_validate_url(cas_url, cas_route, service, ticket,
Example usage:
>>> create_cas_validate_url(
... 'http://sso.pdx.edu',
... '/cas/validate',
... 'cas',
... 'http://localhost:5000/login',
... 'ST-58274-x839euFek492ou832Eena7ee-cas'
... )
@ -119,8 +137,119 @@ def create_cas_validate_url(cas_url, cas_route, service, ticket,
"""
return create_url(
cas_url,
cas_route,
[cas_route_prefix, 'validate'],
('service', service),
('ticket', ticket),
('renew', renew),
)
def create_cas_serviceValidate_url(cas_url, cas_route_prefix, service, ticket,
pgtUrl=None, renew=None):
""" Create a CAS serviceValidate URL.
Keyword arguments:
cas_url -- The url to the CAS (ex. http://sso.pdx.edu)
cas_route_prefix -- The prefix of the CAS endpoint (ex. /cas/)
service -- (ex. http://localhost:5000/login)
ticket -- (ex. 'ST-58274-x839euFek492ou832Eena7ee-cas')
pgtUrl -- The url of the proxy callback
renew -- "true" or "false"
Example usage:
>>> create_cas_serviceValidate_url(
... 'http://sso.pdx.edu',
... 'cas',
... 'http://localhost:5000/login',
... 'ST-58274-x839euFek492ou832Eena7ee-cas'
... )
'http://sso.pdx.edu/cas/serviceValidate?service=http%3A%2F%2Flocalhost%3A5000%2Flogin&ticket=ST-58274-x839euFek492ou832Eena7ee-cas'
"""
return create_url(
cas_url,
[cas_route_prefix, 'serviceValidate'],
('service', service),
('ticket', ticket),
('pgtUrl', pgtUrl),
('renew', renew),
)
def create_cas_proxyValidate_url(cas_url, cas_route_prefix, service, ticket,
pgtUrl=None, renew=None):
""" Create a CAS proxyValidate URL.
Keyword arguments:
cas_url -- The url to the CAS (ex. http://sso.pdx.edu)
cas_route_prefix -- The prefix of the CAS endpoint (ex. /cas/)
service -- (ex. http://localhost:5000/login)
ticket -- (ex. 'ST-58274-x839euFek492ou832Eena7ee-cas')
pgtUrl -- The url of the proxy callback
renew -- "true" or "false"
Example usage:
>>> create_cas_proxyValidate_url(
... 'http://sso.pdx.edu',
... 'cas',
... 'http://localhost:5000/login',
... 'ST-58274-x839euFek492ou832Eena7ee-cas'
... )
'http://sso.pdx.edu/cas/proxyValidate?service=http%3A%2F%2Flocalhost%3A5000%2Flogin&ticket=ST-58274-x839euFek492ou832Eena7ee-cas'
"""
return create_url(
cas_url,
[cas_route_prefix, 'proxyValidate'],
('service', service),
('ticket', ticket),
('pgtUrl', pgtUrl),
('renew', renew),
)
def create_cas_proxy_url(cas_url, cas_route_prefix, pgt, targetService):
""" Create a CAS proxy URL.
Keyword arguments:
cas_url -- The url to the CAS (ex. http://sso.pdx.edu)
cas_route_prefix -- The prefix of the CAS endpoint (ex. /cas/)
pgt -- The proxy-granting ticket
targetService -- The service identifier of the back-end service
Example usage:
>>> create_cas_proxy_url(
... 'http://sso.pdx.edu',
... 'cas',
... 'PGT-490649-W81Y9Sa2vTM7hda7xNTkezTbVge4CUsybAr',
... 'http://www.service.com',
... )
'http://sso.pdx.edu/cas/proxy?pgt=PGT-490649-W81Y9Sa2vTM7hda7xNTkezTbVge4CUsybAr&targetService=http%3A%2F%2Fwww.service.com'
"""
return create_url(
cas_url,
[cas_route_prefix, 'proxy'],
('pgt', pgt),
('targetService', targetService),
)
def create_cas_samIValidate_url(cas_url, cas_route_prefix, target):
""" Create a CAS samIValidate URL.
Keyword arguments:
cas_url -- The url to the CAS (ex. http://sso.pdx.edu)
cas_route_prefix -- The prefix of the CAS endpoint (ex. /cas/)
target -- The url of the back-end service
Example usage:
>>> create_cas_samIValidate_url(
... 'http://sso.pdx.edu',
... 'cas',
... 'http://www.target.com',
... )
'http://sso.pdx.edu/cas/samIValidate?target=http%3A%2F%2Fwww.target.com'
"""
return create_url(
cas_url,
[cas_route_prefix, 'samIValidate'],
('target', target),
)

View file

@ -3,7 +3,9 @@ from flask import current_app
from .cas_urls import create_cas_login_url
from .cas_urls import create_cas_logout_url
from .cas_urls import create_cas_validate_url
from .cas_urls import create_cas_serviceValidate_url
from xml.etree import ElementTree
try:
from urllib import urlopen
@ -31,7 +33,7 @@ def login():
redirect_url = create_cas_login_url(
current_app.config['CAS_SERVER'],
current_app.config['CAS_LOGIN_ROUTE'],
current_app.config['CAS_ROUTE_PREFIX'],
flask.url_for('.login', _external=True))
if 'ticket' in flask.request.args:
@ -63,13 +65,17 @@ def logout():
redirect_url = create_cas_logout_url(
current_app.config['CAS_SERVER'],
current_app.config['CAS_LOGOUT_ROUTE'])
current_app.config['CAS_ROUTE_PREFIX'],
current_app.config['CAS_LOGOUT_RETURN_URL'],
)
current_app.logger.debug('Redirecting to: {}'.format(redirect_url))
return flask.redirect(redirect_url)
#current_app.logger.debug("Making GET request to {}".format(
# cas_validate_url))
def validate(ticket):
def validator(ticket):
"""
Will attempt to validate the ticket. If validation fails, then False
is returned. If validation is successful, then True is returned
@ -77,31 +83,93 @@ def validate(ticket):
key `CAS_USERNAME_SESSION_KEY`.
"""
cas_username_session_key = current_app.config['CAS_USERNAME_SESSION_KEY']
validators = {
'validate': validate,
'serviceValidate': serviceValidate,
}
current_app.logger.debug("validating token {}".format(ticket))
try:
is_valid = validators[current_app.config['CAS_VALIDATOR']](ticket)
if is_valid:
current_app.logger.debug("valid")
else:
current_app.logger.debug("invalid")
return is_valid
except KeyError:
raise ValueError('Unsupported CAS_VALIDATOR %r' % current_app.config['CAS_VALIDATOR'])
def validate(ticket):
cas_validate_url = create_cas_validate_url(
current_app.config['CAS_SERVER'],
current_app.config['CAS_VALIDATE_ROUTE'],
current_app.config['CAS_ROUTE_PREFIX'],
flask.url_for('.login', _external=True),
ticket)
current_app.logger.debug("Making GET request to {}".format(
cas_validate_url))
response = urlopen(cas_validate_url)
try:
(isValid, username) = urlopen(cas_validate_url).readlines()
isValid = True if isValid.strip() == b'yes' else False
username = username.strip().decode('utf8', 'ignore')
(is_valid, username) = response.readlines()
is_valid = True if is_valid.strip() == b'yes' else False
if is_valid:
cas_username_session_key = current_app.config['CAS_USERNAME_SESSION_KEY']
username = username.strip().decode('utf8', 'ignore')
flask.session[cas_username_session_key] = username
except ValueError:
current_app.logger.error("CAS returned unexpected result")
isValid = False
is_valid = False
if isValid:
current_app.logger.debug("valid")
flask.session[cas_username_session_key] = username
else:
current_app.logger.debug("invalid")
response.close()
return isValid
return is_valid
def serviceValidate(ticket):
cas_serviceValidate_url = create_cas_serviceValidate_url(
current_app.config['CAS_SERVER'],
current_app.config['CAS_ROUTE_PREFIX'],
flask.url_for('.login', _external=True),
ticket)
response = urlopen(cas_serviceValidate_url)
try:
data = response.read()
tree = ElementTree.fromstring(data)
user = tree.find('*/cas:user', namespaces=dict(cas='http://www.yale.edu/tp/cas'))
is_valid = user != None
if is_valid:
cas_username_session_key = current_app.config['CAS_USERNAME_SESSION_KEY']
cas_attributes_session_key = current_app.config['CAS_ATTRIBUTES_SESSION_KEY']
attributes = {}
username = user.text
attrs = tree.find('*/cas:attributes', namespaces=dict(cas='http://www.yale.edu/tp/cas'))
if attrs:
for attr in attrs:
tag = attr.tag.split("}").pop()
if tag in attributes:
# found multiple value attribute
if isinstance(attributes[tag], list):
attributes[tag].append(attr.text)
else:
attributes[tag] = [attributes[tag], attr.text]
else:
attributes[tag] = attr.text
flask.session[cas_username_session_key] = username
flask.session[cas_attributes_session_key] = attributes
return True
else:
error = tree.find('cas:authenticationFailure', namespaces=dict(cas='http://www.yale.edu/tp/cas'))
if error is None:
current_app.logger.error('Error: Unknown response, ' + data)
else:
current_app.logger.error('Error: ' + error.get('code') + ', ' + error.text)
return False
finally:
response.close()

View file

@ -8,7 +8,7 @@ import textwrap
if __name__ == "__main__":
setuptools.setup(
name="Flask-CAS",
version="0.4.3",
version="1.0.0",
description="Flask extension for CAS",
author="Cameron Brandon White",
author_email="cameronbwhite90@gmail.com",

View file

@ -118,7 +118,7 @@ class test_create_cas_login_url(unittest.TestCase):
'/cas',
'http://localhost:5000',
),
'http://sso.pdx.edu/cas?service=http%3A%2F%2Flocalhost%3A5000',
'http://sso.pdx.edu/cas/login?service=http%3A%2F%2Flocalhost%3A5000',
)
def test_with_renew(self):
@ -129,7 +129,7 @@ class test_create_cas_login_url(unittest.TestCase):
'http://localhost:5000',
renew="true",
),
'http://sso.pdx.edu/cas?service=http%3A%2F%2Flocalhost%3A5000&renew=true',
'http://sso.pdx.edu/cas/login?service=http%3A%2F%2Flocalhost%3A5000&renew=true',
)
def test_with_gateway(self):
@ -140,7 +140,7 @@ class test_create_cas_login_url(unittest.TestCase):
'http://localhost:5000',
gateway="true",
),
'http://sso.pdx.edu/cas?service=http%3A%2F%2Flocalhost%3A5000&gateway=true',
'http://sso.pdx.edu/cas/login?service=http%3A%2F%2Flocalhost%3A5000&gateway=true',
)
def test_with_renew_and_gateway(self):
@ -152,7 +152,7 @@ class test_create_cas_login_url(unittest.TestCase):
renew="true",
gateway="true",
),
'http://sso.pdx.edu/cas?service=http%3A%2F%2Flocalhost%3A5000&renew=true&gateway=true',
'http://sso.pdx.edu/cas/login?service=http%3A%2F%2Flocalhost%3A5000&renew=true&gateway=true',
)
@ -162,7 +162,7 @@ class test_create_cas_logout_url(unittest.TestCase):
self.assertEqual(
create_cas_logout_url(
'http://sso.pdx.edu',
'/cas/logout'
'/cas/'
),
'http://sso.pdx.edu/cas/logout',
)
@ -171,12 +171,31 @@ class test_create_cas_logout_url(unittest.TestCase):
self.assertEqual(
create_cas_logout_url(
'http://sso.pdx.edu',
'/cas/logout',
'/cas',
'http://localhost:5000',
),
'http://sso.pdx.edu/cas/logout?url=http%3A%2F%2Flocalhost%3A5000'
'http://sso.pdx.edu/cas/logout?service=http%3A%2F%2Flocalhost%3A5000'
)
def test_with_url_for_cas2(self):
self.assertEqual(
create_cas_logout_url(
'http://sso.pdx.edu',
'/cas/',
'http://localhost:5000',
),
'http://sso.pdx.edu/cas/logout?service=http%3A%2F%2Flocalhost%3A5000'
)
def test_with_url_cas3(self):
self.assertEqual(
create_cas_logout_url(
'http://sso.pdx.edu',
'/cas/',
'http://localhost:5000',
),
'http://sso.pdx.edu/cas/logout?service=http%3A%2F%2Flocalhost%3A5000'
)
class test_create_cas_validate_url(unittest.TestCase):
@ -184,7 +203,7 @@ class test_create_cas_validate_url(unittest.TestCase):
self.assertEqual(
create_cas_validate_url(
'http://sso.pdx.edu',
'/cas/validate',
'/cas/',
'http://localhost:5000/login',
'ST-58274-x839euFek492ou832Eena7ee-cas'
),
@ -195,7 +214,7 @@ class test_create_cas_validate_url(unittest.TestCase):
self.assertEqual(
create_cas_validate_url(
'http://sso.pdx.edu',
'/cas/validate',
'/cas/',
'http://localhost:5000/login',
'ST-58274-x839euFek492ou832Eena7ee-cas',
renew='true',

View file

@ -20,7 +20,7 @@ class test_flask_cas(unittest.TestCase):
self.assertEqual(response.status_code, 302)
self.assertEqual(
response.headers['Location'],
'http://cas.server.com/cas?service=http%3A%2F%2Flocalhost%2Flogin%2F')
'http://cas.server.com/cas/login?service=http%3A%2F%2Flocalhost%2Flogin%2F')
def test_cas_constructor_with_url_prefix(self):
self.app = flask.Flask(__name__)
@ -37,7 +37,7 @@ class test_flask_cas(unittest.TestCase):
self.assertEqual(response.status_code, 302)
self.assertEqual(
response.headers['Location'],
'http://cas.server.com/cas?service=http%3A%2F%2Flocalhost%2Fcas%2Flogin%2F')
'http://cas.server.com/cas/login?service=http%3A%2F%2Flocalhost%2Fcas%2Flogin%2F')
def test_cas_constructor_properties(self):
@ -69,7 +69,7 @@ class test_flask_cas(unittest.TestCase):
self.assertEqual(response.status_code, 302)
self.assertEqual(
response.headers['Location'],
'http://cas.server.com/cas?service=http%3A%2F%2Flocalhost%2Flogin%2F')
'http://cas.server.com/cas/login?service=http%3A%2F%2Flocalhost%2Flogin%2F')
def test_cas_init_app_with_prefix_url(self):
self.app = flask.Flask(__name__)
@ -87,7 +87,7 @@ class test_flask_cas(unittest.TestCase):
self.assertEqual(response.status_code, 302)
self.assertEqual(
response.headers['Location'],
'http://cas.server.com/cas?service=http%3A%2F%2Flocalhost%2Fcas%2Flogin%2F')
'http://cas.server.com/cas/login?service=http%3A%2F%2Flocalhost%2Fcas%2Flogin%2F')
def test_cas_init_app_properties(self):

View file

@ -28,13 +28,10 @@ class test_routing(unittest.TestCase):
self.app.config['CAS_SERVER'] = 'http://cas.server.com'
self.app.config['CAS_TOKEN_SESSION_KEY'] = '_CAS_TOKEN'
self.app.config['CAS_USERNAME_SESSION_KEY'] = 'CAS_USERNAME'
self.app.config['CAS_ATTRIBUTES_SESSION_KEY'] = 'CAS_ATTRIBUTES'
self.app.config['CAS_AFTER_LOGIN'] = 'root'
self.app.config['CAS_LOGIN_ROUTE'] = '/cas'
self.app.config['CAS_LOGOUT_ROUTE'] = '/cas/logout'
self.app.config['CAS_VALIDATE_ROUTE'] = '/cas/validate'
self.app.config['CAS_ROUTE_PREFIX'] = 'cas'
def test_setUp(self):
pass
def test_login_by_logged_out_user(self):
with self.app.test_client() as client:
@ -42,7 +39,7 @@ class test_routing(unittest.TestCase):
self.assertEqual(response.status_code, 302)
self.assertEqual(
response.headers['Location'],
'http://cas.server.com/cas?service=http%3A%2F%2Flocalhost%2Flogin%2F')
'http://cas.server.com/cas/login?service=http%3A%2F%2Flocalhost%2Flogin%2F')
@mock.patch.object(routing, 'urlopen',
return_value=io.BytesIO(b'yes\nbob\n'))
@ -95,7 +92,7 @@ class test_routing(unittest.TestCase):
self.assertEqual(response.status_code, 302)
self.assertEqual(
response.headers['Location'],
'http://cas.server.com/cas?service=http%3A%2F%2Flocalhost%2Flogin%2F')
'http://cas.server.com/cas/login?service=http%3A%2F%2Flocalhost%2Flogin%2F')
def test_logout(self):
with self.app.test_client() as client:
@ -105,6 +102,16 @@ class test_routing(unittest.TestCase):
response.headers['Location'],
'http://cas.server.com/cas/logout')
def test_logout_with_return_url(self):
with self.app.test_client() as client:
self.app.config['CAS_VERSION'] = '2'
self.app.config['CAS_LOGOUT_RETURN_URL'] = 'http://example.com'
response = client.get('/logout/')
self.assertEqual(response.status_code, 302)
self.assertEqual(
response.headers['Location'],
'http://cas.server.com/cas/logout?service=http%3A%2F%2Fexample.com')
@mock.patch.object(routing, 'urlopen',
return_value=io.BytesIO(b'yes\nbob\n'))
def test_validate_valid(self, m):
@ -127,3 +134,98 @@ class test_routing(unittest.TestCase):
self.assertNotIn(
self.app.config['CAS_TOKEN_SESSION_KEY'],
flask.session)
@mock.patch.object(
routing,
'urlopen',
return_value=io.BytesIO(b"""
<cas:serviceResponse xmlns:cas="http://www.yale.edu/tp/cas">
<cas:authenticationSuccess>
<cas:user>bob</cas:user>
<cas:proxyGrantingTicket>PGTIOU-84678-8a9d...</cas:proxyGrantingTicket>
</cas:authenticationSuccess>
</cas:serviceResponse>"""))
def test_serviceValidate_valid(self, m):
with self.app.test_request_context('/login/'):
ticket = '12345-abcdefg-cas'
self.assertEqual(routing.serviceValidate(ticket), True)
self.assertEqual(
self.cas.username,
'bob')
@mock.patch.object(
routing,
'urlopen',
return_value=io.BytesIO(b"""
<cas:serviceResponse xmlns:cas="http://www.yale.edu/tp/cas">
<cas:authenticationFailure code="INVALID_TICKET">
Ticket ST-1856339-aA5Yuvrxzpv8Tau1cYQ7 not recognized
</cas:authenticationFailure>
</cas:serviceResponse>"""))
def test_serviceValidate_invalid(self, m):
self.app.config['CAS_VALIDATOR'] = 'serviceValidate'
with self.app.test_request_context('/login/'):
ticket = '12345-abcdefg-cas'
self.assertEqual(routing.serviceValidate(ticket), False)
self.assertNotIn(
self.app.config['CAS_USERNAME_SESSION_KEY'],
flask.session)
self.assertNotIn(
self.app.config['CAS_TOKEN_SESSION_KEY'],
flask.session)
@mock.patch.object(
routing,
'urlopen',
return_value=io.BytesIO(b"""
<cas:serviceResponse xmlns:cas="http://www.yale.edu/tp/cas">
<cas:authenticationSuccess>
<cas:user>bob</cas:user>
<cas:attributes>
<cas:firstname>John</cas:firstname>
<cas:lastname>Doe</cas:lastname>
<cas:title>Mr.</cas:title>
<cas:email>jdoe@example.org</cas:email>
<cas:affiliation>staff</cas:affiliation>
<cas:affiliation>faculty</cas:affiliation>
</cas:attributes>
<cas:proxyGrantingTicket>PGTIOU-84678-8a9d...</cas:proxyGrantingTicket>
</cas:authenticationSuccess>
</cas:serviceResponse>"""))
def test_serviceValidate_with_attributes_valid(self, m):
self.app.config['CAS_VALIDATOR'] = 'serviceValidate'
with self.app.test_request_context('/login/'):
ticket = '12345-abcdefg-cas'
self.assertEqual(routing.serviceValidate(ticket), True)
self.assertEqual(
self.cas.username,
'bob')
self.assertEqual(
self.cas.attributes,
{'firstname': 'John',
'lastname': 'Doe',
'title': 'Mr.',
'email': 'jdoe@example.org',
'affiliation': ['staff', 'faculty']})
@mock.patch.object(
routing,
'urlopen',
return_value=io.BytesIO(b"""
<cas:serviceResponse xmlns:cas="http://www.yale.edu/tp/cas">
<cas:authenticationFailure code="INVALID_TICKET">
Ticket ST-1856339-aA5Yuvrxzpv8Tau1cYQ7 not recognized
</cas:authenticationFailure>
</cas:serviceResponse>"""))
def test_serviceValidate_with_attributes_invalid(self, m):
self.app.config['CAS_VALIDATOR'] = 'serviceValidate'
with self.app.test_request_context('/login/'):
ticket = '12345-abcdefg-cas'
self.assertEqual(routing.serviceValidate(ticket), False)
self.assertNotIn(
self.app.config['CAS_USERNAME_SESSION_KEY'],
flask.session)
self.assertNotIn(
self.app.config['CAS_TOKEN_SESSION_KEY'],
flask.session)