Merge pull request #2755 from normanjaeckel/StatePermission

Fixed state flag required permission to see.
This commit is contained in:
Emanuel Schütze 2016-12-12 13:49:07 +01:00 committed by GitHub
commit 2bf1b3d827
7 changed files with 82 additions and 85 deletions

View File

@ -25,9 +25,15 @@ class MotionAccessPermissions(BaseAccessPermissions):
def get_restricted_data(self, full_data, user):
"""
Returns the restricted serialized data for the instance prepared for
the user. Removes non public comment fields for some unauthorized
users.
the user. Removes motion if the user has not the permission to see
the motion in this state. Removes non public comment fields for
some unauthorized users.
"""
required_permission_to_see = full_data.get('state_required_permission_to_see')
if (not required_permission_to_see or
user.has_perm(required_permission_to_see) or
user.has_perm('motions.can_manage') or
user.pk in full_data.get('submitters_id', [])):
if user.has_perm('motions.can_see_and_manage_comments') or not full_data.get('comments'):
data = full_data
else:
@ -39,6 +45,8 @@ class MotionAccessPermissions(BaseAccessPermissions):
except IndexError:
# No data in range. Just do nothing.
pass
else:
data = None
return data
def get_projector_data(self, full_data):

View File

@ -42,7 +42,7 @@ class MotionManager(models.Manager):
join and prefetch all related models.
"""
return (self.get_queryset()
.select_related('active_version')
.select_related('active_version', 'state')
.prefetch_related(
'versions',
'agenda_items',
@ -604,61 +604,6 @@ class Motion(RESTModelMixin, models.Model):
"""
return self.agenda_item.pk
def get_allowed_actions(self, person):
"""
Return a dictonary with all allowed actions for a specific person.
The dictonary contains the following actions.
* see
* update / edit
* delete
* create_poll
* support
* unsupport
* change_state
* reset_state
* change_recommendation
NOTE: If you update this function please also update the
'isAllowed' function on client side in motions/site.js.
"""
# TODO: Remove this method and implement these things in the views.
actions = {
'see': (person.has_perm('motions.can_see') and
(not self.state.required_permission_to_see or
person.has_perm(self.state.required_permission_to_see) or
self.is_submitter(person))),
'update': (person.has_perm('motions.can_manage') or
(self.is_submitter(person) and
self.state.allow_submitter_edit)),
'delete': person.has_perm('motions.can_manage'),
'create_poll': (person.has_perm('motions.can_manage') and
self.state.allow_create_poll),
'support': (self.state.allow_support and
config['motions_min_supporters'] > 0 and
not self.is_submitter(person) and
not self.is_supporter(person)),
'unsupport': (self.state.allow_support and
self.is_supporter(person)),
'change_state': person.has_perm('motions.can_manage'),
'reset_state': person.has_perm('motions.can_manage'),
'change_recommendation': person.has_perm('motions.can_manage'),
}
actions['edit'] = actions['update']
return actions
def write_log(self, message_list, person=None, skip_autoupdate=False):
"""
Write a log message.

View File

@ -267,6 +267,7 @@ class MotionSerializer(ModelSerializer):
log_messages = MotionLogSerializer(many=True, read_only=True)
polls = MotionPollSerializer(many=True, read_only=True)
reason = CharField(allow_blank=True, required=False, write_only=True)
state_required_permission_to_see = SerializerMethodField()
text = CharField(write_only=True)
title = CharField(max_length=255, write_only=True)
versions = MotionVersionSerializer(many=True, read_only=True)
@ -294,6 +295,7 @@ class MotionSerializer(ModelSerializer):
'supporters',
'comments',
'state',
'state_required_permission_to_see',
'workflow_id',
'recommendation',
'tags',
@ -366,3 +368,13 @@ class MotionSerializer(ModelSerializer):
attr.add(*validated_data[key])
return motion
def get_state_required_permission_to_see(self, motion):
"""
Returns the permission (as string) that is required for non
managers that are not submitters to see this motion in this state.
Hint: Most states have and empty string here so this restriction is
disabled.
"""
return motion.state.required_permission_to_see

View File

@ -418,8 +418,8 @@ angular.module('OpenSlidesApp.motions', [
* - reset_state
* - change_recommendation
*
* NOTE: If you update this function please also update the
* 'get_allowed_actions' function on server side in motions/models.py.
* NOTE: If you update this function please think about
* server permissions, see motions/views.py.
*/
switch (action) {
case 'see':
@ -435,7 +435,7 @@ angular.module('OpenSlidesApp.motions', [
return (
operator.hasPerms('motions.can_manage') ||
(
($.inArray(operator.user, this.submitters) != -1) &&
(_.indexOf(this.submitters, operator.user) !== -1) &&
this.state.allow_submitter_edit
)
);
@ -453,14 +453,11 @@ angular.module('OpenSlidesApp.motions', [
operator.hasPerms('motions.can_support') &&
this.state.allow_support &&
Config.get('motions_min_supporters').value > 0 &&
($.inArray(operator.user, this.submitters) == -1) &&
($.inArray(operator.user, this.supporters) == -1)
(_.indexOf(this.submitters, operator.user) === -1) &&
(_.indexOf(this.supporters, operator.user) === -1)
);
case 'unsupport':
return (
this.state.allow_support &&
($.inArray(operator.user, this.supporters) != -1)
);
return this.state.allow_support && _.indexOf(this.supporters, operator.user) !== -1;
case 'change_state':
return operator.hasPerms('motions.can_manage');
case 'reset_state':

View File

@ -116,14 +116,16 @@ class MotionViewSet(ModelViewSet):
Checks also whether the requesting user can update the motion. He
needs at least the permissions 'motions.can_see' (see
self.check_view_permissions()). Also the instance method
get_allowed_actions() is evaluated.
self.check_view_permissions()). Also check manage permission or
submitter and state.
"""
# Get motion.
motion = self.get_object()
# Check permissions.
if not motion.get_allowed_actions(request.user)['update']:
if (not request.user.has_perm('motions.can_manage') and
not (motion.is_submitter(request.user) and
motion.state.allow_submitter_edit)):
self.permission_denied(request)
# Check permission to send only some data.
@ -215,12 +217,14 @@ class MotionViewSet(ModelViewSet):
"""
# Retrieve motion and allowed actions.
motion = self.get_object()
allowed_actions = motion.get_allowed_actions(request.user)
# Support or unsupport motion.
if request.method == 'POST':
# Support motion.
if not allowed_actions['support']:
if not (motion.state.allow_support and
config['motions_min_supporters'] > 0 and
not motion.is_submitter(request.user) and
not motion.is_supporter(request.user)):
raise ValidationError({'detail': _('You can not support this motion.')})
motion.supporters.add(request.user)
motion.write_log([ugettext_noop('Motion supported')], request.user)
@ -228,7 +232,7 @@ class MotionViewSet(ModelViewSet):
else:
# Unsupport motion.
# request.method == 'DELETE'
if not allowed_actions['unsupport']:
if not motion.state.allow_support or not motion.is_supporter(request.user):
raise ValidationError({'detail': _('You can not unsupport this motion.')})
motion.supporters.remove(request.user)
motion.write_log([ugettext_noop('Motion unsupported')], request.user)
@ -320,6 +324,8 @@ class MotionViewSet(ModelViewSet):
View to create a poll. It is a POST request without any data.
"""
motion = self.get_object()
if not motion.state.allow_create_poll:
raise ValidationError({'detail': 'You can not create a poll in this motion state.'})
try:
with transaction.atomic():
motion.create_poll()

