1
0
mirror of https://github.com/bitwarden/server.git synced 2025-06-30 07:36:14 -05:00

Turn on file scoped namespaces (#2225)

This commit is contained in:
Justin Baur
2022-08-29 14:53:16 -04:00
committed by GitHub
parent 7c4521e0b4
commit 34fb4cca2a
1206 changed files with 73816 additions and 75022 deletions

View File

@ -13,413 +13,412 @@ using Microsoft.AspNetCore.Identity;
using NSubstitute;
using Xunit;
namespace Bit.Api.Test.Controllers
namespace Bit.Api.Test.Controllers;
public class AccountsControllerTests : IDisposable
{
public class AccountsControllerTests : IDisposable
private readonly AccountsController _sut;
private readonly GlobalSettings _globalSettings;
private readonly ICipherRepository _cipherRepository;
private readonly IFolderRepository _folderRepository;
private readonly IOrganizationService _organizationService;
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IPaymentService _paymentService;
private readonly IUserRepository _userRepository;
private readonly IUserService _userService;
private readonly ISendRepository _sendRepository;
private readonly ISendService _sendService;
private readonly IProviderUserRepository _providerUserRepository;
public AccountsControllerTests()
{
_userService = Substitute.For<IUserService>();
_userRepository = Substitute.For<IUserRepository>();
_cipherRepository = Substitute.For<ICipherRepository>();
_folderRepository = Substitute.For<IFolderRepository>();
_organizationService = Substitute.For<IOrganizationService>();
_organizationUserRepository = Substitute.For<IOrganizationUserRepository>();
_providerUserRepository = Substitute.For<IProviderUserRepository>();
_paymentService = Substitute.For<IPaymentService>();
_globalSettings = new GlobalSettings();
_sendRepository = Substitute.For<ISendRepository>();
_sendService = Substitute.For<ISendService>();
_sut = new AccountsController(
_globalSettings,
_cipherRepository,
_folderRepository,
_organizationService,
_organizationUserRepository,
_providerUserRepository,
_paymentService,
_userRepository,
_userService,
_sendRepository,
_sendService
);
}
private readonly AccountsController _sut;
private readonly GlobalSettings _globalSettings;
private readonly ICipherRepository _cipherRepository;
private readonly IFolderRepository _folderRepository;
private readonly IOrganizationService _organizationService;
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IPaymentService _paymentService;
private readonly IUserRepository _userRepository;
private readonly IUserService _userService;
private readonly ISendRepository _sendRepository;
private readonly ISendService _sendService;
private readonly IProviderUserRepository _providerUserRepository;
public void Dispose()
{
_sut?.Dispose();
}
public AccountsControllerTests()
[Fact]
public async Task PostPrelogin_WhenUserExists_ShouldReturnUserKdfInfo()
{
var userKdfInfo = new UserKdfInformation
{
_userService = Substitute.For<IUserService>();
_userRepository = Substitute.For<IUserRepository>();
_cipherRepository = Substitute.For<ICipherRepository>();
_folderRepository = Substitute.For<IFolderRepository>();
_organizationService = Substitute.For<IOrganizationService>();
_organizationUserRepository = Substitute.For<IOrganizationUserRepository>();
_providerUserRepository = Substitute.For<IProviderUserRepository>();
_paymentService = Substitute.For<IPaymentService>();
_globalSettings = new GlobalSettings();
_sendRepository = Substitute.For<ISendRepository>();
_sendService = Substitute.For<ISendService>();
_sut = new AccountsController(
_globalSettings,
_cipherRepository,
_folderRepository,
_organizationService,
_organizationUserRepository,
_providerUserRepository,
_paymentService,
_userRepository,
_userService,
_sendRepository,
_sendService
);
}
Kdf = KdfType.PBKDF2_SHA256,
KdfIterations = 5000
};
_userRepository.GetKdfInformationByEmailAsync(Arg.Any<string>()).Returns(Task.FromResult(userKdfInfo));
public void Dispose()
var response = await _sut.PostPrelogin(new PreloginRequestModel { Email = "user@example.com" });
Assert.Equal(userKdfInfo.Kdf, response.Kdf);
Assert.Equal(userKdfInfo.KdfIterations, response.KdfIterations);
}
[Fact]
public async Task PostPrelogin_WhenUserDoesNotExist_ShouldDefaultToSha256And100000Iterations()
{
_userRepository.GetKdfInformationByEmailAsync(Arg.Any<string>()).Returns(Task.FromResult((UserKdfInformation)null));
var response = await _sut.PostPrelogin(new PreloginRequestModel { Email = "user@example.com" });
Assert.Equal(KdfType.PBKDF2_SHA256, response.Kdf);
Assert.Equal(100000, response.KdfIterations);
}
[Fact]
public async Task PostRegister_ShouldRegisterUser()
{
var passwordHash = "abcdef";
var token = "123456";
var userGuid = new Guid();
_userService.RegisterUserAsync(Arg.Any<User>(), passwordHash, token, userGuid)
.Returns(Task.FromResult(IdentityResult.Success));
var request = new RegisterRequestModel
{
_sut?.Dispose();
}
Name = "Example User",
Email = "user@example.com",
MasterPasswordHash = passwordHash,
MasterPasswordHint = "example",
Token = token,
OrganizationUserId = userGuid
};
[Fact]
public async Task PostPrelogin_WhenUserExists_ShouldReturnUserKdfInfo()
await _sut.PostRegister(request);
await _userService.Received(1).RegisterUserAsync(Arg.Any<User>(), passwordHash, token, userGuid);
}
[Fact]
public async Task PostRegister_WhenUserServiceFails_ShouldThrowBadRequestException()
{
var passwordHash = "abcdef";
var token = "123456";
var userGuid = new Guid();
_userService.RegisterUserAsync(Arg.Any<User>(), passwordHash, token, userGuid)
.Returns(Task.FromResult(IdentityResult.Failed()));
var request = new RegisterRequestModel
{
var userKdfInfo = new UserKdfInformation
{
Kdf = KdfType.PBKDF2_SHA256,
KdfIterations = 5000
};
_userRepository.GetKdfInformationByEmailAsync(Arg.Any<string>()).Returns(Task.FromResult(userKdfInfo));
Name = "Example User",
Email = "user@example.com",
MasterPasswordHash = passwordHash,
MasterPasswordHint = "example",
Token = token,
OrganizationUserId = userGuid
};
var response = await _sut.PostPrelogin(new PreloginRequestModel { Email = "user@example.com" });
await Assert.ThrowsAsync<BadRequestException>(() => _sut.PostRegister(request));
}
Assert.Equal(userKdfInfo.Kdf, response.Kdf);
Assert.Equal(userKdfInfo.KdfIterations, response.KdfIterations);
}
[Fact]
public async Task PostPasswordHint_ShouldNotifyUserService()
{
var email = "user@example.com";
[Fact]
public async Task PostPrelogin_WhenUserDoesNotExist_ShouldDefaultToSha256And100000Iterations()
await _sut.PostPasswordHint(new PasswordHintRequestModel { Email = email });
await _userService.Received(1).SendMasterPasswordHintAsync(email);
}
[Fact]
public async Task PostEmailToken_ShouldInitiateEmailChange()
{
var user = GenerateExampleUser();
ConfigureUserServiceToReturnValidPrincipalFor(user);
ConfigureUserServiceToAcceptPasswordFor(user);
var newEmail = "example@user.com";
await _sut.PostEmailToken(new EmailTokenRequestModel { NewEmail = newEmail });
await _userService.Received(1).InitiateEmailChangeAsync(user, newEmail);
}
[Fact]
public async Task PostEmailToken_WhenNotAuthorized_ShouldThrowUnauthorizedAccessException()
{
ConfigureUserServiceToReturnNullPrincipal();
await Assert.ThrowsAsync<UnauthorizedAccessException>(
() => _sut.PostEmailToken(new EmailTokenRequestModel())
);
}
[Fact]
public async Task PostEmailToken_WhenInvalidPasssword_ShouldThrowBadRequestException()
{
var user = GenerateExampleUser();
ConfigureUserServiceToReturnValidPrincipalFor(user);
ConfigureUserServiceToRejectPasswordFor(user);
await Assert.ThrowsAsync<BadRequestException>(
() => _sut.PostEmailToken(new EmailTokenRequestModel())
);
}
[Fact]
public async Task PostEmail_ShouldChangeUserEmail()
{
var user = GenerateExampleUser();
ConfigureUserServiceToReturnValidPrincipalFor(user);
_userService.ChangeEmailAsync(user, default, default, default, default, default)
.Returns(Task.FromResult(IdentityResult.Success));
await _sut.PostEmail(new EmailRequestModel());
await _userService.Received(1).ChangeEmailAsync(user, default, default, default, default, default);
}
[Fact]
public async Task PostEmail_WhenNotAuthorized_ShouldThrownUnauthorizedAccessException()
{
ConfigureUserServiceToReturnNullPrincipal();
await Assert.ThrowsAsync<UnauthorizedAccessException>(
() => _sut.PostEmail(new EmailRequestModel())
);
}
[Fact]
public async Task PostEmail_WhenEmailCannotBeChanged_ShouldThrowBadRequestException()
{
var user = GenerateExampleUser();
ConfigureUserServiceToReturnValidPrincipalFor(user);
_userService.ChangeEmailAsync(user, default, default, default, default, default)
.Returns(Task.FromResult(IdentityResult.Failed()));
await Assert.ThrowsAsync<BadRequestException>(
() => _sut.PostEmail(new EmailRequestModel())
);
}
[Fact]
public async Task PostVerifyEmail_ShouldSendEmailVerification()
{
var user = GenerateExampleUser();
ConfigureUserServiceToReturnValidPrincipalFor(user);
await _sut.PostVerifyEmail();
await _userService.Received(1).SendEmailVerificationAsync(user);
}
[Fact]
public async Task PostVerifyEmail_WhenNotAuthorized_ShouldThrownUnauthorizedAccessException()
{
ConfigureUserServiceToReturnNullPrincipal();
await Assert.ThrowsAsync<UnauthorizedAccessException>(
() => _sut.PostVerifyEmail()
);
}
[Fact]
public async Task PostVerifyEmailToken_ShouldConfirmEmail()
{
var user = GenerateExampleUser();
ConfigureUserServiceToReturnValidIdFor(user);
_userService.ConfirmEmailAsync(user, Arg.Any<string>())
.Returns(Task.FromResult(IdentityResult.Success));
await _sut.PostVerifyEmailToken(new VerifyEmailRequestModel { UserId = "12345678-1234-1234-1234-123456789012" });
await _userService.Received(1).ConfirmEmailAsync(user, Arg.Any<string>());
}
[Fact]
public async Task PostVerifyEmailToken_WhenUserDoesNotExist_ShouldThrowUnauthorizedAccessException()
{
var user = GenerateExampleUser();
ConfigureUserServiceToReturnNullUserId();
await Assert.ThrowsAsync<UnauthorizedAccessException>(
() => _sut.PostVerifyEmailToken(new VerifyEmailRequestModel { UserId = "12345678-1234-1234-1234-123456789012" })
);
}
[Fact]
public async Task PostVerifyEmailToken_WhenEmailConfirmationFails_ShouldThrowBadRequestException()
{
var user = GenerateExampleUser();
ConfigureUserServiceToReturnValidIdFor(user);
_userService.ConfirmEmailAsync(user, Arg.Any<string>())
.Returns(Task.FromResult(IdentityResult.Failed()));
await Assert.ThrowsAsync<BadRequestException>(
() => _sut.PostVerifyEmailToken(new VerifyEmailRequestModel { UserId = "12345678-1234-1234-1234-123456789012" })
);
}
[Fact]
public async Task PostPassword_ShouldChangePassword()
{
var user = GenerateExampleUser();
ConfigureUserServiceToReturnValidPrincipalFor(user);
_userService.ChangePasswordAsync(user, default, default, default, default)
.Returns(Task.FromResult(IdentityResult.Success));
await _sut.PostPassword(new PasswordRequestModel());
await _userService.Received(1).ChangePasswordAsync(user, default, default, default, default);
}
[Fact]
public async Task PostPassword_WhenNotAuthorized_ShouldThrowUnauthorizedAccessException()
{
ConfigureUserServiceToReturnNullPrincipal();
await Assert.ThrowsAsync<UnauthorizedAccessException>(
() => _sut.PostPassword(new PasswordRequestModel())
);
}
[Fact]
public async Task PostPassword_WhenPasswordChangeFails_ShouldBadRequestException()
{
var user = GenerateExampleUser();
ConfigureUserServiceToReturnValidPrincipalFor(user);
_userService.ChangePasswordAsync(user, default, default, default, default)
.Returns(Task.FromResult(IdentityResult.Failed()));
await Assert.ThrowsAsync<BadRequestException>(
() => _sut.PostPassword(new PasswordRequestModel())
);
}
[Fact]
public async Task GetApiKey_ShouldReturnApiKeyResponse()
{
var user = GenerateExampleUser();
ConfigureUserServiceToReturnValidPrincipalFor(user);
ConfigureUserServiceToAcceptPasswordFor(user);
await _sut.ApiKey(new SecretVerificationRequestModel());
}
[Fact]
public async Task GetApiKey_WhenUserDoesNotExist_ShouldThrowUnauthorizedAccessException()
{
ConfigureUserServiceToReturnNullPrincipal();
await Assert.ThrowsAsync<UnauthorizedAccessException>(
() => _sut.ApiKey(new SecretVerificationRequestModel())
);
}
[Fact]
public async Task GetApiKey_WhenPasswordCheckFails_ShouldThrowBadRequestException()
{
var user = GenerateExampleUser();
ConfigureUserServiceToReturnValidPrincipalFor(user);
ConfigureUserServiceToRejectPasswordFor(user);
await Assert.ThrowsAsync<BadRequestException>(
() => _sut.ApiKey(new SecretVerificationRequestModel())
);
}
[Fact]
public async Task PostRotateApiKey_ShouldRotateApiKey()
{
var user = GenerateExampleUser();
ConfigureUserServiceToReturnValidPrincipalFor(user);
ConfigureUserServiceToAcceptPasswordFor(user);
await _sut.RotateApiKey(new SecretVerificationRequestModel());
}
[Fact]
public async Task PostRotateApiKey_WhenUserDoesNotExist_ShouldThrowUnauthorizedAccessException()
{
ConfigureUserServiceToReturnNullPrincipal();
await Assert.ThrowsAsync<UnauthorizedAccessException>(
() => _sut.ApiKey(new SecretVerificationRequestModel())
);
}
[Fact]
public async Task PostRotateApiKey_WhenPasswordCheckFails_ShouldThrowBadRequestException()
{
var user = GenerateExampleUser();
ConfigureUserServiceToReturnValidPrincipalFor(user);
ConfigureUserServiceToRejectPasswordFor(user);
await Assert.ThrowsAsync<BadRequestException>(
() => _sut.ApiKey(new SecretVerificationRequestModel())
);
}
// Below are helper functions that currently belong to this
// test class, but ultimately may need to be split out into
// something greater in order to share common test steps with
// other test suites. They are included here for the time being
// until that day comes.
private User GenerateExampleUser()
{
return new User
{
_userRepository.GetKdfInformationByEmailAsync(Arg.Any<string>()).Returns(Task.FromResult((UserKdfInformation)null));
Email = "user@example.com"
};
}
var response = await _sut.PostPrelogin(new PreloginRequestModel { Email = "user@example.com" });
private void ConfigureUserServiceToReturnNullPrincipal()
{
_userService.GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>())
.Returns(Task.FromResult((User)null));
}
Assert.Equal(KdfType.PBKDF2_SHA256, response.Kdf);
Assert.Equal(100000, response.KdfIterations);
}
private void ConfigureUserServiceToReturnValidPrincipalFor(User user)
{
_userService.GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>())
.Returns(Task.FromResult(user));
}
[Fact]
public async Task PostRegister_ShouldRegisterUser()
{
var passwordHash = "abcdef";
var token = "123456";
var userGuid = new Guid();
_userService.RegisterUserAsync(Arg.Any<User>(), passwordHash, token, userGuid)
.Returns(Task.FromResult(IdentityResult.Success));
var request = new RegisterRequestModel
{
Name = "Example User",
Email = "user@example.com",
MasterPasswordHash = passwordHash,
MasterPasswordHint = "example",
Token = token,
OrganizationUserId = userGuid
};
private void ConfigureUserServiceToRejectPasswordFor(User user)
{
_userService.CheckPasswordAsync(user, Arg.Any<string>())
.Returns(Task.FromResult(false));
}
await _sut.PostRegister(request);
private void ConfigureUserServiceToAcceptPasswordFor(User user)
{
_userService.CheckPasswordAsync(user, Arg.Any<string>())
.Returns(Task.FromResult(true));
_userService.VerifySecretAsync(user, Arg.Any<string>())
.Returns(Task.FromResult(true));
}
await _userService.Received(1).RegisterUserAsync(Arg.Any<User>(), passwordHash, token, userGuid);
}
private void ConfigureUserServiceToReturnValidIdFor(User user)
{
_userService.GetUserByIdAsync(Arg.Any<Guid>())
.Returns(Task.FromResult(user));
}
[Fact]
public async Task PostRegister_WhenUserServiceFails_ShouldThrowBadRequestException()
{
var passwordHash = "abcdef";
var token = "123456";
var userGuid = new Guid();
_userService.RegisterUserAsync(Arg.Any<User>(), passwordHash, token, userGuid)
.Returns(Task.FromResult(IdentityResult.Failed()));
var request = new RegisterRequestModel
{
Name = "Example User",
Email = "user@example.com",
MasterPasswordHash = passwordHash,
MasterPasswordHint = "example",
Token = token,
OrganizationUserId = userGuid
};
await Assert.ThrowsAsync<BadRequestException>(() => _sut.PostRegister(request));
}
[Fact]
public async Task PostPasswordHint_ShouldNotifyUserService()
{
var email = "user@example.com";
await _sut.PostPasswordHint(new PasswordHintRequestModel { Email = email });
await _userService.Received(1).SendMasterPasswordHintAsync(email);
}
[Fact]
public async Task PostEmailToken_ShouldInitiateEmailChange()
{
var user = GenerateExampleUser();
ConfigureUserServiceToReturnValidPrincipalFor(user);
ConfigureUserServiceToAcceptPasswordFor(user);
var newEmail = "example@user.com";
await _sut.PostEmailToken(new EmailTokenRequestModel { NewEmail = newEmail });
await _userService.Received(1).InitiateEmailChangeAsync(user, newEmail);
}
[Fact]
public async Task PostEmailToken_WhenNotAuthorized_ShouldThrowUnauthorizedAccessException()
{
ConfigureUserServiceToReturnNullPrincipal();
await Assert.ThrowsAsync<UnauthorizedAccessException>(
() => _sut.PostEmailToken(new EmailTokenRequestModel())
);
}
[Fact]
public async Task PostEmailToken_WhenInvalidPasssword_ShouldThrowBadRequestException()
{
var user = GenerateExampleUser();
ConfigureUserServiceToReturnValidPrincipalFor(user);
ConfigureUserServiceToRejectPasswordFor(user);
await Assert.ThrowsAsync<BadRequestException>(
() => _sut.PostEmailToken(new EmailTokenRequestModel())
);
}
[Fact]
public async Task PostEmail_ShouldChangeUserEmail()
{
var user = GenerateExampleUser();
ConfigureUserServiceToReturnValidPrincipalFor(user);
_userService.ChangeEmailAsync(user, default, default, default, default, default)
.Returns(Task.FromResult(IdentityResult.Success));
await _sut.PostEmail(new EmailRequestModel());
await _userService.Received(1).ChangeEmailAsync(user, default, default, default, default, default);
}
[Fact]
public async Task PostEmail_WhenNotAuthorized_ShouldThrownUnauthorizedAccessException()
{
ConfigureUserServiceToReturnNullPrincipal();
await Assert.ThrowsAsync<UnauthorizedAccessException>(
() => _sut.PostEmail(new EmailRequestModel())
);
}
[Fact]
public async Task PostEmail_WhenEmailCannotBeChanged_ShouldThrowBadRequestException()
{
var user = GenerateExampleUser();
ConfigureUserServiceToReturnValidPrincipalFor(user);
_userService.ChangeEmailAsync(user, default, default, default, default, default)
.Returns(Task.FromResult(IdentityResult.Failed()));
await Assert.ThrowsAsync<BadRequestException>(
() => _sut.PostEmail(new EmailRequestModel())
);
}
[Fact]
public async Task PostVerifyEmail_ShouldSendEmailVerification()
{
var user = GenerateExampleUser();
ConfigureUserServiceToReturnValidPrincipalFor(user);
await _sut.PostVerifyEmail();
await _userService.Received(1).SendEmailVerificationAsync(user);
}
[Fact]
public async Task PostVerifyEmail_WhenNotAuthorized_ShouldThrownUnauthorizedAccessException()
{
ConfigureUserServiceToReturnNullPrincipal();
await Assert.ThrowsAsync<UnauthorizedAccessException>(
() => _sut.PostVerifyEmail()
);
}
[Fact]
public async Task PostVerifyEmailToken_ShouldConfirmEmail()
{
var user = GenerateExampleUser();
ConfigureUserServiceToReturnValidIdFor(user);
_userService.ConfirmEmailAsync(user, Arg.Any<string>())
.Returns(Task.FromResult(IdentityResult.Success));
await _sut.PostVerifyEmailToken(new VerifyEmailRequestModel { UserId = "12345678-1234-1234-1234-123456789012" });
await _userService.Received(1).ConfirmEmailAsync(user, Arg.Any<string>());
}
[Fact]
public async Task PostVerifyEmailToken_WhenUserDoesNotExist_ShouldThrowUnauthorizedAccessException()
{
var user = GenerateExampleUser();
ConfigureUserServiceToReturnNullUserId();
await Assert.ThrowsAsync<UnauthorizedAccessException>(
() => _sut.PostVerifyEmailToken(new VerifyEmailRequestModel { UserId = "12345678-1234-1234-1234-123456789012" })
);
}
[Fact]
public async Task PostVerifyEmailToken_WhenEmailConfirmationFails_ShouldThrowBadRequestException()
{
var user = GenerateExampleUser();
ConfigureUserServiceToReturnValidIdFor(user);
_userService.ConfirmEmailAsync(user, Arg.Any<string>())
.Returns(Task.FromResult(IdentityResult.Failed()));
await Assert.ThrowsAsync<BadRequestException>(
() => _sut.PostVerifyEmailToken(new VerifyEmailRequestModel { UserId = "12345678-1234-1234-1234-123456789012" })
);
}
[Fact]
public async Task PostPassword_ShouldChangePassword()
{
var user = GenerateExampleUser();
ConfigureUserServiceToReturnValidPrincipalFor(user);
_userService.ChangePasswordAsync(user, default, default, default, default)
.Returns(Task.FromResult(IdentityResult.Success));
await _sut.PostPassword(new PasswordRequestModel());
await _userService.Received(1).ChangePasswordAsync(user, default, default, default, default);
}
[Fact]
public async Task PostPassword_WhenNotAuthorized_ShouldThrowUnauthorizedAccessException()
{
ConfigureUserServiceToReturnNullPrincipal();
await Assert.ThrowsAsync<UnauthorizedAccessException>(
() => _sut.PostPassword(new PasswordRequestModel())
);
}
[Fact]
public async Task PostPassword_WhenPasswordChangeFails_ShouldBadRequestException()
{
var user = GenerateExampleUser();
ConfigureUserServiceToReturnValidPrincipalFor(user);
_userService.ChangePasswordAsync(user, default, default, default, default)
.Returns(Task.FromResult(IdentityResult.Failed()));
await Assert.ThrowsAsync<BadRequestException>(
() => _sut.PostPassword(new PasswordRequestModel())
);
}
[Fact]
public async Task GetApiKey_ShouldReturnApiKeyResponse()
{
var user = GenerateExampleUser();
ConfigureUserServiceToReturnValidPrincipalFor(user);
ConfigureUserServiceToAcceptPasswordFor(user);
await _sut.ApiKey(new SecretVerificationRequestModel());
}
[Fact]
public async Task GetApiKey_WhenUserDoesNotExist_ShouldThrowUnauthorizedAccessException()
{
ConfigureUserServiceToReturnNullPrincipal();
await Assert.ThrowsAsync<UnauthorizedAccessException>(
() => _sut.ApiKey(new SecretVerificationRequestModel())
);
}
[Fact]
public async Task GetApiKey_WhenPasswordCheckFails_ShouldThrowBadRequestException()
{
var user = GenerateExampleUser();
ConfigureUserServiceToReturnValidPrincipalFor(user);
ConfigureUserServiceToRejectPasswordFor(user);
await Assert.ThrowsAsync<BadRequestException>(
() => _sut.ApiKey(new SecretVerificationRequestModel())
);
}
[Fact]
public async Task PostRotateApiKey_ShouldRotateApiKey()
{
var user = GenerateExampleUser();
ConfigureUserServiceToReturnValidPrincipalFor(user);
ConfigureUserServiceToAcceptPasswordFor(user);
await _sut.RotateApiKey(new SecretVerificationRequestModel());
}
[Fact]
public async Task PostRotateApiKey_WhenUserDoesNotExist_ShouldThrowUnauthorizedAccessException()
{
ConfigureUserServiceToReturnNullPrincipal();
await Assert.ThrowsAsync<UnauthorizedAccessException>(
() => _sut.ApiKey(new SecretVerificationRequestModel())
);
}
[Fact]
public async Task PostRotateApiKey_WhenPasswordCheckFails_ShouldThrowBadRequestException()
{
var user = GenerateExampleUser();
ConfigureUserServiceToReturnValidPrincipalFor(user);
ConfigureUserServiceToRejectPasswordFor(user);
await Assert.ThrowsAsync<BadRequestException>(
() => _sut.ApiKey(new SecretVerificationRequestModel())
);
}
// Below are helper functions that currently belong to this
// test class, but ultimately may need to be split out into
// something greater in order to share common test steps with
// other test suites. They are included here for the time being
// until that day comes.
private User GenerateExampleUser()
{
return new User
{
Email = "user@example.com"
};
}
private void ConfigureUserServiceToReturnNullPrincipal()
{
_userService.GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>())
.Returns(Task.FromResult((User)null));
}
private void ConfigureUserServiceToReturnValidPrincipalFor(User user)
{
_userService.GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>())
.Returns(Task.FromResult(user));
}
private void ConfigureUserServiceToRejectPasswordFor(User user)
{
_userService.CheckPasswordAsync(user, Arg.Any<string>())
.Returns(Task.FromResult(false));
}
private void ConfigureUserServiceToAcceptPasswordFor(User user)
{
_userService.CheckPasswordAsync(user, Arg.Any<string>())
.Returns(Task.FromResult(true));
_userService.VerifySecretAsync(user, Arg.Any<string>())
.Returns(Task.FromResult(true));
}
private void ConfigureUserServiceToReturnValidIdFor(User user)
{
_userService.GetUserByIdAsync(Arg.Any<Guid>())
.Returns(Task.FromResult(user));
}
private void ConfigureUserServiceToReturnNullUserId()
{
_userService.GetUserByIdAsync(Arg.Any<Guid>())
.Returns(Task.FromResult((User)null));
}
private void ConfigureUserServiceToReturnNullUserId()
{
_userService.GetUserByIdAsync(Arg.Any<Guid>())
.Returns(Task.FromResult((User)null));
}
}

