> ## Documentation Index
> Fetch the complete documentation index at: https://docs.emergence.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Authorization

> OpenFGA Relationship-Based Access Control (ReBAC) with relationship tuples, computed permissions, hierarchical inheritance, and the require_permission pattern.

# Authorization

CRAFT uses **OpenFGA** for authorization, implementing **Relationship-Based Access Control (ReBAC)**. Unlike traditional RBAC where roles grant flat permission sets, ReBAC models permissions as relationships between users and resources, enabling fine-grained, hierarchical access control.

<Info>
  OpenFGA is the open-source implementation of Google's Zanzibar authorization system. It evaluates permission checks in single-digit milliseconds, making it suitable for per-request authorization in API endpoints.
</Info>

```mermaid theme={null}
%%{init: {'theme': 'base', 'themeVariables': {'lineColor': '#555555', 'fontFamily': 'sans-serif', 'edgeLabelBackground': '#ffffff'}}}%%
flowchart TD
    U["User / Group"]
    O["Organization"]
    P["Project"]
    A["Agent"]
    DC["Data Connection"]
    MCP["MCP Server"]
    U -- "owner / admin / member" --> O
    O -- "owns" --> P
    U -- "developer / operator / viewer" --> P
    P -- "parent_of" --> A
    P -- "parent_of" --> DC
    P -- "parent_of" --> MCP
    style U fill:#56B4E9,stroke:#555555,color:#000
    style O fill:#E69F00,stroke:#555555,color:#000
    style P fill:#E69F00,stroke:#555555,color:#000
    style A fill:#009E73,stroke:#555555,color:#000
    style DC fill:#009E73,stroke:#555555,color:#000
    style MCP fill:#009E73,stroke:#555555,color:#000
```

## Core Concepts

<AccordionGroup>
  <Accordion title="Relationship Tuples">
    A relationship tuple is the fundamental building block. It states: *"This user/group has this relation on this resource."*

    ```text theme={null}
    user:jane          owner      organization:acme-corp
    group:org-admins   admin      organization:acme-corp
    user:bob           developer  project:analytics-prod
    ```

    Tuples are stored in OpenFGA and queried during permission checks.
  </Accordion>

  <Accordion title="Resource Types">
    The authorization model defines types for every resource in the platform:

    | Type              | Service    | Description                                 |
    | ----------------- | ---------- | ------------------------------------------- |
    | `organization`    | Governance | Top-level tenant boundary                   |
    | `project`         | Governance | Resource grouping within an organization    |
    | `artifact`        | Assets     | Generated outputs (reports, visualizations) |
    | `file`            | Assets     | Uploaded files and documents                |
    | `data_connection` | Assets     | External data source connections            |
    | `mcp_server`      | Assets     | Model Context Protocol servers              |
    | `api_server`      | Assets     | API server registrations                    |
    | `model`           | Assets     | ML model registrations                      |
  </Accordion>

  <Accordion title="Relations (Roles)">
    Relations represent the roles a user or group can have on a resource:

    | Relation    | Scope                 | Description                                    |
    | ----------- | --------------------- | ---------------------------------------------- |
    | `owner`     | All types             | Full control including deletion                |
    | `admin`     | Organization, Project | Administrative access, user/project management |
    | `member`    | Organization          | Standard membership with read access           |
    | `developer` | Project, Resources    | Create and manage resources                    |
    | `operator`  | Project, Resources    | Execute and operate resources                  |
    | `viewer`    | Project, Resources    | Read-only access                               |
  </Accordion>

  <Accordion title="Computed Permissions">
    Computed permissions are derived from relations using OR logic. For example, `can_read` is granted to anyone with `viewer`, `operator`, `developer`, `admin`, or `owner` relation:

    ```text theme={null}
    define can_read: viewer or operator or developer or admin or owner
    ```

    The full set of computed permissions:

    | Permission             | Description                                                     |
    | ---------------------- | --------------------------------------------------------------- |
    | `can_read`             | View the resource                                               |
    | `can_write`            | Modify the resource                                             |
    | `can_delete`           | Remove the resource                                             |
    | `can_execute`          | Run/invoke the resource (agents, MCP servers, data connections) |
    | `can_create_resources` | Create child resources (project-level)                          |
    | `can_share`            | Share with other users                                          |
    | `can_read_secrets`     | View associated secrets                                         |
    | `can_manage_secrets`   | Create/update/delete secrets                                    |
    | `can_read_metadata`    | View resource metadata                                          |
    | `can_manage_metadata`  | Modify resource metadata                                        |
    | `can_manage_projects`  | Create/delete projects (organization-level)                     |
    | `can_manage_users`     | Manage user memberships (organization-level)                    |
  </Accordion>
