1
0
mirror of https://github.com/bitwarden/server.git synced 2025-06-30 15:42:48 -05:00

[AC-1081] Merge feature/billing-obfuscation (#2665)

* [EC-1014] Create Organization Status (Pending/Created) (#2610)

* [EC-427] Add columns 'Type' and 'BillingPhone' to Provider table

* [EC-427] Provider table Type and BillingPhone MySql migrations

* [EC-427] Provider table Type and BillingPhone Postgres migrations

* [EC-427] Add mysql migration script

* [EC-427] Add mysql migration script

* [EC-427] Updated Provider sql script to include default column value

* [EC-427] Removed default value from Provider.Type column

* [EC-427] Changed migration script to include a default value constraint instead of updating the null type

* [EC-427] Updated Sql project Provider table script

* [EC-427] Changed migration script to use 'Create OR Alter' for views and sprocs

* [EC-427] Added default values for 'BillingPhone' and 'Type' fields on sprocs [dbo].[Provider_Create] and [dbo].[Provider_Update]

* [EC-427] Adjusting metadata in migration script

* [EC-427] Updated Provider sprocs SQL script files

* [EC-427] Fixed migration script

* [EC-427] Added sqlite migration

* [EC-427] Add missing Provider_Update sproc default value

* [EC-427] Added missing GO action to migration script

* [EC-428] Redirect to Edit after creating Provider

* Revert "[EC-428] Redirect to Edit after creating Provider"

This reverts commit 6347bca1ed.

* [EC-1014] Create OrganizationStatusType and add Status column to Organizations table

* [EC-1014] Added EF migrations

* [EC-1014] dotnet format

* [EC-1014] Changed Organization.Status from SMALLINT to TINYINT

* [EC-1014] Set Organization.Status default value = 1

* [EC-1014] Setting Organization.Status default value as 1

* [EC-459 / EC-428] Admin panel: Add Provider Type to list and creation flow (#2593)

* [EC-427] Add columns 'Type' and 'BillingPhone' to Provider table

* [EC-427] Provider table Type and BillingPhone MySql migrations

* [EC-427] Provider table Type and BillingPhone Postgres migrations

* [EC-427] Add mysql migration script

* [EC-427] Add mysql migration script

* [EC-427] Updated Provider sql script to include default column value

* [EC-427] Removed default value from Provider.Type column

* [EC-427] Changed migration script to include a default value constraint instead of updating the null type

* [EC-427] Updated Sql project Provider table script

* [EC-427] Changed migration script to use 'Create OR Alter' for views and sprocs

* [EC-427] Added default values for 'BillingPhone' and 'Type' fields on sprocs [dbo].[Provider_Create] and [dbo].[Provider_Update]

* [EC-427] Adjusting metadata in migration script

* [EC-427] Updated Provider sprocs SQL script files

* [EC-427] Fixed migration script

* [EC-427] Added sqlite migration

* [EC-427] Add missing Provider_Update sproc default value

* [EC-427] Added missing GO action to migration script

* [EC-459] Added Type column to Providers list

* [EC-428] Added Type, BusinessName and BillingEmail to CreateProviderModel

* [EC-428] Updated Create Provider view to include new fields

* [EC-428] Updated ProviderService to not create a ProviderUser for the type Reseller

* [EC-428] Added custom validation for Provider fields depending on selected Type

* [EC-428] Redirect to Edit after creating Provider

* [EC-428] Setting Provider status as Created for Resellers

* [EC-428] Redirect on Provider creation depending if self host server

* [EC-428] Split ProviderService.CreateAsync into two methods: CreateMspAsync and CreateResellerAsync

* [EC-428] Created ICreateProviderCommand and added service for injection on Admin.Startup

* [EC-428] Modified Provider views to use DisplayName attribute values

* [EC-428] Moved ICreateProviderCommand to Core project

* [EC-428] Adding ICreateProviderCommand injection next to IProviderService

* [EC-428] Moved CreateProviderCommand to Commercial.Core project

* [EC-459] Added Type column to Providers list

* [EC-428] Added Type, BusinessName and BillingEmail to CreateProviderModel

* [EC-428] Updated Create Provider view to include new fields

* [EC-428] Updated ProviderService to not create a ProviderUser for the type Reseller

* [EC-428] Added custom validation for Provider fields depending on selected Type

* [EC-428] Redirect to Edit after creating Provider

* [EC-428] Setting Provider status as Created for Resellers

* [EC-428] Redirect on Provider creation depending if self host server

* [EC-428] Split ProviderService.CreateAsync into two methods: CreateMspAsync and CreateResellerAsync

* [EC-428] Created ICreateProviderCommand and added service for injection on Admin.Startup

* [EC-428] Modified Provider views to use DisplayName attribute values

* [EC-428] Moved ICreateProviderCommand to Core project

* [EC-428] Adding ICreateProviderCommand injection next to IProviderService

* [EC-428] Moved CreateProviderCommand to Commercial.Core project

* [EC-428] Moved CreateProviderCommand to namespace Bit.Commercial.Core.Providers

* [EC-429] Provider details screen updated with Type, BillingPhone and Organization details (#2666)

* [EC-430] Admin portal: Update organization information screen (#2672)

* [EC-430] Added ProviderOrganizationProviderDetailsView to get Provider details for an Organization

* [EC-430] Added Provider information to Organization Edit/View on Admin panel

* [EC-430] Remove "Add to Reseller" button

* [EC-430] Removed unused property OrganizationEditModel.ClientOwnerEmail

* [EC-430] Replaced IProviderOrganizationRepository.GetProviderDetailsByOrganizationAsync with IProviderRepository.GetByOrganizationIdAsync

* [EC-430] Deleted ProviderOrganizationProviderDetails and ProviderOrganizationProviderDetailsReadByOrganizationIdQuery

* [EC-429] Only show Create/Add Existing Organization buttons for Reseller providers (#2723)

* [EC-432] Add existing Organizations to Provider (#2683)

* [EC-432] Added ProviderOrganizationUnassignedOrganizationDetails_Search stored procedure

* [EC-432] Added IProviderOrganizationRepository.SearchAsync

* [EC-432] Created controller ProviderOrganizationsController to assign Organizations to a Provider

* [EC-432] Filter existing organizations by plans Enterprise or Team

* [EC-432] Existing Organization name links to edit page

* [EC-432] EF filtering out existing organizations by plan type enterprise or teams

* [EC-432] Creating multiple ProviderOrganization records

* [EC-432] Added ProviderOrganizationUnassignedOrganizationDetails_Search stored procedure

* [EC-432] Added IProviderOrganizationRepository.SearchAsync

* [EC-432] Created controller ProviderOrganizationsController to assign Organizations to a Provider

* [EC-432] Filter existing organizations by plans Enterprise or Team

* [EC-432] Existing Organization name links to edit page

* [EC-432] EF filtering out existing organizations by plan type enterprise or teams

* [EC-432] Creating multiple ProviderOrganization records

* [EC-432] Renamed migration script and added missing sproc

* [EC-432] Saving multiple events for the created ProviderOrganizations

* [EC-432] Included unit testing for ProviderService.AddOrganizations and EventService.LogProviderOrganizationEventsAsync

* [EC-432] Removed async from NoopEventService.LogProviderOrganizationEventsAsync

* [EC-432] Remove unused dependency setup in ProviderServiceTests.AddOrganizations_Success

* [EC-432] Renamed AddOrganizations to AddOrganizationsToReseller and removed addingUserId and key arguments

* [EC-432] Added DisplayName attributes to ProviderOrganizationViewModel and used them in the view

* [EC-432] Reverted changes to input fields

* [EC-432] Moved unassigned organizations search to Organizations repo

* [EC-432] Moved AddExistingOrganization action to ProvidersController

* [EC-432] dotnet format

* [EC-432] Fixed unit test issues

* [EC-432] Removed unnecessary Html.DisplayNameFor for labels

* [EC-432] Renamed OrganizationSearchViewModel to OrganizationUnassignedToProviderSearchViewModel

* [EC-432] Modified IEventService.LogProviderOrganizationEventsAsync to receive an IEnumerable as parameter

* [EC-432] Updated IProviderOrganizationRepository and replaced CreateWithManyOrganizations method with CreateManyAsync

* [EC-432] Deleted ProviderOrganization_CreateWithManyOrganizations

* [AC-432] Simplified Organization_UnassignedToProviderSearch query

* [AC-432] Removed unnecessary setup

* [EC-432] Checking if stored procedure exists before creating

* [EC-432] Renamed migration file to recent date

* [EC-435] Admin Portal: Add new Organization creation flow UI (#2707)

* [EC-435] Created _OrganizationForm partial view. Added actions for creating an Organization assigned to a provider

* [EC-435] Remove logic for creating an organization

* [EC-435] Created partial view _OrganizationFormScripts

* [EC-435] Remove unused ReferenceEventType

* [EC-435] Added TODO comment on Organization Create

* [EC-435] Checking if Provider type is Reseller on creating new assigned organization

* [EC-435] Setting the Organization plan type as TeamsMonthly by default when adding to a provider

* [EC-435] Removing unused buttons

* [EC-435] Switched hidden fields to form submit route value

* [EC-435] Moved _OrganizationForm and _OrganizationFormScripts to Shared folder

* [EC-435] Moved Create organization actions from OrganizationsController to ProvidersController

* [EC-435] Fixing bug on saving Organization that would have BillingEmail as null

* [EC-435] Added null check to Provider

* [EC-435] Moved trial buttons script logic to Edit view

* [AC-431] Add new organization invite process (#2737)

* [EC-435] Created _OrganizationForm partial view. Added actions for creating an Organization assigned to a provider

* [EC-435] Remove logic for creating an organization

* [EC-435] Created partial view _OrganizationFormScripts

* [EC-435] Remove unused ReferenceEventType

* [EC-435] Added TODO comment on Organization Create

* [EC-435] Checking if Provider type is Reseller on creating new assigned organization

* [EC-435] Setting the Organization plan type as TeamsMonthly by default when adding to a provider

* [EC-435] Removing unused buttons

* [EC-435] Switched hidden fields to form submit route value

* [EC-435] Moved _OrganizationForm and _OrganizationFormScripts to Shared folder

* [EC-435] Moved Create organization actions from OrganizationsController to ProvidersController

* [AC-431] Added new ReferenceEventType OrganizationCreatedByAdmin

* [AC-431] Added method IOrganizationService.CreateOrganization

* [AC-431] Creating new Organization with Pending status and assigning to Provider

* [AC-431] Added method to IMailService to send invitation to initialize org

* [AC-431] Added methods CreatePendingOrganization and InitPendingOrganization to IOrganizationService

* [AC-431] Org invite includes initOrganization parameter

* [AC-431] Modified existing Accept organization user action to initialize org

* [AC-431] Updated ProvidersController method name

* [AC-431] Created OrganizationUserInitInvitedViewModel to link to 'accept-init-organization' url

* [AC-431] Added action AcceptInit to OrganizationUsersController

* [AC-431] Resend owner invite

* [AC-431] dotnet format

* [AC-431] Removed unused parameter 'addingUserId' from IProviderService.AddOrganization

* [AC-431] Removed setting manual values for CreationDate and RevisionDate

* [AC-431] Updated OrganizationService.InitPendingOrganization to throw exceptions when the Organization does not meet the required criteria

* [AC-431] Modified OrganizationUserInitInvitedViewModel to inherit properties from OrganizationUserInvitedViewModel

* [AC-431] Removed unecessary parameter check

* [AC-431] Moved method description to IOrganizationService.InitPendingOrganization

* [AC-431] Moved ApplicationCacheService.UpsertOrganizationAbilityAsync and ReferenceEventService.RaiseEventAsync to OrganizationService

* [AC-431] Creating collection after creating organization

* [EC-435] Fixing bug on saving Organization that would have BillingEmail as null

* [AC-431] Deleted OrganizationUserInitInvitedViewModel and added parameter InitOrganization to OrganizationUserInvitedViewModel.cs

* [AC-431] Checking if the user has any existing SingleOrg policies before initializing an Org

* [AC-431] Remove commented code

* [EC-435] Added null check to Provider

* [EC-435] Moved trial buttons script logic to Edit view

* [AC-431] Added EncryptedString attribute to OrganizationUserAcceptInitRequestModel.CollectionName

* [AC-431] Refactored plan check condition

* [AC-431] Remove duplicate _applicationCacheService.UpsertOrganizationAbilityAsync call

* [AC-431] Removed IMailService.SendOrganizationInitInviteEmailAsync

* [AC-431] Added parameters ClaimsPrincipal and IUserService to IOrganizationService.CreatePendingOrganization

* [AC-434] Hide Billing screen for Reseller clients (#2783)

* [AC-434] Added ProviderType to ProfileOrganizationResponseModel

* [AC-434] Migration script

* [AC-434] Fixed indentation on migration script

* [AC-434] Hiding sensitive subscription data if the user does not have permissions

* [AC-434] Fixed missing dependency in unit test

* [AC-434] Altered BillingSubscription.Amount and BillingSubscriptionUpcomingInvoice.Amount to nullable

* [AC-434] Replaced CurrentContext.ManageBilling with ViewBillingHistory, ViewSubscription, EditSubscription and EditPaymentMethods

* [AC-434] Reverted change on BillingSubscription.Amount and now setting Subscription.Items = null when User does not have permission

* [AC-434] Added ProviderOrganizationProviderDetails_ReadByUserId

* [AC-434] Added IProviderOrganizationRepository.GetManyByUserAsync

* [AC-434] Added CurrentContext.GetOrganizationProviderDetails

* [AC-434] Remove unneeded join Organization table

* [AC-1255] Search Existing Organizations by partial Email (#2830)

* [AC-1255] Added email search field input validation

* [AC-1255] Reverted added email pattern

* [AC-1255] Modified Organization search by Email to search using substring

* [AC-1276] Displaying an Organizations pending owners if the Organization is in a Pending status (#2834)

* [AC-432] Checking that an existing Organization is not assigned to any Provider before being assigned (#2840)

* [AC-432] Checking if any of the selected Organizations is already assigned to a Provider

* [AC-432] Changed ProviderOrganization_ReadByOrganizationIds to only get count

* [AC-432] Replaced IProviderOrganizationRepository.GetCountByOrganizationIdsAsync with call to IProviderOrganizationRepository.GetByOrganizationId

* [AC-432] undo new line

* [AC-432] Fixed unit test

* Revert "[AC-432] Replaced IProviderOrganizationRepository.GetCountByOrganizationIdsAsync with call to IProviderOrganizationRepository.GetByOrganizationId"

This reverts commit ee6e095e88.

# Conflicts:
#	util/Migrator/DbScripts/2023-03-22_00_ProviderAddExistingOrganizations.sql

* [AC-432] Created new migration script for ProviderOrganization_ReadCountByOrganizationIds
This commit is contained in:
Rui Tomé
2023-04-14 11:13:16 +01:00
committed by GitHub
parent 03c740dffb
commit f5a8cf5c9c
96 changed files with 8689 additions and 431 deletions

View File

@ -13,9 +13,11 @@ namespace Bit.Core.Context;
public class CurrentContext : ICurrentContext
{
private readonly IProviderOrganizationRepository _providerOrganizationRepository;
private readonly IProviderUserRepository _providerUserRepository;
private bool _builtHttpContext;
private bool _builtClaimsPrincipal;
private IEnumerable<ProviderOrganizationProviderDetails> _providerOrganizationProviderDetails;
private IEnumerable<ProviderUserOrganizationDetails> _providerUserOrganizations;
public virtual HttpContext HttpContext { get; set; }
@ -37,8 +39,11 @@ public class CurrentContext : ICurrentContext
public virtual ClientType ClientType { get; set; }
public virtual Guid? ServiceAccountOrganizationId { get; set; }
public CurrentContext(IProviderUserRepository providerUserRepository)
public CurrentContext(
IProviderOrganizationRepository providerOrganizationRepository,
IProviderUserRepository providerUserRepository)
{
_providerOrganizationRepository = providerOrganizationRepository;
_providerUserRepository = providerUserRepository;
}
@ -392,15 +397,34 @@ public class CurrentContext : ICurrentContext
&& (o.Permissions?.ManageResetPassword ?? false)) ?? false);
}
public async Task<bool> ManageBilling(Guid orgId)
public async Task<bool> ViewSubscription(Guid orgId)
{
var orgManagedByProvider = await ProviderIdForOrg(orgId) != null;
var orgManagedByMspProvider = (await GetOrganizationProviderDetails()).Any(po => po.OrganizationId == orgId && po.ProviderType == ProviderType.Msp);
return orgManagedByMspProvider
? await ProviderUserForOrgAsync(orgId)
: await OrganizationOwner(orgId);
}
public async Task<bool> EditSubscription(Guid orgId)
{
var orgManagedByProvider = (await GetOrganizationProviderDetails()).Any(po => po.OrganizationId == orgId);
return orgManagedByProvider
? await ProviderUserForOrgAsync(orgId)
: await OrganizationOwner(orgId);
}
public async Task<bool> EditPaymentMethods(Guid orgId)
{
return await EditSubscription(orgId);
}
public async Task<bool> ViewBillingHistory(Guid orgId)
{
return await EditSubscription(orgId);
}
public bool ProviderProviderAdmin(Guid providerId)
{
return Providers?.Any(o => o.Id == providerId && o.Type == ProviderUserType.ProviderAdmin) ?? false;
@ -433,7 +457,7 @@ public class CurrentContext : ICurrentContext
public async Task<bool> ProviderUserForOrgAsync(Guid orgId)
{
return (await GetProviderOrganizations()).Any(po => po.OrganizationId == orgId);
return (await GetProviderUserOrganizations()).Any(po => po.OrganizationId == orgId);
}
public async Task<Guid?> ProviderIdForOrg(Guid orgId)
@ -443,7 +467,7 @@ public class CurrentContext : ICurrentContext
return null;
}
var po = (await GetProviderOrganizations())
var po = (await GetProviderUserOrganizations())
?.FirstOrDefault(po => po.OrganizationId == orgId);
return po?.ProviderId;
@ -520,7 +544,7 @@ public class CurrentContext : ICurrentContext
};
}
protected async Task<IEnumerable<ProviderUserOrganizationDetails>> GetProviderOrganizations()
protected async Task<IEnumerable<ProviderUserOrganizationDetails>> GetProviderUserOrganizations()
{
if (_providerUserOrganizations == null && UserId.HasValue)
{
@ -529,4 +553,14 @@ public class CurrentContext : ICurrentContext
return _providerUserOrganizations;
}
protected async Task<IEnumerable<ProviderOrganizationProviderDetails>> GetOrganizationProviderDetails()
{
if (_providerOrganizationProviderDetails == null && UserId.HasValue)
{
_providerOrganizationProviderDetails = await _providerOrganizationRepository.GetManyByUserAsync(UserId.Value);
}
return _providerOrganizationProviderDetails;
}
}

View File

@ -52,7 +52,10 @@ public interface ICurrentContext
Task<bool> ManageUsers(Guid orgId);
Task<bool> ManageScim(Guid orgId);
Task<bool> ManageResetPassword(Guid orgId);
Task<bool> ManageBilling(Guid orgId);
Task<bool> ViewSubscription(Guid orgId);
Task<bool> EditSubscription(Guid orgId);
Task<bool> EditPaymentMethods(Guid orgId);
Task<bool> ViewBillingHistory(Guid orgId);
Task<bool> ProviderUserForOrgAsync(Guid orgId);
bool ProviderProviderAdmin(Guid providerId);
bool ProviderUser(Guid providerId);

View File

@ -69,6 +69,7 @@ public class Organization : ITableObject<Guid>, ISubscriber, IStorable, IStorabl
public DateTime RevisionDate { get; set; } = DateTime.UtcNow;
public int? MaxAutoscaleSeats { get; set; } = null;
public DateTime? OwnersNotifiedOfAutoscaling { get; set; } = null;
public OrganizationStatusType Status { get; set; }
public void SetNewId()
{

View File

@ -0,0 +1,7 @@
namespace Bit.Core.Enums;
public enum OrganizationStatusType : byte
{
Pending = 0,
Created = 1
}

View File

@ -1,7 +1,11 @@
namespace Bit.Core.Enums.Provider;
using System.ComponentModel.DataAnnotations;
namespace Bit.Core.Enums.Provider;
public enum ProviderType : byte
{
[Display(ShortName = "MSP", Name = "Managed Service Provider", Description = "Access to clients organization")]
Msp = 0,
[Display(ShortName = "Reseller", Name = "Reseller", Description = "Access to clients billing")]
Reseller = 1,
}

View File

@ -40,6 +40,8 @@ public enum ReferenceEventType
CollectionCreated,
[EnumMember(Value = "organization-edited-by-admin")]
OrganizationEditedByAdmin,
[EnumMember(Value = "organization-created-by-admin")]
OrganizationCreatedByAdmin,
[EnumMember(Value = "sm-service-account-accessed-secret")]
SmServiceAccountAccessedSecret,
}

View File

@ -1,4 +1,6 @@
namespace Bit.Core.Models.Data.Organizations.OrganizationUsers;
using Bit.Core.Enums.Provider;
namespace Bit.Core.Models.Data.Organizations.OrganizationUsers;
public class OrganizationUserOrganizationDetails
{
@ -36,6 +38,7 @@ public class OrganizationUserOrganizationDetails
public string PrivateKey { get; set; }
public Guid? ProviderId { get; set; }
public string ProviderName { get; set; }
public ProviderType? ProviderType { get; set; }
public string FamilySponsorshipFriendlyName { get; set; }
public string SsoConfig { get; set; }
public DateTime? FamilySponsorshipLastSyncDate { get; set; }

View File

@ -1,4 +1,6 @@
namespace Bit.Core.Models.Data;
using Bit.Core.Enums;
namespace Bit.Core.Models.Data;
public class ProviderOrganizationOrganizationDetails
{
@ -13,4 +15,5 @@ public class ProviderOrganizationOrganizationDetails
public int UserCount { get; set; }
public int? Seats { get; set; }
public string Plan { get; set; }
public OrganizationStatusType Status { get; set; }
}

View File

@ -0,0 +1,12 @@
using Bit.Core.Enums.Provider;
namespace Bit.Core.Models.Data;
public class ProviderOrganizationProviderDetails
{
public Guid Id { get; set; }
public Guid ProviderId { get; set; }
public Guid OrganizationId { get; set; }
public string ProviderName { get; set; }
public ProviderType ProviderType { get; set; }
}

View File

@ -9,12 +9,14 @@ public class OrganizationUserInvitedViewModel : BaseTitleContactUsMailModel
public string OrganizationNameUrlEncoded { get; set; }
public string Token { get; set; }
public string ExpirationDate { get; set; }
public bool InitOrganization { get; set; }
public string Url => string.Format("{0}/accept-organization?organizationId={1}&" +
"organizationUserId={2}&email={3}&organizationName={4}&token={5}",
"organizationUserId={2}&email={3}&organizationName={4}&token={5}&initOrganization={6}",
WebVaultUrl,
OrganizationId,
OrganizationUserId,
Email,
OrganizationNameUrlEncoded,
Token);
Token,
InitOrganization);
}

View File

@ -0,0 +1,9 @@
using Bit.Core.Entities.Provider;
namespace Bit.Core.Providers.Interfaces;
public interface ICreateProviderCommand
{
Task CreateMspAsync(Provider provider, string ownerEmail);
Task CreateResellerAsync(Provider provider);
}

View File

@ -13,4 +13,5 @@ public interface IOrganizationRepository : IRepository<Organization, Guid>
Task<ICollection<OrganizationAbility>> GetManyAbilitiesAsync();
Task<Organization> GetByLicenseKeyAsync(string licenseKey);
Task<SelfHostedOrganizationDetails> GetSelfHostedOrganizationDetailsById(Guid id);
Task<ICollection<Organization>> SearchUnassignedToProviderAsync(string name, string ownerEmail, int skip, int take);
}

View File

@ -5,6 +5,9 @@ namespace Bit.Core.Repositories;
public interface IProviderOrganizationRepository : IRepository<ProviderOrganization, Guid>
{
Task<ICollection<ProviderOrganization>> CreateManyAsync(IEnumerable<ProviderOrganization> providerOrganizations);
Task<ICollection<ProviderOrganizationOrganizationDetails>> GetManyDetailsByProviderAsync(Guid providerId);
Task<ProviderOrganization> GetByOrganizationId(Guid organizationId);
Task<IEnumerable<ProviderOrganizationProviderDetails>> GetManyByUserAsync(Guid userId);
Task<int> GetCountByOrganizationIdsAsync(IEnumerable<Guid> organizationIds);
}

View File

@ -5,6 +5,7 @@ namespace Bit.Core.Repositories;
public interface IProviderRepository : IRepository<Provider, Guid>
{
Task<Provider> GetByOrganizationIdAsync(Guid organizationId);
Task<ICollection<Provider>> SearchAsync(string name, string userEmail, int skip, int take);
Task<ICollection<ProviderAbility>> GetManyAbilitiesAsync();
}

View File

@ -25,6 +25,7 @@ public interface IEventService
Task LogProviderUserEventAsync(ProviderUser providerUser, EventType type, DateTime? date = null);
Task LogProviderUsersEventAsync(IEnumerable<(ProviderUser, EventType, DateTime?)> events);
Task LogProviderOrganizationEventAsync(ProviderOrganization providerOrganization, EventType type, DateTime? date = null);
Task LogProviderOrganizationEventsAsync(IEnumerable<(ProviderOrganization, EventType, DateTime?)> events);
Task LogOrganizationDomainEventAsync(OrganizationDomain organizationDomain, EventType type, DateTime? date = null);
Task LogOrganizationDomainEventAsync(OrganizationDomain organizationDomain, EventType type, EventSystemUser systemUser, DateTime? date = null);
Task LogServiceAccountSecretEventAsync(Guid serviceAccountId, Secret secret, EventType type, DateTime? date = null);

View File

@ -15,8 +15,8 @@ public interface IMailService
Task SendTwoFactorEmailAsync(string email, string token);
Task SendNoMasterPasswordHintEmailAsync(string email);
Task SendMasterPasswordHintEmailAsync(string email, string hint);
Task SendOrganizationInviteEmailAsync(string organizationName, OrganizationUser orgUser, ExpiringToken token, bool isFreeOrg);
Task BulkSendOrganizationInviteEmailAsync(string organizationName, IEnumerable<(OrganizationUser orgUser, ExpiringToken token)> invites, bool isFreeOrg);
Task SendOrganizationInviteEmailAsync(string organizationName, OrganizationUser orgUser, ExpiringToken token, bool isFreeOrg, bool initOrganization = false);
Task BulkSendOrganizationInviteEmailAsync(string organizationName, IEnumerable<(OrganizationUser orgUser, ExpiringToken token)> invites, bool isFreeOrg, bool initOrganization = false);
Task SendOrganizationMaxSeatLimitReachedEmailAsync(Organization organization, int maxSeatCount, IEnumerable<string> ownerEmails);
Task SendOrganizationAutoscaledEmailAsync(Organization organization, int initialSeatCount, IEnumerable<string> ownerEmails);
Task SendOrganizationAcceptedEmailAsync(Organization organization, string userIdentifier, IEnumerable<string> adminEmails);

View File

@ -1,4 +1,5 @@
using Bit.Core.Entities;
using System.Security.Claims;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Models.Business;
using Bit.Core.Models.Data;
@ -37,9 +38,8 @@ public interface IOrganizationService
Task<OrganizationUser> InviteUserAsync(Guid organizationId, EventSystemUser systemUser, string email,
OrganizationUserType type, bool accessAll, string externalId, IEnumerable<CollectionAccessSelection> collections, IEnumerable<Guid> groups);
Task<IEnumerable<Tuple<OrganizationUser, string>>> ResendInvitesAsync(Guid organizationId, Guid? invitingUserId, IEnumerable<Guid> organizationUsersId);
Task ResendInviteAsync(Guid organizationId, Guid? invitingUserId, Guid organizationUserId);
Task<OrganizationUser> AcceptUserAsync(Guid organizationUserId, User user, string token,
IUserService userService);
Task ResendInviteAsync(Guid organizationId, Guid? invitingUserId, Guid organizationUserId, bool initOrganization = false);
Task<OrganizationUser> AcceptUserAsync(Guid organizationUserId, User user, string token, IUserService userService);
Task<OrganizationUser> AcceptUserAsync(string orgIdentifier, User user, IUserService userService);
Task<OrganizationUser> ConfirmUserAsync(Guid organizationId, Guid organizationUserId, string key,
Guid confirmingUserId, IUserService userService);
@ -69,5 +69,13 @@ public interface IOrganizationService
Task RestoreUserAsync(OrganizationUser organizationUser, EventSystemUser systemUser, IUserService userService);
Task<List<Tuple<OrganizationUser, string>>> RestoreUsersAsync(Guid organizationId,
IEnumerable<Guid> organizationUserIds, Guid? restoringUserId, IUserService userService);
Task CreatePendingOrganization(Organization organization, string ownerEmail, ClaimsPrincipal user, IUserService userService, bool salesAssistedTrialStarted);
/// <summary>
/// Update an Organization entry by setting the public/private keys, set it as 'Enabled' and move the Status from 'Pending' to 'Created'.
/// </summary>
/// <remarks>
/// This method must target a disabled Organization that has null keys and status as 'Pending'.
/// </remarks>
Task InitPendingOrganization(Guid userId, Guid organizationId, string publicKey, string privateKey, string collectionName);
Task ReplaceAndUpdateCacheAsync(Organization org, EventType? orgEvent = null);
}

View File

@ -7,7 +7,6 @@ namespace Bit.Core.Services;
public interface IProviderService
{
Task CreateAsync(string ownerEmail);
Task<Provider> CompleteSetupAsync(Provider provider, Guid ownerUserId, string token, string key);
Task UpdateAsync(Provider provider, bool updateBilling = false);
@ -20,11 +19,13 @@ public interface IProviderService
Task<List<Tuple<ProviderUser, string>>> DeleteUsersAsync(Guid providerId, IEnumerable<Guid> providerUserIds,
Guid deletingUserId);
Task AddOrganization(Guid providerId, Guid organizationId, Guid addingUserId, string key);
Task AddOrganization(Guid providerId, Guid organizationId, string key);
Task AddOrganizationsToReseller(Guid providerId, IEnumerable<Guid> organizationIds);
Task<ProviderOrganization> CreateOrganizationAsync(Guid providerId, OrganizationSignup organizationSignup,
string clientOwnerEmail, User user);
Task RemoveOrganizationAsync(Guid providerId, Guid providerOrganizationId, Guid removingUserId);
Task LogProviderAccessToOrganizationAsync(Guid organizationId);
Task ResendProviderSetupInviteEmailAsync(Guid providerId, Guid ownerId);
Task SendProviderSetupInviteEmailAsync(Provider provider, string ownerEmail);
}

View File

@ -332,26 +332,38 @@ public class EventService : IEventService
public async Task LogProviderOrganizationEventAsync(ProviderOrganization providerOrganization, EventType type,
DateTime? date = null)
{
await LogProviderOrganizationEventsAsync(new[] { (providerOrganization, type, date) });
}
public async Task LogProviderOrganizationEventsAsync(IEnumerable<(ProviderOrganization, EventType, DateTime?)> events)
{
var providerAbilities = await _applicationCacheService.GetProviderAbilitiesAsync();
if (!CanUseProviderEvents(providerAbilities, providerOrganization.ProviderId))
var eventMessages = new List<IEvent>();
foreach (var (providerOrganization, type, date) in events)
{
return;
if (!CanUseProviderEvents(providerAbilities, providerOrganization.ProviderId))
{
continue;
}
var e = new EventMessage(_currentContext)
{
ProviderId = providerOrganization.ProviderId,
ProviderOrganizationId = providerOrganization.Id,
Type = type,
ActingUserId = _currentContext?.UserId,
Date = date.GetValueOrDefault(DateTime.UtcNow)
};
eventMessages.Add(e);
}
var e = new EventMessage(_currentContext)
{
ProviderId = providerOrganization.ProviderId,
ProviderOrganizationId = providerOrganization.Id,
Type = type,
ActingUserId = _currentContext?.UserId,
Date = date.GetValueOrDefault(DateTime.UtcNow)
};
await _eventWriteService.CreateAsync(e);
await _eventWriteService.CreateManyAsync(eventMessages);
}
public async Task LogOrganizationDomainEventAsync(OrganizationDomain organizationDomain, EventType type,
DateTime? date = null)
DateTime? date = null)
{
var orgAbilities = await _applicationCacheService.GetOrganizationAbilitiesAsync();
if (!CanUseEvents(orgAbilities, organizationDomain.OrganizationId))

View File

@ -203,10 +203,10 @@ public class HandlebarsMailService : IMailService
await _mailDeliveryService.SendEmailAsync(message);
}
public Task SendOrganizationInviteEmailAsync(string organizationName, OrganizationUser orgUser, ExpiringToken token, bool isFreeOrg) =>
BulkSendOrganizationInviteEmailAsync(organizationName, new[] { (orgUser, token) }, isFreeOrg);
public Task SendOrganizationInviteEmailAsync(string organizationName, OrganizationUser orgUser, ExpiringToken token, bool isFreeOrg, bool initOrganization = false) =>
BulkSendOrganizationInviteEmailAsync(organizationName, new[] { (orgUser, token) }, isFreeOrg, initOrganization);
public async Task BulkSendOrganizationInviteEmailAsync(string organizationName, IEnumerable<(OrganizationUser orgUser, ExpiringToken token)> invites, bool isFreeOrg)
public async Task BulkSendOrganizationInviteEmailAsync(string organizationName, IEnumerable<(OrganizationUser orgUser, ExpiringToken token)> invites, bool isFreeOrg, bool initOrganization = false)
{
MailQueueMessage CreateMessage(string email, object model)
{
@ -229,6 +229,7 @@ public class HandlebarsMailService : IMailService
OrganizationNameUrlEncoded = WebUtility.UrlEncode(organizationName),
WebVaultUrl = _globalSettings.BaseServiceUri.VaultWithHash,
SiteName = _globalSettings.SiteName,
InitOrganization = initOrganization
}
));

View File

@ -1,4 +1,5 @@
using System.Text.Json;
using System.Security.Claims;
using System.Text.Json;
using Bit.Core.Context;
using Bit.Core.Entities;
using Bit.Core.Enums;
@ -598,7 +599,7 @@ public class OrganizationService : IOrganizationService
bool provider = false)
{
var plan = StaticStore.Plans.FirstOrDefault(p => p.Type == signup.Plan);
if (!(plan is { LegacyYear: null }))
if (plan is not { LegacyYear: null })
{
throw new BadRequestException("Invalid plan selected.");
}
@ -649,6 +650,7 @@ public class OrganizationService : IOrganizationService
PrivateKey = signup.PrivateKey,
CreationDate = DateTime.UtcNow,
RevisionDate = DateTime.UtcNow,
Status = OrganizationStatusType.Created
};
if (plan.Type == PlanType.Free && !provider)
@ -748,7 +750,8 @@ public class OrganizationService : IOrganizationService
PublicKey = publicKey,
PrivateKey = privateKey,
CreationDate = DateTime.UtcNow,
RevisionDate = DateTime.UtcNow
RevisionDate = DateTime.UtcNow,
Status = OrganizationStatusType.Created
};
var result = await SignUpAsync(organization, owner.Id, ownerKey, collectionName, false);
@ -1167,14 +1170,14 @@ public class OrganizationService : IOrganizationService
continue;
}
await SendInviteAsync(orgUser, org);
await SendInviteAsync(orgUser, org, false);
result.Add(Tuple.Create(orgUser, ""));
}
return result;
}
public async Task ResendInviteAsync(Guid organizationId, Guid? invitingUserId, Guid organizationUserId)
public async Task ResendInviteAsync(Guid organizationId, Guid? invitingUserId, Guid organizationUserId, bool initOrganization = false)
{
var orgUser = await _organizationUserRepository.GetByIdAsync(organizationUserId);
if (orgUser == null || orgUser.OrganizationId != organizationId ||
@ -1184,7 +1187,7 @@ public class OrganizationService : IOrganizationService
}
var org = await GetOrgById(orgUser.OrganizationId);
await SendInviteAsync(orgUser, org);
await SendInviteAsync(orgUser, org, initOrganization);
}
private async Task SendInvitesAsync(IEnumerable<OrganizationUser> orgUsers, Organization organization)
@ -1196,13 +1199,13 @@ public class OrganizationService : IOrganizationService
orgUsers.Select(o => (o, new ExpiringToken(MakeToken(o), DateTime.UtcNow.AddDays(5)))), organization.PlanType == PlanType.Free);
}
private async Task SendInviteAsync(OrganizationUser orgUser, Organization organization)
private async Task SendInviteAsync(OrganizationUser orgUser, Organization organization, bool initOrganization)
{
var now = DateTime.UtcNow;
var nowMillis = CoreHelpers.ToEpocMilliseconds(now);
var token = _dataProtector.Protect(
$"OrganizationUserInvite {orgUser.Id} {orgUser.Email} {nowMillis}");
await _mailService.SendOrganizationInviteEmailAsync(organization.Name, orgUser, new ExpiringToken(token, now.AddDays(5)), organization.PlanType == PlanType.Free);
await _mailService.SendOrganizationInviteEmailAsync(organization.Name, orgUser, new ExpiringToken(token, now.AddDays(5)), organization.PlanType == PlanType.Free, initOrganization);
}
public async Task<OrganizationUser> AcceptUserAsync(Guid organizationUserId, User user, string token,
@ -2425,4 +2428,89 @@ public class OrganizationService : IOrganizationService
return status;
}
public async Task CreatePendingOrganization(Organization organization, string ownerEmail, ClaimsPrincipal user, IUserService userService, bool salesAssistedTrialStarted)
{
var plan = StaticStore.Plans.FirstOrDefault(p => p.Type == organization.PlanType);
if (plan is not { LegacyYear: null })
{
throw new BadRequestException("Invalid plan selected.");
}
if (plan.Disabled)
{
throw new BadRequestException("Plan not found.");
}
organization.Id = CoreHelpers.GenerateComb();
organization.Enabled = false;
organization.Status = OrganizationStatusType.Pending;
await SignUpAsync(organization, default, null, null, true);
var ownerOrganizationUser = new OrganizationUser
{
OrganizationId = organization.Id,
UserId = null,
Email = ownerEmail,
Key = null,
Type = OrganizationUserType.Owner,
Status = OrganizationUserStatusType.Invited,
AccessAll = true
};
await _organizationUserRepository.CreateAsync(ownerOrganizationUser);
await SendInviteAsync(ownerOrganizationUser, organization, true);
await _eventService.LogOrganizationUserEventAsync(ownerOrganizationUser, EventType.OrganizationUser_Invited);
await _referenceEventService.RaiseEventAsync(new ReferenceEvent(ReferenceEventType.OrganizationCreatedByAdmin, organization)
{
EventRaisedByUser = userService.GetUserName(user),
SalesAssistedTrialStarted = salesAssistedTrialStarted,
});
}
public async Task InitPendingOrganization(Guid userId, Guid organizationId, string publicKey, string privateKey, string collectionName)
{
await ValidateSignUpPoliciesAsync(userId);
var org = await GetOrgById(organizationId);
if (org.Enabled)
{
throw new BadRequestException("Organization is already enabled.");
}
if (org.Status != OrganizationStatusType.Pending)
{
throw new BadRequestException("Organization is not on a Pending status.");
}
if (!string.IsNullOrEmpty(org.PublicKey))
{
throw new BadRequestException("Organization already has a Public Key.");
}
if (!string.IsNullOrEmpty(org.PrivateKey))
{
throw new BadRequestException("Organization already has a Private Key.");
}
org.Enabled = true;
org.Status = OrganizationStatusType.Created;
org.PublicKey = publicKey;
org.PrivateKey = privateKey;
await UpdateAsync(org);
if (!string.IsNullOrWhiteSpace(collectionName))
{
var defaultCollection = new Collection
{
Name = collectionName,
OrganizationId = org.Id
};
await _collectionRepository.CreateAsync(defaultCollection);
}
}
}

