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 DrupeReadaction).providers.json— declaresStrings::Matches(string, string) -> { matched: Bool }.matches.rhai— the provider implementation (fn evaluate(text, pattern)).trace.log— threeReadrequests:ABC,abc,AB12.expected.out— the replay verdict stream.
The trace shows:
@0—Readof"ABC"(all uppercase) → ALLOW.@10—Readof"abc"(lowercase) → DENY.@20—Readof"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"
}
}
}
}