1
0
mirror of https://github.com/bitwarden/server.git synced 2025-04-19 03:58:13 -05: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:
Addison Beck 2021-08-05 10:39:05 -04:00 committed by GitHub
parent cfc7fa071b
commit 152f1f7a9b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 210 additions and 73 deletions

View File

@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Bit.Core.Context;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Enums.Provider; using Bit.Core.Enums.Provider;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
@ -31,12 +32,14 @@ namespace Bit.CommCore.Services
private readonly IUserRepository _userRepository; private readonly IUserRepository _userRepository;
private readonly IUserService _userService; private readonly IUserService _userService;
private readonly IOrganizationService _organizationService; private readonly IOrganizationService _organizationService;
private readonly ICurrentContext _currentContext;
public ProviderService(IProviderRepository providerRepository, IProviderUserRepository providerUserRepository, public ProviderService(IProviderRepository providerRepository, IProviderUserRepository providerUserRepository,
IProviderOrganizationRepository providerOrganizationRepository, IUserRepository userRepository, IProviderOrganizationRepository providerOrganizationRepository, IUserRepository userRepository,
IUserService userService, IOrganizationService organizationService, IMailService mailService, IUserService userService, IOrganizationService organizationService, IMailService mailService,
IDataProtectionProvider dataProtectionProvider, IEventService eventService, IDataProtectionProvider dataProtectionProvider, IEventService eventService,
IOrganizationRepository organizationRepository, GlobalSettings globalSettings) IOrganizationRepository organizationRepository, GlobalSettings globalSettings,
ICurrentContext currentContext)
{ {
_providerRepository = providerRepository; _providerRepository = providerRepository;
_providerUserRepository = providerUserRepository; _providerUserRepository = providerUserRepository;
@ -49,6 +52,7 @@ namespace Bit.CommCore.Services
_eventService = eventService; _eventService = eventService;
_globalSettings = globalSettings; _globalSettings = globalSettings;
_dataProtector = dataProtectionProvider.CreateProtector("ProviderServiceDataProtector"); _dataProtector = dataProtectionProvider.CreateProtector("ProviderServiceDataProtector");
_currentContext = currentContext;
} }
public async Task CreateAsync(string ownerEmail) public async Task CreateAsync(string ownerEmail)
@ -75,9 +79,7 @@ namespace Bit.CommCore.Services
Status = ProviderUserStatusType.Confirmed, Status = ProviderUserStatusType.Confirmed,
}; };
await _providerUserRepository.CreateAsync(providerUser); await _providerUserRepository.CreateAsync(providerUser);
await SendProviderSetupInviteEmailAsync(provider, owner.Email);
var token = _dataProtector.Protect($"ProviderSetupInvite {provider.Id} {owner.Email} {CoreHelpers.ToEpocMilliseconds(DateTime.UtcNow)}");
await _mailService.SendProviderSetupInviteEmailAsync(provider, token, owner.Email);
} }
public async Task<Provider> CompleteSetupAsync(Provider provider, Guid ownerUserId, string token, string key) 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) if (provider.Id == default)
{ {
throw new ApplicationException("Cannot create provider this way."); throw new ArgumentException("Cannot create provider this way.");
} }
await _providerRepository.ReplaceAsync(provider); await _providerRepository.ReplaceAsync(provider);
} }
public async Task<List<ProviderUser>> InviteUserAsync(Guid providerId, Guid invitingUserId, public async Task<List<ProviderUser>> InviteUserAsync(ProviderUserInvite<string> invite)
ProviderUserInvite invite)
{ {
var provider = await _providerRepository.GetByIdAsync(providerId); if (!_currentContext.ProviderManageUsers(invite.ProviderId))
if (provider == null || invite?.Emails == null || !invite.Emails.Any()) {
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(); throw new NotFoundException();
} }
var providerUsers = new List<ProviderUser>(); var providerUsers = new List<ProviderUser>();
foreach (var email in invite.Emails) foreach (var email in emails)
{ {
// Make sure user is not already invited // Make sure user is not already invited
var existingProviderUserCount = var existingProviderUserCount =
await _providerUserRepository.GetCountByProviderAsync(providerId, email, false); await _providerUserRepository.GetCountByProviderAsync(invite.ProviderId, email, false);
if (existingProviderUserCount > 0) if (existingProviderUserCount > 0)
{ {
continue; continue;
@ -146,7 +155,7 @@ namespace Bit.CommCore.Services
var providerUser = new ProviderUser var providerUser = new ProviderUser
{ {
ProviderId = providerId, ProviderId = invite.ProviderId,
UserId = null, UserId = null,
Email = email.ToLowerInvariant(), Email = email.ToLowerInvariant(),
Key = null, Key = null,
@ -167,16 +176,20 @@ namespace Bit.CommCore.Services
return providerUsers; return providerUsers;
} }
public async Task<List<Tuple<ProviderUser, string>>> ResendInvitesAsync(Guid providerId, Guid invitingUserId, public async Task<List<Tuple<ProviderUser, string>>> ResendInvitesAsync(ProviderUserInvite<Guid> invite)
IEnumerable<Guid> providerUsersId)
{ {
var providerUsers = await _providerUserRepository.GetManyAsync(providerUsersId); if (!_currentContext.ProviderManageUsers(invite.ProviderId))
var provider = await _providerRepository.GetByIdAsync(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>>(); var result = new List<Tuple<ProviderUser, string>>();
foreach (var providerUser in providerUsers) 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.")); result.Add(Tuple.Create(providerUser, "User invalid."));
continue; continue;
@ -422,6 +435,23 @@ namespace Bit.CommCore.Services
await _eventService.LogProviderOrganizationEventAsync(providerOrganization, EventType.ProviderOrganization_Removed); 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) public async Task LogProviderAccessToOrganizationAsync(Guid organizationId)
{ {
if (organizationId == default) if (organizationId == default)
@ -441,7 +471,6 @@ namespace Bit.CommCore.Services
} }
} }
private async Task SendInviteAsync(ProviderUser providerUser, Provider provider) private async Task SendInviteAsync(ProviderUser providerUser, Provider provider)
{ {
var nowMillis = CoreHelpers.ToEpocMilliseconds(DateTime.UtcNow); var nowMillis = CoreHelpers.ToEpocMilliseconds(DateTime.UtcNow);

View File

@ -21,6 +21,7 @@ using NSubstitute;
using NSubstitute.ReturnsExtensions; using NSubstitute.ReturnsExtensions;
using Xunit; using Xunit;
using ProviderUser = Bit.Core.Models.Table.Provider.ProviderUser; using ProviderUser = Bit.Core.Models.Table.Provider.ProviderUser;
using Bit.Core.Context;
namespace Bit.CommCore.Test.Services namespace Bit.CommCore.Test.Services
{ {
@ -111,53 +112,64 @@ namespace Bit.CommCore.Test.Services
} }
[Theory, CustomAutoData(typeof(SutProviderCustomization))] [Theory, CustomAutoData(typeof(SutProviderCustomization))]
public async Task InviteUserAsync_ProviderIdIsInvalid_Throws(Provider provider, SutProvider<ProviderService> sutProvider) public async Task InviteUserAsync_ProviderIdIsInvalid_Throws(ProviderUserInvite<string> invite, SutProvider<ProviderService> sutProvider)
{ {
provider.Id = default; await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.InviteUserAsync(invite));
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.InviteUserAsync(provider.Id, default, default));
} }
[Theory, CustomAutoData(typeof(SutProviderCustomization))] [Theory, CustomAutoData(typeof(SutProviderCustomization))]
public async Task InviteUserAsync_EmailsInvalid_Throws(Provider provider, ProviderUserInvite providerUserInvite, public async Task InviteUserAsync_InvalidPermissions_Throws(ProviderUserInvite<string> invite, SutProvider<ProviderService> sutProvider)
SutProvider<ProviderService> sutProvider)
{ {
var providerRepository = sutProvider.GetDependency<IProviderRepository>(); sutProvider.GetDependency<ICurrentContext>().ProviderManageUsers(invite.ProviderId).Returns(false);
providerRepository.GetByIdAsync(provider.Id).Returns(provider); await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.InviteUserAsync(invite));
providerUserInvite.Emails = null;
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.InviteUserAsync(provider.Id, default, providerUserInvite));
} }
[Theory, CustomAutoData(typeof(SutProviderCustomization))] [Theory, CustomAutoData(typeof(SutProviderCustomization))]
public async Task InviteUserAsync_AlreadyInvited(Provider provider, ProviderUserInvite providerUserInvite, public async Task InviteUserAsync_EmailsInvalid_Throws(Provider provider, ProviderUserInvite<string> providerUserInvite,
SutProvider<ProviderService> sutProvider) SutProvider<ProviderService> sutProvider)
{ {
var providerRepository = sutProvider.GetDependency<IProviderRepository>(); var providerRepository = sutProvider.GetDependency<IProviderRepository>();
providerRepository.GetByIdAsync(provider.Id).Returns(provider); providerRepository.GetByIdAsync(providerUserInvite.ProviderId).Returns(provider);
providerUserInvite.UserIdentifiers = null;
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.InviteUserAsync(providerUserInvite));
}
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
public async Task InviteUserAsync_AlreadyInvited(Provider provider, ProviderUserInvite<string> providerUserInvite,
SutProvider<ProviderService> sutProvider)
{
var providerRepository = sutProvider.GetDependency<IProviderRepository>();
providerRepository.GetByIdAsync(providerUserInvite.ProviderId).Returns(provider);
var providerUserRepository = sutProvider.GetDependency<IProviderUserRepository>(); var providerUserRepository = sutProvider.GetDependency<IProviderUserRepository>();
providerUserRepository.GetCountByProviderAsync(default, default, default).ReturnsForAnyArgs(1); providerUserRepository.GetCountByProviderAsync(default, default, default).ReturnsForAnyArgs(1);
var result = await sutProvider.Sut.InviteUserAsync(provider.Id, default, providerUserInvite); var result = await sutProvider.Sut.InviteUserAsync(providerUserInvite);
Assert.Empty(result); Assert.Empty(result);
} }
[Theory, CustomAutoData(typeof(SutProviderCustomization))] [Theory, CustomAutoData(typeof(SutProviderCustomization))]
public async Task InviteUserAsync_Success(Provider provider, ProviderUserInvite providerUserInvite, public async Task InviteUserAsync_Success(Provider provider, ProviderUserInvite<string> providerUserInvite,
SutProvider<ProviderService> sutProvider) SutProvider<ProviderService> sutProvider)
{ {
var providerRepository = sutProvider.GetDependency<IProviderRepository>(); var providerRepository = sutProvider.GetDependency<IProviderRepository>();
providerRepository.GetByIdAsync(provider.Id).Returns(provider); providerRepository.GetByIdAsync(providerUserInvite.ProviderId).Returns(provider);
var providerUserRepository = sutProvider.GetDependency<IProviderUserRepository>(); var providerUserRepository = sutProvider.GetDependency<IProviderUserRepository>();
providerUserRepository.GetCountByProviderAsync(default, default, default).ReturnsForAnyArgs(0); providerUserRepository.GetCountByProviderAsync(default, default, default).ReturnsForAnyArgs(0);
var result = await sutProvider.Sut.InviteUserAsync(provider.Id, default, providerUserInvite); var result = await sutProvider.Sut.InviteUserAsync(providerUserInvite);
Assert.Equal(providerUserInvite.Emails.Count(), result.Count); Assert.Equal(providerUserInvite.UserIdentifiers.Count(), result.Count);
Assert.True(result.TrueForAll(pu => pu.Status == ProviderUserStatusType.Invited), "Status must be invited"); Assert.True(result.TrueForAll(pu => pu.Status == ProviderUserStatusType.Invited), "Status must be invited");
Assert.True(result.TrueForAll(pu => pu.ProviderId == provider.Id), "Provider Id must be correct"); Assert.True(result.TrueForAll(pu => pu.ProviderId == providerUserInvite.ProviderId), "Provider Id must be correct");
} }
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
public async Task ResendInviteUserAsync_InvalidPermissions_Throws(ProviderUserInvite<Guid> invite, SutProvider<ProviderService> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>().ProviderManageUsers(invite.ProviderId).Returns(false);
await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.ResendInvitesAsync(invite));
}
[Theory, CustomAutoData(typeof(SutProviderCustomization))] [Theory, CustomAutoData(typeof(SutProviderCustomization))]
public async Task ResendInvitesAsync_Errors(Provider provider, public async Task ResendInvitesAsync_Errors(Provider provider,
@ -175,7 +187,12 @@ namespace Bit.CommCore.Test.Services
var providerUserRepository = sutProvider.GetDependency<IProviderUserRepository>(); var providerUserRepository = sutProvider.GetDependency<IProviderUserRepository>();
providerUserRepository.GetManyAsync(default).ReturnsForAnyArgs(providerUsers.ToList()); providerUserRepository.GetManyAsync(default).ReturnsForAnyArgs(providerUsers.ToList());
var result = await sutProvider.Sut.ResendInvitesAsync(provider.Id, default, providerUsers.Select(pu => pu.Id)); var invite = new ProviderUserInvite<Guid>
{
UserIdentifiers = providerUsers.Select(pu => pu.Id),
ProviderId = provider.Id
};
var result = await sutProvider.Sut.ResendInvitesAsync(invite);
Assert.Equal("", result[0].Item2); Assert.Equal("", result[0].Item2);
Assert.Equal("User invalid.", result[1].Item2); Assert.Equal("User invalid.", result[1].Item2);
Assert.Equal("User invalid.", result[2].Item2); Assert.Equal("User invalid.", result[2].Item2);
@ -197,7 +214,12 @@ namespace Bit.CommCore.Test.Services
var providerUserRepository = sutProvider.GetDependency<IProviderUserRepository>(); var providerUserRepository = sutProvider.GetDependency<IProviderUserRepository>();
providerUserRepository.GetManyAsync(default).ReturnsForAnyArgs(providerUsers.ToList()); providerUserRepository.GetManyAsync(default).ReturnsForAnyArgs(providerUsers.ToList());
var result = await sutProvider.Sut.ResendInvitesAsync(provider.Id, default, providerUsers.Select(pu => pu.Id)); var invite = new ProviderUserInvite<Guid>
{
UserIdentifiers = providerUsers.Select(pu => pu.Id),
ProviderId = provider.Id
};
var result = await sutProvider.Sut.ResendInvitesAsync(invite);
Assert.True(result.All(r => r.Item2 == "")); Assert.True(result.All(r => r.Item2 == ""));
} }

View File

@ -133,5 +133,12 @@ namespace Bit.Admin.Controllers
return RedirectToAction("Index"); return RedirectToAction("Index");
} }
public async Task<IActionResult> ResendInvite(Guid ownerId, Guid providerId)
{
await _providerService.ResendProviderSetupInviteEmailAsync(providerId, ownerId);
TempData["InviteResentTo"] = ownerId;
return RedirectToAction("Edit", new { id = providerId });
}
} }
} }

