From 152f1f7a9be17b56604175d88ff9e122158d564c Mon Sep 17 00:00:00 2001 From: Addison Beck Date: Thu, 5 Aug 2021 10:39:05 -0400 Subject: [PATCH] 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 --- .../src/CommCore/Services/ProviderService.cs | 65 ++++++++++++----- .../Services/ProviderServiceTests.cs | 72 ++++++++++++------- src/Admin/Controllers/ProvidersController.cs | 7 ++ src/Admin/Models/ProviderViewModel.cs | 10 +-- src/Admin/Views/Providers/Admins.cshtml | 58 +++++++++++++++ src/Admin/Views/Providers/Edit.cshtml | 1 + src/Admin/Views/Providers/View.cshtml | 1 + .../Views/Providers/_ViewInformation.cshtml | 3 - src/Admin/Views/_ViewImports.cshtml | 3 +- .../Controllers/ProviderUsersController.cs | 14 ++-- .../Business/Provider/ProviderUserInvite.cs | 32 +++++++-- src/Core/Services/IProviderService.cs | 7 +- .../NoopProviderService.cs | 8 ++- .../Event_ReadPageByProviderId.sql | 2 +- 14 files changed, 210 insertions(+), 73 deletions(-) create mode 100644 src/Admin/Views/Providers/Admins.cshtml diff --git a/bitwarden_license/src/CommCore/Services/ProviderService.cs b/bitwarden_license/src/CommCore/Services/ProviderService.cs index 896c9204f..88c60546a 100644 --- a/bitwarden_license/src/CommCore/Services/ProviderService.cs +++ b/bitwarden_license/src/CommCore/Services/ProviderService.cs @@ -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 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> InviteUserAsync(Guid providerId, Guid invitingUserId, - ProviderUserInvite invite) + public async Task> InviteUserAsync(ProviderUserInvite 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(); - 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>> ResendInvitesAsync(Guid providerId, Guid invitingUserId, - IEnumerable providerUsersId) + public async Task>> ResendInvitesAsync(ProviderUserInvite 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>(); 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); diff --git a/bitwarden_license/test/CmmCore.Test/Services/ProviderServiceTests.cs b/bitwarden_license/test/CmmCore.Test/Services/ProviderServiceTests.cs index 1dd41a3e3..c52fe31b1 100644 --- a/bitwarden_license/test/CmmCore.Test/Services/ProviderServiceTests.cs +++ b/bitwarden_license/test/CmmCore.Test/Services/ProviderServiceTests.cs @@ -21,6 +21,7 @@ using NSubstitute; using NSubstitute.ReturnsExtensions; using Xunit; using ProviderUser = Bit.Core.Models.Table.Provider.ProviderUser; +using Bit.Core.Context; namespace Bit.CommCore.Test.Services { @@ -111,53 +112,64 @@ namespace Bit.CommCore.Test.Services } [Theory, CustomAutoData(typeof(SutProviderCustomization))] - public async Task InviteUserAsync_ProviderIdIsInvalid_Throws(Provider provider, SutProvider sutProvider) - { - provider.Id = default; - - await Assert.ThrowsAsync(() => sutProvider.Sut.InviteUserAsync(provider.Id, default, default)); + public async Task InviteUserAsync_ProviderIdIsInvalid_Throws(ProviderUserInvite invite, SutProvider sutProvider) + { + await Assert.ThrowsAsync(() => sutProvider.Sut.InviteUserAsync(invite)); } - - [Theory, CustomAutoData(typeof(SutProviderCustomization))] - public async Task InviteUserAsync_EmailsInvalid_Throws(Provider provider, ProviderUserInvite providerUserInvite, - SutProvider sutProvider) - { - var providerRepository = sutProvider.GetDependency(); - providerRepository.GetByIdAsync(provider.Id).Returns(provider); - providerUserInvite.Emails = null; - - await Assert.ThrowsAsync(() => sutProvider.Sut.InviteUserAsync(provider.Id, default, providerUserInvite)); + [Theory, CustomAutoData(typeof(SutProviderCustomization))] + public async Task InviteUserAsync_InvalidPermissions_Throws(ProviderUserInvite invite, SutProvider sutProvider) + { + sutProvider.GetDependency().ProviderManageUsers(invite.ProviderId).Returns(false); + await Assert.ThrowsAsync(() => sutProvider.Sut.InviteUserAsync(invite)); } [Theory, CustomAutoData(typeof(SutProviderCustomization))] - public async Task InviteUserAsync_AlreadyInvited(Provider provider, ProviderUserInvite providerUserInvite, + public async Task InviteUserAsync_EmailsInvalid_Throws(Provider provider, ProviderUserInvite providerUserInvite, SutProvider sutProvider) { var providerRepository = sutProvider.GetDependency(); - providerRepository.GetByIdAsync(provider.Id).Returns(provider); + providerRepository.GetByIdAsync(providerUserInvite.ProviderId).Returns(provider); + + providerUserInvite.UserIdentifiers = null; + + await Assert.ThrowsAsync(() => sutProvider.Sut.InviteUserAsync(providerUserInvite)); + } + + [Theory, CustomAutoData(typeof(SutProviderCustomization))] + public async Task InviteUserAsync_AlreadyInvited(Provider provider, ProviderUserInvite providerUserInvite, + SutProvider sutProvider) + { + var providerRepository = sutProvider.GetDependency(); + providerRepository.GetByIdAsync(providerUserInvite.ProviderId).Returns(provider); var providerUserRepository = sutProvider.GetDependency(); 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); } [Theory, CustomAutoData(typeof(SutProviderCustomization))] - public async Task InviteUserAsync_Success(Provider provider, ProviderUserInvite providerUserInvite, + public async Task InviteUserAsync_Success(Provider provider, ProviderUserInvite providerUserInvite, SutProvider sutProvider) { var providerRepository = sutProvider.GetDependency(); - providerRepository.GetByIdAsync(provider.Id).Returns(provider); + providerRepository.GetByIdAsync(providerUserInvite.ProviderId).Returns(provider); var providerUserRepository = sutProvider.GetDependency(); providerUserRepository.GetCountByProviderAsync(default, default, default).ReturnsForAnyArgs(0); - var result = await sutProvider.Sut.InviteUserAsync(provider.Id, default, providerUserInvite); - Assert.Equal(providerUserInvite.Emails.Count(), result.Count); + var result = await sutProvider.Sut.InviteUserAsync(providerUserInvite); + 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.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 invite, SutProvider sutProvider) + { + sutProvider.GetDependency().ProviderManageUsers(invite.ProviderId).Returns(false); + await Assert.ThrowsAsync(() => sutProvider.Sut.ResendInvitesAsync(invite)); + } [Theory, CustomAutoData(typeof(SutProviderCustomization))] public async Task ResendInvitesAsync_Errors(Provider provider, @@ -175,7 +187,12 @@ namespace Bit.CommCore.Test.Services var providerUserRepository = sutProvider.GetDependency(); providerUserRepository.GetManyAsync(default).ReturnsForAnyArgs(providerUsers.ToList()); - var result = await sutProvider.Sut.ResendInvitesAsync(provider.Id, default, providerUsers.Select(pu => pu.Id)); + var invite = new ProviderUserInvite + { + UserIdentifiers = providerUsers.Select(pu => pu.Id), + ProviderId = provider.Id + }; + var result = await sutProvider.Sut.ResendInvitesAsync(invite); Assert.Equal("", result[0].Item2); Assert.Equal("User invalid.", result[1].Item2); Assert.Equal("User invalid.", result[2].Item2); @@ -197,7 +214,12 @@ namespace Bit.CommCore.Test.Services var providerUserRepository = sutProvider.GetDependency(); providerUserRepository.GetManyAsync(default).ReturnsForAnyArgs(providerUsers.ToList()); - var result = await sutProvider.Sut.ResendInvitesAsync(provider.Id, default, providerUsers.Select(pu => pu.Id)); + var invite = new ProviderUserInvite + { + UserIdentifiers = providerUsers.Select(pu => pu.Id), + ProviderId = provider.Id + }; + var result = await sutProvider.Sut.ResendInvitesAsync(invite); Assert.True(result.All(r => r.Item2 == "")); } diff --git a/src/Admin/Controllers/ProvidersController.cs b/src/Admin/Controllers/ProvidersController.cs index 15b7ff257..7f6db8097 100644 --- a/src/Admin/Controllers/ProvidersController.cs +++ b/src/Admin/Controllers/ProvidersController.cs @@ -133,5 +133,12 @@ namespace Bit.Admin.Controllers return RedirectToAction("Index"); } + + public async Task ResendInvite(Guid ownerId, Guid providerId) + { + await _providerService.ResendProviderSetupInviteEmailAsync(providerId, ownerId); + TempData["InviteResentTo"] = ownerId; + return RedirectToAction("Edit", new { id = providerId }); + } } } diff --git a/src/Admin/Models/ProviderViewModel.cs b/src/Admin/Models/ProviderViewModel.cs index 51d854fb3..6a5732d7f 100644 --- a/src/Admin/Models/ProviderViewModel.cs +++ b/src/Admin/Models/ProviderViewModel.cs @@ -14,17 +14,11 @@ namespace Bit.Admin.Models { Provider = provider; UserCount = providerUsers.Count(); - - ProviderAdmins = string.Join(", ", - providerUsers - .Where(u => u.Type == ProviderUserType.ProviderAdmin && u.Status == ProviderUserStatusType.Confirmed) - .Select(u => u.Email)); + ProviderAdmins = providerUsers.Where(u => u.Type == ProviderUserType.ProviderAdmin); } public int UserCount { get; set; } - public Provider Provider { get; set; } - - public string ProviderAdmins { get; set; } + public IEnumerable ProviderAdmins { get; set; } } } diff --git a/src/Admin/Views/Providers/Admins.cshtml b/src/Admin/Views/Providers/Admins.cshtml new file mode 100644 index 000000000..397f6fa30 --- /dev/null +++ b/src/Admin/Views/Providers/Admins.cshtml @@ -0,0 +1,58 @@ +@model ProviderViewModel +

