Received: from mail.netlandish.com (mail.netlandish.com [174.136.98.166]) by code.netlandish.com (Postfix) with ESMTP id BF0FF8317C for <~petersanchez/public-inbox@lists.code.netlandish.com>; Sun, 19 Feb 2023 20:59:39 +0000 (UTC) Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=209.85.128.41; helo=mail-wm1-f41.google.com; envelope-from=sarahvboyce95@gmail.com; receiver= Authentication-Results: mail.netlandish.com; dkim=pass (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b=MWpsKlac Received: from mail-wm1-f41.google.com (mail-wm1-f41.google.com [209.85.128.41]) by mail.netlandish.com (Postfix) with ESMTP id B3919152FB1 for <~petersanchez/public-inbox@lists.code.netlandish.com>; Sun, 19 Feb 2023 20:59:38 +0000 (UTC) Received: by mail-wm1-f41.google.com with SMTP id p18-20020a05600c359200b003dc57ea0dfeso931508wmq.0 for <~petersanchez/public-inbox@lists.code.netlandish.com>; Sun, 19 Feb 2023 12:59:38 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=to:from:date:user-agent:references:in-reply-to:message-id:subject :content-transfer-encoding:mime-version:from:to:cc:subject:date :message-id:reply-to; bh=7u+oRyppRIaYNmDDVrvJj0UZL2YogIME2WuLtNx+h2k=; b=MWpsKlacfH3NTQ46IdDhg6UaloptDtYIRYkkrQK98AiXakDDgIMaQ/Sm0/cwumzdLZ 4gD5+MeZlzjF8PHTgrV+0pYUOQp60QzwqCYjMypeF5wTbQ4U/WBK1b2HC1u8uCVP9ILe IGal772oMg02McVdL2Uc2a/wz1pLJnLURcQLRoG+Eta4xm3EDIbaEdqAc/HnSxm8e596 ZA4F0yU9/C4R7arO5VTfZhEQGnWIhA7JOyz8uhiOeH4nJSnXL+Vuh2IhLzwK9B0fFvhS OhvAfDizjO7eQ5Ps+ucffzmzO9rBnB7ekmHAoAFxxrsHZCEpRqP28Yf4GygjBMgJ97Hj osMw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=to:from:date:user-agent:references:in-reply-to:message-id:subject :content-transfer-encoding:mime-version:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to; bh=7u+oRyppRIaYNmDDVrvJj0UZL2YogIME2WuLtNx+h2k=; b=6ngQVQd5pD3CVyI7lc4XRBjisxN8pwagaFnDORAdh4DY8M/arG64v/Un7T9FlXB/Ob tlT8nK7Ge/4y4qipvyhApn9ph/LgVgA1xXptpT5HLY7JIOQPA+wMhuuych3jwHgQfFWX UUaFwEONzd2I3qplaa5nSKk2z9u9Q/34HatQ5L7WQGSftIsbR38NvglT5JY6gGdu5LhC cAKb0QMPp2iaD9UHtMGGJQLNPH4irwtEjed+9zjPcLW8RXIz8qKJdO5p4gIOrUYNc8pc YgCZlsmdU+XJV7EE/H+mJ85hSv5x6OS7kLyEwmfIR821gHcN1wrtPyMVvTeBq6+ulLYH FCzA== X-Gm-Message-State: AO0yUKXQ36tR4AsSv+be4Ei+ZZxW7LlZP/ErpQAUwxqT/b/Zg+WiKxZC esjwvBpc3uuyJrASmKO237OCi3nnWuo= X-Google-Smtp-Source: AK7set+q9BqKqxX+caGnFWgziZmjTvBJI8y6B3e9GS0Z7O6t3eWvVdSGqPG+Nc1jFpfQQ0oJDoSdAw== X-Received: by 2002:a05:600c:3414:b0:3e2:153e:6940 with SMTP id y20-20020a05600c341400b003e2153e6940mr9226483wmp.3.1676840377267; Sun, 19 Feb 2023 12:59:37 -0800 (PST) Received: from localhost.localdomain (2a0a-a541-b663-0-f165-7174-1322-624e.ipv6dyn.netcologne.de. [2a0a:a541:b663:0:f165:7174:1322:624e]) by smtp.gmail.com with ESMTPSA id y25-20020a1c4b19000000b003dc4480df80sm8547100wma.34.2023.02.19.12.59.36 for <~petersanchez/public-inbox@lists.code.netlandish.com> (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 19 Feb 2023 12:59:36 -0800 (PST) MIME-Version: 1.0 Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Subject: [PATCH 2 of 2 django-impersonate] Added CUSTOM_READ_ONLY setting to customise when to restrict to read only access - Refs #70 X-Mercurial-Node: 49802f32cb83c01c77abf49bd0743055f8331306 X-Mercurial-Series-Index: 2 X-Mercurial-Series-Total: 2 Message-Id: <49802f32cb83c01c77ab.1676840364@localhost.localdomain> X-Mercurial-Series-Id: <77c3932f8751d8457a92.1676840363@localhost.localdomain> In-Reply-To: <77c3932f8751d8457a92.1676840363@localhost.localdomain> References: <77c3932f8751d8457a92.1676840363@localhost.localdomain> User-Agent: Mercurial-patchbomb/6.2.3 Date: Sun, 19 Feb 2023 21:59:24 +0100 From: =?iso-8859-1?q?Sarah_Boyce?= To: ~petersanchez/public-inbox@lists.code.netlandish.com # HG changeset patch # User Sarah Boyce # Date 1676839415 -3600 # Sun Feb 19 21:43:35 2023 +0100 # Node ID 49802f32cb83c01c77abf49bd0743055f8331306 # Parent 77c3932f8751d8457a92596acb8b6a8ba2f73dbb Added CUSTOM_READ_ONLY setting to customise when to restrict to read only access - Refs #70 diff --git a/CHANGELOG b/CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -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. diff --git a/README.md b/README.md --- a/README.md +++ b/README.md @@ -241,11 +241,24 @@ 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 diff --git a/impersonate/helpers.py b/impersonate/helpers.py --- a/impersonate/helpers.py +++ b/impersonate/helpers.py @@ -128,3 +128,14 @@ 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 diff --git a/impersonate/middleware.py b/impersonate/middleware.py --- a/impersonate/middleware.py +++ b/impersonate/middleware.py @@ -7,7 +7,7 @@ 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 @@ 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( diff --git a/impersonate/settings.py b/impersonate/settings.py --- a/impersonate/settings.py +++ b/impersonate/settings.py @@ -20,6 +20,7 @@ '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, diff --git a/impersonate/tests.py b/impersonate/tests.py --- a/impersonate/tests.py +++ b/impersonate/tests.py @@ -89,6 +89,13 @@ 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 @@ 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()