Commit 33905a7f authored by Enrico Zini's avatar Enrico Zini
Browse files

Impersonate through a middleware. refs: #12

parent 4c1bec3e
...@@ -49,6 +49,7 @@ class PersonManager(BaseUserManager): ...@@ -49,6 +49,7 @@ class PersonManager(BaseUserManager):
def create_superuser(self, email, **other_fields): def create_superuser(self, email, **other_fields):
other_fields["is_superuser"] = True other_fields["is_superuser"] = True
other_fields["is_staff"] = True
return self.create_user(email, **other_fields) return self.create_user(email, **other_fields)
def get_or_none(self, *args, **kw): def get_or_none(self, *args, **kw):
......
...@@ -103,6 +103,7 @@ class TestBase(nm2.lib.unittest.TestBase): ...@@ -103,6 +103,7 @@ class TestBase(nm2.lib.unittest.TestBase):
else: else:
raise NotImplementedError(f"{identity.issuer} not supported as identity during testing") raise NotImplementedError(f"{identity.issuer} not supported as identity during testing")
if isinstance(person, str):
person = self.persons[person] person = self.persons[person]
if person is not None: if person is not None:
client.force_login(person, backend=self.TEST_AUTH_BACKEND) client.force_login(person, backend=self.TEST_AUTH_BACKEND)
...@@ -128,6 +129,7 @@ class TestBase(nm2.lib.unittest.TestBase): ...@@ -128,6 +129,7 @@ class TestBase(nm2.lib.unittest.TestBase):
else: else:
raise NotImplementedError(f"{identity.issuer} not supported as identity during testing") raise NotImplementedError(f"{identity.issuer} not supported as identity during testing")
if isinstance(person, str):
person = self.persons[person] person = self.persons[person]
if person is not None: if person is not None:
client.force_login(person, backend=self.TEST_AUTH_BACKEND) client.force_login(person, backend=self.TEST_AUTH_BACKEND)
......
from __future__ import annotations
from django.core.exceptions import ImproperlyConfigured
from django.contrib.auth import get_user_model
class ImpersonateMiddleware:
def __init__(self, get_response):
self.get_response = get_response
self.User = get_user_model()
def __call__(self, request):
# AuthenticationMiddleware is required so that request.user exists.
if not hasattr(request, 'user'):
raise ImproperlyConfigured(
"The impersonator middleware requires the authentication middleware"
" to be installed. Edit your MIDDLEWARE setting to insert"
" 'django.contrib.auth.middleware.AuthenticationMiddleware'"
" before the ImpersonateMiddleware class.")
if request.user.is_authenticated:
# Implement impersonation if requested in session
if request.user.is_staff:
pk = request.session.get("impersonate", None)
if pk is not None:
try:
user = self.User.objects.get(pk=pk)
except self.User.DoesNotExist:
user = None
if user is not None:
request.impersonator = request.user
request.user = user
return self.get_response(request)
from __future__ import annotations from __future__ import annotations
from django.test import TestCase from django.test import TestCase
from django.urls import reverse from django.urls import reverse
from backend.unittest import PersonFixtureMixin from backend.unittest import TestBase
from django.contrib.auth import get_user_model
class TestPermissions(PersonFixtureMixin, TestCase): class TestPermissions(TestBase, TestCase):
@classmethod def test_impersonate_staff(self):
def __add_extra_tests__(cls): User = get_user_model()
non_fd = ["pending", "dc", "dc_ga", "dm", "dm_ga", "dd_nu", "dd_u", "dd_e", "dd_r", "activeam", "oldam"] visitor = User.objects.create_superuser(email="admin@example.org", fullname="Admin", audit_skip=True)
fd = ["fd", "dam"] visited = User.objects.create_user(email="user@example.org", fullname="User", audit_skip=True)
client = self.make_test_client(visitor)
for visitor in [None] + non_fd:
for visited in non_fd + fd:
cls._add_method(cls._test_impersonate_fail, visitor, visited)
for visitor in fd: response = client.get(reverse("impersonate:whoami"))
for visited in non_fd + fd: self.assertJSONEqual(response.content, {
cls._add_method(cls._test_impersonate_success, visitor, visited) 'impersonator': None,
'impersonator_desc': None,
'user': visitor.pk,
'user_desc': str(visitor),
})
def _test_impersonate_success(self, visitor, visited): response = client.post(reverse("impersonate:impersonate"), data={"pk": visited.pk, "next": "/"})
client = self.make_test_client(visitor)
response = client.post(reverse("impersonate"), data={"pk": self.persons[visited].pk, "next": "/"})
self.assertRedirectMatches(response, "^/$") self.assertRedirectMatches(response, "^/$")
def _test_impersonate_fail(self, visitor, visited): response = client.get(reverse("impersonate:whoami"))
self.assertJSONEqual(response.content, {
'impersonator': visitor.pk,
'impersonator_desc': str(visitor),
'user': visited.pk,
'user_desc': str(visited),
})
def test_impersonate_user(self):
User = get_user_model()
visitor = User.objects.create_user(email="user@example.org", fullname="User", audit_skip=True)
visited = User.objects.create_user(email="user1@example.org", fullname="User1", audit_skip=True)
client = self.make_test_client(visitor) client = self.make_test_client(visitor)
response = client.post(reverse("impersonate"), data={"pk": self.persons[visited].pk}) response = client.post(reverse("impersonate:impersonate"), data={"pk": visited.pk})
self.assertPermissionDenied(response) self.assertPermissionDenied(response)
response = client.get(reverse("impersonate:whoami"))
self.assertJSONEqual(response.content, {
'impersonator': None,
'impersonator_desc': None,
'user': visitor.pk,
'user_desc': str(visitor),
})
def test_impersonate_anonymous(self):
User = get_user_model()
visited = User.objects.create_user(email="user@example.org", fullname="User", audit_skip=True)
client = self.make_test_client(None)
response = client.post(reverse("impersonate:impersonate"), data={"pk": visited.pk})
self.assertPermissionDenied(response)
response = client.get(reverse("impersonate:whoami"))
self.assertJSONEqual(response.content, {
'impersonator': None,
'impersonator_desc': None,
'user': None,
'user_desc': "AnonymousUser",
})
...@@ -6,4 +6,5 @@ app_name = "impersonate" ...@@ -6,4 +6,5 @@ app_name = "impersonate"
urlpatterns = [ urlpatterns = [
# Impersonate a user # Impersonate a user
path('impersonate/', views.Impersonate.as_view(), name="impersonate"), path('impersonate/', views.Impersonate.as_view(), name="impersonate"),
path('whoami/', views.Whoami.as_view(), name="whoami"),
] ]
...@@ -5,6 +5,7 @@ from django.shortcuts import redirect ...@@ -5,6 +5,7 @@ from django.shortcuts import redirect
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.contrib import messages from django.contrib import messages
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django import http
class Impersonate(View): class Impersonate(View):
...@@ -13,7 +14,7 @@ class Impersonate(View): ...@@ -13,7 +14,7 @@ class Impersonate(View):
effective_user = getattr(request, "impersonator", None) effective_user = getattr(request, "impersonator", None)
if effective_user is None: if effective_user is None:
effective_user = request.user effective_user = request.user
if not effective_user.is_authenticated or not effective_user.is_admin: if not effective_user.is_authenticated or not effective_user.is_staff:
raise PermissionDenied raise PermissionDenied
pk = request.POST.get("pk") pk = request.POST.get("pk")
if pk is None: if pk is None:
...@@ -33,3 +34,14 @@ class Impersonate(View): ...@@ -33,3 +34,14 @@ class Impersonate(View):
return redirect(user.get_absolute_url()) return redirect(user.get_absolute_url())
else: else:
return redirect(url) return redirect(url)
class Whoami(View):
def get(self, request, *args, **kw):
impersonator = getattr(request, "impersonator", None)
return http.JsonResponse({
"user": request.user.pk,
"user_desc": str(request.user),
"impersonator": None if impersonator is None else impersonator.pk,
"impersonator_desc": None if impersonator is None else str(impersonator),
})
...@@ -88,6 +88,7 @@ MIDDLEWARE = [ ...@@ -88,6 +88,7 @@ MIDDLEWARE = [
'signon.middleware.SignonMiddleware', 'signon.middleware.SignonMiddleware',
'django.contrib.messages.middleware.MessageMiddleware', 'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware',
'impersonate.middleware.ImpersonateMiddleware',
] ]
AUTHENTICATION_BACKENDS = [ AUTHENTICATION_BACKENDS = [
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment