Skip to main content

Authorization Setup Overview

Authorization in projects generated by Castlecraft Architect is built upon the flexible and extensible framework provided by the castlecraft-engineer library. This document provides an overview of how this system works and how Architect helps you integrate with it.

Core Concepts from castlecraft-engineer

The authorization system in castlecraft-engineer revolves around a few key abstractions:

  1. AuthorizationService (Interface): This is the central interface (defined in castlecraft_engineer.abstractions.authorization.base_service.AuthorizationService) that all authorization engines must implement. Its primary method is check_permission.

    # Excerpt from castlecraft_engineer.abstractions.authorization.base_service.py
    class AuthorizationService(abc.ABC):
    @abc.abstractmethod
    async def check_permission(
    self,
    subject_id: Optional[str],
    required_permissions: List[Permission],
    provided_permissions: Optional[List[str]] = None,
    context: Optional[Dict[str, Any]] = None,
    ) -> bool:
    # ... (implementation raises AuthorizationError on failure)
    raise NotImplementedError

    Implementations connect to engines like Casbin, OPA, etc. The check_permission method raises an AuthorizationError if the subject is not authorized.

  2. Permission Dataclass: Represents a single permission required for an operation (defined in castlecraft_engineer.authorization.permission.Permission).

    # Excerpt from castlecraft_engineer.authorization.permission.py
    @dataclass(frozen=True)
    class Permission:
    action: BaseStringEnum # e.g., Action.READ, Action.UPDATE
    resource: BaseStringEnum # e.g., Resource.WIDGET, 'product'
    scope: Optional[BaseStringEnum] = None # e.g., Scope.OWN, 'tenant:123'

    The action, resource, and scope fields often use string-based enums for clarity.

  3. AuthorizationError Exception: A custom exception raised by check_permission upon authorization failure.

  4. @ctx Decorator: Used on command/query handler execute methods to declare the permissions required for that operation. It injects a required_permissions list into the method's kwargs.

    from castlecraft_engineer.authorization.permission import Permission, ctx
    # ...
    class MyCommandHandler(CommandHandler[MyCommand]):
    @ctx(Permission(action=Action.CREATE, resource=Resource.ORDER))
    async def execute(self, command: MyCommand, **kwargs):
    # required_permissions = kwargs.get('required_permissions')
    # ... authorization check ...
    pass

Performing Authorization Checks in Handlers

Within your command or query handlers (generated by Architect or custom-written), you'll typically:

  1. Inject AuthorizationService: Receive an instance via dependency injection.
  2. Retrieve required_permissions: Access the list of Permission objects from kwargs['required_permissions'] (populated by the @ctx decorator).
  3. Gather Context:
    • subject_id: The ID of the user/service making the request (often from get_current_user).
    • provided_permissions: A list of permissions/roles the subject possesses (e.g., from JWT claims).
    • context: A dictionary of additional data for policy evaluation (e.g., resource ID from the command).
  4. Call check_permission: Invoke await self._auth_service.check_permission(...). An AuthorizationError will halt execution if unauthorized.

Architect's Role and Contributions

While the core authorization mechanism is from castlecraft-engineer, Castlecraft Architect assists in several ways:

  1. Scaffolding Permission Components: When you define operations that require authorization, Architect can scaffold "permission" components. These components represent the action, resource, and scope that will be used in your @ctx decorators and policy definitions.

  2. Generating Authorization Model & Policy: Architect has knowledge of all Permission components defined in your application. This allows it to help generate:

    • Authorization Models: For engines like Casbin, Architect can generate the model configuration file (e.g., model.conf) based on the structure of your defined permissions.
      architect generate authorization-model --engine casbin -o ./auth_model.conf
    • Bootstrap Authorization Policies: Architect can generate initial policy data (e.g., a Casbin CSV file) that grants permissions to roles or users. This is particularly useful for setting up default roles like "admin".
    • Syncing and Exporting Policies: Architect provides commands to sync these generated policy files to a live database and export existing policies.
      # Generate the policy file
      architect generate authorization-policy --engine casbin --engine-config ./casbin_bootstrap_config.yaml -o ./bootstrap_policy.csv
      # Sync the generated file to the database
      architect authorization sync-policies --policy-file ./bootstrap_policy.csv --engine casbin

    Refer to the Casbin Engine Configuration guide for details on the --engine-config file.

  3. Providing Authorization Engine Adapters: Architect aims to provide adapters for common authorization engines. An adapter implements the logic for how Architect should generate model and policy files specific to that engine, and optionally, how to persist them.

    • Casbin Adapter: Architect includes a CasbinEngineAdapter. This adapter understands how to translate your application's permission components into a Casbin model and policy rules.
  4. Default AuthorizationService Setup: Architect projects are typically configured to use one of castlecraft-engineer's default AuthorizationService implementations based on the AUTHORIZATION_ENGINE environment variable:

    • allow_all: Uses AllowAllAuthorizationService (bypasses checks - for development/testing only).
    • deny_all (or if unset): Uses DenyAllAuthorizationService (secure default).
    • casbin (or other custom value): Expects a specific AuthorizationService implementation (like Architect's CasbinAuthorizationService) to be registered in the DI container. Architect's default setup for "collaboration mode" often includes the CasbinAuthorizationService.

Implementing Your Authorization Logic

  1. Define Permissions: Use Architect to define "permission" components for the actions and resources in your application.
  2. Decorate Handlers: Apply the @ctx(...) decorator to your command and query handlers with the appropriate Permission objects.
  3. Choose an Engine: Decide on an authorization engine (e.g., Casbin).
  4. Configure the Engine:
    • Set the AUTHORIZATION_ENGINE environment variable (e.g., casbin).
    • Provide the necessary configuration for your chosen engine (e.g., Casbin model and policy files).
  5. Generate Artifacts (Optional but Recommended):
    • Use architect generate authorization-model to create the model file for your engine.
    • Use architect generate authorization-policy with an --engine-config YAML to bootstrap your policy data.
    • Use architect authorization sync-policies to load the bootstrap policies into your database.
  6. Ensure Runtime AuthorizationService is Registered:
    • If using a common engine like Casbin with Architect's adapter, Architect's DI setup usually handles registering the appropriate AuthorizationService (e.g., CasbinAuthorizationService).
    • For a fully custom engine, you would implement your own AuthorizationService and register it in your application's DI container.

Extensibility

  • Custom AuthorizationService: You can implement the AuthorizationService interface to integrate any authorization engine or custom logic.
  • Custom Authorization Adapters for Architect: If you want Architect's generate commands to support a new engine, you can create a custom "Authorization Adapter" plugin. This adapter would teach Architect how to produce model and policy files for that specific engine. See the "Extending Architect" guides for more details.

By leveraging castlecraft-engineer's authorization primitives and Architect's generation capabilities, you can establish a robust and maintainable authorization layer for your applications.