From de7e12bf1ea8cc6e6ababdcedbeb50e1ae3fc5b0 Mon Sep 17 00:00:00 2001 From: Krish Suchak Date: Wed, 12 Aug 2026 03:34:57 -0400 Subject: [PATCH 1/2] docs(policy): document dynamic value mappings --- .../policy/dynamic-value-mappings.mdx | 119 ++++++++++++++++++ docs/components/policy/index.md | 2 + 2 files changed, 121 insertions(+) create mode 100644 docs/components/policy/dynamic-value-mappings.mdx diff --git a/docs/components/policy/dynamic-value-mappings.mdx b/docs/components/policy/dynamic-value-mappings.mdx new file mode 100644 index 00000000..aa32c245 --- /dev/null +++ b/docs/components/policy/dynamic-value-mappings.mdx @@ -0,0 +1,119 @@ +# Dynamic Value Mappings + +Dynamic Value Mappings associate an [Attribute Definition](./attributes) with values from an [Entity Representation](../entity_resolution) and one or more [Actions](./actions). They are useful when Attribute Values come from an external system and change too frequently to manage individually in Policy. + +For example, a healthcare system may store patient assignments in its identity or directory service. A Dynamic Value Mapping can compare those assignments with the medical record number in a resource's Attribute Value. The platform can then determine the entity's entitlement without storing an Attribute Value and [Subject Mapping](./subject_mappings) for every medical record number. + +## Decision Flow + +At decision time, the platform extracts the value segment from the resource's Attribute Value FQN and compares it with the values returned by the mapping's entity selector. + +```mermaid +flowchart LR + Resource["Resource Attribute Value
.../value/mrn-123"] --> Segment["Value segment
mrn-123"] + Entity["Entity Representation
patientAssignments"] --> Selector["Selector
.patientAssignments[]"] + Segment --> Resolver{Resolver match} + Selector --> Resolver + Gate[Optional Subject Condition Set] --> Mapping{Mapping applies} + Resolver --> Mapping + Mapping --> Actions[Permitted Actions] +``` + +The mapping applies when the resolver matches and its optional Subject Condition Set passes. It grants the configured Actions for the requested Attribute Value. + +## Configuration + +Dynamic Value Mapping evaluation is disabled by default. Enable it in the Authorization service configuration: + +```yaml +services: + authorization: + allow_dynamic_value_mappings: true +``` + +When this setting is `false`, Dynamic Value Mappings do not contribute entitlements to authorization decisions. + +## Composition + +A Dynamic Value Mapping contains: + +1. An Attribute Definition with an `ANY_OF` or `ALL_OF` rule +2. A value resolver containing an entity selector and comparison operator +3. One or more permitted Actions +4. An optional Subject Condition Set that limits which entities can use the mapping +5. An optional Namespace +6. Optional metadata + +When a Namespace is specified, the Attribute Definition, Actions, and optional Subject Condition Set must use the same Namespace. When the mapping has no Namespace, its Actions and optional Subject Condition Set must also have no Namespace. + +## Resolver + +The value resolver selects one or more values from the flattened Entity Representation. Array selectors use the same syntax as Subject Mappings, such as `.patientAssignments[]`. + +The resolver supports these operators: + +| Operator | Behavior | +| --- | --- | +| `IN` | Matches when a selected entity value exactly equals the requested resource value segment. Matching is case-sensitive. | +| `IN_CONTAINS` | Matches when a selected entity value contains the requested resource value segment. Matching is case-sensitive. | + +`NOT_IN` is unavailable because the resolver checks whether any selected entity value matches the requested value segment. + +:::caution Substring Matching +`IN_CONTAINS` can match more values than intended. A resource value segment of `admin` matches selected values such as `superadmin` and `admin-readonly`. Use `IN` when the values must be equal. +::: + +## Evaluation + +Multiple Dynamic Value Mappings on the same Attribute Definition use OR behavior. A match from any mapping grants its configured Actions. + +When a mapping includes a Subject Condition Set, every Subject Set in the condition set must pass and the value resolver must match. The Subject Condition Set provides a static gate, such as requiring a `clinician` role before evaluating patient assignments. + +Resources can contain multiple values from one Attribute Definition. The platform evaluates each value through the Dynamic Value Mappings, then applies the Attribute Definition's normal `ANY_OF` or `ALL_OF` rule. + +## Constraints + +- Dynamic Value Mappings support Attribute Definitions with `ANY_OF` and `ALL_OF` rules. They do not support `HIERARCHY` rules. +- An Attribute Definition with a Dynamic Value Mapping cannot also have value-level Subject Mappings. +- Concrete Attribute Values can exist under the same Attribute Definition, but they cannot have Subject Mappings. +- Values under the Attribute Definition cannot be used as a Registered Resource's action Attribute Values. + +These checks apply regardless of which related mapping is created first. + +## Example + +Suppose the Entity Representation contains these patient assignments: + +```json +{ + "patientAssignments": ["mrn-123", "mrn-789"] +} +``` + +Create a Dynamic Value Mapping that permits `read` when an assignment matches the medical record number in the requested resource Attribute Value: + +```shell +otdfctl policy dynamic-value-mappings create \ + --attribute https://hospital.example/attr/mrn \ + --selector '.patientAssignments[]' \ + --operator IN \ + --action read +``` + +The mapping produces these results: + +| Resource Attribute Value | Value Segment | Result | +| --- | --- | --- | +| `https://hospital.example/attr/mrn/value/mrn-123` | `mrn-123` | The mapping grants `read` because `mrn-123` appears in `patientAssignments`. | +| `https://hospital.example/attr/mrn/value/mrn-456` | `mrn-456` | The mapping grants no entitlement because `mrn-456` does not appear in `patientAssignments`. | + +The requested values do not need to be stored as concrete Attribute Values in Policy. + +## Related Documentation + +- [Attributes](./attributes) +- [Subject Mappings](./subject_mappings) +- [Entity Resolution](../entity_resolution) +- [Actions](./actions) +- [Dynamic Value Mapping CLI](/components/cli/policy/dynamic-value-mappings) +- [Dynamic Value Mapping API](/OpenAPI-clients/policy/dynamicvaluemapping) diff --git a/docs/components/policy/index.md b/docs/components/policy/index.md index 6b538514..587b25bc 100644 --- a/docs/components/policy/index.md +++ b/docs/components/policy/index.md @@ -10,6 +10,7 @@ Policy is the all-encompassing name for configuration of cryptographically-bound graph LR; Data<-- Resource Mappings -->Attributes; Attributes<-- Subject Mappings -->Entities; + Attributes<-- Dynamic Value Mappings -->Entities; ``` TDF creation and decryption are driven by the Policy within a Platform instance and the TDF manifest. In other words, on a TDF decryption request, the platform services (KAS, Authorization) compare attributes on the TDF against the requester's entitlements to make a decision to release the key or not. @@ -23,6 +24,7 @@ Components of Policy include: - Actions (optionally scoped to Namespaces) - Subject Mappings (optionally scoped to Namespaces) - Subject Condition Sets (optionally scoped to Namespaces) +- [Dynamic Value Mappings](/components/policy/dynamic-value-mappings) (optionally scoped to Namespaces) - Registered Resources (scoped to Namespaces) - Resource Mappings - Key Access Grants (KAS Grants) From 8da918994a6cca61f0f19f778c44072bc0552f9f Mon Sep 17 00:00:00 2001 From: Krish Suchak Date: Wed, 12 Aug 2026 17:32:44 -0400 Subject: [PATCH 2/2] chore(docs): update vendored OpenAPI specs --- .../kasregistry/key_access_server_registry.openapi.yaml | 6 +++++- specs/policy/objects.openapi.yaml | 5 ++++- specs/policy/unsafe/unsafe.openapi.yaml | 5 ++++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/specs/policy/kasregistry/key_access_server_registry.openapi.yaml b/specs/policy/kasregistry/key_access_server_registry.openapi.yaml index b8c7b84c..9a28e2a5 100644 --- a/specs/policy/kasregistry/key_access_server_registry.openapi.yaml +++ b/specs/policy/kasregistry/key_access_server_registry.openapi.yaml @@ -561,7 +561,10 @@ components: - KEY_STATUS_UNSPECIFIED - KEY_STATUS_ACTIVE - KEY_STATUS_ROTATED - description: The status of the key + description: |- + The status of the key. + Adding a value here requires updating the key_status mapping in the listKeys + query so SORT_KAS_KEYS_TYPE_KEY_STATUS keeps sorting alphabetically. policy.SortDirection: type: string title: SortDirection @@ -593,6 +596,7 @@ components: - SORT_KAS_KEYS_TYPE_KEY_ID - SORT_KAS_KEYS_TYPE_CREATED_AT - SORT_KAS_KEYS_TYPE_UPDATED_AT + - SORT_KAS_KEYS_TYPE_KEY_STATUS policy.kasregistry.SortKeyAccessServersType: type: string title: SortKeyAccessServersType diff --git a/specs/policy/objects.openapi.yaml b/specs/policy/objects.openapi.yaml index 1b3a8729..301971bc 100644 --- a/specs/policy/objects.openapi.yaml +++ b/specs/policy/objects.openapi.yaml @@ -74,7 +74,10 @@ components: - KEY_STATUS_UNSPECIFIED - KEY_STATUS_ACTIVE - KEY_STATUS_ROTATED - description: The status of the key + description: |- + The status of the key. + Adding a value here requires updating the key_status mapping in the listKeys + query so SORT_KAS_KEYS_TYPE_KEY_STATUS keeps sorting alphabetically. policy.SourceType: type: string title: SourceType diff --git a/specs/policy/unsafe/unsafe.openapi.yaml b/specs/policy/unsafe/unsafe.openapi.yaml index 383144f9..c01ae4c2 100644 --- a/specs/policy/unsafe/unsafe.openapi.yaml +++ b/specs/policy/unsafe/unsafe.openapi.yaml @@ -475,7 +475,10 @@ components: - KEY_STATUS_UNSPECIFIED - KEY_STATUS_ACTIVE - KEY_STATUS_ROTATED - description: The status of the key + description: |- + The status of the key. + Adding a value here requires updating the key_status mapping in the listKeys + query so SORT_KAS_KEYS_TYPE_KEY_STATUS keeps sorting alphabetically. policy.SourceType: type: string title: SourceType