import traceback from django.core.exceptions import ObjectDoesNotExist from rest_framework import exceptions from rest_framework.permissions import DjangoObjectPermissions from access.models.tenancy import Tenant, TenancyObject from core import exceptions as centurion_exceptions class OrganizationPermissionMixin( DjangoObjectPermissions, ): """Tenant Permission Mixin This class is to be used as the permission class for API `Views`/`ViewSets`. In combination with the `TenantPermissionsMixin`, 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: if ( ( view.model.__name__ == 'UserSettings' and request._user.id == int(view.kwargs.get('pk', 0)) ) or ( view.model.__name__ == 'AuthToken' and request._user.id == int(view.kwargs.get('model_id', 0)) ) ): return True elif ( ( view.model.__name__ == 'UserSettings' and request._user.id != int(view.kwargs.get('pk', 0)) ) or ( view.model.__name__ == 'AuthToken' and request._user.id != int(view.kwargs.get('model_id', 0)) ) ): return False has_permission_required: bool = False user_permissions = request.tenancy._user_permissions 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: Tenant = 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' elif ( view.action == 'list' ): view_action = 'view' elif ( view.action == 'partial_update' and request.method == 'PATCH' ): view_action = 'change' elif ( view.action == 'update' and request.method == 'PUT' ): view_action = 'change' elif( view.action == 'retrieve' and request.method == 'GET' ): view_action = 'view' 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 request.tenancy.has_organization_permission( organization = obj_organization, 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 if ( ( view.model.__name__ == 'UserSettings' and request._user.id == int(view.kwargs.get('pk', 0)) ) or ( view.model.__name__ == 'AuthToken' and request._user.id == int(view.kwargs.get('model_id', 0)) ) ): return True object_organization = view._obj_organization if object_organization: if( int(object_organization) in view.get_permission_organizations( view.get_permission_required() ) or request.user.is_superuser or getattr(request.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