Skip to main content

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

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.
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.

Core Concepts

A relationship tuple is the fundamental building block. It states: “This user/group has this relation on this resource.”
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.
The authorization model defines types for every resource in the platform:
TypeServiceDescription
organizationGovernanceTop-level tenant boundary
projectGovernanceResource grouping within an organization
artifactAssetsGenerated outputs (reports, visualizations)
fileAssetsUploaded files and documents
data_connectionAssetsExternal data source connections
mcp_serverAssetsModel Context Protocol servers
api_serverAssetsAPI server registrations
modelAssetsML model registrations
Relations represent the roles a user or group can have on a resource:
RelationScopeDescription
ownerAll typesFull control including deletion
adminOrganization, ProjectAdministrative access, user/project management
memberOrganizationStandard membership with read access
developerProject, ResourcesCreate and manage resources
operatorProject, ResourcesExecute and operate resources
viewerProject, ResourcesRead-only access
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:
define can_read: viewer or operator or developer or admin or owner
The full set of computed permissions:
PermissionDescription
can_readView the resource
can_writeModify the resource
can_deleteRemove the resource
can_executeRun/invoke the resource (agents, MCP servers, data connections)
can_create_resourcesCreate child resources (project-level)
can_shareShare with other users
can_read_secretsView associated secrets
can_manage_secretsCreate/update/delete secrets
can_read_metadataView resource metadata
can_manage_metadataModify resource metadata
can_manage_projectsCreate/delete projects (organization-level)
can_manage_usersManage user memberships (organization-level)

Permission Inheritance

The authorization model uses OpenFGA’s from keyword to implement hierarchical permission inheritance:
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:
# 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:
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:
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:
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
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.

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:
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
    ))
):
    ...

Permission Check Flow

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

Extract Resource ID

The resource ID is resolved from path parameters, query parameters, or headers based on the configuration.
2

Get Auth Context

The Auth object is extracted from the validated JWT, providing sub (user ID) and teams (group memberships).
3

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.
4

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

Permission API

The Governance service exposes a Permissions API for programmatic permission management:
EndpointMethodDescription
/governance/permissions/checkGETCheck if a user has a specific permission on a resource
/governance/permissions/grantPOSTGrant a relation to a user or group on a resource
/governance/permissions/revokePOSTRevoke a relation from a user or group
/governance/permissions/set-parentPOSTEstablish a parent-child relationship for inheritance
/governance/permissions/delete-allPOSTRemove all permission tuples for a resource (cleanup)
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.

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

Authentication

Understand how JWT tokens carry group claims for authorization.

Organizations

See how organization-level permissions are bootstrapped.

Projects

Learn about project-level role assignments and inheritance.

Multi-Tenancy

Explore the full tenant isolation architecture.