diff --git a/.vscode/settings.json b/.vscode/settings.json index 4687599b..f4efc816 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,7 +8,8 @@ // "-v", // "--cov", // "--cov-report xml", - "app" + "-s", + "app", ], "python.testing.unittestEnabled": false, "python.testing.pytestEnabled": true, diff --git a/app/access/mixins/organization.py b/app/access/mixins/organization.py new file mode 100644 index 00000000..2dbd6326 --- /dev/null +++ b/app/access/mixins/organization.py @@ -0,0 +1,410 @@ +from django.contrib.auth.models import User, Group +from access.models import Organization, Team + + +class OrganizationMixin: + """Organization Tenancy Mixin + + This class is intended to be included in **ALL** View / Viewset classes as + it contains the functions/methods required to conduct the permission + checking. + """ + + + _obj_organization: int = None + """Cached Object Organization""" + + def get_obj_organization(self, obj = None, request = None) -> Organization: + """Fetch the objects Organization + + Args: + obj (Model): Model of object + + Raises: + ValueError: When `obj` and `request` are both missing + + Returns: + Organization: Organization the object is from + None: No Organization was found + """ + + if obj is None and request is None: + + raise ValueError('Missing Parameter. obj or request must be supplied') + + + if self._obj_organization: + + return self._obj_organization + + + _obj_organization: Organization = None + + + if obj: + + _obj_organization = getattr(obj, 'organization', None) + + + if not _obj_organization: + + _obj_organization = getattr(obj, 'get_organization', lambda: None)() + + elif request: + + if getattr(request.stream, 'method', '') != 'DELETE': + + data = getattr(request, 'data', None) + + if data: + + data_organization = self.kwargs.get('organization_id', None) + + if not data_organization: + + data_organization = request.data.get('organization_id', None) + + + if not data_organization: + + data_organization = request.data.get('organization', None) + + + if data_organization: + + _obj_organization = Organization.objects.get( + pk = int( data_organization ) + ) + + + if self.get_parent_model(): # if defined is to overwrite object organization + + parent_obj = self.get_parent_obj() + + _obj_organization = parent_obj.get_organization() + + + + if _obj_organization: + + self._obj_organization = _obj_organization + + return self._obj_organization + + + + def get_parent_model(self): + """Get the Parent Model + + This function exists so that dynamic parent models can be defined. + They are defined by overriding this method. + + Returns: + Model: Parent Model + """ + + return self.parent_model + + + + def get_parent_obj(self): + """ Get the Parent Model Object + + Use in views where the the model has no organization and the organization should be fetched from the parent model. + + Requires attribute `parent_model` within the view with the value of the parent's model class + + Returns: + parent_model (Model): with PK from kwargs['pk'] + """ + + return self.parent_model.objects.get(pk=self.kwargs[self.parent_model_pk_kwarg]) + + + + def get_permission_organizations(self, permission: str ) -> list([ int ]): + """Return Organization(s) the permission belongs to + + Searches the users organizations for the required permission, if found + the organization is added to the list to return. + + Args: + permission (str): Permission to search users organizations for + + Returns: + Organizations (list): All Organizations where the permission was found. + """ + + _permission_organizations: list = [] + + for team in self.get_user_teams( self.request.user ): + + for team_permission in team.permissions.all(): + + permission_value = str( team_permission.content_type.app_label + '.' + team_permission.codename ) + + if permission_value == permission: + + _permission_organizations += [ team.organization.id ] + + + return _permission_organizations + + + _permission_required: str = None + """Cached Permissions required""" + + + def get_permission_required(self) -> str: + """ Get / Generate Permission Required + + If there is a requirement that there be custom/dynamic permissions, + this function can be safely overridden. + + Raises: + ValueError: Unable to determin the view action + + Returns: + str: Permission in format `._` + """ + + if self._permission_required: + + return self._permission_required + + + if hasattr(self, 'get_dynamic_permissions'): + + self._permission_required = self.get_dynamic_permissions() + + if type(self._permission_required) is list: + + self._permission_required = self._permission_required[0] + + return self._permission_required + + + view_action: str = None + + if( + self.action == 'create' + or getattr(self.request._stream, 'method', '') == 'POST' + ): + + view_action = 'add' + + elif ( + self.action == 'partial_update' + or self.action == 'update' + or getattr(self.request._stream, 'method', '') == 'PATCH' + or getattr(self.request._stream, 'method', '') == 'PUT' + ): + + view_action = 'change' + + elif( + self.action == 'destroy' + or getattr(self.request._stream, 'method', '') == 'DELETE' + ): + + view_action = 'delete' + + elif ( + self.action == 'list' + ): + + view_action = 'view' + + elif self.action == 'retrieve': + + view_action = 'view' + + elif self.action == 'metadata': + + view_action = 'view' + + elif self.action is None: + + return False + + + + if view_action is None: + + raise ValueError('view_action could not be defined.') + + + permission = self.model._meta.app_label + '.' + view_action + '_' + self.model._meta.model_name + + permission_required = permission + + + self._permission_required = permission_required + + return self._permission_required + + + + parent_model: str = None + """ Parent Model + + This attribute defines the parent model for the model in question. The parent model when defined + will be used as the object to obtain the permissions from. + """ + + + parent_model_pk_kwarg: str = 'pk' + """Parent Model kwarg + + This value is used to define the kwarg that is used as the parent objects primary key (pk). + """ + + + _user_organizations: list = [] + """Cached User Organizations""" + + + _user_teams: list = [] + """Cached User Teams""" + + + _user_permissions: list = [] + """Cached User User Permissions""" + + + def get_user_organizations(self, user: User) -> list([int]): + """Get the Organization the user is a part of + + Args: + user (User): User Making the request + + Returns: + list(int()): List containing the organizations the user is a part of. + """ + + if self._user_organizations and self._user_teams and self._user_permissions: + + return self._user_organizations + + + teams = Team.objects.all() + + _user_organizations: list([ int ]) = [] + + _user_teams: list([ Team ]) = [] + + _user_permissions: list([ str ]) = [] + + for group in user.groups.all(): + + team = teams.get(pk=group.id) + + if team not in _user_teams: + + _user_teams += [ team ] + + for permission in team.permissions.all(): + + permission_value = str( permission.content_type.app_label + '.' + permission.codename ) + + if permission_value not in _user_permissions: + + _user_permissions += [ permission_value ] + + + if team.organization.id not in _user_organizations: + + _user_organizations += [ team.organization.id ] + + + if len(_user_organizations) > 0: + + self._user_organizations = _user_organizations + + if len(_user_teams) > 0: + + self._user_teams = _user_teams + + if len(_user_permissions) > 0: + + self._user_permissions = _user_permissions + + + return self._user_organizations + + + + def get_user_teams(self, user: User) -> list([ Team ]): + + if not self._user_teams: + + self.get_user_organizations( user = user ) + + return self._user_teams + + + + def has_organization_permission(self, organization: int, permissions_required: list) -> bool: + """ Check if user has permission within organization. + + Args: + organization (int): Organization to check. + permissions_required (list): if doing object level permissions, pass in required permission. + + Returns: + bool: True for yes. + """ + + has_permission: bool = False + + if not organization: + + return has_permission + + from settings.models.app_settings import AppSettings + + + app_settings = AppSettings.objects.get( + owner_organization = None + ) + + for team in self.get_user_teams( user = self.request.user ): + + if( + team.organization.id == int(organization) + or getattr(app_settings.global_organization, 'id', 0) == int(organization) + ): + + for permission in team.permissions.all(): + + assembled_permission = str(permission.content_type.app_label) + '.' + str( permission.codename ) + + if assembled_permission in permissions_required: + + has_permission = True + + + return has_permission + + + + def is_member(self, organization: int) -> bool: + """Returns true if the current user is a member of the organization + + iterates over the user_organizations list and returns true if the user is a member + + Returns: + bool: _description_ + """ + + is_member: bool = False + + if organization is None: + + return False + + if int(organization) in self.get_user_organizations(self.request.user): + + is_member = True + + return is_member diff --git a/app/access/mixins/permissions.py b/app/access/mixins/permissions.py new file mode 100644 index 00000000..6853e4db --- /dev/null +++ b/app/access/mixins/permissions.py @@ -0,0 +1,306 @@ +import traceback + +from django.core.exceptions import ObjectDoesNotExist + +from rest_framework import exceptions +from rest_framework.permissions import DjangoObjectPermissions + +from access.models import TenancyObject + +from core import exceptions as centurion_exceptions + + + +class OrganizationPermissionMixin( + DjangoObjectPermissions, +): + """Organization Permission Mixin + + This class is to be used as the permission class for API `Views`/`ViewSets`. + In combination with the `OrganizationPermissionsMixin`, permission checking + will be done to ensure the user has the correct permissions to perform the + CRUD operation. + + **Note:** If the user is not authenticated, they will be denied access + globally. + + Permissions are broken down into two areas: + + - `Tenancy` Objects + + This object requires that the user have the correct permission and that + permission be assigned within the organiztion the object belongs to. + + - `Non-Tenancy` Objects. + + This object requires the the use have the correct permission assigned, + regardless of the organization the object is from. This includes objects + that have no organization. + + """ + + _is_tenancy_model: bool = None + + def is_tenancy_model(self, view) -> bool: + """Determin if the Model is a `Tenancy` Model + + Will look at the model defined within the view unless a parent + model is found. If the latter is true, the parent_model will be used to + determin if the model is a `Tenancy` model + + Args: + view (object): The View the HTTP request was mad to + + Returns: + True (bool): Model is a Tenancy Model. + False (bool): Model is not a Tenancy model. + """ + + if not self._is_tenancy_model: + + if hasattr(view, 'model'): + + self._is_tenancy_model = issubclass(view.model, TenancyObject) + + if view.get_parent_model(): + + self._is_tenancy_model = issubclass(view.get_parent_model(), TenancyObject) + + return self._is_tenancy_model + + + + def has_permission(self, request, view): + """ Check if user has the required permission + + Permission flow is as follows: + + - Un-authenticated users. Access Denied + + - Authenticated user whom make a request using wrong method. Access + Denied + + - Authenticated user who is not in same organization as object. Access + Denied + + - Authenticated user who is in same organization as object, however is + missing the correct permission. Access Denied + + Depending upon user type, they will recieve different feedback. In order + they are: + + - Non-authenticated users will **always** recieve HTTP/401 + + - Authenticated users who use an unsupported method, HTTP/405 + + - Authenticated users missing the correct permission recieve HTTP/403 + + Args: + request (object): The HTTP Request Object + view (_type_): The View/Viewset Object the request was made to + + Raises: + PermissionDenied: User does not have the required permission. + NotAuthenticated: User is not logged into Centurion. + ValueError: Could not determin the view action. + + Returns: + True (bool): User has the required permission. + False (bool): User does not have the required permission + """ + + if request.user.is_anonymous: + + raise centurion_exceptions.NotAuthenticated() + + try: + + + view.get_user_organizations( request.user ) + + has_permission_required: bool = False + + user_permissions = getattr(view, '_user_permissions', None) + + permission_required = view.get_permission_required() + + + if permission_required and user_permissions: + # No permission_required couldnt get permissions + # No user_permissions, user missing the required permission + + has_permission_required: bool = permission_required in user_permissions + + + if request.method not in view.allowed_methods: + + raise centurion_exceptions.MethodNotAllowed(method = request.method) + + + elif not has_permission_required and not request.user.is_superuser: + + raise centurion_exceptions.PermissionDenied() + + + obj_organization: Organization = view.get_obj_organization( + request = request + ) + + view_action: str = None + + if( + view.action == 'create' + and request.method == 'POST' + ): + + view_action = 'add' + + elif( + view.action == 'destroy' + and request.method == 'DELETE' + ): + + view_action = 'delete' + + obj_organization: Organization = view.get_obj_organization( + obj = view.get_object() + ) + + elif ( + view.action == 'list' + ): + + view_action = 'view' + + elif ( + view.action == 'partial_update' + and request.method == 'PATCH' + ): + + view_action = 'change' + + obj_organization: Organization = view.get_obj_organization( + obj = view.get_object() + ) + + elif ( + view.action == 'update' + and request.method == 'PUT' + ): + + view_action = 'change' + + obj_organization: Organization = view.get_obj_organization( + obj = view.get_object() + ) + + elif( + view.action == 'retrieve' + and request.method == 'GET' + ): + + view_action = 'view' + + obj_organization: Organization = view.get_obj_organization( + obj = view.get_object() + ) + + elif( + view.action == 'metadata' + and request.method == 'OPTIONS' + ): + + return True + + + if view_action is None: + + raise ValueError('view_action could not be defined.') + + + if obj_organization is None or request.user.is_superuser: + + return True + + elif obj_organization is not None: + + if view.has_organization_permission( + organization = obj_organization.id, + permissions_required = [ view.get_permission_required() ] + ): + + return True + + + except ValueError as e: + + # ToDo: This exception could be used in traces as it provides + # information as to dodgy requests. This exception is raised + # when the method does not match the view action. + + print(traceback.format_exc()) + + except centurion_exceptions.Http404 as e: + # This exception genrally means that the user is not in the same + # organization as the object as objects are filtered to users + # organizations ONLY. + + pass + + except centurion_exceptions.ObjectDoesNotExist as e: + # This exception genrally means that the user is not in the same + # organization as the object as objects are filtered to users + # organizations ONLY. + + pass + + except centurion_exceptions.PermissionDenied as e: + # This Exception will be raised after this function has returned + # False. + + pass + + + return False + + + + def has_object_permission(self, request, view, obj): + + try: + + if request.user.is_anonymous: + + return False + + + object_organization: int = getattr(view.get_obj_organization( obj = obj ), 'id', None) + + from settings.models.app_settings import AppSettings + + app_settings = AppSettings.objects.get( + owner_organization = None + ) + + if object_organization: + + if( + object_organization + in view.get_permission_organizations( view.get_permission_required() ) + or request.user.is_superuser + or getattr(app_settings.global_organization, 'id', 0) == int(object_organization) + ): + + return True + + + elif not self.is_tenancy_model( view ) or request.user.is_superuser: + + return True + + + except Exception as e: + + print(traceback.format_exc()) + + return False diff --git a/app/access/models.py b/app/access/models.py index af2a34d4..a9373033 100644 --- a/app/access/models.py +++ b/app/access/models.py @@ -11,6 +11,7 @@ from core.middleware.get_request import get_request from core.mixin.history_save import SaveHistory + class Organization(SaveHistory): class Meta: @@ -178,6 +179,16 @@ class TenancyManager(models.Manager): if request: + from settings.models.app_settings import AppSettings + + app_settings = AppSettings.objects.get( + owner_organization = None + ) + + if app_settings.global_organization: + + user_organizations += [ app_settings.global_organization.id ] + # user = request.user._wrapped if hasattr(request.user,'_wrapped') else request.user user = request.user diff --git a/app/access/tests/functional/organization/test_organization_viewset.py b/app/access/tests/functional/organization/test_organization_viewset.py index e0d433f5..d30c41eb 100644 --- a/app/access/tests/functional/organization/test_organization_viewset.py +++ b/app/access/tests/functional/organization/test_organization_viewset.py @@ -198,6 +198,17 @@ class OrganizationPermissionsAPI( TestCase ): + def test_returned_data_from_user_and_global_organizations_only(self): + """Check items returned + + This test case is a over-ride of a test case with the same name. + This model is not a tenancy model making this test not-applicable. + + Items returned from the query Must be from the users organization and + global ONLY! + """ + pass + def test_add_has_permission(self): @@ -217,7 +228,7 @@ class OrganizationPermissionsAPI( url = reverse( self.app_namespace + ':' + self.url_name + '-list' ) - client.force_login( self.super_add_user ) + client.force_login( self.add_user ) response = client.post( url, data = self.add_data ) @@ -271,6 +282,17 @@ class OrganizationPermissionsAPI( assert len(response.data['results']) == 2 + def test_add_different_organization_denied(self): + """ Check correct permission for add + + This test is a duplicate of a test case with the same name. + Organizations are not tenancy models so this test does nothing of value + + attempt to add as user from different organization + """ + + pass + class OrganizationViewSet( ViewSetBase, diff --git a/app/access/tests/functional/team/test_team_permission_viewset.py b/app/access/tests/functional/team/test_team_permission_viewset.py index 91400a3b..70afb2f5 100644 --- a/app/access/tests/functional/team/test_team_permission_viewset.py +++ b/app/access/tests/functional/team/test_team_permission_viewset.py @@ -191,7 +191,17 @@ class TeamPermissionsAPI( TestCase, ): - pass + + def test_returned_data_from_user_and_global_organizations_only(self): + """Check items returned + + This test case is a over-ride of a test case with the same name. + This model is not a tenancy model making this test not-applicable. + + Items returned from the query Must be from the users organization and + global ONLY! + """ + pass diff --git a/app/access/tests/functional/team_user/test_team_user_permission_viewset.py b/app/access/tests/functional/team_user/test_team_user_permission_viewset.py index 2dad2d8d..2d93321d 100644 --- a/app/access/tests/functional/team_user/test_team_user_permission_viewset.py +++ b/app/access/tests/functional/team_user/test_team_user_permission_viewset.py @@ -205,6 +205,21 @@ class TeamUserPermissionsAPI( + def test_returned_data_from_user_and_global_organizations_only(self): + """Check items returned + + This test case is a over-ride of a test case with the same name. + This model is not a tenancy model making this test not-applicable. + + Items returned from the query Must be from the users organization and + global ONLY! + """ + pass + + + + + class TeamUserViewSet( ViewSetBase, SerializersTestCases, diff --git a/app/access/tests/unit/mixin/test_permission.py b/app/access/tests/unit/mixin/test_permission.py new file mode 100644 index 00000000..f97aae56 --- /dev/null +++ b/app/access/tests/unit/mixin/test_permission.py @@ -0,0 +1,6787 @@ +import pytest +from unittest.mock import Mock, patch + +from django.contrib.auth.models import User, AnonymousUser +from django.contrib.contenttypes.models import ContentType +from django.test import TestCase + +from rest_framework.generics import GenericAPIView + +from api.viewsets.common import ModelViewSet + +from access.mixins.organization import OrganizationMixin +from access.mixins.permissions import OrganizationPermissionMixin +from access.models import Organization, Team, TeamUsers, Permission + +from core import exceptions as centurion_exceptions +from core.models.manufacturer import Manufacturer + + + +class MyMockView( + ModelViewSet +): + + class MockRequest: + + class MockStream: + + method: str = None + + def __init__(self, method: str): + + self.method = method + + data: dict = None + + method: str = None + + # query_params: dict = {} + + stream: MockStream = None + + _stream: MockStream = None + + + def __init__(self, data: dict, method: str, user: User): + + self.data = data + + self.method = method + + # self._stream = self.MockStream( method = method ) + + # self.stream = self._stream + + self.user = user + + + action: str = None + """create, destroy, list, metadata, retrieve, partial_updata, update""" + + allowed_methods: list = [] + + kwargs: dict = None + + model = None + + mocked_object = None + + request: MockRequest = None + + def __init__(self, action:str, kwargs: dict, method: str, model, obj, user: User, data:dict = None): + + self.action = action + + self.allowed_methods += [ method ] + + self.kwargs = kwargs + + self.model = model + + self.mocked_object = obj + + self.request = self.MockRequest( + data = data, + method = method, + user = user + ) + + + + +class OrganizationPermissionSetup: + + + model = Manufacturer + + + @classmethod + def setUpTestData(self): + """Setup Test + + 1. + """ + + self.organization = Organization.objects.create(name='test_org1', model_notes='random text') + + self.organization_two = Organization.objects.create(name='test_org2', model_notes='random text') + + self.organization_three = Organization.objects.create(name='test_org3', model_notes='random text') + + + + add_permissions = Permission.objects.get( + codename = 'add_' + self.model._meta.model_name, + content_type = ContentType.objects.get( + app_label = self.model._meta.app_label, + model = self.model._meta.model_name, + ) + ) + + add_team = Team.objects.create( + team_name = 'add_team', + organization = self.organization, + ) + + add_team.permissions.set([add_permissions]) + + self.add_user = User.objects.create_user(username="test_user_add", password="password") + teamuser = TeamUsers.objects.create( + team = add_team, + user = self.add_user + ) + + + add_team_two = Team.objects.create( + team_name = 'add_team', + organization = self.organization_two, + ) + + add_team_two.permissions.set([add_permissions]) + + self.add_user_two = User.objects.create_user(username="test_user_add_two", password="password") + teamuser = TeamUsers.objects.create( + team = add_team_two, + user = self.add_user_two + ) + + + + + + + + + + change_permissions = Permission.objects.get( + codename = 'change_' + self.model._meta.model_name, + content_type = ContentType.objects.get( + app_label = self.model._meta.app_label, + model = self.model._meta.model_name, + ) + ) + + change_team = Team.objects.create( + team_name = 'change_team', + organization = self.organization, + ) + + change_team.permissions.set([change_permissions]) + + self.change_user = User.objects.create_user(username="test_user_change", password="password") + teamuser = TeamUsers.objects.create( + team = change_team, + user = self.change_user + ) + + + + change_team_two = Team.objects.create( + team_name = 'change_team_two', + organization = self.organization_two, + ) + + change_team_two.permissions.set([change_permissions]) + + self.change_user_two = User.objects.create_user(username="test_user_change_two", password="password") + teamuser = TeamUsers.objects.create( + team = change_team_two, + user = self.change_user_two + ) + + + + + + delete_permissions = Permission.objects.get( + codename = 'delete_' + self.model._meta.model_name, + content_type = ContentType.objects.get( + app_label = self.model._meta.app_label, + model = self.model._meta.model_name, + ) + ) + + delete_team = Team.objects.create( + team_name = 'delete_team', + organization = self.organization, + ) + + delete_team.permissions.set([delete_permissions]) + + self.delete_user = User.objects.create_user(username="test_user_delete", password="password") + teamuser = TeamUsers.objects.create( + team = delete_team, + user = self.delete_user + ) + + + delete_team_two = Team.objects.create( + team_name = 'delete_team', + organization = self.organization_two, + ) + + delete_team_two.permissions.set([delete_permissions]) + + self.delete_user_two = User.objects.create_user(username="test_user_delete_two", password="password") + teamuser = TeamUsers.objects.create( + team = delete_team_two, + user = self.delete_user_two + ) + + + + + view_permissions = Permission.objects.get( + codename = 'view_' + self.model._meta.model_name, + content_type = ContentType.objects.get( + app_label = self.model._meta.app_label, + model = self.model._meta.model_name, + ) + ) + + view_team = Team.objects.create( + team_name = 'view_team', + organization = self.organization, + ) + + view_team.permissions.set([view_permissions]) + + + self.view_user = User.objects.create_user(username="test_user_view", password="password") + teamuser = TeamUsers.objects.create( + team = view_team, + user = self.view_user + ) + + + view_team_two = Team.objects.create( + team_name = 'view_team_two', + organization = self.organization_two, + ) + + view_team_two.permissions.set([view_permissions]) + + + self.view_user_two = User.objects.create_user(username="test_user_view_two", password="password") + teamuser = TeamUsers.objects.create( + team = view_team_two, + user = self.view_user_two + ) + + + + + + + + + self.obj = self.model.objects.create( + organization = self.organization, + name = 'man 1', + ) + + + +class HasPermissionCommon( + OrganizationPermissionSetup, +): + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_has_permission(self, get_object): + + get_object.return_value = self.obj + + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = self.http_method, + model = self.model, + obj = obj, + user = self.test_user, + ) + + assert OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_has_permission_wrong_method_head(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'HEAD', + model = self.model, + obj = obj, + user = self.test_user, + ) + + assert OrganizationPermissionMixin().has_permission(request = view.request, view = view) is False + + + @patch.object(GenericAPIView, 'get_object') + def test_action_has_permission_wrong_method_head_get_object_not_called(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'HEAD', + model = self.model, + obj = obj, + user = self.test_user, + ) + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + assert get_object.called == False + + +class HasPermission( + HasPermissionCommon, +): + + + @patch.object(GenericAPIView, 'get_object') + def test_action_has_permission_function_get_object_called_once(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = self.http_method, + model = self.model, + obj = obj, + user = self.test_user, + ) + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + assert get_object.call_count == 1 + + + + +class HasPermissionWrongMethodOptions: + + + @patch.object(GenericAPIView, 'get_object') + def test_action_has_permission_wrong_method_options(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'OPTIONS', + model = self.model, + obj = obj, + user = self.test_user, + ) + + assert OrganizationPermissionMixin().has_permission(request = view.request, view = view) is False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_has_permission_wrong_method_options_get_object_not_called(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'OPTIONS', + model = self.model, + obj = obj, + user = self.test_user, + ) + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + assert get_object.called == False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_has_permission_wrong_method_options_raised_method_not_allowed(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'OPTIONS', + model = self.model, + obj = obj, + user = self.test_user, + ) + + view.allowed_methods = [ + self.http_method + ] + + with pytest.raises( centurion_exceptions.MethodNotAllowed ) as ex: + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + + + +class HasPermissionWrongMethodDelete: + + + @patch.object(GenericAPIView, 'get_object') + def test_action_has_permission_wrong_method_delete(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'DELETE', + model = self.model, + obj = obj, + user = self.test_user, + ) + + assert OrganizationPermissionMixin().has_permission(request = view.request, view = view) is False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_has_permission_wrong_method_delete_get_object_not_called(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'DELETE', + model = self.model, + obj = obj, + user = self.test_user, + ) + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + assert get_object.called == False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_has_permission_wrong_method_delete_raised_method_not_allowed(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'DELETE', + model = self.model, + obj = obj, + user = self.test_user, + ) + + view.allowed_methods = [ + self.http_method + ] + + with pytest.raises( centurion_exceptions.MethodNotAllowed ) as ex: + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + + + +class HasPermissionWrongMethodGet: + + + @patch.object(GenericAPIView, 'get_object') + def test_action_has_permission_wrong_method_get(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'GET', + model = self.model, + obj = obj, + user = self.test_user, + ) + + assert OrganizationPermissionMixin().has_permission(request = view.request, view = view) is False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_has_permission_wrong_method_get_get_object_not_called(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'GET', + model = self.model, + obj = obj, + user = self.test_user, + ) + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + assert get_object.called == False + + + @patch.object(GenericAPIView, 'get_object') + def test_action_has_permission_wrong_method_get_raised_method_not_allowed(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'GET', + model = self.model, + obj = obj, + user = self.test_user, + ) + + view.allowed_methods = [ + self.http_method + ] + + with pytest.raises( centurion_exceptions.MethodNotAllowed ) as ex: + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + + + +class HasPermissionWrongMethodPatch: + + + @patch.object(GenericAPIView, 'get_object') + def test_action_has_permission_wrong_method_patch(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'PATCH', + model = self.model, + obj = obj, + user = self.test_user, + ) + + assert OrganizationPermissionMixin().has_permission(request = view.request, view = view) is False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_has_permission_wrong_method_patch_get_object_not_called(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'PATCH', + model = self.model, + obj = obj, + user = self.test_user, + ) + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + assert get_object.called == False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_has_permission_wrong_method_patch_raised_method_not_allowed(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'PATCH', + model = self.model, + obj = obj, + user = self.test_user, + ) + + view.allowed_methods = [ + self.http_method + ] + + with pytest.raises( centurion_exceptions.MethodNotAllowed ) as ex: + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + + + +class HasPermissionWrongMethodPost: + + @patch.object(GenericAPIView, 'get_object') + def test_action_has_permission_wrong_method_post(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'POST', + model = self.model, + obj = obj, + user = self.test_user, + ) + + assert OrganizationPermissionMixin().has_permission(request = view.request, view = view) is False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_has_permission_wrong_method_post_get_object_not_called(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'POST', + model = self.model, + obj = obj, + user = self.test_user, + ) + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + assert get_object.called == False + + + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_has_permission_wrong_method_post_raised_method_not_allowed(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'POST', + model = self.model, + obj = obj, + user = self.test_user, + ) + + view.allowed_methods = [ + self.http_method + ] + + with pytest.raises( centurion_exceptions.MethodNotAllowed ) as ex: + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + + + + +class HasPermissionWrongMethodPut: + + + @patch.object(GenericAPIView, 'get_object') + def test_action_has_permission_wrong_method_put(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'PUT', + model = self.model, + obj = obj, + user = self.test_user, + ) + + assert OrganizationPermissionMixin().has_permission(request = view.request, view = view) is False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_has_permission_wrong_method_put_get_object_not_called(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'PUT', + model = self.model, + obj = obj, + user = self.test_user, + ) + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + assert get_object.called == False + + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_has_permission_wrong_method_put_raised_method_not_allowed(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'PUT', + model = self.model, + obj = obj, + user = self.test_user, + ) + + view.allowed_methods = [ + self.http_method + ] + + with pytest.raises( centurion_exceptions.MethodNotAllowed ) as ex: + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + + + +class HasPermissionDifferentOrganizationCommon: + + @patch.object(GenericAPIView, 'get_object') + def test_action_has_permission_different_org(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = self.http_method, + model = self.model, + obj = obj, + user = self.test_user_two, + ) + + assert OrganizationPermissionMixin().has_permission(request = view.request, view = view) is False + + + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_has_permission_different_org_wrong_method_head(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'HEAD', + model = self.model, + obj = obj, + user = self.test_user_two, + ) + + assert OrganizationPermissionMixin().has_permission(request = view.request, view = view) is False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_has_permission_different_org_wrong_method_head_get_object_not_called(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'HEAD', + model = self.model, + obj = obj, + user = self.test_user_two, + ) + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + assert get_object.called == False + + + +class HasPermissionDifferentOrganization( + HasPermissionDifferentOrganizationCommon, +): + + @patch.object(GenericAPIView, 'get_object') + def test_action_has_permission_different_org_get_object_called_once(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = self.http_method, + model = self.model, + obj = obj, + user = self.test_user_two, + ) + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + assert get_object.call_count == 1 + + + + + +class HasPermissionDifferentOrganizationWrongMethodDelete: + + + @patch.object(GenericAPIView, 'get_object') + def test_action_has_permission_different_org_wrong_method_delete(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'DELETE', + model = self.model, + obj = obj, + user = self.test_user_two, + ) + + assert OrganizationPermissionMixin().has_permission(request = view.request, view = view) is False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_has_permission_different_org_wrong_method_delete_get_object_not_called(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'DELETE', + model = self.model, + obj = obj, + user = self.test_user_two, + ) + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + assert get_object.called == False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_has_permission_different_org_wrong_method_delete_raised_method_not_allowed(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'DELETE', + model = self.model, + obj = obj, + user = self.test_user_two, + ) + + view.allowed_methods = [ + self.http_method + ] + + with pytest.raises( centurion_exceptions.MethodNotAllowed ) as ex: + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + + + + + +class HasPermissionDifferentOrganizationWrongMethodGet: + + + @patch.object(GenericAPIView, 'get_object') + def test_action_has_permission_different_org_wrong_method_Get(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'GET', + model = self.model, + obj = obj, + user = self.test_user_two, + ) + + assert OrganizationPermissionMixin().has_permission(request = view.request, view = view) is False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_has_permission_different_org_wrong_method_Get_get_object_not_called(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'GET', + model = self.model, + obj = obj, + user = self.test_user_two, + ) + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + assert get_object.called == False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_has_permission_different_org_wrong_method_Get_raised_method_not_allowed(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'GET', + model = self.model, + obj = obj, + user = self.test_user_two, + ) + + view.allowed_methods = [ + self.http_method + ] + + with pytest.raises( centurion_exceptions.MethodNotAllowed ) as ex: + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + + +class HasPermissionDifferentOrganizationWrongMethodOptions: + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_has_permission_different_org_wrong_method_options(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'OPTIONS', + model = self.model, + obj = obj, + user = self.test_user_two, + ) + + assert OrganizationPermissionMixin().has_permission(request = view.request, view = view) is False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_has_permission_different_org_wrong_method_options_get_object_not_called(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'OPTIONS', + model = self.model, + obj = obj, + user = self.test_user_two, + ) + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + assert get_object.called == False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_has_permission_different_org_wrong_method_options_raised_method_not_allowed(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'OPTIONS', + model = self.model, + obj = obj, + user = self.test_user_two, + ) + + view.allowed_methods = [ + self.http_method + ] + + with pytest.raises( centurion_exceptions.MethodNotAllowed ) as ex: + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + + +class HasPermissionDifferentOrganizationWrongMethodPatch: + + + @patch.object(GenericAPIView, 'get_object') + def test_action_has_permission_different_org_wrong_method_patch(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'PATCH', + model = self.model, + obj = obj, + user = self.test_user_two, + ) + + assert OrganizationPermissionMixin().has_permission(request = view.request, view = view) is False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_has_permission_different_org_wrong_method_patch_get_object_not_called(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'PATCH', + model = self.model, + obj = obj, + user = self.test_user_two, + ) + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + assert get_object.called == False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_has_permission_different_org_wrong_method_patch_raised_method_not_allowed(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'PATCH', + model = self.model, + obj = obj, + user = self.test_user_two, + ) + + view.allowed_methods = [ + self.http_method + ] + + with pytest.raises( centurion_exceptions.MethodNotAllowed ) as ex: + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + + +class HasPermissionDifferentOrganizationWrongMethodPost: + + + @patch.object(GenericAPIView, 'get_object') + def test_action_has_permission_different_org_wrong_method_post(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'POST', + model = self.model, + obj = obj, + user = self.test_user_two, + ) + + assert OrganizationPermissionMixin().has_permission(request = view.request, view = view) is False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_has_permission_different_org_wrong_method_post_get_object_not_called(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'POST', + model = self.model, + obj = obj, + user = self.test_user_two, + ) + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + assert get_object.called == False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_has_permission_different_org_wrong_method_post_raised_method_not_allowed(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'POST', + model = self.model, + obj = obj, + user = self.test_user_two, + ) + + view.allowed_methods = [ + self.http_method + ] + + with pytest.raises( centurion_exceptions.MethodNotAllowed ) as ex: + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + + +class HasPermissionDifferentOrganizationWrongMethodPut: + + + @patch.object(GenericAPIView, 'get_object') + def test_action_has_permission_different_org_wrong_method_put(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'PUT', + model = self.model, + obj = obj, + user = self.test_user_two, + ) + + assert OrganizationPermissionMixin().has_permission(request = view.request, view = view) is False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_has_permission_different_org_wrong_method_put_get_object_not_called(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'PUT', + model = self.model, + obj = obj, + user = self.test_user_two, + ) + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + assert get_object.called == False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_has_permission_different_org_wrong_method_put_raised_method_not_allowed(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'PUT', + model = self.model, + obj = obj, + user = self.test_user_two, + ) + + view.allowed_methods = [ + self.http_method + ] + + with pytest.raises( centurion_exceptions.MethodNotAllowed ) as ex: + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + + + + + + +class ActionDeniedAddPermission: + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_add_permission_denied(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = self.http_method, + model = self.model, + obj = obj, + user = self.add_user, + ) + + assert OrganizationPermissionMixin().has_permission(request = view.request, view = view) is False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_add_permission_denied_get_object_not_called(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = self.http_method, + model = self.model, + obj = obj, + user = self.add_user, + ) + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + assert get_object.called == False + + + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_add_permission_denied_wrong_method_head(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'HEAD', + model = self.model, + obj = obj, + user = self.add_user, + ) + + assert OrganizationPermissionMixin().has_permission(request = view.request, view = view) is False + + + @patch.object(GenericAPIView, 'get_object') + def test_action_add_permission_denied_wrong_method_head_get_object_not_called(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'HEAD', + model = self.model, + obj = obj, + user = self.add_user, + ) + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + assert get_object.called == False + + + +class ActionDeniedAddPermissionWrongMethodDelete: + + @patch.object(GenericAPIView, 'get_object') + def test_action_add_permission_denied_wrong_method_delete(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'DELETE', + model = self.model, + obj = obj, + user = self.add_user, + ) + + assert OrganizationPermissionMixin().has_permission(request = view.request, view = view) is False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_add_permission_denied_wrong_method_delete_get_object_not_called(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'DELETE', + model = self.model, + obj = obj, + user = self.add_user, + ) + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + assert get_object.called == False + + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_add_permission_denied_wrong_method_delete_raised_method_not_allowed(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'DELETE', + model = self.model, + obj = obj, + user = self.add_user, + ) + + view.allowed_methods = [ + self.http_method + ] + + with pytest.raises( centurion_exceptions.MethodNotAllowed ) as ex: + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + + + +class ActionDeniedAddPermissionWrongMethodGet: + + @patch.object(GenericAPIView, 'get_object') + def test_action_add_permission_denied_wrong_method_get(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'GET', + model = self.model, + obj = obj, + user = self.add_user, + ) + + assert OrganizationPermissionMixin().has_permission(request = view.request, view = view) is False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_add_permission_denied_wrong_method_get_get_object_not_called(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'GET', + model = self.model, + obj = obj, + user = self.add_user, + ) + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + assert get_object.called == False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_add_permission_denied_wrong_method_get_raised_method_not_allowed(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'GET', + model = self.model, + obj = obj, + user = self.add_user, + ) + + view.allowed_methods = [ + self.http_method + ] + + with pytest.raises( centurion_exceptions.MethodNotAllowed ) as ex: + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + + +class ActionDeniedAddPermissionWrongMethodOptions: + + + @patch.object(GenericAPIView, 'get_object') + def test_action_add_permission_denied_wrong_method_options(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'OPTIONS', + model = self.model, + obj = obj, + user = self.add_user, + ) + + assert OrganizationPermissionMixin().has_permission(request = view.request, view = view) is False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_add_permission_denied_wrong_method_options_get_object_not_called(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'OPTIONS', + model = self.model, + obj = obj, + user = self.add_user, + ) + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + assert get_object.called == False + + + @patch.object(GenericAPIView, 'get_object') + def test_action_add_permission_denied_wrong_method_options_raised_method_not_allowed(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'OPTIONS', + model = self.model, + obj = obj, + user = self.add_user, + ) + + view.allowed_methods = [ + self.http_method + ] + + with pytest.raises( centurion_exceptions.MethodNotAllowed ) as ex: + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + + +class ActionDeniedAddPermissionWrongMethodPatch: + + + @patch.object(GenericAPIView, 'get_object') + def test_action_add_permission_denied_wrong_method_patch(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'PATCH', + model = self.model, + obj = obj, + user = self.add_user, + ) + + assert OrganizationPermissionMixin().has_permission(request = view.request, view = view) is False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_add_permission_denied_wrong_method_patch_get_object_not_called(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'PATCH', + model = self.model, + obj = obj, + user = self.add_user, + ) + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + assert get_object.called == False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_add_permission_denied_wrong_method_patch_raised_method_not_allowed(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'PATCH', + model = self.model, + obj = obj, + user = self.add_user, + ) + + view.allowed_methods = [ + self.http_method + ] + + with pytest.raises( centurion_exceptions.MethodNotAllowed ) as ex: + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + + +class ActionDeniedAddPermissionWrongMethodPost: + + + @patch.object(GenericAPIView, 'get_object') + def test_action_add_permission_denied_wrong_method_post(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'POST', + model = self.model, + obj = obj, + user = self.add_user, + ) + + assert OrganizationPermissionMixin().has_permission(request = view.request, view = view) is False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_add_permission_denied_wrong_method_post_get_object_not_called(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'POST', + model = self.model, + obj = obj, + user = self.add_user, + ) + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + assert get_object.called == False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_add_permission_denied_wrong_method_post_raised_method_not_allowed(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'POST', + model = self.model, + obj = obj, + user = self.add_user, + ) + + view.allowed_methods = [ + self.http_method + ] + + with pytest.raises( centurion_exceptions.MethodNotAllowed ) as ex: + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + + +class ActionDeniedAddPermissionWrongMethodPut: + + @patch.object(GenericAPIView, 'get_object') + def test_action_add_permission_denied_wrong_method_put(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'PUT', + model = self.model, + obj = obj, + user = self.add_user, + ) + + assert OrganizationPermissionMixin().has_permission(request = view.request, view = view) is False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_add_permission_denied_wrong_method_put_raised_method_not_allowed(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'PUT', + model = self.model, + obj = obj, + user = self.add_user, + ) + + view.allowed_methods = [ + self.http_method + ] + + with pytest.raises( centurion_exceptions.MethodNotAllowed ) as ex: + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + + + +class ActionDeniedChangePermission: + + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_change_permission_denied(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = self.http_method, + model = self.model, + obj = obj, + user = self.change_user, + ) + + assert OrganizationPermissionMixin().has_permission(request = view.request, view = view) is False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_change_permission_denied_get_object_not_called(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = self.http_method, + model = self.model, + obj = obj, + user = self.change_user, + ) + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + assert get_object.called == False + + + + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_change_permission_denied_wrong_method_head(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'HEAD', + model = self.model, + obj = obj, + user = self.change_user, + ) + + assert OrganizationPermissionMixin().has_permission(request = view.request, view = view) is False + + + @patch.object(GenericAPIView, 'get_object') + def test_action_change_permission_denied_wrong_method_head_get_object_not_called(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'HEAD', + model = self.model, + obj = obj, + user = self.change_user, + ) + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + assert get_object.called == False + + + + + + +class ActionDeniedChangePermissionWrongMethodDelete: + + + @patch.object(GenericAPIView, 'get_object') + def test_action_change_permission_denied_wrong_method_delete(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'DELETE', + model = self.model, + obj = obj, + user = self.change_user, + ) + + assert OrganizationPermissionMixin().has_permission(request = view.request, view = view) is False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_change_permission_denied_wrong_method_delete_get_object_not_called(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'DELETE', + model = self.model, + obj = obj, + user = self.change_user, + ) + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + assert get_object.called == False + + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_change_permission_denied_wrong_method_delete_raised_method_not_allowed(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'DELETE', + model = self.model, + obj = obj, + user = self.change_user, + ) + + view.allowed_methods = [ + self.http_method + ] + + with pytest.raises( centurion_exceptions.MethodNotAllowed ) as ex: + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + + + + + +class ActionDeniedChangePermissionWrongMethodGet: + + + @patch.object(GenericAPIView, 'get_object') + def test_action_change_permission_denied_wrong_method_get(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'GET', + model = self.model, + obj = obj, + user = self.change_user, + ) + + assert OrganizationPermissionMixin().has_permission(request = view.request, view = view) is False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_change_permission_denied_wrong_method_get_get_object_not_called(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'GET', + model = self.model, + obj = obj, + user = self.change_user, + ) + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + assert get_object.called == False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_change_permission_denied_wrong_method_get_raised_method_not_allowed(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'GET', + model = self.model, + obj = obj, + user = self.change_user, + ) + + view.allowed_methods = [ + self.http_method + ] + + with pytest.raises( centurion_exceptions.MethodNotAllowed ) as ex: + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + + +class ActionDeniedChangePermissionWrongMethodOptions: + + + @patch.object(GenericAPIView, 'get_object') + def test_action_change_permission_denied_wrong_method_options(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'OPTIONS', + model = self.model, + obj = obj, + user = self.change_user, + ) + + assert OrganizationPermissionMixin().has_permission(request = view.request, view = view) is False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_change_permission_denied_wrong_method_options_get_object_not_called(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'OPTIONS', + model = self.model, + obj = obj, + user = self.change_user, + ) + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + assert get_object.called == False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_change_permission_denied_wrong_method_options_raised_method_not_allowed(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'OPTIONS', + model = self.model, + obj = obj, + user = self.change_user, + ) + + view.allowed_methods = [ + self.http_method + ] + + with pytest.raises( centurion_exceptions.MethodNotAllowed ) as ex: + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + + +class ActionDeniedChangePermissionWrongMethodPatch: + + @patch.object(GenericAPIView, 'get_object') + def test_action_change_permission_denied_wrong_method_patch(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'PATCH', + model = self.model, + obj = obj, + user = self.change_user, + ) + + assert OrganizationPermissionMixin().has_permission(request = view.request, view = view) is False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_change_permission_denied_wrong_method_patch_get_object_not_called(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'PATCH', + model = self.model, + obj = obj, + user = self.change_user, + ) + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + assert get_object.called == False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_change_permission_denied_wrong_method_patch_raised_method_not_allowed(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'PATCH', + model = self.model, + obj = obj, + user = self.change_user, + ) + + view.allowed_methods = [ + self.http_method + ] + + with pytest.raises( centurion_exceptions.MethodNotAllowed ) as ex: + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + + +class ActionDeniedChangePermissionWrongMethodPost: + + + @patch.object(GenericAPIView, 'get_object') + def test_action_change_permission_denied_wrong_method_post(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'POST', + model = self.model, + obj = obj, + user = self.change_user, + ) + + assert OrganizationPermissionMixin().has_permission(request = view.request, view = view) is False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_change_permission_denied_wrong_method_post_get_object_not_called(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'POST', + model = self.model, + obj = obj, + user = self.change_user, + ) + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + assert get_object.called == False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_change_permission_denied_wrong_method_post_raised_method_not_allowed(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'POST', + model = self.model, + obj = obj, + user = self.change_user, + ) + + view.allowed_methods = [ + self.http_method + ] + + with pytest.raises( centurion_exceptions.MethodNotAllowed ) as ex: + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + + +class ActionDeniedChangePermissionWrongMethodPut: + + @patch.object(GenericAPIView, 'get_object') + def test_action_change_permission_denied_wrong_method_put(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'PUT', + model = self.model, + obj = obj, + user = self.change_user, + ) + + assert OrganizationPermissionMixin().has_permission(request = view.request, view = view) is False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_change_permission_denied_wrong_method_put_get_object_not_called(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'PUT', + model = self.model, + obj = obj, + user = self.change_user, + ) + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + assert get_object.called == False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_change_permission_denied_wrong_method_put_raised_method_not_allowed(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'PUT', + model = self.model, + obj = obj, + user = self.change_user, + ) + + view.allowed_methods = [ + self.http_method + ] + + with pytest.raises( centurion_exceptions.MethodNotAllowed ) as ex: + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + + + + + + + +class ActionDeniedDeletePermission: + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_delete_permission_denied(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = self.http_method, + model = self.model, + obj = obj, + user = self.delete_user, + ) + + assert OrganizationPermissionMixin().has_permission(request = view.request, view = view) is False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_delete_permission_denied_get_object_not_called(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = self.http_method, + model = self.model, + obj = obj, + user = self.delete_user, + ) + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + assert get_object.called == False + + + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_delete_permission_denied_wrong_method_head(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'HEAD', + model = self.model, + obj = obj, + user = self.delete_user, + ) + + assert OrganizationPermissionMixin().has_permission(request = view.request, view = view) is False + + + @patch.object(GenericAPIView, 'get_object') + def test_action_delete_permission_denied_wrong_method_head_get_object_not_called(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'HEAD', + model = self.model, + obj = obj, + user = self.delete_user, + ) + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + assert get_object.called == False + + + +class ActionDeniedDeletePermissionWrongMethodDelete: + + + @patch.object(GenericAPIView, 'get_object') + def test_action_delete_permission_denied_wrong_method_delete(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'DELETE', + model = self.model, + obj = obj, + user = self.delete_user, + ) + + assert OrganizationPermissionMixin().has_permission(request = view.request, view = view) is False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_delete_permission_denied_wrong_method_delete_get_object_not_called(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'DELETE', + model = self.model, + obj = obj, + user = self.delete_user, + ) + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + assert get_object.called == False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_delete_permission_denied_wrong_method_delete_raised_method_not_allowed(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'DELETE', + model = self.model, + obj = obj, + user = self.delete_user, + ) + + view.allowed_methods = [ + self.http_method + ] + + with pytest.raises( centurion_exceptions.MethodNotAllowed ) as ex: + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + + + + +class ActionDeniedDeletePermissionWrongMethodGet: + + + @patch.object(GenericAPIView, 'get_object') + def test_action_delete_permission_denied_wrong_method_get(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'GET', + model = self.model, + obj = obj, + user = self.delete_user, + ) + + assert OrganizationPermissionMixin().has_permission(request = view.request, view = view) is False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_delete_permission_denied_wrong_method_get_get_object_not_called(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'GET', + model = self.model, + obj = obj, + user = self.delete_user, + ) + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + assert get_object.called == False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_delete_permission_denied_wrong_method_get_raised_method_not_allowed(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'GET', + model = self.model, + obj = obj, + user = self.delete_user, + ) + + view.allowed_methods = [ + self.http_method + ] + + with pytest.raises( centurion_exceptions.MethodNotAllowed ) as ex: + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + + +class ActionDeniedDeletePermissionWrongMethodOptions: + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_delete_permission_denied_wrong_method_options(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'OPTIONS', + model = self.model, + obj = obj, + user = self.delete_user, + ) + + assert OrganizationPermissionMixin().has_permission(request = view.request, view = view) is False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_delete_permission_denied_wrong_method_options_get_object_not_called(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'OPTIONS', + model = self.model, + obj = obj, + user = self.delete_user, + ) + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + assert get_object.called == False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_delete_permission_denied_wrong_method_options_raised_method_not_allowed(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'OPTIONS', + model = self.model, + obj = obj, + user = self.delete_user, + ) + + view.allowed_methods = [ + self.http_method + ] + + with pytest.raises( centurion_exceptions.MethodNotAllowed ) as ex: + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + + + + +class ActionDeniedDeletePermissionWrongMethodPatch: + + + @patch.object(GenericAPIView, 'get_object') + def test_action_delete_permission_denied_wrong_method_patch(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'PATCH', + model = self.model, + obj = obj, + user = self.delete_user, + ) + + assert OrganizationPermissionMixin().has_permission(request = view.request, view = view) is False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_delete_permission_denied_wrong_method_patch_get_object_not_called(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'PATCH', + model = self.model, + obj = obj, + user = self.delete_user, + ) + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + assert get_object.called == False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_delete_permission_denied_wrong_method_patch_raised_method_not_allowed(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'PATCH', + model = self.model, + obj = obj, + user = self.delete_user, + ) + + view.allowed_methods = [ + self.http_method + ] + + with pytest.raises( centurion_exceptions.MethodNotAllowed ) as ex: + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + + +class ActionDeniedDeletePermissionWrongMethodPost: + + + @patch.object(GenericAPIView, 'get_object') + def test_action_delete_permission_denied_wrong_method_post(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'POST', + model = self.model, + obj = obj, + user = self.delete_user, + ) + + assert OrganizationPermissionMixin().has_permission(request = view.request, view = view) is False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_delete_permission_denied_wrong_method_post_get_object_not_called(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'POST', + model = self.model, + obj = obj, + user = self.delete_user, + ) + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + assert get_object.called == False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_delete_permission_denied_wrong_method_post_raised_method_not_allowed(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'POST', + model = self.model, + obj = obj, + user = self.delete_user, + ) + + view.allowed_methods = [ + self.http_method + ] + + with pytest.raises( centurion_exceptions.MethodNotAllowed ) as ex: + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + + +class ActionDeniedDeletePermissionWrongMethodPut: + + @patch.object(GenericAPIView, 'get_object') + def test_action_delete_permission_denied_wrong_method_put(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'PUT', + model = self.model, + obj = obj, + user = self.delete_user, + ) + + assert OrganizationPermissionMixin().has_permission(request = view.request, view = view) is False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_delete_permission_denied_wrong_method_put_get_object_not_called(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'PUT', + model = self.model, + obj = obj, + user = self.delete_user, + ) + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + assert get_object.called == False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_delete_permission_denied_wrong_method_put_raised_method_not_allowed(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'PUT', + model = self.model, + obj = obj, + user = self.delete_user, + ) + + view.allowed_methods = [ + self.http_method + ] + + with pytest.raises( centurion_exceptions.MethodNotAllowed ) as ex: + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + + + + + + + +class ActionDeniedViewPermission: + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_view_permission_denied(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = self.http_method, + model = self.model, + obj = obj, + user = self.view_user, + ) + + assert OrganizationPermissionMixin().has_permission(request = view.request, view = view) is False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_view_permission_denied_get_object_not_called(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = self.http_method, + model = self.model, + obj = obj, + user = self.view_user, + ) + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + assert get_object.called == False + + + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_view_permission_denied_wrong_method_head(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'HEAD', + model = self.model, + obj = obj, + user = self.view_user, + ) + + assert OrganizationPermissionMixin().has_permission(request = view.request, view = view) is False + + + @patch.object(GenericAPIView, 'get_object') + def test_action_view_permission_denied_wrong_method_head_get_object_not_called(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'HEAD', + model = self.model, + obj = obj, + user = self.view_user, + ) + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + assert get_object.called == False + + + +class ActionDeniedViewPermissionWrongMethodOptions: + + @patch.object(GenericAPIView, 'get_object') + def test_action_view_permission_denied_wrong_method_options(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'OPTIONS', + model = self.model, + obj = obj, + user = self.view_user, + ) + + assert OrganizationPermissionMixin().has_permission(request = view.request, view = view) is False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_view_permission_denied_wrong_method_options_get_object_not_called(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'OPTIONS', + model = self.model, + obj = obj, + user = self.view_user, + ) + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + assert get_object.called == False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_view_permission_denied_wrong_method_options_raised_method_not_allowed(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'OPTIONS', + model = self.model, + obj = obj, + user = self.view_user, + ) + + view.allowed_methods = [ + self.http_method + ] + + with pytest.raises( centurion_exceptions.MethodNotAllowed ) as ex: + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + + +class ActionDeniedViewPermissionWrongMethodDelete: + + @patch.object(GenericAPIView, 'get_object') + def test_action_view_permission_denied_wrong_method_delete(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'DELETE', + model = self.model, + obj = obj, + user = self.view_user, + ) + + assert OrganizationPermissionMixin().has_permission(request = view.request, view = view) is False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_view_permission_denied_wrong_method_delete_get_object_not_called(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'DELETE', + model = self.model, + obj = obj, + user = self.view_user, + ) + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + assert get_object.called == False + + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_view_permission_denied_wrong_method_delete_raised_method_not_allowed(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'DELETE', + model = self.model, + obj = obj, + user = self.view_user, + ) + + view.allowed_methods = [ + self.http_method + ] + + with pytest.raises( centurion_exceptions.MethodNotAllowed ) as ex: + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + + + +class ActionDeniedViewPermissionWrongMethodGet: + + @patch.object(GenericAPIView, 'get_object') + def test_action_view_permission_denied_wrong_method_get(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'GET', + model = self.model, + obj = obj, + user = self.view_user, + ) + + assert OrganizationPermissionMixin().has_permission(request = view.request, view = view) is False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_view_permission_denied_wrong_method_get_get_object_not_called(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'GET', + model = self.model, + obj = obj, + user = self.view_user, + ) + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + assert get_object.called == False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_view_permission_denied_wrong_method_get_raised_method_not_allowed(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'GET', + model = self.model, + obj = obj, + user = self.view_user, + ) + + view.allowed_methods = [ + self.http_method + ] + + with pytest.raises( centurion_exceptions.MethodNotAllowed ) as ex: + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + + +class ActionDeniedViewPermissionWrongMethodPatch: + + @patch.object(GenericAPIView, 'get_object') + def test_action_view_permission_denied_wrong_method_patch(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'PATCH', + model = self.model, + obj = obj, + user = self.view_user, + ) + + assert OrganizationPermissionMixin().has_permission(request = view.request, view = view) is False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_view_permission_denied_wrong_method_patch_get_object_not_called(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'PATCH', + model = self.model, + obj = obj, + user = self.view_user, + ) + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + assert get_object.called == False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_view_permission_denied_wrong_method_patch_raised_method_not_allowed(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'PATCH', + model = self.model, + obj = obj, + user = self.view_user, + ) + + view.allowed_methods = [ + self.http_method + ] + + with pytest.raises( centurion_exceptions.MethodNotAllowed ) as ex: + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + + +class ActionDeniedViewPermissionWrongMethodPost: + + @patch.object(GenericAPIView, 'get_object') + def test_action_view_permission_denied_wrong_method_post(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'POST', + model = self.model, + obj = obj, + user = self.view_user, + ) + + assert OrganizationPermissionMixin().has_permission(request = view.request, view = view) is False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_view_permission_denied_wrong_method_post_get_object_not_called(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'POST', + model = self.model, + obj = obj, + user = self.view_user, + ) + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + assert get_object.called == False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_view_permission_denied_wrong_method_post_raised_method_not_allowed(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'POST', + model = self.model, + obj = obj, + user = self.view_user, + ) + + view.allowed_methods = [ + self.http_method + ] + + with pytest.raises( centurion_exceptions.MethodNotAllowed ) as ex: + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + + + + +class ActionDeniedViewPermissionWrongMethodPut: + + + @patch.object(GenericAPIView, 'get_object') + def test_action_view_permission_denied_wrong_method_put(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'PUT', + model = self.model, + obj = obj, + user = self.view_user, + ) + + assert OrganizationPermissionMixin().has_permission(request = view.request, view = view) is False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_view_permission_denied_wrong_method_put_get_object_not_called(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'PUT', + model = self.model, + obj = obj, + user = self.view_user, + ) + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + assert get_object.called == False + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_view_permission_denied_wrong_method_put_raised_method_not_allowed(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'PUT', + model = self.model, + obj = obj, + user = self.view_user, + ) + + view.allowed_methods = [ + self.http_method + ] + + with pytest.raises( centurion_exceptions.MethodNotAllowed ) as ex: + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + + + + + +class ActionDeniedAnonymousUser: + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_anon_user_denied(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = self.http_method, + model = self.model, + obj = obj, + user = AnonymousUser(), + ) + + with pytest.raises(centurion_exceptions.NotAuthenticated) as err: + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_anon_user_denied_get_object_not_called(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = self.http_method, + model = self.model, + obj = obj, + user = AnonymousUser(), + ) + + with pytest.raises(centurion_exceptions.NotAuthenticated) as err: + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_anon_user_denied_wrong_method_head(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'HEAD', + model = self.model, + obj = obj, + user = AnonymousUser(), + ) + + with pytest.raises(centurion_exceptions.NotAuthenticated) as err: + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_anon_user_denied_wrong_method_head_get_object_not_called(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'HEAD', + model = self.model, + obj = obj, + user = AnonymousUser(), + ) + + with pytest.raises(centurion_exceptions.NotAuthenticated) as err: + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + + +class ActionDeniedAnonymousUserWrongMethodDelete: + + @patch.object(GenericAPIView, 'get_object') + def test_action_anon_user_denied_wrong_method_delete(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'DELETE', + model = self.model, + obj = obj, + user = AnonymousUser(), + ) + + with pytest.raises(centurion_exceptions.NotAuthenticated) as err: + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + + @patch.object(GenericAPIView, 'get_object') + def test_action_anon_user_denied_wrong_method_delete_get_object_not_called(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'DELETE', + model = self.model, + obj = obj, + user = AnonymousUser(), + ) + + with pytest.raises(centurion_exceptions.NotAuthenticated) as err: + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + + + +class ActionDeniedAnonymousUserWrongMethodGet: + + @patch.object(GenericAPIView, 'get_object') + def test_action_anon_user_denied_wrong_method_get(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'GET', + model = self.model, + obj = obj, + user = AnonymousUser(), + ) + + with pytest.raises(centurion_exceptions.NotAuthenticated) as err: + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + + @patch.object(GenericAPIView, 'get_object') + def test_action_anon_user_denied_wrong_method_get_get_object_not_called(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'GET', + model = self.model, + obj = obj, + user = AnonymousUser(), + ) + + with pytest.raises(centurion_exceptions.NotAuthenticated) as err: + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + + + +class ActionDeniedAnonymousUserWrongMethodOptions: + + + @patch.object(GenericAPIView, 'get_object') + def test_action_anon_user_denied_wrong_method_options(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'OPTIONS', + model = self.model, + obj = obj, + user = AnonymousUser(), + ) + + with pytest.raises(centurion_exceptions.NotAuthenticated) as err: + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + + @patch.object(GenericAPIView, 'get_object') + def test_action_anon_user_denied_wrong_method_options_get_object_not_called(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'OPTIONS', + model = self.model, + obj = obj, + user = AnonymousUser(), + ) + + with pytest.raises(centurion_exceptions.NotAuthenticated) as err: + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + + + +class ActionDeniedAnonymousUserWrongMethodPatch: + + + @patch.object(GenericAPIView, 'get_object') + def test_action_anon_user_denied_wrong_method_patch(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'PATCH', + model = self.model, + obj = obj, + user = AnonymousUser(), + ) + + with pytest.raises(centurion_exceptions.NotAuthenticated) as err: + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + + @patch.object(GenericAPIView, 'get_object') + def test_action_anon_user_denied_wrong_method_patch_get_object_not_called(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'PATCH', + model = self.model, + obj = obj, + user = AnonymousUser(), + ) + + with pytest.raises(centurion_exceptions.NotAuthenticated) as err: + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + +class ActionDeniedAnonymousUserWrongMethodPost: + + + @patch.object(GenericAPIView, 'get_object') + def test_action_anon_user_denied_wrong_method_post(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'POST', + model = self.model, + obj = obj, + user = AnonymousUser(), + ) + + with pytest.raises(centurion_exceptions.NotAuthenticated) as err: + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + + @patch.object(GenericAPIView, 'get_object') + def test_action_anon_user_denied_wrong_method_post_get_object_not_called(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'POST', + model = self.model, + obj = obj, + user = AnonymousUser(), + ) + + with pytest.raises(centurion_exceptions.NotAuthenticated) as err: + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + + + + +class ActionDeniedAnonymousUserWrongMethodPut: + + @patch.object(GenericAPIView, 'get_object') + def test_action_anon_user_denied_wrong_method_put(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'PUT', + model = self.model, + obj = obj, + user = AnonymousUser(), + ) + + with pytest.raises(centurion_exceptions.NotAuthenticated) as err: + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_anon_user_denied_wrong_method_put_get_object_not_called(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = 'PUT', + model = self.model, + obj = obj, + user = AnonymousUser(), + ) + + with pytest.raises(centurion_exceptions.NotAuthenticated) as err: + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +class AddOrganizationPermissions( + # ActionDeniedAddPermission, + # ActionDeniedAddPermissionWrongMethodDelete, + # ActionDeniedAddPermissionWrongMethodGet, + # ActionDeniedAddPermissionWrongMethodOptions, + # ActionDeniedAddPermissionWrongMethodPatch, + # ActionDeniedAddPermissionWrongMethodPost, + # ActionDeniedAddPermissionWrongMethodPut, + ActionDeniedAnonymousUser, + ActionDeniedAnonymousUserWrongMethodDelete, + ActionDeniedAnonymousUserWrongMethodGet, + ActionDeniedAnonymousUserWrongMethodOptions, + ActionDeniedAnonymousUserWrongMethodPatch, + ActionDeniedAnonymousUserWrongMethodPost, + ActionDeniedAnonymousUserWrongMethodPut, + ActionDeniedChangePermission, + ActionDeniedChangePermissionWrongMethodDelete, + ActionDeniedChangePermissionWrongMethodGet, + ActionDeniedChangePermissionWrongMethodOptions, + ActionDeniedChangePermissionWrongMethodPatch, + # ActionDeniedChangePermissionWrongMethodPost, + ActionDeniedChangePermissionWrongMethodPut, + ActionDeniedDeletePermission, + ActionDeniedDeletePermissionWrongMethodDelete, + ActionDeniedDeletePermissionWrongMethodGet, + ActionDeniedDeletePermissionWrongMethodOptions, + ActionDeniedDeletePermissionWrongMethodPatch, + # ActionDeniedDeletePermissionWrongMethodPost, + ActionDeniedDeletePermissionWrongMethodPut, + ActionDeniedViewPermission, + ActionDeniedViewPermissionWrongMethodDelete, + ActionDeniedViewPermissionWrongMethodGet, + ActionDeniedViewPermissionWrongMethodOptions, + ActionDeniedViewPermissionWrongMethodPatch, + # ActionDeniedViewPermissionWrongMethodPost, + ActionDeniedViewPermissionWrongMethodPut, + HasPermissionCommon, + HasPermissionWrongMethodDelete, + HasPermissionWrongMethodGet, + HasPermissionWrongMethodOptions, + HasPermissionWrongMethodPatch, + # HasPermissionWrongMethodPost, + HasPermissionWrongMethodPut, + HasPermissionDifferentOrganizationCommon, + HasPermissionDifferentOrganizationWrongMethodDelete, + HasPermissionDifferentOrganizationWrongMethodGet, + HasPermissionDifferentOrganizationWrongMethodOptions, + HasPermissionDifferentOrganizationWrongMethodPatch, + # HasPermissionDifferentOrganizationWrongMethodPost, + HasPermissionDifferentOrganizationWrongMethodPut, + TestCase, +): + + http_method = 'POST' + + view_action = 'create' + + + + @classmethod + def setUpTestData(self): + + super().setUpTestData() + + + self.test_user = self.add_user + + self.test_user_two = self.add_user_two + + + self.add_data: dict = { + 'organization': self.organization.id, + 'name': 'item_added_post' + } + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_has_permission_different_org_get_object_not_called(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = self.http_method, + model = self.model, + obj = obj, + user = self.test_user_two, + ) + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + assert not get_object.called + + + + @patch.object(GenericAPIView, 'get_object') + def test_action_has_permission_function_get_object_not_called(self, get_object): + + get_object.return_value = self.obj + + if hasattr(self, 'add_data'): + + data = self.add_data + + kwargs = {} + + obj = None + + else: + + data = None + + kwargs = { + 'pk': self.obj.id + } + + obj = self.obj + + + view = MyMockView( + action = self.view_action, + data = data, + kwargs = kwargs, + method = self.http_method, + model = self.model, + obj = obj, + user = self.test_user, + ) + + OrganizationPermissionMixin().has_permission(request = view.request, view = view) + + assert not get_object.called + + + + + + +class ChangeOrganizationPermissions( + ActionDeniedAddPermission, + ActionDeniedAddPermissionWrongMethodDelete, + ActionDeniedAddPermissionWrongMethodGet, + ActionDeniedAddPermissionWrongMethodOptions, + ActionDeniedAddPermissionWrongMethodPatch, + ActionDeniedAddPermissionWrongMethodPost, + # ActionDeniedAddPermissionWrongMethodPut, + ActionDeniedAnonymousUser, + ActionDeniedAnonymousUserWrongMethodDelete, + ActionDeniedAnonymousUserWrongMethodGet, + ActionDeniedAnonymousUserWrongMethodOptions, + ActionDeniedAnonymousUserWrongMethodPatch, + ActionDeniedAnonymousUserWrongMethodPost, + ActionDeniedAnonymousUserWrongMethodPut, + # ActionDeniedChangePermission, + # ActionDeniedChangePermissionWrongMethodGet, + # ActionDeniedChangePermissionWrongMethodOptions, + # ActionDeniedChangePermissionWrongMethodPatch, + # ActionDeniedChangePermissionWrongMethodPost, + # ActionDeniedChangePermissionWrongMethodPut, + ActionDeniedDeletePermission, + ActionDeniedDeletePermissionWrongMethodDelete, + ActionDeniedDeletePermissionWrongMethodGet, + ActionDeniedDeletePermissionWrongMethodOptions, + ActionDeniedDeletePermissionWrongMethodPatch, + ActionDeniedDeletePermissionWrongMethodPost, + # ActionDeniedDeletePermissionWrongMethodPut, + ActionDeniedViewPermission, + ActionDeniedViewPermissionWrongMethodDelete, + ActionDeniedViewPermissionWrongMethodGet, + ActionDeniedViewPermissionWrongMethodOptions, + ActionDeniedViewPermissionWrongMethodPatch, + ActionDeniedViewPermissionWrongMethodPost, + # ActionDeniedViewPermissionWrongMethodPut, + HasPermission, + HasPermissionWrongMethodDelete, + HasPermissionWrongMethodGet, + HasPermissionWrongMethodOptions, + HasPermissionWrongMethodPatch, + HasPermissionWrongMethodPost, + # HasPermissionWrongMethodPut, + HasPermissionDifferentOrganization, + HasPermissionDifferentOrganizationWrongMethodDelete, + HasPermissionDifferentOrganizationWrongMethodGet, + HasPermissionDifferentOrganizationWrongMethodOptions, + HasPermissionDifferentOrganizationWrongMethodPatch, + HasPermissionDifferentOrganizationWrongMethodPost, + # HasPermissionDifferentOrganizationWrongMethodPut, + TestCase, +): + + http_method = 'PUT' + + view_action = 'update' + + + + @classmethod + def setUpTestData(self): + + super().setUpTestData() + + + self.test_user = self.change_user + + self.test_user_two = self.change_user_two + + + + +class DeleteOrganizationPermissions( + ActionDeniedAddPermission, + # ActionDeniedAddPermissionWrongMethodDelete, + ActionDeniedAddPermissionWrongMethodGet, + ActionDeniedAddPermissionWrongMethodOptions, + ActionDeniedAddPermissionWrongMethodPatch, + ActionDeniedAddPermissionWrongMethodPost, + ActionDeniedAddPermissionWrongMethodPut, + ActionDeniedAnonymousUser, + ActionDeniedAnonymousUserWrongMethodDelete, + ActionDeniedAnonymousUserWrongMethodGet, + ActionDeniedAnonymousUserWrongMethodOptions, + ActionDeniedAnonymousUserWrongMethodPatch, + ActionDeniedAnonymousUserWrongMethodPost, + ActionDeniedAnonymousUserWrongMethodPut, + ActionDeniedChangePermission, + # ActionDeniedChangePermissionWrongMethodDelete, + ActionDeniedChangePermissionWrongMethodGet, + ActionDeniedChangePermissionWrongMethodOptions, + ActionDeniedChangePermissionWrongMethodPatch, + ActionDeniedChangePermissionWrongMethodPost, + ActionDeniedChangePermissionWrongMethodPut, + # ActionDeniedDeletePermission, + # ActionDeniedDeletePermissionWrongMethodDelete, + # ActionDeniedDeletePermissionWrongMethodGet, + # ActionDeniedDeletePermissionWrongMethodOptions, + # ActionDeniedDeletePermissionWrongMethodPatch, + # ActionDeniedDeletePermissionWrongMethodPost, + # ActionDeniedDeletePermissionWrongMethodPut, + ActionDeniedViewPermission, + # ActionDeniedViewPermissionWrongMethodDelete, + ActionDeniedViewPermissionWrongMethodGet, + ActionDeniedViewPermissionWrongMethodOptions, + ActionDeniedViewPermissionWrongMethodPatch, + ActionDeniedViewPermissionWrongMethodPost, + ActionDeniedViewPermissionWrongMethodPut, + HasPermission, + # HasPermissionWrongMethodDelete, + HasPermissionWrongMethodGet, + HasPermissionWrongMethodOptions, + HasPermissionWrongMethodPatch, + HasPermissionWrongMethodPost, + HasPermissionWrongMethodPut, + HasPermissionDifferentOrganization, + # HasPermissionDifferentOrganizationWrongMethodDelete, + HasPermissionDifferentOrganizationWrongMethodGet, + HasPermissionDifferentOrganizationWrongMethodOptions, + HasPermissionDifferentOrganizationWrongMethodPatch, + HasPermissionDifferentOrganizationWrongMethodPost, + HasPermissionDifferentOrganizationWrongMethodPut, + TestCase, +): + + http_method = 'DELETE' + + view_action = 'destroy' + + + + @classmethod + def setUpTestData(self): + + super().setUpTestData() + + + self.test_user = self.delete_user + + self.test_user_two = self.delete_user_two + + + + + + +class PartialChangeOrganizationPermissions( + ActionDeniedAddPermission, + ActionDeniedAddPermissionWrongMethodDelete, + ActionDeniedAddPermissionWrongMethodGet, + ActionDeniedAddPermissionWrongMethodOptions, + # ActionDeniedAddPermissionWrongMethodPatch, + ActionDeniedAddPermissionWrongMethodPost, + ActionDeniedAddPermissionWrongMethodPut, + ActionDeniedAnonymousUser, + ActionDeniedAnonymousUserWrongMethodDelete, + ActionDeniedAnonymousUserWrongMethodGet, + ActionDeniedAnonymousUserWrongMethodOptions, + ActionDeniedAnonymousUserWrongMethodPatch, + ActionDeniedAnonymousUserWrongMethodPost, + ActionDeniedAnonymousUserWrongMethodPut, + # ActionDeniedChangePermission, + # ActionDeniedChangePermissionWrongMethodGet, + # ActionDeniedChangePermissionWrongMethodOptions, + # ActionDeniedChangePermissionWrongMethodPatch, + # ActionDeniedChangePermissionWrongMethodPost, + # ActionDeniedChangePermissionWrongMethodPut, + ActionDeniedDeletePermission, + ActionDeniedDeletePermissionWrongMethodDelete, + ActionDeniedDeletePermissionWrongMethodGet, + ActionDeniedDeletePermissionWrongMethodOptions, + # ActionDeniedDeletePermissionWrongMethodPatch, + ActionDeniedDeletePermissionWrongMethodPost, + ActionDeniedDeletePermissionWrongMethodPut, + ActionDeniedViewPermission, + ActionDeniedViewPermissionWrongMethodDelete, + ActionDeniedViewPermissionWrongMethodGet, + ActionDeniedViewPermissionWrongMethodOptions, + # ActionDeniedViewPermissionWrongMethodPatch, + ActionDeniedViewPermissionWrongMethodPost, + ActionDeniedViewPermissionWrongMethodPut, + HasPermission, + HasPermissionWrongMethodDelete, + HasPermissionWrongMethodGet, + HasPermissionWrongMethodOptions, + # HasPermissionWrongMethodPatch, + HasPermissionWrongMethodPost, + HasPermissionWrongMethodPut, + HasPermissionDifferentOrganization, + HasPermissionDifferentOrganizationWrongMethodDelete, + HasPermissionDifferentOrganizationWrongMethodGet, + HasPermissionDifferentOrganizationWrongMethodOptions, + # HasPermissionDifferentOrganizationWrongMethodPatch, + HasPermissionDifferentOrganizationWrongMethodPost, + HasPermissionDifferentOrganizationWrongMethodPut, + TestCase, +): + + http_method = 'PATCH' + + view_action = 'partial_update' + + + + @classmethod + def setUpTestData(self): + + super().setUpTestData() + + + self.test_user = self.change_user + + self.test_user_two = self.change_user_two + + + + + +class ViewOrganizationPermissions( + ActionDeniedAddPermission, + ActionDeniedAddPermissionWrongMethodDelete, + # ActionDeniedAddPermissionWrongMethodGet, + ActionDeniedAddPermissionWrongMethodOptions, + ActionDeniedAddPermissionWrongMethodPatch, + ActionDeniedAddPermissionWrongMethodPost, + ActionDeniedAddPermissionWrongMethodPut, + ActionDeniedAnonymousUser, + ActionDeniedAnonymousUserWrongMethodDelete, + ActionDeniedAnonymousUserWrongMethodGet, + ActionDeniedAnonymousUserWrongMethodOptions, + ActionDeniedAnonymousUserWrongMethodPatch, + ActionDeniedAnonymousUserWrongMethodPost, + ActionDeniedAnonymousUserWrongMethodPut, + ActionDeniedChangePermission, + ActionDeniedChangePermissionWrongMethodDelete, + # ActionDeniedChangePermissionWrongMethodGet, + ActionDeniedChangePermissionWrongMethodOptions, + ActionDeniedChangePermissionWrongMethodPatch, + ActionDeniedChangePermissionWrongMethodPost, + ActionDeniedChangePermissionWrongMethodPut, + ActionDeniedDeletePermission, + ActionDeniedDeletePermissionWrongMethodDelete, + # ActionDeniedDeletePermissionWrongMethodGet, + ActionDeniedDeletePermissionWrongMethodOptions, + ActionDeniedDeletePermissionWrongMethodPatch, + ActionDeniedDeletePermissionWrongMethodPost, + ActionDeniedDeletePermissionWrongMethodPut, + # ActionDeniedViewPermission, + # ActionDeniedViewPermissionWrongMethodDelete, + # ActionDeniedViewPermissionWrongMethodGet, + # ActionDeniedViewPermissionWrongMethodOptions, + # ActionDeniedViewPermissionWrongMethodPatch, + # ActionDeniedViewPermissionWrongMethodPost, + # ActionDeniedViewPermissionWrongMethodPut, + HasPermission, + HasPermissionWrongMethodDelete, + # HasPermissionWrongMethodGet, + HasPermissionWrongMethodOptions, + HasPermissionWrongMethodPatch, + HasPermissionWrongMethodPost, + HasPermissionWrongMethodPut, + HasPermissionDifferentOrganization, + HasPermissionDifferentOrganizationWrongMethodDelete, + # HasPermissionDifferentOrganizationWrongMethodGet, + HasPermissionDifferentOrganizationWrongMethodOptions, + HasPermissionDifferentOrganizationWrongMethodPatch, + HasPermissionDifferentOrganizationWrongMethodPost, + HasPermissionDifferentOrganizationWrongMethodPut, + TestCase, +): + + http_method = 'GET' + + view_action = 'retrieve' + + + + @classmethod + def setUpTestData(self): + + super().setUpTestData() + + + self.test_user = self.view_user + + self.test_user_two = self.view_user_two \ No newline at end of file diff --git a/app/access/tests/unit/test_access_viewset.py b/app/access/tests/unit/test_access_viewset.py index eb2e2250..91a0b414 100644 --- a/app/access/tests/unit/test_access_viewset.py +++ b/app/access/tests/unit/test_access_viewset.py @@ -2,6 +2,8 @@ from django.contrib.auth.models import User from django.shortcuts import reverse from django.test import Client, TestCase +from rest_framework.permissions import IsAuthenticated + from access.models import Organization from api.tests.abstract.viewsets import ViewSetCommon @@ -39,4 +41,18 @@ class AccessViewset( client.force_login(self.view_user) - self.http_options_response_list = client.options(url) \ No newline at end of file + self.http_options_response_list = client.options(url) + + + + def test_view_attr_permission_classes_value(self): + """Attribute Test + + Attribute `permission_classes` must be metadata class `ReactUIMetadata` + """ + + view_set = self.viewset() + + assert view_set.permission_classes[0] is IsAuthenticated + + assert len(view_set.permission_classes) == 1 diff --git a/app/access/viewsets/index.py b/app/access/viewsets/index.py index 552aaa6c..a2912c83 100644 --- a/app/access/viewsets/index.py +++ b/app/access/viewsets/index.py @@ -3,12 +3,12 @@ from drf_spectacular.utils import extend_schema from rest_framework.response import Response from rest_framework.reverse import reverse -from api.viewsets.common import CommonViewSet +from api.viewsets.common import IndexViewset @extend_schema(exclude = True) -class Index(CommonViewSet): +class Index(IndexViewset): allowed_methods: list = [ 'GET', diff --git a/app/access/viewsets/team_user.py b/app/access/viewsets/team_user.py index 7cc9c6a5..f9a8dbc2 100644 --- a/app/access/viewsets/team_user.py +++ b/app/access/viewsets/team_user.py @@ -145,6 +145,10 @@ class ViewSet( ModelViewSet ): model = TeamUsers + parent_model = Team + + parent_model_pk_kwarg = 'team_id' + documentation: str = '' view_description = 'Users belonging to a single team' diff --git a/app/api/tests/abstract/api_permissions_viewset.py b/app/api/tests/abstract/api_permissions_viewset.py index 4916909f..4d5794ec 100644 --- a/app/api/tests/abstract/api_permissions_viewset.py +++ b/app/api/tests/abstract/api_permissions_viewset.py @@ -113,6 +113,16 @@ class APIPermissionView: url = reverse(self.app_namespace + ':' + self.url_name + '-list') + viewable_organizations = [ + self.organization.id, + ] + + if getattr(self, 'global_organization', None): # Cater for above test that also has global org + + viewable_organizations += [ self.global_organization.id ] + + + client.force_login(self.view_user) response = client.get(url) @@ -120,14 +130,59 @@ class APIPermissionView: for item in response.data['results']: - if int(item['organization']['id']) != self.organization.id: + if int(item['organization']['id']) not in viewable_organizations: contains_different_org = True + print(f'Failed returned row was: {item}') assert not contains_different_org + def test_returned_data_from_user_and_global_organizations_only(self): + """Check items returned + + Items returned from the query Must be from the users organization and + global ONLY! + """ + + client = Client() + url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs=self.url_kwargs) + + + only_from_user_org: bool = True + + viewable_organizations = [ + self.organization.id, + self.global_organization.id + ] + + + assert getattr(self.global_organization, 'id', False) # fail if no global org set + assert getattr(self.global_org_item, 'id', False) # fail if no global item set + + + client.force_login(self.view_user) + response = client.get(url) + + assert len(response.data['results']) >= 2 # fail if only one item extist. + + + for row in response.data['results']: + + if row['organization']['id'] not in viewable_organizations: + + only_from_user_org = False + + print(f'Users org: {self.organization.id}') + print(f'global org: {self.global_organization.id}') + print(f'Failed returned row was: {row}') + + assert only_from_user_org + + + + class APIPermissionAdd: diff --git a/app/api/tests/abstract/test_metadata_functional.py b/app/api/tests/abstract/test_metadata_functional.py index fbc36098..0503d7c7 100644 --- a/app/api/tests/abstract/test_metadata_functional.py +++ b/app/api/tests/abstract/test_metadata_functional.py @@ -615,54 +615,7 @@ class MetaDataNavigationEntriesFunctional: content_type='application/json' ) - no_menu_entry_found: bool = True - - for nav_menu in response.data['navigation']: - - if nav_menu['name'] == self.menu_id: - - for menu_entry in nav_menu['pages']: - - if menu_entry['name'] == self.menu_entry_id: - - no_menu_entry_found = False - - assert no_menu_entry_found - - - - def test_navigation_no_empty_menu_add_user(self): - """Test HTTP/Options Method Navigation Entry - - Ensure that a user with add permission, does not - have any nave menu without pages - """ - - client = Client() - client.force_login(self.add_user) - - if getattr(self, 'url_kwargs', None): - - url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs) - - else: - - url = reverse(self.app_namespace + ':' + self.url_name + '-list') - - response = client.options( - url, - content_type='application/json' - ) - - no_empty_menu_found: bool = True - - for nav_menu in response.data['navigation']: - - if len(nav_menu['pages']) == 0: - - no_empty_menu_found = False - - assert no_empty_menu_found + assert response.status_code == 403 @@ -689,54 +642,8 @@ class MetaDataNavigationEntriesFunctional: content_type='application/json' ) - no_menu_entry_found: bool = True - for nav_menu in response.data['navigation']: - - if nav_menu['name'] == self.menu_id: - - for menu_entry in nav_menu['pages']: - - if menu_entry['name'] == self.menu_entry_id: - - no_menu_entry_found = False - - assert no_menu_entry_found - - - - def test_navigation_no_empty_menu_change_user(self): - """Test HTTP/Options Method Navigation Entry - - Ensure that a user with change permission, does not - have any nave menu without pages - """ - - client = Client() - client.force_login(self.change_user) - - if getattr(self, 'url_kwargs', None): - - url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs) - - else: - - url = reverse(self.app_namespace + ':' + self.url_name + '-list') - - response = client.options( - url, - content_type='application/json' - ) - - no_empty_menu_found: bool = True - - for nav_menu in response.data['navigation']: - - if len(nav_menu['pages']) == 0: - - no_empty_menu_found = False - - assert no_empty_menu_found + assert response.status_code == 403 @@ -763,54 +670,8 @@ class MetaDataNavigationEntriesFunctional: content_type='application/json' ) - no_menu_entry_found: bool = True - for nav_menu in response.data['navigation']: - - if nav_menu['name'] == self.menu_id: - - for menu_entry in nav_menu['pages']: - - if menu_entry['name'] == self.menu_entry_id: - - no_menu_entry_found = False - - assert no_menu_entry_found - - - - def test_navigation_no_empty_menu_delete_user(self): - """Test HTTP/Options Method Navigation Entry - - Ensure that a user with delete permission, does not - have any nave menu without pages - """ - - client = Client() - client.force_login(self.delete_user) - - if getattr(self, 'url_kwargs', None): - - url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs) - - else: - - url = reverse(self.app_namespace + ':' + self.url_name + '-list') - - response = client.options( - url, - content_type='application/json' - ) - - no_empty_menu_found: bool = True - - for nav_menu in response.data['navigation']: - - if len(nav_menu['pages']) == 0: - - no_empty_menu_found = False - - assert no_empty_menu_found + assert response.status_code == 403 diff --git a/app/api/tests/abstract/viewsets.py b/app/api/tests/abstract/viewsets.py index 48d4bf9e..905c9d12 100644 --- a/app/api/tests/abstract/viewsets.py +++ b/app/api/tests/abstract/viewsets.py @@ -1,5 +1,6 @@ +from access.mixins.permissions import OrganizationPermissionMixin + from api.react_ui_metadata import ReactUIMetadata -from api.views.mixin import OrganizationPermissionAPI @@ -145,7 +146,7 @@ class AllViewSet: view_set = self.viewset() - assert view_set.permission_classes[0] is OrganizationPermissionAPI + assert view_set.permission_classes[0] is OrganizationPermissionMixin assert len(view_set.permission_classes) == 1 diff --git a/app/api/tests/unit/test_index_viewset.py b/app/api/tests/unit/test_index_viewset.py index 0721f964..0aa000ab 100644 --- a/app/api/tests/unit/test_index_viewset.py +++ b/app/api/tests/unit/test_index_viewset.py @@ -2,6 +2,8 @@ from django.contrib.auth.models import User from django.shortcuts import reverse from django.test import Client, TestCase +from rest_framework.permissions import IsAuthenticated + from access.models import Organization from api.tests.abstract.viewsets import ViewSetCommon @@ -39,4 +41,19 @@ class HomeViewset( client.force_login(self.view_user) - self.http_options_response_list = client.options(url) \ No newline at end of file + self.http_options_response_list = client.options(url) + + + + def test_view_attr_permission_classes_value(self): + """Attribute Test + + Attribute `permission_classes` must be metadata class `ReactUIMetadata` + """ + + view_set = self.viewset() + + assert view_set.permission_classes[0] is IsAuthenticated + + assert len(view_set.permission_classes) == 1 + diff --git a/app/api/urls_v2.py b/app/api/urls_v2.py index ad7e828c..0abccf69 100644 --- a/app/api/urls_v2.py +++ b/app/api/urls_v2.py @@ -73,7 +73,8 @@ from itim.viewsets import ( port as port_v2, problem, service as service_v2, - service_device as service_device_v2 + service_cluster, + service_device as service_device_v2, ) from project_management.viewsets import ( @@ -153,6 +154,7 @@ router.register('itam/software/(?P[0-9]+)/version', software_versio router.register('itim', itim_v2.Index, basename='_api_v2_itim_home') router.register('itim/ticket/change', change.ViewSet, basename='_api_v2_ticket_change') router.register('itim/cluster', cluster_v2.ViewSet, basename='_api_v2_cluster') +router.register('itim/cluster/(?P[0-9]+)/service', service_cluster.ViewSet, basename='_api_v2_service_cluster') router.register('itim/cluster/(?P[0-9]+)/notes', notes_v2.ViewSet, basename='_api_v2_cluster_notes') router.register('itim/ticket/incident', incident.ViewSet, basename='_api_v2_ticket_incident') router.register('itim/ticket/problem', problem.ViewSet, basename='_api_v2_ticket_problem') diff --git a/app/api/views/itam/inventory.py b/app/api/views/itam/inventory.py index 6009fd8c..ae9cc340 100644 --- a/app/api/views/itam/inventory.py +++ b/app/api/views/itam/inventory.py @@ -14,11 +14,10 @@ from api.serializers.inventory import Inventory from core.http.common import Http from itam.models.device import Device +from itam.tasks.inventory import process_inventory from settings.models.user_settings import UserSettings -from api.tasks import process_inventory - class InventoryPermissions(OrganizationPermissionAPI): diff --git a/app/api/viewsets/common.py b/app/api/viewsets/common.py index c549f59e..b12aa148 100644 --- a/app/api/viewsets/common.py +++ b/app/api/viewsets/common.py @@ -5,11 +5,279 @@ from rest_framework.exceptions import APIException from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response -from access.mixin import OrganizationMixin +from access.mixins.organization import OrganizationMixin +from access.mixins.permissions import OrganizationPermissionMixin from api.auth import TokenScheme from api.react_ui_metadata import ReactUIMetadata -from api.views.mixin import OrganizationPermissionAPI + + + +class Create( + viewsets.mixins.CreateModelMixin +): + + + def create(self, request, *args, **kwargs): + """Sainty override + + This function overrides the function of the same name + in the parent class for the purpose of ensuring a + non-api exception will not have the API return a HTTP + 500 error. + + This function is a sanity check that if it triggers, + (an exception occured), the user will be presented with + a stack trace that they will hopefully report as a bug. + + HTTP status set to HTTP/501 so it's distinguishable from + a HTTP/500 which is generally a random error that has not + been planned for. i.e. uncaught exception + """ + + response = None + + try: + + response = super().create(request = request, *args, **kwargs) + + except Exception as e: + + if not isinstance(e, APIException): + + response = Response( + data = { + 'server_error': str(e) + }, + status = 501 + ) + + return response + + + +class Destroy( + viewsets.mixins.DestroyModelMixin +): + + + def destroy(self, request, *args, **kwargs): + """Sainty override + + This function overrides the function of the same name + in the parent class for the purpose of ensuring a + non-api exception will not have the API return a HTTP + 500 error. + + This function is a sanity check that if it triggers, + (an exception occured), the user will be presented with + a stack trace that they will hopefully report as a bug. + + HTTP status set to HTTP/501 so it's distinguishable from + a HTTP/500 which is generally a random error that has not + been planned for. i.e. uncaught exception + """ + + response = None + + try: + + response = super().destroy(request = request, *args, **kwargs) + + except Exception as e: + + if not isinstance(e, APIException): + + response = Response( + data = { + 'server_error': str(e) + }, + status = 501 + ) + + return response + + + + +class List( + viewsets.mixins.ListModelMixin +): + + + def list(self, request, *args, **kwargs): + """Sainty override + + This function overrides the function of the same name + in the parent class for the purpose of ensuring a + non-api exception will not have the API return a HTTP + 500 error. + + This function is a sanity check that if it triggers, + (an exception occured), the user will be presented with + a stack trace that they will hopefully report as a bug. + + HTTP status set to HTTP/501 so it's distinguishable from + a HTTP/500 which is generally a random error that has not + been planned for. i.e. uncaught exception + """ + + response = None + + try: + + response = super().list(request = request, *args, **kwargs) + + except Exception as e: + + if not isinstance(e, APIException): + + response = Response( + data = { + 'server_error': str(e) + }, + status = 501 + ) + + return response + + +# class PartialUpdate: + + + + +class Retrieve( + viewsets.mixins.RetrieveModelMixin +): + + + def retrieve(self, request, *args, **kwargs): + """Sainty override + + This function overrides the function of the same name + in the parent class for the purpose of ensuring a + non-api exception will not have the API return a HTTP + 500 error. + + This function is a sanity check that if it triggers, + (an exception occured), the user will be presented with + a stack trace that they will hopefully report as a bug. + + HTTP status set to HTTP/501 so it's distinguishable from + a HTTP/500 which is generally a random error that has not + been planned for. i.e. uncaught exception + """ + + response = None + + try: + + response = super().retrieve(request = request, *args, **kwargs) + + except Exception as e: + + if not isinstance(e, APIException): + + response = Response( + data = { + 'server_error': str(e) + }, + status = 501 + ) + + else: + + ex = e.get_full_details() + + response = Response( + data = { + 'message': ex['message'] + }, + status = e.status_code + ) + + return response + + + +class Update( + viewsets.mixins.UpdateModelMixin +): + + + def partial_update(self, request, *args, **kwargs): + """Sainty override + + This function overrides the function of the same name + in the parent class for the purpose of ensuring a + non-api exception will not have the API return a HTTP + 500 error. + + This function is a sanity check that if it triggers, + (an exception occured), the user will be presented with + a stack trace that they will hopefully report as a bug. + + HTTP status set to HTTP/501 so it's distinguishable from + a HTTP/500 which is generally a random error that has not + been planned for. i.e. uncaught exception + """ + + response = None + + try: + + response = super().partial_update(request = request, *args, **kwargs) + + except Exception as e: + + if not isinstance(e, APIException): + + response = Response( + data = { + 'server_error': str(e) + }, + status = 501 + ) + + return response + + + def update(self, request, *args, **kwargs): + """Sainty override + + This function overrides the function of the same name + in the parent class for the purpose of ensuring a + non-api exception will not have the API return a HTTP + 500 error. + + This function is a sanity check that if it triggers, + (an exception occured), the user will be presented with + a stack trace that they will hopefully report as a bug. + + HTTP status set to HTTP/501 so it's distinguishable from + a HTTP/500 which is generally a random error that has not + been planned for. i.e. uncaught exception + """ + + response = None + + try: + + response = super().update(request = request, *args, **kwargs) + + except Exception as e: + + if not isinstance(e, APIException): + + response = Response( + data = { + 'server_error': str(e) + }, + status = 501 + ) + + return response @@ -65,7 +333,7 @@ class CommonViewSet( for detail view, Enables the UI can setup the page layout. """ - permission_classes = [ OrganizationPermissionAPI ] + permission_classes = [ OrganizationPermissionMixin ] """Permission Class _Mandatory_, Permission check class @@ -188,7 +456,7 @@ class CommonViewSet( def get_view_name(self): - if hasattr(self, 'model'): + if getattr(self, 'model', None): if self.detail: @@ -268,374 +536,87 @@ class ModelViewSetBase( class ModelViewSet( ModelViewSetBase, + Create, + Retrieve, + Update, + Destroy, + List, viewsets.ModelViewSet, ): - def retrieve(self, request, *args, **kwargs): - """Sainty override - - This function overrides the function of the same name - in the parent class for the purpose of ensuring a - non-api exception will not have the API return a HTTP - 500 error. - - This function is a sanity check that if it triggers, - (an exception occured), the user will be presented with - a stack trace that they will hopefully report as a bug. - - HTTP status set to HTTP/501 so it's distinguishable from - a HTTP/500 which is generally a random error that has not - been planned for. i.e. uncaught exception - """ - - response = None - - try: - - response = super().retrieve(request = request, *args, **kwargs) - - except Exception as e: - - if not isinstance(e, APIException): - - response = Response( - data = { - 'server_error': str(e) - }, - status = 501 - ) - - return response + pass class ModelCreateViewSet( ModelViewSetBase, - viewsets.mixins.CreateModelMixin, + Create, + viewsets.GenericViewSet, ): - def create(self, request, *args, **kwargs): - """Sainty override - - This function overrides the function of the same name - in the parent class for the purpose of ensuring a - non-api exception will not have the API return a HTTP - 500 error. - - This function is a sanity check that if it triggers, - (an exception occured), the user will be presented with - a stack trace that they will hopefully report as a bug. - - HTTP status set to HTTP/501 so it's distinguishable from - a HTTP/500 which is generally a random error that has not - been planned for. i.e. uncaught exception - """ - - response = None - - try: - - response = super().create(request = request, *args, **kwargs) - - except Exception as e: - - if not isinstance(e, APIException): - - response = Response( - data = { - 'server_error': str(e) - }, - status = 501 - ) - - return response + pass class ModelListRetrieveDeleteViewSet( - viewsets.mixins.ListModelMixin, - viewsets.mixins.RetrieveModelMixin, - viewsets.mixins.DestroyModelMixin, + ModelViewSetBase, + List, + Retrieve, + Destroy, viewsets.GenericViewSet, - ModelViewSetBase ): """ Use for models that you wish to delete and view ONLY!""" - def list(self, request, *args, **kwargs): - """Sainty override - - This function overrides the function of the same name - in the parent class for the purpose of ensuring a - non-api exception will not have the API return a HTTP - 500 error. - - This function is a sanity check that if it triggers, - (an exception occured), the user will be presented with - a stack trace that they will hopefully report as a bug. - - HTTP status set to HTTP/501 so it's distinguishable from - a HTTP/500 which is generally a random error that has not - been planned for. i.e. uncaught exception - """ - - response = None - - try: - - response = super().list(request = request, *args, **kwargs) - - except Exception as e: - - if not isinstance(e, APIException): - - response = Response( - data = { - 'server_error': str(e) - }, - status = 501 - ) - - return response - - - def retrieve(self, request, *args, **kwargs): - """Sainty override - - This function overrides the function of the same name - in the parent class for the purpose of ensuring a - non-api exception will not have the API return a HTTP - 500 error. - - This function is a sanity check that if it triggers, - (an exception occured), the user will be presented with - a stack trace that they will hopefully report as a bug. - - HTTP status set to HTTP/501 so it's distinguishable from - a HTTP/500 which is generally a random error that has not - been planned for. i.e. uncaught exception - """ - - response = None - - try: - - response = super().retrieve(request = request, *args, **kwargs) - - except Exception as e: - - if not isinstance(e, APIException): - - response = Response( - data = { - 'server_error': str(e) - }, - status = 501 - ) - - return response - - - def destroy(self, request, *args, **kwargs): - """Sainty override - - This function overrides the function of the same name - in the parent class for the purpose of ensuring a - non-api exception will not have the API return a HTTP - 500 error. - - This function is a sanity check that if it triggers, - (an exception occured), the user will be presented with - a stack trace that they will hopefully report as a bug. - - HTTP status set to HTTP/501 so it's distinguishable from - a HTTP/500 which is generally a random error that has not - been planned for. i.e. uncaught exception - """ - - response = None - - try: - - response = super().destroy(request = request, *args, **kwargs) - - except Exception as e: - - if not isinstance(e, APIException): - - response = Response( - data = { - 'server_error': str(e) - }, - status = 501 - ) - - return response + pass class ModelRetrieveUpdateViewSet( - viewsets.mixins.RetrieveModelMixin, - viewsets.mixins.UpdateModelMixin, + ModelViewSetBase, + Retrieve, + Update, viewsets.GenericViewSet, - ModelViewSetBase ): """ Use for models that you wish to update and view ONLY!""" - - def partial_update(self, request, *args, **kwargs): - """Sainty override - - This function overrides the function of the same name - in the parent class for the purpose of ensuring a - non-api exception will not have the API return a HTTP - 500 error. - - This function is a sanity check that if it triggers, - (an exception occured), the user will be presented with - a stack trace that they will hopefully report as a bug. - - HTTP status set to HTTP/501 so it's distinguishable from - a HTTP/500 which is generally a random error that has not - been planned for. i.e. uncaught exception - """ - - response = None - - try: - - response = super().partial_update(request = request, *args, **kwargs) - - except Exception as e: - - if not isinstance(e, APIException): - - response = Response( - data = { - 'server_error': str(e) - }, - status = 501 - ) - - return response - - - def update(self, request, *args, **kwargs): - """Sainty override - - This function overrides the function of the same name - in the parent class for the purpose of ensuring a - non-api exception will not have the API return a HTTP - 500 error. - - This function is a sanity check that if it triggers, - (an exception occured), the user will be presented with - a stack trace that they will hopefully report as a bug. - - HTTP status set to HTTP/501 so it's distinguishable from - a HTTP/500 which is generally a random error that has not - been planned for. i.e. uncaught exception - """ - - response = None - - try: - - response = super().update(request = request, *args, **kwargs) - - except Exception as e: - - if not isinstance(e, APIException): - - response = Response( - data = { - 'server_error': str(e) - }, - status = 501 - ) - - return response - + pass class ReadOnlyModelViewSet( - viewsets.ReadOnlyModelViewSet, - ModelViewSetBase + ModelViewSetBase, + Retrieve, + List, + viewsets.GenericViewSet, ): + + pass + + + +class AuthUserReadOnlyModelViewSet( + ReadOnlyModelViewSet +): + """Authenticated User Read-Only Viewset + + Use this class if the model only requires that the user be authenticated + to obtain view permission. + + Args: + ReadOnlyModelViewSet (class): Read-Only base class + """ + permission_classes = [ IsAuthenticated, ] - def retrieve(self, request, *args, **kwargs): - """Sainty override +class IndexViewset( + ModelViewSetBase, +): - This function overrides the function of the same name - in the parent class for the purpose of ensuring a - non-api exception will not have the API return a HTTP - 500 error. + permission_classes = [ + IsAuthenticated, + ] - This function is a sanity check that if it triggers, - (an exception occured), the user will be presented with - a stack trace that they will hopefully report as a bug. - - HTTP status set to HTTP/501 so it's distinguishable from - a HTTP/500 which is generally a random error that has not - been planned for. i.e. uncaught exception - """ - - response = None - - try: - - response = super().retrieve(request = request, *args, **kwargs) - - except Exception as e: - - if not isinstance(e, APIException): - - response = Response( - data = { - 'server_error': str(e) - }, - status = 501 - ) - - return response - - def list(self, request, *args, **kwargs): - """Sainty override - - This function overrides the function of the same name - in the parent class for the purpose of ensuring a - non-api exception will not have the API return a HTTP - 500 error. - - This function is a sanity check that if it triggers, - (an exception occured), the user will be presented with - a stack trace that they will hopefully report as a bug. - - HTTP status set to HTTP/501 so it's distinguishable from - a HTTP/500 which is generally a random error that has not - been planned for. i.e. uncaught exception - """ - - response = None - - try: - - response = super().list(request = request, *args, **kwargs) - - except Exception as e: - - if not isinstance(e, APIException): - - response = Response( - data = { - 'server_error': str(e) - }, - status = 501 - ) - - return response diff --git a/app/api/viewsets/index.py b/app/api/viewsets/index.py index 998b04e3..7bc4e56a 100644 --- a/app/api/viewsets/index.py +++ b/app/api/viewsets/index.py @@ -3,12 +3,12 @@ from drf_spectacular.utils import extend_schema from rest_framework.response import Response from rest_framework.reverse import reverse -from api.viewsets.common import CommonViewSet +from api.viewsets.common import IndexViewset @extend_schema(exclude = True) -class Index(CommonViewSet): +class Index(IndexViewset): allowed_methods: list = [ 'GET', diff --git a/app/app/viewsets/base/content_type.py b/app/app/viewsets/base/content_type.py index 2e66aa74..abda7e57 100644 --- a/app/app/viewsets/base/content_type.py +++ b/app/app/viewsets/base/content_type.py @@ -1,6 +1,6 @@ from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiResponse -from api.viewsets.common import ReadOnlyModelViewSet +from api.viewsets.common import AuthUserReadOnlyModelViewSet from app.serializers.content_type import ( ContentType, @@ -26,7 +26,7 @@ from app.serializers.content_type import ( ), ) class ViewSet( - ReadOnlyModelViewSet + AuthUserReadOnlyModelViewSet ): diff --git a/app/app/viewsets/base/index.py b/app/app/viewsets/base/index.py index b2b47294..041aa2be 100644 --- a/app/app/viewsets/base/index.py +++ b/app/app/viewsets/base/index.py @@ -3,12 +3,12 @@ from drf_spectacular.utils import extend_schema from rest_framework.response import Response from rest_framework.reverse import reverse -from api.viewsets.common import CommonViewSet +from api.viewsets.common import IndexViewset @extend_schema(exclude = True) -class Index(CommonViewSet): +class Index(IndexViewset): allowed_methods: list = [ 'GET', diff --git a/app/app/viewsets/base/permisson.py b/app/app/viewsets/base/permisson.py index c1072800..1bc5d245 100644 --- a/app/app/viewsets/base/permisson.py +++ b/app/app/viewsets/base/permisson.py @@ -1,6 +1,6 @@ from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiResponse -from api.viewsets.common import ReadOnlyModelViewSet +from api.viewsets.common import AuthUserReadOnlyModelViewSet from app.serializers.permission import ( Permission, @@ -26,7 +26,7 @@ from app.serializers.permission import ( ), ) class ViewSet( - ReadOnlyModelViewSet + AuthUserReadOnlyModelViewSet ): diff --git a/app/app/viewsets/base/user.py b/app/app/viewsets/base/user.py index 9a796a21..292d05bc 100644 --- a/app/app/viewsets/base/user.py +++ b/app/app/viewsets/base/user.py @@ -1,6 +1,6 @@ from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiResponse -from api.viewsets.common import ReadOnlyModelViewSet +from api.viewsets.common import AuthUserReadOnlyModelViewSet from app.serializers.user import ( User, @@ -28,7 +28,7 @@ from app.serializers.user import ( ), ) class ViewSet( - ReadOnlyModelViewSet + AuthUserReadOnlyModelViewSet ): diff --git a/app/assistance/tests/functional/knowledge_base/test_knowledge_base_viewset.py b/app/assistance/tests/functional/knowledge_base/test_knowledge_base_viewset.py index 2c934413..13207abc 100644 --- a/app/assistance/tests/functional/knowledge_base/test_knowledge_base_viewset.py +++ b/app/assistance/tests/functional/knowledge_base/test_knowledge_base_viewset.py @@ -16,6 +16,9 @@ from api.tests.abstract.test_metadata_functional import MetadataAttributesFuncti from assistance.models.knowledge_base import KnowledgeBase +from settings.models.app_settings import AppSettings + + class ViewSetBase: @@ -50,6 +53,31 @@ class ViewSetBase: self.different_organization = different_organization + self.view_user = User.objects.create_user(username="test_user_view", password="password") + + + self.global_organization = Organization.objects.create( + name = 'test_global_organization' + ) + + self.global_org_item = self.model.objects.create( + organization = self.global_organization, + title = 'one', + content = 'some text for bodygfdgdf', + target_user = self.view_user + ) + + app_settings = AppSettings.objects.get( + owner_organization = None + ) + + app_settings.global_organization = self.global_organization + + app_settings.save() + + + + self.url_kwargs = {} @@ -124,7 +152,6 @@ class ViewSetBase: self.no_permissions_user = User.objects.create_user(username="test_no_permissions", password="password") - self.view_user = User.objects.create_user(username="test_user_view", password="password") self.view_user_b = User.objects.create_user(username="test_user_view_b", password="password") teamuser = TeamUsers.objects.create( team = view_team, diff --git a/app/assistance/tests/functional/knowledge_base_category/test_knowledge_base_category_viewset.py b/app/assistance/tests/functional/knowledge_base_category/test_knowledge_base_category_viewset.py index c11d5ef5..c9bb63e5 100644 --- a/app/assistance/tests/functional/knowledge_base_category/test_knowledge_base_category_viewset.py +++ b/app/assistance/tests/functional/knowledge_base_category/test_knowledge_base_category_viewset.py @@ -12,6 +12,9 @@ from api.tests.abstract.test_metadata_functional import MetadataAttributesFuncti from assistance.models.knowledge_base import KnowledgeBaseCategory +from settings.models.app_settings import AppSettings + + class ViewSetBase: @@ -46,8 +49,29 @@ class ViewSetBase: self.different_organization = different_organization + self.view_user = User.objects.create_user(username="test_user_view", password="password") + + + self.global_organization = Organization.objects.create( + name = 'test_global_organization' + ) + + self.global_org_item = self.model.objects.create( + organization = self.global_organization, + name = 'onesdsad', + target_user = self.view_user + ) + + app_settings = AppSettings.objects.get( + owner_organization = None + ) + + app_settings.global_organization = self.global_organization + + app_settings.save() + + - # self.url_kwargs = {} view_permissions = Permission.objects.get( @@ -120,7 +144,6 @@ class ViewSetBase: self.no_permissions_user = User.objects.create_user(username="test_no_permissions", password="password") - self.view_user = User.objects.create_user(username="test_user_view", password="password") self.view_user_b = User.objects.create_user(username="test_user_view_b", password="password") teamuser = TeamUsers.objects.create( team = view_team, diff --git a/app/assistance/tests/functional/model_knowledge_base_article/test_model_knowledge_base_article_viewset.py b/app/assistance/tests/functional/model_knowledge_base_article/test_model_knowledge_base_article_viewset.py index 65885f6b..45168037 100644 --- a/app/assistance/tests/functional/model_knowledge_base_article/test_model_knowledge_base_article_viewset.py +++ b/app/assistance/tests/functional/model_knowledge_base_article/test_model_knowledge_base_article_viewset.py @@ -226,6 +226,18 @@ class ModelKnowledgeBaseArticlePermissionsAPI( ): + def test_returned_data_from_user_and_global_organizations_only(self): + """Check items returned + + This test case is a over-ride of a test case with the same name. + This model is not a tenancy model making this test not-applicable. + + Items returned from the query Must be from the users organization and + global ONLY! + """ + pass + + @pytest.mark.skip( reason = 'not required' ) def test_delete_permission_change_denied(self): """This model does not have a change user""" diff --git a/app/assistance/tests/functional/test_assistance_viewset.py b/app/assistance/tests/functional/test_assistance_viewset.py index cb64ad27..1291e9b5 100644 --- a/app/assistance/tests/functional/test_assistance_viewset.py +++ b/app/assistance/tests/functional/test_assistance_viewset.py @@ -2,6 +2,8 @@ from django.contrib.auth.models import User from django.shortcuts import reverse from django.test import Client, TestCase +from rest_framework.permissions import IsAuthenticated + from access.models import Organization from api.tests.abstract.viewsets import ViewSetCommon @@ -39,4 +41,18 @@ class AssistanceViewset( client.force_login(self.view_user) - self.http_options_response_list = client.options(url) \ No newline at end of file + self.http_options_response_list = client.options(url) + + + + def test_view_attr_permission_classes_value(self): + """Attribute Test + + Attribute `permission_classes` must be metadata class `ReactUIMetadata` + """ + + view_set = self.viewset() + + assert view_set.permission_classes[0] is IsAuthenticated + + assert len(view_set.permission_classes) == 1 diff --git a/app/assistance/viewsets/index.py b/app/assistance/viewsets/index.py index f2fd76ce..804ffdb5 100644 --- a/app/assistance/viewsets/index.py +++ b/app/assistance/viewsets/index.py @@ -3,12 +3,12 @@ from drf_spectacular.utils import extend_schema from rest_framework.response import Response from rest_framework.reverse import reverse -from api.viewsets.common import CommonViewSet +from api.viewsets.common import IndexViewset @extend_schema(exclude = True) -class Index(CommonViewSet): +class Index(IndexViewset): allowed_methods: list = [ 'GET', diff --git a/app/config_management/tests/functional/config_groups/test_config_groups_notes_viewset.py b/app/config_management/tests/functional/config_groups/test_config_groups_notes_viewset.py index 81c47c08..f14fa502 100644 --- a/app/config_management/tests/functional/config_groups/test_config_groups_notes_viewset.py +++ b/app/config_management/tests/functional/config_groups/test_config_groups_notes_viewset.py @@ -60,3 +60,16 @@ class NotePermissionsAPI( self.url_view_kwargs = {'config_group_id': self.note_item.id, 'pk': self.item.pk } self.add_data = {'note': 'a note added', 'organization': self.organization.id} + + + + def test_returned_data_from_user_and_global_organizations_only(self): + """Check items returned + + This test case is a over-ride of a test case with the same name. + This model is not a tenancy model making this test not-applicable. + + Items returned from the query Must be from the users organization and + global ONLY! + """ + pass diff --git a/app/config_management/tests/functional/config_groups/test_config_groups_viewset.py b/app/config_management/tests/functional/config_groups/test_config_groups_viewset.py index 62fad341..e1894b52 100644 --- a/app/config_management/tests/functional/config_groups/test_config_groups_viewset.py +++ b/app/config_management/tests/functional/config_groups/test_config_groups_viewset.py @@ -16,6 +16,9 @@ from api.tests.abstract.test_metadata_functional import MetadataAttributesFuncti from config_management.models.groups import ConfigGroups +from settings.models.app_settings import AppSettings + + class ViewSetBase: @@ -51,7 +54,27 @@ class ViewSetBase: - # self.url_kwargs = {} + + + self.global_organization = Organization.objects.create( + name = 'test_global_organization' + ) + + self.global_org_item = self.model.objects.create( + organization = self.global_organization, + name = 'global_item' + ) + + app_settings = AppSettings.objects.get( + owner_organization = None + ) + + app_settings.global_organization = self.global_organization + + app_settings.save() + + + view_permissions = Permission.objects.get( diff --git a/app/config_management/tests/functional/config_groups_software/test_config_groups_software_viewset.py b/app/config_management/tests/functional/config_groups_software/test_config_groups_software_viewset.py index bcca13c0..07176d71 100644 --- a/app/config_management/tests/functional/config_groups_software/test_config_groups_software_viewset.py +++ b/app/config_management/tests/functional/config_groups_software/test_config_groups_software_viewset.py @@ -237,7 +237,18 @@ class ConfigGroupSoftwarePermissionsAPI( TestCase, ): - pass + + def test_returned_data_from_user_and_global_organizations_only(self): + """Check items returned + + This test case is a over-ride of a test case with the same name. + This model is not a tenancy model making this test not-applicable. + + Items returned from the query Must be from the users organization and + global ONLY! + """ + pass + diff --git a/app/config_management/tests/functional/test_config_management_viewset.py b/app/config_management/tests/functional/test_config_management_viewset.py index 8b4495c3..017ffe9c 100644 --- a/app/config_management/tests/functional/test_config_management_viewset.py +++ b/app/config_management/tests/functional/test_config_management_viewset.py @@ -2,6 +2,8 @@ from django.contrib.auth.models import User from django.shortcuts import reverse from django.test import Client, TestCase +from rest_framework.permissions import IsAuthenticated + from access.models import Organization from api.tests.abstract.viewsets import ViewSetCommon @@ -39,4 +41,18 @@ class ConfigManagementViewset( client.force_login(self.view_user) - self.http_options_response_list = client.options(url) \ No newline at end of file + self.http_options_response_list = client.options(url) + + + + def test_view_attr_permission_classes_value(self): + """Attribute Test + + Attribute `permission_classes` must be metadata class `ReactUIMetadata` + """ + + view_set = self.viewset() + + assert view_set.permission_classes[0] is IsAuthenticated + + assert len(view_set.permission_classes) == 1 diff --git a/app/config_management/viewsets/index.py b/app/config_management/viewsets/index.py index a4a95700..a9adcd08 100644 --- a/app/config_management/viewsets/index.py +++ b/app/config_management/viewsets/index.py @@ -3,12 +3,12 @@ from drf_spectacular.utils import extend_schema from rest_framework.response import Response from rest_framework.reverse import reverse -from api.viewsets.common import CommonViewSet +from api.viewsets.common import IndexViewset @extend_schema(exclude = True) -class Index(CommonViewSet): +class Index(IndexViewset): allowed_methods: list = [ 'GET', diff --git a/app/core/exceptions.py b/app/core/exceptions.py index 4c949647..15a10f40 100644 --- a/app/core/exceptions.py +++ b/app/core/exceptions.py @@ -1,6 +1,13 @@ +from django.core.exceptions import ( + ObjectDoesNotExist +) + +from django.http import Http404 + from rest_framework import exceptions, status from rest_framework.exceptions import ( MethodNotAllowed, + NotAuthenticated, ParseError, PermissionDenied, ValidationError, diff --git a/app/core/tests/abstract/test_ticket_viewset.py b/app/core/tests/abstract/test_ticket_viewset.py index bf32c92c..f267a276 100644 --- a/app/core/tests/abstract/test_ticket_viewset.py +++ b/app/core/tests/abstract/test_ticket_viewset.py @@ -303,6 +303,19 @@ class TicketViewSetBase: class TicketViewSetPermissionsAPI( TicketViewSetBase, APIPermissions ): + def test_returned_data_from_user_and_global_organizations_only(self): + """Check items returned + + This test case is a over-ride of a test case with the same name. + This model is not a tenancy model making this test not-applicable. + + Items returned from the query Must be from the users organization and + global ONLY! + """ + pass + + + def test_add_triage_user_denied(self): """ Check correct permission for add diff --git a/app/core/tests/functional/manufacturer/test_manufacturer_viewset.py b/app/core/tests/functional/manufacturer/test_manufacturer_viewset.py index f42f9105..5f69c433 100644 --- a/app/core/tests/functional/manufacturer/test_manufacturer_viewset.py +++ b/app/core/tests/functional/manufacturer/test_manufacturer_viewset.py @@ -16,6 +16,9 @@ from api.tests.abstract.test_metadata_functional import MetadataAttributesFuncti from core.models.manufacturer import Manufacturer +from settings.models.app_settings import AppSettings + + class ViewSetBase: @@ -51,7 +54,28 @@ class ViewSetBase: - # self.url_kwargs = {} + + + self.global_organization = Organization.objects.create( + name = 'test_global_organization' + ) + + self.global_org_item = self.model.objects.create( + organization = self.global_organization, + name = 'global_item' + ) + + app_settings = AppSettings.objects.get( + owner_organization = None + ) + + app_settings.global_organization = self.global_organization + + app_settings.save() + + + + view_permissions = Permission.objects.get( diff --git a/app/core/tests/functional/related_ticket/test_related_ticket_viewset.py b/app/core/tests/functional/related_ticket/test_related_ticket_viewset.py index a25cabd7..d40f66f4 100644 --- a/app/core/tests/functional/related_ticket/test_related_ticket_viewset.py +++ b/app/core/tests/functional/related_ticket/test_related_ticket_viewset.py @@ -215,6 +215,19 @@ class RelatedTicketsPermissionsAPI( TestCase, ): + + def test_returned_data_from_user_and_global_organizations_only(self): + """Check items returned + + This test case is a over-ride of a test case with the same name. + This model is not a tenancy model making this test not-applicable. + + Items returned from the query Must be from the users organization and + global ONLY! + """ + pass + + def test_add_has_permission_post_not_allowed(self): """ Check correct permission for add diff --git a/app/core/tests/functional/test_history/test_history_viewset.py b/app/core/tests/functional/test_history/test_history_viewset.py index fce8c102..61a66d54 100644 --- a/app/core/tests/functional/test_history/test_history_viewset.py +++ b/app/core/tests/functional/test_history/test_history_viewset.py @@ -231,6 +231,18 @@ class HistoryPermissionsAPI( ): + def test_returned_data_from_user_and_global_organizations_only(self): + """Check items returned + + This test case is a over-ride of a test case with the same name. + This model is not a tenancy model making this test not-applicable. + + Items returned from the query Must be from the users organization and + global ONLY! + """ + pass + + def test_view_list_has_permission(self): """ Check correct permission for view @@ -263,7 +275,7 @@ class HistoryPermissionsAPI( client.force_login(self.view_user) response = client.get(url) - assert response.status_code == 403 + assert response.status_code == 200 def test_add_has_permission_method_not_allowed(self): @@ -340,6 +352,22 @@ class HistoryPermissionsAPI( pass + # item is not tenancy object + def test_view_different_organizaiton_denied(self): + """ Check correct permission for view + + This test case is a duplicate of a test case with the same name. This + test is not required as currently the history model is not a tenancy + model. + + see https://github.com/nofusscomputing/centurion_erp/issues/455 for + more details. + + Attempt to view with user from different organization + """ + + pass + class HistoryMetadata( ViewSetBase, diff --git a/app/core/tests/functional/test_task_result/test_task_result_viewset.py b/app/core/tests/functional/test_task_result/test_task_result_viewset.py index d0ed4000..4015fabd 100644 --- a/app/core/tests/functional/test_task_result/test_task_result_viewset.py +++ b/app/core/tests/functional/test_task_result/test_task_result_viewset.py @@ -147,7 +147,7 @@ class TaskResultPermissionsAPI( self.item = self.model.objects.create( task_id = 'd15233ee-a14d-4135-afe5-e406b1b61330', - task_name = 'api.tasks.process_inventory', + task_name = 'itam.tasks.process_inventory', task_args = '{"random": "value"}', task_kwargs = 'sdas', status = "SUCCESS", @@ -209,6 +209,18 @@ class TaskResultPermissionsAPI( ) + def test_returned_data_from_user_and_global_organizations_only(self): + """Check items returned + + This test case is a over-ride of a test case with the same name. + This model is not a tenancy model making this test not-applicable. + + Items returned from the query Must be from the users organization and + global ONLY! + """ + pass + + def test_add_no_permission_denied(self): """ Check correct permission for add diff --git a/app/core/tests/functional/ticket_category/test_ticket_category_viewset.py b/app/core/tests/functional/ticket_category/test_ticket_category_viewset.py index ceba3256..ff7725e6 100644 --- a/app/core/tests/functional/ticket_category/test_ticket_category_viewset.py +++ b/app/core/tests/functional/ticket_category/test_ticket_category_viewset.py @@ -16,6 +16,9 @@ from api.tests.abstract.test_metadata_functional import MetadataAttributesFuncti from core.models.ticket.ticket_category import TicketCategory +from settings.models.app_settings import AppSettings + + class ViewSetBase: @@ -50,6 +53,34 @@ class ViewSetBase: self.different_organization = different_organization + + + + + self.global_organization = Organization.objects.create( + name = 'test_global_organization' + ) + + self.global_org_item = self.model.objects.create( + organization = self.global_organization, + name = 'global_item' + ) + + app_settings = AppSettings.objects.get( + owner_organization = None + ) + + app_settings.global_organization = self.global_organization + + app_settings.save() + + + + + + + + view_permissions = Permission.objects.get( codename = 'view_' + self.model._meta.model_name, content_type = ContentType.objects.get( diff --git a/app/core/tests/functional/ticket_comment/test_ticket_comment_viewset.py b/app/core/tests/functional/ticket_comment/test_ticket_comment_viewset.py index 05912cd3..06b58eae 100644 --- a/app/core/tests/functional/ticket_comment/test_ticket_comment_viewset.py +++ b/app/core/tests/functional/ticket_comment/test_ticket_comment_viewset.py @@ -242,6 +242,18 @@ class TicketCommentPermissionsAPI( pass + def test_returned_data_from_user_and_global_organizations_only(self): + """Check items returned + + This test case is a over-ride of a test case with the same name. + This model is not a tenancy model making this test not-applicable. + + Items returned from the query Must be from the users organization and + global ONLY! + """ + pass + + class TicketCommentMetadata( ViewSetBase, diff --git a/app/core/tests/functional/ticket_comment_category/test_ticket_comment_category_viewset.py b/app/core/tests/functional/ticket_comment_category/test_ticket_comment_category_viewset.py index 492331a5..510c2ac4 100644 --- a/app/core/tests/functional/ticket_comment_category/test_ticket_comment_category_viewset.py +++ b/app/core/tests/functional/ticket_comment_category/test_ticket_comment_category_viewset.py @@ -16,6 +16,9 @@ from api.tests.abstract.test_metadata_functional import MetadataAttributesFuncti from core.models.ticket.ticket_comment_category import TicketCommentCategory +from settings.models.app_settings import AppSettings + + class ViewSetBase: @@ -50,6 +53,33 @@ class ViewSetBase: self.different_organization = different_organization + + + + self.global_organization = Organization.objects.create( + name = 'test_global_organization' + ) + + self.global_org_item = self.model.objects.create( + organization = self.global_organization, + name = 'global_item' + ) + + app_settings = AppSettings.objects.get( + owner_organization = None + ) + + app_settings.global_organization = self.global_organization + + app_settings.save() + + + + + + + + view_permissions = Permission.objects.get( codename = 'view_' + self.model._meta.model_name, content_type = ContentType.objects.get( diff --git a/app/core/tests/functional/ticket_linked_item/test_ticket_linked_item_viewset.py b/app/core/tests/functional/ticket_linked_item/test_ticket_linked_item_viewset.py index 1b81d16d..d7a1aaf9 100644 --- a/app/core/tests/functional/ticket_linked_item/test_ticket_linked_item_viewset.py +++ b/app/core/tests/functional/ticket_linked_item/test_ticket_linked_item_viewset.py @@ -247,7 +247,19 @@ class ViewSetBasePermissionsAPI( APIPermissionView, ): - pass + + def test_returned_data_from_user_and_global_organizations_only(self): + """Check items returned + + This test case is a over-ride of a test case with the same name. + This model is not a tenancy model making this test not-applicable. + + Items returned from the query Must be from the users organization and + global ONLY! + """ + pass + + diff --git a/app/core/tests/unit/test_task_result/test_task_result_api_v2.py b/app/core/tests/unit/test_task_result/test_task_result_api_v2.py index 93575aa9..16a54985 100644 --- a/app/core/tests/unit/test_task_result/test_task_result_api_v2.py +++ b/app/core/tests/unit/test_task_result/test_task_result_api_v2.py @@ -53,7 +53,7 @@ class CeleryTaskResultAPI( self.item = self.model.objects.create( task_id = 'd15233ee-a14d-4135-afe5-e406b1b61330', - task_name = 'api.tasks.process_inventory', + task_name = 'itam.tasks.process_inventory', task_args = '{"random": "value"}', task_kwargs = 'sdas', status = "SUCCESS", diff --git a/app/core/viewsets/celery_log.py b/app/core/viewsets/celery_log.py index 647e9e3c..b9d4319e 100644 --- a/app/core/viewsets/celery_log.py +++ b/app/core/viewsets/celery_log.py @@ -6,7 +6,7 @@ from core.serializers.celery_log import ( TaskResultViewSerializer ) -from api.viewsets.common import ReadOnlyModelViewSet +from api.viewsets.common import AuthUserReadOnlyModelViewSet @@ -29,7 +29,7 @@ from api.viewsets.common import ReadOnlyModelViewSet } ), ) -class ViewSet(ReadOnlyModelViewSet): +class ViewSet(AuthUserReadOnlyModelViewSet): filterset_fields = [ 'periodic_task_name', diff --git a/app/core/viewsets/history.py b/app/core/viewsets/history.py index 27fabeda..e9961670 100644 --- a/app/core/viewsets/history.py +++ b/app/core/viewsets/history.py @@ -2,7 +2,7 @@ from django.db.models import Q from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiResponse -from api.viewsets.common import ModelViewSet +from api.viewsets.common import ReadOnlyModelViewSet from core.serializers.history import ( History, @@ -27,7 +27,7 @@ from core.serializers.history import ( update = extend_schema( exclude = True ), partial_update = extend_schema( exclude = True ) ) -class ViewSet(ModelViewSet): +class ViewSet(ReadOnlyModelViewSet): allowed_methods = [ 'GET', diff --git a/app/core/viewsets/related_ticket.py b/app/core/viewsets/related_ticket.py index f4149b28..3e683bde 100644 --- a/app/core/viewsets/related_ticket.py +++ b/app/core/viewsets/related_ticket.py @@ -7,6 +7,7 @@ from access.mixin import OrganizationMixin from api.viewsets.common import ModelListRetrieveDeleteViewSet from core.serializers.ticket_related import ( + Ticket, RelatedTickets, RelatedTicketModelSerializer, RelatedTicketViewSerializer, @@ -79,6 +80,10 @@ class ViewSet(ModelListRetrieveDeleteViewSet): model = RelatedTickets + parent_model = Ticket + + parent_model_pk_kwarg = 'ticket_id' + def get_serializer_class(self): diff --git a/app/core/viewsets/ticket.py b/app/core/viewsets/ticket.py index 99ad18ad..1c6f4f2e 100644 --- a/app/core/viewsets/ticket.py +++ b/app/core/viewsets/ticket.py @@ -86,10 +86,14 @@ class TicketViewSet(ModelViewSet): """ - def get_dynamic_permissions(self): + def get_permission_required(self): organization = None + if self._permission_required: + + return self._permission_required + if( self.action == 'create' @@ -108,9 +112,13 @@ class TicketViewSet(ModelViewSet): or self.action == 'update' ): - obj = list(self.queryset)[0] + queryset = self.get_queryset() - organization = obj.organization + if len(queryset) > 0: + + obj = queryset[0] + + organization = obj.organization if self.action == 'create': @@ -170,7 +178,10 @@ class TicketViewSet(ModelViewSet): action_keyword = 'triage' - elif self.action is None: + elif( + self.action is None + or self.action == 'metadata' + ): action_keyword = 'view' @@ -178,11 +189,12 @@ class TicketViewSet(ModelViewSet): raise ValueError('unable to determin the action_keyword') - self.permission_required = [ - str('core.' + action_keyword + '_ticket_' + self._ticket_type).lower().replace(' ', '_'), - ] + self._permission_required = str( + 'core.' + action_keyword + '_ticket_' + self._ticket_type).lower().replace(' ', '_' + ) + + return self._permission_required - return super().get_permission_required() def get_queryset(self): @@ -273,57 +285,59 @@ class TicketViewSet(ModelViewSet): ).organization.pk - if ( # Must be first as the priority to pickup - self._ticket_type - and self.action != 'list' - and self.action != 'retrieve' - ): + if organization: - - if self.has_organization_permission( - organization = organization, - permissions_required = [ - 'core.import_ticket_' + str(self._ticket_type).lower().replace(' ', '_') - ] + if ( # Must be first as the priority to pickup + self._ticket_type + and self.action != 'list' + and self.action != 'retrieve' ): - serializer_prefix = serializer_prefix + 'Import' - elif self.has_organization_permission( - organization = organization, - permissions_required = [ - 'core.triage_ticket_' + str(self._ticket_type).lower().replace(' ', '_') - ] - ): + if self.has_organization_permission( + organization = organization, + permissions_required = [ + 'core.import_ticket_' + str(self._ticket_type).lower().replace(' ', '_') + ] + ): - serializer_prefix = serializer_prefix + 'Triage' + serializer_prefix = serializer_prefix + 'Import' - elif self.has_organization_permission( - organization = organization, - permissions_required = [ - 'core.change_ticket_' + str(self._ticket_type).lower().replace(' ', '_') - ] - ): + elif self.has_organization_permission( + organization = organization, + permissions_required = [ + 'core.triage_ticket_' + str(self._ticket_type).lower().replace(' ', '_') + ] + ): - serializer_prefix = serializer_prefix + 'Change' + serializer_prefix = serializer_prefix + 'Triage' - elif self.has_organization_permission( - organization = organization, - permissions_required = [ - 'core.add_ticket_' + str(self._ticket_type).lower().replace(' ', '_') - ] - ): + elif self.has_organization_permission( + organization = organization, + permissions_required = [ + 'core.change_ticket_' + str(self._ticket_type).lower().replace(' ', '_') + ] + ): - serializer_prefix = serializer_prefix + 'Add' + serializer_prefix = serializer_prefix + 'Change' - elif self.has_organization_permission( - organization = organization, - permissions_required = [ - 'core.view_ticket_' + str(self._ticket_type).lower().replace(' ', '_') - ] - ): + elif self.has_organization_permission( + organization = organization, + permissions_required = [ + 'core.add_ticket_' + str(self._ticket_type).lower().replace(' ', '_') + ] + ): - serializer_prefix = serializer_prefix + 'View' + serializer_prefix = serializer_prefix + 'Add' + + elif self.has_organization_permission( + organization = organization, + permissions_required = [ + 'core.view_ticket_' + str(self._ticket_type).lower().replace(' ', '_') + ] + ): + + serializer_prefix = serializer_prefix + 'View' if ( diff --git a/app/core/viewsets/ticket_linked_item.py b/app/core/viewsets/ticket_linked_item.py index 1bac747f..9183bb46 100644 --- a/app/core/viewsets/ticket_linked_item.py +++ b/app/core/viewsets/ticket_linked_item.py @@ -127,6 +127,75 @@ class ViewSet(ModelViewSet): model = TicketLinkedItem + + def get_parent_model(self): + + if not self.parent_model: + + if 'ticket_id' in self.kwargs: + + self.parent_model = Ticket + + self.parent_model_pk_kwarg = 'ticket_id' + + elif 'item_id' in self.kwargs: + + item_type: int = None + + self.parent_model_pk_kwarg = 'item_id' + + for choice in list(map(lambda c: c.name, TicketLinkedItem.Modules)): + + if str(getattr(TicketLinkedItem.Modules, 'CLUSTER').label).lower() == self.kwargs['item_class']: + + item_type = getattr(TicketLinkedItem.Modules, 'CLUSTER').value + + self.parent_model = Cluster + + elif str(getattr(TicketLinkedItem.Modules, 'CONFIG_GROUP').label).lower().replace(' ', '_') == self.kwargs['item_class']: + + item_type = getattr(TicketLinkedItem.Modules, 'CONFIG_GROUP').value + + self.parent_model = ConfigGroups + + elif str(getattr(TicketLinkedItem.Modules, 'DEVICE').label).lower() == self.kwargs['item_class']: + + item_type = getattr(TicketLinkedItem.Modules, 'DEVICE').value + + self.parent_model = Device + + elif str(getattr(TicketLinkedItem.Modules, 'KB').label).lower().replace(' ', '_') == self.kwargs['item_class']: + + item_type = getattr(TicketLinkedItem.Modules, 'KB').value + + self.parent_model = KnowledgeBase + + elif str(getattr(TicketLinkedItem.Modules, 'OPERATING_SYSTEM').label).lower().replace(' ', '_') == self.kwargs['item_class']: + + item_type = getattr(TicketLinkedItem.Modules, 'OPERATING_SYSTEM').value + + self.parent_model = OperatingSystem + + elif str(getattr(TicketLinkedItem.Modules, 'SERVICE').label).lower() == self.kwargs['item_class']: + + item_type = getattr(TicketLinkedItem.Modules, 'SERVICE').value + + self.parent_model = Service + + elif str(getattr(TicketLinkedItem.Modules, 'SOFTWARE').label).lower() == self.kwargs['item_class']: + + item_type = getattr(TicketLinkedItem.Modules, 'SOFTWARE').value + + self.parent_model = Software + + + self.item_type = item_type + + + return self.parent_model + + + def get_serializer_class(self): if ( @@ -140,73 +209,21 @@ class ViewSet(ModelViewSet): return globals()[str( self.model._meta.verbose_name).replace(' ', '') + 'ModelSerializer'] + def get_queryset(self): if 'ticket_id' in self.kwargs: self.queryset = TicketLinkedItem.objects.filter(ticket=self.kwargs['ticket_id']).order_by('id') - self.parent_model = Ticket - - self.parent_model_pk_kwarg = 'ticket_id' - elif 'item_id' in self.kwargs: - item_type: int = None - - self.parent_model_pk_kwarg = 'item_id' - - for choice in list(map(lambda c: c.name, TicketLinkedItem.Modules)): - - if str(getattr(TicketLinkedItem.Modules, 'CLUSTER').label).lower() == self.kwargs['item_class']: - - item_type = getattr(TicketLinkedItem.Modules, 'CLUSTER').value - - self.parent_model = Cluster - - elif str(getattr(TicketLinkedItem.Modules, 'CONFIG_GROUP').label).lower().replace(' ', '_') == self.kwargs['item_class']: - - item_type = getattr(TicketLinkedItem.Modules, 'CONFIG_GROUP').value - - self.parent_model = ConfigGroups - - elif str(getattr(TicketLinkedItem.Modules, 'DEVICE').label).lower() == self.kwargs['item_class']: - - item_type = getattr(TicketLinkedItem.Modules, 'DEVICE').value - - self.parent_model = Device - - elif str(getattr(TicketLinkedItem.Modules, 'KB').label).lower().replace(' ', '_') == self.kwargs['item_class']: - - item_type = getattr(TicketLinkedItem.Modules, 'KB').value - - self.parent_model = KnowledgeBase - - elif str(getattr(TicketLinkedItem.Modules, 'OPERATING_SYSTEM').label).lower().replace(' ', '_') == self.kwargs['item_class']: - - item_type = getattr(TicketLinkedItem.Modules, 'OPERATING_SYSTEM').value - - self.parent_model = OperatingSystem - - elif str(getattr(TicketLinkedItem.Modules, 'SERVICE').label).lower() == self.kwargs['item_class']: - - item_type = getattr(TicketLinkedItem.Modules, 'SERVICE').value - - self.parent_model = Service - - elif str(getattr(TicketLinkedItem.Modules, 'SOFTWARE').label).lower() == self.kwargs['item_class']: - - item_type = getattr(TicketLinkedItem.Modules, 'SOFTWARE').value - - self.parent_model = Software self.queryset = TicketLinkedItem.objects.filter( item=int(self.kwargs['item_id']), - item_type = item_type + item_type = self.item_type ) - self.item_type = item_type - if 'pk' in self.kwargs: self.queryset = self.queryset.filter(pk = self.kwargs['pk']) diff --git a/app/itam/migrations/0007_alter_device_uuid.py b/app/itam/migrations/0007_alter_device_uuid.py new file mode 100644 index 00000000..af59db54 --- /dev/null +++ b/app/itam/migrations/0007_alter_device_uuid.py @@ -0,0 +1,19 @@ +# Generated by Django 5.1.4 on 2024-12-31 03:13 + +import itam.models.device +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('itam', '0006_alter_deviceoperatingsystem_device'), + ] + + operations = [ + migrations.AlterField( + model_name='device', + name='uuid', + field=models.CharField(blank=True, help_text='System GUID/UUID.', max_length=50, null=True, unique=True, validators=[itam.models.device.Device.validate_uuid_format], verbose_name='UUID'), + ), + ] diff --git a/app/itam/models/device.py b/app/itam/models/device.py index 26c50f71..bea5beae 100644 --- a/app/itam/models/device.py +++ b/app/itam/models/device.py @@ -183,7 +183,6 @@ class Device(DeviceCommonFieldsName, SaveHistory): uuid = models.CharField( blank = True, - default = None, help_text = 'System GUID/UUID.', max_length = 50, null = True, diff --git a/app/itam/serializers/inventory.py b/app/itam/serializers/inventory.py index f13d94d6..9d78b1e7 100644 --- a/app/itam/serializers/inventory.py +++ b/app/itam/serializers/inventory.py @@ -1,3 +1,4 @@ +from django.db.models import Q from django.urls import reverse from rest_framework import serializers @@ -41,10 +42,60 @@ class InventorySerializer(serializers.Serializer): ): raise centurion_exceptions.ValidationError( - detail = 'Serial Number or UUID is required', + detail = 'Serial Number and/or UUID is required', code = 'no_serial_or_uuid' ) + + obj = Device.objects.filter( + Q( + name=str(data['name']).lower(), + serial_number = str(data['serial_number']).lower() + ) + | + Q( + name = str(data['name']).lower(), + uuid = str(data['uuid']).lower() + ) + | + Q( + serial_number = str(data['serial_number']).lower() + ) + | + Q( + uuid = str(data['uuid']).lower() + ) + ) + + if len(obj) > 1: + + raise centurion_exceptions.ValidationError( + detail = { + 'detail': 'Object is not unique. Confirm that uuid and/or serial number is unique' + }, + code = 'not_unique' + ) + + elif len(obj) == 1: + + obj = obj[0] + + if obj.name == str(data['name']).lower(): + + if( + obj.serial_number != str(data['serial_number']).lower() + and obj.uuid != str(data['uuid']).lower() + ): + + raise centurion_exceptions.ValidationError( + detail = { + 'detail': 'Device exists, however the serial number and/or UUID dont match' + }, + code = 'not_unique' + ) + + + return data diff --git a/app/itam/tasks/__init__.py b/app/itam/tasks/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/app/api/tasks.py b/app/itam/tasks/inventory.py similarity index 100% rename from app/api/tasks.py rename to app/itam/tasks/inventory.py diff --git a/app/itam/tests/functional/device/test_device_notes_viewset.py b/app/itam/tests/functional/device/test_device_notes_viewset.py index 6dae30a1..b063bb1c 100644 --- a/app/itam/tests/functional/device/test_device_notes_viewset.py +++ b/app/itam/tests/functional/device/test_device_notes_viewset.py @@ -65,3 +65,15 @@ class DeviceNotePermissionsAPI( self.url_view_kwargs = {'device_id': self.note_item.id, 'pk': self.item.pk } self.add_data = {'note': 'a note added', 'organization': self.organization.id} + + + def test_returned_data_from_user_and_global_organizations_only(self): + """Check items returned + + This test case is a over-ride of a test case with the same name. + This model is not a tenancy model making this test not-applicable. + + Items returned from the query Must be from the users organization and + global ONLY! + """ + pass \ No newline at end of file diff --git a/app/itam/tests/functional/device/test_device_viewset.py b/app/itam/tests/functional/device/test_device_viewset.py index d859e506..73ab47a2 100644 --- a/app/itam/tests/functional/device/test_device_viewset.py +++ b/app/itam/tests/functional/device/test_device_viewset.py @@ -13,6 +13,8 @@ from api.tests.abstract.test_metadata_functional import MetadataAttributesFuncti from itam.models.device import Device +from settings.models.app_settings import AppSettings + class ViewSetBase: @@ -47,6 +49,27 @@ class ViewSetBase: self.different_organization = different_organization + + self.global_organization = Organization.objects.create( + name = 'test_global_organization' + ) + + self.global_org_item = self.model.objects.create( + organization = self.global_organization, + name = 'global_item' + ) + + app_settings = AppSettings.objects.get( + owner_organization = None + ) + + app_settings.global_organization = self.global_organization + + app_settings.save() + + + + view_permissions = Permission.objects.get( codename = 'view_' + self.model._meta.model_name, content_type = ContentType.objects.get( diff --git a/app/itam/tests/functional/device_model/test_device_model_viewset.py b/app/itam/tests/functional/device_model/test_device_model_viewset.py index 8a621d1d..cf9bd45e 100644 --- a/app/itam/tests/functional/device_model/test_device_model_viewset.py +++ b/app/itam/tests/functional/device_model/test_device_model_viewset.py @@ -12,6 +12,9 @@ from api.tests.abstract.test_metadata_functional import MetadataAttributesFuncti from itam.models.device import DeviceModel +from settings.models.app_settings import AppSettings + + class ViewSetBase: @@ -46,6 +49,32 @@ class ViewSetBase: self.different_organization = different_organization + + + + self.global_organization = Organization.objects.create( + name = 'test_global_organization' + ) + + self.global_org_item = self.model.objects.create( + organization = self.global_organization, + name = 'global_item' + ) + + app_settings = AppSettings.objects.get( + owner_organization = None + ) + + app_settings.global_organization = self.global_organization + + app_settings.save() + + + + + + + view_permissions = Permission.objects.get( codename = 'view_' + self.model._meta.model_name, content_type = ContentType.objects.get( diff --git a/app/itam/tests/functional/device_operating_system/test_device_operating_system_viewset.py b/app/itam/tests/functional/device_operating_system/test_device_operating_system_viewset.py index 9ed62fd3..824d42d0 100644 --- a/app/itam/tests/functional/device_operating_system/test_device_operating_system_viewset.py +++ b/app/itam/tests/functional/device_operating_system/test_device_operating_system_viewset.py @@ -247,7 +247,17 @@ class DeviceOperatingSystemPermissionsAPI( TestCase, ): - pass + + def test_returned_data_from_user_and_global_organizations_only(self): + """Check items returned + + This test case is a over-ride of a test case with the same name. + This model is not a tenancy model making this test not-applicable. + + Items returned from the query Must be from the users organization and + global ONLY! + """ + pass diff --git a/app/itam/tests/functional/device_software/test_device_software_viewset.py b/app/itam/tests/functional/device_software/test_device_software_viewset.py index ec5dd251..71f35c36 100644 --- a/app/itam/tests/functional/device_software/test_device_software_viewset.py +++ b/app/itam/tests/functional/device_software/test_device_software_viewset.py @@ -214,8 +214,17 @@ class DeviceSoftwarePermissionsAPI( TestCase ): - pass + def test_returned_data_from_user_and_global_organizations_only(self): + """Check items returned + + This test case is a over-ride of a test case with the same name. + This model is not a tenancy model making this test not-applicable. + + Items returned from the query Must be from the users organization and + global ONLY! + """ + pass class DeviceSoftwareViewSet( diff --git a/app/itam/tests/functional/device_type/test_device_type_viewset.py b/app/itam/tests/functional/device_type/test_device_type_viewset.py index cf2c1a8a..62d10b65 100644 --- a/app/itam/tests/functional/device_type/test_device_type_viewset.py +++ b/app/itam/tests/functional/device_type/test_device_type_viewset.py @@ -12,6 +12,9 @@ from api.tests.abstract.test_metadata_functional import MetadataAttributesFuncti from itam.models.device import DeviceType +from settings.models.app_settings import AppSettings + + class ViewSetBase: @@ -46,6 +49,34 @@ class ViewSetBase: self.different_organization = different_organization + + + + + self.global_organization = Organization.objects.create( + name = 'test_global_organization' + ) + + self.global_org_item = self.model.objects.create( + organization = self.global_organization, + name = 'global_item' + ) + + app_settings = AppSettings.objects.get( + owner_organization = None + ) + + app_settings.global_organization = self.global_organization + + app_settings.save() + + + + + + + + view_permissions = Permission.objects.get( codename = 'view_' + self.model._meta.model_name, content_type = ContentType.objects.get( diff --git a/app/itam/tests/functional/endpoint_operating_system_installs/test_operating_system_installs_viewset.py b/app/itam/tests/functional/endpoint_operating_system_installs/test_operating_system_installs_viewset.py index eafc9aea..19581e9b 100644 --- a/app/itam/tests/functional/endpoint_operating_system_installs/test_operating_system_installs_viewset.py +++ b/app/itam/tests/functional/endpoint_operating_system_installs/test_operating_system_installs_viewset.py @@ -170,7 +170,17 @@ class OperatingSystemInstallsPermissionsAPI( TestCase ): - pass + + def test_returned_data_from_user_and_global_organizations_only(self): + """Check items returned + + This test case is a over-ride of a test case with the same name. + This model is not a tenancy model making this test not-applicable. + + Items returned from the query Must be from the users organization and + global ONLY! + """ + pass diff --git a/app/itam/tests/functional/endpoint_software_installs/test_software_installs_viewset.py b/app/itam/tests/functional/endpoint_software_installs/test_software_installs_viewset.py index 91c290d7..6771e00b 100644 --- a/app/itam/tests/functional/endpoint_software_installs/test_software_installs_viewset.py +++ b/app/itam/tests/functional/endpoint_software_installs/test_software_installs_viewset.py @@ -219,7 +219,17 @@ class SoftwareInstallsPermissionsAPI( TestCase ): - pass + + def test_returned_data_from_user_and_global_organizations_only(self): + """Check items returned + + This test case is a over-ride of a test case with the same name. + This model is not a tenancy model making this test not-applicable. + + Items returned from the query Must be from the users organization and + global ONLY! + """ + pass diff --git a/app/itam/tests/functional/inventory/test_inventory_viewset.py b/app/itam/tests/functional/inventory/test_inventory_viewset.py new file mode 100644 index 00000000..e74161c7 --- /dev/null +++ b/app/itam/tests/functional/inventory/test_inventory_viewset.py @@ -0,0 +1,510 @@ +import copy +import pytest + +from unittest.mock import patch + +from django.contrib.auth.models import AnonymousUser, User +from django.contrib.contenttypes.models import ContentType +from django.shortcuts import reverse +from django.test import Client, TestCase + +from access.models import Organization, Team, TeamUsers, Permission + +from api.tests.abstract.api_serializer_viewset import SerializersTestCases +from api.tests.abstract.api_permissions_viewset import ( + APIPermissionAdd, + APIPermissionChange +) +from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional, MetaDataNavigationEntriesFunctional + +from itam.models.device import Device +from itam.tasks.inventory import process_inventory + +from settings.models.user_settings import UserSettings + + + +class ViewSetBase: + + model = Device + + app_namespace = 'v2' + + url_name = '_api_v2_inventory' + + @classmethod + def setUpTestData(self): + """Setup Test + + 1. Create an organization for user and item + . create an organization that is different to item + 2. Create a team + 3. create teams with each permission: view, add, change, delete + 4. create a user per team + """ + + organization = Organization.objects.create(name='test_org') + + self.organization = organization + + different_organization = Organization.objects.create(name='test_different_organization') + + self.different_organization = different_organization + + + view_permissions = Permission.objects.get( + codename = 'view_' + self.model._meta.model_name, + content_type = ContentType.objects.get( + app_label = self.model._meta.app_label, + model = self.model._meta.model_name, + ) + ) + + view_team = Team.objects.create( + team_name = 'view_team', + organization = organization, + ) + + view_team.permissions.set([view_permissions]) + + + + add_permissions = Permission.objects.get( + codename = 'add_' + self.model._meta.model_name, + content_type = ContentType.objects.get( + app_label = self.model._meta.app_label, + model = self.model._meta.model_name, + ) + ) + + add_team = Team.objects.create( + team_name = 'add_team', + organization = organization, + ) + + add_team.permissions.set([add_permissions]) + + + + change_permissions = Permission.objects.get( + codename = 'change_' + self.model._meta.model_name, + content_type = ContentType.objects.get( + app_label = self.model._meta.app_label, + model = self.model._meta.model_name, + ) + ) + + change_team = Team.objects.create( + team_name = 'change_team', + organization = organization, + ) + + change_team.permissions.set([change_permissions]) + + + + delete_permissions = Permission.objects.get( + codename = 'delete_' + self.model._meta.model_name, + content_type = ContentType.objects.get( + app_label = self.model._meta.app_label, + model = self.model._meta.model_name, + ) + ) + + delete_team = Team.objects.create( + team_name = 'delete_team', + organization = organization, + ) + + delete_team.permissions.set([delete_permissions]) + + + self.no_permissions_user = User.objects.create_user(username="test_no_permissions", password="password") + + + self.view_user = User.objects.create_user(username="test_user_view", password="password") + teamuser = TeamUsers.objects.create( + team = view_team, + user = self.view_user + ) + + + self.item = self.model.objects.create( + organization = self.organization, + name = 'one-add' + ) + + self.other_org_item = self.model.objects.create( + organization = different_organization, + name = 'other_item' + ) + + + self.add_user = User.objects.create_user(username="test_user_add", password="password") + teamuser = TeamUsers.objects.create( + team = add_team, + user = self.add_user + ) + + user_settings = UserSettings.objects.get( + user = self.add_user + ) + user_settings.default_organization = self.organization + user_settings.save() + + + + + self.change_user = User.objects.create_user(username="test_user_change", password="password") + teamuser = TeamUsers.objects.create( + team = change_team, + user = self.change_user + ) + + user_settings = UserSettings.objects.get( + user = self.change_user + ) + user_settings.default_organization = self.organization + user_settings.save() + + + + self.delete_user = User.objects.create_user(username="test_user_delete", password="password") + teamuser = TeamUsers.objects.create( + team = delete_team, + user = self.delete_user + ) + + + self.different_organization_user = User.objects.create_user(username="test_different_organization_user", password="password") + + user_settings = UserSettings.objects.get( + user = self.different_organization_user + ) + user_settings.default_organization = different_organization + user_settings.save() + + + different_organization_team = Team.objects.create( + team_name = 'different_organization_team', + organization = different_organization, + ) + + different_organization_team.permissions.set([ + view_permissions, + add_permissions, + change_permissions, + delete_permissions, + ]) + + TeamUsers.objects.create( + team = different_organization_team, + user = self.different_organization_user + ) + + + self.inventory: dict = { + "details": { + "name": "string", + "serial_number": "string", + "uuid": "fc65b513-3ddc-4c90-af20-215b2db73455" + }, + "os": { + "name": "string", + "version_major": 1, + "version": "1.2" + }, + "software": [ + { + "name": "string", + "category": "string", + "version": "1.1.1" + } + ] + } + + self.add_data = copy.deepcopy(self.inventory) + + self.change_data = copy.deepcopy(self.inventory) + + self.change_data['details']['name'] = 'device2' + self.change_data['details']['serial_number'] = 'sn123' + self.change_data['details']['uuid'] = '93e8e991-ad07-4b7b-a1a6-59968a5b54f8' + + Device.objects.create( + organization = self.organization, + name = self.change_data['details']['name'], + serial_number = self.change_data['details']['serial_number'], + uuid = self.change_data['details']['uuid'], + ) + + + +class DevicePermissionsAPI( + ViewSetBase, + # APIPermissionAdd, + # APIPermissionChange, + TestCase +): + + url_kwargs = None + + url_view_kwargs = None + + + @patch.object(process_inventory, 'delay') + def test_add_has_permission(self, process_inventory): + """ Check correct permission for add + + This test case is a over-ride of a test case with the same name. + This was done as the testcase needed to be modified to work with the + itam inventory endpoint. + + Attempt to add as user with permission + """ + + client = Client() + if self.url_kwargs: + + url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs) + + else: + + url = reverse(self.app_namespace + ':' + self.url_name + '-list') + + + client.force_login(self.add_user) + response = client.post(url, data=self.add_data, content_type = 'application/json') + + assert response.status_code == 200 + + + + + + + @patch.object(process_inventory, 'delay') + def test_add_user_anon_denied(self, process_inventory): + """ Check correct permission for add + + Attempt to add as anon user + """ + + client = Client() + if self.url_kwargs: + + url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs) + + else: + + url = reverse(self.app_namespace + ':' + self.url_name + '-list') + + + response = client.put(url, data=self.add_data) + + assert response.status_code == 401 + + + @patch.object(process_inventory, 'delay') + def test_add_no_permission_denied(self, process_inventory): + """ Check correct permission for add + + Attempt to add as user with no permissions + """ + + client = Client() + if self.url_kwargs: + + url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs) + + else: + + url = reverse(self.app_namespace + ':' + self.url_name + '-list') + + + client.force_login(self.no_permissions_user) + response = client.post(url, data=self.add_data) + + assert response.status_code == 403 + + + # # @pytest.mark.skip(reason="ToDO: figure out why fails") + # def test_add_different_organization_denied(self): + # """ Check correct permission for add + + # attempt to add as user from different organization + # """ + + # client = Client() + # if self.url_kwargs: + + # url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs) + + # else: + + # url = reverse(self.app_namespace + ':' + self.url_name + '-list') + + + # client.force_login(self.different_organization_user) + # response = client.post(url, data=self.add_data) + + # assert response.status_code == 403 + + + @patch.object(process_inventory, 'delay') + def test_add_permission_view_denied(self, process_inventory): + """ Check correct permission for add + + Attempt to add a user with view permission + """ + + client = Client() + if self.url_kwargs: + + url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs) + + else: + + url = reverse(self.app_namespace + ':' + self.url_name + '-list') + + + client.force_login(self.view_user) + response = client.post(url, data=self.add_data) + + assert response.status_code == 403 + + + + + + + + + + + + + + + + + + + @patch.object(process_inventory, 'delay') + def test_change_has_permission(self, process_inventory): + """ Check correct permission for change + + This test case is a over-ride of a test case with the same name. + This was done as the testcase needed to be modified to work with the + itam inventory endpoint. + + Make change with user who has change permission + """ + + client = Client() + url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs=self.url_view_kwargs) + + + client.force_login(self.change_user) + response = client.post(url, data=self.change_data, content_type='application/json') + + assert response.status_code == 200 + + + + + + + + + + + + + + + + @patch.object(process_inventory, 'delay') + def test_change_user_anon_denied(self, process_inventory): + """ Check correct permission for change + + Attempt to change as anon + """ + + client = Client() + url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs=self.url_view_kwargs) + + + response = client.post(url, data=self.change_data, content_type='application/json') + + assert response.status_code == 401 + + + @patch.object(process_inventory, 'delay') + def test_change_no_permission_denied(self, process_inventory): + """ Ensure permission view cant make change + + Attempt to make change as user without permissions + """ + + client = Client() + url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs=self.url_view_kwargs) + + + client.force_login(self.no_permissions_user) + response = client.post(url, data=self.change_data, content_type='application/json') + + assert response.status_code == 403 + + + @pytest.mark.skip( reason = 'see https://github.com/nofusscomputing/centurion_erp/issues/461' ) + @patch.object(process_inventory, 'delay') + def test_change_different_organization_denied(self, process_inventory): + """ Ensure permission view cant make change + + Attempt to make change as user from different organization + """ + + client = Client() + url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs=self.url_view_kwargs) + + + client.force_login(self.different_organization_user) + response = client.post(url, data=self.change_data, content_type='application/json') + + assert response.status_code == 403 + + + + @patch.object(process_inventory, 'delay') + def test_change_permission_view_denied(self, process_inventory): + """ Ensure permission view cant make change + + Attempt to make change as user with view permission + """ + + client = Client() + url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs=self.url_view_kwargs) + + + client.force_login(self.view_user) + response = client.post(url, data=self.change_data, content_type='application/json') + + assert response.status_code == 403 + + + @patch.object(process_inventory, 'delay') + def test_change_permission_add_denied(self, process_inventory): + """ Ensure permission view cant make change + + Attempt to make change as user with add permission + """ + + client = Client() + url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs=self.url_view_kwargs) + + + client.force_login(self.add_user) + response = client.post(url, data=self.change_data, content_type='application/json') + + assert response.status_code == 403 diff --git a/app/itam/tests/functional/operating_system/test_operating_system_notes_viewset.py b/app/itam/tests/functional/operating_system/test_operating_system_notes_viewset.py index 77120aa5..ada37307 100644 --- a/app/itam/tests/functional/operating_system/test_operating_system_notes_viewset.py +++ b/app/itam/tests/functional/operating_system/test_operating_system_notes_viewset.py @@ -65,3 +65,15 @@ class OperatingSystemNotePermissionsAPI( self.url_view_kwargs = {'operating_system_id': self.note_item.id, 'pk': self.item.pk } self.add_data = {'note': 'a note added', 'organization': self.organization.id} + + + def test_returned_data_from_user_and_global_organizations_only(self): + """Check items returned + + This test case is a over-ride of a test case with the same name. + This model is not a tenancy model making this test not-applicable. + + Items returned from the query Must be from the users organization and + global ONLY! + """ + pass \ No newline at end of file diff --git a/app/itam/tests/functional/operating_system/test_operating_system_viewset.py b/app/itam/tests/functional/operating_system/test_operating_system_viewset.py index ee7ba5a8..114438ff 100644 --- a/app/itam/tests/functional/operating_system/test_operating_system_viewset.py +++ b/app/itam/tests/functional/operating_system/test_operating_system_viewset.py @@ -12,6 +12,9 @@ from api.tests.abstract.test_metadata_functional import MetadataAttributesFuncti from itam.models.operating_system import OperatingSystem +from settings.models.app_settings import AppSettings + + class ViewSetBase: @@ -46,6 +49,34 @@ class ViewSetBase: self.different_organization = different_organization + + + + self.global_organization = Organization.objects.create( + name = 'test_global_organization' + ) + + self.global_org_item = self.model.objects.create( + organization = self.global_organization, + name = 'global_item' + ) + + app_settings = AppSettings.objects.get( + owner_organization = None + ) + + app_settings.global_organization = self.global_organization + + app_settings.save() + + + + + + + + + view_permissions = Permission.objects.get( codename = 'view_' + self.model._meta.model_name, content_type = ContentType.objects.get( diff --git a/app/itam/tests/functional/operating_system_version/test_operating_system_version_viewset.py b/app/itam/tests/functional/operating_system_version/test_operating_system_version_viewset.py index 747c6690..a82a301c 100644 --- a/app/itam/tests/functional/operating_system_version/test_operating_system_version_viewset.py +++ b/app/itam/tests/functional/operating_system_version/test_operating_system_version_viewset.py @@ -12,6 +12,9 @@ from api.tests.abstract.test_metadata_functional import MetadataAttributesFuncti from itam.models.operating_system import OperatingSystem, OperatingSystemVersion +from settings.models.app_settings import AppSettings + + class ViewSetBase: @@ -46,6 +49,39 @@ class ViewSetBase: self.different_organization = different_organization + + os = OperatingSystem.objects.create( + organization = self.organization, + name = 'one-add' + ) + + + + self.global_organization = Organization.objects.create( + name = 'test_global_organization' + ) + + self.global_org_item = self.model.objects.create( + organization = self.global_organization, + name = '22', + operating_system = os + ) + + app_settings = AppSettings.objects.get( + owner_organization = None + ) + + app_settings.global_organization = self.global_organization + + app_settings.save() + + + + + + + + view_permissions = Permission.objects.get( codename = 'view_' + self.model._meta.model_name, content_type = ContentType.objects.get( @@ -122,11 +158,6 @@ class ViewSetBase: user = self.view_user ) - os = OperatingSystem.objects.create( - organization = self.organization, - name = 'one-add' - ) - os_b = OperatingSystem.objects.create( organization = different_organization, name = 'two-add' diff --git a/app/itam/tests/functional/software/test_software_notes_viewset.py b/app/itam/tests/functional/software/test_software_notes_viewset.py index 9e155af0..e9eaa656 100644 --- a/app/itam/tests/functional/software/test_software_notes_viewset.py +++ b/app/itam/tests/functional/software/test_software_notes_viewset.py @@ -65,3 +65,15 @@ class SoftwareNotePermissionsAPI( self.url_view_kwargs = {'software_id': self.note_item.id, 'pk': self.item.pk } self.add_data = {'note': 'a note added', 'organization': self.organization.id} + + + def test_returned_data_from_user_and_global_organizations_only(self): + """Check items returned + + This test case is a over-ride of a test case with the same name. + This model is not a tenancy model making this test not-applicable. + + Items returned from the query Must be from the users organization and + global ONLY! + """ + pass \ No newline at end of file diff --git a/app/itam/tests/functional/software/test_software_viewset.py b/app/itam/tests/functional/software/test_software_viewset.py index 7b05b28a..eb5616cc 100644 --- a/app/itam/tests/functional/software/test_software_viewset.py +++ b/app/itam/tests/functional/software/test_software_viewset.py @@ -12,6 +12,8 @@ from api.tests.abstract.test_metadata_functional import MetadataAttributesFuncti from itam.models.software import Software +from settings.models.app_settings import AppSettings + class ViewSetBase: @@ -46,6 +48,35 @@ class ViewSetBase: self.different_organization = different_organization + + + + + self.global_organization = Organization.objects.create( + name = 'test_global_organization' + ) + + self.global_org_item = self.model.objects.create( + organization = self.global_organization, + name = 'global_item' + ) + + app_settings = AppSettings.objects.get( + owner_organization = None + ) + + app_settings.global_organization = self.global_organization + + app_settings.save() + + + + + + + + + view_permissions = Permission.objects.get( codename = 'view_' + self.model._meta.model_name, content_type = ContentType.objects.get( diff --git a/app/itam/tests/functional/software_category/test_software_category_viewset.py b/app/itam/tests/functional/software_category/test_software_category_viewset.py index 499505a9..94ad01d7 100644 --- a/app/itam/tests/functional/software_category/test_software_category_viewset.py +++ b/app/itam/tests/functional/software_category/test_software_category_viewset.py @@ -12,6 +12,8 @@ from api.tests.abstract.test_metadata_functional import MetadataAttributesFuncti from itam.models.software import SoftwareCategory +from settings.models.app_settings import AppSettings + class ViewSetBase: @@ -46,6 +48,34 @@ class ViewSetBase: self.different_organization = different_organization + + + + + self.global_organization = Organization.objects.create( + name = 'test_global_organization' + ) + + self.global_org_item = self.model.objects.create( + organization = self.global_organization, + name = 'global_item' + ) + + app_settings = AppSettings.objects.get( + owner_organization = None + ) + + app_settings.global_organization = self.global_organization + + app_settings.save() + + + + + + + + view_permissions = Permission.objects.get( codename = 'view_' + self.model._meta.model_name, content_type = ContentType.objects.get( diff --git a/app/itam/tests/functional/software_version/test_software_version_viewset.py b/app/itam/tests/functional/software_version/test_software_version_viewset.py index e8b32cd9..fc20422f 100644 --- a/app/itam/tests/functional/software_version/test_software_version_viewset.py +++ b/app/itam/tests/functional/software_version/test_software_version_viewset.py @@ -12,6 +12,8 @@ from api.tests.abstract.test_metadata_functional import MetadataAttributesFuncti from itam.models.software import Software, SoftwareVersion +from settings.models.app_settings import AppSettings + class ViewSetBase: @@ -46,6 +48,39 @@ class ViewSetBase: self.different_organization = different_organization + + software = Software.objects.create( + organization = self.organization, + name = 'software' + ) + + + + + self.global_organization = Organization.objects.create( + name = 'test_global_organization' + ) + + self.global_org_item = self.model.objects.create( + organization = self.global_organization, + name = '12', + software = software + ) + + app_settings = AppSettings.objects.get( + owner_organization = None + ) + + app_settings.global_organization = self.global_organization + + app_settings.save() + + + + + + + view_permissions = Permission.objects.get( codename = 'view_' + self.model._meta.model_name, content_type = ContentType.objects.get( @@ -122,11 +157,6 @@ class ViewSetBase: user = self.view_user ) - software = Software.objects.create( - organization = self.organization, - name = 'software' - ) - software_b = Software.objects.create( organization = different_organization, name = 'software-b' diff --git a/app/itam/tests/functional/test_itam_viewset.py b/app/itam/tests/functional/test_itam_viewset.py index 128ab06c..a97f618d 100644 --- a/app/itam/tests/functional/test_itam_viewset.py +++ b/app/itam/tests/functional/test_itam_viewset.py @@ -2,6 +2,8 @@ from django.contrib.auth.models import User from django.shortcuts import reverse from django.test import Client, TestCase +from rest_framework.permissions import IsAuthenticated + from access.models import Organization from api.tests.abstract.viewsets import ViewSetCommon @@ -39,4 +41,18 @@ class ItamViewset( client.force_login(self.view_user) - self.http_options_response_list = client.options(url) \ No newline at end of file + self.http_options_response_list = client.options(url) + + + + def test_view_attr_permission_classes_value(self): + """Attribute Test + + Attribute `permission_classes` must be metadata class `ReactUIMetadata` + """ + + view_set = self.viewset() + + assert view_set.permission_classes[0] is IsAuthenticated + + assert len(view_set.permission_classes) == 1 diff --git a/app/api/tests/unit/inventory/test_api_inventory.py b/app/itam/tests/unit/inventory/test_api_inventory.py similarity index 99% rename from app/api/tests/unit/inventory/test_api_inventory.py rename to app/itam/tests/unit/inventory/test_api_inventory.py index 5490d72a..29a346c3 100644 --- a/app/api/tests/unit/inventory/test_api_inventory.py +++ b/app/itam/tests/unit/inventory/test_api_inventory.py @@ -16,11 +16,10 @@ from access.models import Organization, Team, TeamUsers, Permission from api.views.mixin import OrganizationPermissionAPI from api.serializers.inventory import Inventory -from api.tasks import process_inventory - from itam.models.device import Device, DeviceOperatingSystem, DeviceSoftware from itam.models.operating_system import OperatingSystem, OperatingSystemVersion from itam.models.software import Software, SoftwareCategory, SoftwareVersion +from itam.tasks.inventory import process_inventory from settings.models.user_settings import UserSettings diff --git a/app/api/tests/unit/inventory/test_inventory_permission_api.py b/app/itam/tests/unit/inventory/test_inventory_permission_api.py similarity index 100% rename from app/api/tests/unit/inventory/test_inventory_permission_api.py rename to app/itam/tests/unit/inventory/test_inventory_permission_api.py diff --git a/app/itam/viewsets/index.py b/app/itam/viewsets/index.py index f0e9c0f3..dd55e1a7 100644 --- a/app/itam/viewsets/index.py +++ b/app/itam/viewsets/index.py @@ -3,12 +3,12 @@ from drf_spectacular.utils import extend_schema from rest_framework.response import Response from rest_framework.reverse import reverse -from api.viewsets.common import CommonViewSet +from api.viewsets.common import IndexViewset @extend_schema(exclude = True) -class Index(CommonViewSet): +class Index(IndexViewset): allowed_methods: list = [ 'GET', diff --git a/app/itam/viewsets/inventory.py b/app/itam/viewsets/inventory.py index fd6f7f25..acb3eb10 100644 --- a/app/itam/viewsets/inventory.py +++ b/app/itam/viewsets/inventory.py @@ -1,11 +1,13 @@ import json +from django.db.models import Q + +from kombu.exceptions import OperationalError + from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiResponse from rest_framework.response import Response - -from api.tasks import process_inventory from api.viewsets.common import ModelCreateViewSet from core import exceptions as centurion_exception @@ -13,6 +15,7 @@ from core.http.common import Http from itam.models.device import Device from itam.serializers.inventory import InventorySerializer +from itam.tasks.inventory import process_inventory from settings.models.user_settings import UserSettings @@ -101,16 +104,42 @@ class ViewSet( ModelCreateViewSet ): self.default_organization = UserSettings.objects.get(user=request.user).default_organization - if Device.objects.filter(slug=str(data.validated_data['details']['name']).lower()).exists(): + obj_organaization_id = getattr(self.default_organization, 'id', None) - self.obj = Device.objects.get(slug=str(data.validated_data['details']['name']).lower()) - device = self.obj + obj = Device.objects.filter( + Q( + name=str(data.validated_data['details']['name']).lower(), + serial_number = str(data.validated_data['details']['serial_number']).lower() - task = process_inventory.delay(data.validated_data, self.default_organization.id) + ) + | + Q( + name = str(data.validated_data['details']['name']).lower(), + uuid = str(data.validated_data['details']['uuid']).lower() + ) + ) + + + if len(obj) == 1: + + obj_organaization_id = obj[0].organization.id + + + if not obj_organaization_id: + + raise centurion_exception.ValidationError({ + 'detail': 'No Default organization set for user' + }) + + task = process_inventory.delay(data.validated_data, obj_organaization_id) response_data: dict = {"task_id": f"{task.id}"} + except OperationalError as e: + + status = 503 + response_data = f'RabbitMQ error: {e.args[0]}' except centurion_exception.PermissionDenied as e: @@ -193,4 +222,8 @@ class ViewSet( ModelCreateViewSet ): self.inventory_action = 'new' - return super().get_permission_required() + return self.permission_required + + + def get_serializer_class(self): + return InventorySerializer diff --git a/app/itim/serializers/cluster.py b/app/itim/serializers/cluster.py index 774e5fc8..96ad047a 100644 --- a/app/itim/serializers/cluster.py +++ b/app/itim/serializers/cluster.py @@ -54,6 +54,7 @@ class ClusterModelSerializer( return { '_self': item.get_url( request = self._context['view'].request ), + 'external_links': reverse("v2:_api_v2_external_link-list", request=self._context['view'].request) + '?cluster=true', 'history': reverse( "v2:_api_v2_model_history-list", request=self._context['view'].request, @@ -71,6 +72,7 @@ class ClusterModelSerializer( } ), 'notes': reverse("v2:_api_v2_cluster_notes-list", request=self._context['view'].request, kwargs={'cluster_id': item.pk}), + 'service': reverse("v2:_api_v2_service_cluster-list", request=self._context['view'].request, kwargs={'cluster_id': item.pk}), 'tickets': reverse( "v2:_api_v2_item_tickets-list", request=self._context['view'].request, diff --git a/app/itim/tests/functional/cluster/test_cluster_viewset.py b/app/itim/tests/functional/cluster/test_cluster_viewset.py index f200a0ca..6fa23ee3 100644 --- a/app/itim/tests/functional/cluster/test_cluster_viewset.py +++ b/app/itim/tests/functional/cluster/test_cluster_viewset.py @@ -12,6 +12,8 @@ from api.tests.abstract.test_metadata_functional import MetadataAttributesFuncti from itim.models.clusters import Cluster +from settings.models.app_settings import AppSettings + class ViewSetBase: @@ -46,6 +48,33 @@ class ViewSetBase: self.different_organization = different_organization + + + + self.global_organization = Organization.objects.create( + name = 'test_global_organization' + ) + + self.global_org_item = self.model.objects.create( + organization = self.global_organization, + name = 'global_item' + ) + + app_settings = AppSettings.objects.get( + owner_organization = None + ) + + app_settings.global_organization = self.global_organization + + app_settings.save() + + + + + + + + view_permissions = Permission.objects.get( codename = 'view_' + self.model._meta.model_name, content_type = ContentType.objects.get( diff --git a/app/itim/tests/functional/cluster_types/test_cluster_type_viewset.py b/app/itim/tests/functional/cluster_types/test_cluster_type_viewset.py index b130f96e..5b0d79eb 100644 --- a/app/itim/tests/functional/cluster_types/test_cluster_type_viewset.py +++ b/app/itim/tests/functional/cluster_types/test_cluster_type_viewset.py @@ -12,6 +12,8 @@ from api.tests.abstract.test_metadata_functional import MetadataAttributesFuncti from itim.models.clusters import ClusterType +from settings.models.app_settings import AppSettings + class ViewSetBase: @@ -46,6 +48,31 @@ class ViewSetBase: self.different_organization = different_organization + + + + self.global_organization = Organization.objects.create( + name = 'test_global_organization' + ) + + self.global_org_item = self.model.objects.create( + organization = self.global_organization, + name = 'global_item' + ) + + app_settings = AppSettings.objects.get( + owner_organization = None + ) + + app_settings.global_organization = self.global_organization + + app_settings.save() + + + + + + view_permissions = Permission.objects.get( codename = 'view_' + self.model._meta.model_name, content_type = ContentType.objects.get( diff --git a/app/itim/tests/functional/port/test_port_viewset.py b/app/itim/tests/functional/port/test_port_viewset.py index d373d492..00f52513 100644 --- a/app/itim/tests/functional/port/test_port_viewset.py +++ b/app/itim/tests/functional/port/test_port_viewset.py @@ -12,6 +12,8 @@ from api.tests.abstract.test_metadata_functional import MetadataAttributesFuncti from itim.models.services import Port +from settings.models.app_settings import AppSettings + class ViewSetBase: @@ -46,6 +48,34 @@ class ViewSetBase: self.different_organization = different_organization + + + + + + self.global_organization = Organization.objects.create( + name = 'test_global_organization' + ) + + self.global_org_item = self.model.objects.create( + organization = self.global_organization, + number = 8181, + protocol = Port.Protocol.TCP + ) + + app_settings = AppSettings.objects.get( + owner_organization = None + ) + + app_settings.global_organization = self.global_organization + + app_settings.save() + + + + + + view_permissions = Permission.objects.get( codename = 'view_' + self.model._meta.model_name, content_type = ContentType.objects.get( diff --git a/app/itim/tests/functional/service/test_service_notes_viewset.py b/app/itim/tests/functional/service/test_service_notes_viewset.py index 41ace473..820ba2b4 100644 --- a/app/itim/tests/functional/service/test_service_notes_viewset.py +++ b/app/itim/tests/functional/service/test_service_notes_viewset.py @@ -60,3 +60,15 @@ class ServiceNotePermissionsAPI( self.url_view_kwargs = {'service_id': self.note_item.id, 'pk': self.item.pk } self.add_data = {'note': 'a note added', 'organization': self.organization.id} + + + def test_returned_data_from_user_and_global_organizations_only(self): + """Check items returned + + This test case is a over-ride of a test case with the same name. + This model is not a tenancy model making this test not-applicable. + + Items returned from the query Must be from the users organization and + global ONLY! + """ + pass \ No newline at end of file diff --git a/app/itim/tests/functional/service/test_service_viewset.py b/app/itim/tests/functional/service/test_service_viewset.py index 4840817a..e073cfd6 100644 --- a/app/itim/tests/functional/service/test_service_viewset.py +++ b/app/itim/tests/functional/service/test_service_viewset.py @@ -14,6 +14,8 @@ from itam.models.device import Device from itim.models.services import Service, Port +from settings.models.app_settings import AppSettings + class ViewSetBase: @@ -48,6 +50,40 @@ class ViewSetBase: self.different_organization = different_organization + + + device = Device.objects.create( + organization=organization, + name = 'device' + ) + + + + self.global_organization = Organization.objects.create( + name = 'test_global_organization' + ) + + self.global_org_item = self.model.objects.create( + organization = self.global_organization, + name = 'global_item', + device = device, + config_key_variable = 'value' + ) + + app_settings = AppSettings.objects.get( + owner_organization = None + ) + + app_settings.global_organization = self.global_organization + + app_settings.save() + + + + + + + view_permissions = Permission.objects.get( codename = 'view_' + self.model._meta.model_name, content_type = ContentType.objects.get( @@ -124,11 +160,6 @@ class ViewSetBase: user = self.view_user ) - device = Device.objects.create( - organization=organization, - name = 'device' - ) - port = Port.objects.create( organization=organization, number = 80, diff --git a/app/itim/tests/functional/service_cluster/test_service_cluster_viewset.py b/app/itim/tests/functional/service_cluster/test_service_cluster_viewset.py new file mode 100644 index 00000000..b3fd6ef6 --- /dev/null +++ b/app/itim/tests/functional/service_cluster/test_service_cluster_viewset.py @@ -0,0 +1,190 @@ +import pytest + +from django.contrib.auth.models import User +from django.contrib.contenttypes.models import ContentType +from django.test import TestCase + +from access.models import Organization, Team, TeamUsers, Permission + +from api.tests.abstract.api_permissions_viewset import APIPermissionView +from api.tests.abstract.api_serializer_viewset import SerializerView +from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional, MetaDataNavigationEntriesFunctional + +from itim.models.clusters import Cluster +from itim.models.services import Service, Port + + + +class ViewSetBase: + + model = Service + + app_namespace = 'v2' + + url_name = '_api_v2_service_cluster' + + @classmethod + def setUpTestData(self): + """Setup Test + + 1. Create an organization for user and item + . create an organization that is different to item + 2. Create a team + 3. create teams with each permission: view, add, change, delete + 4. create a user per team + """ + + organization = Organization.objects.create(name='test_org') + + self.organization = organization + + different_organization = Organization.objects.create(name='test_different_organization') + + self.different_organization = different_organization + + + view_permissions = Permission.objects.get( + codename = 'view_' + self.model._meta.model_name, + content_type = ContentType.objects.get( + app_label = self.model._meta.app_label, + model = self.model._meta.model_name, + ) + ) + + view_team = Team.objects.create( + team_name = 'view_team', + organization = organization, + ) + + view_team.permissions.set([view_permissions]) + + + + add_permissions = Permission.objects.get( + codename = 'add_' + self.model._meta.model_name, + content_type = ContentType.objects.get( + app_label = self.model._meta.app_label, + model = self.model._meta.model_name, + ) + ) + + + change_permissions = Permission.objects.get( + codename = 'change_' + self.model._meta.model_name, + content_type = ContentType.objects.get( + app_label = self.model._meta.app_label, + model = self.model._meta.model_name, + ) + ) + + + + delete_permissions = Permission.objects.get( + codename = 'delete_' + self.model._meta.model_name, + content_type = ContentType.objects.get( + app_label = self.model._meta.app_label, + model = self.model._meta.model_name, + ) + ) + + + + self.no_permissions_user = User.objects.create_user(username="test_no_permissions", password="password") + + + self.view_user = User.objects.create_user(username="test_user_view", password="password") + teamuser = TeamUsers.objects.create( + team = view_team, + user = self.view_user + ) + + cluster = Cluster.objects.create( + organization=organization, + name = 'cluster' + ) + + port = Port.objects.create( + organization=organization, + number = 80, + protocol = Port.Protocol.TCP + ) + + self.item = self.model.objects.create( + organization=organization, + name = 'os name', + cluster = cluster, + config_key_variable = 'value' + ) + + self.other_org_item = self.model.objects.create( + organization=different_organization, + name = 'os name b', + cluster = cluster, + config_key_variable = 'values' + ) + + self.item.port.set([ port ]) + + + + self.url_view_kwargs = {'cluster_id': cluster.id, 'pk': self.item.id} + + self.url_kwargs = {'cluster_id': cluster.id} + + + self.different_organization_user = User.objects.create_user(username="test_different_organization_user", password="password") + + + different_organization_team = Team.objects.create( + team_name = 'different_organization_team', + organization = different_organization, + ) + + different_organization_team.permissions.set([ + view_permissions, + add_permissions, + change_permissions, + delete_permissions, + ]) + + TeamUsers.objects.create( + team = different_organization_team, + user = self.different_organization_user + ) + + + +class ServicePermissionsAPI(ViewSetBase, APIPermissionView, TestCase): + + + def test_returned_data_from_user_and_global_organizations_only(self): + """Check items returned + + This test case is a over-ride of a test case with the same name. + This model is not a tenancy model making this test not-applicable. + + Items returned from the query Must be from the users organization and + global ONLY! + """ + pass + + + + +class ServiceViewSet(ViewSetBase, SerializerView, TestCase): + + pass + + + +class ServiceMetadata( + ViewSetBase, + MetadataAttributesFunctional, + # MetaDataNavigationEntriesFunctional, + TestCase +): + + # menu_id = 'itim' + + # menu_entry_id = 'service' + pass diff --git a/app/itim/tests/functional/service_device/test_service_device_viewset.py b/app/itim/tests/functional/service_device/test_service_device_viewset.py new file mode 100644 index 00000000..08c79735 --- /dev/null +++ b/app/itim/tests/functional/service_device/test_service_device_viewset.py @@ -0,0 +1,192 @@ +import pytest + +from django.contrib.auth.models import User +from django.contrib.contenttypes.models import ContentType +from django.test import TestCase + +from access.models import Organization, Team, TeamUsers, Permission + +from api.tests.abstract.api_permissions_viewset import APIPermissionView +from api.tests.abstract.api_serializer_viewset import SerializerView +from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional, MetaDataNavigationEntriesFunctional + +from itim.models.clusters import Cluster +from itim.models.services import Service, Port + +from itam.models.device import Device + + + +class ViewSetBase: + + model = Service + + app_namespace = 'v2' + + url_name = '_api_v2_service_device' + + @classmethod + def setUpTestData(self): + """Setup Test + + 1. Create an organization for user and item + . create an organization that is different to item + 2. Create a team + 3. create teams with each permission: view, add, change, delete + 4. create a user per team + """ + + organization = Organization.objects.create(name='test_org') + + self.organization = organization + + different_organization = Organization.objects.create(name='test_different_organization') + + self.different_organization = different_organization + + + view_permissions = Permission.objects.get( + codename = 'view_' + self.model._meta.model_name, + content_type = ContentType.objects.get( + app_label = self.model._meta.app_label, + model = self.model._meta.model_name, + ) + ) + + view_team = Team.objects.create( + team_name = 'view_team', + organization = organization, + ) + + view_team.permissions.set([view_permissions]) + + + + add_permissions = Permission.objects.get( + codename = 'add_' + self.model._meta.model_name, + content_type = ContentType.objects.get( + app_label = self.model._meta.app_label, + model = self.model._meta.model_name, + ) + ) + + + change_permissions = Permission.objects.get( + codename = 'change_' + self.model._meta.model_name, + content_type = ContentType.objects.get( + app_label = self.model._meta.app_label, + model = self.model._meta.model_name, + ) + ) + + + + delete_permissions = Permission.objects.get( + codename = 'delete_' + self.model._meta.model_name, + content_type = ContentType.objects.get( + app_label = self.model._meta.app_label, + model = self.model._meta.model_name, + ) + ) + + + + self.no_permissions_user = User.objects.create_user(username="test_no_permissions", password="password") + + + self.view_user = User.objects.create_user(username="test_user_view", password="password") + teamuser = TeamUsers.objects.create( + team = view_team, + user = self.view_user + ) + + device = Device.objects.create( + organization=organization, + name = 'cluster' + ) + + port = Port.objects.create( + organization=organization, + number = 80, + protocol = Port.Protocol.TCP + ) + + self.item = self.model.objects.create( + organization=organization, + name = 'os name', + device = device, + config_key_variable = 'value' + ) + + self.other_org_item = self.model.objects.create( + organization=different_organization, + name = 'os name b', + device = device, + config_key_variable = 'values' + ) + + self.item.port.set([ port ]) + + + + self.url_view_kwargs = {'device_id': device.id, 'pk': self.item.id} + + self.url_kwargs = {'device_id': device.id} + + + self.different_organization_user = User.objects.create_user(username="test_different_organization_user", password="password") + + + different_organization_team = Team.objects.create( + team_name = 'different_organization_team', + organization = different_organization, + ) + + different_organization_team.permissions.set([ + view_permissions, + add_permissions, + change_permissions, + delete_permissions, + ]) + + TeamUsers.objects.create( + team = different_organization_team, + user = self.different_organization_user + ) + + + +class ServicePermissionsAPI(ViewSetBase, APIPermissionView, TestCase): + + + def test_returned_data_from_user_and_global_organizations_only(self): + """Check items returned + + This test case is a over-ride of a test case with the same name. + This model is not a tenancy model making this test not-applicable. + + Items returned from the query Must be from the users organization and + global ONLY! + """ + pass + + + + +class ServiceViewSet(ViewSetBase, SerializerView, TestCase): + + pass + + + +class ServiceMetadata( + ViewSetBase, + MetadataAttributesFunctional, + # MetaDataNavigationEntriesFunctional, + TestCase +): + + # menu_id = 'itim' + + # menu_entry_id = 'service' + pass diff --git a/app/itim/tests/functional/test_itim_viewset.py b/app/itim/tests/functional/test_itim_viewset.py index d1e0f4dc..c89030d1 100644 --- a/app/itim/tests/functional/test_itim_viewset.py +++ b/app/itim/tests/functional/test_itim_viewset.py @@ -2,6 +2,8 @@ from django.contrib.auth.models import User from django.shortcuts import reverse from django.test import Client, TestCase +from rest_framework.permissions import IsAuthenticated + from access.models import Organization from api.tests.abstract.viewsets import ViewSetCommon @@ -39,4 +41,18 @@ class ITIMViewset( client.force_login(self.view_user) - self.http_options_response_list = client.options(url) \ No newline at end of file + self.http_options_response_list = client.options(url) + + + + def test_view_attr_permission_classes_value(self): + """Attribute Test + + Attribute `permission_classes` must be metadata class `ReactUIMetadata` + """ + + view_set = self.viewset() + + assert view_set.permission_classes[0] is IsAuthenticated + + assert len(view_set.permission_classes) == 1 diff --git a/app/itim/tests/unit/cluster/test_cluster_api_v2.py b/app/itim/tests/unit/cluster/test_cluster_api_v2.py index 66dfb3ca..4de0e927 100644 --- a/app/itim/tests/unit/cluster/test_cluster_api_v2.py +++ b/app/itim/tests/unit/cluster/test_cluster_api_v2.py @@ -467,3 +467,22 @@ class ClusterAPI( """ assert type(self.api_data['_urls']['tickets']) is str + + + + def test_api_field_exists_urls_external_links(self): + """ Test for existance of API Field + + _urls.external_links field must exist + """ + + assert 'external_links' in self.api_data['_urls'] + + + def test_api_field_type_urls_external_links(self): + """ Test for type for API Field + + _urls.external_links field must be str + """ + + assert type(self.api_data['_urls']['external_links']) is str diff --git a/app/itim/viewsets/index.py b/app/itim/viewsets/index.py index 9a0b840a..58e33fdc 100644 --- a/app/itim/viewsets/index.py +++ b/app/itim/viewsets/index.py @@ -3,12 +3,12 @@ from drf_spectacular.utils import extend_schema from rest_framework.response import Response from rest_framework.reverse import reverse -from api.viewsets.common import CommonViewSet +from api.viewsets.common import IndexViewset @extend_schema(exclude = True) -class Index(CommonViewSet): +class Index(IndexViewset): allowed_methods: list = [ 'GET', diff --git a/app/itim/viewsets/service_cluster.py b/app/itim/viewsets/service_cluster.py new file mode 100644 index 00000000..06cba615 --- /dev/null +++ b/app/itim/viewsets/service_cluster.py @@ -0,0 +1,56 @@ +from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiResponse + +from api.viewsets.common import ReadOnlyModelViewSet + +from itim.serializers.service import ( + Service, + ServiceModelSerializer, + ServiceViewSerializer +) + + + +@extend_schema_view( + list=extend_schema(exclude=True), + retrieve=extend_schema(exclude=True), + create=extend_schema(exclude=True), + update=extend_schema(exclude=True), + partial_update=extend_schema(exclude=True), + destroy=extend_schema(exclude=True) + ) +class ViewSet(ReadOnlyModelViewSet): + + filterset_fields = [ + 'cluster', + 'port', + ] + + search_fields = [ + 'name', + ] + + model = Service + + + def get_queryset(self): + + queryset = super().get_queryset() + + queryset = queryset.filter(cluster_id=self.kwargs['cluster_id']) + + self.queryset = queryset + + return self.queryset + + + def get_serializer_class(self): + + if ( + self.action == 'list' + or self.action == 'retrieve' + ): + + return globals()[str( self.model._meta.verbose_name).replace(' ', '') + 'ViewSerializer'] + + + return globals()[str( self.model._meta.verbose_name).replace(' ', '') + 'ModelSerializer'] diff --git a/app/itim/viewsets/service_device.py b/app/itim/viewsets/service_device.py index bfa7306f..4d4cff86 100644 --- a/app/itim/viewsets/service_device.py +++ b/app/itim/viewsets/service_device.py @@ -1,6 +1,6 @@ from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiResponse -from api.viewsets.common import ModelViewSet +from api.viewsets.common import ReadOnlyModelViewSet from itim.serializers.service import ( Service, @@ -18,7 +18,7 @@ from itim.serializers.service import ( partial_update=extend_schema(exclude=True), destroy=extend_schema(exclude=True) ) -class ViewSet(ModelViewSet): +class ViewSet(ReadOnlyModelViewSet): filterset_fields = [ 'cluster', diff --git a/app/project_management/tests/functional/project/test_project_viewset.py b/app/project_management/tests/functional/project/test_project_viewset.py index 5bf6cdff..dc1de20b 100644 --- a/app/project_management/tests/functional/project/test_project_viewset.py +++ b/app/project_management/tests/functional/project/test_project_viewset.py @@ -13,6 +13,8 @@ from api.tests.abstract.test_metadata_functional import MetadataAttributesFuncti from project_management.models.projects import Project +from settings.models.app_settings import AppSettings + class ViewSetBase: @@ -47,6 +49,31 @@ class ViewSetBase: self.different_organization = different_organization + + + + self.global_organization = Organization.objects.create( + name = 'test_global_organization' + ) + + self.global_org_item = self.model.objects.create( + organization = self.global_organization, + name = 'global_item' + ) + + app_settings = AppSettings.objects.get( + owner_organization = None + ) + + app_settings.global_organization = self.global_organization + + app_settings.save() + + + + + + view_permissions = Permission.objects.get( codename = 'view_' + self.model._meta.model_name, content_type = ContentType.objects.get( diff --git a/app/project_management/tests/functional/project_milestone/test_project_milestone_viewset.py b/app/project_management/tests/functional/project_milestone/test_project_milestone_viewset.py index f84b95de..6d645c3d 100644 --- a/app/project_management/tests/functional/project_milestone/test_project_milestone_viewset.py +++ b/app/project_management/tests/functional/project_milestone/test_project_milestone_viewset.py @@ -199,7 +199,18 @@ class ViewSetBase: class ProjectMilestonePermissionsAPI(ViewSetBase, APIPermissions, TestCase): - pass + + def test_returned_data_from_user_and_global_organizations_only(self): + """Check items returned + + This test case is a over-ride of a test case with the same name. + This model is not a tenancy model making this test not-applicable. + + Items returned from the query Must be from the users organization and + global ONLY! + """ + pass + diff --git a/app/project_management/tests/functional/project_state/test_project_state_viewset.py b/app/project_management/tests/functional/project_state/test_project_state_viewset.py index 660848a8..fe13dced 100644 --- a/app/project_management/tests/functional/project_state/test_project_state_viewset.py +++ b/app/project_management/tests/functional/project_state/test_project_state_viewset.py @@ -12,6 +12,8 @@ from api.tests.abstract.test_metadata_functional import MetadataAttributesFuncti from project_management.models.project_states import ProjectState +from settings.models.app_settings import AppSettings + class ViewSetBase: @@ -46,6 +48,32 @@ class ViewSetBase: self.different_organization = different_organization + + + + + self.global_organization = Organization.objects.create( + name = 'test_global_organization' + ) + + self.global_org_item = self.model.objects.create( + organization = self.global_organization, + name = 'global_item' + ) + + app_settings = AppSettings.objects.get( + owner_organization = None + ) + + app_settings.global_organization = self.global_organization + + app_settings.save() + + + + + + view_permissions = Permission.objects.get( codename = 'view_' + self.model._meta.model_name, content_type = ContentType.objects.get( diff --git a/app/project_management/tests/functional/project_type/test_project_type_viewset.py b/app/project_management/tests/functional/project_type/test_project_type_viewset.py index 24fe61d6..bd1c1230 100644 --- a/app/project_management/tests/functional/project_type/test_project_type_viewset.py +++ b/app/project_management/tests/functional/project_type/test_project_type_viewset.py @@ -12,6 +12,8 @@ from api.tests.abstract.test_metadata_functional import MetadataAttributesFuncti from project_management.models.project_types import ProjectType +from settings.models.app_settings import AppSettings + class ViewSetBase: @@ -46,6 +48,31 @@ class ViewSetBase: self.different_organization = different_organization + + + + self.global_organization = Organization.objects.create( + name = 'test_global_organization' + ) + + self.global_org_item = self.model.objects.create( + organization = self.global_organization, + name = 'global_item' + ) + + app_settings = AppSettings.objects.get( + owner_organization = None + ) + + app_settings.global_organization = self.global_organization + + app_settings.save() + + + + + + view_permissions = Permission.objects.get( codename = 'view_' + self.model._meta.model_name, content_type = ContentType.objects.get( diff --git a/app/project_management/tests/functional/test_project_management_viewset.py b/app/project_management/tests/functional/test_project_management_viewset.py index 5af1bd47..fbc98f98 100644 --- a/app/project_management/tests/functional/test_project_management_viewset.py +++ b/app/project_management/tests/functional/test_project_management_viewset.py @@ -2,6 +2,8 @@ from django.contrib.auth.models import User from django.shortcuts import reverse from django.test import Client, TestCase +from rest_framework.permissions import IsAuthenticated + from access.models import Organization from api.tests.abstract.viewsets import ViewSetCommon @@ -40,4 +42,18 @@ class ProjectManagementViewset( client.force_login(self.view_user) - self.http_options_response_list = client.options(url) \ No newline at end of file + self.http_options_response_list = client.options(url) + + + + def test_view_attr_permission_classes_value(self): + """Attribute Test + + Attribute `permission_classes` must be metadata class `ReactUIMetadata` + """ + + view_set = self.viewset() + + assert view_set.permission_classes[0] is IsAuthenticated + + assert len(view_set.permission_classes) == 1 diff --git a/app/project_management/viewsets/index.py b/app/project_management/viewsets/index.py index c9d6fa47..581bd954 100644 --- a/app/project_management/viewsets/index.py +++ b/app/project_management/viewsets/index.py @@ -3,12 +3,12 @@ from drf_spectacular.utils import extend_schema from rest_framework.response import Response from rest_framework.reverse import reverse -from api.viewsets.common import CommonViewSet +from api.viewsets.common import IndexViewset @extend_schema(exclude = True) -class Index(CommonViewSet): +class Index(IndexViewset): allowed_methods: list = [ 'GET', diff --git a/app/settings/tests/functional/app_settings/test_app_settings_viewset.py b/app/settings/tests/functional/app_settings/test_app_settings_viewset.py index 697d9166..bfc06927 100644 --- a/app/settings/tests/functional/app_settings/test_app_settings_viewset.py +++ b/app/settings/tests/functional/app_settings/test_app_settings_viewset.py @@ -197,6 +197,18 @@ class AppSettingsPermissionsAPI( ): + def test_returned_data_from_user_and_global_organizations_only(self): + """Check items returned + + This test case is a over-ride of a test case with the same name. + This model is not a tenancy model making this test not-applicable. + + Items returned from the query Must be from the users organization and + global ONLY! + """ + pass + + def test_add_create_not_allowed(self): """ Check correct permission for add @@ -212,6 +224,18 @@ class AppSettingsPermissionsAPI( assert e.typename == 'NoReverseMatch' + def test_change_different_organization_denied(self): + """ Ensure permission view cant make change + + This test case is N/A as app settings are not a tenancy model + + Attempt to make change as user from different organization + """ + + pass + + + def test_delete_has_permission(self): """ Check correct permission for delete @@ -237,6 +261,17 @@ class AppSettingsPermissionsAPI( pass + def test_view_different_organizaiton_denied(self): + """ Check correct permission for view + + This test case is N/A as app settings are not a tenancy model + + Attempt to view with user from different organization + """ + + pass + + class AppSettingsViewSet( ViewSetBase, SerializerChange, diff --git a/app/settings/tests/functional/external_links/test_external_link_viewset.py b/app/settings/tests/functional/external_links/test_external_link_viewset.py index 2374b2ed..08c5caa6 100644 --- a/app/settings/tests/functional/external_links/test_external_link_viewset.py +++ b/app/settings/tests/functional/external_links/test_external_link_viewset.py @@ -194,7 +194,17 @@ class ViewSetBase: class ExternalLinkPermissionsAPI(ViewSetBase, APIPermissions, TestCase): - pass + + def test_returned_data_from_user_and_global_organizations_only(self): + """Check items returned + + This test case is a over-ride of a test case with the same name. + This model is not a tenancy model making this test not-applicable. + + Items returned from the query Must be from the users organization and + global ONLY! + """ + pass diff --git a/app/settings/tests/functional/test_settings_viewset.py b/app/settings/tests/functional/test_settings_viewset.py index db9effb6..8ea37d32 100644 --- a/app/settings/tests/functional/test_settings_viewset.py +++ b/app/settings/tests/functional/test_settings_viewset.py @@ -2,6 +2,8 @@ from django.contrib.auth.models import User from django.shortcuts import reverse from django.test import Client, TestCase +from rest_framework.permissions import IsAuthenticated + from access.models import Organization from api.tests.abstract.viewsets import ViewSetCommon @@ -40,4 +42,18 @@ class SettingsViewset( client.force_login(self.view_user) - self.http_options_response_list = client.options(url) \ No newline at end of file + self.http_options_response_list = client.options(url) + + + + def test_view_attr_permission_classes_value(self): + """Attribute Test + + Attribute `permission_classes` must be metadata class `ReactUIMetadata` + """ + + view_set = self.viewset() + + assert view_set.permission_classes[0] is IsAuthenticated + + assert len(view_set.permission_classes) == 1 diff --git a/app/settings/tests/functional/user_settings/test_user_settings_viewset.py b/app/settings/tests/functional/user_settings/test_user_settings_viewset.py index 59064522..22c1d435 100644 --- a/app/settings/tests/functional/user_settings/test_user_settings_viewset.py +++ b/app/settings/tests/functional/user_settings/test_user_settings_viewset.py @@ -198,6 +198,19 @@ class UserSettingsPermissionsAPI( ): + def test_returned_data_from_user_and_global_organizations_only(self): + """Check items returned + + This test case is a over-ride of a test case with the same name. + This model is not a tenancy model making this test not-applicable. + + Items returned from the query Must be from the users organization and + global ONLY! + """ + pass + + + def test_add_create_not_allowed(self): """ Check correct permission for add diff --git a/app/settings/viewsets/index.py b/app/settings/viewsets/index.py index 8d1ebdf7..b23205f7 100644 --- a/app/settings/viewsets/index.py +++ b/app/settings/viewsets/index.py @@ -3,12 +3,12 @@ from drf_spectacular.utils import extend_schema from rest_framework.response import Response from rest_framework.reverse import reverse -from api.viewsets.common import CommonViewSet +from api.viewsets.common import IndexViewset @extend_schema(exclude = True) -class Index(CommonViewSet): +class Index(IndexViewset): allowed_methods: list = [ 'GET', diff --git a/docs/projects/centurion_erp/development/api/models/access_organization_permission_checking.md b/docs/projects/centurion_erp/development/api/models/access_organization_permission_checking.md deleted file mode 100644 index 1b916e48..00000000 --- a/docs/projects/centurion_erp/development/api/models/access_organization_permission_checking.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -title: Organization Permission Checking -description: No Fuss Computings Centurion ERP API Documentation for Organization Permission Checking -date: 2024-06-17 -template: project.html -about: https://gitlab.com/nofusscomputing/infrastructure/configuration-management/centurion_erp ---- - -::: app.access.mixin.OrganizationPermission - options: - inherited_members: true diff --git a/docs/projects/centurion_erp/index.md b/docs/projects/centurion_erp/index.md index 9dad3c7c..32753036 100644 --- a/docs/projects/centurion_erp/index.md +++ b/docs/projects/centurion_erp/index.md @@ -139,13 +139,15 @@ Below is a list of modules/features we intend to add to Centurion. To find out w - IT Operations + - Release and Deployment Management _[see #462](https://github.com/nofusscomputing/centurion_erp/issues/462)_ + - Service level management _[see #396](https://github.com/nofusscomputing/centurion_erp/issues/396)_ - Order Management _[see #94](https://github.com/nofusscomputing/centurion_erp/issues/94)_ - Supplier Management _[see #123](https://github.com/nofusscomputing/centurion_erp/issues/123)_ - - Service Catalog _[see #1384](https://github.com/nofusscomputing/centurion_erp/issues/384)_ + - Service Catalog _[see #384](https://github.com/nofusscomputing/centurion_erp/issues/384)_ - **Planned Integrations:** diff --git a/docs/projects/centurion_erp/user/access/index.md b/docs/projects/centurion_erp/user/access/index.md index 47448cd4..6c0675fa 100644 --- a/docs/projects/centurion_erp/user/access/index.md +++ b/docs/projects/centurion_erp/user/access/index.md @@ -6,7 +6,7 @@ template: project.html about: https://gitlab.com/nofusscomputing/infrastructure/configuration-management/centurion_erp --- -The Access module provides the multi-tenancy for this application. Tenancy is organized into organizations, which contain teams which contain users. As part of this module, application permission checking are also conducted. To view the details on how the permissions system works, please view the [application's API documentation](../../development/api/models/access_organization_permission_checking.md). +The Access module provides the multi-tenancy for this application. Tenancy is organized into organizations, which contain teams which contain users. As part of this module, application permission checking is also conducted. ## Components @@ -14,3 +14,31 @@ The Access module provides the multi-tenancy for this application. Tenancy is or - [Organization](./organization.md) - [Team](./team.md) + + +## Permission System + +The permission system within Centurion ERP is custom and built upon Django's core permission types: add, change, delete and view. For a user to be granted access to perform an action, they must be assigned the permission and have that permission assigned to them as part of the organization they are performing the action in. ALL assigned permissions are limited to the organization the permission is assigned. + +!!! tip + User `A` is in organization `A` and has device view permission. User `A` can view devices in Organization `A` **ONLY**. User `A` although they have the device view permission, can **not** view devices in organization `B`. For User `A` to view devices in organization `B` they would also require the device view permission be assigned to them within organization `B`. + +Unlike filesystem based permssions, Centurion ERP permissions are not inclusive, they are mutually exclusive. That is: + +- To `add` an item you must have its corresponding `add` permission + +- To `change` an item you must have its corresponding `change` permission + +- To `delete` an item you must have its corresponding `delete` permission + +- To `view` an item you must have its corresponding `view` permission + +The exclusitvity is that each of the permissions listed above, dont include an assumed permission. For instance if you have the `add` permission for an item, you will not be able to view it. That would require the `view` permission. + + +### Gloabl Organization + +If the webmaster has setup Centurion ERP to have a [global organization](../settings/app_settings.md#global-organization), as long as the user has the a `view` permission for the model in question in **any** organization, they will be able to view that item within the global organization. This is not the same for the other permissions: `add`, `change` and `delete`. To which they must be granted those permissions within the global organization exclusively. + +!!! tip + User `A` is in organization `A` and the webmaster has setup Centurion to use organization `B` as the global organization. If user `A` has been granted permission `itam.view_software` in organization `A` they will be able to view software within both organization `A` and `B`. diff --git a/docs/projects/centurion_erp/user/itam/device.md b/docs/projects/centurion_erp/user/itam/device.md index aea28aba..1b28ad8d 100644 --- a/docs/projects/centurion_erp/user/itam/device.md +++ b/docs/projects/centurion_erp/user/itam/device.md @@ -139,5 +139,13 @@ Example Report ] } - ``` + + +#### Permissions + +The user that uploads an inventory will require the correct permissions. The required permissions are as follows: + +- `itam.add_device` For adding a new device via inventory + +- `itam.change_device` For updating an existing device via inventory diff --git a/docs/projects/centurion_erp/user/settings/app_settings.md b/docs/projects/centurion_erp/user/settings/app_settings.md index 1493ad8d..d530c5cd 100644 --- a/docs/projects/centurion_erp/user/settings/app_settings.md +++ b/docs/projects/centurion_erp/user/settings/app_settings.md @@ -9,6 +9,13 @@ about: https://gitlab.com/nofusscomputing/infrastructure/configuration-managemen Application settings contain global settings that are applicable to the entire application. Only a super admin can change these settings. +## Global Organization + +A Global organization is where **ALL** items that the webmaster has configured as global are placed. This Organization's purpose is the single location where items are saved to if they are set as global via the application settings. + +A global organizations permissions work slightly different than other organizations. Please see [permissions](../access/index.md#gloabl-organization) for more details. + + ## Global Software It's possible to enforce that all software is set as global. On defining this setting you must set an organization that the global software will be created in. Then when any software is created it will be set to global and saved to the global organization regardless of the users selected settings. diff --git a/makefile b/makefile index 6eae6c55..765e2bbd 100644 --- a/makefile +++ b/makefile @@ -40,16 +40,16 @@ lint: markdown-mkdocs-lint test: cd app - pytest --cov --cov-report term --cov-report xml:../artifacts/coverage_unit_functional.xml --cov-report html:../artifacts/coverage/unit_functional/ --junit-xml=../artifacts/unit_functional.JUnit.xml **/tests/unit **/tests/functional + pytest -s --cov --cov-report term --cov-report xml:../artifacts/coverage_unit_functional.xml --cov-report html:../artifacts/coverage/unit_functional/ --junit-xml=../artifacts/unit_functional.JUnit.xml **/tests/unit **/tests/functional test-functional: cd app - pytest --cov --cov-report term --cov-report xml:../artifacts/coverage_functional.xml --cov-report html:../artifacts/coverage/functional/ --junit-xml=../artifacts/functional.JUnit.xml **/tests/functional + pytest -s --cov --cov-report term --cov-report xml:../artifacts/coverage_functional.xml --cov-report html:../artifacts/coverage/functional/ --junit-xml=../artifacts/functional.JUnit.xml **/tests/functional test-unit: cd app - pytest --cov --cov-report term --cov-report xml:../artifacts/coverage_unit.xml --cov-report html:../artifacts/coverage/unit/ --junit-xml=../artifacts/unit.JUnit.xml **/tests/unit + pytest -s --cov --cov-report term --cov-report xml:../artifacts/coverage_unit.xml --cov-report html:../artifacts/coverage/unit/ --junit-xml=../artifacts/unit.JUnit.xml **/tests/unit diff --git a/mkdocs.yml b/mkdocs.yml index 7a5dc91e..70dfc930 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -91,8 +91,6 @@ nav: - projects/centurion_erp/development/api/models/itam_device.md - - projects/centurion_erp/development/api/models/access_organization_permission_checking.md - - projects/centurion_erp/development/api/models/ticket.md - projects/centurion_erp/development/api/models/tenancy_object.md