Refactored view permissions.
Refactored permission check for REST API viewsets. Removed old PermissionMixin. Cleaned up several views.py files.
This commit is contained in:
parent
e6bc1c5016
commit
7e404946e4
@ -20,55 +20,36 @@ from .models import Item, Speaker
|
||||
from .serializers import ItemSerializer
|
||||
|
||||
|
||||
class AgendaPDF(PDFView):
|
||||
"""
|
||||
Create a full agenda-PDF.
|
||||
"""
|
||||
required_permission = 'agenda.can_see'
|
||||
filename = ugettext_lazy('Agenda')
|
||||
document_title = ugettext_lazy('Agenda')
|
||||
|
||||
def append_to_pdf(self, story):
|
||||
tree = Item.objects.get_tree(only_agenda_items=True, include_content=True)
|
||||
|
||||
def walk_tree(tree, ancestors=0):
|
||||
"""
|
||||
Generator that yields a two-element-tuple. The first element is an
|
||||
agenda-item and the second a number for steps to the root element.
|
||||
"""
|
||||
for element in tree:
|
||||
yield element['item'], ancestors
|
||||
yield from walk_tree(element['children'], ancestors + 1)
|
||||
|
||||
for item, ancestors in walk_tree(tree):
|
||||
if ancestors:
|
||||
space = " " * 6 * ancestors
|
||||
story.append(Paragraph(
|
||||
"%s%s" % (space, escape(item.get_title())),
|
||||
stylesheet['Subitem']))
|
||||
else:
|
||||
story.append(Paragraph(escape(item.get_title()), stylesheet['Item']))
|
||||
|
||||
# Viewsets for the REST API
|
||||
|
||||
class ItemViewSet(ModelViewSet):
|
||||
"""
|
||||
API endpoint to list, retrieve, create, update and destroy agenda items.
|
||||
API endpoint for agenda items.
|
||||
|
||||
There are the following views: list, retrieve, create, partial_update,
|
||||
update, destroy, manage_speaker, speak and tree.
|
||||
"""
|
||||
queryset = Item.objects.all()
|
||||
serializer_class = ItemSerializer
|
||||
|
||||
def check_permissions(self, request):
|
||||
def check_view_permissions(self):
|
||||
"""
|
||||
Calls self.permission_denied() if the requesting user has not the
|
||||
permission to see the agenda and in case of create, update or destroy
|
||||
requests the permission to manage the agenda and to see organizational
|
||||
items.
|
||||
Returns True if the user has required permissions.
|
||||
"""
|
||||
if (not request.user.has_perm('agenda.can_see') or
|
||||
(self.action in ('create', 'update', 'destroy') and not
|
||||
(request.user.has_perm('agenda.can_manage') and
|
||||
request.user.has_perm('agenda.can_see_orga_items')))):
|
||||
self.permission_denied(request)
|
||||
if self.action in ('list', 'retrieve', 'manage_speaker', 'tree'):
|
||||
result = self.request.user.has_perm('agenda.can_see')
|
||||
# For manage_speaker and tree requests the rest of the check is
|
||||
# done in the specific method. See below.
|
||||
elif self.action in ('create', 'partial_update', 'update', 'destroy'):
|
||||
result = (self.request.user.has_perm('agenda.can_see') and
|
||||
self.request.user.has_perm('agenda.can_see_orga_items') and
|
||||
self.request.user.has_perm('agenda.can_manage'))
|
||||
elif self.action == 'speak':
|
||||
result = (self.request.user.has_perm('agenda.can_see') and
|
||||
self.request.user.has_perm('agenda.can_manage'))
|
||||
else:
|
||||
result = False
|
||||
return result
|
||||
|
||||
def check_object_permissions(self, request, obj):
|
||||
"""
|
||||
@ -97,11 +78,11 @@ class ItemViewSet(ModelViewSet):
|
||||
|
||||
Checks also whether the requesting user can do this. He needs at
|
||||
least the permissions 'agenda.can_see' (see
|
||||
self.check_permission()). In case of adding himself the permission
|
||||
'agenda.can_be_speaker' is required. In case of adding someone else
|
||||
the permission 'agenda.can_manage' is required. In case of removing
|
||||
someone else 'agenda.can_manage' is required. In case of removing
|
||||
himself no other permission is required.
|
||||
self.check_view_permissions()). In case of adding himself the
|
||||
permission 'agenda.can_be_speaker' is required. In case of adding
|
||||
someone else the permission 'agenda.can_manage' is required. In
|
||||
case of removing someone else 'agenda.can_manage' is required. In
|
||||
case of removing himself no other permission is required.
|
||||
"""
|
||||
# Retrieve item.
|
||||
item = self.get_object()
|
||||
@ -174,16 +155,7 @@ class ItemViewSet(ModelViewSet):
|
||||
Special view endpoint to begin and end speach of speakers. Send PUT
|
||||
{'speaker': <speaker_id>} to begin speach. Omit data to begin speach of
|
||||
the next speaker. Send DELETE to end speach of current speaker.
|
||||
|
||||
Checks also whether the requesting user can do this. He needs at
|
||||
least the permissions 'agenda.can_see' (see
|
||||
self.check_permission()). Also the permission 'agenda.can_manage'
|
||||
is required.
|
||||
"""
|
||||
# Check permission.
|
||||
if not self.request.user.has_perm('agenda.can_manage'):
|
||||
self.permission_denied(request)
|
||||
|
||||
# Retrieve item.
|
||||
item = self.get_object()
|
||||
|
||||
@ -234,3 +206,35 @@ class ItemViewSet(ModelViewSet):
|
||||
else:
|
||||
return Response({'detail': 'Agenda tree successfully updated.'})
|
||||
return Response(Item.objects.get_tree())
|
||||
|
||||
|
||||
# Views to generate PDFs
|
||||
|
||||
class AgendaPDF(PDFView):
|
||||
"""
|
||||
Create a full agenda-PDF.
|
||||
"""
|
||||
required_permission = 'agenda.can_see'
|
||||
filename = ugettext_lazy('Agenda')
|
||||
document_title = ugettext_lazy('Agenda')
|
||||
|
||||
def append_to_pdf(self, story):
|
||||
tree = Item.objects.get_tree(only_agenda_items=True, include_content=True)
|
||||
|
||||
def walk_tree(tree, ancestors=0):
|
||||
"""
|
||||
Generator that yields a two-element-tuple. The first element is an
|
||||
agenda-item and the second a number for steps to the root element.
|
||||
"""
|
||||
for element in tree:
|
||||
yield element['item'], ancestors
|
||||
yield from walk_tree(element['children'], ancestors + 1)
|
||||
|
||||
for item, ancestors in walk_tree(tree):
|
||||
if ancestors:
|
||||
space = " " * 6 * ancestors
|
||||
story.append(Paragraph(
|
||||
"%s%s" % (space, escape(item.get_title())),
|
||||
stylesheet['Subitem']))
|
||||
else:
|
||||
story.append(Paragraph(escape(item.get_title()), stylesheet['Item']))
|
||||
|
@ -37,24 +37,37 @@ from .serializers import (
|
||||
)
|
||||
|
||||
|
||||
# Viewsets for the REST API
|
||||
|
||||
class AssignmentViewSet(ModelViewSet):
|
||||
"""
|
||||
API endpoint to list, retrieve, create, update and destroy assignments
|
||||
and to manage candidatures.
|
||||
API endpoint for assignments.
|
||||
|
||||
There are the following views: list, retrieve, create, partial_update,
|
||||
update, destroy, candidature_self, candidature_other, mark_elected and
|
||||
create_poll.
|
||||
"""
|
||||
queryset = Assignment.objects.all()
|
||||
|
||||
def check_permissions(self, request):
|
||||
def check_view_permissions(self):
|
||||
"""
|
||||
Calls self.permission_denied() if the requesting user has not the
|
||||
permission to see assignments and in case of create, update,
|
||||
partial_update or destroy requests the permission to manage
|
||||
assignments.
|
||||
Returns True if the user has required permissions.
|
||||
"""
|
||||
if (not request.user.has_perm('assignments.can_see') or
|
||||
(self.action in ('create', 'update', 'partial_update', 'destroy') and
|
||||
not request.user.has_perm('assignments.can_manage'))):
|
||||
self.permission_denied(request)
|
||||
if self.action in ('list', 'retrieve'):
|
||||
result = self.request.user.has_perm('assignments.can_see')
|
||||
elif self.action in ('create', 'partial_update', 'update', 'destroy',
|
||||
'mark_elected', 'create_poll'):
|
||||
result = (self.request.user.has_perm('assignments.can_see') and
|
||||
self.request.user.has_perm('assignments.can_manage'))
|
||||
elif self.action == 'candidature_self':
|
||||
result = (self.request.user.has_perm('assignments.can_see') and
|
||||
self.request.user.has_perm('assignments.can_nominate_self'))
|
||||
elif self.action == 'candidature_other':
|
||||
result = (self.request.user.has_perm('assignments.can_see') and
|
||||
self.request.user.has_perm('assignments.can_nominate_other'))
|
||||
else:
|
||||
result = False
|
||||
return result
|
||||
|
||||
def get_serializer_class(self):
|
||||
"""
|
||||
@ -72,8 +85,6 @@ class AssignmentViewSet(ModelViewSet):
|
||||
View to nominate self as candidate (POST) or withdraw own
|
||||
candidature (DELETE).
|
||||
"""
|
||||
if not request.user.has_perm('assignments.can_nominate_self'):
|
||||
self.permission_denied(request)
|
||||
assignment = self.get_object()
|
||||
if assignment.is_elected(request.user):
|
||||
raise ValidationError({'detail': _('You are already elected.')})
|
||||
@ -134,8 +145,6 @@ class AssignmentViewSet(ModelViewSet):
|
||||
View to nominate other users (POST) or delete their candidature
|
||||
status (DELETE). The client has to send {'user': <id>}.
|
||||
"""
|
||||
if not request.user.has_perm('assignments.can_nominate_other'):
|
||||
self.permission_denied(request)
|
||||
user = self.get_user_from_request_data(request)
|
||||
assignment = self.get_object()
|
||||
if assignment.is_elected(user):
|
||||
@ -181,8 +190,6 @@ class AssignmentViewSet(ModelViewSet):
|
||||
View to mark other users as elected (POST) or undo this (DELETE).
|
||||
The client has to send {'user': <id>}.
|
||||
"""
|
||||
if not request.user.has_perm('assignments.can_manage'):
|
||||
self.permission_denied(request)
|
||||
user = self.get_user_from_request_data(request)
|
||||
assignment = self.get_object()
|
||||
if request.method == 'POST':
|
||||
@ -204,8 +211,6 @@ class AssignmentViewSet(ModelViewSet):
|
||||
"""
|
||||
View to create a poll. It is a POST request without any data.
|
||||
"""
|
||||
if not request.user.has_perm('assignments.can_manage'):
|
||||
self.permission_denied(request)
|
||||
assignment = self.get_object()
|
||||
if not assignment.candidates.exists():
|
||||
raise ValidationError({'detail': _('Can not create poll because there are no candidates.')})
|
||||
@ -216,21 +221,23 @@ class AssignmentViewSet(ModelViewSet):
|
||||
|
||||
class AssignmentPollViewSet(UpdateModelMixin, DestroyModelMixin, GenericViewSet):
|
||||
"""
|
||||
API endpoint to update and destroy assignment polls.
|
||||
API endpoint for assignment polls.
|
||||
|
||||
There are the following views: update and destroy.
|
||||
"""
|
||||
queryset = AssignmentPoll.objects.all()
|
||||
serializer_class = AssignmentAllPollSerializer
|
||||
|
||||
def check_permissions(self, request):
|
||||
def check_view_permissions(self):
|
||||
"""
|
||||
Calls self.permission_denied() if the requesting user has not the
|
||||
permission to see assignments and to manage assignments.
|
||||
Returns True if the user has required permissions.
|
||||
"""
|
||||
if (not request.user.has_perm('assignments.can_see') or
|
||||
not request.user.has_perm('assignments.can_manage')):
|
||||
self.permission_denied(request)
|
||||
return (self.request.user.has_perm('assignments.can_see') and
|
||||
self.request.user.has_perm('assignments.can_manage'))
|
||||
|
||||
|
||||
# Views to generate PDFs
|
||||
|
||||
class AssignmentPDF(PDFView):
|
||||
required_permission = 'assignments.can_see'
|
||||
top_space = 0
|
||||
|
@ -35,6 +35,8 @@ from .serializers import (
|
||||
)
|
||||
|
||||
|
||||
# Special Django views
|
||||
|
||||
class IndexView(utils_views.CSRFMixin, utils_views.View):
|
||||
"""
|
||||
The primary view for OpenSlides using AngularJS.
|
||||
@ -61,28 +63,74 @@ class ProjectorView(utils_views.View):
|
||||
return HttpResponse(content)
|
||||
|
||||
|
||||
class AppsJsView(utils_views.View):
|
||||
"""
|
||||
Returns javascript code to be called in the angular app.
|
||||
|
||||
The javascript code loads all js-files defined by the installed (django)
|
||||
apps and creates the angular modules for each angular app.
|
||||
"""
|
||||
def get(self, *args, **kwargs):
|
||||
angular_modules = []
|
||||
js_files = []
|
||||
for app_config in apps.get_app_configs():
|
||||
# Add the angular app, if the module has one.
|
||||
if getattr(app_config,
|
||||
'angular_{}_module'.format(kwargs.get('openslides_app')),
|
||||
False):
|
||||
angular_modules.append('OpenSlidesApp.{app_name}.{app}'.format(
|
||||
app=kwargs.get('openslides_app'),
|
||||
app_name=app_config.label))
|
||||
|
||||
# Add all js files that the module needs
|
||||
try:
|
||||
app_js_files = app_config.js_files
|
||||
except AttributeError:
|
||||
# The app needs no js-files
|
||||
pass
|
||||
else:
|
||||
js_files += [
|
||||
'{static}{path}'.format(
|
||||
static=settings.STATIC_URL,
|
||||
path=path)
|
||||
for path in app_js_files]
|
||||
|
||||
return HttpResponse(
|
||||
"angular.module('OpenSlidesApp.{app}', {angular_modules});"
|
||||
"var deferres = [];"
|
||||
"{js_files}.forEach(function(js_file)deferres.push($.getScript(js_file)));"
|
||||
"$.when.apply(this, deferres).done(function() angular.bootstrap(document,['OpenSlidesApp.{app}']));"
|
||||
.format(
|
||||
app=kwargs.get('openslides_app'),
|
||||
angular_modules=angular_modules,
|
||||
js_files=js_files))
|
||||
|
||||
|
||||
# Viewsets for the REST API
|
||||
|
||||
class ProjectorViewSet(ReadOnlyModelViewSet):
|
||||
"""
|
||||
API endpoint to list, retrieve and update the projector slide info.
|
||||
API endpoint for the projector slide info.
|
||||
|
||||
There are the following views: list, retrieve, activate_elements,
|
||||
prune_elements, deactivate_elements and clear_elements
|
||||
"""
|
||||
queryset = Projector.objects.all()
|
||||
serializer_class = ProjectorSerializer
|
||||
|
||||
def check_permissions(self, request):
|
||||
def check_view_permissions(self):
|
||||
"""
|
||||
Calls self.permission_denied() if the requesting user has not the
|
||||
permission to see the projector and in case of an update request the
|
||||
permission to manage the projector.
|
||||
Returns True if the user has required permissions.
|
||||
"""
|
||||
manage_methods = (
|
||||
'activate_elements',
|
||||
'prune_elements',
|
||||
'deactivate_elements',
|
||||
'clear_elements')
|
||||
if (not request.user.has_perm('core.can_see_projector') or
|
||||
(self.action in manage_methods and
|
||||
not request.user.has_perm('core.can_manage_projector'))):
|
||||
self.permission_denied(request)
|
||||
if self.action in ('list', 'retrieve'):
|
||||
result = self.request.user.has_perm('core.can_see_projector')
|
||||
elif self.action in ('activate_elements', 'prune_elements',
|
||||
'deactivate_elements', 'clear_elements'):
|
||||
result = (self.request.user.has_perm('core.can_see_projector') and
|
||||
self.request.user.has_perm('core.can_manage_projector'))
|
||||
else:
|
||||
result = False
|
||||
return result
|
||||
|
||||
@detail_route(methods=['post'])
|
||||
def activate_elements(self, request, pk):
|
||||
@ -168,38 +216,135 @@ class ProjectorViewSet(ReadOnlyModelViewSet):
|
||||
|
||||
class CustomSlideViewSet(ModelViewSet):
|
||||
"""
|
||||
API endpoint to list, retrieve, create, update and destroy custom slides.
|
||||
API endpoint for custom slides.
|
||||
|
||||
There are the following views: list, retrieve, create, partial_update,
|
||||
update and destroy.
|
||||
"""
|
||||
queryset = CustomSlide.objects.all()
|
||||
serializer_class = CustomSlideSerializer
|
||||
|
||||
def check_permissions(self, request):
|
||||
def check_view_permissions(self):
|
||||
"""
|
||||
Calls self.permission_denied() if the requesting user has not the
|
||||
permission to manage projector.
|
||||
Returns True if the user has required permissions.
|
||||
"""
|
||||
if not request.user.has_perm('core.can_manage_projector'):
|
||||
self.permission_denied(request)
|
||||
return self.request.user.has_perm('core.can_manage_projector')
|
||||
|
||||
|
||||
class TagViewSet(ModelViewSet):
|
||||
"""
|
||||
API endpoint to list, retrieve, create, update and destroy tags.
|
||||
API endpoint for tags.
|
||||
|
||||
There are the following views: list, retrieve, create, partial_update,
|
||||
update and destroy.
|
||||
"""
|
||||
queryset = Tag.objects.all()
|
||||
serializer_class = TagSerializer
|
||||
|
||||
def check_permissions(self, request):
|
||||
def check_view_permissions(self):
|
||||
"""
|
||||
Calls self.permission_denied() if the requesting user has not the
|
||||
permission to manage tags and it is a create, update or detroy request.
|
||||
Users without permissions are able to list and retrieve tags.
|
||||
Returns True if the user has required permissions.
|
||||
"""
|
||||
if (self.action in ('create', 'update', 'destroy') and
|
||||
not request.user.has_perm('core.can_manage_tags')):
|
||||
self.permission_denied(request)
|
||||
if self.action in ('list', 'retrieve'):
|
||||
# Every authenticated user can list or retrieve tags.
|
||||
# Anonymous users can do so if they are enabled.
|
||||
result = self.request.user.is_authenticated() or config['general_system_enable_anonymous']
|
||||
elif self.action in ('create', 'update', 'destroy'):
|
||||
result = self.request.user.has_perm('core.can_manage_tags')
|
||||
else:
|
||||
result = False
|
||||
return result
|
||||
|
||||
|
||||
class ConfigMetadata(SimpleMetadata):
|
||||
"""
|
||||
Custom metadata class to add config info to responses on OPTIONS requests.
|
||||
"""
|
||||
def determine_metadata(self, request, view):
|
||||
# Sort config variables by weight.
|
||||
config_variables = sorted(config.get_config_variables().values(), key=attrgetter('weight'))
|
||||
|
||||
# Build tree.
|
||||
config_groups = []
|
||||
for config_variable in config_variables:
|
||||
if not config_groups or config_groups[-1]['name'] != config_variable.group:
|
||||
config_groups.append(OrderedDict(
|
||||
name=config_variable.group,
|
||||
subgroups=[]))
|
||||
if not config_groups[-1]['subgroups'] or config_groups[-1]['subgroups'][-1]['name'] != config_variable.subgroup:
|
||||
config_groups[-1]['subgroups'].append(OrderedDict(
|
||||
name=config_variable.subgroup,
|
||||
items=[]))
|
||||
config_groups[-1]['subgroups'][-1]['items'].append(config_variable.data)
|
||||
|
||||
# Add tree to metadata.
|
||||
metadata = super().determine_metadata(request, view)
|
||||
metadata['config_groups'] = config_groups
|
||||
return metadata
|
||||
|
||||
|
||||
class ConfigViewSet(ViewSet):
|
||||
"""
|
||||
API endpoint for the config.
|
||||
|
||||
There are the following views: list, retrieve and update.
|
||||
"""
|
||||
metadata_class = ConfigMetadata
|
||||
|
||||
def check_view_permissions(self):
|
||||
"""
|
||||
Returns True if the user has required permissions.
|
||||
"""
|
||||
if self.action in ('list', 'retrieve'):
|
||||
# Every authenticated user can list or retrieve the config.
|
||||
# Anonymous users can do so if they are enabled.
|
||||
result = self.request.user.is_authenticated() or config['general_system_enable_anonymous']
|
||||
elif self.action == 'update':
|
||||
result = self.request.user.has_perm('core.can_manage_config')
|
||||
else:
|
||||
result = False
|
||||
return result
|
||||
|
||||
def list(self, request):
|
||||
"""
|
||||
Lists all config variables. Everybody can see them.
|
||||
"""
|
||||
return Response([{'key': key, 'value': value} for key, value in config.items()])
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
"""
|
||||
Retrieves a config variable. Everybody can see it.
|
||||
"""
|
||||
key = kwargs['pk']
|
||||
try:
|
||||
value = config[key]
|
||||
except ConfigNotFound:
|
||||
raise Http404
|
||||
return Response({'key': key, 'value': value})
|
||||
|
||||
def update(self, request, *args, **kwargs):
|
||||
"""
|
||||
Updates a config variable. Only managers can do this.
|
||||
|
||||
Example: {"value": 42}
|
||||
"""
|
||||
key = kwargs['pk']
|
||||
value = request.data['value']
|
||||
|
||||
# Validate and change value.
|
||||
try:
|
||||
config[key] = value
|
||||
except ConfigNotFound:
|
||||
raise Http404
|
||||
except ConfigError as e:
|
||||
raise ValidationError({'detail': e})
|
||||
|
||||
# Return response.
|
||||
return Response({'key': key, 'value': value})
|
||||
|
||||
|
||||
# Special API views
|
||||
|
||||
class UrlPatternsView(utils_views.APIView):
|
||||
"""
|
||||
Returns a dictionary with all url patterns as json. The patterns kwargs
|
||||
@ -234,121 +379,3 @@ class VersionView(utils_views.APIView):
|
||||
'description': get_plugin_description(plugin),
|
||||
'version': get_plugin_version(plugin)})
|
||||
return result
|
||||
|
||||
|
||||
class ConfigMetadata(SimpleMetadata):
|
||||
"""
|
||||
Custom metadata class to add config info to responses on OPTIONS requests.
|
||||
"""
|
||||
def determine_metadata(self, request, view):
|
||||
# Sort config variables by weight.
|
||||
config_variables = sorted(config.get_config_variables().values(), key=attrgetter('weight'))
|
||||
|
||||
# Build tree.
|
||||
config_groups = []
|
||||
for config_variable in config_variables:
|
||||
if not config_groups or config_groups[-1]['name'] != config_variable.group:
|
||||
config_groups.append(OrderedDict(
|
||||
name=config_variable.group,
|
||||
subgroups=[]))
|
||||
if not config_groups[-1]['subgroups'] or config_groups[-1]['subgroups'][-1]['name'] != config_variable.subgroup:
|
||||
config_groups[-1]['subgroups'].append(OrderedDict(
|
||||
name=config_variable.subgroup,
|
||||
items=[]))
|
||||
config_groups[-1]['subgroups'][-1]['items'].append(config_variable.data)
|
||||
|
||||
# Add tree to metadata.
|
||||
metadata = super().determine_metadata(request, view)
|
||||
metadata['config_groups'] = config_groups
|
||||
return metadata
|
||||
|
||||
|
||||
class ConfigViewSet(ViewSet):
|
||||
"""
|
||||
API endpoint to list, retrieve and update the config.
|
||||
"""
|
||||
metadata_class = ConfigMetadata
|
||||
|
||||
def list(self, request):
|
||||
"""
|
||||
Lists all config variables. Everybody can see them.
|
||||
"""
|
||||
return Response([{'key': key, 'value': value} for key, value in config.items()])
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
"""
|
||||
Retrieves a config variable. Everybody can see it.
|
||||
"""
|
||||
key = kwargs['pk']
|
||||
try:
|
||||
value = config[key]
|
||||
except ConfigNotFound:
|
||||
raise Http404
|
||||
return Response({'key': key, 'value': value})
|
||||
|
||||
def update(self, request, *args, **kwargs):
|
||||
"""
|
||||
Updates a config variable. Only managers can do this.
|
||||
|
||||
Example: {"value": 42}
|
||||
"""
|
||||
# Check permission.
|
||||
if not request.user.has_perm('core.can_manage_config'):
|
||||
self.permission_denied(request)
|
||||
|
||||
key = kwargs['pk']
|
||||
value = request.data['value']
|
||||
|
||||
# Validate and change value.
|
||||
try:
|
||||
config[key] = value
|
||||
except ConfigNotFound:
|
||||
raise Http404
|
||||
except ConfigError as e:
|
||||
raise ValidationError({'detail': e})
|
||||
|
||||
# Return response.
|
||||
return Response({'key': key, 'value': value})
|
||||
|
||||
|
||||
class AppsJsView(utils_views.View):
|
||||
"""
|
||||
Returns javascript code to be called in the angular app.
|
||||
|
||||
The javascript code loads all js-files defined by the installed (django)
|
||||
apps and creates the angular modules for each angular app.
|
||||
"""
|
||||
def get(self, *args, **kwargs):
|
||||
angular_modules = []
|
||||
js_files = []
|
||||
for app_config in apps.get_app_configs():
|
||||
# Add the angular app, if the module has one.
|
||||
if getattr(app_config,
|
||||
'angular_{}_module'.format(kwargs.get('openslides_app')),
|
||||
False):
|
||||
angular_modules.append('OpenSlidesApp.{app_name}.{app}'.format(
|
||||
app=kwargs.get('openslides_app'),
|
||||
app_name=app_config.label))
|
||||
|
||||
# Add all js files that the module needs
|
||||
try:
|
||||
app_js_files = app_config.js_files
|
||||
except AttributeError:
|
||||
# The app needs no js-files
|
||||
pass
|
||||
else:
|
||||
js_files += [
|
||||
'{static}{path}'.format(
|
||||
static=settings.STATIC_URL,
|
||||
path=path)
|
||||
for path in app_js_files]
|
||||
|
||||
return HttpResponse(
|
||||
"angular.module('OpenSlidesApp.{app}', {angular_modules});"
|
||||
"var deferres = [];"
|
||||
"{js_files}.forEach(function(js_file)deferres.push($.getScript(js_file)));"
|
||||
"$.when.apply(this, deferres).done(function() angular.bootstrap(document,['OpenSlidesApp.{app}']));"
|
||||
.format(
|
||||
app=kwargs.get('openslides_app'),
|
||||
angular_modules=angular_modules,
|
||||
js_files=js_files))
|
||||
|
@ -4,23 +4,33 @@ from .models import Mediafile
|
||||
from .serializers import MediafileSerializer
|
||||
|
||||
|
||||
# Viewsets for the REST API
|
||||
|
||||
class MediafileViewSet(ModelViewSet):
|
||||
"""
|
||||
API endpoint to list, retrieve, create, update and destroy mediafile
|
||||
objects.
|
||||
API endpoint for mediafile objects.
|
||||
|
||||
There are the following views: list, retrieve, create, partial_update,
|
||||
update and destroy.
|
||||
"""
|
||||
queryset = Mediafile.objects.all()
|
||||
serializer_class = MediafileSerializer
|
||||
|
||||
def check_permissions(self, request):
|
||||
def check_view_permissions(self):
|
||||
"""
|
||||
Calls self.permission_denied() if the requesting user has not the
|
||||
permission to see mediafile objects and in case of create, update or
|
||||
destroy requests the permission to manage mediafile objects.
|
||||
Returns True if the user has required permissions.
|
||||
"""
|
||||
# TODO: Use mediafiles.can_upload permission to create and update some
|
||||
# objects but restricted concerning the uploader.
|
||||
if (not request.user.has_perm('mediafiles.can_see') or
|
||||
(self.action in ('create', 'update', 'destroy') and not
|
||||
request.user.has_perm('mediafiles.can_manage'))):
|
||||
self.permission_denied(request)
|
||||
if self.action in ('list', 'retrieve'):
|
||||
result = self.request.user.has_perm('mediafiles.can_see')
|
||||
elif self.action in ('create', 'partial_update', 'update'):
|
||||
result = (self.request.user.has_perm('mediafiles.can_see') and
|
||||
self.request.user.has_perm('mediafiles.can_upload') and
|
||||
self.request.user.has_perm('mediafiles.can_manage'))
|
||||
elif self.action == 'destroy':
|
||||
result = (self.request.user.has_perm('mediafiles.can_see') and
|
||||
self.request.user.has_perm('mediafiles.can_manage'))
|
||||
else:
|
||||
result = False
|
||||
return result
|
||||
|
@ -24,40 +24,45 @@ from .serializers import (
|
||||
)
|
||||
|
||||
|
||||
# Viewsets for the REST API
|
||||
|
||||
class MotionViewSet(ModelViewSet):
|
||||
"""
|
||||
API endpoint to list, retrieve, create, update and destroy motions.
|
||||
API endpoint for motions.
|
||||
|
||||
There are the following views: list, retrieve, create, partial_update,
|
||||
update, destroy, manage_version, support and set_state.
|
||||
"""
|
||||
queryset = Motion.objects.all()
|
||||
serializer_class = MotionSerializer
|
||||
|
||||
def check_permissions(self, request):
|
||||
def check_view_permissions(self):
|
||||
"""
|
||||
Calls self.permission_denied() if the requesting user has not the
|
||||
permission to see motions and in case of destroy requests the
|
||||
permission to manage motions.
|
||||
Returns True if the user has required permissions.
|
||||
"""
|
||||
if (not request.user.has_perm('motions.can_see') or
|
||||
(self.action == 'destroy' and not request.user.has_perm('motions.can_manage'))):
|
||||
self.permission_denied(request)
|
||||
if self.action in ('list', 'retrieve', 'partial_update', 'update'):
|
||||
result = self.request.user.has_perm('motions.can_see')
|
||||
# For partial_update and update requests the rest of the check is
|
||||
# done in the update method. See below.
|
||||
elif self.action == 'create':
|
||||
result = (self.request.user.has_perm('motions.can_see') and
|
||||
self.request.user.has_perm('motions.can_create') and
|
||||
(not config['motions_stop_submitting'] or
|
||||
self.request.user.has_perm('motions.can_manage')))
|
||||
elif self.action in ('destroy', 'manage_version', 'set_state'):
|
||||
result = (self.request.user.has_perm('motions.can_see') and
|
||||
self.request.user.has_perm('motions.can_manage'))
|
||||
elif self.action == 'support':
|
||||
result = (self.request.user.has_perm('motions.can_see') and
|
||||
self.request.user.has_perm('motions.can_support'))
|
||||
else:
|
||||
result = False
|
||||
return result
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
"""
|
||||
Customized view endpoint to create a new motion.
|
||||
|
||||
Checks also whether the requesting user can submit a new motion. He
|
||||
needs at least the permissions 'motions.can_see' (see
|
||||
self.check_permission()) and 'motions.can_create'. If the
|
||||
submitting of new motions by non-staff users is stopped via config
|
||||
variable 'motions_stop_submitting', the requesting user needs also
|
||||
to have the permission 'motions.can_manage'.
|
||||
"""
|
||||
# Check permissions.
|
||||
if (not request.user.has_perm('motions.can_create') or
|
||||
(not config['motions_stop_submitting'] and
|
||||
not request.user.has_perm('motions.can_manage'))):
|
||||
self.permission_denied(request)
|
||||
|
||||
# Check permission to send submitter and supporter data.
|
||||
if (not request.user.has_perm('motions.can_manage') and
|
||||
(request.data.getlist('submitters') or request.data.getlist('supporters'))):
|
||||
@ -80,7 +85,7 @@ class MotionViewSet(ModelViewSet):
|
||||
|
||||
Checks also whether the requesting user can update the motion. He
|
||||
needs at least the permissions 'motions.can_see' (see
|
||||
self.check_permission()). Also the instance method
|
||||
self.check_view_permissions()). Also the instance method
|
||||
get_allowed_actions() is evaluated.
|
||||
"""
|
||||
# Get motion.
|
||||
@ -122,10 +127,6 @@ class MotionViewSet(ModelViewSet):
|
||||
{'version_number': <number>} to delete a version. Deleting the
|
||||
active version is not allowed. Only managers can use this view.
|
||||
"""
|
||||
# Check permission.
|
||||
if not request.user.has_perm('motions.can_manage'):
|
||||
self.permission_denied(request)
|
||||
|
||||
# Retrieve motion and version.
|
||||
motion = self.get_object()
|
||||
version_number = request.data.get('version_number')
|
||||
@ -168,17 +169,7 @@ class MotionViewSet(ModelViewSet):
|
||||
(unsupport).
|
||||
|
||||
Send POST to support and DELETE to unsupport.
|
||||
|
||||
Checks also whether the requesting user can do this. He needs at
|
||||
least the permissions 'motions.can_see' (see
|
||||
self.check_permission()). Also the the permission
|
||||
'motions.can_support' is required and the instance method
|
||||
get_allowed_actions() is evaluated.
|
||||
"""
|
||||
# Check permission.
|
||||
if not request.user.has_perm('motions.can_support'):
|
||||
self.permission_denied(request)
|
||||
|
||||
# Retrieve motion and allowed actions.
|
||||
motion = self.get_object()
|
||||
allowed_actions = motion.get_allowed_actions(request.user)
|
||||
@ -211,10 +202,6 @@ class MotionViewSet(ModelViewSet):
|
||||
Send PUT {'state': <state_id>} to set and just PUT {} to reset the
|
||||
state. Only managers can use this view.
|
||||
"""
|
||||
# Check permission.
|
||||
if not request.user.has_perm('motions.can_manage'):
|
||||
self.permission_denied(request)
|
||||
|
||||
# Retrieve motion and state.
|
||||
motion = self.get_object()
|
||||
state = request.data.get('state')
|
||||
@ -245,6 +232,56 @@ class MotionViewSet(ModelViewSet):
|
||||
return Response({'detail': message})
|
||||
|
||||
|
||||
class CategoryViewSet(ModelViewSet):
|
||||
"""
|
||||
API endpoint for categories.
|
||||
|
||||
There are the following views: list, retrieve, create, partial_update,
|
||||
update and destroy.
|
||||
"""
|
||||
queryset = Category.objects.all()
|
||||
serializer_class = CategorySerializer
|
||||
|
||||
def check_view_permissions(self):
|
||||
"""
|
||||
Returns True if the user has required permissions.
|
||||
"""
|
||||
if self.action in ('list', 'retrieve'):
|
||||
result = self.request.user.has_perm('motions.can_see')
|
||||
elif self.action in ('create', 'partial_update', 'update', 'destroy'):
|
||||
result = (self.request.user.has_perm('motions.can_see') and
|
||||
self.request.user.has_perm('motions.can_manage'))
|
||||
else:
|
||||
result = False
|
||||
return result
|
||||
|
||||
|
||||
class WorkflowViewSet(ModelViewSet):
|
||||
"""
|
||||
API endpoint for workflows.
|
||||
|
||||
There are the following views: list, retrieve, create, partial_update,
|
||||
update and destroy.
|
||||
"""
|
||||
queryset = Workflow.objects.all()
|
||||
serializer_class = WorkflowSerializer
|
||||
|
||||
def check_view_permissions(self):
|
||||
"""
|
||||
Returns True if the user has required permissions.
|
||||
"""
|
||||
if self.action in ('list', 'retrieve'):
|
||||
result = self.request.user.has_perm('motions.can_see')
|
||||
elif self.action in ('create', 'partial_update', 'update', 'destroy'):
|
||||
result = (self.request.user.has_perm('motions.can_see') and
|
||||
self.request.user.has_perm('motions.can_manage'))
|
||||
else:
|
||||
result = False
|
||||
return result
|
||||
|
||||
|
||||
# Views to generate PDFs
|
||||
|
||||
class PollPDFView(PDFView):
|
||||
"""
|
||||
Generates a ballotpaper.
|
||||
@ -349,41 +386,3 @@ class MotionPDFView(SingleObjectMixin, PDFView):
|
||||
motions_to_pdf(pdf, motions)
|
||||
else:
|
||||
motion_to_pdf(pdf, self.get_object())
|
||||
|
||||
|
||||
class CategoryViewSet(ModelViewSet):
|
||||
"""
|
||||
API endpoint to list, retrieve, create, update and destroy categories.
|
||||
"""
|
||||
queryset = Category.objects.all()
|
||||
serializer_class = CategorySerializer
|
||||
|
||||
def check_permissions(self, request):
|
||||
"""
|
||||
Calls self.permission_denied() if the requesting user has not the
|
||||
permission to see motions and in case of create, update or destroy
|
||||
requests the permission to manage motions.
|
||||
"""
|
||||
if (not request.user.has_perm('motions.can_see') or
|
||||
(self.action in ('create', 'update', 'destroy') and not
|
||||
request.user.has_perm('motions.can_manage'))):
|
||||
self.permission_denied(request)
|
||||
|
||||
|
||||
class WorkflowViewSet(ModelViewSet):
|
||||
"""
|
||||
API endpoint to list, retrieve, create, update and destroy workflows.
|
||||
"""
|
||||
queryset = Workflow.objects.all()
|
||||
serializer_class = WorkflowSerializer
|
||||
|
||||
def check_permissions(self, request):
|
||||
"""
|
||||
Calls self.permission_denied() if the requesting user has not the
|
||||
permission to see motions and in case of create, update or destroy
|
||||
requests the permission to manage motions.
|
||||
"""
|
||||
if (not request.user.has_perm('motions.can_see') or
|
||||
(self.action in ('create', 'update', 'destroy') and not
|
||||
request.user.has_perm('motions.can_manage'))):
|
||||
self.permission_denied(request)
|
||||
|
@ -18,6 +18,149 @@ from .serializers import (
|
||||
)
|
||||
|
||||
|
||||
# Viewsets for the REST API
|
||||
|
||||
class UserViewSet(ModelViewSet):
|
||||
"""
|
||||
API endpoint for users.
|
||||
|
||||
There are the following views: list, retrieve, create, partial_update,
|
||||
update, destroy and reset_password.
|
||||
"""
|
||||
queryset = User.objects.all()
|
||||
|
||||
def check_view_permissions(self):
|
||||
"""
|
||||
Returns True if the user has required permissions.
|
||||
"""
|
||||
if self.action in ('list', 'retrieve'):
|
||||
result = self.request.user.has_perm('users.can_see_name')
|
||||
elif self.action in ('create', 'partial_update', 'update', 'destroy', 'reset_password'):
|
||||
result = (self.request.user.has_perm('users.can_see_name') and
|
||||
self.request.user.has_perm('users.can_see_extra_data') and
|
||||
self.request.user.has_perm('users.can_manage'))
|
||||
else:
|
||||
result = False
|
||||
return result
|
||||
|
||||
def get_serializer_class(self):
|
||||
"""
|
||||
Returns different serializer classes with respect to action and user's
|
||||
permissions.
|
||||
"""
|
||||
if (self.action in ('create', 'partial_update', 'update') or
|
||||
self.request.user.has_perm('users.can_see_extra_data')):
|
||||
# Return the UserFullSerializer for edit requests or for
|
||||
# list/retrieve requests of users with more permissions.
|
||||
serializer_class = UserFullSerializer
|
||||
else:
|
||||
serializer_class = UserShortSerializer
|
||||
return serializer_class
|
||||
|
||||
@detail_route(methods=['post'])
|
||||
def reset_password(self, request, pk=None):
|
||||
"""
|
||||
View to reset the password (using the default password).
|
||||
"""
|
||||
user = self.get_object()
|
||||
user.set_password(user.default_password)
|
||||
user.save()
|
||||
return Response({'detail': _('Password successfully reset.')})
|
||||
|
||||
|
||||
class GroupViewSet(ModelViewSet):
|
||||
"""
|
||||
API endpoint for groups.
|
||||
|
||||
There are the following views: list, retrieve, create, partial_update,
|
||||
update and destroy.
|
||||
"""
|
||||
queryset = Group.objects.all()
|
||||
serializer_class = GroupSerializer
|
||||
|
||||
def check_view_permissions(self):
|
||||
"""
|
||||
Returns True if the user has required permissions.
|
||||
"""
|
||||
if self.action in ('list', 'retrieve'):
|
||||
# Every authenticated user can list or retrieve groups.
|
||||
# Anonymous users can do so if they are enabled.
|
||||
result = self.request.user.is_authenticated() or config['general_system_enable_anonymous']
|
||||
elif self.action in ('create', 'partial_update', 'update', 'destroy'):
|
||||
# Users with all app permissions can edit groups.
|
||||
result = (self.request.user.has_perm('users.can_see_name') and
|
||||
self.request.user.has_perm('users.can_see_extra_data') and
|
||||
self.request.user.has_perm('users.can_manage'))
|
||||
else:
|
||||
# Deny request in any other case.
|
||||
result = False
|
||||
return result
|
||||
|
||||
def destroy(self, request, *args, **kwargs):
|
||||
"""
|
||||
Protects builtin groups 'Anonymous' (pk=1) and 'Registered' (pk=2)
|
||||
from being deleted.
|
||||
"""
|
||||
instance = self.get_object()
|
||||
if instance.pk in (1, 2):
|
||||
self.permission_denied(request)
|
||||
self.perform_destroy(instance)
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
# Special API views
|
||||
|
||||
class UserLoginView(APIView):
|
||||
"""
|
||||
Login the user via Ajax.
|
||||
"""
|
||||
http_method_names = ['post']
|
||||
|
||||
def post(self, *args, **kwargs):
|
||||
form = AuthenticationForm(self.request, data=self.request.data)
|
||||
if form.is_valid():
|
||||
self.user = form.get_user()
|
||||
auth_login(self.request, self.user)
|
||||
self.success = True
|
||||
else:
|
||||
self.success = False
|
||||
return super().post(*args, **kwargs)
|
||||
|
||||
def get_context_data(self, **context):
|
||||
context['success'] = self.success
|
||||
if self.success:
|
||||
context['user_id'] = self.user.pk
|
||||
return super().get_context_data(**context)
|
||||
|
||||
|
||||
class UserLogoutView(APIView):
|
||||
"""
|
||||
Logout the user via Ajax.
|
||||
"""
|
||||
http_method_names = ['post']
|
||||
|
||||
def post(self, *args, **kwargs):
|
||||
auth_logout(self.request)
|
||||
return super().post(*args, **kwargs)
|
||||
|
||||
|
||||
class WhoAmIView(APIView):
|
||||
"""
|
||||
Returns the id of the requesting user.
|
||||
"""
|
||||
http_method_names = ['get']
|
||||
|
||||
def get_context_data(self, **context):
|
||||
"""
|
||||
Appends the user id into the context.
|
||||
|
||||
Uses None for the anonymous user.
|
||||
"""
|
||||
return super().get_context_data(
|
||||
user_id=self.request.user.pk,
|
||||
**context)
|
||||
|
||||
|
||||
# Views to generate PDFs
|
||||
|
||||
class UsersListPDF(PDFView):
|
||||
@ -51,146 +194,3 @@ class UsersPasswordsPDF(PDFView):
|
||||
Append PDF objects.
|
||||
"""
|
||||
users_passwords_to_pdf(pdf)
|
||||
|
||||
|
||||
# Viewsets for the rest api
|
||||
|
||||
class UserViewSet(ModelViewSet):
|
||||
"""
|
||||
API endpoint to list, retrieve, create, update and delete users.
|
||||
"""
|
||||
queryset = User.objects.all()
|
||||
|
||||
def check_permissions(self, request):
|
||||
"""
|
||||
Calls self.permission_denied() if the requesting user has not the
|
||||
permission to see users and in case of create, update or destroy
|
||||
requests the permission to see extra user data and to manage users.
|
||||
"""
|
||||
if (not request.user.has_perm('users.can_see_name') or
|
||||
(self.action in ('create', 'update', 'destroy') and not
|
||||
(request.user.has_perm('users.can_manage') and
|
||||
request.user.has_perm('users.can_see_extra_data')))):
|
||||
self.permission_denied(request)
|
||||
|
||||
def get_serializer_class(self):
|
||||
"""
|
||||
Returns different serializer classes with respect to action and user's
|
||||
permissions.
|
||||
"""
|
||||
if (self.action in ('create', 'update') or
|
||||
self.request.user.has_perm('users.can_see_extra_data')):
|
||||
serializer_class = UserFullSerializer
|
||||
else:
|
||||
serializer_class = UserShortSerializer
|
||||
return serializer_class
|
||||
|
||||
@detail_route(methods=['post'])
|
||||
def reset_password(self, request, pk=None):
|
||||
"""
|
||||
View to reset the password (using the default password).
|
||||
"""
|
||||
if not request.user.has_perm('users.can_manage'):
|
||||
self.permission_denied(request)
|
||||
user = self.get_object()
|
||||
user.set_password(user.default_password)
|
||||
user.save()
|
||||
return Response({'detail': _('Password successfully reset.')})
|
||||
|
||||
|
||||
class GroupViewSet(ModelViewSet):
|
||||
"""
|
||||
API endpoint to list, retrieve, create, update and delete groups.
|
||||
"""
|
||||
queryset = Group.objects.all()
|
||||
serializer_class = GroupSerializer
|
||||
|
||||
def check_permissions(self, request):
|
||||
"""
|
||||
Calls self.permission_denied() if the requesting user has not the
|
||||
permission to see users and in case of create, update or destroy
|
||||
requests the permission to see extra user data and to manage users.
|
||||
"""
|
||||
# Any logged in user can retrive groups.
|
||||
# Anonymous user can retrive groups when they are activated.
|
||||
if (self.action in ('retrieve', 'list') and
|
||||
(config['general_system_enable_anonymous'] or
|
||||
self.request.user.is_authenticated())):
|
||||
return
|
||||
|
||||
# Users with the permissions 'can_manage' and 'can_see_extra_data' can
|
||||
# edit groups.
|
||||
if (self.action in ('create', 'update', 'destroy', 'partial_update') and
|
||||
request.user.has_perm('users.can_see_name') and
|
||||
request.user.has_perm('users.can_manage') and
|
||||
request.user.has_perm('users.can_see_extra_data')):
|
||||
return
|
||||
|
||||
# Raise permission_denied in any other case.
|
||||
self.permission_denied(request)
|
||||
|
||||
def destroy(self, request, *args, **kwargs):
|
||||
"""
|
||||
Protects builtin groups 'Anonymous' (pk=1) and 'Registered' (pk=2)
|
||||
from being deleted.
|
||||
"""
|
||||
instance = self.get_object()
|
||||
if instance.pk in (1, 2,):
|
||||
self.permission_denied(request)
|
||||
else:
|
||||
self.perform_destroy(instance)
|
||||
response = Response(status=status.HTTP_204_NO_CONTENT)
|
||||
return response
|
||||
|
||||
|
||||
# API Views
|
||||
|
||||
class UserLoginView(APIView):
|
||||
"""
|
||||
Login the user via ajax.
|
||||
"""
|
||||
http_method_names = ['post']
|
||||
|
||||
def post(self, *args, **kwargs):
|
||||
form = AuthenticationForm(self.request, data=self.request.data)
|
||||
if form.is_valid():
|
||||
self.user = form.get_user()
|
||||
auth_login(self.request, self.user)
|
||||
self.success = True
|
||||
else:
|
||||
self.success = False
|
||||
return super().post(*args, **kwargs)
|
||||
|
||||
def get_context_data(self, **context):
|
||||
context['success'] = self.success
|
||||
if self.success:
|
||||
context['user_id'] = self.user.pk
|
||||
return super().get_context_data(**context)
|
||||
|
||||
|
||||
class UserLogoutView(APIView):
|
||||
"""
|
||||
Logout the user via ajax.
|
||||
"""
|
||||
http_method_names = ['post']
|
||||
|
||||
def post(self, *args, **kwargs):
|
||||
auth_logout(self.request)
|
||||
return super().post(*args, **kwargs)
|
||||
|
||||
|
||||
class WhoAmIView(APIView):
|
||||
"""
|
||||
Returns the user id in the session.
|
||||
"""
|
||||
http_method_names = ['get']
|
||||
|
||||
def get_context_data(self, **context):
|
||||
"""
|
||||
Appends the user id into the context.
|
||||
|
||||
Uses None for the anonymous user.
|
||||
"""
|
||||
return super().get_context_data(
|
||||
user_id=self.request.user.pk,
|
||||
**context)
|
||||
|
@ -1,8 +1,7 @@
|
||||
import re
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from rest_framework.decorators import detail_route # noqa
|
||||
from rest_framework.decorators import list_route # noqa
|
||||
from rest_framework.decorators import detail_route, list_route # noqa
|
||||
from rest_framework.metadata import SimpleMetadata # noqa
|
||||
from rest_framework.mixins import DestroyModelMixin, UpdateModelMixin # noqa
|
||||
from rest_framework.response import Response # noqa
|
||||
@ -20,39 +19,43 @@ from rest_framework.serializers import ( # noqa
|
||||
SerializerMethodField,
|
||||
ValidationError,
|
||||
)
|
||||
from rest_framework.viewsets import GenericViewSet as _GenericViewSet # noqa
|
||||
from rest_framework.viewsets import ModelViewSet as _ModelViewSet # noqa
|
||||
from rest_framework.viewsets import ( # noqa
|
||||
GenericViewSet,
|
||||
ReadOnlyModelViewSet,
|
||||
ViewSet,
|
||||
)
|
||||
from rest_framework.viewsets import \
|
||||
ReadOnlyModelViewSet as _ReadOnlyModelViewSet # noqa
|
||||
from rest_framework.viewsets import ViewSet as _ViewSet # noqa
|
||||
|
||||
from .exceptions import OpenSlidesError
|
||||
|
||||
router = DefaultRouter()
|
||||
|
||||
|
||||
class ModelViewSet(_ModelViewSet):
|
||||
class PermissionMixin:
|
||||
"""
|
||||
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.
|
||||
Mixin for subclasses of APIView like GenericViewSet and ModelViewSet.
|
||||
|
||||
The methods check_view_permissions or check_projector_requirements are
|
||||
evaluated. If both return False self.permission_denied() is called.
|
||||
Django REST framework's permission system is disabled.
|
||||
"""
|
||||
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)
|
||||
def get_permissions(self):
|
||||
"""
|
||||
Overriden method to check view and projector permissions. Returns an
|
||||
empty interable so Django REST framework won't do any other
|
||||
permission checks by evaluating Django REST framework style permission
|
||||
classes and the request passes.
|
||||
"""
|
||||
if not self.check_view_permissions() and not self.check_projector_requirements():
|
||||
self.permission_denied(self.request)
|
||||
return ()
|
||||
|
||||
# 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_view_permissions(self):
|
||||
"""
|
||||
Override this and return True if the requesting user should be able to
|
||||
get access to your view.
|
||||
"""
|
||||
return False
|
||||
|
||||
def check_projector_requirements(self):
|
||||
"""
|
||||
@ -70,6 +73,22 @@ class ModelViewSet(_ModelViewSet):
|
||||
return result
|
||||
|
||||
|
||||
class GenericViewSet(PermissionMixin, _GenericViewSet):
|
||||
pass
|
||||
|
||||
|
||||
class ModelViewSet(PermissionMixin, _ModelViewSet):
|
||||
pass
|
||||
|
||||
|
||||
class ReadOnlyModelViewSet(PermissionMixin, _ReadOnlyModelViewSet):
|
||||
pass
|
||||
|
||||
|
||||
class ViewSet(PermissionMixin, _ViewSet):
|
||||
pass
|
||||
|
||||
|
||||
def get_collection_and_id_from_url(url):
|
||||
"""
|
||||
Helper function. Returns a tuple containing the collection name and the id
|
||||
|
@ -1,8 +1,7 @@
|
||||
from io import BytesIO
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.http import HttpResponse, HttpResponseRedirect
|
||||
from django.http import HttpResponse
|
||||
from django.utils.translation import ugettext_lazy
|
||||
from django.views import generic as django_views
|
||||
from django.views.decorators.csrf import ensure_csrf_cookie
|
||||
@ -16,41 +15,6 @@ from .pdf import firstPage, laterPages
|
||||
View = django_views.View
|
||||
|
||||
|
||||
class PermissionMixin:
|
||||
"""
|
||||
Mixin for views, that only can be visited from users with special
|
||||
permissions.
|
||||
|
||||
Set the attribute 'required_permission' to the required permission
|
||||
string or override the method 'check_permission'.
|
||||
"""
|
||||
required_permission = None
|
||||
|
||||
def check_permission(self, request, *args, **kwargs):
|
||||
"""
|
||||
Checks if the user has the required permission.
|
||||
"""
|
||||
if self.required_permission is None:
|
||||
return True
|
||||
else:
|
||||
return request.user.has_perm(self.required_permission)
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
"""
|
||||
Check if the user has the permission.
|
||||
|
||||
If the user is not logged in, redirect the user to the login page.
|
||||
"""
|
||||
if not self.check_permission(request, *args, **kwargs):
|
||||
if not request.user.is_authenticated():
|
||||
path = request.get_full_path()
|
||||
return HttpResponseRedirect(
|
||||
"%s?next=%s" % (settings.LOGIN_URL, path))
|
||||
else:
|
||||
raise PermissionDenied
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
|
||||
class SingleObjectMixin(django_views.detail.SingleObjectMixin):
|
||||
"""
|
||||
Mixin for single objects from the database.
|
||||
@ -88,14 +52,33 @@ class CSRFMixin:
|
||||
return ensure_csrf_cookie(view)
|
||||
|
||||
|
||||
class PDFView(PermissionMixin, View):
|
||||
class PDFView(View):
|
||||
"""
|
||||
View to generate an PDF.
|
||||
"""
|
||||
|
||||
filename = ugettext_lazy('undefined-filename')
|
||||
top_space = 3
|
||||
document_title = None
|
||||
required_permission = None
|
||||
|
||||
def check_permission(self, request, *args, **kwargs):
|
||||
"""
|
||||
Checks if the user has the required permission.
|
||||
"""
|
||||
if self.required_permission is None:
|
||||
return True
|
||||
else:
|
||||
return request.user.has_perm(self.required_permission)
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
"""
|
||||
Check if the user has the permission.
|
||||
|
||||
If the user is not logged in, redirect the user to the login page.
|
||||
"""
|
||||
if not self.check_permission(request, *args, **kwargs):
|
||||
raise PermissionDenied
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_top_space(self):
|
||||
return self.top_space
|
||||
|
@ -1,67 +1,9 @@
|
||||
from unittest import TestCase
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from django.core.exceptions import PermissionDenied
|
||||
|
||||
from openslides.utils import views
|
||||
|
||||
|
||||
class PermissionMixinTest(TestCase):
|
||||
def test_check_permission_non_required_permission(self):
|
||||
view = views.PermissionMixin()
|
||||
view.required_permission = None
|
||||
request = MagicMock()
|
||||
|
||||
self.assertTrue(view.check_permission(request))
|
||||
|
||||
def test_check_permission_with_required_permission(self):
|
||||
view = views.PermissionMixin()
|
||||
view.required_permission = 'required_permission'
|
||||
request = MagicMock()
|
||||
|
||||
view.check_permission(request)
|
||||
|
||||
request.user.has_perm.assert_called_once_with('required_permission')
|
||||
|
||||
@patch('builtins.super')
|
||||
def test_dispatch_with_perm(self, mock_super):
|
||||
view = views.PermissionMixin()
|
||||
view.check_permission = MagicMock(return_value=True)
|
||||
request = MagicMock()
|
||||
|
||||
view.dispatch(request)
|
||||
|
||||
mock_super().dispatch.called_once_with(request)
|
||||
|
||||
@patch('openslides.utils.views.settings')
|
||||
@patch('openslides.utils.views.HttpResponseRedirect')
|
||||
@patch('builtins.super')
|
||||
def test_dispatch_without_perm_logged_out(self, mock_super, mock_response, mock_settings):
|
||||
view = views.PermissionMixin()
|
||||
view.check_permission = MagicMock(return_value=False)
|
||||
request = MagicMock()
|
||||
request.user.is_authenticated.return_value = False
|
||||
request.get_full_path.return_value = '/requested/path/'
|
||||
mock_settings.LOGIN_URL = 'my_login_url'
|
||||
|
||||
value = view.dispatch(request)
|
||||
|
||||
mock_response.assert_called_once_with('my_login_url?next=/requested/path/')
|
||||
self.assertEqual(value, mock_response())
|
||||
|
||||
@patch('openslides.utils.views.settings')
|
||||
@patch('openslides.utils.views.HttpResponseRedirect')
|
||||
@patch('builtins.super')
|
||||
def test_dispatch_without_perm_logged_in(self, mock_super, mock_response, mock_settings):
|
||||
view = views.PermissionMixin()
|
||||
view.check_permission = MagicMock(return_value=False)
|
||||
request = MagicMock()
|
||||
request.user.is_authenticated.return_value = True
|
||||
|
||||
with self.assertRaises(PermissionDenied):
|
||||
view.dispatch(request)
|
||||
|
||||
|
||||
@patch('builtins.super')
|
||||
class SingleObjectMixinTest(TestCase):
|
||||
def test_get_object_cache(self, mock_super):
|
||||
|
Loading…
Reference in New Issue
Block a user