Skip to main content

RBAC System for Ascender Framework from Undore!

Project description

Undore RBAC

RBAC made easy for Ascender Framework

UndoreRBAC is a lightweight, configurable role-based access control (RBAC) system designed for seamless integration with the Ascender Framework.

Its goal is to separate permission-evaluation logic and priority rules from data storage, provide a flexible manager interface for fetching permissions/roles, and offer an easy-to-configure permission map.


Core concepts

  • RBAC Map - a YAML file that declares all available permissions and their configuration (default, explicit, children).
  • RBAC Manager - the user-implemented bridge between your database and UndoreRBAC. You implement methods for authentication and for fetching roles/permissions.
  • RBACGate - You will use this the most: an object that represents a single user’s access state; it performs all the comparison, inheritance, and override logic.
  • Permissions carry a boolean value (True/False). This allows both granting and explicit denial of permissions.

Basic behavior rules

  1. Priority (from lower to higher; later items win in conflicts):

    • children permissions
    • roles (shared permissions) - roles with higher priority take precedence
    • scoped permissions (permissions assigned directly to the user)
    • wildcards (regardless of which hierarchy level they are assigned to)
  2. Wildcard (*), e.g. users.*, means “everything under users.” - but a wildcard can be overridden by a permission marked explicit in the RBAC Map.

  3. explicit: true - a permission marked explicit ignores wildcard/override propagation. Use with caution.

Important note on Wildcard priority

Wildcards are the highest in priority. Even if a wildcard permission is assigned to a lower priority role or children permission, it CANNOT be overwritten by a higher priority permission (if it is not a wildcard itself)

For example, if user has scoped permission users.manage on value True and also has a role, which has a wildcard permission users.* on value False, access will be denied, regardless of the fact that scoped permissions are higher in the hierarchy


Quick start

1) Implement BaseRBACManager

BaseRBACManager is an abstract class. You must implement:

  • authorize(token: str, request: Request | None = None, custom_meta: dict | None = None) -> user_id
  • fetch_user_access(user_id: Any, custom_meta: dict | None = None) -> Access

where Access contains:

{
  "permissions": list[IRBACPermission],
  "roles": list[IRBACRole],
  "user": Any | None
}

Note

  • Make sure fetch_user_access returns data in a predictable order if your logic depends on creation time or role priority.
  • The library can enforce require_sorted_permissions in RBACConfig by default, so it’s best if the manager returns sorted data.

2) Create rbac_map.yml

Permissions can be declared in two styles: nested YAML or dot-notation. Example:

users:
  delete:
  view:
    other:

auth.login:

audit.export:
  _config:
    default: false
    explicit: true
    children:
      - users.view: true
      - users.delete: false

_config options:

  • default - the default boolean value for this permission when the user has no record for it.
  • explicit - if true, this permission cannot be obtained only via wildcard/children inheritance.
  • children - a list of permission:value pairs that are applied automatically when this permission is present.

3) Initialization in Ascender Framework

In your bootstrap.py:

from undore_rbac.interfaces.config import RBACConfig
from undore_rbac.rbac_module import RbacModule
from shared.custom_rbac_manager import CustomRBACManager
import os

appBootstrap: IBootstrap = {
    "providers": [
      RbacModule.for_root(
          RBACConfig(
              rbac_manager=CustomRBACManager(),
              rbac_map_path=os.path.join(BASE_PATH, "rbac_map.yml"),
              require_sorted_permissions=True
          )
      )
   ]
}

Notes

  • rbac_map_path should point to the YAML file you prepared.
  • require_sorted_permissions=True tells the library to expect manager-provided permission records in created_at order

4) Guard - usage examples

Simple Guard


Note: Refer to official Ascender Framework docs for Guard and ParamGuard endpoint usage examples


class RBACGuard(Guard):
   def __init__(self, *permissions: str):
      self.permissions = permissions

   def __post_init__(self, rbac: RbacService):
      self.rbac = rbac
      self.manager = rbac.manager

   async def can_activate(self, request: Request, token: HTTPAuthorizationCredentials = Security(HTTPBearer())):
      user_id = await self.manager.authorize(token.credentials, request=request, custom_meta={"org_id": 123})

      gate = await RBACGate.from_user_id(user_id, custom_meta={"org_id": 123})
      status, reason = gate.check_access(self.permissions)
      if status is False:
         raise InsufficientPermissions(request_url=request.url.path, required_permission=reason)

