From: Radek Czajka Date: Sat, 20 Sep 2014 20:49:12 +0000 (+0200) Subject: Merge remote-tracking branch 'gstrat/elep' into elep X-Git-Tag: 22.4~10^2~5 X-Git-Url: https://git.mdrn.pl/django-cas-provider.git/commitdiff_plain/288b97cb037b17ccab0c9c11ec987ad124e350d1?hp=ef87a0cbe5fa23dd273df9c1360037507b48fc4e Merge remote-tracking branch 'gstrat/elep' into elep Conflicts: cas_provider_examples/simple/urls.py --- diff --git a/.gitignore b/.gitignore index 6152084..2bed7fd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ # Python garbage *.pyc *.egg-info +/build +/dist # Mac OS X garbage .DS_Store diff --git a/MANIFEST.in b/MANIFEST.in index a87457c..d02b852 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,6 @@ recursive-include cas_provider/templates *.html recursive-include cas_provider/fixtures * +recursive-include cas_provider/locale *.po *.mo include AUTHORS.txt include README.rst -include LICENSE \ No newline at end of file +include LICENSE diff --git a/README.rst b/README.rst index 258d685..d7bed27 100644 --- a/README.rst +++ b/README.rst @@ -19,29 +19,6 @@ Or, put `cas_provider` somewhere on your Python path. If you want use CAS v.2 protocol or above, you must install `lxml` package to correct work. -UPDATING FROM PREVIOUS VERSION -=============================== - -I introduced south for DB schema migration. The schema from any previous version without south is 0001_initial. -You will get an error: - - ``Running migrations for cas_provider:`` - - ``- Migrating forwards to 0001_initial.`` - - ``> cas_provider:0001_initial`` - - ``Traceback (most recent call last):`` - - ``...`` - - ``django.db.utils.DatabaseError: relation "cas_provider_serviceticket" already exists`` - -to circumvent that problem you will need to fake the initial migration: - - python manage.py migrate cas_provider 0001_initial --fake - - USAGE ====== diff --git a/cas_provider/__init__.py b/cas_provider/__init__.py index 1dd6c7a..22cedc3 100644 --- a/cas_provider/__init__.py +++ b/cas_provider/__init__.py @@ -8,7 +8,7 @@ _DEFAULTS = { 'CAS_AUTO_REDIRECT_AFTER_LOGOUT': False, } -for key, value in _DEFAULTS.iteritems(): +for key, value in _DEFAULTS.items(): try: getattr(settings, key) except AttributeError: diff --git a/cas_provider/admin.py b/cas_provider/admin.py index 499fa43..90182e2 100644 --- a/cas_provider/admin.py +++ b/cas_provider/admin.py @@ -1,5 +1,5 @@ from django.contrib import admin -from models import * +from .models import * class ServiceTicketAdmin(admin.ModelAdmin): diff --git a/cas_provider/attribute_formatters.py b/cas_provider/attribute_formatters.py index 55eb4dd..b06cd84 100644 --- a/cas_provider/attribute_formatters.py +++ b/cas_provider/attribute_formatters.py @@ -6,11 +6,17 @@ NSMAP = {'cas': CAS_URI} CAS = '{%s}' % CAS_URI +try: + basestring +except NameError: + basestring = (str, bytes) + + def jasig(auth_success, attrs): attributes = etree.SubElement(auth_success, CAS + 'attributes') style = etree.SubElement(attributes, CAS + 'attraStyle') style.text = u'Jasig' - for name, value in attrs.items(): + for name, value in sorted(attrs.items()): if isinstance(value, collections.Iterable) and not isinstance(value, basestring): for e in value: element = etree.SubElement(attributes, CAS + name) @@ -23,7 +29,7 @@ def jasig(auth_success, attrs): def ruby_cas(auth_success, attrs): style = etree.SubElement(auth_success, CAS + 'attraStyle') style.text = u'RubyCAS' - for name, value in attrs.items(): + for name, value in sorted(attrs.items()): if isinstance(value, collections.Iterable) and not isinstance(value, basestring): for e in value: element = etree.SubElement(auth_success, CAS + name) @@ -35,7 +41,7 @@ def ruby_cas(auth_success, attrs): def name_value(auth_success, attrs): etree.SubElement(auth_success, CAS + 'attribute', name=u'attraStyle', value=u'Name-Value') - for name, value in attrs.items(): + for name, value in sorted(attrs.items()): if isinstance(value, collections.Iterable) and not isinstance(value, basestring): for e in value: etree.SubElement(auth_success, CAS + 'attribute', name=name, value=e) diff --git a/cas_provider/forms.py b/cas_provider/forms.py index 44b47b4..7f3d5b6 100644 --- a/cas_provider/forms.py +++ b/cas_provider/forms.py @@ -1,10 +1,12 @@ from django import forms +from django.utils.translation import ugettext_lazy as _ class LoginForm(forms.Form): username = forms.CharField(widget=forms.TextInput(attrs={'autofocus': 'autofocus', - 'max_length': '255'})) - password = forms.CharField(widget=forms.PasswordInput()) + 'max_length': '255'}), + label=_('Username')) + password = forms.CharField(widget=forms.PasswordInput(), label=_('Password')) service = forms.CharField(widget=forms.HiddenInput, required=False) def __init__(self, *args, **kwargs): @@ -16,4 +18,5 @@ class LoginForm(forms.Form): class MergeLoginForm(LoginForm): - username = forms.CharField(max_length=255, widget=forms.HiddenInput) + username = forms.CharField(max_length=255, widget=forms.HiddenInput, + label=_('Username')) diff --git a/cas_provider/locale/pl/LC_MESSAGES/django.mo b/cas_provider/locale/pl/LC_MESSAGES/django.mo new file mode 100644 index 0000000..7701b54 Binary files /dev/null and b/cas_provider/locale/pl/LC_MESSAGES/django.mo differ diff --git a/cas_provider/locale/pl/LC_MESSAGES/django.po b/cas_provider/locale/pl/LC_MESSAGES/django.po new file mode 100644 index 0000000..241d1c8 --- /dev/null +++ b/cas_provider/locale/pl/LC_MESSAGES/django.po @@ -0,0 +1,98 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2013-04-15 10:51+0200\n" +"PO-Revision-Date: 2013-04-15 10:52+0100\n" +"Last-Translator: Radek Czajka \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" + +#: forms.py:8 +#: forms.py:22 +msgid "Username" +msgstr "Użytkownik" + +#: forms.py:9 +msgid "Password" +msgstr "Hasło" + +#: models.py:18 +msgid "ticket" +msgstr "" + +#: models.py:19 +msgid "created" +msgstr "" + +#: models.py:38 +msgid "user" +msgstr "" + +#: models.py:39 +msgid "service" +msgstr "" + +#: models.py:44 +msgid "Service Ticket" +msgstr "" + +#: models.py:45 +msgid "Service Tickets" +msgstr "" + +#: models.py:62 +msgid "Login Ticket" +msgstr "" + +#: models.py:63 +msgid "Login Tickets" +msgstr "" + +#: models.py:68 +msgid "PGTiou" +msgstr "" + +#: models.py:77 +#: models.py:82 +#: models.py:92 +msgid "Proxy Granting Ticket" +msgstr "" + +#: models.py:78 +msgid "Proxy Granting Tickets" +msgstr "" + +#: models.py:87 +msgid "Proxy Ticket" +msgstr "" + +#: models.py:88 +msgid "Proxy Tickets" +msgstr "" + +#: models.py:97 +msgid "Proxy Granting Ticket IOU" +msgstr "" + +#: models.py:98 +msgid "Proxy Granting Tickets IOU" +msgstr "" + +#: views.py:128 +msgid "Incorrect username and/or password." +msgstr "Błędny użytkownik i/lub hasło." + +#: views.py:142 +msgid "This account is disabled. Please contact us if you feel it should be enabled again." +msgstr "To konto jest nieaktywne. Prosimy o kontakt, jeśli uważasz że powinno zostać aktywowane." + diff --git a/cas_provider/management/commands/cleanuptickets.py b/cas_provider/management/commands/cleanuptickets.py index 401bec1..c951993 100644 --- a/cas_provider/management/commands/cleanuptickets.py +++ b/cas_provider/management/commands/cleanuptickets.py @@ -6,6 +6,7 @@ Calls ``ServiceTickets.objects.delete_expired_users()``, which contains the actual logic for determining which accounts are deleted. """ +from __future__ import print_function from django.core.management.base import NoArgsCommand from django.conf import settings @@ -18,21 +19,21 @@ class Command(NoArgsCommand): help = "Delete expired service tickets from the database" def handle_noargs(self, **options): - print "Service tickets:" + print("Service tickets:") tickets = ServiceTicket.objects.all() for ticket in tickets: expiration = datetime.timedelta(minutes=settings.CAS_TICKET_EXPIRATION) if datetime.datetime.now() > ticket.created + expiration: - print "Deleting %s..." % ticket.ticket + print("Deleting %s..." % ticket.ticket) ticket.delete() else: - print "%s not expired..." % ticket.ticket + print("%s not expired..." % ticket.ticket) tickets = LoginTicket.objects.all() - print "Login tickets:" + print("Login tickets:") for ticket in tickets: expiration = datetime.timedelta(minutes=settings.CAS_TICKET_EXPIRATION) if datetime.datetime.now() > ticket.created + expiration: - print "Deleting %s..." % ticket.ticket + print("Deleting %s..." % ticket.ticket) ticket.delete() else: - print "%s not expired..." % ticket.ticket \ No newline at end of file + print("%s not expired..." % ticket.ticket) diff --git a/cas_provider/models.py b/cas_provider/models.py index 4b75fca..43463d5 100644 --- a/cas_provider/models.py +++ b/cas_provider/models.py @@ -3,14 +3,15 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ from random import Random import string -import urllib -import urlparse - -if hasattr(urlparse, 'parse_qs'): - parse_qs = urlparse.parse_qs -else: - # Python <2.6 compatibility - from cgi import parse_qs +try: + from urllib.parse import urlencode, urlparse, parse_qs, ParseResult +except ImportError: + from urllib import urlencode + from urlparse import urlparse, ParseResult + try: + from urlparse import parse_qs + except: # Python <2.6 compatibility + from cgi import parse_qs __all__ = ['ServiceTicket', 'LoginTicket', 'ProxyGrantingTicket', 'ProxyTicket', 'ProxyGrantingTicketIOU'] @@ -45,13 +46,13 @@ class ServiceTicket(BaseTicket): verbose_name_plural = _('Service Tickets') def get_redirect_url(self): - parsed = urlparse.urlparse(self.service) + parsed = urlparse(self.service) query = parse_qs(parsed.query) query['ticket'] = [self.ticket] - query = [((k, v) if len(v) > 1 else (k, v[0])) for k, v in query.iteritems()] - parsed = urlparse.ParseResult(parsed.scheme, parsed.netloc, + query = [((k, v) if len(v) > 1 else (k, v[0])) for k, v in query.items()] + parsed = ParseResult(parsed.scheme, parsed.netloc, parsed.path, parsed.params, - urllib.urlencode(query), parsed.fragment) + urlencode(query), parsed.fragment) return parsed.geturl() diff --git a/cas_provider/templates/cas/warn.html b/cas_provider/templates/cas/warn.html index ca9e561..9c74eee 100644 --- a/cas_provider/templates/cas/warn.html +++ b/cas_provider/templates/cas/warn.html @@ -5,7 +5,7 @@ Warning {% endblock %} {% block content %} -
+
Confirm to log in to {{ service }} diff --git a/cas_provider/tests.py b/cas_provider/tests.py index 46ba3ca..2f87330 100644 --- a/cas_provider/tests.py +++ b/cas_provider/tests.py @@ -1,26 +1,34 @@ -import StringIO -import urllib2 +from __future__ import unicode_literals +from io import StringIO from xml import etree from xml.etree import ElementTree import cas_provider from cas_provider.attribute_formatters import CAS, NSMAP from cas_provider.models import ServiceTicket +from cas_provider.signals import cas_collect_custom_attributes from cas_provider.views import _cas2_sucess_response, INVALID_TICKET, _cas2_error_response, generate_proxy_granting_ticket from django.contrib.auth.models import User, UserManager from django.core.urlresolvers import reverse from django.test import TestCase -from urlparse import urlparse, parse_qsl, parse_qs from django.conf import settings +try: + from urllib.parse import urlparse, parse_qsl, parse_qs + from urllib.request import install_opener +except: + from urlparse import urlparse, parse_qsl, parse_qs + from urllib2 import install_opener -dummy_urlopen_url = None +class DummyOpener(object): + url = None + + @staticmethod + def open(url, *args, **kwargs): + DummyOpener.url = url -def dummy_urlopen(url): - cas_provider.tests.dummy_urlopen_url = url - pass class ViewsTest(TestCase): @@ -33,7 +41,7 @@ class ViewsTest(TestCase): def test_successful_login_with_proxy(self): - urllib2.urlopen = dummy_urlopen # monkey patching urllib2.urlopen so that the testcase doesnt really opens a url + install_opener(DummyOpener) # Don't really open any URLs proxyTarget = "http://my.sweet.service" response = self._login_user('root', '123') @@ -41,7 +49,7 @@ class ViewsTest(TestCase): # Test: I'm acting as the service that will call another service # Step 1: Get the proxy granting ticket - responseXml = ElementTree.parse(StringIO.StringIO(response.content)) + responseXml = ElementTree.parse(StringIO(response.content.decode('utf-8'))) auth_success = responseXml.find(CAS + 'authenticationSuccess', namespaces=NSMAP) pgt = auth_success.find(CAS + "proxyGrantingTicket", namespaces=NSMAP) user = auth_success.find(CAS + "user", namespaces=NSMAP) @@ -49,18 +57,18 @@ class ViewsTest(TestCase): self.assertIsNotNone(pgt.text) self.assertTrue(pgt.text.startswith('PGTIOU')) - pgtId = parse_qs(urlparse(cas_provider.tests.dummy_urlopen_url).query)['pgtId'] + pgtId = parse_qs(urlparse(DummyOpener.url).query)['pgtId'] #Step 2: Get the actual proxy ticket proxyTicketResponse = self.client.get(reverse('proxy'), {'targetService': proxyTarget, 'pgt': pgtId}, follow=False) - proxyTicketResponseXml = ElementTree.parse(StringIO.StringIO(proxyTicketResponse.content)) + proxyTicketResponseXml = ElementTree.parse(StringIO(proxyTicketResponse.content.decode('utf-8'))) self.assertIsNotNone(proxyTicketResponseXml.find(CAS + "proxySuccess", namespaces=NSMAP)) self.assertIsNotNone(proxyTicketResponseXml.find(CAS + "proxySuccess/cas:proxyTicket", namespaces=NSMAP)) proxyTicket = proxyTicketResponseXml.find(CAS + "proxySuccess/cas:proxyTicket", namespaces=NSMAP); #Step 3: I have the proxy ticket I can talk to some other backend service as the currently logged in user! proxyValidateResponse = self.client.get(reverse('cas_proxy_validate'), {'ticket': proxyTicket.text, 'service': proxyTarget}) - proxyValidateResponseXml = ElementTree.parse(StringIO.StringIO(proxyValidateResponse.content)) + proxyValidateResponseXml = ElementTree.parse(StringIO(proxyValidateResponse.content.decode('utf-8'))) auth_success_2 = proxyValidateResponseXml.find(CAS + 'authenticationSuccess', namespaces=NSMAP) user_2 = auth_success.find(CAS + "user", namespaces=NSMAP) @@ -71,7 +79,7 @@ class ViewsTest(TestCase): def test_successful_proxy_chaining(self): - urllib2.urlopen = dummy_urlopen # monkey patching urllib2.urlopen so that the testcase doesnt really opens a url + install_opener(DummyOpener) # Don't really open any URLs proxyTarget_1 = "http://my.sweet.service_1" proxyTarget_2 = "http://my.sweet.service_2" @@ -80,7 +88,7 @@ class ViewsTest(TestCase): # Test: I'm acting as the service that will call another service # Step 1: Get the proxy granting ticket - responseXml = ElementTree.parse(StringIO.StringIO(response.content)) + responseXml = ElementTree.parse(StringIO(response.content.decode('utf-8'))) auth_success_1 = responseXml.find(CAS + 'authenticationSuccess', namespaces=NSMAP) pgt_1 = auth_success_1.find(CAS + "proxyGrantingTicket", namespaces=NSMAP) user_1 = auth_success_1.find(CAS + "user", namespaces=NSMAP) @@ -88,11 +96,11 @@ class ViewsTest(TestCase): self.assertIsNotNone(pgt_1.text) self.assertTrue(pgt_1.text.startswith('PGTIOU')) - pgtId_1 = parse_qs(urlparse(cas_provider.tests.dummy_urlopen_url).query)['pgtId'] + pgtId_1 = parse_qs(urlparse(DummyOpener.url).query)['pgtId'] #Step 2: Get the actual proxy ticket proxyTicketResponse_1 = self.client.get(reverse('proxy'), {'targetService': proxyTarget_1, 'pgt': pgtId_1}, follow=False) - proxyTicketResponseXml_1 = ElementTree.parse(StringIO.StringIO(proxyTicketResponse_1.content)) + proxyTicketResponseXml_1 = ElementTree.parse(StringIO(proxyTicketResponse_1.content.decode('utf-8'))) self.assertIsNotNone(proxyTicketResponseXml_1.find(CAS + "proxySuccess", namespaces=NSMAP)) self.assertIsNotNone(proxyTicketResponseXml_1.find(CAS + "proxySuccess/cas:proxyTicket", namespaces=NSMAP)) proxyTicket_1 = proxyTicketResponseXml_1.find(CAS + "proxySuccess/cas:proxyTicket", namespaces=NSMAP); @@ -100,7 +108,7 @@ class ViewsTest(TestCase): #Step 3: I'm backend service 1 - I have the proxy ticket - I want to talk to back service 2 # proxyValidateResponse_1 = self.client.get(reverse('cas_proxy_validate'), {'ticket': proxyTicket_1.text, 'service': proxyTarget_1, 'pgtUrl': proxyTarget_2}) - proxyValidateResponseXml_1 = ElementTree.parse(StringIO.StringIO(proxyValidateResponse_1.content)) + proxyValidateResponseXml_1 = ElementTree.parse(StringIO(proxyValidateResponse_1.content.decode('utf-8'))) auth_success_2 = proxyValidateResponseXml_1.find(CAS + 'authenticationSuccess', namespaces=NSMAP) user_2 = auth_success_2.find(CAS + "user", namespaces=NSMAP) @@ -116,17 +124,17 @@ class ViewsTest(TestCase): self.assertIsNotNone(pgt_2.text) self.assertTrue(pgt_2.text.startswith('PGTIOU')) - pgtId_2 = parse_qs(urlparse(cas_provider.tests.dummy_urlopen_url).query)['pgtId'] + pgtId_2 = parse_qs(urlparse(DummyOpener.url).query)['pgtId'] #Step 4: Get the second proxy ticket proxyTicketResponse_2 = self.client.get(reverse('proxy'), {'targetService': proxyTarget_2, 'pgt': pgtId_2}) - proxyTicketResponseXml_2 = ElementTree.parse(StringIO.StringIO(proxyTicketResponse_2.content)) + proxyTicketResponseXml_2 = ElementTree.parse(StringIO(proxyTicketResponse_2.content.decode('utf-8'))) self.assertIsNotNone(proxyTicketResponseXml_2.find(CAS + "proxySuccess", namespaces=NSMAP)) self.assertIsNotNone(proxyTicketResponseXml_2.find(CAS + "proxySuccess/cas:proxyTicket", namespaces=NSMAP)) proxyTicket_2 = proxyTicketResponseXml_2.find(CAS + "proxySuccess/cas:proxyTicket", namespaces=NSMAP) proxyValidateResponse_3 = self.client.get(reverse('cas_proxy_validate'), {'ticket': proxyTicket_2.text, 'service': proxyTarget_2, 'pgtUrl': None}) - proxyValidateResponseXml_3 = ElementTree.parse(StringIO.StringIO(proxyValidateResponse_3.content)) + proxyValidateResponseXml_3 = ElementTree.parse(StringIO(proxyValidateResponse_3.content.decode('utf-8'))) auth_success_3 = proxyValidateResponseXml_3.find(CAS + 'authenticationSuccess', namespaces=NSMAP) user_3 = auth_success_3.find(CAS + "user", namespaces=NSMAP) @@ -193,20 +201,41 @@ class ViewsTest(TestCase): user = User.objects.get(username=self.username) self.assertEqual(response.content, _cas2_sucess_response(user).content) + def test_cas2_validate_twice(self): + response = self._login_user('root', '123') + response2 = self._validate_cas2(response, True) + user = User.objects.get(username=self.username) + self.assertEqual(response2.content, _cas2_sucess_response(user).content) + + response2 = self._validate_cas2(response, True) + self.assertEqual(response2.content, _cas2_error_response(INVALID_TICKET).content) + + def test_cas2_validate_twice_with_pgt(self): + proxyTarget = "http://my.sweet.service_1" + response = self._login_user('root', '123') + response2 = self._validate_cas2(response, True, proxyTarget) + user = User.objects.get(username=self.username) + self.assertEqual(response2.content, _cas2_sucess_response(user).content) + + response2 = self._validate_cas2(response, True, proxyTarget) + self.assertEqual(response2.content, _cas2_error_response(INVALID_TICKET).content) + def test_cas2_custom_attrs(self): - settings.CAS_CUSTOM_ATTRIBUTES_CALLBACK = cas_mapping + cas_collect_custom_attributes.connect(cas_mapping) response = self._login_user('editor', '123') + self.maxDiff=None response = self._validate_cas2(response, True) - self.assertEqual(response.content, '''''' + self.assertEqual(response.content.decode('utf-8'), + '''''' '''''' '''editor''' '''''' '''Jasig''' + '''editor@exapmle.com''' '''editor''' - '''True''' '''True''' - '''editor@exapmle.com''' + '''True''' '''''' '''''' '''''') @@ -215,14 +244,15 @@ class ViewsTest(TestCase): response = self._login_user('editor', '123') settings.CAS_CUSTOM_ATTRIBUTES_FORMATER = 'cas_provider.attribute_formatters.ruby_cas' response = self._validate_cas2(response, True) - self.assertEqual(response.content, '''''' + self.assertEqual(response.content.decode('utf-8'), + '''''' '''''' '''editor''' '''RubyCAS''' + '''editor@exapmle.com''' '''editor''' - '''True''' '''True''' - '''editor@exapmle.com''' + '''True''' '''''' '''''') @@ -230,14 +260,15 @@ class ViewsTest(TestCase): response = self._login_user('editor', '123') settings.CAS_CUSTOM_ATTRIBUTES_FORMATER = 'cas_provider.attribute_formatters.name_value' response = self._validate_cas2(response, True) - self.assertEqual(response.content, '''''' + self.assertEqual(response.content.decode('utf-8'), + '''''' '''''' '''editor''' '''''' + '''''' '''''' - '''''' '''''' - '''''' + '''''' '''''' '''''') @@ -249,7 +280,7 @@ class ViewsTest(TestCase): def test_generate_proxy_granting_ticket(self): - urllib2.urlopen = dummy_urlopen # monkey patching urllib2.urlopen so that the testcase doesnt really opens a url + install_opener(DummyOpener) # Don't really open any URLs url = 'http://my.call.back/callhere' user = User.objects.get(username = 'root') @@ -257,7 +288,7 @@ class ViewsTest(TestCase): pgt = generate_proxy_granting_ticket(url, st) self.assertIsNotNone(pgt) - calledUrl = cas_provider.tests.dummy_urlopen_url + calledUrl = DummyOpener.url parsedUrl = urlparse(calledUrl) params = parse_qs(parsedUrl.query) self.assertIsNotNone(params['pgtId']) @@ -286,7 +317,6 @@ class ViewsTest(TestCase): return self.client.post(reverse('cas_login'), { 'username': username, 'password': password, - 'lt': form['lt'].value(), 'service': service }, follow=False) @@ -300,14 +330,14 @@ class ViewsTest(TestCase): response = self.client.get(reverse('cas_validate'), {'ticket': ticket, 'service': self.service}, follow=False) self.assertEqual(response.status_code, 200) - self.assertEqual(unicode(response.content), u'yes\n%s\n' % self.username) + self.assertEqual(response.content.decode('utf-8'), 'yes\n%s\n' % self.username) else: self.assertEqual(response.status_code, 200) - self.assertEqual(len(response.context['form'].errors), 1) + self.assertEqual(len(response.context['errors']), 1) response = self.client.get(reverse('cas_validate'), {'ticket': 'ST-12312312312312312312312', 'service': self.service}, follow=False) self.assertEqual(response.status_code, 200) - self.assertEqual(response.content, u'no\n\n') + self.assertEqual(response.content.decode('utf-8'), 'no\n\n') def _validate_cas2(self, response, is_correct=True, pgtUrl = None): @@ -323,7 +353,7 @@ class ViewsTest(TestCase): self.assertEqual(response.status_code, 200) else: self.assertEqual(response.status_code, 200) - self.assertEqual(len(response.context['form'].errors), 1) + self.assertEqual(len(response.context['errors']), 1) response = self.client.get(reverse('cas_service_validate'), {'ticket': 'ST-12312312312312312312312', 'service': self.service}, follow=False) self.assertEqual(response.status_code, 200) @@ -342,10 +372,10 @@ class ModelsTestCase(TestCase): self.assertEqual(ticket.get_redirect_url(), '%(service)s?ticket=%(ticket)s' % ticket.__dict__) -def cas_mapping(user): +def cas_mapping(sender, user, **kwargs): return { - 'is_staff': unicode(user.is_staff), - 'is_active': unicode(user.is_active), + 'is_staff': str(user.is_staff), + 'is_active': str(user.is_active), 'email': user.email, 'group': [g.name for g in user.groups.all()] } diff --git a/cas_provider/views.py b/cas_provider/views.py index a9beae5..0f53d38 100644 --- a/cas_provider/views.py +++ b/cas_provider/views.py @@ -1,10 +1,14 @@ import logging logger = logging.getLogger('cas_provider.views') -import urllib -from urllib import urlencode -import urllib2 -import urlparse +try: + from urllib.error import HTTPError, URLError + from urllib.parse import parse_qsl, urlencode, urlparse, urlsplit, urlunsplit + from urllib.request import urlopen +except ImportError: + from urllib import urlencode + from urllib2 import HTTPError, URLError, urlopen + from urlparse import parse_qsl, urlparse, urlsplit, urlunsplit from functools import wraps from django.utils.decorators import available_attrs @@ -18,6 +22,7 @@ from django.conf import settings from django.contrib.auth import login as auth_login, logout as auth_logout from django.core.urlresolvers import get_callable from django.shortcuts import render_to_response +from django.utils.translation import ugettext as _ from django.template import RequestContext from django.contrib.auth import authenticate from django.core.urlresolvers import reverse @@ -118,7 +123,7 @@ def login(request, template_name='cas/login.html', ) if service is not None: args['service'] = service - args = urllib.urlencode(args) + args = urlencode(args) url = '%s?%s' % (base_url, args) logging.debug('Redirecting to %s', url) @@ -255,8 +260,8 @@ def ticket_validate(service, ticket_string, pgtUrl): except ServiceTicket.DoesNotExist: return _cas2_error_response(INVALID_TICKET) - ticketUrl = urlparse.urlparse(ticket.service) - serviceUrl = urlparse.urlparse(service) + ticketUrl = urlparse(ticket.service) + serviceUrl = urlparse(service) if not(ticketUrl.hostname == serviceUrl.hostname and ticketUrl.path == serviceUrl.path and ticketUrl.port == serviceUrl.port): return _cas2_error_response(INVALID_SERVICE) @@ -307,7 +312,7 @@ def proxy_validate(request): def generate_proxy_granting_ticket(pgt_url, ticket): proxy_callback_good_status = (200, 202, 301, 302, 304) - uri = list(urlparse.urlsplit(pgt_url)) + uri = list(urlsplit(pgt_url)) pgt = ProxyGrantingTicket() pgt.serviceTicket = ticket @@ -319,18 +324,18 @@ def generate_proxy_granting_ticket(pgt_url, ticket): params = {'pgtId': pgt.ticket, 'pgtIou': pgt.pgtiou} - query = dict(urlparse.parse_qsl(uri[4])) + query = dict(parse_qsl(uri[4])) query.update(params) uri[3] = urlencode(query) try: - urllib2.urlopen(urlparse.urlunsplit(uri)) - except urllib2.HTTPError as e: + urlopen(urlunsplit(uri)) + except HTTPError as e: if not e.code in proxy_callback_good_status: logger.debug('Checking Proxy Callback URL {} returned {}. Not issuing PGT.'.format(uri, e.code)) return - except urllib2.URLError as e: + except URLError as e: logger.debug('Checking Proxy Callback URL {} raised URLError. Not issuing PGT.'.format(uri)) return @@ -343,7 +348,7 @@ def _cas2_proxy_success(pt): def _cas2_sucess_response(user, pgt=None, proxies=None): - return HttpResponse(auth_success_response(user, pgt, proxies), mimetype='text/xml') + return HttpResponse(auth_success_response(user, pgt, proxies), content_type='text/xml') def _cas2_error_response(code, message=None): @@ -354,7 +359,7 @@ def _cas2_error_response(code, message=None): ''' % { 'code': code, 'message': message if message else dict(ERROR_MESSAGES).get(code) - }, mimetype='text/xml') + }, content_type='text/xml') def proxy_success(pt): @@ -362,7 +367,7 @@ def proxy_success(pt): proxySuccess = etree.SubElement(response, CAS + 'proxySuccess') proxyTicket = etree.SubElement(proxySuccess, CAS + 'proxyTicket') proxyTicket.text = pt - return unicode(etree.tostring(response, encoding='utf-8'), 'utf-8') + return etree.tostring(response, encoding='unicode') def auth_success_response(user, pgt, proxies): @@ -397,4 +402,4 @@ def auth_success_response(user, pgt, proxies): proxyElement = etree.SubElement(proxiesElement, CAS + "proxy") proxyElement.text = proxy - return unicode(etree.tostring(response, encoding='utf-8'), 'utf-8') + return etree.tostring(response, encoding='unicode') diff --git a/cas_provider_examples/manage.py b/cas_provider_examples/manage.py new file mode 100644 index 0000000..fc99637 --- /dev/null +++ b/cas_provider_examples/manage.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python +import os +import sys + +if __name__ == "__main__": + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "simple.settings") + + from django.core.management import execute_from_command_line + + execute_from_command_line(sys.argv) diff --git a/cas_provider_examples/simple/manage.py b/cas_provider_examples/simple/manage.py deleted file mode 100644 index 3e4eedc..0000000 --- a/cas_provider_examples/simple/manage.py +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env python -from django.core.management import execute_manager -import imp -try: - imp.find_module('settings') # Assumed to be in the same directory. -except ImportError: - import sys - sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n" % __file__) - sys.exit(1) - -import settings - -if __name__ == "__main__": - execute_manager(settings) diff --git a/cas_provider_examples/simple/settings.py b/cas_provider_examples/simple/settings.py index 209927c..1896e81 100644 --- a/cas_provider_examples/simple/settings.py +++ b/cas_provider_examples/simple/settings.py @@ -104,10 +104,11 @@ import os PROJECT_PATH = os.path.abspath(os.path.dirname(__file__)) TEMPLATE_DIRS = ( - os.path.join(PROJECT_PATH, 'templates') + os.path.join(PROJECT_PATH, 'templates'), ) INSTALLED_APPS = ( + 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', @@ -115,7 +116,6 @@ INSTALLED_APPS = ( 'django.contrib.messages', 'django.contrib.staticfiles', 'cas_provider', - 'south', ) # A sample logging configuration. The only tangible logging diff --git a/cas_provider_examples/simple/urls.py b/cas_provider_examples/simple/urls.py index cf09e9b..1d8eafa 100644 --- a/cas_provider_examples/simple/urls.py +++ b/cas_provider_examples/simple/urls.py @@ -1,10 +1,10 @@ from django.conf.urls import patterns, include, url - -import cas_provider -from django.views.generic.simple import redirect_to, direct_to_template +from django.contrib import admin +from django.views.generic import TemplateView urlpatterns = patterns('', + url(r'^admin/', include(admin.site.urls)), url(r'^', include('cas_provider.urls')), - url(r'^accounts/profile', direct_to_template, {'template': 'login-success-redirect-target.html'}), + url(r'^accounts/profile', TemplateView.as_view(template_name='login-success-redirect-target.html')), )