1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-01 08:02:49 -05:00

[AC-1200] Admin Console code ownership - move OrganizationFeatures (#3369)

This commit is contained in:
Thomas Rittson
2023-10-27 07:47:44 +10:00
committed by GitHub
parent 26dd8b0e47
commit ad230fb6a5
66 changed files with 151 additions and 152 deletions

View File

@ -0,0 +1,32 @@
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationApiKeys.Interfaces;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Repositories;
using Bit.Core.Utilities;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationApiKeys;
public class CreateOrganizationApiKeyCommand : ICreateOrganizationApiKeyCommand
{
private readonly IOrganizationApiKeyRepository _organizationApiKeyRepository;
public CreateOrganizationApiKeyCommand(IOrganizationApiKeyRepository organizationApiKeyRepository)
{
_organizationApiKeyRepository = organizationApiKeyRepository;
}
public async Task<OrganizationApiKey> CreateAsync(Guid organizationId,
OrganizationApiKeyType organizationApiKeyType)
{
var apiKey = new OrganizationApiKey
{
OrganizationId = organizationId,
Type = organizationApiKeyType,
ApiKey = CoreHelpers.SecureRandomString(30),
RevisionDate = DateTime.UtcNow,
};
await _organizationApiKeyRepository.CreateAsync(apiKey);
return apiKey;
}
}

View File

@ -0,0 +1,30 @@
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationApiKeys.Interfaces;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Repositories;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationApiKeys;
public class GetOrganizationApiKeyQuery : IGetOrganizationApiKeyQuery
{
private readonly IOrganizationApiKeyRepository _organizationApiKeyRepository;
public GetOrganizationApiKeyQuery(IOrganizationApiKeyRepository organizationApiKeyRepository)
{
_organizationApiKeyRepository = organizationApiKeyRepository;
}
public async Task<OrganizationApiKey> GetOrganizationApiKeyAsync(Guid organizationId, OrganizationApiKeyType organizationApiKeyType)
{
if (!Enum.IsDefined(organizationApiKeyType))
{
throw new ArgumentOutOfRangeException(nameof(organizationApiKeyType), $"Invalid value for enum {nameof(OrganizationApiKeyType)}");
}
var apiKeys = await _organizationApiKeyRepository
.GetManyByOrganizationIdTypeAsync(organizationId, organizationApiKeyType);
// NOTE: Currently we only allow one type of api key per organization
return apiKeys.SingleOrDefault();
}
}

View File

@ -0,0 +1,9 @@
using Bit.Core.Entities;
using Bit.Core.Enums;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationApiKeys.Interfaces;
public interface ICreateOrganizationApiKeyCommand
{
Task<OrganizationApiKey> CreateAsync(Guid organizationId, OrganizationApiKeyType organizationApiKeyType);
}

View File

@ -0,0 +1,9 @@
using Bit.Core.Entities;
using Bit.Core.Enums;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationApiKeys.Interfaces;
public interface IGetOrganizationApiKeyQuery
{
Task<OrganizationApiKey> GetOrganizationApiKeyAsync(Guid organizationId, OrganizationApiKeyType organizationApiKeyType);
}

View File

@ -0,0 +1,8 @@
using Bit.Core.Entities;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationApiKeys.Interfaces;
public interface IRotateOrganizationApiKeyCommand
{
Task<OrganizationApiKey> RotateApiKeyAsync(OrganizationApiKey organizationApiKey);
}

View File

@ -0,0 +1,24 @@
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationApiKeys.Interfaces;
using Bit.Core.Entities;
using Bit.Core.Repositories;
using Bit.Core.Utilities;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationApiKeys;
public class RotateOrganizationApiKeyCommand : IRotateOrganizationApiKeyCommand
{
private readonly IOrganizationApiKeyRepository _organizationApiKeyRepository;
public RotateOrganizationApiKeyCommand(IOrganizationApiKeyRepository organizationApiKeyRepository)
{
_organizationApiKeyRepository = organizationApiKeyRepository;
}
public async Task<OrganizationApiKey> RotateApiKeyAsync(OrganizationApiKey organizationApiKey)
{
organizationApiKey.ApiKey = CoreHelpers.SecureRandomString(30);
organizationApiKey.RevisionDate = DateTime.UtcNow;
await _organizationApiKeyRepository.UpsertAsync(organizationApiKey);
return organizationApiKey;
}
}

View File

@ -0,0 +1,22 @@
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationConnections.Interfaces;
using Bit.Core.Entities;
using Bit.Core.Models.Data.Organizations.OrganizationConnections;
using Bit.Core.Models.OrganizationConnectionConfigs;
using Bit.Core.Repositories;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationConnections;
public class CreateOrganizationConnectionCommand : ICreateOrganizationConnectionCommand
{
private readonly IOrganizationConnectionRepository _organizationConnectionRepository;
public CreateOrganizationConnectionCommand(IOrganizationConnectionRepository organizationConnectionRepository)
{
_organizationConnectionRepository = organizationConnectionRepository;
}
public async Task<OrganizationConnection> CreateAsync<T>(OrganizationConnectionData<T> connectionData) where T : IConnectionConfig
{
return await _organizationConnectionRepository.CreateAsync(connectionData.ToEntity());
}
}

View File

@ -0,0 +1,20 @@
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationConnections.Interfaces;
using Bit.Core.Entities;
using Bit.Core.Repositories;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationConnections;
public class DeleteOrganizationConnectionCommand : IDeleteOrganizationConnectionCommand
{
private readonly IOrganizationConnectionRepository _organizationConnectionRepository;
public DeleteOrganizationConnectionCommand(IOrganizationConnectionRepository organizationConnectionRepository)
{
_organizationConnectionRepository = organizationConnectionRepository;
}
public async Task DeleteAsync(OrganizationConnection connection)
{
await _organizationConnectionRepository.DeleteAsync(connection);
}
}

View File

@ -0,0 +1,10 @@
using Bit.Core.Entities;
using Bit.Core.Models.Data.Organizations.OrganizationConnections;
using Bit.Core.Models.OrganizationConnectionConfigs;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationConnections.Interfaces;
public interface ICreateOrganizationConnectionCommand
{
Task<OrganizationConnection> CreateAsync<T>(OrganizationConnectionData<T> connectionData) where T : IConnectionConfig;
}

View File

@ -0,0 +1,8 @@
using Bit.Core.Entities;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationConnections.Interfaces;
public interface IDeleteOrganizationConnectionCommand
{
Task DeleteAsync(OrganizationConnection connection);
}

View File

@ -0,0 +1,10 @@
using Bit.Core.Entities;
using Bit.Core.Models.Data.Organizations.OrganizationConnections;
using Bit.Core.Models.OrganizationConnectionConfigs;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationConnections.Interfaces;
public interface IUpdateOrganizationConnectionCommand
{
Task<OrganizationConnection> UpdateAsync<T>(OrganizationConnectionData<T> connectionData) where T : IConnectionConfig;
}

View File

@ -0,0 +1,8 @@
using Bit.Core.Entities;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationConnections.Interfaces;
public interface IValidateBillingSyncKeyCommand
{
Task<bool> ValidateBillingSyncKeyAsync(Organization organization, string billingSyncKey);
}

View File

@ -0,0 +1,37 @@
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationConnections.Interfaces;
using Bit.Core.Entities;
using Bit.Core.Exceptions;
using Bit.Core.Models.Data.Organizations.OrganizationConnections;
using Bit.Core.Models.OrganizationConnectionConfigs;
using Bit.Core.Repositories;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationConnections;
public class UpdateOrganizationConnectionCommand : IUpdateOrganizationConnectionCommand
{
private readonly IOrganizationConnectionRepository _organizationConnectionRepository;
public UpdateOrganizationConnectionCommand(IOrganizationConnectionRepository organizationConnectionRepository)
{
_organizationConnectionRepository = organizationConnectionRepository;
}
public async Task<OrganizationConnection> UpdateAsync<T>(OrganizationConnectionData<T> connectionData) where T : IConnectionConfig
{
if (!connectionData.Id.HasValue)
{
throw new Exception("Cannot update connection, Connection does not exist.");
}
var connection = await _organizationConnectionRepository.GetByIdAsync(connectionData.Id.Value);
if (connection == null)
{
throw new NotFoundException();
}
var entity = connectionData.ToEntity();
await _organizationConnectionRepository.UpsertAsync(entity);
return entity;
}
}

View File

@ -0,0 +1,36 @@
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationConnections.Interfaces;
using Bit.Core.Entities;
using Bit.Core.Exceptions;
using Bit.Core.Repositories;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationConnections;
public class ValidateBillingSyncKeyCommand : IValidateBillingSyncKeyCommand
{
private readonly IOrganizationApiKeyRepository _apiKeyRepository;
public ValidateBillingSyncKeyCommand(
IOrganizationApiKeyRepository organizationApiKeyRepository)
{
_apiKeyRepository = organizationApiKeyRepository;
}
public async Task<bool> ValidateBillingSyncKeyAsync(Organization organization, string billingSyncKey)
{
if (organization == null)
{
throw new BadRequestException("Invalid organization");
}
if (string.IsNullOrWhiteSpace(billingSyncKey))
{
return false;
}
var orgApiKey = (await _apiKeyRepository.GetManyByOrganizationIdTypeAsync(organization.Id, Core.Enums.OrganizationApiKeyType.BillingSync)).FirstOrDefault();
if (string.Equals(orgApiKey.ApiKey, billingSyncKey))
{
return true;
}
return false;
}
}

View File

@ -0,0 +1,76 @@
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains.Interfaces;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Settings;
using Microsoft.Extensions.Logging;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains;
public class CreateOrganizationDomainCommand : ICreateOrganizationDomainCommand
{
private readonly IOrganizationDomainRepository _organizationDomainRepository;
private readonly IEventService _eventService;
private readonly IDnsResolverService _dnsResolverService;
private readonly ILogger<VerifyOrganizationDomainCommand> _logger;
private readonly IGlobalSettings _globalSettings;
public CreateOrganizationDomainCommand(
IOrganizationDomainRepository organizationDomainRepository,
IEventService eventService,
IDnsResolverService dnsResolverService,
ILogger<VerifyOrganizationDomainCommand> logger,
IGlobalSettings globalSettings)
{
_organizationDomainRepository = organizationDomainRepository;
_eventService = eventService;
_dnsResolverService = dnsResolverService;
_logger = logger;
_globalSettings = globalSettings;
}
public async Task<OrganizationDomain> CreateAsync(OrganizationDomain organizationDomain)
{
//Domains claimed and verified by an organization cannot be claimed
var claimedDomain =
await _organizationDomainRepository.GetClaimedDomainsByDomainNameAsync(organizationDomain.DomainName);
if (claimedDomain.Any())
{
throw new ConflictException("The domain is not available to be claimed.");
}
//check for duplicate domain entry for an organization
var duplicateOrgDomain =
await _organizationDomainRepository.GetDomainByOrgIdAndDomainNameAsync(organizationDomain.OrganizationId,
organizationDomain.DomainName);
if (duplicateOrgDomain is not null)
{
throw new ConflictException("A domain already exists for this organization.");
}
try
{
if (await _dnsResolverService.ResolveAsync(organizationDomain.DomainName, organizationDomain.Txt))
{
organizationDomain.SetVerifiedDate();
}
}
catch (Exception e)
{
_logger.LogError("Error verifying Organization domain.", e);
}
organizationDomain.SetNextRunDate(_globalSettings.DomainVerification.VerificationInterval);
organizationDomain.SetLastCheckedDate();
var orgDomain = await _organizationDomainRepository.CreateAsync(organizationDomain);
await _eventService.LogOrganizationDomainEventAsync(orgDomain, EventType.OrganizationDomain_Added);
await _eventService.LogOrganizationDomainEventAsync(orgDomain,
orgDomain.VerifiedDate != null ? EventType.OrganizationDomain_Verified : EventType.OrganizationDomain_NotVerified);
return orgDomain;
}
}

View File

@ -0,0 +1,26 @@
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains.Interfaces;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Repositories;
using Bit.Core.Services;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains;
public class DeleteOrganizationDomainCommand : IDeleteOrganizationDomainCommand
{
private readonly IOrganizationDomainRepository _organizationDomainRepository;
private readonly IEventService _eventService;
public DeleteOrganizationDomainCommand(IOrganizationDomainRepository organizationDomainRepository,
IEventService eventService)
{
_organizationDomainRepository = organizationDomainRepository;
_eventService = eventService;
}
public async Task DeleteAsync(OrganizationDomain organizationDomain)
{
await _organizationDomainRepository.DeleteAsync(organizationDomain);
await _eventService.LogOrganizationDomainEventAsync(organizationDomain, EventType.OrganizationDomain_Removed);
}
}

View File

@ -0,0 +1,18 @@
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains.Interfaces;
using Bit.Core.Entities;
using Bit.Core.Repositories;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains;
public class GetOrganizationDomainByIdOrganizationIdQuery : IGetOrganizationDomainByIdOrganizationIdQuery
{
private readonly IOrganizationDomainRepository _organizationDomainRepository;
public GetOrganizationDomainByIdOrganizationIdQuery(IOrganizationDomainRepository organizationDomainRepository)
{
_organizationDomainRepository = organizationDomainRepository;
}
public async Task<OrganizationDomain> GetOrganizationDomainByIdOrganizationIdAsync(Guid id, Guid organizationId)
=> await _organizationDomainRepository.GetDomainByIdOrganizationIdAsync(id, organizationId);
}

View File

@ -0,0 +1,18 @@
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains.Interfaces;
using Bit.Core.Entities;
using Bit.Core.Repositories;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains;
public class GetOrganizationDomainByOrganizationIdQuery : IGetOrganizationDomainByOrganizationIdQuery
{
private readonly IOrganizationDomainRepository _organizationDomainRepository;
public GetOrganizationDomainByOrganizationIdQuery(IOrganizationDomainRepository organizationDomainRepository)
{
_organizationDomainRepository = organizationDomainRepository;
}
public async Task<ICollection<OrganizationDomain>> GetDomainsByOrganizationIdAsync(Guid orgId)
=> await _organizationDomainRepository.GetDomainsByOrganizationIdAsync(orgId);
}

View File

@ -0,0 +1,8 @@
using Bit.Core.Entities;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains.Interfaces;
public interface ICreateOrganizationDomainCommand
{
Task<OrganizationDomain> CreateAsync(OrganizationDomain organizationDomain);
}

View File

@ -0,0 +1,8 @@
using Bit.Core.Entities;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains.Interfaces;
public interface IDeleteOrganizationDomainCommand
{
Task DeleteAsync(OrganizationDomain organizationDomain);
}

View File

@ -0,0 +1,8 @@
using Bit.Core.Entities;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains.Interfaces;
public interface IGetOrganizationDomainByIdOrganizationIdQuery
{
Task<OrganizationDomain> GetOrganizationDomainByIdOrganizationIdAsync(Guid id, Guid organizationId);
}

View File

@ -0,0 +1,8 @@
using Bit.Core.Entities;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains.Interfaces;
public interface IGetOrganizationDomainByOrganizationIdQuery
{
Task<ICollection<OrganizationDomain>> GetDomainsByOrganizationIdAsync(Guid orgId);
}

View File

@ -0,0 +1,8 @@
using Bit.Core.Entities;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains.Interfaces;
public interface IVerifyOrganizationDomainCommand
{
Task<OrganizationDomain> VerifyOrganizationDomainAsync(OrganizationDomain organizationDomain);
}

View File

@ -0,0 +1,68 @@
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains.Interfaces;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Microsoft.Extensions.Logging;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains;
public class VerifyOrganizationDomainCommand : IVerifyOrganizationDomainCommand
{
private readonly IOrganizationDomainRepository _organizationDomainRepository;
private readonly IDnsResolverService _dnsResolverService;
private readonly IEventService _eventService;
private readonly ILogger<VerifyOrganizationDomainCommand> _logger;
public VerifyOrganizationDomainCommand(
IOrganizationDomainRepository organizationDomainRepository,
IDnsResolverService dnsResolverService,
IEventService eventService,
ILogger<VerifyOrganizationDomainCommand> logger)
{
_organizationDomainRepository = organizationDomainRepository;
_dnsResolverService = dnsResolverService;
_eventService = eventService;
_logger = logger;
}
public async Task<OrganizationDomain> VerifyOrganizationDomainAsync(OrganizationDomain domain)
{
if (domain.VerifiedDate is not null)
{
domain.SetLastCheckedDate();
await _organizationDomainRepository.ReplaceAsync(domain);
throw new ConflictException("Domain has already been verified.");
}
var claimedDomain =
await _organizationDomainRepository.GetClaimedDomainsByDomainNameAsync(domain.DomainName);
if (claimedDomain.Any())
{
domain.SetLastCheckedDate();
await _organizationDomainRepository.ReplaceAsync(domain);
throw new ConflictException("The domain is not available to be claimed.");
}
try
{
if (await _dnsResolverService.ResolveAsync(domain.DomainName, domain.Txt))
{
domain.SetVerifiedDate();
}
}
catch (Exception e)
{
_logger.LogError("Error verifying Organization domain: {domain}. {errorMessage}",
domain.DomainName, e.Message);
}
domain.SetLastCheckedDate();
await _organizationDomainRepository.ReplaceAsync(domain);
await _eventService.LogOrganizationDomainEventAsync(domain,
domain.VerifiedDate != null ? EventType.OrganizationDomain_Verified : EventType.OrganizationDomain_NotVerified);
return domain;
}
}

View File

@ -0,0 +1,54 @@
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
using Bit.Core.Exceptions;
using Bit.Core.Repositories;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers;
public class CountNewSmSeatsRequiredQuery : ICountNewSmSeatsRequiredQuery
{
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IOrganizationRepository _organizationRepository;
public CountNewSmSeatsRequiredQuery(IOrganizationUserRepository organizationUserRepository,
IOrganizationRepository organizationRepository)
{
_organizationUserRepository = organizationUserRepository;
_organizationRepository = organizationRepository;
}
public async Task<int> CountNewSmSeatsRequiredAsync(Guid organizationId, int usersToAdd)
{
if (usersToAdd == 0)
{
return 0;
}
var organization = await _organizationRepository.GetByIdAsync(organizationId);
if (organization == null)
{
throw new NotFoundException();
}
if (!organization.UseSecretsManager)
{
throw new BadRequestException("Organization does not use Secrets Manager");
}
if (!organization.SmSeats.HasValue || organization.SecretsManagerBeta)
{
return 0;
}
var occupiedSmSeats =
await _organizationUserRepository.GetOccupiedSmSeatCountByOrganizationIdAsync(organization.Id);
var availableSmSeats = organization.SmSeats.Value - occupiedSmSeats;
if (availableSmSeats >= usersToAdd)
{
return 0;
}
return usersToAdd - availableSmSeats;
}
}

View File

@ -0,0 +1,45 @@
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Repositories;
using Bit.Core.Services;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers;
public class DeleteOrganizationUserCommand : IDeleteOrganizationUserCommand
{
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IOrganizationService _organizationService;
public DeleteOrganizationUserCommand(
IOrganizationUserRepository organizationUserRepository,
IOrganizationService organizationService
)
{
_organizationUserRepository = organizationUserRepository;
_organizationService = organizationService;
}
public async Task DeleteUserAsync(Guid organizationId, Guid organizationUserId, Guid? deletingUserId)
{
await ValidateDeleteUserAsync(organizationId, organizationUserId);
await _organizationService.DeleteUserAsync(organizationId, organizationUserId, deletingUserId);
}
public async Task DeleteUserAsync(Guid organizationId, Guid organizationUserId, EventSystemUser eventSystemUser)
{
await ValidateDeleteUserAsync(organizationId, organizationUserId);
await _organizationService.DeleteUserAsync(organizationId, organizationUserId, eventSystemUser);
}
private async Task ValidateDeleteUserAsync(Guid organizationId, Guid organizationUserId)
{
var orgUser = await _organizationUserRepository.GetByIdAsync(organizationUserId);
if (orgUser == null || orgUser.OrganizationId != organizationId)
{
throw new NotFoundException("User not found.");
}
}
}

View File

@ -0,0 +1,6 @@
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
public interface ICountNewSmSeatsRequiredQuery
{
public Task<int> CountNewSmSeatsRequiredAsync(Guid organizationId, int usersToAdd);
}

View File

@ -0,0 +1,10 @@
using Bit.Core.Enums;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
public interface IDeleteOrganizationUserCommand
{
Task DeleteUserAsync(Guid organizationId, Guid organizationUserId, Guid? deletingUserId);
Task DeleteUserAsync(Guid organizationId, Guid organizationUserId, EventSystemUser eventSystemUser);
}

View File

@ -0,0 +1,8 @@
using Bit.Core.Entities;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
public interface IUpdateOrganizationUserGroupsCommand
{
Task UpdateUserGroupsAsync(OrganizationUser organizationUser, IEnumerable<Guid> groupIds, Guid? loggedInUserId);
}

View File

@ -0,0 +1,34 @@
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Repositories;
using Bit.Core.Services;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers;
public class UpdateOrganizationUserGroupsCommand : IUpdateOrganizationUserGroupsCommand
{
private readonly IEventService _eventService;
private readonly IOrganizationService _organizationService;
private readonly IOrganizationUserRepository _organizationUserRepository;
public UpdateOrganizationUserGroupsCommand(
IEventService eventService,
IOrganizationService organizationService,
IOrganizationUserRepository organizationUserRepository)
{
_eventService = eventService;
_organizationService = organizationService;
_organizationUserRepository = organizationUserRepository;
}
public async Task UpdateUserGroupsAsync(OrganizationUser organizationUser, IEnumerable<Guid> groupIds, Guid? loggedInUserId)
{
if (loggedInUserId.HasValue)
{
await _organizationService.ValidateOrganizationUserUpdatePermissions(organizationUser.OrganizationId, organizationUser.Type, null, organizationUser.GetPermissions());
}
await _organizationUserRepository.UpdateGroupsAsync(organizationUser.Id, groupIds);
await _eventService.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_UpdatedGroups);
}
}