diff --git a/docs/advanced_topics/privacy.rst b/docs/advanced_topics/privacy.rst index 2702e36336..bb3bd487cd 100644 --- a/docs/advanced_topics/privacy.rst +++ b/docs/advanced_topics/privacy.rst @@ -3,9 +3,33 @@ Private pages ============= -Users with publish permission on a page can set it to be private by clicking the 'Privacy' control in the top right corner of the page explorer or editing interface, and setting a password. Users visiting this page, or any of its subpages, will be prompted to enter a password before they can view the page. +Users with publish permission on a page can set it to be private by clicking the 'Privacy' control in the top right corner of the page explorer or editing interface. This sets a restriction on who is allowed to view the page and its sub-pages. Several different kinds of restriction are available: -Private pages work on Wagtail out of the box - the site implementer does not need to do anything to set them up. However, the default "password required" form is only a bare-bones HTML page, and site implementers may wish to replace this with a page customised to their site design. + * **Accessible to logged-in users:** The user must log in to view the page. All user accounts are granted access, regardless of permission level. + * **Accessible with the following password:** The user must enter the given password to view the page. This is appropriate for situations where you want to share a page with a trusted group of people, but giving them individual user accounts would be overkill. The same password is shared between all users, and this works independently of any user accounts that exist on the site. + * **Accessible to users in specific groups:** The user must be logged in, and a member of one or more of the specified groups, in order to view the page. + +Private pages work on Wagtail out of the box - the site implementer does not need to do anything to set them up. However, the default "log in" and "password required" forms are only bare-bones HTML pages, and site implementers may wish to replace them with a page customised to their site design. + + +Setting up a login page +~~~~~~~~~~~~~~~~~~~~~~~ + +The basic login page can be customised by setting ``WAGTAIL_FRONTEND_LOGIN_TEMPLATE`` to the path of a template you wish to use: + +.. code-block:: python + + WAGTAIL_FRONTEND_LOGIN_TEMPLATE = 'myapp/login.html' + +Wagtail uses Django's standard ``django.contrib.auth.views.login`` view here, and so the context variables available on the template are as detailed in `Django's login view documentation `_. + +If the stock Django login view is not suitable - for example, you wish to use an external authentication system, or you are integrating Wagtail into an existing Django site that already has a working login view - you can specify the URL of the login view via the ``WAGTAIL_FRONTEND_LOGIN_URL`` setting: + +.. code-block:: python + + WAGTAIL_FRONTEND_LOGIN_URL = '/accounts/login/' + +To integrate Wagtail into a Django site with an existing login mechanism, setting ``WAGTAIL_FRONTEND_LOGIN_URL = LOGIN_URL`` will usually be sufficient. Setting up a global "password required" page diff --git a/wagtail/tests/testapp/fixtures/test.json b/wagtail/tests/testapp/fixtures/test.json index 65ef7afb48..8b3fe3643e 100644 --- a/wagtail/tests/testapp/fixtures/test.json +++ b/wagtail/tests/testapp/fixtures/test.json @@ -23,7 +23,7 @@ "model": "wagtailcore.page", "fields": { "title": "Welcome to the Wagtail test site!", - "numchild": 7, + "numchild": 9, "show_in_menus": false, "live": true, "depth": 2, @@ -526,6 +526,51 @@ "submit_time": "2014-01-01T12:00:00.000Z" } }, +{ + "pk": 18, + "model": "wagtailcore.page", + "fields": { + "title": "Secret event editor plans", + "numchild": 0, + "show_in_menus": true, + "live": true, + "depth": 3, + "content_type": ["tests", "simplepage"], + "path": "000100010008", + "url_path": "/home/secret-event-editor-plans/", + "slug": "secret-event-editor-plans" + } +}, +{ + "pk": 18, + "model": "tests.simplepage", + "fields": { + "content": "

let's move Easter to Christmas

" + } +}, +{ + "pk": 19, + "model": "wagtailcore.page", + "fields": { + "title": "Secret login plans", + "numchild": 0, + "show_in_menus": true, + "live": true, + "depth": 3, + "content_type": ["tests", "simplepage"], + "path": "000100010009", + "url_path": "/home/secret-login-plans/", + "slug": "secret-login-plans" + } +}, +{ + "pk": 19, + "model": "tests.simplepage", + "fields": { + "content": "

collect logs

" + } +}, + { "pk": 1, @@ -833,9 +878,29 @@ "model": "wagtailcore.pageviewrestriction", "fields": { "page": 11, + "restriction_type": "password", "password": "swordfish" } }, +{ + "pk": 2, + "model": "wagtailcore.pageviewrestriction", + "fields": { + "page": 18, + "restriction_type": "groups", + "groups": [ + ["Event editors"] + ] + } +}, +{ + "pk": 3, + "model": "wagtailcore.pageviewrestriction", + "fields": { + "page": 19, + "restriction_type": "login" + } +}, { "pk": 1, "model": "wagtailimages.image", diff --git a/wagtail/wagtailadmin/forms.py b/wagtail/wagtailadmin/forms.py index 5cfb361597..1af8c44bab 100644 --- a/wagtail/wagtailadmin/forms.py +++ b/wagtail/wagtailadmin/forms.py @@ -20,7 +20,9 @@ from modelcluster.forms import ClusterForm, ClusterFormMetaclass from taggit.managers import TaggableManager from wagtail.wagtailadmin import widgets -from wagtail.wagtailcore.models import Collection, GroupCollectionPermission, Page +from wagtail.wagtailcore.models import ( + Collection, GroupCollectionPermission, Page, PageViewRestriction +) class URLOrAbsolutePathValidator(validators.URLValidator): @@ -179,21 +181,32 @@ class CopyForm(forms.Form): return cleaned_data -class PageViewRestrictionForm(forms.Form): - restriction_type = forms.ChoiceField(label=ugettext_lazy("Visibility"), choices=[ - ('none', ugettext_lazy("Public")), - ('password', ugettext_lazy("Private, accessible with the following password")), - ], widget=forms.RadioSelect) - password = forms.CharField(label=ugettext_lazy("Password"), required=False) +class PageViewRestrictionForm(forms.ModelForm): + restriction_type = forms.ChoiceField( + label=ugettext_lazy("Visibility"), choices=PageViewRestriction.RESTRICTION_CHOICES, + widget=forms.RadioSelect) - def clean(self): - cleaned_data = super(PageViewRestrictionForm, self).clean() + def __init__(self, *args, **kwargs): + super(PageViewRestrictionForm, self).__init__(*args, **kwargs) - if cleaned_data.get('restriction_type') == 'password' and not cleaned_data.get('password'): - self._errors["password"] = self.error_class([_('This field is required.')]) - del cleaned_data['password'] + self.fields['groups'].widget = forms.CheckboxSelectMultiple() + self.fields['groups'].queryset = Group.objects.all() - return cleaned_data + def clean_password(self): + password = self.cleaned_data.get('password') + if self.cleaned_data.get('restriction_type') == PageViewRestriction.PASSWORD and not password: + raise forms.ValidationError(_("This field is required."), code='invalid') + return password + + def clean_groups(self): + groups = self.cleaned_data.get('groups') + if self.cleaned_data.get('restriction_type') == PageViewRestriction.GROUPS and not groups: + raise forms.ValidationError(_("Please select at least one group."), code='invalid') + return groups + + class Meta: + model = PageViewRestriction + fields = ('restriction_type', 'password', 'groups') # Form field properties to override whenever we encounter a model field diff --git a/wagtail/wagtailadmin/templates/wagtailadmin/page_privacy/set_privacy.html b/wagtail/wagtailadmin/templates/wagtailadmin/page_privacy/set_privacy.html index 8d69938706..925a5fb32a 100644 --- a/wagtail/wagtailadmin/templates/wagtailadmin/page_privacy/set_privacy.html +++ b/wagtail/wagtailadmin/templates/wagtailadmin/page_privacy/set_privacy.html @@ -10,6 +10,9 @@ {% include "wagtailadmin/shared/field_as_li.html" with field=form.restriction_type %} {% include "wagtailadmin/shared/field_as_li.html" with field=form.password li_classes="password-field" %} + diff --git a/wagtail/wagtailadmin/templates/wagtailadmin/page_privacy/set_privacy.js b/wagtail/wagtailadmin/templates/wagtailadmin/page_privacy/set_privacy.js index 0a40c0f85e..045c31b4df 100644 --- a/wagtail/wagtailadmin/templates/wagtailadmin/page_privacy/set_privacy.js +++ b/wagtail/wagtailadmin/templates/wagtailadmin/page_privacy/set_privacy.js @@ -5,12 +5,20 @@ function(modal) { }); var restrictionTypePasswordField = $("input[name='restriction_type'][value='password']", modal.body); - var passwordField = $("#id_password", modal.body); + var restrictionTypeGroupsField = $("input[name='restriction_type'][value='groups']", modal.body); + var passwordField = $(".password-field", modal.body); + var groupsFields = $('#groups-fields', modal.body); + function refreshFormFields() { if (restrictionTypePasswordField.is(':checked')) { - passwordField.removeAttr('disabled'); + passwordField.show(); + groupsFields.hide(); + } else if (restrictionTypeGroupsField.is(':checked')){ + passwordField.hide(); + groupsFields.show(); } else { - passwordField.attr('disabled', true); + passwordField.hide(); + groupsFields.hide(); } } refreshFormFields(); diff --git a/wagtail/wagtailadmin/tests/test_privacy.py b/wagtail/wagtailadmin/tests/test_privacy.py index 962c5657a6..a59fc79e90 100644 --- a/wagtail/wagtailadmin/tests/test_privacy.py +++ b/wagtail/wagtailadmin/tests/test_privacy.py @@ -1,5 +1,6 @@ from __future__ import absolute_import, unicode_literals +from django.contrib.auth.models import Group from django.core.urlresolvers import reverse from django.test import TestCase @@ -26,7 +27,9 @@ class TestSetPrivacyView(TestCase, WagtailTestUtils): content="hello", live=True, )) - PageViewRestriction.objects.create(page=self.private_page, password='password123') + PageViewRestriction.objects.create( + page=self.private_page, restriction_type='password', password='password123' + ) self.private_child_page = self.private_page.add_child(instance=SimplePage( title="Private child page", @@ -34,6 +37,23 @@ class TestSetPrivacyView(TestCase, WagtailTestUtils): live=True, )) + self.private_groups_page = self.homepage.add_child(instance=SimplePage( + title="Private groups page", + content="hello", + live=True, + )) + restriction = PageViewRestriction.objects.create(page=self.private_groups_page, restriction_type='groups') + self.group = Group.objects.create(name='Private page group') + self.group2 = Group.objects.create(name='Private page group2') + restriction.groups.add(self.group) + restriction.groups.add(self.group2) + + self.private_groups_child_page = self.private_groups_page.add_child(instance=SimplePage( + title="Private groups child page", + content="hello", + live=True, + )) + def test_get_public(self): """ This tests that a blank form is returned when a user opens the set_privacy view on a public page @@ -63,6 +83,7 @@ class TestSetPrivacyView(TestCase, WagtailTestUtils): # Check form attributes self.assertEqual(response.context['form']['restriction_type'].value(), 'password') self.assertEqual(response.context['form']['password'].value(), 'password123') + self.assertEqual(response.context['form']['groups'].value(), []) def test_get_private_child(self): """ @@ -83,6 +104,7 @@ class TestSetPrivacyView(TestCase, WagtailTestUtils): post_data = { 'restriction_type': 'password', 'password': 'helloworld', + 'groups': [], } response = self.client.post(reverse('wagtailadmin_pages:set_privacy', args=(self.public_page.id, )), post_data) @@ -92,9 +114,16 @@ class TestSetPrivacyView(TestCase, WagtailTestUtils): # Check that a page restriction has been created self.assertTrue(PageViewRestriction.objects.filter(page=self.public_page).exists()) + restriction = PageViewRestriction.objects.get(page=self.public_page) # Check that the password is set correctly - self.assertEqual(PageViewRestriction.objects.get(page=self.public_page).password, 'helloworld') + self.assertEqual(restriction.password, 'helloworld') + + # Check that the restriction_type is set correctly + self.assertEqual(restriction.restriction_type, 'password') + + # Be sure there are no groups set + self.assertEqual(restriction.groups.count(), 0) def test_set_password_restriction_password_unset(self): """ @@ -103,6 +132,7 @@ class TestSetPrivacyView(TestCase, WagtailTestUtils): post_data = { 'restriction_type': 'password', 'password': '', + 'groups': [], } response = self.client.post(reverse('wagtailadmin_pages:set_privacy', args=(self.public_page.id, )), post_data) @@ -119,6 +149,91 @@ class TestSetPrivacyView(TestCase, WagtailTestUtils): post_data = { 'restriction_type': 'none', 'password': '', + 'groups': [], + } + response = self.client.post( + reverse('wagtailadmin_pages:set_privacy', args=(self.private_page.id, )), post_data) + + # Check response + self.assertEqual(response.status_code, 200) + self.assertContains(response, "modal.respond('setPermission', true);") + + # Check that the page restriction has been deleted + self.assertFalse(PageViewRestriction.objects.filter(page=self.private_page).exists()) + + def test_get_private_groups(self): + """ + This tests that the restriction type and group fields as set correctly when a user opens the set_privacy view on a public page + """ + response = self.client.get(reverse('wagtailadmin_pages:set_privacy', args=(self.private_groups_page.id, ))) + + # Check response + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtailadmin/page_privacy/set_privacy.html') + self.assertEqual(response.context['page'].specific, self.private_groups_page) + + # Check form attributes + self.assertEqual(response.context['form']['restriction_type'].value(), 'groups') + self.assertEqual(response.context['form']['password'].value(), '') + self.assertEqual(response.context['form']['groups'].value(), [self.group.id, self.group2.id]) + + def test_set_group_restriction(self): + """ + This tests that setting a group restriction using the set_privacy view works + """ + post_data = { + 'restriction_type': 'groups', + 'password': '', + 'groups': [self.group.id, self.group2.id], + } + response = self.client.post(reverse('wagtailadmin_pages:set_privacy', args=(self.public_page.id, )), post_data) + + # Check response + self.assertEqual(response.status_code, 200) + self.assertContains(response, "modal.respond('setPermission', false);") + + # Check that a page restriction has been created + self.assertTrue(PageViewRestriction.objects.filter(page=self.public_page).exists()) + + restriction = PageViewRestriction.objects.get(page=self.public_page) + + # restriction_type should be 'groups' + self.assertEqual(restriction.restriction_type, 'groups') + + # Be sure there is no password set + self.assertEqual(restriction.password, '') + + # Check that the groups are set correctly + self.assertEqual( + set(PageViewRestriction.objects.get(page=self.public_page).groups.all()), + set([self.group, self.group2]) + ) + + def test_set_group_restriction_password_unset(self): + """ + This tests that the group fields on the form are validated correctly + """ + post_data = { + 'restriction_type': 'groups', + 'password': '', + 'groups': [], + } + response = self.client.post(reverse('wagtailadmin_pages:set_privacy', args=(self.public_page.id, )), post_data) + + # Check response + self.assertEqual(response.status_code, 200) + + # Check that a form error was raised + self.assertFormError(response, 'form', 'groups', "Please select at least one group.") + + def test_unset_group_restriction(self): + """ + This tests that removing a groups restriction using the set_privacy view works + """ + post_data = { + 'restriction_type': 'none', + 'password': '', + 'groups': [], } response = self.client.post(reverse('wagtailadmin_pages:set_privacy', args=(self.private_page.id, )), post_data) @@ -148,7 +263,9 @@ class TestPrivacyIndicators(TestCase, WagtailTestUtils): content="hello", live=True, )) - PageViewRestriction.objects.create(page=self.private_page, password='password123') + PageViewRestriction.objects.create( + page=self.private_page, restriction_type='password', password='password123' + ) self.private_child_page = self.private_page.add_child(instance=SimplePage( title="Private child page", diff --git a/wagtail/wagtailadmin/views/page_privacy.py b/wagtail/wagtailadmin/views/page_privacy.py index 4944211f02..e6875807a7 100644 --- a/wagtail/wagtailadmin/views/page_privacy.py +++ b/wagtail/wagtailadmin/views/page_privacy.py @@ -24,20 +24,16 @@ def set_privacy(request, page_id): restriction_exists_on_ancestor = False if request.method == 'POST': - form = PageViewRestrictionForm(request.POST) + form = PageViewRestrictionForm(request.POST, instance=restriction) if form.is_valid() and not restriction_exists_on_ancestor: - if form.cleaned_data['restriction_type'] == 'none': + if form.cleaned_data['restriction_type'] == PageViewRestriction.NONE: # remove any existing restriction if restriction: restriction.delete() - else: # restriction_type = 'password' - if restriction: - restriction.password = form.cleaned_data['password'] - restriction.save() - else: - # create a new restriction object - PageViewRestriction.objects.create( - page=page, password=form.cleaned_data['password']) + else: + restriction = form.save(commit=False) + restriction.page = page + form.save() return render_modal_workflow( request, None, 'wagtailadmin/page_privacy/set_privacy_done.js', { @@ -48,9 +44,7 @@ def set_privacy(request, page_id): else: # request is a GET if not restriction_exists_on_ancestor: if restriction: - form = PageViewRestrictionForm(initial={ - 'restriction_type': 'password', 'password': restriction.password - }) + form = PageViewRestrictionForm(instance=restriction) else: # no current view restrictions on this page form = PageViewRestrictionForm(initial={ diff --git a/wagtail/wagtailcore/migrations/0031_add_page_view_restriction_types.py b/wagtail/wagtailcore/migrations/0031_add_page_view_restriction_types.py new file mode 100644 index 0000000000..b845d2253c --- /dev/null +++ b/wagtail/wagtailcore/migrations/0031_add_page_view_restriction_types.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.2 on 2016-10-07 15:33 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('auth', '0001_initial'), + ('wagtailcore', '0030_index_on_pagerevision_created_at'), + ] + + operations = [ + migrations.AddField( + model_name='pageviewrestriction', + name='groups', + field=models.ManyToManyField(blank=True, to='auth.Group'), + ), + migrations.AddField( + model_name='pageviewrestriction', + name='restriction_type', + field=models.CharField(choices=[('none', 'Public'), ('login', 'Private, accessible to logged-in users'), ('password', 'Private, accessible with the following password'), ('groups', 'Private, accessible to users in specific groups')], default='password', max_length=20), + preserve_default=False, + ), + migrations.AlterField( + model_name='pageviewrestriction', + name='password', + field=models.CharField(blank=True, max_length=255, verbose_name='password'), + ), + ] diff --git a/wagtail/wagtailcore/models.py b/wagtail/wagtailcore/models.py index e633eeef2f..8071b619ab 100644 --- a/wagtail/wagtailcore/models.py +++ b/wagtail/wagtailcore/models.py @@ -1799,8 +1799,25 @@ class PagePermissionTester(object): class PageViewRestriction(models.Model): - page = models.ForeignKey('Page', verbose_name=_('page'), related_name='view_restrictions', on_delete=models.CASCADE) - password = models.CharField(verbose_name=_('password'), max_length=255) + NONE = 'none' + PASSWORD = 'password' + GROUPS = 'groups' + LOGIN = 'login' + + RESTRICTION_CHOICES = ( + (NONE, _("Public")), + (LOGIN, _("Private, accessible to logged-in users")), + (PASSWORD, _("Private, accessible with the following password")), + (GROUPS, _("Private, accessible to users in specific groups")), + ) + + restriction_type = models.CharField( + max_length=20, choices=RESTRICTION_CHOICES) + page = models.ForeignKey( + 'Page', verbose_name=_('page'), related_name='view_restrictions', on_delete=models.CASCADE + ) + password = models.CharField(verbose_name=_('password'), max_length=255, blank=True) + groups = models.ManyToManyField(Group, blank=True) class Meta: verbose_name = _('page view restriction') diff --git a/wagtail/wagtailcore/templates/wagtailcore/login.html b/wagtail/wagtailcore/templates/wagtailcore/login.html new file mode 100644 index 0000000000..5bbc0add94 --- /dev/null +++ b/wagtail/wagtailcore/templates/wagtailcore/login.html @@ -0,0 +1,25 @@ +{% load i18n %} + + + + {% trans "Log in" %} + + +

{% trans "Log in" %}

+ + {% if form.errors %} +

{% trans "Your username and password didn't match. Please try again." %}

+ {% endif %} + + {% if next and request.user.is_authenticated %} +

{% trans "Your account doesn't have access to this page. To proceed, please log in with an account that has access." %}

+ {% endif %} + +
+ {% csrf_token %} + {{ form.as_p }} + + +
+ + diff --git a/wagtail/wagtailcore/tests/test_page_privacy.py b/wagtail/wagtailcore/tests/test_page_privacy.py index 2abc6a1c2b..68ad350a39 100644 --- a/wagtail/wagtailcore/tests/test_page_privacy.py +++ b/wagtail/wagtailcore/tests/test_page_privacy.py @@ -1,5 +1,6 @@ from __future__ import absolute_import, unicode_literals +from django.contrib.auth.models import Group from django.test import TestCase from wagtail.wagtailcore.models import Page, PageViewRestriction @@ -13,6 +14,10 @@ class TestPagePrivacy(TestCase): self.view_restriction = PageViewRestriction.objects.get( page=self.secret_plans_page) + self.secret_event_editor_plans_page = Page.objects.get(url_path='/home/secret-event-editor-plans/') + self.event_editors_group = Group.objects.get(name='Event editors') + self.secret_login_plans_page = Page.objects.get(url_path='/home/secret-login-plans/') + def test_anonymous_user_must_authenticate(self): response = self.client.get('/secret-plans/') self.assertEqual(response.templates[0].name, 'wagtailcore/password_required.html') @@ -76,3 +81,34 @@ class TestPagePrivacy(TestCase): # now requests to /secret-plans/ should pass authentication response = self.client.get('/secret-plans/steal-underpants/') self.assertEqual(response.templates[0].name, 'tests/event_page.html') + + def test_group_restriction_with_anonymous_user(self): + response = self.client.get('/secret-event-editor-plans/') + self.assertRedirects(response, '/_util/login/?next=/secret-event-editor-plans/') + + def test_group_restriction_with_unpermitted_user(self): + self.client.login(username='eventmoderator', password='password') + response = self.client.get('/secret-event-editor-plans/') + self.assertRedirects(response, '/_util/login/?next=/secret-event-editor-plans/') + + def test_group_restriction_with_permitted_user(self): + self.client.login(username='eventeditor', password='password') + response = self.client.get('/secret-event-editor-plans/') + self.assertEqual(response.status_code, 200) + self.assertContains(response, "Secret event editor plans") + + def test_group_restriction_with_superuser(self): + self.client.login(username='superuser', password='password') + response = self.client.get('/secret-event-editor-plans/') + self.assertEqual(response.status_code, 200) + self.assertContains(response, "Secret event editor plans") + + def test_login_restriction_with_anonymous_user(self): + response = self.client.get('/secret-login-plans/') + self.assertRedirects(response, '/_util/login/?next=/secret-login-plans/') + + def test_login_restriction_with_logged_in_user(self): + self.client.login(username='eventmoderator', password='password') + response = self.client.get('/secret-login-plans/') + self.assertEqual(response.status_code, 200) + self.assertContains(response, "Secret login plans") diff --git a/wagtail/wagtailcore/tests/test_views.py b/wagtail/wagtailcore/tests/test_views.py new file mode 100644 index 0000000000..bbbf04c0e6 --- /dev/null +++ b/wagtail/wagtailcore/tests/test_views.py @@ -0,0 +1,39 @@ +from __future__ import absolute_import, unicode_literals + +from django.core.urlresolvers import reverse +from django.test import TestCase + +from wagtail.tests.utils import WagtailTestUtils +from wagtail.wagtailcore.models import Page + + +class TestLoginView(TestCase, WagtailTestUtils): + fixtures = ['test.json'] + + def setUp(self): + self.user = self.create_test_user() + self.events_index = Page.objects.get(url_path='/home/events/') + + def test_get(self): + response = self.client.get(reverse('wagtailcore_login')) + self.assertEqual(response.status_code, 200) + self.assertContains(response, "

Log in

") + self.assertNotContains(response, "

Your username and password didn't match. Please try again.

") + + def test_post_incorrect_password(self): + response = self.client.post(reverse('wagtailcore_login'), { + 'username': 'test@email.com', + 'password': 'wrongpassword', + 'next': self.events_index.url, + }) + self.assertEqual(response.status_code, 200) + self.assertContains(response, "

Log in

") + self.assertContains(response, "

Your username and password didn't match. Please try again.

") + + def test_post_correct_password(self): + response = self.client.post(reverse('wagtailcore_login'), { + 'username': 'test@email.com', + 'password': 'password', + 'next': self.events_index.url, + }) + self.assertRedirects(response, self.events_index.url) diff --git a/wagtail/wagtailcore/urls.py b/wagtail/wagtailcore/urls.py index 3a7ad5139b..998fc4e3b1 100644 --- a/wagtail/wagtailcore/urls.py +++ b/wagtail/wagtailcore/urls.py @@ -1,6 +1,8 @@ from __future__ import absolute_import, unicode_literals +from django.conf import settings from django.conf.urls import url +from django.contrib.auth import views as auth_views from wagtail.wagtailcore import views from wagtail.wagtailcore.utils import WAGTAIL_APPEND_SLASH @@ -18,9 +20,16 @@ else: serve_pattern = r'^([\w\-/]*)$' +WAGTAIL_FRONTEND_LOGIN_TEMPLATE = getattr( + settings, 'WAGTAIL_FRONTEND_LOGIN_TEMPLATE', 'wagtailcore/login.html' +) + + urlpatterns = [ url(r'^_util/authenticate_with_password/(\d+)/(\d+)/$', views.authenticate_with_password, name='wagtailcore_authenticate_with_password'), + url(r'^_util/login/$', auth_views.login, {'template_name': WAGTAIL_FRONTEND_LOGIN_TEMPLATE}, + name='wagtailcore_login'), # Front-end page views are handled through Wagtail's core.views.serve # mechanism diff --git a/wagtail/wagtailcore/wagtail_hooks.py b/wagtail/wagtailcore/wagtail_hooks.py index 026fb0fbc3..67065dc082 100644 --- a/wagtail/wagtailcore/wagtail_hooks.py +++ b/wagtail/wagtailcore/wagtail_hooks.py @@ -1,8 +1,17 @@ from __future__ import absolute_import, unicode_literals +from django.conf import settings +from django.contrib.auth.views import redirect_to_login from django.core.urlresolvers import reverse +from wagtail.utils.compat import user_is_authenticated from wagtail.wagtailcore import hooks +from wagtail.wagtailcore.models import PageViewRestriction + + +def require_wagtail_login(next): + login_url = getattr(settings, 'WAGTAIL_FRONTEND_LOGIN_URL', reverse('wagtailcore_login')) + return redirect_to_login(next, login_url) @hooks.register('before_serve_page') @@ -19,9 +28,19 @@ def check_view_restrictions(page, request, serve_args, serve_kwargs): if restrictions: passed_restrictions = request.session.get('passed_page_view_restrictions', []) for restriction in restrictions: - if restriction.id not in passed_restrictions: - from wagtail.wagtailcore.forms import PasswordPageViewRestrictionForm - form = PasswordPageViewRestrictionForm(instance=restriction, - initial={'return_url': request.get_full_path()}) - action_url = reverse('wagtailcore_authenticate_with_password', args=[restriction.id, page.id]) - return page.serve_password_required_response(request, form, action_url) + if restriction.restriction_type == PageViewRestriction.PASSWORD: + if restriction.id not in passed_restrictions: + from wagtail.wagtailcore.forms import PasswordPageViewRestrictionForm + form = PasswordPageViewRestrictionForm(instance=restriction, + initial={'return_url': request.get_full_path()}) + action_url = reverse('wagtailcore_authenticate_with_password', args=[restriction.id, page.id]) + return page.serve_password_required_response(request, form, action_url) + elif restriction.restriction_type == PageViewRestriction.LOGIN: + if not user_is_authenticated(request.user): + return require_wagtail_login(next=request.get_full_path()) + elif restriction.restriction_type == PageViewRestriction.GROUPS: + if not request.user.is_superuser: + current_user_groups = request.user.groups.all() + + if not any(group in current_user_groups for group in restriction.groups.all()): + return require_wagtail_login(next=request.get_full_path())