1
0
mirror of https://github.com/bitwarden/server.git synced 2025-06-30 15:42:48 -05:00

[Provider] Setup provider (#1378)

This commit is contained in:
Oscar Hinton
2021-06-30 09:35:26 +02:00
committed by GitHub
parent 08f508f536
commit 43f7271147
85 changed files with 1810 additions and 113 deletions

View File

@ -30,6 +30,7 @@ namespace Bit.Api.Test.Controllers
private readonly ISsoUserRepository _ssoUserRepository;
private readonly IUserRepository _userRepository;
private readonly IUserService _userService;
private readonly IProviderUserRepository _providerUserRepository;
public AccountsControllerTests()
{
@ -39,6 +40,7 @@ namespace Bit.Api.Test.Controllers
_folderRepository = Substitute.For<IFolderRepository>();
_organizationService = Substitute.For<IOrganizationService>();
_organizationUserRepository = Substitute.For<IOrganizationUserRepository>();
_providerUserRepository = Substitute.For<IProviderUserRepository>();
_paymentService = Substitute.For<IPaymentService>();
_globalSettings = new GlobalSettings();
_sut = new AccountsController(
@ -47,6 +49,7 @@ namespace Bit.Api.Test.Controllers
_folderRepository,
_organizationService,
_organizationUserRepository,
_providerUserRepository,
_paymentService,
_ssoUserRepository,
_userRepository,

View File

@ -5,7 +5,7 @@ using AutoFixture.Xunit2;
namespace Bit.Core.Test.AutoFixture.Attributes
{
internal class CustomAutoDataAttribute : AutoDataAttribute
public class CustomAutoDataAttribute : AutoDataAttribute
{
public CustomAutoDataAttribute(params Type[] iCustomizationTypes) : this(iCustomizationTypes
.Select(t => (ICustomization)Activator.CreateInstance(t)).ToArray())

View File

@ -1,45 +0,0 @@
using System.Reflection;
using AutoFixture;
using AutoFixture.Xunit2;
using Bit.Core.Enums.Provider;
namespace Bit.Core.Test.AutoFixture.ProviderUserFixtures
{
internal class ProviderUser : ICustomization
{
public ProviderUserStatusType Status { get; set; }
public ProviderUserType Type { get; set; }
public ProviderUser(ProviderUserStatusType status, ProviderUserType type)
{
Status = status;
Type = type;
}
public void Customize(IFixture fixture)
{
fixture.Customize<Core.Models.Table.Provider.ProviderUser>(composer => composer
.With(o => o.Type, Type)
.With(o => o.Status, Status));
}
}
public class ProviderUserAttribute : CustomizeAttribute
{
private readonly ProviderUserStatusType _status;
private readonly ProviderUserType _type;
public ProviderUserAttribute(
ProviderUserStatusType status = ProviderUserStatusType.Confirmed,
ProviderUserType type = ProviderUserType.ProviderAdmin)
{
_status = status;
_type = type;
}
public override ICustomization GetCustomization(ParameterInfo parameter)
{
return new ProviderUser(_status, _type);
}
}
}

View File

@ -11,12 +11,14 @@ namespace Bit.Core.Test.Services
private readonly InMemoryApplicationCacheService _sut;
private readonly IOrganizationRepository _organizationRepository;
private readonly IProviderRepository _providerRepository;
public InMemoryApplicationCacheServiceTests()
{
_organizationRepository = Substitute.For<IOrganizationRepository>();
_providerRepository = Substitute.For<IProviderRepository>();
_sut = new InMemoryApplicationCacheService(_organizationRepository);
_sut = new InMemoryApplicationCacheService(_organizationRepository, _providerRepository);
}
// Remove this test when we add actual tests. It only proves that

View File

@ -12,15 +12,18 @@ namespace Bit.Core.Test.Services
private readonly InMemoryServiceBusApplicationCacheService _sut;
private readonly IOrganizationRepository _organizationRepository;
private readonly IProviderRepository _providerRepository;
private readonly GlobalSettings _globalSettings;
public InMemoryServiceBusApplicationCacheServiceTests()
{
_organizationRepository = Substitute.For<IOrganizationRepository>();
_providerRepository = Substitute.For<IProviderRepository>();
_globalSettings = new GlobalSettings();
_sut = new InMemoryServiceBusApplicationCacheService(
_organizationRepository,
_providerRepository,
_globalSettings
);
}

View File

@ -1,392 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Bit.Core.Enums;
using Bit.Core.Enums.Provider;
using Bit.Core.Exceptions;
using Bit.Core.Models.Business.Provider;
using Bit.Core.Models.Table;
using Bit.Core.Models.Table.Provider;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Test.AutoFixture;
using Bit.Core.Test.AutoFixture.Attributes;
using Bit.Core.Test.AutoFixture.ProviderUserFixtures;
using Bit.Core.Utilities;
using Microsoft.AspNetCore.DataProtection;
using NSubstitute;
using Xunit;
using ProviderUser = Bit.Core.Models.Table.Provider.ProviderUser;
namespace Bit.Core.Test.Services
{
public class ProviderServiceTests
{
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
public async Task CreateAsync_UserIdIsInvalid_Throws(SutProvider<ProviderService> sutProvider)
{
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.CreateAsync(default));
Assert.Contains("Invalid owner.", exception.Message);
}
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
public async Task CreateAsync_Success(User user, SutProvider<ProviderService> sutProvider)
{
var userRepository = sutProvider.GetDependency<IUserRepository>();
userRepository.GetByEmailAsync(user.Email).Returns(user);
await sutProvider.Sut.CreateAsync(user.Email);
await sutProvider.GetDependency<IProviderRepository>().ReceivedWithAnyArgs().CreateAsync(default);
await sutProvider.GetDependency<IMailService>().ReceivedWithAnyArgs().SendProviderSetupInviteEmailAsync(default, default, default);
}
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
public async Task CompleteSetupAsync_UserIdIsInvalid_Throws(SutProvider<ProviderService> sutProvider)
{
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.CompleteSetupAsync(default, default, default, default));
Assert.Contains("Invalid owner.", exception.Message);
}
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
public async Task CompleteSetupAsync_TokenIsInvalid_Throws(User user, Provider provider,
SutProvider<ProviderService> sutProvider)
{
var userService = sutProvider.GetDependency<IUserService>();
userService.GetUserByIdAsync(user.Id).Returns(user);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.CompleteSetupAsync(provider, user.Id, default, default));
Assert.Contains("Invalid token.", exception.Message);
}
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
public async Task CompleteSetupAsync_Success(User user, Provider provider,
SutProvider<ProviderService> sutProvider)
{
var userService = sutProvider.GetDependency<IUserService>();
userService.GetUserByIdAsync(user.Id).Returns(user);
var dataProtectionProvider = DataProtectionProvider.Create("ApplicationName");
var protector = dataProtectionProvider.CreateProtector("ProviderServiceDataProtector");
sutProvider.GetDependency<IDataProtectionProvider>().CreateProtector("ProviderServiceDataProtector")
.Returns(protector);
sutProvider.Create();
var token = protector.Protect($"ProviderSetupInvite {provider.Id} {user.Email} {CoreHelpers.ToEpocMilliseconds(DateTime.UtcNow)}");
await sutProvider.Sut.CompleteSetupAsync(provider, user.Id, token, default);
await sutProvider.GetDependency<IProviderRepository>().Received().UpsertAsync(provider);
await sutProvider.GetDependency<IProviderUserRepository>().Received()
.CreateAsync(Arg.Is<ProviderUser>(pu => pu.UserId == user.Id && pu.ProviderId == provider.Id));
}
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
public async Task UpdateAsync_ProviderIdIsInvalid_Throws(Provider provider, SutProvider<ProviderService> sutProvider)
{
provider.Id = default;
var exception = await Assert.ThrowsAsync<ApplicationException>(
() => sutProvider.Sut.UpdateAsync(provider));
Assert.Contains("Cannot create provider this way.", exception.Message);
}
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
public async Task UpdateAsync_Success(Provider provider, SutProvider<ProviderService> sutProvider)
{
await sutProvider.Sut.UpdateAsync(provider);
}
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
public async Task InviteUserAsync_ProviderIdIsInvalid_Throws(Provider provider, SutProvider<ProviderService> sutProvider)
{
provider.Id = default;
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.InviteUserAsync(provider.Id, default, default));
}
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
public async Task InviteUserAsync_EmailsInvalid_Throws(Provider provider, ProviderUserInvite providerUserInvite,
SutProvider<ProviderService> sutProvider)
{
var providerRepository = sutProvider.GetDependency<IProviderRepository>();
providerRepository.GetByIdAsync(provider.Id).Returns(provider);
providerUserInvite.Emails = null;
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.InviteUserAsync(provider.Id, default, providerUserInvite));
}
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
public async Task InviteUserAsync_AlreadyInvited(Provider provider, ProviderUserInvite providerUserInvite,
SutProvider<ProviderService> sutProvider)
{
var providerRepository = sutProvider.GetDependency<IProviderRepository>();
providerRepository.GetByIdAsync(provider.Id).Returns(provider);
var providerUserRepository = sutProvider.GetDependency<IProviderUserRepository>();
providerUserRepository.GetCountByProviderAsync(default, default, default).ReturnsForAnyArgs(1);
var result = await sutProvider.Sut.InviteUserAsync(provider.Id, default, providerUserInvite);
Assert.Empty(result);
}
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
public async Task InviteUserAsync_Success(Provider provider, ProviderUserInvite providerUserInvite,
SutProvider<ProviderService> sutProvider)
{
var providerRepository = sutProvider.GetDependency<IProviderRepository>();
providerRepository.GetByIdAsync(provider.Id).Returns(provider);
var providerUserRepository = sutProvider.GetDependency<IProviderUserRepository>();
providerUserRepository.GetCountByProviderAsync(default, default, default).ReturnsForAnyArgs(0);
var result = await sutProvider.Sut.InviteUserAsync(provider.Id, default, providerUserInvite);
Assert.Equal(providerUserInvite.Emails.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");
}
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
public async Task ResendInvitesAsync_Errors(Provider provider,
[ProviderUser(ProviderUserStatusType.Invited)]ProviderUser pu1,
[ProviderUser(ProviderUserStatusType.Accepted)]ProviderUser pu2,
[ProviderUser(ProviderUserStatusType.Confirmed)]ProviderUser pu3,
[ProviderUser(ProviderUserStatusType.Invited)]ProviderUser pu4,
SutProvider<ProviderService> sutProvider)
{
var providerUsers = new[] {pu1, pu2, pu3, pu4};
pu1.ProviderId = pu2.ProviderId = pu3.ProviderId = provider.Id;
var providerRepository = sutProvider.GetDependency<IProviderRepository>();
providerRepository.GetByIdAsync(provider.Id).Returns(provider);
var providerUserRepository = sutProvider.GetDependency<IProviderUserRepository>();
providerUserRepository.GetManyAsync(default).ReturnsForAnyArgs(providerUsers.ToList());
var result = await sutProvider.Sut.ResendInvitesAsync(provider.Id, default, providerUsers.Select(pu => pu.Id));
Assert.Equal("", result[0].Item2);
Assert.Equal("User invalid.", result[1].Item2);
Assert.Equal("User invalid.", result[2].Item2);
Assert.Equal("User invalid.", result[3].Item2);
}
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
public async Task ResendInvitesAsync_Success(Provider provider, IEnumerable<ProviderUser> providerUsers,
SutProvider<ProviderService> sutProvider)
{
foreach (var providerUser in providerUsers)
{
providerUser.ProviderId = provider.Id;
providerUser.Status = ProviderUserStatusType.Invited;
}
var providerRepository = sutProvider.GetDependency<IProviderRepository>();
providerRepository.GetByIdAsync(provider.Id).Returns(provider);
var providerUserRepository = sutProvider.GetDependency<IProviderUserRepository>();
providerUserRepository.GetManyAsync(default).ReturnsForAnyArgs(providerUsers.ToList());
var result = await sutProvider.Sut.ResendInvitesAsync(provider.Id, default, providerUsers.Select(pu => pu.Id));
Assert.True(result.All(r => r.Item2 == ""));
}
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
public async Task AcceptUserAsync_UserIsInvalid_Throws(ProviderUser providerUser, User user,
SutProvider<ProviderService> sutProvider)
{
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.AcceptUserAsync(default, default, default));
Assert.Equal("User invalid.", exception.Message);
}
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
public async Task AcceptUserAsync_AlreadyAccepted_Throws(
[ProviderUser(ProviderUserStatusType.Accepted)]ProviderUser providerUser, User user,
SutProvider<ProviderService> sutProvider)
{
var providerUserRepository = sutProvider.GetDependency<IProviderUserRepository>();
providerUserRepository.GetByIdAsync(providerUser.Id).Returns(providerUser);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.AcceptUserAsync(providerUser.Id, user, default));
Assert.Equal("Already accepted.", exception.Message);
}
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
public async Task AcceptUserAsync_TokenIsInvalid_Throws(
[ProviderUser(ProviderUserStatusType.Invited)]ProviderUser providerUser, User user,
SutProvider<ProviderService> sutProvider)
{
var providerUserRepository = sutProvider.GetDependency<IProviderUserRepository>();
providerUserRepository.GetByIdAsync(providerUser.Id).Returns(providerUser);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.AcceptUserAsync(providerUser.Id, user, default));
Assert.Equal("Invalid token.", exception.Message);
}
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
public async Task AcceptUserAsync_WrongEmail_Throws(
[ProviderUser(ProviderUserStatusType.Invited)]ProviderUser providerUser, User user,
SutProvider<ProviderService> sutProvider)
{
var providerUserRepository = sutProvider.GetDependency<IProviderUserRepository>();
providerUserRepository.GetByIdAsync(providerUser.Id).Returns(providerUser);
var dataProtectionProvider = DataProtectionProvider.Create("ApplicationName");
var protector = dataProtectionProvider.CreateProtector("ProviderServiceDataProtector");
sutProvider.GetDependency<IDataProtectionProvider>().CreateProtector("ProviderServiceDataProtector")
.Returns(protector);
sutProvider.Create();
var token = protector.Protect($"ProviderUserInvite {providerUser.Id} {user.Email} {CoreHelpers.ToEpocMilliseconds(DateTime.UtcNow)}");
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.AcceptUserAsync(providerUser.Id, user, token));
Assert.Equal("User email does not match invite.", exception.Message);
}
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
public async Task AcceptUserAsync_Success(
[ProviderUser(ProviderUserStatusType.Invited)]ProviderUser providerUser, User user,
SutProvider<ProviderService> sutProvider)
{
var providerUserRepository = sutProvider.GetDependency<IProviderUserRepository>();
providerUserRepository.GetByIdAsync(providerUser.Id).Returns(providerUser);
var dataProtectionProvider = DataProtectionProvider.Create("ApplicationName");
var protector = dataProtectionProvider.CreateProtector("ProviderServiceDataProtector");
sutProvider.GetDependency<IDataProtectionProvider>().CreateProtector("ProviderServiceDataProtector")
.Returns(protector);
sutProvider.Create();
providerUser.Email = user.Email;
var token = protector.Protect($"ProviderUserInvite {providerUser.Id} {user.Email} {CoreHelpers.ToEpocMilliseconds(DateTime.UtcNow)}");
var pu = await sutProvider.Sut.AcceptUserAsync(providerUser.Id, user, token);
Assert.Null(pu.Email);
Assert.Equal(ProviderUserStatusType.Accepted, pu.Status);
Assert.Equal(user.Id, pu.UserId);
}
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
public async Task ConfirmUsersAsync_NoValid(
[ProviderUser(ProviderUserStatusType.Invited)]ProviderUser pu1,
[ProviderUser(ProviderUserStatusType.Accepted)]ProviderUser pu2,
[ProviderUser(ProviderUserStatusType.Confirmed)]ProviderUser pu3,
SutProvider<ProviderService> sutProvider)
{
pu1.ProviderId = pu3.ProviderId;
var providerUsers = new[] {pu1, pu2, pu3};
var providerUserRepository = sutProvider.GetDependency<IProviderUserRepository>();
providerUserRepository.GetManyAsync(default).ReturnsForAnyArgs(providerUsers);
var dict = providerUsers.ToDictionary(pu => pu.Id, _ => "key");
var result = await sutProvider.Sut.ConfirmUsersAsync(pu1.ProviderId, dict, default);
Assert.Empty(result);
}
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
public async Task ConfirmUsersAsync_Success(
[ProviderUser(ProviderUserStatusType.Invited)]ProviderUser pu1, User u1,
[ProviderUser(ProviderUserStatusType.Accepted)]ProviderUser pu2, User u2,
[ProviderUser(ProviderUserStatusType.Confirmed)]ProviderUser pu3, User u3,
Provider provider, User user, SutProvider<ProviderService> sutProvider)
{
pu1.ProviderId = pu2.ProviderId = pu3.ProviderId = provider.Id;
pu1.UserId = u1.Id;
pu2.UserId = u2.Id;
pu3.UserId = u3.Id;
var providerUsers = new[] {pu1, pu2, pu3};
var providerUserRepository = sutProvider.GetDependency<IProviderUserRepository>();
providerUserRepository.GetManyAsync(default).ReturnsForAnyArgs(providerUsers);
var providerRepository = sutProvider.GetDependency<IProviderRepository>();
providerRepository.GetByIdAsync(provider.Id).Returns(provider);
var userRepository = sutProvider.GetDependency<IUserRepository>();
userRepository.GetManyAsync(default).ReturnsForAnyArgs(new[] {u1, u2, u3});
var dict = providerUsers.ToDictionary(pu => pu.Id, _ => "key");
var result = await sutProvider.Sut.ConfirmUsersAsync(pu1.ProviderId, dict, user.Id);
Assert.Equal("Invalid user.", result[0].Item2);
Assert.Equal("", result[1].Item2);
Assert.Equal("Invalid user.", result[2].Item2);
}
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
public async Task SaveUserAsync_UserIdIsInvalid_Throws(ProviderUser providerUser,
SutProvider<ProviderService> sutProvider)
{
providerUser.Id = default;
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.SaveUserAsync(providerUser, default));
Assert.Equal("Invite the user first.", exception.Message);
}
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
public async Task SaveUserAsync_Success(
[ProviderUser(type: ProviderUserType.ProviderAdmin)]ProviderUser providerUser, User savingUser,
SutProvider<ProviderService> sutProvider)
{
var providerUserRepository = sutProvider.GetDependency<IProviderUserRepository>();
providerUserRepository.GetByIdAsync(providerUser.Id).Returns(providerUser);
await sutProvider.Sut.SaveUserAsync(providerUser, savingUser.Id);
await providerUserRepository.Received().ReplaceAsync(providerUser);
await sutProvider.GetDependency<IEventService>().Received()
.LogProviderUserEventAsync(providerUser, EventType.ProviderUser_Updated, null);
}
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
public async Task DeleteUsersAsync_NoRemainingOwner_Throws(Provider provider, User deletingUser,
ICollection<ProviderUser> providerUsers, SutProvider<ProviderService> sutProvider)
{
var userIds = providerUsers.Select(pu => pu.Id);
providerUsers.First().UserId = deletingUser.Id;
foreach (var providerUser in providerUsers)
{
providerUser.ProviderId = provider.Id;
}
providerUsers.Last().ProviderId = default;
var providerUserRepository = sutProvider.GetDependency<IProviderUserRepository>();
providerUserRepository.GetManyAsync(default).ReturnsForAnyArgs(providerUsers);
providerUserRepository.GetManyByProviderAsync(default, default).ReturnsForAnyArgs(new ProviderUser[] {});
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.DeleteUsersAsync(provider.Id, userIds, deletingUser.Id));
Assert.Equal("Provider must have at least one confirmed ProviderAdmin.", exception.Message);
}
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
public async Task DeleteUsersAsync_Success(Provider provider, User deletingUser, ICollection<ProviderUser> providerUsers,
[ProviderUser(ProviderUserStatusType.Confirmed, ProviderUserType.ProviderAdmin)]ProviderUser remainingOwner,
SutProvider<ProviderService> sutProvider)
{
var userIds = providerUsers.Select(pu => pu.Id);
providerUsers.First().UserId = deletingUser.Id;
foreach (var providerUser in providerUsers)
{
providerUser.ProviderId = provider.Id;
}
providerUsers.Last().ProviderId = default;
var providerUserRepository = sutProvider.GetDependency<IProviderUserRepository>();
providerUserRepository.GetManyAsync(default).ReturnsForAnyArgs(providerUsers);
providerUserRepository.GetManyByProviderAsync(default, default).ReturnsForAnyArgs(new[] {remainingOwner});
var result = await sutProvider.Sut.DeleteUsersAsync(provider.Id, userIds, deletingUser.Id);
Assert.NotEmpty(result);
Assert.Equal("You cannot remove yourself.", result[0].Item2);
Assert.Equal("", result[1].Item2);
Assert.Equal("Invalid user.", result[2].Item2);
}
}
}