1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-06 18:42:49 -05:00

Merge branch 'main' of github.com:bitwarden/server into arch/seed-org-users

# Conflicts:
#	bitwarden-server.sln
This commit is contained in:
Hinton
2025-04-24 17:54:46 +02:00
495 changed files with 61931 additions and 2962 deletions

View File

@ -1,13 +1,6 @@
using System.Net.Http.Headers;
using Bit.Api.IntegrationTest.Factories;
using Bit.Api.IntegrationTest.Helpers;
using Bit.Api.Models.Response;
using Bit.Core;
using Bit.Core.Billing.Enums;
using Bit.Core.Enums;
using Bit.Core.Models.Data;
using Bit.Core.Services;
using NSubstitute;
using Xunit;
namespace Bit.Api.IntegrationTest.Controllers;
@ -19,7 +12,7 @@ public class AccountsControllerTest : IClassFixture<ApiApplicationFactory>
public AccountsControllerTest(ApiApplicationFactory factory) => _factory = factory;
[Fact]
public async Task GetPublicKey()
public async Task GetAccountsProfile_success()
{
var tokens = await _factory.LoginWithNewAccount();
var client = _factory.CreateClient();
@ -33,36 +26,13 @@ public class AccountsControllerTest : IClassFixture<ApiApplicationFactory>
var content = await response.Content.ReadFromJsonAsync<ProfileResponseModel>();
Assert.NotNull(content);
Assert.Equal("integration-test@bitwarden.com", content.Email);
Assert.Null(content.Name);
Assert.False(content.EmailVerified);
Assert.NotNull(content.Name);
Assert.True(content.EmailVerified);
Assert.False(content.Premium);
Assert.False(content.PremiumFromOrganization);
Assert.Equal("en-US", content.Culture);
Assert.Null(content.Key);
Assert.Null(content.PrivateKey);
Assert.NotNull(content.Key);
Assert.NotNull(content.PrivateKey);
Assert.NotNull(content.SecurityStamp);
}
private async Task<string> SetupOrganizationManagedAccount()
{
_factory.SubstituteService<IFeatureService>(featureService =>
featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning).Returns(true));
// Create the owner account
var ownerEmail = $"{Guid.NewGuid()}@bitwarden.com";
await _factory.LoginWithNewAccount(ownerEmail);
// Create the organization
var (_organization, _) = await OrganizationTestHelpers.SignUpAsync(_factory, plan: PlanType.EnterpriseAnnually2023,
ownerEmail: ownerEmail, passwordManagerSeats: 10, paymentMethod: PaymentMethodType.Card);
// Create a new organization member
var (email, orgUser) = await OrganizationTestHelpers.CreateNewUserWithAccountAsync(_factory, _organization.Id,
OrganizationUserType.Custom, new Permissions { AccessReports = true, ManageScim = true });
// Add a verified domain
await OrganizationTestHelpers.CreateVerifiedDomainAsync(_factory, _organization.Id, "bitwarden.com");
return email;
}
}

View File

@ -1,4 +1,6 @@
using Bit.Identity.Models.Request.Accounts;
using Bit.Core;
using Bit.Core.Auth.Models.Api.Request.Accounts;
using Bit.Core.Enums;
using Bit.IntegrationTestCommon.Factories;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.TestHost;
@ -42,13 +44,23 @@ public class ApiApplicationFactory : WebApplicationFactoryBase<Startup>
/// <summary>
/// Helper for registering and logging in to a new account
/// </summary>
public async Task<(string Token, string RefreshToken)> LoginWithNewAccount(string email = "integration-test@bitwarden.com", string masterPasswordHash = "master_password_hash")
public async Task<(string Token, string RefreshToken)> LoginWithNewAccount(
string email = "integration-test@bitwarden.com", string masterPasswordHash = "master_password_hash")
{
await _identityApplicationFactory.RegisterAsync(new RegisterRequestModel
{
Email = email,
MasterPasswordHash = masterPasswordHash,
});
await _identityApplicationFactory.RegisterNewIdentityFactoryUserAsync(
new RegisterFinishRequestModel
{
Email = email,
MasterPasswordHash = masterPasswordHash,
Kdf = KdfType.PBKDF2_SHA256,
KdfIterations = AuthConstants.PBKDF2_ITERATIONS.Default,
UserAsymmetricKeys = new KeysRequestModel()
{
PublicKey = "public_key",
EncryptedPrivateKey = "private_key"
},
UserSymmetricKey = "sym_key",
});
return await _identityApplicationFactory.TokenFromPasswordAsync(email, masterPasswordHash);
}

