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

# RBAC Configuration

> How to set up OpenFGA roles and permissions for fine-grained access control across organizations, projects, and resources.

# Configure Roles and Permissions

CRAFT uses **OpenFGA** for Relationship-Based Access Control (ReBAC). This guide covers setting up roles, assigning permissions, and configuring the permission inheritance model across organizations, projects, and resources.

## Prerequisites

<Info>
  Before you begin, ensure you have:

  * A running CRAFT with the Governance service bootstrapped
  * Administrative access (the `admin` or `owner` role on the target organization)
  * A valid JWT token for the admin user
  * The OpenFGA schema loaded during Governance bootstrap
</Info>

## How Permissions Work

The platform uses a **relationship-based** model rather than traditional role-based access control. Permissions are computed at query time from relationships between users, roles, and resources.

```text theme={null}
User --[role]--> Organization --[parent]--> Project --[parent]--> Resource
```

### Role Hierarchy

| Role        | Scope                  | Capabilities                                                             |
| ----------- | ---------------------- | ------------------------------------------------------------------------ |
| `owner`     | Organization           | Full control, can transfer ownership, manage all settings                |
| `admin`     | Organization / Project | Manage users, projects, settings. Cannot transfer organization ownership |
| `member`    | Project                | Standard access to project resources                                     |
| `developer` | Project                | Create and modify agents, data connections, workflows                    |
| `operator`  | Project                | Deploy, schedule, and monitor resources                                  |
| `viewer`    | Project                | Read-only access to all resources                                        |

### Computed Permissions

Permissions are derived from roles via the OpenFGA schema:

| Permission           | owner | admin | member | developer | operator | viewer |
| -------------------- | ----- | ----- | ------ | --------- | -------- | ------ |
| `can_read`           | Yes   | Yes   | Yes    | Yes       | Yes      | Yes    |
| `can_write`          | Yes   | Yes   | Yes    | Yes       | No       | No     |
| `can_delete`         | Yes   | Yes   | No     | No        | No       | No     |
| `can_execute`        | Yes   | Yes   | Yes    | Yes       | Yes      | No     |
| `can_manage_secrets` | Yes   | Yes   | No     | No        | No       | No     |

## Step 1: Understand the OpenFGA Schema

