Files
centurion_erp/app/access/mixins/permissions.py

327 lines
9.0 KiB
Python

import traceback
from rest_framework.permissions import DjangoObjectPermissions
from access.models.tenancy import Tenant
from core import exceptions as centurion_exceptions
from core.mixins.centurion import Centurion
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, Centurion)
if view.get_parent_model():
self._is_tenancy_model = issubclass(view.get_parent_model(), Centurion)
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()
if request.method not in view.allowed_methods:
raise centurion_exceptions.MethodNotAllowed(method = request.method)
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 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))
)
or ( # org=None is the application wide settings.
view.model.__name__ == 'AppSettings'
and request.user.is_superuser
and obj.organization is None
)
):
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