python-django-casclient-1.2.0/ 0000775 0000000 0000000 00000000000 12751112323 0016233 5 ustar 00root root 0000000 0000000 python-django-casclient-1.2.0/.gitignore 0000664 0000000 0000000 00000000410 12751112323 0020216 0 ustar 00root root 0000000 0000000 *.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 python-django-casclient-1.2.0/.travis.yml 0000664 0000000 0000000 00000000547 12751112323 0020352 0 ustar 00root root 0000000 0000000 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 python-django-casclient-1.2.0/AUTHORS 0000664 0000000 0000000 00000000267 12751112323 0017310 0 ustar 00root root 0000000 0000000 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 python-django-casclient-1.2.0/CHANGELOG.md 0000664 0000000 0000000 00000000577 12751112323 0020055 0 ustar 00root root 0000000 0000000 ### 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. python-django-casclient-1.2.0/CONTRIBUTORS 0000664 0000000 0000000 00000000043 12751112323 0020110 0 ustar 00root root 0000000 0000000 epicserve rlmv bryankaplan cordmata python-django-casclient-1.2.0/LICENSE.mit 0000664 0000000 0000000 00000002110 12751112323 0020022 0 ustar 00root root 0000000 0000000 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. python-django-casclient-1.2.0/MANIFEST.in 0000664 0000000 0000000 00000000171 12751112323 0017770 0 ustar 00root root 0000000 0000000 include LICENSE.mit include README.md include CHANGELOG.md include AUTHORS include CONTRIBUTORS recursive-exclude * *.pyc python-django-casclient-1.2.0/README.md 0000664 0000000 0000000 00000010352 12751112323 0017513 0 ustar 00root root 0000000 0000000 # 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 python-django-casclient-1.2.0/cas/ 0000775 0000000 0000000 00000000000 12751112323 0017001 5 ustar 00root root 0000000 0000000 python-django-casclient-1.2.0/cas/__init__.py 0000664 0000000 0000000 00000001504 12751112323 0021112 0 ustar 00root root 0000000 0000000 """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 python-django-casclient-1.2.0/cas/backends.py 0000664 0000000 0000000 00000015403 12751112323 0021130 0 ustar 00root root 0000000 0000000 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 python-django-casclient-1.2.0/cas/decorators.py 0000664 0000000 0000000 00000006271 12751112323 0021526 0 ustar 00root root 0000000 0000000 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('
You do not have staff ' 'privileges.
') 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 python-django-casclient-1.2.0/cas/models.py 0000664 0000000 0000000 00000005656 12751112323 0020652 0 ustar 00root root 0000000 0000000 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) python-django-casclient-1.2.0/cas/tests/ 0000775 0000000 0000000 00000000000 12751112323 0020143 5 ustar 00root root 0000000 0000000 python-django-casclient-1.2.0/cas/tests/__init__.py 0000664 0000000 0000000 00000000152 12751112323 0022252 0 ustar 00root root 0000000 0000000 from cas.tests.test_smoke import * from cas.tests.test_backend import * from cas.tests.test_views import * python-django-casclient-1.2.0/cas/tests/factories.py 0000664 0000000 0000000 00000000466 12751112323 0022502 0 ustar 00root root 0000000 0000000 from django.contrib.auth.models import User import factory class UserFactory(factory.DjangoModelFactory): class Meta: model = User username = "derekst" first_name = "new" last_name = "user" is_staff = True password = factory.PostGenerationMethodCall('set_password', '1234') python-django-casclient-1.2.0/cas/tests/test_backend.py 0000664 0000000 0000000 00000001522 12751112323 0023143 0 ustar 00root root 0000000 0000000 import mock from django.test import TestCase from cas.backends import CASBackend from cas.tests import factories class CASBackendTest(TestCase): def setUp(self): self.user = factories.UserFactory.create() def test_get_user(self): backend = CASBackend() self.assertEqual(backend.get_user(self.user.pk), self.user) @mock.patch('cas.backends._verify') def test_user_auto_create(self, verify): username = 'faker' verify.return_value = username backend = CASBackend() with self.settings(CAS_AUTO_CREATE_USER=False): user = backend.authenticate('fake', 'fake') self.assertIsNone(user) with self.settings(CAS_AUTO_CREATE_USER=True): user = backend.authenticate('fake', 'fake') self.assertEquals(user.username, username) python-django-casclient-1.2.0/cas/tests/test_smoke.py 0000664 0000000 0000000 00000000273 12751112323 0022674 0 ustar 00root root 0000000 0000000 from cas.models import * from cas.backends import * from cas.middleware import * from cas.views import * from cas.decorators import * from cas.exceptions import * from cas.utils import * python-django-casclient-1.2.0/cas/tests/test_views.py 0000664 0000000 0000000 00000002670 12751112323 0022716 0 ustar 00root root 0000000 0000000 from django.test import TestCase, RequestFactory from django.test.utils import override_settings from cas.views import _redirect_url, _login_url, _logout_url, _service_url class RequestFactoryRemix(RequestFactory): path = '/' def get_host(self): return 'signin.k-state.edu' def is_secure(self): return False class CASViewsTestCase(TestCase): def setUp(self): self.request = RequestFactoryRemix() self.request.GET = {} self.request.META = {} def test_service_url(self): self.assertEqual(_service_url(self.request), 'http://signin.k-state.edu/') @override_settings(CAS_FORCE_SSL_SERVICE_URL=True) def test_service_url_forced_ssl(self): self.assertEqual(_service_url(self.request), 'https://signin.k-state.edu/') def test_redirect_url(self): self.assertEqual(_redirect_url(self.request), '/') self.request.META = {'HTTP_REFERER': '/home/'} self.assertEqual(_redirect_url(self.request), '/home/') self.request.GET = {'next': 'foo'} self.assertEqual(_redirect_url(self.request), 'foo') def test_login_url(self): self.assertEqual(_login_url('http://localhost:8000/accounts/login/'), 'http://signin.cas.com/login?service=http%3A%2F%2Flocalhost%3A8000%2Faccounts%2Flogin%2F') def test_logout_url(self): self.assertEqual(_logout_url(self.request), 'http://signin.cas.com/logout') python-django-casclient-1.2.0/cas/utils.py 0000664 0000000 0000000 00000001212 12751112323 0020507 0 ustar 00root root 0000000 0000000 import logging from django.conf import settings logger = logging.getLogger(__name__) def cas_response_callbacks(tree): callbacks = [] callbacks.extend(settings.CAS_RESPONSE_CALLBACKS) for path in callbacks: i = path.rfind('.') module, callback = path[:i], path[i+1:] try: mod = __import__(module, fromlist=['']) except ImportError as e: logger.error("Import Error: %s" % e) raise e try: func = getattr(mod, callback) except AttributeError as e: logger.error("Attribute Error: %s" % e) raise e func(tree) python-django-casclient-1.2.0/cas/views.py 0000664 0000000 0000000 00000016176 12751112323 0020523 0 ustar 00root root 0000000 0000000 import logging import datetime try: from urllib import urlencode except ImportError: from urllib.parse import urlencode try: import urlparse except ImportError: import urllib.parse as urlparse from operator import itemgetter from django.http import HttpResponseRedirect, HttpResponseForbidden, HttpResponse from django.conf import settings from django.contrib.auth import REDIRECT_FIELD_NAME from django.contrib import auth from django.core.urlresolvers import reverse from cas.models import PgtIOU __all__ = ['login', 'logout'] logger = logging.getLogger(__name__) def _service_url(request, redirect_to=None, gateway=False): """ Generates application service URL for CAS :param: request Request Object :param: redirect_to URL to redriect to :param: gateway Should this be a gatewayed pass through """ if settings.CAS_FORCE_SSL_SERVICE_URL: protocol = 'https://' else: protocol = ('http://', 'https://')[request.is_secure()] host = request.get_host() service = protocol + host + request.path if redirect_to: if '?' in service: service += '&' else: service += '?' if gateway: """ If gateway, capture params and reencode them before returning a url """ gateway_params = [(REDIRECT_FIELD_NAME, redirect_to), ('gatewayed', 'true')] query_dict = request.GET.copy() try: del query_dict['ticket'] except: pass query_list = query_dict.items() # remove duplicate params for item in query_list: for index, item2 in enumerate(gateway_params): if item[0] == item2[0]: gateway_params.pop(index) extra_params = gateway_params + query_list #Sort params by key name so they are always in the same order. sorted_params = sorted(extra_params, key=itemgetter(0)) service += urlencode(sorted_params) else: service += urlencode({REDIRECT_FIELD_NAME: redirect_to}) return service def _redirect_url(request): """ Redirects to referring page, or CAS_REDIRECT_URL if no referrer is set. :param: request RequestObj """ next = request.GET.get(REDIRECT_FIELD_NAME) if not next: if settings.CAS_IGNORE_REFERER: next = settings.CAS_REDIRECT_URL else: next = request.META.get('HTTP_REFERER', settings.CAS_REDIRECT_URL) host = request.get_host() prefix = (('http://', 'https://')[request.is_secure()] + host) if next.startswith(prefix): next = next[len(prefix):] return next def _login_url(service, ticket='ST', gateway=False): """ Generates CAS login URL :param: service Service URL :param: ticket Ticket :param: gateway Gatewayed """ LOGINS = {'ST': 'login', 'PT': 'proxyValidate'} if gateway: params = {'service': service, 'gateway': 'true'} else: params = {'service': service} if settings.CAS_EXTRA_LOGIN_PARAMS: params.update(settings.CAS_EXTRA_LOGIN_PARAMS) if not ticket: ticket = 'ST' login_type = LOGINS.get(ticket[:2], 'login') return urlparse.urljoin(settings.CAS_SERVER_URL, login_type) + '?' + urlencode(params) def _logout_url(request, next_page=None): """ Generates CAS logout URL :param: request RequestObj :param: next_page Page to redirect after logout. """ url = urlparse.urljoin(settings.CAS_SERVER_URL, 'logout') if next_page and getattr(settings, 'CAS_PROVIDE_URL_TO_LOGOUT', True): protocol = ('http://', 'https://')[request.is_secure()] host = request.get_host() url += '?' + urlencode({'url': protocol + host + next_page}) return url def login(request, next_page=None, required=False, gateway=False): """ Forwards to CAS login URL or verifies CAS ticket :param: request RequestObj :param: next_page Next page to redirect after login :param: required :param: gateway Gatewayed response """ if not next_page: next_page = _redirect_url(request) if request.user.is_authenticated(): return HttpResponseRedirect(next_page) ticket = request.GET.get('ticket') if gateway: service = _service_url(request, next_page, True) else: service = _service_url(request, next_page, False) if ticket: user = auth.authenticate(ticket=ticket, service=service) if user is not None: auth.login(request, user) if settings.CAS_PROXY_CALLBACK: proxy_callback(request) return HttpResponseRedirect(next_page) elif settings.CAS_RETRY_LOGIN or required: if gateway: return HttpResponseRedirect(_login_url(service, ticket, True)) else: return HttpResponseRedirect(_login_url(service, ticket, False)) else: logger.warning('User has a valid ticket but not a valid session') # Has ticket, not session if gateway: # Gatewayed responses should nto redirect. return False if getattr(settings, 'CAS_CUSTOM_FORBIDDEN'): return HttpResponseRedirect(reverse(settings.CAS_CUSTOM_FORBIDDEN) + "?" + request.META['QUERY_STRING']) else: error = "Login failed.
" return HttpResponseForbidden(error) else: if gateway: return HttpResponseRedirect(_login_url(service, ticket, True)) else: return HttpResponseRedirect(_login_url(service, ticket, False)) def logout(request, next_page=None): """ Redirects to CAS logout page :param: request RequestObj :param: next_page Page to redirect to """ auth.logout(request) if not next_page: next_page = _redirect_url(request) if settings.CAS_LOGOUT_COMPLETELY: return HttpResponseRedirect(_logout_url(request, next_page)) else: return HttpResponseRedirect(next_page) def proxy_callback(request): """Handles CAS 2.0+ XML-based proxy callback call. Stores the proxy granting ticket in the database for future use. NB: Use created and set it in python in case database has issues with setting up the default timestamp value """ pgtIou = request.GET.get('pgtIou') tgt = request.GET.get('pgtId') if not (pgtIou and tgt): logger.info('No pgtIou or tgt found in request.GET') return HttpResponse('No pgtIOO', content_type="text/plain") try: PgtIOU.objects.create(tgt=tgt, pgtIou=pgtIou, created=datetime.datetime.now()) request.session['pgt-TICKET'] = pgtIou return HttpResponse('PGT ticket is: {ticket}'.format(ticket=pgtIou), content_type="text/plain") except Exception as e: logger.warning('PGT storage failed. {message}'.format( message=e )) return HttpResponse('PGT storage failed for {request}'.format(request=str(request.GET)), content_type="text/plain") python-django-casclient-1.2.0/docs/ 0000775 0000000 0000000 00000000000 12751112323 0017163 5 ustar 00root root 0000000 0000000 python-django-casclient-1.2.0/docs/Makefile 0000664 0000000 0000000 00000016436 12751112323 0020635 0 ustar 00root root 0000000 0000000 # Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = build # User-friendly check for sphinx-build ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) endif # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext help: @echo "Please use \`make