ParamGuard (recommended to avoid duplicated DB calls)

class RBACParamGuard(ParamGuard):
    def __init__(self, *permissions: str):
        self.permissions = permissions

    def __post_init__(self, rbac: RbacService):
        self.rbac = rbac
        self.manager = rbac.manager

    async def credentials_guard(self, request: Request, token: HTTPAuthorizationCredentials = Security(HTTPBearer())):
        user_id = await self.manager.authorize(token.credentials, request=request)
        
        if self.permissions:
           gate = await RBACGate.from_user_id(user_id, custom_meta={"org_id": 123})
           status, reason = gate.check_access(self.permissions)
           if status is False:
               raise InsufficientPermissions(request_url=request.url.path, required_permission=reason)
            
           user = gate.user
        else:  # Save performance if permission check is not needed
           user = ... # Your user GET logic
          
        # Your pydantic model for creds kwarg in endpoint
        return AuthCredentials(
            user=user  # You can also pass the gate here to reuse it later
        )

Note: gate.user is not populated automatically by the library. If you want gate.user available, your fetch_user_access implementation must return a user field inside the Access object.


Detailed priority and override logic

  1. Collect all permission records (scoped + shared) and roles for the user.
  2. RBACGate calculates children (using the RBAC Map) for every permission.
  3. Permissions are then applied in this order:
    • Children (applied first),
    • Roles (applied next - consider role.priority and assignment created_at),
    • Scoped permissions assigned directly to the user (applied last - strongest).
    • Wildcards
  4. When conflicting permissions have the same effective priority, the most recent record (by created_at, or the order provided by the manager) wins.
    • If you rely on DB timestamps or insertion order, ensure fetch_user_access returns results in the expected order (Enabling require_sorted_permissions rises an exception if the sorting is wrong).

Best practices & recommendations

  • Log concise check summaries at debug level (do not log tokens or sensitive data).
  • Avoid overusing explicit: true - it can silently block wildcard inheritance causing confusing denials.
  • Cache Access per-request (e.g., in request.state) or use ParamGuard to prevent multiple DB hits in the same request.
  • Be careful with wildcards: Always keep in mind that wildcards override EVERYTHING and they don't care about higher-priority roles and permissions


Common pitfalls and how to avoid them

  1. Wildcard permissions not taking effect - check if the target permission has _config.explicit: true in the RBAC Map.
  2. Permissions must be sorted by created_at Exception - ensure permissions are sorted as exception suggests or turn of this requirement in RBACConfig (not recommended)
  3. Heavy DB workload - cache Access for the lifetime of the request or use ParamGuard to do a single fetch.

Thank you for using UndoreRBAC.

Undore <github.com/Undore>

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

undore_rbac-1.2.2.tar.gz (16.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

undore_rbac-1.2.2-py3-none-any.whl (19.7 kB view details)

Uploaded Python 3

File details

Details for the file undore_rbac-1.2.2.tar.gz.

File metadata

  • Download URL: undore_rbac-1.2.2.tar.gz
  • Upload date:
  • Size: 16.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.1.3 CPython/3.13.5 Windows/11

File hashes

Hashes for undore_rbac-1.2.2.tar.gz
Algorithm Hash digest
SHA256 446d681ad51c58f0593c65314276e4788a9c7f8e1b5b91cc1fbca7f17a31b5b1
MD5 5e156edc9e7a1864fb0d0522d8570ef1
BLAKE2b-256 b0a75bbe7a57ef3d97e00d7c18e3e6bca19e7d697dcc9304883005ffc466543b

See more details on using hashes here.

File details

Details for the file undore_rbac-1.2.2-py3-none-any.whl.

File metadata

  • Download URL: undore_rbac-1.2.2-py3-none-any.whl
  • Upload date:
  • Size: 19.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.1.3 CPython/3.13.5 Windows/11

File hashes

Hashes for undore_rbac-1.2.2-py3-none-any.whl
Algorithm Hash digest
SHA256 06838d08af7845ae8b55843938c1c447fb7c412a67c832435e69a398042e144f
MD5 6fce71b6e503e3edce7462513d8de0c7
BLAKE2b-256 25cc084f4d4056248e8150a9d9dc93a5e34fdb872ca0699c8fb0b9673fe15605

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page