Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 51 additions & 50 deletions src/Rsk.DuendeIdentityServer.AuditEventSink/AdapterFactory.cs
Original file line number Diff line number Diff line change
@@ -1,65 +1,66 @@
using Duende.IdentityServer.Events;
using System;
using System.Collections.Generic;
using Duende.IdentityServer.Events;
using RSK.Audit;
using Rsk.DuendeIdentityServer.AuditEventSink.Adapters;

namespace Rsk.DuendeIdentityServer.AuditEventSink
{
public class AdapterFactory : IAdapterFactory
{
private readonly Dictionary<Type, Func<Event, IAuditEventArguments>> eventAdapters;

public AdapterFactory(IDictionary<Type, Func<Event, IAuditEventArguments>> customEventAdapters = null)
{
eventAdapters = CreateDefaultEventAdapters();

if (customEventAdapters == null) return;

foreach (var mapping in customEventAdapters)
{
eventAdapters[mapping.Key] = mapping.Value;
}
}

public IAuditEventArguments Create(Event evt)
{
if (evt != null)
if (evt == null)
{
switch (evt)
{
case TokenIssuedSuccessEvent e:
return new TokenIssuedSuccessEventAdapter(e);
case UserLoginSuccessEvent e:
return new UserLoginSuccessEventAdapter(e);
case UserLoginFailureEvent e:
return new UserLoginFailureEventAdapter(e);
case UserLogoutSuccessEvent e:
return new UserLogoutSuccessEventAdapter(e);
case ConsentGrantedEvent e:
return new ConsentGrantedEventAdapter(e);
case ConsentDeniedEvent e:
return new ConsentDeniedEventAdapter(e);
case TokenIssuedFailureEvent e:
return new TokenIssuedFailureEventAdapter(e);
case GrantsRevokedEvent e:
return new GrantsRevokedEventAdapter(e);
case DeviceAuthorizationFailureEvent e:
return new DeviceAuthorizationFailureEventAdapter(e);
case DeviceAuthorizationSuccessEvent e:
return new DeviceAuthorizationSuccessEventAdapter(e);
case TokenRevokedSuccessEvent e:
return new TokenRevokedSuccessEventAdapter(e);
case InvalidClientConfigurationEvent e:
return new InvalidClientConfigurationEventAdapter(e);
case TokenIntrospectionFailureEvent e:
return new TokenIntrospectionFailureEventAdapter(e);
case TokenIntrospectionSuccessEvent e:
return new TokenIntrospectionSuccessEventAdapter(e);
case ClientAuthenticationFailureEvent e:
return new ClientAuthenticationFailureEventAdapter(e);
case ClientAuthenticationSuccessEvent e:
return new ClientAuthenticationSuccessEventAdapter(e);
case ApiAuthenticationFailureEvent e:
return new ApiAuthenticationFailureEventAdapter(e);
case ApiAuthenticationSuccessEvent e:
return new ApiAuthenticationSuccessEventAdapter(e);
case UnhandledExceptionEvent e:
return new UnhandledExceptionEventAdapter(e);
case BackchannelAuthenticationSuccessEvent e:
return new BackchannelAuthenticationSuccessEventAdapter(e);
case BackchannelAuthenticationFailureEvent e:
return new BackchannelAuthenticationFailureEventAdapter(e);
case InvalidIdentityProviderConfiguration e:
return new InvalidIdentityProviderConfigurationAdapter(e);
}
return null;
}

return null;
return eventAdapters.TryGetValue(evt.GetType(), out var adapterFactory)
? adapterFactory(evt)
: null;
}

private static Dictionary<Type, Func<Event, IAuditEventArguments>> CreateDefaultEventAdapters()
{
return new Dictionary<Type, Func<Event, IAuditEventArguments>>
{
[typeof(TokenIssuedSuccessEvent)] = e => new TokenIssuedSuccessEventAdapter((TokenIssuedSuccessEvent)e),
[typeof(UserLoginSuccessEvent)] = e => new UserLoginSuccessEventAdapter((UserLoginSuccessEvent)e),
[typeof(UserLoginFailureEvent)] = e => new UserLoginFailureEventAdapter((UserLoginFailureEvent)e),
[typeof(UserLogoutSuccessEvent)] = e => new UserLogoutSuccessEventAdapter((UserLogoutSuccessEvent)e),
[typeof(ConsentGrantedEvent)] = e => new ConsentGrantedEventAdapter((ConsentGrantedEvent)e),
[typeof(ConsentDeniedEvent)] = e => new ConsentDeniedEventAdapter((ConsentDeniedEvent)e),
[typeof(TokenIssuedFailureEvent)] = e => new TokenIssuedFailureEventAdapter((TokenIssuedFailureEvent)e),
[typeof(GrantsRevokedEvent)] = e => new GrantsRevokedEventAdapter((GrantsRevokedEvent)e),
[typeof(DeviceAuthorizationFailureEvent)] = e => new DeviceAuthorizationFailureEventAdapter((DeviceAuthorizationFailureEvent)e),
[typeof(DeviceAuthorizationSuccessEvent)] = e => new DeviceAuthorizationSuccessEventAdapter((DeviceAuthorizationSuccessEvent)e),
[typeof(TokenRevokedSuccessEvent)] = e => new TokenRevokedSuccessEventAdapter((TokenRevokedSuccessEvent)e),
[typeof(InvalidClientConfigurationEvent)] = e => new InvalidClientConfigurationEventAdapter((InvalidClientConfigurationEvent)e),
[typeof(TokenIntrospectionFailureEvent)] = e => new TokenIntrospectionFailureEventAdapter((TokenIntrospectionFailureEvent)e),
[typeof(TokenIntrospectionSuccessEvent)] = e => new TokenIntrospectionSuccessEventAdapter((TokenIntrospectionSuccessEvent)e),
[typeof(ClientAuthenticationFailureEvent)] = e => new ClientAuthenticationFailureEventAdapter((ClientAuthenticationFailureEvent)e),
[typeof(ClientAuthenticationSuccessEvent)] = e => new ClientAuthenticationSuccessEventAdapter((ClientAuthenticationSuccessEvent)e),
[typeof(ApiAuthenticationFailureEvent)] = e => new ApiAuthenticationFailureEventAdapter((ApiAuthenticationFailureEvent)e),
[typeof(ApiAuthenticationSuccessEvent)] = e => new ApiAuthenticationSuccessEventAdapter((ApiAuthenticationSuccessEvent)e),
[typeof(UnhandledExceptionEvent)] = e => new UnhandledExceptionEventAdapter((UnhandledExceptionEvent)e),
[typeof(BackchannelAuthenticationSuccessEvent)] = e => new BackchannelAuthenticationSuccessEventAdapter((BackchannelAuthenticationSuccessEvent)e),
[typeof(BackchannelAuthenticationFailureEvent)] = e => new BackchannelAuthenticationFailureEventAdapter((BackchannelAuthenticationFailureEvent)e),
[typeof(InvalidIdentityProviderConfiguration)] = e => new InvalidIdentityProviderConfigurationAdapter((InvalidIdentityProviderConfiguration)e)
};
}
}
}
40 changes: 19 additions & 21 deletions src/Rsk.DuendeIdentityServer.AuditEventSink/AuditSink.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Duende.IdentityServer.Events;
Expand All @@ -7,34 +8,31 @@