View File

@ -59,7 +59,8 @@ public static class OrganizationTestHelpers
string userEmail,
OrganizationUserType type,
bool accessSecretsManager = false,
Permissions? permissions = null
Permissions? permissions = null,
OrganizationUserStatusType userStatusType = OrganizationUserStatusType.Confirmed
) where T : class
{
var userRepository = factory.GetService<IUserRepository>();
@ -74,7 +75,7 @@ public static class OrganizationTestHelpers
UserId = user.Id,
Key = null,
Type = type,
Status = OrganizationUserStatusType.Confirmed,
Status = userStatusType,
ExternalId = null,
AccessSecretsManager = accessSecretsManager,
};

View File

@ -1,4 +1,5 @@
using System.Net;
#nullable enable
using System.Net;
using Bit.Api.IntegrationTest.Factories;
using Bit.Api.IntegrationTest.Helpers;
using Bit.Api.KeyManagement.Models.Requests;
@ -7,6 +8,7 @@ using Bit.Api.Vault.Models;
using Bit.Api.Vault.Models.Request;
using Bit.Core.Auth.Entities;
using Bit.Core.Auth.Enums;
using Bit.Core.Auth.Models.Api.Request.Accounts;
using Bit.Core.Billing.Enums;
using Bit.Core.Entities;
using Bit.Core.Enums;
@ -29,7 +31,9 @@ public class AccountsKeyManagementControllerTests : IClassFixture<ApiApplication
private readonly ApiApplicationFactory _factory;
private readonly LoginHelper _loginHelper;
private readonly IUserRepository _userRepository;
private readonly IDeviceRepository _deviceRepository;
private readonly IPasswordHasher<User> _passwordHasher;
private readonly IOrganizationRepository _organizationRepository;
private string _ownerEmail = null!;
public AccountsKeyManagementControllerTests(ApiApplicationFactory factory)
@ -40,9 +44,11 @@ public class AccountsKeyManagementControllerTests : IClassFixture<ApiApplication
_client = factory.CreateClient();
_loginHelper = new LoginHelper(_factory, _client);
_userRepository = _factory.GetService<IUserRepository>();
_deviceRepository = _factory.GetService<IDeviceRepository>();
_emergencyAccessRepository = _factory.GetService<IEmergencyAccessRepository>();
_organizationUserRepository = _factory.GetService<IOrganizationUserRepository>();
_passwordHasher = _factory.GetService<IPasswordHasher<User>>();
_organizationRepository = _factory.GetService<IOrganizationRepository>();
}
public async Task InitializeAsync()
@ -172,7 +178,8 @@ public class AccountsKeyManagementControllerTests : IClassFixture<ApiApplication
[Theory]
[BitAutoData]
public async Task RotateUserAccountKeysAsync_NotLoggedIn_Unauthorized(RotateUserAccountKeysAndDataRequestModel request)
public async Task RotateUserAccountKeysAsync_NotLoggedIn_Unauthorized(
RotateUserAccountKeysAndDataRequestModel request)
{
var response = await _client.PostAsJsonAsync("/accounts/key-management/rotate-user-account-keys", request);
@ -238,10 +245,12 @@ public class AccountsKeyManagementControllerTests : IClassFixture<ApiApplication
];
request.AccountUnlockData.MasterPasswordUnlockData.MasterKeyEncryptedUserKey = _mockEncryptedString;
request.AccountUnlockData.PasskeyUnlockData = [];
request.AccountUnlockData.DeviceKeyUnlockData = [];
request.AccountUnlockData.EmergencyAccessUnlockData = [];
request.AccountUnlockData.OrganizationAccountRecoveryUnlockData = [];
var response = await _client.PostAsJsonAsync("/accounts/key-management/rotate-user-account-keys", request);
var responseMessage = await response.Content.ReadAsStringAsync();
response.EnsureSuccessStatusCode();
var userNewState = await _userRepository.GetByEmailAsync(_ownerEmail);
@ -252,4 +261,97 @@ public class AccountsKeyManagementControllerTests : IClassFixture<ApiApplication
Assert.Equal(request.AccountUnlockData.MasterPasswordUnlockData.KdfMemory, userNewState.KdfMemory);
Assert.Equal(request.AccountUnlockData.MasterPasswordUnlockData.KdfParallelism, userNewState.KdfParallelism);
}
[Theory]
[BitAutoData]
public async Task PostSetKeyConnectorKeyAsync_NotLoggedIn_Unauthorized(SetKeyConnectorKeyRequestModel request)
{
var response = await _client.PostAsJsonAsync("/accounts/set-key-connector-key", request);
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
}
[Theory]
[BitAutoData]
public async Task PostSetKeyConnectorKeyAsync_Success(string organizationSsoIdentifier,
SetKeyConnectorKeyRequestModel request)
{
var (organization, _) = await OrganizationTestHelpers.SignUpAsync(_factory,
PlanType.EnterpriseAnnually, _ownerEmail, passwordManagerSeats: 10,
paymentMethod: PaymentMethodType.Card);
organization.UseKeyConnector = true;
organization.UseSso = true;
organization.Identifier = organizationSsoIdentifier;
await _organizationRepository.ReplaceAsync(organization);
var ssoUserEmail = $"integration-test{Guid.NewGuid()}@bitwarden.com";
await _factory.LoginWithNewAccount(ssoUserEmail);
await _loginHelper.LoginAsync(ssoUserEmail);
await OrganizationTestHelpers.CreateUserAsync(_factory, organization.Id, ssoUserEmail,
OrganizationUserType.User, userStatusType: OrganizationUserStatusType.Invited);
var ssoUser = await _userRepository.GetByEmailAsync(ssoUserEmail);
Assert.NotNull(ssoUser);
request.Keys = new KeysRequestModel
{
PublicKey = ssoUser.PublicKey,
EncryptedPrivateKey = ssoUser.PrivateKey
};
request.Key = _mockEncryptedString;
request.OrgIdentifier = organizationSsoIdentifier;
var response = await _client.PostAsJsonAsync("/accounts/set-key-connector-key", request);
response.EnsureSuccessStatusCode();
var user = await _userRepository.GetByEmailAsync(ssoUserEmail);
Assert.NotNull(user);
Assert.Equal(request.Key, user.Key);
Assert.True(user.UsesKeyConnector);
Assert.Equal(DateTime.UtcNow, user.RevisionDate, TimeSpan.FromMinutes(1));
Assert.Equal(DateTime.UtcNow, user.AccountRevisionDate, TimeSpan.FromMinutes(1));
var ssoOrganizationUser =
await _organizationUserRepository.GetByOrganizationAsync(organization.Id, user.Id);
Assert.NotNull(ssoOrganizationUser);
Assert.Equal(OrganizationUserStatusType.Accepted, ssoOrganizationUser.Status);
Assert.Equal(user.Id, ssoOrganizationUser.UserId);
Assert.Null(ssoOrganizationUser.Email);
}
[Fact]
public async Task PostConvertToKeyConnectorAsync_NotLoggedIn_Unauthorized()
{
var response = await _client.PostAsJsonAsync("/accounts/convert-to-key-connector", new { });
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
}
[Fact]
public async Task PostConvertToKeyConnectorAsync_Success()
{
var (organization, _) = await OrganizationTestHelpers.SignUpAsync(_factory,
PlanType.EnterpriseAnnually, _ownerEmail, passwordManagerSeats: 10,
paymentMethod: PaymentMethodType.Card);
organization.UseKeyConnector = true;
organization.UseSso = true;
await _organizationRepository.ReplaceAsync(organization);
var ssoUserEmail = $"integration-test{Guid.NewGuid()}@bitwarden.com";
await _factory.LoginWithNewAccount(ssoUserEmail);
await _loginHelper.LoginAsync(ssoUserEmail);
await OrganizationTestHelpers.CreateUserAsync(_factory, organization.Id, ssoUserEmail,
OrganizationUserType.User, userStatusType: OrganizationUserStatusType.Accepted);
var response = await _client.PostAsJsonAsync("/accounts/convert-to-key-connector", new { });
response.EnsureSuccessStatusCode();
var user = await _userRepository.GetByEmailAsync(ssoUserEmail);
Assert.NotNull(user);
Assert.Null(user.MasterPassword);
Assert.True(user.UsesKeyConnector);
Assert.Equal(DateTime.UtcNow, user.RevisionDate, TimeSpan.FromMinutes(1));
Assert.Equal(DateTime.UtcNow, user.AccountRevisionDate, TimeSpan.FromMinutes(1));
}
}