View File

@ -14,17 +14,11 @@ namespace Bit.Admin.Models
{ {
Provider = provider; Provider = provider;
UserCount = providerUsers.Count(); UserCount = providerUsers.Count();
ProviderAdmins = providerUsers.Where(u => u.Type == ProviderUserType.ProviderAdmin);
ProviderAdmins = string.Join(", ",
providerUsers
.Where(u => u.Type == ProviderUserType.ProviderAdmin && u.Status == ProviderUserStatusType.Confirmed)
.Select(u => u.Email));
} }
public int UserCount { get; set; } public int UserCount { get; set; }
public Provider Provider { get; set; } public Provider Provider { get; set; }
public IEnumerable<ProviderUserUserDetails> ProviderAdmins { get; set; }
public string ProviderAdmins { get; set; }
} }
} }

View File

@ -0,0 +1,58 @@
@model ProviderViewModel
<h2>Provider Admins</h2>
<div class="row">
<div class="col-8">
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead>
<tr>
<th style="width: 190px;">Email</th>
<th style="width: 40px;">Status</th>
<th style="width: 30px;"></th>
</tr>
</thead>
<tbody>
@if(!Model.ProviderAdmins.Any())
{
<tr>
<td colspan="6">No results to list.</td>
</tr>
}
else
{
@foreach(var admin in Model.ProviderAdmins)
{
<tr>
<td class="align-middle">
@admin.Email
</td>
<td class="align-middle">
@admin.Status
</td>
<td>
@if(admin.Status.Equals(ProviderUserStatusType.Confirmed)
&& @Model.Provider.Status.Equals(ProviderStatusType.Pending))
{
@if(@TempData["InviteResentTo"] != null && @TempData["InviteResentTo"].ToString() == @admin.UserId.Value.ToString())
{
<button class="btn btn-outline-success btn-sm disabled" disabled>Invite Resent!</button>
}
else
{
<a class="btn btn-outline-secondary btn-sm"
data-id="@admin.Id" asp-controller="Providers"
asp-action="ResendInvite" asp-route-ownerId="@admin.UserId"
asp-route-providerId="@Model.Provider.Id">
Resend Setup Invite
</a>
}
}
</td>
</tr>
}
}
</tbody>
</table>
</div>
</div>
</div>

