1
0
mirror of https://github.com/bitwarden/server.git synced 2025-04-26 07:12:20 -05:00

[PM-17830] Backend changes for admin initiated sponsorships (#5531)

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* Add `Notes` column to `OrganizationSponsorships` table

* Add feature flag to `CreateAdminInitiatedSponsorshipHandler`

* Unit tests for `CreateSponsorshipHandler`

* More tests for `CreateSponsorshipHandler`

* Forgot to add `Notes` column to `OrganizationSponsorships` table in the migration script

* `CreateAdminInitiatedSponsorshipHandler` unit tests

* Fix `CreateSponsorshipCommandTests`

* Encrypt the notes field

* Wrong business logic checking for invalid permissions.

* Wrong business logic checking for invalid permissions.

* Remove design patterns

* duplicate definition in Constants.cs

* Allow rollback

* Fix stored procedures & type

* Fix stored procedures & type

* Properly encapsulating this PR behind its feature flag

* Removed comments

* Updated ValidateSponsorshipCommand to validate admin initiated requirements

---------

Co-authored-by: Conner Turnbull <133619638+cturnbull-bitwarden@users.noreply.github.com>
Co-authored-by: Conner Turnbull <cturnbull@bitwarden.com>
This commit is contained in:
Jonas Hendrickx 2025-04-16 17:27:58 +02:00 committed by GitHub
parent f678e3db79
commit c182b37347
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
46 changed files with 10466 additions and 76 deletions

View File

@ -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;

View File

@ -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

View File

@ -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
/// </returns>
public bool UserIsClaimedByOrganization { get; set; }
public bool UseRiskInsights { get; set; }
public bool UseAdminSponsoredFamilies { get; set; }
}

View File

@ -50,5 +50,6 @@ public class ProfileProviderOrganizationResponseModel : ProfileOrganizationRespo
LimitItemDeletion = organization.LimitItemDeletion;
AllowAdminAccessToAllCollectionItems = organization.AllowAdminAccessToAllCollectionItems;
UseRiskInsights = organization.UseRiskInsights;
UseAdminSponsoredFamilies = organization.UseAdminSponsoredFamilies;
}
}

View File

@ -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);
}

View File

@ -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}")]

View File

@ -16,4 +16,14 @@ public class OrganizationSponsorshipCreateRequestModel
[StringLength(256)]
public string FriendlyName { get; set; }
/// <summary>
/// (optional) The user to target for the sponsorship.
/// </summary>
/// <remarks>Left empty when creating a sponsorship for the authenticated user.</remarks>
public Guid? SponsoringUserId { get; set; }
[EncryptedString]
[EncryptedStringLength(512)]
public string Notes { get; set; }
}

View File

@ -114,6 +114,11 @@ public class Organization : ITableObject<Guid>, IStorableSubscriber, IRevisable,
/// </summary>
public bool UseRiskInsights { get; set; }
/// <summary>
/// If set to true, admins can initiate organization-issued sponsorships.
/// </summary>
public bool UseAdminSponsoredFamilies { get; set; }
public void SetNewId()
{
if (Id == default(Guid))

View File

@ -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; }
}

View File

@ -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; }
}

View File

@ -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; }
}

View File

@ -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";

View File

@ -20,6 +20,8 @@ public class OrganizationSponsorship : ITableObject<Guid>
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()
{

View File

@ -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; }
}

View File

@ -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)

View File

@ -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 async Task<OrganizationSponsorship> CreateSponsorshipAsync(Organization sponsoringOrganization,
OrganizationUser sponsoringMember, PlanSponsorshipType sponsorshipType, string sponsoredEmail,
string friendlyName, string notes)
{
var sponsoringUser = await userService.GetUserByIdAsync(sponsoringMember.UserId!.Value);
public CreateSponsorshipCommand(IOrganizationSponsorshipRepository organizationSponsorshipRepository,
IUserService userService)
{
_organizationSponsorshipRepository = organizationSponsorshipRepository;
_userService = userService;
}
public async Task<OrganizationSponsorship> 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;
}

View File