View File

@ -70,8 +70,13 @@ public class NoopEventService : IEventService
return Task.FromResult(0);
}
public Task LogProviderOrganizationEventsAsync(IEnumerable<(ProviderOrganization, EventType, DateTime?)> events)
{
return Task.FromResult(0);
}
public Task LogOrganizationDomainEventAsync(OrganizationDomain organizationDomain, EventType type,
DateTime? date = null)
DateTime? date = null)
{
return Task.FromResult(0);
}

View File

@ -52,12 +52,12 @@ public class NoopMailService : IMailService
return Task.FromResult(0);
}
public Task SendOrganizationInviteEmailAsync(string organizationName, OrganizationUser orgUser, ExpiringToken token, bool isFreeOrg)
public Task SendOrganizationInviteEmailAsync(string organizationName, OrganizationUser orgUser, ExpiringToken token, bool isFreeOrg, bool initOrganization = false)
{
return Task.FromResult(0);
}
public Task BulkSendOrganizationInviteEmailAsync(string organizationName, IEnumerable<(OrganizationUser orgUser, ExpiringToken token)> invites, bool isFreeOrg)
public Task BulkSendOrganizationInviteEmailAsync(string organizationName, IEnumerable<(OrganizationUser orgUser, ExpiringToken token)> invites, bool isFreeOrg, bool initOrganization = false)
{
return Task.FromResult(0);
}

