Skip to main content

Policy Management API

Complete API reference for creating, managing, and inspecting authorization policies in the Taruvi Platform using Cerbos.

Introduction

The Taruvi Platform uses Cerbos for fine-grained, policy-based authorization. Cerbos provides a powerful policy engine that separates authorization logic from your application code, making it easier to manage, audit, and update access controls.

What is Cerbos?

Cerbos is an open-source authorization system that uses policies to define who can do what with which resources. It supports:

  • Attribute-Based Access Control (ABAC): Make decisions based on user attributes, resource attributes, and context
  • Role-Based Access Control (RBAC): Define permissions based on user roles
  • Derived Roles: Dynamic roles that change based on context (e.g., "owner" of a resource)
  • Policy Conditions: Fine-grained rules using expressions

Learn more: Cerbos Documentation

Taruvi's Enhancements

Taruvi extends Cerbos with several developer-friendly features:

  1. Automatic Principal Population: No need to manually build principal objects - Taruvi automatically extracts user ID, roles, and attributes from the request context
  2. Flexible Principal Resolution: Check permissions using username, email, or JWT token - not just principal objects
  3. Automatic Scope Injection: Multi-tenant isolation is handled automatically - all policies are scoped to {tenant_id}_{app_slug}
  4. System Policy Lifecycle: Policies are automatically created and deleted when resources (like DataTables) are created/deleted
  5. AppRole Integration: Roles are extracted from the Taruvi AppRole system (not Django Groups), providing app-scoped role management
  6. Query Plan Caching: Improved performance with tenant-scoped caching and stampede protection

System Policies

What are System Policies?

System policies are unrestricted policies automatically created by Taruvi when system resources (like DataTables) are created. They establish baseline permissions without custom rules.

System policies use a two-level approach:

  1. Default Level (scope=None): Global fallback policy

    • Resource policies: DENY all (fail-safe default)
    • Role policies: ALLOW all (permissive default)
    • Applied when no scoped policy exists
  2. Scoped Level (scope={tenant_id}_{app_slug}): Tenant-specific override

    • Resource policies: ALLOW all (permissive for tenant)
    • Role policies: ALLOW all
    • Takes precedence over default level

System Entity Types

The following entity types are protected system entities that have automatic policy lifecycle management:

Entity TypePurposeAuto-CreatedAuto-Deleted
datatableData table resources✅ On DataTable creation✅ On DataTable deletion
functionFunction resources✅ On Function creation✅ On Function deletion
storageStorage resources✅ On Storage creation✅ On Storage deletion
queryQuery resources✅ On Query creation✅ On Query deletion

Use Cases

System policies are used for:

  • Instant resource access: New resources get baseline permissions immediately
  • Fail-safe defaults: Global DENY ensures security by default
  • Tenant isolation: Scoped policies provide tenant-specific access
  • Lifecycle management: Policies are automatically cleaned up when resources are deleted

Learn more about Cerbos policy structure: Cerbos Policy Documentation


Auto-Policy Creation

Taruvi automatically creates and manages policies for system resources. This ensures that new resources have proper access controls from the moment they're created.


Key Features

1. Automatic Principal Population

Taruvi automatically builds the principal (user) object from the request context - no manual construction needed.

What gets auto-populated:

  • id: User ID from authenticated JWT token
  • roles: Extracted from AppRole system (NOT Django Groups) - app-scoped roles
  • attr.tenant_id: Current tenant schema name
  • attr.app_slug: Current app slug from URL
  • User attributes: Merged from User.attributes JSONField

Learn more: Cerbos Principal Concept

2. Flexible Principal Resolution

Check permissions using 4 different methods - no need to always provide a principal object.

Resolution Priority (first match wins):

  1. Full principal object - Explicit principal in request body
  2. Username parameter - ?username=john@example.com (DB lookup)
  3. Email parameter - ?email=john@example.com (DB lookup)
  4. Authenticated user - Use JWT token user (default)

Examples:

# Method 1: Explicit principal object
curl -X POST "/api/apps/crm/check/resources" \
-H "Authorization: Bearer TOKEN" \
-d '{
"principal": {"id": "user_123", "roles": ["admin"]},
"resources": [...]
}'

# Method 2: Username parameter (admin checking other user's permissions)
curl -X POST "/api/apps/crm/check/resources?username=john@example.com" \
-H "Authorization: Bearer ADMIN_TOKEN" \
-d '{"resources": [...]}'

# Method 3: Email parameter
curl -X POST "/api/apps/crm/check/resources?email=john@example.com" \
-H "Authorization: Bearer ADMIN_TOKEN" \
-d '{"resources": [...]}'

