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_regex_matches_uppercase

The canonical worked information-provider example: permit Read only when the requested document is all-uppercase letters (matches ^[A-Z]+$), as decided by the Strings::Matches information provider (a regex matcher implemented in matches.rhai). The provider call sits in an ordinary when { ... }; the projection (.matched) and comparison (== true) are plain Cedar.

Files:

  • policy.dw — the policy.
  • schema.cedarschema — the base Cedar schema (minimal Drupe Read action).
  • providers.json — declares Strings::Matches(string, string) -> { matched: Bool }.
  • matches.rhai — the provider implementation (fn evaluate(text, pattern)).
  • trace.log — three Read requests: ABC, abc, AB12.
  • expected.out — the replay verdict stream.

The trace shows:

  • @0Read of "ABC" (all uppercase) → ALLOW.
  • @10Read of "abc" (lowercase) → DENY.
  • @20Read of "AB12" (digits) → DENY.

Running it

dogwood validate policy.dw --policy-schema schema.cedarschema --providers providers.json
dogwood replay   policy.dw --policy-schema schema.cedarschema --providers providers.json --trace trace.log

Note on providers.json: the CLI’s --providers flag parses the declarations with from_json, which does not resolve a scriptFile reference. So for the CLI path the Rhai body is inlined under implementation.script; the equivalent matches.rhai file is kept alongside for reference (and validation works either way).

Referenced by guide/05-information-providers.md.

Policy

// Permit Read only when the requested document is all-uppercase letters,
// as decided by the Strings::Matches information provider (a regex matcher,
// implemented in matches.rhai). The provider call sits in an ordinary
// when { ... }; the projection (.matched) and comparison (== true) are
// plain Cedar.
@id("read_uppercase_only")
permit (
    principal,
    action == Drupe::Action::"Read",
    resource
)
when {
    Strings::Matches(context.input.document, "^[A-Z]+$").matched == 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(input: { document: "ABC" }) Drupe::Action::"Read"::request(input: { document: "ABC" }, callerPrincipal: Drupe::OAuthUser::"alice", callerResource: Drupe::Gateway::"gw1", requestId: "u1")
@10 scope(principal: Drupe::OAuthUser::"alice", resource: Drupe::Gateway::"gw1") request_context(input: { document: "abc" }) Drupe::Action::"Read"::request(input: { document: "abc" }, callerPrincipal: Drupe::OAuthUser::"alice", callerResource: Drupe::Gateway::"gw1", requestId: "u2")
@20 scope(principal: Drupe::OAuthUser::"alice", resource: Drupe::Gateway::"gw1") request_context(input: { document: "AB12" }) Drupe::Action::"Read"::request(input: { document: "AB12" }, callerPrincipal: Drupe::OAuthUser::"alice", callerResource: Drupe::Gateway::"gw1", requestId: "u3")

Expected Output

@0 (time point 0): ALLOW  [rules: 0]
@10 (time point 1): DENY
@20 (time point 2): DENY

Provider Declarations

{
  "availableProviders": {
    "Strings::Matches": {
      "argumentTypes": [
        {
          "paramType": "string"
        },
        {
          "paramType": "string"
        }
      ],
      "outputType": {
        "paramType": "record",
        "fields": {
          "matched": {
            "paramType": "bool"
          }
        },
        "required": [
          "matched"
        ]
      },
      "implementation": {
        "kind": "rhai",
        "script": "// Information-provider implementation for `Strings::Matches`.\n//\n// A provider script defines `fn evaluate(arg0, arg1, \u2026)` whose parameters\n// correspond positionally to the declared `argumentTypes` in\n// providers.json \u2014 here `(text, pattern)`. It returns a record whose\n// shape matches the declared `outputType` (`{ matched: Bool }`).\n//\n// The engine runs this in a locked-down Rhai engine: no file/network/\n// process access, only the host functions Dogwood registers. Here we use\n// `regex_is_match(pattern, text) -> bool`.\nfn evaluate(text, pattern) {\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(text) == \"()\" || type_of(pattern) == \"()\" {\n        return #{ matched: false };\n    }\n\n    #{ matched: regex_is_match(pattern, text) }\n}\n"
      }
    }
  }
}