Skip to content

Settings & Utilities

This section covers the core configuration logic and the infrastructure utilities that power the django-rebac package under the hood.


Configuration (conf.py)

The configuration module is responsible for parsing the REBAC_CONFIG dictionary defined in your core Django settings.py and falling back to sensible defaults.

Attributes

DEFAULTS module-attribute

DEFAULTS: dict[str, Any] = {'BACKEND': 'rebac.backends.openfga.client.OpenFGABackend', 'BACKEND_OPTIONS': {}, 'BATCH_SIZE': 50, 'MAX_RETRIES': 5, 'REQUEST_HEADER_MAPPINGS': {'X-User-Id': 'rebac_user'}, 'ENABLE_OUTBOX_ADMIN': True, 'REBAC_USER_ATTR': 'rebac_user', 'REBAC_USER_PREFIX': 'user:', 'LOCAL_DEV_FALLBACK': {'USE_DJANGO_USER': True, 'STATIC_USER_ID': None}}

Sensible defaults for the django-rebac integration.

Attributes:

Name Type Description
BACKEND str

The dot-path to the ReBAC engine adapter. Defaults to django_rebac.backends.openfga.client.OpenFGABackend.

BACKEND_OPTIONS dict[str, Any]

Backend-specific configuration (e.g., API URLs, Store IDs, or Pre-shared Keys).

BATCH_SIZE int

Number of items to process in a single synchronization batch. Defaults to 50.

MAX_RETRIES int

How many times to retry failed synchronization attempts. Defaults to 5.

REQUEST_HEADER_MAPPINGS dict[str, str]

Mapping of incoming request headers to ReBAC context variables.

ENABLE_OUTBOX_ADMIN bool

If True, registers the ReBAC Outbox model in the Django Admin. Defaults to True.

REBAC_USER_ATTR str

The attribute on the request/user object to use for ReBAC identity.

REBAC_USER_PREFIX str

Prefix added to user IDs (e.g., user:123).

LOCAL_DEV_FALLBACK dict[str, Any]

Settings for local development when identity providers are absent.

  • USE_DJANGO_USER: Fallback to native Django session user. Defaults to True.
  • STATIC_USER_ID: A hardcoded ID for rapid testing. Defaults to None.

Classes

Functions

get_setting

get_setting(name: str) -> Any

Fetches a setting from the REBAC dictionary in django.conf.settings.

Falls back to the DEFAULTS dictionary if not provided.

Parameters:

Name Type Description Default
name str

The string key of the setting to retrieve.

required

Returns:

Name Type Description
Any Any

The resolved configuration value.

Raises:

Type Description
ImproperlyConfigured

If the setting key is not recognized by the framework.

Source code in src/rebac/conf.py
def get_setting(name: str) -> Any:
    """Fetches a setting from the `REBAC` dictionary in `django.conf.settings`.

    Falls back to the `DEFAULTS` dictionary if not provided.

    Args:
        name: The string key of the setting to retrieve.

    Returns:
        Any: The resolved configuration value.

    Raises:
        ImproperlyConfigured: If the setting key is not recognized by the framework.
    """
    if name not in DEFAULTS:
        raise ImproperlyConfigured(f"'{name}' is not a valid django-rebac setting.")

    # Using 'REBAC_CONFIG' as the standard namespace to match standard Django conventions
    user_settings: dict[str, Any] = getattr(settings, "REBAC_CONFIG", {})

    if name == "BACKEND_OPTIONS":
        return _resolve_backend_options(user_settings)

    return user_settings.get(name, DEFAULTS[name])

validate_settings

validate_settings() -> None

Ensures the ReBAC configuration is logically consistent.

Should be called in AppConfig.ready().

Raises:

Type Description
ImproperlyConfigured

If the configuration prevents the framework from operating.

