Merge pull request #1535 from normanjaeckel/RESTProjectorPermissions

Added permission check for objects required for active projector elem…
This commit is contained in:
Oskar Hahn 2015-06-16 10:35:42 +02:00
commit 237b0630ca
6 changed files with 125 additions and 5 deletions

View File

@ -18,7 +18,7 @@ def add_default_projector(apps, schema_editor):
weight=-500) weight=-500)
Projector = apps.get_model('core', 'Projector') Projector = apps.get_model('core', 'Projector')
projector_config = [ projector_config = [
{'name': 'core/clock'}, {'name': 'core/clock', 'stable': True},
{'name': 'core/customslide', 'id': custom_slide.id}] {'name': 'core/customslide', 'id': custom_slide.id}]
Projector.objects.create(config=projector_config) Projector.objects.create(config=projector_config)

View File

@ -52,7 +52,7 @@ class Projector(RESTModelMixin, models.Model):
for element in ProjectorElement.get_all(): for element in ProjectorElement.get_all():
elements[element.name] = element elements[element.name] = element
for config_entry in self.config: for config_entry in self.config:
name = config_entry.get('name') name = config_entry['name']
element = elements.get(name) element = elements.get(name)
data = {'name': name} data = {'name': name}
if element is None: if element is None:
@ -66,6 +66,22 @@ class Projector(RESTModelMixin, models.Model):
data['error'] = str(e) data['error'] = str(e)
yield data yield data
@classmethod
def get_all_requirements(cls):
"""
Generator which returns all ProjectorRequirement instances of all
active projector elements.
"""
elements = {}
for element in ProjectorElement.get_all():
elements[element.name] = element
for projector in cls.objects.all():
for config_entry in projector.config:
element = elements.get(config_entry['name'])
if element is not None:
for requirement in element.get_requirements(config_entry):
yield requirement
class CustomSlide(RESTModelMixin, AbsoluteUrlMixin, models.Model): class CustomSlide(RESTModelMixin, AbsoluteUrlMixin, models.Model):
""" """

View File

@ -1,10 +1,11 @@
from django.utils.timezone import now from django.utils.timezone import now
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from openslides.utils.projector import ProjectorElement from openslides.utils.projector import ProjectorElement, ProjectorRequirement
from .exceptions import ProjectorException from .exceptions import ProjectorException
from .models import CustomSlide from .models import CustomSlide
from .views import CustomSlideViewSet
class CustomSlideSlide(ProjectorElement): class CustomSlideSlide(ProjectorElement):
@ -22,6 +23,19 @@ class CustomSlideSlide(ProjectorElement):
'collection': 'core/customslide', 'collection': 'core/customslide',
'id': pk}] 'id': pk}]
def get_requirements(self, config_entry):
self.config_entry = config_entry
try:
pk = self.get_context()[0]['id']
except ProjectorException:
# Custom slide does not exist so just do nothing.
pass
else:
yield ProjectorRequirement(
view_class=CustomSlideViewSet,
view_action='retrieve',
pk=str(pk))
class Clock(ProjectorElement): class Clock(ProjectorElement):
""" """

View File

@ -1,9 +1,10 @@
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from openslides.core.exceptions import ProjectorException from openslides.core.exceptions import ProjectorException
from openslides.utils.projector import ProjectorElement from openslides.utils.projector import ProjectorElement, ProjectorRequirement
from .models import User from .models import User
from .views import GroupViewSet, UserViewSet
class UserSlide(ProjectorElement): class UserSlide(ProjectorElement):
@ -27,3 +28,17 @@ class UserSlide(ProjectorElement):
'collection': 'users/group', 'collection': 'users/group',
'id': group.pk}) 'id': group.pk})
return result return result
def get_requirements(self, config_entry):
self.config_entry = config_entry
try:
context = self.get_context()
except ProjectorException:
# User does not exist so just do nothing.
pass
else:
for item in context:
yield ProjectorRequirement(
view_class=UserViewSet if item['collection'] == 'users/user' else GroupViewSet,
view_action='retrieve',
pk=str(item['id']))

View File

@ -64,3 +64,39 @@ class ProjectorElement(object, metaclass=SignalConnectMetaClass):
Returns the context of the projector element. Returns the context of the projector element.
""" """
return None return None
def get_requirements(self, config_entry):
"""
Returns an iterable of ProjectorRequirement instances to setup
which views should be accessable for projector clients if the
projector element is active. The config_entry has to be given.
"""
return ()
class ProjectorRequirement:
"""
Container for required views. Such a view is defined by its class, its
action and its kwargs which come from the URL path.
"""
def __init__(self, view_class, view_action, **kwargs):
self.view_class = view_class
self.view_action = view_action
self.kwargs = kwargs
def is_currently_required(self, view_instance):
"""
Returns True if the view_instance matches the initiated data of this
requirement.
"""
if not type(view_instance) == self.view_class:
result = False
elif not view_instance.action == self.view_action:
result = False
else:
result = True
for key in view_instance.kwargs:
if not self.kwargs[key] == view_instance.kwargs[key]:
result = False
break
return result

View File

@ -19,7 +19,8 @@ from rest_framework.serializers import ( # noqa
from rest_framework.mixins import DestroyModelMixin, UpdateModelMixin # noqa from rest_framework.mixins import DestroyModelMixin, UpdateModelMixin # noqa
from rest_framework.response import Response # noqa from rest_framework.response import Response # noqa
from rest_framework.routers import DefaultRouter from rest_framework.routers import DefaultRouter
from rest_framework.viewsets import GenericViewSet, ModelViewSet, ReadOnlyModelViewSet, ViewSet # noqa from rest_framework.viewsets import ModelViewSet as _ModelViewSet
from rest_framework.viewsets import GenericViewSet, ReadOnlyModelViewSet, ViewSet # noqa
from rest_framework.decorators import list_route # noqa from rest_framework.decorators import list_route # noqa
from .exceptions import OpenSlidesError from .exceptions import OpenSlidesError
@ -51,6 +52,44 @@ class RESTModelMixin:
return reverse(rest_url, args=[str(root_instance.pk)]) return reverse(rest_url, args=[str(root_instance.pk)])
class ModelViewSet(_ModelViewSet):
"""
Viewset for models. Before the method check_permission is called we
check projector requirements. If access for projector client users is
not currently required, check_permission is called, else not.
"""
def initial(self, request, *args, **kwargs):
"""
Runs anything that needs to occur prior to calling the method handler.
"""
self.format_kwarg = self.get_format_suffix(**kwargs)
# Ensure that the incoming request is permitted
self.perform_authentication(request)
if not self.check_projector_requirements():
self.check_permissions(request)
self.check_throttles(request)
# Perform content negotiation and store the accepted info on the request
neg = self.perform_content_negotiation(request)
request.accepted_renderer, request.accepted_media_type = neg
def check_projector_requirements(self):
"""
Helper method which returns True if the current request (on this
view instance) is required for at least one active projector element.
"""
from openslides.core.models import Projector
result = False
if self.request.user.has_perm('core.can_see_projector'):
for requirement in Projector.get_all_requirements():
if requirement.is_currently_required(view_instance=self):
result = True
break
return result
def get_collection_and_id_from_url(url): def get_collection_and_id_from_url(url):
""" """
Helper function. Returns a tuple containing the collection name and the id Helper function. Returns a tuple containing the collection name and the id