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):
|
def get_restricted_data(self, full_data, user):
|
||||||
"""
|
"""
|
||||||
Returns the restricted serialized data for the instance prepared for
|
Returns the restricted serialized data for the instance prepared for
|
||||||
the user. Removes non public comment fields for some unauthorized
|
the user. Removes motion if the user has not the permission to see
|
||||||
users.
|
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'):
|
required_permission_to_see = full_data.get('state_required_permission_to_see')
|
||||||
data = full_data
|
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:
|
else:
|
||||||
data = deepcopy(full_data)
|
data = None
|
||||||
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
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def get_projector_data(self, full_data):
|
def get_projector_data(self, full_data):
|
||||||
|
@ -42,7 +42,7 @@ class MotionManager(models.Manager):
|
|||||||
join and prefetch all related models.
|
join and prefetch all related models.
|
||||||
"""
|
"""
|
||||||
return (self.get_queryset()
|
return (self.get_queryset()
|
||||||
.select_related('active_version')
|
.select_related('active_version', 'state')
|
||||||
.prefetch_related(
|
.prefetch_related(
|
||||||
'versions',
|
'versions',
|
||||||
'agenda_items',
|
'agenda_items',
|
||||||
@ -604,61 +604,6 @@ class Motion(RESTModelMixin, models.Model):
|
|||||||
"""
|
"""
|
||||||
return self.agenda_item.pk
|
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):
|
def write_log(self, message_list, person=None, skip_autoupdate=False):
|
||||||
"""
|
"""
|
||||||
Write a log message.
|
Write a log message.
|
||||||
|
@ -267,6 +267,7 @@ class MotionSerializer(ModelSerializer):
|
|||||||
log_messages = MotionLogSerializer(many=True, read_only=True)
|
log_messages = MotionLogSerializer(many=True, read_only=True)
|
||||||
polls = MotionPollSerializer(many=True, read_only=True)
|
polls = MotionPollSerializer(many=True, read_only=True)
|
||||||
reason = CharField(allow_blank=True, required=False, write_only=True)
|
reason = CharField(allow_blank=True, required=False, write_only=True)
|
||||||
|
state_required_permission_to_see = SerializerMethodField()
|
||||||
text = CharField(write_only=True)
|
text = CharField(write_only=True)
|
||||||
title = CharField(max_length=255, write_only=True)
|
title = CharField(max_length=255, write_only=True)
|
||||||
versions = MotionVersionSerializer(many=True, read_only=True)
|
versions = MotionVersionSerializer(many=True, read_only=True)
|
||||||
@ -294,6 +295,7 @@ class MotionSerializer(ModelSerializer):
|
|||||||
'supporters',
|
'supporters',
|
||||||
'comments',
|
'comments',
|
||||||
'state',
|
'state',
|
||||||
|
'state_required_permission_to_see',
|
||||||
'workflow_id',
|
'workflow_id',
|
||||||
'recommendation',
|
'recommendation',
|
||||||
'tags',
|
'tags',
|
||||||
@ -366,3 +368,13 @@ class MotionSerializer(ModelSerializer):
|
|||||||
attr.add(*validated_data[key])
|
attr.add(*validated_data[key])
|
||||||
|
|
||||||
return motion
|
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
|
* - reset_state
|
||||||
* - change_recommendation
|
* - change_recommendation
|
||||||
*
|
*
|
||||||
* NOTE: If you update this function please also update the
|
* NOTE: If you update this function please think about
|
||||||
* 'get_allowed_actions' function on server side in motions/models.py.
|
* server permissions, see motions/views.py.
|
||||||
*/
|
*/
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case 'see':
|
case 'see':
|
||||||
@ -435,7 +435,7 @@ angular.module('OpenSlidesApp.motions', [
|
|||||||
return (
|
return (
|
||||||
operator.hasPerms('motions.can_manage') ||
|
operator.hasPerms('motions.can_manage') ||
|
||||||
(
|
(
|
||||||
($.inArray(operator.user, this.submitters) != -1) &&
|
(_.indexOf(this.submitters, operator.user) !== -1) &&
|
||||||
this.state.allow_submitter_edit
|
this.state.allow_submitter_edit
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -453,14 +453,11 @@ angular.module('OpenSlidesApp.motions', [
|
|||||||
operator.hasPerms('motions.can_support') &&
|
operator.hasPerms('motions.can_support') &&
|
||||||
this.state.allow_support &&
|
this.state.allow_support &&
|
||||||
Config.get('motions_min_supporters').value > 0 &&
|
Config.get('motions_min_supporters').value > 0 &&
|
||||||
($.inArray(operator.user, this.submitters) == -1) &&
|
(_.indexOf(this.submitters, operator.user) === -1) &&
|
||||||
($.inArray(operator.user, this.supporters) == -1)
|
(_.indexOf(this.supporters, operator.user) === -1)
|
||||||
);
|
);
|
||||||
case 'unsupport':
|
case 'unsupport':
|
||||||
return (
|
return this.state.allow_support && _.indexOf(this.supporters, operator.user) !== -1;
|
||||||
this.state.allow_support &&
|
|
||||||
($.inArray(operator.user, this.supporters) != -1)
|
|
||||||
);
|
|
||||||
case 'change_state':
|
case 'change_state':
|
||||||
return operator.hasPerms('motions.can_manage');
|
return operator.hasPerms('motions.can_manage');
|
||||||
case 'reset_state':
|
case 'reset_state':
|
||||||
|
@ -116,14 +116,16 @@ class MotionViewSet(ModelViewSet):
|
|||||||
|
|
||||||
Checks also whether the requesting user can update the motion. He
|
Checks also whether the requesting user can update the motion. He
|
||||||
needs at least the permissions 'motions.can_see' (see
|
needs at least the permissions 'motions.can_see' (see
|
||||||
self.check_view_permissions()). Also the instance method
|
self.check_view_permissions()). Also check manage permission or
|
||||||
get_allowed_actions() is evaluated.
|
submitter and state.
|
||||||
"""
|
"""
|
||||||
# Get motion.
|
# Get motion.
|
||||||
motion = self.get_object()
|
motion = self.get_object()
|
||||||
|
|
||||||
# Check permissions.
|
# 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)
|
self.permission_denied(request)
|
||||||
|
|
||||||
# Check permission to send only some data.
|
# Check permission to send only some data.
|
||||||
@ -215,12 +217,14 @@ class MotionViewSet(ModelViewSet):
|
|||||||
"""
|
"""
|
||||||
# Retrieve motion and allowed actions.
|
# Retrieve motion and allowed actions.
|
||||||
motion = self.get_object()
|
motion = self.get_object()
|
||||||
allowed_actions = motion.get_allowed_actions(request.user)
|
|
||||||
|
|
||||||
# Support or unsupport motion.
|
# Support or unsupport motion.
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
# Support motion.
|
# 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.')})
|
raise ValidationError({'detail': _('You can not support this motion.')})
|
||||||
motion.supporters.add(request.user)
|
motion.supporters.add(request.user)
|
||||||
motion.write_log([ugettext_noop('Motion supported')], request.user)
|
motion.write_log([ugettext_noop('Motion supported')], request.user)
|
||||||
@ -228,7 +232,7 @@ class MotionViewSet(ModelViewSet):
|
|||||||
else:
|
else:
|
||||||
# Unsupport motion.
|
# Unsupport motion.
|
||||||
# request.method == 'DELETE'
|
# 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.')})
|
raise ValidationError({'detail': _('You can not unsupport this motion.')})
|
||||||
motion.supporters.remove(request.user)
|
motion.supporters.remove(request.user)
|
||||||
motion.write_log([ugettext_noop('Motion unsupported')], 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.
|
View to create a poll. It is a POST request without any data.
|
||||||
"""
|
"""
|
||||||
motion = self.get_object()
|
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:
|
try:
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
motion.create_poll()
|
motion.create_poll()
|
||||||
|
@ -298,6 +298,37 @@ class RetrieveMotion(TestCase):
|
|||||||
with self.assertNumQueries(16):
|
with self.assertNumQueries(16):
|
||||||
self.client.get(reverse('motion-detail', args=[self.motion.pk]))
|
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):
|
class UpdateMotion(TestCase):
|
||||||
"""
|
"""
|
||||||
|
@ -73,8 +73,6 @@ class ModelTest(TestCase):
|
|||||||
self.motion.state = State.objects.get(pk=6)
|
self.motion.state = State.objects.get(pk=6)
|
||||||
self.assertEqual(self.motion.state.name, 'permitted')
|
self.assertEqual(self.motion.state.name, 'permitted')
|
||||||
self.assertEqual(self.motion.state.get_action_word(), 'Permit')
|
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):
|
def test_new_states_or_workflows(self):
|
||||||
workflow_1 = Workflow.objects.create(name='W1')
|
workflow_1 = Workflow.objects.create(name='W1')
|
||||||
|
Loading…
Reference in New Issue
Block a user