Sign in

    mooncedar

    A Cedar policy engine — parser, evaluator, and authorizer in MoonBit.

    Download zip
    Author
    Version
    0.1.5
    License
    Apache-2.0
    Last updated
    2 months ago
    Downloads
    45

    Dependencies

    #MoonCedar

    A Cedar policy engine implemented in MoonBit — parser, evaluator, and authorizer.

    #Installation

    moon add jaredzhou/mooncedar@0.1.2

    #Quick Start

    // 1. Parse a Cedar policy
    let policies = parse_policies(
    #|permit (principal == User::"alice", action == Action::"view", resource in Album::"jane_vacation");|
    )

    // 2. Build an entity store from JSON
    let entities_src =
    #|[{"uid":{"type":"User","id":"alice"},"attrs":{},"tags":{},"parents":[]},{"uid":{"type":"Photo","id":"VacationPhoto94.jpg"},"attrs":{},"tags":{},"parents":[{"type":"Album","id":"jane_vacation"}]}]
    let store : MapEntityStore = @json.from_json(@json.parse(entities_src))

    // 3. Create a request
    let req = Request::{
    principal: @evaluator.concrete_uid("User", "alice"),
    action: @evaluator.concrete_uid("Action", "view"),
    resource: @evaluator.concrete_uid("Photo", "VacationPhoto94.jpg"),
    context: Context::Concrete(Value::Record(Map([]))),
    }

    // 4. Authorize
    let result = is_authorized(req, policies.iter(), store)
    match result.decision {
    Decision::Allow => println("permitted")
    Decision::Deny => println("denied")
    }
    // => permitted

    For a complete, runnable example (multi-user todo app with Cedar policies, pony HTTP routing, and a CLI client), see moon-examples/todo.

    #Packages

    PackagePurpose
    jaredzhou/mooncedarUnified API: parse_policies, stringify, builder fns, type aliases (Expr, Policy, Request, Value, ...), MapEntityStore, is_authorized, evaluate, reauthorize
    jaredzhou/mooncedar/astCore AST: Expr, Policy, Entity, EntityUID, Value, PartialValue, Type + builder methods
    jaredzhou/mooncedar/evaluatorEntityStore trait, EvalError, Request, Context, EntityUIDEntry, helpers (concrete_uid, unknown_uid)
    jaredzhou/mooncedar/parserLexer, recursive descent parser, stringify

    Root types.mbt re-exports all commonly-used types as pub type aliases and builder functions, so most users only need import { "jaredzhou/mooncedar" }.

    #Features

    #Policy Language

    Full Cedar policy syntax support:

    • permit / forbid effects with annotations
    • Scope constraints (==, in, in [set], All)
    • when / unless conditions
    • Expression language: &&, ||, !, >, <, >=, <=, !=, like, in, contains, has, ., .tag
    • Records, sets, if-then-else, extension function calls

    #Expression Builder

    let e = expr_principal()
    .eq(expr_str("alice"))
    .and_(expr_resource()
    .has_tag(expr_str("confidential"))
    )

    #Policy Builder

    let policy = default_policy()
    .permit()
    .principal_eq("User", "alice")
    .action_eq("Action", "view")
    .resource_in("Album", "photos")
    .when_(expr_resource().has_tag(expr_str("public")))

    #Strategy Validation

    let errors = @ast.validate_policies(policies)

    #Entity Store (Pluggable)

    // In-memory store (built-in)
    let store = new_map_store()

    // From Cedar JSON
    let store : MapEntityStore = @json.from_json(@json.parse(entities_src))

    // Custom backend via pub(open) trait
    struct DbStore { conn : Connection }
    pub impl @evaluator.EntityStore for DbStore with get_entity(self, uid) {
    db_lookup(self.conn, uid)
    }

    Entity hierarchy: is_descendant uses BFS for ancestor traversal. Wildcard matching uses two-pointer backtracking.

    #Partial Evaluation

    Support for 4 sources of unknown during partial eval — policies evaluate to residual expressions that can be re-evaluated when more information is available:

    let answer = evaluate(req, policies.iter(), store1) // partial result
    let answer = answer.reauthorize(req, store2, mapping) // fill unknowns
    let result = answer.concretize() // final decision

    The reauthorize method accepts an expanded entity store and a Map[String, Value] mapping to resolve unknowns by name.

    #Request JSON

    // Strings: "Type::\"id\"" → Concrete, "Type" → Unknown
    // Objects: {"type":"...","id":"..."} → Concrete
    let src =
    #|{"principal":"User::\"alice\"","action":"Action::\"view\"","resource":"Photo::\"x\"","context":{}}|
    let dto : RequestJSON = @json.from_json(@json.parse(src))
    let req : Request = dto.to_request()
    let json = to_request_json(req).to_json()

    #Stringify (AST -> Cedar Source)

    let src = stringify(policies)

    #Status

    • 494 tests passing (parser, evaluator, authorizer, JSON, reauthorize, RequestJSON)
    • Entity stores as pluggable pub(open) traits
    • JSON serialization (Cedar-compatible format) for entities and requests
    • Policy validation
    • Full expression evaluation (18 expression variants, 12 binary + 3 unary operators)
    • Partial evaluation with reauthorize (4-category coverage)

    #License

    Apache-2.0

    Condition

    A when/unless condition in a policy.

    Context

    Request context: Concrete, Unknown, or Partial.

    Entity

    Application entity with attrs, tags, and parents.

    EntityType

    An entity type name.

    EntityUID

    A unique entity identifier, e.g. User::"alice".

    EntityUIDEntry

    A PARC slot: either a known EntityUID or a typed Unknown.

    EvalError

    Evaluation error.

    Expr

    A Cedar expression AST node.

    ParseError

    Parser error.

    PartialValue

    Partial evaluation result: concrete Value or residual Expr.

    Policy

    A Cedar policy.

    PolicyEffect

    Policy effect: Permit or Forbid.

    Request

    The PARC authorization request.

    ScopeConstraint

    A scope constraint on principal, action, or resource.

    Type

    The Cedar type system (Bool, Long, String, Set, Record, Entity, Extension).

    Value

    A runtime Cedar value.

    AuthorizationResult

    pub(all) struct AuthorizationResult {
    decision : Decision
    determining_policies : Array[DiagnosticReason]
    errors : Array[DiagnosticError]
    } derive(Eq,
    Debug
    )

    Full result of an authorization request.

    Decision

    pub(all) enum Decision {
    Allow
    Deny
    } derive(Eq,
    Debug
    )

    Authorization decision.

    DecisionRecord

    type DecisionRecord derive(Eq,
    Debug
    )

    Record of a single policy that fully satisfied (scope matched + all conditions passed).

    DiagnosticError

    pub(all) struct DiagnosticError {
    policy_id : String
    position :
    Position

    message : String
    } derive(Eq,
    Debug
    )

    Evaluation error for a specific policy.

    DiagnosticReason

    pub(all) struct DiagnosticReason {
    policy_id : String
    position :
    Position

    } derive(Eq,
    Debug
    )

    Diagnostic — identifying which policy caused a reason or error.

    MapEntityStore

    In-memory entity store backed by a Map.

    PartialAuthorizationAnswer

    pub(all) struct PartialAuthorizationAnswer {
    satisfied : Array[DecisionRecord]
    residuals : Array[ResidualRecord]
    errors : Array[DiagnosticError]
    } derive(
    Debug
    )

    Rich result of evaluating all policies against a Request. Holds satisfied decisions, residuals, and errors separately so downstream can concretize / reauthorize / sqlize as needed.

    PartialAuthorizationAnswer::concretize

    Concretize a PartialAuthorizationAnswer into a binary AuthorizationResult. Permit + no satisfied Forbid → Allow; otherwise → Deny. Residual policies are treated as non-determining (they don't count toward Allow).

    PartialAuthorizationAnswer::reauthorize

    Re-evaluate residual policies from a previous round, with an expanded entity store and/or a mapping from unknown names to concrete values.

    The mapping parameter handles expression-level Unknown("x") nodes (e.g., context values that were unknown in the first pass but are now known). The store parameter provides expanded entity hierarchy (e.g., after entity slicing fetched missing entities).

    Satisfied policies from the prior round are carried forward unconditionally.

    RequestJSON

    pub(all) struct RequestJSON {
    principal : String?
    action : String?
    resource : String?
    context :
    Value
    ?
    } derive(Eq,
    Debug
    )

    JSON-friendly representation of an authorization Request.

    { "principal":"GitApp::User::\"JaneDoe\"", "action":"Action::\"view\"", "resource":"Photo::\"x\"", "context":{"is_admin":true} }

    • PARC strings: "Type::"id"" → Concrete, "Type" → Unknown, null/absent → Unknown("")
    • PARC objects: {"type":"..","id":".."} → normalised to string
    • Context absent/empty-object/null → Unknown, otherwise → Concrete

    RequestJSON::to_request

    Convert a RequestJSON into a full Request.

    ResidualRecord

    type ResidualRecord derive(Eq,
    Debug
    )

    Record of a single policy where some conditions were residual (partial eval).

    bool

    Leaf constructors

    cedar_entity_from_json

    Parse a single Cedar entity from JSON. Handles the Cedar entity JSON format: { "uid": {"type":"...","id":"..."}, "attrs":{...}, "tags":{...}, "parents":[...] }

    cedar_value_from_json

    Parse a Cedar Value from JSON. Handles the Cedar-native entity JSON format:
    • plain numbers -> Long, strings -> String, booleans -> Bool
    • arrays -> Set
    • objects -> Record, EntityUID (__entity), or Extension (__extn)

    cedar_value_from_object

    Parse a Cedar Value from a JSON object: Record, EntityUID (__entity), or Extension (__extn).

    default_policy

    Create a default (permit-all) Policy struct.

    entity_type

    fn entity_type(name : String) ->
    EntityType

    Create an EntityType in builder context.

    entity_uid

    fn entity_uid(type_ : String, id : String) ->
    EntityUID

    Create an EntityUID in builder context.

    euid

    fn euid(type_ : String, id : String) ->
    Expr

    eval_expr

    Evaluate a Cedar expression with the given request and entity store. Returns Value(concrete) for fully-reducible expressions, or Residual(expr) for expressions that cannot be fully reduced (partial evaluation).

    evaluate

    Evaluate all policies against a Request. Returns a rich PartialAuthorizationAnswer that preserves residuals and errors for downstream processing.

    is_authorized

    Evaluate policies and produce a binary authorization decision. Convenience that composes evaluate() + concretize().

    long

    fn long(l : Int64) ->
    Expr

    new_map_store

    fn new_map_store() -> MapEntityStore

    Create an empty MapEntityStore.

    parse_policies

    Parse one or more Cedar policies from source text.

    principal

    resource

    str

    fn str(s : String) ->
    Expr

    stringify

    fn stringify(p :
    Policy
    ) -> String

    Convert a policy to Cedar source text.

    stringify_expr

    fn stringify_expr(e :
    Expr
    ) -> String

    Convert an expression to Cedar source text.

    to_request_json

    Convert a Request to its JSON-friendly representation.

    value_to_expr

    Convert a concrete Value back into an Expr AST node.