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
-
Priority (from lower to higher; later items win in conflicts):
- children permissions
- roles (shared permissions) - roles with higher
prioritytake precedence - scoped permissions (permissions assigned directly to the user)
- wildcards (regardless of which hierarchy level they are assigned to)
-
Wildcard (
*), e.g.users.*, means “everything underusers.” - but a wildcard can be overridden by a permission markedexplicitin the RBAC Map. -
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_idfetch_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_accessreturns data in a predictable order if your logic depends on creation time or role priority. - The library can enforce
require_sorted_permissionsin 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- iftrue, this permission cannot be obtained only via wildcard/children inheritance.children- a list ofpermission:valuepairs 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_pathshould point to the YAML file you prepared.require_sorted_permissions=Truetells the library to expect manager-provided permission records increated_atorder
4) Guard - usage examples
Simple Guard
Note: Refer to official
Ascender Frameworkdocs forGuardandParamGuardendpoint 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.useris not populated automatically by the library. If you wantgate.useravailable, yourfetch_user_accessimplementation must return auserfield inside theAccessobject.
Detailed priority and override logic
- Collect all permission records (scoped + shared) and roles for the user.
RBACGatecalculateschildren(using the RBAC Map) for every permission.- Permissions are then applied in this order:
- Children (applied first),
- Roles (applied next - consider
role.priorityand assignmentcreated_at), - Scoped permissions assigned directly to the user (applied last - strongest).
- Wildcards
- 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_accessreturns results in the expected order (Enablingrequire_sorted_permissionsrises an exception if the sorting is wrong).
- If you rely on DB timestamps or insertion order, ensure
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
Accessper-request (e.g., inrequest.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
- Wildcard permissions not taking effect - check if the target permission has
_config.explicit: truein the RBAC Map. Permissions must be sorted by created_atException - ensure permissions are sorted as exception suggests or turn of this requirement in RBACConfig (not recommended)- Heavy DB workload - cache
Accessfor the lifetime of the request or use ParamGuard to do a single fetch.
Thank you for using UndoreRBAC.
Undore <github.com/Undore>
Metadata
Release files for undore-rbac 1.2.8
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| undore_rbac-1.2.8.tar.gz | 17.0 kB | Without provenance | Details for undore_rbac-1.2.8.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| undore_rbac-1.2.8-py3-none-any.whl | Python 3 | none | any | Details for undore_rbac-1.2.8-py3-none-any.whl |
Total release size: 36.4 kB
Release files / undore_rbac-1.2.8.tar.gz
| Download URL | undore_rbac-1.2.8.tar.gz |
|---|---|
| Size | 17.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
322b45b052515b058344d851490a55cf4a0d73965a1c1a9d4840aebca9f97d11
|
|
BLAKE2b-256 checksum How to use checksums |
2716e042fcc4aa6700af23e0b42d41c69f9ba6073877bc6916cefc78e7dde9ab
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
poetry/2.1.3 CPython/3.11.0 Windows/10
|
Release files / undore_rbac-1.2.8-py3-none-any.whl
| Download URL | undore_rbac-1.2.8-py3-none-any.whl |
|---|---|
| Size | 19.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
945c570ed230c18948d78ff2aa6e294fd3cc3facad83f3a71a092e1a4ea05c0d
|
|
BLAKE2b-256 checksum How to use checksums |
575d0e9970256e7ae2a17064471a7c988439b67d96747f31676a2cea682522f5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
poetry/2.1.3 CPython/3.11.0 Windows/10
|
Security for undore-rbac 1.2.8
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.