# Method 4: Authenticated user (simplest)
curl -X POST "/api/apps/crm/check/resources" \
-H "Authorization: Bearer TOKEN" \
-d '{"resources": [...]}'

Use cases:

  • Method 1: Custom principals with specific attributes
  • Method 2/3: Admin interfaces checking permissions for other users
  • Method 4: Standard permission checks for the authenticated user

3. Automatic Scope Injection

Multi-tenant isolation is handled automatically - you never need to manually manage scopes.

What Taruvi does automatically:

  • All policies are scoped to {tenant_id}_{app_slug} (e.g., public_crm)
  • All resources are scoped during authorization checks
  • Derived roles are prefixed with scope

Benefits:

  • Complete tenant isolation - no cross-tenant data leaks
  • No manual scope management
  • Derived role prefixing for isolation (e.g., public_crm_common_roles)

Learn more: Cerbos Scoped Policies

4. AppRole Integration

Roles are extracted from the Taruvi AppRole system, NOT Django Groups.

Key differences:

FeatureDjango GroupsTaruvi AppRole
ScopeGlobal (per user)App-specific
Modeldjango.contrib.auth.Groupcloud_site.roles.AppRole
RelationshipUser → GroupUser → App → Role
UsageTraditional Django permissionsCerbos authorization

5. Query Plan Caching

Taruvi caches query plans for improved performance with tenant-scoped isolation.

Features:

  • Tenant-scoped cache keys: Prevents cross-tenant cache poisoning
  • Stampede protection: Distributed locks prevent cache stampede
  • 5-minute TTL: Configurable cache timeout
  • Automatic invalidation: Cache cleared on policy updates

Authentication

All endpoints require JWT authentication:

Authorization: Bearer <your_jwt_token>

Base URLs

Standard (tenant from connection context):

/api/apps/{app_slug}/policies/

Site-scoped (explicit tenant override):

/sites/{schema_name}/api/apps/{app_slug}/policies/
Site-Scoped URLs

Site-scoped URLs allow you to explicitly specify the tenant context via the URL path. The SiteSwitchingMiddleware handles tenant switching based on the {schema_name} parameter. This is useful for:

  • Cross-tenant operations (with proper permissions)
  • Admin interfaces managing multiple tenants
  • API clients that need explicit tenant control

Policy Management API

1. Create Policy

Create a new authorization policy.

POST /api/apps/{app_slug}/policies/
POST /sites/{schema_name}/api/apps/{app_slug}/policies/
System Entity Types

POST endpoint rejects system entity types (datatable, function, storage, query). Use PUT endpoint for system types or wait for auto-creation.

Request Body - Resource Policy

{
"policy_type": "resource",
"name": "sales_invoices",
"entity_type": "invoice",
"import_derived_roles": ["common_roles"],
"rules": [
{
"actions": ["read", "update"],
"effect": "EFFECT_ALLOW",
"roles": ["admin", "manager"]
},
{
"actions": ["read", "update"],
"effect": "EFFECT_ALLOW",
"derived_roles": ["owner"],
"condition": {
"match": {
"expr": "R.attr.status != 'archived'"
}
}
}
],
"metadata": {
"description": "Sales invoices access policy",
"tags": ["finance", "sales-team"]
}
}

Request Body - Derived Role Policy

{
"policy_type": "derived_role",
"name": "common_roles",
"definitions": [
{
"name": "owner",
"parentRoles": ["user"],
"condition": {
"match": {
"expr": "R.attr.owner_id == P.id"
}
}
},
{
"name": "manager",
"parentRoles": ["owner"],
"condition": {
"match": {
"expr": "P.attr.role == 'manager'"
}
}
}
]
}
Derived Role Naming

Derived role names are automatically prefixed with {tenant_id}_{app_slug}_ for tenant isolation. When referencing them in import_derived_roles, use the unprefixed name (e.g., "common_roles" not "public_crm_common_roles").

Learn more: Cerbos Derived Roles

Reserved Metadata Fields

The following metadata fields are automatically injected and cannot be set manually:

FieldTypeDescription
created_bystringUser ID who created the policy
created_datestring (ISO 8601)Policy creation timestamp
modified_bystringUser ID who last modified
modified_datestring (ISO 8601)Last modification timestamp

Success Response (201 Created)

{
"success": true,
"message": "Policy created successfully",
"status_code": 201
}

cURL Example