</AccordionGroup>

## Permission Inheritance

The authorization model uses OpenFGA's `from` keyword to implement hierarchical permission inheritance:

```text theme={null}
Projects inherit from Organizations
Resources inherit from Projects
Resource permissions can compute from Project permissions (e.g., can_delete from project)
```

This means an organization `admin` automatically has appropriate access to all projects and resources within their organization, without needing explicit tuples on each resource:

```bash theme={null}
# This single tuple:
group:org-admins   admin   organization:acme-corp

# Grants (via inheritance):
# - can_read on all projects in acme-corp
# - can_write on all projects in acme-corp
# - can_delete on all projects in acme-corp
# - can_read on all resources in those projects
# - can_write on all resources in those projects
# - ... and so on
```

The inheritance chain is established via parent link tuples:

```yaml theme={null}
project:analytics#organization@organization:acme-corp
data_connection:pg-prod#project@project:analytics
```

### Computed delegation: `can_delete from project`

For resource types like `data_connection`, the schema computes resource-level permissions from project-level permissions. For example, `can_delete` on a data connection is satisfied if the user has delete rights on the parent project:

```text theme={null}
type data_connection
  relations
    define project: [project]
    define can_delete: owner or admin or can_delete from project
    define can_execute: operator or developer or admin or owner
                        or can_execute from project
```

This means an organization owner who has `can_delete` on every project (via the org-from-project chain above) can also delete every project-scoped resource without per-resource tuples.

### Service worker permissions on `data_connection`

Service workers (background tasks like the data-readiness worker, scheduled enrichment runs, and dataset sync jobs) need to invoke data connections. The schema grants `can_execute` on `data_connection` to the service-worker role at the project level so workers can `POST /assets/data/{resource_uri}/verify` and `GET /assets/data/{resource_uri}/secret` against any connection in their assigned project, without needing a per-connection tuple.

This follows the same pattern as `can_create_resources` on `project` for service workers: a single role binding at the parent grants narrow operational permissions across all child resources.

## Authorization Schema (OpenFGA DSL)

The schema is defined in `openfga-schema.fga` and compiled to `openfga-schema.json` for runtime use. Here is a simplified excerpt showing the project type:

<CodeGroup>
  ```text theme={null}
  type project
    relations
      define organization: [organization]

      # Direct assignments
      define owner: [user, group#member, role#assignee]
      define admin: [user, group#member, role#assignee]
      define developer: [user, group#member, role#assignee]
      define operator: [user, group#member, role#assignee]
      define viewer: [user, group#member, role#assignee]

      # Computed permissions with inheritance
      define can_read: viewer or operator or developer or admin or owner
                       or admin from organization or owner from organization
      define can_write: operator or developer or admin or owner
                        or admin from organization or owner from organization
      define can_delete: owner or admin
                         or owner from organization or admin from organization
      define can_create_resources: developer or admin or owner
                                  or admin from organization or owner from organization
  ```
</CodeGroup>

<Warning>
  When modifying the OpenFGA schema, always update both the `.fga` DSL file and regenerate the `.json` runtime file by running `./scripts/generate-openfga-schema.sh`. The Governance service reads only the JSON file at startup. Adding new permissions is safe; renaming or removing is a **breaking change**.
</Warning>

## The require\_permission Pattern

All API endpoints use the `require_permission()` dependency to enforce authorization. This is implemented in the shared `common` package and used consistently across all services:

