diff --git a/src/Admin/AdminConsole/Models/OrganizationEditModel.cs b/src/Admin/AdminConsole/Models/OrganizationEditModel.cs
index 1d23afd491..6af6c1b50a 100644
--- a/src/Admin/AdminConsole/Models/OrganizationEditModel.cs
+++ b/src/Admin/AdminConsole/Models/OrganizationEditModel.cs
@@ -86,6 +86,7 @@ public class OrganizationEditModel : OrganizationViewModel
UseApi = org.UseApi;
UseSecretsManager = org.UseSecretsManager;
UseRiskInsights = org.UseRiskInsights;
+ UseAdminSponsoredFamilies = org.UseAdminSponsoredFamilies;
UseResetPassword = org.UseResetPassword;
SelfHost = org.SelfHost;
UsersGetPremium = org.UsersGetPremium;
@@ -154,6 +155,8 @@ public class OrganizationEditModel : OrganizationViewModel
public new bool UseSecretsManager { get; set; }
[Display(Name = "Risk Insights")]
public new bool UseRiskInsights { get; set; }
+ [Display(Name = "Admin Sponsored Families")]
+ public bool UseAdminSponsoredFamilies { get; set; }
[Display(Name = "Self Host")]
public bool SelfHost { get; set; }
[Display(Name = "Users Get Premium")]
@@ -295,6 +298,7 @@ public class OrganizationEditModel : OrganizationViewModel
existingOrganization.UseApi = UseApi;
existingOrganization.UseSecretsManager = UseSecretsManager;
existingOrganization.UseRiskInsights = UseRiskInsights;
+ existingOrganization.UseAdminSponsoredFamilies = UseAdminSponsoredFamilies;
existingOrganization.UseResetPassword = UseResetPassword;
existingOrganization.SelfHost = SelfHost;
existingOrganization.UsersGetPremium = UsersGetPremium;
diff --git a/src/Api/AdminConsole/Models/Response/Organizations/OrganizationResponseModel.cs b/src/Api/AdminConsole/Models/Response/Organizations/OrganizationResponseModel.cs
index 4dc4a4ec55..a14e3efb51 100644
--- a/src/Api/AdminConsole/Models/Response/Organizations/OrganizationResponseModel.cs
+++ b/src/Api/AdminConsole/Models/Response/Organizations/OrganizationResponseModel.cs
@@ -64,6 +64,7 @@ public class OrganizationResponseModel : ResponseModel
LimitItemDeletion = organization.LimitItemDeletion;
AllowAdminAccessToAllCollectionItems = organization.AllowAdminAccessToAllCollectionItems;
UseRiskInsights = organization.UseRiskInsights;
+ UseAdminSponsoredFamilies = organization.UseAdminSponsoredFamilies;
}
public Guid Id { get; set; }
@@ -110,6 +111,7 @@ public class OrganizationResponseModel : ResponseModel
public bool LimitItemDeletion { get; set; }
public bool AllowAdminAccessToAllCollectionItems { get; set; }
public bool UseRiskInsights { get; set; }
+ public bool UseAdminSponsoredFamilies { get; set; }
}
public class OrganizationSubscriptionResponseModel : OrganizationResponseModel
diff --git a/src/Api/AdminConsole/Models/Response/ProfileOrganizationResponseModel.cs b/src/Api/AdminConsole/Models/Response/ProfileOrganizationResponseModel.cs
index 437c30b8b9..c74599a70e 100644
--- a/src/Api/AdminConsole/Models/Response/ProfileOrganizationResponseModel.cs
+++ b/src/Api/AdminConsole/Models/Response/ProfileOrganizationResponseModel.cs
@@ -72,6 +72,7 @@ public class ProfileOrganizationResponseModel : ResponseModel
AllowAdminAccessToAllCollectionItems = organization.AllowAdminAccessToAllCollectionItems;
UserIsClaimedByOrganization = organizationIdsClaimingUser.Contains(organization.OrganizationId);
UseRiskInsights = organization.UseRiskInsights;
+ UseAdminSponsoredFamilies = organization.UseAdminSponsoredFamilies;
if (organization.SsoConfig != null)
{
@@ -155,4 +156,5 @@ public class ProfileOrganizationResponseModel : ResponseModel
///
public bool UserIsClaimedByOrganization { get; set; }
public bool UseRiskInsights { get; set; }
+ public bool UseAdminSponsoredFamilies { get; set; }
}
diff --git a/src/Api/AdminConsole/Models/Response/ProfileProviderOrganizationResponseModel.cs b/src/Api/AdminConsole/Models/Response/ProfileProviderOrganizationResponseModel.cs
index d31cb5a77a..5d5e1f9b85 100644
--- a/src/Api/AdminConsole/Models/Response/ProfileProviderOrganizationResponseModel.cs
+++ b/src/Api/AdminConsole/Models/Response/ProfileProviderOrganizationResponseModel.cs
@@ -50,5 +50,6 @@ public class ProfileProviderOrganizationResponseModel : ProfileOrganizationRespo
LimitItemDeletion = organization.LimitItemDeletion;
AllowAdminAccessToAllCollectionItems = organization.AllowAdminAccessToAllCollectionItems;
UseRiskInsights = organization.UseRiskInsights;
+ UseAdminSponsoredFamilies = organization.UseAdminSponsoredFamilies;
}
}
diff --git a/src/Api/Billing/Controllers/OrganizationSponsorshipsController.cs b/src/Api/Billing/Controllers/OrganizationSponsorshipsController.cs
index a8c9fa622d..04667e61ad 100644
--- a/src/Api/Billing/Controllers/OrganizationSponsorshipsController.cs
+++ b/src/Api/Billing/Controllers/OrganizationSponsorshipsController.cs
@@ -84,10 +84,27 @@ public class OrganizationSponsorshipsController : Controller
throw new BadRequestException("Free Bitwarden Families sponsorship has been disabled by your organization administrator.");
}
+ if (!_featureService.IsEnabled(Bit.Core.FeatureFlagKeys.PM17772_AdminInitiatedSponsorships))
+ {
+ if (model.SponsoringUserId.HasValue)
+ {
+ throw new NotFoundException();
+ }
+
+ if (!string.IsNullOrWhiteSpace(model.Notes))
+ {
+ model.Notes = null;
+ }
+ }
+
+ var targetUser = model.SponsoringUserId ?? _currentContext.UserId!.Value;
var sponsorship = await _createSponsorshipCommand.CreateSponsorshipAsync(
sponsoringOrg,
- await _organizationUserRepository.GetByOrganizationAsync(sponsoringOrgId, _currentContext.UserId ?? default),
- model.PlanSponsorshipType, model.SponsoredEmail, model.FriendlyName);
+ await _organizationUserRepository.GetByOrganizationAsync(sponsoringOrgId, targetUser),
+ model.PlanSponsorshipType,
+ model.SponsoredEmail,
+ model.FriendlyName,
+ model.Notes);
await _sendSponsorshipOfferCommand.SendSponsorshipOfferAsync(sponsorship, sponsoringOrg.Name);
}
diff --git a/src/Api/Controllers/SelfHosted/SelfHostedOrganizationSponsorshipsController.cs b/src/Api/Controllers/SelfHosted/SelfHostedOrganizationSponsorshipsController.cs
index ffb5c7bb98..d2c87c6b6f 100644
--- a/src/Api/Controllers/SelfHosted/SelfHostedOrganizationSponsorshipsController.cs
+++ b/src/Api/Controllers/SelfHosted/SelfHostedOrganizationSponsorshipsController.cs
@@ -3,6 +3,7 @@ using Bit.Core.Context;
using Bit.Core.Exceptions;
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces;
using Bit.Core.Repositories;
+using Bit.Core.Services;
using Bit.Core.Utilities;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
@@ -20,6 +21,7 @@ public class SelfHostedOrganizationSponsorshipsController : Controller
private readonly ICreateSponsorshipCommand _offerSponsorshipCommand;
private readonly IRevokeSponsorshipCommand _revokeSponsorshipCommand;
private readonly ICurrentContext _currentContext;
+ private readonly IFeatureService _featureService;
public SelfHostedOrganizationSponsorshipsController(
ICreateSponsorshipCommand offerSponsorshipCommand,
@@ -27,7 +29,8 @@ public class SelfHostedOrganizationSponsorshipsController : Controller
IOrganizationRepository organizationRepository,
IOrganizationSponsorshipRepository organizationSponsorshipRepository,
IOrganizationUserRepository organizationUserRepository,
- ICurrentContext currentContext
+ ICurrentContext currentContext,
+ IFeatureService featureService
)
{
_offerSponsorshipCommand = offerSponsorshipCommand;
@@ -36,15 +39,29 @@ public class SelfHostedOrganizationSponsorshipsController : Controller
_organizationSponsorshipRepository = organizationSponsorshipRepository;
_organizationUserRepository = organizationUserRepository;
_currentContext = currentContext;
+ _featureService = featureService;
}
[HttpPost("{sponsoringOrgId}/families-for-enterprise")]
public async Task CreateSponsorship(Guid sponsoringOrgId, [FromBody] OrganizationSponsorshipCreateRequestModel model)
{
+ if (!_featureService.IsEnabled(Bit.Core.FeatureFlagKeys.PM17772_AdminInitiatedSponsorships))
+ {
+ if (model.SponsoringUserId.HasValue)
+ {
+ throw new NotFoundException();
+ }
+
+ if (!string.IsNullOrWhiteSpace(model.Notes))
+ {
+ model.Notes = null;
+ }
+ }
+
await _offerSponsorshipCommand.CreateSponsorshipAsync(
await _organizationRepository.GetByIdAsync(sponsoringOrgId),
- await _organizationUserRepository.GetByOrganizationAsync(sponsoringOrgId, _currentContext.UserId ?? default),
- model.PlanSponsorshipType, model.SponsoredEmail, model.FriendlyName);
+ await _organizationUserRepository.GetByOrganizationAsync(sponsoringOrgId, model.SponsoringUserId ?? _currentContext.UserId ?? default),
+ model.PlanSponsorshipType, model.SponsoredEmail, model.FriendlyName, model.Notes);
}
[HttpDelete("{sponsoringOrgId}")]
diff --git a/src/Api/Models/Request/Organizations/OrganizationSponsorshipCreateRequestModel.cs b/src/Api/Models/Request/Organizations/OrganizationSponsorshipCreateRequestModel.cs
index ba88f1b90e..d3f03a7ddc 100644
--- a/src/Api/Models/Request/Organizations/OrganizationSponsorshipCreateRequestModel.cs
+++ b/src/Api/Models/Request/Organizations/OrganizationSponsorshipCreateRequestModel.cs
@@ -16,4 +16,14 @@ public class OrganizationSponsorshipCreateRequestModel
[StringLength(256)]
public string FriendlyName { get; set; }
+
+ ///
+ /// (optional) The user to target for the sponsorship.
+ ///
+ /// Left empty when creating a sponsorship for the authenticated user.
+ public Guid? SponsoringUserId { get; set; }
+
+ [EncryptedString]
+ [EncryptedStringLength(512)]
+ public string Notes { get; set; }
}
diff --git a/src/Core/AdminConsole/Entities/Organization.cs b/src/Core/AdminConsole/Entities/Organization.cs
index e91f1ede29..17d9847574 100644
--- a/src/Core/AdminConsole/Entities/Organization.cs
+++ b/src/Core/AdminConsole/Entities/Organization.cs
@@ -114,6 +114,11 @@ public class Organization : ITableObject, IStorableSubscriber, IRevisable,
///
public bool UseRiskInsights { get; set; }
+ ///
+ /// If set to true, admins can initiate organization-issued sponsorships.
+ ///
+ public bool UseAdminSponsoredFamilies { get; set; }
+
public void SetNewId()
{
if (Id == default(Guid))
diff --git a/src/Core/AdminConsole/Models/Data/Organizations/OrganizationAbility.cs b/src/Core/AdminConsole/Models/Data/Organizations/OrganizationAbility.cs
index 62914f6fa8..d27bf40994 100644
--- a/src/Core/AdminConsole/Models/Data/Organizations/OrganizationAbility.cs
+++ b/src/Core/AdminConsole/Models/Data/Organizations/OrganizationAbility.cs
@@ -26,6 +26,7 @@ public class OrganizationAbility
LimitItemDeletion = organization.LimitItemDeletion;
AllowAdminAccessToAllCollectionItems = organization.AllowAdminAccessToAllCollectionItems;
UseRiskInsights = organization.UseRiskInsights;
+ UseAdminSponsoredFamilies = organization.UseAdminSponsoredFamilies;
}
public Guid Id { get; set; }
@@ -45,4 +46,5 @@ public class OrganizationAbility
public bool LimitItemDeletion { get; set; }
public bool AllowAdminAccessToAllCollectionItems { get; set; }
public bool UseRiskInsights { get; set; }
+ public bool UseAdminSponsoredFamilies { get; set; }
}
diff --git a/src/Core/AdminConsole/Models/Data/Organizations/OrganizationUsers/OrganizationUserOrganizationDetails.cs b/src/Core/AdminConsole/Models/Data/Organizations/OrganizationUsers/OrganizationUserOrganizationDetails.cs
index 18d68af220..0771457d0a 100644
--- a/src/Core/AdminConsole/Models/Data/Organizations/OrganizationUsers/OrganizationUserOrganizationDetails.cs
+++ b/src/Core/AdminConsole/Models/Data/Organizations/OrganizationUsers/OrganizationUserOrganizationDetails.cs
@@ -59,4 +59,5 @@ public class OrganizationUserOrganizationDetails
public bool LimitItemDeletion { get; set; }
public bool AllowAdminAccessToAllCollectionItems { get; set; }
public bool UseRiskInsights { get; set; }
+ public bool UseAdminSponsoredFamilies { get; set; }
}
diff --git a/src/Core/AdminConsole/Models/Data/Provider/ProviderUserOrganizationDetails.cs b/src/Core/AdminConsole/Models/Data/Provider/ProviderUserOrganizationDetails.cs
index 57f176666a..8717a8f008 100644
--- a/src/Core/AdminConsole/Models/Data/Provider/ProviderUserOrganizationDetails.cs
+++ b/src/Core/AdminConsole/Models/Data/Provider/ProviderUserOrganizationDetails.cs
@@ -45,5 +45,6 @@ public class ProviderUserOrganizationDetails
public bool LimitItemDeletion { get; set; }
public bool AllowAdminAccessToAllCollectionItems { get; set; }
public bool UseRiskInsights { get; set; }
+ public bool UseAdminSponsoredFamilies { get; set; }
public ProviderType ProviderType { get; set; }
}
diff --git a/src/Core/Constants.cs b/src/Core/Constants.cs
index e87ec916ba..c57544283b 100644
--- a/src/Core/Constants.cs
+++ b/src/Core/Constants.cs
@@ -141,6 +141,7 @@ 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 PM17772_AdminInitiatedSponsorships = "pm-17772-admin-initiated-sponsorships";
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";
diff --git a/src/Core/Entities/OrganizationSponsorship.cs b/src/Core/Entities/OrganizationSponsorship.cs
index 77c77eab21..0bb21d780b 100644
--- a/src/Core/Entities/OrganizationSponsorship.cs
+++ b/src/Core/Entities/OrganizationSponsorship.cs
@@ -20,6 +20,8 @@ public class OrganizationSponsorship : ITableObject
public DateTime? LastSyncDate { get; set; }
public DateTime? ValidUntil { get; set; }
public bool ToDelete { get; set; }
+ public bool IsAdminInitiated { get; set; }
+ public string? Notes { get; set; }
public void SetNewId()
{
diff --git a/src/Core/Models/Data/Organizations/OrganizationSponsorships/OrganizationSponsorshipData.cs b/src/Core/Models/Data/Organizations/OrganizationSponsorships/OrganizationSponsorshipData.cs
index 927262957a..649459bc6b 100644
--- a/src/Core/Models/Data/Organizations/OrganizationSponsorships/OrganizationSponsorshipData.cs
+++ b/src/Core/Models/Data/Organizations/OrganizationSponsorships/OrganizationSponsorshipData.cs
@@ -16,6 +16,8 @@ public class OrganizationSponsorshipData
LastSyncDate = sponsorship.LastSyncDate;
ValidUntil = sponsorship.ValidUntil;
ToDelete = sponsorship.ToDelete;
+ IsAdminInitiated = sponsorship.IsAdminInitiated;
+ Notes = sponsorship.Notes;
}
public Guid SponsoringOrganizationUserId { get; set; }
public Guid? SponsoredOrganizationId { get; set; }
@@ -25,6 +27,8 @@ public class OrganizationSponsorshipData
public DateTime? LastSyncDate { get; set; }
public DateTime? ValidUntil { get; set; }
public bool ToDelete { get; set; }
+ public bool IsAdminInitiated { get; set; }
+ public string Notes { get; set; }
public bool CloudSponsorshipRemoved { get; set; }
}
diff --git a/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/Cloud/ValidateSponsorshipCommand.cs b/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/Cloud/ValidateSponsorshipCommand.cs
index a7423b067e..6b8d6d6771 100644
--- a/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/Cloud/ValidateSponsorshipCommand.cs
+++ b/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/Cloud/ValidateSponsorshipCommand.cs
@@ -112,6 +112,13 @@ public class ValidateSponsorshipCommand : CancelSponsorshipCommand, IValidateSpo
return false;
}
+ if (existingSponsorship.IsAdminInitiated && !sponsoringOrganization.UseAdminSponsoredFamilies)
+ {
+ _logger.LogWarning("Admin initiated sponsorship for sponsored Organization {SponsoredOrganizationId} is not allowed because sponsoring organization does not have UseAdminSponsoredFamilies enabled", sponsoredOrganizationId);
+ await CancelSponsorshipAsync(sponsoredOrganization, existingSponsorship);
+ return false;
+ }
+
var sponsoringOrgProductTier = sponsoringOrganization.PlanType.GetProductTier();
if (sponsoredPlan.SponsoringProductTierType != sponsoringOrgProductTier)
diff --git a/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/CreateSponsorshipCommand.cs b/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/CreateSponsorshipCommand.cs
index ac65d3b897..27589bea3e 100644
--- a/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/CreateSponsorshipCommand.cs
+++ b/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/CreateSponsorshipCommand.cs
@@ -1,5 +1,6 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Billing.Extensions;
+using Bit.Core.Context;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
@@ -10,29 +11,24 @@ using Bit.Core.Utilities;
namespace Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise;
-public class CreateSponsorshipCommand : ICreateSponsorshipCommand
+public class CreateSponsorshipCommand(
+ ICurrentContext currentContext,
+ IOrganizationSponsorshipRepository organizationSponsorshipRepository,
+ IUserService userService) : ICreateSponsorshipCommand
{
- private readonly IOrganizationSponsorshipRepository _organizationSponsorshipRepository;
- private readonly IUserService _userService;
-
- public CreateSponsorshipCommand(IOrganizationSponsorshipRepository organizationSponsorshipRepository,
- IUserService userService)
+ public async Task CreateSponsorshipAsync(Organization sponsoringOrganization,
+ OrganizationUser sponsoringMember, PlanSponsorshipType sponsorshipType, string sponsoredEmail,
+ string friendlyName, string notes)
{
- _organizationSponsorshipRepository = organizationSponsorshipRepository;
- _userService = userService;
- }
+ var sponsoringUser = await userService.GetUserByIdAsync(sponsoringMember.UserId!.Value);
- public async Task CreateSponsorshipAsync(Organization sponsoringOrg, OrganizationUser sponsoringOrgUser,
- PlanSponsorshipType sponsorshipType, string sponsoredEmail, string friendlyName)
- {
- var sponsoringUser = await _userService.GetUserByIdAsync(sponsoringOrgUser.UserId.Value);
- if (sponsoringUser == null || string.Equals(sponsoringUser.Email, sponsoredEmail, System.StringComparison.InvariantCultureIgnoreCase))
+ if (sponsoringUser == null || string.Equals(sponsoringUser.Email, sponsoredEmail, StringComparison.InvariantCultureIgnoreCase))
{
throw new BadRequestException("Cannot offer a Families Organization Sponsorship to yourself. Choose a different email.");
}
var requiredSponsoringProductType = StaticStore.GetSponsoredPlan(sponsorshipType)?.SponsoringProductTierType;
- var sponsoringOrgProductTier = sponsoringOrg.PlanType.GetProductTier();
+ var sponsoringOrgProductTier = sponsoringOrganization.PlanType.GetProductTier();
if (requiredSponsoringProductType == null ||
sponsoringOrgProductTier != requiredSponsoringProductType.Value)
@@ -40,26 +36,24 @@ public class CreateSponsorshipCommand : ICreateSponsorshipCommand
throw new BadRequestException("Specified Organization cannot sponsor other organizations.");
}
- if (sponsoringOrgUser == null || sponsoringOrgUser.Status != OrganizationUserStatusType.Confirmed)
+ if (sponsoringMember.Status != OrganizationUserStatusType.Confirmed)
{
throw new BadRequestException("Only confirmed users can sponsor other organizations.");
}
- var existingOrgSponsorship = await _organizationSponsorshipRepository
- .GetBySponsoringOrganizationUserIdAsync(sponsoringOrgUser.Id);
+ var existingOrgSponsorship = await organizationSponsorshipRepository
+ .GetBySponsoringOrganizationUserIdAsync(sponsoringMember.Id);
if (existingOrgSponsorship?.SponsoredOrganizationId != null)
{
throw new BadRequestException("Can only sponsor one organization per Organization User.");
}
- var sponsorship = new OrganizationSponsorship
- {
- SponsoringOrganizationId = sponsoringOrg.Id,
- SponsoringOrganizationUserId = sponsoringOrgUser.Id,
- FriendlyName = friendlyName,
- OfferedToEmail = sponsoredEmail,
- PlanSponsorshipType = sponsorshipType,
- };
+ var sponsorship = new OrganizationSponsorship();
+ sponsorship.SponsoringOrganizationId = sponsoringOrganization.Id;
+ sponsorship.SponsoringOrganizationUserId = sponsoringMember.Id;
+ sponsorship.FriendlyName = friendlyName;
+ sponsorship.OfferedToEmail = sponsoredEmail;
+ sponsorship.PlanSponsorshipType = sponsorshipType;
if (existingOrgSponsorship != null)
{
@@ -67,16 +61,42 @@ public class CreateSponsorshipCommand : ICreateSponsorshipCommand
sponsorship.Id = existingOrgSponsorship.Id;
}
+ var isAdminInitiated = false;
+ if (currentContext.UserId != sponsoringMember.UserId)
+ {
+ var organization = currentContext.Organizations.First(x => x.Id == sponsoringOrganization.Id);
+ OrganizationUserType[] allowedUserTypes =
+ [
+ OrganizationUserType.Admin,
+ OrganizationUserType.Owner
+ ];
+
+ if (!organization.Permissions.ManageUsers && allowedUserTypes.All(x => x != organization.Type))
+ {
+ throw new UnauthorizedAccessException("You do not have permissions to send sponsorships on behalf of the organization.");
+ }
+
+ if (!sponsoringOrganization.UseAdminSponsoredFamilies)
+ {
+ throw new BadRequestException("Sponsoring organization cannot sponsor other Family organizations.");
+ }
+
+ isAdminInitiated = true;
+ }
+
+ sponsorship.IsAdminInitiated = isAdminInitiated;
+ sponsorship.Notes = notes;
+
try
{
- await _organizationSponsorshipRepository.UpsertAsync(sponsorship);
+ await organizationSponsorshipRepository.UpsertAsync(sponsorship);
return sponsorship;
}
catch
{
- if (sponsorship.Id != default)
+ if (sponsorship.Id != Guid.Empty)
{
- await _organizationSponsorshipRepository.DeleteAsync(sponsorship);
+ await organizationSponsorshipRepository.DeleteAsync(sponsorship);
}
throw;
}
diff --git a/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/Interfaces/ICreateSponsorshipCommand.cs b/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/Interfaces/ICreateSponsorshipCommand.cs
index 8e3d055a79..4a3e5a63dc 100644
--- a/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/Interfaces/ICreateSponsorshipCommand.cs
+++ b/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/Interfaces/ICreateSponsorshipCommand.cs
@@ -7,5 +7,5 @@ namespace Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnte
public interface ICreateSponsorshipCommand
{
Task CreateSponsorshipAsync(Organization sponsoringOrg, OrganizationUser sponsoringOrgUser,
- PlanSponsorshipType sponsorshipType, string sponsoredEmail, string friendlyName);
+ PlanSponsorshipType sponsorshipType, string sponsoredEmail, string friendlyName, string notes);
}
diff --git a/src/Infrastructure.EntityFramework/AdminConsole/Repositories/OrganizationRepository.cs b/src/Infrastructure.EntityFramework/AdminConsole/Repositories/OrganizationRepository.cs
index 26c782000e..91e29c1b52 100644
--- a/src/Infrastructure.EntityFramework/AdminConsole/Repositories/OrganizationRepository.cs
+++ b/src/Infrastructure.EntityFramework/AdminConsole/Repositories/OrganizationRepository.cs
@@ -106,7 +106,8 @@ public class OrganizationRepository : Repository sutProvider)
+ {
+ sponsoringOrg.PlanType = planType;
+ sponsoringOrg.UseAdminSponsoredFamilies = false;
+ existingSponsorship.SponsoringOrganizationId = sponsoringOrg.Id;
+ existingSponsorship.IsAdminInitiated = true;
+
+ sutProvider.GetDependency()
+ .GetBySponsoredOrganizationIdAsync(sponsoredOrg.Id).Returns(existingSponsorship);
+ sutProvider.GetDependency().GetByIdAsync(sponsoredOrg.Id).Returns(sponsoredOrg);
+ sutProvider.GetDependency().GetByIdAsync(sponsoringOrg.Id).Returns(sponsoringOrg);
+
+ var result = await sutProvider.Sut.ValidateSponsorshipAsync(sponsoredOrg.Id);
+
+ Assert.False(result);
+ await AssertRemovedSponsoredPaymentAsync(sponsoredOrg, existingSponsorship, sutProvider);
+ await AssertDeletedSponsorshipAsync(existingSponsorship, sutProvider);
+ }
+
+ [Theory(Skip = "Temporarily disabled")]
+ [BitMemberAutoData(nameof(EnterprisePlanTypes))]
+ public async Task ValidateSponsorshipAsync_AdminInitiatedAndUseAdminSponsoredFamiliesTrue_ContinuesValidation(
+ PlanType planType, Organization sponsoredOrg, OrganizationSponsorship existingSponsorship,
+ Organization sponsoringOrg, SutProvider sutProvider)
+ {
+ sponsoringOrg.PlanType = planType;
+ sponsoringOrg.UseAdminSponsoredFamilies = true;
+ existingSponsorship.SponsoringOrganizationId = sponsoringOrg.Id;
+ existingSponsorship.IsAdminInitiated = true;
+ existingSponsorship.ToDelete = false;
+ existingSponsorship.LastSyncDate = null; // Not a self-hosted sponsorship
+
+ sutProvider.GetDependency()
+ .GetBySponsoredOrganizationIdAsync(sponsoredOrg.Id).Returns(existingSponsorship);
+ sutProvider.GetDependency().GetByIdAsync(sponsoredOrg.Id).Returns(sponsoredOrg);
+ sutProvider.GetDependency().GetByIdAsync(sponsoringOrg.Id).Returns(sponsoringOrg);
+
+ var result = await sutProvider.Sut.ValidateSponsorshipAsync(sponsoredOrg.Id);
+
+ Assert.True(result);
+ await AssertDidNotRemoveSponsoredPaymentAsync(sutProvider);
+ await AssertDidNotDeleteSponsorshipAsync(sutProvider);
+ }
}
diff --git a/test/Core.Test/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/CreateSponsorshipCommandTests.cs b/test/Core.Test/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/CreateSponsorshipCommandTests.cs
index df75663045..93a2b629f0 100644
--- a/test/Core.Test/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/CreateSponsorshipCommandTests.cs
+++ b/test/Core.Test/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/CreateSponsorshipCommandTests.cs
@@ -1,8 +1,10 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Billing.Enums;
+using Bit.Core.Context;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
+using Bit.Core.Models.Data;
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise;
using Bit.Core.Repositories;
using Bit.Core.Services;
@@ -36,28 +38,28 @@ public class CreateSponsorshipCommandTests : FamiliesForEnterpriseTestsBase
[Theory, BitAutoData]
public async Task CreateSponsorship_OfferedToNotFound_ThrowsBadRequest(OrganizationUser orgUser, SutProvider sutProvider)
{
- sutProvider.GetDependency().GetUserByIdAsync(orgUser.UserId.Value).ReturnsNull();
+ sutProvider.GetDependency().GetUserByIdAsync(orgUser.UserId!.Value).ReturnsNull();
var exception = await Assert.ThrowsAsync(() =>
- sutProvider.Sut.CreateSponsorshipAsync(null, orgUser, PlanSponsorshipType.FamiliesForEnterprise, default, default));
+ sutProvider.Sut.CreateSponsorshipAsync(null, orgUser, PlanSponsorshipType.FamiliesForEnterprise, default, default, null));
Assert.Contains("Cannot offer a Families Organization Sponsorship to yourself. Choose a different email.", exception.Message);
await sutProvider.GetDependency().DidNotReceiveWithAnyArgs()
- .CreateAsync(default);
+ .CreateAsync(null!);
}
[Theory, BitAutoData]
public async Task CreateSponsorship_OfferedToSelf_ThrowsBadRequest(OrganizationUser orgUser, string sponsoredEmail, User user, SutProvider sutProvider)
{
user.Email = sponsoredEmail;
- sutProvider.GetDependency().GetUserByIdAsync(orgUser.UserId.Value).Returns(user);
+ sutProvider.GetDependency().GetUserByIdAsync(orgUser.UserId!.Value).Returns(user);
var exception = await Assert.ThrowsAsync(() =>
- sutProvider.Sut.CreateSponsorshipAsync(null, orgUser, PlanSponsorshipType.FamiliesForEnterprise, sponsoredEmail, default));
+ sutProvider.Sut.CreateSponsorshipAsync(null, orgUser, PlanSponsorshipType.FamiliesForEnterprise, sponsoredEmail, default, null));
Assert.Contains("Cannot offer a Families Organization Sponsorship to yourself. Choose a different email.", exception.Message);
await sutProvider.GetDependency().DidNotReceiveWithAnyArgs()
- .CreateAsync(default);
+ .CreateAsync(null!);
}
[Theory, BitMemberAutoData(nameof(NonEnterprisePlanTypes))]
@@ -67,14 +69,14 @@ public class CreateSponsorshipCommandTests : FamiliesForEnterpriseTestsBase
org.PlanType = sponsoringOrgPlan;
orgUser.Status = OrganizationUserStatusType.Confirmed;
- sutProvider.GetDependency().GetUserByIdAsync(orgUser.UserId.Value).Returns(user);
+ sutProvider.GetDependency().GetUserByIdAsync(orgUser.UserId!.Value).Returns(user);
var exception = await Assert.ThrowsAsync(() =>
- sutProvider.Sut.CreateSponsorshipAsync(org, orgUser, PlanSponsorshipType.FamiliesForEnterprise, default, default));
+ sutProvider.Sut.CreateSponsorshipAsync(org, orgUser, PlanSponsorshipType.FamiliesForEnterprise, default, default, null));
Assert.Contains("Specified Organization cannot sponsor other organizations.", exception.Message);
await sutProvider.GetDependency().DidNotReceiveWithAnyArgs()
- .CreateAsync(default);
+ .CreateAsync(null!);
}
[Theory]
@@ -86,14 +88,14 @@ public class CreateSponsorshipCommandTests : FamiliesForEnterpriseTestsBase
org.PlanType = PlanType.EnterpriseAnnually;
orgUser.Status = statusType;
- sutProvider.GetDependency().GetUserByIdAsync(orgUser.UserId.Value).Returns(user);
+ sutProvider.GetDependency().GetUserByIdAsync(orgUser.UserId!.Value).Returns(user);
var exception = await Assert.ThrowsAsync(() =>
- sutProvider.Sut.CreateSponsorshipAsync(org, orgUser, PlanSponsorshipType.FamiliesForEnterprise, default, default));
+ sutProvider.Sut.CreateSponsorshipAsync(org, orgUser, PlanSponsorshipType.FamiliesForEnterprise, default, default, null));
Assert.Contains("Only confirmed users can sponsor other organizations.", exception.Message);
await sutProvider.GetDependency().DidNotReceiveWithAnyArgs()
- .CreateAsync(default);
+ .CreateAsync(null!);
}
[Theory]
@@ -106,16 +108,48 @@ public class CreateSponsorshipCommandTests : FamiliesForEnterpriseTestsBase
org.PlanType = PlanType.EnterpriseAnnually;
orgUser.Status = OrganizationUserStatusType.Confirmed;
- sutProvider.GetDependency().GetUserByIdAsync(orgUser.UserId.Value).Returns(user);
+ sutProvider.GetDependency().GetUserByIdAsync(orgUser.UserId!.Value).Returns(user);
sutProvider.GetDependency()
.GetBySponsoringOrganizationUserIdAsync(orgUser.Id).Returns(sponsorship);
+ sutProvider.GetDependency().UserId.Returns(orgUser.UserId.Value);
+
var exception = await Assert.ThrowsAsync(() =>
- sutProvider.Sut.CreateSponsorshipAsync(org, orgUser, sponsorship.PlanSponsorshipType.Value, default, default));
+ sutProvider.Sut.CreateSponsorshipAsync(org, orgUser, sponsorship.PlanSponsorshipType!.Value, null, null, null));
Assert.Contains("Can only sponsor one organization per Organization User.", exception.Message);
await sutProvider.GetDependency().DidNotReceiveWithAnyArgs()
- .CreateAsync(default);
+ .CreateAsync(null!);
+ }
+
+ public static readonly OrganizationUserStatusType[] UnconfirmedOrganizationUsersStatuses = Enum
+ .GetValues()
+ .Where(x => x != OrganizationUserStatusType.Confirmed)
+ .ToArray();
+
+ [Theory]
+ [BitMemberAutoData(nameof(UnconfirmedOrganizationUsersStatuses))]
+ public async Task CreateSponsorship_ThrowsBadRequestException_WhenMemberDoesNotHaveConfirmedStatusInOrganization(
+ OrganizationUserStatusType status, Organization sponsoringOrg, OrganizationUser sponsoringOrgUser, User user,
+ string sponsoredEmail, string friendlyName, Guid sponsorshipId,
+ SutProvider sutProvider)
+ {
+ sponsoringOrg.PlanType = PlanType.EnterpriseAnnually;
+ sponsoringOrgUser.Status = status;
+
+ sutProvider.GetDependency().GetUserByIdAsync(sponsoringOrgUser.UserId!.Value).Returns(user);
+ sutProvider.GetDependency().WhenForAnyArgs(x => x.UpsertAsync(null!)).Do(callInfo =>
+ {
+ var sponsorship = callInfo.Arg();
+ sponsorship.Id = sponsorshipId;
+ });
+ sutProvider.GetDependency().UserId.Returns(sponsoringOrgUser.UserId.Value);
+
+
+ var actual = await Assert.ThrowsAsync(async () =>
+ await sutProvider.Sut.CreateSponsorshipAsync(sponsoringOrg, sponsoringOrgUser, PlanSponsorshipType.FamiliesForEnterprise, sponsoredEmail, friendlyName, null));
+
+ Assert.Equal("Only confirmed users can sponsor other organizations.", actual.Message);
}
[Theory]
@@ -126,16 +160,17 @@ public class CreateSponsorshipCommandTests : FamiliesForEnterpriseTestsBase
sponsoringOrg.PlanType = PlanType.EnterpriseAnnually;
sponsoringOrgUser.Status = OrganizationUserStatusType.Confirmed;
- sutProvider.GetDependency().GetUserByIdAsync(sponsoringOrgUser.UserId.Value).Returns(user);
- sutProvider.GetDependency().WhenForAnyArgs(x => x.UpsertAsync(default)).Do(callInfo =>
+ sutProvider.GetDependency().GetUserByIdAsync(sponsoringOrgUser.UserId!.Value).Returns(user);
+ sutProvider.GetDependency().WhenForAnyArgs(x => x.UpsertAsync(null!)).Do(callInfo =>
{
var sponsorship = callInfo.Arg();
sponsorship.Id = sponsorshipId;
});
+ sutProvider.GetDependency().UserId.Returns(sponsoringOrgUser.UserId.Value);
await sutProvider.Sut.CreateSponsorshipAsync(sponsoringOrg, sponsoringOrgUser,
- PlanSponsorshipType.FamiliesForEnterprise, sponsoredEmail, friendlyName);
+ PlanSponsorshipType.FamiliesForEnterprise, sponsoredEmail, friendlyName, null);
var expectedSponsorship = new OrganizationSponsorship
{
@@ -145,6 +180,8 @@ public class CreateSponsorshipCommandTests : FamiliesForEnterpriseTestsBase
FriendlyName = friendlyName,
OfferedToEmail = sponsoredEmail,
PlanSponsorshipType = PlanSponsorshipType.FamiliesForEnterprise,
+ IsAdminInitiated = false,
+ Notes = null
};
await sutProvider.GetDependency().Received(1)
@@ -161,20 +198,138 @@ public class CreateSponsorshipCommandTests : FamiliesForEnterpriseTestsBase
var expectedException = new Exception();
OrganizationSponsorship createdSponsorship = null;
- sutProvider.GetDependency().GetUserByIdAsync(sponsoringOrgUser.UserId.Value).Returns(user);
- sutProvider.GetDependency().UpsertAsync(default).ThrowsForAnyArgs(callInfo =>
+ sutProvider.GetDependency().GetUserByIdAsync(sponsoringOrgUser.UserId!.Value).Returns(user);
+ sutProvider.GetDependency().UpsertAsync(null!).ThrowsForAnyArgs(callInfo =>
{
createdSponsorship = callInfo.ArgAt(0);
createdSponsorship.Id = Guid.NewGuid();
return expectedException;
});
+ sutProvider.GetDependency().UserId.Returns(sponsoringOrgUser.UserId.Value);
var actualException = await Assert.ThrowsAsync(() =>
sutProvider.Sut.CreateSponsorshipAsync(sponsoringOrg, sponsoringOrgUser,
- PlanSponsorshipType.FamiliesForEnterprise, sponsoredEmail, friendlyName));
+ PlanSponsorshipType.FamiliesForEnterprise, sponsoredEmail, friendlyName, null));
Assert.Same(expectedException, actualException);
await sutProvider.GetDependency().Received(1)
.DeleteAsync(createdSponsorship);
}
+
+ [Theory]
+ [BitAutoData]
+ public async Task CreateSponsorship_MissingManageUsersPermission_ThrowsUnauthorizedException(
+ Organization sponsoringOrg, OrganizationUser sponsoringOrgUser, User user, string sponsoredEmail,
+ string friendlyName, Guid sponsorshipId, Guid currentUserId, SutProvider sutProvider)
+ {
+ sponsoringOrg.PlanType = PlanType.EnterpriseAnnually;
+ sponsoringOrgUser.Status = OrganizationUserStatusType.Confirmed;
+
+ sutProvider.GetDependency().GetUserByIdAsync(sponsoringOrgUser.UserId!.Value).Returns(user);
+ sutProvider.GetDependency().WhenForAnyArgs(x => x.UpsertAsync(null!)).Do(callInfo =>
+ {
+ var sponsorship = callInfo.Arg();
+ sponsorship.Id = sponsorshipId;
+ });
+ sutProvider.GetDependency().UserId.Returns(currentUserId);
+ sutProvider.GetDependency().Organizations.Returns([
+ new()
+ {
+ Id = sponsoringOrg.Id,
+ Permissions = new Permissions(),
+ Type = OrganizationUserType.Custom
+ }
+ ]);
+
+
+ var actual = await Assert.ThrowsAsync(async () =>
+ await sutProvider.Sut.CreateSponsorshipAsync(sponsoringOrg, sponsoringOrgUser,
+ PlanSponsorshipType.FamiliesForEnterprise, sponsoredEmail, friendlyName, notes: null));
+
+ Assert.Equal("You do not have permissions to send sponsorships on behalf of the organization.", actual.Message);
+ }
+
+ [Theory]
+ [BitAutoData(OrganizationUserType.User)]
+ [BitAutoData(OrganizationUserType.Custom)]
+ public async Task CreateSponsorship_InvalidUserType_ThrowsUnauthorizedException(
+ OrganizationUserType organizationUserType,
+ Organization sponsoringOrg, OrganizationUser sponsoringOrgUser, User user, string sponsoredEmail,
+ string friendlyName, Guid sponsorshipId, Guid currentUserId, SutProvider sutProvider)
+ {
+ sponsoringOrg.PlanType = PlanType.EnterpriseAnnually;
+ sponsoringOrgUser.Status = OrganizationUserStatusType.Confirmed;
+
+ sutProvider.GetDependency().GetUserByIdAsync(sponsoringOrgUser.UserId!.Value).Returns(user);
+ sutProvider.GetDependency().WhenForAnyArgs(x => x.UpsertAsync(null!)).Do(callInfo =>
+ {
+ var sponsorship = callInfo.Arg();
+ sponsorship.Id = sponsorshipId;
+ });
+ sutProvider.GetDependency().UserId.Returns(currentUserId);
+ sutProvider.GetDependency().Organizations.Returns([
+ new()
+ {
+ Id = sponsoringOrg.Id,
+ Permissions = new Permissions(),
+ Type = organizationUserType
+ }
+ ]);
+
+ var actual = await Assert.ThrowsAsync(async () =>
+ await sutProvider.Sut.CreateSponsorshipAsync(sponsoringOrg, sponsoringOrgUser,
+ PlanSponsorshipType.FamiliesForEnterprise, sponsoredEmail, friendlyName, notes: null));
+
+ Assert.Equal("You do not have permissions to send sponsorships on behalf of the organization.", actual.Message);
+ }
+
+ [Theory]
+ [BitAutoData(OrganizationUserType.Admin)]
+ [BitAutoData(OrganizationUserType.Owner)]
+ public async Task CreateSponsorship_CreatesAdminInitiatedSponsorship(
+ OrganizationUserType organizationUserType,
+ Organization sponsoringOrg, OrganizationUser sponsoringOrgUser, User user, string sponsoredEmail,
+ string friendlyName, Guid sponsorshipId, Guid currentUserId, string notes, SutProvider sutProvider)
+ {
+ sponsoringOrg.PlanType = PlanType.EnterpriseAnnually;
+ sponsoringOrg.UseAdminSponsoredFamilies = true;
+ sponsoringOrgUser.Status = OrganizationUserStatusType.Confirmed;
+
+ sutProvider.GetDependency().GetUserByIdAsync(sponsoringOrgUser.UserId!.Value).Returns(user);
+ sutProvider.GetDependency().WhenForAnyArgs(x => x.UpsertAsync(null!)).Do(callInfo =>
+ {
+ var sponsorship = callInfo.Arg();
+ sponsorship.Id = sponsorshipId;
+ });
+ sutProvider.GetDependency().UserId.Returns(currentUserId);
+ sutProvider.GetDependency().Organizations.Returns([
+ new()
+ {
+ Id = sponsoringOrg.Id,
+ Permissions = new Permissions { ManageUsers = true },
+ Type = organizationUserType
+ }
+ ]);
+
+ var actual = await sutProvider.Sut.CreateSponsorshipAsync(sponsoringOrg, sponsoringOrgUser,
+ PlanSponsorshipType.FamiliesForEnterprise, sponsoredEmail, friendlyName, notes);
+
+
+ var expectedSponsorship = new OrganizationSponsorship
+ {
+ Id = sponsorshipId,
+ SponsoringOrganizationId = sponsoringOrg.Id,
+ SponsoringOrganizationUserId = sponsoringOrgUser.Id,
+ FriendlyName = friendlyName,
+ OfferedToEmail = sponsoredEmail,
+ PlanSponsorshipType = PlanSponsorshipType.FamiliesForEnterprise,
+ IsAdminInitiated = true,
+ Notes = notes
+ };
+
+ Assert.True(SponsorshipValidator(expectedSponsorship, actual));
+
+ await sutProvider.GetDependency().Received(1)
+ .UpsertAsync(Arg.Is(s => SponsorshipValidator(s, expectedSponsorship)));
+ }
}
diff --git a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/OrganizationUserRepositoryTests.cs b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/OrganizationUserRepositoryTests.cs
index 14b6f50415..637e970f8f 100644
--- a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/OrganizationUserRepositoryTests.cs
+++ b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/OrganizationUserRepositoryTests.cs
@@ -261,6 +261,7 @@ public class OrganizationUserRepositoryTests
Assert.Equal(organization.LimitCollectionDeletion, result.LimitCollectionDeletion);
Assert.Equal(organization.AllowAdminAccessToAllCollectionItems, result.AllowAdminAccessToAllCollectionItems);
Assert.Equal(organization.UseRiskInsights, result.UseRiskInsights);
+ Assert.Equal(organization.UseAdminSponsoredFamilies, result.UseAdminSponsoredFamilies);
}
[DatabaseTheory, DatabaseData]
diff --git a/util/Migrator/DbScripts/2025-03-14_00_AddUseAdminInitiatedSponsorship.sql b/util/Migrator/DbScripts/2025-03-14_00_AddUseAdminInitiatedSponsorship.sql
new file mode 100644
index 0000000000..9a47706fc8
--- /dev/null
+++ b/util/Migrator/DbScripts/2025-03-14_00_AddUseAdminInitiatedSponsorship.sql
@@ -0,0 +1,532 @@
+ALTER TABLE [dbo].[Organization] ADD [UseAdminSponsoredFamilies] bit NOT NULL CONSTRAINT [DF_Organization_UseAdminSponsoredFamilies] default (0)
+ GO;
+
+ALTER TABLE [dbo].[OrganizationSponsorship] ADD [IsAdminInitiated] BIT CONSTRAINT [DF_OrganizationSponsorship_IsAdminInitiated] DEFAULT (0) NOT NULL
+ GO;
+
+ALTER TABLE [dbo].[OrganizationSponsorship] ADD [Notes] NVARCHAR(512) NULL
+ GO;
+
+CREATE OR ALTER PROCEDURE [dbo].[Organization_Create]
+ @Id UNIQUEIDENTIFIER OUTPUT,
+ @Identifier NVARCHAR(50),
+ @Name NVARCHAR(50),
+ @BusinessName NVARCHAR(50),
+ @BusinessAddress1 NVARCHAR(50),
+ @BusinessAddress2 NVARCHAR(50),
+ @BusinessAddress3 NVARCHAR(50),
+ @BusinessCountry VARCHAR(2),
+ @BusinessTaxNumber NVARCHAR(30),
+ @BillingEmail NVARCHAR(256),
+ @Plan NVARCHAR(50),
+ @PlanType TINYINT,
+ @Seats INT,
+ @MaxCollections SMALLINT,
+ @UsePolicies BIT,
+ @UseSso BIT,
+ @UseGroups BIT,
+ @UseDirectory BIT,
+ @UseEvents BIT,
+ @UseTotp BIT,
+ @Use2fa BIT,
+ @UseApi BIT,
+ @UseResetPassword BIT,
+ @SelfHost BIT,
+ @UsersGetPremium BIT,
+ @Storage BIGINT,
+ @MaxStorageGb SMALLINT,
+ @Gateway TINYINT,
+ @GatewayCustomerId VARCHAR(50),
+ @GatewaySubscriptionId VARCHAR(50),
+ @ReferenceData VARCHAR(MAX),
+ @Enabled BIT,
+ @LicenseKey VARCHAR(100),
+ @PublicKey VARCHAR(MAX),
+ @PrivateKey VARCHAR(MAX),
+ @TwoFactorProviders NVARCHAR(MAX),
+ @ExpirationDate DATETIME2(7),
+ @CreationDate DATETIME2(7),
+ @RevisionDate DATETIME2(7),
+ @OwnersNotifiedOfAutoscaling DATETIME2(7),
+ @MaxAutoscaleSeats INT,
+ @UseKeyConnector BIT = 0,
+ @UseScim BIT = 0,
+ @UseCustomPermissions BIT = 0,
+ @UseSecretsManager BIT = 0,
+ @Status TINYINT = 0,
+ @UsePasswordManager BIT = 1,
+ @SmSeats INT = null,
+ @SmServiceAccounts INT = null,
+ @MaxAutoscaleSmSeats INT= null,
+ @MaxAutoscaleSmServiceAccounts INT = null,
+ @SecretsManagerBeta BIT = 0,
+ @LimitCollectionCreation BIT = NULL,
+ @LimitCollectionDeletion BIT = NULL,
+ @AllowAdminAccessToAllCollectionItems BIT = 0,
+ @UseRiskInsights BIT = 0,
+ @LimitItemDeletion BIT = 0,
+ @UseAdminSponsoredFamilies BIT = 0
+AS
+BEGIN
+ SET NOCOUNT ON
+
+ INSERT INTO [dbo].[Organization]
+ (
+ [Id],
+ [Identifier],
+ [Name],
+ [BusinessName],
+ [BusinessAddress1],
+ [BusinessAddress2],
+ [BusinessAddress3],
+ [BusinessCountry],
+ [BusinessTaxNumber],
+ [BillingEmail],
+ [Plan],
+ [PlanType],
+ [Seats],
+ [MaxCollections],
+ [UsePolicies],
+ [UseSso],
+ [UseGroups],
+ [UseDirectory],
+ [UseEvents],
+ [UseTotp],
+ [Use2fa],
+ [UseApi],
+ [UseResetPassword],
+ [SelfHost],
+ [UsersGetPremium],
+ [Storage],
+ [MaxStorageGb],
+ [Gateway],
+ [GatewayCustomerId],
+ [GatewaySubscriptionId],
+ [ReferenceData],
+ [Enabled],
+ [LicenseKey],
+ [PublicKey],
+ [PrivateKey],
+ [TwoFactorProviders],
+ [ExpirationDate],
+ [CreationDate],
+ [RevisionDate],
+ [OwnersNotifiedOfAutoscaling],
+ [MaxAutoscaleSeats],
+ [UseKeyConnector],
+ [UseScim],
+ [UseCustomPermissions],
+ [UseSecretsManager],
+ [Status],
+ [UsePasswordManager],
+ [SmSeats],
+ [SmServiceAccounts],
+ [MaxAutoscaleSmSeats],
+ [MaxAutoscaleSmServiceAccounts],
+ [SecretsManagerBeta],
+ [LimitCollectionCreation],
+ [LimitCollectionDeletion],
+ [AllowAdminAccessToAllCollectionItems],
+ [UseRiskInsights],
+ [LimitItemDeletion],
+ [UseAdminSponsoredFamilies]
+ )
+ VALUES
+ (
+ @Id,
+ @Identifier,
+ @Name,
+ @BusinessName,
+ @BusinessAddress1,
+ @BusinessAddress2,
+ @BusinessAddress3,
+ @BusinessCountry,
+ @BusinessTaxNumber,
+ @BillingEmail,
+ @Plan,
+ @PlanType,
+ @Seats,
+ @MaxCollections,
+ @UsePolicies,
+ @UseSso,
+ @UseGroups,
+ @UseDirectory,
+ @UseEvents,
+ @UseTotp,
+ @Use2fa,
+ @UseApi,
+ @UseResetPassword,
+ @SelfHost,
+ @UsersGetPremium,
+ @Storage,
+ @MaxStorageGb,
+ @Gateway,
+ @GatewayCustomerId,
+ @GatewaySubscriptionId,
+ @ReferenceData,
+ @Enabled,
+ @LicenseKey,
+ @PublicKey,
+ @PrivateKey,
+ @TwoFactorProviders,
+ @ExpirationDate,
+ @CreationDate,
+ @RevisionDate,
+ @OwnersNotifiedOfAutoscaling,
+ @MaxAutoscaleSeats,
+ @UseKeyConnector,
+ @UseScim,
+ @UseCustomPermissions,
+ @UseSecretsManager,
+ @Status,
+ @UsePasswordManager,
+ @SmSeats,
+ @SmServiceAccounts,
+ @MaxAutoscaleSmSeats,
+ @MaxAutoscaleSmServiceAccounts,
+ @SecretsManagerBeta,
+ @LimitCollectionCreation,
+ @LimitCollectionDeletion,
+ @AllowAdminAccessToAllCollectionItems,
+ @UseRiskInsights,
+ @LimitItemDeletion,
+ @UseAdminSponsoredFamilies
+ )
+END
+GO;
+
+CREATE OR ALTER PROCEDURE [dbo].[Organization_ReadAbilities]
+AS
+BEGIN
+ SET NOCOUNT ON
+
+SELECT
+ [Id],
+ [UseEvents],
+ [Use2fa],
+ CASE
+ WHEN [Use2fa] = 1 AND [TwoFactorProviders] IS NOT NULL AND [TwoFactorProviders] != '{}' THEN
+ 1
+ ELSE
+ 0
+END AS [Using2fa],
+ [UsersGetPremium],
+ [UseCustomPermissions],
+ [UseSso],
+ [UseKeyConnector],
+ [UseScim],
+ [UseResetPassword],
+ [UsePolicies],
+ [Enabled],
+ [LimitCollectionCreation],
+ [LimitCollectionDeletion],
+ [AllowAdminAccessToAllCollectionItems],
+ [UseRiskInsights],
+ [LimitItemDeletion],
+ [UseAdminSponsoredFamilies]
+ FROM
+ [dbo].[Organization]
+END
+GO;
+
+CREATE OR ALTER PROCEDURE [dbo].[Organization_Update]
+ @Id UNIQUEIDENTIFIER,
+ @Identifier NVARCHAR(50),
+ @Name NVARCHAR(50),
+ @BusinessName NVARCHAR(50),
+ @BusinessAddress1 NVARCHAR(50),
+ @BusinessAddress2 NVARCHAR(50),
+ @BusinessAddress3 NVARCHAR(50),
+ @BusinessCountry VARCHAR(2),
+ @BusinessTaxNumber NVARCHAR(30),
+ @BillingEmail NVARCHAR(256),
+ @Plan NVARCHAR(50),
+ @PlanType TINYINT,
+ @Seats INT,
+ @MaxCollections SMALLINT,
+ @UsePolicies BIT,
+ @UseSso BIT,
+ @UseGroups BIT,
+ @UseDirectory BIT,
+ @UseEvents BIT,
+ @UseTotp BIT,
+ @Use2fa BIT,
+ @UseApi BIT,
+ @UseResetPassword BIT,
+ @SelfHost BIT,
+ @UsersGetPremium BIT,
+ @Storage BIGINT,
+ @MaxStorageGb SMALLINT,
+ @Gateway TINYINT,
+ @GatewayCustomerId VARCHAR(50),
+ @GatewaySubscriptionId VARCHAR(50),
+ @ReferenceData VARCHAR(MAX),
+ @Enabled BIT,
+ @LicenseKey VARCHAR(100),
+ @PublicKey VARCHAR(MAX),
+ @PrivateKey VARCHAR(MAX),
+ @TwoFactorProviders NVARCHAR(MAX),
+ @ExpirationDate DATETIME2(7),
+ @CreationDate DATETIME2(7),
+ @RevisionDate DATETIME2(7),
+ @OwnersNotifiedOfAutoscaling DATETIME2(7),
+ @MaxAutoscaleSeats INT,
+ @UseKeyConnector BIT = 0,
+ @UseScim BIT = 0,
+ @UseCustomPermissions BIT = 0,
+ @UseSecretsManager BIT = 0,
+ @Status TINYINT = 0,
+ @UsePasswordManager BIT = 1,
+ @SmSeats INT = null,
+ @SmServiceAccounts INT = null,
+ @MaxAutoscaleSmSeats INT = null,
+ @MaxAutoscaleSmServiceAccounts INT = null,
+ @SecretsManagerBeta BIT = 0,
+ @LimitCollectionCreation BIT = null,
+ @LimitCollectionDeletion BIT = null,
+ @AllowAdminAccessToAllCollectionItems BIT = 0,
+ @UseRiskInsights BIT = 0,
+ @LimitItemDeletion BIT = 0,
+ @UseAdminSponsoredFamilies BIT = 0
+AS
+BEGIN
+ SET NOCOUNT ON
+
+UPDATE
+ [dbo].[Organization]
+SET
+ [Identifier] = @Identifier,
+ [Name] = @Name,
+ [BusinessName] = @BusinessName,
+ [BusinessAddress1] = @BusinessAddress1,
+ [BusinessAddress2] = @BusinessAddress2,
+ [BusinessAddress3] = @BusinessAddress3,
+ [BusinessCountry] = @BusinessCountry,
+ [BusinessTaxNumber] = @BusinessTaxNumber,
+ [BillingEmail] = @BillingEmail,
+ [Plan] = @Plan,
+ [PlanType] = @PlanType,
+ [Seats] = @Seats,
+ [MaxCollections] = @MaxCollections,
+ [UsePolicies] = @UsePolicies,
+ [UseSso] = @UseSso,
+ [UseGroups] = @UseGroups,
+ [UseDirectory] = @UseDirectory,
+ [UseEvents] = @UseEvents,
+ [UseTotp] = @UseTotp,
+ [Use2fa] = @Use2fa,
+ [UseApi] = @UseApi,
+ [UseResetPassword] = @UseResetPassword,
+ [SelfHost] = @SelfHost,
+ [UsersGetPremium] = @UsersGetPremium,
+ [Storage] = @Storage,
+ [MaxStorageGb] = @MaxStorageGb,
+ [Gateway] = @Gateway,
+ [GatewayCustomerId] = @GatewayCustomerId,
+ [GatewaySubscriptionId] = @GatewaySubscriptionId,
+ [ReferenceData] = @ReferenceData,
+ [Enabled] = @Enabled,
+ [LicenseKey] = @LicenseKey,
+ [PublicKey] = @PublicKey,
+ [PrivateKey] = @PrivateKey,
+ [TwoFactorProviders] = @TwoFactorProviders,
+ [ExpirationDate] = @ExpirationDate,
+ [CreationDate] = @CreationDate,
+ [RevisionDate] = @RevisionDate,
+ [OwnersNotifiedOfAutoscaling] = @OwnersNotifiedOfAutoscaling,
+ [MaxAutoscaleSeats] = @MaxAutoscaleSeats,
+ [UseKeyConnector] = @UseKeyConnector,
+ [UseScim] = @UseScim,
+ [UseCustomPermissions] = @UseCustomPermissions,
+ [UseSecretsManager] = @UseSecretsManager,
+ [Status] = @Status,
+ [UsePasswordManager] = @UsePasswordManager,
+ [SmSeats] = @SmSeats,
+ [SmServiceAccounts] = @SmServiceAccounts,
+ [MaxAutoscaleSmSeats] = @MaxAutoscaleSmSeats,
+ [MaxAutoscaleSmServiceAccounts] = @MaxAutoscaleSmServiceAccounts,
+ [SecretsManagerBeta] = @SecretsManagerBeta,
+ [LimitCollectionCreation] = @LimitCollectionCreation,
+ [LimitCollectionDeletion] = @LimitCollectionDeletion,
+ [AllowAdminAccessToAllCollectionItems] = @AllowAdminAccessToAllCollectionItems,
+ [UseRiskInsights] = @UseRiskInsights,
+ [LimitItemDeletion] = @LimitItemDeletion,
+ [UseAdminSponsoredFamilies] = @UseAdminSponsoredFamilies
+WHERE
+ [Id] = @Id
+END
+GO;
+
+CREATE OR ALTER PROCEDURE [dbo].[OrganizationSponsorship_Update]
+ @Id UNIQUEIDENTIFIER,
+ @SponsoringOrganizationId UNIQUEIDENTIFIER,
+ @SponsoringOrganizationUserID UNIQUEIDENTIFIER,
+ @SponsoredOrganizationId UNIQUEIDENTIFIER,
+ @FriendlyName NVARCHAR(256),
+ @OfferedToEmail NVARCHAR(256),
+ @PlanSponsorshipType TINYINT,
+ @ToDelete BIT,
+ @LastSyncDate DATETIME2 (7),
+ @ValidUntil DATETIME2 (7),
+ @IsAdminInitiated BIT = 0,
+ @Notes NVARCHAR(512) = NULL
+AS
+BEGIN
+ SET NOCOUNT ON
+
+UPDATE
+ [dbo].[OrganizationSponsorship]
+SET
+ [SponsoringOrganizationId] = @SponsoringOrganizationId,
+ [SponsoringOrganizationUserID] = @SponsoringOrganizationUserID,
+ [SponsoredOrganizationId] = @SponsoredOrganizationId,
+ [FriendlyName] = @FriendlyName,
+ [OfferedToEmail] = @OfferedToEmail,
+ [PlanSponsorshipType] = @PlanSponsorshipType,
+ [ToDelete] = @ToDelete,
+ [LastSyncDate] = @LastSyncDate,
+ [ValidUntil] = @ValidUntil,
+ [IsAdminInitiated] = @IsAdminInitiated,
+ [Notes] = @Notes
+WHERE
+ [Id] = @Id
+END
+GO;
+
+CREATE OR ALTER PROCEDURE [dbo].[OrganizationSponsorship_Create]
+ @Id UNIQUEIDENTIFIER OUTPUT,
+ @SponsoringOrganizationId UNIQUEIDENTIFIER,
+ @SponsoringOrganizationUserID UNIQUEIDENTIFIER,
+ @SponsoredOrganizationId UNIQUEIDENTIFIER,
+ @FriendlyName NVARCHAR(256),
+ @OfferedToEmail NVARCHAR(256),
+ @PlanSponsorshipType TINYINT,
+ @ToDelete BIT,
+ @LastSyncDate DATETIME2 (7),
+ @ValidUntil DATETIME2 (7),
+ @IsAdminInitiated BIT = 0,
+ @Notes NVARCHAR(512) = NULL
+AS
+BEGIN
+ SET NOCOUNT ON
+
+ INSERT INTO [dbo].[OrganizationSponsorship]
+ (
+ [Id],
+ [SponsoringOrganizationId],
+ [SponsoringOrganizationUserID],
+ [SponsoredOrganizationId],
+ [FriendlyName],
+ [OfferedToEmail],
+ [PlanSponsorshipType],
+ [ToDelete],
+ [LastSyncDate],
+ [ValidUntil],
+ [IsAdminInitiated],
+ [Notes]
+ )
+ VALUES
+ (
+ @Id,
+ @SponsoringOrganizationId,
+ @SponsoringOrganizationUserID,
+ @SponsoredOrganizationId,
+ @FriendlyName,
+ @OfferedToEmail,
+ @PlanSponsorshipType,
+ @ToDelete,
+ @LastSyncDate,
+ @ValidUntil,
+ @IsAdminInitiated,
+ @Notes
+ )
+END
+GO;
+
+DROP PROCEDURE IF EXISTS [dbo].[OrganizationSponsorship_CreateMany];
+DROP PROCEDURE IF EXISTS [dbo].[OrganizationSponsorship_UpdateMany];
+DROP TYPE IF EXISTS [dbo].[OrganizationSponsorshipType] GO;
+
+CREATE TYPE [dbo].[OrganizationSponsorshipType] AS TABLE(
+ [Id] UNIQUEIDENTIFIER,
+ [SponsoringOrganizationId] UNIQUEIDENTIFIER,
+ [SponsoringOrganizationUserID] UNIQUEIDENTIFIER,
+ [SponsoredOrganizationId] UNIQUEIDENTIFIER,
+ [FriendlyName] NVARCHAR(256),
+ [OfferedToEmail] VARCHAR(256),
+ [PlanSponsorshipType] TINYINT,
+ [LastSyncDate] DATETIME2(7),
+ [ValidUntil] DATETIME2(7),
+ [ToDelete] BIT,
+ [IsAdminInitiated] BIT DEFAULT 0,
+ [Notes] NVARCHAR(512) NULL
+);
+GO;
+
+CREATE PROCEDURE [dbo].[OrganizationSponsorship_CreateMany]
+ @OrganizationSponsorshipsInput [dbo].[OrganizationSponsorshipType] READONLY
+AS
+BEGIN
+ SET NOCOUNT ON
+
+ INSERT INTO [dbo].[OrganizationSponsorship]
+ (
+ [Id],
+ [SponsoringOrganizationId],
+ [SponsoringOrganizationUserID],
+ [SponsoredOrganizationId],
+ [FriendlyName],
+ [OfferedToEmail],
+ [PlanSponsorshipType],
+ [ToDelete],
+ [LastSyncDate],
+ [ValidUntil],
+ [IsAdminInitiated],
+ [Notes]
+ )
+SELECT
+ OS.[Id],
+ OS.[SponsoringOrganizationId],
+ OS.[SponsoringOrganizationUserID],
+ OS.[SponsoredOrganizationId],
+ OS.[FriendlyName],
+ OS.[OfferedToEmail],
+ OS.[PlanSponsorshipType],
+ OS.[ToDelete],
+ OS.[LastSyncDate],
+ OS.[ValidUntil],
+ OS.[IsAdminInitiated],
+ OS.[Notes]
+FROM
+ @OrganizationSponsorshipsInput OS
+END
+GO;
+
+CREATE PROCEDURE [dbo].[OrganizationSponsorship_UpdateMany]
+ @OrganizationSponsorshipsInput [dbo].[OrganizationSponsorshipType] READONLY
+AS
+BEGIN
+ SET NOCOUNT ON
+
+UPDATE
+ OS
+SET
+ [Id] = OSI.[Id],
+ [SponsoringOrganizationId] = OSI.[SponsoringOrganizationId],
+ [SponsoringOrganizationUserID] = OSI.[SponsoringOrganizationUserID],
+ [SponsoredOrganizationId] = OSI.[SponsoredOrganizationId],
+ [FriendlyName] = OSI.[FriendlyName],
+ [OfferedToEmail] = OSI.[OfferedToEmail],
+ [PlanSponsorshipType] = OSI.[PlanSponsorshipType],
+ [ToDelete] = OSI.[ToDelete],
+ [LastSyncDate] = OSI.[LastSyncDate],
+ [ValidUntil] = OSI.[ValidUntil],
+ [IsAdminInitiated] = OSI.[IsAdminInitiated],
+ [Notes] = OSI.[Notes]
+FROM
+ [dbo].[OrganizationSponsorship] OS
+ INNER JOIN
+ @OrganizationSponsorshipsInput OSI ON OS.Id = OSI.Id
+
+END
+GO;
diff --git a/util/Migrator/DbScripts/2025-03-14_01_AddUseAdminInitiatedSponsorship_RefreshView.sql b/util/Migrator/DbScripts/2025-03-14_01_AddUseAdminInitiatedSponsorship_RefreshView.sql
new file mode 100644
index 0000000000..5057dbfb13
--- /dev/null
+++ b/util/Migrator/DbScripts/2025-03-14_01_AddUseAdminInitiatedSponsorship_RefreshView.sql
@@ -0,0 +1,278 @@
+CREATE OR ALTER VIEW [dbo].[OrganizationUserOrganizationDetailsView]
+AS
+SELECT
+ OU.[UserId],
+ OU.[OrganizationId],
+ OU.[Id] OrganizationUserId,
+ O.[Name],
+ O.[Enabled],
+ O.[PlanType],
+ O.[UsePolicies],
+ O.[UseSso],
+ O.[UseKeyConnector],
+ O.[UseScim],
+ O.[UseGroups],
+ O.[UseDirectory],
+ O.[UseEvents],
+ O.[UseTotp],
+ O.[Use2fa],
+ O.[UseApi],
+ O.[UseResetPassword],
+ O.[SelfHost],
+ O.[UsersGetPremium],
+ O.[UseCustomPermissions],
+ O.[UseSecretsManager],
+ O.[Seats],
+ O.[MaxCollections],
+ O.[MaxStorageGb],
+ O.[Identifier],
+ OU.[Key],
+ OU.[ResetPasswordKey],
+ O.[PublicKey],
+ O.[PrivateKey],
+ OU.[Status],
+ OU.[Type],
+ SU.[ExternalId] SsoExternalId,
+ OU.[Permissions],
+ PO.[ProviderId],
+ P.[Name] ProviderName,
+ P.[Type] ProviderType,
+ SS.[Data] SsoConfig,
+ OS.[FriendlyName] FamilySponsorshipFriendlyName,
+ OS.[LastSyncDate] FamilySponsorshipLastSyncDate,
+ OS.[ToDelete] FamilySponsorshipToDelete,
+ OS.[ValidUntil] FamilySponsorshipValidUntil,
+ OU.[AccessSecretsManager],
+ O.[UsePasswordManager],
+ O.[SmSeats],
+ O.[SmServiceAccounts],
+ O.[LimitCollectionCreation],
+ O.[LimitCollectionDeletion],
+ O.[AllowAdminAccessToAllCollectionItems],
+ O.[UseRiskInsights],
+ O.[UseAdminSponsoredFamilies],
+ O.[LimitItemDeletion]
+FROM
+ [dbo].[OrganizationUser] OU
+ LEFT JOIN
+ [dbo].[Organization] O ON O.[Id] = OU.[OrganizationId]
+ LEFT JOIN
+ [dbo].[SsoUser] SU ON SU.[UserId] = OU.[UserId] AND SU.[OrganizationId] = OU.[OrganizationId]
+ LEFT JOIN
+ [dbo].[ProviderOrganization] PO ON PO.[OrganizationId] = O.[Id]
+ LEFT JOIN
+ [dbo].[Provider] P ON P.[Id] = PO.[ProviderId]
+ LEFT JOIN
+ [dbo].[SsoConfig] SS ON SS.[OrganizationId] = OU.[OrganizationId]
+ LEFT JOIN
+ [dbo].[OrganizationSponsorship] OS ON OS.[SponsoringOrganizationUserID] = OU.[Id]
+GO
+
+CREATE OR ALTER VIEW [dbo].[ProviderUserProviderOrganizationDetailsView]
+AS
+SELECT
+ PU.[UserId],
+ PO.[OrganizationId],
+ O.[Name],
+ O.[Enabled],
+ O.[UsePolicies],
+ O.[UseSso],
+ O.[UseKeyConnector],
+ O.[UseScim],
+ O.[UseGroups],
+ O.[UseDirectory],
+ O.[UseEvents],
+ O.[UseTotp],
+ O.[Use2fa],
+ O.[UseApi],
+ O.[UseResetPassword],
+ O.[SelfHost],
+ O.[UsersGetPremium],
+ O.[UseCustomPermissions],
+ O.[Seats],
+ O.[MaxCollections],
+ O.[MaxStorageGb],
+ O.[Identifier],
+ PO.[Key],
+ O.[PublicKey],
+ O.[PrivateKey],
+ PU.[Status],
+ PU.[Type],
+ PO.[ProviderId],
+ PU.[Id] ProviderUserId,
+ P.[Name] ProviderName,
+ O.[PlanType],
+ O.[LimitCollectionCreation],
+ O.[LimitCollectionDeletion],
+ O.[AllowAdminAccessToAllCollectionItems],
+ O.[UseRiskInsights],
+ O.[UseAdminSponsoredFamilies],
+ P.[Type] ProviderType,
+ O.[LimitItemDeletion]
+FROM
+ [dbo].[ProviderUser] PU
+ INNER JOIN
+ [dbo].[ProviderOrganization] PO ON PO.[ProviderId] = PU.[ProviderId]
+ INNER JOIN
+ [dbo].[Organization] O ON O.[Id] = PO.[OrganizationId]
+ INNER JOIN
+ [dbo].[Provider] P ON P.[Id] = PU.[ProviderId]
+GO
+
+
+--Manually refresh [dbo].[OrganizationUserOrganizationDetailsView]
+IF OBJECT_ID('[dbo].[OrganizationUserOrganizationDetailsView]') IS NOT NULL
+BEGIN
+EXECUTE sp_refreshsqlmodule N'[dbo].[OrganizationUserOrganizationDetailsView]';
+END
+GO
+
+IF OBJECT_ID('[dbo].[ProviderUserProviderOrganizationDetailsView]') IS NOT NULL
+BEGIN
+EXECUTE sp_refreshsqlmodule N'[dbo].[ProviderUserProviderOrganizationDetailsView]';
+END
+GO
+
+IF OBJECT_ID('[dbo].[OrganizationView]') IS NOT NULL
+BEGIN
+EXECUTE sp_refreshsqlmodule N'[dbo].[OrganizationView]';
+END
+GO
+
+IF OBJECT_ID('[dbo].[OrganizationSponsorshipView]') IS NOT NULL
+BEGIN
+EXECUTE sp_refreshsqlmodule N'[dbo].[OrganizationSponsorshipView]';
+END
+GO
+
+IF OBJECT_ID('[dbo].[OrganizationUserOrganizationDetailsView]') IS NOT NULL
+BEGIN
+EXECUTE sp_refreshsqlmodule N'[dbo].[OrganizationUserOrganizationDetailsView]';
+END
+GO
+
+IF OBJECT_ID('[dbo].[OrganizationSponsorship_OrganizationUserDeleted]') IS NOT NULL
+BEGIN
+EXECUTE sp_refreshsqlmodule N'[dbo].[OrganizationSponsorship_OrganizationUserDeleted]';
+END
+GO
+
+IF OBJECT_ID('[dbo].[OrganizationSponsorship_CreateMany]') IS NOT NULL
+BEGIN
+EXECUTE sp_refreshsqlmodule N'[dbo].[OrganizationSponsorship_CreateMany]';
+END
+GO
+
+IF OBJECT_ID('[dbo].[OrganizationSponsorship_OrganizationUsersDeleted]') IS NOT NULL
+BEGIN
+EXECUTE sp_refreshsqlmodule N'[dbo].[OrganizationSponsorship_OrganizationUsersDeleted]';
+END
+GO
+
+IF OBJECT_ID('[dbo].[OrganizationSponsorship_DeleteExpired]') IS NOT NULL
+BEGIN
+EXECUTE sp_refreshsqlmodule N'[dbo].[OrganizationSponsorship_DeleteExpired]';
+END
+GO
+
+IF OBJECT_ID('[dbo].[OrganizationSponsorship_Update]') IS NOT NULL
+BEGIN
+EXECUTE sp_refreshsqlmodule N'[dbo].[OrganizationSponsorship_Update]';
+END
+GO
+
+IF OBJECT_ID('[dbo].[OrganizationSponsorship_DeleteById]') IS NOT NULL
+BEGIN
+EXECUTE sp_refreshsqlmodule N'[dbo].[OrganizationSponsorship_DeleteById]';
+END
+GO
+
+IF OBJECT_ID('[dbo].[OrganizationSponsorship_Create]') IS NOT NULL
+BEGIN
+EXECUTE sp_refreshsqlmodule N'[dbo].[OrganizationSponsorship_Create]';
+END
+GO
+
+IF OBJECT_ID('[dbo].[OrganizationSponsorship_OrganizationDeleted]') IS NOT NULL
+BEGIN
+EXECUTE sp_refreshsqlmodule N'[dbo].[OrganizationSponsorship_OrganizationDeleted]';
+END
+GO
+
+IF OBJECT_ID('[dbo].[OrganizationSponsorship_UpdateMany]') IS NOT NULL
+BEGIN
+EXECUTE sp_refreshsqlmodule N'[dbo].[OrganizationSponsorship_UpdateMany]';
+END
+GO
+
+IF OBJECT_ID('[dbo].[OrganizationSponsorship_DeleteByIds]') IS NOT NULL
+BEGIN
+EXECUTE sp_refreshsqlmodule N'[dbo].[OrganizationSponsorship_DeleteByIds]';
+END
+GO
+
+IF OBJECT_ID('[dbo].[OrganizationSponsorship_ReadLatestBySponsoringOrganizationId]') IS NOT NULL
+BEGIN
+EXECUTE sp_refreshsqlmodule N'[dbo].[OrganizationSponsorship_ReadLatestBySponsoringOrganizationId]';
+END
+GO
+
+IF OBJECT_ID('[dbo].[OrganizationSponsorship_ReadByOfferedToEmail]') IS NOT NULL
+BEGIN
+EXECUTE sp_refreshsqlmodule N'[dbo].[OrganizationSponsorship_ReadByOfferedToEmail]';
+END
+GO
+
+IF OBJECT_ID('[dbo].[OrganizationSponsorship_ReadBySponsoredOrganizationId]') IS NOT NULL
+BEGIN
+EXECUTE sp_refreshsqlmodule N'[dbo].[OrganizationSponsorship_ReadBySponsoredOrganizationId]';
+END
+GO
+
+IF OBJECT_ID('[dbo].[OrganizationSponsorship_ReadBySponsoringOrganizationId]') IS NOT NULL
+BEGIN
+EXECUTE sp_refreshsqlmodule N'[dbo].[OrganizationSponsorship_ReadBySponsoringOrganizationId]';
+END
+GO
+
+IF OBJECT_ID('[dbo].[OrganizationSponsorship_ReadById]') IS NOT NULL
+BEGIN
+EXECUTE sp_refreshsqlmodule N'[dbo].[OrganizationSponsorship_ReadById]';
+END
+GO
+
+IF OBJECT_ID('[dbo].[OrganizationSponsorship_ReadBySponsoringOrganizationUserId]') IS NOT NULL
+BEGIN
+EXECUTE sp_refreshsqlmodule N'[dbo].[OrganizationSponsorship_ReadBySponsoringOrganizationUserId]';
+END
+GO
+
+IF OBJECT_ID('[dbo].[OrganizationUserOrganizationDetails_ReadByUserIdStatus]') IS NOT NULL
+BEGIN
+EXECUTE sp_refreshsqlmodule N'[dbo].[OrganizationUserOrganizationDetails_ReadByUserIdStatus]';
+END
+GO
+
+IF OBJECT_ID('[dbo].[OrganizationUserOrganizationDetails_ReadByUserIdStatusOrganizationId]') IS NOT NULL
+BEGIN
+EXECUTE sp_refreshsqlmodule N'[dbo].[OrganizationUserOrganizationDetails_ReadByUserIdStatusOrganizationId]';
+END
+GO
+
+IF OBJECT_ID('[dbo].[OrganizationUser_DeleteById]') IS NOT NULL
+BEGIN
+EXECUTE sp_refreshsqlmodule N'[dbo].[OrganizationUser_DeleteById]';
+END
+GO
+
+IF OBJECT_ID('[dbo].[OrganizationUser_DeleteByIds]') IS NOT NULL
+BEGIN
+EXECUTE sp_refreshsqlmodule N'[dbo].[OrganizationUser_DeleteByIds]';
+END
+GO
+
+IF OBJECT_ID('[dbo].[Organization_DeleteById]') IS NOT NULL
+BEGIN
+EXECUTE sp_refreshsqlmodule N'[dbo].[Organization_DeleteById]';
+END
+GO
diff --git a/util/MySqlMigrations/Migrations/20250326092653_PM17830_AdminInitiatedSponsorships.Designer.cs b/util/MySqlMigrations/Migrations/20250326092653_PM17830_AdminInitiatedSponsorships.Designer.cs
new file mode 100644
index 0000000000..ff9f22a15a
--- /dev/null
+++ b/util/MySqlMigrations/Migrations/20250326092653_PM17830_AdminInitiatedSponsorships.Designer.cs
@@ -0,0 +1,3026 @@
+//
+using System;
+using Bit.Infrastructure.EntityFramework.Repositories;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace Bit.MySqlMigrations.Migrations
+{
+ [DbContext(typeof(DatabaseContext))]
+ [Migration("20250326092653_PM17830_AdminInitiatedSponsorships")]
+ partial class PM17830_AdminInitiatedSponsorships
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "8.0.8")
+ .HasAnnotation("Relational:MaxIdentifierLength", 64);
+
+ MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder);
+
+ modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("char(36)");
+
+ b.Property("AllowAdminAccessToAllCollectionItems")
+ .HasColumnType("tinyint(1)")
+ .HasDefaultValue(true);
+
+ b.Property("BillingEmail")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("varchar(256)");
+
+ b.Property("BusinessAddress1")
+ .HasMaxLength(50)
+ .HasColumnType("varchar(50)");
+
+ b.Property("BusinessAddress2")
+ .HasMaxLength(50)
+ .HasColumnType("varchar(50)");
+
+ b.Property("BusinessAddress3")
+ .HasMaxLength(50)
+ .HasColumnType("varchar(50)");
+
+ b.Property("BusinessCountry")
+ .HasMaxLength(2)
+ .HasColumnType("varchar(2)");
+
+ b.Property("BusinessName")
+ .HasMaxLength(50)
+ .HasColumnType("varchar(50)");
+
+ b.Property("BusinessTaxNumber")
+ .HasMaxLength(30)
+ .HasColumnType("varchar(30)");
+
+ b.Property("CreationDate")
+ .HasColumnType("datetime(6)");
+
+ b.Property("Enabled")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("ExpirationDate")
+ .HasColumnType("datetime(6)");
+
+ b.Property("Gateway")
+ .HasColumnType("tinyint unsigned");
+
+ b.Property("GatewayCustomerId")
+ .HasMaxLength(50)
+ .HasColumnType("varchar(50)");
+
+ b.Property("GatewaySubscriptionId")
+ .HasMaxLength(50)
+ .HasColumnType("varchar(50)");
+
+ b.Property("Identifier")
+ .HasMaxLength(50)
+ .HasColumnType("varchar(50)");
+
+ b.Property("LicenseKey")
+ .HasMaxLength(100)
+ .HasColumnType("varchar(100)");
+
+ b.Property("LimitCollectionCreation")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("LimitCollectionDeletion")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("LimitItemDeletion")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("MaxAutoscaleSeats")
+ .HasColumnType("int");
+
+ b.Property("MaxAutoscaleSmSeats")
+ .HasColumnType("int");
+
+ b.Property("MaxAutoscaleSmServiceAccounts")
+ .HasColumnType("int");
+
+ b.Property("MaxCollections")
+ .HasColumnType("smallint");
+
+ b.Property("MaxStorageGb")
+ .HasColumnType("smallint");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("varchar(50)");
+
+ b.Property("OwnersNotifiedOfAutoscaling")
+ .HasColumnType("datetime(6)");
+
+ b.Property("Plan")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("varchar(50)");
+
+ b.Property("PlanType")
+ .HasColumnType("tinyint unsigned");
+
+ b.Property("PrivateKey")
+ .HasColumnType("longtext");
+
+ b.Property("PublicKey")
+ .HasColumnType("longtext");
+
+ b.Property("ReferenceData")
+ .HasColumnType("longtext");
+
+ b.Property("RevisionDate")
+ .HasColumnType("datetime(6)");
+
+ b.Property("Seats")
+ .HasColumnType("int");
+
+ b.Property("SelfHost")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("SmSeats")
+ .HasColumnType("int");
+
+ b.Property("SmServiceAccounts")
+ .HasColumnType("int");
+
+ b.Property("Status")
+ .HasColumnType("tinyint unsigned");
+
+ b.Property("Storage")
+ .HasColumnType("bigint");
+
+ b.Property("TwoFactorProviders")
+ .HasColumnType("longtext");
+
+ b.Property("Use2fa")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("UseAdminSponsoredFamilies")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("UseApi")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("UseCustomPermissions")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("UseDirectory")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("UseEvents")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("UseGroups")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("UseKeyConnector")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("UsePasswordManager")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("UsePolicies")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("UseResetPassword")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("UseRiskInsights")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("UseScim")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("UseSecretsManager")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("UseSso")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("UseTotp")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("UsersGetPremium")
+ .HasColumnType("tinyint(1)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Id", "Enabled")
+ .HasAnnotation("Npgsql:IndexInclude", new[] { "UseTotp" });
+
+ b.ToTable("Organization", (string)null);
+ });
+
+ modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("char(36)");
+
+ b.Property("CreationDate")
+ .HasColumnType("datetime(6)");
+
+ b.Property("Data")
+ .HasColumnType("longtext");
+
+ b.Property("Enabled")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("OrganizationId")
+ .HasColumnType("char(36)");
+
+ b.Property("RevisionDate")
+ .HasColumnType("datetime(6)");
+
+ b.Property("Type")
+ .HasColumnType("tinyint unsigned");
+
+ b.HasKey("Id");
+
+ b.HasIndex("OrganizationId")
+ .HasAnnotation("SqlServer:Clustered", false);
+
+ b.HasIndex("OrganizationId", "Type")
+ .IsUnique()
+ .HasAnnotation("SqlServer:Clustered", false);
+
+ b.ToTable("Policy", (string)null);
+ });
+
+ modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("char(36)");
+
+ b.Property("BillingEmail")
+ .HasColumnType("longtext");
+
+ b.Property("BillingPhone")
+ .HasColumnType("longtext");
+
+ b.Property("BusinessAddress1")
+ .HasColumnType("longtext");
+
+ b.Property("BusinessAddress2")
+ .HasColumnType("longtext");
+
+ b.Property("BusinessAddress3")
+ .HasColumnType("longtext");
+
+ b.Property("BusinessCountry")
+ .HasColumnType("longtext");
+
+ b.Property("BusinessName")
+ .HasColumnType("longtext");
+
+ b.Property("BusinessTaxNumber")
+ .HasColumnType("longtext");
+
+ b.Property("CreationDate")
+ .HasColumnType("datetime(6)");
+
+ b.Property("DiscountId")
+ .HasColumnType("longtext");
+
+ b.Property("Enabled")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("Gateway")
+ .HasColumnType("tinyint unsigned");
+
+ b.Property("GatewayCustomerId")
+ .HasColumnType("longtext");
+
+ b.Property("GatewaySubscriptionId")
+ .HasColumnType("longtext");
+
+ b.Property("Name")
+ .HasColumnType("longtext");
+
+ b.Property("RevisionDate")
+ .HasColumnType("datetime(6)");
+
+ b.Property("Status")
+ .HasColumnType("tinyint unsigned");
+
+ b.Property("Type")
+ .HasColumnType("tinyint unsigned");
+
+ b.Property("UseEvents")
+ .HasColumnType("tinyint(1)");
+
+ b.HasKey("Id");
+
+ b.ToTable("Provider", (string)null);
+ });
+
+ modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("char(36)");
+
+ b.Property("CreationDate")
+ .HasColumnType("datetime(6)");
+
+ b.Property("Key")
+ .HasColumnType("longtext");
+
+ b.Property("OrganizationId")
+ .HasColumnType("char(36)");
+
+ b.Property("ProviderId")
+ .HasColumnType("char(36)");
+
+ b.Property("RevisionDate")
+ .HasColumnType("datetime(6)");
+
+ b.Property("Settings")
+ .HasColumnType("longtext");
+
+ b.HasKey("Id");
+
+ b.HasIndex("OrganizationId");
+
+ b.HasIndex("ProviderId");
+
+ b.ToTable("ProviderOrganization", (string)null);
+ });
+
+ modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("char(36)");
+
+ b.Property("CreationDate")
+ .HasColumnType("datetime(6)");
+
+ b.Property("Email")
+ .HasColumnType("longtext");
+
+ b.Property("Key")
+ .HasColumnType("longtext");
+
+ b.Property("Permissions")
+ .HasColumnType("longtext");
+
+ b.Property("ProviderId")
+ .HasColumnType("char(36)");
+
+ b.Property("RevisionDate")
+ .HasColumnType("datetime(6)");
+
+ b.Property("Status")
+ .HasColumnType("tinyint unsigned");
+
+ b.Property("Type")
+ .HasColumnType("tinyint unsigned");
+
+ b.Property("UserId")
+ .HasColumnType("char(36)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ProviderId");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("ProviderUser", (string)null);
+ });
+
+ modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("char(36)");
+
+ b.Property("AccessCode")
+ .HasMaxLength(25)
+ .HasColumnType("varchar(25)");
+
+ b.Property("Approved")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("AuthenticationDate")
+ .HasColumnType("datetime(6)");
+
+ b.Property("CreationDate")
+ .HasColumnType("datetime(6)");
+
+ b.Property("Key")
+ .HasColumnType("longtext");
+
+ b.Property("MasterPasswordHash")
+ .HasColumnType("longtext");
+
+ b.Property("OrganizationId")
+ .HasColumnType("char(36)");
+
+ b.Property("PublicKey")
+ .HasColumnType("longtext");
+
+ b.Property("RequestCountryName")
+ .HasMaxLength(200)
+ .HasColumnType("varchar(200)");
+
+ b.Property("RequestDeviceIdentifier")
+ .HasMaxLength(50)
+ .HasColumnType("varchar(50)");
+
+ b.Property("RequestDeviceType")
+ .HasColumnType("tinyint unsigned");
+
+ b.Property("RequestIpAddress")
+ .HasMaxLength(50)
+ .HasColumnType("varchar(50)");
+
+ b.Property("ResponseDate")
+ .HasColumnType("datetime(6)");
+
+ b.Property("ResponseDeviceId")
+ .HasColumnType("char(36)");
+
+ b.Property("Type")
+ .HasColumnType("tinyint unsigned");
+
+ b.Property("UserId")
+ .HasColumnType("char(36)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("OrganizationId");
+
+ b.HasIndex("ResponseDeviceId");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AuthRequest", (string)null);
+ });
+
+ modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("char(36)");
+
+ b.Property("CreationDate")
+ .HasColumnType("datetime(6)");
+
+ b.Property("Email")
+ .HasMaxLength(256)
+ .HasColumnType("varchar(256)");
+
+ b.Property("GranteeId")
+ .HasColumnType("char(36)");
+
+ b.Property("GrantorId")
+ .HasColumnType("char(36)");
+
+ b.Property("KeyEncrypted")
+ .HasColumnType("longtext");
+
+ b.Property("LastNotificationDate")
+ .HasColumnType("datetime(6)");
+
+ b.Property("RecoveryInitiatedDate")
+ .HasColumnType("datetime(6)");
+
+ b.Property("RevisionDate")
+ .HasColumnType("datetime(6)");
+
+ b.Property("Status")
+ .HasColumnType("tinyint unsigned");
+
+ b.Property("Type")
+ .HasColumnType("tinyint unsigned");
+
+ b.Property("WaitTimeDays")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.HasIndex("GranteeId");
+
+ b.HasIndex("GrantorId");
+
+ b.ToTable("EmergencyAccess", (string)null);
+ });
+
+ modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.Grant", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id"));
+
+ b.Property("ClientId")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("varchar(200)");
+
+ b.Property("ConsumedDate")
+ .HasColumnType("datetime(6)");
+
+ b.Property("CreationDate")
+ .HasColumnType("datetime(6)");
+
+ b.Property("Data")
+ .IsRequired()
+ .HasColumnType("longtext");
+
+ b.Property("Description")
+ .HasMaxLength(200)
+ .HasColumnType("varchar(200)");
+
+ b.Property("ExpirationDate")
+ .HasColumnType("datetime(6)");
+
+ b.Property("Key")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("varchar(200)");
+
+ b.Property("SessionId")
+ .HasMaxLength(100)
+ .HasColumnType("varchar(100)");
+
+ b.Property("SubjectId")
+ .HasMaxLength(200)
+ .HasColumnType("varchar(200)");
+
+ b.Property("Type")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("varchar(50)");
+
+ b.HasKey("Id")
+ .HasName("PK_Grant")
+ .HasAnnotation("SqlServer:Clustered", true);
+
+ b.HasIndex("ExpirationDate")
+ .HasAnnotation("SqlServer:Clustered", false);
+
+ b.HasIndex("Key")
+ .IsUnique();
+
+ b.ToTable("Grant", (string)null);
+ });
+
+ modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id"));
+
+ b.Property("CreationDate")
+ .HasColumnType("datetime(6)");
+
+ b.Property("Data")
+ .HasColumnType("longtext");
+
+ b.Property("Enabled")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("OrganizationId")
+ .HasColumnType("char(36)");
+
+ b.Property("RevisionDate")
+ .HasColumnType("datetime(6)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("OrganizationId");
+
+ b.ToTable("SsoConfig", (string)null);
+ });
+
+ modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id"));
+
+ b.Property("CreationDate")
+ .HasColumnType("datetime(6)");
+
+ b.Property("ExternalId")
+ .HasMaxLength(50)
+ .HasColumnType("varchar(50)");
+
+ b.Property("OrganizationId")
+ .HasColumnType("char(36)");
+
+ b.Property("UserId")
+ .HasColumnType("char(36)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("OrganizationId")
+ .HasAnnotation("SqlServer:Clustered", false);
+
+ b.HasIndex("UserId");
+
+ b.HasIndex("OrganizationId", "ExternalId")
+ .IsUnique()
+ .HasAnnotation("Npgsql:IndexInclude", new[] { "UserId" })
+ .HasAnnotation("SqlServer:Clustered", false);
+
+ b.HasIndex("OrganizationId", "UserId")
+ .IsUnique()
+ .HasAnnotation("SqlServer:Clustered", false);
+
+ b.ToTable("SsoUser", (string)null);
+ });
+
+ modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("char(36)");
+
+ b.Property("AaGuid")
+ .HasColumnType("char(36)");
+
+ b.Property("Counter")
+ .HasColumnType("int");
+
+ b.Property("CreationDate")
+ .HasColumnType("datetime(6)");
+
+ b.Property("CredentialId")
+ .HasMaxLength(256)
+ .HasColumnType("varchar(256)");
+
+ b.Property("EncryptedPrivateKey")
+ .HasMaxLength(2000)
+ .HasColumnType("varchar(2000)");
+
+ b.Property("EncryptedPublicKey")
+ .HasMaxLength(2000)
+ .HasColumnType("varchar(2000)");
+
+ b.Property("EncryptedUserKey")
+ .HasMaxLength(2000)
+ .HasColumnType("varchar(2000)");
+
+ b.Property("Name")
+ .HasMaxLength(50)
+ .HasColumnType("varchar(50)");
+
+ b.Property("PublicKey")
+ .HasMaxLength(256)
+ .HasColumnType("varchar(256)");
+
+ b.Property("RevisionDate")
+ .HasColumnType("datetime(6)");
+
+ b.Property("SupportsPrf")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("Type")
+ .HasMaxLength(20)
+ .HasColumnType("varchar(20)");
+
+ b.Property("UserId")
+ .HasColumnType("char(36)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("WebAuthnCredential", (string)null);
+ });
+
+ modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ClientOrganizationMigrationRecord", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("char(36)");
+
+ b.Property("ExpirationDate")
+ .HasColumnType("datetime(6)");
+
+ b.Property("GatewayCustomerId")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("varchar(50)");
+
+ b.Property("GatewaySubscriptionId")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("varchar(50)");
+
+ b.Property