curl -X POST "http://localhost:8000/api/apps/crm/policies/" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"policy_type": "resource",
"name": "sales_invoices",
"entity_type": "invoice",
"rules": [
{
"actions": ["read", "update"],
"effect": "EFFECT_ALLOW",
"roles": ["admin"]
}
]
}'

2. Update Policy

Update an existing authorization policy. Use this endpoint for system entity types.

PUT /api/apps/{app_slug}/policies/
PUT /sites/{schema_name}/api/apps/{app_slug}/policies/
POST vs PUT
  • POST: Stricter validation - rejects role policies and system entity types
  • PUT: Relaxed validation - allows role policies and system entity types

Both are idempotent and call the same underlying service.

cURL Example

curl -X PUT "http://localhost:8000/api/apps/crm/policies/" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"policy_type": "resource",
"entity_type": "datatable",
"name": "users",
"rules": [
{
"actions": ["*"],
"effect": "EFFECT_ALLOW",
"roles": ["admin"]
}
]
}'

3. List Policies

Retrieve all policies for the current app and tenant.

GET /api/apps/{app_slug}/policies/
GET /sites/{schema_name}/api/apps/{app_slug}/policies/

Query Parameters

ParameterTypeDefaultDescription
name_regexpstring.*Regex filter for policy names
scope_regexpstringAuto-filteredRegex filter for scope
version_regexpstringNoneRegex filter for version
include_disabledbooleanfalseInclude disabled policies

Success Response (200 OK)

{
"success": true,
"message": "Policies retrieved successfully",
"status_code": 200,
"data": [
{
"apiVersion": "api.cerbos.dev/v1",
"resourcePolicy": {
"resource": "invoice:sales_invoices",
"version": "default",
"scope": "public_crm",
"rules": [...]
}
}
],
"total": 1
}

cURL Example

curl -X GET "http://localhost:8000/api/apps/crm/policies/?name_regexp=invoice" \
-H "Authorization: Bearer YOUR_JWT_TOKEN"

4. Retrieve Policy

Get a specific policy by its full Cerbos policy ID.

GET /api/apps/{app_slug}/policies/?id={policy_id}
GET /sites/{schema_name}/api/apps/{app_slug}/policies/?id={policy_id}

Policy ID Formats

Policy TypeFormat
Resourceresource.{entity_type}_{name}.{version}/{scope}
Rolerole.{name}/{scope}
Principalprincipal.{name}.{version}/{scope}
Derived Rolederived_roles.{tenant_id}_{app_slug}_{name}

cURL Example

curl -X GET "http://localhost:8000/api/apps/crm/policies/?id=resource.invoice_sales_invoices.default/public_crm" \
-H "Authorization: Bearer YOUR_JWT_TOKEN"

5. Delete Policy

Soft-delete a policy by its full Cerbos policy ID.

DELETE /api/apps/{app_slug}/policies/?id={policy_id}
DELETE /sites/{schema_name}/api/apps/{app_slug}/policies/?id={policy_id}
Soft Delete

Cerbos uses soft delete - policies are disabled for audit trail.

cURL Example

curl -X DELETE "http://localhost:8000/api/apps/crm/policies/?id=resource.invoice_sales_invoices.default/public_crm" \
-H "Authorization: Bearer YOUR_JWT_TOKEN"

6. List Derived Roles

List derived role policies for the current tenant and app.

GET /api/apps/{app_slug}/policies/derived-roles/
GET /sites/{schema_name}/api/apps/{app_slug}/policies/derived-roles/

Query Parameters

ParameterTypeDefaultDescription
name_regexpstring.*Regex filter for role names
include_disabledbooleanfalseInclude disabled policies

cURL Example

curl -X GET "http://localhost:8000/api/apps/crm/policies/derived-roles/" \
-H "Authorization: Bearer YOUR_JWT_TOKEN"

7. Inspect Policies

Retrieve detailed metadata including actions, variables, attributes, derived roles, and constants.

GET /api/apps/{app_slug}/policies/inspect/
GET /sites/{schema_name}/api/apps/{app_slug}/policies/inspect/

Query Parameters

ParameterTypeDescription
policy_idstring (repeatable)Specific policy IDs
name_regexpstringRegex filter for names
scope_regexpstringRegex filter for scope
version_regexpstringRegex filter for version
include_disabledbooleanInclude disabled policies

Success Response (200 OK)

{
"success": true,
"message": "Policies inspected successfully",
"status_code": 200,
"data": {
"results": {
"resource.invoice_sales_invoices.default/public_crm": {
"policyId": "resource.invoice_sales_invoices.default/public_crm",
"actions": ["read", "create", "update", "delete"],
"derivedRoles": [
{"name": "owner", "kind": "IMPORTED", "source": "common_roles"}
],
"variables": [
{"name": "is_owner", "kind": "LOCAL", "value": "R.attr.owner_id == P.id"}
]
}
}
}
}

