From bcc85f9cadd7e5287b01172ced22e5861e8aa406 Mon Sep 17 00:00:00 2001 From: FinnStutzenstein Date: Tue, 21 Feb 2017 09:34:24 +0100 Subject: [PATCH] Autoupdate on user permission change. --- openslides/assignments/apps.py | 7 ++++++ openslides/assignments/signals.py | 13 ++++++++++ openslides/core/apps.py | 8 ++++++- openslides/core/signals.py | 24 ++++++++++++++++++- openslides/mediafiles/apps.py | 7 ++++++ openslides/mediafiles/signals.py | 13 ++++++++++ openslides/motions/apps.py | 6 ++++- openslides/motions/signals.py | 13 ++++++++++ openslides/users/apps.py | 7 ++++-- openslides/users/signals.py | 13 ++++++++++ openslides/users/views.py | 40 ++++++++++++++++++++++++++++++- openslides/utils/autoupdate.py | 17 ++++++++++++- 12 files changed, 161 insertions(+), 7 deletions(-) create mode 100644 openslides/assignments/signals.py create mode 100644 openslides/mediafiles/signals.py diff --git a/openslides/assignments/apps.py b/openslides/assignments/apps.py index d2ca4d030..8dc515f69 100644 --- a/openslides/assignments/apps.py +++ b/openslides/assignments/apps.py @@ -14,13 +14,20 @@ class AssignmentsAppConfig(AppConfig): # Import all required stuff. from openslides.core.config import config + from openslides.core.signals import permission_change from openslides.utils.rest_api import router from .config_variables import get_config_variables + from .signals import get_permission_change_data from .views import AssignmentViewSet, AssignmentPollViewSet # Define config variables config.update_config_variables(get_config_variables()) + # Connect signals. + permission_change.connect( + get_permission_change_data, + dispatch_uid='assignment_get_permission_change_data') + # Register viewsets. router.register(self.get_model('Assignment').get_collection_string(), AssignmentViewSet) router.register('assignments/poll', AssignmentPollViewSet) diff --git a/openslides/assignments/signals.py b/openslides/assignments/signals.py new file mode 100644 index 000000000..00b353e41 --- /dev/null +++ b/openslides/assignments/signals.py @@ -0,0 +1,13 @@ +from django.apps import apps + + +def get_permission_change_data(sender, permissions=None, **kwargs): + """ + Returns all necessary collections if a 'can_see' permission changes. + """ + assignments_app = apps.get_app_config(app_label='assignments') + for permission in permissions: + # There could be only one 'assignment.can_see' and then we want to return data. + if permission.content_type.app_label == assignment_app.label and permission.codename == 'can_see': + return assignment_app.get_startup_elements() + return None diff --git a/openslides/core/apps.py b/openslides/core/apps.py index 1758b94b6..76e809f6e 100644 --- a/openslides/core/apps.py +++ b/openslides/core/apps.py @@ -18,7 +18,10 @@ class CoreAppConfig(AppConfig): from .signals import post_permission_creation from ..utils.rest_api import router from .config_variables import get_config_variables - from .signals import delete_django_app_permissions + from .signals import ( + delete_django_app_permissions, + get_permission_change_data, + permission_change) from .views import ( ChatMessageViewSet, ConfigViewSet, @@ -35,6 +38,9 @@ class CoreAppConfig(AppConfig): post_permission_creation.connect( delete_django_app_permissions, dispatch_uid='delete_django_app_permissions') + permission_change.connect( + get_permission_change_data, + dispatch_uid='core_get_permission_change_data') # Register viewsets. router.register(self.get_model('Projector').get_collection_string(), ProjectorViewSet) diff --git a/openslides/core/signals.py b/openslides/core/signals.py index 033626341..5a5019017 100644 --- a/openslides/core/signals.py +++ b/openslides/core/signals.py @@ -1,13 +1,17 @@ +from django.apps import apps from django.contrib.auth.models import Permission from django.contrib.contenttypes.models import ContentType from django.db.models import Q from django.dispatch import Signal -# This signal is sent 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 # for other things than dealing with Permission objects. post_permission_creation = Signal() +# This signal is send, if a permission is changed. +permission_change = Signal() + def delete_django_app_permissions(sender, **kwargs): """ @@ -19,3 +23,21 @@ def delete_django_app_permissions(sender, **kwargs): Q(app_label='contenttypes') | Q(app_label='sessions')) Permission.objects.filter(content_type__in=contenttypes).delete() + + +def get_permission_change_data(sender, permissions=None, **kwargs): + """ + Returns all necessary collections if the coupled permission changes. + """ + from ..utils.collection import Collection + core_app = apps.get_app_config(app_label='core') + for permission in permissions: + # There could be only one 'users.can_see' and then we want to return data. + if permission.content_type.app_label == core_app.label: + if permission.codename == 'can_see_projector': + yield Collection(core_app.get_model('Projector').get_collection_string()) + elif permission.codename == 'can_manage_projector': + yield Collection(core_app.get_model('ProjectorMessage').get_collection_string()) + 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()) diff --git a/openslides/mediafiles/apps.py b/openslides/mediafiles/apps.py index 31d18c7e0..f86fcf329 100644 --- a/openslides/mediafiles/apps.py +++ b/openslides/mediafiles/apps.py @@ -13,9 +13,16 @@ class MediafilesAppConfig(AppConfig): from . import projector # noqa # Import all required stuff. + from openslides.core.signals import permission_change from openslides.utils.rest_api import router + from .signals import get_permission_change_data from .views import MediafileViewSet + # Connect signals. + permission_change.connect( + get_permission_change_data, + dispatch_uid='mediafile_get_permission_change_data') + # Register viewsets. router.register(self.get_model('Mediafile').get_collection_string(), MediafileViewSet) diff --git a/openslides/mediafiles/signals.py b/openslides/mediafiles/signals.py new file mode 100644 index 000000000..096e3ef6a --- /dev/null +++ b/openslides/mediafiles/signals.py @@ -0,0 +1,13 @@ +from django.apps import apps + + +def get_permission_change_data(sender, permissions=None, **kwargs): + """ + Returns all necessary collections if a 'can_see' permission changes. + """ + mediafile_app = apps.get_app_config(app_label='mediafiles') + for permission in permissions: + # There could be only one 'mediafiles.can_see' and then we want to return data. + if permission.content_type.app_label == mediafile_app.label and permission.codename == 'can_see': + return mediafile_app.get_startup_elements() + return None diff --git a/openslides/motions/apps.py b/openslides/motions/apps.py index da7487943..ce484caf8 100644 --- a/openslides/motions/apps.py +++ b/openslides/motions/apps.py @@ -15,9 +15,10 @@ class MotionsAppConfig(AppConfig): # Import all required stuff. from openslides.core.config import config + from openslides.core.signals import permission_change from openslides.utils.rest_api import router from .config_variables import get_config_variables - from .signals import create_builtin_workflows + from .signals import create_builtin_workflows, get_permission_change_data from .views import CategoryViewSet, MotionViewSet, MotionBlockViewSet, MotionPollViewSet, MotionChangeRecommendationViewSet, WorkflowViewSet # Define config variables @@ -25,6 +26,9 @@ class MotionsAppConfig(AppConfig): # Connect signals. post_migrate.connect(create_builtin_workflows, dispatch_uid='motion_create_builtin_workflows') + permission_change.connect( + get_permission_change_data, + dispatch_uid='motion_get_permission_change_data') # Register viewsets. router.register(self.get_model('Category').get_collection_string(), CategoryViewSet) diff --git a/openslides/motions/signals.py b/openslides/motions/signals.py index 360d113ed..3c9a6c49d 100644 --- a/openslides/motions/signals.py +++ b/openslides/motions/signals.py @@ -1,3 +1,4 @@ +from django.apps import apps from django.utils.translation import ugettext_noop from .models import State, Workflow @@ -102,3 +103,15 @@ def create_builtin_workflows(sender, **kwargs): state_2_2.next_states.add(state_2_3, state_2_4, state_2_5, state_2_6, state_2_7, state_2_8, state_2_9) workflow_2.first_state = state_2_1 workflow_2.save() + + +def get_permission_change_data(sender, permissions=None, **kwargs): + """ + Returns all necessary collections if a 'can_see' permission changes. + """ + motion_app = apps.get_app_config(app_label='motions') + for permission in permissions: + # There could be only one 'motion.can_see' and then we want to return data. + if permission.content_type.app_label == motion_app.label and permission.codename == 'can_see': + return motion_app.get_startup_elements() + return None diff --git a/openslides/users/apps.py b/openslides/users/apps.py index 532a4803e..d0f99424a 100644 --- a/openslides/users/apps.py +++ b/openslides/users/apps.py @@ -14,10 +14,10 @@ class UsersAppConfig(AppConfig): # Import all required stuff. from ..core.config import config - from ..core.signals import post_permission_creation + from ..core.signals import post_permission_creation, permission_change from ..utils.rest_api import router from .config_variables import get_config_variables - from .signals import create_builtin_groups_and_admin + from .signals import create_builtin_groups_and_admin, get_permission_change_data from .views import GroupViewSet, UserViewSet # Define config variables @@ -27,6 +27,9 @@ class UsersAppConfig(AppConfig): post_permission_creation.connect( create_builtin_groups_and_admin, dispatch_uid='create_builtin_groups_and_admin') + permission_change.connect( + get_permission_change_data, + dispatch_uid='user_get_permission_change_data') # Register viewsets. router.register(self.get_model('User').get_collection_string(), UserViewSet) diff --git a/openslides/users/signals.py b/openslides/users/signals.py index 56799d6c1..318308764 100644 --- a/openslides/users/signals.py +++ b/openslides/users/signals.py @@ -1,3 +1,4 @@ +from django.apps import apps from django.contrib.auth.models import Permission from django.db.models import Q @@ -5,6 +6,18 @@ from ..utils.autoupdate import inform_changed_data from .models import Group, User +def get_permission_change_data(sender, permissions=None, **kwargs): + """ + Returns all necessary collections if a 'can_see' permission changes. + """ + user_app = apps.get_app_config(app_label='users') + for permission in permissions: + # There could be only one 'users.can_see' and then we want to return data. + if permission.content_type.app_label == user_app.label and permission.codename == 'can_see': + return user_app.get_startup_elements() + return None + + def create_builtin_groups_and_admin(**kwargs): """ Creates the builtin groups: Default, Delegates, Staff and Committees. diff --git a/openslides/users/views.py b/openslides/users/views.py index 385d3eb98..240be742b 100644 --- a/openslides/users/views.py +++ b/openslides/users/views.py @@ -6,7 +6,9 @@ from django.utils.encoding import force_text from django.utils.translation import ugettext as _ from ..core.config import config +from ..core.signals import permission_change from ..utils.auth import anonymous_is_enabled, has_perm +from ..utils.autoupdate import send_collections_to_users, inform_changed_data from ..utils.collection import CollectionElement from ..utils.rest_api import ( ModelViewSet, @@ -19,7 +21,7 @@ from ..utils.rest_api import ( from ..utils.views import APIView from .access_permissions import GroupAccessPermissions, UserAccessPermissions from .models import Group, User -from .serializers import GroupSerializer +from .serializers import GroupSerializer, PermissionRelatedField # Viewsets for the REST API @@ -169,6 +171,42 @@ class GroupViewSet(ModelViewSet): self.perform_destroy(instance) return Response(status=status.HTTP_204_NO_CONTENT) + def update(self, request, *args, **kwargs): + # Collect old and new permissions to get the difference. + permission_names = request.data['permissions'] + if isinstance(permission_names, str): + permission_names = [permission_names] + + permissionSerializer = PermissionRelatedField(read_only=True) + given_permissions = [ + permissionSerializer.to_internal_value(data=perm) for perm in permission_names] + group = Group.objects.get(pk=int(kwargs['pk'])) + old_permissions = list(group.permissions.all()) # Force evaluation so the perms doesn't change. + + response = super().update(request, *args, **kwargs) + + def diff(first, second): + """ + This helper function calculates the difference of two lists: first-second. + """ + second = set(second) + return [item for item in first if item not in second] + + if response.status_code == 200: + new_permissions = diff(given_permissions, old_permissions) + + # some permissions are added + if(len(new_permissions) > 0): + signal_results = permission_change.send(None, action='added', permissions=new_permissions) + collections_to_send = [] + for (receiver, signal_collections) in signal_results: + if signal_collections: + collections_to_send.extend(signal_collections) + #send_collections_to_users(collections_to_send, [user.id for user in group.user_set.all()]) + # inform_changed_data(what?) + + return response + # Special API views diff --git a/openslides/utils/autoupdate.py b/openslides/utils/autoupdate.py index dff469dab..55fab43eb 100644 --- a/openslides/utils/autoupdate.py +++ b/openslides/utils/autoupdate.py @@ -152,7 +152,7 @@ def send_data(message): user = None else: try: - user = CollectionElement.from_values('users/user', user_id) + user = user_to_collection_user(user_id) except ObjectDoesNotExist: # The user does not exist. Skip him/her. continue @@ -187,6 +187,21 @@ def send_data(message): {'text': json.dumps(output)}) +def send_collections_to_users(collections, users_ids): + for user_id, channel_names in websocket_user_cache.get_all().items(): + if user_id in users_ids: + try: + user = user_to_collection_user(user_id) + except ObjectDoesNotExist: + # The user does not exist. Skip him/her. + continue + output = [] + for collection in collections: + output.extend(collection.as_autoupdate_for_user(user)) + for channel_name in channel_names: + send_or_wait(Channel(channel_name).send, {'text': json.dumps(output)}) + + def inform_changed_data(instances, information=None): """ Informs the autoupdate system and the caching system about the creation or