View File

@ -298,6 +298,37 @@ class RetrieveMotion(TestCase):
with self.assertNumQueries(16):
self.client.get(reverse('motion-detail', args=[self.motion.pk]))
def test__guest_state_with_required_permission_to_see(self):
config['general_system_enable_anonymous'] = True
guest_client = APIClient()
state = self.motion.state
state.required_permission_to_see = 'permission_that_the_user_does_not_have_leeceiz9hi7iuta4ahY2'
state.save()
response = guest_client.get(reverse('motion-detail', args=[self.motion.pk]))
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
def test_admin_state_with_required_permission_to_see(self):
state = self.motion.state
state.required_permission_to_see = 'permission_that_the_user_does_not_have_coo1Iewu8Eing2xahfoo'
state.save()
response = self.client.get(reverse('motion-detail', args=[self.motion.pk]))
self.assertEqual(response.status_code, status.HTTP_200_OK)
def test_submitter_state_with_required_permission_to_see(self):
state = self.motion.state
state.required_permission_to_see = 'permission_that_the_user_does_not_have_eiW8af9caizoh1thaece'
state.save()
user = get_user_model().objects.create_user(
username='username_ohS2opheikaSa5theijo',
password='password_kau4eequaisheeBateef')
self.motion.submitters.add(user)
submitter_client = APIClient()
submitter_client.login(
username='username_ohS2opheikaSa5theijo',
password='password_kau4eequaisheeBateef')
response = submitter_client.get(reverse('motion-detail', args=[self.motion.pk]))
self.assertEqual(response.status_code, status.HTTP_200_OK)
class UpdateMotion(TestCase):
"""

View File

@ -73,8 +73,6 @@ class ModelTest(TestCase):
self.motion.state = State.objects.get(pk=6)
self.assertEqual(self.motion.state.name, 'permitted')
self.assertEqual(self.motion.state.get_action_word(), 'Permit')
self.assertFalse(self.motion.get_allowed_actions(self.test_user)['support'])
self.assertFalse(self.motion.get_allowed_actions(self.test_user)['unsupport'])
def test_new_states_or_workflows(self):
workflow_1 = Workflow.objects.create(name='W1')