cURL Example

curl -X GET "http://localhost:8000/api/apps/crm/policies/inspect/?name_regexp=invoice" \
-H "Authorization: Bearer YOUR_JWT_TOKEN"

Authorization Check API

1. Check Resources

Check if a principal has permissions to perform actions on resources.

POST /api/apps/{app_slug}/check/resources
POST /sites/{schema_name}/api/apps/{app_slug}/check/resources

Principal Resolution

The check endpoint supports 4 ways to specify the principal (see Flexible Principal Resolution):

  1. Full principal object - Explicit principal in request body
  2. Username parameter - ?username=john@example.com
  3. Email parameter - ?email=john@example.com
  4. Authenticated user - JWT token user (default)

Request Body

{
"principal": {
"id": "user_123",
"roles": ["admin"],
"attr": {"department": "sales"}
},
"resources": [
{
"resource": {
"kind": "invoice:sales_invoices",
"id": "inv_001",
"attr": {"owner_id": "user_456"}
},
"actions": ["read", "update", "delete"]
}
]
}

Response

{
"requestId": "test",
"results": [
{
"resource": {
"id": "inv_001",
"kind": "invoice:sales_invoices",
"policyVersion": "default",
"scope": "public_crm"
},
"actions": {
"read": "EFFECT_ALLOW",
"update": "EFFECT_ALLOW",
"delete": "EFFECT_DENY"
},
"meta": {
"effectiveDerivedRoles": ["owner"]
}
}
]
}

cURL Examples

Using authenticated user:

curl -X POST "http://localhost:8000/api/apps/crm/check/resources" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"resources": [
{
"resource": {"kind": "invoice:sales_invoices", "id": "inv_001"},
"actions": ["read", "update"]
}
]
}'

Using username parameter:

curl -X POST "http://localhost:8000/api/apps/crm/check/resources?username=john@example.com" \
-H "Authorization: Bearer ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"resources": [
{
"resource": {"kind": "invoice:sales_invoices", "id": "inv_001"},
"actions": ["read", "update"]
}
]
}'

Learn more: Cerbos Check Resources


2. Plan Resources

Get a query plan for row-level filtering. Returns filter conditions that can be applied to database queries.

POST /api/apps/{app_slug}/plan/resources
POST /sites/{schema_name}/api/apps/{app_slug}/plan/resources

Principal Resolution

Supports the same 4 principal resolution methods as Check Resources.

Request Body

{
"principal": {
"id": "user_123",
"roles": ["viewer"],
"attr": {"department": "sales"}
},
"resource": {
"kind": "invoice:sales_invoices",
"policy_version": "default"
},
"action": "read"
}

Response

{
"filter_kind": "CONDITIONAL",
"condition": {
"expression": {
"operator": "eq",
"operands": [
{"variable": "request.resource.attr.owner_id"},
{"value": "user_123"}
]
}
}
}

Filter Kinds:

  • ALWAYS_ALLOWED: No filtering needed, user has full access
  • CONDITIONAL: Apply the returned condition to filter results
  • ALWAYS_DENIED: User has no access

cURL Example

curl -X POST "http://localhost:8000/api/apps/crm/plan/resources" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"resource": {"kind": "invoice:sales_invoices"},
"action": "read"
}'

Learn more: Cerbos Query Plans


3. AuthZEN Access Evaluation

Single access evaluation using the AuthZEN standard format.

POST /api/apps/{app_slug}/access/v1/evaluation
POST /sites/{schema_name}/api/apps/{app_slug}/access/v1/evaluation

Request Body

{
"subject": {
"type": "user",
"id": "user_123",
"properties": {
"cerbos.roles": ["admin"],
"department": "sales"
}
},
"resource": {
"type": "invoice:sales_invoices",
"id": "inv_001",
"properties": {
"owner_id": "user_456"
}
},
"action": {
"name": "read"
}
}

Response

{
"decision": true,
"context": {
"id": "01HHENANTHFD5DV3HZGDKB87PJ",
"reason_admin": {
"effectiveDerivedRoles": ["owner"]
}
}
}

Learn more: Cerbos AuthZEN API


4. AuthZEN Batch Evaluation

Batch access evaluation for multiple actions/resources.

POST /api/apps/{app_slug}/access/v1/evaluations
POST /sites/{schema_name}/api/apps/{app_slug}/access/v1/evaluations