View File

@ -7,6 +7,7 @@
<h2>Provider Information</h2> <h2>Provider Information</h2>
@await Html.PartialAsync("_ViewInformation", Model) @await Html.PartialAsync("_ViewInformation", Model)
@await Html.PartialAsync("Admins", Model)
<form method="post" id="edit-form"> <form method="post" id="edit-form">
<h2>General</h2> <h2>General</h2>
<div class="row"> <div class="row">

View File

@ -7,6 +7,7 @@
<h2>Information</h2> <h2>Information</h2>
@await Html.PartialAsync("_ViewInformation", Model) @await Html.PartialAsync("_ViewInformation", Model)
@await Html.PartialAsync("Admins", Model)
<form asp-action="Delete" asp-route-id="@Model.Provider.Id" <form asp-action="Delete" asp-route-id="@Model.Provider.Id"
onsubmit="return confirm('Are you sure you want to delete this provider (@Model.Provider.Name)?')"> onsubmit="return confirm('Are you sure you want to delete this provider (@Model.Provider.Name)?')">
<button class="btn btn-danger" type="submit">Delete</button> <button class="btn btn-danger" type="submit">Delete</button>

View File

@ -9,9 +9,6 @@
<dt class="col-sm-4 col-lg-3">Users</dt> <dt class="col-sm-4 col-lg-3">Users</dt>
<dd class="col-sm-8 col-lg-9">@Model.UserCount</dd> <dd class="col-sm-8 col-lg-9">@Model.UserCount</dd>
<dt class="col-sm-4 col-lg-3">ProviderAdmins</dt>
<dd class="col-sm-8 col-lg-9">@(string.IsNullOrWhiteSpace(Model.ProviderAdmins) ? "None" : Model.ProviderAdmins)</dd>
<dt class="col-sm-4 col-lg-3">Created</dt> <dt class="col-sm-4 col-lg-3">Created</dt>
<dd class="col-sm-8 col-lg-9">@Model.Provider.CreationDate.ToString()</dd> <dd class="col-sm-8 col-lg-9">@Model.Provider.CreationDate.ToString()</dd>

