Prevent users from demoting or deactivating themself

Remove is_active and is_superuser fields completely when editing
oneself, to avoid locking oneself out.
pull/3719/head
Stein Strindhaug 2017-02-24 18:05:34 +01:00 zatwierdzone przez Matt Westcott
rodzic 15965fd0cc
commit f663c76490
6 zmienionych plików z 163 dodań i 6 usunięć

Wyświetl plik

@ -10,6 +10,7 @@ Changelog
* Improved performance of sitemap generation (Levi Adler)
* StreamField now respects the `blank` setting; StreamBlock accepts a `required` setting (Loic Teixeira)
* StreamBlock now accepts `min_num`, `max_num` and `block_counts` settings to control the minimum and maximum numbers of blocks (Edwar Baron, Matt Westcott)
* Users can no longer remove their own active / superuser flags through Settings -> Users (Stein Strindhaug, Huub Bouma)
* Fix: FieldBlocks in StreamField now call the field's `prepare_value` method (Tim Heap)
* Fix: Initial disabled state of InlinePanel add button is now set correctly on non-default tabs (Matthew Downey)
* Fix: Redirects with unicode characters now work (Rich Brennan)

Wyświetl plik

@ -26,6 +26,7 @@ Other features
* Improved performance of sitemap generation (Levi Adler)
* StreamField now respects the ``blank`` setting; StreamBlock accepts a ``required`` setting (Loic Teixeira)
* StreamBlock now accepts ``min_num``, ``max_num`` and ``block_counts`` settings to control the minimum and maximum numbers of blocks (Edwar Baron, Matt Westcott)
* Users can no longer remove their own active / superuser flags through Settings -> Users (Stein Strindhaug, Huub Bouma)
Bug fixes
~~~~~~~~~

Wyświetl plik

@ -166,6 +166,14 @@ class UserCreationForm(UserForm):
class UserEditForm(UserForm):
password_required = False
def __init__(self, *args, **kwargs):
editing_self = kwargs.pop('editing_self', False)
super(UserEditForm, self).__init__(*args, **kwargs)
if editing_self:
del self.fields["is_active"]
del self.fields["is_superuser"]
class Meta:
model = User
fields = set([User.USERNAME_FIELD, "is_active"]) | standard_fields | custom_fields

Wyświetl plik

@ -28,7 +28,10 @@
{% block extra_fields %}{% endblock extra_fields %}
{% include "wagtailadmin/shared/field_as_li.html" with field=form.password1 %}
{% include "wagtailadmin/shared/field_as_li.html" with field=form.password2 %}
{% include "wagtailadmin/shared/field_as_li.html" with field=form.is_active %}
{% if form.is_active %}
{% include "wagtailadmin/shared/field_as_li.html" with field=form.is_active %}
{% endif %}
{% endblock fields %}
<li>
<input type="submit" value="{% trans 'Save' %}" class="button" />
@ -40,7 +43,10 @@
</section>
<section id="roles" class="nice-padding">
<ul class="fields">
{% include "wagtailadmin/shared/field_as_li.html" with field=form.is_superuser %}
{% if form.is_superuser %}
{% include "wagtailadmin/shared/field_as_li.html" with field=form.is_superuser %}
{% endif %}
{% include "wagtailadmin/shared/field_as_li.html" with field=form.groups %}
<li>
<input type="submit" value="{% trans 'Save' %}" class="button" />

Wyświetl plik

