Skip to main content

RBAC/ABAC policy engine for Python with policy sets, condition DSL, and hot reload

Project description

RBACX

CI Docs Coverage

License: MIT

PyPI Python

Universal RBAC/ABAC/ReBAC policy engine for Python with a clean core, policy sets, a compact condition DSL (including time ops), and adapters for common web frameworks.

Features

  • Algorithms: deny-overrides (default), permit-overrides, first-applicable
  • Conditions: ==, !=, <, <=, >, >=, contains, in, hasAll, hasAny, startsWith, endsWith, before, after, between
  • Role shorthand: "roles": ["admin", "editor"] on any rule — sugar for hasAny on subject.roles
  • Explainability: decision, reason, rule_id/last_rule_id, obligations
  • Policy sets: combine multiple policies with the same algorithms
  • Hot reload: file/HTTP/S3 sources with ETag and a polling manager
  • Types & lint: mypy-friendly core, Ruff-ready
  • AI Policy Authoring (rbacx[ai]): generate, refine, and explain policies using any OpenAI-compatible LLM

Installation

pip install rbacx

Optional extras include adapters, metrics, YAML support, and AI policy authoring.

Quickstart

from rbacx import Action, Context, Guard, Subject, Resource

policy = {
    "algorithm": "deny-overrides",
    "rules": [
        {
            "id": "doc_read",
            "effect": "permit",
            "actions": ["read"],
            "resource": {"type": "doc", "attrs": {"visibility": ["public", "internal"]}},
            "roles": ["reader", "admin"],  # shorthand: hasAny on subject.roles
            # Or using the explicit condition syntax:
            # "condition": {"hasAny": [ {"attr": "subject.roles"}, ["reader", "admin"] ]},
            "obligations": [ {"type": "require_mfa"} ]
        },
        {"id": "doc_deny_archived", "effect": "deny", "actions": ["*"],
         "resource": {"type": "doc", "attrs": {"archived": True}}}
    ],
}

g = Guard(policy)

d = g.evaluate_sync(
    subject=Subject(id="u1", roles=["reader"]),
    action=Action("read"),
    resource=Resource(type="doc", id="42", attrs={"visibility": "public"}),
    context=Context(attrs={"mfa": True}),
)

assert d.allowed is True
assert d.effect == "permit"
print(d.reason, d.rule_id)  # "matched", "doc_read"

Decision schema

  • decision: "permit" or "deny"
  • reason: one of "matched", "explicit_deny", "action_mismatch", "condition_mismatch", "condition_type_mismatch", "resource_mismatch", "no_match", "obligation_failed"
  • rule_id and last_rule_id (both included for compatibility; last_rule_id is the matched rule id)
  • policy_id (present for policy sets; None for single policies)
  • obligations: list passed to the obligation checker (if a permit was gated)
  • (optional) challenge: present when an authentication/step-up is required (e.g., for MFA); may be used to return 401 with the appropriate challenge header

Policy sets

Default algorithm is:

from rbacx.core.policyset import decide as decide_policyset

policyset = {"algorithm":"deny-overrides", "policies":[ policy, {"rules":[...]} ]}
result = decide_policyset(policyset, {"subject":..., "action":"read", "resource":...})

If you want to test, try this:

from rbacx.core.policyset import decide as decide_policyset

# example set of policies
policyset = {
    "algorithm": "deny-overrides",
    "policies": [
        {"rules": [
            {"id": "allow_public_read", "effect": "permit", "actions": ["read"],
             "resource": {"type": "doc", "attrs": {"visibility": ["public"]}}}
        ]},
        {"rules": [
            {"id": "deny_archived", "effect": "deny", "actions": ["*"],
             "resource": {"type": "doc", "attrs": {"archived": True}}}
        ]},
    ],
}

# example request
req = {
    "subject": {"id": "u1", "roles": ["reader"]},
    "action": "read",
    "resource": {"type": "doc", "id": "42", "attrs": {"visibility": "public", "archived": False}},  # can try: would be `deny` if archived `True`
    "context": {},
}

