Resource-Level Policies
Fine-grained access control layered on top of role permissions — restrict which specific workflows, forms, or agents a user can access even if their role would otherwise allow it.
What Are Resource Policies?
Role permissions define broad capability grants — "managers can execute workflows". Resource policies narrow this to specific resources — "this manager can only execute the payroll workflow, not the HR onboarding workflow".
Resource policies are restrictive only — they cannot grant permissions that the user's role does not already have. They only add constraints on top of existing role grants.
Policy Model
// Resource policy definition
public class ResourcePolicy
{
public required string PolicyId { get; init; }
public required string ResourceType{ get; init; } // "workflow", "form", "agent", "report"
public required string ResourceId { get; init; } // the specific resource GUID
public required string TenantId { get; init; }
public required PolicyEffect Effect{ get; init; } // Allow or Deny
public required string Permission { get; init; } // permission being scoped
public required string[] SubjectIds{ get; init; } // user IDs or group IDs this applies to
public string? Condition { get; init; } // optional CEL expression
public DateTimeOffset? ExpiresAt { get; init; }
}
public enum PolicyEffect { Allow, Deny }
Creating Resource Policies
// Restrict a specific workflow to specific users
POST /passport/admin/resource-policies
Authorization: Bearer {admin-token}
Content-Type: application/json
{
"resourceType": "workflow",
"resourceId": "payroll-monthly-run-workflow-guid",
"tenantId": "tenant-abc",
"effect": "Allow",
"permission": "workflow.initiate",
"subjectIds": ["user-guid-1", "user-guid-2", "group-guid-finance"],
"description": "Only finance team can initiate the monthly payroll workflow"
}
// Deny access for a specific user even though their role would allow it
POST /passport/admin/resource-policies
Authorization: Bearer {admin-token}
Content-Type: application/json
{
"resourceType": "form",
"resourceId": "salary-adjustment-form-guid",
"tenantId": "tenant-abc",
"effect": "Deny",
"permission": "form.submit",
"subjectIds": ["user-guid-contractor"],
"description": "Contractors cannot submit salary adjustment forms"
}
Use Cases
| Resource Type | Example Policy | Rationale |
|---|---|---|
| Workflow | Allow only Finance group to initiate the payroll workflow | Sensitive workflows should be restricted even within the manager role |
| Form | Deny contractors from submitting salary forms | Role says "users can submit forms" but this specific form requires employee status |
| Agent | Allow only IT admins to interact with the infrastructure agent | Infrastructure automation should be restricted to IT administrators |
| Report | Allow Finance role to view the P&L report | Financial reports require explicit access beyond the general viewer permission |
Conditional Policies (CEL Expressions)
Resource policies support optional CEL (Common Expression Language) conditions for time-based or attribute-based restrictions:
// Time-based restriction — only allow access during business hours
{
"resourceType": "workflow",
"resourceId": "live-payment-workflow-guid",
"effect": "Allow",
"permission": "workflow.initiate",
"subjectIds": ["group-guid-payment-team"],
"condition": "timestamp.hours() >= 9 && timestamp.hours() < 17 && timestamp.dayOfWeek() >= 1 && timestamp.dayOfWeek() <= 5"
}
// Attribute-based — only users from a specific department
{
"resourceType": "form",
"resourceId": "payroll-correction-form-guid",
"effect": "Allow",
"permission": "form.submit",
"subjectIds": ["role:manager"],
"condition": "subject.department == 'Finance'"
}
Policy Precedence
When multiple resource policies match a check, the evaluation follows these precedence rules:
- Explicit DENY wins — any matching Deny policy immediately denies the request
- Explicit ALLOW required — if the resource has any Allow policies defined, the subject must match at least one
- No policy = role decision — if no resource policy applies, the role-based decision stands
A resource Allow policy on workflow.initiate for a user with the viewer role does NOT grant them workflow initiation. The user must also have the workflow.initiate permission from their role. Resource policies only restrict — they never expand — what roles already allow.