From a9bb5f62d280cf8c912eb67f57e1fd56a20a6372 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Tue, 11 Jul 2023 17:34:11 -0400 Subject: [PATCH] chore: Remove django.admin We haven't used django-admin in a long time and it was recently disabled in self-hosted (#52329) Given that we don't use it in saas, and it is not part of any common development workflows I think it is time to not include django-admin in our installed app list. --- src/sentry/admin.py | 316 ------------------ src/sentry/conf/server.py | 3 - src/sentry/conf/urls.py | 6 - src/sentry/django_admin.py | 36 -- .../utils/fixtures/sentry_files.json | 1 - 5 files changed, 362 deletions(-) delete mode 100644 src/sentry/admin.py delete mode 100644 src/sentry/django_admin.py diff --git a/src/sentry/admin.py b/src/sentry/admin.py deleted file mode 100644 index c689022fd12164..00000000000000 --- a/src/sentry/admin.py +++ /dev/null @@ -1,316 +0,0 @@ -from html import escape - -from django import forms -from django.conf import settings -from django.contrib import admin, messages -from django.contrib.auth.forms import AdminPasswordChangeForm -from django.contrib.auth.forms import UserChangeForm as DjangoUserChangeForm -from django.contrib.auth.forms import UserCreationForm as DjangoUserCreationForm -from django.core.exceptions import PermissionDenied -from django.db import transaction -from django.http import Http404, HttpResponseRedirect -from django.shortcuts import get_object_or_404 -from django.template.response import TemplateResponse -from django.urls import re_path -from django.utils.decorators import method_decorator -from django.utils.translation import gettext -from django.utils.translation import gettext_lazy as _ -from django.views.decorators.csrf import csrf_protect -from django.views.decorators.debug import sensitive_post_parameters - -from sentry.models import ( - AuditLogEntry, - AuthIdentity, - AuthProvider, - Organization, - OrganizationMember, - Project, - Team, - User, -) - -csrf_protect_m = method_decorator(csrf_protect) -sensitive_post_parameters_m = method_decorator(sensitive_post_parameters()) - - -class ProjectAdmin(admin.ModelAdmin): - list_display = ("name", "slug", "organization", "status", "date_added") - list_filter = ("status", "public") - search_fields = ("name", "organization__slug", "organization__name", "slug") - raw_id_fields = ("organization",) - readonly_fields = ("first_event", "date_added") - - -admin.site.register(Project, ProjectAdmin) - - -class OrganizationProjectInline(admin.TabularInline): - model = Project - extra = 1 - fields = ("name", "slug", "status", "date_added") - raw_id_fields = ("organization",) - - -class OrganizationTeamInline(admin.TabularInline): - model = Team - extra = 1 - fields = ("name", "slug", "status", "date_added") - raw_id_fields = ("organization",) - - -class OrganizationMemberInline(admin.TabularInline): - model = OrganizationMember - extra = 1 - fields = ("organization", "role") - raw_id_fields = ("organization",) - - -class OrganizationUserInline(OrganizationMemberInline): - fk_name = "user" - - -class AuthIdentityInline(admin.TabularInline): - model = AuthIdentity - extra = 1 - fields = ("user", "auth_provider", "ident", "data", "last_verified") - raw_id_fields = ("user", "auth_provider") - - -class OrganizationAdmin(admin.ModelAdmin): - list_display = ("name", "slug", "status") - list_filter = ("status",) - search_fields = ("name", "slug") - fields = ("name", "slug", "status") - inlines = ( - OrganizationMemberInline, - OrganizationTeamInline, - OrganizationProjectInline, - ) - - -admin.site.register(Organization, OrganizationAdmin) - - -class AuthProviderAdmin(admin.ModelAdmin): - list_display = ("organization_id", "provider", "date_added") - list_filter = ("provider",) - - -admin.site.register(AuthProvider, AuthProviderAdmin) - - -class AuthIdentityAdmin(admin.ModelAdmin): - list_display = ("user", "auth_provider", "ident", "date_added", "last_verified") - list_filter = ("auth_provider__provider",) - search_fields = ("user__email", "user__username", "auth_provider__organization__name") - raw_id_fields = ("user", "auth_provider") - - -admin.site.register(AuthIdentity, AuthIdentityAdmin) - - -class TeamAdmin(admin.ModelAdmin): - list_display = ("name", "slug", "organization", "status", "date_added") - list_filter = ("status",) - search_fields = ("name", "organization__name", "slug") - raw_id_fields = ("organization",) - - def save_model(self, request, obj, form, change): - prev_org = obj.organization_id - super().save_model(request, obj, form, change) - if not change: - return - new_org = obj.organization_id - if new_org != prev_org: - return - - obj.transfer_to(obj.organization) - - -admin.site.register(Team, TeamAdmin) - - -class UserChangeForm(DjangoUserChangeForm): - username = forms.RegexField( - label=_("Username"), - max_length=128, - regex=r"^[\w.@+-]+$", - help_text=_("Required. 128 characters or fewer. Letters, digits and " "@/./+/-/_ only."), - error_messages={ - "invalid": _( - "This value may contain only letters, numbers and " "@/./+/-/_ characters." - ) - }, - ) - - -class UserCreationForm(DjangoUserCreationForm): - username = forms.RegexField( - label=_("Username"), - max_length=128, - regex=r"^[\w.@+-]+$", - help_text=_("Required. 128 characters or fewer. Letters, digits and " "@/./+/-/_ only."), - error_messages={ - "invalid": _( - "This value may contain only letters, numbers and " "@/./+/-/_ characters." - ) - }, - ) - - -class UserAdmin(admin.ModelAdmin): - add_form_template = "admin/auth/user/add_form.html" - change_user_password_template = None - fieldsets = ( - (None, {"fields": ("username", "password")}), - (_("Personal info"), {"fields": ("name", "email")}), - (_("Permissions"), {"fields": ("is_active", "is_staff", "is_superuser")}), - (_("Important dates"), {"fields": ("last_login", "date_joined")}), - ) - add_fieldsets = ( - (None, {"classes": ("wide",), "fields": ("username", "password1", "password2")}), - ) - form = UserChangeForm - add_form = UserCreationForm - change_password_form = AdminPasswordChangeForm - list_display = ("username", "email", "name", "is_staff", "date_joined") - list_filter = ("is_staff", "is_superuser", "is_active", "is_managed") - search_fields = ("username", "name", "email") - ordering = ("username",) - inlines = (AuthIdentityInline,) - - def get_fieldsets(self, request, obj=None): - if not obj: - return self.add_fieldsets - return super().get_fieldsets(request, obj) - - def get_form(self, request, obj=None, **kwargs): - """ - Use special form during user creation - """ - defaults = {} - if obj is None: - defaults.update( - {"form": self.add_form, "fields": admin.utils.flatten_fieldsets(self.add_fieldsets)} - ) - defaults.update(kwargs) - return super().get_form(request, obj, **defaults) - - def get_urls(self): - return [ - re_path(r"^(\d+)/password/$", self.admin_site.admin_view(self.user_change_password)) - ] + super().get_urls() - - def lookup_allowed(self, lookup, value): - # See #20078: we don't want to allow any lookups involving passwords. - if lookup.startswith("password"): - return False - return super().lookup_allowed(lookup, value) - - @sensitive_post_parameters_m - @csrf_protect_m - @transaction.atomic - def add_view(self, request, form_url="", extra_context=None): - # It's an error for a user to have add permission but NOT change - # permission for users. If we allowed such users to add users, they - # could create superusers, which would mean they would essentially have - # the permission to change users. To avoid the problem entirely, we - # disallow users from adding users if they don't have change - # permission. - if not self.has_change_permission(request): - if self.has_add_permission(request) and settings.DEBUG: - # Raise Http404 in debug mode so that the user gets a helpful - # error message. - raise Http404( - 'Your user does not have the "Change user" permission. In ' - "order to add users, Django requires that your user " - 'account have both the "Add user" and "Change user" ' - "permissions set." - ) - raise PermissionDenied - if extra_context is None: - extra_context = {} - username_field = self.model._meta.get_field(self.model.USERNAME_FIELD) - defaults = {"auto_populated_fields": (), "username_help_text": username_field.help_text} - extra_context.update(defaults) - return super().add_view(request, form_url, extra_context) - - @sensitive_post_parameters_m - def user_change_password(self, request, id, form_url=""): - if not self.has_change_permission(request): - raise PermissionDenied - user = get_object_or_404(self.queryset(request), pk=id) - if request.method == "POST": - form = self.change_password_form(user, request.POST) - if form.is_valid(): - form.save() - msg = gettext("Password changed successfully.") - messages.success(request, msg) - return HttpResponseRedirect("..") - else: - form = self.change_password_form(user) - - fieldsets = [(None, {"fields": list(form.base_fields)})] - adminForm = admin.helpers.AdminForm(form, fieldsets, {}) - - context = { - "title": _("Change password: %s") % escape(user.get_username()), - "adminForm": adminForm, - "form_url": form_url, - "form": form, - "is_popup": "_popup" in request.GET, - "add": True, - "change": False, - "has_delete_permission": False, - "has_change_permission": True, - "has_absolute_url": False, - "opts": self.model._meta, - "original": user, - "save_as": False, - "show_save": True, - } - return TemplateResponse( - request, - self.change_user_password_template or "admin/auth/user/change_password.html", - context, - current_app=self.admin_site.name, - ) - - def response_add(self, request, obj, post_url_continue=None): - """ - Determines the HttpResponse for the add_view stage. It mostly defers to - its superclass implementation but is customized because the User model - has a slightly different workflow. - """ - # We should allow further modification of the user just added i.e. the - # 'Save' button should behave like the 'Save and continue editing' - # button except in two scenarios: - # * The user has pressed the 'Save and add another' button - # * We are adding a user in a popup - if "_addanother" not in request.POST and "_popup" not in request.POST: - request.POST["_continue"] = 1 - return super().response_add(request, obj, post_url_continue) - - -admin.site.register(User, UserAdmin) - - -class AuditLogEntryAdmin(admin.ModelAdmin): - list_display = ("event", "organization_id", "actor", "datetime") - list_filter = ("event", "datetime", "organization_id") - search_fields = ("actor__email",) - raw_id_fields = ("actor", "target_user") - readonly_fields = ( - "organization_id", - "actor", - "actor_key", - "target_object", - "target_user", - "event", - "ip_address", - "data", - "datetime", - ) - - -admin.site.register(AuditLogEntry, AuditLogEntryAdmin) diff --git a/src/sentry/conf/server.py b/src/sentry/conf/server.py index c88bbf81e86f86..3bfa682510296a 100644 --- a/src/sentry/conf/server.py +++ b/src/sentry/conf/server.py @@ -88,8 +88,6 @@ def env( DEBUG = IS_DEV -ADMIN_ENABLED = DEBUG - ADMINS = () # Hosts that are considered in the same network (including VPNs). @@ -358,7 +356,6 @@ def env( ] INSTALLED_APPS = ( - "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.messages", diff --git a/src/sentry/conf/urls.py b/src/sentry/conf/urls.py index 6053b9ec144d7a..09bc7fe52a7569 100644 --- a/src/sentry/conf/urls.py +++ b/src/sentry/conf/urls.py @@ -1,6 +1,5 @@ from __future__ import annotations -from django.conf import settings from django.urls import URLPattern, URLResolver, re_path from sentry.web.frontend import csrf_failure @@ -29,9 +28,4 @@ ), ] -if "django.contrib.admin" in settings.INSTALLED_APPS and settings.ADMIN_ENABLED: - from sentry import django_admin - - urlpatterns += django_admin.urlpatterns - urlpatterns += web_urlpatterns diff --git a/src/sentry/django_admin.py b/src/sentry/django_admin.py deleted file mode 100644 index dbb398ac370aab..00000000000000 --- a/src/sentry/django_admin.py +++ /dev/null @@ -1,36 +0,0 @@ -from copy import copy - -from django.conf.urls import include -from django.contrib import admin -from django.urls import re_path - -from sentry.auth.superuser import is_active_superuser - - -class RestrictiveAdminSite(admin.AdminSite): - def has_permission(self, request): - return is_active_superuser(request) - - -def make_site(): - admin.autodiscover() - - # now kill off autodiscover since it would reset the registry - admin.autodiscover = lambda: None - - site = RestrictiveAdminSite() - # copy over the autodiscovery - site._registry = copy(admin.site._registry) - - # clear the default site registry to avoid leaking an insecure admin - admin.site._registry = {} - - # rebind our admin site to maintain compatibility - admin.site = site - - return site - - -site = make_site() - -urlpatterns = [re_path(r"^admin/", include(site.urls[:2]))] diff --git a/tests/sentry/integrations/utils/fixtures/sentry_files.json b/tests/sentry/integrations/utils/fixtures/sentry_files.json index e19ef6752c873c..b4927d1c897cdd 100644 --- a/tests/sentry/integrations/utils/fixtures/sentry_files.json +++ b/tests/sentry/integrations/utils/fixtures/sentry_files.json @@ -912,7 +912,6 @@ "src/sentry/discover/models.py", "src/sentry/discover/tasks.py", "src/sentry/discover/utils.py", - "src/sentry/django_admin.py", "src/sentry/event_manager.py", "src/sentry/eventstore/__init__.py", "src/sentry/eventstore/base.py",