pax_global_header 0000666 0000000 0000000 00000000064 13511130012 0014475 g ustar 00root root 0000000 0000000 52 comment=2d2a202a2c1bcdedc8a66eebfac318ae494e6a86 django-cas-1.5.1/ 0000775 0000000 0000000 00000000000 13511130012 0013507 5 ustar 00root root 0000000 0000000 django-cas-1.5.1/.gitignore 0000664 0000000 0000000 00000000410 13511130012 0015472 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 django-cas-1.5.1/.travis.yml 0000664 0000000 0000000 00000000547 13511130012 0015626 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 django-cas-1.5.1/AUTHORS 0000664 0000000 0000000 00000000267 13511130012 0014564 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 django-cas-1.5.1/CHANGELOG.md 0000664 0000000 0000000 00000002056 13511130012 0015323 0 ustar 00root root 0000000 0000000 ### 1.5.1 - Include request in authenticate and evaluate gateway query_list (https://github.com/kstateome/django-cas/pull/87) ### 1.5.0 - Path Change (https://github.com/kstateome/django-cas/pull/76) - Update authenticate method (https://github.com/kstateome/django-cas/pull/77/files) ### 1.4.0 - Compatibility with Django 2.0 (https://github.com/kstateome/django-cas/pull/72) ### 1.3.0 - Compatibility with Django 1.10 Middleware (https://github.com/kstateome/django-cas/pull/69) - Add support for protocol-rooted URL as "next_page" argument for _logout_url constructor (https://github.com/kstateome/django-cas/pull/67) - Fix typo (https://github.com/kstateome/django-cas/pull/65) ### 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. django-cas-1.5.1/CONTRIBUTORS 0000664 0000000 0000000 00000000126 13511130012 0015366 0 ustar 00root root 0000000 0000000 epicserve rlmv bryankaplan cordmata bltravis JordanReiter balsdorf kensler ryanbagwell django-cas-1.5.1/LICENSE.mit 0000664 0000000 0000000 00000002110 13511130012 0015276 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. django-cas-1.5.1/MANIFEST.in 0000664 0000000 0000000 00000000171 13511130012 0015244 0 ustar 00root root 0000000 0000000 include LICENSE.mit include README.md include CHANGELOG.md include AUTHORS include CONTRIBUTORS recursive-exclude * *.pyc django-cas-1.5.1/README.md 0000664 0000000 0000000 00000010373 13511130012 0014772 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.5.1 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.5.1 ### Add to URLs Add the login and logout patterns to your main URLS conf. import cas.views ... # CAS path('admin/login/', cas.views.login, name='login'), path('admin/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 ``False``. ## 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 django-cas-1.5.1/cas/ 0000775 0000000 0000000 00000000000 13511130012 0014255 5 ustar 00root root 0000000 0000000 django-cas-1.5.1/cas/__init__.py 0000664 0000000 0000000 00000001476 13511130012 0016376 0 ustar 00root root 0000000 0000000 default_app_config = 'cas.apps.CASConfig' 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 django-cas-1.5.1/cas/apps.py 0000664 0000000 0000000 00000000151 13511130012 0015567 0 ustar 00root root 0000000 0000000 from django.apps import AppConfig class CASConfig(AppConfig): name = 'cas' verbose_name = "cas" django-cas-1.5.1/cas/backends.py 0000664 0000000 0000000 00000015414 13511130012 0016406 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, request, 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 django-cas-1.5.1/cas/decorators.py 0000664 0000000 0000000 00000007150 13511130012 0016777 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): try: # use callable for pre-django 2.0 is_authenticated = request.user.is_authenticated() except TypeError: is_authenticated = request.user.is_authenticated if test_func(request.user): return view_func(request, *args, **kwargs) elif 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(MiddlewareMixin): # 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 django-cas-1.5.1/cas/models.py 0000664 0000000 0000000 00000005656 13511130012 0016126 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) django-cas-1.5.1/cas/tests/ 0000775 0000000 0000000 00000000000 13511130012 0015417 5 ustar 00root root 0000000 0000000 django-cas-1.5.1/cas/tests/__init__.py 0000664 0000000 0000000 00000000152 13511130012 0017526 0 ustar 00root root 0000000 0000000 from cas.tests.test_smoke import * from cas.tests.test_backend import * from cas.tests.test_views import * django-cas-1.5.1/cas/tests/factories.py 0000664 0000000 0000000 00000000466 13511130012 0017756 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') django-cas-1.5.1/cas/tests/test_backend.py 0000664 0000000 0000000 00000001522 13511130012 0020417 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) django-cas-1.5.1/cas/tests/test_smoke.py 0000664 0000000 0000000 00000000273 13511130012 0020150 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 * django-cas-1.5.1/cas/tests/test_views.py 0000664 0000000 0000000 00000003707 13511130012 0020174 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 SecureRequestFactory(RequestFactory): path = '/' def get_host(self): return 'signin.k-state.edu' def is_secure(self): return True 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') @override_settings(CAS_IGNORE_REFERER=False) @override_settings(CAS_REDIRECT_URL=None) def test_https_redirect_url(self): self.request = SecureRequestFactory() self.request.GET = {} self.request.META = {'HTTP_REFERER': 'https://example.com/'} self.assertEqual(_redirect_url(self.request), 'https://example.com/') 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') django-cas-1.5.1/cas/utils.py 0000664 0000000 0000000 00000001212 13511130012 0015763 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) django-cas-1.5.1/cas/views.py 0000664 0000000 0000000 00000017165 13511130012 0015776 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 try: from django.urls import reverse except ImportError: 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 + list(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): parsed_url = urlparse.urlparse(next_page) if parsed_url.scheme: #If next_page is a protocol-rooted url, skip redirect url construction url += '?' + urlencode({'service': next_page}) else: protocol = ('http://', 'https://')[request.is_secure()] host = request.get_host() url += '?' + urlencode({'service': 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) try: # use callable for pre-django 2.0 is_authenticated = request.user.is_authenticated() except TypeError: is_authenticated = request.user.is_authenticated if 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(request=request, 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") django-cas-1.5.1/docs/ 0000775 0000000 0000000 00000000000 13511130012 0014437 5 ustar 00root root 0000000 0000000 django-cas-1.5.1/docs/Makefile 0000664 0000000 0000000 00000016436 13511130012 0016111 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