1
0
mirror of https://github.com/bitwarden/server.git synced 2024-11-21 12:05:42 +01:00

[PM-3007] Caching user policies on PolicyService variable (#3117)

* [PM-3007] Caching user policies on PolicyService variable

* [PM-3007] Added missing newlines on sql files
This commit is contained in:
Rui Tomé 2023-08-03 18:36:47 +01:00 committed by GitHub
parent 73c6421bd3
commit 78588d0246
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 65 additions and 36 deletions

View File

@ -39,6 +39,6 @@ public interface IOrganizationUserRepository : IRepository<OrganizationUser, Gui
Task<IEnumerable<OrganizationUserUserDetails>> GetManyByMinimumRoleAsync(Guid organizationId, OrganizationUserType minRole); Task<IEnumerable<OrganizationUserUserDetails>> GetManyByMinimumRoleAsync(Guid organizationId, OrganizationUserType minRole);
Task RevokeAsync(Guid id); Task RevokeAsync(Guid id);
Task RestoreAsync(Guid id, OrganizationUserStatusType status); Task RestoreAsync(Guid id, OrganizationUserStatusType status);
Task<IEnumerable<OrganizationUserPolicyDetails>> GetByUserIdWithPolicyDetailsAsync(Guid userId, PolicyType policyType); Task<IEnumerable<OrganizationUserPolicyDetails>> GetByUserIdWithPolicyDetailsAsync(Guid userId);
Task<int> GetOccupiedSmSeatCountByOrganizationIdAsync(Guid organizationId); Task<int> GetOccupiedSmSeatCountByOrganizationIdAsync(Guid organizationId);
} }

View File

