mirror of
https://github.com/bitwarden/server.git
synced 2024-11-25 12:45:18 +01:00
5537470703
* Get limited life attachment download URL This change limits url download to a 1min lifetime. This requires moving to a new container to allow for non-public blob access. Clients will have to call GetAttachmentData api function to receive the download URL. For backwards compatibility, attachment URLs are still present, but will not work for attachments stored in non-public access blobs. * Make GlobalSettings interface for testing * Test LocalAttachmentStorageService equivalence * Remove comment * Add missing globalSettings using * Simplify default attachment container * Default to attachments containe for existing methods A new upload method will be made for uploading to attachments-v2. For compatibility for clients which don't use these new methods, we need to still use the old container. The new container will be used only for new uploads * Remove Default MetaData fixture. * Keep attachments container blob-level security for all instances * Close unclosed FileStream * Favor default value for noop services
419 lines
15 KiB
C#
419 lines
15 KiB
C#
using Bit.Api.Controllers;
|
|
using Bit.Core;
|
|
using Bit.Core.Enums;
|
|
using Bit.Core.Exceptions;
|
|
using Bit.Core.Models.Api;
|
|
using Bit.Core.Models.Data;
|
|
using Bit.Core.Models.Table;
|
|
using Bit.Core.Repositories;
|
|
using Bit.Core.Services;
|
|
using Bit.Core.Settings;
|
|
using Microsoft.AspNetCore.Identity;
|
|
using NSubstitute;
|
|
using System;
|
|
using System.Security.Claims;
|
|
using System.Threading.Tasks;
|
|
using Xunit;
|
|
|
|
namespace Bit.Api.Test.Controllers
|
|
{
|
|
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 ISsoUserRepository _ssoUserRepository;
|
|
private readonly IUserRepository _userRepository;
|
|
private readonly IUserService _userService;
|
|
|
|
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>();
|
|
_paymentService = Substitute.For<IPaymentService>();
|
|
_globalSettings = new GlobalSettings();
|
|
_sut = new AccountsController(
|
|
_globalSettings,
|
|
_cipherRepository,
|
|
_folderRepository,
|
|
_organizationService,
|
|
_organizationUserRepository,
|
|
_paymentService,
|
|
_ssoUserRepository,
|
|
_userRepository,
|
|
_userService
|
|
);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
_sut?.Dispose();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task PostPrelogin_WhenUserExists_ShouldReturnUserKdfInfo()
|
|
{
|
|
var userKdfInfo = new UserKdfInformation
|
|
{
|
|
Kdf = KdfType.PBKDF2_SHA256,
|
|
KdfIterations = 5000
|
|
};
|
|
_userRepository.GetKdfInformationByEmailAsync(Arg.Any<string>()).Returns(Task.FromResult(userKdfInfo));
|
|
|
|
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
|
|
{
|
|
Name = "Example User",
|
|
Email = "user@example.com",
|
|
MasterPasswordHash = passwordHash,
|
|
MasterPasswordHint = "example",
|
|
Token = token,
|
|
OrganizationUserId = userGuid
|
|
};
|
|
|
|
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
|
|
{
|
|
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)
|
|
.Returns(Task.FromResult(IdentityResult.Success));
|
|
|
|
await _sut.PostPassword(new PasswordRequestModel());
|
|
|
|
await _userService.Received(1).ChangePasswordAsync(user, 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)
|
|
.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 ApiKeyRequestModel());
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetApiKey_WhenUserDoesNotExist_ShouldThrowUnauthorizedAccessException()
|
|
{
|
|
ConfigureUserServiceToReturnNullPrincipal();
|
|
|
|
await Assert.ThrowsAsync<UnauthorizedAccessException>(
|
|
() => _sut.ApiKey(new ApiKeyRequestModel())
|
|
);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetApiKey_WhenPasswordCheckFails_ShouldThrowBadRequestException()
|
|
{
|
|
var user = GenerateExampleUser();
|
|
ConfigureUserServiceToReturnValidPrincipalFor(user);
|
|
ConfigureUserServiceToRejectPasswordFor(user);
|
|
await Assert.ThrowsAsync<BadRequestException>(
|
|
() => _sut.ApiKey(new ApiKeyRequestModel())
|
|
);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task PostRotateApiKey_ShouldRotateApiKey()
|
|
{
|
|
var user = GenerateExampleUser();
|
|
ConfigureUserServiceToReturnValidPrincipalFor(user);
|
|
ConfigureUserServiceToAcceptPasswordFor(user);
|
|
await _sut.RotateApiKey(new ApiKeyRequestModel());
|
|
}
|
|
|
|
[Fact]
|
|
public async Task PostRotateApiKey_WhenUserDoesNotExist_ShouldThrowUnauthorizedAccessException()
|
|
{
|
|
ConfigureUserServiceToReturnNullPrincipal();
|
|
|
|
await Assert.ThrowsAsync<UnauthorizedAccessException>(
|
|
() => _sut.ApiKey(new ApiKeyRequestModel())
|
|
);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task PostRotateApiKey_WhenPasswordCheckFails_ShouldThrowBadRequestException()
|
|
{
|
|
var user = GenerateExampleUser();
|
|
ConfigureUserServiceToReturnValidPrincipalFor(user);
|
|
ConfigureUserServiceToRejectPasswordFor(user);
|
|
await Assert.ThrowsAsync<BadRequestException>(
|
|
() => _sut.ApiKey(new ApiKeyRequestModel())
|
|
);
|
|
}
|
|
|
|
// 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));
|
|
}
|
|
|
|
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));
|
|
}
|
|
}
|
|
}
|
|
|