diff --git a/django/contrib/admin/sites.py b/django/contrib/admin/sites.py index 99c46a3e126..699a8b87c10 100644 --- a/django/contrib/admin/sites.py +++ b/django/contrib/admin/sites.py @@ -226,6 +226,7 @@ class AdminSite(object): # Admin-site-wide views. urlpatterns = patterns('', url(r'^$', wrap(self.index), name='index'), + url(r'^login/$', self.login, name='login'), url(r'^logout/$', wrap(self.logout), name='logout'), url(r'^password_change/$', wrap(self.password_change, cacheable=True), name='password_change'), url(r'^password_change/done/$', wrap(self.password_change_done, cacheable=True), name='password_change_done'), @@ -337,7 +338,9 @@ class AdminSite(object): title=_('Log in'), app_path=request.get_full_path(), ) - context[REDIRECT_FIELD_NAME] = request.get_full_path() + if (REDIRECT_FIELD_NAME not in request.GET and + REDIRECT_FIELD_NAME not in request.POST): + context[REDIRECT_FIELD_NAME] = request.get_full_path() context.update(extra_context or {}) defaults = { diff --git a/django/contrib/admin/views/decorators.py b/django/contrib/admin/views/decorators.py index e19265fc83c..f58375f2833 100644 --- a/django/contrib/admin/views/decorators.py +++ b/django/contrib/admin/views/decorators.py @@ -1,30 +1,14 @@ -from functools import wraps -from django.utils.translation import ugettext as _ -from django.contrib.admin.forms import AdminAuthenticationForm -from django.contrib.auth.views import login from django.contrib.auth import REDIRECT_FIELD_NAME +from django.contrib.auth.decorators import user_passes_test -def staff_member_required(view_func): +def staff_member_required(view_func, redirect_field_name=REDIRECT_FIELD_NAME, login_url='admin:login'): """ Decorator for views that checks that the user is logged in and is a staff member, displaying the login page if necessary. """ - @wraps(view_func) - def _checklogin(request, *args, **kwargs): - if request.user.is_active and request.user.is_staff: - # The user is valid. Continue to the admin page. - return view_func(request, *args, **kwargs) - - assert hasattr(request, 'session'), "The Django admin requires session middleware to be installed. Edit your MIDDLEWARE_CLASSES setting to insert 'django.contrib.sessions.middleware.SessionMiddleware'." - defaults = { - 'template_name': 'admin/login.html', - 'authentication_form': AdminAuthenticationForm, - 'extra_context': { - 'title': _('Log in'), - 'app_path': request.get_full_path(), - REDIRECT_FIELD_NAME: request.get_full_path(), - }, - } - return login(request, **defaults) - return _checklogin + return user_passes_test( + lambda u: u.is_active and u.is_staff, + login_url=login_url, + redirect_field_name=redirect_field_name + )(view_func) diff --git a/tests/admin_docs/tests.py b/tests/admin_docs/tests.py index d2f8386bdb1..e3f1460f4bd 100644 --- a/tests/admin_docs/tests.py +++ b/tests/admin_docs/tests.py @@ -38,7 +38,7 @@ class AdminDocViewTests(TestCase): def test_index(self): self.client.logout() - response = self.client.get(reverse('django-admindocs-docroot')) + response = self.client.get(reverse('django-admindocs-docroot'), follow=True) # Should display the login screen self.assertContains(response, '', html=True) diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py index 60280655b62..ccabd9c1362 100644 --- a/tests/admin_views/tests.py +++ b/tests/admin_views/tests.py @@ -1297,11 +1297,11 @@ class AdminViewPermissionsTest(TestCase): superuser.is_active = False superuser.save() - response = self.client.get('/test_admin/admin/') + response = self.client.get('/test_admin/admin/', follow=True) self.assertContains(response, 'id="login-form"') self.assertNotContains(response, 'Log out') - response = self.client.get('/test_admin/admin/secure-view/') + response = self.client.get('/test_admin/admin/secure-view/', follow=True) self.assertContains(response, 'id="login-form"') def testDisabledStaffPermissionsWhenLoggedIn(self): @@ -1310,11 +1310,11 @@ class AdminViewPermissionsTest(TestCase): superuser.is_staff = False superuser.save() - response = self.client.get('/test_admin/admin/') + response = self.client.get('/test_admin/admin/', follow=True) self.assertContains(response, 'id="login-form"') self.assertNotContains(response, 'Log out') - response = self.client.get('/test_admin/admin/secure-view/') + response = self.client.get('/test_admin/admin/secure-view/', follow=True) self.assertContains(response, 'id="login-form"') def testAppIndexFailEarly(self): @@ -1338,6 +1338,25 @@ class AdminViewPermissionsTest(TestCase): response = self.client.get('/test_admin/admin/admin_views/') self.assertEqual(response.status_code, 200) + def test_shortcut_view_only_available_to_staff(self): + """ + Only admin users should be able to use the admin shortcut view. + """ + model_ctype = ContentType.objects.get_for_model(ModelWithStringPrimaryKey) + obj = ModelWithStringPrimaryKey.objects.create(string_pk='foo') + shortcut_url = "/test_admin/admin/r/%s/%s/" % (model_ctype.pk, obj.pk) + + # Not logged in: we should see the login page. + response = self.client.get(shortcut_url, follow=False) + self.assertTemplateUsed(response, 'admin/login.html') + + # Logged in? Redirect. + self.client.login(username='super', password='secret') + response = self.client.get(shortcut_url, follow=False) + # Can't use self.assertRedirects() because User.get_absolute_url() is silly. + self.assertEqual(response.status_code, 302) + self.assertEqual(response.url, 'http://example.com/dummy/foo/') + @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) class AdminViewsNoUrlTest(TestCase): @@ -1625,162 +1644,25 @@ class AdminViewStringPrimaryKeyTest(TestCase): @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) class SecureViewTests(TestCase): + """ + Test behaviour of a view protected by the staff_member_required decorator. + """ urls = "admin_views.urls" fixtures = ['admin-views-users.xml'] - def setUp(self): - # login POST dicts - self.super_login = { - LOGIN_FORM_KEY: 1, - REDIRECT_FIELD_NAME: '/test_admin/admin/secure-view/', - 'username': 'super', - 'password': 'secret', - } - self.super_email_login = { - LOGIN_FORM_KEY: 1, - REDIRECT_FIELD_NAME: '/test_admin/admin/secure-view/', - 'username': 'super@example.com', - 'password': 'secret', - } - self.super_email_bad_login = { - LOGIN_FORM_KEY: 1, - REDIRECT_FIELD_NAME: '/test_admin/admin/secure-view/', - 'username': 'super@example.com', - 'password': 'notsecret', - } - self.adduser_login = { - LOGIN_FORM_KEY: 1, - REDIRECT_FIELD_NAME: '/test_admin/admin/secure-view/', - 'username': 'adduser', - 'password': 'secret', - } - self.changeuser_login = { - LOGIN_FORM_KEY: 1, - REDIRECT_FIELD_NAME: '/test_admin/admin/secure-view/', - 'username': 'changeuser', - 'password': 'secret', - } - self.deleteuser_login = { - LOGIN_FORM_KEY: 1, - REDIRECT_FIELD_NAME: '/test_admin/admin/secure-view/', - 'username': 'deleteuser', - 'password': 'secret', - } - self.joepublic_login = { - LOGIN_FORM_KEY: 1, - REDIRECT_FIELD_NAME: '/test_admin/admin/secure-view/', - 'username': 'joepublic', - 'password': 'secret', - } - def tearDown(self): self.client.logout() def test_secure_view_shows_login_if_not_logged_in(self): - "Ensure that we see the login form" - response = self.client.get('/test_admin/admin/secure-view/') + """ + Ensure that we see the admin login form. + """ + secure_url = '/test_admin/admin/secure-view/' + response = self.client.get(secure_url) + self.assertRedirects(response, '%s?next=%s' % (reverse('admin:login'), secure_url)) + response = self.client.get(secure_url, follow=True) self.assertTemplateUsed(response, 'admin/login.html') - - def test_secure_view_login_successfully_redirects_to_original_url(self): - response = self.client.get('/test_admin/admin/secure-view/') - self.assertEqual(response.status_code, 200) - query_string = 'the-answer=42' - redirect_url = '/test_admin/admin/secure-view/?%s' % query_string - new_next = {REDIRECT_FIELD_NAME: redirect_url} - login = self.client.post('/test_admin/admin/secure-view/', dict(self.super_login, **new_next), QUERY_STRING=query_string) - self.assertRedirects(login, redirect_url) - - def test_staff_member_required_decorator_works_as_per_admin_login(self): - """ - Make sure only staff members can log in. - - Successful posts to the login page will redirect to the orignal url. - Unsuccessfull attempts will continue to render the login page with - a 200 status code. - """ - # Super User - response = self.client.get('/test_admin/admin/secure-view/') - self.assertEqual(response.status_code, 200) - login = self.client.post('/test_admin/admin/secure-view/', self.super_login) - self.assertRedirects(login, '/test_admin/admin/secure-view/') - self.assertFalse(login.context) - self.client.get('/test_admin/admin/logout/') - # make sure the view removes test cookie - self.assertEqual(self.client.session.test_cookie_worked(), False) - - # Test if user enters email address - response = self.client.get('/test_admin/admin/secure-view/') - self.assertEqual(response.status_code, 200) - login = self.client.post('/test_admin/admin/secure-view/', self.super_email_login) - self.assertContains(login, ERROR_MESSAGE) - # only correct passwords get a username hint - login = self.client.post('/test_admin/admin/secure-view/', self.super_email_bad_login) - self.assertContains(login, ERROR_MESSAGE) - new_user = User(username='jondoe', password='secret', email='super@example.com') - new_user.save() - # check to ensure if there are multiple email addresses a user doesn't get a 500 - login = self.client.post('/test_admin/admin/secure-view/', self.super_email_login) - self.assertContains(login, ERROR_MESSAGE) - - # Add User - response = self.client.get('/test_admin/admin/secure-view/') - self.assertEqual(response.status_code, 200) - login = self.client.post('/test_admin/admin/secure-view/', self.adduser_login) - self.assertRedirects(login, '/test_admin/admin/secure-view/') - self.assertFalse(login.context) - self.client.get('/test_admin/admin/logout/') - - # Change User - response = self.client.get('/test_admin/admin/secure-view/') - self.assertEqual(response.status_code, 200) - login = self.client.post('/test_admin/admin/secure-view/', self.changeuser_login) - self.assertRedirects(login, '/test_admin/admin/secure-view/') - self.assertFalse(login.context) - self.client.get('/test_admin/admin/logout/') - - # Delete User - response = self.client.get('/test_admin/admin/secure-view/') - self.assertEqual(response.status_code, 200) - login = self.client.post('/test_admin/admin/secure-view/', self.deleteuser_login) - self.assertRedirects(login, '/test_admin/admin/secure-view/') - self.assertFalse(login.context) - self.client.get('/test_admin/admin/logout/') - - # Regular User should not be able to login. - response = self.client.get('/test_admin/admin/secure-view/') - self.assertEqual(response.status_code, 200) - login = self.client.post('/test_admin/admin/secure-view/', self.joepublic_login) - self.assertEqual(login.status_code, 200) - # Login.context is a list of context dicts we just need to check the first one. - self.assertContains(login, ERROR_MESSAGE) - - # 8509 - if a normal user is already logged in, it is possible - # to change user into the superuser without error - login = self.client.login(username='joepublic', password='secret') - # Check and make sure that if user expires, data still persists - self.client.get('/test_admin/admin/secure-view/') - self.client.post('/test_admin/admin/secure-view/', self.super_login) - # make sure the view removes test cookie - self.assertEqual(self.client.session.test_cookie_worked(), False) - - def test_shortcut_view_only_available_to_staff(self): - """ - Only admin users should be able to use the admin shortcut view. - """ - model_ctype = ContentType.objects.get_for_model(ModelWithStringPrimaryKey) - obj = ModelWithStringPrimaryKey.objects.create(string_pk='foo') - shortcut_url = "/test_admin/admin/r/%s/%s/" % (model_ctype.pk, obj.pk) - - # Not logged in: we should see the login page. - response = self.client.get(shortcut_url, follow=False) - self.assertTemplateUsed(response, 'admin/login.html') - - # Logged in? Redirect. - self.client.login(username='super', password='secret') - response = self.client.get(shortcut_url, follow=False) - # Can't use self.assertRedirects() because User.get_absolute_url() is silly. - self.assertEqual(response.status_code, 302) - self.assertEqual(response.url, 'http://example.com/dummy/foo/') + self.assertEqual(response.context[REDIRECT_FIELD_NAME], secure_url) @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))