Lewati ke konten utama
Versi: v0.0.82

Permissions Reference

Honeyframe is in the middle of a transition between two authorization layers:

  • require_role(*roles) (legacy) — flat per-user role string stored on honeyframe.users.role. Most existing endpoints still gate on this.
  • require_permission("<resource>.<action>", target_param?) (new) — group-based check resolved by services/permissions.user_has_permission() against honeyframe.group_permissions. New endpoints are written against this; old ones are migrated opportunistically.

Both layers are honored simultaneously. The resolver allows a request when any of the following is true (in order):

  1. user.is_superadmin == true
  2. user.role == 'admin' (legacy compatibility shim)
  3. The user belongs to a group with a matching group_permissions row (specific target_id or target_id IS NULL)

If none match, the request returns 403 Forbidden with a JSON body of { "error": "permission_denied", "permission": "...", "target_id": "..." }.

Layer 1 — Roles

The role column on honeyframe.users holds one of (v0.0.54+):

RoleTypical use
adminFull org access. Bypasses all permission checks via the resolver shim.
designerPower user / flow author. Builds datasets, recipes, dashboards, agents.
analystVisual-only consumer. Reads dashboards, builds dashboards/queries, no ingestion edits.
viewerRead-only.

The legacy require_role() gate was retired in PaaS in v0.0.54 — all 557 router callsites now go through require_permission() (see below). require_role() is kept defined only for SaaS vertical routers until they get their own migration.

The role == 'admin' shim in the resolver stays as a permanent per-request optimization (Administrators groups hold org.admin via the v0.0.54 seed; the shortcut just saves a DB roundtrip). Implicit-grant shims also map designer/analyst/viewer onto their seeded permissions so tenants where user_groups hasn't been backfilled yet still resolve correctly.

Layer 2 — Permission strings

Every permission Honeyframe checks at this layer is a string of the form <resource>.<action>. The resolver lives at services/permissions.user_has_permission() and is invoked via the FastAPI dependency require_permission(permission_type, target_param=None).

Catalog (today)

Every gate in the migrated PaaS routers (v0.0.54+) resolves to one of the strings below.

org.admin — full administrative access to the organization. Required for all /api/groups mutations + every administration surface (audit, branding, connectors before v0.0.55, infrastructure, invites, lineage, notifications, org_backup, org_oidc, org_saml, org_smtp, org_usage, org_webhooks, organizations, plugins, projects, site_auth, storage, system_health, users).

project.edit — designer-or-better paths: every endpoint that previously gated on the legacy ("admin", "designer") tuple (publishing, dashboards, dataset readwrite, chat, column_lineage, data_dictionary, datasets, flow_ai, knowledge, knowledge_bases, lineage, sync_recipes).

project.view — broad-read paths previously gated on OVERVIEW_ROLES (analytics overview, etc.). Held by analyst + viewer org-wide via the v0.0.54 seed.

dashboard.edit — modify a dashboard's tiles, layout, or settings. Used as require_permission("dashboard.edit", "dashboard_id") so the check is per-dashboard.

dashboard.view — view a dashboard and run its cards.

connector.read / connector.edit — split off org.admin in v0.0.55 so designers can manage data sources without full org-admin. See Connectors → Permission model.

dataset.read / dataset.readwrite — per-dataset authorization tier.

feature.<feature_name> — generic capability gate. feature.agent_builder and feature.chat are the wired entries today.

Catalog (planned)

The resolver's docstring documents the intended schema as the migration off require_role proceeds. These strings are reserved — implementations should use them rather than inventing new shapes.

PermissionScopeMeaning
org.adminorgFull org-level admin (already wired).
project.admin / project.edit / project.viewobject (project_id)Per-project authorization tier.
dashboard.view / dashboard.editobject (dashboard_id)Per-dashboard authorization tier (dashboard.edit already wired).
dataset.read / dataset.readwriteobject (dataset_id)Per-dataset authorization tier.
feature.<feature_name>orgGeneric capability gate. feature.agent_builder is the only one wired today.

Granting a permission

A row in honeyframe.group_permissions looks like:

INSERT INTO honeyframe.group_permissions (group_id, permission_type, target_id)
VALUES (
42, -- the group
'dashboard.edit', -- the permission string
'7' -- the dashboard_id this grant applies to
);

Set target_id to NULL to grant the permission organization-wide (any dashboard, any project, etc.). The resolver matches a specific target_id first and falls back to NULL grants.

Group memberships live in honeyframe.user_groups. A user inherits the union of all permissions across every group they belong to.

Resolution order

When require_permission("dashboard.edit", "dashboard_id") runs on a request to DELETE /api/dashboards/7:

  1. The dependency reads dashboard_id=7 from the path.
  2. user_has_permission(user, "dashboard.edit", "7") is called.
  3. Resolver returns true if any of:
    • user.is_superadmin
    • user.role == 'admin'
    • Some group the user belongs to has a row in group_permissions with permission_type='dashboard.edit' and (target_id='7' OR target_id IS NULL).
  4. Otherwise the request returns 403.

Migrating from require_role to require_permission

When converting an endpoint:

  1. Identify which roles previously had access (require_role("admin", "editor") → admins and editors).
  2. Pick a <resource>.<action> string from the catalog above. Use the planned schema if possible; only mint a new string if no existing one fits.
  3. Replace the dependency: require_role(...)require_permission("dataset.readwrite", "dataset_id").
  4. Seed the corresponding group_permissions rows for groups the legacy roles used to map to (adminorg.admin; resource roles → <resource>.<action> per the new strings).
  5. Keep require_role in place during the transition; the resolver's role == 'admin' shim means migrated endpoints still pass for legacy admins.