From af261383a4c5cfbe945c6ef814bbd1ab5e285389 Mon Sep 17 00:00:00 2001 From: Santos Gallegos Date: Thu, 9 Jan 2025 11:31:00 -0500 Subject: [PATCH] Add django-impersonate when admin is enabled (#11894) This used to live in .com, but I also find it useful to debug things in .org, specially now with the new dashboard. We used to check if the IMPERSONATE setting was defined in order to add the URLs, but since we are always using this when the admin is enabled, I'm just checking for that, that will allow us to have less code on .com/ops. --- readthedocs/core/admin.py | 6 +++++- readthedocs/settings/base.py | 13 +++++++++++++ readthedocs/urls.py | 18 ++++++++++++++++++ requirements/deploy.txt | 2 ++ requirements/docker.txt | 2 ++ requirements/pip.in | 3 +++ requirements/pip.txt | 2 ++ requirements/testing.txt | 2 ++ 8 files changed, 47 insertions(+), 1 deletion(-) diff --git a/readthedocs/core/admin.py b/readthedocs/core/admin.py index e488758d093..48d37a3066f 100644 --- a/readthedocs/core/admin.py +++ b/readthedocs/core/admin.py @@ -10,6 +10,7 @@ from django.utils.html import format_html from django.utils.safestring import mark_safe from django.utils.translation import gettext_lazy as _ +from impersonate.admin import UserAdminImpersonateMixin from rest_framework.authtoken.admin import TokenAdmin from readthedocs.core.history import ExtraSimpleHistoryAdmin @@ -64,7 +65,7 @@ def queryset(self, request, queryset): return queryset.filter(projects__builds__date__gt=recent_date) -class UserAdminExtra(ExtraSimpleHistoryAdmin, UserAdmin): +class UserAdminExtra(ExtraSimpleHistoryAdmin, UserAdminImpersonateMixin, UserAdmin): """Admin configuration for User.""" @@ -80,6 +81,9 @@ class UserAdminExtra(ExtraSimpleHistoryAdmin, UserAdmin): actions = ["ban_user", "sync_remote_repositories_action"] inlines = [UserProjectInline] + # Open a new tab when impersonating a user. + open_new_window = True + @admin.display( description="Banned", boolean=True, diff --git a/readthedocs/settings/base.py b/readthedocs/settings/base.py index cc119dc6644..b98cc1ea04f 100644 --- a/readthedocs/settings/base.py +++ b/readthedocs/settings/base.py @@ -297,6 +297,11 @@ def INSTALLED_APPS(self): # noqa "allauth.socialaccount.providers.gitlab", "allauth.socialaccount.providers.bitbucket_oauth2", "allauth.mfa", + # Others + # NOTE: impersonate functionality is only enabled when ALLOW_ADMIN is True, + # but we still need to include it even when not enabled, since it has objects + # related to the user model that Django needs to know about when deleting users. + "impersonate", "cacheops", ] if ext: @@ -348,6 +353,8 @@ def MIDDLEWARE(self): ] if self.SHOW_DEBUG_TOOLBAR: middlewares.insert(0, "debug_toolbar.middleware.DebugToolbarMiddleware") + if self.ALLOW_ADMIN: + middlewares.append("impersonate.middleware.ImpersonateMiddleware") return middlewares AUTHENTICATION_BACKENDS = ( @@ -830,6 +837,12 @@ def SOCIALACCOUNT_PROVIDERS(self): INTERNAL_IPS = ("127.0.0.1",) + # django-impersonate. + IMPERSONATE = { + # By default, only staff users can impersonate. + "REQUIRE_SUPERUSER": True, + } + # Taggit # https://django-taggit.readthedocs.io TAGGIT_TAGS_FROM_STRING = "readthedocs.projects.tag_utils.rtd_parse_tags" diff --git a/readthedocs/urls.py b/readthedocs/urls.py index 5e5d7b94912..db638596019 100644 --- a/readthedocs/urls.py +++ b/readthedocs/urls.py @@ -7,6 +7,7 @@ from django.contrib import admin from django.urls import include, path, re_path from django.views.generic.base import RedirectView, TemplateView +from impersonate.views import impersonate, stop_impersonate from readthedocs.core.views import ErrorView, HomepageView, SupportView, do_not_track from readthedocs.search.views import GlobalSearchView @@ -105,6 +106,22 @@ re_path(r"^admin/", admin.site.urls), ] +impersonate_urls = [ + # We don't use ``include('impersonate.urls')`` here because we don't want to + # define ``/search`` and ``/list`` URLs + path( + "impersonate/stop/", + stop_impersonate, + name="impersonate-stop", + ), + path( + "impersonate//", + impersonate, + name="impersonate-start", + ), +] + + dnt_urls = [ re_path(r"^\.well-known/dnt/$", do_not_track), # https://github.com/EFForg/dnt-guide#12-how-to-assert-dnt-compliance @@ -158,6 +175,7 @@ if settings.ALLOW_ADMIN: groups.append(admin_urls) + groups.append(impersonate_urls) if settings.SHOW_DEBUG_TOOLBAR: import debug_toolbar diff --git a/requirements/deploy.txt b/requirements/deploy.txt index 2c6400c892a..62735911369 100644 --- a/requirements/deploy.txt +++ b/requirements/deploy.txt @@ -155,6 +155,8 @@ django-formtools==2.5.1 # via -r requirements/pip.txt django-gravatar2==1.4.5 # via -r requirements/pip.txt +django-impersonate==1.9.4 + # via -r requirements/pip.txt django-ipware==5.0.2 # via # -r requirements/pip.txt diff --git a/requirements/docker.txt b/requirements/docker.txt index 242c7949bbb..290195ddc1c 100644 --- a/requirements/docker.txt +++ b/requirements/docker.txt @@ -165,6 +165,8 @@ django-formtools==2.5.1 # via -r requirements/pip.txt django-gravatar2==1.4.5 # via -r requirements/pip.txt +django-impersonate==1.9.4 + # via -r requirements/pip.txt django-ipware==5.0.2 # via # -r requirements/pip.txt diff --git a/requirements/pip.in b/requirements/pip.in index 03a6a14508e..b37876668ac 100644 --- a/requirements/pip.in +++ b/requirements/pip.in @@ -35,6 +35,9 @@ jsonfield django-safemigrate +# Impersonate users in the Django admin for support. +django-impersonate + # The latest version of requests isn't compatible with the # version of the docker-py library we're using. # See https://github.com/docker/docker-py/issues/3256. diff --git a/requirements/pip.txt b/requirements/pip.txt index 8db700b9176..f709b4335de 100644 --- a/requirements/pip.txt +++ b/requirements/pip.txt @@ -117,6 +117,8 @@ django-formtools==2.5.1 # via -r requirements/pip.in django-gravatar2==1.4.5 # via -r requirements/pip.in +django-impersonate==1.9.4 + # via -r requirements/pip.in django-ipware==5.0.2 # via # -r requirements/pip.in diff --git a/requirements/testing.txt b/requirements/testing.txt index f748814ae7a..2ea45b91dca 100644 --- a/requirements/testing.txt +++ b/requirements/testing.txt @@ -162,6 +162,8 @@ django-formtools==2.5.1 # via -r requirements/pip.txt django-gravatar2==1.4.5 # via -r requirements/pip.txt +django-impersonate==1.9.4 + # via -r requirements/pip.txt django-ipware==5.0.2 # via # -r requirements/pip.txt