1
0
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:
Conner Turnbull 2025-06-06 09:57:31 -04:00
commit 71cb4a4ea8
No known key found for this signature in database
27 changed files with 111 additions and 40 deletions

View File

@ -31,6 +31,7 @@ public record PlanAdapter : Plan
HasScim = HasFeature("scim");
HasResetPassword = HasFeature("resetPassword");
UsersGetPremium = HasFeature("usersGetPremium");
HasCustomPermissions = HasFeature("customPermissions");
UpgradeSortOrder = plan.AdditionalData.TryGetValue("upgradeSortOrder", out var upgradeSortOrder)
? int.Parse(upgradeSortOrder)
: 0;
@ -141,6 +142,7 @@ public record PlanAdapter : Plan
var stripeSeatPlanId = GetStripeSeatPlanId(seats);
var hasAdditionalSeatsOption = seats.IsScalable;
var seatPrice = GetSeatPrice(seats);
var baseSeats = GetBaseSeats(seats);
var maxSeats = GetMaxSeats(seats);
var allowSeatAutoscale = seats.IsScalable;
var maxProjects = plan.AdditionalData.TryGetValue("secretsManager.maxProjects", out var value) ? short.Parse(value) : 0;
@ -156,6 +158,7 @@ public record PlanAdapter : Plan
StripeSeatPlanId = stripeSeatPlanId,
HasAdditionalSeatsOption = hasAdditionalSeatsOption,
SeatPrice = seatPrice,
BaseSeats = baseSeats,
MaxSeats = maxSeats,
AllowSeatAutoscale = allowSeatAutoscale,
MaxProjects = maxProjects
@ -168,8 +171,16 @@ public record PlanAdapter : Plan
private static decimal GetBasePrice(PurchasableDTO purchasable)
=> 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)
=> purchasable.FromPackaged(x => x.Quantity);
=> purchasable.Match(
free => free.Quantity,
packaged => packaged.Quantity,
scalable => scalable.Provided);
private static short GetBaseServiceAccount(FreeOrScalableDTO freeOrScalable)
=> freeOrScalable.Match(

View File

@ -3,6 +3,8 @@ using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace Bit.Core.Exceptions;
#nullable enable
public class BadRequestException : Exception
{
public BadRequestException() : base()
@ -41,5 +43,5 @@ public class BadRequestException : Exception
}
}
public ModelStateDictionary ModelState { get; set; }
public ModelStateDictionary? ModelState { get; set; }
}

View File

