M CHANGELOG +6 -0
@@ 1,6 1,12 @@
Changes
-------
+1.9.0 (unreleased)
+
+- Prevent redirect loop when MAX_DURATION is used. Refs ~petersanchez/django-impersonate#67
+- Allow OPTIONS requests when READ_ONLY is True. Refs ~petersanchez/django-impersonate#69
+- Added CUSTOM_READ_ONLY to customise when to restrict to read only access. Refs ~petersanchez/django-impersonate#70
+
1.8.1 (2022-02-17)
- Patch version bump for README updates. I know... I'm shameful.
M README.md +15 -2
@@ 241,11 241,24 @@ Value should be a string containing the
READ_ONLY
A boolean that if set to `True` any requests that are not either `GET` or
-`HEAD` will result in a "Bad Request" response (status code 405). Use this if
-you want to limit your impersonating users to read only impersonation sessions.
+`HEAD` or `OPTIONS` will result in a "Bad Request" response (status code 405).
+Use this if you want to limit your impersonating users to read only
+impersonation sessions.
Value should be a boolean, defaults to `False`
+If the `CUSTOM_READ_ONLY` is set, then that custom function is used, and this
+setting is ignored.
+
+ CUSTOM_READ_ONLY
+
+A string that represents a function (e.g. `module.submodule.mod.function_name`)
+that allows more fine grained control over who has read only access. It takes
+one argument, the request object, and should return True to restrict the user
+to only allow `GET`, `HEAD` and `OPTIONS` requests.
+
+It is optional, and if it is not present, `READ_ONLY` setting value applies.
+
USE_HTTP_REFERER
If this is set to `True`, then the app will attempt to be redirect you to
M impersonate/helpers.py +11 -0
@@ 128,3 128,14 @@ def check_allow_for_uri(uri):
if re.search(exclusion, uri):
return False
return True
+
+
+def check_read_only(request):
+ ''' Returns True if can only do read only requests.
+ Uses the CUSTOM_READ_ONLY function if required, else
+ looks at the READ_ONLY setting.
+ '''
+ if settings.CUSTOM_READ_ONLY is not None:
+ custom_read_only_func = import_func_from_string(settings.CUSTOM_READ_ONLY)
+ return custom_read_only_func(request)
+ return settings.READ_ONLY
M impersonate/middleware.py +2 -2
@@ 7,7 7,7 @@ from django.utils import timezone
from django.utils.deprecation import MiddlewareMixin
from django.utils.functional import SimpleLazyObject
-from .helpers import User, check_allow_for_uri, check_allow_for_user
+from .helpers import User, check_allow_for_uri, check_allow_for_user, check_read_only
from .settings import settings
@@ 50,7 50,7 @@ class ImpersonateMiddleware(MiddlewareMi
except User.DoesNotExist:
return
- if settings.READ_ONLY and request.method not in ['GET', 'HEAD', 'OPTIONS']:
+ if check_read_only(request) and request.method not in ['GET', 'HEAD', 'OPTIONS']:
return HttpResponseNotAllowed(['GET', 'HEAD', 'OPTIONS'])
if check_allow_for_user(request, new_user) and check_allow_for_uri(
M impersonate/settings.py +1 -0
@@ 20,6 20,7 @@ username_field = getattr(User, 'USERNAME
'SEARCH_FIELDS': [username_field, 'first_name', 'last_name', 'email'],
'REDIRECT_URL': getattr(django_settings, 'LOGIN_REDIRECT_URL', u'/'),
'READ_ONLY': False,
+ 'CUSTOM_READ_ONLY': None,
'ADMIN_DELETE_PERMISSION': False,
'ADMIN_ADD_PERMISSION': False,
'ADMIN_READ_ONLY': True,
M impersonate/tests.py +33 -0
@@ 89,6 89,13 @@ def test_qs(request):
return User.objects.all().order_by('pk')
+def test_allow_read_only(request):
+ ''' Used via the IMPERSONATE['CUSTOM_READ_ONLY'] setting.
+ Simple check that the user is not a superuser.
+ '''
+ return not request.user.is_superuser
+
+
class UserFactory(object):
@staticmethod
def create(**kwargs):
@@ 852,3 859,29 @@ class TestImpersonation(TestCase):
self.assertEqual(resp.status_code, 200)
resp = self.client.options(reverse('impersonate-test'))
self.assertEqual(resp.status_code, 200)
+
+ @override_settings(IMPERSONATE={'CUSTOM_READ_ONLY': 'impersonate.tests.test_allow_read_only'})
+ def test_impersonate_custom_read_only(self):
+ # superuser is able to do all requests
+ self._impersonate_helper('user1', 'foobar', 4)
+ resp = self.client.post(reverse('impersonate-test'))
+ self.assertEqual(resp.status_code, 200)
+ resp = self.client.get(reverse('impersonate-test'))
+ self.assertEqual(resp.status_code, 200)
+ resp = self.client.head(reverse('impersonate-test'))
+ self.assertEqual(resp.status_code, 200)
+ resp = self.client.options(reverse('impersonate-test'))
+ self.assertEqual(resp.status_code, 200)
+ self.client.logout()
+
+ # staff user is only able to do read only requests
+ self._impersonate_helper('user3', 'foobar', 4)
+ resp = self.client.post(reverse('impersonate-test'))
+ self.assertEqual(resp.status_code, 405)
+ resp = self.client.get(reverse('impersonate-test'))
+ self.assertEqual(resp.status_code, 200)
+ resp = self.client.head(reverse('impersonate-test'))
+ self.assertEqual(resp.status_code, 200)
+ resp = self.client.options(reverse('impersonate-test'))
+ self.assertEqual(resp.status_code, 200)
+ self.client.logout()