Request Body

{
"subject": {
"type": "user",
"id": "user_123",
"properties": {
"cerbos.roles": ["admin"]
}
},
"resource": {
"type": "invoice:sales_invoices",
"id": "inv_001"
},
"evaluations": [
{"action": {"name": "read"}},
{"action": {"name": "update"}},
{
"resource": {"type": "invoice:sales_invoices", "id": "inv_002"},
"action": {"name": "delete"}
}
]
}

Response

{
"evaluations": [
{"decision": true, "context": {...}},
{"decision": true, "context": {...}},
{"decision": false, "context": {...}}
]
}

5. System Actions

Get available actions for system entity types.

GET /api/apps/{app_slug}/authorization/system-action/

Response

{
"success": true,
"data": {
"datatable": ["create", "read", "update", "delete", "materialize"],
"function": ["create", "read", "update", "delete", "execute"],
"storage": ["create", "read", "update", "delete", "upload", "download"],
"query": ["create", "read", "update", "delete", "execute"]
}
}

Error Responses

Standard Error Format

{
"success": false,
"message": "Error description",
"status_code": 400,
"errors": {"detail": "Detailed error message"}
}

HTTP Status Codes

CodeDescription
200Success (GET, DELETE)
201Created (POST)
400Bad Request (validation error)
401Unauthorized (missing/invalid JWT)
403Forbidden (permission denied)
404Not Found (policy doesn't exist)
500Internal Server Error

Best Practices

1. Use Meaningful Policy Names

{
"name": "sales_invoices",
"entity_type": "invoice"
}

2. Use Specific Actions

{"actions": ["read", "update"]}  // ✅ Good
{"actions": ["*"]} // ❌ Avoid in production

3. Leverage Derived Roles

{
"import_derived_roles": ["ownership_roles"],
"rules": [
{"actions": ["read", "update"], "derived_roles": ["owner"]}
]
}

4. Use Conditions for Fine-Grained Control

{
"condition": {
"match": {
"expr": "R.attr.department == P.attr.department && R.attr.status != 'archived'"
}
}
}

5. Use Inspection for Debugging

GET /api/apps/crm/policies/inspect/?name_regexp=invoice

Learn more: Cerbos Best Practices


Authorization Checks (SDK)

The Policy SDK provides methods for checking user permissions on resources. Unlike the REST API endpoints above (which manage policy definitions), these SDK methods evaluate authorization at runtime.

Check Permissions for Resources

# Check permissions for multiple resources
result = auth_client.policy.check_resources([
{
"resource": {
"kind": "datatable",
"id": "orders"
},
"actions": ["read", "write"]
},
{
"resource": {
"kind": "datatable",
"id": "invoices"
},
"actions": ["read", "delete"]
}
])

# Check results
for res in result["results"]:
resource_id = res["resource"]["id"]
for action, effect in res["actions"].items():
allowed = effect == "EFFECT_ALLOW"
print(f"{resource_id}.{action}: {allowed}")

# Check if specific action is allowed
can_write_orders = result["results"][0]["actions"]["write"] == "EFFECT_ALLOW"
print(f"Can write orders: {can_write_orders}")

Filter Allowed Resources

# Get all datatables
all_tables = [
{'kind': 'datatable', 'id': 'users'},
{'kind': 'datatable', 'id': 'orders'},
{'kind': 'datatable', 'id': 'invoices'},
{'kind': 'datatable', 'id': 'admin_logs'}
]

# Filter to only tables where user can read AND write
allowed_tables = auth_client.policy.filter_allowed(
resources=all_tables,
actions=['read', 'write']
)

print(f"User can read+write {len(allowed_tables)} tables:")
for table in allowed_tables:
print(f" - {table['id']}")

Get Allowed Actions

# Get allowed actions for a specific resource
allowed_actions = auth_client.policy.get_allowed_actions(
resource={'kind': 'datatable', 'id': 'users'},
actions=['read', 'write', 'create', 'update', 'delete']
)

print(f"Allowed actions on 'users': {allowed_actions}")
# Output: ['read', 'write', 'update']

# Check if user has specific permission
can_delete = 'delete' in allowed_actions
print(f"Can delete users: {can_delete}")

# Use default CRUD actions if not specified
all_allowed = auth_client.policy.get_allowed_actions(
resource={'kind': 'datatable', 'id': 'orders'}
# Defaults to: ['read', 'write', 'create', 'update', 'delete']
)

print(f"Allowed CRUD actions: {all_allowed}")

See Also

Additional Resources