From 3d2e02a16e38894282ebf2f5f79eab405a61c784 Mon Sep 17 00:00:00 2001
From: Todd Dembrey <todd.dembrey@torchbox.com>
Date: Mon, 12 Aug 2019 15:52:31 +0100
Subject: [PATCH] Prevent users becoming someone they shouldn't be

This was previously only enforced by gating the frontend component, this PR
tidies things up by making sure the backend prevent it happening as well
---
 opentech/apply/users/tests/factories.py  |  5 ++-
 opentech/apply/users/tests/test_views.py | 51 +++++++++++++++++++++++-
 opentech/apply/users/views.py            | 10 ++++-
 3 files changed, 63 insertions(+), 3 deletions(-)

diff --git a/opentech/apply/users/tests/factories.py b/opentech/apply/users/tests/factories.py
index 8db74b40f..ea11d35ec 100644
--- a/opentech/apply/users/tests/factories.py
+++ b/opentech/apply/users/tests/factories.py
@@ -1,5 +1,8 @@
+import uuid
+
 from django.contrib.auth import get_user_model
 from django.contrib.auth.models import Group
+from django.utils.text import slugify
 
 import factory
 
@@ -18,7 +21,7 @@ class UserFactory(factory.DjangoModelFactory):
     class Meta:
         model = get_user_model()
 
-    email = factory.Sequence('email{}@email.com'.format)
+    email = factory.LazyAttribute(lambda o: '{}+{}@email.com'.format(slugify(o.full_name), uuid.uuid4()))
     full_name = factory.Faker('name')
     password = factory.PostGenerationMethodCall('set_password', 'defaultpassword')
 
diff --git a/opentech/apply/users/tests/test_views.py b/opentech/apply/users/tests/test_views.py
index 044a9566d..a62923d8a 100644
--- a/opentech/apply/users/tests/test_views.py
+++ b/opentech/apply/users/tests/test_views.py
@@ -3,7 +3,12 @@ from django.test import override_settings, TestCase
 from django.urls import reverse
 
 from opentech.apply.utils.testing.tests import BaseViewTestCase
-from .factories import OAuthUserFactory, StaffFactory, UserFactory
+from .factories import (
+    OAuthUserFactory,
+    StaffFactory,
+    SuperUserFactory,
+    UserFactory,
+)
 
 
 @override_settings(ROOT_URLCONF='opentech.apply.urls')
@@ -58,3 +63,47 @@ class TestPasswordReset(BaseViewTestCase):
         self.assertRedirects(response, self.url(None, view_name='password_reset_done'))
         self.assertEqual(len(mail.outbox), 1)
         self.assertIn('https://testserver/account/password/reset/confirm', mail.outbox[0].body)
+
+
+@override_settings(ROOT_URLCONF='opentech.apply.urls')
+class TestBecome(TestCase):
+    def setUp(self):
+        self.staff = StaffFactory()
+        self.user = UserFactory()
+        self.superuser = SuperUserFactory()
+
+    def become_request(self, user, target):
+        self.client.force_login(user)
+        url = reverse('users:become')
+        response = self.client.post(url, {'user': target.pk}, follow=True, secure=True)
+        return response
+
+    def test_staff_can_become_user(self):
+        response = self.become_request(self.staff, self.user)
+        self.assertEqual(response.status_code, 200)
+
+    def test_staff_cannot_become_superuser(self):
+        response = self.become_request(self.staff, self.superuser)
+        self.assertEqual(response.status_code, 403)
+
+    def test_superuser_can_become_staff(self):
+        response = self.become_request(self.superuser, self.staff)
+        self.assertEqual(response.status_code, 200)
+
+    def test_superuser_cannot_become_superuser(self):
+        other_superuser = SuperUserFactory()
+        response = self.become_request(self.superuser, other_superuser)
+        self.assertEqual(response.status_code, 403)
+
+    def test_user_cannot_become_staff(self):
+        response = self.become_request(self.user, self.staff)
+        self.assertEqual(response.status_code, 403)
+
+    def test_user_cannot_become_other_user(self):
+        other_user = UserFactory()
+        response = self.become_request(self.user, other_user)
+        self.assertEqual(response.status_code, 403)
+
+    def test_user_cannot_become_superuser(self):
+        response = self.become_request(self.user, self.superuser)
+        self.assertEqual(response.status_code, 403)
diff --git a/opentech/apply/users/views.py b/opentech/apply/users/views.py
index 86b54c1bd..fdc63e60a 100644
--- a/opentech/apply/users/views.py
+++ b/opentech/apply/users/views.py
@@ -5,6 +5,7 @@ from django.contrib.auth.decorators import login_required
 from django.contrib.auth.forms import AdminPasswordChangeForm
 from django.contrib.auth.tokens import PasswordResetTokenGenerator
 from django.contrib.auth.views import SuccessURLAllowedHostsMixin
+from django.core.exceptions import PermissionDenied
 from django.http import HttpResponseRedirect
 from django.shortcuts import redirect, render, resolve_url
 from django.template.response import TemplateResponse
@@ -93,8 +94,15 @@ class AccountView(UpdateView):
 
 @login_required()
 def become(request):
-    id = request.POST['user']
+    if not request.user.is_apply_staff:
+        raise PermissionDenied()
+
+    id = request.POST.get('user')
     if request.POST and id:
+        target_user = User.objects.get(pk=id)
+        if target_user.is_superuser:
+            raise PermissionDenied()
+
         return login_with_id(request, id)
     return redirect('users:account')
 
-- 
GitLab