Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,8 @@ public static IIdentityServerBuilder AddCoreServices(this IIdentityServerBuilder

builder.Services.AddSingleton<IDiagnosticEntry, AssemblyInfoDiagnosticEntry>();
builder.Services.AddSingleton<IDiagnosticEntry, AuthSchemeInfoDiagnosticEntry>();
builder.Services.AddSingleton(new ServiceCollectionAccessor(builder.Services));
builder.Services.AddSingleton<IDiagnosticEntry, RegisteredImplementationsDiagnosticEntry>();
builder.Services.AddSingleton<DiagnosticSummary>();
builder.Services.AddHostedService<DiagnosticHostedService>();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
// Copyright (c) Duende Software. All rights reserved.
// See LICENSE in the project root for license information.

using System.Text.Json;
using Duende.IdentityServer.Hosting;
using Duende.IdentityServer.Internal;
using Duende.IdentityServer.ResponseHandling;
using Duende.IdentityServer.Services;
using Duende.IdentityServer.Services.KeyManagement;
using Duende.IdentityServer.Stores;
using Duende.IdentityServer.Stores.Serialization;
using Duende.IdentityServer.Validation;

namespace Duende.IdentityServer.Licensing.V2.Diagnostics.DiagnosticEntries;

internal class RegisteredImplementationsDiagnosticEntry(ServiceCollectionAccessor serviceCollectionAccessor)
: IDiagnosticEntry
{
private readonly Dictionary<string, IEnumerable<Type>> _typesToInspect = new()
{
{
"Root", [ typeof(IIdentityServerTools) ]
},
{
"Hosting", [
typeof(IEndpointHandler),
typeof(IEndpointResult),
typeof(IEndpointRouter),
typeof(IHttpResponseWriter<>)
]
},
{
"Infrastructure", [typeof(IClock), typeof(IConcurrencyLock<>)]
},
{
"ResponseHandling", [
typeof(IAuthorizeInteractionResponseGenerator),
typeof(IAuthorizeResponseGenerator),
typeof(IBackchannelAuthenticationResponseGenerator),
typeof(IDeviceAuthorizationResponseGenerator),
typeof(IDiscoveryResponseGenerator),
typeof(IIntrospectionResponseGenerator),
typeof(IPushedAuthorizationResponseGenerator),
typeof(ITokenResponseGenerator),
typeof(ITokenRevocationResponseGenerator),
typeof(IUserInfoResponseGenerator)
]
},
{
"Services", [
typeof(IAutomaticKeyManagerKeyStore),
typeof(IBackchannelAuthenticationInteractionService),
typeof(IBackchannelAuthenticationThrottlingService),
typeof(IBackchannelAuthenticationUserNotificationService),
typeof(IBackChannelLogoutHttpClient),
typeof(IBackChannelLogoutService),
typeof(ICache<>),
typeof(ICancellationTokenProvider),
typeof(IClaimsService),
typeof(IConsentService),
typeof(ICorsPolicyService),
typeof(IDeviceFlowCodeService),
typeof(IDeviceFlowInteractionService),
typeof(IDeviceFlowThrottlingService),
typeof(IEventService),
typeof(IEventSink),
typeof(IHandleGenerationService),
typeof(IIdentityServerInteractionService),
typeof(IIssuerNameService),
typeof(IJwtRequestUriHttpClient),
typeof(IKeyManager),
typeof(IKeyMaterialService),
typeof(ILogoutNotificationService),
typeof(IPersistedGrantService),
typeof(IProfileService),
typeof(IPushedAuthorizationSerializer),
typeof(IPushedAuthorizationService),
typeof(IRefreshTokenService),
typeof(IReplayCache),
typeof(IReturnUrlParser),
typeof(IServerUrls),
typeof(ISessionCoordinationService),
typeof(ISessionManagementService),
typeof(ISigningKeyProtector),
typeof(ISigningKeyStoreCache),
typeof(ITokenCreationService),
typeof(ITokenService),
typeof(IUserCodeGenerator),
typeof(IUserCodeService),
typeof(IUserSession)
]
},
{
"Stores", [
typeof(IAuthorizationCodeStore),
typeof(IAuthorizationParametersMessageStore),
typeof(IBackChannelAuthenticationRequestStore),
typeof(IClientStore),
typeof(IConsentMessageStore),
typeof(IDeviceFlowStore),
typeof(IIdentityProviderStore),
typeof(IMessageStore<>),
typeof(IPersistentGrantSerializer),
typeof(IPersistedGrantStore),
typeof(IPushedAuthorizationRequestStore),
typeof(IReferenceTokenStore),
typeof(IRefreshTokenStore),
typeof(IResourceStore),
typeof(IServerSideSessionsMarker),
typeof(IServerSideSessionStore),
typeof(IServerSideTicketStore),
typeof(ISigningCredentialStore),
typeof(ISigningKeyStore),
typeof(IUserConsentStore),
typeof(IValidationKeysStore)
]
},
{
"Validation", [
typeof(IApiSecretValidator),
typeof(IAuthorizeRequestValidator),
typeof(IBackchannelAuthenticationRequestIdValidator),
typeof(IBackchannelAuthenticationRequestValidator),
typeof(IBackchannelAuthenticationUserValidator),
typeof(IClientConfigurationValidator),
typeof(IClientSecretValidator),
typeof(ICustomAuthorizeRequestValidator),
typeof(ICustomBackchannelAuthenticationValidator),
typeof(ICustomTokenRequestValidator),
typeof(ICustomTokenValidator),
typeof(IDeviceAuthorizationRequestValidator),
typeof(IDeviceCodeValidator),
typeof(IDPoPProofValidator),
typeof(IEndSessionRequestValidator),
typeof(IExtensionGrantValidator),
typeof(IIdentityProviderConfigurationValidator),
typeof(IIntrospectionRequestValidator),
typeof(IJwtRequestValidator),
typeof(IPushedAuthorizationRequestValidator),
typeof(IRedirectUriValidator),
typeof(IResourceOwnerPasswordValidator),
typeof(IResourceValidator),
typeof(IScopeParser),
typeof(ISecretParser),
typeof(ISecretsListParser),
typeof(ISecretsListValidator),
typeof(ISecretValidator),
typeof(ITokenRequestValidator),
typeof(ITokenRevocationRequestValidator),
typeof(ITokenValidator),
typeof(IUserInfoRequestValidator)
]
}
};

public Task WriteAsync(Utf8JsonWriter writer)
{
writer.WriteStartObject("RegisteredImplementations");

foreach (var group in _typesToInspect)
{
writer.WriteStartArray(group.Key);

foreach (var type in group.Value)
{
WriteImplementationDetails(type, type.Name, writer);
}

writer.WriteEndArray();
}

writer.WriteEndObject();

return Task.CompletedTask;
}

private void WriteImplementationDetails(Type targetType, string serviceName, Utf8JsonWriter writer)
{
writer.WriteStartObject();
writer.WriteStartArray(serviceName);

var services = serviceCollectionAccessor.ServiceCollection.Where(descriptor =>
descriptor.ServiceType == targetType &&
descriptor.ImplementationType != null);
if (services.Any())
{
foreach (var service in services)
{
var type = service.ImplementationType!;
writer.WriteStartObject();
writer.WriteString("TypeName", type.FullName);
writer.WriteString("Assembly", type.Assembly.GetName().Name);
writer.WriteString("AssemblyVersion", type.Assembly.GetName().Version?.ToString());
writer.WriteEndObject();
}
}
else
{
writer.WriteStartObject();
writer.WriteString("TypeName", "Not Registered");
writer.WriteString("Assembly", "Not Registered");
writer.WriteString("AssemblyVersion", "Not Registered");
writer.WriteEndObject();
}


writer.WriteEndArray();
writer.WriteEndObject();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Copyright (c) Duende Software. All rights reserved.
// See LICENSE in the project root for license information.

using Microsoft.Extensions.DependencyInjection;

namespace Duende.IdentityServer.Licensing.V2;

internal class ServiceCollectionAccessor(IServiceCollection serviceCollection)
{
public IServiceCollection ServiceCollection { get; } = serviceCollection;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// Copyright (c) Duende Software. All rights reserved.
// See LICENSE in the project root for license information.

using Duende.IdentityServer.Licensing.V2;
using Duende.IdentityServer.Licensing.V2.Diagnostics.DiagnosticEntries;
using Duende.IdentityServer.Services;
using Microsoft.Extensions.DependencyInjection;
using UnitTests.Common;

namespace IdentityServer.UnitTests.Licensing.V2.DiagnosticEntries;

public class RegisteredImplementationsDiagnosticEntryTests
{
[Fact]
public async Task WriteAsync_ShouldWriteRegisteredImplementationInfo()
{
var serviceCollection = new ServiceCollection()
.AddSingleton<IProfileService, MockProfileService>();
var subject = new RegisteredImplementationsDiagnosticEntry(new ServiceCollectionAccessor(serviceCollection));

var result = await DiagnosticEntryTestHelper.WriteEntryToJson(subject);

var registeredImplementations = result.RootElement.GetProperty("RegisteredImplementations");
var services = registeredImplementations.GetProperty("Services");
var profileServiceEntry = services.EnumerateArray().ToList().SingleOrDefault(entry => entry.TryGetProperty(nameof(IProfileService), out _));
var assemblyInfo = profileServiceEntry.GetProperty(nameof(IProfileService)).EnumerateArray().First();
var expectedTypeInfo = typeof(MockProfileService);
assemblyInfo.GetProperty("TypeName").GetString().ShouldBe(expectedTypeInfo.FullName);
assemblyInfo.GetProperty("Assembly").GetString().ShouldBe(expectedTypeInfo.Assembly.GetName().Name);
assemblyInfo.GetProperty("AssemblyVersion").GetString().ShouldBe(expectedTypeInfo.Assembly.GetName().Version?.ToString());
}

[Fact]
public async Task WriteAsync_GroupsImplementationsByCategory()
{
var subject = new RegisteredImplementationsDiagnosticEntry(new ServiceCollectionAccessor(new ServiceCollection()));

var result = await DiagnosticEntryTestHelper.WriteEntryToJson(subject);

var registeredImplementations = result.RootElement.GetProperty("RegisteredImplementations");
registeredImplementations.TryGetProperty("Root", out _).ShouldBeTrue();
registeredImplementations.TryGetProperty("Hosting", out _).ShouldBeTrue();
registeredImplementations.TryGetProperty("Infrastructure", out _).ShouldBeTrue();
registeredImplementations.TryGetProperty("ResponseHandling", out _).ShouldBeTrue();
registeredImplementations.TryGetProperty("Services", out _).ShouldBeTrue();
registeredImplementations.TryGetProperty("Stores", out _).ShouldBeTrue();
registeredImplementations.TryGetProperty("Validation", out _).ShouldBeTrue();
}

[Fact]
public async Task WriteAsync_HandlesMultipleRegistrationsForAService()
{
var serviceCollection = new ServiceCollection()
.AddSingleton<IProfileService, DefaultProfileService>()
.AddSingleton<IProfileService, MockProfileService>();
var subject = new RegisteredImplementationsDiagnosticEntry(new ServiceCollectionAccessor(serviceCollection));

var result = await DiagnosticEntryTestHelper.WriteEntryToJson(subject);

var registeredImplementations = result.RootElement.GetProperty("RegisteredImplementations");
var services = registeredImplementations.GetProperty("Services");
var profileServiceEntry = services.EnumerateArray().ToList().SingleOrDefault(entry => entry.TryGetProperty(nameof(IProfileService), out _));
var firstAssemblyInfo = profileServiceEntry.GetProperty(nameof(IProfileService)).EnumerateArray().First();
var firstExpectedTypeInfo = typeof(DefaultProfileService);
firstAssemblyInfo.GetProperty("TypeName").GetString().ShouldBe(firstExpectedTypeInfo.FullName);
firstAssemblyInfo.GetProperty("Assembly").GetString().ShouldBe(firstExpectedTypeInfo.Assembly.GetName().Name);
firstAssemblyInfo.GetProperty("AssemblyVersion").GetString().ShouldBe(firstExpectedTypeInfo.Assembly.GetName().Version?.ToString());
var secondAssemblyInfo = profileServiceEntry.GetProperty(nameof(IProfileService)).EnumerateArray().Last();
var secondExpectedTypeInfo = typeof(MockProfileService);
secondAssemblyInfo.GetProperty("TypeName").GetString().ShouldBe(secondExpectedTypeInfo.FullName);
secondAssemblyInfo.GetProperty("Assembly").GetString().ShouldBe(secondExpectedTypeInfo.Assembly.GetName().Name);
secondAssemblyInfo.GetProperty("AssemblyVersion").GetString().ShouldBe(secondExpectedTypeInfo.Assembly.GetName().Version?.ToString());
}

[Fact]
public async Task WriteAsync_HandlesNoServiceRegisteredForInterface()
{
var subject = new RegisteredImplementationsDiagnosticEntry(new ServiceCollectionAccessor(new ServiceCollection()));

var result = await DiagnosticEntryTestHelper.WriteEntryToJson(subject);

var registeredImplementations = result.RootElement.GetProperty("RegisteredImplementations");
var services = registeredImplementations.GetProperty("Services");
var profileServiceEntry = services.EnumerateArray().ToList().SingleOrDefault(entry => entry.TryGetProperty(nameof(IProfileService), out _));
var assemblyInfo = profileServiceEntry.GetProperty(nameof(IProfileService)).EnumerateArray().First();
assemblyInfo.GetProperty("TypeName").GetString().ShouldBe("Not Registered");
assemblyInfo.GetProperty("Assembly").GetString().ShouldBe("Not Registered");
assemblyInfo.GetProperty("AssemblyVersion").GetString().ShouldBe("Not Registered");
}

[Fact]
public async Task WriteAsync_ShouldIncludeAllPublicInterfaces()
{
var interfaces = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(assembly => assembly.GetExportedTypes())
.Where(type => type.IsInterface && type.IsPublic && type.Namespace != null &&
type.Namespace.StartsWith(
"Duende.IdentityServer"))
.Select(type => type.Name);
var subject = new RegisteredImplementationsDiagnosticEntry(new ServiceCollectionAccessor(new ServiceCollection()));

var result = await DiagnosticEntryTestHelper.WriteEntryToJson(subject);

var registeredImplementations = result.RootElement.GetProperty("RegisteredImplementations");
var entries = registeredImplementations.EnumerateObject()
.SelectMany(property => property.Value.EnumerateArray()).Select(element => element.EnumerateObject().First().Name);
entries.ShouldBe(interfaces, ignoreOrder: true);
}
}
Loading