res = decide_policyset(policyset, req)
print(res.get("decision", res))  # -> "permit"

Hot reloading

Default algorithm is:

from rbacx import Guard, HotReloader
from rbacx.store import FilePolicySource

guard = Guard(policy={})
mgr = HotReloader(guard, FilePolicySource("policy.json"), initial_load=...)
mgr.check_and_reload()        # initial load
mgr.start(10)  # background polling thread

If you want to test, try this:

⚠️ Important: this example creates a file on disk. You also can rewrite it with TempFile (tempfile.NamedTemporaryFile)

import json
import time
from rbacx import Action, Context, Guard, HotReloader, Resource, Subject
from rbacx.store import FilePolicySource

# create a tiny policy file next to the script
policy_path = "policy.json"
json.dump({
    "algorithm": "deny-overrides",
    "rules": [{
        "id": "allow_public_read", "effect": "permit", "actions": ["read"],
        "resource": {"type": "doc", "attrs": {"visibility": ["public"]}}
    }]
}, open(policy_path, "w", encoding="utf-8"))

guard = Guard({})
mgr = HotReloader(guard, FilePolicySource(policy_path), initial_load=True)
mgr.check_and_reload()  # initial load

print(guard.evaluate_sync(
    subject=Subject(id="u1", roles=["reader"]),
    action=Action("read"),
    resource=Resource(type="doc", id="1", attrs={"visibility": "public"}),
    context=Context(),
).effect)  # -> "permit"

# update policy and wait 3 second for reload
json.dump({
    "algorithm": "deny-overrides",
    "rules": [{"id": "deny_all", "effect": "deny", "actions": ["*"], "resource": {"type": "doc"}}]
}, open(policy_path, "w", encoding="utf-8"))
mgr.start(3)  # starting polling
time.sleep(3)

print(guard.evaluate_sync(
    subject=Subject(id="u1", roles=["reader"]),
    action=Action("read"),
    resource=Resource(type="doc", id="1", attrs={"visibility": "public"}),
    context=Context(),
).effect)  # -> "deny"

Quick links

Packaging

  • We ship py.typed so type checkers pick up annotations.
  • Standard PyPA flow: python -m build, then twine upload to (Test)PyPI.

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

rbacx-1.18.0.tar.gz (92.9 kB view details)

Uploaded Source

Built Distribution

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

rbacx-1.18.0-py3-none-any.whl (115.0 kB view details)

Uploaded Python 3

File details

Details for the file rbacx-1.18.0.tar.gz.

File metadata

  • Download URL: rbacx-1.18.0.tar.gz
  • Upload date:
  • Size: 92.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for rbacx-1.18.0.tar.gz
Algorithm Hash digest
SHA256 e45232ee9b3328a34819b8869c8aeb18e7d9e1d5a93f370a2ba762435832e1aa
MD5 cdb637fe074c261fc74c541e276bf87c
BLAKE2b-256 b8cc05e7b32cd0d060c40c846577eccb43972b6044ca8f1a1e6239af3608bf64

See more details on using hashes here.

Provenance

The following attestation bundles were made for rbacx-1.18.0.tar.gz:

Publisher: release.yml on Cheater121/rbacx

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

File details

Details for the file rbacx-1.18.0-py3-none-any.whl.

File metadata

  • Download URL: rbacx-1.18.0-py3-none-any.whl
  • Upload date:
  • Size: 115.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for rbacx-1.18.0-py3-none-any.whl
Algorithm Hash digest
SHA256 9cf251ea3f02f0ae8e3c8337c7ab05d1a2da9be86047f4a3d3eece86357f5ec1
MD5 55e1480ff30c897fa2b2593a32214be8
BLAKE2b-256 da905b53bd15e7f8dfb8a70add067b949bf6d2611581b07f962bf0190a9ac615

See more details on using hashes here.

Provenance

The following attestation bundles were made for rbacx-1.18.0-py3-none-any.whl:

Publisher: release.yml on Cheater121/rbacx

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