2023-04-14 11:13:16 +01:00
|
|
|
|
using System.Security.Claims;
|
|
|
|
|
|
using System.Text.Json;
|
2023-04-18 18:31:00 -05:00
|
|
|
|
using Bit.Core.AdminConsole.Models.OrganizationConnectionConfigs;
|
2023-04-14 13:25:56 -04:00
|
|
|
|
using Bit.Core.Auth.Enums;
|
|
|
|
|
|
using Bit.Core.Auth.Models.Business;
|
|
|
|
|
|
using Bit.Core.Auth.Repositories;
|
2017-03-03 00:07:11 -05:00
|
|
|
|
using Bit.Core.Context;
|
2022-01-11 10:40:51 +01:00
|
|
|
|
using Bit.Core.Entities;
|
2017-04-10 10:44:27 -04:00
|
|
|
|
using Bit.Core.Enums;
|
2023-01-26 11:51:26 -05:00
|
|
|
|
using Bit.Core.Enums.Provider;
|
2017-03-03 00:07:11 -05:00
|
|
|
|
using Bit.Core.Exceptions;
|
|
|
|
|
|
using Bit.Core.Models.Business;
|
2017-05-11 14:52:35 -04:00
|
|
|
|
using Bit.Core.Models.Data;
|
2022-06-08 08:44:28 -05:00
|
|
|
|
using Bit.Core.Models.Data.Organizations.Policies;
|
2017-03-03 00:07:11 -05:00
|
|
|
|
using Bit.Core.Repositories;
|
2021-02-22 15:35:16 -06:00
|
|
|
|
using Bit.Core.Settings;
|
2023-04-18 14:05:17 +02:00
|
|
|
|
using Bit.Core.Tools.Enums;
|
|
|
|
|
|
using Bit.Core.Tools.Models.Business;
|
|
|
|
|
|
using Bit.Core.Tools.Services;
|
2017-03-03 00:07:11 -05:00
|
|
|
|
using Bit.Core.Utilities;
|
2017-03-23 00:17:34 -04:00
|
|
|
|
using Microsoft.AspNetCore.DataProtection;
|
2021-09-23 06:36:08 -04:00
|
|
|
|
using Microsoft.Extensions.Logging;
|
2017-04-04 10:13:16 -04:00
|
|
|
|
using Stripe;
|
2017-03-03 00:07:11 -05:00
|
|
|
|
|
|
|
|
|
|
namespace Bit.Core.Services;
|
2022-08-29 16:06:55 -04:00
|
|
|
|
|
2017-03-03 00:07:11 -05:00
|
|
|
|
public class OrganizationService : IOrganizationService
|
|
|
|
|
|
{
|
|
|
|
|
|
private readonly IOrganizationRepository _organizationRepository;
|
|
|
|
|
|
private readonly IOrganizationUserRepository _organizationUserRepository;
|
2017-04-27 09:19:30 -04:00
|
|
|
|
private readonly ICollectionRepository _collectionRepository;
|
2017-03-04 21:28:41 -05:00
|
|
|
|
private readonly IUserRepository _userRepository;
|
2017-05-13 12:00:40 -04:00
|
|
|
|
private readonly IGroupRepository _groupRepository;
|
2017-03-23 00:17:34 -04:00
|
|
|
|
private readonly IDataProtector _dataProtector;
|
|
|
|
|
|
private readonly IMailService _mailService;
|
2017-05-26 22:52:50 -04:00
|
|
|
|
private readonly IPushNotificationService _pushNotificationService;
|
|
|
|
|
|
private readonly IPushRegistrationService _pushRegistrationService;
|
2017-08-11 08:57:31 -04:00
|
|
|
|
private readonly IDeviceRepository _deviceRepository;
|
2017-08-14 20:57:45 -04:00
|
|
|
|
private readonly ILicensingService _licensingService;
|
2017-12-01 16:00:30 -05:00
|
|
|
|
private readonly IEventService _eventService;
|
2017-08-15 16:11:08 -04:00
|
|
|
|
private readonly IInstallationRepository _installationRepository;
|
2017-12-19 16:02:39 -05:00
|
|
|
|
private readonly IApplicationCacheService _applicationCacheService;
|
2019-02-08 23:53:09 -05:00
|
|
|
|
private readonly IPaymentService _paymentService;
|
2020-01-15 15:00:54 -05:00
|
|
|
|
private readonly IPolicyRepository _policyRepository;
|
2023-05-12 08:22:19 +01:00
|
|
|
|
private readonly IPolicyService _policyService;
|
2020-07-22 09:38:39 -04:00
|
|
|
|
private readonly ISsoConfigRepository _ssoConfigRepository;
|
2020-08-26 14:12:04 -04:00
|
|
|
|
private readonly ISsoUserRepository _ssoUserRepository;
|
2020-07-07 12:01:34 -04:00
|
|
|
|
private readonly IReferenceEventService _referenceEventService;
|
2021-09-23 06:36:08 -04:00
|
|
|
|
private readonly IGlobalSettings _globalSettings;
|
2022-05-10 17:12:09 -04:00
|
|
|
|
private readonly IOrganizationApiKeyRepository _organizationApiKeyRepository;
|
2022-07-14 15:58:48 -04:00
|
|
|
|
private readonly IOrganizationConnectionRepository _organizationConnectionRepository;
|
2021-07-01 14:31:05 +02:00
|
|
|
|
private readonly ICurrentContext _currentContext;
|
2021-09-23 06:36:08 -04:00
|
|
|
|
private readonly ILogger<OrganizationService> _logger;
|
2023-01-26 11:51:26 -05:00
|
|
|
|
private readonly IProviderOrganizationRepository _providerOrganizationRepository;
|
|
|
|
|
|
private readonly IProviderUserRepository _providerUserRepository;
|
2022-08-29 16:06:55 -04:00
|
|
|
|
|
2017-03-03 00:07:11 -05:00
|
|
|
|
public OrganizationService(
|
|
|
|
|
|
IOrganizationRepository organizationRepository,
|
2017-03-04 21:28:41 -05:00
|
|
|
|
IOrganizationUserRepository organizationUserRepository,
|
2017-04-27 09:19:30 -04:00
|
|
|
|
ICollectionRepository collectionRepository,
|
2017-03-23 00:17:34 -04:00
|
|
|
|
IUserRepository userRepository,
|
2017-05-13 12:00:40 -04:00
|
|
|
|
IGroupRepository groupRepository,
|
2017-03-23 00:17:34 -04:00
|
|
|
|
IDataProtectionProvider dataProtectionProvider,
|
2017-04-21 14:22:32 -04:00
|
|
|
|
IMailService mailService,
|
2017-05-26 22:52:50 -04:00
|
|
|
|
IPushNotificationService pushNotificationService,
|
2017-08-11 08:57:31 -04:00
|
|
|
|
IPushRegistrationService pushRegistrationService,
|
2017-08-14 20:57:45 -04:00
|
|
|
|
IDeviceRepository deviceRepository,
|
|
|
|
|
|
ILicensingService licensingService,
|
2017-12-01 16:00:30 -05:00
|
|
|
|
IEventService eventService,
|
2017-08-15 16:11:08 -04:00
|
|
|
|
IInstallationRepository installationRepository,
|
2017-12-19 16:02:39 -05:00
|
|
|
|
IApplicationCacheService applicationCacheService,
|
2019-02-08 23:53:09 -05:00
|
|
|
|
IPaymentService paymentService,
|
2020-01-15 15:00:54 -05:00
|
|
|
|
IPolicyRepository policyRepository,
|
2023-05-12 08:22:19 +01:00
|
|
|
|
IPolicyService policyService,
|
2020-07-22 09:38:39 -04:00
|
|
|
|
ISsoConfigRepository ssoConfigRepository,
|
2020-08-26 14:12:04 -04:00
|
|
|
|
ISsoUserRepository ssoUserRepository,
|
2020-07-07 12:01:34 -04:00
|
|
|
|
IReferenceEventService referenceEventService,
|
2021-09-23 06:36:08 -04:00
|
|
|
|
IGlobalSettings globalSettings,
|
2022-05-10 17:12:09 -04:00
|
|
|
|
IOrganizationApiKeyRepository organizationApiKeyRepository,
|
2022-07-14 15:58:48 -04:00
|
|
|
|
IOrganizationConnectionRepository organizationConnectionRepository,
|
2021-09-23 06:36:08 -04:00
|
|
|
|
ICurrentContext currentContext,
|
2023-01-26 11:51:26 -05:00
|
|
|
|
ILogger<OrganizationService> logger,
|
|
|
|
|
|
IProviderOrganizationRepository providerOrganizationRepository,
|
|
|
|
|
|
IProviderUserRepository providerUserRepository)
|
2017-03-03 00:07:11 -05:00
|
|
|
|
{
|
|
|
|
|
|
_organizationRepository = organizationRepository;
|
|
|
|
|
|
_organizationUserRepository = organizationUserRepository;
|
2017-04-27 09:19:30 -04:00
|
|
|
|
_collectionRepository = collectionRepository;
|
2017-03-04 21:28:41 -05:00
|
|
|
|
_userRepository = userRepository;
|
2017-05-13 12:00:40 -04:00
|
|
|
|
_groupRepository = groupRepository;
|
2017-03-23 00:17:34 -04:00
|
|
|
|
_dataProtector = dataProtectionProvider.CreateProtector("OrganizationServiceDataProtector");
|
|
|
|
|
|
_mailService = mailService;
|
2017-05-26 22:52:50 -04:00
|
|
|
|
_pushNotificationService = pushNotificationService;
|
|
|
|
|
|
_pushRegistrationService = pushRegistrationService;
|
2017-08-11 08:57:31 -04:00
|
|
|
|
_deviceRepository = deviceRepository;
|
2017-08-14 20:57:45 -04:00
|
|
|
|
_licensingService = licensingService;
|
2017-12-01 16:00:30 -05:00
|
|
|
|
_eventService = eventService;
|
2017-08-15 16:11:08 -04:00
|
|
|
|
_installationRepository = installationRepository;
|
2017-12-19 16:02:39 -05:00
|
|
|
|
_applicationCacheService = applicationCacheService;
|
2019-02-08 23:53:09 -05:00
|
|
|
|
_paymentService = paymentService;
|
2020-01-15 15:00:54 -05:00
|
|
|
|
_policyRepository = policyRepository;
|
2023-05-12 08:22:19 +01:00
|
|
|
|
_policyService = policyService;
|
2020-07-22 09:38:39 -04:00
|
|
|
|
_ssoConfigRepository = ssoConfigRepository;
|
2020-08-26 14:12:04 -04:00
|
|
|
|
_ssoUserRepository = ssoUserRepository;
|
2020-07-07 12:01:34 -04:00
|
|
|
|
_referenceEventService = referenceEventService;
|
2017-08-14 20:57:45 -04:00
|
|
|
|
_globalSettings = globalSettings;
|
2022-05-10 17:12:09 -04:00
|
|
|
|
_organizationApiKeyRepository = organizationApiKeyRepository;
|
2022-07-14 15:58:48 -04:00
|
|
|
|
_organizationConnectionRepository = organizationConnectionRepository;
|
2021-07-01 14:31:05 +02:00
|
|
|
|
_currentContext = currentContext;
|
2021-09-23 06:36:08 -04:00
|
|
|
|
_logger = logger;
|
2023-01-26 11:51:26 -05:00
|
|
|
|
_providerOrganizationRepository = providerOrganizationRepository;
|
|
|
|
|
|
_providerUserRepository = providerUserRepository;
|
2018-12-31 13:34:02 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
2020-07-07 12:01:34 -04:00
|
|
|
|
public async Task ReplacePaymentMethodAsync(Guid organizationId, string paymentToken,
|
|
|
|
|
|
PaymentMethodType paymentMethodType, TaxInfo taxInfo)
|
2022-08-29 15:53:48 -04:00
|
|
|
|
{
|
2020-07-07 12:01:34 -04:00
|
|
|
|
var organization = await GetOrgById(organizationId);
|
2020-03-27 14:36:37 -04:00
|
|
|
|
if (organization == null)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2020-07-07 12:01:34 -04:00
|
|
|
|
throw new NotFoundException();
|
2017-04-08 18:15:20 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2017-04-10 18:20:21 -04:00
|
|
|
|
await _paymentService.SaveTaxInfoAsync(organization, taxInfo);
|
|
|
|
|
|
var updated = await _paymentService.UpdatePaymentMethodAsync(organization,
|
|
|
|
|
|
paymentMethodType, paymentToken);
|
|
|
|
|
|
if (updated)
|
2017-04-10 16:42:53 -04:00
|
|
|
|
{
|
2023-02-24 07:54:19 +10:00
|
|
|
|
await ReplaceAndUpdateCacheAsync(organization);
|
2022-08-29 14:53:16 -04:00
|
|
|
|
}
|
2017-04-10 16:42:53 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2019-02-08 23:53:09 -05:00
|
|
|
|
public async Task CancelSubscriptionAsync(Guid organizationId, bool? endOfPeriod = null)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2019-02-08 23:53:09 -05:00
|
|
|
|
var organization = await GetOrgById(organizationId);
|
|
|
|
|
|
if (organization == null)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2019-02-08 23:53:09 -05:00
|
|
|
|
throw new NotFoundException();
|
2017-04-10 16:42:53 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2017-12-19 16:02:39 -05:00
|
|
|
|
var eop = endOfPeriod.GetValueOrDefault(true);
|
2019-08-09 23:56:26 -04:00
|
|
|
|
if (!endOfPeriod.HasValue && organization.ExpirationDate.HasValue &&
|
|
|
|
|
|
organization.ExpirationDate.Value < DateTime.UtcNow)
|
2017-04-10 09:36:21 -04:00
|
|
|
|
{
|
2017-12-19 16:02:39 -05:00
|
|
|
|
eop = false;
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2017-12-19 16:02:39 -05:00
|
|
|
|
await _paymentService.CancelSubscriptionAsync(organization, eop);
|
2019-02-08 23:53:09 -05:00
|
|
|
|
await _referenceEventService.RaiseEventAsync(
|
2023-05-16 16:21:57 +02:00
|
|
|
|
new ReferenceEvent(ReferenceEventType.CancelSubscription, organization, _currentContext)
|
2022-08-29 15:53:48 -04:00
|
|
|
|
{
|
2017-04-10 09:36:21 -04:00
|
|
|
|
EndOfPeriod = endOfPeriod,
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-03-27 14:36:37 -04:00
|
|
|
|
public async Task ReinstateSubscriptionAsync(Guid organizationId)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2019-03-21 21:36:03 -04:00
|
|
|
|
var organization = await GetOrgById(organizationId);
|
2020-03-27 14:36:37 -04:00
|
|
|
|
if (organization == null)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2019-03-21 21:36:03 -04:00
|
|
|
|
throw new NotFoundException();
|
2017-04-10 09:36:21 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2017-08-15 16:11:08 -04:00
|
|
|
|
await _paymentService.ReinstateSubscriptionAsync(organization);
|
2019-03-21 21:36:03 -04:00
|
|
|
|
await _referenceEventService.RaiseEventAsync(
|
2023-05-16 16:21:57 +02:00
|
|
|
|
new ReferenceEvent(ReferenceEventType.ReinstateSubscription, organization, _currentContext));
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2017-04-10 09:36:21 -04:00
|
|
|
|
public async Task<Tuple<bool, string>> UpgradePlanAsync(Guid organizationId, OrganizationUpgrade upgrade)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2017-04-10 09:36:21 -04:00
|
|
|
|
var organization = await GetOrgById(organizationId);
|
2020-03-27 14:36:37 -04:00
|
|
|
|
if (organization == null)
|
2017-04-10 09:36:21 -04:00
|
|
|
|
{
|
|
|
|
|
|
throw new NotFoundException();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-03-21 21:36:03 -04:00
|
|
|
|
if (string.IsNullOrWhiteSpace(organization.GatewayCustomerId))
|
2017-04-10 09:36:21 -04:00
|
|
|
|
{
|
|
|
|
|
|
throw new BadRequestException("Your account has no payment method available.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-06-16 11:12:38 +01:00
|
|
|
|
var existingPlan = StaticStore.PasswordManagerPlans.FirstOrDefault(p => p.Type == organization.PlanType);
|
2020-03-27 14:36:37 -04:00
|
|
|
|
if (existingPlan == null)
|
2017-04-10 09:36:21 -04:00
|
|
|
|
{
|
|
|
|
|
|
throw new BadRequestException("Existing plan not found.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-06-16 11:12:38 +01:00
|
|
|
|
var newPlan = StaticStore.PasswordManagerPlans.FirstOrDefault(p => p.Type == upgrade.Plan && !p.Disabled);
|
2020-03-27 14:36:37 -04:00
|
|
|
|
if (newPlan == null)
|
2017-04-10 09:36:21 -04:00
|
|
|
|
{
|
|
|
|
|
|
throw new BadRequestException("Plan not found.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-03-27 14:36:37 -04:00
|
|
|
|
if (existingPlan.Type == newPlan.Type)
|
2017-04-10 09:36:21 -04:00
|
|
|
|
{
|
2019-03-21 21:36:03 -04:00
|
|
|
|
throw new BadRequestException("Organization is already on this plan.");
|
2017-04-10 09:36:21 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2019-03-21 21:36:03 -04:00
|
|
|
|
if (existingPlan.UpgradeSortOrder >= newPlan.UpgradeSortOrder)
|
2022-08-29 15:53:48 -04:00
|
|
|
|
{
|
2019-03-21 21:36:03 -04:00
|
|
|
|
throw new BadRequestException("You cannot upgrade to this plan.");
|
2022-08-29 15:53:48 -04:00
|
|
|
|
}
|
2017-04-10 09:36:21 -04:00
|
|
|
|
|
2020-03-27 14:36:37 -04:00
|
|
|
|
if (existingPlan.Type != PlanType.Free)
|
2017-04-10 09:36:21 -04:00
|
|
|
|
{
|
2019-03-21 21:36:03 -04:00
|
|
|
|
throw new BadRequestException("You can only upgrade from the free plan. Contact support.");
|
2017-04-10 09:36:21 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2017-08-11 08:57:31 -04:00
|
|
|
|
ValidateOrganizationUpgradeParameters(newPlan, upgrade);
|
2022-08-29 15:53:48 -04:00
|
|
|
|
|
2020-03-27 14:36:37 -04:00
|
|
|
|
var newPlanSeats = (short)(newPlan.BaseSeats +
|
|
|
|
|
|
(newPlan.HasAdditionalSeatsOption ? upgrade.AdditionalSeats : 0));
|
|
|
|
|
|
if (!organization.Seats.HasValue || organization.Seats.Value > newPlanSeats)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2023-02-24 07:54:19 +10:00
|
|
|
|
var occupiedSeats = await _organizationUserRepository.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id);
|
2022-09-23 14:30:39 +10:00
|
|
|
|
if (occupiedSeats > newPlanSeats)
|
2017-04-10 09:36:21 -04:00
|
|
|
|
{
|
2022-09-23 14:30:39 +10:00
|
|
|
|
throw new BadRequestException($"Your organization currently has {occupiedSeats} seats filled. " +
|
2017-04-27 09:19:30 -04:00
|
|
|
|
$"Your new plan only has ({newPlanSeats}) seats. Remove some users.");
|
2017-04-10 09:36:21 -04:00
|
|
|
|
}
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2017-04-10 09:36:21 -04:00
|
|
|
|
|
2020-08-11 14:19:56 -04:00
|
|
|
|
if (newPlan.MaxCollections.HasValue && (!organization.MaxCollections.HasValue ||
|
2019-03-21 21:36:03 -04:00
|
|
|
|
organization.MaxCollections.Value > newPlan.MaxCollections.Value))
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2019-03-21 21:36:03 -04:00
|
|
|
|
var collectionCount = await _collectionRepository.GetCountByOrganizationIdAsync(organization.Id);
|
|
|
|
|
|
if (collectionCount > newPlan.MaxCollections.Value)
|
2017-04-10 09:36:21 -04:00
|
|
|
|
{
|
2019-03-21 21:36:03 -04:00
|
|
|
|
throw new BadRequestException($"Your organization currently has {collectionCount} collections. " +
|
|
|
|
|
|
$"Your new plan allows for a maximum of ({newPlan.MaxCollections.Value}) collections. " +
|
|
|
|
|
|
"Remove some collections.");
|
|
|
|
|
|
}
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2017-05-08 14:40:04 -04:00
|
|
|
|
|
2020-08-11 14:19:56 -04:00
|
|
|
|
if (!newPlan.HasGroups && organization.UseGroups)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2020-01-15 15:00:54 -05:00
|
|
|
|
var groups = await _groupRepository.GetManyByOrganizationIdAsync(organization.Id);
|
2020-03-27 14:36:37 -04:00
|
|
|
|
if (groups.Any())
|
2020-01-15 15:00:54 -05:00
|
|
|
|
{
|
|
|
|
|
|
throw new BadRequestException($"Your new plan does not allow the groups feature. " +
|
|
|
|
|
|
$"Remove your groups.");
|
|
|
|
|
|
}
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2020-08-11 14:19:56 -04:00
|
|
|
|
|
|
|
|
|
|
if (!newPlan.HasPolicies && organization.UsePolicies)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2020-01-15 15:00:54 -05:00
|
|
|
|
var policies = await _policyRepository.GetManyByOrganizationIdAsync(organization.Id);
|
2020-03-27 14:36:37 -04:00
|
|
|
|
if (policies.Any(p => p.Enabled))
|
2020-07-22 09:38:39 -04:00
|
|
|
|
{
|
|
|
|
|
|
throw new BadRequestException($"Your new plan does not allow the policies feature. " +
|
|
|
|
|
|
$"Disable your policies.");
|
|
|
|
|
|
}
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2021-09-23 06:36:08 -04:00
|
|
|
|
|
2021-11-17 11:46:35 +01:00
|
|
|
|
if (!newPlan.HasSso && organization.UseSso)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2021-11-17 11:46:35 +01:00
|
|
|
|
var ssoConfig = await _ssoConfigRepository.GetByOrganizationIdAsync(organization.Id);
|
2020-07-22 09:38:39 -04:00
|
|
|
|
if (ssoConfig != null && ssoConfig.Enabled)
|
2021-11-17 11:46:35 +01:00
|
|
|
|
{
|
|
|
|
|
|
throw new BadRequestException($"Your new plan does not allow the SSO feature. " +
|
|
|
|
|
|
$"Disable your SSO configuration.");
|
|
|
|
|
|
}
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2021-11-17 11:46:35 +01:00
|
|
|
|
|
2021-05-12 14:47:00 -05:00
|
|
|
|
if (!newPlan.HasKeyConnector && organization.UseKeyConnector)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2022-07-14 15:58:48 -04:00
|
|
|
|
var ssoConfig = await _ssoConfigRepository.GetByOrganizationIdAsync(organization.Id);
|
2023-05-10 12:52:08 -07:00
|
|
|
|
if (ssoConfig != null && ssoConfig.GetData().MemberDecryptionType == MemberDecryptionType.KeyConnector)
|
2022-08-29 14:53:16 -04:00
|
|
|
|
{
|
2021-11-17 11:46:35 +01:00
|
|
|
|
throw new BadRequestException("Your new plan does not allow the Key Connector feature. " +
|
|
|
|
|
|
"Disable your Key Connector.");
|
2021-05-12 14:47:00 -05:00
|
|
|
|
}
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2020-01-15 15:00:54 -05:00
|
|
|
|
|
2021-05-06 14:53:12 -05:00
|
|
|
|
if (!newPlan.HasResetPassword && organization.UseResetPassword)
|
2022-08-29 15:53:48 -04:00
|
|
|
|
{
|
2021-05-12 14:47:00 -05:00
|
|
|
|
var resetPasswordPolicy =
|
2022-07-14 15:58:48 -04:00
|
|
|
|
await _policyRepository.GetByOrganizationIdTypeAsync(organization.Id, PolicyType.ResetPassword);
|
|
|
|
|
|
if (resetPasswordPolicy != null && resetPasswordPolicy.Enabled)
|
2022-08-29 15:53:48 -04:00
|
|
|
|
{
|
2022-07-14 15:58:48 -04:00
|
|
|
|
throw new BadRequestException("Your new plan does not allow the Password Reset feature. " +
|
2021-05-12 14:47:00 -05:00
|
|
|
|
"Disable your Password Reset policy.");
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-03-21 21:36:03 -04:00
|
|
|
|
if (!newPlan.HasScim && organization.UseScim)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2017-12-19 16:02:39 -05:00
|
|
|
|
var scimConnections = await _organizationConnectionRepository.GetByOrganizationIdTypeAsync(organization.Id,
|
2020-03-27 14:36:37 -04:00
|
|
|
|
OrganizationConnectionType.Scim);
|
|
|
|
|
|
if (scimConnections != null && scimConnections.Any(c => c.GetConfig<ScimConfig>()?.Enabled == true))
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2017-07-11 10:59:59 -04:00
|
|
|
|
throw new BadRequestException("Your new plan does not allow the SCIM feature. " +
|
|
|
|
|
|
"Disable your SCIM configuration.");
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-12-06 09:50:08 +00:00
|
|
|
|
if (!newPlan.HasCustomPermissions && organization.UseCustomPermissions)
|
|
|
|
|
|
{
|
|
|
|
|
|
var organizationCustomUsers =
|
|
|
|
|
|
await _organizationUserRepository.GetManyByOrganizationAsync(organization.Id,
|
|
|
|
|
|
OrganizationUserType.Custom);
|
|
|
|
|
|
if (organizationCustomUsers.Any())
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new BadRequestException("Your new plan does not allow the Custom Permissions feature. " +
|
|
|
|
|
|
"Disable your Custom Permissions configuration.");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-08-09 23:56:26 -04:00
|
|
|
|
// TODO: Check storage?
|
2022-08-29 16:06:55 -04:00
|
|
|
|
|
2019-08-09 23:56:26 -04:00
|
|
|
|
string paymentIntentClientSecret = null;
|
|
|
|
|
|
var success = true;
|
2019-03-21 21:36:03 -04:00
|
|
|
|
if (string.IsNullOrWhiteSpace(organization.GatewaySubscriptionId))
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2019-03-21 21:36:03 -04:00
|
|
|
|
paymentIntentClientSecret = await _paymentService.UpgradeFreeOrganizationAsync(organization, newPlan,
|
2020-09-18 14:10:30 -04:00
|
|
|
|
upgrade.AdditionalStorageGb, upgrade.AdditionalSeats, upgrade.PremiumAccessAddon, upgrade.TaxInfo);
|
|
|
|
|
|
success = string.IsNullOrWhiteSpace(paymentIntentClientSecret);
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2019-03-21 21:36:03 -04:00
|
|
|
|
// TODO: Update existing sub
|
2020-09-18 14:10:30 -04:00
|
|
|
|
throw new BadRequestException("You can only upgrade from the free plan. Contact support.");
|
2022-07-14 15:58:48 -04:00
|
|
|
|
}
|
2022-08-29 16:06:55 -04:00
|
|
|
|
|
2019-03-21 21:36:03 -04:00
|
|
|
|
organization.BusinessName = upgrade.BusinessName;
|
|
|
|
|
|
organization.PlanType = newPlan.Type;
|
|
|
|
|
|
organization.Seats = (short)(newPlan.BaseSeats + upgrade.AdditionalSeats);
|
|
|
|
|
|
organization.MaxCollections = newPlan.MaxCollections;
|
2020-08-11 14:19:56 -04:00
|
|
|
|
organization.UseGroups = newPlan.HasGroups;
|
2019-03-21 21:36:03 -04:00
|
|
|
|
organization.UseDirectory = newPlan.HasDirectory;
|
|
|
|
|
|
organization.UseEvents = newPlan.HasEvents;
|
|
|
|
|
|
organization.UseTotp = newPlan.HasTotp;
|
2020-08-11 14:19:56 -04:00
|
|
|
|
organization.Use2fa = newPlan.Has2fa;
|
|
|
|
|
|
organization.UseApi = newPlan.HasApi;
|
2019-03-21 21:36:03 -04:00
|
|
|
|
organization.SelfHost = newPlan.HasSelfHost;
|
|
|
|
|
|
organization.UsePolicies = newPlan.HasPolicies;
|
|
|
|
|
|
organization.MaxStorageGb = !newPlan.BaseStorageGb.HasValue ?
|
|
|
|
|
|
(short?)null : (short)(newPlan.BaseStorageGb.Value + upgrade.AdditionalStorageGb);
|
|
|
|
|
|
organization.UseGroups = newPlan.HasGroups;
|
|
|
|
|
|
organization.UseDirectory = newPlan.HasDirectory;
|
|
|
|
|
|
organization.UseEvents = newPlan.HasEvents;
|
|
|
|
|
|
organization.UseTotp = newPlan.HasTotp;
|
2020-08-11 14:19:56 -04:00
|
|
|
|
organization.Use2fa = newPlan.Has2fa;
|
|
|
|
|
|
organization.UseApi = newPlan.HasApi;
|
2020-10-07 15:03:47 -04:00
|
|
|
|
organization.UseSso = newPlan.HasSso;
|
2021-11-17 11:46:35 +01:00
|
|
|
|
organization.UseKeyConnector = newPlan.HasKeyConnector;
|
2022-07-14 15:58:48 -04:00
|
|
|
|
organization.UseScim = newPlan.HasScim;
|
2019-03-21 21:36:03 -04:00
|
|
|
|
organization.UseResetPassword = newPlan.HasResetPassword;
|
|
|
|
|
|
organization.SelfHost = newPlan.HasSelfHost;
|
|
|
|
|
|
organization.UsersGetPremium = newPlan.UsersGetPremium || upgrade.PremiumAccessAddon;
|
2022-12-06 09:50:08 +00:00
|
|
|
|
organization.UseCustomPermissions = newPlan.HasCustomPermissions;
|
2019-03-21 21:36:03 -04:00
|
|
|
|
organization.Plan = newPlan.Name;
|
|
|
|
|
|
organization.Enabled = success;
|
2021-05-06 14:53:12 -05:00
|
|
|
|
organization.PublicKey = upgrade.PublicKey;
|
|
|
|
|
|
organization.PrivateKey = upgrade.PrivateKey;
|
2023-02-24 07:54:19 +10:00
|
|
|
|
await ReplaceAndUpdateCacheAsync(organization);
|
2019-03-21 21:36:03 -04:00
|
|
|
|
if (success)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2020-07-07 12:01:34 -04:00
|
|
|
|
await _referenceEventService.RaiseEventAsync(
|
2023-05-16 16:21:57 +02:00
|
|
|
|
new ReferenceEvent(ReferenceEventType.UpgradePlan, organization, _currentContext)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2020-07-07 12:01:34 -04:00
|
|
|
|
PlanName = newPlan.Name,
|
|
|
|
|
|
PlanType = newPlan.Type,
|
2021-08-10 14:38:58 -04:00
|
|
|
|
OldPlanName = existingPlan.Name,
|
|
|
|
|
|
OldPlanType = existingPlan.Type,
|
2020-07-07 12:01:34 -04:00
|
|
|
|
Seats = organization.Seats,
|
|
|
|
|
|
Storage = organization.MaxStorageGb,
|
2022-08-29 16:06:55 -04:00
|
|
|
|
});
|
2022-08-29 15:53:48 -04:00
|
|
|
|
}
|
2022-07-14 15:58:48 -04:00
|
|
|
|
|
2017-07-11 10:59:59 -04:00
|
|
|
|
return new Tuple<bool, string>(success, paymentIntentClientSecret);
|
2022-08-29 15:53:48 -04:00
|
|
|
|
}
|
2017-04-10 09:36:21 -04:00
|
|
|
|
|
2019-08-09 23:56:26 -04:00
|
|
|
|
public async Task<string> AdjustStorageAsync(Guid organizationId, short storageAdjustmentGb)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2017-07-11 10:59:59 -04:00
|
|
|
|
var organization = await GetOrgById(organizationId);
|
2021-09-23 06:36:08 -04:00
|
|
|
|
if (organization == null)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2017-07-11 10:59:59 -04:00
|
|
|
|
throw new NotFoundException();
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2019-03-21 21:36:03 -04:00
|
|
|
|
|
2023-06-16 11:12:38 +01:00
|
|
|
|
var plan = StaticStore.PasswordManagerPlans.FirstOrDefault(p => p.Type == organization.PlanType);
|
2020-09-18 14:10:30 -04:00
|
|
|
|
if (plan == null)
|
2022-08-29 15:53:48 -04:00
|
|
|
|
{
|
2020-09-18 14:10:30 -04:00
|
|
|
|
throw new BadRequestException("Existing plan not found.");
|
2022-08-29 14:53:16 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2019-03-21 21:36:03 -04:00
|
|
|
|
if (!plan.HasAdditionalStorageOption)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2019-03-21 21:36:03 -04:00
|
|
|
|
throw new BadRequestException("Plan does not allow additional storage.");
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2019-03-21 21:36:03 -04:00
|
|
|
|
var secret = await BillingHelpers.AdjustStorageAsync(_paymentService, organization, storageAdjustmentGb,
|
|
|
|
|
|
plan.StripeStoragePlanId);
|
|
|
|
|
|
await _referenceEventService.RaiseEventAsync(
|
2023-05-16 16:21:57 +02:00
|
|
|
|
new ReferenceEvent(ReferenceEventType.AdjustStorage, organization, _currentContext)
|
2020-07-07 12:01:34 -04:00
|
|
|
|
{
|
|
|
|
|
|
PlanName = plan.Name,
|
|
|
|
|
|
PlanType = plan.Type,
|
|
|
|
|
|
Storage = storageAdjustmentGb,
|
2022-08-29 16:06:55 -04:00
|
|
|
|
});
|
2023-02-24 07:54:19 +10:00
|
|
|
|
await ReplaceAndUpdateCacheAsync(organization);
|
2020-07-07 12:01:34 -04:00
|
|
|
|
return secret;
|
|
|
|
|
|
}
|
2019-08-09 23:56:26 -04:00
|
|
|
|
|
|
|
|
|
|
public async Task UpdateSubscription(Guid organizationId, int seatAdjustment, int? maxAutoscaleSeats)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2019-08-09 23:56:26 -04:00
|
|
|
|
var organization = await GetOrgById(organizationId);
|
2021-09-23 06:36:08 -04:00
|
|
|
|
if (organization == null)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2019-08-09 23:56:26 -04:00
|
|
|
|
throw new NotFoundException();
|
2017-04-10 09:36:21 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2019-08-10 12:59:32 -04:00
|
|
|
|
var newSeatCount = organization.Seats + seatAdjustment;
|
|
|
|
|
|
if (maxAutoscaleSeats.HasValue && newSeatCount > maxAutoscaleSeats.Value)
|
2017-07-11 10:59:59 -04:00
|
|
|
|
{
|
|
|
|
|
|
throw new BadRequestException("Cannot set max seat autoscaling below seat count.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (seatAdjustment != 0)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2017-07-11 10:59:59 -04:00
|
|
|
|
await AdjustSeatsAsync(organization, seatAdjustment);
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2017-07-11 10:59:59 -04:00
|
|
|
|
if (maxAutoscaleSeats != organization.MaxAutoscaleSeats)
|
|
|
|
|
|
{
|
|
|
|
|
|
await UpdateAutoscalingAsync(organization, maxAutoscaleSeats);
|
2022-08-29 14:53:16 -04:00
|
|
|
|
}
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2017-07-11 10:59:59 -04:00
|
|
|
|
|
|
|
|
|
|
private async Task UpdateAutoscalingAsync(Organization organization, int? maxAutoscaleSeats)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2017-07-11 10:59:59 -04:00
|
|
|
|
|
2021-09-23 06:36:08 -04:00
|
|
|
|
if (maxAutoscaleSeats.HasValue &&
|
2019-08-10 12:59:32 -04:00
|
|
|
|
organization.Seats.HasValue &&
|
|
|
|
|
|
maxAutoscaleSeats.Value < organization.Seats.Value)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2019-08-10 12:59:32 -04:00
|
|
|
|
throw new BadRequestException($"Cannot set max seat autoscaling below current seat count.");
|
2017-07-11 10:59:59 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-06-16 11:12:38 +01:00
|
|
|
|
var plan = StaticStore.PasswordManagerPlans.FirstOrDefault(p => p.Type == organization.PlanType);
|
2021-09-23 06:36:08 -04:00
|
|
|
|
if (plan == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new BadRequestException("Existing plan not found.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!plan.AllowSeatAutoscale)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new BadRequestException("Your plan does not allow seat autoscaling.");
|
2022-08-29 14:53:16 -04:00
|
|
|
|
}
|
2021-09-23 06:36:08 -04:00
|
|
|
|
|
|
|
|
|
|
if (plan.MaxUsers.HasValue && maxAutoscaleSeats.HasValue &&
|
|
|
|
|
|
maxAutoscaleSeats > plan.MaxUsers)
|
2022-08-29 14:53:16 -04:00
|
|
|
|
{
|
2017-04-10 09:36:21 -04:00
|
|
|
|
throw new BadRequestException(string.Concat($"Your plan has a seat limit of {plan.MaxUsers}, ",
|
2021-09-23 06:36:08 -04:00
|
|
|
|
$"but you have specified a max autoscale count of {maxAutoscaleSeats}.",
|
2017-04-10 09:36:21 -04:00
|
|
|
|
"Reduce your max autoscale seat count."));
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2021-09-23 06:36:08 -04:00
|
|
|
|
|
|
|
|
|
|
organization.MaxAutoscaleSeats = maxAutoscaleSeats;
|
|
|
|
|
|
|
2023-02-24 07:54:19 +10:00
|
|
|
|
await ReplaceAndUpdateCacheAsync(organization);
|
2022-08-29 14:53:16 -04:00
|
|
|
|
}
|
2021-09-23 06:36:08 -04:00
|
|
|
|
|
|
|
|
|
|
public async Task<string> AdjustSeatsAsync(Guid organizationId, int seatAdjustment, DateTime? prorationDate = null)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2021-09-23 06:36:08 -04:00
|
|
|
|
var organization = await GetOrgById(organizationId);
|
2017-04-10 09:36:21 -04:00
|
|
|
|
if (organization == null)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2021-09-23 06:36:08 -04:00
|
|
|
|
throw new NotFoundException();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2017-04-10 09:36:21 -04:00
|
|
|
|
return await AdjustSeatsAsync(organization, seatAdjustment, prorationDate);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-09-23 06:36:08 -04:00
|
|
|
|
private async Task<string> AdjustSeatsAsync(Organization organization, int seatAdjustment, DateTime? prorationDate = null, IEnumerable<string> ownerEmails = null)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2021-09-23 06:36:08 -04:00
|
|
|
|
if (organization.Seats == null)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2021-09-23 06:36:08 -04:00
|
|
|
|
throw new BadRequestException("Organization has no seat limit, no need to adjust seats");
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2021-09-23 06:36:08 -04:00
|
|
|
|
|
2021-09-14 09:18:06 -04:00
|
|
|
|
if (string.IsNullOrWhiteSpace(organization.GatewayCustomerId))
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2021-09-14 09:18:06 -04:00
|
|
|
|
throw new BadRequestException("No payment method found.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-03-27 14:36:37 -04:00
|
|
|
|
if (string.IsNullOrWhiteSpace(organization.GatewaySubscriptionId))
|
2017-04-10 09:36:21 -04:00
|
|
|
|
{
|
|
|
|
|
|
throw new BadRequestException("No subscription found.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-06-16 11:12:38 +01:00
|
|
|
|
var plan = StaticStore.PasswordManagerPlans.FirstOrDefault(p => p.Type == organization.PlanType);
|
2020-03-27 14:36:37 -04:00
|
|
|
|
if (plan == null)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2017-04-10 09:36:21 -04:00
|
|
|
|
throw new BadRequestException("Existing plan not found.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!plan.HasAdditionalSeatsOption)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new BadRequestException("Plan does not allow additional seats.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-08-11 14:19:56 -04:00
|
|
|
|
var newSeatTotal = organization.Seats.Value + seatAdjustment;
|
|
|
|
|
|
if (plan.BaseSeats > newSeatTotal)
|
2017-04-10 09:36:21 -04:00
|
|
|
|
{
|
2017-04-10 10:44:27 -04:00
|
|
|
|
throw new BadRequestException($"Plan has a minimum of {plan.BaseSeats} seats.");
|
2017-04-10 09:36:21 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-09-14 09:18:06 -04:00
|
|
|
|
if (newSeatTotal <= 0)
|
2017-04-10 11:49:53 -04:00
|
|
|
|
{
|
|
|
|
|
|
throw new BadRequestException("You must have at least 1 seat.");
|
2022-08-29 15:53:48 -04:00
|
|
|
|
}
|
2017-04-10 11:49:53 -04:00
|
|
|
|
|
2020-03-27 14:36:37 -04:00
|
|
|
|
var additionalSeats = newSeatTotal - plan.BaseSeats;
|
|
|
|
|
|
if (plan.MaxAdditionalSeats.HasValue && additionalSeats > plan.MaxAdditionalSeats.Value)
|
2017-05-20 15:33:17 -04:00
|
|
|
|
{
|
|
|
|
|
|
throw new BadRequestException($"Organization plan allows a maximum of " +
|
|
|
|
|
|
$"{plan.MaxAdditionalSeats.Value} additional seats.");
|
|
|
|
|
|
}
|
2022-08-29 15:53:48 -04:00
|
|
|
|
|
2020-03-27 14:36:37 -04:00
|
|
|
|
if (!organization.Seats.HasValue || organization.Seats.Value > newSeatTotal)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2023-02-24 07:54:19 +10:00
|
|
|
|
var occupiedSeats = await _organizationUserRepository.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id);
|
2022-09-23 14:30:39 +10:00
|
|
|
|
if (occupiedSeats > newSeatTotal)
|
2017-04-10 09:36:21 -04:00
|
|
|
|
{
|
2022-09-23 14:30:39 +10:00
|
|
|
|
throw new BadRequestException($"Your organization currently has {occupiedSeats} seats filled. " +
|
2017-04-10 09:36:21 -04:00
|
|
|
|
$"Your new plan only has ({newSeatTotal}) seats. Remove some users.");
|
|
|
|
|
|
}
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2017-04-10 09:36:21 -04:00
|
|
|
|
|
2019-05-14 11:16:30 -04:00
|
|
|
|
var paymentIntentClientSecret = await _paymentService.AdjustSeatsAsync(organization, plan, additionalSeats, prorationDate);
|
2020-03-27 14:36:37 -04:00
|
|
|
|
await _referenceEventService.RaiseEventAsync(
|
2023-05-16 16:21:57 +02:00
|
|
|
|
new ReferenceEvent(ReferenceEventType.AdjustSeats, organization, _currentContext)
|
2022-08-29 15:53:48 -04:00
|
|
|
|
{
|
2019-05-14 11:16:30 -04:00
|
|
|
|
PlanName = plan.Name,
|
2020-07-07 12:01:34 -04:00
|
|
|
|
PlanType = plan.Type,
|
2019-05-14 11:16:30 -04:00
|
|
|
|
Seats = newSeatTotal,
|
|
|
|
|
|
PreviousSeats = organization.Seats
|
|
|
|
|
|
});
|
|
|
|
|
|
organization.Seats = (short?)newSeatTotal;
|
2023-02-24 07:54:19 +10:00
|
|
|
|
await ReplaceAndUpdateCacheAsync(organization);
|
2017-04-10 09:36:21 -04:00
|
|
|
|
|
2021-08-10 14:38:58 -04:00
|
|
|
|
if (organization.Seats.HasValue && organization.MaxAutoscaleSeats.HasValue && organization.Seats == organization.MaxAutoscaleSeats)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
|
|
|
|
|
try
|
2022-08-29 14:53:16 -04:00
|
|
|
|
{
|
2017-12-19 16:02:39 -05:00
|
|
|
|
if (ownerEmails == null)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2017-12-19 16:02:39 -05:00
|
|
|
|
ownerEmails = (await _organizationUserRepository.GetManyByMinimumRoleAsync(organization.Id,
|
|
|
|
|
|
OrganizationUserType.Owner)).Select(u => u.Email).Distinct();
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2017-12-19 16:02:39 -05:00
|
|
|
|
await _mailService.SendOrganizationMaxSeatLimitReachedEmailAsync(organization, organization.MaxAutoscaleSeats.Value, ownerEmails);
|
2022-08-29 14:53:16 -04:00
|
|
|
|
}
|
2021-09-23 06:36:08 -04:00
|
|
|
|
catch (Exception e)
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger.LogError(e, "Error encountered notifying organization owners of seat limit reached.");
|
|
|
|
|
|
}
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2021-09-23 06:36:08 -04:00
|
|
|
|
|
2017-08-14 09:23:54 -04:00
|
|
|
|
return paymentIntentClientSecret;
|
2022-08-29 15:53:48 -04:00
|
|
|
|
}
|
2017-04-11 10:00:36 -04:00
|
|
|
|
|
2017-08-14 09:23:54 -04:00
|
|
|
|
public async Task VerifyBankAsync(Guid organizationId, int amount1, int amount2)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2017-08-14 09:23:54 -04:00
|
|
|
|
var organization = await GetOrgById(organizationId);
|
2017-04-10 11:49:53 -04:00
|
|
|
|
if (organization == null)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2017-08-14 09:23:54 -04:00
|
|
|
|
throw new NotFoundException();
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2017-08-14 09:23:54 -04:00
|
|
|
|
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(organization.GatewayCustomerId))
|
2022-08-29 15:53:48 -04:00
|
|
|
|
{
|
2017-08-14 09:23:54 -04:00
|
|
|
|
throw new GatewayException("Not a gateway customer.");
|
2022-08-29 15:53:48 -04:00
|
|
|
|
}
|
2017-08-14 09:23:54 -04:00
|
|
|
|
|
|
|
|
|
|
var bankService = new BankAccountService();
|
2019-01-29 14:41:37 -05:00
|
|
|
|
var customerService = new CustomerService();
|
2017-08-14 09:23:54 -04:00
|
|
|
|
var customer = await customerService.GetAsync(organization.GatewayCustomerId,
|
|
|
|
|
|
new CustomerGetOptions { Expand = new List<string> { "sources" } });
|
2021-07-08 17:05:32 +02:00
|
|
|
|
if (customer == null)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2021-07-08 17:05:32 +02:00
|
|
|
|
throw new GatewayException("Cannot find customer.");
|
2017-08-14 09:23:54 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2019-01-29 14:41:37 -05:00
|
|
|
|
var bankAccount = customer.Sources
|
|
|
|
|
|
.FirstOrDefault(s => s is BankAccount && ((BankAccount)s).Status != "verified") as BankAccount;
|
|
|
|
|
|
if (bankAccount == null)
|
2017-08-14 09:23:54 -04:00
|
|
|
|
{
|
|
|
|
|
|
throw new GatewayException("Cannot find an unverified bank account.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2017-08-14 09:23:54 -04:00
|
|
|
|
var result = await bankService.VerifyAsync(organization.GatewayCustomerId, bankAccount.Id,
|
|
|
|
|
|
new BankAccountVerifyOptions { Amounts = new List<long> { amount1, amount2 } });
|
|
|
|
|
|
if (result.Status != "verified")
|
2022-08-29 14:53:16 -04:00
|
|
|
|
{
|
2017-08-14 09:23:54 -04:00
|
|
|
|
throw new GatewayException("Unable to verify account.");
|
|
|
|
|
|
}
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2021-07-08 17:05:32 +02:00
|
|
|
|
catch (StripeException e)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2017-03-03 00:07:11 -05:00
|
|
|
|
throw new GatewayException(e.Message);
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2017-08-14 09:23:54 -04:00
|
|
|
|
|
2021-07-08 17:05:32 +02:00
|
|
|
|
public async Task<Tuple<Organization, OrganizationUser>> SignUpAsync(OrganizationSignup signup,
|
|
|
|
|
|
bool provider = false)
|
|
|
|
|
|
{
|
2023-06-16 11:12:38 +01:00
|
|
|
|
var plan = StaticStore.PasswordManagerPlans.FirstOrDefault(p => p.Type == signup.Plan);
|
2023-04-14 11:13:16 +01:00
|
|
|
|
if (plan is not { LegacyYear: null })
|
2022-08-29 15:53:48 -04:00
|
|
|
|
{
|
2021-07-08 17:05:32 +02:00
|
|
|
|
throw new BadRequestException("Invalid plan selected.");
|
2022-08-29 15:53:48 -04:00
|
|
|
|
}
|
2022-08-29 14:53:16 -04:00
|
|
|
|
|
2017-04-10 10:44:27 -04:00
|
|
|
|
if (plan.Disabled)
|
2022-08-29 15:53:48 -04:00
|
|
|
|
{
|
2021-07-08 17:05:32 +02:00
|
|
|
|
throw new BadRequestException("Plan not found.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!provider)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2021-07-08 17:05:32 +02:00
|
|
|
|
await ValidateSignUpPoliciesAsync(signup.Owner.Id);
|
2022-08-29 14:53:16 -04:00
|
|
|
|
}
|
2022-08-29 15:53:48 -04:00
|
|
|
|
|
2021-07-08 17:05:32 +02:00
|
|
|
|
ValidateOrganizationUpgradeParameters(plan, signup);
|
2022-08-29 16:06:55 -04:00
|
|
|
|
|
2021-07-08 17:05:32 +02:00
|
|
|
|
var organization = new Organization
|
2022-08-29 14:53:16 -04:00
|
|
|
|
{
|
2019-02-19 17:13:21 -05:00
|
|
|
|
// Pre-generate the org id so that we can save it with the Stripe subscription..
|
|
|
|
|
|
Id = CoreHelpers.GenerateComb(),
|
|
|
|
|
|
Name = signup.Name,
|
2019-01-31 14:25:46 -05:00
|
|
|
|
BillingEmail = signup.BillingEmail,
|
2017-04-04 12:57:50 -04:00
|
|
|
|
BusinessName = signup.BusinessName,
|
2019-02-19 17:13:21 -05:00
|
|
|
|
PlanType = plan.Type,
|
2019-03-21 21:36:03 -04:00
|
|
|
|
Seats = (short)(plan.BaseSeats + signup.AdditionalSeats),
|
2019-02-19 17:13:21 -05:00
|
|
|
|
MaxCollections = plan.MaxCollections,
|
2020-08-11 14:19:56 -04:00
|
|
|
|
MaxStorageGb = !plan.BaseStorageGb.HasValue ?
|
2019-02-19 17:13:21 -05:00
|
|
|
|
(short?)null : (short)(plan.BaseStorageGb.Value + signup.AdditionalStorageGb),
|
|
|
|
|
|
UsePolicies = plan.HasPolicies,
|
2020-08-11 14:19:56 -04:00
|
|
|
|
UseSso = plan.HasSso,
|
2019-01-31 14:25:46 -05:00
|
|
|
|
UseGroups = plan.HasGroups,
|
2020-08-11 14:19:56 -04:00
|
|
|
|
UseEvents = plan.HasEvents,
|
2019-01-31 14:25:46 -05:00
|
|
|
|
UseDirectory = plan.HasDirectory,
|
2020-08-11 14:19:56 -04:00
|
|
|
|
UseTotp = plan.HasTotp,
|
|
|
|
|
|
Use2fa = plan.Has2fa,
|
|
|
|
|
|
UseApi = plan.HasApi,
|
2019-01-31 14:25:46 -05:00
|
|
|
|
UseResetPassword = plan.HasResetPassword,
|
|
|
|
|
|
SelfHost = plan.HasSelfHost,
|
|
|
|
|
|
UsersGetPremium = plan.UsersGetPremium || signup.PremiumAccessAddon,
|
2022-12-06 09:50:08 +00:00
|
|
|
|
UseCustomPermissions = plan.HasCustomPermissions,
|
2022-07-21 22:26:51 +10:00
|
|
|
|
UseScim = plan.HasScim,
|
2019-01-31 14:25:46 -05:00
|
|
|
|
Plan = plan.Name,
|
|
|
|
|
|
Gateway = null,
|
|
|
|
|
|
ReferenceData = signup.Owner.ReferenceData,
|
|
|
|
|
|
Enabled = true,
|
|
|
|
|
|
LicenseKey = CoreHelpers.SecureRandomString(20),
|
2021-05-06 14:53:12 -05:00
|
|
|
|
PublicKey = signup.PublicKey,
|
2019-01-31 14:25:46 -05:00
|
|
|
|
PrivateKey = signup.PrivateKey,
|
|
|
|
|
|
CreationDate = DateTime.UtcNow,
|
2020-06-25 12:28:22 -04:00
|
|
|
|
RevisionDate = DateTime.UtcNow,
|
2023-04-14 11:13:16 +01:00
|
|
|
|
Status = OrganizationStatusType.Created
|
2022-08-29 14:53:16 -04:00
|
|
|
|
};
|
2017-03-03 00:07:11 -05:00
|
|
|
|
|
2021-07-08 17:05:32 +02:00
|
|
|
|
if (plan.Type == PlanType.Free && !provider)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2019-01-31 14:25:46 -05:00
|
|
|
|
var adminCount =
|
2021-07-08 17:05:32 +02:00
|
|
|
|
await _organizationUserRepository.GetCountByFreeOrganizationAdminUserAsync(signup.Owner.Id);
|
|
|
|
|
|
if (adminCount > 0)
|
|
|
|
|
|
{
|
2020-07-07 12:01:34 -04:00
|
|
|
|
throw new BadRequestException("You can only be an admin of one free organization.");
|
2021-07-08 17:05:32 +02:00
|
|
|
|
}
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2021-07-08 17:05:32 +02:00
|
|
|
|
else if (plan.Type != PlanType.Free)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2021-07-08 17:05:32 +02:00
|
|
|
|
await _paymentService.PurchaseOrganizationAsync(organization, signup.PaymentMethodType.Value,
|
|
|
|
|
|
signup.PaymentToken, plan, signup.AdditionalStorageGb, signup.AdditionalSeats,
|
2023-04-10 14:40:04 +01:00
|
|
|
|
signup.PremiumAccessAddon, signup.TaxInfo, provider);
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2021-07-08 17:05:32 +02:00
|
|
|
|
|
2019-03-21 21:36:03 -04:00
|
|
|
|
var ownerId = provider ? default : signup.Owner.Id;
|
2021-06-30 09:21:41 +02:00
|
|
|
|
var returnValue = await SignUpAsync(organization, ownerId, signup.OwnerKey, signup.CollectionName, true);
|
|
|
|
|
|
await _referenceEventService.RaiseEventAsync(
|
2023-05-16 16:21:57 +02:00
|
|
|
|
new ReferenceEvent(ReferenceEventType.Signup, organization, _currentContext)
|
2022-08-29 15:53:48 -04:00
|
|
|
|
{
|
2020-07-07 12:01:34 -04:00
|
|
|
|
PlanName = plan.Name,
|
2021-06-30 09:21:41 +02:00
|
|
|
|
PlanType = plan.Type,
|
2019-03-21 21:36:03 -04:00
|
|
|
|
Seats = returnValue.Item1.Seats,
|
2021-06-30 09:21:41 +02:00
|
|
|
|
Storage = returnValue.Item1.MaxStorageGb,
|
2019-03-21 21:36:03 -04:00
|
|
|
|
});
|
2021-06-30 09:21:41 +02:00
|
|
|
|
return returnValue;
|
2022-08-29 14:53:16 -04:00
|
|
|
|
}
|
2017-04-08 10:52:10 -04:00
|
|
|
|
|
2017-03-03 00:07:11 -05:00
|
|
|
|
private async Task ValidateSignUpPoliciesAsync(Guid ownerId)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2023-05-12 08:22:19 +01:00
|
|
|
|
var anySingleOrgPolicies = await _policyService.AnyPoliciesApplicableToUserAsync(ownerId, PolicyType.SingleOrg);
|
|
|
|
|
|
if (anySingleOrgPolicies)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2017-03-03 00:07:11 -05:00
|
|
|
|
throw new BadRequestException("You may not create an organization. You belong to an organization " +
|
|
|
|
|
|
"which has a policy that prohibits you from being a member of any other organization.");
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2017-03-03 00:07:11 -05:00
|
|
|
|
|
2019-01-31 14:25:46 -05:00
|
|
|
|
public async Task<Tuple<Organization, OrganizationUser>> SignUpAsync(
|
|
|
|
|
|
OrganizationLicense license, User owner, string ownerKey, string collectionName, string publicKey,
|
|
|
|
|
|
string privateKey)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2023-02-24 07:54:19 +10:00
|
|
|
|
var canUse = license.CanUse(_globalSettings, _licensingService, out var exception);
|
|
|
|
|
|
if (!canUse)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2023-02-24 07:54:19 +10:00
|
|
|
|
throw new BadRequestException(exception);
|
2021-06-30 09:21:41 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2017-11-06 08:12:36 -05:00
|
|
|
|
if (license.PlanType != PlanType.Custom &&
|
2023-06-16 11:12:38 +01:00
|
|
|
|
StaticStore.PasswordManagerPlans.FirstOrDefault(p => p.Type == license.PlanType && !p.Disabled) == null)
|
2022-08-29 14:53:16 -04:00
|
|
|
|
{
|
2017-11-06 08:12:36 -05:00
|
|
|
|
throw new BadRequestException("Plan not found.");
|
2022-08-29 14:53:16 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2017-08-30 21:25:46 -04:00
|
|
|
|
var enabledOrgs = await _organizationRepository.GetManyByEnabledAsync();
|
2022-11-30 08:40:12 -05:00
|
|
|
|
if (enabledOrgs.Any(o => string.Equals(o.LicenseKey, license.LicenseKey)))
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2017-08-30 21:25:46 -04:00
|
|
|
|
throw new BadRequestException("License is already in use by another organization.");
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-07-08 17:05:32 +02:00
|
|
|
|
await ValidateSignUpPoliciesAsync(owner.Id);
|
2022-08-29 16:06:55 -04:00
|
|
|
|
|
2017-03-23 00:17:34 -04:00
|
|
|
|
var organization = new Organization
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2017-08-14 20:57:45 -04:00
|
|
|
|
Name = license.Name,
|
2017-08-16 13:58:52 -04:00
|
|
|
|
BillingEmail = license.BillingEmail,
|
|
|
|
|
|
BusinessName = license.BusinessName,
|
2017-08-14 20:57:45 -04:00
|
|
|
|
PlanType = license.PlanType,
|
|
|
|
|
|
Seats = license.Seats,
|
|
|
|
|
|
MaxCollections = license.MaxCollections,
|
2021-07-08 17:05:32 +02:00
|
|
|
|
MaxStorageGb = _globalSettings.SelfHosted ? 10240 : license.MaxStorageGb, // 10 TB
|
2020-01-15 15:00:54 -05:00
|
|
|
|
UsePolicies = license.UsePolicies,
|
2020-07-22 09:38:39 -04:00
|
|
|
|
UseSso = license.UseSso,
|
2021-11-17 11:46:35 +01:00
|
|
|
|
UseKeyConnector = license.UseKeyConnector,
|
2022-07-14 15:58:48 -04:00
|
|
|
|
UseScim = license.UseScim,
|
2021-07-08 17:05:32 +02:00
|
|
|
|
UseGroups = license.UseGroups,
|
2017-08-14 20:57:45 -04:00
|
|
|
|
UseDirectory = license.UseDirectory,
|
2017-12-14 15:48:44 -05:00
|
|
|
|
UseEvents = license.UseEvents,
|
2017-08-14 20:57:45 -04:00
|
|
|
|
UseTotp = license.UseTotp,
|
2018-04-02 14:53:19 -04:00
|
|
|
|
Use2fa = license.Use2fa,
|
2021-07-08 17:05:32 +02:00
|
|
|
|
UseApi = license.UseApi,
|
2021-05-06 14:53:12 -05:00
|
|
|
|
UseResetPassword = license.UseResetPassword,
|
2017-08-14 20:57:45 -04:00
|
|
|
|
Plan = license.Plan,
|
2021-07-08 17:05:32 +02:00
|
|
|
|
SelfHost = license.SelfHost,
|
2017-11-06 16:01:58 -05:00
|
|
|
|
UsersGetPremium = license.UsersGetPremium,
|
2022-12-06 09:50:08 +00:00
|
|
|
|
UseCustomPermissions = license.UseCustomPermissions,
|
2017-08-14 20:57:45 -04:00
|
|
|
|
Gateway = null,
|
2021-07-08 17:05:32 +02:00
|
|
|
|
GatewayCustomerId = null,
|
|
|
|
|
|
GatewaySubscriptionId = null,
|
|
|
|
|
|
ReferenceData = owner.ReferenceData,
|
|
|
|
|
|
Enabled = license.Enabled,
|
2017-08-14 20:57:45 -04:00
|
|
|
|
ExpirationDate = license.Expires,
|
2021-07-08 17:05:32 +02:00
|
|
|
|
LicenseKey = license.LicenseKey,
|
2017-08-14 20:57:45 -04:00
|
|
|
|
PublicKey = publicKey,
|
2021-05-06 14:53:12 -05:00
|
|
|
|
PrivateKey = privateKey,
|
2017-08-14 20:57:45 -04:00
|
|
|
|
CreationDate = DateTime.UtcNow,
|
2023-04-14 11:13:16 +01:00
|
|
|
|
RevisionDate = DateTime.UtcNow,
|
|
|
|
|
|
Status = OrganizationStatusType.Created
|
2022-08-29 16:06:55 -04:00
|
|
|
|
};
|
|
|
|
|
|
|
2021-07-08 17:05:32 +02:00
|
|
|
|
var result = await SignUpAsync(organization, owner.Id, ownerKey, collectionName, false);
|
2022-08-29 16:06:55 -04:00
|
|
|
|
|
2017-08-16 15:43:11 -04:00
|
|
|
|
var dir = $"{_globalSettings.LicenseDirectory}/organization";
|
|
|
|
|
|
Directory.CreateDirectory(dir);
|
2022-06-30 12:19:15 -04:00
|
|
|
|
await using var fs = new FileStream(Path.Combine(dir, $"{organization.Id}.json"), FileMode.Create);
|
2022-01-21 09:36:25 -05:00
|
|
|
|
await JsonSerializer.SerializeAsync(fs, license, JsonHelpers.Indented);
|
2017-08-16 15:43:11 -04:00
|
|
|
|
return result;
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2017-08-14 20:57:45 -04:00
|
|
|
|
private async Task<Tuple<Organization, OrganizationUser>> SignUpAsync(Organization organization,
|
|
|
|
|
|
Guid ownerId, string ownerKey, string collectionName, bool withPayment)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2021-05-06 14:53:12 -05:00
|
|
|
|
try
|
2017-08-14 20:57:45 -04:00
|
|
|
|
{
|
2022-02-07 09:43:00 +01:00
|
|
|
|
await _organizationRepository.CreateAsync(organization);
|
2021-07-08 17:05:32 +02:00
|
|
|
|
await _organizationApiKeyRepository.CreateAsync(new OrganizationApiKey
|
2022-02-07 09:43:00 +01:00
|
|
|
|
{
|
|
|
|
|
|
OrganizationId = organization.Id,
|
|
|
|
|
|
ApiKey = CoreHelpers.SecureRandomString(30),
|
|
|
|
|
|
Type = OrganizationApiKeyType.Default,
|
|
|
|
|
|
RevisionDate = DateTime.UtcNow,
|
2022-08-29 16:06:55 -04:00
|
|
|
|
});
|
2022-02-07 09:43:00 +01:00
|
|
|
|
await _applicationCacheService.UpsertOrganizationAbilityAsync(organization);
|
|
|
|
|
|
|
2020-03-27 14:36:37 -04:00
|
|
|
|
if (!string.IsNullOrWhiteSpace(collectionName))
|
2017-08-14 20:57:45 -04:00
|
|
|
|
{
|
|
|
|
|
|
var defaultCollection = new Collection
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2017-08-30 21:25:46 -04:00
|
|
|
|
Name = collectionName,
|
2017-08-14 20:57:45 -04:00
|
|
|
|
OrganizationId = organization.Id,
|
2017-08-30 21:25:46 -04:00
|
|
|
|
CreationDate = organization.CreationDate,
|
2021-07-08 17:05:32 +02:00
|
|
|
|
RevisionDate = organization.CreationDate
|
2022-08-29 16:06:55 -04:00
|
|
|
|
};
|
2017-08-14 20:57:45 -04:00
|
|
|
|
await _collectionRepository.CreateAsync(defaultCollection);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-03-27 14:36:37 -04:00
|
|
|
|
OrganizationUser orgUser = null;
|
|
|
|
|
|
if (ownerId != default)
|
2017-08-16 15:43:11 -04:00
|
|
|
|
{
|
2017-11-06 08:12:36 -05:00
|
|
|
|
orgUser = new OrganizationUser
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2021-07-08 17:05:32 +02:00
|
|
|
|
OrganizationId = organization.Id,
|
2017-11-06 08:12:36 -05:00
|
|
|
|
UserId = ownerId,
|
|
|
|
|
|
Key = ownerKey,
|
|
|
|
|
|
Type = OrganizationUserType.Owner,
|
|
|
|
|
|
Status = OrganizationUserStatusType.Confirmed,
|
|
|
|
|
|
AccessAll = true,
|
|
|
|
|
|
CreationDate = organization.CreationDate,
|
2019-02-08 23:53:09 -05:00
|
|
|
|
RevisionDate = organization.CreationDate
|
2017-11-06 08:12:36 -05:00
|
|
|
|
};
|
|
|
|
|
|
|
2017-12-19 16:02:39 -05:00
|
|
|
|
await _organizationUserRepository.CreateAsync(orgUser);
|
2017-04-04 12:57:50 -04:00
|
|
|
|
|
2017-08-30 21:25:46 -04:00
|
|
|
|
var deviceIds = await GetUserDeviceIdsAsync(orgUser.UserId.Value);
|
|
|
|
|
|
await _pushRegistrationService.AddUserRegistrationOrganizationAsync(deviceIds,
|
|
|
|
|
|
organization.Id.ToString());
|
|
|
|
|
|
await _pushNotificationService.PushSyncOrgKeysAsync(ownerId);
|
|
|
|
|
|
}
|
2017-08-30 21:08:05 -04:00
|
|
|
|
|
2017-03-23 00:17:34 -04:00
|
|
|
|
return new Tuple<Organization, OrganizationUser>(organization, orgUser);
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2017-08-14 20:57:45 -04:00
|
|
|
|
catch
|
2022-08-29 14:53:16 -04:00
|
|
|
|
{
|
2017-08-14 21:25:06 -04:00
|
|
|
|
if (withPayment)
|
2022-08-29 15:53:48 -04:00
|
|
|
|
{
|
2021-11-17 11:46:35 +01:00
|
|
|
|
await _paymentService.CancelAndRecoverChargesAsync(organization);
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2021-07-08 17:05:32 +02:00
|
|
|
|
|
|
|
|
|
|
if (organization.Id != default(Guid))
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2021-07-08 17:05:32 +02:00
|
|
|
|
await _organizationRepository.DeleteAsync(organization);
|
|
|
|
|
|
await _applicationCacheService.DeleteOrganizationAbilityAsync(organization.Id);
|
|
|
|
|
|
}
|
2017-04-21 22:39:46 -04:00
|
|
|
|
|
2017-03-03 00:07:11 -05:00
|
|
|
|
throw;
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2017-08-14 20:57:45 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2020-07-22 09:38:39 -04:00
|
|
|
|
public async Task DeleteAsync(Organization organization)
|
|
|
|
|
|
{
|
|
|
|
|
|
await ValidateDeleteOrganizationAsync(organization);
|
2021-09-23 06:36:08 -04:00
|
|
|
|
|
2020-03-27 14:36:37 -04:00
|
|
|
|
if (!string.IsNullOrWhiteSpace(organization.GatewaySubscriptionId))
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
|
|
|
|
|
try
|
2022-08-29 15:53:48 -04:00
|
|
|
|
{
|
2021-11-17 11:46:35 +01:00
|
|
|
|
var eop = !organization.ExpirationDate.HasValue ||
|
|
|
|
|
|
organization.ExpirationDate.Value >= DateTime.UtcNow;
|
|
|
|
|
|
await _paymentService.CancelSubscriptionAsync(organization, eop);
|
|
|
|
|
|
await _referenceEventService.RaiseEventAsync(
|
2023-05-16 16:21:57 +02:00
|
|
|
|
new ReferenceEvent(ReferenceEventType.DeleteAccount, organization, _currentContext));
|
2022-08-29 15:53:48 -04:00
|
|
|
|
}
|
2020-03-27 14:36:37 -04:00
|
|
|
|
catch (GatewayException) { }
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2021-11-17 11:46:35 +01:00
|
|
|
|
|
2022-07-14 15:58:48 -04:00
|
|
|
|
await _organizationRepository.DeleteAsync(organization);
|
|
|
|
|
|
await _applicationCacheService.DeleteOrganizationAbilityAsync(organization.Id);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-05-12 14:47:00 -05:00
|
|
|
|
public async Task EnableAsync(Guid organizationId, DateTime? expirationDate)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2021-05-12 14:47:00 -05:00
|
|
|
|
var org = await GetOrgById(organizationId);
|
|
|
|
|
|
if (org != null && !org.Enabled && org.Gateway.HasValue)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2021-05-12 14:47:00 -05:00
|
|
|
|
org.Enabled = true;
|
|
|
|
|
|
org.ExpirationDate = expirationDate;
|
2021-09-23 06:36:08 -04:00
|
|
|
|
org.RevisionDate = DateTime.UtcNow;
|
2023-02-24 07:54:19 +10:00
|
|
|
|
await ReplaceAndUpdateCacheAsync(org);
|
2022-08-29 15:53:48 -04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2020-01-15 15:00:54 -05:00
|
|
|
|
|
2017-08-16 13:58:52 -04:00
|
|
|
|
public async Task DisableAsync(Guid organizationId, DateTime? expirationDate)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2017-08-16 13:58:52 -04:00
|
|
|
|
var org = await GetOrgById(organizationId);
|
|
|
|
|
|
if (org != null && org.Enabled)
|
2022-08-29 15:53:48 -04:00
|
|
|
|
{
|
2017-08-16 13:58:52 -04:00
|
|
|
|
org.Enabled = false;
|
|
|
|
|
|
org.ExpirationDate = expirationDate;
|
|
|
|
|
|
org.RevisionDate = DateTime.UtcNow;
|
2023-02-24 07:54:19 +10:00
|
|
|
|
await ReplaceAndUpdateCacheAsync(org);
|
2017-08-14 21:25:06 -04:00
|
|
|
|
|
2017-08-16 15:43:11 -04:00
|
|
|
|
// TODO: send email to owners?
|
2018-09-07 14:00:56 -04:00
|
|
|
|
}
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2017-04-11 10:52:28 -04:00
|
|
|
|
|
2017-12-19 16:02:39 -05:00
|
|
|
|
public async Task UpdateExpirationDateAsync(Guid organizationId, DateTime? expirationDate)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2017-12-19 16:02:39 -05:00
|
|
|
|
var org = await GetOrgById(organizationId);
|
|
|
|
|
|
if (org != null)
|
2022-08-29 14:53:16 -04:00
|
|
|
|
{
|
2019-08-09 23:56:26 -04:00
|
|
|
|
org.ExpirationDate = expirationDate;
|
|
|
|
|
|
org.RevisionDate = DateTime.UtcNow;
|
2023-02-24 07:54:19 +10:00
|
|
|
|
await ReplaceAndUpdateCacheAsync(org);
|
2022-08-29 15:53:48 -04:00
|
|
|
|
}
|
2020-03-27 14:36:37 -04:00
|
|
|
|
}
|
2022-08-29 15:53:48 -04:00
|
|
|
|
|
2019-08-09 23:56:26 -04:00
|
|
|
|
public async Task EnableAsync(Guid organizationId)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2019-08-09 23:56:26 -04:00
|
|
|
|
var org = await GetOrgById(organizationId);
|
2017-04-10 19:07:38 -04:00
|
|
|
|
if (org != null && !org.Enabled)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2017-04-10 19:07:38 -04:00
|
|
|
|
org.Enabled = true;
|
2023-02-24 07:54:19 +10:00
|
|
|
|
await ReplaceAndUpdateCacheAsync(org);
|
2019-08-09 23:56:26 -04:00
|
|
|
|
}
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2019-08-09 23:56:26 -04:00
|
|
|
|
|
2017-08-12 22:16:42 -04:00
|
|
|
|
public async Task UpdateAsync(Organization organization, bool updateBilling = false)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2017-08-12 22:16:42 -04:00
|
|
|
|
if (organization.Id == default(Guid))
|
2017-04-26 16:14:15 -04:00
|
|
|
|
{
|
2017-08-12 22:16:42 -04:00
|
|
|
|
throw new ApplicationException("Cannot create org this way. Call SignUpAsync.");
|
2017-04-26 16:14:15 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2017-08-12 22:16:42 -04:00
|
|
|
|
if (!string.IsNullOrWhiteSpace(organization.Identifier))
|
|
|
|
|
|
{
|
2017-12-19 16:02:39 -05:00
|
|
|
|
var orgById = await _organizationRepository.GetByIdentifierAsync(organization.Identifier);
|
|
|
|
|
|
if (orgById != null && orgById.Id != organization.Id)
|
2017-08-12 22:16:42 -04:00
|
|
|
|
{
|
|
|
|
|
|
throw new BadRequestException("Identifier already in use by another organization.");
|
2017-12-19 16:02:39 -05:00
|
|
|
|
}
|
2017-08-12 22:16:42 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-02-24 07:54:19 +10:00
|
|
|
|
await ReplaceAndUpdateCacheAsync(organization, EventType.Organization_Updated);
|
2022-08-29 16:06:55 -04:00
|
|
|
|
|
2017-04-26 16:14:15 -04:00
|
|
|
|
if (updateBilling && !string.IsNullOrWhiteSpace(organization.GatewayCustomerId))
|
|
|
|
|
|
{
|
2017-12-19 16:02:39 -05:00
|
|
|
|
var customerService = new CustomerService();
|
|
|
|
|
|
await customerService.UpdateAsync(organization.GatewayCustomerId, new CustomerUpdateOptions
|
2022-08-29 15:53:48 -04:00
|
|
|
|
{
|
2017-12-19 16:02:39 -05:00
|
|
|
|
Email = organization.BillingEmail,
|
|
|
|
|
|
Description = organization.BusinessName
|
|
|
|
|
|
});
|
2017-04-26 16:14:15 -04:00
|
|
|
|
}
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2017-04-26 16:14:15 -04:00
|
|
|
|
|
2017-04-10 19:07:38 -04:00
|
|
|
|
public async Task UpdateTwoFactorProviderAsync(Organization organization, TwoFactorProviderType type)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2020-03-27 14:36:37 -04:00
|
|
|
|
if (!type.ToString().Contains("Organization"))
|
2017-04-10 19:07:38 -04:00
|
|
|
|
{
|
|
|
|
|
|
throw new ArgumentException("Not an organization provider type.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-08-12 16:38:22 -04:00
|
|
|
|
if (!organization.Use2fa)
|
2022-08-29 14:53:16 -04:00
|
|
|
|
{
|
2020-08-12 16:38:22 -04:00
|
|
|
|
throw new BadRequestException("Organization cannot use 2FA.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2017-12-19 16:02:39 -05:00
|
|
|
|
var providers = organization.GetTwoFactorProviders();
|
|
|
|
|
|
if (!providers?.ContainsKey(type) ?? true)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2017-12-19 16:02:39 -05:00
|
|
|
|
return;
|
2022-08-29 15:53:48 -04:00
|
|
|
|
}
|
2017-04-10 19:07:38 -04:00
|
|
|
|
|
2019-01-29 14:41:37 -05:00
|
|
|
|
providers[type].Enabled = true;
|
|
|
|
|
|
organization.SetTwoFactorProviders(providers);
|
|
|
|
|
|
await UpdateAsync(organization);
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2022-08-29 15:53:48 -04:00
|
|
|
|
|
2019-01-29 14:41:37 -05:00
|
|
|
|
public async Task DisableTwoFactorProviderAsync(Organization organization, TwoFactorProviderType type)
|
2017-04-10 19:07:38 -04:00
|
|
|
|
{
|
|
|
|
|
|
if (!type.ToString().Contains("Organization"))
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2017-04-10 19:07:38 -04:00
|
|
|
|
throw new ArgumentException("Not an organization provider type.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2018-04-02 23:18:26 -04:00
|
|
|
|
var providers = organization.GetTwoFactorProviders();
|
|
|
|
|
|
if (!providers?.ContainsKey(type) ?? true)
|
|
|
|
|
|
{
|
2020-03-27 14:36:37 -04:00
|
|
|
|
return;
|
2018-04-02 23:18:26 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
providers.Remove(type);
|
|
|
|
|
|
organization.SetTwoFactorProviders(providers);
|
2020-08-12 16:38:22 -04:00
|
|
|
|
await UpdateAsync(organization);
|
2022-08-29 15:53:48 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2018-04-02 23:18:26 -04:00
|
|
|
|
public async Task<List<OrganizationUser>> InviteUsersAsync(Guid organizationId, Guid? invitingUserId,
|
|
|
|
|
|
IEnumerable<(OrganizationUserInvite invite, string externalId)> invites)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2018-04-02 23:18:26 -04:00
|
|
|
|
var inviteTypes = new HashSet<OrganizationUserType>(invites.Where(i => i.invite.Type.HasValue)
|
|
|
|
|
|
.Select(i => i.invite.Type.Value));
|
|
|
|
|
|
if (invitingUserId.HasValue && inviteTypes.Count > 0)
|
|
|
|
|
|
{
|
2023-05-17 14:17:37 +01:00
|
|
|
|
foreach (var (invite, _) in invites)
|
2022-08-29 15:53:48 -04:00
|
|
|
|
{
|
2023-05-17 14:17:37 +01:00
|
|
|
|
await ValidateOrganizationUserUpdatePermissions(organizationId, invite.Type.Value, null, invite.Permissions);
|
|
|
|
|
|
await ValidateOrganizationCustomPermissionsEnabledAsync(organizationId, invite.Type.Value);
|
2022-08-29 15:53:48 -04:00
|
|
|
|
}
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2022-08-29 15:53:48 -04:00
|
|
|
|
|
2023-05-17 16:39:08 +01:00
|
|
|
|
var (organizationUsers, events) = await SaveUsersSendInvitesAsync(organizationId, invites, systemUser: null);
|
2022-11-09 12:13:29 +00:00
|
|
|
|
|
|
|
|
|
|
await _eventService.LogOrganizationUserEventsAsync(events);
|
|
|
|
|
|
|
|
|
|
|
|
return organizationUsers;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public async Task<List<OrganizationUser>> InviteUsersAsync(Guid organizationId, EventSystemUser systemUser,
|
|
|
|
|
|
IEnumerable<(OrganizationUserInvite invite, string externalId)> invites)
|
|
|
|
|
|
{
|
2023-05-17 16:39:08 +01:00
|
|
|
|
var (organizationUsers, events) = await SaveUsersSendInvitesAsync(organizationId, invites, systemUser);
|
2022-11-09 12:13:29 +00:00
|
|
|
|
|
|
|
|
|
|
await _eventService.LogOrganizationUserEventsAsync(events.Select(e => (e.Item1, e.Item2, systemUser, e.Item3)));
|
|
|
|
|
|
|
|
|
|
|
|
return organizationUsers;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private async Task<(List<OrganizationUser> organizationUsers, List<(OrganizationUser, EventType, DateTime?)> events)> SaveUsersSendInvitesAsync(Guid organizationId,
|
2023-05-17 16:39:08 +01:00
|
|
|
|
IEnumerable<(OrganizationUserInvite invite, string externalId)> invites, EventSystemUser? systemUser)
|
2022-11-09 12:13:29 +00:00
|
|
|
|
{
|
|
|
|
|
|
var organization = await GetOrgById(organizationId);
|
|
|
|
|
|
var initialSeatCount = organization.Seats;
|
|
|
|
|
|
if (organization == null || invites.Any(i => i.invite.Emails == null))
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new NotFoundException();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2018-04-02 23:18:26 -04:00
|
|
|
|
var newSeatsRequired = 0;
|
|
|
|
|
|
var existingEmails = new HashSet<string>(await _organizationUserRepository.SelectKnownEmailsAsync(
|
|
|
|
|
|
organizationId, invites.SelectMany(i => i.invite.Emails), false), StringComparer.InvariantCultureIgnoreCase);
|
|
|
|
|
|
if (organization.Seats.HasValue)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2023-02-24 07:54:19 +10:00
|
|
|
|
var occupiedSeats = await _organizationUserRepository.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id);
|
2022-09-23 14:30:39 +10:00
|
|
|
|
var availableSeats = organization.Seats.Value - occupiedSeats;
|
2018-04-02 23:18:26 -04:00
|
|
|
|
newSeatsRequired = invites.Sum(i => i.invite.Emails.Count()) - existingEmails.Count() - availableSeats;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-09-23 06:36:08 -04:00
|
|
|
|
if (newSeatsRequired > 0)
|
2021-05-17 09:43:02 -05:00
|
|
|
|
{
|
|
|
|
|
|
var (canScale, failureReason) = CanScale(organization, newSeatsRequired);
|
2021-09-23 06:36:08 -04:00
|
|
|
|
if (!canScale)
|
2021-05-17 09:43:02 -05:00
|
|
|
|
{
|
|
|
|
|
|
throw new BadRequestException(failureReason);
|
|
|
|
|
|
}
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2021-05-17 09:43:02 -05:00
|
|
|
|
|
|
|
|
|
|
var invitedAreAllOwners = invites.All(i => i.invite.Type == OrganizationUserType.Owner);
|
2023-05-17 16:39:08 +01:00
|
|
|
|
if (!invitedAreAllOwners && !await HasConfirmedOwnersExceptAsync(organizationId, new Guid[] { }, includeProvider: true))
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2021-07-08 17:05:32 +02:00
|
|
|
|
throw new BadRequestException("Organization must have at least one confirmed owner.");
|
2022-08-29 15:53:48 -04:00
|
|
|
|
}
|
2021-05-17 09:43:02 -05:00
|
|
|
|
|
|
|
|
|
|
var orgUsers = new List<OrganizationUser>();
|
2023-01-19 17:00:54 +01:00
|
|
|
|
var limitedCollectionOrgUsers = new List<(OrganizationUser, IEnumerable<CollectionAccessSelection>)>();
|
|
|
|
|
|
var orgUserGroups = new List<(OrganizationUser, IEnumerable<Guid>)>();
|
2021-09-28 16:18:44 -04:00
|
|
|
|
var orgUserInvitedCount = 0;
|
2021-09-23 06:36:08 -04:00
|
|
|
|
var exceptions = new List<Exception>();
|
2021-09-28 16:18:44 -04:00
|
|
|
|
var events = new List<(OrganizationUser, EventType, DateTime?)>();
|
|
|
|
|
|
foreach (var (invite, externalId) in invites)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2022-07-13 09:21:28 -04:00
|
|
|
|
// Prevent duplicate invitations
|
2021-09-23 06:36:08 -04:00
|
|
|
|
foreach (var email in invite.Emails.Distinct())
|
|
|
|
|
|
{
|
2022-08-29 16:06:55 -04:00
|
|
|
|
try
|
2021-09-28 16:18:44 -04:00
|
|
|
|
{
|
|
|
|
|
|
// Make sure user is not already invited
|
|
|
|
|
|
if (existingEmails.Contains(email))
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2021-09-28 16:18:44 -04:00
|
|
|
|
continue;
|
2021-09-23 06:36:08 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var orgUser = new OrganizationUser
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2021-05-17 09:43:02 -05:00
|
|
|
|
OrganizationId = organizationId,
|
2021-09-23 06:36:08 -04:00
|
|
|
|
UserId = null,
|
|
|
|
|
|
Email = email.ToLowerInvariant(),
|
|
|
|
|
|
Key = null,
|
|
|
|
|
|
Type = invite.Type.Value,
|
|
|
|
|
|
Status = OrganizationUserStatusType.Invited,
|
|
|
|
|
|
AccessAll = invite.AccessAll,
|
2023-01-31 18:38:53 +01:00
|
|
|
|
AccessSecretsManager = invite.AccessSecretsManager,
|
2021-09-23 06:36:08 -04:00
|
|
|
|
ExternalId = externalId,
|
|
|
|
|
|
CreationDate = DateTime.UtcNow,
|
|
|
|
|
|
RevisionDate = DateTime.UtcNow,
|
2021-05-17 09:43:02 -05:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
if (invite.Permissions != null)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2022-01-21 09:36:25 -05:00
|
|
|
|
orgUser.Permissions = JsonSerializer.Serialize(invite.Permissions, JsonHelpers.CamelCase);
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2021-09-23 06:36:08 -04:00
|
|
|
|
|
|
|
|
|
|
if (!orgUser.AccessAll && invite.Collections.Any())
|
2021-05-17 09:43:02 -05:00
|
|
|
|
{
|
2021-09-23 06:36:08 -04:00
|
|
|
|
limitedCollectionOrgUsers.Add((orgUser, invite.Collections));
|
2021-05-17 09:43:02 -05:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
orgUsers.Add(orgUser);
|
|
|
|
|
|
}
|
2017-04-07 16:41:04 -04:00
|
|
|
|
|
2023-01-19 17:00:54 +01:00
|
|
|
|
if (invite.Groups != null && invite.Groups.Any())
|
|
|
|
|
|
{
|
|
|
|
|
|
orgUserGroups.Add((orgUser, invite.Groups));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2017-05-11 14:52:35 -04:00
|
|
|
|
events.Add((orgUser, EventType.OrganizationUser_Invited, DateTime.UtcNow));
|
|
|
|
|
|
orgUserInvitedCount++;
|
2022-08-29 15:53:48 -04:00
|
|
|
|
}
|
2021-07-08 17:05:32 +02:00
|
|
|
|
catch (Exception e)
|
2022-08-29 15:53:48 -04:00
|
|
|
|
{
|
2021-09-23 06:36:08 -04:00
|
|
|
|
exceptions.Add(e);
|
2017-04-20 23:50:12 -04:00
|
|
|
|
}
|
2022-08-29 15:53:48 -04:00
|
|
|
|
}
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2022-08-29 15:53:48 -04:00
|
|
|
|
|
2021-05-25 19:23:47 +02:00
|
|
|
|
if (exceptions.Any())
|
2022-08-29 14:53:16 -04:00
|
|
|
|
{
|
2021-05-12 11:18:25 +02:00
|
|
|
|
throw new AggregateException("One or more errors occurred while inviting users.", exceptions);
|
2022-08-29 15:53:48 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-09-23 06:36:08 -04:00
|
|
|
|
var prorationDate = DateTime.UtcNow;
|
2022-08-29 16:06:55 -04:00
|
|
|
|
try
|
|
|
|
|
|
{
|
2021-05-17 09:43:02 -05:00
|
|
|
|
await _organizationUserRepository.CreateManyAsync(orgUsers);
|
2021-09-23 06:36:08 -04:00
|
|
|
|
foreach (var (orgUser, collections) in limitedCollectionOrgUsers)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2021-05-12 11:18:25 +02:00
|
|
|
|
await _organizationUserRepository.CreateAsync(orgUser, collections);
|
2022-08-29 15:53:48 -04:00
|
|
|
|
}
|
2021-05-25 19:23:47 +02:00
|
|
|
|
|
2023-01-19 17:00:54 +01:00
|
|
|
|
foreach (var (orgUser, groups) in orgUserGroups)
|
|
|
|
|
|
{
|
|
|
|
|
|
await _organizationUserRepository.UpdateGroupsAsync(orgUser.Id, groups);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-05-25 19:23:47 +02:00
|
|
|
|
if (!await _currentContext.ManageUsers(organization.Id))
|
2021-05-12 11:18:25 +02:00
|
|
|
|
{
|
2021-05-25 19:23:47 +02:00
|
|
|
|
throw new BadRequestException("Cannot add seats. Cannot manage organization users.");
|
2021-05-12 11:18:25 +02:00
|
|
|
|
}
|
2021-05-25 19:23:47 +02:00
|
|
|
|
|
2021-09-23 06:36:08 -04:00
|
|
|
|
await AutoAddSeatsAsync(organization, newSeatsRequired, prorationDate);
|
|
|
|
|
|
await SendInvitesAsync(orgUsers.Concat(limitedCollectionOrgUsers.Select(u => u.Item1)), organization);
|
2022-08-29 14:53:16 -04:00
|
|
|
|
|
2019-10-07 16:23:38 -04:00
|
|
|
|
await _referenceEventService.RaiseEventAsync(
|
2023-05-16 16:21:57 +02:00
|
|
|
|
new ReferenceEvent(ReferenceEventType.InvitedUsers, organization, _currentContext)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2021-09-23 06:36:08 -04:00
|
|
|
|
Users = orgUserInvitedCount
|
2022-08-29 16:06:55 -04:00
|
|
|
|
});
|
|
|
|
|
|
}
|
2021-09-23 06:36:08 -04:00
|
|
|
|
catch (Exception e)
|
2022-08-29 14:53:16 -04:00
|
|
|
|
{
|
2021-09-23 06:36:08 -04:00
|
|
|
|
// Revert any added users.
|
2021-05-25 19:23:47 +02:00
|
|
|
|
var invitedOrgUserIds = orgUsers.Select(u => u.Id).Concat(limitedCollectionOrgUsers.Select(u => u.Item1.Id));
|
|
|
|
|
|
await _organizationUserRepository.DeleteManyAsync(invitedOrgUserIds);
|
|
|
|
|
|
var currentSeatCount = (await _organizationRepository.GetByIdAsync(organization.Id)).Seats;
|
2021-05-12 11:18:25 +02:00
|
|
|
|
|
2019-10-07 16:23:38 -04:00
|
|
|
|
if (initialSeatCount.HasValue && currentSeatCount.HasValue && currentSeatCount.Value != initialSeatCount.Value)
|
2022-08-29 15:53:48 -04:00
|
|
|
|
{
|
2020-03-27 14:36:37 -04:00
|
|
|
|
await AdjustSeatsAsync(organization, initialSeatCount.Value - currentSeatCount.Value, prorationDate);
|
2022-08-29 15:53:48 -04:00
|
|
|
|
}
|
2022-08-29 14:53:16 -04:00
|
|
|
|
|
2021-09-23 06:36:08 -04:00
|
|
|
|
exceptions.Add(e);
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-09-23 06:36:08 -04:00
|
|
|
|
if (exceptions.Any())
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2021-05-25 19:23:47 +02:00
|
|
|
|
throw new AggregateException("One or more errors occurred while inviting users.", exceptions);
|
2022-08-29 15:53:48 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-11-09 12:13:29 +00:00
|
|
|
|
return (orgUsers, events);
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-05-25 19:23:47 +02:00
|
|
|
|
public async Task<IEnumerable<Tuple<OrganizationUser, string>>> ResendInvitesAsync(Guid organizationId, Guid? invitingUserId,
|
|
|
|
|
|
IEnumerable<Guid> organizationUsersId)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2021-05-25 19:23:47 +02:00
|
|
|
|
var orgUsers = await _organizationUserRepository.GetManyAsync(organizationUsersId);
|
|
|
|
|
|
var org = await GetOrgById(organizationId);
|
2022-08-29 16:06:55 -04:00
|
|
|
|
|
2019-10-07 16:23:38 -04:00
|
|
|
|
var result = new List<Tuple<OrganizationUser, string>>();
|
2021-05-25 19:23:47 +02:00
|
|
|
|
foreach (var orgUser in orgUsers)
|
2017-03-23 00:17:34 -04:00
|
|
|
|
{
|
2020-03-27 14:36:37 -04:00
|
|
|
|
if (orgUser.Status != OrganizationUserStatusType.Invited || orgUser.OrganizationId != organizationId)
|
2017-03-23 00:17:34 -04:00
|
|
|
|
{
|
|
|
|
|
|
result.Add(Tuple.Create(orgUser, "User invalid."));
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-04-14 11:13:16 +01:00
|
|
|
|
await SendInviteAsync(orgUser, org, false);
|
2019-10-07 16:23:38 -04:00
|
|
|
|
result.Add(Tuple.Create(orgUser, ""));
|
2017-03-23 00:17:34 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-05-17 09:43:02 -05:00
|
|
|
|
return result;
|
|
|
|
|
|
}
|
2021-12-16 15:35:09 +01:00
|
|
|
|
|
2023-04-14 11:13:16 +01:00
|
|
|
|
public async Task ResendInviteAsync(Guid organizationId, Guid? invitingUserId, Guid organizationUserId, bool initOrganization = false)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2022-05-10 17:12:09 -04:00
|
|
|
|
var orgUser = await _organizationUserRepository.GetByIdAsync(organizationUserId);
|
|
|
|
|
|
if (orgUser == null || orgUser.OrganizationId != organizationId ||
|
|
|
|
|
|
orgUser.Status != OrganizationUserStatusType.Invited)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2022-05-10 17:12:09 -04:00
|
|
|
|
throw new BadRequestException("User invalid.");
|
2017-03-23 00:17:34 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2020-07-15 12:38:45 -04:00
|
|
|
|
var org = await GetOrgById(orgUser.OrganizationId);
|
2023-04-14 11:13:16 +01:00
|
|
|
|
await SendInviteAsync(orgUser, org, initOrganization);
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-05-10 17:12:09 -04:00
|
|
|
|
private async Task SendInvitesAsync(IEnumerable<OrganizationUser> orgUsers, Organization organization)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2021-05-17 09:43:02 -05:00
|
|
|
|
string MakeToken(OrganizationUser orgUser) =>
|
|
|
|
|
|
_dataProtector.Protect($"OrganizationUserInvite {orgUser.Id} {orgUser.Email} {CoreHelpers.ToEpocMilliseconds(DateTime.UtcNow)}");
|
2022-08-29 16:06:55 -04:00
|
|
|
|
|
2017-03-04 21:28:41 -05:00
|
|
|
|
await _mailService.BulkSendOrganizationInviteEmailAsync(organization.Name,
|
2023-03-21 14:44:58 +00:00
|
|
|
|
orgUsers.Select(o => (o, new ExpiringToken(MakeToken(o), DateTime.UtcNow.AddDays(5)))), organization.PlanType == PlanType.Free);
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-04-14 11:13:16 +01:00
|
|
|
|
private async Task SendInviteAsync(OrganizationUser orgUser, Organization organization, bool initOrganization)
|
2017-03-04 21:28:41 -05:00
|
|
|
|
{
|
|
|
|
|
|
var now = DateTime.UtcNow;
|
|
|
|
|
|
var nowMillis = CoreHelpers.ToEpocMilliseconds(now);
|
|
|
|
|
|
var token = _dataProtector.Protect(
|
|
|
|
|
|
$"OrganizationUserInvite {orgUser.Id} {orgUser.Email} {nowMillis}");
|
2023-04-14 11:13:16 +01:00
|
|
|
|
await _mailService.SendOrganizationInviteEmailAsync(organization.Name, orgUser, new ExpiringToken(token, now.AddDays(5)), organization.PlanType == PlanType.Free, initOrganization);
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2022-08-29 15:53:48 -04:00
|
|
|
|
|
2017-03-04 21:28:41 -05:00
|
|
|
|
public async Task<OrganizationUser> AcceptUserAsync(Guid organizationUserId, User user, string token,
|
|
|
|
|
|
IUserService userService)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2017-03-04 21:28:41 -05:00
|
|
|
|
var orgUser = await _organizationUserRepository.GetByIdAsync(organizationUserId);
|
|
|
|
|
|
if (orgUser == null)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2017-03-04 21:28:41 -05:00
|
|
|
|
throw new BadRequestException("User invalid.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-10-13 15:00:33 -05:00
|
|
|
|
if (!CoreHelpers.UserInviteTokenIsValid(_dataProtector, token, user.Email, orgUser.Id, _globalSettings))
|
2022-08-29 14:53:16 -04:00
|
|
|
|
{
|
2020-10-13 15:00:33 -05:00
|
|
|
|
throw new BadRequestException("Invalid token.");
|
2017-03-23 00:17:34 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2020-10-13 15:00:33 -05:00
|
|
|
|
var existingOrgUserCount = await _organizationUserRepository.GetCountByOrganizationAsync(
|
|
|
|
|
|
orgUser.OrganizationId, user.Email, true);
|
|
|
|
|
|
if (existingOrgUserCount > 0)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2020-10-13 15:00:33 -05:00
|
|
|
|
if (orgUser.Status == OrganizationUserStatusType.Accepted)
|
2022-08-29 15:53:48 -04:00
|
|
|
|
{
|
2020-10-13 15:00:33 -05:00
|
|
|
|
throw new BadRequestException("Invitation already accepted. You will receive an email when your organization membership is confirmed.");
|
2022-08-29 15:53:48 -04:00
|
|
|
|
}
|
2020-10-13 15:00:33 -05:00
|
|
|
|
throw new BadRequestException("You are already part of this organization.");
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2022-08-29 15:53:48 -04:00
|
|
|
|
|
2020-10-13 15:00:33 -05:00
|
|
|
|
if (string.IsNullOrWhiteSpace(orgUser.Email) ||
|
|
|
|
|
|
!orgUser.Email.Equals(user.Email, StringComparison.InvariantCultureIgnoreCase))
|
2022-08-29 15:53:48 -04:00
|
|
|
|
{
|
2021-02-17 10:28:49 +11:00
|
|
|
|
throw new BadRequestException("User email does not match invite.");
|
2022-08-29 15:53:48 -04:00
|
|
|
|
}
|
2020-10-13 15:00:33 -05:00
|
|
|
|
|
2021-02-17 10:28:49 +11:00
|
|
|
|
return await AcceptUserAsync(orgUser, user, userService);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-10-13 15:00:33 -05:00
|
|
|
|
public async Task<OrganizationUser> AcceptUserAsync(string orgIdentifier, User user, IUserService userService)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2020-10-13 15:00:33 -05:00
|
|
|
|
var org = await _organizationRepository.GetByIdentifierAsync(orgIdentifier);
|
|
|
|
|
|
if (org == null)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2020-10-13 15:00:33 -05:00
|
|
|
|
throw new BadRequestException("Organization invalid.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var usersOrgs = await _organizationUserRepository.GetManyByUserAsync(user.Id);
|
|
|
|
|
|
var orgUser = usersOrgs.FirstOrDefault(u => u.OrganizationId == org.Id);
|
|
|
|
|
|
if (orgUser == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new BadRequestException("User not found within organization.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return await AcceptUserAsync(orgUser, user, userService);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private async Task<OrganizationUser> AcceptUserAsync(OrganizationUser orgUser, User user,
|
|
|
|
|
|
IUserService userService)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2022-09-08 07:54:58 +10:00
|
|
|
|
if (orgUser.Status == OrganizationUserStatusType.Revoked)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new BadRequestException("Your organization access has been revoked.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-10-13 15:00:33 -05:00
|
|
|
|
if (orgUser.Status != OrganizationUserStatusType.Invited)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2020-10-13 15:00:33 -05:00
|
|
|
|
throw new BadRequestException("Already accepted.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-05-17 09:43:02 -05:00
|
|
|
|
if (orgUser.Type == OrganizationUserType.Owner || orgUser.Type == OrganizationUserType.Admin)
|
2020-10-13 15:00:33 -05:00
|
|
|
|
{
|
2017-12-19 16:02:39 -05:00
|
|
|
|
var org = await GetOrgById(orgUser.OrganizationId);
|
|
|
|
|
|
if (org.PlanType == PlanType.Free)
|
2022-08-29 15:53:48 -04:00
|
|
|
|
{
|
2017-12-19 16:02:39 -05:00
|
|
|
|
var adminCount = await _organizationUserRepository.GetCountByFreeOrganizationAdminUserAsync(
|
|
|
|
|
|
user.Id);
|
2020-03-27 14:36:37 -04:00
|
|
|
|
if (adminCount > 0)
|
2017-09-08 17:14:15 -04:00
|
|
|
|
{
|
|
|
|
|
|
throw new BadRequestException("You can only be an admin of one free organization.");
|
|
|
|
|
|
}
|
2017-04-07 14:03:36 -04:00
|
|
|
|
}
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2017-04-07 14:03:36 -04:00
|
|
|
|
|
2021-09-28 06:54:28 +10:00
|
|
|
|
// Enforce Single Organization Policy of organization user is trying to join
|
2021-03-03 08:15:42 +10:00
|
|
|
|
var allOrgUsers = await _organizationUserRepository.GetManyByUserAsync(user.Id);
|
2021-09-28 06:54:28 +10:00
|
|
|
|
var hasOtherOrgs = allOrgUsers.Any(ou => ou.OrganizationId != orgUser.OrganizationId);
|
2023-05-12 08:22:19 +01:00
|
|
|
|
var invitedSingleOrgPolicies = await _policyService.GetPoliciesApplicableToUserAsync(user.Id,
|
2021-09-28 06:54:28 +10:00
|
|
|
|
PolicyType.SingleOrg, OrganizationUserStatusType.Invited);
|
2021-03-03 08:15:42 +10:00
|
|
|
|
|
2021-09-28 06:54:28 +10:00
|
|
|
|
if (hasOtherOrgs && invitedSingleOrgPolicies.Any(p => p.OrganizationId == orgUser.OrganizationId))
|
2020-02-19 14:56:16 -05:00
|
|
|
|
{
|
2021-03-03 08:15:42 +10:00
|
|
|
|
throw new BadRequestException("You may not join this organization until you leave or remove " +
|
|
|
|
|
|
"all other organizations.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Enforce Single Organization Policy of other organizations user is a member of
|
2023-05-12 08:22:19 +01:00
|
|
|
|
var anySingleOrgPolicies = await _policyService.AnyPoliciesApplicableToUserAsync(user.Id,
|
2021-09-28 06:54:28 +10:00
|
|
|
|
PolicyType.SingleOrg);
|
2023-05-12 08:22:19 +01:00
|
|
|
|
if (anySingleOrgPolicies)
|
2021-03-03 08:15:42 +10:00
|
|
|
|
{
|
|
|
|
|
|
throw new BadRequestException("You cannot join this organization because you are a member of " +
|
2021-09-28 06:54:28 +10:00
|
|
|
|
"another organization which forbids it");
|
2021-03-03 08:15:42 +10:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-09-28 06:54:28 +10:00
|
|
|
|
// Enforce Two Factor Authentication Policy of organization user is trying to join
|
|
|
|
|
|
if (!await userService.TwoFactorIsEnabledAsync(user))
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2023-05-12 08:22:19 +01:00
|
|
|
|
var invitedTwoFactorPolicies = await _policyService.GetPoliciesApplicableToUserAsync(user.Id,
|
2021-09-28 06:54:28 +10:00
|
|
|
|
PolicyType.TwoFactorAuthentication, OrganizationUserStatusType.Invited);
|
|
|
|
|
|
if (invitedTwoFactorPolicies.Any(p => p.OrganizationId == orgUser.OrganizationId))
|
2021-03-03 08:15:42 +10:00
|
|
|
|
{
|
2021-09-28 06:54:28 +10:00
|
|
|
|
throw new BadRequestException("You cannot join this organization until you enable " +
|
|
|
|
|
|
"two-step login on your user account.");
|
2020-02-19 14:56:16 -05:00
|
|
|
|
}
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2020-02-19 14:56:16 -05:00
|
|
|
|
|
2017-04-12 11:00:40 -04:00
|
|
|
|
orgUser.Status = OrganizationUserStatusType.Accepted;
|
2017-03-23 16:56:25 -04:00
|
|
|
|
orgUser.UserId = user.Id;
|
2017-03-04 21:28:41 -05:00
|
|
|
|
orgUser.Email = null;
|
2022-04-20 21:05:21 +02:00
|
|
|
|
|
|
|
|
|
|
await _organizationUserRepository.ReplaceAsync(orgUser);
|
|
|
|
|
|
|
2017-03-04 21:28:41 -05:00
|
|
|
|
var admins = await _organizationUserRepository.GetManyByMinimumRoleAsync(orgUser.OrganizationId, OrganizationUserType.Admin);
|
2021-09-23 06:36:08 -04:00
|
|
|
|
var adminEmails = admins.Select(a => a.Email).Distinct().ToList();
|
2022-08-29 15:53:48 -04:00
|
|
|
|
|
2022-04-20 21:05:21 +02:00
|
|
|
|
if (adminEmails.Count > 0)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2021-09-23 06:36:08 -04:00
|
|
|
|
var organization = await _organizationRepository.GetByIdAsync(orgUser.OrganizationId);
|
2017-08-14 09:23:54 -04:00
|
|
|
|
await _mailService.SendOrganizationAcceptedEmailAsync(organization, user.Email, adminEmails);
|
2017-03-04 21:28:41 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-09-23 06:36:08 -04:00
|
|
|
|
return orgUser;
|
|
|
|
|
|
}
|
2021-05-25 19:23:47 +02:00
|
|
|
|
|
2017-03-23 00:17:34 -04:00
|
|
|
|
public async Task<OrganizationUser> ConfirmUserAsync(Guid organizationId, Guid organizationUserId, string key,
|
|
|
|
|
|
Guid confirmingUserId, IUserService userService)
|
|
|
|
|
|
{
|
|
|
|
|
|
var result = await ConfirmUsersAsync(organizationId, new Dictionary<Guid, string>() { { organizationUserId, key } },
|
|
|
|
|
|
confirmingUserId, userService);
|
2017-03-04 21:28:41 -05:00
|
|
|
|
|
2021-05-25 19:23:47 +02:00
|
|
|
|
if (!result.Any())
|
2017-09-08 17:14:15 -04:00
|
|
|
|
{
|
2021-05-25 19:23:47 +02:00
|
|
|
|
throw new BadRequestException("User not valid.");
|
|
|
|
|
|
}
|
2021-09-23 06:36:08 -04:00
|
|
|
|
|
2021-05-25 19:23:47 +02:00
|
|
|
|
var (orgUser, error) = result[0];
|
|
|
|
|
|
if (error != "")
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new BadRequestException(error);
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2021-05-25 19:23:47 +02:00
|
|
|
|
return orgUser;
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2022-08-29 14:53:16 -04:00
|
|
|
|
|
2021-05-25 19:23:47 +02:00
|
|
|
|
public async Task<List<Tuple<OrganizationUser, string>>> ConfirmUsersAsync(Guid organizationId, Dictionary<Guid, string> keys,
|
|
|
|
|
|
Guid confirmingUserId, IUserService userService)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2021-05-25 19:23:47 +02:00
|
|
|
|
var organizationUsers = await _organizationUserRepository.GetManyAsync(keys.Keys);
|
|
|
|
|
|
var validOrganizationUsers = organizationUsers
|
|
|
|
|
|
.Where(u => u.Status == OrganizationUserStatusType.Accepted && u.OrganizationId == organizationId && u.UserId != null)
|
|
|
|
|
|
.ToList();
|
2022-08-29 16:06:55 -04:00
|
|
|
|
|
2021-05-25 19:23:47 +02:00
|
|
|
|
if (!validOrganizationUsers.Any())
|
|
|
|
|
|
{
|
|
|
|
|
|
return new List<Tuple<OrganizationUser, string>>();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var validOrganizationUserIds = validOrganizationUsers.Select(u => u.UserId.Value).ToList();
|
2021-09-23 06:36:08 -04:00
|
|
|
|
|
2021-05-25 19:23:47 +02:00
|
|
|
|
var organization = await GetOrgById(organizationId);
|
|
|
|
|
|
var policies = await _policyRepository.GetManyByOrganizationIdAsync(organizationId);
|
|
|
|
|
|
var usersOrgs = await _organizationUserRepository.GetManyByManyUsersAsync(validOrganizationUserIds);
|
|
|
|
|
|
var users = await _userRepository.GetManyAsync(validOrganizationUserIds);
|
|
|
|
|
|
|
|
|
|
|
|
var keyedFilteredUsers = validOrganizationUsers.ToDictionary(u => u.UserId.Value, u => u);
|
|
|
|
|
|
var keyedOrganizationUsers = usersOrgs.GroupBy(u => u.UserId.Value)
|
|
|
|
|
|
.ToDictionary(u => u.Key, u => u.ToList());
|
|
|
|
|
|
|
|
|
|
|
|
var succeededUsers = new List<OrganizationUser>();
|
|
|
|
|
|
var result = new List<Tuple<OrganizationUser, string>>();
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var user in users)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2021-08-10 12:16:10 -04:00
|
|
|
|
if (!keyedFilteredUsers.ContainsKey(user.Id))
|
2022-08-29 14:53:16 -04:00
|
|
|
|
{
|
2021-08-10 12:16:10 -04:00
|
|
|
|
continue;
|
2022-08-29 15:53:48 -04:00
|
|
|
|
}
|
2021-08-10 12:16:10 -04:00
|
|
|
|
var orgUser = keyedFilteredUsers[user.Id];
|
|
|
|
|
|
var orgUsers = keyedOrganizationUsers.GetValueOrDefault(user.Id, new List<OrganizationUser>());
|
|
|
|
|
|
try
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2021-08-10 12:16:10 -04:00
|
|
|
|
if (organization.PlanType == PlanType.Free && (orgUser.Type == OrganizationUserType.Admin
|
|
|
|
|
|
|| orgUser.Type == OrganizationUserType.Owner))
|
2022-08-29 15:53:48 -04:00
|
|
|
|
{
|
2021-08-10 12:16:10 -04:00
|
|
|
|
// Since free organizations only supports a few users there is not much point in avoiding N+1 queries for this.
|
|
|
|
|
|
var adminCount = await _organizationUserRepository.GetCountByFreeOrganizationAdminUserAsync(user.Id);
|
|
|
|
|
|
if (adminCount > 0)
|
2021-05-25 19:23:47 +02:00
|
|
|
|
{
|
|
|
|
|
|
throw new BadRequestException("User can only be an admin of one free organization.");
|
|
|
|
|
|
}
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2021-05-25 19:23:47 +02:00
|
|
|
|
|
|
|
|
|
|
await CheckPolicies(policies, organizationId, user, orgUsers, userService);
|
|
|
|
|
|
orgUser.Status = OrganizationUserStatusType.Confirmed;
|
|
|
|
|
|
orgUser.Key = keys[orgUser.Id];
|
|
|
|
|
|
orgUser.Email = null;
|
2021-09-23 06:36:08 -04:00
|
|
|
|
|
2021-05-25 19:23:47 +02:00
|
|
|
|
await _eventService.LogOrganizationUserEventAsync(orgUser, EventType.OrganizationUser_Confirmed);
|
|
|
|
|
|
await _mailService.SendOrganizationConfirmedEmailAsync(organization.Name, user.Email);
|
|
|
|
|
|
await DeleteAndPushUserRegistrationAsync(organizationId, user.Id);
|
|
|
|
|
|
succeededUsers.Add(orgUser);
|
|
|
|
|
|
result.Add(Tuple.Create(orgUser, ""));
|
2022-08-29 14:53:16 -04:00
|
|
|
|
}
|
2021-05-25 19:23:47 +02:00
|
|
|
|
catch (BadRequestException e)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2021-05-25 19:23:47 +02:00
|
|
|
|
result.Add(Tuple.Create(orgUser, e.Message));
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-05-25 19:23:47 +02:00
|
|
|
|
await _organizationUserRepository.ReplaceManyAsync(succeededUsers);
|
2017-09-08 17:14:15 -04:00
|
|
|
|
|
2021-05-25 19:23:47 +02:00
|
|
|
|
return result;
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2021-05-25 19:23:47 +02:00
|
|
|
|
|
2021-10-25 10:19:37 -05:00
|
|
|
|
internal (bool canScale, string failureReason) CanScale(Organization organization,
|
|
|
|
|
|
int seatsToAdd)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2021-09-23 06:36:08 -04:00
|
|
|
|
var failureReason = "";
|
|
|
|
|
|
if (_globalSettings.SelfHosted)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2021-09-23 06:36:08 -04:00
|
|
|
|
failureReason = "Cannot autoscale on self-hosted instance.";
|
2021-05-25 19:23:47 +02:00
|
|
|
|
return (false, failureReason);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-10-25 10:19:37 -05:00
|
|
|
|
if (seatsToAdd < 1)
|
2021-09-23 06:36:08 -04:00
|
|
|
|
{
|
|
|
|
|
|
return (true, failureReason);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (organization.Seats.HasValue &&
|
|
|
|
|
|
organization.MaxAutoscaleSeats.HasValue &&
|
|
|
|
|
|
organization.MaxAutoscaleSeats.Value < organization.Seats.Value + seatsToAdd)
|
|
|
|
|
|
{
|
2022-09-23 14:30:39 +10:00
|
|
|
|
return (false, $"Seat limit has been reached.");
|
2021-09-23 06:36:08 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return (true, failureReason);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public async Task AutoAddSeatsAsync(Organization organization, int seatsToAdd, DateTime? prorationDate = null)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2021-09-23 06:36:08 -04:00
|
|
|
|
if (seatsToAdd < 1 || !organization.Seats.HasValue)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2021-09-23 06:36:08 -04:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-10-25 10:19:37 -05:00
|
|
|
|
var (canScale, failureMessage) = CanScale(organization, seatsToAdd);
|
|
|
|
|
|
if (!canScale)
|
2021-09-23 06:36:08 -04:00
|
|
|
|
{
|
|
|
|
|
|
throw new BadRequestException(failureMessage);
|
2022-08-29 15:53:48 -04:00
|
|
|
|
}
|
2021-09-23 06:36:08 -04:00
|
|
|
|
|
2023-01-26 11:51:26 -05:00
|
|
|
|
var providerOrg = await this._providerOrganizationRepository.GetByOrganizationId(organization.Id);
|
|
|
|
|
|
|
|
|
|
|
|
IEnumerable<string> ownerEmails;
|
|
|
|
|
|
if (providerOrg != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
ownerEmails = (await _providerUserRepository.GetManyDetailsByProviderAsync(providerOrg.ProviderId, ProviderUserStatusType.Confirmed))
|
|
|
|
|
|
.Select(u => u.Email).Distinct();
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
ownerEmails = (await _organizationUserRepository.GetManyByMinimumRoleAsync(organization.Id,
|
|
|
|
|
|
OrganizationUserType.Owner)).Select(u => u.Email).Distinct();
|
|
|
|
|
|
}
|
2021-09-23 06:36:08 -04:00
|
|
|
|
var initialSeatCount = organization.Seats.Value;
|
|
|
|
|
|
|
2021-10-07 08:05:02 -05:00
|
|
|
|
await AdjustSeatsAsync(organization, seatsToAdd, prorationDate, ownerEmails);
|
2021-09-23 06:36:08 -04:00
|
|
|
|
|
2021-05-25 19:23:47 +02:00
|
|
|
|
if (!organization.OwnersNotifiedOfAutoscaling.HasValue)
|
2022-08-29 15:53:48 -04:00
|
|
|
|
{
|
2021-05-25 19:23:47 +02:00
|
|
|
|
await _mailService.SendOrganizationAutoscaledEmailAsync(organization, initialSeatCount,
|
2021-09-23 06:36:08 -04:00
|
|
|
|
ownerEmails);
|
2021-05-25 19:23:47 +02:00
|
|
|
|
organization.OwnersNotifiedOfAutoscaling = DateTime.UtcNow;
|
|
|
|
|
|
await _organizationRepository.UpsertAsync(organization);
|
2020-03-09 15:13:40 -04:00
|
|
|
|
}
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2020-03-09 15:13:40 -04:00
|
|
|
|
|
2020-10-27 10:28:41 -04:00
|
|
|
|
private async Task CheckPolicies(ICollection<Policy> policies, Guid organizationId, User user,
|
|
|
|
|
|
ICollection<OrganizationUser> userOrgs, IUserService userService)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2020-10-20 02:48:10 -04:00
|
|
|
|
var usingTwoFactorPolicy = policies.Any(p => p.Type == PolicyType.TwoFactorAuthentication && p.Enabled);
|
|
|
|
|
|
if (usingTwoFactorPolicy && !await userService.TwoFactorIsEnabledAsync(user))
|
2022-08-29 14:53:16 -04:00
|
|
|
|
{
|
2020-10-20 02:48:10 -04:00
|
|
|
|
throw new BadRequestException("User does not have two-step login enabled.");
|
|
|
|
|
|
}
|
2017-03-09 23:58:43 -05:00
|
|
|
|
|
2019-03-05 23:24:14 -05:00
|
|
|
|
var usingSingleOrgPolicy = policies.Any(p => p.Type == PolicyType.SingleOrg && p.Enabled);
|
2022-08-03 07:09:22 +10:00
|
|
|
|
if (usingSingleOrgPolicy)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2019-03-05 23:24:14 -05:00
|
|
|
|
if (userOrgs.Any(ou => ou.OrganizationId != organizationId && ou.Status != OrganizationUserStatusType.Invited))
|
2022-08-29 15:53:48 -04:00
|
|
|
|
{
|
2017-03-09 23:58:43 -05:00
|
|
|
|
throw new BadRequestException("User is a member of another organization.");
|
2022-08-29 15:53:48 -04:00
|
|
|
|
}
|
2017-03-09 23:58:43 -05:00
|
|
|
|
}
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2017-03-09 23:58:43 -05:00
|
|
|
|
|
2021-01-12 11:02:39 -05:00
|
|
|
|
public async Task SaveUserAsync(OrganizationUser user, Guid? savingUserId,
|
2023-01-19 17:00:54 +01:00
|
|
|
|
IEnumerable<CollectionAccessSelection> collections,
|
|
|
|
|
|
IEnumerable<Guid> groups)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2021-01-12 11:02:39 -05:00
|
|
|
|
if (user.Id.Equals(default(Guid)))
|
2021-05-17 09:43:02 -05:00
|
|
|
|
{
|
2021-01-12 11:02:39 -05:00
|
|
|
|
throw new BadRequestException("Invite the user first.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-07-08 17:05:32 +02:00
|
|
|
|
var originalUser = await _organizationUserRepository.GetByIdAsync(user.Id);
|
|
|
|
|
|
if (user.Equals(originalUser))
|
2022-08-29 15:53:48 -04:00
|
|
|
|
{
|
2021-07-08 17:05:32 +02:00
|
|
|
|
throw new BadRequestException("Please make changes before saving.");
|
2017-09-27 22:37:13 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2020-03-27 14:36:37 -04:00
|
|
|
|
if (savingUserId.HasValue)
|
2021-09-23 06:36:08 -04:00
|
|
|
|
{
|
2023-05-17 14:17:37 +01:00
|
|
|
|
await ValidateOrganizationUserUpdatePermissions(user.OrganizationId, user.Type, originalUser.Type, user.GetPermissions());
|
2017-03-29 21:26:19 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-12-06 09:50:08 +00:00
|
|
|
|
await ValidateOrganizationCustomPermissionsEnabledAsync(user.OrganizationId, user.Type);
|
|
|
|
|
|
|
2017-05-11 14:52:35 -04:00
|
|
|
|
if (user.Type != OrganizationUserType.Owner &&
|
2017-12-01 16:00:30 -05:00
|
|
|
|
!await HasConfirmedOwnersExceptAsync(user.OrganizationId, new[] { user.Id }))
|
2022-08-29 15:53:48 -04:00
|
|
|
|
{
|
2017-12-01 16:00:30 -05:00
|
|
|
|
throw new BadRequestException("Organization must have at least one confirmed owner.");
|
2022-08-29 14:53:16 -04:00
|
|
|
|
}
|
2017-03-09 23:58:43 -05:00
|
|
|
|
|
2017-12-12 13:21:15 -05:00
|
|
|
|
if (user.AccessAll)
|
2022-08-29 15:53:48 -04:00
|
|
|
|
{
|
2017-12-12 13:21:15 -05:00
|
|
|
|
// We don't need any collections if we're flagged to have all access.
|
2023-01-19 17:00:54 +01:00
|
|
|
|
collections = new List<CollectionAccessSelection>();
|
2017-03-23 00:17:34 -04:00
|
|
|
|
}
|
|
|
|
|
|
await _organizationUserRepository.ReplaceAsync(user, collections);
|
2023-01-19 17:00:54 +01:00
|
|
|
|
|
|
|
|
|
|
if (groups != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
await _organizationUserRepository.UpdateGroupsAsync(user.Id, groups);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-03-27 14:36:37 -04:00
|
|
|
|
await _eventService.LogOrganizationUserEventAsync(user, EventType.OrganizationUser_Updated);
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2017-03-23 00:17:34 -04:00
|
|
|
|
|
2022-10-31 09:58:21 +00:00
|
|
|
|
[Obsolete("IDeleteOrganizationUserCommand should be used instead. To be removed by EC-607.")]
|
2020-03-27 14:36:37 -04:00
|
|
|
|
public async Task DeleteUserAsync(Guid organizationId, Guid organizationUserId, Guid? deletingUserId)
|
2022-11-09 12:13:29 +00:00
|
|
|
|
{
|
|
|
|
|
|
var orgUser = await RepositoryDeleteUserAsync(organizationId, organizationUserId, deletingUserId);
|
|
|
|
|
|
await _eventService.LogOrganizationUserEventAsync(orgUser, EventType.OrganizationUser_Removed);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[Obsolete("IDeleteOrganizationUserCommand should be used instead. To be removed by EC-607.")]
|
|
|
|
|
|
public async Task DeleteUserAsync(Guid organizationId, Guid organizationUserId,
|
|
|
|
|
|
EventSystemUser systemUser)
|
|
|
|
|
|
{
|
|
|
|
|
|
var orgUser = await RepositoryDeleteUserAsync(organizationId, organizationUserId, null);
|
|
|
|
|
|
await _eventService.LogOrganizationUserEventAsync(orgUser, EventType.OrganizationUser_Removed, systemUser);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private async Task<OrganizationUser> RepositoryDeleteUserAsync(Guid organizationId, Guid organizationUserId, Guid? deletingUserId)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2017-04-18 15:27:54 -04:00
|
|
|
|
var orgUser = await _organizationUserRepository.GetByIdAsync(organizationUserId);
|
2021-07-16 14:17:24 -04:00
|
|
|
|
if (orgUser == null || orgUser.OrganizationId != organizationId)
|
2017-04-18 15:27:54 -04:00
|
|
|
|
{
|
|
|
|
|
|
throw new BadRequestException("User not valid.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-05-17 10:10:44 +02:00
|
|
|
|
if (deletingUserId.HasValue && orgUser.UserId == deletingUserId.Value)
|
2017-09-27 22:37:13 -04:00
|
|
|
|
{
|
2021-05-17 10:10:44 +02:00
|
|
|
|
throw new BadRequestException("You cannot remove yourself.");
|
2017-09-27 22:37:13 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-09-23 06:36:08 -04:00
|
|
|
|
if (orgUser.Type == OrganizationUserType.Owner && deletingUserId.HasValue &&
|
|
|
|
|
|
!await _currentContext.OrganizationOwner(organizationId))
|
2017-03-29 21:26:19 -04:00
|
|
|
|
{
|
|
|
|
|
|
throw new BadRequestException("Only owners can delete other owners.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-05-17 16:39:08 +01:00
|
|
|
|
if (!await HasConfirmedOwnersExceptAsync(organizationId, new[] { organizationUserId }, includeProvider: true))
|
2022-08-29 15:53:48 -04:00
|
|
|
|
{
|
2017-12-01 16:00:30 -05:00
|
|
|
|
throw new BadRequestException("Organization must have at least one confirmed owner.");
|
2022-08-29 15:53:48 -04:00
|
|
|
|
}
|
2017-05-26 22:52:50 -04:00
|
|
|
|
|
2020-03-27 14:36:37 -04:00
|
|
|
|
await _organizationUserRepository.DeleteAsync(orgUser);
|
2017-04-12 10:07:27 -04:00
|
|
|
|
|
|
|
|
|
|
if (orgUser.UserId.HasValue)
|
2022-08-29 14:53:16 -04:00
|
|
|
|
{
|
2017-04-12 10:07:27 -04:00
|
|
|
|
await DeleteAndPushUserRegistrationAsync(organizationId, orgUser.UserId.Value);
|
|
|
|
|
|
}
|
2022-11-09 12:13:29 +00:00
|
|
|
|
|
|
|
|
|
|
return orgUser;
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2017-04-12 10:07:27 -04:00
|
|
|
|
|
2021-09-23 06:36:08 -04:00
|
|
|
|
public async Task DeleteUserAsync(Guid organizationId, Guid userId)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2017-12-01 16:00:30 -05:00
|
|
|
|
var orgUser = await _organizationUserRepository.GetByOrganizationAsync(organizationId, userId);
|
|
|
|
|
|
if (orgUser == null)
|
2017-04-12 10:07:27 -04:00
|
|
|
|
{
|
2017-12-01 16:00:30 -05:00
|
|
|
|
throw new NotFoundException();
|
2022-08-29 15:53:48 -04:00
|
|
|
|
}
|
2017-05-26 22:52:50 -04:00
|
|
|
|
|
2021-05-17 10:10:44 +02:00
|
|
|
|
if (!await HasConfirmedOwnersExceptAsync(organizationId, new[] { orgUser.Id }))
|
2022-08-29 15:53:48 -04:00
|
|
|
|
{
|
2021-05-17 10:10:44 +02:00
|
|
|
|
throw new BadRequestException("Organization must have at least one confirmed owner.");
|
2022-08-29 14:53:16 -04:00
|
|
|
|
}
|
2017-03-23 00:17:34 -04:00
|
|
|
|
|
2021-05-25 19:23:47 +02:00
|
|
|
|
await _organizationUserRepository.DeleteAsync(orgUser);
|
2021-05-17 10:10:44 +02:00
|
|
|
|
await _eventService.LogOrganizationUserEventAsync(orgUser, EventType.OrganizationUser_Removed);
|
|
|
|
|
|
|
|
|
|
|
|
if (orgUser.UserId.HasValue)
|
|
|
|
|
|
{
|
|
|
|
|
|
await DeleteAndPushUserRegistrationAsync(organizationId, orgUser.UserId.Value);
|
|
|
|
|
|
}
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2021-05-17 10:10:44 +02:00
|
|
|
|
|
2021-05-25 19:23:47 +02:00
|
|
|
|
public async Task<List<Tuple<OrganizationUser, string>>> DeleteUsersAsync(Guid organizationId,
|
|
|
|
|
|
IEnumerable<Guid> organizationUsersId,
|
|
|
|
|
|
Guid? deletingUserId)
|
2021-05-17 10:10:44 +02:00
|
|
|
|
{
|
2021-05-25 19:23:47 +02:00
|
|
|
|
var orgUsers = await _organizationUserRepository.GetManyAsync(organizationUsersId);
|
|
|
|
|
|
var filteredUsers = orgUsers.Where(u => u.OrganizationId == organizationId)
|
|
|
|
|
|
.ToList();
|
2021-05-17 10:10:44 +02:00
|
|
|
|
|
2021-05-25 19:23:47 +02:00
|
|
|
|
if (!filteredUsers.Any())
|
2021-05-17 10:10:44 +02:00
|
|
|
|
{
|
2021-07-08 17:05:32 +02:00
|
|
|
|
throw new BadRequestException("Users invalid.");
|
2021-05-17 10:10:44 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-05-25 19:23:47 +02:00
|
|
|
|
if (!await HasConfirmedOwnersExceptAsync(organizationId, organizationUsersId))
|
2021-05-17 10:10:44 +02:00
|
|
|
|
{
|
2021-05-25 19:23:47 +02:00
|
|
|
|
throw new BadRequestException("Organization must have at least one confirmed owner.");
|
2022-08-29 15:53:48 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-05-25 19:23:47 +02:00
|
|
|
|
var deletingUserIsOwner = false;
|
|
|
|
|
|
if (deletingUserId.HasValue)
|
2022-08-29 15:53:48 -04:00
|
|
|
|
{
|
2021-05-25 19:23:47 +02:00
|
|
|
|
deletingUserIsOwner = await _currentContext.OrganizationOwner(organizationId);
|
|
|
|
|
|
}
|
2021-05-17 10:10:44 +02:00
|
|
|
|
|
2021-05-25 19:23:47 +02:00
|
|
|
|
var result = new List<Tuple<OrganizationUser, string>>();
|
|
|
|
|
|
var deletedUserIds = new List<Guid>();
|
|
|
|
|
|
foreach (var orgUser in filteredUsers)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
|
|
|
|
|
try
|
2022-08-29 15:53:48 -04:00
|
|
|
|
{
|
2021-05-25 19:23:47 +02:00
|
|
|
|
if (deletingUserId.HasValue && orgUser.UserId == deletingUserId)
|
|
|
|
|
|
{
|
2021-11-09 16:37:32 +01:00
|
|
|
|
throw new BadRequestException("You cannot remove yourself.");
|
2022-08-29 15:53:48 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-06-16 15:59:57 -04:00
|
|
|
|
if (orgUser.Type == OrganizationUserType.Owner && deletingUserId.HasValue && !deletingUserIsOwner)
|
2022-08-29 15:53:48 -04:00
|
|
|
|
{
|
2021-05-25 19:23:47 +02:00
|
|
|
|
throw new BadRequestException("Only owners can delete other owners.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
await _eventService.LogOrganizationUserEventAsync(orgUser, EventType.OrganizationUser_Removed);
|
|
|
|
|
|
|
|
|
|
|
|
if (orgUser.UserId.HasValue)
|
2021-05-17 10:10:44 +02:00
|
|
|
|
{
|
2021-05-25 19:23:47 +02:00
|
|
|
|
await DeleteAndPushUserRegistrationAsync(organizationId, orgUser.UserId.Value);
|
2021-05-17 10:10:44 +02:00
|
|
|
|
}
|
2021-08-05 08:50:41 -04:00
|
|
|
|
result.Add(Tuple.Create(orgUser, ""));
|
|
|
|
|
|
deletedUserIds.Add(orgUser.Id);
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2021-08-05 08:50:41 -04:00
|
|
|
|
catch (BadRequestException e)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2021-05-25 19:23:47 +02:00
|
|
|
|
result.Add(Tuple.Create(orgUser, e.Message));
|
2021-05-17 10:10:44 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-07-08 17:05:32 +02:00
|
|
|
|
await _organizationUserRepository.DeleteManyAsync(deletedUserIds);
|
2017-12-01 16:00:30 -05:00
|
|
|
|
}
|
2021-05-17 09:43:02 -05:00
|
|
|
|
|
2021-01-30 17:56:37 -05:00
|
|
|
|
return result;
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-06-08 08:44:28 -05:00
|
|
|
|
public async Task<bool> HasConfirmedOwnersExceptAsync(Guid organizationId, IEnumerable<Guid> organizationUsersId, bool includeProvider = true)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2021-03-30 09:48:52 -05:00
|
|
|
|
var confirmedOwners = await GetConfirmedOwnersAsync(organizationId);
|
2021-05-17 10:10:44 +02:00
|
|
|
|
var confirmedOwnersIds = confirmedOwners.Select(u => u.Id);
|
2021-03-30 09:48:52 -05:00
|
|
|
|
bool hasOtherOwner = confirmedOwnersIds.Except(organizationUsersId).Any();
|
2021-08-05 08:50:41 -04:00
|
|
|
|
if (!hasOtherOwner && includeProvider)
|
2021-03-30 09:48:52 -05:00
|
|
|
|
{
|
2023-05-17 16:39:08 +01:00
|
|
|
|
return (await _providerUserRepository.GetManyByOrganizationAsync(organizationId, ProviderUserStatusType.Confirmed)).Any();
|
2021-03-30 09:48:52 -05:00
|
|
|
|
}
|
2021-08-05 08:50:41 -04:00
|
|
|
|
return hasOtherOwner;
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2021-09-23 06:36:08 -04:00
|
|
|
|
|
2021-05-19 09:40:32 -05:00
|
|
|
|
public async Task UpdateUserGroupsAsync(OrganizationUser organizationUser, IEnumerable<Guid> groupIds, Guid? loggedInUserId)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2021-05-19 09:40:32 -05:00
|
|
|
|
if (loggedInUserId.HasValue)
|
|
|
|
|
|
{
|
2023-05-17 14:17:37 +01:00
|
|
|
|
await ValidateOrganizationUserUpdatePermissions(organizationUser.OrganizationId, organizationUser.Type, null, organizationUser.GetPermissions());
|
2021-05-19 09:40:32 -05:00
|
|
|
|
}
|
|
|
|
|
|
await _organizationUserRepository.UpdateGroupsAsync(organizationUser.Id, groupIds);
|
|
|
|
|
|
await _eventService.LogOrganizationUserEventAsync(organizationUser,
|
|
|
|
|
|
EventType.OrganizationUser_UpdatedGroups);
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2021-03-30 09:48:52 -05:00
|
|
|
|
|
2021-05-19 09:40:32 -05:00
|
|
|
|
public async Task UpdateUserResetPasswordEnrollmentAsync(Guid organizationId, Guid userId, string resetPasswordKey, Guid? callingUserId)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2021-05-19 09:40:32 -05:00
|
|
|
|
// Org User must be the same as the calling user and the organization ID associated with the user must match passed org ID
|
|
|
|
|
|
var orgUser = await _organizationUserRepository.GetByOrganizationAsync(organizationId, userId);
|
|
|
|
|
|
if (!callingUserId.HasValue || orgUser == null || orgUser.UserId != callingUserId.Value ||
|
2021-03-30 09:48:52 -05:00
|
|
|
|
orgUser.OrganizationId != organizationId)
|
2021-05-19 09:40:32 -05:00
|
|
|
|
{
|
|
|
|
|
|
throw new BadRequestException("User not valid.");
|
|
|
|
|
|
}
|
2021-09-23 06:36:08 -04:00
|
|
|
|
|
2021-07-08 10:48:43 -05:00
|
|
|
|
// Make sure the organization has the ability to use password reset
|
|
|
|
|
|
var org = await _organizationRepository.GetByIdAsync(organizationId);
|
|
|
|
|
|
if (org == null || !org.UseResetPassword)
|
|
|
|
|
|
{
|
2022-03-07 16:53:30 -05:00
|
|
|
|
throw new BadRequestException("Organization does not allow password reset enrollment.");
|
2022-08-29 15:53:48 -04:00
|
|
|
|
}
|
2021-07-08 10:48:43 -05:00
|
|
|
|
|
|
|
|
|
|
// Make sure the organization has the policy enabled
|
2021-05-19 09:40:32 -05:00
|
|
|
|
var resetPasswordPolicy =
|
2021-07-08 10:48:43 -05:00
|
|
|
|
await _policyRepository.GetByOrganizationIdTypeAsync(organizationId, PolicyType.ResetPassword);
|
|
|
|
|
|
if (resetPasswordPolicy == null || !resetPasswordPolicy.Enabled)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new BadRequestException("Organization does not have the password reset policy enabled.");
|
|
|
|
|
|
}
|
2017-12-01 16:00:30 -05:00
|
|
|
|
|
2021-05-17 09:43:02 -05:00
|
|
|
|
// Block the user from withdrawal if auto enrollment is enabled
|
|
|
|
|
|
if (resetPasswordKey == null && resetPasswordPolicy.Data != null)
|
2022-08-29 15:53:48 -04:00
|
|
|
|
{
|
2021-03-30 09:48:52 -05:00
|
|
|
|
var data = JsonSerializer.Deserialize<ResetPasswordDataModel>(resetPasswordPolicy.Data, JsonHelpers.IgnoreCase);
|
2022-08-29 14:53:16 -04:00
|
|
|
|
|
2017-08-15 16:11:08 -04:00
|
|
|
|
if (data?.AutoEnrollEnabled ?? false)
|
2022-08-29 14:53:16 -04:00
|
|
|
|
{
|
2017-08-30 11:23:55 -04:00
|
|
|
|
throw new BadRequestException("Due to an Enterprise Policy, you are not allowed to withdraw from Password Reset.");
|
2022-08-29 14:53:16 -04:00
|
|
|
|
}
|
2017-08-30 11:23:55 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2020-08-18 17:00:21 -04:00
|
|
|
|
orgUser.ResetPasswordKey = resetPasswordKey;
|
|
|
|
|
|
await _organizationUserRepository.ReplaceAsync(orgUser);
|
|
|
|
|
|
await _eventService.LogOrganizationUserEventAsync(orgUser, resetPasswordKey != null ?
|
|
|
|
|
|
EventType.OrganizationUser_ResetPassword_Enroll : EventType.OrganizationUser_ResetPassword_Withdraw);
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2017-08-15 16:11:08 -04:00
|
|
|
|
public async Task<OrganizationUser> InviteUserAsync(Guid organizationId, Guid? invitingUserId, string email,
|
2023-01-19 17:00:54 +01:00
|
|
|
|
OrganizationUserType type, bool accessAll, string externalId, IEnumerable<CollectionAccessSelection> collections,
|
|
|
|
|
|
IEnumerable<Guid> groups)
|
2022-11-09 12:13:29 +00:00
|
|
|
|
{
|
2023-01-19 17:00:54 +01:00
|
|
|
|
return await SaveUserSendInviteAsync(organizationId, invitingUserId, systemUser: null, email, type, accessAll, externalId, collections, groups);
|
2022-11-09 12:13:29 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public async Task<OrganizationUser> InviteUserAsync(Guid organizationId, EventSystemUser systemUser, string email,
|
2023-01-19 17:00:54 +01:00
|
|
|
|
OrganizationUserType type, bool accessAll, string externalId, IEnumerable<CollectionAccessSelection> collections,
|
|
|
|
|
|
IEnumerable<Guid> groups)
|
2022-11-09 12:13:29 +00:00
|
|
|
|
{
|
2023-01-19 17:00:54 +01:00
|
|
|
|
return await SaveUserSendInviteAsync(organizationId, invitingUserId: null, systemUser, email, type, accessAll, externalId, collections, groups);
|
2022-11-09 12:13:29 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private async Task<OrganizationUser> SaveUserSendInviteAsync(Guid organizationId, Guid? invitingUserId, EventSystemUser? systemUser, string email,
|
2023-01-19 17:00:54 +01:00
|
|
|
|
OrganizationUserType type, bool accessAll, string externalId, IEnumerable<CollectionAccessSelection> collections, IEnumerable<Guid> groups)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2021-01-30 17:56:37 -05:00
|
|
|
|
var invite = new OrganizationUserInvite()
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2021-01-30 17:56:37 -05:00
|
|
|
|
Emails = new List<string> { email },
|
|
|
|
|
|
Type = type,
|
|
|
|
|
|
AccessAll = accessAll,
|
|
|
|
|
|
Collections = collections,
|
2023-01-19 17:00:54 +01:00
|
|
|
|
Groups = groups
|
2022-08-29 16:06:55 -04:00
|
|
|
|
};
|
2022-11-09 12:13:29 +00:00
|
|
|
|
var results = systemUser.HasValue ? await InviteUsersAsync(organizationId, systemUser.Value,
|
|
|
|
|
|
new (OrganizationUserInvite, string)[] { (invite, externalId) }) : await InviteUsersAsync(organizationId, invitingUserId,
|
2021-01-30 17:56:37 -05:00
|
|
|
|
new (OrganizationUserInvite, string)[] { (invite, externalId) });
|
2017-05-13 12:00:40 -04:00
|
|
|
|
var result = results.FirstOrDefault();
|
2021-01-30 17:56:37 -05:00
|
|
|
|
if (result == null)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2021-01-30 17:56:37 -05:00
|
|
|
|
throw new BadRequestException("This user has already been invited.");
|
|
|
|
|
|
}
|
|
|
|
|
|
return result;
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2021-01-30 17:56:37 -05:00
|
|
|
|
|
2017-05-16 00:11:21 -04:00
|
|
|
|
public async Task ImportAsync(Guid organizationId,
|
|
|
|
|
|
Guid? importingUserId,
|
|
|
|
|
|
IEnumerable<ImportedGroup> groups,
|
|
|
|
|
|
IEnumerable<ImportedOrganizationUser> newUsers,
|
|
|
|
|
|
IEnumerable<string> removeUserExternalIds,
|
|
|
|
|
|
bool overwriteExisting)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2017-05-16 00:11:21 -04:00
|
|
|
|
var organization = await GetOrgById(organizationId);
|
|
|
|
|
|
if (organization == null)
|
2017-05-13 12:00:40 -04:00
|
|
|
|
{
|
|
|
|
|
|
throw new NotFoundException();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2017-05-20 15:31:16 -04:00
|
|
|
|
if (!organization.UseDirectory)
|
2022-08-29 15:53:48 -04:00
|
|
|
|
{
|
2017-05-20 15:31:16 -04:00
|
|
|
|
throw new BadRequestException("Organization cannot use directory syncing.");
|
2022-08-29 15:53:48 -04:00
|
|
|
|
}
|
2017-05-13 12:00:40 -04:00
|
|
|
|
|
2017-05-18 10:41:47 -04:00
|
|
|
|
var newUsersSet = new HashSet<string>(newUsers?.Select(u => u.ExternalId) ?? new List<string>());
|
|
|
|
|
|
var existingUsers = await _organizationUserRepository.GetManyDetailsByOrganizationAsync(organizationId);
|
|
|
|
|
|
var existingExternalUsers = existingUsers.Where(u => !string.IsNullOrWhiteSpace(u.ExternalId)).ToList();
|
|
|
|
|
|
var existingExternalUsersIdDict = existingExternalUsers.ToDictionary(u => u.ExternalId, u => u.Id);
|
2017-05-13 12:00:40 -04:00
|
|
|
|
|
2017-05-16 00:11:21 -04:00
|
|
|
|
// Users
|
2017-11-13 12:09:39 -05:00
|
|
|
|
|
2017-05-18 10:41:47 -04:00
|
|
|
|
// Remove Users
|
2021-05-17 09:43:02 -05:00
|
|
|
|
if (removeUserExternalIds?.Any() ?? false)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2021-05-17 09:43:02 -05:00
|
|
|
|
var removeUsersSet = new HashSet<string>(removeUserExternalIds);
|
|
|
|
|
|
var existingUsersDict = existingExternalUsers.ToDictionary(u => u.ExternalId);
|
2017-05-13 14:14:20 -04:00
|
|
|
|
|
2021-05-17 09:43:02 -05:00
|
|
|
|
await _organizationUserRepository.DeleteManyAsync(removeUsersSet
|
|
|
|
|
|
.Except(newUsersSet)
|
|
|
|
|
|
.Where(u => existingUsersDict.ContainsKey(u) && existingUsersDict[u].Type != OrganizationUserType.Owner)
|
|
|
|
|
|
.Select(u => existingUsersDict[u].Id));
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2022-08-29 15:53:48 -04:00
|
|
|
|
|
2020-03-27 14:36:37 -04:00
|
|
|
|
if (overwriteExisting)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2021-05-17 09:43:02 -05:00
|
|
|
|
// Remove existing external users that are not in new user set
|
|
|
|
|
|
var usersToDelete = existingExternalUsers.Where(u =>
|
|
|
|
|
|
u.Type != OrganizationUserType.Owner &&
|
|
|
|
|
|
!newUsersSet.Contains(u.ExternalId) &&
|
2017-05-18 10:41:47 -04:00
|
|
|
|
existingExternalUsersIdDict.ContainsKey(u.ExternalId));
|
2021-05-17 09:43:02 -05:00
|
|
|
|
await _organizationUserRepository.DeleteManyAsync(usersToDelete.Select(u => u.Id));
|
|
|
|
|
|
foreach (var deletedUser in usersToDelete)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2021-05-17 09:43:02 -05:00
|
|
|
|
existingExternalUsersIdDict.Remove(deletedUser.ExternalId);
|
2017-05-13 12:00:40 -04:00
|
|
|
|
}
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2017-05-13 12:00:40 -04:00
|
|
|
|
|
2020-03-27 14:36:37 -04:00
|
|
|
|
if (newUsers?.Any() ?? false)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2020-03-27 14:36:37 -04:00
|
|
|
|
// Marry existing users
|
2017-11-10 15:22:19 -05:00
|
|
|
|
var existingUsersEmailsDict = existingUsers
|
2020-03-27 14:36:37 -04:00
|
|
|
|
.Where(u => string.IsNullOrWhiteSpace(u.ExternalId))
|
2017-11-10 15:22:19 -05:00
|
|
|
|
.ToDictionary(u => u.Email);
|
|
|
|
|
|
var newUsersEmailsDict = newUsers.ToDictionary(u => u.Email);
|
|
|
|
|
|
var usersToAttach = existingUsersEmailsDict.Keys.Intersect(newUsersEmailsDict.Keys).ToList();
|
|
|
|
|
|
var usersToUpsert = new List<OrganizationUser>();
|
2020-03-27 14:36:37 -04:00
|
|
|
|
foreach (var user in usersToAttach)
|
2019-05-06 21:31:20 -04:00
|
|
|
|
{
|
2021-05-17 09:43:02 -05:00
|
|
|
|
var orgUserDetails = existingUsersEmailsDict[user];
|
|
|
|
|
|
var orgUser = await _organizationUserRepository.GetByIdAsync(orgUserDetails.Id);
|
|
|
|
|
|
if (orgUser != null)
|
2019-05-06 21:31:20 -04:00
|
|
|
|
{
|
2021-05-17 09:43:02 -05:00
|
|
|
|
orgUser.ExternalId = newUsersEmailsDict[user].ExternalId;
|
2017-05-15 14:41:20 -04:00
|
|
|
|
usersToUpsert.Add(orgUser);
|
2021-05-17 09:43:02 -05:00
|
|
|
|
existingExternalUsersIdDict.Add(orgUser.ExternalId, orgUser.Id);
|
2019-05-06 21:31:20 -04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2017-05-15 14:41:20 -04:00
|
|
|
|
await _organizationUserRepository.UpsertManyAsync(usersToUpsert);
|
2017-11-10 15:22:19 -05:00
|
|
|
|
|
2020-03-27 14:36:37 -04:00
|
|
|
|
// Add new users
|
2017-11-10 15:22:19 -05:00
|
|
|
|
var existingUsersSet = new HashSet<string>(existingExternalUsersIdDict.Keys);
|
|
|
|
|
|
var usersToAdd = newUsersSet.Except(existingUsersSet).ToList();
|
2017-05-13 12:00:40 -04:00
|
|
|
|
|
2020-03-27 14:36:37 -04:00
|
|
|
|
var seatsAvailable = int.MaxValue;
|
|
|
|
|
|
var enoughSeatsAvailable = true;
|
|
|
|
|
|
if (organization.Seats.HasValue)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2023-02-24 07:54:19 +10:00
|
|
|
|
var occupiedSeats = await _organizationUserRepository.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id);
|
2022-09-23 14:30:39 +10:00
|
|
|
|
seatsAvailable = organization.Seats.Value - occupiedSeats;
|
2020-03-27 14:36:37 -04:00
|
|
|
|
enoughSeatsAvailable = seatsAvailable >= usersToAdd.Count;
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2022-08-29 15:53:48 -04:00
|
|
|
|
|
2020-03-27 14:36:37 -04:00
|
|
|
|
var userInvites = new List<(OrganizationUserInvite, string)>();
|
|
|
|
|
|
foreach (var user in newUsers)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2020-03-27 14:36:37 -04:00
|
|
|
|
if (!usersToAdd.Contains(user.ExternalId) || string.IsNullOrWhiteSpace(user.Email))
|
2017-05-13 12:00:40 -04:00
|
|
|
|
{
|
2017-05-15 14:41:20 -04:00
|
|
|
|
continue;
|
2017-05-13 12:00:40 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-05-17 09:43:02 -05:00
|
|
|
|
try
|
2021-03-25 08:42:04 -05:00
|
|
|
|
{
|
2021-05-17 09:43:02 -05:00
|
|
|
|
var invite = new OrganizationUserInvite
|
2022-08-29 15:53:48 -04:00
|
|
|
|
{
|
2021-05-17 09:43:02 -05:00
|
|
|
|
Emails = new List<string> { user.Email },
|
|
|
|
|
|
Type = OrganizationUserType.User,
|
|
|
|
|
|
AccessAll = false,
|
2023-01-19 17:00:54 +01:00
|
|
|
|
Collections = new List<CollectionAccessSelection>(),
|
2022-08-29 16:06:55 -04:00
|
|
|
|
};
|
2021-05-17 09:43:02 -05:00
|
|
|
|
userInvites.Add((invite, user.ExternalId));
|
2017-05-13 12:00:40 -04:00
|
|
|
|
}
|
2021-05-17 09:43:02 -05:00
|
|
|
|
catch (BadRequestException)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Thrown when the user is already invited to the organization
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
2017-05-13 12:00:40 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-05-17 09:43:02 -05:00
|
|
|
|
var invitedUsers = await InviteUsersAsync(organizationId, importingUserId, userInvites);
|
|
|
|
|
|
foreach (var invitedUser in invitedUsers)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2021-05-17 09:43:02 -05:00
|
|
|
|
existingExternalUsersIdDict.Add(invitedUser.ExternalId, invitedUser.Id);
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2017-11-13 12:09:39 -05:00
|
|
|
|
|
2021-05-17 09:43:02 -05:00
|
|
|
|
// Groups
|
2020-03-27 14:36:37 -04:00
|
|
|
|
if (groups?.Any() ?? false)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2020-03-27 14:36:37 -04:00
|
|
|
|
if (!organization.UseGroups)
|
2017-05-13 12:00:40 -04:00
|
|
|
|
{
|
2017-05-20 15:31:16 -04:00
|
|
|
|
throw new BadRequestException("Organization cannot use groups.");
|
|
|
|
|
|
}
|
2017-05-15 14:41:20 -04:00
|
|
|
|
|
|
|
|
|
|
var groupsDict = groups.ToDictionary(g => g.Group.ExternalId);
|
2017-05-18 10:41:47 -04:00
|
|
|
|
var existingGroups = await _groupRepository.GetManyByOrganizationIdAsync(organizationId);
|
2017-05-15 14:41:20 -04:00
|
|
|
|
var existingExternalGroups = existingGroups
|
2017-05-18 10:41:47 -04:00
|
|
|
|
.Where(u => !string.IsNullOrWhiteSpace(u.ExternalId)).ToList();
|
|
|
|
|
|
var existingExternalGroupsDict = existingExternalGroups.ToDictionary(g => g.ExternalId);
|
2017-05-15 14:41:20 -04:00
|
|
|
|
|
2020-03-27 14:36:37 -04:00
|
|
|
|
var newGroups = groups
|
2017-05-15 14:41:20 -04:00
|
|
|
|
.Where(g => !existingExternalGroupsDict.ContainsKey(g.Group.ExternalId))
|
|
|
|
|
|
.Select(g => g.Group);
|
2017-05-13 12:00:40 -04:00
|
|
|
|
|
2017-05-15 14:41:20 -04:00
|
|
|
|
foreach (var group in newGroups)
|
2022-08-29 15:53:48 -04:00
|
|
|
|
{
|
2017-05-15 14:41:20 -04:00
|
|
|
|
group.CreationDate = group.RevisionDate = DateTime.UtcNow;
|
2017-05-13 14:14:20 -04:00
|
|
|
|
|
2017-05-18 10:41:47 -04:00
|
|
|
|
await _groupRepository.CreateAsync(group);
|
2017-05-15 16:37:56 -04:00
|
|
|
|
await UpdateUsersAsync(group, groupsDict[group.ExternalId].ExternalUserIds,
|
|
|
|
|
|
existingExternalUsersIdDict);
|
2022-08-29 15:53:48 -04:00
|
|
|
|
}
|
2017-05-15 16:37:56 -04:00
|
|
|
|
|
|
|
|
|
|
var updateGroups = existingExternalGroups
|
|
|
|
|
|
.Where(g => groupsDict.ContainsKey(g.ExternalId))
|
|
|
|
|
|
.ToList();
|
2017-05-15 14:41:20 -04:00
|
|
|
|
|
2020-03-27 14:36:37 -04:00
|
|
|
|
if (updateGroups.Any())
|
2022-08-29 14:53:16 -04:00
|
|
|
|
{
|
2017-05-16 00:11:21 -04:00
|
|
|
|
var groupUsers = await _groupRepository.GetManyGroupUsersByOrganizationIdAsync(organizationId);
|
2017-05-15 16:37:56 -04:00
|
|
|
|
var existingGroupUsers = groupUsers
|
|
|
|
|
|
.GroupBy(gu => gu.GroupId)
|
|
|
|
|
|
.ToDictionary(g => g.Key, g => new HashSet<Guid>(g.Select(gr => gr.OrganizationUserId)));
|
2022-08-29 14:53:16 -04:00
|
|
|
|
|
2020-03-27 14:36:37 -04:00
|
|
|
|
foreach (var group in updateGroups)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2019-05-14 11:16:30 -04:00
|
|
|
|
var updatedGroup = groupsDict[group.ExternalId].Group;
|
2020-03-27 14:36:37 -04:00
|
|
|
|
if (group.Name != updatedGroup.Name)
|
2022-08-29 15:53:48 -04:00
|
|
|
|
{
|
2017-05-15 16:37:56 -04:00
|
|
|
|
group.RevisionDate = DateTime.UtcNow;
|
2020-03-27 14:36:37 -04:00
|
|
|
|
group.Name = updatedGroup.Name;
|
2022-08-29 16:06:55 -04:00
|
|
|
|
|
2017-05-15 16:37:56 -04:00
|
|
|
|
await _groupRepository.ReplaceAsync(group);
|
2017-05-15 15:01:16 -04:00
|
|
|
|
}
|
2022-08-29 16:06:55 -04:00
|
|
|
|
|
2019-05-14 11:16:30 -04:00
|
|
|
|
await UpdateUsersAsync(group, groupsDict[group.ExternalId].ExternalUserIds,
|
|
|
|
|
|
existingExternalUsersIdDict,
|
2017-05-15 16:37:56 -04:00
|
|
|
|
existingGroupUsers.ContainsKey(group.Id) ? existingGroupUsers[group.Id] : null);
|
2017-05-13 12:00:40 -04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2022-08-29 14:53:16 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-08-17 13:12:55 -04:00
|
|
|
|
await _referenceEventService.RaiseEventAsync(
|
2023-05-16 16:21:57 +02:00
|
|
|
|
new ReferenceEvent(ReferenceEventType.DirectorySynced, organization, _currentContext));
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2020-08-26 14:12:04 -04:00
|
|
|
|
public async Task DeleteSsoUserAsync(Guid userId, Guid? organizationId)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2020-08-26 14:12:04 -04:00
|
|
|
|
await _ssoUserRepository.DeleteAsync(userId, organizationId);
|
|
|
|
|
|
if (organizationId.HasValue)
|
|
|
|
|
|
{
|
|
|
|
|
|
var organizationUser = await _organizationUserRepository.GetByOrganizationAsync(organizationId.Value, userId);
|
|
|
|
|
|
if (organizationUser != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
await _eventService.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_UnlinkedSso);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2021-05-19 09:40:32 -05:00
|
|
|
|
|
2017-05-15 15:01:16 -04:00
|
|
|
|
public async Task<Organization> UpdateOrganizationKeysAsync(Guid orgId, string publicKey, string privateKey)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2020-03-27 14:36:37 -04:00
|
|
|
|
if (!await _currentContext.ManageResetPassword(orgId))
|
2017-05-15 14:41:20 -04:00
|
|
|
|
{
|
2020-03-27 14:36:37 -04:00
|
|
|
|
throw new UnauthorizedAccessException();
|
2017-03-29 21:26:19 -04:00
|
|
|
|
}
|
2017-08-11 08:57:31 -04:00
|
|
|
|
|
2017-11-14 08:39:16 -05:00
|
|
|
|
// If the keys already exist, error out
|
|
|
|
|
|
var org = await _organizationRepository.GetByIdAsync(orgId);
|
|
|
|
|
|
if (org.PublicKey != null && org.PrivateKey != null)
|
2021-05-17 10:10:44 +02:00
|
|
|
|
{
|
2017-12-19 16:02:39 -05:00
|
|
|
|
throw new BadRequestException("Organization Keys already exist");
|
2017-08-11 08:57:31 -04:00
|
|
|
|
}
|
2017-12-19 16:02:39 -05:00
|
|
|
|
|
|
|
|
|
|
// Update org with generated public/private key
|
|
|
|
|
|
org.PublicKey = publicKey;
|
2021-05-19 09:40:32 -05:00
|
|
|
|
org.PrivateKey = privateKey;
|
2017-12-01 16:00:30 -05:00
|
|
|
|
await UpdateAsync(org);
|
2022-08-29 16:06:55 -04:00
|
|
|
|
|
2017-05-13 12:00:40 -04:00
|
|
|
|
return org;
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2017-12-19 16:02:39 -05:00
|
|
|
|
private async Task UpdateUsersAsync(Group group, HashSet<string> groupUsers,
|
|
|
|
|
|
Dictionary<string, Guid> existingUsersIdDict, HashSet<Guid> existingUsers = null)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2017-12-19 16:02:39 -05:00
|
|
|
|
var availableUsers = groupUsers.Intersect(existingUsersIdDict.Keys);
|
|
|
|
|
|
var users = new HashSet<Guid>(availableUsers.Select(u => existingUsersIdDict[u]));
|
|
|
|
|
|
if (existingUsers != null && existingUsers.Count == users.Count && users.SetEquals(existingUsers))
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2017-08-11 08:57:31 -04:00
|
|
|
|
await _groupRepository.UpdateUsersAsync(group.Id, users);
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-05-10 17:12:09 -04:00
|
|
|
|
private async Task<IEnumerable<OrganizationUser>> GetConfirmedOwnersAsync(Guid organizationId)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2017-12-19 16:02:39 -05:00
|
|
|
|
var owners = await _organizationUserRepository.GetManyByOrganizationAsync(organizationId,
|
2017-05-13 12:00:40 -04:00
|
|
|
|
OrganizationUserType.Owner);
|
2017-12-19 16:02:39 -05:00
|
|
|
|
return owners.Where(o => o.Status == OrganizationUserStatusType.Confirmed);
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2017-12-19 16:02:39 -05:00
|
|
|
|
private async Task DeleteAndPushUserRegistrationAsync(Guid organizationId, Guid userId)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2017-12-19 16:02:39 -05:00
|
|
|
|
var deviceIds = await GetUserDeviceIdsAsync(userId);
|
|
|
|
|
|
await _pushRegistrationService.DeleteUserRegistrationOrganizationAsync(deviceIds,
|
|
|
|
|
|
organizationId.ToString());
|
|
|
|
|
|
await _pushNotificationService.PushSyncOrgKeysAsync(userId);
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2020-08-11 14:19:56 -04:00
|
|
|
|
private async Task<IEnumerable<string>> GetUserDeviceIdsAsync(Guid userId)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2019-03-21 21:36:03 -04:00
|
|
|
|
var devices = await _deviceRepository.GetManyByUserIdAsync(userId);
|
|
|
|
|
|
return devices.Where(d => !string.IsNullOrWhiteSpace(d.PushToken)).Select(d => d.Id.ToString());
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-02-24 07:54:19 +10:00
|
|
|
|
public async Task ReplaceAndUpdateCacheAsync(Organization org, EventType? orgEvent = null)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2019-03-21 21:36:03 -04:00
|
|
|
|
await _organizationRepository.ReplaceAsync(org);
|
|
|
|
|
|
await _applicationCacheService.UpsertOrganizationAbilityAsync(org);
|
2022-08-29 16:06:55 -04:00
|
|
|
|
|
2020-03-27 14:36:37 -04:00
|
|
|
|
if (orgEvent.HasValue)
|
2017-12-19 16:02:39 -05:00
|
|
|
|
{
|
|
|
|
|
|
await _eventService.LogOrganizationEventAsync(org, orgEvent.Value);
|
|
|
|
|
|
}
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2019-03-21 21:36:03 -04:00
|
|
|
|
|
|
|
|
|
|
private async Task<Organization> GetOrgById(Guid id)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2019-03-21 21:36:03 -04:00
|
|
|
|
return await _organizationRepository.GetByIdAsync(id);
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2019-03-21 21:36:03 -04:00
|
|
|
|
|
2020-08-11 14:19:56 -04:00
|
|
|
|
private void ValidateOrganizationUpgradeParameters(Models.StaticStore.Plan plan, OrganizationUpgrade upgrade)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2020-08-11 14:19:56 -04:00
|
|
|
|
if (!plan.HasAdditionalStorageOption && upgrade.AdditionalStorageGb > 0)
|
2019-03-21 21:36:03 -04:00
|
|
|
|
{
|
|
|
|
|
|
throw new BadRequestException("Plan does not allow additional storage.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-03-27 14:36:37 -04:00
|
|
|
|
if (upgrade.AdditionalStorageGb < 0)
|
2019-03-21 21:36:03 -04:00
|
|
|
|
{
|
|
|
|
|
|
throw new BadRequestException("You can't subtract storage!");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!plan.HasPremiumAccessOption && upgrade.PremiumAccessAddon)
|
2022-08-29 14:53:16 -04:00
|
|
|
|
{
|
2019-03-21 21:36:03 -04:00
|
|
|
|
throw new BadRequestException("This plan does not allow you to buy the premium access addon.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-08-11 14:19:56 -04:00
|
|
|
|
if (plan.BaseSeats + upgrade.AdditionalSeats <= 0)
|
2019-03-21 21:36:03 -04:00
|
|
|
|
{
|
|
|
|
|
|
throw new BadRequestException("You do not have any seats!");
|
2021-01-12 11:02:39 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-07-01 14:31:05 +02:00
|
|
|
|
if (upgrade.AdditionalSeats < 0)
|
2021-01-12 11:02:39 -05:00
|
|
|
|
{
|
|
|
|
|
|
throw new BadRequestException("You can't subtract seats!");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-07-08 17:05:32 +02:00
|
|
|
|
if (!plan.HasAdditionalSeatsOption && upgrade.AdditionalSeats > 0)
|
2022-08-29 14:53:16 -04:00
|
|
|
|
{
|
2021-07-08 17:05:32 +02:00
|
|
|
|
throw new BadRequestException("Plan does not allow additional users.");
|
2021-01-12 11:02:39 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-07-01 14:31:05 +02:00
|
|
|
|
if (plan.HasAdditionalSeatsOption && plan.MaxAdditionalSeats.HasValue &&
|
|
|
|
|
|
upgrade.AdditionalSeats > plan.MaxAdditionalSeats.Value)
|
2021-01-12 11:02:39 -05:00
|
|
|
|
{
|
|
|
|
|
|
throw new BadRequestException($"Selected plan allows a maximum of " +
|
|
|
|
|
|
$"{plan.MaxAdditionalSeats.GetValueOrDefault(0)} additional users.");
|
|
|
|
|
|
}
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2021-01-12 11:02:39 -05:00
|
|
|
|
|
2023-05-17 14:17:37 +01:00
|
|
|
|
private async Task ValidateOrganizationUserUpdatePermissions(Guid organizationId, OrganizationUserType newType, OrganizationUserType? oldType, Permissions permissions)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2021-01-12 11:02:39 -05:00
|
|
|
|
if (await _currentContext.OrganizationOwner(organizationId))
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2021-01-12 11:02:39 -05:00
|
|
|
|
return;
|
2022-08-29 14:53:16 -04:00
|
|
|
|
}
|
2021-01-12 11:02:39 -05:00
|
|
|
|
|
2021-07-01 14:31:05 +02:00
|
|
|
|
if (oldType == OrganizationUserType.Owner || newType == OrganizationUserType.Owner)
|
2021-01-12 11:02:39 -05:00
|
|
|
|
{
|
|
|
|
|
|
throw new BadRequestException("Only an Owner can configure another Owner's account.");
|
|
|
|
|
|
}
|
2021-11-09 16:37:32 +01:00
|
|
|
|
|
|
|
|
|
|
if (await _currentContext.OrganizationAdmin(organizationId))
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2022-06-16 15:59:57 -04:00
|
|
|
|
|
2022-07-25 10:47:44 +10:00
|
|
|
|
if (!await _currentContext.ManageUsers(organizationId))
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2022-07-25 10:47:44 +10:00
|
|
|
|
throw new BadRequestException("Your account does not have permission to manage users.");
|
2022-06-16 15:59:57 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-07-25 10:47:44 +10:00
|
|
|
|
if (oldType == OrganizationUserType.Admin || newType == OrganizationUserType.Admin)
|
2022-08-29 15:53:48 -04:00
|
|
|
|
{
|
2022-06-16 15:59:57 -04:00
|
|
|
|
throw new BadRequestException("Custom users can not manage Admins or Owners.");
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2023-05-17 14:17:37 +01:00
|
|
|
|
|
|
|
|
|
|
if (newType == OrganizationUserType.Custom && !await ValidateCustomPermissionsGrant(organizationId, permissions))
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new BadRequestException("Custom users can only grant the same custom permissions that they have.");
|
|
|
|
|
|
}
|
2022-06-16 15:59:57 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-12-06 09:50:08 +00:00
|
|
|
|
private async Task ValidateOrganizationCustomPermissionsEnabledAsync(Guid organizationId, OrganizationUserType newType)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (newType != OrganizationUserType.Custom)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var organization = await _organizationRepository.GetByIdAsync(organizationId);
|
|
|
|
|
|
if (organization == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new NotFoundException();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!organization.UseCustomPermissions)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new BadRequestException("To enable custom permissions the organization must be on an Enterprise plan.");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-05-17 14:17:37 +01:00
|
|
|
|
private async Task<bool> ValidateCustomPermissionsGrant(Guid organizationId, Permissions permissions)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (permissions == null || await _currentContext.OrganizationOwner(organizationId) || await _currentContext.OrganizationAdmin(organizationId))
|
|
|
|
|
|
{
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (permissions.ManageUsers && !await _currentContext.ManageUsers(organizationId))
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (permissions.AccessReports && !await _currentContext.AccessReports(organizationId))
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (permissions.ManageGroups && !await _currentContext.ManageGroups(organizationId))
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (permissions.ManagePolicies && !await _currentContext.ManagePolicies(organizationId))
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (permissions.ManageScim && !await _currentContext.ManageScim(organizationId))
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (permissions.ManageSso && !await _currentContext.ManageSso(organizationId))
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (permissions.AccessEventLogs && !await _currentContext.AccessEventLogs(organizationId))
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (permissions.AccessImportExport && !await _currentContext.AccessImportExport(organizationId))
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (permissions.CreateNewCollections && !await _currentContext.CreateNewCollections(organizationId))
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (permissions.DeleteAnyCollection && !await _currentContext.DeleteAnyCollection(organizationId))
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (permissions.DeleteAssignedCollections && !await _currentContext.DeleteAssignedCollections(organizationId))
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (permissions.EditAnyCollection && !await _currentContext.EditAnyCollection(organizationId))
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (permissions.EditAssignedCollections && !await _currentContext.EditAssignedCollections(organizationId))
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (permissions.ManageResetPassword && !await _currentContext.ManageResetPassword(organizationId))
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-06-16 15:59:57 -04:00
|
|
|
|
private async Task ValidateDeleteOrganizationAsync(Organization organization)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2022-06-16 15:59:57 -04:00
|
|
|
|
var ssoConfig = await _ssoConfigRepository.GetByOrganizationIdAsync(organization.Id);
|
2023-05-10 12:52:08 -07:00
|
|
|
|
if (ssoConfig?.GetData()?.MemberDecryptionType == MemberDecryptionType.KeyConnector)
|
2022-08-29 15:53:48 -04:00
|
|
|
|
{
|
2022-06-16 15:59:57 -04:00
|
|
|
|
throw new BadRequestException("You cannot delete an Organization that is using Key Connector.");
|
2022-08-29 15:53:48 -04:00
|
|
|
|
}
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2022-06-16 15:59:57 -04:00
|
|
|
|
|
2022-07-25 10:47:44 +10:00
|
|
|
|
public async Task RevokeUserAsync(OrganizationUser organizationUser, Guid? revokingUserId)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2022-07-25 10:47:44 +10:00
|
|
|
|
if (revokingUserId.HasValue && organizationUser.UserId == revokingUserId.Value)
|
2022-06-16 15:59:57 -04:00
|
|
|
|
{
|
|
|
|
|
|
throw new BadRequestException("You cannot revoke yourself.");
|
2022-08-29 14:53:16 -04:00
|
|
|
|
}
|
2022-06-16 15:59:57 -04:00
|
|
|
|
|
|
|
|
|
|
if (organizationUser.Type == OrganizationUserType.Owner && revokingUserId.HasValue &&
|
|
|
|
|
|
!await _currentContext.OrganizationOwner(organizationUser.OrganizationId))
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new BadRequestException("Only owners can revoke other owners.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-11-09 12:13:29 +00:00
|
|
|
|
await RepositoryRevokeUserAsync(organizationUser);
|
|
|
|
|
|
await _eventService.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Revoked);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public async Task RevokeUserAsync(OrganizationUser organizationUser,
|
|
|
|
|
|
EventSystemUser systemUser)
|
|
|
|
|
|
{
|
|
|
|
|
|
await RepositoryRevokeUserAsync(organizationUser);
|
|
|
|
|
|
await _eventService.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Revoked, systemUser);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private async Task RepositoryRevokeUserAsync(OrganizationUser organizationUser)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (organizationUser.Status == OrganizationUserStatusType.Revoked)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new BadRequestException("Already revoked.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-05-17 16:39:08 +01:00
|
|
|
|
if (!await HasConfirmedOwnersExceptAsync(organizationUser.OrganizationId, new[] { organizationUser.Id }, includeProvider: true))
|
2022-08-29 15:53:48 -04:00
|
|
|
|
{
|
2022-06-16 15:59:57 -04:00
|
|
|
|
throw new BadRequestException("Organization must have at least one confirmed owner.");
|
2022-08-29 15:53:48 -04:00
|
|
|
|
}
|
2022-06-16 15:59:57 -04:00
|
|
|
|
|
|
|
|
|
|
await _organizationUserRepository.RevokeAsync(organizationUser.Id);
|
|
|
|
|
|
organizationUser.Status = OrganizationUserStatusType.Revoked;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public async Task<List<Tuple<OrganizationUser, string>>> RevokeUsersAsync(Guid organizationId,
|
|
|
|
|
|
IEnumerable<Guid> organizationUserIds, Guid? revokingUserId)
|
|
|
|
|
|
{
|
|
|
|
|
|
var orgUsers = await _organizationUserRepository.GetManyAsync(organizationUserIds);
|
|
|
|
|
|
var filteredUsers = orgUsers.Where(u => u.OrganizationId == organizationId)
|
|
|
|
|
|
.ToList();
|
|
|
|
|
|
|
|
|
|
|
|
if (!filteredUsers.Any())
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2022-06-16 15:59:57 -04:00
|
|
|
|
throw new BadRequestException("Users invalid.");
|
2022-06-17 11:04:25 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-07-25 10:47:44 +10:00
|
|
|
|
if (!await HasConfirmedOwnersExceptAsync(organizationId, organizationUserIds))
|
2022-08-29 15:53:48 -04:00
|
|
|
|
{
|
2022-07-25 10:47:44 +10:00
|
|
|
|
throw new BadRequestException("Organization must have at least one confirmed owner.");
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2022-06-16 15:59:57 -04:00
|
|
|
|
|
2022-07-25 10:47:44 +10:00
|
|
|
|
var deletingUserIsOwner = false;
|
|
|
|
|
|
if (revokingUserId.HasValue)
|
2022-08-29 15:53:48 -04:00
|
|
|
|
{
|
2022-07-25 10:47:44 +10:00
|
|
|
|
deletingUserIsOwner = await _currentContext.OrganizationOwner(organizationId);
|
2022-08-29 14:53:16 -04:00
|
|
|
|
}
|
2022-08-29 15:53:48 -04:00
|
|
|
|
|
2022-07-20 11:42:06 +10:00
|
|
|
|
var result = new List<Tuple<OrganizationUser, string>>();
|
2022-06-16 15:59:57 -04:00
|
|
|
|
|
|
|
|
|
|
foreach (var organizationUser in filteredUsers)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
|
|
|
|
|
try
|
2022-08-29 15:53:48 -04:00
|
|
|
|
{
|
2022-07-25 10:47:44 +10:00
|
|
|
|
if (organizationUser.Status == OrganizationUserStatusType.Revoked)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2022-07-25 10:47:44 +10:00
|
|
|
|
throw new BadRequestException("Already revoked.");
|
2022-08-29 14:53:16 -04:00
|
|
|
|
}
|
2022-06-16 15:59:57 -04:00
|
|
|
|
|
|
|
|
|
|
if (revokingUserId.HasValue && organizationUser.UserId == revokingUserId)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2022-06-16 15:59:57 -04:00
|
|
|
|
throw new BadRequestException("You cannot revoke yourself.");
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2022-06-16 15:59:57 -04:00
|
|
|
|
|
|
|
|
|
|
if (organizationUser.Type == OrganizationUserType.Owner && revokingUserId.HasValue && !deletingUserIsOwner)
|
2022-08-29 15:53:48 -04:00
|
|
|
|
{
|
2022-06-16 15:59:57 -04:00
|
|
|
|
throw new BadRequestException("Only owners can revoke other owners.");
|
2022-08-29 15:53:48 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-06-16 15:59:57 -04:00
|
|
|
|
await _organizationUserRepository.RevokeAsync(organizationUser.Id);
|
|
|
|
|
|
organizationUser.Status = OrganizationUserStatusType.Revoked;
|
|
|
|
|
|
await _eventService.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Revoked);
|
2022-08-29 15:53:48 -04:00
|
|
|
|
|
2022-07-25 10:47:44 +10:00
|
|
|
|
result.Add(Tuple.Create(organizationUser, ""));
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2022-06-16 15:59:57 -04:00
|
|
|
|
catch (BadRequestException e)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2022-06-16 15:59:57 -04:00
|
|
|
|
result.Add(Tuple.Create(organizationUser, e.Message));
|
2022-08-29 15:53:48 -04:00
|
|
|
|
}
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2022-08-29 15:53:48 -04:00
|
|
|
|
|
2022-06-16 15:59:57 -04:00
|
|
|
|
return result;
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2022-08-29 15:53:48 -04:00
|
|
|
|
|
2022-11-09 12:13:29 +00:00
|
|
|
|
public async Task RestoreUserAsync(OrganizationUser organizationUser, Guid? restoringUserId,
|
|
|
|
|
|
IUserService userService)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2022-06-16 15:59:57 -04:00
|
|
|
|
if (restoringUserId.HasValue && organizationUser.UserId == restoringUserId.Value)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2022-06-16 15:59:57 -04:00
|
|
|
|
throw new BadRequestException("You cannot restore yourself.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-08-03 07:09:22 +10:00
|
|
|
|
if (organizationUser.Type == OrganizationUserType.Owner && restoringUserId.HasValue &&
|
|
|
|
|
|
!await _currentContext.OrganizationOwner(organizationUser.OrganizationId))
|
2022-06-16 15:59:57 -04:00
|
|
|
|
{
|
|
|
|
|
|
throw new BadRequestException("Only owners can restore other owners.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-11-09 12:13:29 +00:00
|
|
|
|
await RepositoryRestoreUserAsync(organizationUser, userService);
|
|
|
|
|
|
await _eventService.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Restored);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public async Task RestoreUserAsync(OrganizationUser organizationUser, EventSystemUser systemUser,
|
|
|
|
|
|
IUserService userService)
|
|
|
|
|
|
{
|
|
|
|
|
|
await RepositoryRestoreUserAsync(organizationUser, userService);
|
|
|
|
|
|
await _eventService.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Restored, systemUser);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private async Task RepositoryRestoreUserAsync(OrganizationUser organizationUser, IUserService userService)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (organizationUser.Status != OrganizationUserStatusType.Revoked)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new BadRequestException("Already active.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-09-23 14:30:39 +10:00
|
|
|
|
var organization = await _organizationRepository.GetByIdAsync(organizationUser.OrganizationId);
|
2023-02-24 07:54:19 +10:00
|
|
|
|
var occupiedSeats = await _organizationUserRepository.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id);
|
2022-09-23 14:30:39 +10:00
|
|
|
|
var availableSeats = organization.Seats.GetValueOrDefault(0) - occupiedSeats;
|
|
|
|
|
|
if (availableSeats < 1)
|
|
|
|
|
|
{
|
|
|
|
|
|
await AutoAddSeatsAsync(organization, 1, DateTime.UtcNow);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-07-25 10:47:44 +10:00
|
|
|
|
await CheckPoliciesBeforeRestoreAsync(organizationUser, userService);
|
2022-06-16 15:59:57 -04:00
|
|
|
|
|
2022-07-25 10:47:44 +10:00
|
|
|
|
var status = GetPriorActiveOrganizationUserStatusType(organizationUser);
|
2022-06-16 15:59:57 -04:00
|
|
|
|
|
2022-08-03 07:09:22 +10:00
|
|
|
|
await _organizationUserRepository.RestoreAsync(organizationUser.Id, status);
|
2022-07-25 10:47:44 +10:00
|
|
|
|
organizationUser.Status = status;
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2022-08-03 07:09:22 +10:00
|
|
|
|
|
2022-06-16 15:59:57 -04:00
|
|
|
|
public async Task<List<Tuple<OrganizationUser, string>>> RestoreUsersAsync(Guid organizationId,
|
|
|
|
|
|
IEnumerable<Guid> organizationUserIds, Guid? restoringUserId, IUserService userService)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2022-06-16 15:59:57 -04:00
|
|
|
|
var orgUsers = await _organizationUserRepository.GetManyAsync(organizationUserIds);
|
|
|
|
|
|
var filteredUsers = orgUsers.Where(u => u.OrganizationId == organizationId)
|
|
|
|
|
|
.ToList();
|
|
|
|
|
|
|
2022-07-25 10:47:44 +10:00
|
|
|
|
if (!filteredUsers.Any())
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2022-07-25 10:47:44 +10:00
|
|
|
|
throw new BadRequestException("Users invalid.");
|
2022-06-16 15:59:57 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-09-23 14:30:39 +10:00
|
|
|
|
var organization = await _organizationRepository.GetByIdAsync(organizationId);
|
2023-02-24 07:54:19 +10:00
|
|
|
|
var occupiedSeats = await _organizationUserRepository.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id);
|
2022-09-23 14:30:39 +10:00
|
|
|
|
var availableSeats = organization.Seats.GetValueOrDefault(0) - occupiedSeats;
|
|
|
|
|
|
var newSeatsRequired = organizationUserIds.Count() - availableSeats;
|
|
|
|
|
|
await AutoAddSeatsAsync(organization, newSeatsRequired, DateTime.UtcNow);
|
|
|
|
|
|
|
2022-07-25 10:47:44 +10:00
|
|
|
|
var deletingUserIsOwner = false;
|
2022-08-03 07:09:22 +10:00
|
|
|
|
if (restoringUserId.HasValue)
|
2022-06-16 15:59:57 -04:00
|
|
|
|
{
|
2022-07-25 10:47:44 +10:00
|
|
|
|
deletingUserIsOwner = await _currentContext.OrganizationOwner(organizationId);
|
2022-06-16 15:59:57 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-07-25 10:47:44 +10:00
|
|
|
|
var result = new List<Tuple<OrganizationUser, string>>();
|
2022-06-16 15:59:57 -04:00
|
|
|
|
|
2022-07-25 10:47:44 +10:00
|
|
|
|
foreach (var organizationUser in filteredUsers)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
|
|
|
|
|
try
|
2022-08-29 15:53:48 -04:00
|
|
|
|
{
|
2022-07-20 11:42:06 +10:00
|
|
|
|
if (organizationUser.Status != OrganizationUserStatusType.Revoked)
|
2022-06-16 15:59:57 -04:00
|
|
|
|
{
|
2022-07-20 11:42:06 +10:00
|
|
|
|
throw new BadRequestException("Already active.");
|
2022-08-29 15:53:48 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-07-20 11:42:06 +10:00
|
|
|
|
if (restoringUserId.HasValue && organizationUser.UserId == restoringUserId)
|
2022-08-29 15:53:48 -04:00
|
|
|
|
{
|
2022-07-20 11:42:06 +10:00
|
|
|
|
throw new BadRequestException("You cannot restore yourself.");
|
2022-06-16 15:59:57 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-08-03 07:09:22 +10:00
|
|
|
|
if (organizationUser.Type == OrganizationUserType.Owner && restoringUserId.HasValue && !deletingUserIsOwner)
|
2022-08-29 15:53:48 -04:00
|
|
|
|
{
|
2022-08-03 07:09:22 +10:00
|
|
|
|
throw new BadRequestException("Only owners can restore other owners.");
|
2022-08-29 15:53:48 -04:00
|
|
|
|
}
|
2022-08-03 07:09:22 +10:00
|
|
|
|
|
2022-06-16 15:59:57 -04:00
|
|
|
|
await CheckPoliciesBeforeRestoreAsync(organizationUser, userService);
|
|
|
|
|
|
|
2022-07-25 10:47:44 +10:00
|
|
|
|
var status = GetPriorActiveOrganizationUserStatusType(organizationUser);
|
2022-06-16 15:59:57 -04:00
|
|
|
|
|
2022-07-25 10:47:44 +10:00
|
|
|
|
await _organizationUserRepository.RestoreAsync(organizationUser.Id, status);
|
2022-06-16 15:59:57 -04:00
|
|
|
|
organizationUser.Status = status;
|
|
|
|
|
|
await _eventService.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Restored);
|
2022-08-29 15:53:48 -04:00
|
|
|
|
|
2022-06-16 15:59:57 -04:00
|
|
|
|
result.Add(Tuple.Create(organizationUser, ""));
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (BadRequestException e)
|
|
|
|
|
|
{
|
|
|
|
|
|
result.Add(Tuple.Create(organizationUser, e.Message));
|
|
|
|
|
|
}
|
2022-08-29 14:53:16 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-06-16 15:59:57 -04:00
|
|
|
|
return result;
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-08-03 07:09:22 +10:00
|
|
|
|
private async Task CheckPoliciesBeforeRestoreAsync(OrganizationUser orgUser, IUserService userService)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2022-08-03 07:09:22 +10:00
|
|
|
|
// An invited OrganizationUser isn't linked with a user account yet, so these checks are irrelevant
|
|
|
|
|
|
// The user will be subject to the same checks when they try to accept the invite
|
|
|
|
|
|
if (GetPriorActiveOrganizationUserStatusType(orgUser) == OrganizationUserStatusType.Invited)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var userId = orgUser.UserId.Value;
|
|
|
|
|
|
|
|
|
|
|
|
// Enforce Single Organization Policy of organization user is being restored to
|
|
|
|
|
|
var allOrgUsers = await _organizationUserRepository.GetManyByUserAsync(userId);
|
|
|
|
|
|
var hasOtherOrgs = allOrgUsers.Any(ou => ou.OrganizationId != orgUser.OrganizationId);
|
2023-05-12 08:22:19 +01:00
|
|
|
|
var singleOrgPoliciesApplyingToRevokedUsers = await _policyService.GetPoliciesApplicableToUserAsync(userId,
|
2022-08-03 07:09:22 +10:00
|
|
|
|
PolicyType.SingleOrg, OrganizationUserStatusType.Revoked);
|
|
|
|
|
|
var singleOrgPolicyApplies = singleOrgPoliciesApplyingToRevokedUsers.Any(p => p.OrganizationId == orgUser.OrganizationId);
|
|
|
|
|
|
|
|
|
|
|
|
if (hasOtherOrgs && singleOrgPolicyApplies)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new BadRequestException("You cannot restore this user until " +
|
|
|
|
|
|
"they leave or remove all other organizations.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Enforce Single Organization Policy of other organizations user is a member of
|
2023-05-12 08:22:19 +01:00
|
|
|
|
var anySingleOrgPolicies = await _policyService.AnyPoliciesApplicableToUserAsync(userId,
|
2022-08-03 07:09:22 +10:00
|
|
|
|
PolicyType.SingleOrg);
|
2023-05-12 08:22:19 +01:00
|
|
|
|
if (anySingleOrgPolicies)
|
2022-08-03 07:09:22 +10:00
|
|
|
|
{
|
|
|
|
|
|
throw new BadRequestException("You cannot restore this user because they are a member of " +
|
|
|
|
|
|
"another organization which forbids it");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Enforce Two Factor Authentication Policy of organization user is trying to join
|
|
|
|
|
|
var user = await _userRepository.GetByIdAsync(userId);
|
|
|
|
|
|
if (!await userService.TwoFactorIsEnabledAsync(user))
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2023-05-12 08:22:19 +01:00
|
|
|
|
var invitedTwoFactorPolicies = await _policyService.GetPoliciesApplicableToUserAsync(userId,
|
2022-08-03 07:09:22 +10:00
|
|
|
|
PolicyType.TwoFactorAuthentication, OrganizationUserStatusType.Invited);
|
|
|
|
|
|
if (invitedTwoFactorPolicies.Any(p => p.OrganizationId == orgUser.OrganizationId))
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new BadRequestException("You cannot restore this user until they enable " +
|
|
|
|
|
|
"two-step login on their user account.");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2022-08-03 07:09:22 +10:00
|
|
|
|
|
2022-06-16 15:59:57 -04:00
|
|
|
|
static OrganizationUserStatusType GetPriorActiveOrganizationUserStatusType(OrganizationUser organizationUser)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2022-06-16 15:59:57 -04:00
|
|
|
|
// Determine status to revert back to
|
|
|
|
|
|
var status = OrganizationUserStatusType.Invited;
|
|
|
|
|
|
if (organizationUser.UserId.HasValue && string.IsNullOrWhiteSpace(organizationUser.Email))
|
|
|
|
|
|
{
|
|
|
|
|
|
// Has UserId & Email is null, then Accepted
|
|
|
|
|
|
status = OrganizationUserStatusType.Accepted;
|
|
|
|
|
|
if (!string.IsNullOrWhiteSpace(organizationUser.Key))
|
|
|
|
|
|
{
|
|
|
|
|
|
// We have an org key for this user, user was confirmed
|
|
|
|
|
|
status = OrganizationUserStatusType.Confirmed;
|
|
|
|
|
|
}
|
2022-08-29 15:53:48 -04:00
|
|
|
|
}
|
2022-08-29 16:06:55 -04:00
|
|
|
|
|
2022-06-16 15:59:57 -04:00
|
|
|
|
return status;
|
2017-03-03 00:07:11 -05:00
|
|
|
|
}
|
2023-04-14 11:13:16 +01:00
|
|
|
|
|
|
|
|
|
|
public async Task CreatePendingOrganization(Organization organization, string ownerEmail, ClaimsPrincipal user, IUserService userService, bool salesAssistedTrialStarted)
|
|
|
|
|
|
{
|
2023-06-16 11:12:38 +01:00
|
|
|
|
var plan = StaticStore.PasswordManagerPlans.FirstOrDefault(p => p.Type == organization.PlanType);
|
2023-04-14 11:13:16 +01:00
|
|
|
|
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);
|
|
|
|
|
|
|
2023-05-16 16:21:57 +02:00
|
|
|
|
await _referenceEventService.RaiseEventAsync(new ReferenceEvent(ReferenceEventType.OrganizationCreatedByAdmin, organization, _currentContext)
|
2023-04-14 11:13:16 +01:00
|
|
|
|
{
|
|
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2017-03-03 00:07:11 -05:00
|
|
|
|
}
|