[assembly:InternalsVisibleTo("RSK.DuendeIdentityServer.AuditEventSink.Tests")]

namespace Rsk.DuendeIdentityServer.AuditEventSink
namespace Rsk.DuendeIdentityServer.AuditEventSink;

public class AuditSink(
IRecordAuditableActions auditRecorder,
IDictionary<Type, Func<Event, IAuditEventArguments>> customEventAdapters = null)
: IEventSink
{
public class AuditSink : IEventSink
{
private readonly IRecordAuditableActions auditRecorder;
private readonly IRecordAuditableActions auditRecorder = auditRecorder ?? throw new ArgumentNullException();

internal IAdapterFactory Factory { get; set; } = new AdapterFactory();
internal IAdapterFactory Factory { get; init; } = new AdapterFactory(customEventAdapters);

public AuditSink(IRecordAuditableActions auditRecorder)
{
this.auditRecorder = auditRecorder ?? throw new ArgumentNullException();
}
public Task PersistAsync(Event evt)
{
var auditArgument = Factory.Create(evt);

public Task PersistAsync(Event evt)
if (auditArgument != null)
{
var auditArgument = Factory.Create(evt);

if (auditArgument != null)
if (evt.EventType == EventTypes.Success || evt.EventType == EventTypes.Information)
{
if (evt.EventType == EventTypes.Success || evt.EventType == EventTypes.Information)
{
return auditRecorder.RecordSuccess(auditArgument);
}

return auditRecorder.RecordFailure(auditArgument);
return auditRecorder.RecordSuccess(auditArgument);
}

return Task.CompletedTask;
return auditRecorder.RecordFailure(auditArgument);
}

return Task.CompletedTask;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
<Authors>Rock Solid Knowledge Ltd</Authors>
<Description>Duende IdentityServer event sink to add audit records into AdminUI auditing</Description>
<PackageProjectUrl>https://github.com/RockSolidKnowledge/RSK.IdentityServer4.AuditEventSink</PackageProjectUrl>
<PackageReleaseNotes>Upgrade to .NET 10</PackageReleaseNotes>
<Copyright>Copyright 2022 (c) Rock Solid Knowledge Ltd. All rights reserved</Copyright>
<PackageReleaseNotes>Add event extensibility</PackageReleaseNotes>
<Copyright>Copyright 2026 (c) Rock Solid Knowledge Ltd. All rights reserved</Copyright>
<PackageTags>Audit AdminUI IdentityServer Events</PackageTags>
<IncludeSymbols>true</IncludeSymbols>
<PackageIcon>icon.png</PackageIcon>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
<Version>4.0.0</Version>
<Version>4.1.0</Version>
</PropertyGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.Threading.Tasks;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Duende.IdentityServer.Events;
using Moq;
using RSK.Audit;
Expand Down Expand Up @@ -88,11 +90,39 @@ public async Task PersistAsync_WhenFailureEvent_WillCallFailureAuditRecord()
recorder.Verify(x => x.RecordFailure(It.IsAny<IAuditEventArguments>()), Times.Once);
}

[Fact]
public async Task PersistAsync_WhenCustomMappingIsProvided_WillUseCustomAdapter()
{
// Arrange
var recorder = new Mock<IRecordAuditableActions>();
var customAuditEventArguments = new Mock<IAuditEventArguments>().Object;
var customMappings = new Dictionary<Type, Func<Event, IAuditEventArguments>>
{
[typeof(CustomStubEvent)] = _ => customAuditEventArguments
};

var sut = new AuditSink(recorder.Object, customMappings);
var evt = new CustomStubEvent(string.Empty, string.Empty, EventTypes.Success, -1);

// Act
await sut.PersistAsync(evt);

// Assert
recorder.Verify(x => x.RecordSuccess(customAuditEventArguments), Times.Once);
}

private class StubEvent : Event
{
public StubEvent(string category, string name, EventTypes type, int id, string message = null) : base(category, name, type, id, message)
{
}
}

private class CustomStubEvent : Event
{
public CustomStubEvent(string category, string name, EventTypes type, int id, string message = null) : base(category, name, type, id, message)
{
}
}
}
}
Loading