Allow users to reject their own blocklist submission (#13622)

* Allow users to reject their own blocklist submission

* _signoff to _approve

* add to test to verify Reject button is there for own submissions
This commit is contained in:
Andrew Williamson 2020-03-03 12:34:31 +00:00 коммит произвёл GitHub
Родитель f797caaaab
Коммит 9cff74924c
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
3 изменённых файлов: 96 добавлений и 20 удалений

Просмотреть файл

@ -144,17 +144,18 @@ class BlockSubmissionAdmin(admin.ModelAdmin):
we need to return true if the user has sign-off permission instead.
We can override that permissive behavior with `strict=True`."""
change_perm = super().has_change_permission(request, obj=obj)
signoff_perm = self.has_signoff_permission(request, obj=obj)
either_perm = change_perm or (signoff_perm and not strict)
approve_perm = self.has_signoff_approve_permission(request, obj=obj)
either_perm = change_perm or (approve_perm and not strict)
return either_perm and (not obj or self.is_pending_signoff(obj))
def has_view_permission(self, request, obj=None):
return (
super().has_view_permission(request, obj) or
self.has_signoff_permission(request, obj))
self.has_signoff_approve_permission(request, obj) or
self.has_signoff_reject_permission(request, obj))
def has_signoff_permission(self, request, obj=None):
""" This controls whether the sign-off approve and reject actions are
def has_signoff_approve_permission(self, request, obj=None):
""" This controls whether the sign-off approve action is
available on the change form. `BlockSubmission.can_user_signoff`
confirms the current user, who will signoff, is different from the user
who submitted the guids (unless settings.DEBUG is True when the check
@ -164,6 +165,16 @@ class BlockSubmissionAdmin(admin.ModelAdmin):
has_perm = request.user.has_perm("%s.%s" % (opts.app_label, codename))
return has_perm and (not obj or obj.can_user_signoff(request.user))
def has_signoff_reject_permission(self, request, obj=None):
""" This controls whether the sign-off reject action is
available on the change form. Users can reject their own submission
regardless."""
opts = self.opts
codename = auth.get_permission_codename('signoff', opts)
has_perm = request.user.has_perm("%s.%s" % (opts.app_label, codename))
is_own_submission = obj and obj.updated_by == request.user
return has_perm or is_own_submission
def get_fieldsets(self, request, obj):
input_guids = (
'Input Guids', {
@ -356,8 +367,10 @@ class BlockSubmissionAdmin(admin.ModelAdmin):
def change_view(self, request, object_id, form_url='', extra_context=None):
extra_context = extra_context or {}
obj = self.model.objects.filter(id=object_id).latest()
extra_context['has_signoff_permission'] = self.has_signoff_permission(
request, obj)
extra_context['has_signoff_approve_permission'] = (
self.has_signoff_approve_permission(request, obj))
extra_context['has_signoff_reject_permission'] = (
self.has_signoff_reject_permission(request, obj))
extra_context['can_change_object'] = (
obj.action == BlockSubmission.ACTION_ADDCHANGE and
self.has_change_permission(request, obj, strict=True))
@ -387,15 +400,16 @@ class BlockSubmissionAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
if change and self.is_pending_signoff(obj):
is_signoff = '_signoff' in request.POST
is_approve = '_approve' in request.POST
is_reject = '_reject' in request.POST
if ((is_signoff or is_reject) and
not self.has_signoff_permission(request, obj)):
raise PermissionDenied
if is_signoff:
if is_approve:
if not self.has_signoff_approve_permission(request, obj):
raise PermissionDenied
obj.signoff_state = BlockSubmission.SIGNOFF_APPROVED
obj.signoff_by = request.user
elif is_reject:
if not self.has_signoff_reject_permission(request, obj):
raise PermissionDenied
obj.signoff_state = BlockSubmission.SIGNOFF_REJECTED
elif not self.has_change_permission(request, obj, strict=True):
# users without full change permission should only do signoff
@ -411,9 +425,9 @@ class BlockSubmissionAdmin(admin.ModelAdmin):
def log_change(self, request, obj, message):
log_entry = None
is_signoff = '_signoff' in request.POST
is_approve = '_approve' in request.POST
is_reject = '_reject' in request.POST
if is_signoff:
if is_approve:
signoff_msg = 'Sign-off Approval'
elif is_reject:
signoff_msg = 'Sign-off Rejection'

Просмотреть файл

@ -10,9 +10,11 @@
{% if can_change_object and change %}
<input type="submit" value="{% trans 'Update' %}" class="default" name="_save">
{% endif %}
{% if has_signoff_permission and is_pending_signoff %}
{% if has_signoff_reject_permission and is_pending_signoff %}
<input type="submit" value="{% trans 'Reject Submission' %}" name="_reject">
<input type="submit" value="{% trans 'Approve Submission' %}" name="_signoff">
{% endif %}
{% if has_signoff_approve_permission and is_pending_signoff %}
<input type="submit" value="{% trans 'Approve Submission' %}" name="_approve">
{% endif %}
</div>
{% endblock %}

Просмотреть файл

@ -908,7 +908,7 @@ class TestBlockSubmissionAdmin(TestCase):
'max_version': '99', # should be ignored
'url': 'new.url', # should be ignored
'reason': 'a reason', # should be ignored
'_signoff': 'Approve Submission',
'_approve': 'Approve Submission',
},
follow=True)
assert response.status_code == 200
@ -1047,7 +1047,7 @@ class TestBlockSubmissionAdmin(TestCase):
'max_version': '99', # should be ignored
'url': 'new.url', # could be updated with this permission
'reason': 'a reason', # could be updated with this permission
'_signoff': 'Approve Submission',
'_approve': 'Approve Submission',
},
follow=True)
assert response.status_code == 403
@ -1059,8 +1059,68 @@ class TestBlockSubmissionAdmin(TestCase):
assert mbs.url != 'new.url'
assert mbs.reason != 'a reason'
def test_cannot_reject_with_only_block_create_permission(self):
pass
def test_can_only_reject_your_own_with_only_block_create_permission(self):
addon = addon_factory(guid='guid@', name='Danger Danger')
submission = BlockSubmission.objects.create(
input_guids='guid@\ninvalid@',
updated_by=user_factory())
assert submission.to_block == [
{'guid': 'guid@',
'id': None,
'average_daily_users': addon.average_daily_users}]
user = user_factory()
self.grant_permission(user, 'Admin:Tools')
self.grant_permission(user, 'Blocklist:Create')
self.client.login(email=user.email)
change_url = reverse(
'admin:blocklist_blocksubmission_change', args=(submission.id,))
response = self.client.post(
change_url, {
'input_guids': 'guid2@\nfoo@baa', # should be ignored
'min_version': '1', # should be ignored
'max_version': '99', # should be ignored
'url': 'new.url', # could be updated with this permission
'reason': 'a reason', # could be updated with this permission
'_reject': 'Reject Submission',
},
follow=True)
assert response.status_code == 403
submission = submission.reload()
# It wasn't signed off
assert not submission.signoff_by
assert submission.signoff_state == BlockSubmission.SIGNOFF_PENDING
# And the details weren't updated either
assert submission.url != 'new.url'
assert submission.reason != 'a reason'
# except if it's your own submission
submission.update(updated_by=user)
response = self.client.get(change_url, follow=True)
assert response.status_code == 200
doc = pq(response.content)
buttons = doc('.submit-row input')
assert buttons[0].attrib['value'] == 'Update'
assert buttons[1].attrib['value'] == 'Reject Submission'
assert len(buttons) == 2
assert b'Approve Submission' not in response.content
response = self.client.post(
change_url, {
'input_guids': 'guid2@\nfoo@baa', # should be ignored
'min_version': '1', # should be ignored
'max_version': '99', # should be ignored
'url': 'new.url', # could be updated with this permission
'reason': 'a reason', # could be updated with this permission
'_reject': 'Reject Submission',
},
follow=True)
assert response.status_code == 200
submission = submission.reload()
assert submission.signoff_state == BlockSubmission.SIGNOFF_REJECTED
assert not submission.signoff_by
assert submission.url == 'new.url'
assert submission.reason == 'a reason'
def test_signed_off_view(self):
addon = addon_factory(guid='guid@', name='Danger Danger')