@ -7,5 +7,5 @@ namespace Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnte
public interface ICreateSponsorshipCommand
{
Task<OrganizationSponsorship> CreateSponsorshipAsync(Organization sponsoringOrg, OrganizationUser sponsoringOrgUser,
PlanSponsorshipType sponsorshipType, string sponsoredEmail, string friendlyName);
PlanSponsorshipType sponsorshipType, string sponsoredEmail, string friendlyName, string notes);
}

View File

@ -106,7 +106,8 @@ public class OrganizationRepository : Repository<Core.AdminConsole.Entities.Orga
LimitCollectionDeletion = e.LimitCollectionDeletion,
LimitItemDeletion = e.LimitItemDeletion,
AllowAdminAccessToAllCollectionItems = e.AllowAdminAccessToAllCollectionItems,
UseRiskInsights = e.UseRiskInsights
UseRiskInsights = e.UseRiskInsights,
UseAdminSponsoredFamilies = e.UseAdminSponsoredFamilies
}).ToListAsync();
}
}

View File

@ -71,6 +71,7 @@ public class OrganizationUserOrganizationDetailsViewQuery : IQuery<OrganizationU
LimitItemDeletion = o.LimitItemDeletion,
AllowAdminAccessToAllCollectionItems = o.AllowAdminAccessToAllCollectionItems,
UseRiskInsights = o.UseRiskInsights,
UseAdminSponsoredFamilies = o.UseAdminSponsoredFamilies,
};
return query;
}

View File

@ -49,6 +49,7 @@ public class ProviderUserOrganizationDetailsViewQuery : IQuery<ProviderUserOrgan
LimitItemDeletion = x.o.LimitItemDeletion,
AllowAdminAccessToAllCollectionItems = x.o.AllowAdminAccessToAllCollectionItems,
UseRiskInsights = x.o.UseRiskInsights,
UseAdminSponsoredFamilies = x.o.UseAdminSponsoredFamilies,
ProviderType = x.p.Type
});
}

View File

@ -8,7 +8,9 @@ CREATE PROCEDURE [dbo].[OrganizationSponsorship_Create]
@PlanSponsorshipType TINYINT,
@ToDelete BIT,
@LastSyncDate DATETIME2 (7),
@ValidUntil DATETIME2 (7)
@ValidUntil DATETIME2 (7),
@IsAdminInitiated BIT = 0,
@Notes NVARCHAR(512) = NULL
AS
BEGIN
SET NOCOUNT ON
@ -24,7 +26,9 @@ BEGIN
[PlanSponsorshipType],
[ToDelete],
[LastSyncDate],
[ValidUntil]
[ValidUntil],
[IsAdminInitiated],
[Notes]
)
VALUES
(
@ -37,7 +41,9 @@ BEGIN
@PlanSponsorshipType,
@ToDelete,
@LastSyncDate,
@ValidUntil
@ValidUntil,
@IsAdminInitiated,
@Notes
)
END
GO

View File

@ -15,7 +15,9 @@ BEGIN
[PlanSponsorshipType],
[ToDelete],
[LastSyncDate],
[ValidUntil]
[ValidUntil],
[IsAdminInitiated],
[Notes]
)
SELECT
OS.[Id],
@ -27,7 +29,9 @@ BEGIN
OS.[PlanSponsorshipType],
OS.[ToDelete],
OS.[LastSyncDate],
OS.[ValidUntil]
OS.[ValidUntil],
OS.[IsAdminInitiated],
OS.[Notes]
FROM
@OrganizationSponsorshipsInput OS
END

View File

@ -8,7 +8,9 @@ CREATE PROCEDURE [dbo].[OrganizationSponsorship_Update]
@PlanSponsorshipType TINYINT,
@ToDelete BIT,
@LastSyncDate DATETIME2 (7),
@ValidUntil DATETIME2 (7)
@ValidUntil DATETIME2 (7),
@IsAdminInitiated BIT = 0,
@Notes NVARCHAR(512) = NULL
AS
BEGIN
SET NOCOUNT ON
@ -24,7 +26,9 @@ BEGIN
[PlanSponsorshipType] = @PlanSponsorshipType,
[ToDelete] = @ToDelete,
[LastSyncDate] = @LastSyncDate,
[ValidUntil] = @ValidUntil
[ValidUntil] = @ValidUntil,
[IsAdminInitiated] = @IsAdminInitiated,
[Notes] = @Notes
WHERE
[Id] = @Id
END