The permission model is defined in the [OpenFGA Schema DSL](https://openfga.dev/docs/configuration-language).
Key type definitions used by the platform:

```text theme={null}
type organization
  relations
    define owner: [user]
    define admin: [user] or owner
    define member: [user] or admin

type project
  relations
    define parent: [organization]
    define admin: [user] or admin from parent
    define developer: [user] or admin
    define operator: [user] or admin
    define viewer: [user] or developer or operator
    define can_read: viewer or member from parent
    define can_write: developer or admin
    define can_delete: admin
    define can_execute: operator or developer or admin
```

## Step 2: Grant a role on a resource

The Governance API exposes a single grant endpoint that is used for every
role-on-resource combination — there is no separate "add organization member"
or "add project member" endpoint. A "role assignment" is a tuple
`(user_or_group, relation, resource_type:resource_id)` written to the OpenFGA
store via `POST /governance/permissions/grant`.

<Tabs>
  <Tab title="API">
    ```bash theme={null}
    # Grant the 'admin' relation to a user on an organization
    curl -X POST "https://<platform-host>/api/governance/permissions/grant" \
      -H "Authorization: Bearer $TOKEN" \
      -H "X-Project-ID: <project-id>" \
      -H "Content-Type: application/json" \
      -d '{
        "user_or_group": "<user-id-or-email>",
        "relation": "admin",
        "resource_type": "organization",
        "resource_id": "<org-id>"
      }'
    ```

    Response (`200 OK`):

    ```json theme={null}
    { "message": "Granted admin permission to user '<user-id>' on organization '<org-id>'" }
    ```

    The same endpoint also assigns project-scoped roles — change
    `resource_type` to `project` and `resource_id` to the project UUID:

    ```bash theme={null}
    curl -X POST "https://<platform-host>/api/governance/permissions/grant" \
      -H "Authorization: Bearer $TOKEN" \
      -H "X-Project-ID: <project-id>" \
      -H "Content-Type: application/json" \
      -d '{
        "user_or_group": "<user-id-or-email>",
        "relation": "developer",
        "resource_type": "project",
        "resource_id": "<project-id>"
      }'
    ```

    Same shape for any resource type: `artifact`, `data_connection`,
    `secret`, etc. The `relation` must be one of the role names defined
    on that resource type in the OpenFGA schema (Step 1).
  </Tab>

  <Tab title="Python SDK">
    The auto-generated client is an
    [openapi-python-client](https://github.com/openapi-generators/openapi-python-client)
    package — module-level functions per endpoint, called against an
    `AuthenticatedClient` instance. There is no nested `client.permissions.grant(...)`
    method.

    ```python theme={null}
    from em_runtime_governance_sdk import AuthenticatedClient
    from em_runtime_governance_sdk.api.permissions import grant_permission
    from em_runtime_governance_sdk.models import GrantPermissionRequest

    client = AuthenticatedClient(
        base_url="https://<platform-host>/api/governance",
        token=admin_token,
    )

    # Grant on an organization
    await grant_permission.asyncio(
        client=client,
        body=GrantPermissionRequest(
            user_or_group=user_id,
            relation="admin",
            resource_type="organization",
            resource_id=org_id,
        ),
    )

    # Grant on a project — same call, different resource_type/resource_id
    await grant_permission.asyncio(
        client=client,
        body=GrantPermissionRequest(
            user_or_group=user_id,
            relation="developer",
            resource_type="project",
            resource_id=project_id,
        ),
    )
    ```

    The same pattern applies to the other permission endpoints — import
    `revoke_permission`, `check_permission`, `list_accessible_objects`,
    `set_resource_parent`, or `delete_resource_permissions` from the same
    `em_runtime_governance_sdk.api.permissions` module.
  </Tab>
</Tabs>

### Permission Inheritance

Roles inherit downward through the hierarchy because the OpenFGA schema
expresses each role on a child as derivable from its parent:

<Steps>
  <Step title="Organization owner">
    Automatically has admin-level permissions on all projects and resources within the organization.
  </Step>

  <Step title="Organization admin">
    Automatically has admin-level permissions on all projects. Can create and delete projects.
  </Step>

  <Step title="Project developer">
    Has `can_read`, `can_write`, and `can_execute` on all resources within the specific project. Cannot delete resources or manage secrets.
  </Step>

  <Step title="Project viewer">
    Has `can_read` only. Cannot modify, execute, or delete any resource.
  </Step>
</Steps>

When you create a child resource (e.g. an artifact within a project),
register the parent relationship so inheritance applies:

```bash theme={null}
curl -X POST "https://<platform-host>/api/governance/permissions/set-parent" \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-Project-ID: <project-id>" \
  -H "Content-Type: application/json" \
  -d '{
    "resource_type": "artifact",
    "resource_id": "<artifact-uri>",
    "parent_type": "project",
    "parent_id": "<project-id>"
  }'
```

## Step 3: Check or list permissions

Two read endpoints answer the common questions:

```bash theme={null}
# 1) Can <current user> perform <action> on <resource>?
#    Note: this is a GET with query params — NOT a POST with a body.
curl -G "https://<platform-host>/api/governance/permissions/check" \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-Project-ID: <project-id>" \
  --data-urlencode "action=can_write" \
  --data-urlencode "resource_type=agent" \
  --data-urlencode "resource_id=<agent-id>"
```

Response (`200 OK`): the response body for an allowed call is `null`
(absence of a `403` body means the action is permitted). A denied check
returns `403 Forbidden`.

```bash theme={null}
# 2) Which objects can <current user> read across every resource type?
curl "https://<platform-host>/api/governance/permissions/accessible-objects" \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-Project-ID: <project-id>"
```

Response (`200 OK`):

```json theme={null}
{
  "object_ids": [
    "project:<project-id>",
    "data_connection:data:<org-id>:<project-id>:<connection-name>",
    "agent:agent:<org-id>:<project-id>:<agent-name>"
  ]
}
```

Each entry is `<resource_type>:<resource_id>`, with `resource_id` echoing
the canonical `resource_uri` for `agent` / `data_connection` /
`artifact` resources.

## Step 4: Revoke a role

`POST /governance/permissions/revoke` mirrors `grant`:

```bash theme={null}
curl -X POST "https://<platform-host>/api/governance/permissions/revoke" \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-Project-ID: <project-id>" \
  -H "Content-Type: application/json" \
  -d '{
    "user_or_group": "<user-id-or-email>",
    "relation": "developer",
    "resource_type": "project",
    "resource_id": "<project-id>"
  }'
```

Response (`200 OK`):

```json theme={null}
{ "message": "Revoked developer permission from user '<user-id>' on project '<project-id>'" }
```

To delete every permission tuple for a resource (e.g. before deleting the
resource itself), use `POST /governance/permissions/delete-all`:

```bash theme={null}
curl -X POST "https://<platform-host>/api/governance/permissions/delete-all" \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-Project-ID: <project-id>" \
  -H "Content-Type: application/json" \
  -d '{ "resource_type": "project", "resource_id": "<project-id>" }'
```

Response includes `deleted_count` so you can confirm the sweep.

## Common Permission Patterns

<AccordionGroup>
  <Accordion title="Data team with read-only analytics access">
    Assign `viewer` on the project containing data connections and dashboards. Users can run queries via Data Insights but cannot modify data connections or agent configurations.
  </Accordion>

  <Accordion title="DevOps team managing deployments">
    Assign `operator` on the target project. Operators can deploy agents, trigger schedules, and monitor health, but cannot modify agent code or data connection credentials.
  </Accordion>

  <Accordion title="Service accounts for CI/CD">
    Create Keycloak client credentials and assign `developer` or `operator` on the target project. Service accounts authenticate via client credentials grant and have the same permission model as human users.
  </Accordion>

  <Accordion title="Cross-project access">
    A user can have different roles on different projects within the same organization. Assign roles per project to implement least-privilege access.
  </Accordion>
</AccordionGroup>

## Integration with SSO Groups

When SSO is configured, IdP groups map through Keycloak to OpenFGA:

```text theme={null}
IdP Group -> Keycloak Group -> OpenFGA Relation (role assignment)
```

This pipeline automates role assignment when users are provisioned via SSO. See the [SSO Integration guide](/guides/sso-integration) for configuring the IdP side.

## Troubleshooting

<AccordionGroup>
  <Accordion title="Permission checks returning 403">
    Verify the OpenFGA schema is loaded by checking Governance bootstrap logs. Ensure the user's JWT contains the correct groups claim. Confirm the user has been assigned a role on the target project.
  </Accordion>

  <Accordion title="Inherited permissions not working">
    Check that the project has a `parent` relationship to the organization in OpenFGA. This is set automatically when projects are created via the Governance API.
  </Accordion>

  <Accordion title="Role changes not taking effect">
    OpenFGA evaluates permissions at query time -- there is no caching delay. If changes are not reflected, verify the role assignment was successful by listing members on the project.
  </Accordion>
</AccordionGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="SSO Integration" icon="key" href="/guides/sso-integration">
    Configure SSO to automate group-to-role mapping from your IdP.
  </Card>

  <Card title="Authorization Deep Dive" icon="lock" href="/platform/authorization">
    Learn about the full OpenFGA schema and computed permissions model.
  </Card>

  <Card title="Multi-Tenancy" icon="building" href="/platform/multi-tenancy">
    Understand how permissions integrate with the multi-tenant architecture.
  </Card>

  <Card title="Security Overview" icon="shield" href="/security/overview">
    Review the complete security model including authentication and data protection.
  </Card>
</CardGroup>
