mirror of
https://github.com/bitwarden/server.git
synced 2025-04-30 01:02:21 -05:00
Merge branch 'main' into test-docker-stuff
This commit is contained in:
commit
ac6148d8e9
3
.github/workflows/build.yml
vendored
3
.github/workflows/build.yml
vendored
@ -569,10 +569,9 @@ jobs:
|
||||
&& contains(github.event.pull_request.labels.*.name, 'ephemeral-environment')
|
||||
uses: bitwarden/gh-actions/.github/workflows/_ephemeral_environment_manager.yml@main
|
||||
with:
|
||||
ephemeral_env_branch: process.env.GITHUB_HEAD_REF
|
||||
project: server
|
||||
sync_environment: true
|
||||
pull_request_number: ${{ github.event.number }}
|
||||
create_branch: true
|
||||
secrets: inherit
|
||||
|
||||
check-failures:
|
||||
|
39
.github/workflows/ephemeral-environment.yml
vendored
39
.github/workflows/ephemeral-environment.yml
vendored
@ -5,34 +5,13 @@ on:
|
||||
types: [labeled]
|
||||
|
||||
jobs:
|
||||
trigger-ee-updates:
|
||||
name: Trigger Ephemeral Environment updates
|
||||
runs-on: ubuntu-24.04
|
||||
setup-ephemeral-environment:
|
||||
name: Setup Ephemeral Environment
|
||||
if: github.event.label.name == 'ephemeral-environment'
|
||||
steps:
|
||||
- name: Log in to Azure - CI subscription
|
||||
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
|
||||
with:
|
||||
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
|
||||
|
||||
- name: Retrieve GitHub PAT secrets
|
||||
id: retrieve-secret-pat
|
||||
uses: bitwarden/gh-actions/get-keyvault-secrets@main
|
||||
with:
|
||||
keyvault: "bitwarden-ci"
|
||||
secrets: "github-pat-bitwarden-devops-bot-repo-scope"
|
||||
|
||||
- name: Trigger Ephemeral Environment update
|
||||
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
||||
with:
|
||||
github-token: ${{ steps.retrieve-secret-pat.outputs.github-pat-bitwarden-devops-bot-repo-scope }}
|
||||
script: |
|
||||
await github.rest.actions.createWorkflowDispatch({
|
||||
owner: 'bitwarden',
|
||||
repo: 'devops',
|
||||
workflow_id: '_update_ephemeral_tags.yml',
|
||||
ref: 'main',
|
||||
inputs: {
|
||||
ephemeral_env_branch: process.env.GITHUB_HEAD_REF
|
||||
}
|
||||
})
|
||||
uses: bitwarden/gh-actions/.github/workflows/_ephemeral_environment_manager.yml@main
|
||||
with:
|
||||
project: server
|
||||
pull_request_number: ${{ github.event.number }}
|
||||
sync_environment: true
|
||||
create_branch: true
|
||||
secrets: inherit
|
||||
|
@ -65,6 +65,7 @@ public class OrganizationsController : Controller
|
||||
private readonly IOrganizationDeleteCommand _organizationDeleteCommand;
|
||||
private readonly IPolicyRequirementQuery _policyRequirementQuery;
|
||||
private readonly IPricingClient _pricingClient;
|
||||
private readonly IOrganizationUpdateKeysCommand _organizationUpdateKeysCommand;
|
||||
|
||||
public OrganizationsController(
|
||||
IOrganizationRepository organizationRepository,
|
||||
@ -88,7 +89,8 @@ public class OrganizationsController : Controller
|
||||
ICloudOrganizationSignUpCommand cloudOrganizationSignUpCommand,
|
||||
IOrganizationDeleteCommand organizationDeleteCommand,
|
||||
IPolicyRequirementQuery policyRequirementQuery,
|
||||
IPricingClient pricingClient)
|
||||
IPricingClient pricingClient,
|
||||
IOrganizationUpdateKeysCommand organizationUpdateKeysCommand)
|
||||
{
|
||||
_organizationRepository = organizationRepository;
|
||||
_organizationUserRepository = organizationUserRepository;
|
||||
@ -112,6 +114,7 @@ public class OrganizationsController : Controller
|
||||
_organizationDeleteCommand = organizationDeleteCommand;
|
||||
_policyRequirementQuery = policyRequirementQuery;
|
||||
_pricingClient = pricingClient;
|
||||
_organizationUpdateKeysCommand = organizationUpdateKeysCommand;
|
||||
}
|
||||
|
||||
[HttpGet("{id}")]
|
||||
@ -490,7 +493,7 @@ public class OrganizationsController : Controller
|
||||
}
|
||||
|
||||
[HttpPost("{id}/keys")]
|
||||
public async Task<OrganizationKeysResponseModel> PostKeys(string id, [FromBody] OrganizationKeysRequestModel model)
|
||||
public async Task<OrganizationKeysResponseModel> PostKeys(Guid id, [FromBody] OrganizationKeysRequestModel model)
|
||||
{
|
||||
var user = await _userService.GetUserByPrincipalAsync(User);
|
||||
if (user == null)
|
||||
@ -498,7 +501,7 @@ public class OrganizationsController : Controller
|
||||
throw new UnauthorizedAccessException();
|
||||
}
|
||||
|
||||
var org = await _organizationService.UpdateOrganizationKeysAsync(new Guid(id), model.PublicKey,
|
||||
var org = await _organizationUpdateKeysCommand.UpdateOrganizationKeysAsync(id, model.PublicKey,
|
||||
model.EncryptedPrivateKey);
|
||||
return new OrganizationKeysResponseModel(org);
|
||||
}
|
||||
|
@ -67,10 +67,12 @@ public class OrganizationUserDetailsResponseModel : OrganizationUserResponseMode
|
||||
public OrganizationUserDetailsResponseModel(
|
||||
OrganizationUser organizationUser,
|
||||
bool claimedByOrganization,
|
||||
string ssoExternalId,
|
||||
IEnumerable<CollectionAccessSelection> collections)
|
||||
: base(organizationUser, "organizationUserDetails")
|
||||
{
|
||||
ClaimedByOrganization = claimedByOrganization;
|
||||
SsoExternalId = ssoExternalId;
|
||||
Collections = collections.Select(c => new SelectionReadOnlyResponseModel(c));
|
||||
}
|
||||
|
||||
@ -80,6 +82,7 @@ public class OrganizationUserDetailsResponseModel : OrganizationUserResponseMode
|
||||
: base(organizationUser, "organizationUserDetails")
|
||||
{
|
||||
ClaimedByOrganization = claimedByOrganization;
|
||||
SsoExternalId = organizationUser.SsoExternalId;
|
||||
Collections = collections.Select(c => new SelectionReadOnlyResponseModel(c));
|
||||
}
|
||||
|
||||
@ -90,6 +93,7 @@ public class OrganizationUserDetailsResponseModel : OrganizationUserResponseMode
|
||||
set => ClaimedByOrganization = value;
|
||||
}
|
||||
public bool ClaimedByOrganization { get; set; }
|
||||
public string SsoExternalId { get; set; }
|
||||
|
||||
public IEnumerable<SelectionReadOnlyResponseModel> Collections { get; set; }
|
||||
|
||||
|
11
src/Api/Auth/Models/Request/UntrustDevicesModel.cs
Normal file
11
src/Api/Auth/Models/Request/UntrustDevicesModel.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Bit.Api.Auth.Models.Request;
|
||||
|
||||
public class UntrustDevicesRequestModel
|
||||
{
|
||||
[Required]
|
||||
public IEnumerable<Guid> Devices { get; set; } = null!;
|
||||
}
|
@ -4,6 +4,7 @@ using Bit.Api.Models.Request;
|
||||
using Bit.Api.Models.Response;
|
||||
using Bit.Core.Auth.Models.Api.Request;
|
||||
using Bit.Core.Auth.Models.Api.Response;
|
||||
using Bit.Core.Auth.UserFeatures.DeviceTrust;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
@ -21,6 +22,7 @@ public class DevicesController : Controller
|
||||
private readonly IDeviceRepository _deviceRepository;
|
||||
private readonly IDeviceService _deviceService;
|
||||
private readonly IUserService _userService;
|
||||
private readonly IUntrustDevicesCommand _untrustDevicesCommand;
|
||||
private readonly IUserRepository _userRepository;
|
||||
private readonly ICurrentContext _currentContext;
|
||||
private readonly ILogger<DevicesController> _logger;
|
||||
@ -29,6 +31,7 @@ public class DevicesController : Controller
|
||||
IDeviceRepository deviceRepository,
|
||||
IDeviceService deviceService,
|
||||
IUserService userService,
|
||||
IUntrustDevicesCommand untrustDevicesCommand,
|
||||
IUserRepository userRepository,
|
||||
ICurrentContext currentContext,
|
||||
ILogger<DevicesController> logger)
|
||||
@ -36,6 +39,7 @@ public class DevicesController : Controller
|
||||
_deviceRepository = deviceRepository;
|
||||
_deviceService = deviceService;
|
||||
_userService = userService;
|
||||
_untrustDevicesCommand = untrustDevicesCommand;
|
||||
_userRepository = userRepository;
|
||||
_currentContext = currentContext;
|
||||
_logger = logger;
|
||||
@ -165,6 +169,19 @@ public class DevicesController : Controller
|
||||
model.OtherDevices ?? Enumerable.Empty<OtherDeviceKeysUpdateRequestModel>());
|
||||
}
|
||||
|
||||
[HttpPost("untrust")]
|
||||
public async Task PostUntrust([FromBody] UntrustDevicesRequestModel model)
|
||||
{
|
||||
var user = await _userService.GetUserByPrincipalAsync(User);
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
throw new UnauthorizedAccessException();
|
||||
}
|
||||
|
||||
await _untrustDevicesCommand.UntrustDevices(user, model.Devices);
|
||||
}
|
||||
|
||||
[HttpPut("identifier/{identifier}/token")]
|
||||
[HttpPost("identifier/{identifier}/token")]
|
||||
public async Task PutToken(string identifier, [FromBody] DeviceTokenRequestModel model)
|
||||
|
@ -1,6 +1,5 @@
|
||||
using Bit.Billing.Constants;
|
||||
using Bit.Billing.Jobs;
|
||||
using Bit.Core;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces;
|
||||
using Bit.Core.Billing.Pricing;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces;
|
||||
@ -24,7 +23,6 @@ public class SubscriptionUpdatedHandler : ISubscriptionUpdatedHandler
|
||||
private readonly IPushNotificationService _pushNotificationService;
|
||||
private readonly IOrganizationRepository _organizationRepository;
|
||||
private readonly ISchedulerFactory _schedulerFactory;
|
||||
private readonly IFeatureService _featureService;
|
||||
private readonly IOrganizationEnableCommand _organizationEnableCommand;
|
||||
private readonly IOrganizationDisableCommand _organizationDisableCommand;
|
||||
private readonly IPricingClient _pricingClient;
|
||||
@ -39,7 +37,6 @@ public class SubscriptionUpdatedHandler : ISubscriptionUpdatedHandler
|
||||
IPushNotificationService pushNotificationService,
|
||||
IOrganizationRepository organizationRepository,
|
||||
ISchedulerFactory schedulerFactory,
|
||||
IFeatureService featureService,
|
||||
IOrganizationEnableCommand organizationEnableCommand,
|
||||
IOrganizationDisableCommand organizationDisableCommand,
|
||||
IPricingClient pricingClient)
|
||||
@ -53,7 +50,6 @@ public class SubscriptionUpdatedHandler : ISubscriptionUpdatedHandler
|
||||
_pushNotificationService = pushNotificationService;
|
||||
_organizationRepository = organizationRepository;
|
||||
_schedulerFactory = schedulerFactory;
|
||||
_featureService = featureService;
|
||||
_organizationEnableCommand = organizationEnableCommand;
|
||||
_organizationDisableCommand = organizationDisableCommand;
|
||||
_pricingClient = pricingClient;
|
||||
@ -227,12 +223,6 @@ public class SubscriptionUpdatedHandler : ISubscriptionUpdatedHandler
|
||||
|
||||
private async Task ScheduleCancellationJobAsync(string subscriptionId, Guid organizationId)
|
||||
{
|
||||
var isResellerManagedOrgAlertEnabled = _featureService.IsEnabled(FeatureFlagKeys.ResellerManagedOrgAlert);
|
||||
if (!isResellerManagedOrgAlertEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var scheduler = await _schedulerFactory.GetScheduler();
|
||||
|
||||
var job = JobBuilder.Create<SubscriptionCancellationJob>()
|
||||
|
@ -0,0 +1,13 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
|
||||
public interface IOrganizationUpdateKeysCommand
|
||||
{
|
||||
/// <summary>
|
||||
/// Update the keys for an organization.
|
||||
/// </summary>
|
||||
/// <param name="orgId">The ID of the organization to update.</param>
|
||||
/// <param name="publicKey">The public key for the organization.</param>
|
||||
/// <param name="privateKey">The private key for the organization.</param>
|
||||
/// <returns>The updated organization.</returns>
|
||||
Task<Organization> UpdateOrganizationKeysAsync(Guid orgId, string publicKey, string privateKey);
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
|
||||
public class OrganizationUpdateKeysCommand : IOrganizationUpdateKeysCommand
|
||||
{
|
||||
private readonly ICurrentContext _currentContext;
|
||||
private readonly IOrganizationRepository _organizationRepository;
|
||||
private readonly IOrganizationService _organizationService;
|
||||
|
||||
public const string OrganizationKeysAlreadyExistErrorMessage = "Organization Keys already exist.";
|
||||
|
||||
public OrganizationUpdateKeysCommand(
|
||||
ICurrentContext currentContext,
|
||||
IOrganizationRepository organizationRepository,
|
||||
IOrganizationService organizationService)
|
||||
{
|
||||
_currentContext = currentContext;
|
||||
_organizationRepository = organizationRepository;
|
||||
_organizationService = organizationService;
|
||||
}
|
||||
|
||||
public async Task<Organization> UpdateOrganizationKeysAsync(Guid organizationId, string publicKey, string privateKey)
|
||||
{
|
||||
if (!await _currentContext.ManageResetPassword(organizationId))
|
||||
{
|
||||
throw new UnauthorizedAccessException();
|
||||
}
|
||||
|
||||
// If the keys already exist, error out
|
||||
var organization = await _organizationRepository.GetByIdAsync(organizationId);
|
||||
if (organization.PublicKey != null && organization.PrivateKey != null)
|
||||
{
|
||||
throw new BadRequestException(OrganizationKeysAlreadyExistErrorMessage);
|
||||
}
|
||||
|
||||
// Update org with generated public/private key
|
||||
organization.PublicKey = publicKey;
|
||||
organization.PrivateKey = privateKey;
|
||||
|
||||
await _organizationService.UpdateAsync(organization);
|
||||
|
||||
return organization;
|
||||
}
|
||||
}
|
@ -43,7 +43,6 @@ public interface IOrganizationService
|
||||
IEnumerable<ImportedOrganizationUser> newUsers, IEnumerable<string> removeUserExternalIds,
|
||||
bool overwriteExisting, EventSystemUser eventSystemUser);
|
||||
Task DeleteSsoUserAsync(Guid userId, Guid? organizationId);
|
||||
Task<Organization> UpdateOrganizationKeysAsync(Guid orgId, string publicKey, string privateKey);
|
||||
Task RevokeUserAsync(OrganizationUser organizationUser, Guid? revokingUserId);
|
||||
Task RevokeUserAsync(OrganizationUser organizationUser, EventSystemUser systemUser);
|
||||
Task<List<Tuple<OrganizationUser, string>>> RevokeUsersAsync(Guid organizationId,
|
||||
|
@ -1418,28 +1418,6 @@ public class OrganizationService : IOrganizationService
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Organization> UpdateOrganizationKeysAsync(Guid orgId, string publicKey, string privateKey)
|
||||
{
|
||||
if (!await _currentContext.ManageResetPassword(orgId))
|
||||
{
|
||||
throw new UnauthorizedAccessException();
|
||||
}
|
||||
|
||||
// If the keys already exist, error out
|
||||
var org = await _organizationRepository.GetByIdAsync(orgId);
|
||||
if (org.PublicKey != null && org.PrivateKey != null)
|
||||
{
|
||||
throw new BadRequestException("Organization Keys already exist");
|
||||
}
|
||||
|
||||
// Update org with generated public/private key
|
||||
org.PublicKey = publicKey;
|
||||
org.PrivateKey = privateKey;
|
||||
await UpdateAsync(org);
|
||||
|
||||
return org;
|
||||
}
|
||||
|
||||
private async Task UpdateUsersAsync(Group group, HashSet<string> groupUsers,
|
||||
Dictionary<string, Guid> existingUsersIdDict, HashSet<Guid> existingUsers = null)
|
||||
{
|
||||
|
@ -0,0 +1,8 @@
|
||||
using Bit.Core.Entities;
|
||||
|
||||
namespace Bit.Core.Auth.UserFeatures.DeviceTrust;
|
||||
|
||||
public interface IUntrustDevicesCommand
|
||||
{
|
||||
public Task UntrustDevices(User user, IEnumerable<Guid> devicesToUntrust);
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Repositories;
|
||||
|
||||
namespace Bit.Core.Auth.UserFeatures.DeviceTrust;
|
||||
|
||||
public class UntrustDevicesCommand : IUntrustDevicesCommand
|
||||
{
|
||||
private readonly IDeviceRepository _deviceRepository;
|
||||
|
||||
public UntrustDevicesCommand(
|
||||
IDeviceRepository deviceRepository)
|
||||
{
|
||||
_deviceRepository = deviceRepository;
|
||||
}
|
||||
|
||||
public async Task UntrustDevices(User user, IEnumerable<Guid> devicesToUntrust)
|
||||
{
|
||||
var userDevices = await _deviceRepository.GetManyByUserIdAsync(user.Id);
|
||||
var deviceIdDict = userDevices.ToDictionary(device => device.Id);
|
||||
|
||||
// Validate that the user owns all devices that they passed in
|
||||
foreach (var deviceId in devicesToUntrust)
|
||||
{
|
||||
if (!deviceIdDict.ContainsKey(deviceId))
|
||||
{
|
||||
throw new UnauthorizedAccessException($"User {user.Id} does not have access to device {deviceId}");
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var deviceId in devicesToUntrust)
|
||||
{
|
||||
var device = deviceIdDict[deviceId];
|
||||
device.EncryptedPrivateKey = null;
|
||||
device.EncryptedPublicKey = null;
|
||||
device.EncryptedUserKey = null;
|
||||
await _deviceRepository.UpsertAsync(device);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
|
||||
|
||||
using Bit.Core.Auth.UserFeatures.DeviceTrust;
|
||||
using Bit.Core.Auth.UserFeatures.Registration;
|
||||
using Bit.Core.Auth.UserFeatures.Registration.Implementations;
|
||||
using Bit.Core.Auth.UserFeatures.TdeOffboardingPassword.Interfaces;
|
||||
@ -22,6 +23,7 @@ public static class UserServiceCollectionExtensions
|
||||
public static void AddUserServices(this IServiceCollection services, IGlobalSettings globalSettings)
|
||||
{
|
||||
services.AddScoped<IUserService, UserService>();
|
||||
services.AddDeviceTrustCommands();
|
||||
services.AddUserPasswordCommands();
|
||||
services.AddUserRegistrationCommands();
|
||||
services.AddWebAuthnLoginCommands();
|
||||
@ -29,6 +31,11 @@ public static class UserServiceCollectionExtensions
|
||||
services.AddTwoFactorQueries();
|
||||
}
|
||||
|
||||
public static void AddDeviceTrustCommands(this IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<IUntrustDevicesCommand, UntrustDevicesCommand>();
|
||||
}
|
||||
|
||||
public static void AddUserKeyCommands(this IServiceCollection services, IGlobalSettings globalSettings)
|
||||
{
|
||||
services.AddScoped<IRotateUserKeyCommand, RotateUserKeyCommand>();
|
||||
|
@ -141,7 +141,6 @@ public static class FeatureFlagKeys
|
||||
/* Billing Team */
|
||||
public const string AC2101UpdateTrialInitiationEmail = "AC-2101-update-trial-initiation-email";
|
||||
public const string TrialPayment = "PM-8163-trial-payment";
|
||||
public const string ResellerManagedOrgAlert = "PM-15814-alert-owners-of-reseller-managed-orgs";
|
||||
public const string UsePricingService = "use-pricing-service";
|
||||
public const string P15179_AddExistingOrgsFromProviderPortal = "pm-15179-add-existing-orgs-from-provider-portal";
|
||||
public const string PM12276Breadcrumbing = "pm-12276-breadcrumbing-for-business-features";
|
||||
@ -169,14 +168,16 @@ public static class FeatureFlagKeys
|
||||
public const string SingleTapPasskeyAuthentication = "single-tap-passkey-authentication";
|
||||
public const string EnablePMAuthenticatorSync = "enable-pm-bwa-sync";
|
||||
public const string PM3503_MobileAnonAddySelfHostAlias = "anon-addy-self-host-alias";
|
||||
|
||||
public const string PM3553_MobileSimpleLoginSelfHostAlias = "simple-login-self-host-alias";
|
||||
public const string EnablePMFlightRecorder = "enable-pm-flight-recorder";
|
||||
public const string MobileErrorReporting = "mobile-error-reporting";
|
||||
|
||||
/* Platform Team */
|
||||
public const string PersistPopupView = "persist-popup-view";
|
||||
public const string StorageReseedRefactor = "storage-reseed-refactor";
|
||||
public const string WebPush = "web-push";
|
||||
public const string RecordInstallationLastActivityDate = "installation-last-activity-date";
|
||||
public const string IpcChannelFramework = "ipc-channel-framework";
|
||||
|
||||
/* Tools Team */
|
||||
public const string ItemShare = "item-share";
|
||||
|
@ -60,6 +60,7 @@ public static class OrganizationServiceCollectionExtensions
|
||||
services.AddOrganizationDomainCommandsQueries();
|
||||
services.AddOrganizationSignUpCommands();
|
||||
services.AddOrganizationDeleteCommands();
|
||||
services.AddOrganizationUpdateCommands();
|
||||
services.AddOrganizationEnableCommands();
|
||||
services.AddOrganizationDisableCommands();
|
||||
services.AddOrganizationAuthCommands();
|
||||
@ -77,6 +78,11 @@ public static class OrganizationServiceCollectionExtensions
|
||||
services.AddScoped<IOrganizationInitiateDeleteCommand, OrganizationInitiateDeleteCommand>();
|
||||
}
|
||||
|
||||
private static void AddOrganizationUpdateCommands(this IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<IOrganizationUpdateKeysCommand, OrganizationUpdateKeysCommand>();
|
||||
}
|
||||
|
||||
private static void AddOrganizationEnableCommands(this IServiceCollection services) =>
|
||||
services.AddScoped<IOrganizationEnableCommand, OrganizationEnableCommand>();
|
||||
|
||||
|
@ -60,6 +60,7 @@ public class OrganizationsControllerTests : IDisposable
|
||||
private readonly IOrganizationDeleteCommand _organizationDeleteCommand;
|
||||
private readonly IPolicyRequirementQuery _policyRequirementQuery;
|
||||
private readonly IPricingClient _pricingClient;
|
||||
private readonly IOrganizationUpdateKeysCommand _organizationUpdateKeysCommand;
|
||||
private readonly OrganizationsController _sut;
|
||||
|
||||
public OrganizationsControllerTests()
|
||||
@ -86,6 +87,7 @@ public class OrganizationsControllerTests : IDisposable
|
||||
_organizationDeleteCommand = Substitute.For<IOrganizationDeleteCommand>();
|
||||
_policyRequirementQuery = Substitute.For<IPolicyRequirementQuery>();
|
||||
_pricingClient = Substitute.For<IPricingClient>();
|
||||
_organizationUpdateKeysCommand = Substitute.For<IOrganizationUpdateKeysCommand>();
|
||||
|
||||
_sut = new OrganizationsController(
|
||||
_organizationRepository,
|
||||
@ -109,7 +111,8 @@ public class OrganizationsControllerTests : IDisposable
|
||||
_cloudOrganizationSignUpCommand,
|
||||
_organizationDeleteCommand,
|
||||
_policyRequirementQuery,
|
||||
_pricingClient);
|
||||
_pricingClient,
|
||||
_organizationUpdateKeysCommand);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
@ -2,6 +2,7 @@
|
||||
using Bit.Api.Models.Response;
|
||||
using Bit.Core.Auth.Models.Api.Response;
|
||||
using Bit.Core.Auth.Models.Data;
|
||||
using Bit.Core.Auth.UserFeatures.DeviceTrust;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
@ -19,6 +20,7 @@ public class DevicesControllerTest
|
||||
private readonly IDeviceRepository _deviceRepositoryMock;
|
||||
private readonly IDeviceService _deviceServiceMock;
|
||||
private readonly IUserService _userServiceMock;
|
||||
private readonly IUntrustDevicesCommand _untrustDevicesCommand;
|
||||
private readonly IUserRepository _userRepositoryMock;
|
||||
private readonly ICurrentContext _currentContextMock;
|
||||
private readonly IGlobalSettings _globalSettingsMock;
|
||||
@ -30,6 +32,7 @@ public class DevicesControllerTest
|
||||
_deviceRepositoryMock = Substitute.For<IDeviceRepository>();
|
||||
_deviceServiceMock = Substitute.For<IDeviceService>();
|
||||
_userServiceMock = Substitute.For<IUserService>();
|
||||
_untrustDevicesCommand = Substitute.For<IUntrustDevicesCommand>();
|
||||
_userRepositoryMock = Substitute.For<IUserRepository>();
|
||||
_currentContextMock = Substitute.For<ICurrentContext>();
|
||||
_loggerMock = Substitute.For<ILogger<DevicesController>>();
|
||||
@ -38,6 +41,7 @@ public class DevicesControllerTest
|
||||
_deviceRepositoryMock,
|
||||
_deviceServiceMock,
|
||||
_userServiceMock,
|
||||
_untrustDevicesCommand,
|
||||
_userRepositoryMock,
|
||||
_currentContextMock,
|
||||
_loggerMock);
|
||||
|
@ -1,7 +1,6 @@
|
||||
using Bit.Billing.Constants;
|
||||
using Bit.Billing.Services;
|
||||
using Bit.Billing.Services.Implementations;
|
||||
using Bit.Core;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces;
|
||||
using Bit.Core.Billing.Enums;
|
||||
@ -31,7 +30,6 @@ public class SubscriptionUpdatedHandlerTests
|
||||
private readonly IPushNotificationService _pushNotificationService;
|
||||
private readonly IOrganizationRepository _organizationRepository;
|
||||
private readonly ISchedulerFactory _schedulerFactory;
|
||||
private readonly IFeatureService _featureService;
|
||||
private readonly IOrganizationEnableCommand _organizationEnableCommand;
|
||||
private readonly IOrganizationDisableCommand _organizationDisableCommand;
|
||||
private readonly IPricingClient _pricingClient;
|
||||
@ -49,7 +47,6 @@ public class SubscriptionUpdatedHandlerTests
|
||||
_pushNotificationService = Substitute.For<IPushNotificationService>();
|
||||
_organizationRepository = Substitute.For<IOrganizationRepository>();
|
||||
_schedulerFactory = Substitute.For<ISchedulerFactory>();
|
||||
_featureService = Substitute.For<IFeatureService>();
|
||||
_organizationEnableCommand = Substitute.For<IOrganizationEnableCommand>();
|
||||
_organizationDisableCommand = Substitute.For<IOrganizationDisableCommand>();
|
||||
_pricingClient = Substitute.For<IPricingClient>();
|
||||
@ -67,7 +64,6 @@ public class SubscriptionUpdatedHandlerTests
|
||||
_pushNotificationService,
|
||||
_organizationRepository,
|
||||
_schedulerFactory,
|
||||
_featureService,
|
||||
_organizationEnableCommand,
|
||||
_organizationDisableCommand,
|
||||
_pricingClient);
|
||||
@ -97,9 +93,6 @@ public class SubscriptionUpdatedHandlerTests
|
||||
_stripeEventUtilityService.GetIdsFromMetadata(Arg.Any<Dictionary<string, string>>())
|
||||
.Returns(Tuple.Create<Guid?, Guid?, Guid?>(organizationId, null, null));
|
||||
|
||||
_featureService.IsEnabled(FeatureFlagKeys.ResellerManagedOrgAlert)
|
||||
.Returns(true);
|
||||
|
||||
// Act
|
||||
await _sut.HandleAsync(parsedEvent);
|
||||
|
||||
|
@ -0,0 +1,75 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Organizations;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class OrganizationUpdateKeysCommandTests
|
||||
{
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateOrganizationKeysAsync_WithoutManageResetPasswordPermission_ThrowsUnauthorizedException(
|
||||
Guid orgId, string publicKey, string privateKey, SutProvider<OrganizationUpdateKeysCommand> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.ManageResetPassword(orgId)
|
||||
.Returns(false);
|
||||
|
||||
await Assert.ThrowsAsync<UnauthorizedAccessException>(
|
||||
() => sutProvider.Sut.UpdateOrganizationKeysAsync(orgId, publicKey, privateKey));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateOrganizationKeysAsync_WhenKeysAlreadyExist_ThrowsBadRequestException(
|
||||
Organization organization, string publicKey, string privateKey,
|
||||
SutProvider<OrganizationUpdateKeysCommand> sutProvider)
|
||||
{
|
||||
organization.PublicKey = "existingPublicKey";
|
||||
organization.PrivateKey = "existingPrivateKey";
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.ManageResetPassword(organization.Id)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByIdAsync(organization.Id)
|
||||
.Returns(organization);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.UpdateOrganizationKeysAsync(organization.Id, publicKey, privateKey));
|
||||
|
||||
Assert.Equal(OrganizationUpdateKeysCommand.OrganizationKeysAlreadyExistErrorMessage, exception.Message);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateOrganizationKeysAsync_WhenKeysDoNotExist_UpdatesOrganization(
|
||||
Organization organization, string publicKey, string privateKey,
|
||||
SutProvider<OrganizationUpdateKeysCommand> sutProvider)
|
||||
{
|
||||
organization.PublicKey = null;
|
||||
organization.PrivateKey = null;
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.ManageResetPassword(organization.Id)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByIdAsync(organization.Id)
|
||||
.Returns(organization);
|
||||
|
||||
var result = await sutProvider.Sut.UpdateOrganizationKeysAsync(organization.Id, publicKey, privateKey);
|
||||
|
||||
Assert.Equal(publicKey, result.PublicKey);
|
||||
Assert.Equal(privateKey, result.PrivateKey);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationService>()
|
||||
.Received(1)
|
||||
.UpdateAsync(organization);
|
||||
}
|
||||
}
|
@ -814,48 +814,6 @@ public class OrganizationServiceTests
|
||||
sutProvider.GetDependency<ICurrentContext>().ManageUsers(organization.Id).Returns(true);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateOrganizationKeysAsync_WithoutManageResetPassword_Throws(Guid orgId, string publicKey,
|
||||
string privateKey, SutProvider<OrganizationService> sutProvider)
|
||||
{
|
||||
var currentContext = Substitute.For<ICurrentContext>();
|
||||
currentContext.ManageResetPassword(orgId).Returns(false);
|
||||
|
||||
await Assert.ThrowsAsync<UnauthorizedAccessException>(
|
||||
() => sutProvider.Sut.UpdateOrganizationKeysAsync(orgId, publicKey, privateKey));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateOrganizationKeysAsync_KeysAlreadySet_Throws(Organization org, string publicKey,
|
||||
string privateKey, SutProvider<OrganizationService> sutProvider)
|
||||
{
|
||||
var currentContext = sutProvider.GetDependency<ICurrentContext>();
|
||||
currentContext.ManageResetPassword(org.Id).Returns(true);
|
||||
|
||||
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
|
||||
organizationRepository.GetByIdAsync(org.Id).Returns(org);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.UpdateOrganizationKeysAsync(org.Id, publicKey, privateKey));
|
||||
Assert.Contains("Organization Keys already exist", exception.Message);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateOrganizationKeysAsync_KeysAlreadySet_Success(Organization org, string publicKey,
|
||||
string privateKey, SutProvider<OrganizationService> sutProvider)
|
||||
{
|
||||
org.PublicKey = null;
|
||||
org.PrivateKey = null;
|
||||
|
||||
var currentContext = sutProvider.GetDependency<ICurrentContext>();
|
||||
currentContext.ManageResetPassword(org.Id).Returns(true);
|
||||
|
||||
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
|
||||
organizationRepository.GetByIdAsync(org.Id).Returns(org);
|
||||
|
||||
await sutProvider.Sut.UpdateOrganizationKeysAsync(org.Id, publicKey, privateKey);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[PaidOrganizationCustomize(CheckedPlanType = PlanType.EnterpriseAnnually)]
|
||||
[BitAutoData("Cannot set max seat autoscaling below seat count", 1, 0, 2, 2)]
|
||||
|
@ -0,0 +1,55 @@
|
||||
using Bit.Core.Auth.UserFeatures.DeviceTrust;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Auth.UserFeatures.WebAuthnLogin;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class UntrustDevicesCommandTests
|
||||
{
|
||||
[Theory, BitAutoData]
|
||||
public async Task SetsKeysToNull(SutProvider<UntrustDevicesCommand> sutProvider, User user)
|
||||
{
|
||||
var deviceId = Guid.NewGuid();
|
||||
// Arrange
|
||||
sutProvider.GetDependency<IDeviceRepository>()
|
||||
.GetManyByUserIdAsync(user.Id)
|
||||
.Returns([new Device
|
||||
{
|
||||
Id = deviceId,
|
||||
EncryptedPrivateKey = "encryptedPrivateKey",
|
||||
EncryptedPublicKey = "encryptedPublicKey",
|
||||
EncryptedUserKey = "encryptedUserKey"
|
||||
}]);
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.UntrustDevices(user, new List<Guid> { deviceId });
|
||||
|
||||
// Assert
|
||||
await sutProvider.GetDependency<IDeviceRepository>()
|
||||
.Received()
|
||||
.UpsertAsync(Arg.Is<Device>(d =>
|
||||
d.Id == deviceId &&
|
||||
d.EncryptedPrivateKey == null &&
|
||||
d.EncryptedPublicKey == null &&
|
||||
d.EncryptedUserKey == null));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task RejectsWrongUser(SutProvider<UntrustDevicesCommand> sutProvider, User user)
|
||||
{
|
||||
var deviceId = Guid.NewGuid();
|
||||
// Arrange
|
||||
sutProvider.GetDependency<IDeviceRepository>()
|
||||
.GetManyByUserIdAsync(user.Id)
|
||||
.Returns([]);
|
||||
|
||||
// Act
|
||||
await Assert.ThrowsAsync<UnauthorizedAccessException>(async () =>
|
||||
await sutProvider.Sut.UntrustDevices(user, new List<Guid> { deviceId }));
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user