platform icon indicating copy to clipboard operation
platform copied to clipboard

ADR: Introduce new nestable/recursive condition structure for obligation fulfillments

Open jakedoublev opened this issue 6 months ago • 1 comments

Table of Contents

  1. Background
  2. Option 1: Use SubjectConditionSets again but make names only agnostic
  3. Option 2: Use ConditionSets with a flexible and potentially recursive/nestable structure

Background

Obligations Support issue and ADRs initial and secondary

To build out obligations as a concept within the platform, we must support a logical mechanism for admins to configure that dictates whether a Subject or Environment Entity has fulfilled an obligation.

This is relatively to the use of Subject Condition Sets in Subject Mappings to entitle Subject entities to data, but it is distinct because obligations can apply to Environment or Subject Entities, and our Subject Condition Set proto messages are specific to subject terminology.

It is known that Subject Condition Sets see ADR are one of the more challenging aspects of the platform to understand. They contain named associations like:

  • Subject Condition Sets (1 or more Subject Sets implicitly OR'd together)
  • Subject Sets (1 or more Subject Set implicitly AND'd together)
  • Condition Groups (1 or more Conditions + AND/OR logic)
  • Conditions (fieldSelector + IN/NOT_IN/CONTAINS logic + comparisonValues)
  • "SubjectMappingOperatorEnum" as IN/NOT_IN/CONTAINS

They have some downsides:

  1. challenging to understand names + logic
  2. implicit behaviors
  3. restrictive depth in structure

Since their introduction, we've also started categorizing entities by type as subject or environment (see ADR).

We have the opportunity when building out condition fulfillment for experimental obligations to:

  1. try an experimental logical construct
  2. reduce complexity
  3. enhance condition building flexibility
  4. reduce implicit behavior

If successful with positive customer/developer feedback, we could potentially deprecate SubjectConditionSets in favor of entity-type-agnostic generic ConditionSets.

Options

Option 1: Use SubjectConditionSets again but make names entity-type agnostic

We could employ the same structure as SubjectConditionSets but with entity type-agnostic naming (i.e. just leave off "subject")

  • :green_square: Good, because it meets the product need of a logical conditional matcher resolving varied entities to obligations
  • :green_square: Good, because admins used to SCS logic will have an easier time defining obligation fulfillments
  • :yellow_square: Neutral, because we have to write new resolution logic anyway due to strong typing in Go gencode and different struct names
  • :red_square: Bad, because if we want to change not the name of these condition associations but the functionality of how they work, we now have named protos with breaking changes in two places
  • :red_square: Bad, because we still have the challenges we experience with SCS level name concepts and implicit behaviors

Option 2: Use ConditionSets with a flexible and potentially recursive/nestable structure

In English, an admin defines a set of conditions, where a condition could be oneof:

  1. a set of conditions with an AND/OR relation
  2. a condition with IN/NOT_IN/CONTAINS logic between selected fields and a set of values (just like the Condition in an SCS)

This potentially recursive/nestable structure has no implicit behavior (only explicit) and is both easier to drive in a builder GUI and easier to understand without new names for the relations.

A resulting condition set could look like:

{
  "condition_set": {
    "boolean_operator": "AND",
    "conditions": [
      {
        "condition": {
          "external_selector_value": ".email",
          "operator": "CONTAINS",
          "external_values": ["example.com"]
        }
      },
      {
        "condition_set": {
          "boolean_operator": "OR",
          "conditions": [
            {
              "condition": {
                "external_selector_value": "role",
                "operator": "IN",
                "external_values": ["admin", "manager"]
              }
            },
            {
              "condition": {
                "external_selector_value": "department",
                "operator": "EQUALS",
                "external_values": ["engineering"]
              }
            }
          ]
        }
      }
    ]
  }
}

This is a pseudocode because we are not writing SQL, but I think structured query language is a great mental model and example for conditionally joining data with ANDs/ORs at unknown levels of depth.

SELECT *
FROM entity_representation
WHERE org = 'virtru'
  AND (
    role IN ('admin', 'manager')
    OR (
      department IN ('engineering')
      AND (
        time_zone IN ('EST')
        OR location IN ('remote')
      )
    )
  );
-- Note we could use '=' in a few places above but we only use IN/NOT_IN/CONTAINS in current conditions
-- so replicated that above for example
  • :green_square: Good, because it meets the product need of a logical conditional matcher resolving varied entities to obligations
  • :green_square: Good, because it's easier to understand and build for both admins and developers
  • :green_square: Good, because admins can explicitly define more complex sets of conditions with more obvious behavior
  • :green_square: Good, because we can test in experimental obligations and decide if we like it before deprecating SCS and migrating over
  • :yellow_square: Neutral, because we have to write new resolution logic anyway due to strong typing in Go gencode and different struct names
  • :yellow_square: Neutral, because we have to write new CLI CRUD for the subject-type agnostic condition fulfillments so it's required work anyway
  • :yellow_square: Neutral, because allowing users to define recursive structures that get evaluated in code is a potential security issue, but we can define a strict limit to depth and mitigate any threat
  • :red_square: Bad, because admins familiar with SCS already have one more thing to learn (even if it's an improvement and clearer)

Deprecation path for SubjectConditionSets if we eventually decided to utilize option 2 within Subject Mappings

[!NOTE]
This deprecation is not up for decision in this ADR, but is added as context for architectural planning

If we've received positive user feedback on working with this new structure for obligation fulfillments and we decide to deprecate SubjectConditionSets in favor of these, we could follow the following migration path:

  1. declare EOL for SubjectConditionSets terminology (docs, protos)
  2. allow ConditionSets to be stored instead of SubjectConditionSets for SubjectMappings
  3. add deprecation warnings to the CLI for SubjectConditionSets
  4. detect type within authorization service when making entitlement decisions
    1. authorization service would need to keep evaluating SubjectMappings the same way until EOL
    2. authorization service would need to support evaluating EntitlementMappings from now forward
  5. remove SubjectConditionSets and use these nestable structures

jakedoublev avatar Aug 07 '24 22:08 jakedoublev