mirror of
https://github.com/bitwarden/server.git
synced 2025-05-23 04:21:05 -05:00
WIP
This commit is contained in:
parent
224ef1272e
commit
08924e10d2
@ -77,9 +77,10 @@ public class OrganizationSponsorshipsController : Controller
|
||||
{
|
||||
var sponsoringOrg = await _organizationRepository.GetByIdAsync(sponsoringOrgId);
|
||||
|
||||
var targetUser = model.SponsoringUserId ?? _currentContext.UserId!.Value;
|
||||
var sponsorship = await _createSponsorshipCommand.CreateSponsorshipAsync(
|
||||
sponsoringOrg,
|
||||
await _organizationUserRepository.GetByOrganizationAsync(sponsoringOrgId, _currentContext.UserId ?? default),
|
||||
await _organizationUserRepository.GetByOrganizationAsync(sponsoringOrgId, targetUser),
|
||||
model.PlanSponsorshipType, model.SponsoredEmail, model.FriendlyName);
|
||||
await _sendSponsorshipOfferCommand.SendSponsorshipOfferAsync(sponsorship, sponsoringOrg.Name);
|
||||
}
|
||||
|
@ -16,4 +16,10 @@ 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; }
|
||||
}
|
||||
|
@ -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))
|
||||
|
@ -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; }
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ 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 void SetNewId()
|
||||
{
|
||||
|
@ -16,6 +16,7 @@ public class OrganizationSponsorshipData
|
||||
LastSyncDate = sponsorship.LastSyncDate;
|
||||
ValidUntil = sponsorship.ValidUntil;
|
||||
ToDelete = sponsorship.ToDelete;
|
||||
IsAdminInitiated = sponsorship.IsAdminInitiated;
|
||||
}
|
||||
public Guid SponsoringOrganizationUserId { get; set; }
|
||||
public Guid? SponsoredOrganizationId { get; set; }
|
||||
@ -25,6 +26,7 @@ public class OrganizationSponsorshipData
|
||||
public DateTime? LastSyncDate { get; set; }
|
||||
public DateTime? ValidUntil { get; set; }
|
||||
public bool ToDelete { get; set; }
|
||||
public bool IsAdminInitiated { get; set; }
|
||||
|
||||
public bool CloudSponsorshipRemoved { get; set; }
|
||||
}
|
||||
|
@ -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,22 +11,16 @@ using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise;
|
||||
|
||||
public class CreateSponsorshipCommand : ICreateSponsorshipCommand
|
||||
public class CreateSponsorshipCommand(
|
||||
IOrganizationSponsorshipRepository organizationSponsorshipRepository,
|
||||
IUserService userService,
|
||||
ICurrentContext currentContext)
|
||||
: ICreateSponsorshipCommand
|
||||
{
|
||||
private readonly IOrganizationSponsorshipRepository _organizationSponsorshipRepository;
|
||||
private readonly IUserService _userService;
|
||||
|
||||
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);
|
||||
var sponsoringUser = await userService.GetUserByIdAsync(sponsoringOrgUser.UserId.Value);
|
||||
if (sponsoringUser == null || string.Equals(sponsoringUser.Email, sponsoredEmail, System.StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
throw new BadRequestException("Cannot offer a Families Organization Sponsorship to yourself. Choose a different email.");
|
||||
@ -45,7 +40,24 @@ public class CreateSponsorshipCommand : ICreateSponsorshipCommand
|
||||
throw new BadRequestException("Only confirmed users can sponsor other organizations.");
|
||||
}
|
||||
|
||||
var existingOrgSponsorship = await _organizationSponsorshipRepository
|
||||
var isAdminInitiated = false;
|
||||
if (currentContext.UserId != sponsoringOrgUser.UserId)
|
||||
{
|
||||
var organization = currentContext.Organizations.First(x => x.Id == sponsoringOrg.Id);
|
||||
OrganizationUserType[] allowedUserTypes =
|
||||
[
|
||||
OrganizationUserType.Admin,
|
||||
OrganizationUserType.Owner,
|
||||
OrganizationUserType.Custom
|
||||
];
|
||||
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.");
|
||||
}
|
||||
isAdminInitiated = true;
|
||||
}
|
||||
|
||||
var existingOrgSponsorship = await organizationSponsorshipRepository
|
||||
.GetBySponsoringOrganizationUserIdAsync(sponsoringOrgUser.Id);
|
||||
if (existingOrgSponsorship?.SponsoredOrganizationId != null)
|
||||
{
|
||||
@ -59,6 +71,7 @@ public class CreateSponsorshipCommand : ICreateSponsorshipCommand
|
||||
FriendlyName = friendlyName,
|
||||
OfferedToEmail = sponsoredEmail,
|
||||
PlanSponsorshipType = sponsorshipType,
|
||||
IsAdminInitiated = isAdminInitiated
|
||||
};
|
||||
|
||||
if (existingOrgSponsorship != null)
|
||||
@ -69,14 +82,14 @@ public class CreateSponsorshipCommand : ICreateSponsorshipCommand
|
||||
|
||||
try
|
||||
{
|
||||
await _organizationSponsorshipRepository.UpsertAsync(sponsorship);
|
||||
await organizationSponsorshipRepository.UpsertAsync(sponsorship);
|
||||
return sponsorship;
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (sponsorship.Id != default)
|
||||
{
|
||||
await _organizationSponsorshipRepository.DeleteAsync(sponsorship);
|
||||
await organizationSponsorshipRepository.DeleteAsync(sponsorship);
|
||||
}
|
||||
throw;
|
||||
}
|
||||
|
@ -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)
|
||||
);
|
||||
|
||||
|
@ -9,6 +9,7 @@ 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),
|
||||
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]),
|
||||
|
@ -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;
|
||||
@ -110,6 +112,8 @@ public class CreateSponsorshipCommandTests : FamiliesForEnterpriseTestsBase
|
||||
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));
|
||||
|
||||
@ -132,6 +136,7 @@ public class CreateSponsorshipCommandTests : FamiliesForEnterpriseTestsBase
|
||||
var sponsorship = callInfo.Arg<OrganizationSponsorship>();
|
||||
sponsorship.Id = sponsorshipId;
|
||||
});
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(sponsoringOrgUser.UserId.Value);
|
||||
|
||||
|
||||
await sutProvider.Sut.CreateSponsorshipAsync(sponsoringOrg, sponsoringOrgUser,
|
||||
@ -168,6 +173,7 @@ public class CreateSponsorshipCommandTests : FamiliesForEnterpriseTestsBase
|
||||
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,
|
||||
@ -177,4 +183,74 @@ public class CreateSponsorshipCommandTests : FamiliesForEnterpriseTestsBase
|
||||
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(default)).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.Admin
|
||||
}
|
||||
]);
|
||||
|
||||
|
||||
var actual = await Assert.ThrowsAsync<UnauthorizedAccessException>(async () =>
|
||||
await sutProvider.Sut.CreateSponsorshipAsync(sponsoringOrg, sponsoringOrgUser,
|
||||
PlanSponsorshipType.FamiliesForEnterprise, sponsoredEmail, friendlyName));
|
||||
|
||||
Assert.Equal("You do not have permissions to send sponsorships on behalf of the organization.", actual.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(OrganizationUserType.User)]
|
||||
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(default)).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 Assert.ThrowsAsync<UnauthorizedAccessException>(async () =>
|
||||
await sutProvider.Sut.CreateSponsorshipAsync(sponsoringOrg, sponsoringOrgUser,
|
||||
PlanSponsorshipType.FamiliesForEnterprise, sponsoredEmail, friendlyName));
|
||||
|
||||
Assert.Equal("You do not have permissions to send sponsorships on behalf of the organization.", actual.Message);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user