-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[PM-14439] Add PolicyRequirements for enforcement logic #5336
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Point of interest: this sproc only returns policies where they're enabled and the organization's plan supports them. This is a fundamental requirement of the policy domain so it's not left up to the policy requirements to decide. (Of course if this changed in the future we could lift it up.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is an intentional split of responsibilities here: all business logic is in the policy requirements, which are written in a functional style. The query is agnostic about what policies are being handled - its only job is to connect policy requirements to dependencies.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Point of interest: we have a separate policy type for "Disable Send" and "Send Options". In practice we always need to check both, so we can encapsulate them in the same requirement.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Point of interest: an example of a more complex requirement that can encapsulate additional logic (beyond enabled/disabled) that is currently in the caller (in this case, OrganizationService
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Simple example usage by an outside caller.
_policyRequirements.Add(SendPolicyRequirement.Create); | ||
_policyRequirements.Add(up | ||
=> SsoPolicyRequirement.Create(up, globalSettings.Sso)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Example of how you can just register the factory function directly for simple requirements (which will be most of them), or inject additional values if needed (settings, feature flags, etc).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just an idea but maybe we could use Reflection here to find all classes that inherit IPolicyRequirement
and automatically register them. Maybe it adds some unnecessary complexity though.
Great job, no security vulnerabilities found in this Pull Request |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is looking very good! Please add some xmldoc to the interfaces
|
||
public class SendPolicyRequirement : IPolicyRequirement | ||
{ | ||
public bool DisableSend { get; init; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
public bool DisableSend { get; init; } | |
public bool DisableSend { get; private init; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is great!
|
||
public static IEnumerable<OrganizationUserPolicyDetails> ExcludeOwnersAndAdmins( | ||
this IEnumerable<OrganizationUserPolicyDetails> userPolicyDetails) => | ||
userPolicyDetails.Where(x => x.OrganizationUserType != OrganizationUserType.Owner); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
userPolicyDetails.Where(x => x.OrganizationUserType != OrganizationUserType.Owner); | |
userPolicyDetails.Where(x => x.OrganizationUserType is not OrganizationUserType.Owner and not OrganizationUserType.Admin); |
_policyRequirements.Add(SendPolicyRequirement.Create); | ||
_policyRequirements.Add(up | ||
=> SsoPolicyRequirement.Create(up, globalSettings.Sso)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just an idea but maybe we could use Reflection here to find all classes that inherit IPolicyRequirement
and automatically register them. Maybe it adds some unnecessary complexity though.
🎟️ Tracking
https://bitwarden.atlassian.net/jira/dashboards/10012
📔 Objective
Background
Enterprise policies are organization-wide settings that limit app behavior for members. For example, if the Disable Send policy is enabled, members of that organization cannot use the Send feature.
Policies are complex because they're cross-domain and cross-team, affecting any area of the application. They can also have different enforcement logic. For example:
IGlobalSettings
overrideToday, this is mostly handled by
PolicyService
. However, this has to know about every type of policy, which is not very maintainable or extendible. It also has a poor interface:AnyPoliciesApplicableToUserAsync
returns true if any policy of a given type is Enabled - which is handy for very simple boolean policies, but only covers a subset of policies.GetPoliciesApplicableToUserAsync
returns all policies of a given type that should be enforced against a user, but then leaves the caller to evaluate and reconcile thePolicy
objects (including deserializing theData
json blob).Additionally, many devs don't know about this, and access policies directly using the repository. This bypasses the business logic entirely and leads to repeated bugs.
Proposed solution
Policy
objects in our business logic. This PR introduces the idea of a "policy requirement", which is the result of filtering and combining policies to create a single object. This object represents business requirements rather than data. In other words, it's a bridge between the policy domain and the caller's domain. Callers no longer have to know about the underlying policies.PolicyRequirementQuery
.PolicyRequirementQuery
to get the requirement they want to check.This PR implements a draft version of this for feedback.
TODO
📸 Screenshots
⏰ Reminders before review
🦮 Reviewer guidelines
:+1:
) or similar for great changes:memo:
) or ℹ️ (:information_source:
) for notes or general info:question:
) for questions:thinking:
) or 💭 (:thought_balloon:
) for more open inquiry that's not quite a confirmed issue and could potentially benefit from discussion:art:
) for suggestions / improvements:x:
) or:warning:
) for more significant problems or concerns needing attention:seedling:
) or ♻️ (:recycle:
) for future improvements or indications of technical debt:pick:
) for minor or nitpick changes