Secure, DIP-based reusable RBAC infrastructure
Project description
rbac_infra
A reusable, framework-friendly Role-Based Access Control (RBAC) infrastructure package with:
- A clean core service based on abstractions (Dependency Inversion Principle).
- Pluggable repositories for roles and permissions.
- Policy fallback hooks for contextual authorization rules.
- Optional caching backends (in-memory and Redis).
- A ready-to-use Django adapter (models, repositories, auth backend).
Features
- Multi-tenant permission keys in the form:
tenant:action:resource - Strict tenant isolation during permission matching
- Wildcard support for action/resource (
*) with safe matching - Extensible policy interface for custom checks (e.g., owner-based access)
- Adapter-friendly architecture for integrating with any persistence layer
- Django integration out of the box
Project structure
rbac_infra/
├── core/
│ ├── entities.py # Permission entity + key generation
│ ├── interfaces.py # RoleRepository, PermissionRepository, Policy
│ ├── service.py # RBACService permission engine
│ └── exceptions.py # AccessDenied
├── caching/
│ ├── interfaces.py # CacheBackend interface
│ └── memory_cache.py # InMemoryCache + RedisCache
└── adapter/
├── models.py # Django models for Tenant/Role/Permission/UserRole
├── repository.py # Django repository implementations
├── backend.py # Django auth backend using RBACService
└── migrations/
Installation
Base package
pip install rbac_infra
Add required variable
REDIS_URL=redis://localhost:6379/0 # (Use your production redis url.)
Local Development (includes Django + test tools)
git clone "[repo_url](https://github.com/0FFSIDE1/rbac_infra.git)"
Core concepts
1. Permission model
Permissions are represented as:
roletenant_idactionresource
Canonical key format:
tenant:action:resource
Examples:
tenant1:read:invoicetenant2:update:usertenant1:*:invoice(all actions on invoice)tenant1:read:*(read any resource)
2. Repository abstraction
The core service does not know about Django, SQLAlchemy, or any DB directly. You inject:
RoleRepository→ returns role names for a user in a tenantPermissionRepository→ returns permissions for a role in a tenant
This keeps your domain logic portable and testable.
3. Policy fallback
If role/permission checks fail, the service evaluates optional policies.
Use policies for contextual rules such as:
- ownership
- time-based access
- business workflow constraints
Using the core service directly
from rbac_infra.core.service import RBACService
from rbac_infra.core.interfaces import RoleRepository, PermissionRepository
from rbac_infra.core.entities import Permission
class MyRoleRepo(RoleRepository):
def get_user_roles(self, user_id, tenant_id):
return ["admin"]
class MyPermissionRepo(PermissionRepository):
def get_role_permissions(self, role_name, tenant_id):
return [Permission(action="read", resource="invoice", tenant_id=tenant_id)]
service = RBACService(
role_repo=MyRoleRepo(),
permission_repo=MyPermissionRepo(),
)
allowed = service.check(
user_id="user-1",
tenant_id="tenant1",
action="read",
resource="invoice",
)
If access is denied, RBACService.check(...) raises AccessDenied.
Caching
You can provide any cache implementation that satisfies CacheBackend.
Built-ins:
InMemoryCache— simple process-local cacheRedisCache— Redis-backed cache usingREDIS_URL(defaultredis://localhost:6379/0)
Example:
from rbac_infra.caching.memory_cache import InMemoryCache
from rbac_infra.core.service import RBACService
service = RBACService(
role_repo=role_repo,
permission_repo=permission_repo,
cache=InMemoryCache(),
)
Django adapter (ready to use)
The project includes a Django adapter with:
- Models:
Tenant,Role,Permission,UserRole - Repositories:
DjangoRoleRepository,DjangoPermissionRepository - Backend:
RBACBackend
1) Add app and backend
# settings.py
INSTALLED_APPS = [
# ...
"rbac_infra.adapter.apps.RBACAdapterConfig",
]
AUTHENTICATION_BACKENDS = [
"django.contrib.auth.backends.ModelBackend",
"rbac_infra.adapter.backend.RBACBackend",
]
2) Run migrations
python manage.py migrate
3) Check permissions
user.has_perm("tenant1:read:invoice")
Permission string format must always be:
tenant:action:resource
Create custom policies
Implement Policy.evaluate(user_id, action, resource, context) -> bool.
from rbac_infra.core.interfaces import Policy
class OwnerPolicy(Policy):
def evaluate(self, user_id, action, resource, context):
return context.get("owner_id") == user_id
Inject into the service:
service = RBACService(
role_repo=role_repo,
permission_repo=permission_repo,
policies=[OwnerPolicy()],
)
You can pass context at runtime:
service.check(
user_id="u1",
tenant_id="tenant1",
action="update",
resource="invoice",
context={"owner_id": "u1"},
)
Build your own adapter/repository (beyond Django)
If you want to support another framework (Flask/FastAPI/custom service) or data store (SQLAlchemy/Mongo/HTTP API), follow the same adapter pattern used in rbac_infra/adapter:
- Implement a
RoleRepository. - Implement a
PermissionRepository. - Map your storage records to
rbac_infra.core.entities.Permission. - Inject these into
RBACService. - (Optional) wrap service calls in your framework-specific authorization hook/middleware.
Example: custom SQLAlchemy-style adapter skeleton
from rbac_infra.core.interfaces import RoleRepository, PermissionRepository
from rbac_infra.core.entities import Permission
class SQLARoleRepository(RoleRepository):
def __init__(self, session):
self.session = session
def get_user_roles(self, user_id, tenant_id):
# Query your own tables
# return list[str]
...
class SQLAPermissionRepository(PermissionRepository):
def __init__(self, session):
self.session = session
def get_role_permissions(self, role_name, tenant_id):
# Query your own tables
rows = ...
return [
Permission(
action=row.action,
resource=row.resource,
tenant_id=tenant_id,
)
for row in rows
]
Then use it:
service = RBACService(
role_repo=SQLARoleRepository(session),
permission_repo=SQLAPermissionRepository(session),
)
This keeps your authorization rules centralized while letting your persistence and framework vary.
Testing
Run tests:
pytest
The test suite covers:
- core permission checks
- wildcard matching
- tenant isolation
- policy fallback
- Django repository/backend behavior
- Redis cache behavior
Security notes
- Tenant matching is strict: tenant IDs must match exactly.
- Invalid permission string formats are denied.
- Backend behavior is fail-closed for inactive/anonymous users.
Contributing
- Fork the repo
- Create a feature branch
git checkout -b feature/awesome
- Run test after implementing your feature
pytest
- Commit changes
git commit -m 'Add awesome feature'
- Push branch and open a PR
Support
For enterprise inquiries, please contact offsideint@gmail.com
For bugs, open an issue on GitHub.
Built with ❤️ by OFFSIDE INTEGRATED TECHNOLOGY — because developers do not need hassle wiring RBAC.
License
MIT
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file rbac_infra-0.1.6.tar.gz.
File metadata
- Download URL: rbac_infra-0.1.6.tar.gz
- Upload date:
- Size: 14.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
09faa7be0ee784c000c08f352ce0bf9204b3ddc9c6fcd1f9e5498340dd20a96b
|
|
| MD5 |
1423e6d92a349348303a14c21c6cfa85
|
|
| BLAKE2b-256 |
919f68608fd15a69f095758182ddc7f35428aabc454e92e504a8719a8169ad86
|
Provenance
The following attestation bundles were made for rbac_infra-0.1.6.tar.gz:
Publisher:
publish.yml on 0FFSIDE1/rbac_infra
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
rbac_infra-0.1.6.tar.gz -
Subject digest:
09faa7be0ee784c000c08f352ce0bf9204b3ddc9c6fcd1f9e5498340dd20a96b - Sigstore transparency entry: 1059508284
- Sigstore integration time:
-
Permalink:
0FFSIDE1/rbac_infra@cdb96973bf43b1380f3c087d469bd01a7af16bd0 -
Branch / Tag:
refs/tags/v0.1.6 - Owner: https://github.com/0FFSIDE1
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@cdb96973bf43b1380f3c087d469bd01a7af16bd0 -
Trigger Event:
push
-
Statement type:
File details
Details for the file rbac_infra-0.1.6-py3-none-any.whl.
File metadata
- Download URL: rbac_infra-0.1.6-py3-none-any.whl
- Upload date:
- Size: 14.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
88ad6ebc5757112c90ff5bb8c59588571c259ed93dd0229aa4d7c5f7ef4886d0
|
|
| MD5 |
b63306344696ea696b78412bb3b37bb5
|
|
| BLAKE2b-256 |
64a4743a146d8fd5c1eca3f2db88fe88c8b15c7181ce8dc553a183bdceb40978
|
Provenance
The following attestation bundles were made for rbac_infra-0.1.6-py3-none-any.whl:
Publisher:
publish.yml on 0FFSIDE1/rbac_infra
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
rbac_infra-0.1.6-py3-none-any.whl -
Subject digest:
88ad6ebc5757112c90ff5bb8c59588571c259ed93dd0229aa4d7c5f7ef4886d0 - Sigstore transparency entry: 1059508294
- Sigstore integration time:
-
Permalink:
0FFSIDE1/rbac_infra@cdb96973bf43b1380f3c087d469bd01a7af16bd0 -
Branch / Tag:
refs/tags/v0.1.6 - Owner: https://github.com/0FFSIDE1
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@cdb96973bf43b1380f3c087d469bd01a7af16bd0 -
Trigger Event:
push
-
Statement type: