Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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:

tpprincipalallowedverdict
0alicetrueALLOW
1malloryfalseDENY

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"
      }
    }
  }
}