Added new flag to motion state to control access for different users.
This commit is contained in:
parent
dc1e48329f
commit
c355326466
@ -38,6 +38,7 @@ Motions:
|
||||
recommendations [#4037, #4132].
|
||||
- Added timestampes for motions [#4134].
|
||||
- New config option to set reason as required field [#4232]
|
||||
- Added new flag to motion state to control access for different users [#4235].
|
||||
|
||||
User:
|
||||
- Added new admin group which grants all permissions. Users of existing group
|
||||
|
@ -37,13 +37,17 @@ class MotionAccessPermissions(BaseAccessPermissions):
|
||||
is_submitter = False
|
||||
|
||||
# Check see permission for this motion.
|
||||
required_permission_to_see = full["state_required_permission_to_see"]
|
||||
permission = (
|
||||
not required_permission_to_see
|
||||
or await async_has_perm(user_id, required_permission_to_see)
|
||||
or await async_has_perm(user_id, "motions.can_manage")
|
||||
or is_submitter
|
||||
)
|
||||
from .models import State
|
||||
|
||||
if await async_has_perm(user_id, "motions.can_manage"):
|
||||
level = State.MANAGERS_ONLY
|
||||
elif await async_has_perm(user_id, "motions.can_manage_metadata"):
|
||||
level = State.EXTENDED_MANAGERS
|
||||
elif is_submitter:
|
||||
level = State.EXTENDED_MANAGERS_AND_SUBMITTER
|
||||
else:
|
||||
level = State.ALL
|
||||
permission = level >= full["state_access_level"]
|
||||
|
||||
# Parse single motion.
|
||||
if permission:
|
||||
@ -57,7 +61,6 @@ class MotionAccessPermissions(BaseAccessPermissions):
|
||||
data.append(full_copy)
|
||||
else:
|
||||
data = []
|
||||
|
||||
return data
|
||||
|
||||
|
||||
|
43
openslides/motions/migrations/0021_state_access_level.py
Normal file
43
openslides/motions/migrations/0021_state_access_level.py
Normal file
@ -0,0 +1,43 @@
|
||||
# Generated by Django 2.1.5 on 2019-02-01 11:58
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
def transform_required_permission_to_see_field(apps, schema_editor):
|
||||
"""
|
||||
Sets new access_level of states to EXTENDED_MANAGERS_AND_SUBMITTER
|
||||
if required_permission_to_see is given
|
||||
"""
|
||||
# We get the model from the versioned app registry;
|
||||
# if we directly import it, it will be the wrong version.
|
||||
State = apps.get_model("motions", "State")
|
||||
for state in State.objects.all():
|
||||
if state.required_permission_to_see:
|
||||
state.access_level = 1
|
||||
state.save(skip_autoupdate=True)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [("motions", "0020_auto_20190119_1425")]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="state",
|
||||
name="access_level",
|
||||
field=models.IntegerField(
|
||||
choices=[
|
||||
(0, "All users with permission to see motions"),
|
||||
(
|
||||
1,
|
||||
"Submitters, managers and users with permission to manage metadata",
|
||||
),
|
||||
(2, "Only managers and users with permission to manage metadata"),
|
||||
(3, "Only managers"),
|
||||
],
|
||||
default=0,
|
||||
),
|
||||
),
|
||||
migrations.RunPython(transform_required_permission_to_see_field),
|
||||
migrations.RemoveField(model_name="state", name="required_permission_to_see"),
|
||||
]
|
@ -1054,6 +1054,24 @@ class State(RESTModelMixin, models.Model):
|
||||
state.
|
||||
"""
|
||||
|
||||
ALL = 0
|
||||
EXTENDED_MANAGERS_AND_SUBMITTER = 1
|
||||
EXTENDED_MANAGERS = 2
|
||||
MANAGERS_ONLY = 3
|
||||
|
||||
ACCESS_LEVELS = (
|
||||
(ALL, "All users with permission to see motions"),
|
||||
(
|
||||
EXTENDED_MANAGERS_AND_SUBMITTER,
|
||||
"Submitters, managers and users with permission to manage metadata",
|
||||
),
|
||||
(
|
||||
EXTENDED_MANAGERS,
|
||||
"Only managers and users with permission to manage metadata",
|
||||
),
|
||||
(MANAGERS_ONLY, "Only managers"),
|
||||
)
|
||||
|
||||
name = models.CharField(max_length=255)
|
||||
"""A string representing the state."""
|
||||
|
||||
@ -1075,14 +1093,10 @@ class State(RESTModelMixin, models.Model):
|
||||
Default value is 'primary' (blue).
|
||||
"""
|
||||
|
||||
required_permission_to_see = models.CharField(max_length=255, blank=True)
|
||||
access_level = models.IntegerField(choices=ACCESS_LEVELS, default=0)
|
||||
"""
|
||||
A permission string. If not empty, the user has to have this permission to
|
||||
see a motion in this state.
|
||||
|
||||
To use this feature change the database entry of a state object and add
|
||||
your favourite permission string. You can do this e. g. by editing the
|
||||
definitions in create_builtin_workflows() in openslides/motions/signals.py.
|
||||
Defines which users may see motions in this state e. g. only managers,
|
||||
users with permission to manage metadata and submitters.
|
||||
"""
|
||||
|
||||
allow_support = models.BooleanField(default=False)
|
||||
|
@ -102,7 +102,7 @@ class StateSerializer(ModelSerializer):
|
||||
"name",
|
||||
"recommendation_label",
|
||||
"css_class",
|
||||
"required_permission_to_see",
|
||||
"access_level",
|
||||
"allow_support",
|
||||
"allow_create_poll",
|
||||
"allow_submitter_edit",
|
||||
@ -389,7 +389,7 @@ class MotionSerializer(ModelSerializer):
|
||||
polls = MotionPollSerializer(many=True, read_only=True)
|
||||
modified_final_version = CharField(allow_blank=True, required=False)
|
||||
reason = CharField(allow_blank=True, required=False)
|
||||
state_required_permission_to_see = SerializerMethodField()
|
||||
state_access_level = SerializerMethodField()
|
||||
text = CharField(allow_blank=True)
|
||||
title = CharField(max_length=255)
|
||||
amendment_paragraphs = AmendmentParagraphsJSONSerializerField(required=False)
|
||||
@ -424,7 +424,7 @@ class MotionSerializer(ModelSerializer):
|
||||
"supporters",
|
||||
"state",
|
||||
"state_extension",
|
||||
"state_required_permission_to_see",
|
||||
"state_access_level",
|
||||
"statute_paragraph",
|
||||
"workflow_id",
|
||||
"recommendation",
|
||||
@ -531,12 +531,9 @@ class MotionSerializer(ModelSerializer):
|
||||
|
||||
return result
|
||||
|
||||
def get_state_required_permission_to_see(self, motion):
|
||||
def get_state_access_level(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.
|
||||
Returns the access level of this state. The default is 0 so everybody
|
||||
with permission to see motions can see this motion.
|
||||
"""
|
||||
return motion.state.required_permission_to_see
|
||||
return motion.state.access_level
|
||||
|
@ -479,13 +479,11 @@ class RetrieveMotion(TestCase):
|
||||
username=f"user_{index}", password="password"
|
||||
)
|
||||
|
||||
def test_guest_state_with_required_permission_to_see(self):
|
||||
def test_guest_state_with_access_level(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.access_level = State.MANAGERS_ONLY
|
||||
state.save()
|
||||
# The cache has to be cleared, see:
|
||||
# https://github.com/OpenSlides/OpenSlides/issues/3396
|
||||
@ -494,20 +492,17 @@ class RetrieveMotion(TestCase):
|
||||
response = guest_client.get(reverse("motion-detail", args=[self.motion.pk]))
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
def test_admin_state_with_required_permission_to_see(self):
|
||||
def test_admin_state_with_access_level(self):
|
||||
state = self.motion.state
|
||||
state.required_permission_to_see = (
|
||||
"permission_that_the_user_does_not_have_coo1Iewu8Eing2xahfoo"
|
||||
)
|
||||
state.access_level = State.MANAGERS_ONLY
|
||||
|
||||
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):
|
||||
def test_submitter_state_with_access_level(self):
|
||||
state = self.motion.state
|
||||
state.required_permission_to_see = (
|
||||
"permission_that_the_user_does_not_have_eiW8af9caizoh1thaece"
|
||||
)
|
||||
state.access_level = State.EXTENDED_MANAGERS_AND_SUBMITTER
|
||||
state.save()
|
||||
user = get_user_model().objects.create_user(
|
||||
username="username_ohS2opheikaSa5theijo",
|
||||
|
@ -29,7 +29,7 @@ def all_data():
|
||||
"supporters_id": [],
|
||||
"state_id": 1,
|
||||
"state_extension": None,
|
||||
"state_required_permission_to_see": "",
|
||||
"state_access_level": 0,
|
||||
"statute_paragraph_id": None,
|
||||
"workflow_id": 1,
|
||||
"recommendation_id": None,
|
||||
@ -86,7 +86,7 @@ def all_data():
|
||||
"name": "submitted",
|
||||
"recommendation_label": None,
|
||||
"css_class": "primary",
|
||||
"required_permission_to_see": "",
|
||||
"access_level": 0,
|
||||
"allow_support": True,
|
||||
"allow_create_poll": True,
|
||||
"allow_submitter_edit": True,
|
||||
@ -102,7 +102,7 @@ def all_data():
|
||||
"name": "accepted",
|
||||
"recommendation_label": "Acceptance",
|
||||
"css_class": "success",
|
||||
"required_permission_to_see": "",
|
||||
"access_level": 0,
|
||||
"allow_support": False,
|
||||
"allow_create_poll": False,
|
||||
"allow_submitter_edit": False,
|
||||
@ -118,7 +118,7 @@ def all_data():
|
||||
"name": "rejected",
|
||||
"recommendation_label": "Rejection",
|
||||
"css_class": "danger",
|
||||
"required_permission_to_see": "",
|
||||
"access_level": 0,
|
||||
"allow_support": False,
|
||||
"allow_create_poll": False,
|
||||
"allow_submitter_edit": False,
|
||||
@ -134,7 +134,7 @@ def all_data():
|
||||
"name": "not decided",
|
||||
"recommendation_label": "No decision",
|
||||
"css_class": "default",
|
||||
"required_permission_to_see": "",
|
||||
"access_level": 0,
|
||||
"allow_support": False,
|
||||
"allow_create_poll": False,
|
||||
"allow_submitter_edit": False,
|
||||
|
Loading…
Reference in New Issue
Block a user