mirror of
https://github.com/bitwarden/server.git
synced 2024-11-22 12:15:36 +01:00
Reference event service implementation (#811)
* Reference event service implementation * Fix IReferenceable implementation of Id * add structure to event body
This commit is contained in:
parent
b4524fbcb6
commit
7af50172e0
12
src/Core/Enums/ReferenceEventSource.cs
Normal file
12
src/Core/Enums/ReferenceEventSource.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Bit.Core.Enums
|
||||
{
|
||||
public enum ReferenceEventSource
|
||||
{
|
||||
[EnumMember(Value = "organization")]
|
||||
Organization,
|
||||
[EnumMember(Value = "user")]
|
||||
User,
|
||||
}
|
||||
}
|
22
src/Core/Enums/ReferenceEventType.cs
Normal file
22
src/Core/Enums/ReferenceEventType.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Bit.Core.Enums
|
||||
{
|
||||
public enum ReferenceEventType
|
||||
{
|
||||
[EnumMember(Value = "signup")]
|
||||
Signup,
|
||||
[EnumMember(Value = "upgrade-plan")]
|
||||
UpgradePlan,
|
||||
[EnumMember(Value = "adjust-storage")]
|
||||
AdjustStorage,
|
||||
[EnumMember(Value = "adjust-seats")]
|
||||
AdjustSeats,
|
||||
[EnumMember(Value = "cancel-subscription")]
|
||||
CancelSubscription,
|
||||
[EnumMember(Value = "reinstate-subscription")]
|
||||
ReinstateSubscription,
|
||||
[EnumMember(Value = "delete-account")]
|
||||
DeleteAccount,
|
||||
}
|
||||
}
|
47
src/Core/Models/Business/ReferenceEvent.cs
Normal file
47
src/Core/Models/Business/ReferenceEvent.cs
Normal file
@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using Bit.Core.Enums;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace Bit.Core.Models.Business
|
||||
{
|
||||
[JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))]
|
||||
public class ReferenceEvent
|
||||
{
|
||||
public ReferenceEvent() { }
|
||||
|
||||
public ReferenceEvent(ReferenceEventType type, IReferenceable source)
|
||||
{
|
||||
Type = type;
|
||||
if (source != null)
|
||||
{
|
||||
Source = source.IsUser() ? ReferenceEventSource.User : ReferenceEventSource.Organization;
|
||||
Id = source.Id;
|
||||
ReferenceId = source.ReferenceId;
|
||||
}
|
||||
}
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public ReferenceEventType Type { get; set; }
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public ReferenceEventSource Source { get; set; }
|
||||
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public string ReferenceId { get; set; }
|
||||
|
||||
public DateTime EventDate { get; set; } = DateTime.UtcNow;
|
||||
|
||||
public bool? EndOfPeriod { get; set; }
|
||||
|
||||
public string PlanName { get; set; }
|
||||
|
||||
public PlanType? PlanType { get; set; }
|
||||
|
||||
public short? Seats { get; set; }
|
||||
|
||||
public short? Storage { get; set; }
|
||||
}
|
||||
}
|
11
src/Core/Models/Table/IReferenceable.cs
Normal file
11
src/Core/Models/Table/IReferenceable.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace Bit.Core.Models
|
||||
{
|
||||
public interface IReferenceable
|
||||
{
|
||||
Guid Id { get; set; }
|
||||
string ReferenceId { get; set; }
|
||||
bool IsUser();
|
||||
}
|
||||
}
|
@ -7,7 +7,7 @@ using System.Linq;
|
||||
|
||||
namespace Bit.Core.Models.Table
|
||||
{
|
||||
public class Organization : ITableObject<Guid>, ISubscriber, IStorable, IStorableSubscriber, IRevisable
|
||||
public class Organization : ITableObject<Guid>, ISubscriber, IStorable, IStorableSubscriber, IRevisable, IReferenceable
|
||||
{
|
||||
private Dictionary<TwoFactorProviderType, TwoFactorProvider> _twoFactorProviders;
|
||||
|
||||
|
@ -9,7 +9,7 @@ using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace Bit.Core.Models.Table
|
||||
{
|
||||
public class User : ITableObject<Guid>, ISubscriber, IStorable, IStorableSubscriber, IRevisable, ITwoFactorProvidersUser
|
||||
public class User : ITableObject<Guid>, ISubscriber, IStorable, IStorableSubscriber, IRevisable, ITwoFactorProvidersUser, IReferenceable
|
||||
{
|
||||
private Dictionary<TwoFactorProviderType, TwoFactorProvider> _twoFactorProviders;
|
||||
|
||||
|
10
src/Core/Services/IReferenceEventService.cs
Normal file
10
src/Core/Services/IReferenceEventService.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Models.Business;
|
||||
|
||||
namespace Bit.Core.Services
|
||||
{
|
||||
public interface IReferenceEventService
|
||||
{
|
||||
Task RaiseEventAsync(ReferenceEvent referenceEvent);
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
using System.Threading.Tasks;
|
||||
using Azure.Storage.Queues;
|
||||
using Bit.Core.Models.Business;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Bit.Core.Services
|
||||
{
|
||||
public class AzureQueueReferenceEventService : IReferenceEventService
|
||||
{
|
||||
private const string _queueName = "reference-events";
|
||||
|
||||
private readonly QueueClient _queueClient;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private readonly JsonSerializerSettings _jsonSerializerSettings = new JsonSerializerSettings
|
||||
{
|
||||
NullValueHandling = NullValueHandling.Ignore,
|
||||
};
|
||||
|
||||
public AzureQueueReferenceEventService (
|
||||
GlobalSettings globalSettings)
|
||||
{
|
||||
_queueClient = new QueueClient(globalSettings.Storage.ConnectionString, _queueName);
|
||||
_globalSettings = globalSettings;
|
||||
}
|
||||
|
||||
public async Task RaiseEventAsync(ReferenceEvent referenceEvent)
|
||||
{
|
||||
await SendMessageAsync(referenceEvent);
|
||||
}
|
||||
|
||||
private async Task SendMessageAsync(ReferenceEvent referenceEvent)
|
||||
{
|
||||
if (_globalSettings.SelfHosted || string.IsNullOrWhiteSpace(referenceEvent.ReferenceId))
|
||||
{
|
||||
// Ignore for self-hosted, OR, where there is no ReferenceId
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
var message = JsonConvert.SerializeObject(referenceEvent, _jsonSerializerSettings);
|
||||
await _queueClient.SendMessageAsync(message);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore failure
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -34,6 +34,7 @@ namespace Bit.Core.Services
|
||||
private readonly IApplicationCacheService _applicationCacheService;
|
||||
private readonly IPaymentService _paymentService;
|
||||
private readonly IPolicyRepository _policyRepository;
|
||||
private readonly IReferenceEventService _referenceEventService;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
|
||||
public OrganizationService(
|
||||
@ -53,6 +54,7 @@ namespace Bit.Core.Services
|
||||
IApplicationCacheService applicationCacheService,
|
||||
IPaymentService paymentService,
|
||||
IPolicyRepository policyRepository,
|
||||
IReferenceEventService referenceEventService,
|
||||
GlobalSettings globalSettings)
|
||||
{
|
||||
_organizationRepository = organizationRepository;
|
||||
@ -71,6 +73,7 @@ namespace Bit.Core.Services
|
||||
_applicationCacheService = applicationCacheService;
|
||||
_paymentService = paymentService;
|
||||
_policyRepository = policyRepository;
|
||||
_referenceEventService = referenceEventService;
|
||||
_globalSettings = globalSettings;
|
||||
}
|
||||
|
||||
@ -108,6 +111,11 @@ namespace Bit.Core.Services
|
||||
}
|
||||
|
||||
await _paymentService.CancelSubscriptionAsync(organization, eop);
|
||||
await _referenceEventService.RaiseEventAsync(
|
||||
new ReferenceEvent(ReferenceEventType.CancelSubscription, organization)
|
||||
{
|
||||
EndOfPeriod = endOfPeriod,
|
||||
});
|
||||
}
|
||||
|
||||
public async Task ReinstateSubscriptionAsync(Guid organizationId)
|
||||
@ -119,6 +127,8 @@ namespace Bit.Core.Services
|
||||
}
|
||||
|
||||
await _paymentService.ReinstateSubscriptionAsync(organization);
|
||||
await _referenceEventService.RaiseEventAsync(
|
||||
new ReferenceEvent(ReferenceEventType.ReinstateSubscription, organization));
|
||||
}
|
||||
|
||||
public async Task<Tuple<bool, string>> UpgradePlanAsync(Guid organizationId, OrganizationUpgrade upgrade)
|
||||
@ -240,6 +250,17 @@ namespace Bit.Core.Services
|
||||
organization.Plan = newPlan.Name;
|
||||
organization.Enabled = success;
|
||||
await ReplaceAndUpdateCache(organization);
|
||||
if (success)
|
||||
{
|
||||
await _referenceEventService.RaiseEventAsync(
|
||||
new ReferenceEvent(ReferenceEventType.UpgradePlan, organization)
|
||||
{
|
||||
PlanName = newPlan.Name,
|
||||
PlanType = newPlan.Type,
|
||||
Seats = organization.Seats,
|
||||
Storage = organization.MaxStorageGb,
|
||||
});
|
||||
}
|
||||
|
||||
return new Tuple<bool, string>(success, paymentIntentClientSecret);
|
||||
}
|
||||
@ -265,6 +286,13 @@ namespace Bit.Core.Services
|
||||
|
||||
var secret = await BillingHelpers.AdjustStorageAsync(_paymentService, organization, storageAdjustmentGb,
|
||||
plan.StripeStoragePlanId);
|
||||
await _referenceEventService.RaiseEventAsync(
|
||||
new ReferenceEvent(ReferenceEventType.AdjustStorage, organization)
|
||||
{
|
||||
PlanName = plan.Name,
|
||||
PlanType = plan.Type,
|
||||
Storage = storageAdjustmentGb,
|
||||
});
|
||||
await ReplaceAndUpdateCache(organization);
|
||||
return secret;
|
||||
}
|
||||
@ -399,6 +427,13 @@ namespace Bit.Core.Services
|
||||
}
|
||||
|
||||
organization.Seats = (short?)newSeatTotal;
|
||||
await _referenceEventService.RaiseEventAsync(
|
||||
new ReferenceEvent(ReferenceEventType.AdjustSeats, organization)
|
||||
{
|
||||
PlanName = plan.Name,
|
||||
PlanType = plan.Type,
|
||||
Seats = organization.Seats,
|
||||
});
|
||||
await ReplaceAndUpdateCache(organization);
|
||||
return paymentIntentClientSecret;
|
||||
}
|
||||
@ -503,7 +538,16 @@ namespace Bit.Core.Services
|
||||
signup.PremiumAccessAddon, signup.TaxInfo);
|
||||
}
|
||||
|
||||
return await SignUpAsync(organization, signup.Owner.Id, signup.OwnerKey, signup.CollectionName, true);
|
||||
var returnValue = await SignUpAsync(organization, signup.Owner.Id, signup.OwnerKey, signup.CollectionName, true);
|
||||
await _referenceEventService.RaiseEventAsync(
|
||||
new ReferenceEvent(ReferenceEventType.Signup, organization)
|
||||
{
|
||||
PlanName = plan.Name,
|
||||
PlanType = plan.Type,
|
||||
Seats = returnValue.Item1.Seats,
|
||||
Storage = returnValue.Item1.MaxStorageGb,
|
||||
});
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
public async Task<Tuple<Organization, OrganizationUser>> SignUpAsync(
|
||||
@ -741,6 +785,8 @@ namespace Bit.Core.Services
|
||||
var eop = !organization.ExpirationDate.HasValue ||
|
||||
organization.ExpirationDate.Value >= DateTime.UtcNow;
|
||||
await _paymentService.CancelSubscriptionAsync(organization, eop);
|
||||
await _referenceEventService.RaiseEventAsync(
|
||||
new ReferenceEvent(ReferenceEventType.DeleteAccount, organization));
|
||||
}
|
||||
catch (GatewayException) { }
|
||||
}
|
||||
|
@ -45,6 +45,7 @@ namespace Bit.Core.Services
|
||||
private readonly IPaymentService _paymentService;
|
||||
private readonly IPolicyRepository _policyRepository;
|
||||
private readonly IDataProtector _organizationServiceDataProtector;
|
||||
private readonly IReferenceEventService _referenceEventService;
|
||||
private readonly CurrentContext _currentContext;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
|
||||
@ -71,6 +72,7 @@ namespace Bit.Core.Services
|
||||
IDataProtectionProvider dataProtectionProvider,
|
||||
IPaymentService paymentService,
|
||||
IPolicyRepository policyRepository,
|
||||
IReferenceEventService referenceEventService,
|
||||
CurrentContext currentContext,
|
||||
GlobalSettings globalSettings)
|
||||
: base(
|
||||
@ -102,6 +104,7 @@ namespace Bit.Core.Services
|
||||
_policyRepository = policyRepository;
|
||||
_organizationServiceDataProtector = dataProtectionProvider.CreateProtector(
|
||||
"OrganizationServiceDataProtector");
|
||||
_referenceEventService = referenceEventService;
|
||||
_currentContext = currentContext;
|
||||
_globalSettings = globalSettings;
|
||||
}
|
||||
@ -219,6 +222,8 @@ namespace Bit.Core.Services
|
||||
}
|
||||
|
||||
await _userRepository.DeleteAsync(user);
|
||||
await _referenceEventService.RaiseEventAsync(
|
||||
new ReferenceEvent(ReferenceEventType.DeleteAccount, user));
|
||||
await _pushService.PushLogOutAsync(user.Id);
|
||||
return IdentityResult.Success;
|
||||
}
|
||||
@ -288,6 +293,8 @@ namespace Bit.Core.Services
|
||||
if (result == IdentityResult.Success)
|
||||
{
|
||||
await _mailService.SendWelcomeEmailAsync(user);
|
||||
await _referenceEventService.RaiseEventAsync(
|
||||
new ReferenceEvent(ReferenceEventType.Signup, user));
|
||||
}
|
||||
|
||||
return result;
|
||||
@ -765,6 +772,12 @@ namespace Bit.Core.Services
|
||||
{
|
||||
await SaveUserAsync(user);
|
||||
await _pushService.PushSyncVaultAsync(user.Id);
|
||||
await _referenceEventService.RaiseEventAsync(
|
||||
new ReferenceEvent(ReferenceEventType.UpgradePlan, user)
|
||||
{
|
||||
Storage = user.MaxStorageGb,
|
||||
PlanName = PremiumPlanId,
|
||||
});
|
||||
}
|
||||
catch when(!_globalSettings.SelfHosted)
|
||||
{
|
||||
@ -841,6 +854,12 @@ namespace Bit.Core.Services
|
||||
|
||||
var secret = await BillingHelpers.AdjustStorageAsync(_paymentService, user, storageAdjustmentGb,
|
||||
StoragePlanId);
|
||||
await _referenceEventService.RaiseEventAsync(
|
||||
new ReferenceEvent(ReferenceEventType.AdjustStorage, user)
|
||||
{
|
||||
Storage = storageAdjustmentGb,
|
||||
PlanName = StoragePlanId,
|
||||
});
|
||||
await SaveUserAsync(user);
|
||||
return secret;
|
||||
}
|
||||
@ -868,11 +887,18 @@ namespace Bit.Core.Services
|
||||
eop = false;
|
||||
}
|
||||
await _paymentService.CancelSubscriptionAsync(user, eop, accountDelete);
|
||||
await _referenceEventService.RaiseEventAsync(
|
||||
new ReferenceEvent(ReferenceEventType.CancelSubscription, user)
|
||||
{
|
||||
EndOfPeriod = eop,
|
||||
});
|
||||
}
|
||||
|
||||
public async Task ReinstatePremiumAsync(User user)
|
||||
{
|
||||
await _paymentService.ReinstateSubscriptionAsync(user);
|
||||
await _referenceEventService.RaiseEventAsync(
|
||||
new ReferenceEvent(ReferenceEventType.ReinstateSubscription, user));
|
||||
}
|
||||
|
||||
public async Task EnablePremiumAsync(Guid userId, DateTime? expirationDate)
|
||||
|
@ -0,0 +1,13 @@
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Models.Business;
|
||||
|
||||
namespace Bit.Core.Services
|
||||
{
|
||||
public class NoopReferenceEventService : IReferenceEventService
|
||||
{
|
||||
public Task RaiseEventAsync(ReferenceEvent referenceEvent)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
@ -196,6 +196,15 @@ namespace Bit.Core.Utilities
|
||||
{
|
||||
services.AddSingleton<IAttachmentStorageService, NoopAttachmentStorageService>();
|
||||
}
|
||||
|
||||
if (globalSettings.SelfHosted)
|
||||
{
|
||||
services.AddSingleton<IReferenceEventService, NoopReferenceEventService>();
|
||||
}
|
||||
else
|
||||
{
|
||||
services.AddSingleton<IReferenceEventService, AzureQueueReferenceEventService>();
|
||||
}
|
||||
}
|
||||
|
||||
public static void AddNoopServices(this IServiceCollection services)
|
||||
|
Loading…
Reference in New Issue
Block a user