Skip to content

Commit c4854d6

Browse files
committed
Added diagnostic entry for resource info
1 parent 4a6c7df commit c4854d6

File tree

3 files changed

+200
-0
lines changed

3 files changed

+200
-0
lines changed

identity-server/src/IdentityServer/Configuration/DependencyInjection/BuilderExtensions/Core.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,8 @@ public static IIdentityServerBuilder AddCoreServices(this IIdentityServerBuilder
225225
builder.Services.AddSingleton<IDiagnosticEntry, EndpointUsageDiagnosticEntry>();
226226
builder.Services.AddSingleton<ClientLoadedTracker>();
227227
builder.Services.AddSingleton<IDiagnosticEntry, ClientInfoDiagnosticEntry>();
228+
builder.Services.AddSingleton<ResourceLoadedTracker>();
229+
builder.Services.AddSingleton<IDiagnosticEntry, ResourceInfoDiagnosticEntry>();
228230
builder.Services.AddSingleton<DiagnosticSummary>();
229231
builder.Services.AddHostedService<DiagnosticHostedService>();
230232

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Copyright (c) Duende Software. All rights reserved.
2+
// See LICENSE in the project root for license information.
3+
4+
using System.Text.Json;
5+
6+
namespace Duende.IdentityServer.Licensing.V2.Diagnostics.DiagnosticEntries;
7+
8+
internal class ResourceInfoDiagnosticEntry(ResourceLoadedTracker resourceLoadedTracker) : IDiagnosticEntry
9+
{
10+
public Task WriteAsync(Utf8JsonWriter writer)
11+
{
12+
writer.WriteStartObject("Resources");
13+
14+
var resourceGroups =
15+
resourceLoadedTracker.Resources.GroupBy(resource => resource.Value.Type, resource => resource.Value);
16+
foreach (var group in resourceGroups)
17+
{
18+
writer.WriteStartArray(group.Key);
19+
20+
if (group.Key == "ApiResource")
21+
{
22+
WriteApiResources(writer, group);
23+
}
24+
else
25+
{
26+
WriteSimpleResources(writer, group);
27+
}
28+
29+
writer.WriteEndArray();
30+
}
31+
32+
writer.WriteEndObject();
33+
34+
return Task.CompletedTask;
35+
}
36+
37+
private static void WriteApiResources(Utf8JsonWriter writer, IEnumerable<TrackedResource> apiResources)
38+
{
39+
foreach (var resource in apiResources)
40+
{
41+
writer.WriteStartObject();
42+
writer.WriteString("Name", resource.Name);
43+
writer.WriteBoolean("ResourceIndicatorRequired", resource.ResourceIndicatorRequired.GetValueOrDefault());
44+
writer.WriteStartArray("SecretTypes");
45+
foreach (var secretType in resource.SecretTypes ?? [])
46+
{
47+
writer.WriteStringValue(secretType);
48+
}
49+
50+
writer.WriteEndArray();
51+
52+
writer.WriteEndObject();
53+
}
54+
}
55+
56+
private static void WriteSimpleResources(Utf8JsonWriter writer, IEnumerable<TrackedResource> resources)
57+
{
58+
foreach (var resource in resources)
59+
{
60+
writer.WriteStringValue(resource.Name);
61+
}
62+
}
63+
}
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
// Copyright (c) Duende Software. All rights reserved.
2+
// See LICENSE in the project root for license information.
3+
4+
using Duende.IdentityModel.Client;
5+
using Duende.IdentityServer.Licensing.V2.Diagnostics;
6+
using Duende.IdentityServer.Licensing.V2.Diagnostics.DiagnosticEntries;
7+
using Duende.IdentityServer.Models;
8+
using IdentityServer.UnitTests.Licensing.V2.DiagnosticEntries;
9+
10+
namespace IdentityServer.UnitTests.Licensing.v2.DiagnosticEntries;
11+
12+
public class ResourceInfoDiagnosticEntryTests
13+
{
14+
[Fact]
15+
public async Task Should_Write_Resource_Info()
16+
{
17+
var tracker = new ResourceLoadedTracker();
18+
tracker.TrackResources(new Resources
19+
{
20+
ApiResources = new List<ApiResource>
21+
{
22+
new("TestApi", "Test API")
23+
{
24+
RequireResourceIndicator = true,
25+
ApiSecrets = new List<Secret> { new Secret { Type = "Test" } }
26+
}
27+
},
28+
IdentityResources = new List<IdentityResource>
29+
{
30+
new("TestIdentity", ["IrrelevantClaim"])
31+
},
32+
ApiScopes = new List<ApiScope>
33+
{
34+
new("TestScope", "Test Scope")
35+
}
36+
});
37+
var subject = new ResourceInfoDiagnosticEntry(tracker);
38+
39+
var result = await DiagnosticEntryTestHelper.WriteEntryToJson(subject);
40+
41+
var resources = result.RootElement.GetProperty("Resources");
42+
var apiResources = resources.GetProperty("ApiResource").EnumerateArray();
43+
var apiResource = apiResources.First();
44+
apiResource.GetProperty("Name").GetString().ShouldBe("TestApi");
45+
apiResource.GetProperty("ResourceIndicatorRequired").GetBoolean().ShouldBeTrue();
46+
apiResource.TryGetStringArray("SecretTypes").ShouldBe(["Test"]);
47+
resources.TryGetStringArray("IdentityResource").ShouldBe(["TestIdentity"]);
48+
resources.TryGetStringArray("ApiScope").ShouldBe(["TestScope"]);
49+
}
50+
51+
[Fact]
52+
public async Task Should_Write_Empty_Resource_Info()
53+
{
54+
var tracker = new ResourceLoadedTracker();
55+
var subject = new ResourceInfoDiagnosticEntry(tracker);
56+
57+
var result = await DiagnosticEntryTestHelper.WriteEntryToJson(subject);
58+
59+
result.RootElement.GetProperty("Resources").GetRawText().ShouldBe("{}");
60+
}
61+
62+
[Fact]
63+
public async Task Should_Write_Resource_Info_With_Empty_Secret_Types()
64+
{
65+
var tracker = new ResourceLoadedTracker();
66+
tracker.TrackResources(new Resources
67+
{
68+
ApiResources = new List<ApiResource>
69+
{
70+
new("TestApi", "Test API")
71+
{
72+
RequireResourceIndicator = true,
73+
ApiSecrets = new List<Secret>()
74+
}
75+
}
76+
});
77+
var subject = new ResourceInfoDiagnosticEntry(tracker);
78+
79+
var result = await DiagnosticEntryTestHelper.WriteEntryToJson(subject);
80+
81+
var resources = result.RootElement.GetProperty("Resources");
82+
var apiResources = resources.GetProperty("ApiResource").EnumerateArray();
83+
var apiResource = apiResources.First();
84+
apiResource.GetProperty("Name").GetString().ShouldBe("TestApi");
85+
apiResource.GetProperty("ResourceIndicatorRequired").GetBoolean().ShouldBeTrue();
86+
apiResource.TryGetStringArray("SecretTypes").ShouldBe([]);
87+
}
88+
89+
[Fact]
90+
public async Task Should_Write_Multiple_Resources()
91+
{
92+
var tracker = new ResourceLoadedTracker();
93+
tracker.TrackResources(new Resources
94+
{
95+
ApiResources = new List<ApiResource>
96+
{
97+
new("ApiResourceOne", "Test API 1")
98+
{
99+
RequireResourceIndicator = true
100+
},
101+
new("ApiResourceTwo", "Test API 2")
102+
{
103+
RequireResourceIndicator = false,
104+
ApiSecrets = new List<Secret> { new() { Type = "SecretType" } }
105+
}
106+
},
107+
IdentityResources = new List<IdentityResource>
108+
{
109+
new("IdentityResourceOne", ["Claim1"]),
110+
new("IdentityResourceTwo", ["Claim2"])
111+
},
112+
ApiScopes = new List<ApiScope>
113+
{
114+
new("ApiScopeOne", "Test Scope 1"),
115+
new("ApiScopeTwo", "Test Scope 2")
116+
}
117+
});
118+
var subject = new ResourceInfoDiagnosticEntry(tracker);
119+
120+
var result = await DiagnosticEntryTestHelper.WriteEntryToJson(subject);
121+
122+
var resources = result.RootElement.GetProperty("Resources");
123+
var apiResources = resources.GetProperty("ApiResource").EnumerateArray().OrderBy(resource => resource.GetProperty("Name").GetString()).ToList();
124+
var firstApiResource = apiResources.First();
125+
firstApiResource.GetProperty("Name").GetString().ShouldBe("ApiResourceOne");
126+
firstApiResource.GetProperty("ResourceIndicatorRequired").GetBoolean().ShouldBeTrue();
127+
firstApiResource.TryGetStringArray("SecretTypes").ShouldBe([]);
128+
var secondApiResource = apiResources.Last();
129+
secondApiResource.GetProperty("Name").GetString().ShouldBe("ApiResourceTwo");
130+
secondApiResource.GetProperty("ResourceIndicatorRequired").GetBoolean().ShouldBeFalse();
131+
secondApiResource.TryGetStringArray("SecretTypes").ShouldBe(["SecretType"]);
132+
resources.TryGetStringArray("IdentityResource").ShouldBe(["IdentityResourceOne", "IdentityResourceTwo"], ignoreOrder: true);
133+
resources.TryGetStringArray("ApiScope").ShouldBe(["ApiScopeOne", "ApiScopeTwo"], ignoreOrder: true);
134+
}
135+
}

0 commit comments

Comments
 (0)