mirror of
https://github.com/bitwarden/server.git
synced 2025-07-04 01:22:50 -05:00
Merge branch 'main' into ac/pm-15161/create-resellerclientorganizationsignup-command
This commit is contained in:
@ -0,0 +1,37 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Bit.Core.Models.Data.Integrations;
|
||||
|
||||
public class IntegrationTemplateContext(EventMessage eventMessage)
|
||||
{
|
||||
public EventMessage Event { get; } = eventMessage;
|
||||
|
||||
public string DomainName => Event.DomainName;
|
||||
public string IpAddress => Event.IpAddress;
|
||||
public DeviceType? DeviceType => Event.DeviceType;
|
||||
public Guid? ActingUserId => Event.ActingUserId;
|
||||
public Guid? OrganizationUserId => Event.OrganizationUserId;
|
||||
public DateTime Date => Event.Date;
|
||||
public EventType Type => Event.Type;
|
||||
public Guid? UserId => Event.UserId;
|
||||
public Guid? OrganizationId => Event.OrganizationId;
|
||||
public Guid? CipherId => Event.CipherId;
|
||||
public Guid? CollectionId => Event.CollectionId;
|
||||
public Guid? GroupId => Event.GroupId;
|
||||
public Guid? PolicyId => Event.PolicyId;
|
||||
|
||||
public User? User { get; set; }
|
||||
public string? UserName => User?.Name;
|
||||
public string? UserEmail => User?.Email;
|
||||
|
||||
public User? ActingUser { get; set; }
|
||||
public string? ActingUserName => ActingUser?.Name;
|
||||
public string? ActingUserEmail => ActingUser?.Email;
|
||||
|
||||
public Organization? Organization { get; set; }
|
||||
public string? OrganizationName => Organization?.DisplayName();
|
||||
}
|
@ -0,0 +1,128 @@
|
||||
using Bit.Core.AdminConsole.Enums;
|
||||
using Bit.Core.AdminConsole.Services;
|
||||
using Bit.Core.Auth.Models.Business.Tokenables;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Settings;
|
||||
using Bit.Core.Tokens;
|
||||
using Microsoft.AspNetCore.DataProtection;
|
||||
|
||||
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers;
|
||||
|
||||
public class InitPendingOrganizationCommand : IInitPendingOrganizationCommand
|
||||
{
|
||||
|
||||
private readonly IOrganizationService _organizationService;
|
||||
private readonly ICollectionRepository _collectionRepository;
|
||||
private readonly IOrganizationRepository _organizationRepository;
|
||||
private readonly IDataProtectorTokenFactory<OrgUserInviteTokenable> _orgUserInviteTokenDataFactory;
|
||||
private readonly IDataProtector _dataProtector;
|
||||
private readonly IGlobalSettings _globalSettings;
|
||||
private readonly IPolicyService _policyService;
|
||||
private readonly IOrganizationUserRepository _organizationUserRepository;
|
||||
|
||||
public InitPendingOrganizationCommand(
|
||||
IOrganizationService organizationService,
|
||||
ICollectionRepository collectionRepository,
|
||||
IOrganizationRepository organizationRepository,
|
||||
IDataProtectorTokenFactory<OrgUserInviteTokenable> orgUserInviteTokenDataFactory,
|
||||
IDataProtectionProvider dataProtectionProvider,
|
||||
IGlobalSettings globalSettings,
|
||||
IPolicyService policyService,
|
||||
IOrganizationUserRepository organizationUserRepository
|
||||
)
|
||||
{
|
||||
_organizationService = organizationService;
|
||||
_collectionRepository = collectionRepository;
|
||||
_organizationRepository = organizationRepository;
|
||||
_orgUserInviteTokenDataFactory = orgUserInviteTokenDataFactory;
|
||||
_dataProtector = dataProtectionProvider.CreateProtector(OrgUserInviteTokenable.DataProtectorPurpose);
|
||||
_globalSettings = globalSettings;
|
||||
_policyService = policyService;
|
||||
_organizationUserRepository = organizationUserRepository;
|
||||
}
|
||||
|
||||
public async Task InitPendingOrganizationAsync(User user, Guid organizationId, Guid organizationUserId, string publicKey, string privateKey, string collectionName, string emailToken)
|
||||
{
|
||||
await ValidateSignUpPoliciesAsync(user.Id);
|
||||
|
||||
var orgUser = await _organizationUserRepository.GetByIdAsync(organizationUserId);
|
||||
if (orgUser == null)
|
||||
{
|
||||
throw new BadRequestException("User invalid.");
|
||||
}
|
||||
|
||||
var tokenValid = ValidateInviteToken(orgUser, user, emailToken);
|
||||
|
||||
if (!tokenValid)
|
||||
{
|
||||
throw new BadRequestException("Invalid token");
|
||||
}
|
||||
|
||||
var org = await _organizationRepository.GetByIdAsync(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 _organizationService.UpdateAsync(org);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(collectionName))
|
||||
{
|
||||
// give the owner Can Manage access over the default collection
|
||||
List<CollectionAccessSelection> defaultOwnerAccess =
|
||||
[new CollectionAccessSelection { Id = orgUser.Id, HidePasswords = false, ReadOnly = false, Manage = true }];
|
||||
|
||||
var defaultCollection = new Collection
|
||||
{
|
||||
Name = collectionName,
|
||||
OrganizationId = org.Id
|
||||
};
|
||||
await _collectionRepository.CreateAsync(defaultCollection, null, defaultOwnerAccess);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ValidateSignUpPoliciesAsync(Guid ownerId)
|
||||
{
|
||||
var anySingleOrgPolicies = await _policyService.AnyPoliciesApplicableToUserAsync(ownerId, PolicyType.SingleOrg);
|
||||
if (anySingleOrgPolicies)
|
||||
{
|
||||
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.");
|
||||
}
|
||||
}
|
||||
|
||||
private bool ValidateInviteToken(OrganizationUser orgUser, User user, string emailToken)
|
||||
{
|
||||
var tokenValid = OrgUserInviteTokenable.ValidateOrgUserInviteStringToken(
|
||||
_orgUserInviteTokenDataFactory, emailToken, orgUser);
|
||||
|
||||
return tokenValid;
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
using Bit.Core.Entities;
|
||||
namespace Bit.Core.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||
|
||||
public interface IInitPendingOrganizationCommand
|
||||
{
|
||||
/// <summary>
|
||||
/// Update an Organization entry by setting the public/private keys, set it as 'Enabled' and move the Status from 'Pending' to 'Created'.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method must target a disabled Organization that has null keys and status as 'Pending'.
|
||||
/// </remarks>
|
||||
Task InitPendingOrganizationAsync(User user, Guid organizationId, Guid organizationUserId, string publicKey, string privateKey, string collectionName, string emailToken);
|
||||
}
|
@ -45,14 +45,8 @@ public interface IOrganizationService
|
||||
Task<List<Tuple<OrganizationUser, string>>> RevokeUsersAsync(Guid organizationId,
|
||||
IEnumerable<Guid> organizationUserIds, Guid? revokingUserId);
|
||||
Task CreatePendingOrganization(Organization organization, string ownerEmail, ClaimsPrincipal user, IUserService userService, bool salesAssistedTrialStarted);
|
||||
/// <summary>
|
||||
/// Update an Organization entry by setting the public/private keys, set it as 'Enabled' and move the Status from 'Pending' to 'Created'.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method must target a disabled Organization that has null keys and status as 'Pending'.
|
||||
/// </remarks>
|
||||
Task InitPendingOrganization(User user, Guid organizationId, Guid organizationUserId, string publicKey, string privateKey, string collectionName, string emailToken);
|
||||
Task ReplaceAndUpdateCacheAsync(Organization org, EventType? orgEvent = null);
|
||||
Task<(bool canScale, string failureReason)> CanScaleAsync(Organization organization, int seatsToAdd);
|
||||
|
||||
void ValidatePasswordManagerPlan(Models.StaticStore.Plan plan, OrganizationUpgrade upgrade);
|
||||
void ValidateSecretsManagerPlan(Models.StaticStore.Plan plan, OrganizationUpgrade upgrade);
|
||||
|
@ -1,5 +1,6 @@
|
||||
using Bit.Core.AdminConsole.Entities.Provider;
|
||||
using Bit.Core.AdminConsole.Models.Business.Provider;
|
||||
using Bit.Core.Billing.Models;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Models.Business;
|
||||
|
||||
@ -7,7 +8,8 @@ namespace Bit.Core.AdminConsole.Services;
|
||||
|
||||
public interface IProviderService
|
||||
{
|
||||
Task<Provider> CompleteSetupAsync(Provider provider, Guid ownerUserId, string token, string key, TaxInfo taxInfo = null);
|
||||
Task<Provider> CompleteSetupAsync(Provider provider, Guid ownerUserId, string token, string key, TaxInfo taxInfo,
|
||||
TokenizedPaymentSource tokenizedPaymentSource = null);
|
||||
Task UpdateAsync(Provider provider, bool updateBilling = false);
|
||||
|
||||
Task<List<ProviderUser>> InviteUserAsync(ProviderUserInvite<string> invite);
|
||||
|
@ -0,0 +1,34 @@
|
||||
using Bit.Core.Models.Data;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Bit.Core.Services;
|
||||
|
||||
public class EventRouteService(
|
||||
[FromKeyedServices("broadcast")] IEventWriteService broadcastEventWriteService,
|
||||
[FromKeyedServices("storage")] IEventWriteService storageEventWriteService,
|
||||
IFeatureService _featureService) : IEventWriteService
|
||||
{
|
||||
public async Task CreateAsync(IEvent e)
|
||||
{
|
||||
if (_featureService.IsEnabled(FeatureFlagKeys.EventBasedOrganizationIntegrations))
|
||||
{
|
||||
await broadcastEventWriteService.CreateAsync(e);
|
||||
}
|
||||
else
|
||||
{
|
||||
await storageEventWriteService.CreateAsync(e);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task CreateManyAsync(IEnumerable<IEvent> e)
|
||||
{
|
||||
if (_featureService.IsEnabled(FeatureFlagKeys.EventBasedOrganizationIntegrations))
|
||||
{
|
||||
await broadcastEventWriteService.CreateManyAsync(e);
|
||||
}
|
||||
else
|
||||
{
|
||||
await storageEventWriteService.CreateManyAsync(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
using System.Text.Json.Nodes;
|
||||
using Bit.Core.AdminConsole.Utilities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Models.Data.Integrations;
|
||||
using Bit.Core.Repositories;
|
||||
|
||||
namespace Bit.Core.Services;
|
||||
|
||||
public abstract class IntegrationEventHandlerBase(
|
||||
IUserRepository userRepository,
|
||||
IOrganizationRepository organizationRepository,
|
||||
IOrganizationIntegrationConfigurationRepository configurationRepository)
|
||||
: IEventMessageHandler
|
||||
{
|
||||
public async Task HandleEventAsync(EventMessage eventMessage)
|
||||
{
|
||||
var organizationId = eventMessage.OrganizationId ?? Guid.Empty;
|
||||
var configurations = await configurationRepository.GetConfigurationDetailsAsync(
|
||||
organizationId,
|
||||
GetIntegrationType(),
|
||||
eventMessage.Type);
|
||||
|
||||
foreach (var configuration in configurations)
|
||||
{
|
||||
var context = await BuildContextAsync(eventMessage, configuration.Template);
|
||||
var renderedTemplate = IntegrationTemplateProcessor.ReplaceTokens(configuration.Template, context);
|
||||
|
||||
await ProcessEventIntegrationAsync(configuration.MergedConfiguration, renderedTemplate);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task HandleManyEventsAsync(IEnumerable<EventMessage> eventMessages)
|
||||
{
|
||||
foreach (var eventMessage in eventMessages)
|
||||
{
|
||||
await HandleEventAsync(eventMessage);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<IntegrationTemplateContext> BuildContextAsync(EventMessage eventMessage, string template)
|
||||
{
|
||||
var context = new IntegrationTemplateContext(eventMessage);
|
||||
|
||||
if (IntegrationTemplateProcessor.TemplateRequiresUser(template) && eventMessage.UserId.HasValue)
|
||||
{
|
||||
context.User = await userRepository.GetByIdAsync(eventMessage.UserId.Value);
|
||||
}
|
||||
|
||||
if (IntegrationTemplateProcessor.TemplateRequiresActingUser(template) && eventMessage.ActingUserId.HasValue)
|
||||
{
|
||||
context.ActingUser = await userRepository.GetByIdAsync(eventMessage.ActingUserId.Value);
|
||||
}
|
||||
|
||||
if (IntegrationTemplateProcessor.TemplateRequiresOrganization(template) && eventMessage.OrganizationId.HasValue)
|
||||
{
|
||||
context.Organization = await organizationRepository.GetByIdAsync(eventMessage.OrganizationId.Value);
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
protected abstract IntegrationType GetIntegrationType();
|
||||
|
||||
protected abstract Task ProcessEventIntegrationAsync(JsonObject mergedConfiguration, string renderedTemplate);
|
||||
}
|
@ -13,7 +13,6 @@ using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.AdminConsole.Services;
|
||||
using Bit.Core.Auth.Enums;
|
||||
using Bit.Core.Auth.Models.Business.Tokenables;
|
||||
using Bit.Core.Auth.Repositories;
|
||||
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
|
||||
using Bit.Core.Billing.Constants;
|
||||
@ -31,12 +30,10 @@ using Bit.Core.OrganizationFeatures.OrganizationSubscriptions.Interface;
|
||||
using Bit.Core.Platform.Push;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Settings;
|
||||
using Bit.Core.Tokens;
|
||||
using Bit.Core.Tools.Enums;
|
||||
using Bit.Core.Tools.Models.Business;
|
||||
using Bit.Core.Tools.Services;
|
||||
using Bit.Core.Utilities;
|
||||
using Microsoft.AspNetCore.DataProtection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Stripe;
|
||||
using OrganizationUserInvite = Bit.Core.Models.Business.OrganizationUserInvite;
|
||||
@ -77,8 +74,6 @@ public class OrganizationService : IOrganizationService
|
||||
private readonly IPricingClient _pricingClient;
|
||||
private readonly IPolicyRequirementQuery _policyRequirementQuery;
|
||||
private readonly ISendOrganizationInvitesCommand _sendOrganizationInvitesCommand;
|
||||
private readonly IDataProtectorTokenFactory<OrgUserInviteTokenable> _orgUserInviteTokenDataFactory;
|
||||
private readonly IDataProtector _dataProtector;
|
||||
|
||||
public OrganizationService(
|
||||
IOrganizationRepository organizationRepository,
|
||||
@ -112,9 +107,7 @@ public class OrganizationService : IOrganizationService
|
||||
IHasConfirmedOwnersExceptQuery hasConfirmedOwnersExceptQuery,
|
||||
IPricingClient pricingClient,
|
||||
IPolicyRequirementQuery policyRequirementQuery,
|
||||
ISendOrganizationInvitesCommand sendOrganizationInvitesCommand,
|
||||
IDataProtectorTokenFactory<OrgUserInviteTokenable> orgUserInviteTokenDataFactory,
|
||||
IDataProtectionProvider dataProtectionProvider
|
||||
ISendOrganizationInvitesCommand sendOrganizationInvitesCommand
|
||||
)
|
||||
{
|
||||
_organizationRepository = organizationRepository;
|
||||
@ -149,8 +142,6 @@ public class OrganizationService : IOrganizationService
|
||||
_pricingClient = pricingClient;
|
||||
_policyRequirementQuery = policyRequirementQuery;
|
||||
_sendOrganizationInvitesCommand = sendOrganizationInvitesCommand;
|
||||
_orgUserInviteTokenDataFactory = orgUserInviteTokenDataFactory;
|
||||
_dataProtector = dataProtectionProvider.CreateProtector(OrgUserInviteTokenable.DataProtectorPurpose);
|
||||
}
|
||||
|
||||
public async Task ReplacePaymentMethodAsync(Guid organizationId, string paymentToken,
|
||||
@ -1008,7 +999,7 @@ public class OrganizationService : IOrganizationService
|
||||
organization: organization,
|
||||
initOrganization: initOrganization));
|
||||
|
||||
internal async Task<(bool canScale, string failureReason)> CanScaleAsync(
|
||||
public async Task<(bool canScale, string failureReason)> CanScaleAsync(
|
||||
Organization organization,
|
||||
int seatsToAdd)
|
||||
{
|
||||
@ -1862,71 +1853,4 @@ public class OrganizationService : IOrganizationService
|
||||
SalesAssistedTrialStarted = salesAssistedTrialStarted,
|
||||
});
|
||||
}
|
||||
|
||||
public async Task InitPendingOrganization(User user, Guid organizationId, Guid organizationUserId, string publicKey, string privateKey, string collectionName, string emailToken)
|
||||
{
|
||||
await ValidateSignUpPoliciesAsync(user.Id);
|
||||
|
||||
var orgUser = await _organizationUserRepository.GetByIdAsync(organizationUserId);
|
||||
if (orgUser == null)
|
||||
{
|
||||
throw new BadRequestException("User invalid.");
|
||||
}
|
||||
|
||||
// TODO: PM-4142 - remove old token validation logic once 3 releases of backwards compatibility are complete
|
||||
var newTokenValid = OrgUserInviteTokenable.ValidateOrgUserInviteStringToken(
|
||||
_orgUserInviteTokenDataFactory, emailToken, orgUser);
|
||||
|
||||
var tokenValid = newTokenValid ||
|
||||
CoreHelpers.UserInviteTokenIsValid(_dataProtector, emailToken, user.Email, orgUser.Id,
|
||||
_globalSettings);
|
||||
|
||||
if (!tokenValid)
|
||||
{
|
||||
throw new BadRequestException("Invalid token.");
|
||||
}
|
||||
|
||||
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))
|
||||
{
|
||||
// give the owner Can Manage access over the default collection
|
||||
List<CollectionAccessSelection> defaultOwnerAccess =
|
||||
[new CollectionAccessSelection { Id = organizationUserId, HidePasswords = false, ReadOnly = false, Manage = true }];
|
||||
|
||||
var defaultCollection = new Collection
|
||||
{
|
||||
Name = collectionName,
|
||||
OrganizationId = org.Id
|
||||
};
|
||||
await _collectionRepository.CreateAsync(defaultCollection, null, defaultOwnerAccess);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,46 +1,35 @@
|
||||
using System.Text.Json;
|
||||
using Bit.Core.AdminConsole.Utilities;
|
||||
using System.Text.Json.Nodes;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Models.Data.Integrations;
|
||||
using Bit.Core.Repositories;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Bit.Core.Services;
|
||||
|
||||
public class SlackEventHandler(
|
||||
IUserRepository userRepository,
|
||||
IOrganizationRepository organizationRepository,
|
||||
IOrganizationIntegrationConfigurationRepository configurationRepository,
|
||||
ISlackService slackService)
|
||||
: IEventMessageHandler
|
||||
: IntegrationEventHandlerBase(userRepository, organizationRepository, configurationRepository)
|
||||
{
|
||||
public async Task HandleEventAsync(EventMessage eventMessage)
|
||||
protected override IntegrationType GetIntegrationType() => IntegrationType.Slack;
|
||||
|
||||
protected override async Task ProcessEventIntegrationAsync(JsonObject mergedConfiguration,
|
||||
string renderedTemplate)
|
||||
{
|
||||
var organizationId = eventMessage.OrganizationId ?? Guid.Empty;
|
||||
var configurations = await configurationRepository.GetConfigurationDetailsAsync(
|
||||
organizationId,
|
||||
IntegrationType.Slack,
|
||||
eventMessage.Type);
|
||||
|
||||
foreach (var configuration in configurations)
|
||||
var config = mergedConfiguration.Deserialize<SlackIntegrationConfigurationDetails>();
|
||||
if (config is null)
|
||||
{
|
||||
var config = configuration.MergedConfiguration.Deserialize<SlackIntegrationConfigurationDetails>();
|
||||
if (config is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
await slackService.SendSlackMessageByChannelIdAsync(
|
||||
config.token,
|
||||
IntegrationTemplateProcessor.ReplaceTokens(configuration.Template, eventMessage),
|
||||
config.channelId
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task HandleManyEventsAsync(IEnumerable<EventMessage> eventMessages)
|
||||
{
|
||||
foreach (var eventMessage in eventMessages)
|
||||
{
|
||||
await HandleEventAsync(eventMessage);
|
||||
}
|
||||
await slackService.SendSlackMessageByChannelIdAsync(
|
||||
config.token,
|
||||
renderedTemplate,
|
||||
config.channelId
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,7 @@
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using Bit.Core.AdminConsole.Utilities;
|
||||
using System.Text.Json.Nodes;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Models.Data.Integrations;
|
||||
using Bit.Core.Repositories;
|
||||
|
||||
@ -12,46 +11,28 @@ namespace Bit.Core.Services;
|
||||
|
||||
public class WebhookEventHandler(
|
||||
IHttpClientFactory httpClientFactory,
|
||||
IUserRepository userRepository,
|
||||
IOrganizationRepository organizationRepository,
|
||||
IOrganizationIntegrationConfigurationRepository configurationRepository)
|
||||
: IEventMessageHandler
|
||||
: IntegrationEventHandlerBase(userRepository, organizationRepository, configurationRepository)
|
||||
{
|
||||
private readonly HttpClient _httpClient = httpClientFactory.CreateClient(HttpClientName);
|
||||
|
||||
public const string HttpClientName = "WebhookEventHandlerHttpClient";
|
||||
|
||||
public async Task HandleEventAsync(EventMessage eventMessage)
|
||||
protected override IntegrationType GetIntegrationType() => IntegrationType.Webhook;
|
||||
|
||||
protected override async Task ProcessEventIntegrationAsync(JsonObject mergedConfiguration,
|
||||
string renderedTemplate)
|
||||
{
|
||||
var organizationId = eventMessage.OrganizationId ?? Guid.Empty;
|
||||
var configurations = await configurationRepository.GetConfigurationDetailsAsync(
|
||||
organizationId,
|
||||
IntegrationType.Webhook,
|
||||
eventMessage.Type);
|
||||
|
||||
foreach (var configuration in configurations)
|
||||
var config = mergedConfiguration.Deserialize<WebhookIntegrationConfigurationDetils>();
|
||||
if (config is null || string.IsNullOrEmpty(config.url))
|
||||
{
|
||||
var config = configuration.MergedConfiguration.Deserialize<WebhookIntegrationConfigurationDetils>();
|
||||
if (config is null || string.IsNullOrEmpty(config.url))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var content = new StringContent(
|
||||
IntegrationTemplateProcessor.ReplaceTokens(configuration.Template, eventMessage),
|
||||
Encoding.UTF8,
|
||||
"application/json"
|
||||
);
|
||||
var response = await _httpClient.PostAsync(
|
||||
config.url,
|
||||
content);
|
||||
response.EnsureSuccessStatusCode();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task HandleManyEventsAsync(IEnumerable<EventMessage> eventMessages)
|
||||
{
|
||||
foreach (var eventMessage in eventMessages)
|
||||
{
|
||||
await HandleEventAsync(eventMessage);
|
||||
}
|
||||
var content = new StringContent(renderedTemplate, Encoding.UTF8, "application/json");
|
||||
var response = await _httpClient.PostAsync(config.url, content);
|
||||
response.EnsureSuccessStatusCode();
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
using Bit.Core.AdminConsole.Entities.Provider;
|
||||
using Bit.Core.AdminConsole.Models.Business.Provider;
|
||||
using Bit.Core.Billing.Models;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Models.Business;
|
||||
|
||||
@ -7,7 +8,7 @@ namespace Bit.Core.AdminConsole.Services.NoopImplementations;
|
||||
|
||||
public class NoopProviderService : IProviderService
|
||||
{
|
||||
public Task<Provider> CompleteSetupAsync(Provider provider, Guid ownerUserId, string token, string key, TaxInfo taxInfo = null) => throw new NotImplementedException();
|
||||
public Task<Provider> CompleteSetupAsync(Provider provider, Guid ownerUserId, string token, string key, TaxInfo taxInfo, TokenizedPaymentSource tokenizedPaymentSource = null) => throw new NotImplementedException();
|
||||
|
||||
public Task UpdateAsync(Provider provider, bool updateBilling = false) => throw new NotImplementedException();
|
||||
|
||||
|
@ -10,8 +10,9 @@ public static partial class IntegrationTemplateProcessor
|
||||
public static string ReplaceTokens(string template, object values)
|
||||
{
|
||||
if (string.IsNullOrEmpty(template) || values == null)
|
||||
{
|
||||
return template;
|
||||
|
||||
}
|
||||
var type = values.GetType();
|
||||
return TokenRegex().Replace(template, match =>
|
||||
{
|
||||
@ -20,4 +21,36 @@ public static partial class IntegrationTemplateProcessor
|
||||
return property?.GetValue(values)?.ToString() ?? match.Value;
|
||||
});
|
||||
}
|
||||
|
||||
public static bool TemplateRequiresUser(string template)
|
||||
{
|
||||
if (string.IsNullOrEmpty(template))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return template.Contains("#UserName#", StringComparison.Ordinal)
|
||||
|| template.Contains("#UserEmail#", StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
public static bool TemplateRequiresActingUser(string template)
|
||||
{
|
||||
if (string.IsNullOrEmpty(template))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return template.Contains("#ActingUserName#", StringComparison.Ordinal)
|
||||
|| template.Contains("#ActingUserEmail#", StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
public static bool TemplateRequiresOrganization(string template)
|
||||
{
|
||||
if (string.IsNullOrEmpty(template))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return template.Contains("#OrganizationName#", StringComparison.Ordinal);
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,10 @@
|
||||
|
||||
public static class StripeConstants
|
||||
{
|
||||
public static class Prices
|
||||
{
|
||||
public const string StoragePlanPersonal = "personal-storage-gb-annually";
|
||||
}
|
||||
public static class AutomaticTaxStatus
|
||||
{
|
||||
public const string Failed = "failed";
|
||||
@ -42,10 +46,12 @@ public static class StripeConstants
|
||||
{
|
||||
public const string Draft = "draft";
|
||||
public const string Open = "open";
|
||||
public const string Paid = "paid";
|
||||
}
|
||||
|
||||
public static class MetadataKeys
|
||||
{
|
||||
public const string BraintreeCustomerId = "btCustomerId";
|
||||
public const string InvoiceApproved = "invoice_approved";
|
||||
public const string OrganizationId = "organizationId";
|
||||
public const string ProviderId = "providerId";
|
||||
|
@ -34,6 +34,7 @@ public static class OrganizationLicenseConstants
|
||||
public const string UseSecretsManager = nameof(UseSecretsManager);
|
||||
public const string SmSeats = nameof(SmSeats);
|
||||
public const string SmServiceAccounts = nameof(SmServiceAccounts);
|
||||
public const string SmMaxProjects = nameof(SmMaxProjects);
|
||||
public const string LimitCollectionCreationDeletion = nameof(LimitCollectionCreationDeletion);
|
||||
public const string AllowAdminAccessToAllCollectionItems = nameof(AllowAdminAccessToAllCollectionItems);
|
||||
public const string UseRiskInsights = nameof(UseRiskInsights);
|
||||
|
@ -7,4 +7,5 @@ public class LicenseContext
|
||||
{
|
||||
public Guid? InstallationId { get; init; }
|
||||
public required SubscriptionInfo SubscriptionInfo { get; init; }
|
||||
public int? SmMaxProjects { get; set; }
|
||||
}
|
||||
|
@ -112,6 +112,11 @@ public class OrganizationLicenseClaimsFactory : ILicenseClaimsFactory<Organizati
|
||||
}
|
||||
claims.Add(new Claim(nameof(OrganizationLicenseConstants.UseAdminSponsoredFamilies), entity.UseAdminSponsoredFamilies.ToString()));
|
||||
|
||||
if (licenseContext.SmMaxProjects.HasValue)
|
||||
{
|
||||
claims.Add(new Claim(nameof(OrganizationLicenseConstants.SmMaxProjects), licenseContext.SmMaxProjects.ToString()));
|
||||
}
|
||||
|
||||
return Task.FromResult(claims);
|
||||
}
|
||||
|
||||
|
@ -38,7 +38,7 @@ public record Families2019Plan : Plan
|
||||
HasPremiumAccessOption = true;
|
||||
|
||||
StripePlanId = "personal-org-annually";
|
||||
StripeStoragePlanId = "storage-gb-annually";
|
||||
StripeStoragePlanId = "personal-storage-gb-annually";
|
||||
StripePremiumAccessPlanId = "personal-org-premium-access-annually";
|
||||
BasePrice = 12;
|
||||
AdditionalStoragePricePerGb = 4;
|
||||
|
@ -37,7 +37,7 @@ public record FamiliesPlan : Plan
|
||||
HasAdditionalStorageOption = true;
|
||||
|
||||
StripePlanId = "2020-families-org-annually";
|
||||
StripeStoragePlanId = "storage-gb-annually";
|
||||
StripeStoragePlanId = "personal-storage-gb-annually";
|
||||
BasePrice = 40;
|
||||
AdditionalStoragePricePerGb = 4;
|
||||
|
||||
|
@ -79,10 +79,12 @@ public interface IProviderBillingService
|
||||
/// </summary>
|
||||
/// <param name="provider">The <see cref="Provider"/> to create a Stripe customer for.</param>
|
||||
/// <param name="taxInfo">The <see cref="TaxInfo"/> to use for calculating the customer's automatic tax.</param>
|
||||
/// <param name="tokenizedPaymentSource">The <see cref="TokenizedPaymentSource"/> (ex. Credit Card) to attach to the customer.</param>
|
||||
/// <returns>The newly created <see cref="Stripe.Customer"/> for the <paramref name="provider"/>.</returns>
|
||||
Task<Customer> SetupCustomer(
|
||||
Provider provider,
|
||||
TaxInfo taxInfo);
|
||||
TaxInfo taxInfo,
|
||||
TokenizedPaymentSource tokenizedPaymentSource = null);
|
||||
|
||||
/// <summary>
|
||||
/// For use during the provider setup process, this method starts a Stripe <see cref="Stripe.Subscription"/> for the given <paramref name="provider"/>.
|
||||
|
@ -313,7 +313,7 @@ public class PremiumUserBillingService(
|
||||
{
|
||||
subscriptionItemOptionsList.Add(new SubscriptionItemOptions
|
||||
{
|
||||
Price = "storage-gb-annually",
|
||||
Price = StripeConstants.Prices.StoragePlanPersonal,
|
||||
Quantity = storage
|
||||
});
|
||||
}
|
||||
|
@ -149,6 +149,8 @@ public static class FeatureFlagKeys
|
||||
public const string PM19422_AllowAutomaticTaxUpdates = "pm-19422-allow-automatic-tax-updates";
|
||||
public const string PM18770_EnableOrganizationBusinessUnitConversion = "pm-18770-enable-organization-business-unit-conversion";
|
||||
public const string PM199566_UpdateMSPToChargeAutomatically = "pm-199566-update-msp-to-charge-automatically";
|
||||
public const string PM19956_RequireProviderPaymentMethodDuringSetup = "pm-19956-require-provider-payment-method-during-setup";
|
||||
public const string UseOrganizationWarningsService = "use-organization-warnings-service";
|
||||
|
||||
/* Data Insights and Reporting Team */
|
||||
public const string RiskInsightsCriticalApplication = "pm-14466-risk-insights-critical-application";
|
||||
|
@ -0,0 +1,37 @@
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Data.Organizations.OrganizationSponsorships;
|
||||
|
||||
namespace Bit.Core.Models.Api.Response.OrganizationSponsorships;
|
||||
|
||||
public class OrganizationSponsorshipInvitesResponseModel : ResponseModel
|
||||
{
|
||||
public OrganizationSponsorshipInvitesResponseModel(OrganizationSponsorshipData sponsorshipData, string obj = "organizationSponsorship") : base(obj)
|
||||
{
|
||||
if (sponsorshipData == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(sponsorshipData));
|
||||
}
|
||||
|
||||
SponsoringOrganizationUserId = sponsorshipData.SponsoringOrganizationUserId;
|
||||
FriendlyName = sponsorshipData.FriendlyName;
|
||||
OfferedToEmail = sponsorshipData.OfferedToEmail;
|
||||
PlanSponsorshipType = sponsorshipData.PlanSponsorshipType;
|
||||
LastSyncDate = sponsorshipData.LastSyncDate;
|
||||
ValidUntil = sponsorshipData.ValidUntil;
|
||||
ToDelete = sponsorshipData.ToDelete;
|
||||
IsAdminInitiated = sponsorshipData.IsAdminInitiated;
|
||||
Notes = sponsorshipData.Notes;
|
||||
CloudSponsorshipRemoved = sponsorshipData.CloudSponsorshipRemoved;
|
||||
}
|
||||
|
||||
public Guid SponsoringOrganizationUserId { get; set; }
|
||||
public string FriendlyName { get; set; }
|
||||
public string OfferedToEmail { get; set; }
|
||||
public PlanSponsorshipType PlanSponsorshipType { get; set; }
|
||||
public DateTime? LastSyncDate { get; set; }
|
||||
public DateTime? ValidUntil { get; set; }
|
||||
public bool ToDelete { get; set; }
|
||||
public bool IsAdminInitiated { get; set; }
|
||||
public string Notes { get; set; }
|
||||
public bool CloudSponsorshipRemoved { get; set; }
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Billing.Pricing;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Business;
|
||||
@ -16,19 +17,22 @@ public class CloudGetOrganizationLicenseQuery : ICloudGetOrganizationLicenseQuer
|
||||
private readonly ILicensingService _licensingService;
|
||||
private readonly IProviderRepository _providerRepository;
|
||||
private readonly IFeatureService _featureService;
|
||||
private readonly IPricingClient _pricingClient;
|
||||
|
||||
public CloudGetOrganizationLicenseQuery(
|
||||
IInstallationRepository installationRepository,
|
||||
IPaymentService paymentService,
|
||||
ILicensingService licensingService,
|
||||
IProviderRepository providerRepository,
|
||||
IFeatureService featureService)
|
||||
IFeatureService featureService,
|
||||
IPricingClient pricingClient)
|
||||
{
|
||||
_installationRepository = installationRepository;
|
||||
_paymentService = paymentService;
|
||||
_licensingService = licensingService;
|
||||
_providerRepository = providerRepository;
|
||||
_featureService = featureService;
|
||||
_pricingClient = pricingClient;
|
||||
}
|
||||
|
||||
public async Task<OrganizationLicense> GetLicenseAsync(Organization organization, Guid installationId,
|
||||
@ -42,7 +46,11 @@ public class CloudGetOrganizationLicenseQuery : ICloudGetOrganizationLicenseQuer
|
||||
|
||||
var subscriptionInfo = await GetSubscriptionAsync(organization);
|
||||
var license = new OrganizationLicense(organization, subscriptionInfo, installationId, _licensingService, version);
|
||||
license.Token = await _licensingService.CreateOrganizationTokenAsync(organization, installationId, subscriptionInfo);
|
||||
var plan = await _pricingClient.GetPlan(organization.PlanType);
|
||||
int? smMaxProjects = plan?.SupportsSecretsManager ?? false
|
||||
? plan.SecretsManager.MaxProjects
|
||||
: null;
|
||||
license.Token = await _licensingService.CreateOrganizationTokenAsync(organization, installationId, subscriptionInfo, smMaxProjects);
|
||||
|
||||
return license;
|
||||
}
|
||||
|
@ -196,6 +196,7 @@ public static class OrganizationServiceCollectionExtensions
|
||||
services.AddScoped<IInviteUsersOrganizationValidator, InviteUsersOrganizationValidator>();
|
||||
services.AddScoped<IInviteUsersPasswordManagerValidator, InviteUsersPasswordManagerValidator>();
|
||||
services.AddScoped<IInviteUsersEnvironmentValidator, InviteUsersEnvironmentValidator>();
|
||||
services.AddScoped<IInitPendingOrganizationCommand, InitPendingOrganizationCommand>();
|
||||
}
|
||||
|
||||
// TODO: move to OrganizationSubscriptionServiceCollectionExtensions when OrganizationUser methods are moved out of
|
||||
|
@ -15,7 +15,8 @@ public class CreateSponsorshipCommand(
|
||||
ICurrentContext currentContext,
|
||||
IOrganizationSponsorshipRepository organizationSponsorshipRepository,
|
||||
IUserService userService,
|
||||
IOrganizationService organizationService) : ICreateSponsorshipCommand
|
||||
IOrganizationService organizationService,
|
||||
IOrganizationUserRepository organizationUserRepository) : ICreateSponsorshipCommand
|
||||
{
|
||||
public async Task<OrganizationSponsorship> CreateSponsorshipAsync(
|
||||
Organization sponsoringOrganization,
|
||||
@ -47,11 +48,12 @@ public class CreateSponsorshipCommand(
|
||||
throw new BadRequestException("Only confirmed users can sponsor other organizations.");
|
||||
}
|
||||
|
||||
var existingOrgSponsorship = await organizationSponsorshipRepository
|
||||
.GetBySponsoringOrganizationUserIdAsync(sponsoringMember.Id);
|
||||
if (existingOrgSponsorship?.SponsoredOrganizationId != null)
|
||||
var sponsorships =
|
||||
await organizationSponsorshipRepository.GetManyBySponsoringOrganizationAsync(sponsoringOrganization.Id);
|
||||
var existingSponsorship = sponsorships.FirstOrDefault(s => s.FriendlyName == friendlyName);
|
||||
if (existingSponsorship != null)
|
||||
{
|
||||
throw new BadRequestException("Can only sponsor one organization per Organization User.");
|
||||
return existingSponsorship;
|
||||
}
|
||||
|
||||
if (isAdminInitiated)
|
||||
@ -70,15 +72,37 @@ public class CreateSponsorshipCommand(
|
||||
Notes = notes
|
||||
};
|
||||
|
||||
if (existingOrgSponsorship != null)
|
||||
if (!isAdminInitiated)
|
||||
{
|
||||
// Replace existing invalid offer with our new sponsorship offer
|
||||
sponsorship.Id = existingOrgSponsorship.Id;
|
||||
var existingOrgSponsorship = await organizationSponsorshipRepository
|
||||
.GetBySponsoringOrganizationUserIdAsync(sponsoringMember.Id);
|
||||
if (existingOrgSponsorship?.SponsoredOrganizationId != null)
|
||||
{
|
||||
throw new BadRequestException("Can only sponsor one organization per Organization User.");
|
||||
}
|
||||
|
||||
if (existingOrgSponsorship != null)
|
||||
{
|
||||
sponsorship.Id = existingOrgSponsorship.Id;
|
||||
}
|
||||
}
|
||||
|
||||
if (isAdminInitiated && sponsoringOrganization.Seats.HasValue)
|
||||
{
|
||||
await organizationService.AutoAddSeatsAsync(sponsoringOrganization, 1);
|
||||
var occupiedSeats = await organizationUserRepository.GetOccupiedSeatCountByOrganizationIdAsync(sponsoringOrganization.Id);
|
||||
var availableSeats = sponsoringOrganization.Seats.Value - occupiedSeats;
|
||||
|
||||
if (availableSeats <= 0)
|
||||
{
|
||||
var newSeatsRequired = 1;
|
||||
var (canScale, failureReason) = await organizationService.CanScaleAsync(sponsoringOrganization, newSeatsRequired);
|
||||
if (!canScale)
|
||||
{
|
||||
throw new BadRequestException(failureReason);
|
||||
}
|
||||
|
||||
await organizationService.AutoAddSeatsAsync(sponsoringOrganization, newSeatsRequired);
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
|
@ -39,7 +39,7 @@ public class AzurePhishingDomainStorageService
|
||||
var content = await streamReader.ReadToEndAsync();
|
||||
|
||||
return [.. content
|
||||
.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries)
|
||||
.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(line => line.Trim())
|
||||
.Where(line => !string.IsNullOrWhiteSpace(line) && !line.StartsWith('#'))];
|
||||
}
|
||||
|
@ -92,7 +92,7 @@ public class CloudPhishingDomainDirectQuery : ICloudPhishingDomainQuery
|
||||
}
|
||||
|
||||
return content
|
||||
.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries)
|
||||
.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(line => line.Trim())
|
||||
.Where(line => !string.IsNullOrWhiteSpace(line) && !line.StartsWith("#"))
|
||||
.ToList();
|
||||
|
@ -21,7 +21,8 @@ public interface ILicensingService
|
||||
Task<string?> CreateOrganizationTokenAsync(
|
||||
Organization organization,
|
||||
Guid installationId,
|
||||
SubscriptionInfo subscriptionInfo);
|
||||
SubscriptionInfo subscriptionInfo,
|
||||
int? smMaxProjects);
|
||||
|
||||
Task<string?> CreateUserTokenAsync(User user, SubscriptionInfo subscriptionInfo);
|
||||
}
|
||||
|
@ -339,12 +339,13 @@ public class LicensingService : ILicensingService
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string> CreateOrganizationTokenAsync(Organization organization, Guid installationId, SubscriptionInfo subscriptionInfo)
|
||||
public async Task<string> CreateOrganizationTokenAsync(Organization organization, Guid installationId, SubscriptionInfo subscriptionInfo, int? smMaxProjects)
|
||||
{
|
||||
var licenseContext = new LicenseContext
|
||||
{
|
||||
InstallationId = installationId,
|
||||
SubscriptionInfo = subscriptionInfo,
|
||||
SmMaxProjects = smMaxProjects
|
||||
};
|
||||
|
||||
var claims = await _organizationLicenseClaimsFactory.GenerateClaims(organization, licenseContext);
|
||||
|
@ -11,6 +11,7 @@ using Bit.Core.AdminConsole.Services;
|
||||
using Bit.Core.Auth.Enums;
|
||||
using Bit.Core.Auth.Models;
|
||||
using Bit.Core.Auth.Models.Business.Tokenables;
|
||||
using Bit.Core.Billing.Constants;
|
||||
using Bit.Core.Billing.Models;
|
||||
using Bit.Core.Billing.Models.Sales;
|
||||
using Bit.Core.Billing.Services;
|
||||
@ -45,7 +46,6 @@ namespace Bit.Core.Services;
|
||||
public class UserService : UserManager<User>, IUserService, IDisposable
|
||||
{
|
||||
private const string PremiumPlanId = "premium-annually";
|
||||
private const string StoragePlanId = "storage-gb-annually";
|
||||
|
||||
private readonly IUserRepository _userRepository;
|
||||
private readonly ICipherRepository _cipherRepository;
|
||||
@ -1106,12 +1106,12 @@ public class UserService : UserManager<User>, IUserService, IDisposable
|
||||
}
|
||||
|
||||
var secret = await BillingHelpers.AdjustStorageAsync(_paymentService, user, storageAdjustmentGb,
|
||||
StoragePlanId);
|
||||
StripeConstants.Prices.StoragePlanPersonal);
|
||||
await _referenceEventService.RaiseEventAsync(
|
||||
new ReferenceEvent(ReferenceEventType.AdjustStorage, user, _currentContext)
|
||||
{
|
||||
Storage = storageAdjustmentGb,
|
||||
PlanName = StoragePlanId,
|
||||
PlanName = StripeConstants.Prices.StoragePlanPersonal,
|
||||
});
|
||||
await SaveUserAsync(user);
|
||||
return secret;
|
||||
|
@ -62,7 +62,7 @@ public class NoopLicensingService : ILicensingService
|
||||
return null;
|
||||
}
|
||||
|
||||
public Task<string?> CreateOrganizationTokenAsync(Organization organization, Guid installationId, SubscriptionInfo subscriptionInfo)
|
||||
public Task<string?> CreateOrganizationTokenAsync(Organization organization, Guid installationId, SubscriptionInfo subscriptionInfo, int? smMaxProjects)
|
||||
{
|
||||
return Task.FromResult<string?>(null);
|
||||
}
|
||||
|
@ -379,7 +379,7 @@ public class CipherService : ICipherService
|
||||
if (!valid || realSize > MAX_FILE_SIZE)
|
||||
{
|
||||
// File reported differs in size from that promised. Must be a rogue client. Delete Send
|
||||
await DeleteAttachmentAsync(cipher, attachmentData);
|
||||
await DeleteAttachmentAsync(cipher, attachmentData, false);
|
||||
return false;
|
||||
}
|
||||
// Update Send data if necessary
|
||||
@ -483,7 +483,7 @@ public class CipherService : ICipherService
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
return await DeleteAttachmentAsync(cipher, cipher.GetAttachments()[attachmentId]);
|
||||
return await DeleteAttachmentAsync(cipher, cipher.GetAttachments()[attachmentId], orgAdmin);
|
||||
}
|
||||
|
||||
public async Task PurgeAsync(Guid organizationId)
|
||||
@ -877,7 +877,7 @@ public class CipherService : ICipherService
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<DeleteAttachmentResponseData> DeleteAttachmentAsync(Cipher cipher, CipherAttachment.MetaData attachmentData)
|
||||
private async Task<DeleteAttachmentResponseData> DeleteAttachmentAsync(Cipher cipher, CipherAttachment.MetaData attachmentData, bool orgAdmin)
|
||||
{
|
||||
if (attachmentData == null || string.IsNullOrWhiteSpace(attachmentData.AttachmentId))
|
||||
{
|
||||
@ -891,7 +891,7 @@ public class CipherService : ICipherService
|
||||
|
||||
// Update the revision date when an attachment is deleted
|
||||
cipher.RevisionDate = DateTime.UtcNow;
|
||||
await _cipherRepository.ReplaceAsync((CipherDetails)cipher);
|
||||
await _cipherRepository.ReplaceAsync(orgAdmin ? cipher : (CipherDetails)cipher);
|
||||
|
||||
// push
|
||||
await _pushService.PushSyncCipherUpdateAsync(cipher, null);
|
||||
|
Reference in New Issue
Block a user