Repository: django-hijack/django-hijack

Issue:
Hijack of an inactive User can't be released
When using a "standard" django setup with the standard User model and `django.contrib.auth.backends.ModelBackend` as the authentication backend, if you do the following:

1. Have a User with `is_active = False`
2. Hijack this user (`inactive_user`)
3. At this point even though `"_auth_user_id"` will be `inactive_user.id` in your session, `ModelBackend` will not allow inactive users so `request.user` will be `AnonymousUser`
4. attempt to release the hijack, this will fail because it uses `LoginRequiredMixin` which rejects `AnonymousUser`.
5. So releasing the hijack is impossible

I'm not sure what solution, if any, would be best for django-hijack.

In my project I am addressing this by prevent the hijack of inactive users in the first place through the `HIJACK_PERMISSION_CHECK` setting.

<hr/>

Thinking about this more, I feel that the `is_active` field is just an implementation detail of `ModelBackend`, the django-hijack library shouldn't look at this field for compatibility with custom user models/backends.

The high-level problem is that `AcquireUserView` allows you to acquire a User which is then not recognized by any backend in `AUTHENTICATION_BACKENDS`, so `request.user` will be `AnonymousUser`, so the `ReleaseUserView` will not allow the releasing of the hijack.


Discussion:
Hi @LincolnPuzey,

Thank you for reaching out. Seems like we missed this bug, excellent catch.
It shouldn't be too hard to fix, this check only needs to be implemented in the permission functions and users will be no longer able to hijack inactive users.

I'll create a patch.

Thanks Joe

Resolving patch:
diff --git a/hijack/permissions.py b/hijack/permissions.py
--- a/hijack/permissions.py
+++ b/hijack/permissions.py
@@ -1,15 +1,18 @@
-def superusers_only(*, hijacker=None, hijacked=None):
+def superusers_only(*, hijacker, hijacked):
     """Superusers may hijack any other user."""
-    return hijacker.is_superuser
+    return hijacked.is_active and hijacker.is_superuser
 
 
-def superusers_and_staff(*, hijacker=None, hijacked=None):
+def superusers_and_staff(*, hijacker, hijacked):
     """
     Superusers and staff members may hijack other users.
 
     A superuser may hijack any other user.
     A staff member may hijack any user, except another staff member or superuser.
     """
+    if not hijacked.is_active:
+        return False
+
     if hijacker.is_superuser:
         return True
 


Test patch:
diff --git a/hijack/tests/test_permissions.py b/hijack/tests/test_permissions.py
--- a/hijack/tests/test_permissions.py
+++ b/hijack/tests/test_permissions.py
@@ -6,6 +6,7 @@
 superuser = get_user_model()(is_superuser=True)
 staff_member = get_user_model()(is_staff=True)
 regular_user = get_user_model()()
+inactive_user = get_user_model()(is_active=False)
 
 
 @pytest.mark.parametrize(
@@ -14,12 +15,15 @@
         (superuser, superuser, True),
         (superuser, staff_member, True),
         (superuser, regular_user, True),
+        (superuser, inactive_user, False),
         (staff_member, regular_user, False),
         (staff_member, staff_member, False),
         (staff_member, superuser, False),
+        (staff_member, inactive_user, False),
         (regular_user, superuser, False),
         (regular_user, staff_member, False),
         (regular_user, regular_user, False),
+        (regular_user, inactive_user, False),
     ],
 )
 def test_superusers_only(hijacker, hijacked, has_perm):
@@ -32,12 +36,15 @@ def test_superusers_only(hijacker, hijacked, has_perm):
         (superuser, superuser, True),
         (superuser, staff_member, True),
         (superuser, regular_user, True),
+        (superuser, inactive_user, False),
         (staff_member, regular_user, True),
         (staff_member, staff_member, False),
         (staff_member, superuser, False),
+        (staff_member, inactive_user, False),
         (regular_user, superuser, False),
         (regular_user, staff_member, False),
         (regular_user, regular_user, False),
+        (regular_user, inactive_user, False),
     ],
 )
 def test_superusers_and_staff(hijacker, hijacked, has_perm):