Provider Admins

+
+
+
+ + + + + + + + + + @if(!Model.ProviderAdmins.Any()) + { + + + + } + else + { + @foreach(var admin in Model.ProviderAdmins) + { + + + + + + } + } + +
EmailStatus
No results to list.
+ @admin.Email + + @admin.Status + + @if(admin.Status.Equals(ProviderUserStatusType.Confirmed) + && @Model.Provider.Status.Equals(ProviderStatusType.Pending)) + { + @if(@TempData["InviteResentTo"] != null && @TempData["InviteResentTo"].ToString() == @admin.UserId.Value.ToString()) + { + + } + else + { + + Resend Setup Invite + + } + } +
+
+
+
diff --git a/src/Admin/Views/Providers/Edit.cshtml b/src/Admin/Views/Providers/Edit.cshtml index 2e75614aa..3439bca4f 100644 --- a/src/Admin/Views/Providers/Edit.cshtml +++ b/src/Admin/Views/Providers/Edit.cshtml @@ -7,6 +7,7 @@

Provider Information

@await Html.PartialAsync("_ViewInformation", Model) +@await Html.PartialAsync("Admins", Model)

General

diff --git a/src/Admin/Views/Providers/View.cshtml b/src/Admin/Views/Providers/View.cshtml index 14bb9ed6c..d59b661a7 100644 --- a/src/Admin/Views/Providers/View.cshtml +++ b/src/Admin/Views/Providers/View.cshtml @@ -7,6 +7,7 @@