View File

@ -1,5 +1,6 @@
@using Microsoft.AspNetCore.Identity @using Microsoft.AspNetCore.Identity
@using Bit.Admin @using Bit.Admin
@using Bit.Admin.Models @using Bit.Admin.Models
@using Bit.Core.Enums.Provider
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper "*, Admin" @addTagHelper "*, Admin"

View File

@ -67,8 +67,9 @@ namespace Bit.Api.Controllers
throw new NotFoundException(); throw new NotFoundException();
} }
var userId = _userService.GetProperUserId(User); var invite = ProviderUserInviteFactory.CreateIntialInvite(model.Emails, model.Type.Value,
await _providerService.InviteUserAsync(providerId, userId.Value, new ProviderUserInvite(model)); _userService.GetProperUserId(User).Value, providerId);
await _providerService.InviteUserAsync(invite);
} }
[HttpPost("reinvite")] [HttpPost("reinvite")]
@ -79,8 +80,8 @@ namespace Bit.Api.Controllers
throw new NotFoundException(); throw new NotFoundException();
} }
var userId = _userService.GetProperUserId(User); var invite = ProviderUserInviteFactory.CreateReinvite(model.Ids, _userService.GetProperUserId(User).Value, providerId);
var result = await _providerService.ResendInvitesAsync(providerId, userId.Value, model.Ids); var result = await _providerService.ResendInvitesAsync(invite);
return new ListResponseModel<ProviderUserBulkResponseModel>( return new ListResponseModel<ProviderUserBulkResponseModel>(
result.Select(t => new ProviderUserBulkResponseModel(t.Item1.Id, t.Item2))); result.Select(t => new ProviderUserBulkResponseModel(t.Item1.Id, t.Item2)));
} }
@ -93,8 +94,9 @@ namespace Bit.Api.Controllers
throw new NotFoundException(); throw new NotFoundException();
} }
var userId = _userService.GetProperUserId(User); var invite = ProviderUserInviteFactory.CreateReinvite(new [] { id },
await _providerService.ResendInvitesAsync(providerId, userId.Value, new [] { id }); _userService.GetProperUserId(User).Value, providerId);
await _providerService.ResendInvitesAsync(invite);
} }
[HttpPost("{id:guid}/accept")] [HttpPost("{id:guid}/accept")]

