OpenSlides/openslides/utils/access_permissions.py
Oskar Hahn cd34d30866 Remove utils.collections.Collection class and other cleanups
* Activate restricted_data_cache on inmemory cache
* Use ElementCache in rest-api get requests
* Get requests on the restapi return 404 when the user has no permission
* Added async function for has_perm and in_some_groups
* changed Cachable.get_restricted_data to be an ansync function
* rewrote required_user_system
* changed default implementation of access_permission.check_permission to
  check a given permission or check if anonymous is enabled
2018-11-03 20:48:19 +01:00

132 lines
4.7 KiB
Python

from typing import Any, Callable, Dict, List, Optional, Set
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_collection_user,
)
from .cache import element_cache
from .collection import CollectionElement
class BaseAccessPermissions:
"""
Base access permissions container.
Every app which has autoupdate models has to create classes subclassing
from this base class for every autoupdate root model.
"""
base_permission = ''
"""
Set to a permission the user needs to see the element.
If this string is empty, all users can see it.
"""
def check_permissions(self, user: Optional[CollectionElement]) -> bool:
"""
Returns True if the user has read access to model instances.
"""
# Convert user to right type
# TODO: Remove this and make sure, that user has always the right type
user = user_to_collection_user(user)
return async_to_sync(self.async_check_permissions)(user)
async def async_check_permissions(self, user: Optional[CollectionElement]) -> bool:
"""
Returns True if the user has read access to model instances.
"""
if self.base_permission:
return await async_has_perm(user, self.base_permission)
else:
return user is not None or await async_anonymous_is_enabled()
def get_serializer_class(self, user: CollectionElement = None) -> 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().
"""
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(user=None)(instance).data
async def get_restricted_data(
self, full_data: List[Dict[str, Any]],
user: Optional[CollectionElement]) -> List[Dict[str, Any]]:
"""
Returns the restricted serialized data for the instance prepared
for the user.
The argument full_data has to be a list of full_data dicts as they are
created with CollectionElement.get_full_data(). The type of 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: 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) else []
class RequiredUsers:
"""
Helper class to find all users that are required by another element.
"""
callables: Dict[str, Callable[[Dict[str, Any]], Set[int]]] = {}
def get_collection_strings(self) -> Set[str]:
"""
Returns all collection strings for elements that could have required users.
"""
return set(self.callables.keys())
def add_collection_string(self, collection_string: str, callable: Callable[[Dict[str, Any]], Set[int]]) -> None:
"""
Add a callable for a collection_string to get the required users of the
elements.
"""
self.callables[collection_string] = callable
async def get_required_users(self, collection_strings: Set[str]) -> Set[int]:
"""
Returns the user ids that are required by other elements.
Returns only user ids required by elements with a collection_string
in the argument collection_strings.
"""
user_ids: Set[int] = set()
all_full_data = await element_cache.get_all_full_data()
for collection_string in collection_strings:
# Get the callable for the collection_string
get_user_ids = self.callables.get(collection_string)
elements = all_full_data.get(collection_string, {})
if not (get_user_ids and elements):
# if the collection_string is unknown or it has no data, do nothing
continue
for element in elements:
user_ids.update(get_user_ids(element))
return user_ids
required_user = RequiredUsers()