[Python-modules-commits] [python-django-casclient] 01/02: Imported Upstream version 1.2.0
Joost van Baal
joostvb at moszumanska.debian.org
Tue Mar 15 09:03:33 UTC 2016
This is an automated email from the git hooks/post-receive script.
joostvb pushed a commit to branch master
in repository python-django-casclient.
commit 44a0492246fb5ce7f71aba1fb3c2966fb86e11c2
Author: Joost van Baal-Ilić <joostvb at janacopoulos.mdcc.cx>
Date: Tue Mar 15 10:02:58 2016 +0100
Imported Upstream version 1.2.0
---
.gitignore | 31 +++++
.travis.yml | 22 ++++
AUTHORS | 6 +
CHANGELOG.md | 11 ++
CONTRIBUTORS | 4 +
LICENSE.mit | 22 ++++
MANIFEST.in | 6 +
README.md | 158 +++++++++++++++++++++++
cas/__init__.py | 32 +++++
cas/backends.py | 257 ++++++++++++++++++++++++++++++++++++
cas/decorators.py | 93 +++++++++++++
cas/exceptions.py | 17 +++
cas/middleware.py | 92 +++++++++++++
cas/models.py | 108 ++++++++++++++++
cas/tests/__init__.py | 3 +
cas/tests/factories.py | 15 +++
cas/tests/test_backend.py | 30 +++++
cas/tests/test_smoke.py | 7 +
cas/tests/test_views.py | 46 +++++++
cas/utils.py | 26 ++++
cas/views.py | 257 ++++++++++++++++++++++++++++++++++++
docs/Makefile | 192 +++++++++++++++++++++++++++
docs/source/changelog.rst | 21 +++
docs/source/conf.py | 287 +++++++++++++++++++++++++++++++++++++++++
docs/source/contributing.rst | 12 ++
docs/source/gettingstarted.rst | 133 +++++++++++++++++++
docs/source/index.rst | 27 ++++
requirements-dev.txt | 1 +
run_tests.py | 59 +++++++++
setup.cfg | 2 +
setup.py | 41 ++++++
31 files changed, 2018 insertions(+)
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..835e4ac
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,31 @@
+*.py[co]
+
+# Packages
+*.egg
+*.egg-info
+dist
+build
+eggs
+parts
+bin
+var
+sdist
+develop-eggs
+.installed.cfg
+
+# Installer logs
+pip-log.txt
+
+# Unit test / coverage reports
+.coverage
+.tox
+
+#Translations
+*.mo
+
+#Mr Developer
+.mr.developer.cfg
+.idea
+
+# testing
+test_config.py
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..a18521b
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,22 @@
+language: python
+
+python:
+ - "2.7"
+ - "3.3"
+ - "3.4"
+
+env:
+ - DJANGO_VERSION=Django==1.5
+ - DJANGO_VERSION=Django==1.6
+ - DJANGO_VERSION=Django==1.7
+ - DJANGO_VERSION=Django==1.8
+
+# command to install dependencies
+install:
+ - pip install $DJANGO_VERSION
+ - pip install -r requirements-dev.txt
+
+# command to run tests
+
+script:
+ - python run_tests.py
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..1b63dff
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,6 @@
+This fork of Django-CAS was originally created at https://bitbucket.org/cpcc/django-cas/
+
+The authors maintainers of this library are:
+
+ * Garrett Pennington
+ * Derek Stegelman
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..fa3f0b2
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,11 @@
+### 1.2.0
+
+- Allow opt out of time delay caused by fetching PGT tickets
+- Add support for gateway not returning a response
+- Allow forcing service URL over HTTPS (https://github.com/kstateome/django-cas/pull/48)
+- Allow user creation on first login to be optional (https://github.com/kstateome/django-cas/pull/49)
+
+### 1.1.1
+
+- Add a few logging statements
+- Add official change log.
\ No newline at end of file
diff --git a/CONTRIBUTORS b/CONTRIBUTORS
new file mode 100644
index 0000000..7a2b681
--- /dev/null
+++ b/CONTRIBUTORS
@@ -0,0 +1,4 @@
+epicserve
+rlmv
+bryankaplan
+cordmata
\ No newline at end of file
diff --git a/LICENSE.mit b/LICENSE.mit
new file mode 100644
index 0000000..37f7704
--- /dev/null
+++ b/LICENSE.mit
@@ -0,0 +1,22 @@
+Copyright (c) 2013 Office of Mediated Education, Kansas State University
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..ab70df3
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,6 @@
+include LICENSE.mit
+include README.md
+include CHANGELOG.md
+include AUTHORS
+include CONTRIBUTORS
+recursive-exclude * *.pyc
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..2375726
--- /dev/null
+++ b/README.md
@@ -0,0 +1,158 @@
+# django-cas
+
+CAS client for Django. This library requires Django 1.5 or above, and Python 2.6, 2.7, 3.4
+
+Current version: 1.2.0
+
+This is [K-State's fork](https://github.com/kstateome/django-cas) of [the original](https://bitbucket.org/cpcc/django-cas/overview) and includes [several additional features](https://github.com/kstateome/django-cas/#additional-features) as well as features merged from
+
+* [KTHse's django-cas2](https://github.com/KTHse/django-cas2).
+* [Edmund Crewe's proxy ticket patch](http://code.google.com/r/edmundcrewe-proxypatch/source/browse/django-cas-proxy.patch).
+
+
+## Install
+
+This project is registered on PyPi as django-cas-client. To install::
+
+ pip install django-cas-client==1.2.0
+
+
+### Add to URLs
+
+Add the login and logout patterns to your main URLS conf.
+
+ # CAS
+ url(r'^accounts/login/$', 'cas.views.login', name='login'),
+ url(r'^accounts/logout/$', 'cas.views.logout', name='logout'),
+
+### Add middleware and settings
+
+Set your CAS server URL
+
+ CAS_SERVER_URL = "https://signin.somehwere/cas/"
+
+Add cas to middleware classes
+
+ 'cas.middleware.CASMiddleware',
+
+
+### Add authentication backends
+
+ AUTHENTICATION_BACKENDS = (
+ 'django.contrib.auth.backends.ModelBackend',
+ 'cas.backends.CASBackend',
+ )
+
+## How to Contribute
+
+Fork and branch off of the ``develop`` branch. Submit Pull requests back to ``kstateome:develop``.
+
+### Run The Tests
+
+All PRs must pass unit tests. To run the tests locally:
+
+ pip install -r requirements-dev.txt
+ python run_tests.py
+
+
+## Settings.py for CAS
+
+Add the following to middleware if you want to use CAS::
+
+ MIDDLEWARE_CLASSES = (
+ 'cas.middleware.CASMiddleware',
+ )
+
+
+Add these to ``settings.py`` to use the CAS Backend::
+
+
+ CAS_SERVER_URL = "Your Cas Server"
+ CAS_LOGOUT_COMPLETELY = True
+ CAS_PROVIDE_URL_TO_LOGOUT = True
+
+# Additional Features
+
+This fork contains additional features not found in the original:
+* Proxied Hosts
+* CAS Response Callbacks
+* CAS Gateway
+* Proxy Tickets (From Edmund Crewe)
+
+## Proxied Hosts
+
+You will need to setup middleware to handle the use of proxies.
+
+Add a setting ``PROXY_DOMAIN`` of the domain you want the client to use. Then add
+
+ MIDDLEWARE_CLASSES = (
+ 'cas.middleware.ProxyMiddleware',
+ )
+
+This middleware needs to be added before the django ``common`` middleware.
+
+
+## CAS Response Callbacks
+
+To store data from CAS, create a callback function that accepts the ElementTree object from the
+proxyValidate response. There can be multiple callbacks, and they can live anywhere. Define the
+callback(s) in ``settings.py``:
+
+ CAS_RESPONSE_CALLBACKS = (
+ 'path.to.module.callbackfunction',
+ 'anotherpath.to.module.callbackfunction2',
+ )
+
+and create the functions in ``path/to/module.py``:
+
+ def callbackfunction(tree):
+ username = tree[0][0].text
+
+ user, user_created = User.objects.get_or_create(username=username)
+ profile, created = user.get_profile()
+
+ profile.email = tree[0][1].text
+ profile.position = tree[0][2].text
+ profile.save()
+
+
+## CAS Gateway
+
+To use the CAS Gateway feature, first enable it in settings. Trying to use it without explicitly
+enabling this setting will raise an ImproperlyConfigured:
+
+ CAS_GATEWAY = True
+
+Then, add the ``gateway`` decorator to a view:
+
+ from cas.decorators import gateway
+
+ @gateway()
+ def foo(request):
+ #stuff
+ return render(request, 'foo/bar.html')
+
+
+## Custom Forbidden Page
+
+To show a custom forbidden page, set ``CAS_CUSTOM_FORBIDDEN`` to a ``path.to.some_view``. Otherwise,
+a generic ``HttpResponseForbidden`` will be returned.
+
+## Require SSL Login
+
+To force the service url to always target HTTPS, set ``CAS_FORCE_SSL_SERVICE_URL`` to ``True``.
+
+## Automatically Create Users on First Login
+
+By default, a stub user record will be created on the first successful CAS authentication
+using the username in the response. If this behavior is not desired set
+``CAS_AUTO_CREATE_USER`` to ``Flase``.
+
+## Proxy Tickets
+
+This fork also includes
+[Edmund Crewe's proxy ticket patch](http://code.google.com/r/edmundcrewe-proxypatch/source/browse/django-cas-proxy.patch).
+
+You can opt out of the time delay sometimes caused by proxy ticket validation by setting:
+
+ CAS_PGT_FETCH_WAIT = False
diff --git a/cas/__init__.py b/cas/__init__.py
new file mode 100644
index 0000000..59ea0eb
--- /dev/null
+++ b/cas/__init__.py
@@ -0,0 +1,32 @@
+"""Django CAS 1.0/2.0 authentication backend"""
+
+from django.conf import settings
+
+__all__ = []
+
+_DEFAULTS = {
+ 'CAS_ADMIN_PREFIX': None,
+ 'CAS_EXTRA_LOGIN_PARAMS': None,
+ 'CAS_IGNORE_REFERER': False,
+ 'CAS_LOGOUT_COMPLETELY': True,
+ 'CAS_REDIRECT_URL': '/',
+ 'CAS_RETRY_LOGIN': False,
+ 'CAS_SERVER_URL': None,
+ 'CAS_VERSION': '2',
+ 'CAS_GATEWAY': False,
+ 'CAS_PROXY_CALLBACK': None,
+ 'CAS_RESPONSE_CALLBACKS': None,
+ 'CAS_CUSTOM_FORBIDDEN': None,
+ 'CAS_PGT_FETCH_WAIT': True,
+ 'CAS_FORCE_SSL_SERVICE_URL': False,
+ 'CAS_AUTO_CREATE_USER': True,
+}
+
+for key, value in _DEFAULTS.items():
+ try:
+ getattr(settings, key)
+ except AttributeError:
+ setattr(settings, key, value)
+ # Suppress errors from DJANGO_SETTINGS_MODULE not being set
+ except ImportError:
+ pass
diff --git a/cas/backends.py b/cas/backends.py
new file mode 100644
index 0000000..5a646aa
--- /dev/null
+++ b/cas/backends.py
@@ -0,0 +1,257 @@
+import logging
+from xml.dom import minidom
+import time
+
+try:
+ from xml.etree import ElementTree
+except ImportError:
+ from elementtree import ElementTree
+
+try:
+ from urllib import urlencode
+except ImportError:
+ from urllib.parse import urlencode
+try:
+ from urllib import urlopen
+except ImportError:
+ from urllib.request import urlopen
+try:
+ from urlparse import urljoin
+except ImportError:
+ from urllib.parse import urljoin
+
+
+from django.conf import settings
+from django.contrib.auth import get_user_model
+
+from cas.exceptions import CasTicketException
+from cas.models import Tgt, PgtIOU
+from cas.utils import cas_response_callbacks
+
+__all__ = ['CASBackend']
+
+logger = logging.getLogger(__name__)
+
+
+def _verify_cas1(ticket, service):
+ """
+ Verifies CAS 1.0 authentication ticket.
+
+ :param: ticket
+ :param: service
+
+ Returns username on success and None on failure.
+ """
+
+ params = {'ticket': ticket, 'service': service}
+ url = (urljoin(settings.CAS_SERVER_URL, 'validate') + '?' +
+ urlencode(params))
+ page = urlopen(url)
+
+ try:
+ verified = page.readline().strip()
+ if verified == 'yes':
+ return page.readline().strip()
+ else:
+ return None
+ finally:
+ page.close()
+
+
+def _verify_cas2(ticket, service):
+ """
+ Verifies CAS 2.0+ XML-based authentication ticket.
+
+ :param: ticket
+ :param: service
+ """
+ return _internal_verify_cas(ticket, service, 'proxyValidate')
+
+
+def _verify_cas3(ticket, service):
+ return _internal_verify_cas(ticket, service, 'p3/proxyValidate')
+
+
+def _internal_verify_cas(ticket, service, suffix):
+ """Verifies CAS 2.0 and 3.0 XML-based authentication ticket.
+
+ Returns username on success and None on failure.
+ """
+
+ params = {'ticket': ticket, 'service': service}
+ if settings.CAS_PROXY_CALLBACK:
+ params['pgtUrl'] = settings.CAS_PROXY_CALLBACK
+
+ url = (urljoin(settings.CAS_SERVER_URL, suffix) + '?' +
+ urlencode(params))
+
+ page = urlopen(url)
+
+ username = None
+
+ try:
+ response = page.read()
+ tree = ElementTree.fromstring(response)
+ document = minidom.parseString(response)
+
+ if tree[0].tag.endswith('authenticationSuccess'):
+ if settings.CAS_RESPONSE_CALLBACKS:
+ cas_response_callbacks(tree)
+
+ username = tree[0][0].text
+
+ pgt_el = document.getElementsByTagName('cas:proxyGrantingTicket')
+
+ if pgt_el:
+ pgt = pgt_el[0].firstChild.nodeValue
+ try:
+ pgtIou = _get_pgtiou(pgt)
+ tgt = Tgt.objects.get(username=username)
+ tgt.tgt = pgtIou.tgt
+ tgt.save()
+ pgtIou.delete()
+ except Tgt.DoesNotExist:
+ Tgt.objects.create(username=username, tgt=pgtIou.tgt)
+ logger.info('Creating TGT ticket for {user}'.format(
+ user=username
+ ))
+ pgtIou.delete()
+ except Exception as e:
+ logger.warning('Failed to do proxy authentication. {message}'.format(
+ message=e
+ ))
+
+ else:
+ failure = document.getElementsByTagName('cas:authenticationFailure')
+ if failure:
+ logger.warn('Authentication failed from CAS server: %s',
+ failure[0].firstChild.nodeValue)
+
+ except Exception as e:
+ logger.error('Failed to verify CAS authentication: {message}'.format(
+ message=e
+ ))
+
+ finally:
+ page.close()
+
+ return username
+
+
+def verify_proxy_ticket(ticket, service):
+ """
+ Verifies CAS 2.0+ XML-based proxy ticket.
+
+ :param: ticket
+ :param: service
+
+ Returns username on success and None on failure.
+ """
+
+ params = {'ticket': ticket, 'service': service}
+
+ url = (urljoin(settings.CAS_SERVER_URL, 'proxyValidate') + '?' +
+ urlencode(params))
+
+ page = urlopen(url)
+
+ try:
+ response = page.read()
+ tree = ElementTree.fromstring(response)
+ if tree[0].tag.endswith('authenticationSuccess'):
+ username = tree[0][0].text
+ proxies = []
+ if len(tree[0]) > 1:
+ for element in tree[0][1]:
+ proxies.append(element.text)
+ return {"username": username, "proxies": proxies}
+ else:
+ return None
+ finally:
+ page.close()
+
+_PROTOCOLS = {'1': _verify_cas1, '2': _verify_cas2, '3': _verify_cas3}
+
+if settings.CAS_VERSION not in _PROTOCOLS:
+ raise ValueError('Unsupported CAS_VERSION %r' % settings.CAS_VERSION)
+
+_verify = _PROTOCOLS[settings.CAS_VERSION]
+
+
+def _get_pgtiou(pgt):
+ """
+ Returns a PgtIOU object given a pgt.
+
+ The PgtIOU (tgt) is set by the CAS server in a different request
+ that has completed before this call, however, it may not be found in
+ the database by this calling thread, hence the attempt to get the
+ ticket is retried for up to 5 seconds. This should be handled some
+ better way.
+
+ Users can opt out of this waiting period by setting CAS_PGT_FETCH_WAIT = False
+
+ :param: pgt
+
+ """
+
+ pgtIou = None
+ retries_left = 5
+
+ if not settings.CAS_PGT_FETCH_WAIT:
+ retries_left = 1
+
+ while not pgtIou and retries_left:
+ try:
+ return PgtIOU.objects.get(tgt=pgt)
+ except PgtIOU.DoesNotExist:
+ if settings.CAS_PGT_FETCH_WAIT:
+ time.sleep(1)
+ retries_left -= 1
+ logger.info('Did not fetch ticket, trying again. {tries} tries left.'.format(
+ tries=retries_left
+ ))
+ raise CasTicketException("Could not find pgtIou for pgt %s" % pgt)
+
+
+class CASBackend(object):
+ """
+ CAS authentication backend
+ """
+
+ supports_object_permissions = False
+ supports_inactive_user = False
+
+ def authenticate(self, ticket, service):
+ """
+ Verifies CAS ticket and gets or creates User object
+ NB: Use of PT to identify proxy
+ """
+
+ User = get_user_model()
+ username = _verify(ticket, service)
+
+ if not username:
+ return None
+
+ try:
+ user = User.objects.get(username__iexact=username)
+ except User.DoesNotExist:
+ # user will have an "unusable" password
+ if settings.CAS_AUTO_CREATE_USER:
+ user = User.objects.create_user(username, '')
+ user.save()
+ else:
+ user = None
+ return user
+
+ def get_user(self, user_id):
+ """
+ Retrieve the user's entry in the User model if it exists
+ """
+
+ User = get_user_model()
+
+ try:
+ return User.objects.get(pk=user_id)
+ except User.DoesNotExist:
+ return None
diff --git a/cas/decorators.py b/cas/decorators.py
new file mode 100644
index 0000000..7cc0e3c
--- /dev/null
+++ b/cas/decorators.py
@@ -0,0 +1,93 @@
+try:
+ from functools import wraps
+except ImportError:
+ from django.utils.functional import wraps
+
+try:
+ from urllib import urlencode
+except ImportError:
+ from urllib.parse import urlencode
+
+from django.contrib.auth import REDIRECT_FIELD_NAME
+from django.http import HttpResponseForbidden, HttpResponseRedirect
+from django.utils.http import urlquote
+from django.conf import settings
+from django.core.exceptions import ImproperlyConfigured
+
+__all__ = ['permission_required', 'user_passes_test']
+
+
+def user_passes_test(test_func, login_url=None, redirect_field_name=REDIRECT_FIELD_NAME):
+ """
+ Replacement for django.contrib.auth.decorators.user_passes_test that
+ returns 403 Forbidden if the user is already logged in.
+ """
+
+ if not login_url:
+ login_url = settings.LOGIN_URL
+
+ def decorator(view_func):
+ @wraps(view_func)
+ def wrapper(request, *args, **kwargs):
+ if test_func(request.user):
+ return view_func(request, *args, **kwargs)
+ elif request.user.is_authenticated():
+ return HttpResponseForbidden('<h1>Permission denied</h1>')
+ else:
+ path = '%s?%s=%s' % (login_url, redirect_field_name,
+ urlquote(request.get_full_path()))
+ return HttpResponseRedirect(path)
+ return wrapper
+ return decorator
+
+
+def permission_required(perm, login_url=None):
+ """
+ Replacement for django.contrib.auth.decorators.permission_required that
+ returns 403 Forbidden if the user is already logged in.
+ """
+
+ return user_passes_test(lambda u: u.has_perm(perm), login_url=login_url)
+
+
+def gateway():
+ """
+ Authenticates single sign on session if ticket is available,
+ but doesn't redirect to sign in url otherwise.
+ """
+
+ if settings.CAS_GATEWAY == False:
+ raise ImproperlyConfigured('CAS_GATEWAY must be set to True')
+
+ def wrap(func):
+ def wrapped_f(*args):
+
+ from cas.views import login
+ request = args[0]
+
+ if request.user.is_authenticated():
+ # Is Authed, fine
+ pass
+ else:
+ path_with_params = request.path + '?' + urlencode(request.GET.copy())
+ if request.GET.get('ticket'):
+ # Not Authed, but have a ticket!
+ # Try to authenticate
+ response = login(request, path_with_params, False, True)
+ if isinstance(response, HttpResponseRedirect):
+ # For certain instances where a forbidden occurs, we need to pass instead of return a response.
+ return response
+ else:
+ #Not Authed, but no ticket
+ gatewayed = request.GET.get('gatewayed')
+ if gatewayed == 'true':
+ pass
+ else:
+ # Not Authed, try to authenticate
+ response = login(request, path_with_params, False, True)
+ if isinstance(response, HttpResponseRedirect):
+ return response
+
+ return func(*args)
+ return wrapped_f
+ return wrap
diff --git a/cas/exceptions.py b/cas/exceptions.py
new file mode 100644
index 0000000..c50d3c2
--- /dev/null
+++ b/cas/exceptions.py
@@ -0,0 +1,17 @@
+from django.core.exceptions import ValidationError
+
+
+class CasTicketException(ValidationError):
+ """
+ The ticket fails to validate
+ """
+
+ pass
+
+
+class CasConfigException(ValidationError):
+ """
+ The config is wrong
+ """
+
+ pass
diff --git a/cas/middleware.py b/cas/middleware.py
new file mode 100644
index 0000000..771a1d1
--- /dev/null
+++ b/cas/middleware.py
@@ -0,0 +1,92 @@
+"""CAS authentication middleware"""
+
+try:
+ from urllib import urlencode
+except ImportError:
+ from urllib.parse import urlencode
+
+from django.conf import settings
+from django.contrib.auth import REDIRECT_FIELD_NAME
+from django.contrib.auth import logout as do_logout
+from django.contrib.auth.views import login, logout
+from django.core.urlresolvers import reverse
+from django.http import HttpResponseRedirect, HttpResponseForbidden
+from django.core.exceptions import ImproperlyConfigured
+
+from cas.exceptions import CasTicketException
+from cas.views import login as cas_login, logout as cas_logout
+
+__all__ = ['CASMiddleware']
+
+
+class CASMiddleware(object):
+ """
+ Middleware that allows CAS authentication on admin pages
+ """
+
+ def process_request(self, request):
+ """
+ Checks that the authentication middleware is installed
+
+ :param: request
+
+ """
+
+ error = ("The Django CAS middleware requires authentication "
+ "middleware to be installed. Edit your MIDDLEWARE_CLASSES "
+ "setting to insert 'django.contrib.auth.middleware."
+ "AuthenticationMiddleware'.")
+ assert hasattr(request, 'user'), error
+
+ def process_view(self, request, view_func, view_args, view_kwargs):
+ """
+ Forwards unauthenticated requests to the admin page to the CAS
+ login URL, as well as calls to django.contrib.auth.views.login and
+ logout.
+ """
+
+ if view_func == login:
+ return cas_login(request, *view_args, **view_kwargs)
+ elif view_func == logout:
+ return cas_logout(request, *view_args, **view_kwargs)
+
+ if settings.CAS_ADMIN_PREFIX:
+ if not request.path.startswith(settings.CAS_ADMIN_PREFIX):
+ return None
+ elif not view_func.__module__.startswith('django.contrib.admin.'):
+ return None
+
+ if request.user.is_authenticated():
+ if request.user.is_staff:
+ return None
+ else:
+ error = ('<h1>Forbidden</h1><p>You do not have staff '
+ 'privileges.</p>')
+ return HttpResponseForbidden(error)
+
+ params = urlencode({REDIRECT_FIELD_NAME: request.get_full_path()})
+ return HttpResponseRedirect(reverse(cas_login) + '?' + params)
+
+ def process_exception(self, request, exception):
+ """
+ When we get a CasTicketException, that is probably caused by the ticket timing out.
+ So logout/login and get the same page again.
+ """
+
+ if isinstance(exception, CasTicketException):
+ do_logout(request)
+ # This assumes that request.path requires authentication.
+ return HttpResponseRedirect(request.path)
+ else:
+ return None
+
+
+class ProxyMiddleware(object):
+
+ # Middleware used to "fake" the django app that it lives at the Proxy Domain
+ def process_request(self, request):
+ proxy = getattr(settings, 'PROXY_DOMAIN', None)
+ if not proxy:
+ raise ImproperlyConfigured('To use Proxy Middleware you must set a PROXY_DOMAIN setting.')
+ else:
+ request.META['HTTP_HOST'] = proxy
diff --git a/cas/models.py b/cas/models.py
new file mode 100644
index 0000000..54b1606
--- /dev/null
+++ b/cas/models.py
@@ -0,0 +1,108 @@
+import logging
+from datetime import datetime
+try:
+ from xml.etree import ElementTree
+except ImportError:
+ from elementtree import ElementTree
+try:
+ from urlparse import urljoin
+except ImportError:
+ from urllib.parse import urljoin
+try:
+ from urllib import urlencode
+except ImportError:
+ from urllib.parse import urlencode
+try:
+ from urllib import urlopen
+except ImportError:
+ from urllib.request import urlopen
+
+from django.db import models
+from django.conf import settings
+from django.core.exceptions import ObjectDoesNotExist
+from django.db.models.signals import post_save
+
+
+from cas.exceptions import CasTicketException, CasConfigException
+
+
+logger = logging.getLogger(__name__)
+
+
+class Tgt(models.Model):
+ username = models.CharField(max_length=255, unique=True)
+ tgt = models.CharField(max_length=255)
+
+ def get_proxy_ticket_for(self, service):
+ """
+ Verifies CAS 2.0+ XML-based authentication ticket.
+
+ :param: service
+
+ Returns username on success and None on failure.
+ """
+
+ if not settings.CAS_PROXY_CALLBACK:
+ raise CasConfigException("No proxy callback set in settings")
+
+ params = {'pgt': self.tgt, 'targetService': service}
+
+ url = (urljoin(settings.CAS_SERVER_URL, 'proxy') + '?' +
+ urlencode(params))
+
+ page = urlopen(url)
+
+ try:
+ response = page.read()
+ tree = ElementTree.fromstring(response)
+ if tree[0].tag.endswith('proxySuccess'):
+ return tree[0][0].text
+ else:
+ logger.warning('Failed to get proxy ticket')
+ raise CasTicketException('Failed to get proxy ticket: %s' % \
+ tree[0].text.strip())
+ finally:
+ page.close()
+
+
+class PgtIOU(models.Model):
+ """
+ Proxy granting ticket and IOU
+ """
+ pgtIou = models.CharField(max_length = 255, unique = True)
+ tgt = models.CharField(max_length = 255)
+ created = models.DateTimeField(auto_now = True)
+
+
+def get_tgt_for(user):
+ """
+ Fetch a ticket granting ticket for a given user.
+
+ :param user: UserObj
+
+ :return: TGT or Exepction
+ """
+ if not settings.CAS_PROXY_CALLBACK:
+ raise CasConfigException("No proxy callback set in settings")
+
+ try:
+ return Tgt.objects.get(username=user.username)
+ except ObjectDoesNotExist:
+ logger.warning('No ticket found for user {user}'.format(
+ user=user.username
+ ))
+ raise CasTicketException("no ticket found for user " + user.username)
+
+
+def delete_old_tickets(**kwargs):
+ """
+ Delete tickets if they are over 2 days old
+ kwargs = ['raw', 'signal', 'instance', 'sender', 'created']
+
+ """
+ sender = kwargs.get('sender', None)
+ now = datetime.now()
+ expire = datetime(now.year, now.month, now.day - 2)
+ sender.objects.filter(created__lt=expire).delete()
+
+post_save.connect(delete_old_tickets, sender=PgtIOU)
diff --git a/cas/tests/__init__.py b/cas/tests/__init__.py
new file mode 100644
index 0000000..3b172cc
--- /dev/null
+++ b/cas/tests/__init__.py
@@ -0,0 +1,3 @@
+from cas.tests.test_smoke import *
+from cas.tests.test_backend import *
+from cas.tests.test_views import *
\ No newline at end of file
diff --git a/cas/tests/factories.py b/cas/tests/factories.py
new file mode 100644
index 0000000..865d348
--- /dev/null
... 1251 lines suppressed ...
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/python-django-casclient.git
More information about the Python-modules-commits
mailing list