mirror of
https://github.com/bitwarden/server.git
synced 2024-12-13 15:36:45 +01:00
Merge branch 'main' into pm-14302-Suspended-icon-remains-after-the-suspended-org-status-updated-to-Active
This commit is contained in:
commit
f33f93dc0f
12
.github/CODEOWNERS
vendored
12
.github/CODEOWNERS
vendored
@ -15,11 +15,7 @@
|
||||
|
||||
## These are shared workflows ##
|
||||
.github/workflows/_move_finalization_db_scripts.yml
|
||||
.github/workflows/build.yml
|
||||
.github/workflows/cleanup-after-pr.yml
|
||||
.github/workflows/cleanup-rc-branch.yml
|
||||
.github/workflows/release.yml
|
||||
.github/workflows/repository-management.yml
|
||||
|
||||
# Database Operations for database changes
|
||||
src/Sql/** @bitwarden/dept-dbops
|
||||
@ -68,6 +64,14 @@ src/EventsProcessor @bitwarden/team-admin-console-dev
|
||||
src/Admin/Controllers/ToolsController.cs @bitwarden/team-billing-dev
|
||||
src/Admin/Views/Tools @bitwarden/team-billing-dev
|
||||
|
||||
# Platform team
|
||||
.github/workflows/build.yml @bitwarden/team-platform-dev
|
||||
.github/workflows/cleanup-after-pr.yml @bitwarden/team-platform-dev
|
||||
.github/workflows/cleanup-rc-branch.yml @bitwarden/team-platform-dev
|
||||
.github/workflows/repository-management.yml @bitwarden/team-platform-dev
|
||||
.github/workflows/test-database.yml @bitwarden/team-platform-dev
|
||||
.github/workflows/test.yml @bitwarden/team-platform-dev
|
||||
|
||||
# Multiple owners - DO NOT REMOVE (BRE)
|
||||
**/packages.lock.json
|
||||
Directory.Build.props
|
||||
|
@ -3,7 +3,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
|
||||
<Version>2024.12.0</Version>
|
||||
<Version>2024.12.1</Version>
|
||||
|
||||
<RootNamespace>Bit.$(MSBuildProjectName)</RootNamespace>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
@ -64,4 +64,4 @@
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
</Project>
|
||||
</Project>
|
@ -3,7 +3,9 @@ using System.Text.Json;
|
||||
using Bit.Admin.Enums;
|
||||
using Bit.Admin.Models;
|
||||
using Bit.Admin.Utilities;
|
||||
using Bit.Core;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Models.BitStripe;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationLicenses.Interfaces;
|
||||
@ -28,6 +30,7 @@ public class ToolsController : Controller
|
||||
private readonly ITransactionRepository _transactionRepository;
|
||||
private readonly IInstallationRepository _installationRepository;
|
||||
private readonly IOrganizationUserRepository _organizationUserRepository;
|
||||
private readonly IProviderUserRepository _providerUserRepository;
|
||||
private readonly IPaymentService _paymentService;
|
||||
private readonly ITaxRateRepository _taxRateRepository;
|
||||
private readonly IStripeAdapter _stripeAdapter;
|
||||
@ -41,6 +44,7 @@ public class ToolsController : Controller
|
||||
ITransactionRepository transactionRepository,
|
||||
IInstallationRepository installationRepository,
|
||||
IOrganizationUserRepository organizationUserRepository,
|
||||
IProviderUserRepository providerUserRepository,
|
||||
ITaxRateRepository taxRateRepository,
|
||||
IPaymentService paymentService,
|
||||
IStripeAdapter stripeAdapter,
|
||||
@ -53,6 +57,7 @@ public class ToolsController : Controller
|
||||
_transactionRepository = transactionRepository;
|
||||
_installationRepository = installationRepository;
|
||||
_organizationUserRepository = organizationUserRepository;
|
||||
_providerUserRepository = providerUserRepository;
|
||||
_taxRateRepository = taxRateRepository;
|
||||
_paymentService = paymentService;
|
||||
_stripeAdapter = stripeAdapter;
|
||||
@ -220,6 +225,46 @@ public class ToolsController : Controller
|
||||
return RedirectToAction("Edit", "Organizations", new { id = model.OrganizationId.Value });
|
||||
}
|
||||
|
||||
[RequireFeature(FeatureFlagKeys.PromoteProviderServiceUserTool)]
|
||||
[RequirePermission(Permission.Tools_PromoteProviderServiceUser)]
|
||||
public IActionResult PromoteProviderServiceUser()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
[RequireFeature(FeatureFlagKeys.PromoteProviderServiceUserTool)]
|
||||
[RequirePermission(Permission.Tools_PromoteProviderServiceUser)]
|
||||
public async Task<IActionResult> PromoteProviderServiceUser(PromoteProviderServiceUserModel model)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return View(model);
|
||||
}
|
||||
|
||||
var providerUsers = await _providerUserRepository.GetManyByProviderAsync(
|
||||
model.ProviderId.Value, null);
|
||||
var serviceUser = providerUsers.FirstOrDefault(u => u.UserId == model.UserId.Value);
|
||||
if (serviceUser == null)
|
||||
{
|
||||
ModelState.AddModelError(nameof(model.UserId), "Service User Id not found in this provider.");
|
||||
}
|
||||
else if (serviceUser.Type != Core.AdminConsole.Enums.Provider.ProviderUserType.ServiceUser)
|
||||
{
|
||||
ModelState.AddModelError(nameof(model.UserId), "User is not a service user of this provider.");
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return View(model);
|
||||
}
|
||||
|
||||
serviceUser.Type = Core.AdminConsole.Enums.Provider.ProviderUserType.ProviderAdmin;
|
||||
await _providerUserRepository.ReplaceAsync(serviceUser);
|
||||
return RedirectToAction("Edit", "Providers", new { id = model.ProviderId.Value });
|
||||
}
|
||||
|
||||
[RequirePermission(Permission.Tools_GenerateLicenseFile)]
|
||||
public IActionResult GenerateLicense()
|
||||
{
|
||||
|
@ -44,6 +44,7 @@ public enum Permission
|
||||
|
||||
Tools_ChargeBrainTreeCustomer,
|
||||
Tools_PromoteAdmin,
|
||||
Tools_PromoteProviderServiceUser,
|
||||
Tools_GenerateLicenseFile,
|
||||
Tools_ManageTaxRates,
|
||||
Tools_ManageStripeSubscriptions,
|
||||
|
13
src/Admin/Models/PromoteProviderServiceUserModel.cs
Normal file
13
src/Admin/Models/PromoteProviderServiceUserModel.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Bit.Admin.Models;
|
||||
|
||||
public class PromoteProviderServiceUserModel
|
||||
{
|
||||
[Required]
|
||||
[Display(Name = "Provider Service User Id")]
|
||||
public Guid? UserId { get; set; }
|
||||
[Required]
|
||||
[Display(Name = "Provider Id")]
|
||||
public Guid? ProviderId { get; set; }
|
||||
}
|
@ -45,6 +45,7 @@ public static class RolePermissionMapping
|
||||
Permission.Provider_ResendEmailInvite,
|
||||
Permission.Tools_ChargeBrainTreeCustomer,
|
||||
Permission.Tools_PromoteAdmin,
|
||||
Permission.Tools_PromoteProviderServiceUser,
|
||||
Permission.Tools_GenerateLicenseFile,
|
||||
Permission.Tools_ManageTaxRates,
|
||||
Permission.Tools_ManageStripeSubscriptions
|
||||
@ -91,6 +92,7 @@ public static class RolePermissionMapping
|
||||
Permission.Provider_ResendEmailInvite,
|
||||
Permission.Tools_ChargeBrainTreeCustomer,
|
||||
Permission.Tools_PromoteAdmin,
|
||||
Permission.Tools_PromoteProviderServiceUser,
|
||||
Permission.Tools_GenerateLicenseFile,
|
||||
Permission.Tools_ManageTaxRates,
|
||||
Permission.Tools_ManageStripeSubscriptions,
|
||||
|
@ -1,8 +1,10 @@
|
||||
@using Bit.Admin.Enums;
|
||||
@using Bit.Core
|
||||
|
||||
@inject SignInManager<IdentityUser> SignInManager
|
||||
@inject Bit.Core.Settings.GlobalSettings GlobalSettings
|
||||
@inject Bit.Admin.Services.IAccessControlService AccessControlService
|
||||
@inject Bit.Core.Services.IFeatureService FeatureService
|
||||
|
||||
@{
|
||||
var canViewUsers = AccessControlService.UserHasPermission(Permission.User_List_View);
|
||||
@ -11,13 +13,15 @@
|
||||
var canChargeBraintree = AccessControlService.UserHasPermission(Permission.Tools_ChargeBrainTreeCustomer);
|
||||
var canCreateTransaction = AccessControlService.UserHasPermission(Permission.Tools_CreateEditTransaction);
|
||||
var canPromoteAdmin = AccessControlService.UserHasPermission(Permission.Tools_PromoteAdmin);
|
||||
var canPromoteProviderServiceUser = FeatureService.IsEnabled(FeatureFlagKeys.PromoteProviderServiceUserTool) &&
|
||||
AccessControlService.UserHasPermission(Permission.Tools_PromoteProviderServiceUser);
|
||||
var canGenerateLicense = AccessControlService.UserHasPermission(Permission.Tools_GenerateLicenseFile);
|
||||
var canManageTaxRates = AccessControlService.UserHasPermission(Permission.Tools_ManageTaxRates);
|
||||
var canManageStripeSubscriptions = AccessControlService.UserHasPermission(Permission.Tools_ManageStripeSubscriptions);
|
||||
var canProcessStripeEvents = AccessControlService.UserHasPermission(Permission.Tools_ProcessStripeEvents);
|
||||
var canMigrateProviders = AccessControlService.UserHasPermission(Permission.Tools_MigrateProviders);
|
||||
|
||||
var canViewTools = canChargeBraintree || canCreateTransaction || canPromoteAdmin ||
|
||||
var canViewTools = canChargeBraintree || canCreateTransaction || canPromoteAdmin || canPromoteProviderServiceUser ||
|
||||
canGenerateLicense || canManageTaxRates || canManageStripeSubscriptions;
|
||||
}
|
||||
|
||||
@ -91,6 +95,12 @@
|
||||
Promote Admin
|
||||
</a>
|
||||
}
|
||||
@if (canPromoteProviderServiceUser)
|
||||
{
|
||||
<a class="dropdown-item" asp-controller="Tools" asp-action="PromoteProviderServiceUser">
|
||||
Promote Provider Service User
|
||||
</a>
|
||||
}
|
||||
@if (canGenerateLicense)
|
||||
{
|
||||
<a class="dropdown-item" asp-controller="Tools" asp-action="GenerateLicense">
|
||||
|
25
src/Admin/Views/Tools/PromoteProviderServiceUser.cshtml
Normal file
25
src/Admin/Views/Tools/PromoteProviderServiceUser.cshtml
Normal file
@ -0,0 +1,25 @@
|
||||
@model PromoteProviderServiceUserModel
|
||||
@{
|
||||
ViewData["Title"] = "Promote Provider Service User";
|
||||
}
|
||||
|
||||
<h1>Promote Provider Service User</h1>
|
||||
|
||||
<form method="post">
|
||||
<div asp-validation-summary="All" class="alert alert-danger"></div>
|
||||
<div class="row">
|
||||
<div class="col-md">
|
||||
<div class="mb-3">
|
||||
<label asp-for="UserId" class="form-label"></label>
|
||||
<input type="text" class="form-control" asp-for="UserId">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md">
|
||||
<div class="mb-3">
|
||||
<label asp-for="ProviderId" class="form-label"></label>
|
||||
<input type="text" class="form-control" asp-for="ProviderId">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Promote Service User</button>
|
||||
</form>
|
@ -259,7 +259,7 @@ public class OrganizationsController : Controller
|
||||
throw new BadRequestException("Managed user account cannot leave managing organization. Contact your organization administrator for additional details.");
|
||||
}
|
||||
|
||||
await _removeOrganizationUserCommand.RemoveUserAsync(id, user.Id);
|
||||
await _removeOrganizationUserCommand.UserLeaveAsync(id, user.Id);
|
||||
}
|
||||
|
||||
[HttpDelete("{id}")]
|
||||
@ -540,4 +540,17 @@ public class OrganizationsController : Controller
|
||||
await _organizationService.UpdateAsync(model.ToOrganization(organization, _featureService), eventType: EventType.Organization_CollectionManagement_Updated);
|
||||
return new OrganizationResponseModel(organization);
|
||||
}
|
||||
|
||||
[HttpGet("{id}/plan-type")]
|
||||
public async Task<PlanType> GetPlanType(string id)
|
||||
{
|
||||
var orgIdGuid = new Guid(id);
|
||||
var organization = await _organizationRepository.GetByIdAsync(orgIdGuid);
|
||||
if (organization == null)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
return organization.PlanType;
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,9 @@ using Bit.Api.Models.Request.Organizations;
|
||||
using Bit.Api.Models.Response;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.Billing.Constants;
|
||||
using Bit.Core.Billing.Entities;
|
||||
using Bit.Core.Billing.Models;
|
||||
using Bit.Core.Billing.Repositories;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Enums;
|
||||
@ -42,7 +44,8 @@ public class OrganizationsController(
|
||||
IUpgradeOrganizationPlanCommand upgradeOrganizationPlanCommand,
|
||||
IAddSecretsManagerSubscriptionCommand addSecretsManagerSubscriptionCommand,
|
||||
IReferenceEventService referenceEventService,
|
||||
ISubscriberService subscriberService)
|
||||
ISubscriberService subscriberService,
|
||||
IOrganizationInstallationRepository organizationInstallationRepository)
|
||||
: Controller
|
||||
{
|
||||
[HttpGet("{id:guid}/subscription")]
|
||||
@ -97,6 +100,8 @@ public class OrganizationsController(
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
await SaveOrganizationInstallationAsync(id, installationId);
|
||||
|
||||
return license;
|
||||
}
|
||||
|
||||
@ -366,4 +371,24 @@ public class OrganizationsController(
|
||||
|
||||
return await organizationRepository.GetByIdAsync(id);
|
||||
}
|
||||
|
||||
private async Task SaveOrganizationInstallationAsync(Guid organizationId, Guid installationId)
|
||||
{
|
||||
var organizationInstallation =
|
||||
await organizationInstallationRepository.GetByInstallationIdAsync(installationId);
|
||||
|
||||
if (organizationInstallation == null)
|
||||
{
|
||||
await organizationInstallationRepository.CreateAsync(new OrganizationInstallation
|
||||
{
|
||||
OrganizationId = organizationId,
|
||||
InstallationId = installationId
|
||||
});
|
||||
}
|
||||
else if (organizationInstallation.OrganizationId == organizationId)
|
||||
{
|
||||
organizationInstallation.RevisionDate = DateTime.UtcNow;
|
||||
await organizationInstallationRepository.ReplaceAsync(organizationInstallation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -50,4 +50,11 @@ public interface IRemoveOrganizationUserCommand
|
||||
/// </returns>
|
||||
Task<IEnumerable<(Guid OrganizationUserId, string ErrorMessage)>> RemoveUsersAsync(
|
||||
Guid organizationId, IEnumerable<Guid> organizationUserIds, EventSystemUser eventSystemUser);
|
||||
|
||||
/// <summary>
|
||||
/// Removes a user from an organization when they have left voluntarily. This should only be called by the same user who is being removed.
|
||||
/// </summary>
|
||||
/// <param name="organizationId">Organization to leave.</param>
|
||||
/// <param name="userId">User to leave.</param>
|
||||
Task UserLeaveAsync(Guid organizationId, Guid userId);
|
||||
}
|
||||
|
@ -114,6 +114,16 @@ public class RemoveOrganizationUserCommand : IRemoveOrganizationUserCommand
|
||||
return result.Select(r => (r.OrganizationUser.Id, r.ErrorMessage));
|
||||
}
|
||||
|
||||
public async Task UserLeaveAsync(Guid organizationId, Guid userId)
|
||||
{
|
||||
var organizationUser = await _organizationUserRepository.GetByOrganizationAsync(organizationId, userId);
|
||||
ValidateRemoveUser(organizationId, organizationUser);
|
||||
|
||||
await RepositoryRemoveUserAsync(organizationUser, deletingUserId: null, eventSystemUser: null);
|
||||
|
||||
await _eventService.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Left);
|
||||
}
|
||||
|
||||
private void ValidateRemoveUser(Guid organizationId, OrganizationUser orgUser)
|
||||
{
|
||||
if (orgUser == null || orgUser.OrganizationId != organizationId)
|
||||
@ -234,7 +244,7 @@ public class RemoveOrganizationUserCommand : IRemoveOrganizationUserCommand
|
||||
await _organizationUserRepository.DeleteManyAsync(organizationUsersToRemove.Select(ou => ou.Id));
|
||||
foreach (var orgUser in organizationUsersToRemove.Where(ou => ou.UserId.HasValue))
|
||||
{
|
||||
await DeleteAndPushUserRegistrationAsync(organizationId, orgUser.UserId.Value);
|
||||
await DeleteAndPushUserRegistrationAsync(organizationId, orgUser.UserId!.Value);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,7 @@ public class OrganizationDomainService : IOrganizationDomainService
|
||||
private readonly TimeProvider _timeProvider;
|
||||
private readonly ILogger<OrganizationDomainService> _logger;
|
||||
private readonly IGlobalSettings _globalSettings;
|
||||
private readonly IFeatureService _featureService;
|
||||
|
||||
public OrganizationDomainService(
|
||||
IOrganizationDomainRepository domainRepository,
|
||||
@ -26,7 +27,8 @@ public class OrganizationDomainService : IOrganizationDomainService
|
||||
IVerifyOrganizationDomainCommand verifyOrganizationDomainCommand,
|
||||
TimeProvider timeProvider,
|
||||
ILogger<OrganizationDomainService> logger,
|
||||
IGlobalSettings globalSettings)
|
||||
IGlobalSettings globalSettings,
|
||||
IFeatureService featureService)
|
||||
{
|
||||
_domainRepository = domainRepository;
|
||||
_organizationUserRepository = organizationUserRepository;
|
||||
@ -36,6 +38,7 @@ public class OrganizationDomainService : IOrganizationDomainService
|
||||
_timeProvider = timeProvider;
|
||||
_logger = logger;
|
||||
_globalSettings = globalSettings;
|
||||
_featureService = featureService;
|
||||
}
|
||||
|
||||
public async Task ValidateOrganizationsDomainAsync()
|
||||
@ -90,8 +93,16 @@ public class OrganizationDomainService : IOrganizationDomainService
|
||||
//Send email to administrators
|
||||
if (adminEmails.Count > 0)
|
||||
{
|
||||
await _mailService.SendUnverifiedOrganizationDomainEmailAsync(adminEmails,
|
||||
domain.OrganizationId.ToString(), domain.DomainName);
|
||||
if (_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning))
|
||||
{
|
||||
await _mailService.SendUnclaimedOrganizationDomainEmailAsync(adminEmails,
|
||||
domain.OrganizationId.ToString(), domain.DomainName);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _mailService.SendUnverifiedOrganizationDomainEmailAsync(adminEmails,
|
||||
domain.OrganizationId.ToString(), domain.DomainName);
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogInformation(Constants.BypassFiltersEventId, "Expired domain: {domainName}", domain.DomainName);
|
||||
|
@ -14,6 +14,7 @@ using Bit.Core.Auth.Models.Business;
|
||||
using Bit.Core.Auth.Models.Business.Tokenables;
|
||||
using Bit.Core.Auth.Repositories;
|
||||
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
|
||||
using Bit.Core.Billing.Constants;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Billing.Extensions;
|
||||
using Bit.Core.Billing.Services;
|
||||
@ -1324,6 +1325,12 @@ public class OrganizationService : IOrganizationService
|
||||
return (false, $"Seat limit has been reached.");
|
||||
}
|
||||
|
||||
var subscription = await _paymentService.GetSubscriptionAsync(organization);
|
||||
if (subscription?.Subscription?.Status == StripeConstants.SubscriptionStatus.Canceled)
|
||||
{
|
||||
return (false, "Cannot autoscale with a canceled subscription.");
|
||||
}
|
||||
|
||||
return (true, failureReason);
|
||||
}
|
||||
|
||||
|
24
src/Core/Billing/Entities/OrganizationInstallation.cs
Normal file
24
src/Core/Billing/Entities/OrganizationInstallation.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.Core.Billing.Entities;
|
||||
|
||||
#nullable enable
|
||||
|
||||
public class OrganizationInstallation : ITableObject<Guid>
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public Guid OrganizationId { get; set; }
|
||||
public Guid InstallationId { get; set; }
|
||||
public DateTime CreationDate { get; internal set; } = DateTime.UtcNow;
|
||||
public DateTime? RevisionDate { get; set; }
|
||||
|
||||
public void SetNewId()
|
||||
{
|
||||
if (Id == default)
|
||||
{
|
||||
Id = CoreHelpers.GenerateComb();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
using Bit.Core.Billing.Entities;
|
||||
using Bit.Core.Repositories;
|
||||
|
||||
namespace Bit.Core.Billing.Repositories;
|
||||
|
||||
public interface IOrganizationInstallationRepository : IRepository<OrganizationInstallation, Guid>
|
||||
{
|
||||
Task<OrganizationInstallation> GetByInstallationIdAsync(Guid installationId);
|
||||
Task<ICollection<OrganizationInstallation>> GetByOrganizationIdAsync(Guid organizationId);
|
||||
}
|
@ -144,7 +144,6 @@ public static class FeatureFlagKeys
|
||||
public const string AccessIntelligence = "pm-13227-access-intelligence";
|
||||
public const string VerifiedSsoDomainEndpoint = "pm-12337-refactor-sso-details-endpoint";
|
||||
public const string PM12275_MultiOrganizationEnterprises = "pm-12275-multi-organization-enterprises";
|
||||
public const string Pm13322AddPolicyDefinitions = "pm-13322-add-policy-definitions";
|
||||
public const string GeneratorToolsModernization = "generator-tools-modernization";
|
||||
public const string NewDeviceVerification = "new-device-verification";
|
||||
public const string RiskInsightsCriticalApplication = "pm-14466-risk-insights-critical-application";
|
||||
@ -158,7 +157,9 @@ public static class FeatureFlagKeys
|
||||
public const string MacOsNativeCredentialSync = "macos-native-credential-sync";
|
||||
public const string PM9111ExtensionPersistAddEditForm = "pm-9111-extension-persist-add-edit-form";
|
||||
public const string InlineMenuTotp = "inline-menu-totp";
|
||||
public const string PM12443RemovePagingLogic = "pm-12443-remove-paging-logic";
|
||||
public const string SelfHostLicenseRefactor = "pm-11516-self-host-license-refactor";
|
||||
public const string PromoteProviderServiceUserTool = "pm-15128-promote-provider-service-user-tool";
|
||||
|
||||
public static List<string> GetAllKeys()
|
||||
{
|
||||
@ -174,7 +175,6 @@ public static class FeatureFlagKeys
|
||||
return new Dictionary<string, string>()
|
||||
{
|
||||
{ DuoRedirect, "true" },
|
||||
{ CipherKeyEncryption, "true" },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using Quartz;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Quartz;
|
||||
using Quartz.Spi;
|
||||
|
||||
namespace Bit.Core.Jobs;
|
||||
@ -14,7 +15,8 @@ public class JobFactory : IJobFactory
|
||||
|
||||
public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
|
||||
{
|
||||
return _container.GetService(bundle.JobDetail.JobType) as IJob;
|
||||
var scope = _container.CreateScope();
|
||||
return scope.ServiceProvider.GetService(bundle.JobDetail.JobType) as IJob;
|
||||
}
|
||||
|
||||
public void ReturnJob(IJob job)
|
||||
|
@ -0,0 +1,27 @@
|
||||
{{#>FullHtmlLayout}}
|
||||
<table width="100%" cellpadding="0" cellspacing="0" style="margin: 0; box-sizing: border-box; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
|
||||
<tr style="margin: 0; box-sizing: border-box; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
|
||||
<td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0 0 10px; -webkit-text-size-adjust: none; text-align: left;" valign="top">
|
||||
The domain {{DomainName}} in your Bitwarden organization could not be claimed.
|
||||
</td>
|
||||
</tr>
|
||||
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
|
||||
<td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0 0 10px; -webkit-text-size-adjust: none;" valign="top">
|
||||
Check the corresponding record in your domain host. Then reclaim this domain in Bitwarden to use it for your organization.
|
||||
</td>
|
||||
</tr>
|
||||
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
|
||||
<td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0 0 10px; -webkit-text-size-adjust: none;" valign="top">
|
||||
The domain will be removed from your organization in 7 days if it is not claimed.
|
||||
</td>
|
||||
</tr>
|
||||
<tr style="margin: 0; box-sizing: border-box; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
|
||||
<td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0 0 10px; -webkit-text-size-adjust: none; text-align: center;" valign="top" align="center">
|
||||
<a href="{{{Url}}}" clicktracking=off target="_blank" style="color: #ffffff; text-decoration: none; text-align: center; cursor: pointer; display: inline-block; border-radius: 5px; background-color: #175DDC; border-color: #175DDC; border-style: solid; border-width: 10px 20px; margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
|
||||
Manage Domains
|
||||
</a>
|
||||
<br style="margin: 0; box-sizing: border-box; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;" />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
{{/FullHtmlLayout}}
|
@ -0,0 +1,10 @@
|
||||
{{#>BasicTextLayout}}
|
||||
The domain {{DomainName}} in your Bitwarden organization could not be claimed.
|
||||
|
||||
Check the corresponding record in your domain host. Then reclaim this domain in Bitwarden to use it for your organization.
|
||||
|
||||
The domain will be removed from your organization in 7 days if it is not claimed.
|
||||
|
||||
{{Url}}
|
||||
|
||||
{{/BasicTextLayout}}
|
@ -1,4 +1,6 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Business;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationLicenses.Interfaces;
|
||||
@ -12,15 +14,18 @@ public class CloudGetOrganizationLicenseQuery : ICloudGetOrganizationLicenseQuer
|
||||
private readonly IInstallationRepository _installationRepository;
|
||||
private readonly IPaymentService _paymentService;
|
||||
private readonly ILicensingService _licensingService;
|
||||
private readonly IProviderRepository _providerRepository;
|
||||
|
||||
public CloudGetOrganizationLicenseQuery(
|
||||
IInstallationRepository installationRepository,
|
||||
IPaymentService paymentService,
|
||||
ILicensingService licensingService)
|
||||
ILicensingService licensingService,
|
||||
IProviderRepository providerRepository)
|
||||
{
|
||||
_installationRepository = installationRepository;
|
||||
_paymentService = paymentService;
|
||||
_licensingService = licensingService;
|
||||
_providerRepository = providerRepository;
|
||||
}
|
||||
|
||||
public async Task<OrganizationLicense> GetLicenseAsync(Organization organization, Guid installationId,
|
||||
@ -32,11 +37,22 @@ public class CloudGetOrganizationLicenseQuery : ICloudGetOrganizationLicenseQuer
|
||||
throw new BadRequestException("Invalid installation id");
|
||||
}
|
||||
|
||||
var subscriptionInfo = await _paymentService.GetSubscriptionAsync(organization);
|
||||
var subscriptionInfo = await GetSubscriptionAsync(organization);
|
||||
|
||||
return new OrganizationLicense(organization, subscriptionInfo, installationId, _licensingService, version)
|
||||
{
|
||||
Token = await _licensingService.CreateOrganizationTokenAsync(organization, installationId, subscriptionInfo)
|
||||
};
|
||||
}
|
||||
|
||||
private async Task<SubscriptionInfo> GetSubscriptionAsync(Organization organization)
|
||||
{
|
||||
if (organization is not { Status: OrganizationStatusType.Managed })
|
||||
{
|
||||
return await _paymentService.GetSubscriptionAsync(organization);
|
||||
}
|
||||
|
||||
var provider = await _providerRepository.GetByOrganizationIdAsync(organization.Id);
|
||||
return await _paymentService.GetSubscriptionAsync(provider);
|
||||
}
|
||||
}
|
||||
|
@ -85,6 +85,7 @@ public interface IMailService
|
||||
Task SendFailedLoginAttemptsEmailAsync(string email, DateTime utcNow, string ip);
|
||||
Task SendFailedTwoFactorAttemptsEmailAsync(string email, DateTime utcNow, string ip);
|
||||
Task SendUnverifiedOrganizationDomainEmailAsync(IEnumerable<string> adminEmails, string organizationId, string domainName);
|
||||
Task SendUnclaimedOrganizationDomainEmailAsync(IEnumerable<string> adminEmails, string organizationId, string domainName);
|
||||
Task SendSecretsManagerMaxSeatLimitReachedEmailAsync(Organization organization, int maxSeatCount, IEnumerable<string> ownerEmails);
|
||||
Task SendSecretsManagerMaxServiceAccountLimitReachedEmailAsync(Organization organization, int maxSeatCount, IEnumerable<string> ownerEmails);
|
||||
Task SendTrustedDeviceAdminApprovalEmailAsync(string email, DateTime utcNow, string ip, string deviceTypeAndIdentifier);
|
||||
|
@ -1068,6 +1068,19 @@ public class HandlebarsMailService : IMailService
|
||||
await _mailDeliveryService.SendEmailAsync(message);
|
||||
}
|
||||
|
||||
public async Task SendUnclaimedOrganizationDomainEmailAsync(IEnumerable<string> adminEmails, string organizationId, string domainName)
|
||||
{
|
||||
var message = CreateDefaultMessage("Domain not claimed", adminEmails);
|
||||
var model = new OrganizationDomainUnverifiedViewModel
|
||||
{
|
||||
Url = $"{_globalSettings.BaseServiceUri.VaultWithHash}/organizations/{organizationId}/settings/domain-verification",
|
||||
DomainName = domainName
|
||||
};
|
||||
await AddMessageContentAsync(message, "OrganizationDomainUnclaimed", model);
|
||||
message.Category = "UnclaimedOrganizationDomain";
|
||||
await _mailDeliveryService.SendEmailAsync(message);
|
||||
}
|
||||
|
||||
public async Task SendSecretsManagerMaxSeatLimitReachedEmailAsync(Organization organization, int maxSeatCount,
|
||||
IEnumerable<string> ownerEmails)
|
||||
{
|
||||
|
@ -273,6 +273,11 @@ public class NoopMailService : IMailService
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendUnclaimedOrganizationDomainEmailAsync(IEnumerable<string> adminEmails, string organizationId, string domainName)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendSecretsManagerMaxSeatLimitReachedEmailAsync(Organization organization, int maxSeatCount,
|
||||
IEnumerable<string> ownerEmails)
|
||||
{
|
||||
|
@ -0,0 +1,39 @@
|
||||
using System.Data;
|
||||
using Bit.Core.Billing.Entities;
|
||||
using Bit.Core.Billing.Repositories;
|
||||
using Bit.Core.Settings;
|
||||
using Bit.Infrastructure.Dapper.Repositories;
|
||||
using Dapper;
|
||||
using Microsoft.Data.SqlClient;
|
||||
|
||||
namespace Bit.Infrastructure.Dapper.Billing.Repositories;
|
||||
|
||||
public class OrganizationInstallationRepository(
|
||||
GlobalSettings globalSettings) : Repository<OrganizationInstallation, Guid>(
|
||||
globalSettings.SqlServer.ConnectionString,
|
||||
globalSettings.SqlServer.ReadOnlyConnectionString), IOrganizationInstallationRepository
|
||||
{
|
||||
public async Task<OrganizationInstallation> GetByInstallationIdAsync(Guid installationId)
|
||||
{
|
||||
var sqlConnection = new SqlConnection(ConnectionString);
|
||||
|
||||
var results = await sqlConnection.QueryAsync<OrganizationInstallation>(
|
||||
"[dbo].[OrganizationInstallation_ReadByInstallationId]",
|
||||
new { InstallationId = installationId },
|
||||
commandType: CommandType.StoredProcedure);
|
||||
|
||||
return results.FirstOrDefault();
|
||||
}
|
||||
|
||||
public async Task<ICollection<OrganizationInstallation>> GetByOrganizationIdAsync(Guid organizationId)
|
||||
{
|
||||
var sqlConnection = new SqlConnection(ConnectionString);
|
||||
|
||||
var results = await sqlConnection.QueryAsync<OrganizationInstallation>(
|
||||
"[dbo].[OrganizationInstallation_ReadByOrganizationId]",
|
||||
new { OrganizationId = organizationId },
|
||||
commandType: CommandType.StoredProcedure);
|
||||
|
||||
return results.ToArray();
|
||||
}
|
||||
}
|
@ -63,6 +63,7 @@ public static class DapperServiceCollectionExtensions
|
||||
services.AddSingleton<IPasswordHealthReportApplicationRepository, PasswordHealthReportApplicationRepository>();
|
||||
services.AddSingleton<ISecurityTaskRepository, SecurityTaskRepository>();
|
||||
services.AddSingleton<IUserAsymmetricKeysRepository, UserAsymmetricKeysRepository>();
|
||||
services.AddSingleton<IOrganizationInstallationRepository, OrganizationInstallationRepository>();
|
||||
|
||||
if (selfHosted)
|
||||
{
|
||||
|
@ -0,0 +1,29 @@
|
||||
using Bit.Infrastructure.EntityFramework.Billing.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace Bit.Infrastructure.EntityFramework.Billing.Configurations;
|
||||
|
||||
public class OrganizationInstallationEntityTypeConfiguration : IEntityTypeConfiguration<OrganizationInstallation>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<OrganizationInstallation> builder)
|
||||
{
|
||||
builder
|
||||
.Property(oi => oi.Id)
|
||||
.ValueGeneratedNever();
|
||||
|
||||
builder
|
||||
.HasKey(oi => oi.Id)
|
||||
.IsClustered();
|
||||
|
||||
builder
|
||||
.HasIndex(oi => oi.OrganizationId)
|
||||
.IsClustered(false);
|
||||
|
||||
builder
|
||||
.HasIndex(oi => oi.InstallationId)
|
||||
.IsClustered(false);
|
||||
|
||||
builder.ToTable(nameof(OrganizationInstallation));
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
using AutoMapper;
|
||||
using Bit.Infrastructure.EntityFramework.AdminConsole.Models;
|
||||
using Bit.Infrastructure.EntityFramework.Models;
|
||||
|
||||
namespace Bit.Infrastructure.EntityFramework.Billing.Models;
|
||||
|
||||
public class OrganizationInstallation : Core.Billing.Entities.OrganizationInstallation
|
||||
{
|
||||
public virtual Installation Installation { get; set; }
|
||||
public virtual Organization Organization { get; set; }
|
||||
}
|
||||
|
||||
public class OrganizationInstallationMapperProfile : Profile
|
||||
{
|
||||
public OrganizationInstallationMapperProfile()
|
||||
{
|
||||
CreateMap<Core.Billing.Entities.OrganizationInstallation, OrganizationInstallation>().ReverseMap();
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
using AutoMapper;
|
||||
using Bit.Core.Billing.Entities;
|
||||
using Bit.Core.Billing.Repositories;
|
||||
using Bit.Infrastructure.EntityFramework.Repositories;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using EFOrganizationInstallation = Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation;
|
||||
|
||||
namespace Bit.Infrastructure.EntityFramework.Billing.Repositories;
|
||||
|
||||
public class OrganizationInstallationRepository(
|
||||
IMapper mapper,
|
||||
IServiceScopeFactory serviceScopeFactory) : Repository<OrganizationInstallation, EFOrganizationInstallation, Guid>(
|
||||
serviceScopeFactory,
|
||||
mapper,
|
||||
context => context.OrganizationInstallations), IOrganizationInstallationRepository
|
||||
{
|
||||
public async Task<OrganizationInstallation> GetByInstallationIdAsync(Guid installationId)
|
||||
{
|
||||
using var serviceScope = ServiceScopeFactory.CreateScope();
|
||||
|
||||
var databaseContext = GetDatabaseContext(serviceScope);
|
||||
|
||||
var query =
|
||||
from organizationInstallation in databaseContext.OrganizationInstallations
|
||||
where organizationInstallation.Id == installationId
|
||||
select organizationInstallation;
|
||||
|
||||
return await query.FirstOrDefaultAsync();
|
||||
}
|
||||
|
||||
public async Task<ICollection<OrganizationInstallation>> GetByOrganizationIdAsync(Guid organizationId)
|
||||
{
|
||||
using var serviceScope = ServiceScopeFactory.CreateScope();
|
||||
|
||||
var databaseContext = GetDatabaseContext(serviceScope);
|
||||
|
||||
var query =
|
||||
from organizationInstallation in databaseContext.OrganizationInstallations
|
||||
where organizationInstallation.OrganizationId == organizationId
|
||||
select organizationInstallation;
|
||||
|
||||
return await query.ToArrayAsync();
|
||||
}
|
||||
}
|
@ -78,6 +78,7 @@ public class DatabaseContext : DbContext
|
||||
public DbSet<ClientOrganizationMigrationRecord> ClientOrganizationMigrationRecords { get; set; }
|
||||
public DbSet<PasswordHealthReportApplication> PasswordHealthReportApplications { get; set; }
|
||||
public DbSet<SecurityTask> SecurityTasks { get; set; }
|
||||
public DbSet<OrganizationInstallation> OrganizationInstallations { get; set; }
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder builder)
|
||||
{
|
||||
|
@ -0,0 +1,27 @@
|
||||
CREATE PROCEDURE [dbo].[OrganizationInstallation_Create]
|
||||
@Id UNIQUEIDENTIFIER OUTPUT,
|
||||
@OrganizationId UNIQUEIDENTIFIER,
|
||||
@InstallationId UNIQUEIDENTIFIER,
|
||||
@CreationDate DATETIME2 (7),
|
||||
@RevisionDate DATETIME2 (7) = NULL
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
INSERT INTO [dbo].[OrganizationInstallation]
|
||||
(
|
||||
[Id],
|
||||
[OrganizationId],
|
||||
[InstallationId],
|
||||
[CreationDate],
|
||||
[RevisionDate]
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
@Id,
|
||||
@OrganizationId,
|
||||
@InstallationId,
|
||||
@CreationDate,
|
||||
@RevisionDate
|
||||
)
|
||||
END
|
@ -0,0 +1,12 @@
|
||||
CREATE PROCEDURE [dbo].[OrganizationInstallation_DeleteById]
|
||||
@Id UNIQUEIDENTIFIER
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
DELETE
|
||||
FROM
|
||||
[dbo].[OrganizationInstallation]
|
||||
WHERE
|
||||
[Id] = @Id
|
||||
END
|
@ -0,0 +1,13 @@
|
||||
CREATE PROCEDURE [dbo].[OrganizationInstallation_ReadById]
|
||||
@Id UNIQUEIDENTIFIER
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
[dbo].[OrganizationInstallationView]
|
||||
WHERE
|
||||
[Id] = @Id
|
||||
END
|
@ -0,0 +1,13 @@
|
||||
CREATE PROCEDURE [dbo].[OrganizationInstallation_ReadByInstallationId]
|
||||
@InstallationId UNIQUEIDENTIFIER
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
[dbo].[OrganizationInstallationView]
|
||||
WHERE
|
||||
[InstallationId] = @InstallationId
|
||||
END
|
@ -0,0 +1,13 @@
|
||||
CREATE PROCEDURE [dbo].[OrganizationInstallation_ReadByOrganizationId]
|
||||
@OrganizationId UNIQUEIDENTIFIER
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
[dbo].[OrganizationInstallationView]
|
||||
WHERE
|
||||
[OrganizationId] = @OrganizationId
|
||||
END
|
@ -0,0 +1,17 @@
|
||||
CREATE PROCEDURE [dbo].[OrganizationInstallation_Update]
|
||||
@Id UNIQUEIDENTIFIER OUTPUT,
|
||||
@OrganizationId UNIQUEIDENTIFIER,
|
||||
@InstallationId UNIQUEIDENTIFIER,
|
||||
@CreationDate DATETIME2 (7),
|
||||
@RevisionDate DATETIME2 (7)
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
UPDATE
|
||||
[dbo].[OrganizationInstallation]
|
||||
SET
|
||||
[RevisionDate] = @RevisionDate
|
||||
WHERE
|
||||
[Id] = @Id
|
||||
END
|
18
src/Sql/Billing/dbo/Tables/OrganizationInstallation.sql
Normal file
18
src/Sql/Billing/dbo/Tables/OrganizationInstallation.sql
Normal file
@ -0,0 +1,18 @@
|
||||
CREATE TABLE [dbo].[OrganizationInstallation] (
|
||||
[Id] UNIQUEIDENTIFIER NOT NULL,
|
||||
[OrganizationId] UNIQUEIDENTIFIER NOT NULL,
|
||||
[InstallationId] UNIQUEIDENTIFIER NOT NULL,
|
||||
[CreationDate] DATETIME2 (7) NOT NULL,
|
||||
[RevisionDate] DATETIME2 (7) NULL,
|
||||
CONSTRAINT [PK_OrganizationInstallation] PRIMARY KEY CLUSTERED ([Id] ASC),
|
||||
CONSTRAINT [FK_OrganizationInstallation_Organization] FOREIGN KEY ([OrganizationId]) REFERENCES [dbo].[Organization] ([Id]) ON DELETE CASCADE,
|
||||
CONSTRAINT [FK_OrganizationInstallation_Installation] FOREIGN KEY ([InstallationId]) REFERENCES [dbo].[Installation] ([Id]) ON DELETE CASCADE
|
||||
);
|
||||
GO
|
||||
|
||||
CREATE NONCLUSTERED INDEX [IX_OrganizationInstallation_OrganizationId]
|
||||
ON [dbo].[OrganizationInstallation]([OrganizationId] ASC);
|
||||
GO
|
||||
|
||||
CREATE NONCLUSTERED INDEX [IX_OrganizationInstallation_InstallationId]
|
||||
ON [dbo].[OrganizationInstallation]([InstallationId] ASC);
|
@ -0,0 +1,6 @@
|
||||
CREATE VIEW [dbo].[OrganizationInstallationView]
|
||||
AS
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
[dbo].[OrganizationInstallation];
|
@ -130,7 +130,7 @@ public class OrganizationsControllerTests : IDisposable
|
||||
Assert.Contains("Your organization's Single Sign-On settings prevent you from leaving.",
|
||||
exception.Message);
|
||||
|
||||
await _removeOrganizationUserCommand.DidNotReceiveWithAnyArgs().RemoveUserAsync(default, default);
|
||||
await _removeOrganizationUserCommand.DidNotReceiveWithAnyArgs().UserLeaveAsync(default, default);
|
||||
}
|
||||
|
||||
[Theory, AutoData]
|
||||
@ -193,7 +193,7 @@ public class OrganizationsControllerTests : IDisposable
|
||||
|
||||
await _sut.Leave(orgId);
|
||||
|
||||
await _removeOrganizationUserCommand.Received(1).RemoveUserAsync(orgId, user.Id);
|
||||
await _removeOrganizationUserCommand.Received(1).UserLeaveAsync(orgId, user.Id);
|
||||
}
|
||||
|
||||
[Theory, AutoData]
|
||||
|
@ -10,6 +10,7 @@ using Bit.Core.Auth.Enums;
|
||||
using Bit.Core.Auth.Models.Data;
|
||||
using Bit.Core.Auth.Repositories;
|
||||
using Bit.Core.Auth.Services;
|
||||
using Bit.Core.Billing.Repositories;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Entities;
|
||||
@ -47,6 +48,7 @@ public class OrganizationsControllerTests : IDisposable
|
||||
private readonly IReferenceEventService _referenceEventService;
|
||||
private readonly ISubscriberService _subscriberService;
|
||||
private readonly IRemoveOrganizationUserCommand _removeOrganizationUserCommand;
|
||||
private readonly IOrganizationInstallationRepository _organizationInstallationRepository;
|
||||
|
||||
private readonly OrganizationsController _sut;
|
||||
|
||||
@ -70,6 +72,7 @@ public class OrganizationsControllerTests : IDisposable
|
||||
_referenceEventService = Substitute.For<IReferenceEventService>();
|
||||
_subscriberService = Substitute.For<ISubscriberService>();
|
||||
_removeOrganizationUserCommand = Substitute.For<IRemoveOrganizationUserCommand>();
|
||||
_organizationInstallationRepository = Substitute.For<IOrganizationInstallationRepository>();
|
||||
|
||||
_sut = new OrganizationsController(
|
||||
_organizationRepository,
|
||||
@ -85,7 +88,8 @@ public class OrganizationsControllerTests : IDisposable
|
||||
_upgradeOrganizationPlanCommand,
|
||||
_addSecretsManagerSubscriptionCommand,
|
||||
_referenceEventService,
|
||||
_subscriberService);
|
||||
_subscriberService,
|
||||
_organizationInstallationRepository);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Divergic.Logging.Xunit" Version="4.3.0" />
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.2" />
|
||||
<PackageReference Include="FluentAssertions" Version="7.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(MicrosoftNetTestSdkVersion)" />
|
||||
<PackageReference Include="RichardSzalay.MockHttp" Version="7.0.0" />
|
||||
<PackageReference Include="xunit" Version="$(XUnitVersion)" />
|
||||
|
@ -202,6 +202,14 @@ public class RemoveOrganizationUserCommandTests
|
||||
.HasConfirmedOwnersExceptAsync(
|
||||
organizationUser.OrganizationId,
|
||||
Arg.Is<IEnumerable<Guid>>(i => i.Contains(organizationUser.Id)), true);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.DeleteAsync(default);
|
||||
|
||||
await sutProvider.GetDependency<IEventService>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.LogOrganizationUserEventAsync((OrganizationUser)default, default);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
@ -346,9 +354,7 @@ public class RemoveOrganizationUserCommandTests
|
||||
[OrganizationUser(type: OrganizationUserType.User)] OrganizationUser organizationUser,
|
||||
SutProvider<RemoveOrganizationUserCommand> sutProvider)
|
||||
{
|
||||
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
|
||||
|
||||
organizationUserRepository
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetByOrganizationAsync(organizationUser.OrganizationId, organizationUser.UserId!.Value)
|
||||
.Returns(organizationUser);
|
||||
|
||||
@ -361,7 +367,13 @@ public class RemoveOrganizationUserCommandTests
|
||||
|
||||
await sutProvider.Sut.RemoveUserAsync(organizationUser.OrganizationId, organizationUser.UserId.Value);
|
||||
|
||||
await sutProvider.GetDependency<IEventService>().Received(1).LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Removed);
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.Received(1)
|
||||
.DeleteAsync(organizationUser);
|
||||
|
||||
await sutProvider.GetDependency<IEventService>()
|
||||
.Received(1)
|
||||
.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Removed);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
@ -370,6 +382,14 @@ public class RemoveOrganizationUserCommandTests
|
||||
{
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.RemoveUserAsync(organizationId, userId));
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.DeleteAsync(default);
|
||||
|
||||
await sutProvider.GetDependency<IEventService>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.LogOrganizationUserEventAsync((OrganizationUser)default, default);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
@ -413,6 +433,14 @@ public class RemoveOrganizationUserCommandTests
|
||||
organizationUser.OrganizationId,
|
||||
Arg.Is<IEnumerable<Guid>>(i => i.Contains(organizationUser.Id)),
|
||||
Arg.Any<bool>());
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.DeleteAsync(default);
|
||||
|
||||
await sutProvider.GetDependency<IEventService>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.LogOrganizationUserEventAsync((OrganizationUser)default, default);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
@ -424,6 +452,7 @@ public class RemoveOrganizationUserCommandTests
|
||||
var sutProvider = SutProviderFactory();
|
||||
var eventDate = sutProvider.GetDependency<FakeTimeProvider>().GetUtcNow().UtcDateTime;
|
||||
orgUser1.OrganizationId = orgUser2.OrganizationId = deletingUser.OrganizationId;
|
||||
|
||||
var organizationUsers = new[] { orgUser1, orgUser2 };
|
||||
var organizationUserIds = organizationUsers.Select(u => u.Id);
|
||||
|
||||
@ -774,6 +803,105 @@ public class RemoveOrganizationUserCommandTests
|
||||
Assert.Contains(RemoveOrganizationUserCommand.RemoveLastConfirmedOwnerErrorMessage, exception.Message);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UserLeave_Success(
|
||||
[OrganizationUser(type: OrganizationUserType.User)] OrganizationUser organizationUser,
|
||||
SutProvider<RemoveOrganizationUserCommand> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetByOrganizationAsync(organizationUser.OrganizationId, organizationUser.UserId!.Value)
|
||||
.Returns(organizationUser);
|
||||
|
||||
sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
|
||||
.HasConfirmedOwnersExceptAsync(
|
||||
organizationUser.OrganizationId,
|
||||
Arg.Is<IEnumerable<Guid>>(i => i.Contains(organizationUser.Id)),
|
||||
Arg.Any<bool>())
|
||||
.Returns(true);
|
||||
|
||||
await sutProvider.Sut.UserLeaveAsync(organizationUser.OrganizationId, organizationUser.UserId.Value);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.Received(1)
|
||||
.DeleteAsync(organizationUser);
|
||||
|
||||
await sutProvider.GetDependency<IEventService>()
|
||||
.Received(1)
|
||||
.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Left);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UserLeave_NotFound_ThrowsException(SutProvider<RemoveOrganizationUserCommand> sutProvider,
|
||||
Guid organizationId, Guid userId)
|
||||
{
|
||||
await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.UserLeaveAsync(organizationId, userId));
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.DeleteAsync(default);
|
||||
|
||||
await sutProvider.GetDependency<IEventService>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.LogOrganizationUserEventAsync((OrganizationUser)default, default);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UserLeave_InvalidUser_ThrowsException(OrganizationUser organizationUser,
|
||||
SutProvider<RemoveOrganizationUserCommand> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetByOrganizationAsync(organizationUser.OrganizationId, organizationUser.UserId!.Value)
|
||||
.Returns(organizationUser);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => sutProvider.Sut.UserLeaveAsync(Guid.NewGuid(), organizationUser.UserId.Value));
|
||||
|
||||
Assert.Contains(RemoveOrganizationUserCommand.UserNotFoundErrorMessage, exception.Message);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.DeleteAsync(default);
|
||||
|
||||
await sutProvider.GetDependency<IEventService>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.LogOrganizationUserEventAsync((OrganizationUser)default, default);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UserLeave_RemovingLastOwner_ThrowsException(
|
||||
[OrganizationUser(type: OrganizationUserType.Owner)] OrganizationUser organizationUser,
|
||||
SutProvider<RemoveOrganizationUserCommand> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetByOrganizationAsync(organizationUser.OrganizationId, organizationUser.UserId!.Value)
|
||||
.Returns(organizationUser);
|
||||
sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
|
||||
.HasConfirmedOwnersExceptAsync(
|
||||
organizationUser.OrganizationId,
|
||||
Arg.Is<IEnumerable<Guid>>(i => i.Contains(organizationUser.Id)),
|
||||
Arg.Any<bool>())
|
||||
.Returns(false);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.UserLeaveAsync(organizationUser.OrganizationId, organizationUser.UserId.Value));
|
||||
|
||||
Assert.Contains(RemoveOrganizationUserCommand.RemoveLastConfirmedOwnerErrorMessage, exception.Message);
|
||||
_ = sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
|
||||
.Received(1)
|
||||
.HasConfirmedOwnersExceptAsync(
|
||||
organizationUser.OrganizationId,
|
||||
Arg.Is<IEnumerable<Guid>>(i => i.Contains(organizationUser.Id)),
|
||||
Arg.Any<bool>());
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.DeleteAsync(default);
|
||||
|
||||
await sutProvider.GetDependency<IEventService>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.LogOrganizationUserEventAsync((OrganizationUser)default, default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new SutProvider with a FakeTimeProvider registered in the Sut.
|
||||
/// </summary>
|
||||
|
@ -1,4 +1,6 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Entities.Provider;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
@ -11,6 +13,7 @@ using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using NSubstitute.ReturnsExtensions;
|
||||
using Stripe;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.OrganizationFeatures.OrganizationLicenses;
|
||||
@ -62,4 +65,34 @@ public class CloudGetOrganizationLicenseQueryTests
|
||||
Assert.Equal(installationId, result.InstallationId);
|
||||
Assert.Equal(licenseSignature, result.SignatureBytes);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task GetLicenseAsync_MSPManagedOrganization_UsesProviderSubscription(SutProvider<CloudGetOrganizationLicenseQuery> sutProvider,
|
||||
Organization organization, Guid installationId, Installation installation, SubscriptionInfo subInfo,
|
||||
byte[] licenseSignature, Provider provider)
|
||||
{
|
||||
organization.Status = OrganizationStatusType.Managed;
|
||||
organization.ExpirationDate = null;
|
||||
|
||||
subInfo.Subscription = new SubscriptionInfo.BillingSubscription(new Subscription
|
||||
{
|
||||
CurrentPeriodStart = DateTime.UtcNow,
|
||||
CurrentPeriodEnd = DateTime.UtcNow.AddMonths(1)
|
||||
});
|
||||
|
||||
installation.Enabled = true;
|
||||
sutProvider.GetDependency<IInstallationRepository>().GetByIdAsync(installationId).Returns(installation);
|
||||
sutProvider.GetDependency<IProviderRepository>().GetByOrganizationIdAsync(organization.Id).Returns(provider);
|
||||
sutProvider.GetDependency<IPaymentService>().GetSubscriptionAsync(provider).Returns(subInfo);
|
||||
sutProvider.GetDependency<ILicensingService>().SignLicense(Arg.Any<ILicense>()).Returns(licenseSignature);
|
||||
|
||||
var result = await sutProvider.Sut.GetLicenseAsync(organization, installationId);
|
||||
|
||||
Assert.Equal(LicenseType.Organization, result.LicenseType);
|
||||
Assert.Equal(organization.Id, result.Id);
|
||||
Assert.Equal(installationId, result.InstallationId);
|
||||
Assert.Equal(licenseSignature, result.SignatureBytes);
|
||||
Assert.Equal(DateTime.UtcNow.AddYears(1).Date, result.Expires!.Value.Date);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,158 @@
|
||||
-- OrganizationInstallation
|
||||
|
||||
-- Table
|
||||
IF OBJECT_ID('[dbo].[OrganizationInstallation]') IS NULL
|
||||
BEGIN
|
||||
CREATE TABLE [dbo].[OrganizationInstallation] (
|
||||
[Id] UNIQUEIDENTIFIER NOT NULL,
|
||||
[OrganizationId] UNIQUEIDENTIFIER NOT NULL,
|
||||
[InstallationId] UNIQUEIDENTIFIER NOT NULL,
|
||||
[CreationDate] DATETIME2 (7) NOT NULL,
|
||||
[RevisionDate] DATETIME2 (7) NULL,
|
||||
CONSTRAINT [PK_OrganizationInstallation] PRIMARY KEY CLUSTERED ([Id] ASC),
|
||||
CONSTRAINT [FK_OrganizationInstallation_Organization] FOREIGN KEY ([OrganizationId]) REFERENCES [dbo].[Organization] ([Id]) ON DELETE CASCADE,
|
||||
CONSTRAINT [FK_OrganizationInstallation_Installation] FOREIGN KEY ([InstallationId]) REFERENCES [dbo].[Installation] ([Id]) ON DELETE CASCADE
|
||||
);
|
||||
END
|
||||
GO
|
||||
|
||||
-- Indexes
|
||||
IF NOT EXISTS(SELECT name FROM sys.indexes WHERE name = 'IX_OrganizationInstallation_OrganizationId')
|
||||
BEGIN
|
||||
CREATE NONCLUSTERED INDEX [IX_OrganizationInstallation_OrganizationId]
|
||||
ON [dbo].[OrganizationInstallation]([OrganizationId] ASC);
|
||||
END
|
||||
|
||||
IF NOT EXISTS(SELECT name FROM sys.indexes WHERE name = 'IX_OrganizationInstallation_InstallationId')
|
||||
BEGIN
|
||||
CREATE NONCLUSTERED INDEX [IX_OrganizationInstallation_InstallationId]
|
||||
ON [dbo].[OrganizationInstallation]([InstallationId] ASC);
|
||||
END
|
||||
|
||||
-- View
|
||||
IF EXISTS(SELECT * FROM sys.views WHERE [Name] = 'OrganizationInstallationView')
|
||||
BEGIN
|
||||
DROP VIEW [dbo].[OrganizationInstallationView];
|
||||
END
|
||||
GO
|
||||
|
||||
CREATE VIEW [dbo].[OrganizationInstallationView]
|
||||
AS
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
[dbo].[OrganizationInstallation]
|
||||
GO
|
||||
|
||||
-- Stored Procedures: Create
|
||||
CREATE OR ALTER PROCEDURE [dbo].[OrganizationInstallation_Create]
|
||||
@Id UNIQUEIDENTIFIER OUTPUT,
|
||||
@OrganizationId UNIQUEIDENTIFIER,
|
||||
@InstallationId UNIQUEIDENTIFIER,
|
||||
@CreationDate DATETIME2 (7),
|
||||
@RevisionDate DATETIME2 (7) = NULL
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
INSERT INTO [dbo].[OrganizationInstallation]
|
||||
(
|
||||
[Id],
|
||||
[OrganizationId],
|
||||
[InstallationId],
|
||||
[CreationDate],
|
||||
[RevisionDate]
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
@Id,
|
||||
@OrganizationId,
|
||||
@InstallationId,
|
||||
@CreationDate,
|
||||
@RevisionDate
|
||||
)
|
||||
END
|
||||
GO
|
||||
|
||||
-- Stored Procedures: DeleteById
|
||||
CREATE OR ALTER PROCEDURE [dbo].[OrganizationInstallation_DeleteById]
|
||||
@Id UNIQUEIDENTIFIER
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
DELETE
|
||||
FROM
|
||||
[dbo].[OrganizationInstallation]
|
||||
WHERE
|
||||
[Id] = @Id
|
||||
END
|
||||
GO
|
||||
|
||||
-- Stored Procedures: ReadById
|
||||
CREATE PROCEDURE [dbo].[OrganizationInstallation_ReadById]
|
||||
@Id UNIQUEIDENTIFIER
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
[dbo].[OrganizationInstallationView]
|
||||
WHERE
|
||||
[Id] = @Id
|
||||
END
|
||||
GO
|
||||
|
||||
-- Stored Procedures: ReadByInstallationId
|
||||
CREATE PROCEDURE [dbo].[OrganizationInstallation_ReadByInstallationId]
|
||||
@InstallationId UNIQUEIDENTIFIER
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
[dbo].[OrganizationInstallationView]
|
||||
WHERE
|
||||
[InstallationId] = @InstallationId
|
||||
END
|
||||
GO
|
||||
|
||||
-- Stored Procedures: ReadByOrganizationId
|
||||
CREATE PROCEDURE [dbo].[OrganizationInstallation_ReadByOrganizationId]
|
||||
@OrganizationId UNIQUEIDENTIFIER
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
[dbo].[OrganizationInstallationView]
|
||||
WHERE
|
||||
[OrganizationId] = @OrganizationId
|
||||
END
|
||||
GO
|
||||
|
||||
-- Stored Procedures: Update
|
||||
CREATE PROCEDURE [dbo].[OrganizationInstallation_Update]
|
||||
@Id UNIQUEIDENTIFIER OUTPUT,
|
||||
@OrganizationId UNIQUEIDENTIFIER,
|
||||
@InstallationId UNIQUEIDENTIFIER,
|
||||
@CreationDate DATETIME2 (7),
|
||||
@RevisionDate DATETIME2 (7)
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
UPDATE
|
||||
[dbo].[OrganizationInstallation]
|
||||
SET
|
||||
[RevisionDate] = @RevisionDate
|
||||
WHERE
|
||||
[Id] = @Id
|
||||
END
|
||||
GO
|
@ -9,7 +9,7 @@ internal class Program
|
||||
}
|
||||
|
||||
[DefaultCommand]
|
||||
public void Execute(
|
||||
public int Execute(
|
||||
[Operand(Description = "Database connection string")]
|
||||
string databaseConnectionString,
|
||||
[Option('r', "repeatable", Description = "Mark scripts as repeatable")]
|
||||
@ -20,7 +20,11 @@ internal class Program
|
||||
bool dryRun = false,
|
||||
[Option("no-transaction", Description = "Run without adding transaction per script or all scripts")]
|
||||
bool noTransactionMigration = false
|
||||
) => MigrateDatabase(databaseConnectionString, repeatable, folderName, dryRun, noTransactionMigration);
|
||||
)
|
||||
{
|
||||
return MigrateDatabase(databaseConnectionString, repeatable, folderName, dryRun, noTransactionMigration) ? 0 : -1;
|
||||
}
|
||||
|
||||
|
||||
private static bool MigrateDatabase(string databaseConnectionString,
|
||||
bool repeatable = false, string folderName = "", bool dryRun = false, bool noTransactionMigration = false)
|
||||
|
2988
util/MySqlMigrations/Migrations/20241126185456_AddTable_OrganizationInstallation.Designer.cs
generated
Normal file
2988
util/MySqlMigrations/Migrations/20241126185456_AddTable_OrganizationInstallation.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,58 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Bit.MySqlMigrations.Migrations;
|
||||
|
||||
/// <inheritdoc />
|
||||
public partial class AddTable_OrganizationInstallation : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "OrganizationInstallation",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
OrganizationId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
InstallationId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
CreationDate = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
RevisionDate = table.Column<DateTime>(type: "datetime(6)", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_OrganizationInstallation", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_OrganizationInstallation_Installation_InstallationId",
|
||||
column: x => x.InstallationId,
|
||||
principalTable: "Installation",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_OrganizationInstallation_Organization_OrganizationId",
|
||||
column: x => x.OrganizationId,
|
||||
principalTable: "Organization",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_OrganizationInstallation_InstallationId",
|
||||
table: "OrganizationInstallation",
|
||||
column: "InstallationId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_OrganizationInstallation_OrganizationId",
|
||||
table: "OrganizationInstallation",
|
||||
column: "OrganizationId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "OrganizationInstallation");
|
||||
}
|
||||
}
|
@ -737,6 +737,35 @@ namespace Bit.MySqlMigrations.Migrations
|
||||
b.ToTable("ClientOrganizationMigrationRecord", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<DateTime>("CreationDate")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<Guid>("InstallationId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<Guid>("OrganizationId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<DateTime?>("RevisionDate")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasAnnotation("SqlServer:Clustered", true);
|
||||
|
||||
b.HasIndex("InstallationId")
|
||||
.HasAnnotation("SqlServer:Clustered", false);
|
||||
|
||||
b.HasIndex("OrganizationId")
|
||||
.HasAnnotation("SqlServer:Clustered", false);
|
||||
|
||||
b.ToTable("OrganizationInstallation", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
@ -2336,6 +2365,25 @@ namespace Bit.MySqlMigrations.Migrations
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b =>
|
||||
{
|
||||
b.HasOne("Bit.Infrastructure.EntityFramework.Models.Installation", "Installation")
|
||||
.WithMany()
|
||||
.HasForeignKey("InstallationId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization")
|
||||
.WithMany()
|
||||
.HasForeignKey("OrganizationId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Installation");
|
||||
|
||||
b.Navigation("Organization");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b =>
|
||||
{
|
||||
b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider")
|
||||
|
2994
util/PostgresMigrations/Migrations/20241126185450_AddTable_OrganizationInstallation.Designer.cs
generated
Normal file
2994
util/PostgresMigrations/Migrations/20241126185450_AddTable_OrganizationInstallation.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,57 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Bit.PostgresMigrations.Migrations;
|
||||
|
||||
/// <inheritdoc />
|
||||
public partial class AddTable_OrganizationInstallation : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "OrganizationInstallation",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
OrganizationId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
InstallationId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
CreationDate = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
RevisionDate = table.Column<DateTime>(type: "timestamp with time zone", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_OrganizationInstallation", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_OrganizationInstallation_Installation_InstallationId",
|
||||
column: x => x.InstallationId,
|
||||
principalTable: "Installation",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_OrganizationInstallation_Organization_OrganizationId",
|
||||
column: x => x.OrganizationId,
|
||||
principalTable: "Organization",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_OrganizationInstallation_InstallationId",
|
||||
table: "OrganizationInstallation",
|
||||
column: "InstallationId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_OrganizationInstallation_OrganizationId",
|
||||
table: "OrganizationInstallation",
|
||||
column: "OrganizationId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "OrganizationInstallation");
|
||||
}
|
||||
}
|
@ -742,6 +742,35 @@ namespace Bit.PostgresMigrations.Migrations
|
||||
b.ToTable("ClientOrganizationMigrationRecord", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTime>("CreationDate")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<Guid>("InstallationId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("OrganizationId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTime?>("RevisionDate")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasAnnotation("SqlServer:Clustered", true);
|
||||
|
||||
b.HasIndex("InstallationId")
|
||||
.HasAnnotation("SqlServer:Clustered", false);
|
||||
|
||||
b.HasIndex("OrganizationId")
|
||||
.HasAnnotation("SqlServer:Clustered", false);
|
||||
|
||||
b.ToTable("OrganizationInstallation", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
@ -2342,6 +2371,25 @@ namespace Bit.PostgresMigrations.Migrations
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b =>
|
||||
{
|
||||
b.HasOne("Bit.Infrastructure.EntityFramework.Models.Installation", "Installation")
|
||||
.WithMany()
|
||||
.HasForeignKey("InstallationId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization")
|
||||
.WithMany()
|
||||
.HasForeignKey("OrganizationId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Installation");
|
||||
|
||||
b.Navigation("Organization");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b =>
|
||||
{
|
||||
b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider")
|
||||
|
2977
util/SqliteMigrations/Migrations/20241126185500_AddTable_OrganizationInstallation.Designer.cs
generated
Normal file
2977
util/SqliteMigrations/Migrations/20241126185500_AddTable_OrganizationInstallation.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,57 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Bit.SqliteMigrations.Migrations;
|
||||
|
||||
/// <inheritdoc />
|
||||
public partial class AddTable_OrganizationInstallation : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "OrganizationInstallation",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
OrganizationId = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
InstallationId = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
CreationDate = table.Column<DateTime>(type: "TEXT", nullable: false),
|
||||
RevisionDate = table.Column<DateTime>(type: "TEXT", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_OrganizationInstallation", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_OrganizationInstallation_Installation_InstallationId",
|
||||
column: x => x.InstallationId,
|
||||
principalTable: "Installation",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_OrganizationInstallation_Organization_OrganizationId",
|
||||
column: x => x.OrganizationId,
|
||||
principalTable: "Organization",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_OrganizationInstallation_InstallationId",
|
||||
table: "OrganizationInstallation",
|
||||
column: "InstallationId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_OrganizationInstallation_OrganizationId",
|
||||
table: "OrganizationInstallation",
|
||||
column: "OrganizationId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "OrganizationInstallation");
|
||||
}
|
||||
}
|
@ -726,6 +726,35 @@ namespace Bit.SqliteMigrations.Migrations
|
||||
b.ToTable("ClientOrganizationMigrationRecord", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreationDate")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("InstallationId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("OrganizationId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime?>("RevisionDate")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasAnnotation("SqlServer:Clustered", true);
|
||||
|
||||
b.HasIndex("InstallationId")
|
||||
.HasAnnotation("SqlServer:Clustered", false);
|
||||
|
||||
b.HasIndex("OrganizationId")
|
||||
.HasAnnotation("SqlServer:Clustered", false);
|
||||
|
||||
b.ToTable("OrganizationInstallation", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
@ -2325,6 +2354,25 @@ namespace Bit.SqliteMigrations.Migrations
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b =>
|
||||
{
|
||||
b.HasOne("Bit.Infrastructure.EntityFramework.Models.Installation", "Installation")
|
||||
.WithMany()
|
||||
.HasForeignKey("InstallationId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization")
|
||||
.WithMany()
|
||||
.HasForeignKey("OrganizationId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Installation");
|
||||
|
||||
b.Navigation("Organization");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b =>
|
||||
{
|
||||
b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider")
|
||||
|
Loading…
Reference in New Issue
Block a user