Information

@await Html.PartialAsync("_ViewInformation", Model) +@await Html.PartialAsync("Admins", Model) diff --git a/src/Admin/Views/Providers/_ViewInformation.cshtml b/src/Admin/Views/Providers/_ViewInformation.cshtml index 89e5f5ace..9a3170743 100644 --- a/src/Admin/Views/Providers/_ViewInformation.cshtml +++ b/src/Admin/Views/Providers/_ViewInformation.cshtml @@ -9,9 +9,6 @@
Users
@Model.UserCount
-
ProviderAdmins
-
@(string.IsNullOrWhiteSpace(Model.ProviderAdmins) ? "None" : Model.ProviderAdmins)
-
Created
@Model.Provider.CreationDate.ToString()
diff --git a/src/Admin/Views/_ViewImports.cshtml b/src/Admin/Views/_ViewImports.cshtml index d7a2fbc3a..194be5ed4 100644 --- a/src/Admin/Views/_ViewImports.cshtml +++ b/src/Admin/Views/_ViewImports.cshtml @@ -1,5 +1,6 @@ @using Microsoft.AspNetCore.Identity @using Bit.Admin @using Bit.Admin.Models +@using Bit.Core.Enums.Provider @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers -@addTagHelper "*, Admin" \ No newline at end of file +@addTagHelper "*, Admin" diff --git a/src/Api/Controllers/ProviderUsersController.cs b/src/Api/Controllers/ProviderUsersController.cs index 5da6a3bc1..dfd17cf4d 100644 --- a/src/Api/Controllers/ProviderUsersController.cs +++ b/src/Api/Controllers/ProviderUsersController.cs @@ -67,8 +67,9 @@ namespace Bit.Api.Controllers throw new NotFoundException(); } - var userId = _userService.GetProperUserId(User); - await _providerService.InviteUserAsync(providerId, userId.Value, new ProviderUserInvite(model)); + var invite = ProviderUserInviteFactory.CreateIntialInvite(model.Emails, model.Type.Value, + _userService.GetProperUserId(User).Value, providerId); + await _providerService.InviteUserAsync(invite); } [HttpPost("reinvite")] @@ -79,8 +80,8 @@ namespace Bit.Api.Controllers throw new NotFoundException(); } - var userId = _userService.GetProperUserId(User); - var result = await _providerService.ResendInvitesAsync(providerId, userId.Value, model.Ids); + var invite = ProviderUserInviteFactory.CreateReinvite(model.Ids, _userService.GetProperUserId(User).Value, providerId); + var result = await _providerService.ResendInvitesAsync(invite); return new ListResponseModel( result.Select(t => new ProviderUserBulkResponseModel(t.Item1.Id, t.Item2))); } @@ -93,8 +94,9 @@ namespace Bit.Api.Controllers throw new NotFoundException(); } - var userId = _userService.GetProperUserId(User); - await _providerService.ResendInvitesAsync(providerId, userId.Value, new [] { id }); + var invite = ProviderUserInviteFactory.CreateReinvite(new [] { id }, + _userService.GetProperUserId(User).Value, providerId); + await _providerService.ResendInvitesAsync(invite); } [HttpPost("{id:guid}/accept")] diff --git a/src/Core/Models/Business/Provider/ProviderUserInvite.cs b/src/Core/Models/Business/Provider/ProviderUserInvite.cs index 39bc43348..65713554b 100644 --- a/src/Core/Models/Business/Provider/ProviderUserInvite.cs +++ b/src/Core/Models/Business/Provider/ProviderUserInvite.cs @@ -1,19 +1,39 @@ +using System; using System.Collections.Generic; using Bit.Core.Enums.Provider; using Bit.Core.Models.Api; -using Bit.Core.Models.Data; namespace Bit.Core.Models.Business.Provider { - public class ProviderUserInvite + public class ProviderUserInvite { - public IEnumerable Emails { get; set; } + public IEnumerable UserIdentifiers { 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 + { + public static ProviderUserInvite CreateIntialInvite(IEnumerable inviteeEmails, ProviderUserType type, Guid invitingUserId, Guid providerId) { - Emails = requestModel.Emails; - Type = requestModel.Type.Value; + return new ProviderUserInvite + { + UserIdentifiers = inviteeEmails, + Type = type, + InvitingUserId = invitingUserId, + ProviderId = providerId + }; + } + + public static ProviderUserInvite CreateReinvite(IEnumerable inviteeUserIds, Guid invitingUserId, Guid providerId) + { + return new ProviderUserInvite + { + UserIdentifiers = inviteeUserIds, + InvitingUserId = invitingUserId, + ProviderId = providerId + }; } } } diff --git a/src/Core/Services/IProviderService.cs b/src/Core/Services/IProviderService.cs index 8c2c10131..8cef1bcf8 100644 --- a/src/Core/Services/IProviderService.cs +++ b/src/Core/Services/IProviderService.cs @@ -14,9 +14,8 @@ namespace Bit.Core.Services Task CompleteSetupAsync(Provider provider, Guid ownerUserId, string token, string key); Task UpdateAsync(Provider provider, bool updateBilling = false); - Task> InviteUserAsync(Guid providerId, Guid invitingUserId, ProviderUserInvite providerUserInvite); - Task>> ResendInvitesAsync(Guid providerId, Guid invitingUserId, - IEnumerable providerUsersId); + Task> InviteUserAsync(ProviderUserInvite invite); + Task>> ResendInvitesAsync(ProviderUserInvite invite); Task AcceptUserAsync(Guid providerUserId, User user, string token); Task>> ConfirmUsersAsync(Guid providerId, Dictionary keys, Guid confirmingUserId); @@ -29,5 +28,7 @@ namespace Bit.Core.Services string clientOwnerEmail, User user); Task RemoveOrganizationAsync(Guid providerId, Guid providerOrganizationId, Guid removingUserId); Task LogProviderAccessToOrganizationAsync(Guid organizationId); + Task ResendProviderSetupInviteEmailAsync(Guid providerId, Guid ownerId); } } + diff --git a/src/Core/Services/NoopImplementations/NoopProviderService.cs b/src/Core/Services/NoopImplementations/NoopProviderService.cs index 53427a9e9..24b575360 100644 --- a/src/Core/Services/NoopImplementations/NoopProviderService.cs +++ b/src/Core/Services/NoopImplementations/NoopProviderService.cs @@ -16,9 +16,9 @@ namespace Bit.Core.Services public Task UpdateAsync(Provider provider, bool updateBilling = false) => throw new NotImplementedException(); - public Task> InviteUserAsync(Guid providerId, Guid invitingUserId, ProviderUserInvite providerUserInvite) => throw new NotImplementedException(); + public Task> InviteUserAsync(ProviderUserInvite invite) => throw new NotImplementedException(); - public Task>> ResendInvitesAsync(Guid providerId, Guid invitingUserId, IEnumerable providerUsersId) => throw new NotImplementedException(); + public Task>> ResendInvitesAsync(ProviderUserInvite invite) => throw new NotImplementedException(); public Task AcceptUserAsync(Guid providerUserId, User user, string token) => throw new NotImplementedException(); @@ -29,9 +29,13 @@ namespace Bit.Core.Services public Task>> DeleteUsersAsync(Guid providerId, IEnumerable providerUserIds, Guid deletingUserId) => throw new NotImplementedException(); public Task AddOrganization(Guid providerId, Guid organizationId, Guid addingUserId, string key) => throw new NotImplementedException(); + public Task 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 LogProviderAccessToOrganizationAsync(Guid organizationId) => throw new NotImplementedException(); + + public Task ResendProviderSetupInviteEmailAsync(Guid providerId, Guid userId) => throw new NotImplementedException(); } } diff --git a/src/Sql/dbo/Stored Procedures/Event_ReadPageByProviderId.sql b/src/Sql/dbo/Stored Procedures/Event_ReadPageByProviderId.sql index 58a5aca88..4b336a692 100644 --- a/src/Sql/dbo/Stored Procedures/Event_ReadPageByProviderId.sql +++ b/src/Sql/dbo/Stored Procedures/Event_ReadPageByProviderId.sql @@ -16,7 +16,7 @@ BEGIN [Date] >= @StartDate AND (@BeforeDate IS NOT NULL OR [Date] <= @EndDate) AND (@BeforeDate IS NULL OR [Date] < @BeforeDate) - AND [Providerid] = @ProviderId + AND [ProviderId] = @ProviderId ORDER BY [Date] DESC OFFSET 0 ROWS FETCH NEXT @PageSize ROWS ONLY