diff options
| author | Jon Dufresne <jon.dufresne@gmail.com> | 2021-01-12 05:37:56 -0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-01-12 14:37:56 +0100 |
| commit | ba31b0103442ac891fb3cb98f316781254e366c3 (patch) | |
| tree | c41bf2e135811961ca80477521d6e6df5eb17624 /tests/admin_views | |
| parent | 3071660acfbdf4b5c59457c8e9dc345d5e8894c5 (diff) | |
Fixed #31747 -- Fixed model enumeration via admin URLs.
Co-authored-by: Carlton Gibson <carlton.gibson@noumenal.es>
Diffstat (limited to 'tests/admin_views')
| -rw-r--r-- | tests/admin_views/admin.py | 31 | ||||
| -rw-r--r-- | tests/admin_views/tests.py | 211 | ||||
| -rw-r--r-- | tests/admin_views/urls.py | 10 |
3 files changed, 250 insertions, 2 deletions
diff --git a/tests/admin_views/admin.py b/tests/admin_views/admin.py index 4a72e3070f..1140f03496 100644 --- a/tests/admin_views/admin.py +++ b/tests/admin_views/admin.py @@ -15,10 +15,11 @@ from django.core.files.storage import FileSystemStorage from django.core.mail import EmailMessage from django.db import models from django.forms.models import BaseModelFormSet -from django.http import HttpResponse, StreamingHttpResponse +from django.http import HttpResponse, JsonResponse, StreamingHttpResponse from django.urls import path from django.utils.html import format_html from django.utils.safestring import mark_safe +from django.views.decorators.common import no_append_slash from .forms import MediaActionForm from .models import ( @@ -100,7 +101,19 @@ class ArticleForm(forms.ModelForm): model = Article -class ArticleAdmin(admin.ModelAdmin): +class ArticleAdminWithExtraUrl(admin.ModelAdmin): + def get_urls(self): + urlpatterns = super().get_urls() + urlpatterns.append( + path('extra.json', self.admin_site.admin_view(self.extra_json), name='article_extra_json') + ) + return urlpatterns + + def extra_json(self, request): + return JsonResponse({}) + + +class ArticleAdmin(ArticleAdminWithExtraUrl): list_display = ( 'content', 'date', callable_year, 'model_year', 'modeladmin_year', 'model_year_reversed', 'section', lambda obj: obj.title, @@ -1181,5 +1194,19 @@ class ArticleAdmin9(admin.ModelAdmin): return obj is None +class ActorAdmin9(admin.ModelAdmin): + def get_urls(self): + # Opt-out of append slash for single model. + urls = super().get_urls() + for pattern in urls: + pattern.callback = no_append_slash(pattern.callback) + return urls + + site9 = admin.AdminSite(name='admin9') site9.register(Article, ArticleAdmin9) +site9.register(Actor, ActorAdmin9) + +site10 = admin.AdminSite(name='admin10') +site10.final_catch_all_view = False +site10.register(Article, ArticleAdminWithExtraUrl) diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py index 6f47465c4a..297760f807 100644 --- a/tests/admin_views/tests.py +++ b/tests/admin_views/tests.py @@ -6470,3 +6470,214 @@ class GetFormsetsWithInlinesArgumentTest(TestCase): post_data = {'name': '2'} response = self.client.post(reverse('admin:admin_views_implicitlygeneratedpk_change', args=(1,)), post_data) self.assertEqual(response.status_code, 302) + + +@override_settings(ROOT_URLCONF='admin_views.urls') +class AdminSiteFinalCatchAllPatternTests(TestCase): + """ + Verifies the behaviour of the admin catch-all view. + + * Anonynous/non-staff users are redirected to login for all URLs, whether + otherwise valid or not. + * APPEND_SLASH is applied for staff if needed. + * Otherwise Http404. + * Catch-all view disabled via AdminSite.final_catch_all_view. + """ + def test_unknown_url_redirects_login_if_not_authenticated(self): + unknown_url = '/test_admin/admin/unknown/' + response = self.client.get(unknown_url) + self.assertRedirects(response, '%s?next=%s' % (reverse('admin:login'), unknown_url)) + + def test_unknown_url_404_if_authenticated(self): + superuser = User.objects.create_superuser( + username='super', + password='secret', + email='super@example.com', + ) + self.client.force_login(superuser) + unknown_url = '/test_admin/admin/unknown/' + response = self.client.get(unknown_url) + self.assertEqual(response.status_code, 404) + + def test_known_url_redirects_login_if_not_authenticated(self): + known_url = reverse('admin:admin_views_article_changelist') + response = self.client.get(known_url) + self.assertRedirects(response, '%s?next=%s' % (reverse('admin:login'), known_url)) + + def test_known_url_missing_slash_redirects_login_if_not_authenticated(self): + known_url = reverse('admin:admin_views_article_changelist')[:-1] + response = self.client.get(known_url) + # Redirects with the next URL also missing the slash. + self.assertRedirects(response, '%s?next=%s' % (reverse('admin:login'), known_url)) + + def test_non_admin_url_shares_url_prefix(self): + url = reverse('non_admin')[:-1] + response = self.client.get(url) + # Redirects with the next URL also missing the slash. + self.assertRedirects(response, '%s?next=%s' % (reverse('admin:login'), url)) + + def test_url_without_trailing_slash_if_not_authenticated(self): + url = reverse('admin:article_extra_json') + response = self.client.get(url) + self.assertRedirects(response, '%s?next=%s' % (reverse('admin:login'), url)) + + def test_unkown_url_without_trailing_slash_if_not_authenticated(self): + url = reverse('admin:article_extra_json')[:-1] + response = self.client.get(url) + self.assertRedirects(response, '%s?next=%s' % (reverse('admin:login'), url)) + + @override_settings(APPEND_SLASH=True) + def test_missing_slash_append_slash_true_unknown_url(self): + superuser = User.objects.create_user( + username='staff', + password='secret', + email='staff@example.com', + is_staff=True, + ) + self.client.force_login(superuser) + unknown_url = '/test_admin/admin/unknown/' + response = self.client.get(unknown_url[:-1]) + self.assertEqual(response.status_code, 404) + + @override_settings(APPEND_SLASH=True) + def test_missing_slash_append_slash_true(self): + superuser = User.objects.create_user( + username='staff', + password='secret', + email='staff@example.com', + is_staff=True, + ) + self.client.force_login(superuser) + known_url = reverse('admin:admin_views_article_changelist') + response = self.client.get(known_url[:-1]) + self.assertRedirects(response, known_url, status_code=301, target_status_code=403) + + @override_settings(APPEND_SLASH=True) + def test_missing_slash_append_slash_true_non_staff_user(self): + user = User.objects.create_user( + username='user', + password='secret', + email='user@example.com', + is_staff=False, + ) + self.client.force_login(user) + known_url = reverse('admin:admin_views_article_changelist') + response = self.client.get(known_url[:-1]) + self.assertRedirects(response, '/test_admin/admin/login/?next=/test_admin/admin/admin_views/article') + + @override_settings(APPEND_SLASH=False) + def test_missing_slash_append_slash_false(self): + superuser = User.objects.create_user( + username='staff', + password='secret', + email='staff@example.com', + is_staff=True, + ) + self.client.force_login(superuser) + known_url = reverse('admin:admin_views_article_changelist') + response = self.client.get(known_url[:-1]) + self.assertEqual(response.status_code, 404) + + @override_settings(APPEND_SLASH=True) + def test_single_model_no_append_slash(self): + superuser = User.objects.create_user( + username='staff', + password='secret', + email='staff@example.com', + is_staff=True, + ) + self.client.force_login(superuser) + known_url = reverse('admin9:admin_views_actor_changelist') + response = self.client.get(known_url[:-1]) + self.assertEqual(response.status_code, 404) + + # Same tests above with final_catch_all_view=False. + + def test_unknown_url_404_if_not_authenticated_without_final_catch_all_view(self): + unknown_url = '/test_admin/admin10/unknown/' + response = self.client.get(unknown_url) + self.assertEqual(response.status_code, 404) + + def test_unknown_url_404_if_authenticated_without_final_catch_all_view(self): + superuser = User.objects.create_superuser( + username='super', + password='secret', + email='super@example.com', + ) + self.client.force_login(superuser) + unknown_url = '/test_admin/admin10/unknown/' + response = self.client.get(unknown_url) + self.assertEqual(response.status_code, 404) + + def test_known_url_redirects_login_if_not_authenticated_without_final_catch_all_view(self): + known_url = reverse('admin10:admin_views_article_changelist') + response = self.client.get(known_url) + self.assertRedirects(response, '%s?next=%s' % (reverse('admin10:login'), known_url)) + + def test_known_url_missing_slash_redirects_with_slash_if_not_authenticated_without_final_catch_all_view(self): + known_url = reverse('admin10:admin_views_article_changelist') + response = self.client.get(known_url[:-1]) + self.assertRedirects(response, known_url, status_code=301, fetch_redirect_response=False) + + def test_non_admin_url_shares_url_prefix_without_final_catch_all_view(self): + url = reverse('non_admin10') + response = self.client.get(url[:-1]) + self.assertRedirects(response, url, status_code=301) + + def test_url_without_trailing_slash_if_not_authenticated_without_final_catch_all_view(self): + url = reverse('admin10:article_extra_json') + response = self.client.get(url) + self.assertRedirects(response, '%s?next=%s' % (reverse('admin10:login'), url)) + + def test_unkown_url_without_trailing_slash_if_not_authenticated_without_final_catch_all_view(self): + url = reverse('admin10:article_extra_json')[:-1] + response = self.client.get(url) + # Matches test_admin/admin10/admin_views/article/<path:object_id>/ + self.assertRedirects(response, url + '/', status_code=301, fetch_redirect_response=False) + + @override_settings(APPEND_SLASH=True) + def test_missing_slash_append_slash_true_unknown_url_without_final_catch_all_view(self): + superuser = User.objects.create_user( + username='staff', + password='secret', + email='staff@example.com', + is_staff=True, + ) + self.client.force_login(superuser) + unknown_url = '/test_admin/admin10/unknown/' + response = self.client.get(unknown_url[:-1]) + self.assertEqual(response.status_code, 404) + + @override_settings(APPEND_SLASH=True) + def test_missing_slash_append_slash_true_without_final_catch_all_view(self): + superuser = User.objects.create_user( + username='staff', + password='secret', + email='staff@example.com', + is_staff=True, + ) + self.client.force_login(superuser) + known_url = reverse('admin10:admin_views_article_changelist') + response = self.client.get(known_url[:-1]) + self.assertRedirects(response, known_url, status_code=301, target_status_code=403) + + @override_settings(APPEND_SLASH=False) + def test_missing_slash_append_slash_false_without_final_catch_all_view(self): + superuser = User.objects.create_user( + username='staff', + password='secret', + email='staff@example.com', + is_staff=True, + ) + self.client.force_login(superuser) + known_url = reverse('admin10:admin_views_article_changelist') + response = self.client.get(known_url[:-1]) + self.assertEqual(response.status_code, 404) + + # Outside admin. + + def test_non_admin_url_404_if_not_authenticated(self): + unknown_url = '/unknown/' + response = self.client.get(unknown_url) + # Does not redirect to the admin login. + self.assertEqual(response.status_code, 404) diff --git a/tests/admin_views/urls.py b/tests/admin_views/urls.py index ca684b2f2e..355e082b68 100644 --- a/tests/admin_views/urls.py +++ b/tests/admin_views/urls.py @@ -1,8 +1,14 @@ +from django.http import HttpResponse from django.urls import include, path from . import admin, custom_has_permission_admin, customadmin, views from .test_autocomplete_view import site as autocomplete_site + +def non_admin_view(request): + return HttpResponse() + + urlpatterns = [ path('test_admin/admin/doc/', include('django.contrib.admindocs.urls')), path('test_admin/admin/secure-view/', views.secure_view, name='secure_view'), @@ -17,6 +23,10 @@ urlpatterns = [ # All admin views accept `extra_context` to allow adding it like this: path('test_admin/admin8/', (admin.site.get_urls(), 'admin', 'admin-extra-context'), {'extra_context': {}}), path('test_admin/admin9/', admin.site9.urls), + path('test_admin/admin10/', admin.site10.urls), path('test_admin/has_permission_admin/', custom_has_permission_admin.site.urls), path('test_admin/autocomplete_admin/', autocomplete_site.urls), + # Shares the admin URL prefix. + path('test_admin/admin/non_admin_view/', non_admin_view, name='non_admin'), + path('test_admin/admin10/non_admin_view/', non_admin_view, name='non_admin10'), ] |
