Skip to content

Commit

Permalink
Merge pull request #235 from Fruity-Loops/fix_permissions
Browse files Browse the repository at this point in the history
Fix permissions and permissions refactor.
  • Loading branch information
Amanda-Kolopa authored Jan 31, 2021
2 parents 1c0ded2 + bf49638 commit 589ea60
Show file tree
Hide file tree
Showing 11 changed files with 171 additions and 139 deletions.
1 change: 1 addition & 0 deletions server/audit/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from inventory_item.models import Item
from user_account.models import CustomUser


class Audit(models.Model):
audit_id = models.AutoField(primary_key=True)
organization = models.ForeignKey(Organization,
Expand Down
28 changes: 23 additions & 5 deletions server/audit/permissions.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from user_account.permissions import IsInventoryManager
from rest_framework.permissions import BasePermission

from user_account.models import CustomUser
from .models import Audit


class IsInventoryManagerAudit(IsInventoryManager):
class CheckAuditOrganizationById(BasePermission):
message = "You must be an Inventory Manager of this organization to do this operation"

def has_permission(self, request, view):
Expand All @@ -12,10 +13,27 @@ def has_permission(self, request, view):
if request.parser_context['kwargs'] is not None \
and 'pk' in request.parser_context['kwargs']:
temp = Audit.objects.get(audit_id=request.parser_context['kwargs']['pk'])
return temp.organization_id == user.organization.org_id and user.role == 'IM'
return temp.organization_id == user.organization.org_id

return True


class CheckInitAuditData(BasePermission):
message = "The requested audit must be for the same organization as the requesting user"

def has_permission(self, request, view):
user = CustomUser.objects.get(email=request.user)
if 'init_audit' in request.data:
get_audit = Audit.objects.get(audit_id=request.data['init_audit'])
return get_audit.organization_id == user.organization_id and user.role == 'IM'
return get_audit.organization_id == user.organization_id
return True


return super().has_permission(request, view)
class ValidateSKOfSameOrg(BasePermission):
message = "The requested user must be part of the same organization"

def has_permission(self, request, view):
if 'customuser' in request.data:
user = CustomUser.objects.get(id=request.data['customuser'])
return user.organization_id == request.user.organization_id
return True
20 changes: 11 additions & 9 deletions server/audit/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,9 @@
from user_account.models import CustomUser
from inventory_item.models import Item
from .models import Audit
from django.forms.models import model_to_dict
import json


class AuditTestCase(APITestCase):

fixtures = ["items.json", "users.json", "organizations.json", "audits.json"]

def setUp(self):
Expand All @@ -28,7 +25,7 @@ def setUp(self):
self.item_one = Item.objects.get(_id=12731369.0)
self.item_two = Item.objects.get(_id=12752842.0)
self.audit = Audit.objects.create()
self.audit.inventory_items.add(self.item_one._id, self.item_two._id) #check if this was there before
self.audit.inventory_items.add(self.item_one._id, self.item_two._id) # check if this was there before

def test_audit_unauthorized_request(self):
""" User can't access any of the method if token is not in header of request """
Expand Down Expand Up @@ -103,18 +100,23 @@ def test_item_to_sk_designation(self):
""" Create ItemToSK designation as inventory manager """
self.client.force_authenticate(user=self.inv_manager)
self.predefined_audit = Audit.objects.get(pk=1)
request_body = {"init_audit": 1,
"customuser": 5,
"item_ids": [12752842],
"bins": ['A10']}

response = self.client.post("/item-to-sk/",
{"init_audit": 1,
"customuser": 5,
"item_ids": [12752842],
"bins": ['A10']}, format="json")
response = self.client.post("/item-to-sk/", request_body, format="json")

self.assertEqual(response.status_code,
status.HTTP_201_CREATED)

self.assertEqual(response.data['success'], "success")

# Now testing with a user from a different organization
request_body['customuser'] = 6
new_response = self.client.post("/item-to-sk/", request_body, format='json')
self.assertEqual(new_response.status_code, status.HTTP_403_FORBIDDEN)

def test_item_to_sk_designation_bad_org(self):
""" Try to create ItemToSK designation as inventory manager from another organization """

Expand Down
23 changes: 17 additions & 6 deletions server/audit/views.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from rest_framework import viewsets, status
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
from user_account.permissions import IsSystemAdmin
from .permissions import IsInventoryManagerAudit
from user_account.permissions import HasSameOrgInQuery, \
PermissionFactory
from .permissions import CheckAuditOrganizationById, CheckInitAuditData, ValidateSKOfSameOrg
from .serializers import AuditSerializer, ItemToSKSerializer, GetAuditSerializer
from .models import Audit, ItemToSK

Expand All @@ -13,7 +13,12 @@ class AuditViewSet(viewsets.ModelViewSet):
"""
http_method_names = ['post', 'patch', 'get']
queryset = Audit.objects.all()
permission_classes = [IsAuthenticated, IsSystemAdmin | IsInventoryManagerAudit]

def get_permissions(self):
factory = PermissionFactory(self.request)
permission_classes = factory.get_general_permissions([
CheckAuditOrganizationById, HasSameOrgInQuery])
return [permission() for permission in permission_classes]

def get_serializer(self, *args, **kwargs):
serializer_class = AuditSerializer
Expand All @@ -31,14 +36,20 @@ def list(self, request):
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)


class ItemToSKViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows Audits to be created.
"""
http_method_names = ['post', 'get']
http_method_names = ['post']
queryset = ItemToSK.objects.all()
serializer_class = ItemToSKSerializer
permission_classes = [IsAuthenticated, IsInventoryManagerAudit | IsSystemAdmin]

def get_permissions(self):
factory = PermissionFactory(self.request)
permission_classes = factory.get_general_permissions([
CheckInitAuditData, HasSameOrgInQuery, ValidateSKOfSameOrg])
return [permission() for permission in permission_classes]

def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
Expand Down
10 changes: 6 additions & 4 deletions server/audit_template/permissions.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
from user_account.permissions import IsInventoryManager
from rest_framework.permissions import BasePermission

from .models import AuditTemplate


class IsInventoryManagerTemplate(IsInventoryManager):
class CheckTemplateOrganizationById(BasePermission):
message = "The requested template must be of the same organization"

def has_permission(self, request, view):
if request.parser_context['kwargs'] is not None \
and 'pk' in request.parser_context['kwargs']:
temp = AuditTemplate.objects.get(template_id=request.parser_context['kwargs']['pk'])
return temp.organization == request.user.organization and request.user.role == 'IM'
return super().has_permission(request, view)
return temp.organization == request.user.organization
return True
12 changes: 9 additions & 3 deletions server/audit_template/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response

from user_account.permissions import IsSystemAdmin
from .permissions import IsInventoryManagerTemplate
from user_account.permissions import IsSystemAdmin, HasSameOrgInQuery, PermissionFactory
from .permissions import CheckTemplateOrganizationById
from .serializers import AuditTemplateSerializer
from .models import AuditTemplate

Expand All @@ -17,9 +17,15 @@ class AuditTemplateViewSet(viewsets.ModelViewSet):

queryset = AuditTemplate.objects.all()
serializer_class = AuditTemplateSerializer
permission_classes = [IsAuthenticated, IsInventoryManagerTemplate | IsSystemAdmin]
permission_classes = [IsAuthenticated, CheckTemplateOrganizationById | IsSystemAdmin]
http_method_names = ['post', 'get', 'patch', 'delete']

def get_permissions(self):
factory = PermissionFactory(self.request)
permission_classes = factory.get_general_permissions([
CheckTemplateOrganizationById, HasSameOrgInQuery])
return [permission() for permission in permission_classes]

def create(self, request, *args, **kwargs):
data = request.data
user = request.user
Expand Down
24 changes: 6 additions & 18 deletions server/organization/permissions.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,11 @@
from rest_framework.permissions import BasePermission
from user_account.permissions import IsSystemAdmin, IsInventoryManager


class UserOrganizationPermission(BasePermission):
class ValidateOrgMatchesUser(BasePermission):
message = "The organization requested must match the user's"

def has_permission(self, request, view):
"""
Overriding default has_permission method in order to add Custom
permissions to our views
This can be used either inside the permission_class directly or
you can call it from other permission files
:param request:
:param view:
:return: True/False : Whether the user is allowed to perform CRUD
"""
if view.action in ['list', 'retrieve', 'update', 'partial_update']:
return IsSystemAdmin.has_permission(None, request, None)\
or IsInventoryManager.has_permission(None, request, view)
if view.action in ['create', 'destroy']:
return IsSystemAdmin.has_permission(None, request, None)

return False
user = request.user
if 'pk' in request.parser_context['kwargs']:
return str(user.organization_id) == request.parser_context['kwargs']['pk']
return True
18 changes: 14 additions & 4 deletions server/organization/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from inventory_item.updater import start_new_job
from user_account.permissions import IsInventoryManager, IsSystemAdmin
from user_account.permissions import IsInventoryManager, IsSystemAdmin, HasSameOrgInBody, \
PermissionFactory

from .serializers import OrganizationSerializer
from .models import Organization
from .permissions import UserOrganizationPermission
from .permissions import ValidateOrgMatchesUser


class OrganizationViewSet(viewsets.ModelViewSet):
Expand All @@ -17,7 +18,14 @@ class OrganizationViewSet(viewsets.ModelViewSet):

queryset = Organization.objects.all()
serializer_class = OrganizationSerializer
permission_classes = [IsAuthenticated, UserOrganizationPermission]

def get_permissions(self):
factory = PermissionFactory(self.request)
if self.action in ['retrieve', 'update', 'partial_update']:
permission_classes = factory.get_general_permissions([ValidateOrgMatchesUser])
else:
permission_classes = factory.base_sa_permissions
return [permission() for permission in permission_classes]

def create(self, request, *args, **kwargs):
data = request.data
Expand All @@ -32,7 +40,9 @@ class ModifyOrganizationInventoryItemsDataUpdate(generics.GenericAPIView):
API endpoint that allow a user to update the timing at which
the Inventory Data is refreshed
"""
permission_classes = [IsAuthenticated, IsInventoryManager | IsSystemAdmin]

# Note: if other methods are added here, keep in mind that the permissions will need to change
permission_classes = [IsAuthenticated, IsSystemAdmin | (IsInventoryManager, HasSameOrgInBody)]

def post(self, request):
data = request.data
Expand Down
Loading

0 comments on commit 589ea60

Please sign in to comment.