View File

@ -16,7 +16,9 @@ BEGIN
[PlanSponsorshipType] = OSI.[PlanSponsorshipType],
[ToDelete] = OSI.[ToDelete],
[LastSyncDate] = OSI.[LastSyncDate],
[ValidUntil] = OSI.[ValidUntil]
[ValidUntil] = OSI.[ValidUntil],
[IsAdminInitiated] = OSI.[IsAdminInitiated],
[Notes] = OSI.[Notes]
FROM
[dbo].[OrganizationSponsorship] OS
INNER JOIN

View File

@ -55,7 +55,8 @@ CREATE PROCEDURE [dbo].[Organization_Create]
@LimitCollectionDeletion BIT = NULL,
@AllowAdminAccessToAllCollectionItems BIT = 0,
@UseRiskInsights BIT = 0,
@LimitItemDeletion BIT = 0
@LimitItemDeletion BIT = 0,
@UseAdminSponsoredFamilies BIT = 0
AS
BEGIN
SET NOCOUNT ON
@ -118,7 +119,8 @@ BEGIN
[LimitCollectionDeletion],
[AllowAdminAccessToAllCollectionItems],
[UseRiskInsights],
[LimitItemDeletion]
[LimitItemDeletion],
[UseAdminSponsoredFamilies]
)
VALUES
(
@ -178,6 +180,7 @@ BEGIN
@LimitCollectionDeletion,
@AllowAdminAccessToAllCollectionItems,
@UseRiskInsights,
@LimitItemDeletion
@LimitItemDeletion,
@UseAdminSponsoredFamilies
)
END

View File

@ -25,7 +25,8 @@ BEGIN
[LimitCollectionDeletion],
[AllowAdminAccessToAllCollectionItems],
[UseRiskInsights],
[LimitItemDeletion]
[LimitItemDeletion],
[UseAdminSponsoredFamilies]
FROM
[dbo].[Organization]
END

View File

@ -55,7 +55,8 @@ CREATE PROCEDURE [dbo].[Organization_Update]
@LimitCollectionDeletion BIT = null,
@AllowAdminAccessToAllCollectionItems BIT = 0,
@UseRiskInsights BIT = 0,
@LimitItemDeletion BIT = 0
@LimitItemDeletion BIT = 0,
@UseAdminSponsoredFamilies BIT = 0
AS
BEGIN
SET NOCOUNT ON
@ -118,7 +119,8 @@ BEGIN
[LimitCollectionDeletion] = @LimitCollectionDeletion,
[AllowAdminAccessToAllCollectionItems] = @AllowAdminAccessToAllCollectionItems,
[UseRiskInsights] = @UseRiskInsights,
[LimitItemDeletion] = @LimitItemDeletion
[LimitItemDeletion] = @LimitItemDeletion,
[UseAdminSponsoredFamilies] = @UseAdminSponsoredFamilies
WHERE
[Id] = @Id
END

View File

@ -56,6 +56,7 @@ CREATE TABLE [dbo].[Organization] (
[LimitItemDeletion] BIT NOT NULL CONSTRAINT [DF_Organization_LimitItemDeletion] DEFAULT (0),
[AllowAdminAccessToAllCollectionItems] BIT NOT NULL CONSTRAINT [DF_Organization_AllowAdminAccessToAllCollectionItems] DEFAULT (0),
[UseRiskInsights] BIT NOT NULL CONSTRAINT [DF_Organization_UseRiskInsights] DEFAULT (0),
[UseAdminSponsoredFamilies] BIT NOT NULL CONSTRAINT [DF_Organization_UseAdminSponsoredFamilies] DEFAULT (0),
CONSTRAINT [PK_Organization] PRIMARY KEY CLUSTERED ([Id] ASC)
);

View File

