User without permission to see users can now see some required users.
These are - agenda item speakers, - motion submitters and supporters, - assignment candidates, - mediafile uploader and - chat message users but only if the user has respective permissions. Fixed #3002.
This commit is contained in:
parent
152585a73f
commit
c4ec26c4c0
@ -19,6 +19,12 @@ Core:
|
|||||||
- No reload on logoff. OpenSlides is now a full single page
|
- No reload on logoff. OpenSlides is now a full single page
|
||||||
application [#3172].
|
application [#3172].
|
||||||
|
|
||||||
|
Users:
|
||||||
|
- User without permission to see users can now see agenda item speakers,
|
||||||
|
motion submitters and supporters, assignment candidates, mediafile
|
||||||
|
uploader and chat message users if they have the respective
|
||||||
|
permissions [#3191].
|
||||||
|
|
||||||
|
|
||||||
Version 2.1.1 (2017-04-05)
|
Version 2.1.1 (2017-04-05)
|
||||||
==========================
|
==========================
|
||||||
|
@ -17,11 +17,12 @@ class AgendaAppConfig(AppConfig):
|
|||||||
# Import all required stuff.
|
# Import all required stuff.
|
||||||
from django.db.models.signals import pre_delete, post_save
|
from django.db.models.signals import pre_delete, post_save
|
||||||
from openslides.core.config import config
|
from openslides.core.config import config
|
||||||
from openslides.core.signals import permission_change
|
from openslides.core.signals import permission_change, user_data_required
|
||||||
from openslides.utils.rest_api import router
|
from openslides.utils.rest_api import router
|
||||||
from .config_variables import get_config_variables
|
from .config_variables import get_config_variables
|
||||||
from .signals import (
|
from .signals import (
|
||||||
get_permission_change_data,
|
get_permission_change_data,
|
||||||
|
is_user_data_required,
|
||||||
listen_to_related_object_post_delete,
|
listen_to_related_object_post_delete,
|
||||||
listen_to_related_object_post_save)
|
listen_to_related_object_post_save)
|
||||||
from .views import ItemViewSet
|
from .views import ItemViewSet
|
||||||
@ -39,6 +40,9 @@ class AgendaAppConfig(AppConfig):
|
|||||||
permission_change.connect(
|
permission_change.connect(
|
||||||
get_permission_change_data,
|
get_permission_change_data,
|
||||||
dispatch_uid='agenda_get_permission_change_data')
|
dispatch_uid='agenda_get_permission_change_data')
|
||||||
|
user_data_required.connect(
|
||||||
|
is_user_data_required,
|
||||||
|
dispatch_uid='agenda_is_user_data_required')
|
||||||
|
|
||||||
# Register viewsets.
|
# Register viewsets.
|
||||||
router.register(self.get_model('Item').get_collection_string(), ItemViewSet)
|
router.register(self.get_model('Item').get_collection_string(), ItemViewSet)
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
|
||||||
from openslides.utils.autoupdate import inform_changed_data
|
from ..utils.auth import has_perm
|
||||||
|
from ..utils.autoupdate import inform_changed_data
|
||||||
|
from ..utils.collection import Collection
|
||||||
from .models import Item
|
from .models import Item
|
||||||
|
|
||||||
|
|
||||||
@ -51,3 +52,22 @@ def get_permission_change_data(sender, permissions, **kwargs):
|
|||||||
and permission.codename in ('can_see', 'can_see_hidden_items')):
|
and permission.codename in ('can_see', 'can_see_hidden_items')):
|
||||||
yield from agenda_app.get_startup_elements()
|
yield from agenda_app.get_startup_elements()
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
|
def is_user_data_required(sender, request_user, user_data, **kwargs):
|
||||||
|
"""
|
||||||
|
Returns True if request user can see the agenda and user_data is required
|
||||||
|
to be displayed as speaker.
|
||||||
|
"""
|
||||||
|
result = False
|
||||||
|
if has_perm(request_user, 'agenda.can_see'):
|
||||||
|
for item_collection_element in Collection(Item.get_collection_string()).element_generator():
|
||||||
|
full_data = item_collection_element.get_full_data()
|
||||||
|
for speaker in full_data['speakers']:
|
||||||
|
if user_data['id'] == speaker['user_id']:
|
||||||
|
result = True
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
break
|
||||||
|
return result
|
||||||
|
@ -16,10 +16,10 @@ class AssignmentsAppConfig(AppConfig):
|
|||||||
|
|
||||||
# Import all required stuff.
|
# Import all required stuff.
|
||||||
from openslides.core.config import config
|
from openslides.core.config import config
|
||||||
from openslides.core.signals import permission_change
|
from openslides.core.signals import permission_change, user_data_required
|
||||||
from openslides.utils.rest_api import router
|
from openslides.utils.rest_api import router
|
||||||
from .config_variables import get_config_variables
|
from .config_variables import get_config_variables
|
||||||
from .signals import get_permission_change_data
|
from .signals import get_permission_change_data, is_user_data_required
|
||||||
from .views import AssignmentViewSet, AssignmentPollViewSet
|
from .views import AssignmentViewSet, AssignmentPollViewSet
|
||||||
|
|
||||||
# Define config variables
|
# Define config variables
|
||||||
@ -29,6 +29,9 @@ class AssignmentsAppConfig(AppConfig):
|
|||||||
permission_change.connect(
|
permission_change.connect(
|
||||||
get_permission_change_data,
|
get_permission_change_data,
|
||||||
dispatch_uid='assignments_get_permission_change_data')
|
dispatch_uid='assignments_get_permission_change_data')
|
||||||
|
user_data_required.connect(
|
||||||
|
is_user_data_required,
|
||||||
|
dispatch_uid='assignments_is_user_data_required')
|
||||||
|
|
||||||
# Register viewsets.
|
# Register viewsets.
|
||||||
router.register(self.get_model('Assignment').get_collection_string(), AssignmentViewSet)
|
router.register(self.get_model('Assignment').get_collection_string(), AssignmentViewSet)
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
|
|
||||||
|
from ..utils.auth import has_perm
|
||||||
|
from ..utils.collection import Collection
|
||||||
|
from .models import Assignment
|
||||||
|
|
||||||
|
|
||||||
def get_permission_change_data(sender, permissions=None, **kwargs):
|
def get_permission_change_data(sender, permissions=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
@ -10,3 +14,32 @@ def get_permission_change_data(sender, permissions=None, **kwargs):
|
|||||||
# There could be only one 'assignment.can_see' and then we want to return data.
|
# There could be only one 'assignment.can_see' and then we want to return data.
|
||||||
if permission.content_type.app_label == assignments_app.label and permission.codename == 'can_see':
|
if permission.content_type.app_label == assignments_app.label and permission.codename == 'can_see':
|
||||||
yield from assignments_app.get_startup_elements()
|
yield from assignments_app.get_startup_elements()
|
||||||
|
|
||||||
|
|
||||||
|
def is_user_data_required(sender, request_user, user_data, **kwargs):
|
||||||
|
"""
|
||||||
|
Returns True if request user can see assignments and user_data is required
|
||||||
|
to be displayed as candidates (including poll options).
|
||||||
|
"""
|
||||||
|
result = False
|
||||||
|
if has_perm(request_user, 'assignments.can_see'):
|
||||||
|
for assignment_collection_element in Collection(Assignment.get_collection_string()).element_generator():
|
||||||
|
full_data = assignment_collection_element.get_full_data()
|
||||||
|
for related_user in full_data['assignment_related_users']:
|
||||||
|
if user_data['id'] == related_user['user_id']:
|
||||||
|
result = True
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
for poll in full_data['polls']:
|
||||||
|
for option in poll['options']:
|
||||||
|
if user_data['id'] == option['candidate_id']:
|
||||||
|
result = True
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
break
|
||||||
|
break
|
||||||
|
return result
|
||||||
|
@ -23,7 +23,9 @@ class CoreAppConfig(AppConfig):
|
|||||||
from .signals import (
|
from .signals import (
|
||||||
delete_django_app_permissions,
|
delete_django_app_permissions,
|
||||||
get_permission_change_data,
|
get_permission_change_data,
|
||||||
permission_change)
|
is_user_data_required,
|
||||||
|
permission_change,
|
||||||
|
user_data_required)
|
||||||
from .views import (
|
from .views import (
|
||||||
ChatMessageViewSet,
|
ChatMessageViewSet,
|
||||||
ConfigViewSet,
|
ConfigViewSet,
|
||||||
@ -43,6 +45,9 @@ class CoreAppConfig(AppConfig):
|
|||||||
permission_change.connect(
|
permission_change.connect(
|
||||||
get_permission_change_data,
|
get_permission_change_data,
|
||||||
dispatch_uid='core_get_permission_change_data')
|
dispatch_uid='core_get_permission_change_data')
|
||||||
|
user_data_required.connect(
|
||||||
|
is_user_data_required,
|
||||||
|
dispatch_uid='core_is_user_data_required')
|
||||||
|
|
||||||
# Register viewsets.
|
# Register viewsets.
|
||||||
router.register(self.get_model('Projector').get_collection_string(), ProjectorViewSet)
|
router.register(self.get_model('Projector').get_collection_string(), ProjectorViewSet)
|
||||||
|
@ -4,7 +4,9 @@ from django.contrib.contenttypes.models import ContentType
|
|||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.dispatch import Signal
|
from django.dispatch import Signal
|
||||||
|
|
||||||
|
from ..utils.auth import has_perm
|
||||||
from ..utils.collection import Collection
|
from ..utils.collection import Collection
|
||||||
|
from .models import ChatMessage
|
||||||
|
|
||||||
# This signal is send when the migrate command is done. That means it is sent
|
# This signal is send when the migrate command is done. That means it is sent
|
||||||
# after post_migrate sending and creating all Permission objects. Don't use it
|
# after post_migrate sending and creating all Permission objects. Don't use it
|
||||||
@ -15,6 +17,12 @@ post_permission_creation = Signal()
|
|||||||
# permission). Connected receivers may yield Collections.
|
# permission). Connected receivers may yield Collections.
|
||||||
permission_change = Signal()
|
permission_change = Signal()
|
||||||
|
|
||||||
|
# This signal is sent if someone wants to see basic user data. Connected
|
||||||
|
# receivers may answer True if the user data is required for the request user
|
||||||
|
# (this can be anything that is allowd as argument for utils.auth.has_perm())
|
||||||
|
# e. g. as motion submitter or assignment candidate.
|
||||||
|
user_data_required = Signal(providing_args=['request_user', 'user_data'])
|
||||||
|
|
||||||
|
|
||||||
def delete_django_app_permissions(sender, **kwargs):
|
def delete_django_app_permissions(sender, **kwargs):
|
||||||
"""
|
"""
|
||||||
@ -42,3 +50,18 @@ def get_permission_change_data(sender, permissions, **kwargs):
|
|||||||
yield Collection(core_app.get_model('Countdown').get_collection_string())
|
yield Collection(core_app.get_model('Countdown').get_collection_string())
|
||||||
elif permission.codename == 'can_use_chat':
|
elif permission.codename == 'can_use_chat':
|
||||||
yield Collection(core_app.get_model('ChatMessage').get_collection_string())
|
yield Collection(core_app.get_model('ChatMessage').get_collection_string())
|
||||||
|
|
||||||
|
|
||||||
|
def is_user_data_required(sender, request_user, user_data, **kwargs):
|
||||||
|
"""
|
||||||
|
Returns True if request user can use chat and user_data is required
|
||||||
|
to be displayed as chatter.
|
||||||
|
"""
|
||||||
|
result = False
|
||||||
|
if has_perm(request_user, 'core.can_use_chat'):
|
||||||
|
for chat_message_collection_element in Collection(ChatMessage.get_collection_string()).element_generator():
|
||||||
|
full_data = chat_message_collection_element.get_full_data()
|
||||||
|
if user_data['id'] == full_data['user_id']:
|
||||||
|
result = True
|
||||||
|
break
|
||||||
|
return result
|
||||||
|
@ -15,15 +15,18 @@ class MediafilesAppConfig(AppConfig):
|
|||||||
from . import projector # noqa
|
from . import projector # noqa
|
||||||
|
|
||||||
# Import all required stuff.
|
# Import all required stuff.
|
||||||
from openslides.core.signals import permission_change
|
from openslides.core.signals import permission_change, user_data_required
|
||||||
from openslides.utils.rest_api import router
|
from openslides.utils.rest_api import router
|
||||||
from .signals import get_permission_change_data
|
from .signals import get_permission_change_data, is_user_data_required
|
||||||
from .views import MediafileViewSet
|
from .views import MediafileViewSet
|
||||||
|
|
||||||
# Connect signals.
|
# Connect signals.
|
||||||
permission_change.connect(
|
permission_change.connect(
|
||||||
get_permission_change_data,
|
get_permission_change_data,
|
||||||
dispatch_uid='mediafiles_get_permission_change_data')
|
dispatch_uid='mediafiles_get_permission_change_data')
|
||||||
|
user_data_required.connect(
|
||||||
|
is_user_data_required,
|
||||||
|
dispatch_uid='mediafiles_is_user_data_required')
|
||||||
|
|
||||||
# Register viewsets.
|
# Register viewsets.
|
||||||
router.register(self.get_model('Mediafile').get_collection_string(), MediafileViewSet)
|
router.register(self.get_model('Mediafile').get_collection_string(), MediafileViewSet)
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
|
|
||||||
|
from ..utils.auth import has_perm
|
||||||
|
from ..utils.collection import Collection
|
||||||
|
from .models import Mediafile
|
||||||
|
|
||||||
|
|
||||||
def get_permission_change_data(sender, permissions=None, **kwargs):
|
def get_permission_change_data(sender, permissions=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
@ -10,3 +14,18 @@ def get_permission_change_data(sender, permissions=None, **kwargs):
|
|||||||
# There could be only one 'mediafiles.can_see' and then we want to return data.
|
# There could be only one 'mediafiles.can_see' and then we want to return data.
|
||||||
if permission.content_type.app_label == mediafiles_app.label and permission.codename == 'can_see':
|
if permission.content_type.app_label == mediafiles_app.label and permission.codename == 'can_see':
|
||||||
yield from mediafiles_app.get_startup_elements()
|
yield from mediafiles_app.get_startup_elements()
|
||||||
|
|
||||||
|
|
||||||
|
def is_user_data_required(sender, request_user, user_data, **kwargs):
|
||||||
|
"""
|
||||||
|
Returns True if request user can see mediafiles and user_data is required
|
||||||
|
to be displayed as uploader.
|
||||||
|
"""
|
||||||
|
result = False
|
||||||
|
if has_perm(request_user, 'mediafiles.can_see'):
|
||||||
|
for mediafile_collection_element in Collection(Mediafile.get_collection_string()).element_generator():
|
||||||
|
full_data = mediafile_collection_element.get_full_data()
|
||||||
|
if user_data['id'] == full_data['uploader_id']:
|
||||||
|
result = True
|
||||||
|
break
|
||||||
|
return result
|
||||||
|
@ -17,10 +17,10 @@ class MotionsAppConfig(AppConfig):
|
|||||||
|
|
||||||
# Import all required stuff.
|
# Import all required stuff.
|
||||||
from openslides.core.config import config
|
from openslides.core.config import config
|
||||||
from openslides.core.signals import permission_change
|
from openslides.core.signals import permission_change, user_data_required
|
||||||
from openslides.utils.rest_api import router
|
from openslides.utils.rest_api import router
|
||||||
from .config_variables import get_config_variables
|
from .config_variables import get_config_variables
|
||||||
from .signals import create_builtin_workflows, get_permission_change_data
|
from .signals import create_builtin_workflows, get_permission_change_data, is_user_data_required
|
||||||
from .views import CategoryViewSet, MotionViewSet, MotionBlockViewSet, MotionPollViewSet, MotionChangeRecommendationViewSet, WorkflowViewSet
|
from .views import CategoryViewSet, MotionViewSet, MotionBlockViewSet, MotionPollViewSet, MotionChangeRecommendationViewSet, WorkflowViewSet
|
||||||
|
|
||||||
# Define config variables
|
# Define config variables
|
||||||
@ -31,6 +31,9 @@ class MotionsAppConfig(AppConfig):
|
|||||||
permission_change.connect(
|
permission_change.connect(
|
||||||
get_permission_change_data,
|
get_permission_change_data,
|
||||||
dispatch_uid='motions_get_permission_change_data')
|
dispatch_uid='motions_get_permission_change_data')
|
||||||
|
user_data_required.connect(
|
||||||
|
is_user_data_required,
|
||||||
|
dispatch_uid='motions_is_user_data_required')
|
||||||
|
|
||||||
# Register viewsets.
|
# Register viewsets.
|
||||||
router.register(self.get_model('Category').get_collection_string(), CategoryViewSet)
|
router.register(self.get_model('Category').get_collection_string(), CategoryViewSet)
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.utils.translation import ugettext_noop
|
from django.utils.translation import ugettext_noop
|
||||||
|
|
||||||
from .models import State, Workflow
|
from ..utils.auth import has_perm
|
||||||
|
from ..utils.collection import Collection
|
||||||
|
from .models import Motion, State, Workflow
|
||||||
|
|
||||||
|
|
||||||
def create_builtin_workflows(sender, **kwargs):
|
def create_builtin_workflows(sender, **kwargs):
|
||||||
@ -114,3 +116,18 @@ def get_permission_change_data(sender, permissions, **kwargs):
|
|||||||
# There could be only one 'motions.can_see' and then we want to return data.
|
# There could be only one 'motions.can_see' and then we want to return data.
|
||||||
if permission.content_type.app_label == motions_app.label and permission.codename == 'can_see':
|
if permission.content_type.app_label == motions_app.label and permission.codename == 'can_see':
|
||||||
yield from motions_app.get_startup_elements()
|
yield from motions_app.get_startup_elements()
|
||||||
|
|
||||||
|
|
||||||
|
def is_user_data_required(sender, request_user, user_data, **kwargs):
|
||||||
|
"""
|
||||||
|
Returns True if request user can see motions and user_data is required
|
||||||
|
to be displayed as motion submitter or supporter.
|
||||||
|
"""
|
||||||
|
result = False
|
||||||
|
if has_perm(request_user, 'motions.can_see'):
|
||||||
|
for motion_collection_element in Collection(Motion.get_collection_string()).element_generator():
|
||||||
|
full_data = motion_collection_element.get_full_data()
|
||||||
|
if user_data['id'] in full_data['submitters_id'] or user_data['id'] in full_data['supporters_id']:
|
||||||
|
result = True
|
||||||
|
break
|
||||||
|
return result
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
from django.contrib.auth.models import AnonymousUser
|
from django.contrib.auth.models import AnonymousUser
|
||||||
|
|
||||||
|
from ..core.signals import user_data_required
|
||||||
from ..utils.access_permissions import BaseAccessPermissions
|
from ..utils.access_permissions import BaseAccessPermissions
|
||||||
from ..utils.auth import anonymous_is_enabled, has_perm
|
from ..utils.auth import anonymous_is_enabled, has_perm
|
||||||
|
|
||||||
@ -49,7 +50,18 @@ class UserAccessPermissions(BaseAccessPermissions):
|
|||||||
# to see himself.
|
# to see himself.
|
||||||
case = LITTLE_DATA
|
case = LITTLE_DATA
|
||||||
else:
|
else:
|
||||||
case = NO_DATA
|
# Now check if the user to be sent out is required by any app e. g.
|
||||||
|
# as motion submitter or assignment candidate.
|
||||||
|
receiver_responses = user_data_required.send(
|
||||||
|
sender=self.__class__,
|
||||||
|
request_user=user,
|
||||||
|
user_data=full_data)
|
||||||
|
for receiver, response in receiver_responses:
|
||||||
|
if response:
|
||||||
|
case = LITTLE_DATA
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
case = NO_DATA
|
||||||
|
|
||||||
# Setup data.
|
# Setup data.
|
||||||
if case == FULL_DATA:
|
if case == FULL_DATA:
|
||||||
|
@ -413,6 +413,27 @@ class RetrieveMotion(TestCase):
|
|||||||
response = submitter_client.get(reverse('motion-detail', args=[self.motion.pk]))
|
response = submitter_client.get(reverse('motion-detail', args=[self.motion.pk]))
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
def test_user_without_can_see_user_permission_to_see_motion_and_submitter_data(self):
|
||||||
|
self.motion.submitters.add(get_user_model().objects.get(username='admin'))
|
||||||
|
group = get_user_model().groups.field.related_model.objects.get(pk=1) # Group with pk 1 is for anonymous and default users.
|
||||||
|
permission_string = 'users.can_see_name'
|
||||||
|
app_label, codename = permission_string.split('.')
|
||||||
|
permission = group.permissions.get(content_type__app_label=app_label, codename=codename)
|
||||||
|
group.permissions.remove(permission)
|
||||||
|
config['general_system_enable_anonymous'] = True
|
||||||
|
guest_client = APIClient()
|
||||||
|
|
||||||
|
response_1 = guest_client.get(reverse('motion-detail', args=[self.motion.pk]))
|
||||||
|
self.assertEqual(response_1.status_code, status.HTTP_200_OK)
|
||||||
|
response_2 = guest_client.get(reverse('user-detail', args=[response_1.data['submitters_id'][0]]))
|
||||||
|
self.assertEqual(response_2.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
extra_user = get_user_model().objects.create_user(
|
||||||
|
username='username_wequePhieFoom0hai3wa',
|
||||||
|
password='password_ooth7taechai5Oocieya')
|
||||||
|
response_3 = guest_client.get(reverse('user-detail', args=[extra_user.pk]))
|
||||||
|
self.assertEqual(response_3.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
|
||||||
|
|
||||||
class UpdateMotion(TestCase):
|
class UpdateMotion(TestCase):
|
||||||
"""
|
"""
|
||||||
|
@ -103,6 +103,19 @@ class UserGetTest(TestCase):
|
|||||||
|
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
def test_get_with_user_without_permissions(self):
|
||||||
|
group = Group.objects.get(pk=1)
|
||||||
|
permission_string = 'users.can_see_name'
|
||||||
|
app_label, codename = permission_string.split('.')
|
||||||
|
permission = group.permissions.get(content_type__app_label=app_label, codename=codename)
|
||||||
|
group.permissions.remove(permission)
|
||||||
|
config['general_system_enable_anonymous'] = True
|
||||||
|
guest_client = APIClient()
|
||||||
|
|
||||||
|
response = guest_client.get('/rest/users/user/1/')
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
|
|
||||||
class UserCreate(TestCase):
|
class UserCreate(TestCase):
|
||||||
"""
|
"""
|
||||||
|
Loading…
Reference in New Issue
Block a user