python-django-casclient-1.2.0/000077500000000000000000000000001275111232300162335ustar00rootroot00000000000000python-django-casclient-1.2.0/.gitignore000066400000000000000000000004101275111232300202160ustar00rootroot00000000000000*.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.yml000066400000000000000000000005471275111232300203520ustar00rootroot00000000000000language: 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/AUTHORS000066400000000000000000000002671275111232300173100ustar00rootroot00000000000000This 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.md000066400000000000000000000005771275111232300200550ustar00rootroot00000000000000### 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/CONTRIBUTORS000066400000000000000000000000431275111232300201100ustar00rootroot00000000000000epicserve rlmv bryankaplan cordmatapython-django-casclient-1.2.0/LICENSE.mit000066400000000000000000000021101275111232300200220ustar00rootroot00000000000000Copyright (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.in000066400000000000000000000001711275111232300177700ustar00rootroot00000000000000include LICENSE.mit include README.md include CHANGELOG.md include AUTHORS include CONTRIBUTORS recursive-exclude * *.pycpython-django-casclient-1.2.0/README.md000066400000000000000000000103521275111232300175130ustar00rootroot00000000000000# 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/000077500000000000000000000000001275111232300170015ustar00rootroot00000000000000python-django-casclient-1.2.0/cas/__init__.py000066400000000000000000000015041275111232300211120ustar00rootroot00000000000000"""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.py000066400000000000000000000154031275111232300211300ustar00rootroot00000000000000import 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.py000066400000000000000000000062711275111232300215260ustar00rootroot00000000000000try: 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('

Permission denied

') 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 python-django-casclient-1.2.0/cas/exceptions.py000066400000000000000000000003721275111232300215360ustar00rootroot00000000000000from django.core.exceptions import ValidationError class CasTicketException(ValidationError): """ The ticket fails to validate """ pass class CasConfigException(ValidationError): """ The config is wrong """ pass python-django-casclient-1.2.0/cas/middleware.py000066400000000000000000000062111275111232300214700ustar00rootroot00000000000000"""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 = ('

Forbidden

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.py000066400000000000000000000056561275111232300206520ustar00rootroot00000000000000import 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/000077500000000000000000000000001275111232300201435ustar00rootroot00000000000000python-django-casclient-1.2.0/cas/tests/__init__.py000066400000000000000000000001521275111232300222520ustar00rootroot00000000000000from 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.py000066400000000000000000000004661275111232300225020ustar00rootroot00000000000000from 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.py000066400000000000000000000015221275111232300231430ustar00rootroot00000000000000import 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.py000066400000000000000000000002731275111232300226740ustar00rootroot00000000000000from 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.py000066400000000000000000000026701275111232300227160ustar00rootroot00000000000000from 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.py000066400000000000000000000012121275111232300205070ustar00rootroot00000000000000import 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.py000066400000000000000000000161761275111232300205230ustar00rootroot00000000000000import 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 = "