@ -1,5 +1,7 @@
namespace Bit.Core.Exceptions;
#nullable enable
public class ConflictException : Exception
{
public ConflictException() : base("Conflict.") { }

View File

@ -1,5 +1,7 @@
namespace Bit.Core.Exceptions;
#nullable enable
public class DnsQueryException : Exception
{
public DnsQueryException(string message)

View File

@ -1,5 +1,7 @@
namespace Bit.Core.Exceptions;
#nullable enable
public class DomainClaimedException : Exception
{
public DomainClaimedException()

View File

@ -1,5 +1,7 @@
namespace Bit.Core.Exceptions;
#nullable enable
public class DomainVerifiedException : Exception
{
public DomainVerifiedException()

View File

@ -1,5 +1,7 @@
namespace Bit.Core.Exceptions;
#nullable enable
public class DuplicateDomainException : Exception
{
public DuplicateDomainException()

View File

@ -1,5 +1,7 @@
namespace Bit.Core.Exceptions;
#nullable enable
/// <summary>
/// Exception to throw when a requested feature is not yet enabled/available for the requesting context.
/// </summary>

View File

@ -1,8 +1,10 @@
namespace Bit.Core.Exceptions;
#nullable enable
public class GatewayException : Exception
{
public GatewayException(string message, Exception innerException = null)
public GatewayException(string message, Exception? innerException = null)
: base(message, innerException)
{ }
}

View File

@ -1,5 +1,7 @@
namespace Bit.Core.Exceptions;
#nullable enable
public class InvalidEmailException : Exception
{
public InvalidEmailException()

View File

@ -1,5 +1,7 @@
namespace Bit.Core.Exceptions;
#nullable enable
public class InvalidGatewayCustomerIdException : Exception
{
public InvalidGatewayCustomerIdException()

View File

@ -1,5 +1,7 @@
namespace Bit.Core.Exceptions;
#nullable enable
public class NotFoundException : Exception
{
public NotFoundException() : base()

View File

@ -10,9 +10,11 @@ using Microsoft.Extensions.Logging;
namespace Bit.Core.HostedServices;
#nullable enable
public class ApplicationCacheHostedService : IHostedService, IDisposable
{
private readonly InMemoryServiceBusApplicationCacheService _applicationCacheService;
private readonly InMemoryServiceBusApplicationCacheService? _applicationCacheService;
private readonly IOrganizationRepository _organizationRepository;
protected readonly ILogger<ApplicationCacheHostedService> _logger;
private readonly ServiceBusClient _serviceBusClient;
@ -20,8 +22,8 @@ public class ApplicationCacheHostedService : IHostedService, IDisposable
private readonly ServiceBusAdministrationClient _serviceBusAdministrationClient;
private readonly string _subName;
private readonly string _topicName;
private CancellationTokenSource _cts;
private Task _executingTask;
private CancellationTokenSource? _cts;
private Task? _executingTask;
public ApplicationCacheHostedService(
@ -67,13 +69,17 @@ public class ApplicationCacheHostedService : IHostedService, IDisposable
{
await _subscriptionReceiver.CloseAsync(cancellationToken);
await _serviceBusClient.DisposeAsync();
_cts.Cancel();
_cts?.Cancel();
try
{
await _serviceBusAdministrationClient.DeleteSubscriptionAsync(_topicName, _subName, cancellationToken);
}
catch { }
await _executingTask;
if (_executingTask != null)
{
await _executingTask;
}
}
public virtual void Dispose()

View File

@ -3,6 +3,8 @@ using Microsoft.Extensions.Hosting;
namespace Bit.Core.HostedServices;
#nullable enable
/// <summary>
/// A startup service that will seed the IP rate limiting stores with any values in the
/// GlobalSettings configuration.

View File

@ -3,6 +3,8 @@ using Quartz;
namespace Bit.Core.Jobs;
#nullable enable
public abstract class BaseJob : IJob
{
protected readonly ILogger _logger;

View File

@ -8,6 +8,8 @@ using Quartz.Impl.Matchers;
namespace Bit.Core.Jobs;
#nullable enable
public abstract class BaseJobsHostedService : IHostedService, IDisposable
{
private const int MaximumJobRetries = 10;
@ -16,7 +18,7 @@ public abstract class BaseJobsHostedService : IHostedService, IDisposable
private readonly ILogger<JobListener> _listenerLogger;
protected readonly ILogger _logger;
private IScheduler _scheduler;
private IScheduler? _scheduler;
protected GlobalSettings _globalSettings;
public BaseJobsHostedService(
@ -31,7 +33,7 @@ public abstract class BaseJobsHostedService : IHostedService, IDisposable
_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)
{
@ -61,10 +63,19 @@ public abstract class BaseJobsHostedService : IHostedService, IDisposable
_scheduler.ListenerManager.AddJobListener(new JobListener(_listenerLogger),
GroupMatcher<JobKey>.AnyGroup());
await _scheduler.Start(cancellationToken);
var jobKeys = new List<JobKey>();
var triggerKeys = new List<TriggerKey>();
if (Jobs != null)
{
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++)
{
// 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)
.WithIdentity(job.FullName)
.WithIdentity(job.FullName!)
.Build();
var dupeJ = await _scheduler.GetJobDetail(jobDetail.Key);
@ -106,13 +117,6 @@ public abstract class BaseJobsHostedService : IHostedService, IDisposable
// Delete old Jobs and Triggers
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)
{
@ -126,7 +130,6 @@ public abstract class BaseJobsHostedService : IHostedService, IDisposable
}
var existingTriggerKeys = await _scheduler.GetTriggerKeys(GroupMatcher<TriggerKey>.AnyGroup());
var triggerKeys = Jobs.Select(j => j.Item2.Key);
foreach (var key in existingTriggerKeys)
{
@ -142,7 +145,10 @@ public abstract class BaseJobsHostedService : IHostedService, IDisposable
public virtual async Task StopAsync(CancellationToken cancellationToken)
{
await _scheduler?.Shutdown(cancellationToken);
if (_scheduler is not null)
{
await _scheduler.Shutdown(cancellationToken);
}
}
public virtual void Dispose()

View File

@ -4,6 +4,8 @@ using Quartz.Spi;
namespace Bit.Core.Jobs;
#nullable enable
public class JobFactory : IJobFactory
{
private readonly IServiceProvider _container;
@ -16,7 +18,7 @@ public class JobFactory : IJobFactory
public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
{
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)

View File

@ -3,6 +3,8 @@ using Quartz;
namespace Bit.Core.Jobs;
#nullable enable
public class JobListener : IJobListener
{
private readonly ILogger<JobListener> _logger;
@ -28,7 +30,7 @@ public class JobListener : IJobListener
return Task.FromResult(0);
}
public Task JobWasExecuted(IJobExecutionContext context, JobExecutionException jobException,
public Task JobWasExecuted(IJobExecutionContext context, JobExecutionException? jobException,
CancellationToken cancellationToken = default(CancellationToken))
{
_logger.LogInformation(Constants.BypassFiltersEventId, null, "Finished job {0} at {1}.",

View File

@ -2,6 +2,8 @@
namespace Bit.Core.NotificationHub;
#nullable enable
public interface INotificationHubProxy
{
Task<(INotificationHubClient Client, NotificationOutcome Outcome)[]> SendTemplateNotificationAsync(IDictionary<string, string> properties, string tagExpression);

View File

@ -2,6 +2,8 @@
namespace Bit.Core.NotificationHub;
#nullable enable
public interface INotificationHubPool
{
NotificationHubConnection ConnectionFor(Guid comb);

View File

@ -2,6 +2,8 @@
namespace Bit.Core.NotificationHub;
#nullable enable
public class NotificationHubClientProxy : INotificationHubProxy
{
private readonly IEnumerable<INotificationHubClient> _clients;

View File

@ -1,4 +1,5 @@
using System.Security.Cryptography;
using System.Diagnostics.CodeAnalysis;
using System.Security.Cryptography;
using System.Text;
using System.Web;
using Bit.Core.Settings;
@ -7,16 +8,18 @@ using Microsoft.Azure.NotificationHubs;
namespace Bit.Core.NotificationHub;
#nullable enable
public class NotificationHubConnection
{
public string HubName { get; init; }
public string ConnectionString { get; init; }
public string? HubName { get; init; }
public string? ConnectionString { get; init; }
private Lazy<NotificationHubConnectionStringBuilder> _parsedConnectionString;
public Uri Endpoint => _parsedConnectionString.Value.Endpoint;
private string SasKey => _parsedConnectionString.Value.SharedAccessKey;
private string SasKeyName => _parsedConnectionString.Value.SharedAccessKeyName;
public bool EnableSendTracing { get; init; }
private NotificationHubClient _hubClient;
private NotificationHubClient? _hubClient;
/// <summary>
/// Gets the NotificationHubClient for this connection.
///
@ -155,9 +158,10 @@ public class NotificationHubConnection
};
}
[MemberNotNull(nameof(_hubClient))]
private NotificationHubConnection Init()
{
HubClient = NotificationHubClient.CreateClientFromConnectionString(ConnectionString, HubName, EnableSendTracing);
_hubClient = NotificationHubClient.CreateClientFromConnectionString(ConnectionString, HubName, EnableSendTracing);
return this;
}

View File

@ -5,6 +5,8 @@ using Microsoft.Extensions.Logging;
namespace Bit.Core.NotificationHub;
#nullable enable
public class NotificationHubPool : INotificationHubPool
{
private List<NotificationHubConnection> _connections { get; }

View File

@ -19,6 +19,8 @@ using Notification = Bit.Core.NotificationCenter.Entities.Notification;
namespace Bit.Core.NotificationHub;
#nullable enable
/// <summary>
/// Sends mobile push notifications to the Azure Notification Hub.
/// Used by Cloud-Hosted environments.

View File

@ -13,6 +13,8 @@ using Microsoft.Extensions.Logging;
namespace Bit.Core.NotificationHub;
#nullable enable
public class NotificationHubPushRegistrationService : IPushRegistrationService
{
private static readonly JsonSerializerOptions webPushSerializationOptions = new()
@ -37,7 +39,7 @@ public class NotificationHubPushRegistrationService : IPushRegistrationService
}
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 clientType = DeviceTypes.ToClientType(type);
@ -79,7 +81,7 @@ public class NotificationHubPushRegistrationService : IPushRegistrationService
}
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))
{
@ -137,7 +139,7 @@ public class NotificationHubPushRegistrationService : IPushRegistrationService
}
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.
// 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,
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}";

View File

@ -1,5 +1,7 @@
namespace Bit.Core.NotificationHub;
#nullable enable
public record struct WebPushRegistrationData
{
public string Endpoint { get; init; }
@ -9,9 +11,9 @@ public record struct WebPushRegistrationData
public record class PushRegistrationData
{
public string Token { get; set; }
public string? Token { get; set; }
public WebPushRegistrationData? WebPush { get; set; }
public PushRegistrationData(string token)
public PushRegistrationData(string? token)
{
Token = token;
}

View File

@ -175,7 +175,7 @@ public class AccountsKeyManagementControllerTests
}
catch (BadRequestException ex)
{
Assert.NotEmpty(ex.ModelState.Values);
Assert.NotEmpty(ex.ModelState!.Values);
}
}
@ -210,7 +210,7 @@ public class AccountsKeyManagementControllerTests
var badRequestException =
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);
await sutProvider.GetDependency<IUserService>().Received(1)
.SetKeyConnectorKeyAsync(Arg.Do<User>(user =>
@ -284,7 +284,7 @@ public class AccountsKeyManagementControllerTests
var badRequestException =
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);
await sutProvider.GetDependency<IUserService>().Received(1)
.ConvertToKeyConnectorAsync(Arg.Is(expectedUser));