improve testing
authorOlivier Larchevêque <olivier.larcheveque@auf.org>
Mon, 28 Oct 2013 21:09:40 +0000 (17:09 -0400)
committerOlivier Larchevêque <olivier.larcheveque@auf.org>
Mon, 28 Oct 2013 21:09:40 +0000 (17:09 -0400)
auf/django/saml/decorators.py
auf/django/saml/monkey.py
auf/django/saml/tests/__init__.py
auf/django/saml/tests/common.py
auf/django/saml/tests/middleware.py [new file with mode: 0644]
auf/django/saml/tests/saml.py
auf/django/saml/tests/settings.py
auf/django/saml/tests/templates/test_tags.html
auf/django/saml/tests/urls.py
auf/django/saml/views.py
tox.ini

index cb79c50..ff0dc3e 100644 (file)
@@ -1,5 +1,8 @@
 # -*- coding: utf-8 -*-
 
+from django.http import HttpResponseForbidden
+from django.utils.translation import ugettext as _
+
 from views import redirect_to_login
 from permissions import is_employe
 from settings import SAML_REDIRECT_FIELD_NAME
@@ -15,10 +18,14 @@ def employe_required(
     """
 
     def _wrapped_view(request, *args, **kwargs):
+        if not request.user.is_authenticated():
+            return redirect_to_login(request, redirect_to=login_url)
         if is_employe(request.user):
             return function(request, *args, **kwargs)
         else:
-            return redirect_to_login(request, redirect_to=login_url)
+            return HttpResponseForbidden(
+                _(u"Votre compte ne permet pas d'accéder à cette page"))
+
     return _wrapped_view
 
 
index 511662e..b67d01b 100644 (file)
@@ -54,6 +54,8 @@ def custom_admin_view(self, view, cacheable=False):
     def inner(request, *args, **kwargs):
         """
         """
+        if not request.user.is_authenticated():
+            return self.login(request)
         if not self.has_permission(request):
             return HttpResponseForbidden(
                 _(u"Votre compte ne permet pas d'accéder à cette page"))
index 3e8b690..c357504 100644 (file)
@@ -1 +1,3 @@
-from .saml import TemplateTagTest  # flake8: noqa
+from .saml import TemplateTagTest  # noqa
+from .saml import AdminTest   # noqa
+from .saml import PermissionTest   # noqa
index 8f86a70..07441ac 100644 (file)
@@ -1,10 +1,26 @@
 # -*- coding: utf-8 -*-
 
+import urllib
+import urlparse
+
 from django.test import TestCase
 from django.test.client import Client
 
+from .middleware import ANONYMOUS_KEY
+
 
 class CommonTest(TestCase):
 
     def setUp(self):
         self.client = Client()
+
+    def anonymize(self, url):
+        params = {ANONYMOUS_KEY: 1, }
+        url_parts = urlparse.urlsplit(url)
+        qs = urlparse.parse_qs(url_parts[4])
+        qs.update(params)
+        return "%s?%s" % (url, urllib.urlencode(qs), )
+
+    def redirectize(self, url):
+        url_parts = urlparse.urlsplit(url)
+        return "%s?%s" % (url_parts[2], url_parts[3], )
diff --git a/auf/django/saml/tests/middleware.py b/auf/django/saml/tests/middleware.py
new file mode 100644 (file)
index 0000000..0a247e3
--- /dev/null
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+
+ANONYMOUS_KEY = 'anonymous'
+
+LOGGED_USER_EMAIL = 'admin@auf.org'
+LOGGED_USER_GN = 'admin_gn'
+LOGGED_USER_SN = 'admin_sn'
+
+
+class MockMiddleware(object):
+
+    def process_request(self, request):
+        if not ANONYMOUS_KEY in request.META['QUERY_STRING']:
+            request.META['REMOTE_USER'] = LOGGED_USER_EMAIL
+            request.META['MELLON_mail'] = LOGGED_USER_EMAIL
+            request.META['MELLON_gn'] = LOGGED_USER_GN
+            request.META['MELLON_sn'] = LOGGED_USER_SN
index 5602a03..90fe9e2 100644 (file)
 # -*- coding: utf-8 -*-
 
 import re
+import urllib
 
 from django.core.urlresolvers import reverse
 
+from django.contrib.auth.models import User
+
 from auf.django.saml import settings
+from auf.django.references import models as ref
 
+from .middleware import LOGGED_USER_EMAIL, LOGGED_USER_GN, LOGGED_USER_SN
 from .common import CommonTest
 
 
+class PermissionTest(CommonTest):
+    """
+    Teste les outils de sécurisation.
+    """
+
+    def test_employe_required_anonymous(self):
+        """
+        Test le decorateur sans utilisateur connecté.
+        """
+        url = self.anonymize(reverse('test_employe_required'))
+        response = self.client.get(url)
+        self.assertEqual(response.status_code, 302)
+
+    def test_employe_required_authenticated(self):
+        """
+        Test le decorateur avec un utilisateur connecté.
+        """
+        url = reverse('test_employe_required')
+        response = self.client.get(url)
+        self.assertEqual(response.status_code, 403)
+
+    def test_employe_required_ok(self):
+        """
+        Test le decorateur avec un employé connecté.
+        """
+        ref.Employe(
+            courriel=LOGGED_USER_EMAIL).save()
+        url = reverse('test_employe_required')
+        response = self.client.get(url)
+        self.assertEqual(response.status_code, 200)
+
+    def test_login_required(self):
+        """
+        Test le décorateur de connexion requise.
+        """
+        url = self.anonymize(reverse('test_login_required'))
+        response = self.client.get(url)
+        self.assertEqual(response.status_code, 302)
+
+
+class AdminTest(CommonTest):
+    """
+    Teste le comportement de l'admin avec l'IDP.
+    """
+
+    def test_anonymize(self):
+        """
+        Test la fonction qui ajoute un params GET.
+        """
+        url = self.anonymize(reverse('admin:index'))
+        self.assertEqual(url.count('?'), 1)
+
+    def test_admin_index_anonymous(self):
+        """
+        La page de login de l'admin doît être désactivée.
+        Il n'y a pas de redirection vers le login.
+        """
+        url = self.anonymize(reverse('admin:index'))
+        response = self.client.get(url)
+        self.assertEqual(
+            response['location'].count('?'),
+            1,
+            response['location'])
+        self.assertEqual(response.status_code, 302)
+        location, dummy = self.redirectize(response['location']).split('?')
+        self.assertEqual(location, settings.SAML_MELLON_LOGIN_URL)
+
+    def test_admin_index_authenticated(self):
+        """
+        L'admin est inacessible par défaut, mais le user est crée à la volée.
+        """
+        url = reverse('admin:index')
+        response = self.client.get(url)
+        self.assertEqual(response.status_code, 403)
+
+        self.assertEqual(User.objects.count(), 1)
+        user = User.objects.all()[0]
+        email = LOGGED_USER_EMAIL.replace('@auf.org', '')
+        self.assertEqual(user.username, email,)
+        self.assertEqual(user.email, LOGGED_USER_EMAIL,)
+        self.assertEqual(user.first_name, LOGGED_USER_GN)
+        self.assertEqual(user.last_name, LOGGED_USER_SN)
+
+    def test_admin_login(self):
+        """
+        Test l'accès à l'admin selon le flag is_staff du compte local.
+        """
+        url = reverse('admin:index')
+        self.client.get(url)
+        user = User.objects.all()[0]
+        user.is_staff = True
+        user.save()
+
+        response = self.client.get(url)
+        self.assertEqual(response.status_code, 200)
+
+    def test_admin_logout(self):
+        """
+        Test la redirection du logout local puis au IdP
+        """
+        url = reverse('admin:index')
+        self.client.get(url)
+        user = User.objects.all()[0]
+        user.is_staff = True
+        user.save()
+
+        url = reverse('admin:logout')
+        response = self.client.get(url)
+        location, qs = self.redirectize(response['location']).split('?')
+        self.assertEqual(response.status_code, 302)
+        self.assertEqual(location, reverse('local_logout'))
+
+
 class TemplateTagTest(CommonTest):
     """
     Teste les fonctionnalités des templatetags SAML.
     """
-    url = reverse('test_tags')
 
     def setUp(self):
         super(TemplateTagTest, self).setUp()
@@ -30,7 +147,7 @@ class TemplateTagTest(CommonTest):
         m = re.search(regex, self.response.content)
         login_url, redirect_url = m.groups()
         self.assertEqual(login_url, settings.SAML_MELLON_LOGIN_URL)
-        self.assertEqual(redirect_url, self.url)
+        self.assertEqual(redirect_url, urllib.quote_plus(self.url))
 
     def test_templatetag_login_default(self):
         """
@@ -41,7 +158,7 @@ class TemplateTagTest(CommonTest):
         m = re.search(regex, self.response.content)
         login_url, redirect_url = m.groups()
         self.assertEqual(login_url, settings.SAML_MELLON_LOGIN_URL)
-        self.assertEqual(redirect_url, self.url)
+        self.assertEqual(redirect_url, urllib.quote_plus(self.url))
 
     def test_templatetag_login_string(self):
         """
@@ -52,7 +169,7 @@ class TemplateTagTest(CommonTest):
         m = re.search(regex, self.response.content)
         login_url, redirect_url = m.groups()
         self.assertEqual(login_url, settings.SAML_MELLON_LOGIN_URL)
-        self.assertEqual(redirect_url, '/admin')
+        self.assertEqual(redirect_url, urllib.quote_plus('/admin'))
 
     def test_templatetag_logout_var(self):
         """
@@ -64,7 +181,7 @@ class TemplateTagTest(CommonTest):
         m = re.search(regex, self.response.content)
         logout_url, redirect_url = m.groups()
         self.assertEqual(logout_url, reverse('local_logout'))
-        self.assertEqual(redirect_url, self.url)
+        self.assertEqual(redirect_url, urllib.quote_plus(self.url))
 
     def test_templatetag_logout_default(self):
         """
@@ -75,7 +192,9 @@ class TemplateTagTest(CommonTest):
         m = re.search(regex, self.response.content)
         logout_url, redirect_url = m.groups()
         self.assertEqual(logout_url, reverse('local_logout'))
-        self.assertEqual(redirect_url, settings.SAML_LOGOUT_REDIRECT_URL)
+        self.assertEqual(
+            redirect_url,
+            urllib.quote_plus(settings.SAML_LOGOUT_REDIRECT_URL))
 
     def test_templatetag_logout_string(self):
         """
@@ -86,4 +205,4 @@ class TemplateTagTest(CommonTest):
         m = re.search(regex, self.response.content)
         logout_url, redirect_url = m.groups()
         self.assertEqual(logout_url, reverse('local_logout'))
-        self.assertEqual(redirect_url, '/admin')
+        self.assertEqual(redirect_url, urllib.quote_plus('/admin'))
index 0be039e..b297327 100644 (file)
@@ -24,6 +24,7 @@ MIDDLEWARE_CLASSES = (
     'django.contrib.sessions.middleware.SessionMiddleware',
     'django.middleware.csrf.CsrfViewMiddleware',
     'django.contrib.auth.middleware.AuthenticationMiddleware',
+    'auf.django.saml.tests.middleware.MockMiddleware',
     'auf.django.saml.middleware.SPMiddleware',
     'django.contrib.messages.middleware.MessageMiddleware',
 )
@@ -39,6 +40,10 @@ TEMPLATE_CONTEXT_PROCESSORS = (
     'django.contrib.messages.context_processors.messages',
     )
 
+AUTHENTICATION_BACKENDS = (
+    'auf.django.saml.backends.SPBackend',
+)
+
 TEMPLATE_DIRS = (
     os.path.join(os.path.dirname(__file__), 'templates'),
     )
index 97dbc72..d33a9f8 100644 (file)
@@ -1,4 +1,4 @@
-{% load saml_tags %}
+{% load saml_tags saml %}
 
 test_templatetag_login_default:{% mellon_login_url %}
 
index d949ffd..300b367 100644 (file)
@@ -5,6 +5,7 @@ from django.views.generic import TemplateView
 
 from django.contrib import admin
 
+from auf.django.saml.decorators import employe_required, login_required
 
 admin.autodiscover()
 
@@ -14,4 +15,12 @@ urlpatterns = patterns(
     (r'^admin/', include(admin.site.urls)),
     url(r'^test_tags/', TemplateView.as_view(template_name="test_tags.html"),
         name='test_tags'),
+    url(r'^test_employed_required/',
+        employe_required(
+            TemplateView.as_view(template_name="test_tags.html")),
+        name='test_employe_required'),
+    url(r'^test_login_required/',
+        login_required(
+            TemplateView.as_view(template_name="test_tags.html")),
+        name='test_login_required'),
     )
index d13e179..0eb83d1 100644 (file)
@@ -1,5 +1,7 @@
 # -*- coding: utf-8 -*-
 
+import urllib
+
 from django.core.urlresolvers import reverse
 from django.contrib.auth import login as auth_login
 from django.contrib.auth import logout as auth_logout
@@ -17,17 +19,19 @@ from settings import SAML_REDIRECT_FIELD_NAME,\
 
 
 def redirect_to_login(request, redirect_to=None, do_redirect=True):
+
     if redirect_to is None:
         redirect_to = request.get_full_path()
+
+    qs = {SAML_REDIRECT_FIELD_NAME: redirect_to}
+
     if SAML_AUTH:
         base_url = SAML_MELLON_LOGIN_URL
     else:
         base_url = reverse('sandbox_login')
-    url = "%s?%s=%s" % (
-        base_url,
-        SAML_REDIRECT_FIELD_NAME,
-        redirect_to,
-        )
+
+    url = "%s?%s" % (base_url, urllib.urlencode(qs))
+
     if do_redirect:
         return redirect(url)
     else:
@@ -37,11 +41,12 @@ def redirect_to_login(request, redirect_to=None, do_redirect=True):
 def redirect_to_logout(request, redirect_to=None, do_redirect=True):
     if redirect_to is None:
         redirect_to = SAML_LOGOUT_REDIRECT_URL
-    url = "%s?%s=%s" % (
-        reverse('local_logout'),
-        SAML_REDIRECT_FIELD_NAME,
-        redirect_to,
-        )
+
+    qs = {SAML_REDIRECT_FIELD_NAME: redirect_to}
+
+    url = u"%s?%s" % (
+        reverse('local_logout'), urllib.urlencode(qs), )
+
     if do_redirect:
         return redirect(url)
     else:
@@ -53,7 +58,9 @@ def login_form(request, ):
     Page de login en mode développement
     permet de se connecter avec un user selon son username défini localemement
     """
-    redirect_to = request.REQUEST.get(SAML_REDIRECT_FIELD_NAME, '/')
+    redirect_to = urllib.unquote_plus(
+        request.REQUEST.get(SAML_REDIRECT_FIELD_NAME, '/'))
+
     if request.method == "POST":
         form = RemoteUserForm(request, request.POST)
         if form.is_valid():
@@ -87,7 +94,8 @@ def mellon_logout(request, ):
     """
     Simule la vue qui de mellon qui initie le logout sur le l'IdP
     """
-    redirect_to = request.REQUEST.get(SAML_REDIRECT_FIELD_NAME, '/')
+    redirect_to = urllib.unquote_plus(
+        request.REQUEST.get(SAML_REDIRECT_FIELD_NAME, '/'))
     return redirect(redirect_to)
 
 
diff --git a/tox.ini b/tox.ini
index 127af5c..e515fd5 100644 (file)
--- a/tox.ini
+++ b/tox.ini
@@ -1,10 +1,13 @@
 [tox]
 envlist = django1.4
+indexserver =
+    AUF = http://pypi.auf.org
 
 [testenv]
 deps =
     coverage
     pep8
+    :AUF:auf.django.references
 
 commands =
     coverage erase