View File

@ -11,79 +11,78 @@ using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
using Xunit;
namespace Bit.Api.Test.Controllers
namespace Bit.Api.Test.Controllers;
[ControllerCustomize(typeof(CollectionsController))]
[SutProviderCustomize]
public class CollectionsControllerTests
{
[ControllerCustomize(typeof(CollectionsController))]
[SutProviderCustomize]
public class CollectionsControllerTests
[Theory, BitAutoData]
public async Task Post_Success(Guid orgId, SutProvider<CollectionsController> sutProvider)
{
[Theory, BitAutoData]
public async Task Post_Success(Guid orgId, SutProvider<CollectionsController> sutProvider)
sutProvider.GetDependency<ICurrentContext>()
.CreateNewCollections(orgId)
.Returns(true);
sutProvider.GetDependency<ICurrentContext>()
.EditAnyCollection(orgId)
.Returns(false);
var collectionRequest = new CollectionRequestModel
{
sutProvider.GetDependency<ICurrentContext>()
.CreateNewCollections(orgId)
.Returns(true);
Name = "encrypted_string",
ExternalId = "my_external_id"
};
sutProvider.GetDependency<ICurrentContext>()
.EditAnyCollection(orgId)
.Returns(false);
_ = await sutProvider.Sut.Post(orgId, collectionRequest);
var collectionRequest = new CollectionRequestModel
await sutProvider.GetDependency<ICollectionService>()
.Received(1)
.SaveAsync(Arg.Any<Collection>(), Arg.Any<IEnumerable<SelectionReadOnly>>(), null);
}
[Theory, BitAutoData]
public async Task Put_Success(Guid orgId, Guid collectionId, Guid userId, CollectionRequestModel collectionRequest,
SutProvider<CollectionsController> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>()
.ViewAssignedCollections(orgId)
.Returns(true);
sutProvider.GetDependency<ICurrentContext>()
.EditAssignedCollections(orgId)
.Returns(true);
sutProvider.GetDependency<ICurrentContext>()
.UserId
.Returns(userId);
sutProvider.GetDependency<ICollectionRepository>()
.GetByIdAsync(collectionId, userId)
.Returns(new CollectionDetails
{
Name = "encrypted_string",
ExternalId = "my_external_id"
};
OrganizationId = orgId,
});
_ = await sutProvider.Sut.Post(orgId, collectionRequest);
_ = await sutProvider.Sut.Put(orgId, collectionId, collectionRequest);
}
await sutProvider.GetDependency<ICollectionService>()
.Received(1)
.SaveAsync(Arg.Any<Collection>(), Arg.Any<IEnumerable<SelectionReadOnly>>(), null);
}
[Theory, BitAutoData]
public async Task Put_CanNotEditAssignedCollection_ThrowsNotFound(Guid orgId, Guid collectionId, Guid userId, CollectionRequestModel collectionRequest,
SutProvider<CollectionsController> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>()
.EditAssignedCollections(orgId)
.Returns(true);
[Theory, BitAutoData]
public async Task Put_Success(Guid orgId, Guid collectionId, Guid userId, CollectionRequestModel collectionRequest,
SutProvider<CollectionsController> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>()
.ViewAssignedCollections(orgId)
.Returns(true);
sutProvider.GetDependency<ICurrentContext>()
.UserId
.Returns(userId);
sutProvider.GetDependency<ICurrentContext>()
.EditAssignedCollections(orgId)
.Returns(true);
sutProvider.GetDependency<ICollectionRepository>()
.GetByIdAsync(collectionId, userId)
.Returns(Task.FromResult<CollectionDetails>(null));
sutProvider.GetDependency<ICurrentContext>()
.UserId
.Returns(userId);
sutProvider.GetDependency<ICollectionRepository>()
.GetByIdAsync(collectionId, userId)
.Returns(new CollectionDetails
{
OrganizationId = orgId,
});
_ = await sutProvider.Sut.Put(orgId, collectionId, collectionRequest);
}
[Theory, BitAutoData]
public async Task Put_CanNotEditAssignedCollection_ThrowsNotFound(Guid orgId, Guid collectionId, Guid userId, CollectionRequestModel collectionRequest,
SutProvider<CollectionsController> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>()
.EditAssignedCollections(orgId)
.Returns(true);
sutProvider.GetDependency<ICurrentContext>()
.UserId
.Returns(userId);
sutProvider.GetDependency<ICollectionRepository>()
.GetByIdAsync(collectionId, userId)
.Returns(Task.FromResult<CollectionDetails>(null));
_ = await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.Put(orgId, collectionId, collectionRequest));
}
_ = await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.Put(orgId, collectionId, collectionRequest));
}
}