@ -20,6 +20,8 @@ public class PolicyService : IPolicyService
private readonly IMailService _mailService; private readonly IMailService _mailService;
private readonly GlobalSettings _globalSettings; private readonly GlobalSettings _globalSettings;
private IEnumerable<OrganizationUserPolicyDetails> _cachedOrganizationUserPolicyDetails;
public PolicyService( public PolicyService(
IEventService eventService, IEventService eventService,
IOrganizationRepository organizationRepository, IOrganizationRepository organizationRepository,
@ -194,18 +196,25 @@ public class PolicyService : IPolicyService
return result.Any(); return result.Any();
} }
private async Task<IEnumerable<OrganizationUserPolicyDetails>> QueryOrganizationUserPolicyDetailsAsync(Guid userId, PolicyType policyType, OrganizationUserStatusType minStatus = OrganizationUserStatusType.Accepted) private async Task<IEnumerable<OrganizationUserPolicyDetails>> QueryOrganizationUserPolicyDetailsAsync(Guid userId, PolicyType? policyType, OrganizationUserStatusType minStatus = OrganizationUserStatusType.Accepted)
{ {
var organizationUserPolicyDetails = await _organizationUserRepository.GetByUserIdWithPolicyDetailsAsync(userId, policyType); // Check if the cached policies are available
if (_cachedOrganizationUserPolicyDetails == null)
{
// Cached policies not available, retrieve from the repository
_cachedOrganizationUserPolicyDetails = await _organizationUserRepository.GetByUserIdWithPolicyDetailsAsync(userId);
}
var excludedUserTypes = GetUserTypesExcludedFromPolicy(policyType); var excludedUserTypes = GetUserTypesExcludedFromPolicy(policyType);
return organizationUserPolicyDetails.Where(o => return _cachedOrganizationUserPolicyDetails.Where(o =>
(policyType == null || o.PolicyType == policyType) &&
o.PolicyEnabled && o.PolicyEnabled &&
!excludedUserTypes.Contains(o.OrganizationUserType) && !excludedUserTypes.Contains(o.OrganizationUserType) &&
o.OrganizationUserStatus >= minStatus && o.OrganizationUserStatus >= minStatus &&
!o.IsProvider); !o.IsProvider);
} }
private OrganizationUserType[] GetUserTypesExcludedFromPolicy(PolicyType policyType) private OrganizationUserType[] GetUserTypesExcludedFromPolicy(PolicyType? policyType)
{ {
switch (policyType) switch (policyType)
{ {

View File

@ -505,13 +505,13 @@ public class OrganizationUserRepository : Repository<OrganizationUser, Guid>, IO
} }
} }
public async Task<IEnumerable<OrganizationUserPolicyDetails>> GetByUserIdWithPolicyDetailsAsync(Guid userId, PolicyType policyType) public async Task<IEnumerable<OrganizationUserPolicyDetails>> GetByUserIdWithPolicyDetailsAsync(Guid userId)
{ {
using (var connection = new SqlConnection(ConnectionString)) using (var connection = new SqlConnection(ConnectionString))
{ {
var results = await connection.QueryAsync<OrganizationUserPolicyDetails>( var results = await connection.QueryAsync<OrganizationUserPolicyDetails>(
$"[{Schema}].[{Table}_ReadByUserIdWithPolicyDetails]", $"[{Schema}].[{Table}_ReadByUserIdWithPolicyDetails]",
new { UserId = userId, PolicyType = policyType }, new { UserId = userId },
commandType: CommandType.StoredProcedure); commandType: CommandType.StoredProcedure);
return results.ToList(); return results.ToList();

View File

@ -588,7 +588,7 @@ public class OrganizationUserRepository : Repository<Core.Entities.OrganizationU
} }
} }
public async Task<IEnumerable<OrganizationUserPolicyDetails>> GetByUserIdWithPolicyDetailsAsync(Guid userId, PolicyType policyType) public async Task<IEnumerable<OrganizationUserPolicyDetails>> GetByUserIdWithPolicyDetailsAsync(Guid userId)
{ {
using (var scope = ServiceScopeFactory.CreateScope()) using (var scope = ServiceScopeFactory.CreateScope())
{ {
@ -604,8 +604,7 @@ public class OrganizationUserRepository : Repository<Core.Entities.OrganizationU
join ou in dbContext.OrganizationUsers join ou in dbContext.OrganizationUsers
on p.OrganizationId equals ou.OrganizationId on p.OrganizationId equals ou.OrganizationId
let email = dbContext.Users.Find(userId).Email // Invited orgUsers do not have a UserId associated with them, so we have to match up their email let email = dbContext.Users.Find(userId).Email // Invited orgUsers do not have a UserId associated with them, so we have to match up their email
where p.Type == policyType && where ou.UserId == userId || ou.Email == email
(ou.UserId == userId || ou.Email == email)
select new OrganizationUserPolicyDetails select new OrganizationUserPolicyDetails
{ {
OrganizationUserId = ou.Id, OrganizationUserId = ou.Id,

View File

@ -1,6 +1,5 @@
CREATE PROCEDURE [dbo].[OrganizationUser_ReadByUserIdWithPolicyDetails] CREATE PROCEDURE [dbo].[OrganizationUser_ReadByUserIdWithPolicyDetails]
@UserId UNIQUEIDENTIFIER, @UserId UNIQUEIDENTIFIER
@PolicyType TINYINT
AS AS
BEGIN BEGIN
SET NOCOUNT ON SET NOCOUNT ON
@ -13,22 +12,19 @@ SELECT
OU.[Type] AS OrganizationUserType, OU.[Type] AS OrganizationUserType,
OU.[Status] AS OrganizationUserStatus, OU.[Status] AS OrganizationUserStatus,
OU.[Permissions] AS OrganizationUserPermissionsData, OU.[Permissions] AS OrganizationUserPermissionsData,
CASE WHEN EXISTS ( CASE WHEN PU.[ProviderId] IS NOT NULL THEN 1 ELSE 0 END AS IsProvider
SELECT 1
FROM [dbo].[ProviderUserView] PU
INNER JOIN [dbo].[ProviderOrganizationView] PO ON PO.[ProviderId] = PU.[ProviderId]
WHERE PU.[UserId] = OU.[UserId] AND PO.[OrganizationId] = P.[OrganizationId]
) THEN 1 ELSE 0 END AS IsProvider
FROM [dbo].[PolicyView] P FROM [dbo].[PolicyView] P
INNER JOIN [dbo].[OrganizationUserView] OU INNER JOIN [dbo].[OrganizationUserView] OU
ON P.[OrganizationId] = OU.[OrganizationId] ON P.[OrganizationId] = OU.[OrganizationId]
WHERE P.[Type] = @PolicyType AND LEFT JOIN [dbo].[ProviderUserView] PU
( ON PU.[UserId] = OU.[UserId]
(OU.[Status] != 0 AND OU.[UserId] = @UserId) -- OrgUsers who have accepted their invite and are linked to a UserId LEFT JOIN [dbo].[ProviderOrganizationView] PO
OR EXISTS ( ON PO.[ProviderId] = PU.[ProviderId] AND PO.[OrganizationId] = P.[OrganizationId]
SELECT 1 WHERE
FROM [dbo].[UserView] U (OU.[Status] != 0 AND OU.[UserId] = @UserId) -- OrgUsers who have accepted their invite and are linked to a UserId
WHERE U.[Id] = @UserId AND OU.[Email] = U.[Email] AND OU.[Status] = 0 -- 'Invited' OrgUsers are not linked to a UserId yet, so we have to look up their email OR EXISTS (
) SELECT 1
FROM [dbo].[UserView] U
WHERE U.[Id] = @UserId AND OU.[Email] = U.[Email] AND OU.[Status] = 0 -- 'Invited' OrgUsers are not linked to a UserId yet, so we have to look up their email
) )
END END

View File

@ -624,18 +624,12 @@ public class PolicyServiceTests
private static void SetupUserPolicies(Guid userId, SutProvider<PolicyService> sutProvider) private static void SetupUserPolicies(Guid userId, SutProvider<PolicyService> sutProvider)
{ {
sutProvider.GetDependency<IOrganizationUserRepository>() sutProvider.GetDependency<IOrganizationUserRepository>()
.GetByUserIdWithPolicyDetailsAsync(userId, PolicyType.RequireSso) .GetByUserIdWithPolicyDetailsAsync(userId)
.Returns(new List<OrganizationUserPolicyDetails> .Returns(new List<OrganizationUserPolicyDetails>
{ {
new() { OrganizationId = Guid.NewGuid(), PolicyType = PolicyType.RequireSso, PolicyEnabled = false, OrganizationUserType = OrganizationUserType.Owner, OrganizationUserStatus = OrganizationUserStatusType.Confirmed, IsProvider = false}, new() { OrganizationId = Guid.NewGuid(), PolicyType = PolicyType.RequireSso, PolicyEnabled = false, OrganizationUserType = OrganizationUserType.Owner, OrganizationUserStatus = OrganizationUserStatusType.Confirmed, IsProvider = false},
new() { OrganizationId = Guid.NewGuid(), PolicyType = PolicyType.RequireSso, PolicyEnabled = true, OrganizationUserType = OrganizationUserType.Owner, OrganizationUserStatus = OrganizationUserStatusType.Confirmed, IsProvider = false }, new() { OrganizationId = Guid.NewGuid(), PolicyType = PolicyType.RequireSso, PolicyEnabled = true, OrganizationUserType = OrganizationUserType.Owner, OrganizationUserStatus = OrganizationUserStatusType.Confirmed, IsProvider = false },
new() { OrganizationId = Guid.NewGuid(), PolicyType = PolicyType.RequireSso, PolicyEnabled = true, OrganizationUserType = OrganizationUserType.Owner, OrganizationUserStatus = OrganizationUserStatusType.Confirmed, IsProvider = true } new() { OrganizationId = Guid.NewGuid(), PolicyType = PolicyType.RequireSso, PolicyEnabled = true, OrganizationUserType = OrganizationUserType.Owner, OrganizationUserStatus = OrganizationUserStatusType.Confirmed, IsProvider = true },
});
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetByUserIdWithPolicyDetailsAsync(userId, PolicyType.DisableSend)
.Returns(new List<OrganizationUserPolicyDetails>
{
new() { OrganizationId = Guid.NewGuid(), PolicyType = PolicyType.DisableSend, PolicyEnabled = true, OrganizationUserType = OrganizationUserType.User, OrganizationUserStatus = OrganizationUserStatusType.Invited, IsProvider = false }, new() { OrganizationId = Guid.NewGuid(), PolicyType = PolicyType.DisableSend, PolicyEnabled = true, OrganizationUserType = OrganizationUserType.User, OrganizationUserStatus = OrganizationUserStatusType.Invited, IsProvider = false },
new() { OrganizationId = Guid.NewGuid(), PolicyType = PolicyType.DisableSend, PolicyEnabled = true, OrganizationUserType = OrganizationUserType.User, OrganizationUserStatus = OrganizationUserStatusType.Invited, IsProvider = true } new() { OrganizationId = Guid.NewGuid(), PolicyType = PolicyType.DisableSend, PolicyEnabled = true, OrganizationUserType = OrganizationUserType.User, OrganizationUserStatus = OrganizationUserStatusType.Invited, IsProvider = true }
}); });

View File

@ -274,7 +274,7 @@ public class OrganizationUserRepositoryTests
} }
// Act // Act
var result = await orgUserRepos[i].GetByUserIdWithPolicyDetailsAsync(savedUser.Id, policy.Type); var result = await orgUserRepos[i].GetByUserIdWithPolicyDetailsAsync(savedUser.Id);
results.Add(result.FirstOrDefault()); results.Add(result.FirstOrDefault());
} }

View File

@ -0,0 +1,31 @@
CREATE OR ALTER PROCEDURE [dbo].[OrganizationUser_ReadByUserIdWithPolicyDetails]
@UserId UNIQUEIDENTIFIER
AS
BEGIN
SET NOCOUNT ON
SELECT
OU.[Id] AS OrganizationUserId,
P.[OrganizationId],
P.[Type] AS PolicyType,
P.[Enabled] AS PolicyEnabled,
P.[Data] AS PolicyData,
OU.[Type] AS OrganizationUserType,
OU.[Status] AS OrganizationUserStatus,
OU.[Permissions] AS OrganizationUserPermissionsData,
CASE WHEN PU.[ProviderId] IS NOT NULL THEN 1 ELSE 0 END AS IsProvider
FROM [dbo].[PolicyView] P
INNER JOIN [dbo].[OrganizationUserView] OU
ON P.[OrganizationId] = OU.[OrganizationId]
LEFT JOIN [dbo].[ProviderUserView] PU
ON PU.[UserId] = OU.[UserId]
LEFT JOIN [dbo].[ProviderOrganizationView] PO
ON PO.[ProviderId] = PU.[ProviderId] AND PO.[OrganizationId] = P.[OrganizationId]
WHERE
(OU.[Status] != 0 AND OU.[UserId] = @UserId) -- OrgUsers who have accepted their invite and are linked to a UserId
OR EXISTS (
SELECT 1
FROM [dbo].[UserView] U
WHERE U.[Id] = @UserId AND OU.[Email] = U.[Email] AND OU.[Status] = 0 -- 'Invited' OrgUsers are not linked to a UserId yet, so we have to look up their email
)
END
GO