Forbidden

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/000077500000000000000000000000001275111232300171635ustar00rootroot00000000000000python-django-casclient-1.2.0/docs/Makefile000066400000000000000000000164361275111232300206350ustar00rootroot00000000000000# 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 ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " applehelp to make an Apple Help Book" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" @echo " coverage to run coverage check of the documentation (if enabled)" clean: rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/DjangoCasClient.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/DjangoCasClient.qhc" applehelp: $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp @echo @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." @echo "N.B. You won't be able to view it unless you put it in" \ "~/Library/Documentation/Help or install it in your application" \ "bundle." devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/DjangoCasClient" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/DjangoCasClient" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." coverage: $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage @echo "Testing of coverage in the sources finished, look at the " \ "results in $(BUILDDIR)/coverage/python.txt." xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." python-django-casclient-1.2.0/docs/source/000077500000000000000000000000001275111232300204635ustar00rootroot00000000000000python-django-casclient-1.2.0/docs/source/changelog.rst000066400000000000000000000010501275111232300231400ustar00rootroot00000000000000Changelog ========= 1.2.0 (Unreleased) ------------------ - 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 ----- *Released 4-23-2015* - Add a few logging statements - Add official change log. Releases before 1.1.1 were tracked on GitHub https://github.com/kstateome/django-cas/releasespython-django-casclient-1.2.0/docs/source/conf.py000066400000000000000000000223431275111232300217660ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Django Cas Client documentation build configuration file, created by # sphinx-quickstart on Mon Apr 27 10:26:32 2015. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys import os import shlex # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.viewcode', ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # source_suffix = ['.rst', '.md'] source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'Django Cas Client' copyright = u'2015, Derek Stegelman, Garrett Pennington, and Other Contributors' author = u'Derek Stegelman, Garrett Pennington, and Other Contributors' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = '1.2.0' # The full version, including alpha/beta/rc tags. release = '1.2.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = [] # The reST default role (used for this markup: `text`) to use for all # documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. #keep_warnings = False # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'sphinx_rtd_theme' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. #html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Language to be used for generating the HTML full-text search index. # Sphinx supports the following languages: # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' #html_search_language = 'en' # A dictionary with options for the search language support, empty by default. # Now only 'ja' uses this config value #html_search_options = {'type': 'default'} # The name of a javascript file (relative to the configuration directory) that # implements a search results scorer. If empty, the default will be used. #html_search_scorer = 'scorer.js' # Output file base name for HTML help builder. htmlhelp_basename = 'DjangoCasClientdoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', # Additional stuff for the LaTeX preamble. #'preamble': '', # Latex figure (float) alignment #'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, 'DjangoCasClient.tex', u'Django Cas Client Documentation', u'Derek Stegelman Garrett Pennington Other Contributors', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ (master_doc, 'djangocasclient', u'Django Cas Client Documentation', [author], 1) ] # If true, show URL addresses after external links. #man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ (master_doc, 'DjangoCasClient', u'Django Cas Client Documentation', author, 'DjangoCasClient', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. #texinfo_appendices = [] # If false, no module index is generated. #texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. #texinfo_no_detailmenu = False python-django-casclient-1.2.0/docs/source/contributing.rst000066400000000000000000000004421275111232300237240ustar00rootroot00000000000000How 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 python-django-casclient-1.2.0/docs/source/gettingstarted.rst000066400000000000000000000064001275111232300242450ustar00rootroot00000000000000Getting Started =============== Requirements ------------ CAS client for Django. This library requires Django 1.5 or above, and Python 2.6, 2.7, 3.4 Install ------- This project is registered on PyPi as django-cas-client. To install:: pip install django-cas-client==1.1.1 Configuration ------------- **Add to URLs** Add the login and logout patterns to your main URLS conf:: 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', ) **Settings** 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 **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): 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). python-django-casclient-1.2.0/docs/source/index.rst000066400000000000000000000010151275111232300223210ustar00rootroot00000000000000.. Django Cas Client documentation master file, created by sphinx-quickstart on Mon Apr 27 10:26:32 2015. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Django Cas Client ================= Django Cas Client is CAS Client maintained by the webteam at Kansas State University. .. toctree:: :maxdepth: 2 gettingstarted contributing changelog Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` python-django-casclient-1.2.0/requirements-dev.txt000066400000000000000000000000141275111232300222660ustar00rootroot00000000000000factory_boy python-django-casclient-1.2.0/run_tests.py000066400000000000000000000040401275111232300206310ustar00rootroot00000000000000#!/usr/bin/env python import os, sys from django.conf import settings import django DIRNAME = os.path.dirname(__file__) if django.VERSION[1] < 4: # If the version is NOT django 4 or greater # then remove the TZ setting. settings.configure(DEBUG=True, DATABASES={ 'default': { 'ENGINE': 'django.db.backends.sqlite3', } }, #ROOT_URLCONF='mailqueue.urls', INSTALLED_APPS=('django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.admin', 'cas',), CAS_SERVER_URL = 'http://signin.cas.com', ) else: settings.configure(DEBUG=True, DATABASES={ 'default': { 'ENGINE': 'django.db.backends.sqlite3', } }, #ROOT_URLCONF='mailqueue.urls', INSTALLED_APPS=('django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.admin', 'cas',), USE_TZ=True, CAS_SERVER_URL = 'http://signin.cas.com',) try: # Django 1.7 needs this, but other versions dont. django.setup() except AttributeError: pass try: from django.test.simple import DjangoTestSuiteRunner test_runner = DjangoTestSuiteRunner(verbosity=1) except ImportError: from django.test.utils import get_runner TestRunner = get_runner(settings) test_runner = TestRunner() failures = test_runner.run_tests(['cas', ]) if failures: sys.exit(failures) python-django-casclient-1.2.0/setup.cfg000066400000000000000000000000331275111232300200500ustar00rootroot00000000000000[bdist_wheel] universal = 1python-django-casclient-1.2.0/setup.py000066400000000000000000000025011275111232300177430ustar00rootroot00000000000000import os from setuptools import setup, find_packages version = '1.2.0' def read(fname): return open(os.path.join(os.path.dirname(__file__), fname)).read() setup(name='django-cas-client', version=version, description="Django Cas Client", long_description=read('README.md'), classifiers=[ "Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "Framework :: Django", "Framework :: Django :: 1.5", "Framework :: Django :: 1.6", "Framework :: Django :: 1.7", "Intended Audience :: Developers", "Natural Language :: English", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 2", "Topic :: Internet :: WWW/HTTP :: Dynamic Content :: CGI Tools/Libraries", "Topic :: Utilities", "License :: OSI Approved :: MIT License", ], keywords='django cas', author='Derek Stegelman, Garrett Pennington', author_email='derekst@k-state.edu, garrettp@gmail.com', url='http://github.com/kstateome/django-cas/', license='MIT', packages=find_packages(), include_package_data=True, zip_safe=True, )