@ -9,6 +9,8 @@ CREATE TABLE [dbo].[OrganizationSponsorship] (
[ToDelete] BIT DEFAULT (0) NOT NULL,
[LastSyncDate] DATETIME2 (7) NULL,
[ValidUntil] DATETIME2 (7) NULL,
[IsAdminInitiated] BIT NOT NULL CONSTRAINT [DF_OrganizationSponsorship_IsAdminInitiated] DEFAULT (0),
[Notes] NVARCHAR(512) NULL,
CONSTRAINT [PK_OrganizationSponsorship] PRIMARY KEY CLUSTERED ([Id] ASC),
CONSTRAINT [FK_OrganizationSponsorship_SponsoringOrg] FOREIGN KEY ([SponsoringOrganizationId]) REFERENCES [dbo].[Organization] ([Id]),
CONSTRAINT [FK_OrganizationSponsorship_SponsoredOrg] FOREIGN KEY ([SponsoredOrganizationId]) REFERENCES [dbo].[Organization] ([Id]),

View File

@ -8,5 +8,7 @@ CREATE TYPE [dbo].[OrganizationSponsorshipType] AS TABLE(
[PlanSponsorshipType] TINYINT,
[LastSyncDate] DATETIME2(7),
[ValidUntil] DATETIME2(7),
[ToDelete] BIT
[ToDelete] BIT,
[IsAdminInitiated] BIT DEFAULT 0,
[Notes] NVARCHAR(512) NULL
)

View File

@ -50,6 +50,7 @@ SELECT
O.[LimitCollectionDeletion],
O.[AllowAdminAccessToAllCollectionItems],
O.[UseRiskInsights],
O.[UseAdminSponsoredFamilies],
O.[LimitItemDeletion]
FROM
[dbo].[OrganizationUser] OU

View File

@ -36,6 +36,7 @@ SELECT
O.[LimitCollectionDeletion],
O.[AllowAdminAccessToAllCollectionItems],
O.[UseRiskInsights],
O.[UseAdminSponsoredFamilies],
P.[Type] ProviderType,
O.[LimitItemDeletion]
FROM

View File

@ -251,4 +251,52 @@ public class ValidateSponsorshipCommandTests : CancelSponsorshipCommandTestsBase
await AssertDidNotRemoveSponsoredPaymentAsync(sutProvider);
await AssertDidNotDeleteSponsorshipAsync(sutProvider);
}
[Theory(Skip = "Temporarily disabled")]
[BitMemberAutoData(nameof(EnterprisePlanTypes))]
public async Task ValidateSponsorshipAsync_AdminInitiatedButUseAdminSponsoredFamiliesFalse_IsInvalid(
PlanType planType, Organization sponsoredOrg, OrganizationSponsorship existingSponsorship,
Organization sponsoringOrg, SutProvider<ValidateSponsorshipCommand> sutProvider)
{
sponsoringOrg.PlanType = planType;
sponsoringOrg.UseAdminSponsoredFamilies = false;
existingSponsorship.SponsoringOrganizationId = sponsoringOrg.Id;
existingSponsorship.IsAdminInitiated = true;
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.GetBySponsoredOrganizationIdAsync(sponsoredOrg.Id).Returns(existingSponsorship);
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoredOrg.Id).Returns(sponsoredOrg);
sutProvider.GetDependency<IOrganizationRepository>().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<ValidateSponsorshipCommand> 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<IOrganizationSponsorshipRepository>()
.GetBySponsoredOrganizationIdAsync(sponsoredOrg.Id).Returns(existingSponsorship);
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoredOrg.Id).Returns(sponsoredOrg);
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoringOrg.Id).Returns(sponsoringOrg);
var result = await sutProvider.Sut.ValidateSponsorshipAsync(sponsoredOrg.Id);
Assert.True(result);
await AssertDidNotRemoveSponsoredPaymentAsync(sutProvider);
await AssertDidNotDeleteSponsorshipAsync(sutProvider);
}
}

View File

