Files
server/src/Infrastructure.EntityFramework/AdminConsole/Repositories/PolicyRepository.cs
Jared McCannon 1eb396cb40 [PM-26636] - Auto Confirm Org User Command (#6488)
* Adding auto confirm endpoint and initial command work.

* Adding validator

* Finished command implementation.

* Enabled the feature renomved used method. Enabled the policy in the tests.

* Added extension functions to allow for railroad programming.

* Removed guid from route template. Added xml docs

* Added validation for command.

* Added default collection creation to command.

* formatting.

* Added additional error types and mapped to appropriate results.

* Added tests for auto confirm validator

* Adding tests

* fixing file name

* Cleaned up OrgUserController. Added integration tests.

* Consolidated CommandResult and validation result stuff into a v2 directory.

* changing result to match handle method.

* Moves validation thenasync method.

* Added brackets.

* Updated XML comment

* Adding idempotency comment.

* Fixed up merge problems. Fixed return types for handle.

* Renamed to ValidationRequest

* I added some methods for CommandResult to cover some future use cases. Added ApplyAsync method to execute multiple functions against CommandResult without an error stopping the workflow for side-effects.

* Fixed up logic around should create default colleciton. Added more methods for chaining ValidationResult together. Added logic for user type.

* Clearing nullable enable.

* Fixed up validator tests.

* Tests for auto confirm command

* Fixed up command result and AutoConfirmCommand.

* Removed some unused methods.

* Moved autoconfirm tests to their own class.

* Moved some stuff around. Need to clean up creation of accepted org user yet.

* Moved some more code around. Folded Key into accepted constructor. removed unneeded tests since key and accepted are now a part of AcceptedOrgUser Creation.

* Clean up clean up everybody everywhere. Clean up clean up everybody do your share.

* Another quick one

* Removed aggregate Errors.cs

* Cleaned up validator and fixed up tests.

* Fixed auto confirm repo

* Cleaned up command tests.

* Unused method.

* Restoring Bulk command back to what it was. deleted handle method for bulk.

* Remove unused method.

* removed unnecssary lines and comments

* fixed layout.

* Fixed test.

* fixed spelling mistake. removed unused import.

* Update test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/AutoConfirmUsers/AutomaticallyConfirmUsersCommandTests.cs

Co-authored-by: Rui Tomé <108268980+r-tome@users.noreply.github.com>

* Ensuring collection is created before full sync. Cleaning up tests and added a few more. Added check that the policy is enabled.

* Added org cleanup

* Lowering to 5 to see if  that helps the runner.

* 🤷

* Trying this

* Maybe this time will be different.

* seeing if awaiting and checking independently will work in ci

* I figured it out. Locally, it would be fast enough to all return NoContent, however in CI, its slow enough for it to return 400 due to the user already being confirmed via validation.

* Updated tests and validator

* Fixed name

---------

Co-authored-by: Rui Tomé <108268980+r-tome@users.noreply.github.com>
2025-11-19 08:09:48 -06:00

238 lines
10 KiB
C#

// FIXME: Update this file to be null safe and then delete the line below
#nullable disable
using AutoMapper;
using Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Enums;
using Bit.Infrastructure.EntityFramework.AdminConsole.Models;
using Bit.Infrastructure.EntityFramework.AdminConsole.Repositories.Queries;
using Bit.Infrastructure.EntityFramework.Repositories;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using AdminConsoleEntities = Bit.Core.AdminConsole.Entities;
namespace Bit.Infrastructure.EntityFramework.AdminConsole.Repositories;
public class PolicyRepository : Repository<AdminConsoleEntities.Policy, Policy, Guid>, IPolicyRepository
{
public PolicyRepository(IServiceScopeFactory serviceScopeFactory, IMapper mapper)
: base(serviceScopeFactory, mapper, (DatabaseContext context) => context.Policies)
{ }
public async Task<AdminConsoleEntities.Policy> GetByOrganizationIdTypeAsync(Guid organizationId, PolicyType type)
{
using (var scope = ServiceScopeFactory.CreateScope())
{
var dbContext = GetDatabaseContext(scope);
var results = await dbContext.Policies
.FirstOrDefaultAsync(p => p.OrganizationId == organizationId && p.Type == type);
return Mapper.Map<AdminConsoleEntities.Policy>(results);
}
}
public async Task<ICollection<AdminConsoleEntities.Policy>> GetManyByOrganizationIdAsync(Guid organizationId)
{
using (var scope = ServiceScopeFactory.CreateScope())
{
var dbContext = GetDatabaseContext(scope);
var results = await dbContext.Policies
.Where(p => p.OrganizationId == organizationId)
.ToListAsync();
return Mapper.Map<List<AdminConsoleEntities.Policy>>(results);
}
}
public async Task<ICollection<AdminConsoleEntities.Policy>> GetManyByUserIdAsync(Guid userId)
{
using (var scope = ServiceScopeFactory.CreateScope())
{
var dbContext = GetDatabaseContext(scope);
var query = new PolicyReadByUserIdQuery(userId);
var results = await query.Run(dbContext).ToListAsync();
return Mapper.Map<List<AdminConsoleEntities.Policy>>(results);
}
}
public async Task<IEnumerable<OrganizationPolicyDetails>> GetPolicyDetailsByOrganizationIdAsync(Guid organizationId, PolicyType policyType)
{
using var scope = ServiceScopeFactory.CreateScope();
var dbContext = GetDatabaseContext(scope);
var givenOrgUsers =
from ou in dbContext.OrganizationUsers
where ou.OrganizationId == organizationId
from u in dbContext.Users
where
(u.Email == ou.Email && ou.Email != null)
|| (ou.UserId == u.Id && ou.UserId != null)
select new
{
ou.Id,
ou.OrganizationId,
UserId = u.Id,
u.Email
};
var orgUsersLinkedByUserId =
from ou in dbContext.OrganizationUsers
join gou in givenOrgUsers
on ou.UserId equals gou.UserId
select new
{
ou.Id,
ou.OrganizationId,
gou.UserId,
ou.Type,
ou.Status,
ou.Permissions
};
var orgUsersLinkedByEmail =
from ou in dbContext.OrganizationUsers
join gou in givenOrgUsers
on ou.Email equals gou.Email
select new
{
ou.Id,
ou.OrganizationId,
gou.UserId,
ou.Type,
ou.Status,
ou.Permissions
};
var allAffectedOrgUsers = orgUsersLinkedByEmail.Union(orgUsersLinkedByUserId);
var providerOrganizations = from pu in dbContext.ProviderUsers
join po in dbContext.ProviderOrganizations
on pu.ProviderId equals po.ProviderId
join ou in allAffectedOrgUsers
on pu.UserId equals ou.UserId
where pu.UserId == ou.UserId
select new
{
pu.UserId,
po.OrganizationId
};
var policyWithAffectedUsers =
from p in dbContext.Policies
join o in dbContext.Organizations
on p.OrganizationId equals o.Id
join ou in allAffectedOrgUsers
on o.Id equals ou.OrganizationId
where p.Enabled
&& o.Enabled
&& o.UsePolicies
&& p.Type == policyType
select new OrganizationPolicyDetails
{
UserId = ou.UserId,
OrganizationUserId = ou.Id,
OrganizationId = p.OrganizationId,
PolicyType = p.Type,
PolicyData = p.Data,
OrganizationUserType = ou.Type,
OrganizationUserStatus = ou.Status,
OrganizationUserPermissionsData = ou.Permissions,
IsProvider = providerOrganizations.Any(po => po.OrganizationId == p.OrganizationId)
};
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 the provider 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();
}
}