<CodeGroup>
  ```python theme={null}
  from common.permissions import Permissions, require_permission

  @router.delete("/data/{resource_uri}")
  async def delete_data_connection(
      resource_uri: str,
      _: None = Depends(require_permission(
          Permissions.DATA_CONNECTION,                       # Resource type
          Permissions.DATA_CONNECTION.actions.CAN_DELETE,     # Required permission
          "resource_uri"                                      # Path parameter with resource ID
      ))
  ):
      ...
  ```

  ```python theme={null}
  from common.permissions import Permissions, require_permission, HEADER_PROJECT_ID

  @router.post("/data")
  async def create_data_connection(
      payload: DataConnectionCreateRequest,
      _: None = Depends(require_permission(
          Permissions.PROJECT,                               # Check on the project
          Permissions.PROJECT.actions.CAN_CREATE_RESOURCES,   # Required permission
          HEADER_PROJECT_ID                                   # From X-Project-ID header
      ))
  ):
      ...
  ```

  ```python theme={null}
  from common.permissions import require_permission_dynamic

  @router.get("/{resource_type}/{resource_id}/secrets")
  async def list_secrets(
      resource_type: str,
      resource_id: str,
      _: None = Depends(require_permission_dynamic(
          resource_type_param="resource_type",   # Resolved from path parameter
          permission="can_read_secrets",
          resource_id_param="resource_id"
      ))
  ):
      ...
  ```
</CodeGroup>

## Permission Check Flow

When `require_permission()` is invoked, the following occurs:

<Steps>
  <Step title="Extract Resource ID">
    The resource ID is resolved from path parameters, query parameters, or headers based on the configuration.
  </Step>

  <Step title="Get Auth Context">
    The `Auth` object is extracted from the validated JWT, providing `sub` (user ID) and `teams` (group memberships).
  </Step>

  <Step title="Check OpenFGA">
    The permission checker queries OpenFGA with:

    * **User:** `user:{sub}` and each `group:{group_name}#member`
    * **Relation:** The computed permission (e.g., `can_delete`)
    * **Object:** `{resource_type}:{resource_id}`

    If any user or group check returns `true`, access is granted.
  </Step>

  <Step title="Return Result">
    * **200/success:** Permission granted, the endpoint handler executes
    * **403 Forbidden:** Permission denied, the request is rejected
    * **500 Internal Server Error:** Permission system misconfigured
  </Step>
</Steps>

## Permission API

The Governance service exposes a Permissions API for programmatic permission management:

| Endpoint                             | Method | Description                                             |
| ------------------------------------ | ------ | ------------------------------------------------------- |
| `/governance/permissions/check`      | GET    | Check if a user has a specific permission on a resource |
| `/governance/permissions/grant`      | POST   | Grant a relation to a user or group on a resource       |
| `/governance/permissions/revoke`     | POST   | Revoke a relation from a user or group                  |
| `/governance/permissions/set-parent` | POST   | Establish a parent-child relationship for inheritance   |
| `/governance/permissions/delete-all` | POST   | Remove all permission tuples for a resource (cleanup)   |

<Tip>
  Assets and Utils never call the Permissions API directly. They use the `require_permission()` dependency, which internally forwards the user's JWT to the Governance service for permission validation via the auto-generated SDK.
</Tip>

## Keycloak Groups and OpenFGA

The bridge between Keycloak (identity) and OpenFGA (authorization) is **Keycloak groups**:

1. Users are assigned to groups in Keycloak (e.g., `org-admins`, `project-developers`)
2. A groups mapper on the realm ensures group memberships appear in the JWT `groups` claim
3. During bootstrap, permission tuples map groups to OpenFGA relations (e.g., `group:org-admins#member` -> `admin` on `organization`)
4. At runtime, `require_permission()` checks both the user's direct tuples and their group-based tuples

This means adding a user to a Keycloak group is sufficient to grant them the corresponding platform permissions -- no separate OpenFGA management is needed.

## Next Steps

<CardGroup cols={2}>
  <Card title="Authentication" icon="lock" href="/platform/authentication">
    Understand how JWT tokens carry group claims for authorization.
  </Card>

  <Card title="Organizations" icon="building" href="/platform/organizations">
    See how organization-level permissions are bootstrapped.
  </Card>

  <Card title="Projects" icon="folder" href="/platform/projects">
    Learn about project-level role assignments and inheritance.
  </Card>

  <Card title="Multi-Tenancy" icon="users" href="/platform/multi-tenancy">
    Explore the full tenant isolation architecture.
  </Card>
</CardGroup>