@ -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<CreateSponsorshipCommand> sutProvider)
{
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(orgUser.UserId.Value).ReturnsNull();
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(orgUser.UserId!.Value).ReturnsNull();
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
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<IOrganizationSponsorshipRepository>().DidNotReceiveWithAnyArgs()
.CreateAsync(default);
.CreateAsync(null!);
}
[Theory, BitAutoData]
public async Task CreateSponsorship_OfferedToSelf_ThrowsBadRequest(OrganizationUser orgUser, string sponsoredEmail, User user, SutProvider<CreateSponsorshipCommand> sutProvider)
{
user.Email = sponsoredEmail;
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(orgUser.UserId.Value).Returns(user);
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(orgUser.UserId!.Value).Returns(user);
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
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<IOrganizationSponsorshipRepository>().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<IUserService>().GetUserByIdAsync(orgUser.UserId.Value).Returns(user);
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(orgUser.UserId!.Value).Returns(user);
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
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<IOrganizationSponsorshipRepository>().DidNotReceiveWithAnyArgs()
.CreateAsync(default);
.CreateAsync(null!);
}
[Theory]
@ -86,14 +88,14 @@ public class CreateSponsorshipCommandTests : FamiliesForEnterpriseTestsBase
org.PlanType = PlanType.EnterpriseAnnually;
orgUser.Status = statusType;
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(orgUser.UserId.Value).Returns(user);
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(orgUser.UserId!.Value).Returns(user);
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
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<IOrganizationSponsorshipRepository>().DidNotReceiveWithAnyArgs()
.CreateAsync(default);
.CreateAsync(null!);
}
[Theory]
@ -106,16 +108,48 @@ public class CreateSponsorshipCommandTests : FamiliesForEnterpriseTestsBase
org.PlanType = PlanType.EnterpriseAnnually;
orgUser.Status = OrganizationUserStatusType.Confirmed;
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(orgUser.UserId.Value).Returns(user);
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(orgUser.UserId!.Value).Returns(user);
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.GetBySponsoringOrganizationUserIdAsync(orgUser.Id).Returns(sponsorship);
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(orgUser.UserId.Value);
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
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<IOrganizationSponsorshipRepository>().DidNotReceiveWithAnyArgs()
.CreateAsync(default);
.CreateAsync(null!);
}
public static readonly OrganizationUserStatusType[] UnconfirmedOrganizationUsersStatuses = Enum
.GetValues<OrganizationUserStatusType>()
.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<CreateSponsorshipCommand> sutProvider)
{
sponsoringOrg.PlanType = PlanType.EnterpriseAnnually;
sponsoringOrgUser.Status = status;
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(sponsoringOrgUser.UserId!.Value).Returns(user);
sutProvider.GetDependency<IOrganizationSponsorshipRepository>().WhenForAnyArgs(x => x.UpsertAsync(null!)).Do(callInfo =>
{
var sponsorship = callInfo.Arg<OrganizationSponsorship>();
sponsorship.Id = sponsorshipId;
});
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(sponsoringOrgUser.UserId.Value);
var actual = await Assert.ThrowsAsync<BadRequestException>(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<IUserService>().GetUserByIdAsync(sponsoringOrgUser.UserId.Value).Returns(user);
sutProvider.GetDependency<IOrganizationSponsorshipRepository>().WhenForAnyArgs(x => x.UpsertAsync(default)).Do(callInfo =>
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(sponsoringOrgUser.UserId!.Value).Returns(user);
sutProvider.GetDependency<IOrganizationSponsorshipRepository>().WhenForAnyArgs(x => x.UpsertAsync(null!)).Do(callInfo =>
{
var sponsorship = callInfo.Arg<OrganizationSponsorship>();
sponsorship.Id = sponsorshipId;
});
sutProvider.GetDependency<ICurrentContext>().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<IOrganizationSponsorshipRepository>().Received(1)
@ -161,20 +198,138 @@ public class CreateSponsorshipCommandTests : FamiliesForEnterpriseTestsBase
var expectedException = new Exception();
OrganizationSponsorship createdSponsorship = null;
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(sponsoringOrgUser.UserId.Value).Returns(user);
sutProvider.GetDependency<IOrganizationSponsorshipRepository>().UpsertAsync(default).ThrowsForAnyArgs(callInfo =>
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(sponsoringOrgUser.UserId!.Value).Returns(user);
sutProvider.GetDependency<IOrganizationSponsorshipRepository>().UpsertAsync(null!).ThrowsForAnyArgs(callInfo =>
{
createdSponsorship = callInfo.ArgAt<OrganizationSponsorship>(0);
createdSponsorship.Id = Guid.NewGuid();
return expectedException;
});
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(sponsoringOrgUser.UserId.Value);
var actualException = await Assert.ThrowsAsync<Exception>(() =>
sutProvider.Sut.CreateSponsorshipAsync(sponsoringOrg, sponsoringOrgUser,
PlanSponsorshipType.FamiliesForEnterprise, sponsoredEmail, friendlyName));
PlanSponsorshipType.FamiliesForEnterprise, sponsoredEmail, friendlyName, null));
Assert.Same(expectedException, actualException);
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().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<CreateSponsorshipCommand> sutProvider)
{
sponsoringOrg.PlanType = PlanType.EnterpriseAnnually;
sponsoringOrgUser.Status = OrganizationUserStatusType.Confirmed;
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(sponsoringOrgUser.UserId!.Value).Returns(user);
sutProvider.GetDependency<IOrganizationSponsorshipRepository>().WhenForAnyArgs(x => x.UpsertAsync(null!)).Do(callInfo =>
{
var sponsorship = callInfo.Arg<OrganizationSponsorship>();
sponsorship.Id = sponsorshipId;
});
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(currentUserId);
sutProvider.GetDependency<ICurrentContext>().Organizations.Returns([
new()
{
Id = sponsoringOrg.Id,
Permissions = new Permissions(),
Type = OrganizationUserType.Custom
}
]);
var actual = await Assert.ThrowsAsync<UnauthorizedAccessException>(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<CreateSponsorshipCommand> sutProvider)
{
sponsoringOrg.PlanType = PlanType.EnterpriseAnnually;
sponsoringOrgUser.Status = OrganizationUserStatusType.Confirmed;
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(sponsoringOrgUser.UserId!.Value).Returns(user);
sutProvider.GetDependency<IOrganizationSponsorshipRepository>().WhenForAnyArgs(x => x.UpsertAsync(null!)).Do(callInfo =>
{
var sponsorship = callInfo.Arg<OrganizationSponsorship>();
sponsorship.Id = sponsorshipId;
});
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(currentUserId);
sutProvider.GetDependency<ICurrentContext>().Organizations.Returns([
new()
{
Id = sponsoringOrg.Id,
Permissions = new Permissions(),
Type = organizationUserType
}
]);
var actual = await Assert.ThrowsAsync<UnauthorizedAccessException>(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<CreateSponsorshipCommand> sutProvider)
{
sponsoringOrg.PlanType = PlanType.EnterpriseAnnually;
sponsoringOrg.UseAdminSponsoredFamilies = true;
sponsoringOrgUser.Status = OrganizationUserStatusType.Confirmed;
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(sponsoringOrgUser.UserId!.Value).Returns(user);
sutProvider.GetDependency<IOrganizationSponsorshipRepository>().WhenForAnyArgs(x => x.UpsertAsync(null!)).Do(callInfo =>
{
var sponsorship = callInfo.Arg<OrganizationSponsorship>();
sponsorship.Id = sponsorshipId;
});
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(currentUserId);
sutProvider.GetDependency<ICurrentContext>().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<IOrganizationSponsorshipRepository>().Received(1)
.UpsertAsync(Arg.Is<OrganizationSponsorship>(s => SponsorshipValidator(s, expectedSponsorship)));
}
}

View File

@ -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]

View File

@ -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;

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,50 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Bit.MySqlMigrations.Migrations;
/// <inheritdoc />
public partial class PM17830_AdminInitiatedSponsorships : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "IsAdminInitiated",
table: "OrganizationSponsorship",
type: "tinyint(1)",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<string>(
name: "Notes",
table: "OrganizationSponsorship",
type: "longtext",
nullable: true)
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AddColumn<bool>(
name: "UseAdminSponsoredFamilies",
table: "Organization",
type: "tinyint(1)",
nullable: false,
defaultValue: false);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "IsAdminInitiated",
table: "OrganizationSponsorship");
migrationBuilder.DropColumn(
name: "Notes",
table: "OrganizationSponsorship");
migrationBuilder.DropColumn(
name: "UseAdminSponsoredFamilies",
table: "Organization");
}
}

