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:
-
AuthorizationService
(Interface): This is the central interface (defined incastlecraft_engineer.abstractions.authorization.base_service.AuthorizationService
) that all authorization engines must implement. Its primary method ischeck_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 NotImplementedErrorImplementations connect to engines like Casbin, OPA, etc. The
check_permission
method raises anAuthorizationError
if the subject is not authorized. -
Permission
Dataclass: Represents a single permission required for an operation (defined incastlecraft_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
, andscope
fields often use string-based enums for clarity. -
AuthorizationError
Exception: A custom exception raised bycheck_permission
upon authorization failure. -
@ctx
Decorator: Used on command/query handlerexecute
methods to declare the permissions required for that operation. It injects arequired_permissions
list into the method'skwargs
.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:
- Inject
AuthorizationService
: Receive an instance via dependency injection. - Retrieve
required_permissions
: Access the list ofPermission
objects fromkwargs['required_permissions']
(populated by the@ctx
decorator). - Gather Context:
subject_id
: The ID of the user/service making the request (often fromget_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).
- Call
check_permission
: Invokeawait self._auth_service.check_permission(...)
. AnAuthorizationError
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:
-
Scaffolding Permission Components: When you define operations that require authorization, Architect can scaffold "permission" components. These components represent the
action
,resource
, andscope
that will be used in your@ctx
decorators and policy definitions. -
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. - Authorization Models: For engines like Casbin, Architect can generate the model configuration file (e.g.,
-
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.
- Casbin Adapter: Architect includes a
-
Default
AuthorizationService
Setup: Architect projects are typically configured to use one ofcastlecraft-engineer
's defaultAuthorizationService
implementations based on theAUTHORIZATION_ENGINE
environment variable:allow_all
: UsesAllowAllAuthorizationService
(bypasses checks - for development/testing only).deny_all
(or if unset): UsesDenyAllAuthorizationService
(secure default).casbin
(or other custom value): Expects a specificAuthorizationService
implementation (like Architect'sCasbinAuthorizationService
) to be registered in the DI container. Architect's default setup for "collaboration mode" often includes theCasbinAuthorizationService
.
Implementing Your Authorization Logic
- Define Permissions: Use Architect to define "permission" components for the actions and resources in your application.
- Decorate Handlers: Apply the
@ctx(...)
decorator to your command and query handlers with the appropriatePermission
objects. - Choose an Engine: Decide on an authorization engine (e.g., Casbin).
- 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).
- Set the
- 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.
- Use
- 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.
- If using a common engine like Casbin with Architect's adapter, Architect's DI setup usually handles registering the appropriate
Extensibility
- Custom
AuthorizationService
: You can implement theAuthorizationService
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.