View File

@ -1,19 +1,39 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using Bit.Core.Enums.Provider; using Bit.Core.Enums.Provider;
using Bit.Core.Models.Api; using Bit.Core.Models.Api;
using Bit.Core.Models.Data;
namespace Bit.Core.Models.Business.Provider namespace Bit.Core.Models.Business.Provider
{ {
public class ProviderUserInvite public class ProviderUserInvite<T>
{ {
public IEnumerable<string> Emails { get; set; } public IEnumerable<T> UserIdentifiers { get; set; }
public ProviderUserType Type { get; set; } public ProviderUserType Type { get; set; }
public Guid InvitingUserId { get; set; }
public Guid ProviderId { get; set; }
}
public ProviderUserInvite(ProviderUserInviteRequestModel requestModel) public static class ProviderUserInviteFactory
{ {
Emails = requestModel.Emails; public static ProviderUserInvite<string> CreateIntialInvite(IEnumerable<string> inviteeEmails, ProviderUserType type, Guid invitingUserId, Guid providerId)
Type = requestModel.Type.Value; {
return new ProviderUserInvite<string>
{
UserIdentifiers = inviteeEmails,
Type = type,
InvitingUserId = invitingUserId,
ProviderId = providerId
};
}
public static ProviderUserInvite<Guid> CreateReinvite(IEnumerable<Guid> inviteeUserIds, Guid invitingUserId, Guid providerId)
{
return new ProviderUserInvite<Guid>
{
UserIdentifiers = inviteeUserIds,
InvitingUserId = invitingUserId,
ProviderId = providerId
};
} }
} }
} }

