Merge pull request #3985 from ostcar/cleanup_access_permission

Refactor assess_permission
This commit is contained in:
Oskar Hahn 2018-12-16 15:12:35 +01:00 committed by GitHub
commit 5fc868cbee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 49 additions and 237 deletions

View File

@ -12,7 +12,9 @@ def check(args=None):
""" """
Checks for pep8 and other code styling conventions. Checks for pep8 and other code styling conventions.
""" """
return call('flake8 --max-line-length=150 --statistics openslides tests') value = call('flake8 --max-line-length=150 --statistics openslides tests')
value += call('python -m mypy openslides/ tests/')
return value
@command('travis', help='Runs the code that travis does') @command('travis', help='Runs the code that travis does')

View File

@ -10,17 +10,8 @@ class ItemAccessPermissions(BaseAccessPermissions):
""" """
base_permission = 'agenda.can_see' base_permission = 'agenda.can_see'
def get_serializer_class(self, user=None):
"""
Returns serializer class.
"""
from .serializers import ItemSerializer
return ItemSerializer
# TODO: In the following method we use full_data['is_hidden'] and # TODO: In the following method we use full_data['is_hidden'] and
# full_data['is_internal'] but this can be out of date. # full_data['is_internal'] but this can be out of date.
async def get_restricted_data( async def get_restricted_data(
self, self,
full_data: List[Dict[str, Any]], full_data: List[Dict[str, Any]],

View File

@ -22,6 +22,7 @@ class AgendaAppConfig(AppConfig):
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
from . import serializers # noqa
from ..utils.access_permissions import required_user from ..utils.access_permissions import required_user
# Define projector elements. # Define projector elements.

View File

@ -1,7 +1,7 @@
from typing import Any, Dict, List from typing import Any, Dict, List
from ..utils.access_permissions import BaseAccessPermissions from ..utils.access_permissions import BaseAccessPermissions
from ..utils.auth import async_has_perm, has_perm from ..utils.auth import async_has_perm
class AssignmentAccessPermissions(BaseAccessPermissions): class AssignmentAccessPermissions(BaseAccessPermissions):
@ -10,18 +10,6 @@ class AssignmentAccessPermissions(BaseAccessPermissions):
""" """
base_permission = 'assignments.can_see' base_permission = 'assignments.can_see'
def get_serializer_class(self, user=None):
"""
Returns different serializer classes according to users permissions.
"""
from .serializers import AssignmentFullSerializer, AssignmentShortSerializer
if user is None or (has_perm(user, 'assignments.can_see') and has_perm(user, 'assignments.can_manage')):
serializer_class = AssignmentFullSerializer
else:
serializer_class = AssignmentShortSerializer
return serializer_class
async def get_restricted_data( async def get_restricted_data(
self, self,
full_data: List[Dict[str, Any]], full_data: List[Dict[str, Any]],

View File

@ -15,11 +15,12 @@ class AssignmentsAppConfig(AppConfig):
def ready(self): def ready(self):
# Import all required stuff. # Import all required stuff.
from ..core.signals import permission_change from ..core.signals import permission_change
from ..utils.access_permissions import required_user
from ..utils.rest_api import router from ..utils.rest_api import router
from . import serializers # noqa
from .projector import get_projector_elements from .projector import get_projector_elements
from .signals import get_permission_change_data from .signals import get_permission_change_data
from .views import AssignmentViewSet, AssignmentPollViewSet from .views import AssignmentViewSet, AssignmentPollViewSet
from ..utils.access_permissions import required_user
# Define projector elements. # Define projector elements.
register_projector_elements(get_projector_elements()) register_projector_elements(get_projector_elements())

View File

@ -238,31 +238,3 @@ class AssignmentFullSerializer(ModelSerializer):
assignment.agenda_item_update_information['parent_id'] = agenda_parent_id assignment.agenda_item_update_information['parent_id'] = agenda_parent_id
assignment.save() assignment.save()
return assignment return assignment
class AssignmentShortSerializer(AssignmentFullSerializer):
"""
Serializer for assignment.models.Assignment objects. Without unpublished poll.
"""
assignment_related_users = AssignmentRelatedUserSerializer(many=True, read_only=True)
polls = AssignmentShortPollSerializer(many=True, read_only=True)
class Meta:
model = Assignment
fields = (
'id',
'title',
'description',
'open_posts',
'phase',
'assignment_related_users',
'poll_description_default',
'polls',
'agenda_item_id',
'tags',)
validators = (posts_validator,)
def validate(self, data):
if 'description' in data:
data['description'] = validate_html(data['description'])
return data

View File

@ -8,28 +8,12 @@ class ProjectorAccessPermissions(BaseAccessPermissions):
""" """
base_permission = 'core.can_see_projector' base_permission = 'core.can_see_projector'
def get_serializer_class(self, user=None):
"""
Returns serializer class.
"""
from .serializers import ProjectorSerializer
return ProjectorSerializer
class TagAccessPermissions(BaseAccessPermissions): class TagAccessPermissions(BaseAccessPermissions):
""" """
Access permissions container for Tag and TagViewSet. Access permissions container for Tag and TagViewSet.
""" """
def get_serializer_class(self, user=None):
"""
Returns serializer class.
"""
from .serializers import TagSerializer
return TagSerializer
class ChatMessageAccessPermissions(BaseAccessPermissions): class ChatMessageAccessPermissions(BaseAccessPermissions):
""" """
@ -37,14 +21,6 @@ class ChatMessageAccessPermissions(BaseAccessPermissions):
""" """
base_permission = 'core.can_use_chat' base_permission = 'core.can_use_chat'
def get_serializer_class(self, user=None):
"""
Returns serializer class.
"""
from .serializers import ChatMessageSerializer
return ChatMessageSerializer
class ProjectorMessageAccessPermissions(BaseAccessPermissions): class ProjectorMessageAccessPermissions(BaseAccessPermissions):
""" """
@ -52,14 +28,6 @@ class ProjectorMessageAccessPermissions(BaseAccessPermissions):
""" """
base_permission = 'core.can_see_projector' base_permission = 'core.can_see_projector'
def get_serializer_class(self, user=None):
"""
Returns serializer class.
"""
from .serializers import ProjectorMessageSerializer
return ProjectorMessageSerializer
class CountdownAccessPermissions(BaseAccessPermissions): class CountdownAccessPermissions(BaseAccessPermissions):
""" """
@ -67,14 +35,6 @@ class CountdownAccessPermissions(BaseAccessPermissions):
""" """
base_permission = 'core.can_see_projector' base_permission = 'core.can_see_projector'
def get_serializer_class(self, user=None):
"""
Returns serializer class.
"""
from .serializers import CountdownSerializer
return CountdownSerializer
class ConfigAccessPermissions(BaseAccessPermissions): class ConfigAccessPermissions(BaseAccessPermissions):
""" """
@ -82,14 +42,6 @@ class ConfigAccessPermissions(BaseAccessPermissions):
ConfigViewSet). ConfigViewSet).
""" """
def get_serializer_class(self, user=None):
"""
Returns serializer class.
"""
from .serializers import ConfigSerializer
return ConfigSerializer
class HistoryAccessPermissions(BaseAccessPermissions): class HistoryAccessPermissions(BaseAccessPermissions):
""" """
@ -102,11 +54,3 @@ class HistoryAccessPermissions(BaseAccessPermissions):
model instances. model instances.
""" """
return await async_in_some_groups(user_id, [GROUP_ADMIN_PK]) return await async_in_some_groups(user_id, [GROUP_ADMIN_PK])
def get_serializer_class(self, user=None):
"""
Returns serializer class.
"""
from .serializers import HistorySerializer
return HistorySerializer

View File

@ -22,6 +22,7 @@ class CoreAppConfig(AppConfig):
from ..utils.rest_api import router from ..utils.rest_api import router
from ..utils.cache import element_cache from ..utils.cache import element_cache
from .projector import get_projector_elements from .projector import get_projector_elements
from . import serializers # noqa
from .signals import ( from .signals import (
delete_django_app_permissions, delete_django_app_permissions,
get_permission_change_data, get_permission_change_data,

View File

@ -10,14 +10,6 @@ class MediafileAccessPermissions(BaseAccessPermissions):
""" """
base_permission = 'mediafiles.can_see' base_permission = 'mediafiles.can_see'
def get_serializer_class(self, user=None):
"""
Returns serializer class.
"""
from .serializers import MediafileSerializer
return MediafileSerializer
async def get_restricted_data( async def get_restricted_data(
self, self,
full_data: List[Dict[str, Any]], full_data: List[Dict[str, Any]],

View File

@ -18,6 +18,7 @@ class MediafilesAppConfig(AppConfig):
from .projector import get_projector_elements from .projector import get_projector_elements
from .signals import get_permission_change_data from .signals import get_permission_change_data
from .views import MediafileViewSet from .views import MediafileViewSet
from . import serializers # noqa
from ..utils.access_permissions import required_user from ..utils.access_permissions import required_user
# Define projector elements. # Define projector elements.

View File

@ -11,14 +11,6 @@ class MotionAccessPermissions(BaseAccessPermissions):
""" """
base_permission = 'motions.can_see' base_permission = 'motions.can_see'
def get_serializer_class(self, user=None):
"""
Returns serializer class.
"""
from .serializers import MotionSerializer
return MotionSerializer
async def get_restricted_data( async def get_restricted_data(
self, self,
full_data: List[Dict[str, Any]], full_data: List[Dict[str, Any]],
@ -71,14 +63,6 @@ class MotionChangeRecommendationAccessPermissions(BaseAccessPermissions):
""" """
base_permission = 'motions.can_see' base_permission = 'motions.can_see'
def get_serializer_class(self, user=None):
"""
Returns serializer class.
"""
from .serializers import MotionChangeRecommendationSerializer
return MotionChangeRecommendationSerializer
async def get_restricted_data( async def get_restricted_data(
self, self,
full_data: List[Dict[str, Any]], full_data: List[Dict[str, Any]],
@ -107,14 +91,6 @@ class MotionCommentSectionAccessPermissions(BaseAccessPermissions):
""" """
base_permission = 'motions.can_see' base_permission = 'motions.can_see'
def get_serializer_class(self, user=None):
"""
Returns serializer class.
"""
from .serializers import MotionCommentSectionSerializer
return MotionCommentSectionSerializer
async def get_restricted_data( async def get_restricted_data(
self, self,
full_data: List[Dict[str, Any]], full_data: List[Dict[str, Any]],
@ -140,14 +116,6 @@ class StatuteParagraphAccessPermissions(BaseAccessPermissions):
""" """
base_permission = 'motions.can_see' base_permission = 'motions.can_see'
def get_serializer_class(self, user=None):
"""
Returns serializer class.
"""
from .serializers import StatuteParagraphSerializer
return StatuteParagraphSerializer
class CategoryAccessPermissions(BaseAccessPermissions): class CategoryAccessPermissions(BaseAccessPermissions):
""" """
@ -155,14 +123,6 @@ class CategoryAccessPermissions(BaseAccessPermissions):
""" """
base_permission = 'motions.can_see' base_permission = 'motions.can_see'
def get_serializer_class(self, user=None):
"""
Returns serializer class.
"""
from .serializers import CategorySerializer
return CategorySerializer
class MotionBlockAccessPermissions(BaseAccessPermissions): class MotionBlockAccessPermissions(BaseAccessPermissions):
""" """
@ -170,25 +130,9 @@ class MotionBlockAccessPermissions(BaseAccessPermissions):
""" """
base_permission = 'motions.can_see' base_permission = 'motions.can_see'
def get_serializer_class(self, user=None):
"""
Returns serializer class.
"""
from .serializers import MotionBlockSerializer
return MotionBlockSerializer
class WorkflowAccessPermissions(BaseAccessPermissions): class WorkflowAccessPermissions(BaseAccessPermissions):
""" """
Access permissions container for Workflow and WorkflowViewSet. Access permissions container for Workflow and WorkflowViewSet.
""" """
base_permission = 'motions.can_see' base_permission = 'motions.can_see'
def get_serializer_class(self, user=None):
"""
Returns serializer class.
"""
from .serializers import WorkflowSerializer
return WorkflowSerializer

View File

@ -21,6 +21,7 @@ class MotionsAppConfig(AppConfig):
create_builtin_workflows, create_builtin_workflows,
get_permission_change_data, get_permission_change_data,
) )
from . import serializers # noqa
from .views import ( from .views import (
CategoryViewSet, CategoryViewSet,
StatuteParagraphViewSet, StatuteParagraphViewSet,

View File

@ -6,11 +6,3 @@ class TopicAccessPermissions(BaseAccessPermissions):
Access permissions container for Topic and TopicViewSet. Access permissions container for Topic and TopicViewSet.
""" """
base_permission = 'agenda.can_see' base_permission = 'agenda.can_see'
def get_serializer_class(self, user=None):
"""
Returns serializer class.
"""
from .serializers import TopicSerializer
return TopicSerializer

View File

@ -16,6 +16,7 @@ class TopicsAppConfig(AppConfig):
from .projector import get_projector_elements from .projector import get_projector_elements
from .signals import get_permission_change_data from .signals import get_permission_change_data
from .views import TopicViewSet from .views import TopicViewSet
from . import serializers # noqa
# Define projector elements. # Define projector elements.
register_projector_elements(get_projector_elements()) register_projector_elements(get_projector_elements())

View File

@ -10,14 +10,6 @@ class UserAccessPermissions(BaseAccessPermissions):
Access permissions container for User and UserViewSet. Access permissions container for User and UserViewSet.
""" """
def get_serializer_class(self, user=None):
"""
Returns different serializer classes with respect user's permissions.
"""
from .serializers import UserFullSerializer
return UserFullSerializer
async def get_restricted_data( async def get_restricted_data(
self, self,
full_data: List[Dict[str, Any]], full_data: List[Dict[str, Any]],
@ -95,14 +87,6 @@ class GroupAccessPermissions(BaseAccessPermissions):
Access permissions container for Groups. Everyone can see them Access permissions container for Groups. Everyone can see them
""" """
def get_serializer_class(self, user=None):
"""
Returns serializer class.
"""
from .serializers import GroupSerializer
return GroupSerializer
class PersonalNoteAccessPermissions(BaseAccessPermissions): class PersonalNoteAccessPermissions(BaseAccessPermissions):
""" """
@ -110,14 +94,6 @@ class PersonalNoteAccessPermissions(BaseAccessPermissions):
can handle personal notes. can handle personal notes.
""" """
def get_serializer_class(self, user=None):
"""
Returns serializer class.
"""
from .serializers import PersonalNoteSerializer
return PersonalNoteSerializer
async def get_restricted_data( async def get_restricted_data(
self, self,
full_data: List[Dict[str, Any]], full_data: List[Dict[str, Any]],

View File

@ -1,8 +1,6 @@
from typing import Any, Callable, Dict, List, Set from typing import Any, Callable, Dict, List, Set
from asgiref.sync import async_to_sync from asgiref.sync import async_to_sync
from django.db.models import Model
from rest_framework.serializers import Serializer
from .auth import async_anonymous_is_enabled, async_has_perm, user_to_user_id from .auth import async_anonymous_is_enabled, async_has_perm, user_to_user_id
from .cache import element_cache from .cache import element_cache
@ -41,25 +39,6 @@ class BaseAccessPermissions:
else: else:
return bool(user_id) or await async_anonymous_is_enabled() return bool(user_id) or await async_anonymous_is_enabled()
def get_serializer_class(self, user_id: int = 0) -> Serializer:
"""
Returns different serializer classes according to users permissions.
This should return the serializer for full data access if user is
None. See get_full_data().
"""
# TODO: Rewrite me by using an serializer_class attribute and removing
# the user_id argument.
raise NotImplementedError(
"You have to add the method 'get_serializer_class' to your "
"access permissions class.".format(self))
def get_full_data(self, instance: Model) -> Dict[str, Any]:
"""
Returns all possible serialized data for the given instance.
"""
return self.get_serializer_class()(instance).data
async def get_restricted_data( async def get_restricted_data(
self, full_data: List[Dict[str, Any]], self, full_data: List[Dict[str, Any]],
user_id: int) -> List[Dict[str, Any]]: user_id: int) -> List[Dict[str, Any]]:
@ -71,11 +50,6 @@ class BaseAccessPermissions:
the return is the same. Returns an empty list if the user has no read the return is the same. Returns an empty list if the user has no read
access. Returns reduced data if the user has limited access. Default: access. Returns reduced data if the user has limited access. Default:
Returns full data if the user has read access to model instances. Returns full data if the user has read access to model instances.
Hint: You should override this method if your get_serializer_class()
method returns different serializers for different users or if you
have access restrictions in your view or viewset in methods like
retrieve() or list().
""" """
return full_data if await self.async_check_permissions(user_id) else [] return full_data if await self.async_check_permissions(user_id) else []

View File

@ -4,6 +4,7 @@ from django.core.exceptions import ImproperlyConfigured
from django.db import models from django.db import models
from .access_permissions import BaseAccessPermissions from .access_permissions import BaseAccessPermissions
from .rest_api import model_serializer_classes
from .utils import convert_camel_case_to_pseudo_snake_case from .utils import convert_camel_case_to_pseudo_snake_case
@ -134,4 +135,5 @@ class RESTModelMixin:
""" """
Returns the full_data of the instance. Returns the full_data of the instance.
""" """
return self.get_access_permissions().get_full_data(self) serializer_class = model_serializer_classes[type(self)]
return serializer_class(self).data

View File

@ -2,6 +2,7 @@ from collections import OrderedDict
from typing import Any, Dict, Iterable, Optional, Type from typing import Any, Dict, Iterable, Optional, Type
from asgiref.sync import async_to_sync from asgiref.sync import async_to_sync
from django.db.models import Model
from django.http import Http404 from django.http import Http404
from rest_framework import status from rest_framework import status
from rest_framework.decorators import detail_route, list_route from rest_framework.decorators import detail_route, list_route
@ -32,6 +33,7 @@ from rest_framework.serializers import (
PrimaryKeyRelatedField, PrimaryKeyRelatedField,
RelatedField, RelatedField,
Serializer, Serializer,
SerializerMetaclass,
SerializerMethodField, SerializerMethodField,
ValidationError, ValidationError,
) )
@ -152,17 +154,44 @@ class PermissionMixin:
def get_serializer_class(self) -> Type[Serializer]: def get_serializer_class(self) -> Type[Serializer]:
""" """
Overridden method to return the serializer class given by the Overridden method to return the serializer class for the model.
access permissions container.
""" """
if self.get_access_permissions() is not None: model = self.get_queryset().model # type: ignore
serializer_class = self.get_access_permissions().get_serializer_class(self.request.user) # type: ignore try:
return model_serializer_classes[model]
except AttributeError:
# If there is no known serializer class for the model, return the
# default serializer class.
return super().get_serializer_class() # type: ignore
model_serializer_classes: Dict[Type[Model], Serializer] = {}
class ModelSerializerRegisterer(SerializerMetaclass):
"""
Meta class for model serializer that detects the corresponding model
and saves it.
"""
def __new__(cls, name, bases, attrs): # type: ignore
"""
Detects the corresponding model from the ModelSerializer by
looking into the Meta-class.
Does nothing, if the Meta-class does not have the model attribute.
"""
serializer_class = super().__new__(cls, name, bases, attrs)
try:
model = serializer_class.Meta.model
except AttributeError:
pass
else: else:
serializer_class = super().get_serializer_class() # type: ignore model_serializer_classes[model] = serializer_class
return serializer_class return serializer_class
class ModelSerializer(_ModelSerializer): class ModelSerializer(_ModelSerializer, metaclass=ModelSerializerRegisterer):
""" """
ModelSerializer that changes the field names of related fields to ModelSerializer that changes the field names of related fields to
FIELD_NAME_id. FIELD_NAME_id.