Source code in src/rebac/conf.py
def validate_settings() -> None:
    """Ensures the ReBAC configuration is logically consistent.

    Should be called in AppConfig.ready().

    Raises:
        ImproperlyConfigured: If the configuration prevents the framework from operating.
    """
    user_attr = get_setting("REBAC_USER_ATTR")
    mappings = get_setting("REQUEST_HEADER_MAPPINGS")

    # THE HANDSHAKE CHECK
    # Ensure the attribute we use for checks is actually being populated by the middleware
    if user_attr not in mappings.values():
        raise ImproperlyConfigured(
            f"REBAC['REBAC_USER_ATTR'] is set to '{user_attr}', "
            f"but this attribute is not defined as a target in 'REQUEST_HEADER_MAPPINGS'. "
            f"The GatewayIdentityMiddleware will never be able to set the user context."
        )

    # Ensure the prefix is provided for Zanzibar compatibility
    prefix = get_setting("REBAC_USER_PREFIX")
    if not prefix or not prefix.endswith(":"):
        # Warning: They forgot the colon in 'user:'
        dev_logger.warning(
            f"REBAC_USER_PREFIX ('{prefix}') does not end with a colon. "
            f"Standard ReBAC object strings usually follow 'type:id' format."
        )

ReBAC Client Utility (backends/openfga/client.py)

The get_rebac_client function is the centralized infrastructure utility responsible for instantiating and configuring the ReBAC backend Python SDK client.

By utilizing this utility, we adhere to the Single Responsibility Principle (SRP). Our application services and permission classes do not need to know how to authenticate with the ReBAC engine or where the ReBAC server lives; they simply request a ready-to-use client and execute their checks.

⚙️ How it works

Under the hood, get_rebac_client() fetches the necessary environment configuration from your Django settings.py (specifically the REBAC_CONFIG dictionary). It ensures that parameters like the API_URL and STORE_ID are properly loaded for OpenFGA Backend.

Performance Note: It utilizes Python's @lru_cache to act as a thread-safe Singleton, ensuring the underlying HTTP connection pool is reused across requests for maximum performance.

Classes

Functions

get_rebac_client cached

get_rebac_client() -> BaseReBACBackend

Dynamically loads and instantiates the configured ReBAC backend.

Returns:

Name Type Description
BaseReBACBackend BaseReBACBackend

An instantiated adapter ready to process authorization queries.

Raises:

Type Description
ImproperlyConfigured

If the backend path is invalid or the class fails to load.

Source code in src/rebac/utils.py
@lru_cache(maxsize=1)
def get_rebac_client() -> BaseReBACBackend:
    """Dynamically loads and instantiates the configured ReBAC backend.

    Returns:
        BaseReBACBackend: An instantiated adapter ready to process authorization queries.

    Raises:
        ImproperlyConfigured: If the backend path is invalid or the class fails to load.
    """
    backend_path: str = get_setting("BACKEND")
    options: dict[str, Any] = get_setting("BACKEND_OPTIONS")

    try:
        module_path, class_name = backend_path.rsplit(".", 1)
        module = importlib.import_module(module_path)
        backend_class = getattr(module, class_name)
    except (ImportError, ValueError) as e:
        raise ImproperlyConfigured(
            f"Could not import ReBAC backend '{backend_path}'. Error: {e}"
        ) from e

    if not issubclass(backend_class, BaseReBACBackend):
        raise ImproperlyConfigured(f"Backend '{backend_path}' must inherit from BaseReBACBackend.")

    try:
        return backend_class(**options)
    except Exception as e:
        raise ImproperlyConfigured(
            f"Failed to initialize ReBAC backend '{backend_path}': {e}"
        ) from e

🏗️ Architectural Usage Guidelines

To maintain our Clean Architecture and strict layer separation, follow these rules when using get_rebac_client() in your own application:

Rules of Engagement

  • DO NOT use this client directly inside a Django Model (Layer 3). Models should only define ReBAC mappings declaratively using the rebac_config attribute with the RebacModelConfig data class.
  • DO use this client inside Custom Permissions (Layer 3) to protect your DRF API views.
  • DO use this client inside your Service Layer (Layer 2) if you need to manually query the authorization graph to make complex business logic decisions.

Example usage in a Service:

# services.py
from rebac.utils import get_rebac_client

class DocumentService:
    def publish_document(self, document_id: str, user_id: str):
        # 1. Fetch the configured (and cached) agnostic ReBAC client
        rebac_client = get_rebac_client()

        # 2. Query ReBAC to ensure the user has the 'editor' role
        is_allowed = rebac_client.check(
            user=f"user:{user_id}",
            relation="editor",
            obj=f"document:{document_id}"
        )

        if not is_allowed:
            raise PermissionError("Only editors can publish this document.")

        # ... proceed with publishing business logic ...