provider_principal_id_allowlist
Permit Read only when the requesting principal is on the provider’s
allowlist. The distinctive shape is the provider argument: Access::Allowed
is passed principal.id — an attribute path rooted at principal, not
context. Provider arguments are resolved pre-Cedar against the request
event; principal / resource roots resolve to the request scope entity, and
a trailing .id projects that entity’s id.
allowed.rhai stands in for a membership lookup with a one-name allowlist
(alice). Because the CLI loads providers.json via from_json (text only,
scriptFile is not resolved), the script is inlined into providers.json as
implementation.script; allowed.rhai is kept alongside for reference.
The trace has two decision points:
| tp | principal | allowed | verdict |
|---|---|---|---|
| 0 | alice | true | ALLOW |
| 1 | mallory | false | DENY |
Referenced by guide/05-information-providers.md.
Policy
// Permit Read only when the requesting principal is on the provider's
// allowlist. The distinctive shape is the ARGUMENT: the provider is passed
// principal.id -- an attribute path rooted at principal, not context. Provider
// arguments are resolved pre-Cedar against the request event, and principal /
// resource roots resolve to the request scope entity (trailing .id projects
// the entity's id).
@id("read_principal_allowed")
permit (
principal,
action == Drupe::Action::"Read",
resource
)
when {
Access::Allowed(principal.id).allowed == true
};
Schema
namespace Drupe {
type ReadInput = {
document: String
};
entity Gateway;
entity OAuthUser = {
id: String
} tags String;
action "Read" appliesTo {
principal: [OAuthUser],
resource: [Gateway],
context: {
input: ReadInput
}
};
}
Trace
@0 scope(principal: Drupe::OAuthUser::"alice", resource: Drupe::Gateway::"gw1") request_context(document: "hello") Drupe::Action::"Read"::request(callerPrincipal: Drupe::OAuthUser::"alice", callerResource: Drupe::Gateway::"gw1", document: "hello", requestId: "u1")
@10 scope(principal: Drupe::OAuthUser::"mallory", resource: Drupe::Gateway::"gw1") request_context(document: "hello") Drupe::Action::"Read"::request(callerPrincipal: Drupe::OAuthUser::"mallory", callerResource: Drupe::Gateway::"gw1", document: "hello", requestId: "u2")
Expected Output
@0 (time point 0): ALLOW [rules: 0]
@10 (time point 1): DENY
Provider Declarations
{
"availableProviders": {
"Access::Allowed": {
"argumentTypes": [
{
"paramType": "string"
}
],
"outputType": {
"paramType": "record",
"fields": {
"allowed": {
"paramType": "bool"
}
},
"required": [
"allowed"
]
},
"implementation": {
"kind": "rhai",
"script": "// Access::Allowed(principalId) -> { allowed: bool }. A tiny hardcoded\n// allowlist stands in for a real membership lookup: alice is allowed,\n// everyone else is not. Mirrors allowed.rhai (inlined because the CLI loads\n// providers.json via from_json, which does not resolve scriptFile).\nfn evaluate(principal_id) {\n // Defensive per the provider contract: a provider may be evaluated\n // for ANY decision event, so any argument may be absent (unit).\n // Return a conforming sentinel instead of erroring (errors are UB).\n if type_of(principal_id) == \"()\" {\n return #{ allowed: false };\n }\n\n let allowed = principal_id == \"alice\";\n #{ allowed: allowed }\n}\n"
}
}
}
}