View File

@ -18,302 +18,301 @@ using Bit.Test.Common.Helpers;
using NSubstitute;
using Xunit;
namespace Bit.Api.Test.Controllers
namespace Bit.Api.Test.Controllers;
[ControllerCustomize(typeof(OrganizationConnectionsController))]
[SutProviderCustomize]
[JsonDocumentCustomize]
public class OrganizationConnectionsControllerTests
{
[ControllerCustomize(typeof(OrganizationConnectionsController))]
[SutProviderCustomize]
[JsonDocumentCustomize]
public class OrganizationConnectionsControllerTests
public static IEnumerable<object[]> ConnectionTypes =>
Enum.GetValues<OrganizationConnectionType>().Select(p => new object[] { p });
[Theory]
[BitAutoData(true, true)]
[BitAutoData(false, true)]
[BitAutoData(true, false)]
[BitAutoData(false, false)]
public void ConnectionEnabled_RequiresBothSelfHostAndCommunications(bool selfHosted, bool enableCloudCommunication, SutProvider<OrganizationConnectionsController> sutProvider)
{
public static IEnumerable<object[]> ConnectionTypes =>
Enum.GetValues<OrganizationConnectionType>().Select(p => new object[] { p });
var globalSettingsMock = sutProvider.GetDependency<IGlobalSettings>();
globalSettingsMock.SelfHosted.Returns(selfHosted);
globalSettingsMock.EnableCloudCommunication.Returns(enableCloudCommunication);
Action<bool> assert = selfHosted && enableCloudCommunication ? Assert.True : Assert.False;
[Theory]
[BitAutoData(true, true)]
[BitAutoData(false, true)]
[BitAutoData(true, false)]
[BitAutoData(false, false)]
public void ConnectionEnabled_RequiresBothSelfHostAndCommunications(bool selfHosted, bool enableCloudCommunication, SutProvider<OrganizationConnectionsController> sutProvider)
{
var globalSettingsMock = sutProvider.GetDependency<IGlobalSettings>();
globalSettingsMock.SelfHosted.Returns(selfHosted);
globalSettingsMock.EnableCloudCommunication.Returns(enableCloudCommunication);
var result = sutProvider.Sut.ConnectionsEnabled();
Action<bool> assert = selfHosted && enableCloudCommunication ? Assert.True : Assert.False;
var result = sutProvider.Sut.ConnectionsEnabled();
assert(result);
}
[Theory]
[BitAutoData]
public async Task CreateConnection_CloudBillingSync_RequiresOwnerPermissions(SutProvider<OrganizationConnectionsController> sutProvider)
{
var model = new OrganizationConnectionRequestModel
{
Type = OrganizationConnectionType.CloudBillingSync,
};
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.CreateConnection(model));
Assert.Contains($"You do not have permission to create a connection of type", exception.Message);
}
[Theory]
[BitMemberAutoData(nameof(ConnectionTypes))]
public async Task CreateConnection_OnlyOneConnectionOfEachType(OrganizationConnectionType type,
OrganizationConnectionRequestModel model, BillingSyncConfig config, Guid existingEntityId,
SutProvider<OrganizationConnectionsController> sutProvider)
{
model.Type = type;
model.Config = JsonDocumentFromObject(config);
var typedModel = new OrganizationConnectionRequestModel<BillingSyncConfig>(model);
var existing = typedModel.ToData(existingEntityId).ToEntity();
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(model.OrganizationId).Returns(true);
sutProvider.GetDependency<IOrganizationConnectionRepository>().GetByOrganizationIdTypeAsync(model.OrganizationId, type).Returns(new[] { existing });
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.CreateConnection(model));
Assert.Contains($"The requested organization already has a connection of type {model.Type}. Only one of each connection type may exist per organization.", exception.Message);
}
[Theory]
[BitAutoData]
public async Task CreateConnection_BillingSyncType_InvalidLicense_Throws(OrganizationConnectionRequestModel model,
BillingSyncConfig config, Guid cloudOrgId, OrganizationLicense organizationLicense,
SutProvider<OrganizationConnectionsController> sutProvider)
{
model.Type = OrganizationConnectionType.CloudBillingSync;
organizationLicense.Id = cloudOrgId;
model.Config = JsonDocumentFromObject(config);
var typedModel = new OrganizationConnectionRequestModel<BillingSyncConfig>(model);
typedModel.ParsedConfig.CloudOrganizationId = cloudOrgId;
sutProvider.GetDependency<ICurrentContext>()
.OrganizationOwner(model.OrganizationId)
.Returns(true);
sutProvider.GetDependency<ILicensingService>()
.ReadOrganizationLicenseAsync(model.OrganizationId)
.Returns(organizationLicense);
sutProvider.GetDependency<ILicensingService>()
.VerifyLicense(organizationLicense)
.Returns(false);
await Assert.ThrowsAsync<BadRequestException>(async () => await sutProvider.Sut.CreateConnection(model));
}
[Theory]
[BitAutoData]
public async Task CreateConnection_Success(OrganizationConnectionRequestModel model, BillingSyncConfig config,
Guid cloudOrgId, OrganizationLicense organizationLicense, SutProvider<OrganizationConnectionsController> sutProvider)
{
organizationLicense.Id = cloudOrgId;
model.Config = JsonDocumentFromObject(config);
var typedModel = new OrganizationConnectionRequestModel<BillingSyncConfig>(model);
typedModel.ParsedConfig.CloudOrganizationId = cloudOrgId;
sutProvider.GetDependency<IGlobalSettings>().SelfHosted.Returns(true);
sutProvider.GetDependency<ICreateOrganizationConnectionCommand>().CreateAsync<BillingSyncConfig>(default)
.ReturnsForAnyArgs(typedModel.ToData(Guid.NewGuid()).ToEntity());
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(model.OrganizationId).Returns(true);
sutProvider.GetDependency<ILicensingService>()
.ReadOrganizationLicenseAsync(Arg.Any<Guid>())
.Returns(organizationLicense);
sutProvider.GetDependency<ILicensingService>()
.VerifyLicense(organizationLicense)
.Returns(true);
await sutProvider.Sut.CreateConnection(model);
await sutProvider.GetDependency<ICreateOrganizationConnectionCommand>().Received(1)
.CreateAsync(Arg.Is(AssertHelper.AssertPropertyEqual(typedModel.ToData())));
}
[Theory]
[BitAutoData]
public async Task UpdateConnection_RequiresOwnerPermissions(SutProvider<OrganizationConnectionsController> sutProvider)
{
sutProvider.GetDependency<IOrganizationConnectionRepository>()
.GetByIdAsync(Arg.Any<Guid>())
.Returns(new OrganizationConnection());
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.UpdateConnection(default, null));
Assert.Contains("You do not have permission to update this connection.", exception.Message);
}
[Theory]
[BitAutoData(OrganizationConnectionType.CloudBillingSync)]
public async Task UpdateConnection_BillingSync_OnlyOneConnectionOfEachType(OrganizationConnectionType type,
OrganizationConnection existing1, OrganizationConnection existing2, BillingSyncConfig config,
SutProvider<OrganizationConnectionsController> sutProvider)
{
existing1.Type = existing2.Type = type;
existing1.Config = JsonSerializer.Serialize(config);
var typedModel = RequestModelFromEntity<BillingSyncConfig>(existing1);
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(typedModel.OrganizationId).Returns(true);
var orgConnectionRepository = sutProvider.GetDependency<IOrganizationConnectionRepository>();
orgConnectionRepository.GetByIdAsync(existing1.Id).Returns(existing1);
orgConnectionRepository.GetByIdAsync(existing2.Id).Returns(existing2);
orgConnectionRepository.GetByOrganizationIdTypeAsync(typedModel.OrganizationId, type).Returns(new[] { existing1, existing2 });
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.UpdateConnection(existing1.Id, typedModel));
Assert.Contains($"The requested organization already has a connection of type {typedModel.Type}. Only one of each connection type may exist per organization.", exception.Message);
}
[Theory]
[BitAutoData(OrganizationConnectionType.Scim)]
public async Task UpdateConnection_Scim_OnlyOneConnectionOfEachType(OrganizationConnectionType type,
OrganizationConnection existing1, OrganizationConnection existing2, ScimConfig config,
SutProvider<OrganizationConnectionsController> sutProvider)
{
existing1.Type = existing2.Type = type;
existing1.Config = JsonSerializer.Serialize(config);
var typedModel = RequestModelFromEntity<ScimConfig>(existing1);
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(typedModel.OrganizationId).Returns(true);
sutProvider.GetDependency<IOrganizationConnectionRepository>()
.GetByIdAsync(existing1.Id)
.Returns(existing1);
sutProvider.GetDependency<ICurrentContext>().ManageScim(typedModel.OrganizationId).Returns(true);
sutProvider.GetDependency<IOrganizationConnectionRepository>()
.GetByOrganizationIdTypeAsync(typedModel.OrganizationId, type)
.Returns(new[] { existing1, existing2 });
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.UpdateConnection(existing1.Id, typedModel));
Assert.Contains($"The requested organization already has a connection of type {typedModel.Type}. Only one of each connection type may exist per organization.", exception.Message);
}
[Theory]
[BitAutoData]
public async Task UpdateConnection_Success(OrganizationConnection existing, BillingSyncConfig config,
OrganizationConnection updated,
SutProvider<OrganizationConnectionsController> sutProvider)
{
existing.SetConfig(new BillingSyncConfig
{
CloudOrganizationId = config.CloudOrganizationId,
});
updated.Config = JsonSerializer.Serialize(config);
updated.Id = existing.Id;
updated.Type = OrganizationConnectionType.CloudBillingSync;
var model = RequestModelFromEntity<BillingSyncConfig>(updated);
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(model.OrganizationId).Returns(true);
sutProvider.GetDependency<IOrganizationConnectionRepository>()
.GetByOrganizationIdTypeAsync(model.OrganizationId, model.Type)
.Returns(new[] { existing });
sutProvider.GetDependency<IUpdateOrganizationConnectionCommand>()
.UpdateAsync<BillingSyncConfig>(default)
.ReturnsForAnyArgs(updated);
sutProvider.GetDependency<IOrganizationConnectionRepository>()
.GetByIdAsync(existing.Id)
.Returns(existing);
var expected = new OrganizationConnectionResponseModel(updated, typeof(BillingSyncConfig));
var result = await sutProvider.Sut.UpdateConnection(existing.Id, model);
AssertHelper.AssertPropertyEqual(expected, result);
await sutProvider.GetDependency<IUpdateOrganizationConnectionCommand>().Received(1)
.UpdateAsync(Arg.Is(AssertHelper.AssertPropertyEqual(model.ToData(updated.Id))));
}
[Theory]
[BitAutoData]
public async Task UpdateConnection_DoesNotExist_ThrowsNotFound(SutProvider<OrganizationConnectionsController> sutProvider)
{
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.UpdateConnection(Guid.NewGuid(), null));
}
[Theory]
[BitAutoData]
public async Task GetConnection_RequiresOwnerPermissions(Guid connectionId, SutProvider<OrganizationConnectionsController> sutProvider)
{
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.GetConnection(connectionId, OrganizationConnectionType.CloudBillingSync));
Assert.Contains("You do not have permission to retrieve a connection of type", exception.Message);
}
[Theory]
[BitAutoData]
public async Task GetConnection_Success(OrganizationConnection connection, BillingSyncConfig config,
SutProvider<OrganizationConnectionsController> sutProvider)
{
connection.Config = JsonSerializer.Serialize(config);
sutProvider.GetDependency<IGlobalSettings>().SelfHosted.Returns(true);
sutProvider.GetDependency<IOrganizationConnectionRepository>()
.GetByOrganizationIdTypeAsync(connection.OrganizationId, connection.Type)
.Returns(new[] { connection });
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(connection.OrganizationId).Returns(true);
var expected = new OrganizationConnectionResponseModel(connection, typeof(BillingSyncConfig));
var actual = await sutProvider.Sut.GetConnection(connection.OrganizationId, connection.Type);
AssertHelper.AssertPropertyEqual(expected, actual);
}
[Theory]
[BitAutoData]
public async Task DeleteConnection_NotFound(Guid connectionId,
SutProvider<OrganizationConnectionsController> sutProvider)
{
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.DeleteConnection(connectionId));
}
[Theory]
[BitAutoData]
public async Task DeleteConnection_RequiresOwnerPermissions(OrganizationConnection connection,
SutProvider<OrganizationConnectionsController> sutProvider)
{
sutProvider.GetDependency<IOrganizationConnectionRepository>().GetByIdAsync(connection.Id).Returns(connection);
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.DeleteConnection(connection.Id));
Assert.Contains("You do not have permission to remove this connection of type", exception.Message);
}
[Theory]
[BitAutoData]
public async Task DeleteConnection_Success(OrganizationConnection connection,
SutProvider<OrganizationConnectionsController> sutProvider)
{
sutProvider.GetDependency<IOrganizationConnectionRepository>().GetByIdAsync(connection.Id).Returns(connection);
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(connection.OrganizationId).Returns(true);
await sutProvider.Sut.DeleteConnection(connection.Id);
await sutProvider.GetDependency<IDeleteOrganizationConnectionCommand>().DeleteAsync(connection);
}
private static OrganizationConnectionRequestModel<T> RequestModelFromEntity<T>(OrganizationConnection entity)
where T : new()
{
return new(new OrganizationConnectionRequestModel()
{
Type = entity.Type,
OrganizationId = entity.OrganizationId,
Enabled = entity.Enabled,
Config = JsonDocument.Parse(entity.Config),
});
}
private static JsonDocument JsonDocumentFromObject<T>(T obj) => JsonDocument.Parse(JsonSerializer.Serialize(obj));
assert(result);
}
[Theory]
[BitAutoData]
public async Task CreateConnection_CloudBillingSync_RequiresOwnerPermissions(SutProvider<OrganizationConnectionsController> sutProvider)
{
var model = new OrganizationConnectionRequestModel
{
Type = OrganizationConnectionType.CloudBillingSync,
};
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.CreateConnection(model));
Assert.Contains($"You do not have permission to create a connection of type", exception.Message);
}
[Theory]
[BitMemberAutoData(nameof(ConnectionTypes))]
public async Task CreateConnection_OnlyOneConnectionOfEachType(OrganizationConnectionType type,
OrganizationConnectionRequestModel model, BillingSyncConfig config, Guid existingEntityId,
SutProvider<OrganizationConnectionsController> sutProvider)
{
model.Type = type;
model.Config = JsonDocumentFromObject(config);
var typedModel = new OrganizationConnectionRequestModel<BillingSyncConfig>(model);
var existing = typedModel.ToData(existingEntityId).ToEntity();
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(model.OrganizationId).Returns(true);
sutProvider.GetDependency<IOrganizationConnectionRepository>().GetByOrganizationIdTypeAsync(model.OrganizationId, type).Returns(new[] { existing });
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.CreateConnection(model));
Assert.Contains($"The requested organization already has a connection of type {model.Type}. Only one of each connection type may exist per organization.", exception.Message);
}
[Theory]
[BitAutoData]
public async Task CreateConnection_BillingSyncType_InvalidLicense_Throws(OrganizationConnectionRequestModel model,
BillingSyncConfig config, Guid cloudOrgId, OrganizationLicense organizationLicense,
SutProvider<OrganizationConnectionsController> sutProvider)
{
model.Type = OrganizationConnectionType.CloudBillingSync;
organizationLicense.Id = cloudOrgId;
model.Config = JsonDocumentFromObject(config);
var typedModel = new OrganizationConnectionRequestModel<BillingSyncConfig>(model);
typedModel.ParsedConfig.CloudOrganizationId = cloudOrgId;
sutProvider.GetDependency<ICurrentContext>()
.OrganizationOwner(model.OrganizationId)
.Returns(true);
sutProvider.GetDependency<ILicensingService>()
.ReadOrganizationLicenseAsync(model.OrganizationId)
.Returns(organizationLicense);
sutProvider.GetDependency<ILicensingService>()
.VerifyLicense(organizationLicense)
.Returns(false);
await Assert.ThrowsAsync<BadRequestException>(async () => await sutProvider.Sut.CreateConnection(model));
}
[Theory]
[BitAutoData]
public async Task CreateConnection_Success(OrganizationConnectionRequestModel model, BillingSyncConfig config,
Guid cloudOrgId, OrganizationLicense organizationLicense, SutProvider<OrganizationConnectionsController> sutProvider)
{
organizationLicense.Id = cloudOrgId;
model.Config = JsonDocumentFromObject(config);
var typedModel = new OrganizationConnectionRequestModel<BillingSyncConfig>(model);
typedModel.ParsedConfig.CloudOrganizationId = cloudOrgId;
sutProvider.GetDependency<IGlobalSettings>().SelfHosted.Returns(true);
sutProvider.GetDependency<ICreateOrganizationConnectionCommand>().CreateAsync<BillingSyncConfig>(default)
.ReturnsForAnyArgs(typedModel.ToData(Guid.NewGuid()).ToEntity());
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(model.OrganizationId).Returns(true);
sutProvider.GetDependency<ILicensingService>()
.ReadOrganizationLicenseAsync(Arg.Any<Guid>())
.Returns(organizationLicense);
sutProvider.GetDependency<ILicensingService>()
.VerifyLicense(organizationLicense)
.Returns(true);
await sutProvider.Sut.CreateConnection(model);
await sutProvider.GetDependency<ICreateOrganizationConnectionCommand>().Received(1)
.CreateAsync(Arg.Is(AssertHelper.AssertPropertyEqual(typedModel.ToData())));
}
[Theory]
[BitAutoData]
public async Task UpdateConnection_RequiresOwnerPermissions(SutProvider<OrganizationConnectionsController> sutProvider)
{
sutProvider.GetDependency<IOrganizationConnectionRepository>()
.GetByIdAsync(Arg.Any<Guid>())
.Returns(new OrganizationConnection());
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.UpdateConnection(default, null));
Assert.Contains("You do not have permission to update this connection.", exception.Message);
}
[Theory]
[BitAutoData(OrganizationConnectionType.CloudBillingSync)]
public async Task UpdateConnection_BillingSync_OnlyOneConnectionOfEachType(OrganizationConnectionType type,
OrganizationConnection existing1, OrganizationConnection existing2, BillingSyncConfig config,
SutProvider<OrganizationConnectionsController> sutProvider)
{
existing1.Type = existing2.Type = type;
existing1.Config = JsonSerializer.Serialize(config);
var typedModel = RequestModelFromEntity<BillingSyncConfig>(existing1);
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(typedModel.OrganizationId).Returns(true);
var orgConnectionRepository = sutProvider.GetDependency<IOrganizationConnectionRepository>();
orgConnectionRepository.GetByIdAsync(existing1.Id).Returns(existing1);
orgConnectionRepository.GetByIdAsync(existing2.Id).Returns(existing2);
orgConnectionRepository.GetByOrganizationIdTypeAsync(typedModel.OrganizationId, type).Returns(new[] { existing1, existing2 });
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.UpdateConnection(existing1.Id, typedModel));
Assert.Contains($"The requested organization already has a connection of type {typedModel.Type}. Only one of each connection type may exist per organization.", exception.Message);
}
[Theory]
[BitAutoData(OrganizationConnectionType.Scim)]
public async Task UpdateConnection_Scim_OnlyOneConnectionOfEachType(OrganizationConnectionType type,
OrganizationConnection existing1, OrganizationConnection existing2, ScimConfig config,
SutProvider<OrganizationConnectionsController> sutProvider)
{
existing1.Type = existing2.Type = type;
existing1.Config = JsonSerializer.Serialize(config);
var typedModel = RequestModelFromEntity<ScimConfig>(existing1);
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(typedModel.OrganizationId).Returns(true);
sutProvider.GetDependency<IOrganizationConnectionRepository>()
.GetByIdAsync(existing1.Id)
.Returns(existing1);
sutProvider.GetDependency<ICurrentContext>().ManageScim(typedModel.OrganizationId).Returns(true);
sutProvider.GetDependency<IOrganizationConnectionRepository>()
.GetByOrganizationIdTypeAsync(typedModel.OrganizationId, type)
.Returns(new[] { existing1, existing2 });
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.UpdateConnection(existing1.Id, typedModel));
Assert.Contains($"The requested organization already has a connection of type {typedModel.Type}. Only one of each connection type may exist per organization.", exception.Message);
}
[Theory]
[BitAutoData]
public async Task UpdateConnection_Success(OrganizationConnection existing, BillingSyncConfig config,
OrganizationConnection updated,
SutProvider<OrganizationConnectionsController> sutProvider)
{
existing.SetConfig(new BillingSyncConfig
{
CloudOrganizationId = config.CloudOrganizationId,
});
updated.Config = JsonSerializer.Serialize(config);
updated.Id = existing.Id;
updated.Type = OrganizationConnectionType.CloudBillingSync;
var model = RequestModelFromEntity<BillingSyncConfig>(updated);
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(model.OrganizationId).Returns(true);
sutProvider.GetDependency<IOrganizationConnectionRepository>()
.GetByOrganizationIdTypeAsync(model.OrganizationId, model.Type)
.Returns(new[] { existing });
sutProvider.GetDependency<IUpdateOrganizationConnectionCommand>()
.UpdateAsync<BillingSyncConfig>(default)
.ReturnsForAnyArgs(updated);
sutProvider.GetDependency<IOrganizationConnectionRepository>()
.GetByIdAsync(existing.Id)
.Returns(existing);
var expected = new OrganizationConnectionResponseModel(updated, typeof(BillingSyncConfig));
var result = await sutProvider.Sut.UpdateConnection(existing.Id, model);
AssertHelper.AssertPropertyEqual(expected, result);
await sutProvider.GetDependency<IUpdateOrganizationConnectionCommand>().Received(1)
.UpdateAsync(Arg.Is(AssertHelper.AssertPropertyEqual(model.ToData(updated.Id))));
}
[Theory]
[BitAutoData]
public async Task UpdateConnection_DoesNotExist_ThrowsNotFound(SutProvider<OrganizationConnectionsController> sutProvider)
{
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.UpdateConnection(Guid.NewGuid(), null));
}
[Theory]
[BitAutoData]
public async Task GetConnection_RequiresOwnerPermissions(Guid connectionId, SutProvider<OrganizationConnectionsController> sutProvider)
{
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.GetConnection(connectionId, OrganizationConnectionType.CloudBillingSync));
Assert.Contains("You do not have permission to retrieve a connection of type", exception.Message);
}
[Theory]
[BitAutoData]
public async Task GetConnection_Success(OrganizationConnection connection, BillingSyncConfig config,
SutProvider<OrganizationConnectionsController> sutProvider)
{
connection.Config = JsonSerializer.Serialize(config);
sutProvider.GetDependency<IGlobalSettings>().SelfHosted.Returns(true);
sutProvider.GetDependency<IOrganizationConnectionRepository>()
.GetByOrganizationIdTypeAsync(connection.OrganizationId, connection.Type)
.Returns(new[] { connection });
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(connection.OrganizationId).Returns(true);
var expected = new OrganizationConnectionResponseModel(connection, typeof(BillingSyncConfig));
var actual = await sutProvider.Sut.GetConnection(connection.OrganizationId, connection.Type);
AssertHelper.AssertPropertyEqual(expected, actual);
}
[Theory]
[BitAutoData]
public async Task DeleteConnection_NotFound(Guid connectionId,
SutProvider<OrganizationConnectionsController> sutProvider)
{
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.DeleteConnection(connectionId));
}
[Theory]
[BitAutoData]
public async Task DeleteConnection_RequiresOwnerPermissions(OrganizationConnection connection,
SutProvider<OrganizationConnectionsController> sutProvider)
{
sutProvider.GetDependency<IOrganizationConnectionRepository>().GetByIdAsync(connection.Id).Returns(connection);
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.DeleteConnection(connection.Id));
Assert.Contains("You do not have permission to remove this connection of type", exception.Message);
}
[Theory]
[BitAutoData]
public async Task DeleteConnection_Success(OrganizationConnection connection,
SutProvider<OrganizationConnectionsController> sutProvider)
{
sutProvider.GetDependency<IOrganizationConnectionRepository>().GetByIdAsync(connection.Id).Returns(connection);
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(connection.OrganizationId).Returns(true);
await sutProvider.Sut.DeleteConnection(connection.Id);
await sutProvider.GetDependency<IDeleteOrganizationConnectionCommand>().DeleteAsync(connection);
}
private static OrganizationConnectionRequestModel<T> RequestModelFromEntity<T>(OrganizationConnection entity)
where T : new()
{
return new(new OrganizationConnectionRequestModel()
{
Type = entity.Type,
OrganizationId = entity.OrganizationId,
Enabled = entity.Enabled,
Config = JsonDocument.Parse(entity.Config),
});
}
private static JsonDocument JsonDocumentFromObject<T>(T obj) => JsonDocument.Parse(JsonSerializer.Serialize(obj));
}

