diff --git a/All.sln b/All.sln index dfd6409..a0d69df 100644 --- a/All.sln +++ b/All.sln @@ -7,7 +7,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{17CDA34C-429 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "IdentityServer", "IdentityServer", "{D264CAEC-2137-46D8-B637-FCE7CBE3B390}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Messaging.AzureServiceBus", "src\IdentityServer\Messaging.AzureServiceBus\Messaging.AzureServiceBus.csproj", "{C8F188CA-2671-4364-B53C-43832627C0DA}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Messaging.Azure", "src\IdentityServer\Messaging.Azure\Messaging.Azure.csproj", "{C8F188CA-2671-4364-B53C-43832627C0DA}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Messaging.RabbitMQ", "src\IdentityServer\Messaging.RabbitMQ\Messaging.RabbitMQ.csproj", "{9C1A424A-64A5-4BAA-BCE5-529F2148497F}" EndProject diff --git a/Directory.Packages.props b/Directory.Packages.props index 3432ee5..48655fe 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -11,14 +11,15 @@ + - - - + + + diff --git a/src/IdentityServer/Messaging.Azure/EventHubSender.cs b/src/IdentityServer/Messaging.Azure/EventHubSender.cs new file mode 100644 index 0000000..32e00f9 --- /dev/null +++ b/src/IdentityServer/Messaging.Azure/EventHubSender.cs @@ -0,0 +1,67 @@ +using System; +using System.Threading; +using System.Threading.Channels; +using System.Threading.Tasks; +using IdOps.IdentityServer.Abstractions; +using IdOps.Messages; +using MassTransit; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace IdOps.IdentityServer.Azure; + +public sealed class EventHubSender : BackgroundService, IEventSenderWorker +{ + private readonly IServiceProvider _serviceProvider; + private readonly ChannelReader _channelReader; + + public EventHubSender( + IServiceProvider serviceProvider, + ChannelReader channelReader) + { + _serviceProvider = serviceProvider; + _channelReader = channelReader; + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + await Task.Yield(); + await using AsyncServiceScope scope = _serviceProvider.CreateAsyncScope(); + IEventHubProducerProvider provider = + scope.ServiceProvider.GetRequiredService(); + + IEventHubProducer producer = await provider.GetProducer("identity-events"); + + // we reuse the buffer to avoid allocations + var buffer = new IdentityEventMessage[50]; + try + { + while (await _channelReader.WaitToReadAsync(stoppingToken)) + { + // we read as many messages as we can + for (var i = 0; i < buffer.Length; i++) + { + if (!_channelReader.TryRead(out IdentityEventMessage? entity)) + { + break; + } + + buffer[i] = entity; + } + + // create a batch of messages to send + var batch = new IdentityEventMessage[buffer.Length]; + Array.Copy(buffer, batch, buffer.Length); + buffer.AsSpan().Clear(); + + IdOpsMeters.RecordSenderBatchSize(batch.Length); + + await producer.Produce(batch, stoppingToken); + } + } + catch + { + // ignored + } + } +} diff --git a/src/IdentityServer/Messaging.Azure/Extensions/AzureServiceBusIdOpsBuilderExtensions.cs b/src/IdentityServer/Messaging.Azure/Extensions/AzureServiceBusIdOpsBuilderExtensions.cs new file mode 100644 index 0000000..118f12e --- /dev/null +++ b/src/IdentityServer/Messaging.Azure/Extensions/AzureServiceBusIdOpsBuilderExtensions.cs @@ -0,0 +1,101 @@ +using System; +using Azure.Identity; +using IdOps.IdentityServer.Abstractions; +using MassTransit; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using static IdOps.IdentityServer.Wellknown.ConfigSections; + +namespace IdOps.IdentityServer.Azure +{ + public static class AzureServiceBusIdOpsBuilderExtensions + { + public static IIdOpsIdentityServerBuilder UseAzure(this BusBuilder builder) + { + AzureOptions? options = builder + .IdOpsBuilder.Configuration?.GetSection($"{Messaging}:Azure") + .Get(); + + if (options == null) + { + throw new ApplicationException( + "Could not get AzureOptions configuration from " + + $"{Messaging}:Azure." + + "Please check you configuration"); + } + + return builder.UseAzure(options); + } + + private static IIdOpsIdentityServerBuilder UseAzure( + this BusBuilder builder, + AzureOptions options) + { + if (options.EventHub is not null) + { + builder.IdOpsBuilder.Services.AddSingleton(); + } + + builder.IdOpsBuilder.Services.AddMassTransit(s => + { + builder.BusSetup?.Invoke(s); + + if (options.ServiceBus is { }) + { + s.RegisterServiceBus(options.ServiceBus, builder); + } + + if (options.EventHub is { } eventHub) + { + s.RegisterEventHub(eventHub); + } + }); + + return builder.IdOpsBuilder; + } + + private static void RegisterEventHub( + this IBusRegistrationConfigurator configurator, + EventHubOptions eventHub) + { + configurator.AddRider(x => + x.UsingEventHub((_, k) => + { + if (eventHub.Namespace is { } @namespace) + { + k.Host(@namespace, new DefaultAzureCredential()); + } + else if (eventHub.ConnectionString is not null) + { + k.Host(eventHub.ConnectionString); + } + else + { + throw new ApplicationException( + "EventHub configuration is missing. Please check your settings."); + } + }) + ); + } + + private static void RegisterServiceBus( + this IBusRegistrationConfigurator configurator, + AzureServiceBusOptions options, + BusBuilder builder) + { + configurator.UsingAzureServiceBus((provider, cfg) => + { + var serverGroup = builder.IdOpsBuilder.Options!.ServerGroup.ToLower(); + var environmentName = builder.IdOpsBuilder.Options!.EnvironmentName.ToLower(); + cfg.Host(options.ConnectionString); + cfg.ReceiveEndpoint( + $"id-{serverGroup}-{environmentName}", + e => + { + e.ConfigureConsumers(provider); + e.PrefetchCount = options.PrefetchCount; + }); + }); + } + } +} diff --git a/src/IdentityServer/Messaging.AzureServiceBus/Messaging.AzureServiceBus.csproj b/src/IdentityServer/Messaging.Azure/Messaging.Azure.csproj similarity index 91% rename from src/IdentityServer/Messaging.AzureServiceBus/Messaging.AzureServiceBus.csproj rename to src/IdentityServer/Messaging.Azure/Messaging.Azure.csproj index 37efdb6..4819acf 100644 --- a/src/IdentityServer/Messaging.AzureServiceBus/Messaging.AzureServiceBus.csproj +++ b/src/IdentityServer/Messaging.Azure/Messaging.Azure.csproj @@ -10,6 +10,7 @@ + diff --git a/src/IdentityServer/Messaging.Azure/Options/AzureOptions.cs b/src/IdentityServer/Messaging.Azure/Options/AzureOptions.cs new file mode 100644 index 0000000..40658c5 --- /dev/null +++ b/src/IdentityServer/Messaging.Azure/Options/AzureOptions.cs @@ -0,0 +1,8 @@ +namespace IdOps.IdentityServer.Azure; + +public sealed class AzureOptions +{ + public AzureServiceBusOptions? ServiceBus { get; set; } = default!; + public EventHubOptions? EventHub { get; set; } = default!; +} + diff --git a/src/IdentityServer/Messaging.Azure/Options/AzureServiceBusOptions.cs b/src/IdentityServer/Messaging.Azure/Options/AzureServiceBusOptions.cs new file mode 100644 index 0000000..4d0eb0d --- /dev/null +++ b/src/IdentityServer/Messaging.Azure/Options/AzureServiceBusOptions.cs @@ -0,0 +1,9 @@ +namespace IdOps.IdentityServer.Azure; + +public class AzureServiceBusOptions +{ + public string ConnectionString { get; set; } = default!; + + public int PrefetchCount { get; set; } = 10; +} + diff --git a/src/IdentityServer/Messaging.Azure/Options/EventHubOptions.cs b/src/IdentityServer/Messaging.Azure/Options/EventHubOptions.cs new file mode 100644 index 0000000..c9a0df3 --- /dev/null +++ b/src/IdentityServer/Messaging.Azure/Options/EventHubOptions.cs @@ -0,0 +1,8 @@ +namespace IdOps.IdentityServer.Azure; + +public class EventHubOptions +{ + public string? ConnectionString { get; set; } + + public string? Namespace { get; set; } +} diff --git a/src/IdentityServer/Messaging.AzureServiceBus/AzureServiceBusIdOpsBuilderExtensions.cs b/src/IdentityServer/Messaging.AzureServiceBus/AzureServiceBusIdOpsBuilderExtensions.cs deleted file mode 100644 index 95884d6..0000000 --- a/src/IdentityServer/Messaging.AzureServiceBus/AzureServiceBusIdOpsBuilderExtensions.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using MassTransit; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; - -namespace IdOps.IdentityServer.AzureServiceBus -{ - public static class AzureServiceBusIdOpsBuilderExtensions - { - public static IIdOpsIdentityServerBuilder UseAzureServiceBus(this BusBuilder builder) - { - AzureServiceBusOptions? options = builder.IdOpsBuilder.Configuration? - .GetSection($"{Wellknown.ConfigSections.Messaging}:AzureServiceBus") - .Get(); - - if (options == null) - { - throw new ApplicationException( - "Could not get AzureServiceBus configuration from " + - $"{Wellknown.ConfigSections.Messaging}:AzureServiceBus." + - "Please check you configuration"); - } - - return builder.UseAzureServiceBus(options); - } - - public static IIdOpsIdentityServerBuilder UseAzureServiceBus( - this BusBuilder builder, - AzureServiceBusOptions options) - { - builder.IdOpsBuilder.Services.AddMassTransit(s => - { - builder.BusSetup?.Invoke(s); - - s.UsingAzureServiceBus((provider, cfg) => - { - cfg.Host(options.ConnectionString); - cfg.ReceiveEndpoint($"id-" + - $"{builder.IdOpsBuilder.Options!.ServerGroup.ToLower()}-" + - $"{builder.IdOpsBuilder.Options!.EnvironmentName.ToLower()}", - e => - { - e.ConfigureConsumers(provider); - e.PrefetchCount = options.PrefetchCount; - }); - }); - }); - - return builder.IdOpsBuilder; - } - - public static IIdOpsIdentityServerBuilder UseAzureServiceBus( - this BusBuilder builder, - Action setupAction) - { - var options = new AzureServiceBusOptions(); - setupAction.Invoke(options); - - return builder.UseAzureServiceBus(options); - } - } -} diff --git a/src/IdentityServer/Messaging.AzureServiceBus/AzureServiceBusOptions.cs b/src/IdentityServer/Messaging.AzureServiceBus/AzureServiceBusOptions.cs deleted file mode 100644 index cd96fb9..0000000 --- a/src/IdentityServer/Messaging.AzureServiceBus/AzureServiceBusOptions.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace IdOps.IdentityServer.AzureServiceBus -{ - public class AzureServiceBusOptions - { - public string ConnectionString { get; set; } = default!; - - public int PrefetchCount { get; set; } = 10; - } -} diff --git a/src/IdentityServer/samples/Server/IdentityServer.csproj b/src/IdentityServer/samples/Server/IdentityServer.csproj index f569aa6..917da20 100644 --- a/src/IdentityServer/samples/Server/IdentityServer.csproj +++ b/src/IdentityServer/samples/Server/IdentityServer.csproj @@ -11,7 +11,7 @@ - + diff --git a/src/IdentityServer/samples/Server/Program.cs b/src/IdentityServer/samples/Server/Program.cs index b6d4bc9..d784077 100644 --- a/src/IdentityServer/samples/Server/Program.cs +++ b/src/IdentityServer/samples/Server/Program.cs @@ -1,11 +1,6 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; namespace IdOps.IdentityServer.Samples { diff --git a/src/IdentityServer/samples/Server/Startup.cs b/src/IdentityServer/samples/Server/Startup.cs index 44e1d46..c2bc923 100644 --- a/src/IdentityServer/samples/Server/Startup.cs +++ b/src/IdentityServer/samples/Server/Startup.cs @@ -1,5 +1,5 @@ using System; -using IdOps.IdentityServer.AzureServiceBus; +using IdOps.IdentityServer.Azure; using IdOps.IdentityServer.Events; using IdOps.IdentityServer.RabbitMQ; using IdOps.IdentityServer.Samples.DataSeeding; @@ -49,10 +49,11 @@ public void ConfigureServices(IServiceCollection services) case MessagingTransport.RabbitMq: busBuilder.UseRabbitMq(); break; - case MessagingTransport.AzureServiceBus: - busBuilder.UseAzureServiceBus(); + case MessagingTransport.Azure: + busBuilder.UseAzure(); break; } + }).AddProfileService(); services.AddSingleton(); diff --git a/src/IdentityServer/src/Abstractions/IEventSenderWorker.cs b/src/IdentityServer/src/Abstractions/IEventSenderWorker.cs new file mode 100644 index 0000000..38095af --- /dev/null +++ b/src/IdentityServer/src/Abstractions/IEventSenderWorker.cs @@ -0,0 +1,7 @@ +using Microsoft.Extensions.Hosting; + +namespace IdOps.IdentityServer.Abstractions; + +public interface IEventSenderWorker : IHostedService +{ +} diff --git a/src/IdentityServer/src/Abstractions/IIdOpsIdentityServerBuilder.cs b/src/IdentityServer/src/Abstractions/IIdOpsIdentityServerBuilder.cs index 061c97e..c8dd3db 100644 --- a/src/IdentityServer/src/Abstractions/IIdOpsIdentityServerBuilder.cs +++ b/src/IdentityServer/src/Abstractions/IIdOpsIdentityServerBuilder.cs @@ -6,7 +6,9 @@ namespace IdOps.IdentityServer public interface IIdOpsIdentityServerBuilder { IConfiguration? Configuration { get; } + IServiceCollection Services { get; } + IdOpsOptions Options { get; } } @@ -30,6 +32,6 @@ public enum MessagingTransport { Memory, RabbitMq, - AzureServiceBus + Azure } } diff --git a/src/IdentityServer/src/Abstractions/Stores/IUserDataConnectorDataRepository.cs b/src/IdentityServer/src/Abstractions/Stores/IUserDataConnectorDataRepository.cs deleted file mode 100644 index e7f58ae..0000000 --- a/src/IdentityServer/src/Abstractions/Stores/IUserDataConnectorDataRepository.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; -using IdOps.IdentityServer.DataConnector; - -namespace IdOps.IdentityServer.Storage -{ - public interface IUserDataConnectorDataRepository - { - Task GetAsync( - string key, - string connector, - CancellationToken cancellationToken); - - Task SaveAsync(UserDataConnectorData data, CancellationToken cancellationToken); - } -} diff --git a/src/IdentityServer/src/IdentityServer.Core/Core.csproj b/src/IdentityServer/src/IdentityServer.Core/Core.csproj index 615124f..94431cd 100644 --- a/src/IdentityServer/src/IdentityServer.Core/Core.csproj +++ b/src/IdentityServer/src/IdentityServer.Core/Core.csproj @@ -13,6 +13,7 @@ + diff --git a/src/IdentityServer/src/IdentityServer.Core/DataConnector/UserDataConnectorService.cs b/src/IdentityServer/src/IdentityServer.Core/DataConnector/UserDataConnectorService.cs index d43fd1d..af61ddd 100644 --- a/src/IdentityServer/src/IdentityServer.Core/DataConnector/UserDataConnectorService.cs +++ b/src/IdentityServer/src/IdentityServer.Core/DataConnector/UserDataConnectorService.cs @@ -20,7 +20,6 @@ public class UserDataConnectorService : IUserDataConnectorService { private readonly ILogger _logger; private readonly IEnumerable _connectors; - private readonly IUserDataConnectorDataRepository _repository; private readonly IEventService _eventService; private static readonly Dictionary ProfileTypeMap @@ -34,12 +33,10 @@ private static readonly Dictionary ProfileTypeMap public UserDataConnectorService( ILogger logger, IEnumerable connectors, - IUserDataConnectorDataRepository repository, IEventService eventService) { _logger = logger; _connectors = connectors; - _repository = repository; _eventService = eventService; } @@ -108,7 +105,7 @@ private async Task> LoadDataAsync( IUserDataConnector? connector = _connectors .Single(x => x.Name == options.Name); - var timeOutToken = new CancellationTokenSource(Debugger.IsAttached ? 300000 : 5000); + var timeOutToken = new CancellationTokenSource(300000); CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource( cancellationToken, @@ -121,58 +118,9 @@ private async Task> LoadDataAsync( activity?.EnrichDataConnectorResult(result); - if (result.Success && result.Executed && result.CacheKey != null) - { - try - { - await SaveDataAsync(options, result, context.Subject, cancellationToken); - } - catch (Exception ex) - { - _logger.DataConnectorSaveDataFailed(connector.Name); - activity?.RecordException(ex); - } - } - else if (!result.Success && result.CacheKey != null) - { - UserDataConnectorData? storedData = await _repository.GetAsync( - result.CacheKey, - connector.Name, - cancellationToken); - - if (storedData != null) - { - claims = storedData.Claims.Select(x => new Claim(x.Type, x.Value)); - } - else - { - throw result.Error; - } - } - return claims; } - private async Task SaveDataAsync( - DataConnectorOptions options, - UserDataConnectorResult result, - string subject, - CancellationToken cancellationToken) - { - await _repository.SaveAsync(new UserDataConnectorData - { - Claims = result.Claims.Select(x => new ClaimData - { - Type = x.Type, - Value = x.Value - }), - Key = result.CacheKey!, - SubjectId = subject, - Connector = options.Name, - LastModifiedAt = DateTime.UtcNow - }, cancellationToken); - } - private bool ShouldExecute(DataConnectorOptions options, string caller) { return options.Enabled && diff --git a/src/IdentityServer/src/IdentityServer.Core/Events/BusEventSender.cs b/src/IdentityServer/src/IdentityServer.Core/Events/BusEventSender.cs index 8b1a07a..9ea9b3e 100644 --- a/src/IdentityServer/src/IdentityServer.Core/Events/BusEventSender.cs +++ b/src/IdentityServer/src/IdentityServer.Core/Events/BusEventSender.cs @@ -1,42 +1,42 @@ using System.Threading; using System.Threading.Channels; using System.Threading.Tasks; +using IdOps.IdentityServer.Abstractions; using IdOps.Messages; using MassTransit; using Microsoft.Extensions.Hosting; -namespace IdOps.IdentityServer.Events +namespace IdOps.IdentityServer.Events; + +public sealed class BusEventSender : BackgroundService, IEventSenderWorker { - public class BusEventSender : BackgroundService + private readonly IBus _bus; + private readonly ChannelReader _channelReader; + + public BusEventSender( + IBus bus, + ChannelReader channelReader) { - private readonly IBus _bus; - private readonly ChannelReader _channelReader; + _bus = bus; + _channelReader = channelReader; + } - public BusEventSender( - IBus bus, - ChannelReader channelReader) - { - _bus = bus; - _channelReader = channelReader; - } + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + await Task.Yield(); - protected override async Task ExecuteAsync(CancellationToken stoppingToken) + try { - await Task.Yield(); - - try + while (await _channelReader.WaitToReadAsync(stoppingToken)) { - while (await _channelReader.WaitToReadAsync(stoppingToken)) - { - IdentityEventMessage entity = await _channelReader.ReadAsync(stoppingToken); + IdentityEventMessage entity = await _channelReader.ReadAsync(stoppingToken); - await _bus.Publish(entity, stoppingToken); - } - } - catch - { - // ignored + await _bus.Publish(entity, stoppingToken); } } + catch + { + // ignored + } } } diff --git a/src/IdentityServer/src/IdentityServer.Core/Events/BusEventSink.cs b/src/IdentityServer/src/IdentityServer.Core/Events/BusEventSink.cs index 1f76851..df84c96 100644 --- a/src/IdentityServer/src/IdentityServer.Core/Events/BusEventSink.cs +++ b/src/IdentityServer/src/IdentityServer.Core/Events/BusEventSink.cs @@ -10,49 +10,52 @@ using IdOps.Messages; using Microsoft.AspNetCore.Http; -namespace IdOps.IdentityServer.Events +namespace IdOps.IdentityServer.Events; + +public class BusEventSink : IIdOpsEventSink { - public class BusEventSink : IIdOpsEventSink + private readonly ChannelWriter _channelWriter; + private readonly IHttpContextAccessor _httpContextAccessor; + private readonly IdOpsOptions _idOpsOptions; + private readonly JsonSerializerOptions _jsonOptions = new() { - private readonly ChannelWriter _channelWriter; - private readonly IHttpContextAccessor _httpContextAccessor; - private readonly IdOpsOptions _idOpsOptions; - private readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions - { - IgnoreNullValues = true, - WriteIndented = true - }; + DefaultIgnoreCondition = JsonIgnoreCondition.Never, WriteIndented = true + }; + + public BusEventSink( + ChannelWriter channelWriter, + IHttpContextAccessor httpContextAccessor, + IdOpsOptions idOpsOptions) + { + _channelWriter = channelWriter; + _httpContextAccessor = httpContextAccessor; + _idOpsOptions = idOpsOptions; + _jsonOptions.Converters.Add(new JsonStringEnumConverter()); + } - public BusEventSink( - ChannelWriter channelWriter, - IHttpContextAccessor httpContextAccessor, - IdOpsOptions idOpsOptions) + public async ValueTask ProcessAsync(Event evt, Activity? activity) + { + if (activity != null) { - _channelWriter = channelWriter; - _httpContextAccessor = httpContextAccessor; - _idOpsOptions = idOpsOptions; - _jsonOptions.Converters.Add(new JsonStringEnumConverter()); + evt.ActivityId = activity.Id; } - public async ValueTask ProcessAsync(Event evt, Activity? activity) + evt.RemoteIpAddress = _httpContextAccessor.HttpContext?.GetRemoteIpAddress(); + + if (evt.GetType().FullName is not { } fullName) { - if (activity != null) - { - evt.ActivityId = activity.Id; - } - - evt.RemoteIpAddress = _httpContextAccessor.HttpContext?.GetRemoteIpAddress(); - - var entity = new IdentityEventMessage - { - EnvironmentName = _idOpsOptions.EnvironmentName, - ServerGroup = _idOpsOptions.ServerGroup, - Type = evt.GetType().FullName, - Hostname = Environment.MachineName, - Data = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(evt, _jsonOptions)), - }; - - await _channelWriter.WriteAsync(entity); + return; } + + var entity = new IdentityEventMessage + { + EnvironmentName = _idOpsOptions.EnvironmentName, + ServerGroup = _idOpsOptions.ServerGroup, + Type = fullName, + Hostname = Environment.MachineName, + Data = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(evt, _jsonOptions)) + }; + + await _channelWriter.WriteAsync(entity); } } diff --git a/src/IdentityServer/src/IdentityServer.Core/IdOpsActivity.cs b/src/IdentityServer/src/IdentityServer.Core/IdOpsActivity.cs index 76caa88..14c2b89 100644 --- a/src/IdentityServer/src/IdentityServer.Core/IdOpsActivity.cs +++ b/src/IdentityServer/src/IdentityServer.Core/IdOpsActivity.cs @@ -1,4 +1,5 @@ using System.Diagnostics; +using Duende.IdentityServer; using IdOps.IdentityServer.DataConnector; using IdOps.IdentityServer.Model; using OpenTelemetry.Trace; @@ -7,13 +8,12 @@ namespace IdOps.IdentityServer; internal static class IdOpsActivity { - private static readonly ActivitySource ActivitySource = new("IdOps.IdentityServer"); - public static Activity? StartDataConnector( DataConnectorOptions options, UserDataConnectorCallerContext context) { - Activity? activity = ActivitySource.StartActivity($"DataConnector {options.Name}"); + Activity? activity = + Telemetry.ActivitySource.StartActivity($"DataConnector {options.Name}"); activity?.SetTag("idops.identityserver.dataconnector.enabled", options.Enabled); Activity? parentOrCurrent = activity?.Parent ?? activity; @@ -23,7 +23,9 @@ internal static class IdOpsActivity return activity; } - public static void EnrichDataConnectorResult(this Activity? activity, UserDataConnectorResult result) + public static void EnrichDataConnectorResult( + this Activity? activity, + UserDataConnectorResult result) { if (result.Error != null) { diff --git a/src/IdentityServer/src/IdentityServer.Core/IdOpsMeters.cs b/src/IdentityServer/src/IdentityServer.Core/IdOpsMeters.cs new file mode 100644 index 0000000..6d08f60 --- /dev/null +++ b/src/IdentityServer/src/IdentityServer.Core/IdOpsMeters.cs @@ -0,0 +1,17 @@ +using System.Diagnostics.Metrics; + +namespace IdOps.IdentityServer; + +public static class IdOpsMeters +{ + private static readonly Histogram _eventBatchSize = Telemetry.Meter + .CreateHistogram( + "idops.events.sender.batch_size", + "{event}", + "The size of the event batch being sent"); + + public static void RecordSenderBatchSize(int size) + { + _eventBatchSize.Record(size); + } +} diff --git a/src/IdentityServer/src/IdentityServer.Core/IdentityServerBuilderExtensions.cs b/src/IdentityServer/src/IdentityServer.Core/IdentityServerBuilderExtensions.cs index 5cc8011..7831440 100644 --- a/src/IdentityServer/src/IdentityServer.Core/IdentityServerBuilderExtensions.cs +++ b/src/IdentityServer/src/IdentityServer.Core/IdentityServerBuilderExtensions.cs @@ -3,6 +3,7 @@ using Duende.IdentityServer.Services; using Duende.IdentityServer.Validation; using IdOps.IdentityServer; +using IdOps.IdentityServer.Abstractions; using IdOps.IdentityServer.DataConnector; using IdOps.IdentityServer.Events; using IdOps.IdentityServer.Hashing; @@ -22,7 +23,7 @@ public record IdOpsIdentityServerBuilder(IServiceCollection Services) { public IConfiguration? Configuration { get; init; } - public IdOpsOptions Options { get; internal set; } = new IdOpsOptions(); + public IdOpsOptions Options { get; internal set; } = new(); } public record BusBuilder( @@ -50,11 +51,10 @@ public static IIdentityServerBuilder AddIdOps( var idOpsBuilder = new IdOpsIdentityServerBuilder(builder.Services) { - Configuration = configuration, - Options = options ?? new IdOpsOptions() + Configuration = configuration, Options = options ?? new IdOpsOptions() }; - return idOpsBuilder.AddIdOps(builder,configure); + return idOpsBuilder.AddIdOps(builder, configure); } private static IIdentityServerBuilder AddIdOps( @@ -62,6 +62,7 @@ private static IIdentityServerBuilder AddIdOps( IIdentityServerBuilder identityServerBuilder, Action? configure) { + builder.Services.AddSingleton(); configure?.Invoke(builder); var channel = Channel @@ -71,20 +72,23 @@ private static IIdentityServerBuilder AddIdOps( SingleReader = true, AllowSynchronousContinuations = false }); - + builder.Services.AddSingleton(channel.Reader); builder.Services.AddSingleton(channel.Writer); - builder.Services.AddHostedService(); + builder.Services.AddHostedService(sp => sp.GetRequiredService()); + builder.Services.AddSingleton(); builder.Services.AddResources(); builder.Services.AddSingleton(builder.Options); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); - builder.Services.AddTransient(); + builder.Services + .AddTransient(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton( - builder.Configuration?.GetSection("InternalIpFilter").Get() ?? + builder.Configuration?.GetSection("InternalIpFilter") + .Get() ?? new InternalIpFilterConfiguration()); builder.Services .AddSingleton(); @@ -104,10 +108,11 @@ private static IIdentityServerBuilder AddIdOps( public static BusBuilder AddMassTransit(this IIdOpsIdentityServerBuilder builder) { - return new BusBuilder(builder, (b) => - { - b.AddConsumer(); - }); + return new BusBuilder(builder, + b => + { + b.AddConsumer(); + }); } public static IIdOpsIdentityServerBuilder UseInMemory(this BusBuilder builder) @@ -119,10 +124,11 @@ public static IIdOpsIdentityServerBuilder UseInMemory(this BusBuilder builder) s.UsingInMemory((provider, cfg) => { cfg.ReceiveEndpoint( - $"id-{builder.IdOpsBuilder.Options!.EnvironmentName.ToLower()}", e => - { - e.ConfigureConsumers(provider); - }); + $"id-{builder.IdOpsBuilder.Options!.EnvironmentName.ToLower()}", + e => + { + e.ConfigureConsumers(provider); + }); }); }); @@ -169,9 +175,7 @@ public static IIdOpsIdentityServerBuilder RegisterUserDataConnector( return builder; } - - private static IIdentityServerBuilder AddIdOpsStores( - this IIdentityServerBuilder builder) + private static IIdentityServerBuilder AddIdOpsStores(this IIdentityServerBuilder builder) { builder.Services.AddSingleton(); builder.Services.AddSingleton(); diff --git a/src/IdentityServer/src/IdentityServer.Core/Telemetry.cs b/src/IdentityServer/src/IdentityServer.Core/Telemetry.cs new file mode 100644 index 0000000..3120079 --- /dev/null +++ b/src/IdentityServer/src/IdentityServer.Core/Telemetry.cs @@ -0,0 +1,10 @@ +using System.Diagnostics; +using System.Diagnostics.Metrics; + +namespace IdOps.IdentityServer; + +internal static class Telemetry +{ + public static readonly ActivitySource ActivitySource = new("IdOps.IdentityServer"); + public static readonly Meter Meter = new("IdOps.IdentityServer"); +} diff --git a/src/IdentityServer/src/Store.Mongo/MongoStoreServiceCollectionExtensions.cs b/src/IdentityServer/src/Store.Mongo/MongoStoreServiceCollectionExtensions.cs index 28a4f5c..1050dcb 100644 --- a/src/IdentityServer/src/Store.Mongo/MongoStoreServiceCollectionExtensions.cs +++ b/src/IdentityServer/src/Store.Mongo/MongoStoreServiceCollectionExtensions.cs @@ -51,9 +51,6 @@ public static IServiceCollection AddMongoStores( services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - services.AddSingleton< - IUserDataConnectorDataRepository, - UserDataConnectorDataRepository>(); return services; } diff --git a/src/IdentityServer/src/Store.Mongo/UserDataConnectorDataRepository.cs b/src/IdentityServer/src/Store.Mongo/UserDataConnectorDataRepository.cs deleted file mode 100644 index cc41679..0000000 --- a/src/IdentityServer/src/Store.Mongo/UserDataConnectorDataRepository.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using IdOps.IdentityServer.DataConnector; -using MongoDB.Driver; -using MongoDB.Driver.Linq; - -namespace IdOps.IdentityServer.Storage.Mongo -{ - public class UserDataConnectorDataRepository : IUserDataConnectorDataRepository - { - private readonly IIdentityStoreDbContext _dbContext; - - public UserDataConnectorDataRepository(IIdentityStoreDbContext dbContext) - { - _dbContext = dbContext; - } - - public async Task GetAsync( - string key, - string connector, - CancellationToken cancellationToken) - { - return await _dbContext.ConnectorData.AsQueryable() - .Where(x => x.Key == key && x.Connector == connector) - .FirstOrDefaultAsync(cancellationToken); - } - - public async Task SaveAsync( - UserDataConnectorData data, - CancellationToken cancellationToken) - { - FilterDefinition filter = - Builders.Filter - .Eq(x => x.Key, data.Key) & - Builders.Filter - .Eq(x => x.Connector, data.Connector); - - await _dbContext.ConnectorData.ReplaceOneAsync( - filter, - data, - options: new ReplaceOptions { IsUpsert = true }, - cancellationToken); - } - } -} diff --git a/src/Server/src/Abstractions/Configuration/IdOpsServerOptions.cs b/src/Server/src/Abstractions/Configuration/IdOpsServerOptions.cs index 7db635f..ebeefcd 100644 --- a/src/Server/src/Abstractions/Configuration/IdOpsServerOptions.cs +++ b/src/Server/src/Abstractions/Configuration/IdOpsServerOptions.cs @@ -22,6 +22,24 @@ public class MessagingOptions public string Username { get; set; } = "guest"; public string Password { get; set; } = "guest"; + + public EventHubOptions? EventHub { get; set; } = default!; + } + + public class EventHubOptions + { + public string? ConnectionString { get; set; } + + public string? Namespace { get; set; } + + public EventStorageHubOptions? Storage { get; set; } + } + + public sealed class EventStorageHubOptions + { + public string? ConnectionString { get; set; } + + public string? Url { get; set; } } public enum MessagingTransport diff --git a/src/Server/src/AspNet/Security/AuthenticationExtensions.cs b/src/Server/src/AspNet/Security/AuthenticationExtensions.cs index 9c2f611..7091a80 100644 --- a/src/Server/src/AspNet/Security/AuthenticationExtensions.cs +++ b/src/Server/src/AspNet/Security/AuthenticationExtensions.cs @@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.OpenIdConnect; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -51,6 +52,8 @@ public static AuthenticationBuilder AddAuthentication( options.GetClaimsFromUserInfoEndpoint = true; options.ClaimActions.MapAllExcept("role", "user_application", "idp", "nbf", "iat", "exp", "amr", "nonce", "at_hash", "auth_time", "tenant"); options.ClaimActions.MapUserRoles(); + options.NonceCookie.SameSite = SameSiteMode.Lax; + options.CorrelationCookie.SameSite = SameSiteMode.Lax; options.Events = new OpenIdConnectEvents { diff --git a/src/Server/src/AspNet/Security/EnsureAuthenticatedMiddleware.cs b/src/Server/src/AspNet/Security/EnsureAuthenticatedMiddleware.cs index 8acfa53..75c332f 100644 --- a/src/Server/src/AspNet/Security/EnsureAuthenticatedMiddleware.cs +++ b/src/Server/src/AspNet/Security/EnsureAuthenticatedMiddleware.cs @@ -22,16 +22,19 @@ public EnsureAuthenticatedMiddleware( public async Task InvokeAsync(HttpContext context) { - if (context.Request.Path.StartsWithSegments("/_health") || - context.Request.Path.StartsWithSegments("/api/session") || - _env.IsDevelopment()) + if (context.Request.Path.StartsWithSegments("/api/session/auth")) + { + await context.ChallengeAsync(new AuthenticationProperties { RedirectUri = "/" }); + } + else if (context.Request.Path.StartsWithSegments("/_health") || + context.Request.Path.StartsWithSegments("/fonts") || + context.Request.Path.StartsWithSegments("/api/session") || + _env.IsDevelopment()) { await _next(context); - - return; } - - if (context.Request.Path.StartsWithSegments("/api") + else if ( + context.Request.Path.StartsWithSegments("/api") || context.Request.Path.StartsWithSegments("/graphql") || context.Request.Path.StartsWithSegments("/signalR") || context.Request.Path.StartsWithSegments("/error")) diff --git a/src/Server/src/Core/Configuration/IdOpsServerBuilderExtensions.cs b/src/Server/src/Core/Configuration/IdOpsServerBuilderExtensions.cs index f931fa5..b904b8c 100644 --- a/src/Server/src/Core/Configuration/IdOpsServerBuilderExtensions.cs +++ b/src/Server/src/Core/Configuration/IdOpsServerBuilderExtensions.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Azure.Identity; using IdOps.Configuration; using IdOps.Consumers; using IdOps.IdentityServer.Hashing; @@ -160,6 +161,51 @@ private static IBusRegistrationConfigurator UseAzureServiceBus( return s; } + private static void UseEventHub( + this IBusRegistrationConfigurator s, + EventHubOptions options) + { + s.AddRider(x => + { + x.AddConsumer(); + x.UsingEventHub((contex, k) => + { + if (options.Namespace is { } @namespace) + { + k.Host(@namespace, new DefaultAzureCredential()); + } + else if (options.ConnectionString is not null) + { + k.Host(options.ConnectionString); + } + else + { + throw new ApplicationException( + "EventHub configuration is missing. Please check your settings."); + } + + k.ReceiveEndpoint("identity-events", e => e.ConfigureConsumers(contex)); + + if (options.Storage is { } storageOption) + { + if (storageOption.Url is { } url) + { + k.Storage(new Uri(url), new DefaultAzureCredential()); + } + else if (storageOption.ConnectionString is { } connectionString) + { + k.Storage(connectionString); + } + else + { + throw new ApplicationException( + "EventHub storage configuration is missing. Please check your settings."); + } + } + }); + }); + } + private static IBusRegistrationConfigurator UseInMemory( this IBusRegistrationConfigurator s, MessagingOptions options) @@ -184,13 +230,20 @@ private static IServiceCollection AddMessaging( case MessagingTransport.RabbitMq: s.UseRabbitMq(options); break; + case MessagingTransport.AzureServiceBus: s.UseAzureServiceBus(options); break; + case MessagingTransport.Memory: s.UseInMemory(options); break; } + + if (options.EventHub is { } eventHub) + { + s.UseEventHub(eventHub); + } }); return services; diff --git a/src/Server/src/Core/Consumers/IdentityServerEventBatchConsumer.cs b/src/Server/src/Core/Consumers/IdentityServerEventBatchConsumer.cs index 07e6301..c312083 100644 --- a/src/Server/src/Core/Consumers/IdentityServerEventBatchConsumer.cs +++ b/src/Server/src/Core/Consumers/IdentityServerEventBatchConsumer.cs @@ -46,6 +46,7 @@ public async Task Consume(ConsumeContext> context) if (events.Length > 0) { + IdOpsMeters.RecordReceiverBatchSize(events.Length); await _eventStore.CreateManyAsync(events, context.CancellationToken); } } diff --git a/src/Server/src/Core/Core.csproj b/src/Server/src/Core/Core.csproj index 84772d6..b4f9c84 100644 --- a/src/Server/src/Core/Core.csproj +++ b/src/Server/src/Core/Core.csproj @@ -14,6 +14,7 @@ + diff --git a/src/Server/src/Core/IdOpsActivity.cs b/src/Server/src/Core/IdOpsActivity.cs index 01c7f69..0c23aa5 100644 --- a/src/Server/src/Core/IdOpsActivity.cs +++ b/src/Server/src/Core/IdOpsActivity.cs @@ -6,11 +6,11 @@ namespace IdOps; internal static class IdOpsActivity { - private static readonly ActivitySource ActivitySource = new("IdOps"); - - public static Activity? StartEventBatchConsumer(ConsumeContext> context) + public static Activity? StartEventBatchConsumer( + ConsumeContext> context) { - Activity? activity = ActivitySource.StartActivity("IdentityServer Event BatchConsumer"); + Activity? activity = + Telemetry.ActivitySource.StartActivity("IdentityServer Event BatchConsumer"); activity?.EnrichStartEventBatchConsumer(context); @@ -23,4 +23,4 @@ public static void EnrichStartEventBatchConsumer( { activity?.SetTag("messaging.masstransit.batch_count", context.Message.Length); } -} +} \ No newline at end of file diff --git a/src/Server/src/Core/IdOpsMeters.cs b/src/Server/src/Core/IdOpsMeters.cs new file mode 100644 index 0000000..c48f62a --- /dev/null +++ b/src/Server/src/Core/IdOpsMeters.cs @@ -0,0 +1,17 @@ +using System.Diagnostics.Metrics; + +namespace IdOps; + +internal static class IdOpsMeters +{ + private static readonly Histogram _eventBatchSize = Telemetry.Meter + .CreateHistogram( + "idops.events.receiver.batch_size", + "{event}", + "The size of the event batch being sent"); + + public static void RecordReceiverBatchSize(int size) + { + _eventBatchSize.Record(size); + } +} diff --git a/src/Server/src/Core/SecretService.cs b/src/Server/src/Core/SecretService.cs index 332946a..d41deef 100644 --- a/src/Server/src/Core/SecretService.cs +++ b/src/Server/src/Core/SecretService.cs @@ -40,12 +40,6 @@ public SecretService(IEnumerable sharedSecretGenerators, secret.Value = secretValue.ToSha256(); - if (request.SaveValue.GetValueOrDefault() && _encryptionService is not NoEncryptionProvider) - { - secret.EncryptedValue = - await _encryptionService.EncryptAsync(secretValue, CancellationToken.None); - } - return (secret, secretValue); } diff --git a/src/Server/src/Core/Telemetry.cs b/src/Server/src/Core/Telemetry.cs new file mode 100644 index 0000000..143ebed --- /dev/null +++ b/src/Server/src/Core/Telemetry.cs @@ -0,0 +1,11 @@ +using System.Diagnostics; +using System.Diagnostics.Metrics; + +namespace IdOps; + +internal static class Telemetry +{ + public static readonly ActivitySource ActivitySource = new("IdOps"); + + public static readonly Meter Meter = new("IdOps"); +} diff --git a/src/UI/public/fonts/materialdesignicons-webfont.eot b/src/UI/public/fonts/materialdesignicons-webfont.eot new file mode 100644 index 0000000..3eb8969 Binary files /dev/null and b/src/UI/public/fonts/materialdesignicons-webfont.eot differ diff --git a/src/UI/public/fonts/materialdesignicons-webfont.ttf b/src/UI/public/fonts/materialdesignicons-webfont.ttf new file mode 100644 index 0000000..bba7dcf Binary files /dev/null and b/src/UI/public/fonts/materialdesignicons-webfont.ttf differ diff --git a/src/UI/public/fonts/materialdesignicons-webfont.woff b/src/UI/public/fonts/materialdesignicons-webfont.woff new file mode 100644 index 0000000..a36a5f2 Binary files /dev/null and b/src/UI/public/fonts/materialdesignicons-webfont.woff differ diff --git a/src/UI/public/fonts/materialdesignicons-webfont.woff2 b/src/UI/public/fonts/materialdesignicons-webfont.woff2 new file mode 100644 index 0000000..8c69b85 Binary files /dev/null and b/src/UI/public/fonts/materialdesignicons-webfont.woff2 differ