Skip to main content

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:

  • role
  • tenant_id
  • action
  • resource

Canonical key format:

tenant:action:resource

Examples:

  • tenant1:read:invoice
  • tenant2:update:user
  • tenant1:*: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 tenant
  • PermissionRepository → 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 cache
  • RedisCache — Redis-backed cache using REDIS_URL (default redis://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:

  1. Implement a RoleRepository.
  2. Implement a PermissionRepository.
  3. Map your storage records to rbac_infra.core.entities.Permission.
  4. Inject these into RBACService.
  5. (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

rbac_infra-0.1.6.tar.gz (14.2 kB view details)

Uploaded Source

Built Distribution

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

rbac_infra-0.1.6-py3-none-any.whl (14.4 kB view details)

Uploaded Python 3

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

Hashes for rbac_infra-0.1.6.tar.gz
Algorithm Hash digest
SHA256 09faa7be0ee784c000c08f352ce0bf9204b3ddc9c6fcd1f9e5498340dd20a96b
MD5 1423e6d92a349348303a14c21c6cfa85
BLAKE2b-256 919f68608fd15a69f095758182ddc7f35428aabc454e92e504a8719a8169ad86

See more details on using hashes here.

Provenance

The following attestation bundles were made for rbac_infra-0.1.6.tar.gz:

Publisher: publish.yml on 0FFSIDE1/rbac_infra

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for rbac_infra-0.1.6-py3-none-any.whl
Algorithm Hash digest
SHA256 88ad6ebc5757112c90ff5bb8c59588571c259ed93dd0229aa4d7c5f7ef4886d0
MD5 b63306344696ea696b78412bb3b37bb5
BLAKE2b-256 64a4743a146d8fd5c1eca3f2db88fe88c8b15c7181ce8dc553a183bdceb40978

See more details on using hashes here.

Provenance

The following attestation bundles were made for rbac_infra-0.1.6-py3-none-any.whl:

Publisher: publish.yml on 0FFSIDE1/rbac_infra

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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