mirror of
https://github.com/bitwarden/server.git
synced 2025-06-07 19:50:32 -05:00
Merge branch 'main' into billing/license-refactor
This commit is contained in:
commit
71cb4a4ea8
@ -31,6 +31,7 @@ public record PlanAdapter : Plan
|
|||||||
HasScim = HasFeature("scim");
|
HasScim = HasFeature("scim");
|
||||||
HasResetPassword = HasFeature("resetPassword");
|
HasResetPassword = HasFeature("resetPassword");
|
||||||
UsersGetPremium = HasFeature("usersGetPremium");
|
UsersGetPremium = HasFeature("usersGetPremium");
|
||||||
|
HasCustomPermissions = HasFeature("customPermissions");
|
||||||
UpgradeSortOrder = plan.AdditionalData.TryGetValue("upgradeSortOrder", out var upgradeSortOrder)
|
UpgradeSortOrder = plan.AdditionalData.TryGetValue("upgradeSortOrder", out var upgradeSortOrder)
|
||||||
? int.Parse(upgradeSortOrder)
|
? int.Parse(upgradeSortOrder)
|
||||||
: 0;
|
: 0;
|
||||||
@ -141,6 +142,7 @@ public record PlanAdapter : Plan
|
|||||||
var stripeSeatPlanId = GetStripeSeatPlanId(seats);
|
var stripeSeatPlanId = GetStripeSeatPlanId(seats);
|
||||||
var hasAdditionalSeatsOption = seats.IsScalable;
|
var hasAdditionalSeatsOption = seats.IsScalable;
|
||||||
var seatPrice = GetSeatPrice(seats);
|
var seatPrice = GetSeatPrice(seats);
|
||||||
|
var baseSeats = GetBaseSeats(seats);
|
||||||
var maxSeats = GetMaxSeats(seats);
|
var maxSeats = GetMaxSeats(seats);
|
||||||
var allowSeatAutoscale = seats.IsScalable;
|
var allowSeatAutoscale = seats.IsScalable;
|
||||||
var maxProjects = plan.AdditionalData.TryGetValue("secretsManager.maxProjects", out var value) ? short.Parse(value) : 0;
|
var maxProjects = plan.AdditionalData.TryGetValue("secretsManager.maxProjects", out var value) ? short.Parse(value) : 0;
|
||||||
@ -156,6 +158,7 @@ public record PlanAdapter : Plan
|
|||||||
StripeSeatPlanId = stripeSeatPlanId,
|
StripeSeatPlanId = stripeSeatPlanId,
|
||||||
HasAdditionalSeatsOption = hasAdditionalSeatsOption,
|
HasAdditionalSeatsOption = hasAdditionalSeatsOption,
|
||||||
SeatPrice = seatPrice,
|
SeatPrice = seatPrice,
|
||||||
|
BaseSeats = baseSeats,
|
||||||
MaxSeats = maxSeats,
|
MaxSeats = maxSeats,
|
||||||
AllowSeatAutoscale = allowSeatAutoscale,
|
AllowSeatAutoscale = allowSeatAutoscale,
|
||||||
MaxProjects = maxProjects
|
MaxProjects = maxProjects
|
||||||
@ -168,8 +171,16 @@ public record PlanAdapter : Plan
|
|||||||
private static decimal GetBasePrice(PurchasableDTO purchasable)
|
private static decimal GetBasePrice(PurchasableDTO purchasable)
|
||||||
=> purchasable.FromPackaged(x => x.Price);
|
=> purchasable.FromPackaged(x => x.Price);
|
||||||
|
|
||||||
|
private static int GetBaseSeats(FreeOrScalableDTO freeOrScalable)
|
||||||
|
=> freeOrScalable.Match(
|
||||||
|
free => free.Quantity,
|
||||||
|
scalable => scalable.Provided);
|
||||||
|
|
||||||
private static int GetBaseSeats(PurchasableDTO purchasable)
|
private static int GetBaseSeats(PurchasableDTO purchasable)
|
||||||
=> purchasable.FromPackaged(x => x.Quantity);
|
=> purchasable.Match(
|
||||||
|
free => free.Quantity,
|
||||||
|
packaged => packaged.Quantity,
|
||||||
|
scalable => scalable.Provided);
|
||||||
|
|
||||||
private static short GetBaseServiceAccount(FreeOrScalableDTO freeOrScalable)
|
private static short GetBaseServiceAccount(FreeOrScalableDTO freeOrScalable)
|
||||||
=> freeOrScalable.Match(
|
=> freeOrScalable.Match(
|
||||||
|
@ -3,6 +3,8 @@ using Microsoft.AspNetCore.Mvc.ModelBinding;
|
|||||||
|
|
||||||
namespace Bit.Core.Exceptions;
|
namespace Bit.Core.Exceptions;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
public class BadRequestException : Exception
|
public class BadRequestException : Exception
|
||||||
{
|
{
|
||||||
public BadRequestException() : base()
|
public BadRequestException() : base()
|
||||||
@ -41,5 +43,5 @@ public class BadRequestException : Exception
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ModelStateDictionary ModelState { get; set; }
|
public ModelStateDictionary? ModelState { get; set; }
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
namespace Bit.Core.Exceptions;
|
namespace Bit.Core.Exceptions;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
public class ConflictException : Exception
|
public class ConflictException : Exception
|
||||||
{
|
{
|
||||||
public ConflictException() : base("Conflict.") { }
|
public ConflictException() : base("Conflict.") { }
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
namespace Bit.Core.Exceptions;
|
namespace Bit.Core.Exceptions;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
public class DnsQueryException : Exception
|
public class DnsQueryException : Exception
|
||||||
{
|
{
|
||||||
public DnsQueryException(string message)
|
public DnsQueryException(string message)
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
namespace Bit.Core.Exceptions;
|
namespace Bit.Core.Exceptions;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
public class DomainClaimedException : Exception
|
public class DomainClaimedException : Exception
|
||||||
{
|
{
|
||||||
public DomainClaimedException()
|
public DomainClaimedException()
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
namespace Bit.Core.Exceptions;
|
namespace Bit.Core.Exceptions;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
public class DomainVerifiedException : Exception
|
public class DomainVerifiedException : Exception
|
||||||
{
|
{
|
||||||
public DomainVerifiedException()
|
public DomainVerifiedException()
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
namespace Bit.Core.Exceptions;
|
namespace Bit.Core.Exceptions;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
public class DuplicateDomainException : Exception
|
public class DuplicateDomainException : Exception
|
||||||
{
|
{
|
||||||
public DuplicateDomainException()
|
public DuplicateDomainException()
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
namespace Bit.Core.Exceptions;
|
namespace Bit.Core.Exceptions;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Exception to throw when a requested feature is not yet enabled/available for the requesting context.
|
/// Exception to throw when a requested feature is not yet enabled/available for the requesting context.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
namespace Bit.Core.Exceptions;
|
namespace Bit.Core.Exceptions;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
public class GatewayException : Exception
|
public class GatewayException : Exception
|
||||||
{
|
{
|
||||||
public GatewayException(string message, Exception innerException = null)
|
public GatewayException(string message, Exception? innerException = null)
|
||||||
: base(message, innerException)
|
: base(message, innerException)
|
||||||
{ }
|
{ }
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
namespace Bit.Core.Exceptions;
|
namespace Bit.Core.Exceptions;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
public class InvalidEmailException : Exception
|
public class InvalidEmailException : Exception
|
||||||
{
|
{
|
||||||
public InvalidEmailException()
|
public InvalidEmailException()
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
namespace Bit.Core.Exceptions;
|
namespace Bit.Core.Exceptions;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
public class InvalidGatewayCustomerIdException : Exception
|
public class InvalidGatewayCustomerIdException : Exception
|
||||||
{
|
{
|
||||||
public InvalidGatewayCustomerIdException()
|
public InvalidGatewayCustomerIdException()
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
namespace Bit.Core.Exceptions;
|
namespace Bit.Core.Exceptions;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
public class NotFoundException : Exception
|
public class NotFoundException : Exception
|
||||||
{
|
{
|
||||||
public NotFoundException() : base()
|
public NotFoundException() : base()
|
||||||
|
@ -10,9 +10,11 @@ using Microsoft.Extensions.Logging;
|
|||||||
|
|
||||||
namespace Bit.Core.HostedServices;
|
namespace Bit.Core.HostedServices;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
public class ApplicationCacheHostedService : IHostedService, IDisposable
|
public class ApplicationCacheHostedService : IHostedService, IDisposable
|
||||||
{
|
{
|
||||||
private readonly InMemoryServiceBusApplicationCacheService _applicationCacheService;
|
private readonly InMemoryServiceBusApplicationCacheService? _applicationCacheService;
|
||||||
private readonly IOrganizationRepository _organizationRepository;
|
private readonly IOrganizationRepository _organizationRepository;
|
||||||
protected readonly ILogger<ApplicationCacheHostedService> _logger;
|
protected readonly ILogger<ApplicationCacheHostedService> _logger;
|
||||||
private readonly ServiceBusClient _serviceBusClient;
|
private readonly ServiceBusClient _serviceBusClient;
|
||||||
@ -20,8 +22,8 @@ public class ApplicationCacheHostedService : IHostedService, IDisposable
|
|||||||
private readonly ServiceBusAdministrationClient _serviceBusAdministrationClient;
|
private readonly ServiceBusAdministrationClient _serviceBusAdministrationClient;
|
||||||
private readonly string _subName;
|
private readonly string _subName;
|
||||||
private readonly string _topicName;
|
private readonly string _topicName;
|
||||||
private CancellationTokenSource _cts;
|
private CancellationTokenSource? _cts;
|
||||||
private Task _executingTask;
|
private Task? _executingTask;
|
||||||
|
|
||||||
|
|
||||||
public ApplicationCacheHostedService(
|
public ApplicationCacheHostedService(
|
||||||
@ -67,13 +69,17 @@ public class ApplicationCacheHostedService : IHostedService, IDisposable
|
|||||||
{
|
{
|
||||||
await _subscriptionReceiver.CloseAsync(cancellationToken);
|
await _subscriptionReceiver.CloseAsync(cancellationToken);
|
||||||
await _serviceBusClient.DisposeAsync();
|
await _serviceBusClient.DisposeAsync();
|
||||||
_cts.Cancel();
|
_cts?.Cancel();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await _serviceBusAdministrationClient.DeleteSubscriptionAsync(_topicName, _subName, cancellationToken);
|
await _serviceBusAdministrationClient.DeleteSubscriptionAsync(_topicName, _subName, cancellationToken);
|
||||||
}
|
}
|
||||||
catch { }
|
catch { }
|
||||||
await _executingTask;
|
|
||||||
|
if (_executingTask != null)
|
||||||
|
{
|
||||||
|
await _executingTask;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void Dispose()
|
public virtual void Dispose()
|
||||||
|
@ -3,6 +3,8 @@ using Microsoft.Extensions.Hosting;
|
|||||||
|
|
||||||
namespace Bit.Core.HostedServices;
|
namespace Bit.Core.HostedServices;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A startup service that will seed the IP rate limiting stores with any values in the
|
/// A startup service that will seed the IP rate limiting stores with any values in the
|
||||||
/// GlobalSettings configuration.
|
/// GlobalSettings configuration.
|
||||||
|
@ -3,6 +3,8 @@ using Quartz;
|
|||||||
|
|
||||||
namespace Bit.Core.Jobs;
|
namespace Bit.Core.Jobs;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
public abstract class BaseJob : IJob
|
public abstract class BaseJob : IJob
|
||||||
{
|
{
|
||||||
protected readonly ILogger _logger;
|
protected readonly ILogger _logger;
|
||||||
|
@ -8,6 +8,8 @@ using Quartz.Impl.Matchers;
|
|||||||
|
|
||||||
namespace Bit.Core.Jobs;
|
namespace Bit.Core.Jobs;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
public abstract class BaseJobsHostedService : IHostedService, IDisposable
|
public abstract class BaseJobsHostedService : IHostedService, IDisposable
|
||||||
{
|
{
|
||||||
private const int MaximumJobRetries = 10;
|
private const int MaximumJobRetries = 10;
|
||||||
@ -16,7 +18,7 @@ public abstract class BaseJobsHostedService : IHostedService, IDisposable
|
|||||||
private readonly ILogger<JobListener> _listenerLogger;
|
private readonly ILogger<JobListener> _listenerLogger;
|
||||||
protected readonly ILogger _logger;
|
protected readonly ILogger _logger;
|
||||||
|
|
||||||
private IScheduler _scheduler;
|
private IScheduler? _scheduler;
|
||||||
protected GlobalSettings _globalSettings;
|
protected GlobalSettings _globalSettings;
|
||||||
|
|
||||||
public BaseJobsHostedService(
|
public BaseJobsHostedService(
|
||||||
@ -31,7 +33,7 @@ public abstract class BaseJobsHostedService : IHostedService, IDisposable
|
|||||||
_globalSettings = globalSettings;
|
_globalSettings = globalSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<Tuple<Type, ITrigger>> Jobs { get; protected set; }
|
public IEnumerable<Tuple<Type, ITrigger>>? Jobs { get; protected set; }
|
||||||
|
|
||||||
public virtual async Task StartAsync(CancellationToken cancellationToken)
|
public virtual async Task StartAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
@ -61,10 +63,19 @@ public abstract class BaseJobsHostedService : IHostedService, IDisposable
|
|||||||
_scheduler.ListenerManager.AddJobListener(new JobListener(_listenerLogger),
|
_scheduler.ListenerManager.AddJobListener(new JobListener(_listenerLogger),
|
||||||
GroupMatcher<JobKey>.AnyGroup());
|
GroupMatcher<JobKey>.AnyGroup());
|
||||||
await _scheduler.Start(cancellationToken);
|
await _scheduler.Start(cancellationToken);
|
||||||
|
|
||||||
|
var jobKeys = new List<JobKey>();
|
||||||
|
var triggerKeys = new List<TriggerKey>();
|
||||||
|
|
||||||
if (Jobs != null)
|
if (Jobs != null)
|
||||||
{
|
{
|
||||||
foreach (var (job, trigger) in Jobs)
|
foreach (var (job, trigger) in Jobs)
|
||||||
{
|
{
|
||||||
|
jobKeys.Add(JobBuilder.Create(job)
|
||||||
|
.WithIdentity(job.FullName!)
|
||||||
|
.Build().Key);
|
||||||
|
triggerKeys.Add(trigger.Key);
|
||||||
|
|
||||||
for (var retry = 0; retry < MaximumJobRetries; retry++)
|
for (var retry = 0; retry < MaximumJobRetries; retry++)
|
||||||
{
|
{
|
||||||
// There's a race condition when starting multiple containers simultaneously, retry until it succeeds..
|
// There's a race condition when starting multiple containers simultaneously, retry until it succeeds..
|
||||||
@ -77,7 +88,7 @@ public abstract class BaseJobsHostedService : IHostedService, IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
var jobDetail = JobBuilder.Create(job)
|
var jobDetail = JobBuilder.Create(job)
|
||||||
.WithIdentity(job.FullName)
|
.WithIdentity(job.FullName!)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
var dupeJ = await _scheduler.GetJobDetail(jobDetail.Key);
|
var dupeJ = await _scheduler.GetJobDetail(jobDetail.Key);
|
||||||
@ -106,13 +117,6 @@ public abstract class BaseJobsHostedService : IHostedService, IDisposable
|
|||||||
|
|
||||||
// Delete old Jobs and Triggers
|
// Delete old Jobs and Triggers
|
||||||
var existingJobKeys = await _scheduler.GetJobKeys(GroupMatcher<JobKey>.AnyGroup());
|
var existingJobKeys = await _scheduler.GetJobKeys(GroupMatcher<JobKey>.AnyGroup());
|
||||||
var jobKeys = Jobs.Select(j =>
|
|
||||||
{
|
|
||||||
var job = j.Item1;
|
|
||||||
return JobBuilder.Create(job)
|
|
||||||
.WithIdentity(job.FullName)
|
|
||||||
.Build().Key;
|
|
||||||
});
|
|
||||||
|
|
||||||
foreach (var key in existingJobKeys)
|
foreach (var key in existingJobKeys)
|
||||||
{
|
{
|
||||||
@ -126,7 +130,6 @@ public abstract class BaseJobsHostedService : IHostedService, IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
var existingTriggerKeys = await _scheduler.GetTriggerKeys(GroupMatcher<TriggerKey>.AnyGroup());
|
var existingTriggerKeys = await _scheduler.GetTriggerKeys(GroupMatcher<TriggerKey>.AnyGroup());
|
||||||
var triggerKeys = Jobs.Select(j => j.Item2.Key);
|
|
||||||
|
|
||||||
foreach (var key in existingTriggerKeys)
|
foreach (var key in existingTriggerKeys)
|
||||||
{
|
{
|
||||||
@ -142,7 +145,10 @@ public abstract class BaseJobsHostedService : IHostedService, IDisposable
|
|||||||
|
|
||||||
public virtual async Task StopAsync(CancellationToken cancellationToken)
|
public virtual async Task StopAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
await _scheduler?.Shutdown(cancellationToken);
|
if (_scheduler is not null)
|
||||||
|
{
|
||||||
|
await _scheduler.Shutdown(cancellationToken);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void Dispose()
|
public virtual void Dispose()
|
||||||
|
@ -4,6 +4,8 @@ using Quartz.Spi;
|
|||||||
|
|
||||||
namespace Bit.Core.Jobs;
|
namespace Bit.Core.Jobs;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
public class JobFactory : IJobFactory
|
public class JobFactory : IJobFactory
|
||||||
{
|
{
|
||||||
private readonly IServiceProvider _container;
|
private readonly IServiceProvider _container;
|
||||||
@ -16,7 +18,7 @@ public class JobFactory : IJobFactory
|
|||||||
public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
|
public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
|
||||||
{
|
{
|
||||||
var scope = _container.CreateScope();
|
var scope = _container.CreateScope();
|
||||||
return scope.ServiceProvider.GetService(bundle.JobDetail.JobType) as IJob;
|
return (scope.ServiceProvider.GetService(bundle.JobDetail.JobType) as IJob)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ReturnJob(IJob job)
|
public void ReturnJob(IJob job)
|
||||||
|
@ -3,6 +3,8 @@ using Quartz;
|
|||||||
|
|
||||||
namespace Bit.Core.Jobs;
|
namespace Bit.Core.Jobs;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
public class JobListener : IJobListener
|
public class JobListener : IJobListener
|
||||||
{
|
{
|
||||||
private readonly ILogger<JobListener> _logger;
|
private readonly ILogger<JobListener> _logger;
|
||||||
@ -28,7 +30,7 @@ public class JobListener : IJobListener
|
|||||||
return Task.FromResult(0);
|
return Task.FromResult(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task JobWasExecuted(IJobExecutionContext context, JobExecutionException jobException,
|
public Task JobWasExecuted(IJobExecutionContext context, JobExecutionException? jobException,
|
||||||
CancellationToken cancellationToken = default(CancellationToken))
|
CancellationToken cancellationToken = default(CancellationToken))
|
||||||
{
|
{
|
||||||
_logger.LogInformation(Constants.BypassFiltersEventId, null, "Finished job {0} at {1}.",
|
_logger.LogInformation(Constants.BypassFiltersEventId, null, "Finished job {0} at {1}.",
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
namespace Bit.Core.NotificationHub;
|
namespace Bit.Core.NotificationHub;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
public interface INotificationHubProxy
|
public interface INotificationHubProxy
|
||||||
{
|
{
|
||||||
Task<(INotificationHubClient Client, NotificationOutcome Outcome)[]> SendTemplateNotificationAsync(IDictionary<string, string> properties, string tagExpression);
|
Task<(INotificationHubClient Client, NotificationOutcome Outcome)[]> SendTemplateNotificationAsync(IDictionary<string, string> properties, string tagExpression);
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
namespace Bit.Core.NotificationHub;
|
namespace Bit.Core.NotificationHub;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
public interface INotificationHubPool
|
public interface INotificationHubPool
|
||||||
{
|
{
|
||||||
NotificationHubConnection ConnectionFor(Guid comb);
|
NotificationHubConnection ConnectionFor(Guid comb);
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
namespace Bit.Core.NotificationHub;
|
namespace Bit.Core.NotificationHub;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
public class NotificationHubClientProxy : INotificationHubProxy
|
public class NotificationHubClientProxy : INotificationHubProxy
|
||||||
{
|
{
|
||||||
private readonly IEnumerable<INotificationHubClient> _clients;
|
private readonly IEnumerable<INotificationHubClient> _clients;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System.Security.Cryptography;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Web;
|
using System.Web;
|
||||||
using Bit.Core.Settings;
|
using Bit.Core.Settings;
|
||||||
@ -7,21 +8,23 @@ using Microsoft.Azure.NotificationHubs;
|
|||||||
|
|
||||||
namespace Bit.Core.NotificationHub;
|
namespace Bit.Core.NotificationHub;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
public class NotificationHubConnection
|
public class NotificationHubConnection
|
||||||
{
|
{
|
||||||
public string HubName { get; init; }
|
public string? HubName { get; init; }
|
||||||
public string ConnectionString { get; init; }
|
public string? ConnectionString { get; init; }
|
||||||
private Lazy<NotificationHubConnectionStringBuilder> _parsedConnectionString;
|
private Lazy<NotificationHubConnectionStringBuilder> _parsedConnectionString;
|
||||||
public Uri Endpoint => _parsedConnectionString.Value.Endpoint;
|
public Uri Endpoint => _parsedConnectionString.Value.Endpoint;
|
||||||
private string SasKey => _parsedConnectionString.Value.SharedAccessKey;
|
private string SasKey => _parsedConnectionString.Value.SharedAccessKey;
|
||||||
private string SasKeyName => _parsedConnectionString.Value.SharedAccessKeyName;
|
private string SasKeyName => _parsedConnectionString.Value.SharedAccessKeyName;
|
||||||
public bool EnableSendTracing { get; init; }
|
public bool EnableSendTracing { get; init; }
|
||||||
private NotificationHubClient _hubClient;
|
private NotificationHubClient? _hubClient;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the NotificationHubClient for this connection.
|
/// Gets the NotificationHubClient for this connection.
|
||||||
///
|
///
|
||||||
/// If the client is null, it will be initialized.
|
/// If the client is null, it will be initialized.
|
||||||
///
|
///
|
||||||
/// <throws>Exception</throws> if the connection is invalid.
|
/// <throws>Exception</throws> if the connection is invalid.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public NotificationHubClient HubClient
|
public NotificationHubClient HubClient
|
||||||
@ -45,13 +48,13 @@ public class NotificationHubConnection
|
|||||||
}
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the start date for registration.
|
/// Gets the start date for registration.
|
||||||
///
|
///
|
||||||
/// If null, registration is always disabled.
|
/// If null, registration is always disabled.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public DateTime? RegistrationStartDate { get; init; }
|
public DateTime? RegistrationStartDate { get; init; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the end date for registration.
|
/// Gets the end date for registration.
|
||||||
///
|
///
|
||||||
/// If null, registration has no end date.
|
/// If null, registration has no end date.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public DateTime? RegistrationEndDate { get; init; }
|
public DateTime? RegistrationEndDate { get; init; }
|
||||||
@ -155,9 +158,10 @@ public class NotificationHubConnection
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MemberNotNull(nameof(_hubClient))]
|
||||||
private NotificationHubConnection Init()
|
private NotificationHubConnection Init()
|
||||||
{
|
{
|
||||||
HubClient = NotificationHubClient.CreateClientFromConnectionString(ConnectionString, HubName, EnableSendTracing);
|
_hubClient = NotificationHubClient.CreateClientFromConnectionString(ConnectionString, HubName, EnableSendTracing);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,8 @@ using Microsoft.Extensions.Logging;
|
|||||||
|
|
||||||
namespace Bit.Core.NotificationHub;
|
namespace Bit.Core.NotificationHub;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
public class NotificationHubPool : INotificationHubPool
|
public class NotificationHubPool : INotificationHubPool
|
||||||
{
|
{
|
||||||
private List<NotificationHubConnection> _connections { get; }
|
private List<NotificationHubConnection> _connections { get; }
|
||||||
|
@ -19,6 +19,8 @@ using Notification = Bit.Core.NotificationCenter.Entities.Notification;
|
|||||||
|
|
||||||
namespace Bit.Core.NotificationHub;
|
namespace Bit.Core.NotificationHub;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sends mobile push notifications to the Azure Notification Hub.
|
/// Sends mobile push notifications to the Azure Notification Hub.
|
||||||
/// Used by Cloud-Hosted environments.
|
/// Used by Cloud-Hosted environments.
|
||||||
|
@ -13,6 +13,8 @@ using Microsoft.Extensions.Logging;
|
|||||||
|
|
||||||
namespace Bit.Core.NotificationHub;
|
namespace Bit.Core.NotificationHub;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
public class NotificationHubPushRegistrationService : IPushRegistrationService
|
public class NotificationHubPushRegistrationService : IPushRegistrationService
|
||||||
{
|
{
|
||||||
private static readonly JsonSerializerOptions webPushSerializationOptions = new()
|
private static readonly JsonSerializerOptions webPushSerializationOptions = new()
|
||||||
@ -37,7 +39,7 @@ public class NotificationHubPushRegistrationService : IPushRegistrationService
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async Task CreateOrUpdateRegistrationAsync(PushRegistrationData data, string deviceId, string userId,
|
public async Task CreateOrUpdateRegistrationAsync(PushRegistrationData data, string deviceId, string userId,
|
||||||
string identifier, DeviceType type, IEnumerable<string> organizationIds, Guid installationId)
|
string? identifier, DeviceType type, IEnumerable<string> organizationIds, Guid installationId)
|
||||||
{
|
{
|
||||||
var orgIds = organizationIds.ToList();
|
var orgIds = organizationIds.ToList();
|
||||||
var clientType = DeviceTypes.ToClientType(type);
|
var clientType = DeviceTypes.ToClientType(type);
|
||||||
@ -79,7 +81,7 @@ public class NotificationHubPushRegistrationService : IPushRegistrationService
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async Task CreateOrUpdateMobileRegistrationAsync(Installation installation, string userId,
|
private async Task CreateOrUpdateMobileRegistrationAsync(Installation installation, string userId,
|
||||||
string identifier, ClientType clientType, List<string> organizationIds, DeviceType type, Guid installationId)
|
string? identifier, ClientType clientType, List<string> organizationIds, DeviceType type, Guid installationId)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(installation.PushChannel))
|
if (string.IsNullOrWhiteSpace(installation.PushChannel))
|
||||||
{
|
{
|
||||||
@ -137,7 +139,7 @@ public class NotificationHubPushRegistrationService : IPushRegistrationService
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async Task CreateOrUpdateWebRegistrationAsync(string endpoint, string p256dh, string auth, Installation installation, string userId,
|
private async Task CreateOrUpdateWebRegistrationAsync(string endpoint, string p256dh, string auth, Installation installation, string userId,
|
||||||
string identifier, ClientType clientType, List<string> organizationIds, Guid installationId)
|
string? identifier, ClientType clientType, List<string> organizationIds, Guid installationId)
|
||||||
{
|
{
|
||||||
// The Azure SDK is currently lacking support for web push registrations.
|
// The Azure SDK is currently lacking support for web push registrations.
|
||||||
// We need to use the REST API directly.
|
// We need to use the REST API directly.
|
||||||
@ -187,7 +189,7 @@ public class NotificationHubPushRegistrationService : IPushRegistrationService
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static KeyValuePair<string, InstallationTemplate> BuildInstallationTemplate(string templateId, [StringSyntax(StringSyntaxAttribute.Json)] string templateBody,
|
private static KeyValuePair<string, InstallationTemplate> BuildInstallationTemplate(string templateId, [StringSyntax(StringSyntaxAttribute.Json)] string templateBody,
|
||||||
string userId, string identifier, ClientType clientType, List<string> organizationIds, Guid installationId)
|
string userId, string? identifier, ClientType clientType, List<string> organizationIds, Guid installationId)
|
||||||
{
|
{
|
||||||
var fullTemplateId = $"template:{templateId}";
|
var fullTemplateId = $"template:{templateId}";
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
namespace Bit.Core.NotificationHub;
|
namespace Bit.Core.NotificationHub;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
public record struct WebPushRegistrationData
|
public record struct WebPushRegistrationData
|
||||||
{
|
{
|
||||||
public string Endpoint { get; init; }
|
public string Endpoint { get; init; }
|
||||||
@ -9,9 +11,9 @@ public record struct WebPushRegistrationData
|
|||||||
|
|
||||||
public record class PushRegistrationData
|
public record class PushRegistrationData
|
||||||
{
|
{
|
||||||
public string Token { get; set; }
|
public string? Token { get; set; }
|
||||||
public WebPushRegistrationData? WebPush { get; set; }
|
public WebPushRegistrationData? WebPush { get; set; }
|
||||||
public PushRegistrationData(string token)
|
public PushRegistrationData(string? token)
|
||||||
{
|
{
|
||||||
Token = token;
|
Token = token;
|
||||||
}
|
}
|
||||||
|
@ -175,7 +175,7 @@ public class AccountsKeyManagementControllerTests
|
|||||||
}
|
}
|
||||||
catch (BadRequestException ex)
|
catch (BadRequestException ex)
|
||||||
{
|
{
|
||||||
Assert.NotEmpty(ex.ModelState.Values);
|
Assert.NotEmpty(ex.ModelState!.Values);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -210,7 +210,7 @@ public class AccountsKeyManagementControllerTests
|
|||||||
var badRequestException =
|
var badRequestException =
|
||||||
await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.PostSetKeyConnectorKeyAsync(data));
|
await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.PostSetKeyConnectorKeyAsync(data));
|
||||||
|
|
||||||
Assert.Equal(1, badRequestException.ModelState.ErrorCount);
|
Assert.Equal(1, badRequestException.ModelState!.ErrorCount);
|
||||||
Assert.Equal("set key connector key error", badRequestException.ModelState.Root.Errors[0].ErrorMessage);
|
Assert.Equal("set key connector key error", badRequestException.ModelState.Root.Errors[0].ErrorMessage);
|
||||||
await sutProvider.GetDependency<IUserService>().Received(1)
|
await sutProvider.GetDependency<IUserService>().Received(1)
|
||||||
.SetKeyConnectorKeyAsync(Arg.Do<User>(user =>
|
.SetKeyConnectorKeyAsync(Arg.Do<User>(user =>
|
||||||
@ -284,7 +284,7 @@ public class AccountsKeyManagementControllerTests
|
|||||||
var badRequestException =
|
var badRequestException =
|
||||||
await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.PostConvertToKeyConnectorAsync());
|
await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.PostConvertToKeyConnectorAsync());
|
||||||
|
|
||||||
Assert.Equal(1, badRequestException.ModelState.ErrorCount);
|
Assert.Equal(1, badRequestException.ModelState!.ErrorCount);
|
||||||
Assert.Equal("convert to key connector error", badRequestException.ModelState.Root.Errors[0].ErrorMessage);
|
Assert.Equal("convert to key connector error", badRequestException.ModelState.Root.Errors[0].ErrorMessage);
|
||||||
await sutProvider.GetDependency<IUserService>().Received(1)
|
await sutProvider.GetDependency<IUserService>().Received(1)
|
||||||
.ConvertToKeyConnectorAsync(Arg.Is(expectedUser));
|
.ConvertToKeyConnectorAsync(Arg.Is(expectedUser));
|
||||||
|
Loading…
x
Reference in New Issue
Block a user