From 08b3ac966ac0302952982930c77e8f13a71baf7f Mon Sep 17 00:00:00 2001 From: David Slusser Date: Sat, 2 Jan 2021 15:58:18 -0800 Subject: [PATCH] added mixins for encource service account enabled for api reponses --- userextensions/__init__.py | 2 +- userextensions/mixins.py | 58 +++++++++++++++++++ .../userextensions/detail/detail_user.html | 2 +- userextensions/views/ajax.py | 3 +- userextensions/views/gui.py | 14 ++++- 5 files changed, 74 insertions(+), 5 deletions(-) create mode 100644 userextensions/mixins.py diff --git a/userextensions/__init__.py b/userextensions/__init__.py index 7d360b1..d8bd479 100644 --- a/userextensions/__init__.py +++ b/userextensions/__init__.py @@ -7,7 +7,7 @@ """ __title__ = 'django-userextensions' -__version__ = '0.0.12' +__version__ = '0.0.13' __author__ = 'David Slusser' __email__ = 'dbslusser@gmail.com' __license__ = 'GPL-3.0' diff --git a/userextensions/mixins.py b/userextensions/mixins.py new file mode 100644 index 0000000..bc4c0b7 --- /dev/null +++ b/userextensions/mixins.py @@ -0,0 +1,58 @@ +import re +from django.http import JsonResponse +from rest_framework import status +from .models import ServiceAccount + + +class ServiceAccountControlMixin: + """ A mixin for Django Rest Framework viewsets that checks if the request is made from a ServiceAccount and only + allows access to the endpoint if the ServiceAccount is enabled and admin_enabled. If the ServiceAccount is disabled + by the owner or an admin, a 403 error will be received instead of the API response. This currently works for APIs + using TokenAuthentication (rest_framework.authentication.TokenAuthentication). """ + def dispatch(self, request, *args, **kwargs): + mo = re.search('Token (\S+)', request.META.get('HTTP_AUTHORIZATION', '')) + if mo: + srv_acct = ServiceAccount.objects.get_object_or_none(user__auth_token__key=mo.group(1)) + if srv_acct: + if srv_acct.admin_enabled is False: + return JsonResponse(data={'detail': 'this service account has been administratively disabled'}, + status=status.HTTP_403_FORBIDDEN) + elif srv_acct.enabled is False: + return JsonResponse(data={'detail': 'this service account has been disabled'}, + status=status.HTTP_403_FORBIDDEN) + return super().dispatch(request, *args, **kwargs) + + +class AllowOnlyServiceAccountMixin: + """ A mixin for Django Rest Framework viewsets that only allows responses to requests made from ServiceAccounts. + This currently works for APIs using TokenAuthentication (rest_framework.authentication.TokenAuthentication).""" + def dispatch(self, request, *args, **kwargs): + mo = re.search('Token (\S+)', request.META.get('HTTP_AUTHORIZATION', '')) + if mo: + srv_acct = ServiceAccount.objects.get_object_or_none(user__auth_token__key=mo.group(1)) + if not srv_acct: + return JsonResponse(data={'detail': 'access to this endpoint is only available to service accounts'}, + status=status.HTTP_403_FORBIDDEN) + return super().dispatch(request, *args, **kwargs) + + +class AllowOnlyEnabledServiceAccountMixin: + """ A mixin for Django Rest Framework viewsets that checks if the request is made from a ServiceAccount and only + allows access to the endpoint if an enabled ServiceAccount made the request. If the ServiceAccount is disabled + by the owner or an admin, a 403 error will be received instead of the API response. This currently works for APIs + using TokenAuthentication (rest_framework.authentication.TokenAuthentication). """ + def dispatch(self, request, *args, **kwargs): + mo = re.search('Token (\S+)', request.META.get('HTTP_AUTHORIZATION', '')) + if mo: + srv_acct = ServiceAccount.objects.get_object_or_none(user__auth_token__key=mo.group(1)) + if srv_acct: + if srv_acct.admin_enabled is False: + return JsonResponse(data={'detail': 'this service account has been administratively disabled'}, + status=status.HTTP_403_FORBIDDEN) + elif srv_acct.enabled is False: + return JsonResponse(data={'detail': 'this service account has been disabled'}, + status=status.HTTP_403_FORBIDDEN) + else: + return JsonResponse(data={'detail': 'access to this endpoint is only available to enabled ' + 'service accounts'}, status=status.HTTP_403_FORBIDDEN) + return super().dispatch(request, *args, **kwargs) diff --git a/userextensions/templates/userextensions/detail/detail_user.html b/userextensions/templates/userextensions/detail/detail_user.html index 1749e20..52af3c9 100644 --- a/userextensions/templates/userextensions/detail/detail_user.html +++ b/userextensions/templates/userextensions/detail/detail_user.html @@ -57,7 +57,7 @@

User Profile: API Token:
{{ token }}    - diff --git a/userextensions/views/ajax.py b/userextensions/views/ajax.py index 048a79e..5b8375c 100644 --- a/userextensions/views/ajax.py +++ b/userextensions/views/ajax.py @@ -29,7 +29,8 @@ def get_users_per_group(request): obj_id = request.GET['client_response'] obj = Group.objects.get(id=obj_id) template = loader.get_template('userextensions/ajax/get_users_per_group.htm') - return HttpResponse(json.dumps({'server_response': template.render({'queryset': obj.user_set.all()})}), + return HttpResponse(json.dumps({'server_response': template.render( + {'queryset': obj.user_set.all().filter_by('username')})}), content_type='application/javascript') else: return HttpResponse('Invalid request inputs', status=400) diff --git a/userextensions/views/gui.py b/userextensions/views/gui.py index 21d2119..e9a6126 100644 --- a/userextensions/views/gui.py +++ b/userextensions/views/gui.py @@ -104,7 +104,17 @@ def get(self, request): select_related('user', 'group', 'user__auth_token').order_by('group__name') # get list of groups that do not have a service account - context['groups'] = request.user.groups.filter(serviceaccount=None).\ - prefetch_related('user_set').order_by('name') + # If the SRV_ACCOUNT_GROUP_FILTER_LIST (list) settings variable is set, only include groups whose name matches a + # provided regex pattern, otherwise include all groups. Set the SRV_ACCOUNT_ENFORCE_MATCHING (bool) settings + # variable to False to allow all groups, but maintain naming of the service account based on regex patterns. + regex_list = getattr(settings, 'SRV_ACCOUNT_GROUP_FILTER_LIST', list()) + if regex_list and getattr(settings, 'SRV_ACCOUNT_ENFORCE_MATCHING', True): + group_queryset = request.user.groups.none() + for regex in regex_list: + group_queryset = group_queryset | request.user.groups.filter(serviceaccount=None, name__regex=regex) + else: + group_queryset = request.user.groups.filter(serviceaccount=None) + + context['groups'] = group_queryset.prefetch_related('user_set').order_by('name') context['base_template'] = self.base_template return render(request, self.template, context=context)