@ -20,6 +20,7 @@ from wagtail.wagtailusers.views.users import get_user_creation_form, get_user_ed
delete_user_perm_codename = "delete_{0}".format(AUTH_USER_MODEL_NAME.lower())
change_user_perm_codename = "change_{0}".format(AUTH_USER_MODEL_NAME.lower())
class CustomUserCreationForm(UserCreationForm):
@ -314,7 +315,7 @@ class TestUserEditView(TestCase, WagtailTestUtils):
)
# Login
self.login()
self.current_user = self.login()
def get(self, params={}, user_id=None):
return self.client.get(reverse('wagtailusers_users:edit', args=(user_id or self.test_user.pk, )), params)
@ -330,7 +331,7 @@ class TestUserEditView(TestCase, WagtailTestUtils):
def test_nonexistant_redirect(self):
self.assertEqual(self.get(user_id=100000).status_code, 404)
def test_edit(self):
def test_edit_and_deactivate(self):
response = self.post({
'username': "testuser",
'email': "test@user.com",
@ -338,6 +339,9 @@ class TestUserEditView(TestCase, WagtailTestUtils):
'last_name': "User",
'password1': "password",
'password2': "password",
# Leaving out these fields, thus setting them to False:
# 'is_active': 'on'
# 'is_superuser': 'on',
})
# Should redirect back to index
@ -346,6 +350,88 @@ class TestUserEditView(TestCase, WagtailTestUtils):
# Check that the user was edited
user = get_user_model().objects.get(pk=self.test_user.pk)
self.assertEqual(user.first_name, 'Edited')
# Check that the user is no longer superuser
self.assertEqual(user.is_superuser, False)
# Check that the user is no longer active
self.assertEqual(user.is_active, False)
def test_edit_and_make_superuser(self):
response = self.post({
'username': "testuser",
'email': "test@user.com",
'first_name': "Edited",
'last_name': "User",
'password1': "password",
'password2': "password",
'is_active': 'on',
'is_superuser': 'on',
})
# Should redirect back to index
self.assertRedirects(response, reverse('wagtailusers_users:index'))
# Check that the user was edited
user = get_user_model().objects.get(pk=self.test_user.pk)
# Check that the user is now superuser
self.assertEqual(user.is_superuser, True)
# Check that the user is now active
self.assertEqual(user.is_active, True)
def test_edit_self(self):
response = self.post({
'username': 'test@email.com',
'email': 'test@email.com',
'first_name': "Edited Myself",
'last_name': "User",
# 'password1': "password",
# 'password2': "password",
'is_active': 'on',
'is_superuser': 'on',
}, self.current_user.pk)
# Should redirect back to index
self.assertRedirects(response, reverse('wagtailusers_users:index'))
# Check that the user was edited
user = get_user_model().objects.get(pk=self.current_user.pk)
self.assertEqual(user.first_name, 'Edited Myself')
# Check that the user is still superuser
self.assertEqual(user.is_superuser, True)
# Check that the user is still active
self.assertEqual(user.is_active, True)
def test_cannot_demote_self(self):
"""
check that unsetting a user's own is_active or is_superuser flag has no effect
"""
response = self.post({
'username': 'test@email.com',
'email': 'test@email.com',
'first_name': "Edited Myself",
'last_name': "User",
# 'password1': "password",
# 'password2': "password",
# failing to submit is_active or is_superuser would unset those flags,
# if we didn't explicitly prevent that when editing self
# 'is_active': 'on',
# 'is_superuser': 'on',
}, self.current_user.pk)
# Should redirect back to index
self.assertRedirects(response, reverse('wagtailusers_users:index'))
# Check that the user was edited
user = get_user_model().objects.get(pk=self.current_user.pk)
self.assertEqual(user.first_name, 'Edited Myself')
# Check that the user is still superuser
self.assertEqual(user.is_superuser, True)
# Check that the user is still active
self.assertEqual(user.is_active, True)
@override_settings(
WAGTAIL_USER_EDIT_FORM='wagtail.wagtailusers.tests.CustomUserEditForm',
@ -406,6 +492,60 @@ class TestUserProfileCreation(TestCase, WagtailTestUtils):
self.assertEqual(UserProfile.objects.filter(user=self.test_user).count(), 1)
class TestUserEditViewForNonSuperuser(TestCase, WagtailTestUtils):
def setUp(self):
# create a user with edit permission
self.editor_user = get_user_model().objects.create_user(
username='editor',
email='editor@email.com',
password='password'
)
editors_group = Group.objects.create(name='User editors')
editors_group.permissions.add(Permission.objects.get(codename='access_admin'))
editors_group.permissions.add(Permission.objects.get(
content_type__app_label=AUTH_USER_APP_LABEL, codename=change_user_perm_codename
))
self.editor_user.groups.add(editors_group)
self.client.login(username='editor', password='password')
def test_user_cannot_escalate_privileges(self):
"""
Check that a non-superuser cannot edit their own is_active or is_superuser flag.
(note: this doesn't necessarily guard against other routes to escalating privileges, such
as creating a new user with is_superuser=True or adding oneself to a group with additional
privileges - the latter will be dealt with by #537)
"""
editors_group = Group.objects.get(name='User editors')
post_data = {
'username': "editor",
'email': "editor@email.com",
'first_name': "Escalating",
'last_name': "User",
'password1': "",
'password2': "",
'groups': [editors_group.id, ],
# These should not be possible without manipulating the form in the DOM:
'is_superuser': 'on',
'is_active': 'on',
}
response = self.client.post(
reverse('wagtailusers_users:edit', args=(self.editor_user.pk, )),
post_data)
# Should redirect back to index
self.assertRedirects(response, reverse('wagtailusers_users:index'))
user = get_user_model().objects.get(pk=self.editor_user.pk)
# check if user is still in the editors group
self.assertTrue(user.groups.filter(name='User editors').exists())
# check that non-permission-related edits went ahead
self.assertEqual(user.first_name, "Escalating")
# Check that the user did not escalate its is_superuser status
self.assertEqual(user.is_superuser, False)
class TestGroupIndexView(TestCase, WagtailTestUtils):
def setUp(self):
self.login()

Wyświetl plik

@ -143,9 +143,10 @@ def create(request):
def edit(request, user_id):
user = get_object_or_404(User, pk=user_id)
can_delete = user_can_delete_user(request.user, user)
editing_self = request.user == user
if request.method == 'POST':
form = get_user_edit_form()(request.POST, request.FILES, instance=user)
form = get_user_edit_form()(request.POST, request.FILES, instance=user, editing_self=editing_self)
if form.is_valid():
user = form.save()
messages.success(request, _("User '{0}' updated.").format(user), buttons=[
@ -155,7 +156,7 @@ def edit(request, user_id):
else:
messages.error(request, _("The user could not be saved due to errors."))
else:
form = get_user_edit_form()(instance=user)
form = get_user_edit_form()(instance=user, editing_self=editing_self)
return render(request, 'wagtailusers/users/edit.html', {
'user': user,