Source code for ferrosoft.apps.ferrobase.auth.roles

#  Copyright (c) 2025 Ferrosoft GmbH. All rights reserved.
from dataclasses import dataclass, field
from enum import StrEnum, IntEnum
from typing import List, Any

from django.http import HttpRequest


[docs] class Role(StrEnum): """Roles are identifiers for PermissionSets""" EMIFLOW_USER = "emiflow_user" EMIFLOW_MANAGER = "emiflow_manager" USER_MANAGER = "user_manager"
[docs] class Verb(IntEnum): """ Enumerate model actions. """ CREATE = 1 READ = 2 UPDATE = 3 DELETE = 4 READ_FILTERED = 5 READ_TABLE_FILTERED = 6
[docs] class Permissions: """Utility for generating and checking Django permission strings. Maps ``Verb`` enum values to the corresponding Django CRUD verbs (``add``, ``view``, ``change``, ``delete``) and assembles fully qualified ``<app_label>.<verb>_<model_name>`` permission strings. """ _DJANGO_PERMISSIONS = { Verb.CREATE: "add", Verb.READ: "view", Verb.UPDATE: "change", Verb.DELETE: "delete", Verb.READ_FILTERED: "view", Verb.READ_TABLE_FILTERED: "view", }
[docs] @classmethod def for_model(cls, model_class: Any, verb: Verb) -> str: """Build the Django permission string for a model and verb. Args: model_class: Model class or bare model name string. When a class is provided the ``app_label`` is included in the result. verb: The ``Verb`` action to translate. Returns: str: Permission string, e.g. ``"ferrobase.view_businesspartner"``. """ django_verb = cls._DJANGO_PERMISSIONS[verb] if isinstance(model_class, str): return f"{django_verb}_{model_class.lower()}" # noinspection PyProtectedMember m = model_class._meta return f"{m.app_label}.{django_verb}_{m.model_name}"
[docs] @classmethod def allowed(cls, request: HttpRequest, model_class: Any, verb: Verb) -> bool: """Check whether the current request user has the given permission. Args: request: The current HTTP request. model_class: Model class or name string. verb: The action to check. Returns: bool: ``True`` if the user holds the corresponding Django permission. """ return request.user.has_perm(cls.for_model(model_class, verb))
[docs] @dataclass class PermissionSet: """Associates a Django group name with a list of model-level permissions. ``group`` holds the display name of the Django ``Group`` this set maps to; ``permissions`` accumulates the individual Django permission strings. """ group: str permissions: List[str] = field(default_factory=list)
[docs] def with_model(self, model_class: str, *verbs: Verb) -> "PermissionSet": """Append permissions for the given model and verbs and return self. Args: model_class: Model name string (lower-case) or model class. *verbs: One or more ``Verb`` values whose Django equivalents are appended to ``permissions``. Returns: PermissionSet: ``self``, enabling a fluent builder pattern. """ self.permissions += [Permissions.for_model(model_class, verb) for verb in verbs] return self
[docs] class Registry: """Static registry mapping each ``Role`` to its ``PermissionSet``. Provides the single source of truth for which Django permissions belong to each built-in role. The mapping is consumed when setting up tenant groups and when assigning roles to users. """ _PERMISSION_SETS: dict[Role, PermissionSet] = { Role.EMIFLOW_USER: ( PermissionSet("Emiflow User") .with_model( "businesspartner", Verb.READ, ) .with_model( "companysite", Verb.READ, ) .with_model( "consignment", Verb.CREATE, Verb.UPDATE, Verb.DELETE, Verb.READ, ) .with_model( "emissionbookingrecord", Verb.CREATE, Verb.READ, ) .with_model( "emissionfactor", Verb.CREATE, Verb.UPDATE, Verb.DELETE, Verb.READ, ) .with_model( "emissionintensity", Verb.CREATE, Verb.UPDATE, Verb.DELETE, Verb.READ, ) .with_model( "emissionscatalog", Verb.CREATE, Verb.UPDATE, Verb.DELETE, Verb.READ, ) .with_model( "emitter", Verb.READ, ) .with_model( "energycarrier", Verb.READ, ) .with_model( "freightitem", Verb.CREATE, Verb.UPDATE, Verb.DELETE, Verb.READ, ) .with_model( "importjob", Verb.CREATE, Verb.UPDATE, Verb.DELETE, Verb.READ, ) .with_model( "importjobtemplate", Verb.READ, ) .with_model( "lifecyclecategory", Verb.READ, ) .with_model( "logentry", Verb.READ, ) .with_model( "material", Verb.READ, ) .with_model( "multilinebookingrecord", Verb.CREATE, Verb.UPDATE, Verb.DELETE, Verb.READ, ) .with_model( "multilineimportline", Verb.CREATE, Verb.UPDATE, Verb.DELETE, Verb.READ, ) .with_model( "report", Verb.CREATE, Verb.UPDATE, Verb.DELETE, Verb.READ, ) .with_model( "shipment", Verb.CREATE, Verb.UPDATE, Verb.DELETE, Verb.READ, ) .with_model( "simpleimportline", Verb.CREATE, Verb.UPDATE, Verb.DELETE, Verb.READ, ) .with_model( "tcegeodestination", Verb.CREATE, Verb.UPDATE, Verb.DELETE, Verb.READ, ) .with_model( "tcegeoorigin", Verb.CREATE, Verb.UPDATE, Verb.DELETE, Verb.READ, ) .with_model( "transportchain", Verb.CREATE, Verb.UPDATE, Verb.READ, Verb.DELETE, ) .with_model( "transportchaindefaults", Verb.READ, ) .with_model( "transportchainelement", Verb.CREATE, Verb.UPDATE, Verb.DELETE, Verb.READ, ) .with_model( "transportlccvalue", Verb.CREATE, Verb.READ, Verb.UPDATE, Verb.DELETE, ) .with_model( "transportoperation", Verb.READ, ) .with_model( "treatment", Verb.READ, Verb.CREATE, Verb.UPDATE, ) .with_model( "treatmentoperation", Verb.READ, ) .with_model( "treatmentinputline", Verb.READ, Verb.CREATE, Verb.UPDATE, ) .with_model( "treatmentlccvalue", Verb.READ, Verb.CREATE, Verb.UPDATE, Verb.DELETE, ) .with_model( "treatmentoutputline", Verb.READ, Verb.CREATE, Verb.UPDATE, ) .with_model( "treatmentoperationinputline", Verb.READ, ) .with_model( "treatmentoperationoutputline", Verb.READ, ) ), Role.EMIFLOW_MANAGER: ( PermissionSet("Emiflow Manager") .with_model( "businesscentralsettings", Verb.READ, Verb.UPDATE, ) .with_model( "businesspartner", Verb.CREATE, Verb.UPDATE, Verb.DELETE, ) .with_model( "companysite", Verb.CREATE, Verb.UPDATE, Verb.DELETE, ) .with_model( "emitter", Verb.CREATE, Verb.UPDATE, Verb.DELETE, ) .with_model( "energycarrier", Verb.CREATE, Verb.UPDATE, Verb.DELETE, ) .with_model( "idsequenceconfig", Verb.CREATE, Verb.READ, Verb.UPDATE, ) .with_model( "lifecyclecategory", Verb.CREATE, Verb.UPDATE, Verb.DELETE, ) .with_model( "material", Verb.CREATE, Verb.UPDATE, Verb.DELETE, ) .with_model( "settings", Verb.CREATE, Verb.UPDATE, Verb.DELETE, ) .with_model( "transportchaindefaults", Verb.CREATE, Verb.UPDATE, Verb.DELETE, ) .with_model( "transportoperation", Verb.CREATE, Verb.UPDATE, Verb.DELETE, ) .with_model( "treatmentoperation", Verb.CREATE, Verb.UPDATE, Verb.DELETE, ) .with_model( "treatmentoperationinputline", Verb.CREATE, Verb.UPDATE, Verb.DELETE, ) .with_model( "treatmentoperationoutputline", Verb.CREATE, Verb.UPDATE, Verb.DELETE, ) ), Role.USER_MANAGER: ( PermissionSet("User Manager") .with_model( "user", Verb.CREATE, Verb.UPDATE, Verb.DELETE, Verb.READ, ) .with_model( "apitoken", Verb.CREATE, Verb.UPDATE, Verb.DELETE, Verb.READ, ) ), }
[docs] @classmethod def get_permission_sets(cls) -> dict[Role, PermissionSet]: """Return the full role-to-permission-set mapping. Returns: dict[Role, PermissionSet]: Mapping of every defined role to its associated ``PermissionSet``. """ return cls._PERMISSION_SETS