Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ jobs:
run: sudo apt-get install -y gettext
- name: Install
run: |
python setup.py develop
pip install Django==${{ matrix.DJANGO_VERSION }}
python setup.py develop
pip install -e .
pip install -r requirements.test.txt
pip install coveralls
Expand Down
48 changes: 47 additions & 1 deletion django_su/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from django.contrib import auth
from django.contrib.auth import get_user_model
from django.contrib.sessions.backends import cached_db
from django.http import HttpRequest
from django.test import Client, TestCase
from django.urls import reverse
from django.utils.datetime_safe import datetime
Expand Down Expand Up @@ -60,10 +61,31 @@ def test_login_success(self):
self.assertEqual(str(pk), str(self.authorized_user.pk))
self.assertEqual(backend, "django.contrib.auth.backends.ModelBackend")

def test_login_success_without_custom_login_action(self):
"""Ensure login works for a valid user when SU_CUSTOM_LOGIN_ACTION is None"""
self.client.login(username="authorized", password="pass")
with self.settings(SU_CUSTOM_LOGIN_ACTION=None):
response = self.client.post(
reverse("login_as_user", args=[self.destination_user.id])
)
self.assertEqual(response.status_code, 302)
# Check the user is logged in in the session
self.assertIn(auth.SESSION_KEY, self.client.session)
self.assertEqual(
str(self.client.session[auth.SESSION_KEY]), str(self.destination_user.id)
)
# Check the 'exit_users_pk' is set so we know which user to change back to
self.assertIn("exit_users_pk", self.client.session)
pk, backend = self.client.session["exit_users_pk"][0]
self.assertEqual(str(pk), str(self.authorized_user.pk))
self.assertEqual(backend, "django.contrib.auth.backends.ModelBackend")

def test_login_user_id_invalid(self):
"""Ensure login fails with an invalid user id"""
self.client.login(username="authorized", password="pass")
response = self.client.post("/su/abc/")
user_ids = User.objects.all().values_list("id", flat=True)
invalid_user_id = max(user_ids) + 1
response = self.client.post(reverse("login_as_user", args=[invalid_user_id]))
self.assertEqual(response.status_code, 404)
# User should still be logged in, but as the original user
self.assertIn(auth.SESSION_KEY, self.client.session)
Expand Down Expand Up @@ -149,6 +171,30 @@ def error_action(request, user):
self.assertTrue(connections)


class SuInUtilsTestCase(SuViewsBaseTestCase):
def test_su_in_no_perms(self):
"""Ensure user must still have the auth.change_user permission when calling
su_in from user code"""
no_perms_user = self.user("noperms")
self.client.login(username="noperms", password="pass")

from ..utils import su_in

request = HttpRequest()
request.user = no_perms_user

result = su_in(request, no_perms_user)

self.assertEqual(result, None)
# User should still be logged in, but as the original user
self.assertIn(auth.SESSION_KEY, self.client.session)
self.assertEqual(
str(self.client.session[auth.SESSION_KEY]), str(no_perms_user.id)
)
# Exit user should never get set
self.assertNotIn("exit_users_pk", self.client.session)


class LoginViewTestCase(SuViewsBaseTestCase):
def test_get_authorised(self):
"""Load the login page as an authorised user"""
Expand Down
33 changes: 33 additions & 0 deletions django_su/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,42 @@
from collections.abc import Callable

from django.conf import settings
from django.contrib.auth import BACKEND_SESSION_KEY, SESSION_KEY, authenticate, login
from django.utils.module_loading import import_string


def su_in(request, user_id):
"""
Returns: a User Object or None
"""
if not request.user.has_perm("auth.change_user"):
return None

userobj = authenticate(request=request, su=True, user_id=user_id)
if not userobj:
return None

exit_users_pk = request.session.get("exit_users_pk", default=[])
exit_users_pk.append(
(request.session[SESSION_KEY], request.session[BACKEND_SESSION_KEY])
)

maintain_last_login = hasattr(userobj, "last_login")
if maintain_last_login:
last_login = userobj.last_login

try:
if not custom_login_action(request, userobj):
login(request, userobj)
request.session["exit_users_pk"] = exit_users_pk
finally:
if maintain_last_login:
userobj.last_login = last_login
userobj.save(update_fields=["last_login"])

return userobj


def su_login_callback(user):
if hasattr(settings, "SU_LOGIN"):
warnings.warn(
Expand Down
31 changes: 3 additions & 28 deletions django_su/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,15 @@
import warnings

from django.conf import settings
from django.contrib.auth import (
BACKEND_SESSION_KEY,
SESSION_KEY,
authenticate,
get_user_model,
login,
)
from django.contrib.auth import get_user_model, login
from django.contrib.auth.decorators import user_passes_test
from django.http import Http404, HttpResponseBadRequest, HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.views.decorators.csrf import csrf_protect
from django.views.decorators.http import require_http_methods

from .forms import UserSuForm
from .utils import custom_login_action, su_login_callback
from .utils import custom_login_action, su_in, su_login_callback


User = get_user_model()
Expand All @@ -27,28 +21,9 @@
@require_http_methods(["POST"])
@user_passes_test(su_login_callback)
def login_as_user(request, user_id):
userobj = authenticate(request=request, su=True, user_id=user_id)
if not userobj:
if not su_in(request, user_id):
raise Http404("User not found")

exit_users_pk = request.session.get("exit_users_pk", default=[])
exit_users_pk.append(
(request.session[SESSION_KEY], request.session[BACKEND_SESSION_KEY])
)

maintain_last_login = hasattr(userobj, "last_login")
if maintain_last_login:
last_login = userobj.last_login

try:
if not custom_login_action(request, userobj):
login(request, userobj)
request.session["exit_users_pk"] = exit_users_pk
finally:
if maintain_last_login:
userobj.last_login = last_login
userobj.save(update_fields=["last_login"])

if hasattr(settings, "SU_REDIRECT_LOGIN"):
warnings.warn(
"SU_REDIRECT_LOGIN is deprecated, use SU_LOGIN_REDIRECT_URL",
Expand Down