Repository: dpgmediamagazines/django-arctic

Issue:
support for django 1.10
The current install doesn't work with django 1.10 as django-extra-views is lacking support for django 1.10 at the moment. 

We currently use a install from github/sanoma/django-extra-views, however because django arctic has still django-extra-views 0.7.1 as requirement our deployment process overwrites it. 


Check that 'next' redirect in the login view only goes to own host
The current login screen allows a 'next' query parameter that has the location to redirect to after a successful login, for security reasons this next parameter needs to be checked in order to ensure redirects are only within the same host. Django already has a method, `is_safe_url ` to check this in `django.utils.http` which should be used to solve this issue.


Discussion:

Resolving patch:
diff --git a/arctic/generics.py b/arctic/generics.py
--- a/arctic/generics.py
+++ b/arctic/generics.py
@@ -9,7 +9,7 @@
 from django.db.models.deletion import (Collector, ProtectedError)
 from django.shortcuts import (redirect, render, resolve_url)
 from django.utils.formats import get_format
-from django.utils.http import quote
+from django.utils.http import is_safe_url, quote
 from django.utils.text import capfirst
 from django.utils.translation import (get_language, ugettext as _)
 from django.views import generic as base
@@ -637,7 +637,12 @@ def post(self, request, *args, **kwargs):
                             password=request.POST['password'])
         if user and user.is_active:
             login(request, user)
-            return redirect(request.GET.get('next', '/'))
+
+            next_url = request.GET.get('next')
+            if is_safe_url(next_url, request.get_host()):
+                return redirect(next_url)
+
+            return redirect('/')
 
         self.messages.append('Invalid username/password combination')
 


Test patch:
diff --git a/tests/test_cms.py b/tests/test_cms.py
--- a/tests/test_cms.py
+++ b/tests/test_cms.py
@@ -12,6 +12,57 @@ class TestCMS(object):
     def init(self):
         self.__class__.client = Client()
 
+    def test_login_success_redirect_no_next(self, admin_user):
+        """
+        Asserts that an admin user is redirected to root after login,
+        when no 'next' query param is given.
+
+        :param admin_user: fixture provided by pytest-django
+        :return: void
+        """
+        response = self.client.post('/login/',
+                                    {
+                                        'username': 'admin',
+                                        'password': 'password'
+                                    })
+
+        assert response.status_code == 302
+        assert response.url in ('/', 'http://testserver/')
+
+    def test_login_success_redirect_next(self, admin_user):
+        """
+        Asserts that an admin user is redirected to the next page after
+        login, when a 'next' query param is given.
+
+        :param admin_user: fixture provided by pytest-django
+        :return: void
+        """
+        response = self.client.post('/login/?next=/articles/',
+                                    {
+                                        'username': 'admin',
+                                        'password': 'password'
+                                    })
+
+        assert response.status_code == 302
+        assert response.url in ('/articles/', 'http://testserver/articles/')
+
+    def test_login_success_redirect_next_other_host(self, admin_user):
+        """
+        Asserts that an admin user is redirected to root after login,
+        when the 'next' query param refers to another host.
+
+        :param admin_user: fixture provided by pytest-django
+        :return: void
+        """
+        response = self.client.post('/login/?next=http://www.no.domain/test/',
+                                    {
+                                        'username': 'admin',
+                                        'password': 'password'
+                                    })
+
+        assert response.status_code == 302
+        assert response.url in ('/', 'http://testserver/')
+
     def test_login_success(self, admin_user):
         """
         Asserts that an admin user can log in an see a restricted page