mirror of
https://github.com/bitwarden/server
synced 2025-12-17 00:33:23 +00:00
Allow Resending Provider Setup Emails From The Admin Portal (#1497)
* Added a button for resending provider setup emails * Fixed a case typo in a stored procedure * Turned a couple lines of code into a method call * Added service level validation against inviting users for MSP invites * Code review improvements for provider invites created a factory for provider user invites wrote tests for provider invite permissions" * changed a few exception types
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Enums.Provider;
|
||||
using Bit.Core.Exceptions;
|
||||
@@ -31,12 +32,14 @@ namespace Bit.CommCore.Services
|
||||
private readonly IUserRepository _userRepository;
|
||||
private readonly IUserService _userService;
|
||||
private readonly IOrganizationService _organizationService;
|
||||
private readonly ICurrentContext _currentContext;
|
||||
|
||||
public ProviderService(IProviderRepository providerRepository, IProviderUserRepository providerUserRepository,
|
||||
IProviderOrganizationRepository providerOrganizationRepository, IUserRepository userRepository,
|
||||
IUserService userService, IOrganizationService organizationService, IMailService mailService,
|
||||
IDataProtectionProvider dataProtectionProvider, IEventService eventService,
|
||||
IOrganizationRepository organizationRepository, GlobalSettings globalSettings)
|
||||
IOrganizationRepository organizationRepository, GlobalSettings globalSettings,
|
||||
ICurrentContext currentContext)
|
||||
{
|
||||
_providerRepository = providerRepository;
|
||||
_providerUserRepository = providerUserRepository;
|
||||
@@ -49,6 +52,7 @@ namespace Bit.CommCore.Services
|
||||
_eventService = eventService;
|
||||
_globalSettings = globalSettings;
|
||||
_dataProtector = dataProtectionProvider.CreateProtector("ProviderServiceDataProtector");
|
||||
_currentContext = currentContext;
|
||||
}
|
||||
|
||||
public async Task CreateAsync(string ownerEmail)
|
||||
@@ -75,9 +79,7 @@ namespace Bit.CommCore.Services
|
||||
Status = ProviderUserStatusType.Confirmed,
|
||||
};
|
||||
await _providerUserRepository.CreateAsync(providerUser);
|
||||
|
||||
var token = _dataProtector.Protect($"ProviderSetupInvite {provider.Id} {owner.Email} {CoreHelpers.ToEpocMilliseconds(DateTime.UtcNow)}");
|
||||
await _mailService.SendProviderSetupInviteEmailAsync(provider, token, owner.Email);
|
||||
await SendProviderSetupInviteEmailAsync(provider, owner.Email);
|
||||
}
|
||||
|
||||
public async Task<Provider> CompleteSetupAsync(Provider provider, Guid ownerUserId, string token, string key)
|
||||
@@ -118,27 +120,34 @@ namespace Bit.CommCore.Services
|
||||
{
|
||||
if (provider.Id == default)
|
||||
{
|
||||
throw new ApplicationException("Cannot create provider this way.");
|
||||
throw new ArgumentException("Cannot create provider this way.");
|
||||
}
|
||||
|
||||
await _providerRepository.ReplaceAsync(provider);
|
||||
}
|
||||
|
||||
public async Task<List<ProviderUser>> InviteUserAsync(Guid providerId, Guid invitingUserId,
|
||||
ProviderUserInvite invite)
|
||||
public async Task<List<ProviderUser>> InviteUserAsync(ProviderUserInvite<string> invite)
|
||||
{
|
||||
var provider = await _providerRepository.GetByIdAsync(providerId);
|
||||
if (provider == null || invite?.Emails == null || !invite.Emails.Any())
|
||||
if (!_currentContext.ProviderManageUsers(invite.ProviderId))
|
||||
{
|
||||
throw new InvalidOperationException("Invalid permissions.");
|
||||
}
|
||||
|
||||
var emails = invite?.UserIdentifiers;
|
||||
var invitingUser = await _providerUserRepository.GetByProviderUserAsync(invite.ProviderId, invite.InvitingUserId);
|
||||
|
||||
var provider = await _providerRepository.GetByIdAsync(invite.ProviderId);
|
||||
if (provider == null || emails == null || !emails.Any())
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var providerUsers = new List<ProviderUser>();
|
||||
foreach (var email in invite.Emails)
|
||||
foreach (var email in emails)
|
||||
{
|
||||
// Make sure user is not already invited
|
||||
var existingProviderUserCount =
|
||||
await _providerUserRepository.GetCountByProviderAsync(providerId, email, false);
|
||||
await _providerUserRepository.GetCountByProviderAsync(invite.ProviderId, email, false);
|
||||
if (existingProviderUserCount > 0)
|
||||
{
|
||||
continue;
|
||||
@@ -146,7 +155,7 @@ namespace Bit.CommCore.Services
|
||||
|
||||
var providerUser = new ProviderUser
|
||||
{
|
||||
ProviderId = providerId,
|
||||
ProviderId = invite.ProviderId,
|
||||
UserId = null,
|
||||
Email = email.ToLowerInvariant(),
|
||||
Key = null,
|
||||
@@ -167,16 +176,20 @@ namespace Bit.CommCore.Services
|
||||
return providerUsers;
|
||||
}
|
||||
|
||||
public async Task<List<Tuple<ProviderUser, string>>> ResendInvitesAsync(Guid providerId, Guid invitingUserId,
|
||||
IEnumerable<Guid> providerUsersId)
|
||||
public async Task<List<Tuple<ProviderUser, string>>> ResendInvitesAsync(ProviderUserInvite<Guid> invite)
|
||||
{
|
||||
var providerUsers = await _providerUserRepository.GetManyAsync(providerUsersId);
|
||||
var provider = await _providerRepository.GetByIdAsync(providerId);
|
||||
if (!_currentContext.ProviderManageUsers(invite.ProviderId))
|
||||
{
|
||||
throw new BadRequestException("Invalid permissions.");
|
||||
}
|
||||
|
||||
var providerUsers = await _providerUserRepository.GetManyAsync(invite.UserIdentifiers);
|
||||
var provider = await _providerRepository.GetByIdAsync(invite.ProviderId);
|
||||
|
||||
var result = new List<Tuple<ProviderUser, string>>();
|
||||
foreach (var providerUser in providerUsers)
|
||||
{
|
||||
if (providerUser.Status != ProviderUserStatusType.Invited || providerUser.ProviderId != providerId)
|
||||
if (providerUser.Status != ProviderUserStatusType.Invited || providerUser.ProviderId != invite.ProviderId)
|
||||
{
|
||||
result.Add(Tuple.Create(providerUser, "User invalid."));
|
||||
continue;
|
||||
@@ -422,6 +435,23 @@ namespace Bit.CommCore.Services
|
||||
await _eventService.LogProviderOrganizationEventAsync(providerOrganization, EventType.ProviderOrganization_Removed);
|
||||
}
|
||||
|
||||
public async Task ResendProviderSetupInviteEmailAsync(Guid providerId, Guid ownerId)
|
||||
{
|
||||
var provider = await _providerRepository.GetByIdAsync(providerId);
|
||||
var owner = await _userRepository.GetByIdAsync(ownerId);
|
||||
if (owner == null)
|
||||
{
|
||||
throw new BadRequestException("Invalid owner.");
|
||||
}
|
||||
await SendProviderSetupInviteEmailAsync(provider, owner.Email);
|
||||
}
|
||||
|
||||
private async Task SendProviderSetupInviteEmailAsync(Provider provider, string ownerEmail)
|
||||
{
|
||||
var token = _dataProtector.Protect($"ProviderSetupInvite {provider.Id} {ownerEmail} {CoreHelpers.ToEpocMilliseconds(DateTime.UtcNow)}");
|
||||
await _mailService.SendProviderSetupInviteEmailAsync(provider, token, ownerEmail);
|
||||
}
|
||||
|
||||
public async Task LogProviderAccessToOrganizationAsync(Guid organizationId)
|
||||
{
|
||||
if (organizationId == default)
|
||||
@@ -441,7 +471,6 @@ namespace Bit.CommCore.Services
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private async Task SendInviteAsync(ProviderUser providerUser, Provider provider)
|
||||
{
|
||||
var nowMillis = CoreHelpers.ToEpocMilliseconds(DateTime.UtcNow);
|
||||
|
||||
Reference in New Issue
Block a user