Skip to content

[PM-35785] [PM-35787] Add strongly typed data and aggregation for policies - #1219

Draft
eliykat wants to merge 11 commits into
mainfrom
ac/pm-35785/add-strongly-typed-policy-data-handling
Draft

[PM-35785] [PM-35787] Add strongly typed data and aggregation for policies#1219
eliykat wants to merge 11 commits into
mainfrom
ac/pm-35785/add-strongly-typed-policy-data-handling

Conversation

@eliykat

@eliykat eliykat commented Jun 27, 2026

Copy link
Copy Markdown
Member

🎟️ Tracking

https://bitwarden.atlassian.net/browse/PM-35785
https://bitwarden.atlassian.net/browse/PM-35787

📔 Objective

#964 and associated PRs added basic policy enforcement to the SDK: given a list of policies, it would tell you which should be enforced against the user.

This PR adds additional functionality to the domain:

  • strongly typed data handling, so that consumers can access the policyView.data JSON blob without deserializing it themselves
  • policy aggregation, so that consumers can understand how multiple policies must be combined and enforced against the user. (e.g. if a user is part of multiple organizations that all have the master password policy enabled, their MP must satisfy all policies simultaneously).

Overview of changes:

  • new mod called enforcement to house all enforcement logic
  • filter.rs has been largely moved to enforcement::Policy because it defines the Policy trait
  • data.rs adds trait and logic for strongly typed data handling
  • aggregate.rs adds trait and logic for policy aggregation

TODOs:

  • this PR turned out to be quite large (~1k LOC) but I wanted to illustrate how everything interacted. If desired I can break it up per the separate Jira tickets (data.rs in one PR, aggregate.rs in the next)
  • should PolicyFilter.enforced panic, or return results?
  • the big one: how to return the Data types across the FFI boundary? I think we need a PolicyType enum that is used to wrap data; but we already have one that is a plain discriminator for raw policy structs, and I'm not sure how that interacts. Do we just have to have 2 separate enum definitions: one without strongly typed data and one with?

🚨 Breaking Changes

@eliykat
eliykat requested a review from quexten June 27, 2026 06:00
@sonarqubecloud

Copy link
Copy Markdown

@github-actions

github-actions Bot commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

🔍 SDK Breaking Change Detection

SDK Version: ac/pm-35785/add-strongly-typed-policy-data-handling (bed1149)

⚠️ If breaking changes are detected, a corresponding pull request addressing them must be ready for merge in the affected client repository.

Client Status Details
typescript ✅ No breaking changes detected Compilation passed with new SDK version - View Details
android ✅ No breaking changes detected Compilation passed with new SDK version - View Details

Breaking change detection uses the build of the SDK from this branch, including any incompatibities pre-existing on or merged into this branch. Check the workflow logs to confirm.
Results update as workflows complete.

@codecov

codecov Bot commented Jun 27, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.39516% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 85.19%. Comparing base (e58d732) to head (bed1149).

Files with missing lines Patch % Lines
...rates/bitwarden-policies/src/enforcement/policy.rs 98.28% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1219      +/-   ##
==========================================
+ Coverage   85.12%   85.19%   +0.07%     
==========================================
  Files         466      469       +3     
  Lines       64327    64636     +309     
==========================================
+ Hits        54759    55068     +309     
  Misses       9568     9568              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment on lines +34 to +45
/// Opt-in extension for policies whose data can be combined across
/// organizations.
///
/// Implementing this trait unlocks
/// [`PolicyAggregateFilter::get_enforced_aggregate_policy`] for the policy.
/// [`NoData`] policies get a trivial implementation for free.
pub trait PolicyAggregate: PolicyData {
/// Combines multiple [`Data`](PolicyData::Data) values into a single value.
/// How those values are combined is determined by the policy. Will only be
/// called with a non-empty `items`.
fn aggregate(&self, items: Vec<Self::Data>) -> Self::Data;
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be implemented by the Policy definition, or by the Self::Data type? It relates more to the Data type than the policy generally.

Alternative example:

pub trait FoldableData {
    fn fold_closure(result: Self, next: Self) -> Self;
}

/// if you want aggregate functionality, you implement this _instead of_ `PolicyData` (rather than in addition to)
pub trait AggregatePolicy {
    type Data: Default + DeserializeOwned + FoldableData;
}

And then fold_closure can be called in the iterator chain which is nice:

            filtered
                .iter()
                .map(|p| self.get_data_or_default(p))
                .fold(Self::Data::default(), |result, d| result.fold_closure(d))

I'm not sure if this is better or just different. It may be less intuitive for the policy author to implement.

/// `view.organization_id`. Both invariants should be guaranteed by
/// upstream filtering and lookup.
///
/// # Panics

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On panics:

  • If it is a public API, avoid panics.
  • If it is a non-public API, you MAY panic, if you control all callsites
    • ⚠️ The farther the invariant enforcing code is from the code that relies on the invariant and panics, the greater the risk. Usually you don't want to have more than a handful of callsites or LoC between.

Currently it seems rather easy to get this to panic, so I'd re-design this so that it cannot happen.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants