1
0
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:
Chad Scharf 2020-07-07 12:01:34 -04:00 committed by GitHub
parent b4524fbcb6
commit 7af50172e0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 248 additions and 3 deletions

View File

@ -0,0 +1,12 @@
using System.Runtime.Serialization;
namespace Bit.Core.Enums
{
public enum ReferenceEventSource
{
[EnumMember(Value = "organization")]
Organization,
[EnumMember(Value = "user")]
User,
}
}

View 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,
}
}

View 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; }
}
}

View File

@ -0,0 +1,11 @@
using System;
namespace Bit.Core.Models
{
public interface IReferenceable
{
Guid Id { get; set; }
string ReferenceId { get; set; }
bool IsUser();
}
}

View File

@ -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;

View File

@ -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;

View 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);
}
}

View File

@ -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
}
}
}
}

View File

@ -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) { }
}

View File

@ -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)

View File

@ -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;
}
}
}

View File

@ -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)