Merge pull request #2755 from normanjaeckel/StatePermission
Fixed state flag required permission to see.
This commit is contained in:
commit
2bf1b3d827
@ -25,20 +25,28 @@ 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.
|
||||
"""
|
||||
if user.has_perm('motions.can_see_and_manage_comments') or not full_data.get('comments'):
|
||||
data = full_data
|
||||
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:
|
||||
data = deepcopy(full_data)
|
||||
for i, field in enumerate(config['motions_comments']):
|
||||
if not field.get('public'):
|
||||
try:
|
||||
data['comments'][i] = None
|
||||
except IndexError:
|
||||
# No data in range. Just do nothing.
|
||||
pass
|
||||
else:
|
||||
data = deepcopy(full_data)
|
||||
for i, field in enumerate(config['motions_comments']):
|
||||
if not field.get('public'):
|
||||
try:
|
||||
data['comments'][i] = None
|
||||
except IndexError:
|
||||
# No data in range. Just do nothing.
|
||||
pass
|
||||
data = None
|
||||
return data
|
||||
|
||||
def get_projector_data(self, full_data):
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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':
|
||||
|
@ -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()
|
||||
|
@ -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):
|
||||
"""
|
||||
|
@ -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')
|
||||
|
Loading…
Reference in New Issue
Block a user