View File

@ -13,136 +13,135 @@ using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
using Xunit;
namespace Bit.Api.Test.Controllers
namespace Bit.Api.Test.Controllers;
[ControllerCustomize(typeof(OrganizationSponsorshipsController))]
[SutProviderCustomize]
public class OrganizationSponsorshipsControllerTests
{
[ControllerCustomize(typeof(OrganizationSponsorshipsController))]
[SutProviderCustomize]
public class OrganizationSponsorshipsControllerTests
public static IEnumerable<object[]> EnterprisePlanTypes =>
Enum.GetValues<PlanType>().Where(p => StaticStore.GetPlan(p).Product == ProductType.Enterprise).Select(p => new object[] { p });
public static IEnumerable<object[]> NonEnterprisePlanTypes =>
Enum.GetValues<PlanType>().Where(p => StaticStore.GetPlan(p).Product != ProductType.Enterprise).Select(p => new object[] { p });
public static IEnumerable<object[]> NonFamiliesPlanTypes =>
Enum.GetValues<PlanType>().Where(p => StaticStore.GetPlan(p).Product != ProductType.Families).Select(p => new object[] { p });
public static IEnumerable<object[]> NonConfirmedOrganizationUsersStatuses =>
Enum.GetValues<OrganizationUserStatusType>()
.Where(s => s != OrganizationUserStatusType.Confirmed)
.Select(s => new object[] { s });
[Theory]
[BitAutoData]
public async Task RedeemSponsorship_BadToken_ThrowsBadRequest(string sponsorshipToken, User user,
OrganizationSponsorshipRedeemRequestModel model, SutProvider<OrganizationSponsorshipsController> sutProvider)
{
public static IEnumerable<object[]> EnterprisePlanTypes =>
Enum.GetValues<PlanType>().Where(p => StaticStore.GetPlan(p).Product == ProductType.Enterprise).Select(p => new object[] { p });
public static IEnumerable<object[]> NonEnterprisePlanTypes =>
Enum.GetValues<PlanType>().Where(p => StaticStore.GetPlan(p).Product != ProductType.Enterprise).Select(p => new object[] { p });
public static IEnumerable<object[]> NonFamiliesPlanTypes =>
Enum.GetValues<PlanType>().Where(p => StaticStore.GetPlan(p).Product != ProductType.Families).Select(p => new object[] { p });
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(user.Id);
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(user.Id)
.Returns(user);
sutProvider.GetDependency<IValidateRedemptionTokenCommand>().ValidateRedemptionTokenAsync(sponsorshipToken,
user.Email).Returns((false, null));
public static IEnumerable<object[]> NonConfirmedOrganizationUsersStatuses =>
Enum.GetValues<OrganizationUserStatusType>()
.Where(s => s != OrganizationUserStatusType.Confirmed)
.Select(s => new object[] { s });
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.RedeemSponsorship(sponsorshipToken, model));
Assert.Contains("Failed to parse sponsorship token.", exception.Message);
await sutProvider.GetDependency<ISetUpSponsorshipCommand>()
.DidNotReceiveWithAnyArgs()
.SetUpSponsorshipAsync(default, default);
}
[Theory]
[BitAutoData]
public async Task RedeemSponsorship_BadToken_ThrowsBadRequest(string sponsorshipToken, User user,
OrganizationSponsorshipRedeemRequestModel model, SutProvider<OrganizationSponsorshipsController> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(user.Id);
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(user.Id)
.Returns(user);
sutProvider.GetDependency<IValidateRedemptionTokenCommand>().ValidateRedemptionTokenAsync(sponsorshipToken,
user.Email).Returns((false, null));
[Theory]
[BitAutoData]
public async Task RedeemSponsorship_NotSponsoredOrgOwner_ThrowsBadRequest(string sponsorshipToken, User user,
OrganizationSponsorship sponsorship, OrganizationSponsorshipRedeemRequestModel model,
SutProvider<OrganizationSponsorshipsController> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(user.Id);
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(user.Id)
.Returns(user);
sutProvider.GetDependency<IValidateRedemptionTokenCommand>().ValidateRedemptionTokenAsync(sponsorshipToken,
user.Email).Returns((true, sponsorship));
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(model.SponsoredOrganizationId).Returns(false);
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.RedeemSponsorship(sponsorshipToken, model));
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.RedeemSponsorship(sponsorshipToken, model));
Assert.Contains("Failed to parse sponsorship token.", exception.Message);
await sutProvider.GetDependency<ISetUpSponsorshipCommand>()
.DidNotReceiveWithAnyArgs()
.SetUpSponsorshipAsync(default, default);
}
Assert.Contains("Can only redeem sponsorship for an organization you own.", exception.Message);
await sutProvider.GetDependency<ISetUpSponsorshipCommand>()
.DidNotReceiveWithAnyArgs()
.SetUpSponsorshipAsync(default, default);
}
[Theory]
[BitAutoData]
public async Task RedeemSponsorship_NotSponsoredOrgOwner_ThrowsBadRequest(string sponsorshipToken, User user,
OrganizationSponsorship sponsorship, OrganizationSponsorshipRedeemRequestModel model,
SutProvider<OrganizationSponsorshipsController> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(user.Id);
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(user.Id)
.Returns(user);
sutProvider.GetDependency<IValidateRedemptionTokenCommand>().ValidateRedemptionTokenAsync(sponsorshipToken,
user.Email).Returns((true, sponsorship));
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(model.SponsoredOrganizationId).Returns(false);
[Theory]
[BitAutoData]
public async Task RedeemSponsorship_NotSponsoredOrgOwner_Success(string sponsorshipToken, User user,
OrganizationSponsorship sponsorship, Organization sponsoringOrganization,
OrganizationSponsorshipRedeemRequestModel model, SutProvider<OrganizationSponsorshipsController> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(user.Id);
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(user.Id)
.Returns(user);
sutProvider.GetDependency<IValidateRedemptionTokenCommand>().ValidateRedemptionTokenAsync(sponsorshipToken,
user.Email).Returns((true, sponsorship));
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(model.SponsoredOrganizationId).Returns(true);
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(model.SponsoredOrganizationId).Returns(sponsoringOrganization);
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.RedeemSponsorship(sponsorshipToken, model));
await sutProvider.Sut.RedeemSponsorship(sponsorshipToken, model);
Assert.Contains("Can only redeem sponsorship for an organization you own.", exception.Message);
await sutProvider.GetDependency<ISetUpSponsorshipCommand>()
.DidNotReceiveWithAnyArgs()
.SetUpSponsorshipAsync(default, default);
}
await sutProvider.GetDependency<ISetUpSponsorshipCommand>().Received(1)
.SetUpSponsorshipAsync(sponsorship, sponsoringOrganization);
}
[Theory]
[BitAutoData]
public async Task RedeemSponsorship_NotSponsoredOrgOwner_Success(string sponsorshipToken, User user,
OrganizationSponsorship sponsorship, Organization sponsoringOrganization,
OrganizationSponsorshipRedeemRequestModel model, SutProvider<OrganizationSponsorshipsController> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(user.Id);
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(user.Id)
.Returns(user);
sutProvider.GetDependency<IValidateRedemptionTokenCommand>().ValidateRedemptionTokenAsync(sponsorshipToken,
user.Email).Returns((true, sponsorship));
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(model.SponsoredOrganizationId).Returns(true);
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(model.SponsoredOrganizationId).Returns(sponsoringOrganization);
[Theory]
[BitAutoData]
public async Task PreValidateSponsorshipToken_ValidatesToken_Success(string sponsorshipToken, User user,
OrganizationSponsorship sponsorship, SutProvider<OrganizationSponsorshipsController> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(user.Id);
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(user.Id)
.Returns(user);
sutProvider.GetDependency<IValidateRedemptionTokenCommand>()
.ValidateRedemptionTokenAsync(sponsorshipToken, user.Email).Returns((true, sponsorship));
await sutProvider.Sut.RedeemSponsorship(sponsorshipToken, model);
await sutProvider.Sut.PreValidateSponsorshipToken(sponsorshipToken);
await sutProvider.GetDependency<ISetUpSponsorshipCommand>().Received(1)
.SetUpSponsorshipAsync(sponsorship, sponsoringOrganization);
}
await sutProvider.GetDependency<IValidateRedemptionTokenCommand>().Received(1)
.ValidateRedemptionTokenAsync(sponsorshipToken, user.Email);
}
[Theory]
[BitAutoData]
public async Task PreValidateSponsorshipToken_ValidatesToken_Success(string sponsorshipToken, User user,
OrganizationSponsorship sponsorship, SutProvider<OrganizationSponsorshipsController> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(user.Id);
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(user.Id)
.Returns(user);
sutProvider.GetDependency<IValidateRedemptionTokenCommand>()
.ValidateRedemptionTokenAsync(sponsorshipToken, user.Email).Returns((true, sponsorship));
[Theory]
[BitAutoData]
public async Task RevokeSponsorship_WrongSponsoringUser_ThrowsBadRequest(OrganizationUser sponsoringOrgUser,
Guid currentUserId, SutProvider<OrganizationSponsorshipsController> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(currentUserId);
sutProvider.GetDependency<IOrganizationUserRepository>().GetByIdAsync(sponsoringOrgUser.Id)
.Returns(sponsoringOrgUser);
await sutProvider.Sut.PreValidateSponsorshipToken(sponsorshipToken);
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.RevokeSponsorship(sponsoringOrgUser.Id));
await sutProvider.GetDependency<IValidateRedemptionTokenCommand>().Received(1)
.ValidateRedemptionTokenAsync(sponsorshipToken, user.Email);
}
Assert.Contains("Can only revoke a sponsorship you granted.", exception.Message);
await sutProvider.GetDependency<IRemoveSponsorshipCommand>()
.DidNotReceiveWithAnyArgs()
.RemoveSponsorshipAsync(default);
}
[Theory]
[BitAutoData]
public async Task RevokeSponsorship_WrongSponsoringUser_ThrowsBadRequest(OrganizationUser sponsoringOrgUser,
Guid currentUserId, SutProvider<OrganizationSponsorshipsController> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(currentUserId);
sutProvider.GetDependency<IOrganizationUserRepository>().GetByIdAsync(sponsoringOrgUser.Id)
.Returns(sponsoringOrgUser);
[Theory]
[BitAutoData]
public async Task RemoveSponsorship_WrongOrgUserType_ThrowsBadRequest(Organization sponsoredOrg,
SutProvider<OrganizationSponsorshipsController> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(Arg.Any<Guid>()).Returns(false);
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.RevokeSponsorship(sponsoringOrgUser.Id));
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.RemoveSponsorship(sponsoredOrg.Id));
Assert.Contains("Can only revoke a sponsorship you granted.", exception.Message);
await sutProvider.GetDependency<IRemoveSponsorshipCommand>()
.DidNotReceiveWithAnyArgs()
.RemoveSponsorshipAsync(default);
}
[Theory]
[BitAutoData]
public async Task RemoveSponsorship_WrongOrgUserType_ThrowsBadRequest(Organization sponsoredOrg,
SutProvider<OrganizationSponsorshipsController> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(Arg.Any<Guid>()).Returns(false);
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.RemoveSponsorship(sponsoredOrg.Id));
Assert.Contains("Only the owner of an organization can remove sponsorship.", exception.Message);
await sutProvider.GetDependency<IRemoveSponsorshipCommand>()
.DidNotReceiveWithAnyArgs()
.RemoveSponsorshipAsync(default);
}
Assert.Contains("Only the owner of an organization can remove sponsorship.", exception.Message);
await sutProvider.GetDependency<IRemoveSponsorshipCommand>()
.DidNotReceiveWithAnyArgs()
.RemoveSponsorshipAsync(default);
}
}