View File

@ -14,9 +14,8 @@ namespace Bit.Core.Services
Task<Provider> CompleteSetupAsync(Provider provider, Guid ownerUserId, string token, string key); Task<Provider> CompleteSetupAsync(Provider provider, Guid ownerUserId, string token, string key);
Task UpdateAsync(Provider provider, bool updateBilling = false); Task UpdateAsync(Provider provider, bool updateBilling = false);
Task<List<ProviderUser>> InviteUserAsync(Guid providerId, Guid invitingUserId, ProviderUserInvite providerUserInvite); Task<List<ProviderUser>> InviteUserAsync(ProviderUserInvite<string> invite);
Task<List<Tuple<ProviderUser, string>>> ResendInvitesAsync(Guid providerId, Guid invitingUserId, Task<List<Tuple<ProviderUser, string>>> ResendInvitesAsync(ProviderUserInvite<Guid> invite);
IEnumerable<Guid> providerUsersId);
Task<ProviderUser> AcceptUserAsync(Guid providerUserId, User user, string token); Task<ProviderUser> AcceptUserAsync(Guid providerUserId, User user, string token);
Task<List<Tuple<ProviderUser, string>>> ConfirmUsersAsync(Guid providerId, Dictionary<Guid, string> keys, Guid confirmingUserId); Task<List<Tuple<ProviderUser, string>>> ConfirmUsersAsync(Guid providerId, Dictionary<Guid, string> keys, Guid confirmingUserId);
@ -29,5 +28,7 @@ namespace Bit.Core.Services
string clientOwnerEmail, User user); string clientOwnerEmail, User user);
Task RemoveOrganizationAsync(Guid providerId, Guid providerOrganizationId, Guid removingUserId); Task RemoveOrganizationAsync(Guid providerId, Guid providerOrganizationId, Guid removingUserId);
Task LogProviderAccessToOrganizationAsync(Guid organizationId); Task LogProviderAccessToOrganizationAsync(Guid organizationId);
Task ResendProviderSetupInviteEmailAsync(Guid providerId, Guid ownerId);
} }
} }