View File

@ -164,6 +164,9 @@ namespace Bit.MySqlMigrations.Migrations
b.Property<bool>("Use2fa")
.HasColumnType("tinyint(1)");
b.Property<bool>("UseAdminSponsoredFamilies")
.HasColumnType("tinyint(1)");
b.Property<bool>("UseApi")
.HasColumnType("tinyint(1)");
@ -1311,9 +1314,15 @@ namespace Bit.MySqlMigrations.Migrations
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.Property<bool>("IsAdminInitiated")
.HasColumnType("tinyint(1)");
b.Property<DateTime?>("LastSyncDate")
.HasColumnType("datetime(6)");
b.Property<string>("Notes")
.HasColumnType("longtext");
b.Property<string>("OfferedToEmail")
.HasMaxLength(256)
.HasColumnType("varchar(256)");

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,49 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Bit.PostgresMigrations.Migrations;
/// <inheritdoc />
public partial class PM17830_AdminInitiatedSponsorships : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "IsAdminInitiated",
table: "OrganizationSponsorship",
type: "boolean",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<string>(
name: "Notes",
table: "OrganizationSponsorship",
type: "text",
nullable: true);
migrationBuilder.AddColumn<bool>(
name: "UseAdminSponsoredFamilies",
table: "Organization",
type: "boolean",
nullable: false,
defaultValue: false);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "IsAdminInitiated",
table: "OrganizationSponsorship");
migrationBuilder.DropColumn(
name: "Notes",
table: "OrganizationSponsorship");
migrationBuilder.DropColumn(
name: "UseAdminSponsoredFamilies",
table: "Organization");
}
}

