mirror of
https://github.com/bitwarden/server
synced 2025-12-26 05:03:18 +00:00
[PM-25126] Add Bulk Policy Details (#6256)
* Added new bulk get for policy details * Query improvements to avoid unnecessary look-ups.
This commit is contained in:
@@ -44,4 +44,15 @@ public interface IPolicyRepository : IRepository<Policy, Guid>
|
||||
/// You probably do not want to call it directly.
|
||||
/// </remarks>
|
||||
Task<IEnumerable<OrganizationPolicyDetails>> GetPolicyDetailsByOrganizationIdAsync(Guid organizationId, PolicyType policyType);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves policy details for a list of users filtered by the specified policy type.
|
||||
/// </summary>
|
||||
/// <param name="userIds">A collection of user identifiers for which the policy details are to be fetched.</param>
|
||||
/// <param name="policyType">The type of policy for which the details are required.</param>
|
||||
/// <returns>
|
||||
/// An asynchronous task that returns a collection of <see cref="OrganizationPolicyDetails"/> objects containing the policy information
|
||||
/// associated with the specified users and policy type.
|
||||
/// </returns>
|
||||
Task<IEnumerable<OrganizationPolicyDetails>> GetPolicyDetailsByUserIdsAndPolicyType(IEnumerable<Guid> userIds, PolicyType policyType);
|
||||
}
|
||||
|
||||
@@ -74,6 +74,21 @@ public class PolicyRepository : Repository<Policy, Guid>, IPolicyRepository
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<OrganizationPolicyDetails>> GetPolicyDetailsByUserIdsAndPolicyType(IEnumerable<Guid> userIds, PolicyType type)
|
||||
{
|
||||
await using var connection = new SqlConnection(ConnectionString);
|
||||
var results = await connection.QueryAsync<OrganizationPolicyDetails>(
|
||||
$"[{Schema}].[PolicyDetails_ReadByUserIdsPolicyType]",
|
||||
new
|
||||
{
|
||||
UserIds = userIds.ToGuidIdArrayTVP(),
|
||||
PolicyType = (byte)type
|
||||
},
|
||||
commandType: CommandType.StoredProcedure);
|
||||
|
||||
return results.ToList();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<OrganizationPolicyDetails>> GetPolicyDetailsByOrganizationIdAsync(Guid organizationId, PolicyType policyType)
|
||||
{
|
||||
using (var connection = new SqlConnection(ConnectionString))
|
||||
|
||||
@@ -183,4 +183,94 @@ public class PolicyRepository : Repository<AdminConsoleEntities.Policy, Policy,
|
||||
|
||||
return await policyWithAffectedUsers.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<OrganizationPolicyDetails>> GetPolicyDetailsByUserIdsAndPolicyType(
|
||||
IEnumerable<Guid> userIds, PolicyType policyType)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(userIds);
|
||||
|
||||
var userIdsList = userIds.Where(id => id != Guid.Empty).ToList();
|
||||
|
||||
if (userIdsList.Count == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
using var scope = ServiceScopeFactory.CreateScope();
|
||||
await using var dbContext = GetDatabaseContext(scope);
|
||||
|
||||
// Get provider relationships
|
||||
var providerLookup = await (from pu in dbContext.ProviderUsers
|
||||
join po in dbContext.ProviderOrganizations on pu.ProviderId equals po.ProviderId
|
||||
where pu.UserId != null && userIdsList.Contains(pu.UserId.Value)
|
||||
select new { pu.UserId, po.OrganizationId })
|
||||
.ToListAsync();
|
||||
|
||||
// Hashset for lookup
|
||||
var providerSet = new HashSet<(Guid UserId, Guid OrganizationId)>(
|
||||
providerLookup.Select(p => (p.UserId!.Value, p.OrganizationId)));
|
||||
|
||||
// Branch 1: Accepted users
|
||||
var acceptedUsers = await (from p in dbContext.Policies
|
||||
join ou in dbContext.OrganizationUsers on p.OrganizationId equals ou.OrganizationId
|
||||
join o in dbContext.Organizations on p.OrganizationId equals o.Id
|
||||
where p.Enabled
|
||||
&& p.Type == policyType
|
||||
&& o.Enabled
|
||||
&& o.UsePolicies
|
||||
&& ou.Status != OrganizationUserStatusType.Invited
|
||||
&& ou.UserId != null
|
||||
&& userIdsList.Contains(ou.UserId.Value)
|
||||
select new
|
||||
{
|
||||
OrganizationUserId = ou.Id,
|
||||
OrganizationId = p.OrganizationId,
|
||||
PolicyType = p.Type,
|
||||
PolicyData = p.Data,
|
||||
OrganizationUserType = ou.Type,
|
||||
OrganizationUserStatus = ou.Status,
|
||||
OrganizationUserPermissionsData = ou.Permissions,
|
||||
UserId = ou.UserId.Value
|
||||
}).ToListAsync();
|
||||
|
||||
// Branch 2: Invited users
|
||||
var invitedUsers = await (from p in dbContext.Policies
|
||||
join ou in dbContext.OrganizationUsers on p.OrganizationId equals ou.OrganizationId
|
||||
join o in dbContext.Organizations on p.OrganizationId equals o.Id
|
||||
join u in dbContext.Users on ou.Email equals u.Email
|
||||
where p.Enabled
|
||||
&& o.Enabled
|
||||
&& o.UsePolicies
|
||||
&& ou.Status == OrganizationUserStatusType.Invited
|
||||
&& userIdsList.Contains(u.Id)
|
||||
&& p.Type == policyType
|
||||
select new
|
||||
{
|
||||
OrganizationUserId = ou.Id,
|
||||
OrganizationId = p.OrganizationId,
|
||||
PolicyType = p.Type,
|
||||
PolicyData = p.Data,
|
||||
OrganizationUserType = ou.Type,
|
||||
OrganizationUserStatus = ou.Status,
|
||||
OrganizationUserPermissionsData = ou.Permissions,
|
||||
UserId = u.Id
|
||||
}).ToListAsync();
|
||||
|
||||
// Combine results with provder lookup
|
||||
var allResults = acceptedUsers.Concat(invitedUsers)
|
||||
.Select(item => new OrganizationPolicyDetails
|
||||
{
|
||||
OrganizationUserId = item.OrganizationUserId,
|
||||
OrganizationId = item.OrganizationId,
|
||||
PolicyType = item.PolicyType,
|
||||
PolicyData = item.PolicyData,
|
||||
OrganizationUserType = item.OrganizationUserType,
|
||||
OrganizationUserStatus = item.OrganizationUserStatus,
|
||||
OrganizationUserPermissionsData = item.OrganizationUserPermissionsData,
|
||||
UserId = item.UserId,
|
||||
IsProvider = providerSet.Contains((item.UserId, item.OrganizationId))
|
||||
});
|
||||
|
||||
return allResults.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
CREATE PROCEDURE [dbo].[PolicyDetails_ReadByUserIdsPolicyType]
|
||||
@UserIds AS [dbo].[GuidIdArray] READONLY,
|
||||
@PolicyType AS TINYINT
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
|
||||
WITH AcceptedUsers AS (
|
||||
-- Branch 1: Accepted users linked by UserId
|
||||
SELECT
|
||||
OU.[Id] AS OrganizationUserId,
|
||||
P.[OrganizationId],
|
||||
P.[Type] AS PolicyType,
|
||||
P.[Data] AS PolicyData,
|
||||
OU.[Type] AS OrganizationUserType,
|
||||
OU.[Status] AS OrganizationUserStatus,
|
||||
OU.[Permissions] AS OrganizationUserPermissionsData,
|
||||
OU.[UserId] AS UserId
|
||||
FROM [dbo].[PolicyView] P
|
||||
INNER JOIN [dbo].[OrganizationUserView] OU ON P.[OrganizationId] = OU.[OrganizationId]
|
||||
INNER JOIN [dbo].[OrganizationView] O ON P.[OrganizationId] = O.[Id]
|
||||
INNER JOIN @UserIds UI ON OU.[UserId] = UI.Id -- Direct join with TVP
|
||||
WHERE
|
||||
P.Enabled = 1
|
||||
AND O.Enabled = 1
|
||||
AND O.UsePolicies = 1
|
||||
AND OU.[Status] != 0 -- Accepted users
|
||||
AND P.[Type] = @PolicyType
|
||||
),
|
||||
InvitedUsers AS (
|
||||
-- Branch 2: Invited users matched by email
|
||||
SELECT
|
||||
OU.[Id] AS OrganizationUserId,
|
||||
P.[OrganizationId],
|
||||
P.[Type] AS PolicyType,
|
||||
P.[Data] AS PolicyData,
|
||||
OU.[Type] AS OrganizationUserType,
|
||||
OU.[Status] AS OrganizationUserStatus,
|
||||
OU.[Permissions] AS OrganizationUserPermissionsData,
|
||||
U.[Id] AS UserId
|
||||
FROM [dbo].[PolicyView] P
|
||||
INNER JOIN [dbo].[OrganizationUserView] OU ON P.[OrganizationId] = OU.[OrganizationId]
|
||||
INNER JOIN [dbo].[OrganizationView] O ON P.[OrganizationId] = O.[Id]
|
||||
INNER JOIN [dbo].[UserView] U ON U.[Email] = OU.[Email] -- Join on email
|
||||
INNER JOIN @UserIds UI ON U.[Id] = UI.Id -- Join with TVP
|
||||
WHERE
|
||||
P.Enabled = 1
|
||||
AND O.Enabled = 1
|
||||
AND O.UsePolicies = 1
|
||||
AND OU.[Status] = 0 -- Invited users only
|
||||
AND P.[Type] = @PolicyType
|
||||
),
|
||||
AllUsers AS (
|
||||
-- Combine both user sets
|
||||
SELECT * FROM AcceptedUsers
|
||||
UNION
|
||||
SELECT * FROM InvitedUsers
|
||||
),
|
||||
ProviderLookup AS (
|
||||
-- Pre-calculate provider relationships for all relevant user/org combinations
|
||||
SELECT DISTINCT
|
||||
PU.[UserId],
|
||||
PO.[OrganizationId]
|
||||
FROM [dbo].[ProviderUserView] PU
|
||||
INNER JOIN [dbo].[ProviderOrganizationView] PO ON PO.[ProviderId] = PU.[ProviderId]
|
||||
INNER JOIN AllUsers AU ON PU.[UserId] = AU.UserId AND PO.[OrganizationId] = AU.OrganizationId
|
||||
)
|
||||
-- Final result with efficient IsProvider lookup
|
||||
SELECT
|
||||
AU.OrganizationUserId,
|
||||
AU.OrganizationId,
|
||||
AU.PolicyType,
|
||||
AU.PolicyData,
|
||||
AU.OrganizationUserType,
|
||||
AU.OrganizationUserStatus,
|
||||
AU.OrganizationUserPermissionsData,
|
||||
AU.UserId,
|
||||
IIF(PL.UserId IS NOT NULL, 1, 0) AS IsProvider
|
||||
FROM AllUsers AU
|
||||
LEFT JOIN ProviderLookup PL
|
||||
ON AU.UserId = PL.UserId
|
||||
AND AU.OrganizationId = PL.OrganizationId
|
||||
END
|
||||
Reference in New Issue
Block a user