View File

@ -7,8 +7,6 @@ namespace Bit.Core.Services;
public class NoopProviderService : IProviderService
{
public Task CreateAsync(string ownerEmail) => throw new NotImplementedException();
public Task<Provider> CompleteSetupAsync(Provider provider, Guid ownerUserId, string token, string key) => throw new NotImplementedException();
public Task UpdateAsync(Provider provider, bool updateBilling = false) => throw new NotImplementedException();
@ -25,7 +23,9 @@ public class NoopProviderService : IProviderService
public Task<List<Tuple<ProviderUser, string>>> DeleteUsersAsync(Guid providerId, IEnumerable<Guid> providerUserIds, Guid deletingUserId) => throw new NotImplementedException();
public Task AddOrganization(Guid providerId, Guid organizationId, Guid addingUserId, string key) => throw new NotImplementedException();
public Task AddOrganization(Guid providerId, Guid organizationId, string key) => throw new NotImplementedException();
public Task AddOrganizationsToReseller(Guid providerId, IEnumerable<Guid> organizationIds) => throw new NotImplementedException();
public Task<ProviderOrganization> CreateOrganizationAsync(Guid providerId, OrganizationSignup organizationSignup, string clientOwnerEmail, User user) => throw new NotImplementedException();
@ -34,4 +34,5 @@ public class NoopProviderService : IProviderService
public Task LogProviderAccessToOrganizationAsync(Guid organizationId) => throw new NotImplementedException();
public Task ResendProviderSetupInviteEmailAsync(Guid providerId, Guid userId) => throw new NotImplementedException();
public Task SendProviderSetupInviteEmailAsync(Provider provider, string ownerEmail) => throw new NotImplementedException();
}