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
|
||||
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)
|
||||
==========================
|
||||
|
@ -17,11 +17,12 @@ class AgendaAppConfig(AppConfig):
|
||||
# Import all required stuff.
|
||||
from django.db.models.signals import pre_delete, post_save
|
||||
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 .config_variables import get_config_variables
|
||||
from .signals import (
|
||||
get_permission_change_data,
|
||||
is_user_data_required,
|
||||
listen_to_related_object_post_delete,
|
||||
listen_to_related_object_post_save)
|
||||
from .views import ItemViewSet
|
||||
@ -39,6 +40,9 @@ class AgendaAppConfig(AppConfig):
|
||||
permission_change.connect(
|
||||
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.
|
||||
router.register(self.get_model('Item').get_collection_string(), ItemViewSet)
|
||||
|
@ -1,8 +1,9 @@
|
||||
from django.apps import apps
|
||||
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
|
||||
|
||||
|
||||
@ -51,3 +52,22 @@ def get_permission_change_data(sender, permissions, **kwargs):
|
||||
and permission.codename in ('can_see', 'can_see_hidden_items')):
|
||||
yield from agenda_app.get_startup_elements()
|
||||
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.
|
||||
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 .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
|
||||
|
||||
# Define config variables
|
||||
@ -29,6 +29,9 @@ class AssignmentsAppConfig(AppConfig):
|
||||
permission_change.connect(
|
||||
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.
|
||||
router.register(self.get_model('Assignment').get_collection_string(), AssignmentViewSet)
|
||||
|
@ -1,5 +1,9 @@
|
||||
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):
|
||||
"""
|
||||
@ -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.
|
||||
if permission.content_type.app_label == assignments_app.label and permission.codename == 'can_see':
|
||||
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 (
|
||||
delete_django_app_permissions,
|
||||
get_permission_change_data,
|
||||
permission_change)
|
||||
is_user_data_required,
|
||||
permission_change,
|
||||
user_data_required)
|
||||
from .views import (
|
||||
ChatMessageViewSet,
|
||||
ConfigViewSet,
|
||||
@ -43,6 +45,9 @@ class CoreAppConfig(AppConfig):
|
||||
permission_change.connect(
|
||||
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.
|
||||
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.dispatch import Signal
|
||||
|
||||
from ..utils.auth import has_perm
|
||||
from ..utils.collection import Collection
|
||||
from .models import ChatMessage
|
||||
|
||||
# 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
|
||||
@ -15,6 +17,12 @@ post_permission_creation = Signal()
|
||||
# permission). Connected receivers may yield Collections.
|
||||
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):
|
||||
"""
|
||||
@ -42,3 +50,18 @@ def get_permission_change_data(sender, permissions, **kwargs):
|
||||
yield Collection(core_app.get_model('Countdown').get_collection_string())
|
||||
elif permission.codename == 'can_use_chat':
|
||||
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
|
||||
|
||||
# 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 .signals import get_permission_change_data
|
||||
from .signals import get_permission_change_data, is_user_data_required
|
||||
from .views import MediafileViewSet
|
||||
|
||||
# Connect signals.
|
||||
permission_change.connect(
|
||||
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.
|
||||
router.register(self.get_model('Mediafile').get_collection_string(), MediafileViewSet)
|
||||
|
@ -1,5 +1,9 @@
|
||||
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):
|
||||
"""
|
||||
@ -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.
|
||||
if permission.content_type.app_label == mediafiles_app.label and permission.codename == 'can_see':
|
||||
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.
|
||||
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 .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
|
||||
|
||||
# Define config variables
|
||||
@ -31,6 +31,9 @@ class MotionsAppConfig(AppConfig):
|
||||
permission_change.connect(
|
||||
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.
|
||||
router.register(self.get_model('Category').get_collection_string(), CategoryViewSet)
|
||||
|
@ -1,7 +1,9 @@
|
||||
from django.apps import apps
|
||||
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):
|
||||
@ -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.
|
||||
if permission.content_type.app_label == motions_app.label and permission.codename == 'can_see':
|
||||
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 ..core.signals import user_data_required
|
||||
from ..utils.access_permissions import BaseAccessPermissions
|
||||
from ..utils.auth import anonymous_is_enabled, has_perm
|
||||
|
||||
@ -48,6 +49,17 @@ class UserAccessPermissions(BaseAccessPermissions):
|
||||
# An authenticated user without the permission to see users tries
|
||||
# to see himself.
|
||||
case = LITTLE_DATA
|
||||
else:
|
||||
# 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
|
||||
|
||||
|
@ -413,6 +413,27 @@ class RetrieveMotion(TestCase):
|
||||
response = submitter_client.get(reverse('motion-detail', args=[self.motion.pk]))
|
||||
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):
|
||||
"""
|
||||
|
@ -103,6 +103,19 @@ class UserGetTest(TestCase):
|
||||
|
||||
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):
|
||||
"""
|
||||
|
Loading…
Reference in New Issue
Block a user