View File

@ -10,57 +10,56 @@ using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
using Xunit;
namespace Bit.Api.Test.Controllers
namespace Bit.Api.Test.Controllers;
[ControllerCustomize(typeof(OrganizationUsersController))]
[SutProviderCustomize]
public class OrganizationUsersControllerTests
{
[ControllerCustomize(typeof(OrganizationUsersController))]
[SutProviderCustomize]
public class OrganizationUsersControllerTests
[Theory]
[BitAutoData]
public async Task Accept_RequiresKnownUser(Guid orgId, Guid orgUserId, OrganizationUserAcceptRequestModel model,
SutProvider<OrganizationUsersController> sutProvider)
{
[Theory]
[BitAutoData]
public async Task Accept_RequiresKnownUser(Guid orgId, Guid orgUserId, OrganizationUserAcceptRequestModel model,
SutProvider<OrganizationUsersController> sutProvider)
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(default).ReturnsForAnyArgs((User)null);
await Assert.ThrowsAsync<UnauthorizedAccessException>(() => sutProvider.Sut.Accept(orgId, orgUserId, model));
}
[Theory]
[BitAutoData]
public async Task Accept_NoMasterPasswordReset(Guid orgId, Guid orgUserId,
OrganizationUserAcceptRequestModel model, User user, SutProvider<OrganizationUsersController> sutProvider)
{
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(default).ReturnsForAnyArgs(user);
await sutProvider.Sut.Accept(orgId, orgUserId, model);
await sutProvider.GetDependency<IOrganizationService>().Received(1)
.AcceptUserAsync(orgUserId, user, model.Token, sutProvider.GetDependency<IUserService>());
await sutProvider.GetDependency<IOrganizationService>().DidNotReceiveWithAnyArgs()
.UpdateUserResetPasswordEnrollmentAsync(default, default, default, default);
}
[Theory]
[BitAutoData]
public async Task Accept_RequireMasterPasswordReset(Guid orgId, Guid orgUserId,
OrganizationUserAcceptRequestModel model, User user, SutProvider<OrganizationUsersController> sutProvider)
{
var policy = new Policy
{
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(default).ReturnsForAnyArgs((User)null);
Enabled = true,
Data = CoreHelpers.ClassToJsonData(new ResetPasswordDataModel { AutoEnrollEnabled = true, }),
};
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(default).ReturnsForAnyArgs(user);
sutProvider.GetDependency<IPolicyRepository>().GetByOrganizationIdTypeAsync(orgId,
Core.Enums.PolicyType.ResetPassword).Returns(policy);
await Assert.ThrowsAsync<UnauthorizedAccessException>(() => sutProvider.Sut.Accept(orgId, orgUserId, model));
}
await sutProvider.Sut.Accept(orgId, orgUserId, model);
[Theory]
[BitAutoData]
public async Task Accept_NoMasterPasswordReset(Guid orgId, Guid orgUserId,
OrganizationUserAcceptRequestModel model, User user, SutProvider<OrganizationUsersController> sutProvider)
{
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(default).ReturnsForAnyArgs(user);
await sutProvider.Sut.Accept(orgId, orgUserId, model);
await sutProvider.GetDependency<IOrganizationService>().Received(1)
.AcceptUserAsync(orgUserId, user, model.Token, sutProvider.GetDependency<IUserService>());
await sutProvider.GetDependency<IOrganizationService>().DidNotReceiveWithAnyArgs()
.UpdateUserResetPasswordEnrollmentAsync(default, default, default, default);
}
[Theory]
[BitAutoData]
public async Task Accept_RequireMasterPasswordReset(Guid orgId, Guid orgUserId,
OrganizationUserAcceptRequestModel model, User user, SutProvider<OrganizationUsersController> sutProvider)
{
var policy = new Policy
{
Enabled = true,
Data = CoreHelpers.ClassToJsonData(new ResetPasswordDataModel { AutoEnrollEnabled = true, }),
};
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(default).ReturnsForAnyArgs(user);
sutProvider.GetDependency<IPolicyRepository>().GetByOrganizationIdTypeAsync(orgId,
Core.Enums.PolicyType.ResetPassword).Returns(policy);
await sutProvider.Sut.Accept(orgId, orgUserId, model);
await sutProvider.GetDependency<IOrganizationService>().Received(1)
.AcceptUserAsync(orgUserId, user, model.Token, sutProvider.GetDependency<IUserService>());
await sutProvider.GetDependency<IOrganizationService>().Received(1)
.UpdateUserResetPasswordEnrollmentAsync(orgId, user.Id, model.ResetPasswordKey, user.Id);
}
await sutProvider.GetDependency<IOrganizationService>().Received(1)
.AcceptUserAsync(orgUserId, user, model.Token, sutProvider.GetDependency<IUserService>());
await sutProvider.GetDependency<IOrganizationService>().Received(1)
.UpdateUserResetPasswordEnrollmentAsync(orgId, user.Id, model.ResetPasswordKey, user.Id);
}
}