View File

@ -166,6 +166,9 @@ namespace Bit.PostgresMigrations.Migrations
b.Property<bool>("Use2fa")
.HasColumnType("boolean");
b.Property<bool>("UseAdminSponsoredFamilies")
.HasColumnType("boolean");
b.Property<bool>("UseApi")
.HasColumnType("boolean");
@ -1316,9 +1319,15 @@ namespace Bit.PostgresMigrations.Migrations
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<bool>("IsAdminInitiated")
.HasColumnType("boolean");
b.Property<DateTime?>("LastSyncDate")
.HasColumnType("timestamp with time zone");
b.Property<string>("Notes")
.HasColumnType("text");
b.Property<string>("OfferedToEmail")
.HasMaxLength(256)
.HasColumnType("character varying(256)");

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,49 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Bit.SqliteMigrations.Migrations;
/// <inheritdoc />
public partial class PM17830_AdminInitiatedSponsorships : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "IsAdminInitiated",
table: "OrganizationSponsorship",
type: "INTEGER",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<string>(
name: "Notes",
table: "OrganizationSponsorship",
type: "TEXT",
nullable: true);
migrationBuilder.AddColumn<bool>(
name: "UseAdminSponsoredFamilies",
table: "Organization",
type: "INTEGER",
nullable: false,
defaultValue: false);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "IsAdminInitiated",
table: "OrganizationSponsorship");
migrationBuilder.DropColumn(
name: "Notes",
table: "OrganizationSponsorship");
migrationBuilder.DropColumn(
name: "UseAdminSponsoredFamilies",
table: "Organization");
}
}

View File

@ -159,6 +159,9 @@ namespace Bit.SqliteMigrations.Migrations
b.Property<bool>("Use2fa")
.HasColumnType("INTEGER");
b.Property<bool>("UseAdminSponsoredFamilies")
.HasColumnType("INTEGER");
b.Property<bool>("UseApi")
.HasColumnType("INTEGER");
@ -1300,9 +1303,15 @@ namespace Bit.SqliteMigrations.Migrations
.HasMaxLength(256)
.HasColumnType("TEXT");
b.Property<bool>("IsAdminInitiated")
.HasColumnType("INTEGER");
b.Property<DateTime?>("LastSyncDate")
.HasColumnType("TEXT");
b.Property<string>("Notes")
.HasColumnType("TEXT");
b.Property<string>("OfferedToEmail")
.HasMaxLength(256)
.HasColumnType("TEXT");