Merge remote-tracking branch 'gstrat/elep' into elep
authorRadek Czajka <rczajka@rczajka.pl>
Sat, 20 Sep 2014 20:49:12 +0000 (22:49 +0200)
committerRadek Czajka <rczajka@rczajka.pl>
Sat, 20 Sep 2014 20:49:12 +0000 (22:49 +0200)
Conflicts:
cas_provider_examples/simple/urls.py

18 files changed:
.gitignore
MANIFEST.in
README.rst
cas_provider/__init__.py
cas_provider/admin.py
cas_provider/attribute_formatters.py
cas_provider/forms.py
cas_provider/locale/pl/LC_MESSAGES/django.mo [new file with mode: 0644]
cas_provider/locale/pl/LC_MESSAGES/django.po [new file with mode: 0644]
cas_provider/management/commands/cleanuptickets.py
cas_provider/models.py
cas_provider/templates/cas/warn.html
cas_provider/tests.py
cas_provider/views.py
cas_provider_examples/manage.py [new file with mode: 0644]
cas_provider_examples/simple/manage.py [deleted file]
cas_provider_examples/simple/settings.py
cas_provider_examples/simple/urls.py

index 6152084..2bed7fd 100644 (file)
@@ -1,6 +1,8 @@
 # Python garbage
 *.pyc
 *.egg-info
+/build
+/dist
 
 # Mac OS X garbage
 .DS_Store
index a87457c..d02b852 100644 (file)
@@ -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
index 258d685..d7bed27 100644 (file)
@@ -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
 ======
 
index 1dd6c7a..22cedc3 100644 (file)
@@ -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:
index 499fa43..90182e2 100644 (file)
@@ -1,5 +1,5 @@
 from django.contrib import admin
-from models import *
+from .models import *
 
 
 class ServiceTicketAdmin(admin.ModelAdmin):
index 55eb4dd..b06cd84 100644 (file)
@@ -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)
index 44b47b4..7f3d5b6 100644 (file)
@@ -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 (file)
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 (file)
index 0000000..241d1c8
--- /dev/null
@@ -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 <EMAIL@ADDRESS>, 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 <radoslaw.czajka@nowoczesnapolska.org.pl>\n"
+"Language-Team: LANGUAGE <LL@li.org>\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."
+
index 401bec1..c951993 100644 (file)
@@ -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)
index 4b75fca..43463d5 100644 (file)
@@ -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()
 
 
index ca9e561..9c74eee 100644 (file)
@@ -5,7 +5,7 @@ Warning
 {% endblock %}
 
 {% block content %}
-  <form action='{% url cas_login %}' method='get'>
+  <form action='{% url 'cas_login' %}' method='get'>
     <fieldset>
       <legend>Confirm to log in to {{ service }}</legend>
          <input type="hidden" name="service" value="{{ service }}">
index 46ba3ca..2f87330 100644 (file)
@@ -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, '''<cas:serviceResponse xmlns:cas="http://www.yale.edu/tp/cas">'''
+        self.assertEqual(response.content.decode('utf-8'),
+                                           '''<cas:serviceResponse xmlns:cas="http://www.yale.edu/tp/cas">'''
                                            '''<cas:authenticationSuccess>'''
                                            '''<cas:user>editor</cas:user>'''
                                            '''<cas:attributes>'''
                                            '''<cas:attraStyle>Jasig</cas:attraStyle>'''
+                                           '''<cas:email>editor@exapmle.com</cas:email>'''
                                            '''<cas:group>editor</cas:group>'''
-                                           '''<cas:is_staff>True</cas:is_staff>'''
                                            '''<cas:is_active>True</cas:is_active>'''
-                                           '''<cas:email>editor@exapmle.com</cas:email>'''
+                                           '''<cas:is_staff>True</cas:is_staff>'''
                                            '''</cas:attributes>'''
                                            '''</cas:authenticationSuccess>'''
                                            '''</cas:serviceResponse>''')
@@ -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, '''<cas:serviceResponse xmlns:cas="http://www.yale.edu/tp/cas">'''
+        self.assertEqual(response.content.decode('utf-8'),
+                                           '''<cas:serviceResponse xmlns:cas="http://www.yale.edu/tp/cas">'''
                                            '''<cas:authenticationSuccess>'''
                                            '''<cas:user>editor</cas:user>'''
                                            '''<cas:attraStyle>RubyCAS</cas:attraStyle>'''
+                                           '''<cas:email>editor@exapmle.com</cas:email>'''
                                            '''<cas:group>editor</cas:group>'''
-                                           '''<cas:is_staff>True</cas:is_staff>'''
                                            '''<cas:is_active>True</cas:is_active>'''
-                                           '''<cas:email>editor@exapmle.com</cas:email>'''
+                                           '''<cas:is_staff>True</cas:is_staff>'''
                                            '''</cas:authenticationSuccess>'''
                                            '''</cas:serviceResponse>''')
 
@@ -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, '''<cas:serviceResponse xmlns:cas="http://www.yale.edu/tp/cas">'''
+        self.assertEqual(response.content.decode('utf-8'),
+                                           '''<cas:serviceResponse xmlns:cas="http://www.yale.edu/tp/cas">'''
                                            '''<cas:authenticationSuccess>'''
                                            '''<cas:user>editor</cas:user>'''
                                            '''<cas:attribute name="attraStyle" value="Name-Value"/>'''
+                                           '''<cas:attribute name="email" value="editor@exapmle.com"/>'''
                                            '''<cas:attribute name="group" value="editor"/>'''
-                                           '''<cas:attribute name="is_staff" value="True"/>'''
                                            '''<cas:attribute name="is_active" value="True"/>'''
-                                           '''<cas:attribute name="email" value="editor@exapmle.com"/>'''
+                                           '''<cas:attribute name="is_staff" value="True"/>'''
                                            '''</cas:authenticationSuccess>'''
                                            '''</cas:serviceResponse>''')
 
@@ -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()]
     }
index a9beae5..0f53d38 100644 (file)
@@ -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):
         </cas:serviceResponse>''' % {
         '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 (file)
index 0000000..fc99637
--- /dev/null
@@ -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 (file)
index 3e4eedc..0000000
+++ /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)
index 209927c..1896e81 100644 (file)
@@ -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
index cf09e9b..1d8eafa 100644 (file)
@@ -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')),
 
                        )