View File

@ -12,109 +12,108 @@ using Bit.Core.Settings;
using NSubstitute;
using Xunit;
namespace Bit.Api.Test.Controllers
namespace Bit.Api.Test.Controllers;
public class OrganizationsControllerTests : IDisposable
{
public class OrganizationsControllerTests : IDisposable
private readonly GlobalSettings _globalSettings;
private readonly ICurrentContext _currentContext;
private readonly IOrganizationRepository _organizationRepository;
private readonly IOrganizationService _organizationService;
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IPaymentService _paymentService;
private readonly IPolicyRepository _policyRepository;
private readonly ISsoConfigRepository _ssoConfigRepository;
private readonly ISsoConfigService _ssoConfigService;
private readonly IUserService _userService;
private readonly IGetOrganizationApiKeyCommand _getOrganizationApiKeyCommand;
private readonly IRotateOrganizationApiKeyCommand _rotateOrganizationApiKeyCommand;
private readonly IOrganizationApiKeyRepository _organizationApiKeyRepository;
private readonly OrganizationsController _sut;
public OrganizationsControllerTests()
{
private readonly GlobalSettings _globalSettings;
private readonly ICurrentContext _currentContext;
private readonly IOrganizationRepository _organizationRepository;
private readonly IOrganizationService _organizationService;
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IPaymentService _paymentService;
private readonly IPolicyRepository _policyRepository;
private readonly ISsoConfigRepository _ssoConfigRepository;
private readonly ISsoConfigService _ssoConfigService;
private readonly IUserService _userService;
private readonly IGetOrganizationApiKeyCommand _getOrganizationApiKeyCommand;
private readonly IRotateOrganizationApiKeyCommand _rotateOrganizationApiKeyCommand;
private readonly IOrganizationApiKeyRepository _organizationApiKeyRepository;
_currentContext = Substitute.For<ICurrentContext>();
_globalSettings = Substitute.For<GlobalSettings>();
_organizationRepository = Substitute.For<IOrganizationRepository>();
_organizationService = Substitute.For<IOrganizationService>();
_organizationUserRepository = Substitute.For<IOrganizationUserRepository>();
_paymentService = Substitute.For<IPaymentService>();
_policyRepository = Substitute.For<IPolicyRepository>();
_ssoConfigRepository = Substitute.For<ISsoConfigRepository>();
_ssoConfigService = Substitute.For<ISsoConfigService>();
_getOrganizationApiKeyCommand = Substitute.For<IGetOrganizationApiKeyCommand>();
_rotateOrganizationApiKeyCommand = Substitute.For<IRotateOrganizationApiKeyCommand>();
_organizationApiKeyRepository = Substitute.For<IOrganizationApiKeyRepository>();
_userService = Substitute.For<IUserService>();
private readonly OrganizationsController _sut;
_sut = new OrganizationsController(_organizationRepository, _organizationUserRepository,
_policyRepository, _organizationService, _userService, _paymentService, _currentContext,
_ssoConfigRepository, _ssoConfigService, _getOrganizationApiKeyCommand, _rotateOrganizationApiKeyCommand,
_organizationApiKeyRepository, _globalSettings);
}
public OrganizationsControllerTests()
public void Dispose()
{
_sut?.Dispose();
}
[Theory, AutoData]
public async Task OrganizationsController_UserCannotLeaveOrganizationThatProvidesKeyConnector(
Guid orgId, User user)
{
var ssoConfig = new SsoConfig
{
_currentContext = Substitute.For<ICurrentContext>();
_globalSettings = Substitute.For<GlobalSettings>();
_organizationRepository = Substitute.For<IOrganizationRepository>();
_organizationService = Substitute.For<IOrganizationService>();
_organizationUserRepository = Substitute.For<IOrganizationUserRepository>();
_paymentService = Substitute.For<IPaymentService>();
_policyRepository = Substitute.For<IPolicyRepository>();
_ssoConfigRepository = Substitute.For<ISsoConfigRepository>();
_ssoConfigService = Substitute.For<ISsoConfigService>();
_getOrganizationApiKeyCommand = Substitute.For<IGetOrganizationApiKeyCommand>();
_rotateOrganizationApiKeyCommand = Substitute.For<IRotateOrganizationApiKeyCommand>();
_organizationApiKeyRepository = Substitute.For<IOrganizationApiKeyRepository>();
_userService = Substitute.For<IUserService>();
_sut = new OrganizationsController(_organizationRepository, _organizationUserRepository,
_policyRepository, _organizationService, _userService, _paymentService, _currentContext,
_ssoConfigRepository, _ssoConfigService, _getOrganizationApiKeyCommand, _rotateOrganizationApiKeyCommand,
_organizationApiKeyRepository, _globalSettings);
}
public void Dispose()
{
_sut?.Dispose();
}
[Theory, AutoData]
public async Task OrganizationsController_UserCannotLeaveOrganizationThatProvidesKeyConnector(
Guid orgId, User user)
{
var ssoConfig = new SsoConfig
Id = default,
Data = new SsoConfigurationData
{
Id = default,
Data = new SsoConfigurationData
{
KeyConnectorEnabled = true,
}.Serialize(),
Enabled = true,
OrganizationId = orgId,
};
KeyConnectorEnabled = true,
}.Serialize(),
Enabled = true,
OrganizationId = orgId,
};
user.UsesKeyConnector = true;
user.UsesKeyConnector = true;
_currentContext.OrganizationUser(orgId).Returns(true);
_ssoConfigRepository.GetByOrganizationIdAsync(orgId).Returns(ssoConfig);
_userService.GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).Returns(user);
_currentContext.OrganizationUser(orgId).Returns(true);
_ssoConfigRepository.GetByOrganizationIdAsync(orgId).Returns(ssoConfig);
_userService.GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).Returns(user);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => _sut.Leave(orgId.ToString()));
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => _sut.Leave(orgId.ToString()));
Assert.Contains("Your organization's Single Sign-On settings prevent you from leaving.",
exception.Message);
Assert.Contains("Your organization's Single Sign-On settings prevent you from leaving.",
exception.Message);
await _organizationService.DidNotReceiveWithAnyArgs().DeleteUserAsync(default, default);
}
await _organizationService.DidNotReceiveWithAnyArgs().DeleteUserAsync(default, default);
}
[Theory]
[InlineAutoData(true, false)]
[InlineAutoData(false, true)]
[InlineAutoData(false, false)]
public async Task OrganizationsController_UserCanLeaveOrganizationThatDoesntProvideKeyConnector(
bool keyConnectorEnabled, bool userUsesKeyConnector, Guid orgId, User user)
[Theory]
[InlineAutoData(true, false)]
[InlineAutoData(false, true)]
[InlineAutoData(false, false)]
public async Task OrganizationsController_UserCanLeaveOrganizationThatDoesntProvideKeyConnector(
bool keyConnectorEnabled, bool userUsesKeyConnector, Guid orgId, User user)
{
var ssoConfig = new SsoConfig
{
var ssoConfig = new SsoConfig
Id = default,
Data = new SsoConfigurationData
{
Id = default,
Data = new SsoConfigurationData
{
KeyConnectorEnabled = keyConnectorEnabled,
}.Serialize(),
Enabled = true,
OrganizationId = orgId,
};
KeyConnectorEnabled = keyConnectorEnabled,
}.Serialize(),
Enabled = true,
OrganizationId = orgId,
};
user.UsesKeyConnector = userUsesKeyConnector;
user.UsesKeyConnector = userUsesKeyConnector;
_currentContext.OrganizationUser(orgId).Returns(true);
_ssoConfigRepository.GetByOrganizationIdAsync(orgId).Returns(ssoConfig);
_userService.GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).Returns(user);
_currentContext.OrganizationUser(orgId).Returns(true);
_ssoConfigRepository.GetByOrganizationIdAsync(orgId).Returns(ssoConfig);
_userService.GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).Returns(user);
await _organizationService.DeleteUserAsync(orgId, user.Id);
await _organizationService.Received(1).DeleteUserAsync(orgId, user.Id);
}
await _organizationService.DeleteUserAsync(orgId, user.Id);
await _organizationService.Received(1).DeleteUserAsync(orgId, user.Id);
}
}

