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

Delegation

Your customer grants a bank application the right to pay on their behalf: the application may initiate payments up to 100.00 EUR, but the customer’s own standing grant allows 10,000.00 EUR and also covers cancellation. The effective permission the application holds is the intersection of both grants: what the application may do is bounded by both what the customer was granted and what the application was granted.

compute_intersection computes that intersection over lists of AuthorizationDetail.

Computing the intersection

#![allow(unused)]
fn main() {
use authz_details_rs::prelude::*;

let subject_allowed = vec![AuthorizationDetail::new(
    "payment_initiation",
    serde_json::json!({
        "actions": ["initiate", "cancel"],
        "instructedAmount": {"currency": "EUR", "amount": 10000.0}
    }),
)];

let actor_allowed = vec![AuthorizationDetail::new(
    "payment_initiation",
    serde_json::json!({
        "actions": ["initiate", "status"],
        "instructedAmount": {"currency": "EUR", "amount": 100.0}
    }),
)];

let registry: Vec<TypeSchema> = vec![];

let effective = compute_intersection(&subject_allowed, &actor_allowed, &registry).unwrap();

assert_eq!(effective.len(), 1);
assert_eq!(effective[0].type_id, "payment_initiation");
assert_eq!(
    effective[0].detail_object,
    serde_json::json!({
        "actions": ["initiate"],
        "instructedAmount": {"currency": "EUR", "amount": 100.0}
    })
);
}

For every subject/actor pair that shares a type, the pair’s detail objects are merged and one result entry is produced. A pair whose arrays share no elements or whose values conflict contributes nothing, without affecting the other pairs. If nothing intersects – no shared type, or only conflicting pairs – the result is an empty list: there is nothing to delegate.

With a TypeRegistry (see Schema Validation) the same operation is registry.intersect(&subject_allowed, &actor_allowed), using the comparators registered with each type.

Typed vs. structural merge

If the registry contains a schema for the type with a ComparatorConfig, the merge is typed: every registered rule merges its field:

  • NumericFieldRule paths take the minimum of the two values (MustMatch rules require equal values). RFC 9396 decimal strings are compared numerically and keep their original representation.
  • ArrayFieldRule paths take the intersection of the two arrays (MustMatch rules require the same set of elements).

A ruled field present on only one side keeps that side’s value. Every field covered by neither rule is merged structurally, exactly as below, so restrictions from both grants survive: an actor-only locations list is kept, and a currency that differs between the grants is a conflict. Rule paths must name a single field ($.a.b or $['a']['b']); anything else fails with InvalidMerge.

Without a comparator, the merge is structural:

KindRule
ObjectCommon fields merge recursively; fields present on only one side are kept
ArrayIntersection of the elements
NumberMinimum of the two
Identical primitivesPassed through
Anything elseRejected as a merge conflict

The structural merge above explains the example result: actions intersects to ["initiate"], currency matches exactly, and amount takes the minimum, 100.0.

Error handling

A disjoint delegation is a policy outcome, reported as an empty list. The only error is DelegationError::InvalidMerge(String): a comparator rule is malformed (a path that does not name a single field) or a ruled value has the wrong type (a non-numeric amount under a numeric rule), both of which indicate misconfigured grants.

#![allow(unused)]
fn main() {
use authz_details_rs::prelude::*;

let subject = [AuthorizationDetail::new("payment_initiation", serde_json::json!({"actions": ["initiate"]}))];
let search_agent = [AuthorizationDetail::new("web_search", serde_json::json!({"actions": ["query"]}))];

let delegated = compute_intersection(&subject, &search_agent, &[]).unwrap();
assert!(delegated.is_empty()); // refuse the delegation
}

Best practices

  • Use your TypeRegistry (registry.intersect) whenever the types have comparators; typed merges honor your CanReduce/CanNarrow intent instead of falling back to structural rules.
  • Treat an empty result as a delegation refusal, not an internal error – two parties can legally hold disjoint grants.
  • Audit the merged detail objects before storing them: they are the exact grant the actor may exercise on the subject’s behalf.

Next steps

For field-level restrictions on the grants themselves, see HBAC Integration; to enforce that a token re-request does not exceed a single grant, use Subset Comparison.