View File

@ -16,9 +16,9 @@ namespace Bit.Core.Services
public Task UpdateAsync(Provider provider, bool updateBilling = false) => throw new NotImplementedException(); public Task UpdateAsync(Provider provider, bool updateBilling = false) => throw new NotImplementedException();
public Task<List<ProviderUser>> InviteUserAsync(Guid providerId, Guid invitingUserId, ProviderUserInvite providerUserInvite) => throw new NotImplementedException(); public Task<List<ProviderUser>> InviteUserAsync(ProviderUserInvite<string> invite) => throw new NotImplementedException();
public Task<List<Tuple<ProviderUser, string>>> ResendInvitesAsync(Guid providerId, Guid invitingUserId, IEnumerable<Guid> providerUsersId) => throw new NotImplementedException(); public Task<List<Tuple<ProviderUser, string>>> ResendInvitesAsync(ProviderUserInvite<Guid> invite) => throw new NotImplementedException();
public Task<ProviderUser> AcceptUserAsync(Guid providerUserId, User user, string token) => throw new NotImplementedException(); public Task<ProviderUser> AcceptUserAsync(Guid providerUserId, User user, string token) => throw new NotImplementedException();
@ -29,9 +29,13 @@ namespace Bit.Core.Services
public Task<List<Tuple<ProviderUser, string>>> DeleteUsersAsync(Guid providerId, IEnumerable<Guid> providerUserIds, Guid deletingUserId) => throw new NotImplementedException(); public Task<List<Tuple<ProviderUser, string>>> DeleteUsersAsync(Guid providerId, IEnumerable<Guid> providerUserIds, Guid deletingUserId) => throw new NotImplementedException();
public Task AddOrganization(Guid providerId, Guid organizationId, Guid addingUserId, string key) => throw new NotImplementedException(); public Task AddOrganization(Guid providerId, Guid organizationId, Guid addingUserId, string key) => throw new NotImplementedException();
public Task<ProviderOrganization> CreateOrganizationAsync(Guid providerId, OrganizationSignup organizationSignup, string clientOwnerEmail, User user) => throw new NotImplementedException(); public Task<ProviderOrganization> CreateOrganizationAsync(Guid providerId, OrganizationSignup organizationSignup, string clientOwnerEmail, User user) => throw new NotImplementedException();
public Task RemoveOrganizationAsync(Guid providerId, Guid providerOrganizationId, Guid removingUserId) => throw new NotImplementedException(); public Task RemoveOrganizationAsync(Guid providerId, Guid providerOrganizationId, Guid removingUserId) => throw new NotImplementedException();
public Task LogProviderAccessToOrganizationAsync(Guid organizationId) => throw new NotImplementedException(); public Task LogProviderAccessToOrganizationAsync(Guid organizationId) => throw new NotImplementedException();
public Task ResendProviderSetupInviteEmailAsync(Guid providerId, Guid userId) => throw new NotImplementedException();
} }
} }

View File

@ -16,7 +16,7 @@ BEGIN
[Date] >= @StartDate [Date] >= @StartDate
AND (@BeforeDate IS NOT NULL OR [Date] <= @EndDate) AND (@BeforeDate IS NOT NULL OR [Date] <= @EndDate)
AND (@BeforeDate IS NULL OR [Date] < @BeforeDate) AND (@BeforeDate IS NULL OR [Date] < @BeforeDate)
AND [Providerid] = @ProviderId AND [ProviderId] = @ProviderId
ORDER BY [Date] DESC ORDER BY [Date] DESC
OFFSET 0 ROWS OFFSET 0 ROWS
FETCH NEXT @PageSize ROWS ONLY FETCH NEXT @PageSize ROWS ONLY