-
Notifications
You must be signed in to change notification settings - Fork 1.5k
[PM-21411] Refactor interface for determining premium status and features #6688
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
Open
r-tome
wants to merge
52
commits into
main
Choose a base branch
from
ac/pm-21411/refactor-interface-for-premium-status
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+11,701
โ21
Open
Changes from 9 commits
Commits
Show all changes
52 commits
Select commit
Hold shift + click to select a range
5629d6f
Removed 2FA user interface from premium method signatures
trmartin4 c09a973
Added some more comments for clarity and small touchups.
Patrick-Pimentel-Bitwarden f83a790
Add PremiumAccessCacheCheck feature flag to Constants.cs
r-tome d30f3bd
Add IPremiumAccessQuery interface and PremiumAccessQuery implementatiโฆ
r-tome 377ae31
Add unit tests for PremiumAccessQuery to validate user premium accessโฆ
r-tome 683a6cb
Add XML documentation to Premium in OrganizationUserUserDetails and Uโฆ
r-tome 08e1413
Add PremiumAccessQueries to UserServiceCollectionExtensions
r-tome e494a10
Refactor TwoFactorIsEnabledQuery to incorporate PremiumAccessQuery anโฆ
r-tome 0cdadd1
Mark methods in IUserRepository and IUserService as obsolete, directiโฆ
r-tome 192052b
Rename CanAccessPremiumBulkAsync to CanAccessPremiumAsync in IPremiumโฆ
r-tome 2d31237
Update TwoFactorIsEnabledQuery to use CanAccessPremiumAsync for premiโฆ
r-tome 123f579
Refactor TwoFactorIsEnabledQuery to introduce VNextAsync methods for โฆ
r-tome 57252a7
Refactor IPremiumAccessQuery and PremiumAccessQuery to remove the oveโฆ
r-tome bf0cc7a
Add new sync static method to determine if TwoFactor is enabled
r-tome 65941d4
Enhance XML documentation for Premium property in OrganizationUserUseโฆ
r-tome 2a966e3
Refactor IPremiumAccessQuery and PremiumAccessQuery to replace User pโฆ
r-tome 96a3615
Update feature flag references in IUserRepository and IUserService toโฆ
r-tome 516824a
Rename IPremiumAccessQuery to IHasPremiumAccessQuery and move to Billโฆ
r-tome b8ff0ea
Remove unnecessary whitespace from IHasPremiumAccessQuery interface.
r-tome 415a0b9
Refactor HasPremiumAccessQuery to throw NotFoundException for null users
r-tome 303b81c
Add NotFoundException handling in HasPremiumAccessQuery for mismatcheโฆ
r-tome 30ff3da
Refactor TwoFactorIsEnabledQuery to optimize premium access checks anโฆ
r-tome 02c2aa8
Refactor TwoFactorIsEnabledQueryTests to enhance clarity and optimizeโฆ
r-tome 2184296
Add UserPremiumAccess model to represent user premium access status fโฆ
r-tome 5257dc5
Add User_ReadPremiumAccessByIds stored procedure and UserPremiumAccesโฆ
r-tome 97bcf41
Add SQL migration script
r-tome 0a42c9e
Add premium access retrieval methods to IUserRepository and implementโฆ
r-tome 651d5e3
Refactor HasPremiumAccessQuery and IHasPremiumAccessQuery to streamliโฆ
r-tome 353a07a
Update IUserRepository to reflect new method names for premium accessโฆ
r-tome da9f0a1
Refactor TwoFactorIsEnabledQuery to utilize IFeatureService for premiโฆ
r-tome df7bd08
Enhance EF UserRepository to improve premium access retrieval by inclโฆ
r-tome cd0b3da
Add unit tests for premium access retrieval in UserRepositoryTests.
r-tome 245ce71
Optimize HasPremiumAccessQuery to eliminate duplicate user IDs beforeโฆ
r-tome 0279cb5
Refactor TwoFactorIsEnabledQuery to improve handling of users withoutโฆ
r-tome 1415517
Merge branch 'main' into ac/pm-21411/refactor-interface-for-premium-sโฆ
r-tome 2bd00c2
Update HasPremiumAccessQueryTests to use simplified exception handlinโฆ
r-tome dbb8619
Enhance TwoFactorIsEnabledQuery to throw NotFoundException for non-exโฆ
r-tome 2f11c13
Move premium access query to Billing owned ServiceCollectionExtensions
r-tome 9a68a97
Refactor IUserService to enhance premium access checks
r-tome 1cd5749
Update IUserRepository to clarify usage of premium access methods
r-tome 61775fb
Update IUserRepository and IUserService to clarify deprecation of preโฆ
r-tome 19627f4
Refactor TwoFactorIsEnabledQuery to streamline user ID retrieval
r-tome 6a783ff
Rename migration script to fix the date
r-tome 85e0e1a
Merge branch 'main' into ac/pm-21411/refactor-interface-for-premium-sโฆ
r-tome f833040
Merge branch 'main' into ac/pm-21411/refactor-interface-for-premium-sโฆ
r-tome de13a7c
Update migration script to create the index with DROP_EXISTING = ON
r-tome 03b0431
Refactor UserPremiumAccessView to use LEFT JOINs and GROUP BY for impโฆ
r-tome 125b4de
Update HasPremiumAccessQueryTests to return null for GetPremiumAccessโฆ
r-tome 2081dd4
Add unit tests for premium access scenarios in UserRepositoryTests
r-tome de42f20
Bump date on migration script
r-tome 33a464a
Update OrganizationEntityTypeConfiguration to include UsersGetPremiumโฆ
r-tome 97bf24e
Add migration scripts for OrganizationUsersGetPremiumIndex across MySโฆ
r-tome File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,22 +1,25 @@ | ||
| ๏ปฟ// FIXME: Update this file to be null safe and then delete the line below | ||
| #nullable disable | ||
|
|
||
| using Bit.Core.Auth.Enums; | ||
| ๏ปฟusing Bit.Core.Auth.Enums; | ||
| using Bit.Core.Services; | ||
|
|
||
| namespace Bit.Core.Auth.Models; | ||
|
|
||
| /// <summary> | ||
| /// An interface representing a user entity that supports two-factor providers | ||
| /// </summary> | ||
| public interface ITwoFactorProvidersUser | ||
| { | ||
| string TwoFactorProviders { get; } | ||
| string? TwoFactorProviders { get; } | ||
| /// <summary> | ||
| /// Get the two factor providers for the user. Currently it can be assumed providers are enabled | ||
| /// if they exists in the dictionary. When two factor providers are disabled they are removed | ||
| /// from the dictionary. <see cref="IUserService.DisableTwoFactorProviderAsync"/> | ||
| /// <see cref="IOrganizationService.DisableTwoFactorProviderAsync"/> | ||
| /// </summary> | ||
| /// <returns>Dictionary of providers with the type enum as the key</returns> | ||
| Dictionary<TwoFactorProviderType, TwoFactorProvider> GetTwoFactorProviders(); | ||
| Dictionary<TwoFactorProviderType, TwoFactorProvider>? GetTwoFactorProviders(); | ||
| /// <summary> | ||
| /// The unique `UserId` of the user entity for which there are two-factor providers configured. | ||
| /// </summary> | ||
| /// <returns>The unique identifier for the user</returns> | ||
| Guid? GetUserId(); | ||
| bool GetPremium(); | ||
| } |
50 changes: 50 additions & 0 deletions
50
src/Core/Auth/UserFeatures/PremiumAccess/IPremiumAccessQuery.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| ๏ปฟusing Bit.Core.Entities; | ||
|
|
||
| namespace Bit.Core.Auth.UserFeatures.PremiumAccess; | ||
|
|
||
| /// <summary> | ||
| /// Query for checking premium access status for users. | ||
| /// This is the centralized location for determining if a user can access premium features | ||
| /// (either through personal subscription or organization membership). | ||
| /// | ||
| /// <para> | ||
| /// <strong>Note:</strong> This is different from checking User.Premium, which only indicates | ||
| /// personal subscription status. Use these methods to check actual premium feature access. | ||
| /// </para> | ||
| /// </summary> | ||
| public interface IPremiumAccessQuery | ||
| { | ||
| /// <summary> | ||
| /// Checks if a user has access to premium features (personal subscription or organization). | ||
| /// This is the definitive way to check premium access for a single user. | ||
| /// </summary> | ||
| /// <param name="user">The user to check for premium access</param> | ||
| /// <returns>True if user can access premium features; false otherwise</returns> | ||
| Task<bool> CanAccessPremiumAsync(User user); | ||
|
|
||
| /// <summary> | ||
| /// Checks if a user has access to premium features (personal subscription or organization). | ||
| /// Use this overload when you already know the personal premium status and only need to check organization premium. | ||
| /// </summary> | ||
| /// <param name="userId">The user ID to check for premium access</param> | ||
| /// <param name="hasPersonalPremium">Whether the user has a personal premium subscription</param> | ||
| /// <returns>True if user can access premium features; false otherwise</returns> | ||
| Task<bool> CanAccessPremiumAsync(Guid userId, bool hasPersonalPremium); | ||
|
|
||
| /// <summary> | ||
| /// Checks if a user has access to premium features through organization membership only. | ||
| /// This is useful for determining the source of premium access (personal vs organization). | ||
| /// </summary> | ||
| /// <param name="userId">The user ID to check for organization premium access</param> | ||
| /// <returns>True if user has premium access through any organization; false otherwise</returns> | ||
| Task<bool> HasPremiumFromOrganizationAsync(Guid userId); | ||
|
|
||
| /// <summary> | ||
| /// Checks if multiple users have access to premium features (optimized bulk operation). | ||
| /// Uses cached organization abilities and minimizes database queries. | ||
| /// </summary> | ||
| /// <param name="users">The users to check for premium access</param> | ||
| /// <returns>Dictionary mapping user IDs to their premium access status (personal or through organization)</returns> | ||
| Task<Dictionary<Guid, bool>> CanAccessPremiumBulkAsync(IEnumerable<User> users); | ||
r-tome marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
97 changes: 97 additions & 0 deletions
97
src/Core/Auth/UserFeatures/PremiumAccess/PremiumAccessQuery.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,97 @@ | ||
| ๏ปฟusing Bit.Core.Entities; | ||
| using Bit.Core.Repositories; | ||
| using Bit.Core.Services; | ||
|
|
||
| namespace Bit.Core.Auth.UserFeatures.PremiumAccess; | ||
|
|
||
| /// <summary> | ||
| /// Query for checking premium access status for users using cached organization abilities. | ||
| /// </summary> | ||
| public class PremiumAccessQuery : IPremiumAccessQuery | ||
| { | ||
| private readonly IOrganizationUserRepository _organizationUserRepository; | ||
| private readonly IApplicationCacheService _applicationCacheService; | ||
|
|
||
| public PremiumAccessQuery( | ||
| IOrganizationUserRepository organizationUserRepository, | ||
| IApplicationCacheService applicationCacheService) | ||
| { | ||
| _organizationUserRepository = organizationUserRepository; | ||
| _applicationCacheService = applicationCacheService; | ||
| } | ||
|
|
||
| public async Task<bool> CanAccessPremiumAsync(User user) | ||
| { | ||
| return await CanAccessPremiumAsync(user.Id, user.Premium); | ||
| } | ||
|
|
||
| public async Task<bool> CanAccessPremiumAsync(Guid userId, bool hasPersonalPremium) | ||
| { | ||
| if (hasPersonalPremium) | ||
| { | ||
| return true; | ||
| } | ||
|
|
||
| return await HasPremiumFromOrganizationAsync(userId); | ||
| } | ||
|
|
||
| public async Task<bool> HasPremiumFromOrganizationAsync(Guid userId) | ||
| { | ||
| // Note: GetManyByUserAsync only returns Accepted and Confirmed status org users | ||
| var orgUsers = await _organizationUserRepository.GetManyByUserAsync(userId); | ||
| if (!orgUsers.Any()) | ||
| { | ||
| return false; | ||
| } | ||
|
|
||
| var orgAbilities = await _applicationCacheService.GetOrganizationAbilitiesAsync(); | ||
| return orgUsers.Any(ou => | ||
| orgAbilities.TryGetValue(ou.OrganizationId, out var orgAbility) && | ||
| orgAbility.UsersGetPremium && | ||
| orgAbility.Enabled); | ||
| } | ||
|
|
||
| public async Task<Dictionary<Guid, bool>> CanAccessPremiumBulkAsync(IEnumerable<User> users) | ||
| { | ||
| var result = new Dictionary<Guid, bool>(); | ||
| var usersList = users.ToList(); | ||
|
|
||
| if (!usersList.Any()) | ||
| { | ||
| return result; | ||
| } | ||
|
|
||
| var userIds = usersList.Select(u => u.Id).ToList(); | ||
|
|
||
| // Get all org memberships for these users in one query | ||
| // Note: GetManyByManyUsersAsync only returns Accepted and Confirmed status org users | ||
| var allOrgUsers = await _organizationUserRepository.GetManyByManyUsersAsync(userIds); | ||
| var orgUsersGrouped = allOrgUsers | ||
| .Where(ou => ou.UserId.HasValue) | ||
| .GroupBy(ou => ou.UserId!.Value) | ||
| .ToDictionary(g => g.Key, g => g.ToList()); | ||
|
|
||
| var orgAbilities = await _applicationCacheService.GetOrganizationAbilitiesAsync(); | ||
|
|
||
| foreach (var user in usersList) | ||
| { | ||
| var hasPersonalPremium = user.Premium; | ||
| if (hasPersonalPremium) | ||
| { | ||
| result[user.Id] = true; | ||
| continue; | ||
| } | ||
|
|
||
| var hasPremiumFromOrg = orgUsersGrouped.TryGetValue(user.Id, out var userOrgs) && | ||
| userOrgs.Any(ou => | ||
| orgAbilities.TryGetValue(ou.OrganizationId, out var orgAbility) && | ||
| orgAbility.UsersGetPremium && | ||
| orgAbility.Enabled); | ||
|
|
||
| result[user.Id] = hasPremiumFromOrg; | ||
| } | ||
|
|
||
| return result; | ||
| } | ||
r-tome marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
eliykat marked this conversation as resolved.
Show resolved
Hide resolved
eliykat marked this conversation as resolved.
Show resolved
Hide resolved
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.