View File

@ -15,66 +15,65 @@ using Microsoft.Extensions.Logging;
using NSubstitute;
using Xunit;
namespace Bit.Api.Test.Controllers
namespace Bit.Api.Test.Controllers;
public class SendsControllerTests : IDisposable
{
public class SendsControllerTests : IDisposable
private readonly SendsController _sut;
private readonly GlobalSettings _globalSettings;
private readonly IUserService _userService;
private readonly ISendRepository _sendRepository;
private readonly ISendService _sendService;
private readonly ISendFileStorageService _sendFileStorageService;
private readonly ILogger<SendsController> _logger;
private readonly ICurrentContext _currentContext;
public SendsControllerTests()
{
_userService = Substitute.For<IUserService>();
_sendRepository = Substitute.For<ISendRepository>();
_sendService = Substitute.For<ISendService>();
_sendFileStorageService = Substitute.For<ISendFileStorageService>();
_globalSettings = new GlobalSettings();
_logger = Substitute.For<ILogger<SendsController>>();
_currentContext = Substitute.For<ICurrentContext>();
private readonly SendsController _sut;
private readonly GlobalSettings _globalSettings;
private readonly IUserService _userService;
private readonly ISendRepository _sendRepository;
private readonly ISendService _sendService;
private readonly ISendFileStorageService _sendFileStorageService;
private readonly ILogger<SendsController> _logger;
private readonly ICurrentContext _currentContext;
_sut = new SendsController(
_sendRepository,
_userService,
_sendService,
_sendFileStorageService,
_logger,
_globalSettings,
_currentContext
);
}
public SendsControllerTests()
{
_userService = Substitute.For<IUserService>();
_sendRepository = Substitute.For<ISendRepository>();
_sendService = Substitute.For<ISendService>();
_sendFileStorageService = Substitute.For<ISendFileStorageService>();
_globalSettings = new GlobalSettings();
_logger = Substitute.For<ILogger<SendsController>>();
_currentContext = Substitute.For<ICurrentContext>();
public void Dispose()
{
_sut?.Dispose();
}
_sut = new SendsController(
_sendRepository,
_userService,
_sendService,
_sendFileStorageService,
_logger,
_globalSettings,
_currentContext
);
}
[Theory, AutoData]
public async Task SendsController_WhenSendHidesEmail_CreatorIdentifierShouldBeNull(
Guid id, Send send, User user)
{
var accessId = CoreHelpers.Base64UrlEncode(id.ToByteArray());
public void Dispose()
{
_sut?.Dispose();
}
send.Id = default;
send.Type = SendType.Text;
send.Data = JsonSerializer.Serialize(new Dictionary<string, string>());
send.HideEmail = true;
[Theory, AutoData]
public async Task SendsController_WhenSendHidesEmail_CreatorIdentifierShouldBeNull(
Guid id, Send send, User user)
{
var accessId = CoreHelpers.Base64UrlEncode(id.ToByteArray());
_sendService.AccessAsync(id, null).Returns((send, false, false));
_userService.GetUserByIdAsync(Arg.Any<Guid>()).Returns(user);
send.Id = default;
send.Type = SendType.Text;
send.Data = JsonSerializer.Serialize(new Dictionary<string, string>());
send.HideEmail = true;
var request = new SendAccessRequestModel();
var actionResult = await _sut.Access(accessId, request);
var response = (actionResult as ObjectResult)?.Value as SendAccessResponseModel;
_sendService.AccessAsync(id, null).Returns((send, false, false));
_userService.GetUserByIdAsync(Arg.Any<Guid>()).Returns(user);
var request = new SendAccessRequestModel();
var actionResult = await _sut.Access(accessId, request);
var response = (actionResult as ObjectResult)?.Value as SendAccessResponseModel;
Assert.NotNull(response);
Assert.Null(response.CreatorIdentifier);
}
Assert.NotNull(response);
Assert.Null(response.CreatorIdentifier);
}
}