mirror of
https://github.com/DuendeSoftware/products
synced 2026-05-24 09:28:24 +00:00
Merge pull request #2271 from DuendeSoftware/ev/bff/update-bff-3.1.x-to-main
Ensure BFF V3.1.x release branch is up to date with main.
This commit is contained in:
commit
fc63bc0ef3
57 changed files with 1240 additions and 282 deletions
2
.github/workflow-gen/Program.cs
vendored
2
.github/workflow-gen/Program.cs
vendored
|
|
@ -95,7 +95,7 @@ void GenerateCiWorkflow(Product product)
|
|||
.Job(BuildJobId)
|
||||
.RunEitherOnBranchOrAsPR()
|
||||
.Name("Build and test (unit)")
|
||||
.RunsOn(GitHubHostedRunners.UbuntuLatest)
|
||||
.RunsOn("large", ["ubuntu-latest-x64-16core"])
|
||||
.Defaults().Run("bash", product.Name)
|
||||
.Job;
|
||||
|
||||
|
|
|
|||
2
.github/workflow-gen/StepExtensions.cs
vendored
2
.github/workflow-gen/StepExtensions.cs
vendored
|
|
@ -18,7 +18,7 @@ public static class StepExtensions
|
|||
|
||||
job.Step()
|
||||
.Name("Setup .NET")
|
||||
.ActionsSetupDotNet("3e891b0cb619bf60e2c25674b222b8940e2c1c25", ["8.0.x", "9.0.203", "10.0.100-rc.2.25502.107"]);
|
||||
.ActionsSetupDotNet("3e891b0cb619bf60e2c25674b222b8940e2c1c25", ["8.0.x", "9.0.203", "10.0.100"]);
|
||||
// v4.1.0
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ jobs:
|
|||
dotnet-version: |-
|
||||
8.0.x
|
||||
9.0.203
|
||||
10.0.100-rc.2.25502.107
|
||||
10.0.100
|
||||
- name: Restore
|
||||
run: dotnet restore aspnetcore-authentication-jwtbearer.slnf
|
||||
- name: Verify Formatting
|
||||
|
|
@ -61,7 +61,9 @@ jobs:
|
|||
build:
|
||||
name: Build and test (unit)
|
||||
if: (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) || (github.event_name == 'push') || (github.event_name == 'workflow_dispatch')
|
||||
runs-on: ubuntu-latest
|
||||
runs-on:
|
||||
group: large
|
||||
labels: [ubuntu-latest-x64-16core]
|
||||
permissions:
|
||||
actions: read
|
||||
checks: write
|
||||
|
|
@ -85,7 +87,7 @@ jobs:
|
|||
dotnet-version: |-
|
||||
8.0.x
|
||||
9.0.203
|
||||
10.0.100-rc.2.25502.107
|
||||
10.0.100
|
||||
- name: Restore
|
||||
run: dotnet restore aspnetcore-authentication-jwtbearer.slnf
|
||||
- name: Build
|
||||
|
|
@ -174,7 +176,7 @@ jobs:
|
|||
dotnet-version: |-
|
||||
8.0.x
|
||||
9.0.203
|
||||
10.0.100-rc.2.25502.107
|
||||
10.0.100
|
||||
- name: Tool restore
|
||||
run: dotnet tool restore
|
||||
- name: Pack aspnetcore-authentication-jwtbearer.slnf
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ jobs:
|
|||
dotnet-version: |-
|
||||
8.0.x
|
||||
9.0.203
|
||||
10.0.100-rc.2.25502.107
|
||||
10.0.100
|
||||
- name: Pack aspnetcore-authentication-jwtbearer.slnf
|
||||
run: dotnet pack -c Release aspnetcore-authentication-jwtbearer.slnf -o artifacts
|
||||
- name: Tool restore
|
||||
|
|
@ -110,7 +110,7 @@ jobs:
|
|||
dotnet-version: |-
|
||||
8.0.x
|
||||
9.0.203
|
||||
10.0.100-rc.2.25502.107
|
||||
10.0.100
|
||||
- name: List files
|
||||
run: tree
|
||||
shell: bash
|
||||
|
|
|
|||
12
.github/workflows/bff-ci.yml
vendored
12
.github/workflows/bff-ci.yml
vendored
|
|
@ -53,7 +53,7 @@ jobs:
|
|||
dotnet-version: |-
|
||||
8.0.x
|
||||
9.0.203
|
||||
10.0.100-rc.2.25502.107
|
||||
10.0.100
|
||||
- name: Restore
|
||||
run: dotnet restore bff.slnf
|
||||
- name: Verify Formatting
|
||||
|
|
@ -61,7 +61,9 @@ jobs:
|
|||
build:
|
||||
name: Build and test (unit)
|
||||
if: (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) || (github.event_name == 'push') || (github.event_name == 'workflow_dispatch')
|
||||
runs-on: ubuntu-latest
|
||||
runs-on:
|
||||
group: large
|
||||
labels: [ubuntu-latest-x64-16core]
|
||||
permissions:
|
||||
actions: read
|
||||
checks: write
|
||||
|
|
@ -85,7 +87,7 @@ jobs:
|
|||
dotnet-version: |-
|
||||
8.0.x
|
||||
9.0.203
|
||||
10.0.100-rc.2.25502.107
|
||||
10.0.100
|
||||
- name: Restore
|
||||
run: dotnet restore bff.slnf
|
||||
- name: Build
|
||||
|
|
@ -134,7 +136,7 @@ jobs:
|
|||
dotnet-version: |-
|
||||
8.0.x
|
||||
9.0.203
|
||||
10.0.100-rc.2.25502.107
|
||||
10.0.100
|
||||
- name: Restore
|
||||
run: dotnet restore bff.slnf
|
||||
- name: Build
|
||||
|
|
@ -221,7 +223,7 @@ jobs:
|
|||
dotnet-version: |-
|
||||
8.0.x
|
||||
9.0.203
|
||||
10.0.100-rc.2.25502.107
|
||||
10.0.100
|
||||
- name: Tool restore
|
||||
run: dotnet tool restore
|
||||
- name: Pack bff.slnf
|
||||
|
|
|
|||
4
.github/workflows/bff-release.yml
vendored
4
.github/workflows/bff-release.yml
vendored
|
|
@ -68,7 +68,7 @@ jobs:
|
|||
dotnet-version: |-
|
||||
8.0.x
|
||||
9.0.203
|
||||
10.0.100-rc.2.25502.107
|
||||
10.0.100
|
||||
- name: Pack bff.slnf
|
||||
run: dotnet pack -c Release bff.slnf -o artifacts
|
||||
- name: Tool restore
|
||||
|
|
@ -110,7 +110,7 @@ jobs:
|
|||
dotnet-version: |-
|
||||
8.0.x
|
||||
9.0.203
|
||||
10.0.100-rc.2.25502.107
|
||||
10.0.100
|
||||
- name: List files
|
||||
run: tree
|
||||
shell: bash
|
||||
|
|
|
|||
10
.github/workflows/docs-mcp-ci.yml
vendored
10
.github/workflows/docs-mcp-ci.yml
vendored
|
|
@ -53,7 +53,7 @@ jobs:
|
|||
dotnet-version: |-
|
||||
8.0.x
|
||||
9.0.203
|
||||
10.0.100-rc.2.25502.107
|
||||
10.0.100
|
||||
- name: Restore
|
||||
run: dotnet restore docs-mcp.slnf
|
||||
- name: Verify Formatting
|
||||
|
|
@ -61,7 +61,9 @@ jobs:
|
|||
build:
|
||||
name: Build and test (unit)
|
||||
if: (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) || (github.event_name == 'push') || (github.event_name == 'workflow_dispatch')
|
||||
runs-on: ubuntu-latest
|
||||
runs-on:
|
||||
group: large
|
||||
labels: [ubuntu-latest-x64-16core]
|
||||
permissions:
|
||||
actions: read
|
||||
checks: write
|
||||
|
|
@ -85,7 +87,7 @@ jobs:
|
|||
dotnet-version: |-
|
||||
8.0.x
|
||||
9.0.203
|
||||
10.0.100-rc.2.25502.107
|
||||
10.0.100
|
||||
- name: Restore
|
||||
run: dotnet restore docs-mcp.slnf
|
||||
- name: Build
|
||||
|
|
@ -160,7 +162,7 @@ jobs:
|
|||
dotnet-version: |-
|
||||
8.0.x
|
||||
9.0.203
|
||||
10.0.100-rc.2.25502.107
|
||||
10.0.100
|
||||
- name: Tool restore
|
||||
run: dotnet tool restore
|
||||
- name: Pack docs-mcp.slnf
|
||||
|
|
|
|||
4
.github/workflows/docs-mcp-release.yml
vendored
4
.github/workflows/docs-mcp-release.yml
vendored
|
|
@ -68,7 +68,7 @@ jobs:
|
|||
dotnet-version: |-
|
||||
8.0.x
|
||||
9.0.203
|
||||
10.0.100-rc.2.25502.107
|
||||
10.0.100
|
||||
- name: Pack docs-mcp.slnf
|
||||
run: dotnet pack -c Release docs-mcp.slnf -o artifacts
|
||||
- name: Tool restore
|
||||
|
|
@ -110,7 +110,7 @@ jobs:
|
|||
dotnet-version: |-
|
||||
8.0.x
|
||||
9.0.203
|
||||
10.0.100-rc.2.25502.107
|
||||
10.0.100
|
||||
- name: List files
|
||||
run: tree
|
||||
shell: bash
|
||||
|
|
|
|||
12
.github/workflows/identity-server-ci.yml
vendored
12
.github/workflows/identity-server-ci.yml
vendored
|
|
@ -53,7 +53,7 @@ jobs:
|
|||
dotnet-version: |-
|
||||
8.0.x
|
||||
9.0.203
|
||||
10.0.100-rc.2.25502.107
|
||||
10.0.100
|
||||
- name: Restore
|
||||
run: dotnet restore identity-server.slnf
|
||||
- name: Verify Formatting
|
||||
|
|
@ -61,7 +61,9 @@ jobs:
|
|||
build:
|
||||
name: Build and test (unit)
|
||||
if: (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) || (github.event_name == 'push') || (github.event_name == 'workflow_dispatch')
|
||||
runs-on: ubuntu-latest
|
||||
runs-on:
|
||||
group: large
|
||||
labels: [ubuntu-latest-x64-16core]
|
||||
permissions:
|
||||
actions: read
|
||||
checks: write
|
||||
|
|
@ -85,7 +87,7 @@ jobs:
|
|||
dotnet-version: |-
|
||||
8.0.x
|
||||
9.0.203
|
||||
10.0.100-rc.2.25502.107
|
||||
10.0.100
|
||||
- name: Restore
|
||||
run: dotnet restore identity-server.slnf
|
||||
- name: Build
|
||||
|
|
@ -148,7 +150,7 @@ jobs:
|
|||
dotnet-version: |-
|
||||
8.0.x
|
||||
9.0.203
|
||||
10.0.100-rc.2.25502.107
|
||||
10.0.100
|
||||
- name: Restore
|
||||
run: dotnet restore identity-server.slnf
|
||||
- name: Build
|
||||
|
|
@ -235,7 +237,7 @@ jobs:
|
|||
dotnet-version: |-
|
||||
8.0.x
|
||||
9.0.203
|
||||
10.0.100-rc.2.25502.107
|
||||
10.0.100
|
||||
- name: Tool restore
|
||||
run: dotnet tool restore
|
||||
- name: Pack identity-server.slnf
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ jobs:
|
|||
dotnet-version: |-
|
||||
8.0.x
|
||||
9.0.203
|
||||
10.0.100-rc.2.25502.107
|
||||
10.0.100
|
||||
- name: Pack identity-server.slnf
|
||||
run: dotnet pack -c Release identity-server.slnf -o artifacts
|
||||
- name: Tool restore
|
||||
|
|
@ -110,7 +110,7 @@ jobs:
|
|||
dotnet-version: |-
|
||||
8.0.x
|
||||
9.0.203
|
||||
10.0.100-rc.2.25502.107
|
||||
10.0.100
|
||||
- name: List files
|
||||
run: tree
|
||||
shell: bash
|
||||
|
|
|
|||
4
.github/workflows/templates-release.yml
vendored
4
.github/workflows/templates-release.yml
vendored
|
|
@ -46,7 +46,7 @@ jobs:
|
|||
dotnet-version: |-
|
||||
8.0.x
|
||||
9.0.203
|
||||
10.0.100-rc.2.25502.107
|
||||
10.0.100
|
||||
- name: Checkout target branch
|
||||
if: github.event.inputs.branch != 'main'
|
||||
run: git checkout ${{ github.event.inputs.branch }}
|
||||
|
|
@ -110,7 +110,7 @@ jobs:
|
|||
dotnet-version: |-
|
||||
8.0.x
|
||||
9.0.203
|
||||
10.0.100-rc.2.25502.107
|
||||
10.0.100
|
||||
- name: List files
|
||||
run: tree
|
||||
shell: bash
|
||||
|
|
|
|||
|
|
@ -1,59 +1,56 @@
|
|||
<!-- We have agreed to explicitly target where possible version 10 of the package(s)
|
||||
that supports the target frameworks our products target (8, 9, 10) -->
|
||||
<Project>
|
||||
<PropertyGroup Condition=" '$(TargetFramework)' == 'net8.0'">
|
||||
<FrameworkVersion>8.0.1</FrameworkVersion>
|
||||
<!-- Package Versions Note:
|
||||
These versions are here because they don't explicitly have a version 10 of the package that supports .NET 8, 9 and 10 -->
|
||||
<EFCoreVersion>9.0.9</EFCoreVersion>
|
||||
<IdentityEFCoreVersion>8.0.20</IdentityEFCoreVersion>
|
||||
<RuntimeCompilationVersion>8.0.20</RuntimeCompilationVersion>
|
||||
<MvcRazorRuntimeCompilationVersion>8.0.20</MvcRazorRuntimeCompilationVersion>
|
||||
<AuthenticationCertificateVersion>8.0.16</AuthenticationCertificateVersion>
|
||||
<!-- /End Note -->
|
||||
<IdentityModelVersion>7.1.2</IdentityModelVersion>
|
||||
<CachingMemoryVersion>9.0.3</CachingMemoryVersion>
|
||||
<SystemTextJsonVersion>9.0.9</SystemTextJsonVersion>
|
||||
<LoggingAbstractionsVersion>9.0.9</LoggingAbstractionsVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(TargetFramework)' == 'net9.0'">
|
||||
<FrameworkVersion>9.0.3</FrameworkVersion>
|
||||
<EFCoreVersion>9.0.9</EFCoreVersion>
|
||||
<IdentityEFCoreVersion>9.0.3</IdentityEFCoreVersion>
|
||||
<RuntimeCompilationVersion>9.0.3</RuntimeCompilationVersion>
|
||||
<MvcRazorRuntimeCompilationVersion>9.0.3</MvcRazorRuntimeCompilationVersion>
|
||||
<AuthenticationCertificateVersion>9.0.3</AuthenticationCertificateVersion>
|
||||
<IdentityModelVersion>8.0.1</IdentityModelVersion>
|
||||
<CachingMemoryVersion>9.0.3</CachingMemoryVersion>
|
||||
<SystemTextJsonVersion>9.0.9</SystemTextJsonVersion>
|
||||
<LoggingAbstractionsVersion>9.0.9</LoggingAbstractionsVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(TargetFramework)' == 'net10.0'">
|
||||
<FrameworkVersion>10.0.0-rc.2.25502.107</FrameworkVersion>
|
||||
<EFCoreVersion>10.0.0-rc.2.25502.107</EFCoreVersion>
|
||||
<IdentityEFCoreVersion>10.0.0-rc.2.25502.107</IdentityEFCoreVersion>
|
||||
<RuntimeCompilationVersion>10.0.0-rc.2.25502.107</RuntimeCompilationVersion>
|
||||
<AuthenticationCertificateVersion>10.0.0-rc.2.25502.107</AuthenticationCertificateVersion>
|
||||
<FrameworkVersion>10.0.0</FrameworkVersion>
|
||||
<EFCoreVersion>10.0.0</EFCoreVersion>
|
||||
<IdentityEFCoreVersion>10.0.0</IdentityEFCoreVersion>
|
||||
<MvcRazorRuntimeCompilationVersion>10.0.0</MvcRazorRuntimeCompilationVersion>
|
||||
<AuthenticationCertificateVersion>10.0.0</AuthenticationCertificateVersion>
|
||||
<IdentityModelVersion>8.0.1</IdentityModelVersion>
|
||||
<CachingMemoryVersion>10.0.0-rc.2.25502.107</CachingMemoryVersion>
|
||||
<SystemTextJsonVersion>10.0.0-rc.2.25502.107</SystemTextJsonVersion>
|
||||
<LoggingAbstractionsVersion>10.0.0-rc.2.25502.107</LoggingAbstractionsVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="AngleSharp" Version="1.1.2" />
|
||||
<PackageVersion Include="Aspire.Hosting.AppHost" Version="9.5.0" />
|
||||
<PackageVersion Include="Aspire.Hosting.Testing" Version="9.5.0" />
|
||||
<PackageVersion Include="Aspire.Hosting.SqlServer" Version="9.5.0" />
|
||||
<PackageVersion Include="Aspire.Hosting.AppHost" Version="13.0.0" />
|
||||
<PackageVersion Include="Aspire.Hosting.Testing" Version="13.0.0" />
|
||||
<PackageVersion Include="Aspire.Hosting.SqlServer" Version="13.0.0" />
|
||||
<PackageVersion Include="BenchmarkDotNet" Version="0.15.0" />
|
||||
<PackageVersion Include="BullsEye" Version="5.0.0" />
|
||||
<PackageVersion Include="coverlet.collector" Version="6.0.2" />
|
||||
<!-- Added aspire transitive package to resolve package vulnerability -->
|
||||
<PackageVersion Include="KubernetesClient" Version="17.0.14" />
|
||||
<PackageVersion Include="Duende.AccessTokenManagement" Version="[3.3.0-preview.1, 4.0.0)" Condition="'$(IsBffProject)' == 'true'" />
|
||||
<PackageVersion Include="Duende.AccessTokenManagement" Version="4.1.0-preview.2" Condition="'$(IsBffProject)' != 'true'" />
|
||||
<PackageVersion Include="Duende.AccessTokenManagement.OpenIdConnect" Version="[3.3.0-preview.1, 4.0.0)" Condition="'$(IsBffProject)' == 'true'" />
|
||||
<PackageVersion Include="Duende.AccessTokenManagement.OpenIdConnect" Version="4.1.0-preview.2" Condition="'$(IsBffProject)' != 'true'" />
|
||||
<PackageVersion Include="Duende.AccessTokenManagement" Version="[3.3.0-rc.1, 4.0.0)" Condition="'$(IsBffProject)' == 'true'" />
|
||||
<PackageVersion Include="Duende.AccessTokenManagement" Version="4.1.0-rc.1" Condition="'$(IsBffProject)' != 'true'" />
|
||||
<PackageVersion Include="Duende.AccessTokenManagement.OpenIdConnect" Version="[3.3.0-rc.1, 4.0.0)" Condition="'$(IsBffProject)' == 'true'" />
|
||||
<PackageVersion Include="Duende.AccessTokenManagement.OpenIdConnect" Version="4.1.0-rc.1" Condition="'$(IsBffProject)' != 'true'" />
|
||||
<PackageVersion Include="Duende.AspNetCore.Authentication.JwtBearer" Version="0.1.3" />
|
||||
<PackageVersion Include="Duende.IdentityModel" Version="8.0.0-preview.1" />
|
||||
<PackageVersion Include="Duende.IdentityModel.OidcClient" Version="7.0.0-preview.2" />
|
||||
<PackageVersion Include="Duende.IdentityModel" Version="8.0.0-rc.1" />
|
||||
<PackageVersion Include="Duende.IdentityModel.OidcClient" Version="7.0.0-rc.1" />
|
||||
<PackageVersion Include="Duende.IdentityServer" Version="7.4.0-preview.2" />
|
||||
<PackageVersion Include="Duende.Private.Licensing" Version="1.0.0" />
|
||||
<PackageVersion Include="IdentityModel.AspNetCore.OAuth2Introspection" Version="6.2.0" />
|
||||
<PackageVersion Include="Markdig" Version="0.42.0" />
|
||||
<PackageVersion Include="Meziantou.Extensions.Logging.Xunit" Version="1.0.8" />
|
||||
<!-- Review Packages -->
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Authentication.Certificate" Version="$(AuthenticationCertificateVersion)" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="$(FrameworkVersion)" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="$(FrameworkVersion)" />
|
||||
|
|
@ -61,10 +58,9 @@
|
|||
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="$(FrameworkVersion)" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="$(FrameworkVersion)" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Components.Authorization" Version="$(FrameworkVersion)" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.DataProtection.Abstractions" Version="$(FrameworkVersion)" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Identity" Version="$(FrameworkVersion)" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.DataProtection.Abstractions" Version="10.0.0" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="$(IdentityEFCoreVersion)" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="$(RuntimeCompilationVersion)" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="$(MvcRazorRuntimeCompilationVersion)" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="$(FrameworkVersion)" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.TestHost" Version="$(FrameworkVersion)" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.WebUtilities" Version="2.2.0" />
|
||||
|
|
@ -75,31 +71,27 @@
|
|||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="$(EFCoreVersion)" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="$(EFCoreVersion)" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="$(EFCoreVersion)" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="$(FrameworkVersion)" />
|
||||
<!-- TODO - Upgrade hybrid cache-->
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="$(CachingMemoryVersion)" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Hybrid" Version="9.3.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="$(FrameworkVersion)" />
|
||||
<!-- TODO - Upgrade diagnostics.testing-->
|
||||
<PackageVersion Include="Microsoft.Extensions.Diagnostics.Testing" Version="8.10.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.FileProviders.Embedded" Version="$(FrameworkVersion)" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Http" Version="$(FrameworkVersion)" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Http.Polly" Version="$(FrameworkVersion)" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Http.Resilience" Version="9.9.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="$(FrameworkVersion)" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging" Version="$(LoggingAbstractionsVersion)" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="$(LoggingAbstractionsVersion)" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="$(LoggingAbstractionsVersion)" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Options" Version="$(FrameworkVersion)" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="$(FrameworkVersion)" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Primitives" Version="$(FrameworkVersion)" />
|
||||
<!-- TODO - Upgrade service discovery and timeprovider.testing-->
|
||||
<PackageVersion Include="Microsoft.Extensions.ServiceDiscovery" Version="9.1.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.TimeProvider.Testing" Version="9.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="10.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="10.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Hybrid" Version="10.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Diagnostics.Testing" Version="10.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.FileProviders.Embedded" Version="10.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Http" Version="10.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Http.Polly" Version="10.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Http.Resilience" Version="10.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="10.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging" Version="10.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="10.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Options" Version="10.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="10.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Primitives" Version="10.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.ServiceDiscovery" Version="10.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.TimeProvider.Testing" Version="10.0.0" />
|
||||
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="$(IdentityModelVersion)" />
|
||||
<PackageVersion Include="Microsoft.IdentityModel.Logging" Version="$(IdentityModelVersion)" />
|
||||
<PackageVersion Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" Version="$(IdentityModelVersion)" />
|
||||
<PackageVersion Include="Microsoft.Net.Http.Headers" Version="9.0.6" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||
<PackageVersion Include="Microsoft.NETCore.Jit" Version="2.0.8" />
|
||||
<PackageVersion Include="Microsoft.Playwright.Xunit" Version="1.50.0" />
|
||||
|
|
@ -133,9 +125,11 @@
|
|||
<PackageVersion Include="Shouldly" Version="4.2.1" />
|
||||
<PackageVersion Include="SimpleExec" Version="12.0.0" />
|
||||
<PackageVersion Include="SimpleFeedReader" Version="2.0.4" />
|
||||
<PackageVersion Include="Spectre.Console.Cli" Version="0.53.0" />
|
||||
<PackageVersion Include="Spectre.Console.Json" Version="0.53.0" />
|
||||
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="$(IdentityModelVersion)" />
|
||||
<PackageVersion Include="System.Net.Http" Version="4.3.4" />
|
||||
<PackageVersion Include="System.Text.Json" Version="$(SystemTextJsonVersion)" />
|
||||
<PackageVersion Include="System.Text.Json" Version="10.0.0" />
|
||||
<PackageVersion Include="System.Text.RegularExpressions" Version="4.3.1" />
|
||||
<PackageVersion Include="xunit.core" Version="2.9.3" />
|
||||
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.2" />
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<Sdk Name="Aspire.AppHost.Sdk" Version="9.5.0" />
|
||||
<Sdk Name="Aspire.AppHost.Sdk" Version="9.5.2" />
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
|
|
|
|||
2
global.json
vendored
2
global.json
vendored
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"sdk": {
|
||||
"version": "10.0.100-rc.2.25502.107",
|
||||
"version": "10.0.100",
|
||||
"rollForward": "latestMajor",
|
||||
"allowPrerelease": true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<Sdk Name="Aspire.AppHost.Sdk" Version="9.5.0" />
|
||||
<Sdk Name="Aspire.AppHost.Sdk" Version="9.5.2" />
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
|
|
@ -58,6 +58,9 @@
|
|||
<ProjectReference Include="..\..\..\hosts\net9\AspNetIdentity9\Host.AspNetIdentity9.csproj" />
|
||||
<ProjectReference Include="..\..\..\hosts\net9\EntityFramework9\Host.EntityFramework9.csproj" />
|
||||
<ProjectReference Include="..\..\..\hosts\net9\Main9\Host.Main9.csproj" />
|
||||
<ProjectReference Include="..\..\..\hosts\net10\AspNetIdentity10\Host.AspNetIdentity10.csproj" />
|
||||
<ProjectReference Include="..\..\..\hosts\net10\EntityFramework10\Host.EntityFramework10.csproj" />
|
||||
<ProjectReference Include="..\..\..\hosts\net10\Main10\Host.Main10.csproj" />
|
||||
<ProjectReference Include="..\..\..\migrations\AspNetIdentityDb\AspNetIdentityDb.csproj" />
|
||||
<ProjectReference Include="..\..\..\migrations\IdentityServerDb\IdentityServerDb.csproj" />
|
||||
<ProjectReference Include="..\..\..\templates\src\IdentityServerAspNetIdentity\IdentityServerAspNetIdentity.csproj" />
|
||||
|
|
|
|||
|
|
@ -46,13 +46,25 @@ void ConfigureIdentityServerHosts()
|
|||
projectRegistry.Add("is-host", hostMain);
|
||||
}
|
||||
|
||||
if (HostIsEnabled(nameof(Projects.Host_Main10)))
|
||||
{
|
||||
var hostMain = builder
|
||||
.AddProject<Projects.Host_Main10>("is-host")
|
||||
.WithHttpHealthCheck(path: "/.well-known/openid-configuration");
|
||||
|
||||
projectRegistry.Add("is-host", hostMain);
|
||||
}
|
||||
|
||||
|
||||
// These hosts require a database
|
||||
var dbHosts = new List<string>
|
||||
{
|
||||
nameof(Projects.Host_AspNetIdentity8),
|
||||
nameof(Projects.Host_AspNetIdentity9),
|
||||
nameof(Projects.Host_AspNetIdentity10),
|
||||
nameof(Projects.Host_EntityFramework8),
|
||||
nameof(Projects.Host_EntityFramework9)
|
||||
nameof(Projects.Host_EntityFramework9),
|
||||
nameof(Projects.Host_EntityFramework10)
|
||||
};
|
||||
|
||||
if (dbHosts.Any(HostIsEnabled))
|
||||
|
|
@ -100,6 +112,23 @@ void ConfigureIdentityServerHosts()
|
|||
projectRegistry.Add("is-host", hostAspNetIdentity);
|
||||
}
|
||||
|
||||
if (HostIsEnabled(nameof(Projects.Host_AspNetIdentity10)))
|
||||
{
|
||||
var hostAspNetIdentity = builder.AddProject<Projects.Host_AspNetIdentity10>(name: "is-host")
|
||||
.WithHttpHealthCheck(path: "/.well-known/openid-configuration")
|
||||
.WithReference(identityServerDb, connectionName: "DefaultConnection");
|
||||
|
||||
if (appConfig.RunDatabaseMigrations)
|
||||
{
|
||||
var aspnetMigration = builder.AddProject<Projects.AspNetIdentityDb>(name: "aspnetidentitydb-migrations")
|
||||
.WithReference(identityServerDb, connectionName: "DefaultConnection")
|
||||
.WaitFor(identityServerDb);
|
||||
hostAspNetIdentity.WaitForCompletion(aspnetMigration);
|
||||
}
|
||||
|
||||
projectRegistry.Add("is-host", hostAspNetIdentity);
|
||||
}
|
||||
|
||||
if (HostIsEnabled(nameof(Projects.Host_EntityFramework8)))
|
||||
{
|
||||
var hostEntityFramework = builder.AddProject<Projects.Host_EntityFramework8>(name: "is-host")
|
||||
|
|
@ -133,6 +162,23 @@ void ConfigureIdentityServerHosts()
|
|||
|
||||
projectRegistry.Add("is-host", hostEntityFramework);
|
||||
}
|
||||
|
||||
if (HostIsEnabled(nameof(Projects.Host_EntityFramework10)))
|
||||
{
|
||||
var hostEntityFramework = builder.AddProject<Projects.Host_EntityFramework10>(name: "is-host")
|
||||
.WithHttpHealthCheck(path: "/.well-known/openid-configuration")
|
||||
.WithReference(identityServerDb, connectionName: "DefaultConnection");
|
||||
|
||||
if (appConfig.RunDatabaseMigrations)
|
||||
{
|
||||
var idSrvMigration = builder.AddProject<Projects.IdentityServerDb>(name: "identityserverdb-migrations")
|
||||
.WithReference(identityServerDb, connectionName: "DefaultConnection")
|
||||
.WaitFor(identityServerDb);
|
||||
hostEntityFramework.WaitForCompletion(idSrvMigration);
|
||||
}
|
||||
|
||||
projectRegistry.Add("is-host", hostEntityFramework);
|
||||
}
|
||||
}
|
||||
|
||||
bool HostIsEnabled(string name) =>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
}
|
||||
},
|
||||
"AspireProjectConfiguration": {
|
||||
"IdentityHost": "Host_Main9",
|
||||
"IdentityHost": "Host_Main10",
|
||||
"UseClients": {
|
||||
"ConsoleCibaClient": false,
|
||||
"ConsoleClientCredentialsFlow": false,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<Sdk Name="Aspire.AppHost.Sdk" Version="9.5.0" />
|
||||
<Sdk Name="Aspire.AppHost.Sdk" Version="9.5.2" />
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@
|
|||
|
||||
<PackageReference Include="Duende.IdentityModel.OidcClient" />
|
||||
<PackageReference Include="Serilog.AspNetCore" />
|
||||
<PackageReference Include="Spectre.Console.Cli" />
|
||||
<PackageReference Include="Spectre.Console.Json" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
// Copyright (c) Duende Software. All rights reserved.
|
||||
// See LICENSE in the project root for license information.
|
||||
|
||||
namespace ConsoleResourceIndicators;
|
||||
|
||||
internal enum OutputMode
|
||||
{
|
||||
Verbose,
|
||||
Table
|
||||
}
|
||||
|
|
@ -1,23 +1,60 @@
|
|||
// Copyright (c) Duende Software. All rights reserved.
|
||||
// See LICENSE in the project root for license information.
|
||||
|
||||
using System.Buffers.Text;
|
||||
using System.Text;
|
||||
using Clients;
|
||||
using ConsoleResourceIndicators;
|
||||
using Duende.IdentityModel.Client;
|
||||
using Duende.IdentityModel.OidcClient;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Serilog;
|
||||
using Spectre.Console;
|
||||
|
||||
var builder = Host.CreateApplicationBuilder(args);
|
||||
|
||||
// Add ServiceDefaults from Aspire
|
||||
builder.AddServiceDefaults();
|
||||
|
||||
OidcClient _oidcClient;
|
||||
// Display banner
|
||||
AnsiConsole.Write(new Rule("[bold green]Resource Indicators Demo[/]").Centered());
|
||||
AnsiConsole.WriteLine();
|
||||
|
||||
"Resource Indicators Demo".ConsoleBox(ConsoleColor.Green);
|
||||
// Resolve the authority from the configuration
|
||||
var authority = builder.Configuration["is-host"]
|
||||
?? throw new InvalidOperationException("Authority configuration 'is-host' is missing.");
|
||||
|
||||
// Display important setup information
|
||||
var setupPanel = new Panel(
|
||||
new Markup($"[yellow]⚠[/] [bold]Before running tests:[/]\n" +
|
||||
$"[dim]→[/] Ensure your Identity Server is running at: [cyan]{authority}[/]\n" +
|
||||
$"[dim]→[/] Sign in to the Identity Server before starting tests\n" +
|
||||
$"[dim]→[/] This will allow tests to complete quickly and smoothly"))
|
||||
.Border(BoxBorder.Rounded)
|
||||
.BorderColor(Color.Yellow)
|
||||
.Header("[yellow]Setup Checklist[/]");
|
||||
|
||||
AnsiConsole.Write(setupPanel);
|
||||
AnsiConsole.WriteLine();
|
||||
|
||||
// Determine output mode based on whether console is interactive
|
||||
OutputMode mode;
|
||||
|
||||
if (Console.IsInputRedirected || Console.IsOutputRedirected || !Environment.UserInteractive)
|
||||
{
|
||||
// Non-interactive environment, use verbose mode by default
|
||||
AnsiConsole.MarkupLine("[dim]Running in non-interactive mode. Using verbose output.[/]");
|
||||
mode = OutputMode.Verbose;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Interactive environment, prompt user for output mode
|
||||
var outputMode = AnsiConsole.Prompt(
|
||||
new SelectionPrompt<string>()
|
||||
.Title("[cyan]Choose output mode:[/]")
|
||||
.AddChoices("Table View (Live Status)", "Verbose Output (Detailed)")
|
||||
.HighlightStyle(new Style(Color.Green)));
|
||||
|
||||
mode = outputMode.StartsWith("Table") ? OutputMode.Table : OutputMode.Verbose;
|
||||
}
|
||||
|
||||
AnsiConsole.WriteLine();
|
||||
|
||||
var testRunner = new TestRunner(authority, mode);
|
||||
|
||||
var testsToRun = new List<Test>
|
||||
{
|
||||
|
|
@ -31,138 +68,126 @@ var testsToRun = new List<Test>
|
|||
new() { Id = "8", Enabled = true, Scope = "resource1.scope1 resource2.scope1 resource3.scope1 shared.scope", Resources = ["urn:resource3"] },
|
||||
new() { Id = "9", Enabled = true, Scope = "resource3.scope1 offline_access", Resources = ["urn:resource3"] },
|
||||
new() { Id = "10", Enabled = true, Scope = "resource3.scope1", Resources = ["urn:resource3"] },
|
||||
new() { Id = "11", Enabled = true, Scope = "resource1.scope1 offline_access", Resources = ["urn:resource3"] },
|
||||
new() { Id = "12", Enabled = true, Scope = "shared.scope", Resources = ["urn:invalid"] }
|
||||
new() { Id = "11", Enabled = true, Scope = "resource1.scope1 offline_access", Resources = ["urn:resource3"], AccessTokenExpected = false },
|
||||
new() { Id = "12", Enabled = true, Scope = "shared.scope", Resources = ["urn:invalid"], AccessTokenExpected = false }
|
||||
};
|
||||
|
||||
foreach (var test in testsToRun.Where(t => t.Enabled))
|
||||
await testRunner.RunAllTestsAsync(testsToRun);
|
||||
|
||||
// Show summary
|
||||
AnsiConsole.WriteLine();
|
||||
AnsiConsole.Write(new Rule("[bold green]Test Summary[/]").Centered());
|
||||
|
||||
var summary = new Table()
|
||||
.Border(TableBorder.Rounded)
|
||||
.AddColumn("[bold]Status[/]")
|
||||
.AddColumn("[bold]Count[/]");
|
||||
|
||||
var completed = testsToRun.Count(t => t.Enabled && t.Status == TestStatus.Completed);
|
||||
var failed = testsToRun.Count(t => t.Enabled && t.Status == TestStatus.Failed);
|
||||
var total = testsToRun.Count(t => t.Enabled);
|
||||
|
||||
summary.AddRow("[green]Completed[/]", completed.ToString());
|
||||
summary.AddRow("[red]Failed[/]", failed.ToString());
|
||||
summary.AddRow("[cyan]Total[/]", total.ToString());
|
||||
|
||||
AnsiConsole.Write(summary);
|
||||
|
||||
// Show expected errors section
|
||||
var testsWithExpectedErrors = testsToRun
|
||||
.Where(t => t.Enabled && t.Result?.RefreshResults.Any(r => r.WasExpectedError) == true)
|
||||
.ToList();
|
||||
|
||||
if (testsWithExpectedErrors.Any())
|
||||
{
|
||||
var resources = test.Resources != null ? test.Resources.Aggregate((x, y) => $"{x}, {y}") : "-none-";
|
||||
($"Runing test: ({test.Id}) SCOPES: " + test.Scope + ", RESOURCES: " + resources).ConsoleBox(ConsoleColor.Green);
|
||||
AnsiConsole.WriteLine();
|
||||
AnsiConsole.Write(new Rule("[bold yellow]Expected Errors (By Design)[/]").Centered());
|
||||
AnsiConsole.WriteLine();
|
||||
AnsiConsole.MarkupLine("[dim]The following errors were expected as part of the test validation:[/]");
|
||||
AnsiConsole.WriteLine();
|
||||
|
||||
try
|
||||
var expectedErrorsTable = new Table()
|
||||
.Border(TableBorder.Rounded)
|
||||
.AddColumn("[bold]Test ID[/]")
|
||||
.AddColumn("[bold]Resource[/]")
|
||||
.AddColumn("[bold]Error[/]")
|
||||
.AddColumn("[bold]Reason[/]");
|
||||
|
||||
foreach (var test in testsWithExpectedErrors)
|
||||
{
|
||||
await FrontChannel(test.Scope, test.Resources);
|
||||
Thread.Sleep(millisecondsTimeout: 1000);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Exception: {ex.Message}");
|
||||
}
|
||||
}
|
||||
var expectedErrors = test.Result!.RefreshResults.Where(r => r.WasExpectedError).ToList();
|
||||
|
||||
// Exit the application
|
||||
"Exiting application...".ConsoleYellow();
|
||||
Environment.Exit(0);
|
||||
|
||||
async Task FrontChannel(string scope, IEnumerable<string> resource)
|
||||
{
|
||||
// Resolve the authority from the configuration.
|
||||
var authority = builder.Configuration["is-host"];
|
||||
|
||||
resource ??= [];
|
||||
|
||||
// create a redirect URI using an available port on the loopback address.
|
||||
// requires the OP to allow random ports on 127.0.0.1 - otherwise set a static port
|
||||
var browser = new SystemBrowser();
|
||||
var redirectUri = string.Format($"http://127.0.0.1:{browser.Port}");
|
||||
|
||||
var options = new OidcClientOptions
|
||||
{
|
||||
Authority = authority,
|
||||
|
||||
ClientId = "console.resource.indicators",
|
||||
|
||||
RedirectUri = redirectUri,
|
||||
Scope = scope,
|
||||
Resource = [.. resource],
|
||||
FilterClaims = false,
|
||||
LoadProfile = false,
|
||||
Browser = browser,
|
||||
|
||||
Policy =
|
||||
foreach (var error in expectedErrors)
|
||||
{
|
||||
RequireIdentityTokenSignature = false
|
||||
var reason = "Resource not configured for this test";
|
||||
|
||||
expectedErrorsTable.AddRow(
|
||||
test.Id,
|
||||
error.Resource.Replace("urn:", ""),
|
||||
$"[yellow]{error.Error ?? "unknown"}[/]",
|
||||
$"[dim]{reason}[/]"
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
var serilog = new LoggerConfiguration()
|
||||
.MinimumLevel.Warning()
|
||||
.Enrich.FromLogContext()
|
||||
.WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message}{NewLine}{Exception}{NewLine}")
|
||||
.CreateLogger();
|
||||
|
||||
options.LoggerFactory.AddSerilog(serilog);
|
||||
|
||||
_oidcClient = new OidcClient(options);
|
||||
var result = await _oidcClient.LoginAsync();
|
||||
|
||||
var parts = result.AccessToken.Split('.');
|
||||
var header = parts[0];
|
||||
var payload = parts[1];
|
||||
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Standard access token:");
|
||||
Console.WriteLine(Encoding.UTF8.GetString(Base64Url.DecodeFromChars(header)).PrettyPrintJson());
|
||||
Console.WriteLine(Encoding.UTF8.GetString(Base64Url.DecodeFromChars(payload)).PrettyPrintJson());
|
||||
|
||||
if (result.RefreshToken == null)
|
||||
{
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("No Refresh Token, exiting.");
|
||||
|
||||
Environment.Exit(0);
|
||||
}
|
||||
|
||||
await BackChannel(result);
|
||||
AnsiConsole.Write(expectedErrorsTable);
|
||||
}
|
||||
|
||||
async Task BackChannel(LoginResult result)
|
||||
// Show detailed results if available
|
||||
if (mode == OutputMode.Table)
|
||||
{
|
||||
Console.WriteLine("\n\n");
|
||||
Console.WriteLine("Refreshing with resource parameters");
|
||||
var testsWithResults = testsToRun.Where(t => t.Enabled && t.Result != null).ToList();
|
||||
|
||||
var resources = new List<string>() { "urn:resource1", "urn:resource2", "urn:resource3" };
|
||||
|
||||
foreach (var resource in resources)
|
||||
if (testsWithResults.Any())
|
||||
{
|
||||
$"Refreshing for resource: {resource}...".ConsoleGreen();
|
||||
await Refresh(result.RefreshToken, resource);
|
||||
AnsiConsole.WriteLine();
|
||||
AnsiConsole.Write(new Rule("[bold cyan]Detailed Test Results[/]").Centered());
|
||||
|
||||
Thread.Sleep(millisecondsTimeout: 500);
|
||||
}
|
||||
}
|
||||
var detailsTable = new Table()
|
||||
.Border(TableBorder.Rounded)
|
||||
.AddColumn("[bold]Test ID[/]")
|
||||
.AddColumn("[bold]Access Token[/]")
|
||||
.AddColumn("[bold]Refresh Token[/]")
|
||||
.AddColumn("[bold]Refresh Operations[/]");
|
||||
|
||||
async Task Refresh(string refreshToken, string resource)
|
||||
{
|
||||
var result = await _oidcClient.RefreshTokenAsync(refreshToken,
|
||||
new Parameters
|
||||
foreach (var test in testsWithResults)
|
||||
{
|
||||
{ "resource", resource }
|
||||
});
|
||||
var accessToken = test.Result!.AccessTokenReceived
|
||||
? "[green]✓ Received[/]"
|
||||
: test.AccessTokenExpected
|
||||
? "[red]✗ Not Received[/]"
|
||||
: "[green]✓ Not Expected[/]";
|
||||
|
||||
if (result.IsError)
|
||||
{
|
||||
Console.WriteLine();
|
||||
Console.WriteLine(result.Error);
|
||||
return;
|
||||
var refreshToken = test.Result.RefreshTokenReceived
|
||||
? "[green]✓ Received[/]"
|
||||
: test.RefreshTokenExpected
|
||||
? "[red]✗ Not Received[/]"
|
||||
: "[green]✓ Not Expected[/]";
|
||||
|
||||
var refreshOps = test.Result.RefreshResults.Any()
|
||||
? $"{test.Result.RefreshResults.Count(r => r.Success)}/{test.Result.RefreshResults.Count} successful"
|
||||
: "-";
|
||||
|
||||
detailsTable.AddRow(
|
||||
test.Id,
|
||||
accessToken,
|
||||
refreshToken,
|
||||
refreshOps
|
||||
);
|
||||
}
|
||||
|
||||
AnsiConsole.Write(detailsTable);
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("down-scoped access token:");
|
||||
|
||||
var parts = result.AccessToken.Split('.');
|
||||
var header = parts[0];
|
||||
var payload = parts[1];
|
||||
|
||||
Console.WriteLine(Encoding.UTF8.GetString(Base64Url.DecodeFromChars(header)).PrettyPrintJson());
|
||||
Console.WriteLine(Encoding.UTF8.GetString(Base64Url.DecodeFromChars(payload)).PrettyPrintJson());
|
||||
}
|
||||
|
||||
internal class Test
|
||||
// Exit prompt - only in interactive mode
|
||||
if (Environment.UserInteractive && !Console.IsInputRedirected)
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public bool Enabled { get; set; }
|
||||
public string Scope { get; set; }
|
||||
public IEnumerable<string> Resources { get; set; } = null;
|
||||
AnsiConsole.WriteLine();
|
||||
AnsiConsole.Markup("[dim]Press Enter to exit...[/]");
|
||||
Console.ReadLine();
|
||||
}
|
||||
else
|
||||
{
|
||||
Environment.Exit(0);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
// Copyright (c) Duende Software. All rights reserved.
|
||||
// See LICENSE in the project root for license information.
|
||||
|
||||
namespace ConsoleResourceIndicators;
|
||||
|
||||
internal enum TestStatus
|
||||
{
|
||||
Pending,
|
||||
Running,
|
||||
Completed,
|
||||
Failed
|
||||
}
|
||||
|
||||
internal class TestResult
|
||||
{
|
||||
public bool AccessTokenReceived { get; set; }
|
||||
public bool RefreshTokenReceived { get; set; }
|
||||
public List<RefreshResult> RefreshResults { get; set; } = [];
|
||||
}
|
||||
|
||||
internal class RefreshResult
|
||||
{
|
||||
public string Resource { get; set; } = string.Empty;
|
||||
public bool Success { get; set; }
|
||||
public string Error { get; set; }
|
||||
public bool WasExpectedError { get; set; }
|
||||
}
|
||||
|
||||
internal class Test
|
||||
{
|
||||
public string Id { get; set; } = string.Empty;
|
||||
public bool Enabled { get; set; }
|
||||
public bool AccessTokenExpected { get; set; } = true;
|
||||
public bool RefreshTokenExpected => Scope.Contains("offline_access") && AccessTokenExpected;
|
||||
public string Scope { get; set; } = string.Empty;
|
||||
public IEnumerable<string> Resources { get; set; } = [];
|
||||
public TestStatus Status { get; set; } = TestStatus.Pending;
|
||||
public string ErrorMessage { get; set; }
|
||||
public DateTime? StartTime { get; set; }
|
||||
public DateTime? EndTime { get; set; }
|
||||
public TestResult Result { get; set; }
|
||||
}
|
||||
|
|
@ -0,0 +1,360 @@
|
|||
// Copyright (c) Duende Software. All rights reserved.
|
||||
// See LICENSE in the project root for license information.
|
||||
|
||||
using System.Buffers.Text;
|
||||
using System.Text;
|
||||
using Duende.IdentityModel.Client;
|
||||
using Duende.IdentityModel.OidcClient;
|
||||
using Spectre.Console;
|
||||
using Spectre.Console.Json;
|
||||
|
||||
namespace ConsoleResourceIndicators;
|
||||
|
||||
internal class TestRunner(string authority, OutputMode outputMode)
|
||||
{
|
||||
private readonly string _authority = authority;
|
||||
private readonly OutputMode _outputMode = outputMode;
|
||||
private OidcClient _oidcClient;
|
||||
|
||||
private static readonly string[] RefreshResources = ["urn:resource1", "urn:resource2", "urn:resource3"];
|
||||
private const int DelayBetweenTestsMs = 1000;
|
||||
private const int DelayBetweenRefreshMs = 500;
|
||||
|
||||
public async Task RunAllTestsAsync(List<Test> tests)
|
||||
{
|
||||
if (_outputMode == OutputMode.Table)
|
||||
{
|
||||
await RunTestsWithTableAsync(tests);
|
||||
}
|
||||
else
|
||||
{
|
||||
await RunTestsVerboseAsync(tests);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RunTestsVerboseAsync(List<Test> tests)
|
||||
{
|
||||
foreach (var test in tests.Where(t => t.Enabled))
|
||||
{
|
||||
await RunTestVerboseAsync(test);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RunTestsWithTableAsync(List<Test> tests)
|
||||
{
|
||||
var enabledTests = tests.Where(t => t.Enabled).ToList();
|
||||
|
||||
await AnsiConsole.Live(CreateTestTable(enabledTests))
|
||||
.StartAsync(async ctx =>
|
||||
{
|
||||
foreach (var test in enabledTests)
|
||||
{
|
||||
test.Status = TestStatus.Running;
|
||||
test.StartTime = DateTime.Now;
|
||||
ctx.UpdateTarget(CreateTestTable(enabledTests));
|
||||
|
||||
try
|
||||
{
|
||||
await ExecuteTestAsync(test, verbose: false);
|
||||
test.Status = TestStatus.Completed;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
test.Status = TestStatus.Failed;
|
||||
test.ErrorMessage = ex.Message;
|
||||
}
|
||||
|
||||
test.EndTime = DateTime.Now;
|
||||
ctx.UpdateTarget(CreateTestTable(enabledTests));
|
||||
await Task.Delay(DelayBetweenTestsMs);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static Table CreateTestTable(List<Test> tests)
|
||||
{
|
||||
var table = new Table()
|
||||
.Border(TableBorder.Rounded)
|
||||
.AddColumn("[bold]ID[/]")
|
||||
.AddColumn("[bold]Status[/]")
|
||||
.AddColumn("[bold]Scopes[/]")
|
||||
.AddColumn("[bold]Resources[/]")
|
||||
.AddColumn("[bold]Duration[/]");
|
||||
|
||||
foreach (var test in tests)
|
||||
{
|
||||
var status = test.Status switch
|
||||
{
|
||||
TestStatus.Pending => "[grey]Pending[/]",
|
||||
TestStatus.Running => "[yellow]Running...[/]",
|
||||
TestStatus.Completed => "[green]✓ Completed[/]",
|
||||
TestStatus.Failed => $"[red]✗ Failed[/]",
|
||||
_ => "[grey]Unknown[/]"
|
||||
};
|
||||
|
||||
var resourcesList = test.Resources?.Any() == true
|
||||
? string.Join(", ", test.Resources.Select(r => r.Replace("urn:", "")))
|
||||
: "-";
|
||||
|
||||
var duration = test.StartTime.HasValue && test.EndTime.HasValue
|
||||
? $"{(test.EndTime.Value - test.StartTime.Value).TotalSeconds:F1}s"
|
||||
: test.StartTime.HasValue
|
||||
? "..."
|
||||
: "-";
|
||||
|
||||
// Truncate scopes for table display
|
||||
var scopeDisplay = test.Scope.Length > 40
|
||||
? string.Concat(test.Scope.AsSpan(0, 37), "...")
|
||||
: test.Scope;
|
||||
|
||||
table.AddRow(
|
||||
test.Id,
|
||||
status,
|
||||
scopeDisplay,
|
||||
resourcesList,
|
||||
duration
|
||||
);
|
||||
}
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
private async Task RunTestVerboseAsync(Test test)
|
||||
{
|
||||
var resourcesList = test.Resources?.Any() == true
|
||||
? string.Join(", ", test.Resources)
|
||||
: "-none-";
|
||||
|
||||
// Escape the text to prevent Spectre.Console from interpreting it as markup
|
||||
var scopeText = test.Scope.EscapeMarkup();
|
||||
var resourcesText = resourcesList.EscapeMarkup();
|
||||
|
||||
var panel = new Panel(
|
||||
new Markup($"[bold]Test {test.Id}[/]\n" +
|
||||
$"[dim]Scopes:[/] {scopeText}\n" +
|
||||
$"[dim]Resources:[/] {resourcesText}"))
|
||||
.Border(BoxBorder.Rounded)
|
||||
.BorderColor(Color.Blue)
|
||||
.Header("[blue]Running Test[/]");
|
||||
|
||||
AnsiConsole.Write(panel);
|
||||
|
||||
try
|
||||
{
|
||||
test.Status = TestStatus.Running;
|
||||
test.StartTime = DateTime.Now;
|
||||
await ExecuteTestAsync(test, verbose: true);
|
||||
test.Status = TestStatus.Completed;
|
||||
test.EndTime = DateTime.Now;
|
||||
AnsiConsole.MarkupLine("[green]✓ Test completed successfully[/]\n");
|
||||
await Task.Delay(DelayBetweenTestsMs);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
test.Status = TestStatus.Failed;
|
||||
test.ErrorMessage = ex.Message;
|
||||
test.EndTime = DateTime.Now;
|
||||
AnsiConsole.MarkupLine($"[red]✗ Test failed: {Markup.Escape(ex.Message)}[/]\n");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ExecuteTestAsync(Test test, bool verbose)
|
||||
{
|
||||
test.Result = new TestResult();
|
||||
|
||||
var browser = new SystemBrowser();
|
||||
var redirectUri = $"http://127.0.0.1:{browser.Port}";
|
||||
|
||||
var options = new OidcClientOptions
|
||||
{
|
||||
Authority = _authority,
|
||||
ClientId = "console.resource.indicators",
|
||||
RedirectUri = redirectUri,
|
||||
Scope = test.Scope,
|
||||
Resource = test.Resources?.ToList() ?? [],
|
||||
FilterClaims = false,
|
||||
LoadProfile = false,
|
||||
Browser = browser,
|
||||
Policy =
|
||||
{
|
||||
RequireIdentityTokenSignature = false
|
||||
}
|
||||
};
|
||||
|
||||
_oidcClient = new OidcClient(options);
|
||||
var result = await _oidcClient.LoginAsync();
|
||||
|
||||
test.Result.AccessTokenReceived = result.AccessToken != null;
|
||||
|
||||
if (verbose)
|
||||
{
|
||||
HandleAccessTokenVerbose(result.AccessToken, test.AccessTokenExpected);
|
||||
|
||||
if (test.AccessTokenExpected && test.RefreshTokenExpected)
|
||||
{
|
||||
test.Result.RefreshTokenReceived = result.RefreshToken != null;
|
||||
await HandleRefreshTokenVerbose(result, test.Resources, test.Result);
|
||||
}
|
||||
else if (!test.RefreshTokenExpected)
|
||||
{
|
||||
AnsiConsole.MarkupLine("[green]✓ Refresh Token was not expected and not received[/]");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// In table mode, just validate and collect results without output
|
||||
if (test.AccessTokenExpected && result.AccessToken == null)
|
||||
{
|
||||
throw new Exception("Access token expected but not received");
|
||||
}
|
||||
|
||||
if (test.AccessTokenExpected && test.RefreshTokenExpected)
|
||||
{
|
||||
if (result.RefreshToken == null)
|
||||
{
|
||||
throw new Exception("Refresh token expected but not received");
|
||||
}
|
||||
test.Result.RefreshTokenReceived = true;
|
||||
await HandleRefreshTokenSilent(result, test.Resources, test.Result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void HandleAccessTokenVerbose(string accessToken, bool expected)
|
||||
{
|
||||
if (expected)
|
||||
{
|
||||
if (accessToken is null)
|
||||
{
|
||||
AnsiConsole.MarkupLine("[red]✗ An Access Token was expected but not received[/]");
|
||||
return;
|
||||
}
|
||||
|
||||
AnsiConsole.MarkupLine("[green]✓ Access Token received[/]");
|
||||
AnsiConsole.WriteLine();
|
||||
|
||||
PrintJwtToken(accessToken, "Standard Access Token");
|
||||
}
|
||||
else
|
||||
{
|
||||
AnsiConsole.MarkupLine("[green]✓ Access Token was not expected and not received[/]");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleRefreshTokenVerbose(LoginResult result, IEnumerable<string> testResources, TestResult testResult)
|
||||
{
|
||||
if (result.RefreshToken is null)
|
||||
{
|
||||
AnsiConsole.MarkupLine("[red]✗ A Refresh Token was expected but not received[/]");
|
||||
return;
|
||||
}
|
||||
|
||||
AnsiConsole.WriteLine();
|
||||
AnsiConsole.WriteLine();
|
||||
|
||||
AnsiConsole.Write(new Rule("[yellow]Refreshing with Resource Parameters[/]").LeftJustified());
|
||||
|
||||
var resourcesSet = testResources?.ToHashSet() ?? [];
|
||||
|
||||
foreach (var resource in RefreshResources)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[cyan]→ Refreshing for resource: {resource}[/]");
|
||||
await RefreshTokenAsync(result.RefreshToken, resource, resourcesSet.Contains(resource), verbose: true, testResult);
|
||||
await Task.Delay(DelayBetweenRefreshMs);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleRefreshTokenSilent(LoginResult result, IEnumerable<string> testResources, TestResult testResult)
|
||||
{
|
||||
var resourcesSet = testResources?.ToHashSet() ?? [];
|
||||
|
||||
foreach (var resource in RefreshResources)
|
||||
{
|
||||
await RefreshTokenAsync(result.RefreshToken!, resource, resourcesSet.Contains(resource), verbose: false, testResult);
|
||||
await Task.Delay(DelayBetweenRefreshMs);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RefreshTokenAsync(string refreshToken, string resource, bool resourceIsConfigured, bool verbose, TestResult testResult)
|
||||
{
|
||||
if (_oidcClient == null)
|
||||
{
|
||||
throw new InvalidOperationException("OIDC client not initialized");
|
||||
}
|
||||
|
||||
var result = await _oidcClient.RefreshTokenAsync(refreshToken,
|
||||
new Parameters
|
||||
{
|
||||
{ "resource", resource }
|
||||
});
|
||||
|
||||
var refreshResult = new RefreshResult
|
||||
{
|
||||
Resource = resource,
|
||||
Success = !result.IsError,
|
||||
Error = result.Error,
|
||||
WasExpectedError = !resourceIsConfigured && result.IsError
|
||||
};
|
||||
|
||||
testResult.RefreshResults.Add(refreshResult);
|
||||
|
||||
if (result.IsError)
|
||||
{
|
||||
if (resourceIsConfigured)
|
||||
{
|
||||
var message = $"An error was not expected but was received: {result.Error}";
|
||||
if (verbose)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[red]✗ {Markup.Escape(message)}[/]");
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception(message);
|
||||
}
|
||||
}
|
||||
else if (verbose)
|
||||
{
|
||||
// Expected error - show in verbose mode
|
||||
AnsiConsole.MarkupLine($"[green]✓ Expected error received: [/][yellow]{Markup.Escape(result.Error ?? "unknown")}[/]");
|
||||
}
|
||||
// In non-verbose mode, we don't show expected errors here - they'll be in the summary
|
||||
return;
|
||||
}
|
||||
|
||||
if (verbose)
|
||||
{
|
||||
AnsiConsole.WriteLine();
|
||||
PrintJwtToken(result.AccessToken!, "Down-scoped access token");
|
||||
}
|
||||
}
|
||||
|
||||
private static void PrintJwtToken(string token, string blockHeader = "JWT Token")
|
||||
{
|
||||
var parts = token.Split('.');
|
||||
if (parts.Length < 2)
|
||||
{
|
||||
AnsiConsole.MarkupLine("[red]Invalid JWT token format[/]");
|
||||
return;
|
||||
}
|
||||
|
||||
var header = parts[0];
|
||||
var payload = parts[1];
|
||||
|
||||
var headerJson = Encoding.UTF8.GetString(Base64Url.DecodeFromChars(header));
|
||||
var payloadJson = Encoding.UTF8.GetString(Base64Url.DecodeFromChars(payload));
|
||||
|
||||
// Use Spectre.Console's built-in JSON rendering with proper namespace
|
||||
AnsiConsole.Write(
|
||||
new Panel(
|
||||
new Rows(
|
||||
new Markup("[bold yellow]Header:[/]"),
|
||||
new JsonText(headerJson),
|
||||
new Text(""),
|
||||
new Markup("[bold yellow]Payload:[/]"),
|
||||
new JsonText(payloadJson)
|
||||
))
|
||||
.Border(BoxBorder.Rounded)
|
||||
.BorderColor(Color.Grey)
|
||||
.Header($"[dim]{blockHeader}[/]"));
|
||||
}
|
||||
}
|
||||
|
|
@ -34,12 +34,12 @@ response.Show();
|
|||
|
||||
var refresh_token = response.RefreshToken;
|
||||
|
||||
while (true)
|
||||
for (var i = 0; i < 10; i++)
|
||||
{
|
||||
response = await RefreshTokenAsync(refresh_token);
|
||||
response.Show();
|
||||
|
||||
Thread.Sleep(5000);
|
||||
Thread.Sleep(50);
|
||||
|
||||
await CallServiceAsync(response.AccessToken);
|
||||
|
||||
|
|
|
|||
|
|
@ -36,8 +36,8 @@ var plannedRuns = new List<PlannedRun>
|
|||
new() { Enabled = true, Id = "J", Name = "No scope (resource: resource1)", Scope = "", Resource = "urn:resource1" },
|
||||
new() { Enabled = true, Id = "K", Name = "No scope (resource: resource3)", Scope = "", Resource = "urn:resource3" },
|
||||
new() { Enabled = true, Id = "L", Name = "Isolated scope without resource parameter", Scope = "resource3.scope1" },
|
||||
new() { Enabled = true, Id = "M", Name = "Isolated scope without resource parameter", Scope = "resource3.scope1", Resource = "urn:resource3" },
|
||||
new() { Enabled = true, Id = "N", Name = "Isolated scope without resource parameter", Scope = "resource3.scope1", Resource = "urn:resource2" }
|
||||
new() { Enabled = true, Id = "M", Name = "Isolated scope with resource parameter", Scope = "resource3.scope1", Resource = "urn:resource3" },
|
||||
new() { Enabled = true, Id = "N", Name = "Shared scope with resource parameter", Scope = "shared.scope", Resource = "urn:resource2" }
|
||||
};
|
||||
|
||||
// Execute the planned runs
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ internal class Program
|
|||
{
|
||||
Authority = Constants.Authority,
|
||||
ClientId = "winconsole",
|
||||
Scope = "openid profile scope1",
|
||||
Scope = "openid profile resource1.scope1",
|
||||
RedirectUri = redirectUri,
|
||||
};
|
||||
|
||||
|
|
@ -94,7 +94,12 @@ internal class Program
|
|||
var callbackManager = new CallbackManager(state.State);
|
||||
|
||||
// open system browser to start authentication
|
||||
Process.Start(state.StartUrl);
|
||||
var psi = new ProcessStartInfo
|
||||
{
|
||||
FileName = state.StartUrl,
|
||||
UseShellExecute = true
|
||||
};
|
||||
Process.Start(psi);
|
||||
|
||||
Console.WriteLine("Running callback manager");
|
||||
var response = await callbackManager.RunServer();
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
// Copyright (c) Duende Software. All rights reserved.
|
||||
// See LICENSE in the project root for license information.
|
||||
|
||||
using System.Reflection;
|
||||
using System.Runtime.Versioning;
|
||||
using Microsoft.Win32;
|
||||
|
||||
|
|
@ -36,7 +35,7 @@ internal class RegistryConfig
|
|||
|
||||
private const string CommandKeyValueName = "";
|
||||
private const string CommandKeyValueFormat = "\"{0}\" \"%1\"";
|
||||
private static string CommandKeyValueValue => string.Format(CommandKeyValueFormat, Assembly.GetExecutingAssembly().Location);
|
||||
private static string CommandKeyValueValue => string.Format(CommandKeyValueFormat, Environment.ProcessPath);
|
||||
|
||||
private const string UrlProtocolValueName = "URL Protocol";
|
||||
private const string UrlProtocolValueValue = "";
|
||||
|
|
|
|||
|
|
@ -269,7 +269,7 @@ public static class ClientsConsole
|
|||
RedirectUris = { "sample-windows-client://callback" },
|
||||
RequireConsent = false,
|
||||
AllowOfflineAccess = true,
|
||||
AllowedIdentityTokenSigningAlgorithms = { "ES256" },
|
||||
AllowedIdentityTokenSigningAlgorithms = { "RS256" },
|
||||
AllowedScopes =
|
||||
{
|
||||
IdentityServerConstants.StandardScopes.OpenId,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ namespace Duende.IdentityServer.Hosts.Shared.Configuration;
|
|||
|
||||
public static class TestClients
|
||||
{
|
||||
public static IEnumerable<Client> Get()
|
||||
public static List<Client> Get()
|
||||
{
|
||||
var clients = new List<Client>();
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ public sealed class CustomClientRegistrationProcessor(
|
|||
var clientId = clientIdParameter.ToString();
|
||||
if (clientId != null)
|
||||
{
|
||||
var existingClient = clientStore.FindClientByIdAsync(clientId);
|
||||
var existingClient = await clientStore.FindClientByIdAsync(clientId);
|
||||
if (existingClient is not null)
|
||||
{
|
||||
return new DynamicClientRegistrationError(
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// See LICENSE in the project root for license information.
|
||||
|
||||
using Duende.IdentityServer;
|
||||
using Duende.IdentityServer.Configuration;
|
||||
using Duende.IdentityServer.UI;
|
||||
using Duende.IdentityServer.UI.AspNetIdentity.Models;
|
||||
using IdentityServerHost.Data;
|
||||
|
|
@ -122,6 +123,9 @@ internal static class HostingExtensions
|
|||
app.MapRazorPages()
|
||||
.RequireAuthorization();
|
||||
|
||||
app.MapDynamicClientRegistration()
|
||||
.AllowAnonymous();
|
||||
|
||||
return app;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
// Copyright (c) Duende Software. All rights reserved.
|
||||
// See LICENSE in the project root for license information.
|
||||
|
||||
using Duende.IdentityServer.Configuration;
|
||||
using Duende.IdentityServer.Configuration.RequestProcessing;
|
||||
using Duende.IdentityServer.Hosts.Shared.Configuration;
|
||||
using Duende.IdentityServer.Hosts.Shared.Customization;
|
||||
using Duende.IdentityServer.UI.AspNetIdentity.Models;
|
||||
|
||||
namespace IdentityServerHost;
|
||||
|
|
@ -26,6 +29,10 @@ internal static class IdentityServerExtensions
|
|||
.AddInMemoryClients(TestClients.Get())
|
||||
.AddAspNetIdentity<ApplicationUser>();
|
||||
|
||||
builder.Services.AddIdentityServerConfiguration(opt => { })
|
||||
.AddInMemoryClientConfigurationStore();
|
||||
builder.Services.AddTransient<IDynamicClientRegistrationRequestProcessor, CustomClientRegistrationProcessor>();
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
using System.Globalization;
|
||||
using System.Security.Claims;
|
||||
using Duende.IdentityServer;
|
||||
using Duende.IdentityServer.Configuration;
|
||||
using Duende.IdentityServer.Hosts.Shared.Customization;
|
||||
using Duende.IdentityServer.UI;
|
||||
using Microsoft.AspNetCore.Mvc.Razor;
|
||||
|
|
@ -171,6 +172,9 @@ internal static class HostingExtensions
|
|||
app.MapRazorPages()
|
||||
.RequireAuthorization();
|
||||
|
||||
app.MapDynamicClientRegistration()
|
||||
.AllowAnonymous();
|
||||
|
||||
return app;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using System.Security.Cryptography.X509Certificates;
|
|||
using Duende.IdentityModel;
|
||||
using Duende.IdentityServer;
|
||||
using Duende.IdentityServer.Configuration;
|
||||
using Duende.IdentityServer.Configuration.RequestProcessing;
|
||||
using Duende.IdentityServer.Hosts.Shared.Configuration;
|
||||
using Duende.IdentityServer.Hosts.Shared.Customization;
|
||||
using Microsoft.AspNetCore.Authentication.Certificate;
|
||||
|
|
@ -51,7 +52,7 @@ internal static class IdentityServerExtensions
|
|||
options.Diagnostics.ChunkSize = 1024 * 1000 - 32; // 1 MB minus some formatting space;
|
||||
})
|
||||
.AddServerSideSessions()
|
||||
.AddInMemoryClients([.. TestClients.Get()])
|
||||
.AddInMemoryClients(TestClients.Get())
|
||||
.AddInMemoryIdentityResources(TestResources.IdentityResources)
|
||||
.AddInMemoryApiResources(TestResources.ApiResources)
|
||||
.AddInMemoryApiScopes(TestResources.ApiScopes)
|
||||
|
|
@ -81,6 +82,7 @@ internal static class IdentityServerExtensions
|
|||
|
||||
builder.Services.AddIdentityServerConfiguration(opt => { })
|
||||
.AddInMemoryClientConfigurationStore();
|
||||
builder.Services.AddTransient<IDynamicClientRegistrationRequestProcessor, CustomClientRegistrationProcessor>();
|
||||
|
||||
builder.Services.AddDistributedMemoryCache();
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// See LICENSE in the project root for license information.
|
||||
|
||||
using Duende.IdentityServer;
|
||||
using Duende.IdentityServer.Configuration;
|
||||
using Duende.IdentityServer.UI;
|
||||
using Duende.IdentityServer.UI.AspNetIdentity.Models;
|
||||
using IdentityServerHost.Data;
|
||||
|
|
@ -120,6 +121,9 @@ internal static class HostingExtensions
|
|||
app.MapRazorPages()
|
||||
.RequireAuthorization();
|
||||
|
||||
app.MapDynamicClientRegistration()
|
||||
.AllowAnonymous();
|
||||
|
||||
return app;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
// Copyright (c) Duende Software. All rights reserved.
|
||||
// See LICENSE in the project root for license information.
|
||||
|
||||
using Duende.IdentityServer.Configuration;
|
||||
using Duende.IdentityServer.Configuration.RequestProcessing;
|
||||
using Duende.IdentityServer.Hosts.Shared.Configuration;
|
||||
using Duende.IdentityServer.Hosts.Shared.Customization;
|
||||
using Duende.IdentityServer.UI.AspNetIdentity.Models;
|
||||
|
||||
namespace IdentityServerHost;
|
||||
|
|
@ -26,6 +29,10 @@ internal static class IdentityServerExtensions
|
|||
.AddInMemoryClients(TestClients.Get())
|
||||
.AddAspNetIdentity<ApplicationUser>();
|
||||
|
||||
builder.Services.AddIdentityServerConfiguration(opt => { })
|
||||
.AddInMemoryClientConfigurationStore();
|
||||
builder.Services.AddTransient<IDynamicClientRegistrationRequestProcessor, CustomClientRegistrationProcessor>();
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
using System.Globalization;
|
||||
using System.Security.Claims;
|
||||
using Duende.IdentityServer;
|
||||
using Duende.IdentityServer.Configuration;
|
||||
using Duende.IdentityServer.Hosts.Shared.Customization;
|
||||
using Duende.IdentityServer.UI;
|
||||
using Microsoft.AspNetCore.Mvc.Razor;
|
||||
|
|
@ -171,6 +172,9 @@ internal static class HostingExtensions
|
|||
app.MapRazorPages()
|
||||
.RequireAuthorization();
|
||||
|
||||
app.MapDynamicClientRegistration()
|
||||
.AllowAnonymous();
|
||||
|
||||
return app;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using System.Security.Cryptography.X509Certificates;
|
|||
using Duende.IdentityModel;
|
||||
using Duende.IdentityServer;
|
||||
using Duende.IdentityServer.Configuration;
|
||||
using Duende.IdentityServer.Configuration.RequestProcessing;
|
||||
using Duende.IdentityServer.Hosts.Shared.Configuration;
|
||||
using Duende.IdentityServer.Hosts.Shared.Customization;
|
||||
using Microsoft.AspNetCore.Authentication.Certificate;
|
||||
|
|
@ -51,7 +52,7 @@ internal static class IdentityServerExtensions
|
|||
options.Diagnostics.ChunkSize = 1024 * 1000 - 32; // 1 MB minus some formatting space;
|
||||
})
|
||||
.AddServerSideSessions()
|
||||
.AddInMemoryClients([.. TestClients.Get()])
|
||||
.AddInMemoryClients(TestClients.Get())
|
||||
.AddInMemoryIdentityResources(TestResources.IdentityResources)
|
||||
.AddInMemoryApiResources(TestResources.ApiResources)
|
||||
.AddInMemoryApiScopes(TestResources.ApiScopes)
|
||||
|
|
@ -81,6 +82,7 @@ internal static class IdentityServerExtensions
|
|||
|
||||
builder.Services.AddIdentityServerConfiguration(opt => { })
|
||||
.AddInMemoryClientConfigurationStore();
|
||||
builder.Services.AddTransient<IDynamicClientRegistrationRequestProcessor, CustomClientRegistrationProcessor>();
|
||||
|
||||
builder.Services.AddDistributedMemoryCache();
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// See LICENSE in the project root for license information.
|
||||
|
||||
using Duende.IdentityServer;
|
||||
using Duende.IdentityServer.Configuration;
|
||||
using Duende.IdentityServer.UI;
|
||||
using Duende.IdentityServer.UI.AspNetIdentity.Models;
|
||||
using IdentityServerHost.Data;
|
||||
|
|
@ -122,6 +123,9 @@ internal static class HostingExtensions
|
|||
app.MapRazorPages()
|
||||
.RequireAuthorization();
|
||||
|
||||
app.MapDynamicClientRegistration()
|
||||
.AllowAnonymous();
|
||||
|
||||
return app;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
// Copyright (c) Duende Software. All rights reserved.
|
||||
// See LICENSE in the project root for license information.
|
||||
|
||||
using Duende.IdentityServer.Configuration;
|
||||
using Duende.IdentityServer.Configuration.RequestProcessing;
|
||||
using Duende.IdentityServer.Hosts.Shared.Configuration;
|
||||
using Duende.IdentityServer.Hosts.Shared.Customization;
|
||||
using Duende.IdentityServer.UI.AspNetIdentity.Models;
|
||||
|
||||
namespace IdentityServerHost;
|
||||
|
|
@ -26,6 +29,10 @@ internal static class IdentityServerExtensions
|
|||
.AddInMemoryClients(TestClients.Get())
|
||||
.AddAspNetIdentity<ApplicationUser>();
|
||||
|
||||
builder.Services.AddIdentityServerConfiguration(opt => { })
|
||||
.AddInMemoryClientConfigurationStore();
|
||||
builder.Services.AddTransient<IDynamicClientRegistrationRequestProcessor, CustomClientRegistrationProcessor>();
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
using System.Globalization;
|
||||
using System.Security.Claims;
|
||||
using Duende.IdentityServer;
|
||||
using Duende.IdentityServer.Configuration;
|
||||
using Duende.IdentityServer.Hosts.Shared.Customization;
|
||||
using Duende.IdentityServer.UI;
|
||||
using Microsoft.AspNetCore.Mvc.Razor;
|
||||
|
|
@ -171,6 +172,9 @@ internal static class HostingExtensions
|
|||
app.MapRazorPages()
|
||||
.RequireAuthorization();
|
||||
|
||||
app.MapDynamicClientRegistration()
|
||||
.AllowAnonymous();
|
||||
|
||||
return app;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using System.Security.Cryptography.X509Certificates;
|
|||
using Duende.IdentityModel;
|
||||
using Duende.IdentityServer;
|
||||
using Duende.IdentityServer.Configuration;
|
||||
using Duende.IdentityServer.Configuration.RequestProcessing;
|
||||
using Duende.IdentityServer.Hosts.Shared.Configuration;
|
||||
using Duende.IdentityServer.Hosts.Shared.Customization;
|
||||
using Microsoft.AspNetCore.Authentication.Certificate;
|
||||
|
|
@ -51,7 +52,7 @@ internal static class IdentityServerExtensions
|
|||
options.Diagnostics.ChunkSize = 1024 * 1000 - 32; // 1 MB minus some formatting space;
|
||||
})
|
||||
.AddServerSideSessions()
|
||||
.AddInMemoryClients([.. TestClients.Get()])
|
||||
.AddInMemoryClients(TestClients.Get())
|
||||
.AddInMemoryIdentityResources(TestResources.IdentityResources)
|
||||
.AddInMemoryApiResources(TestResources.ApiResources)
|
||||
.AddInMemoryApiScopes(TestResources.ApiScopes)
|
||||
|
|
@ -81,6 +82,7 @@ internal static class IdentityServerExtensions
|
|||
|
||||
builder.Services.AddIdentityServerConfiguration(opt => { })
|
||||
.AddInMemoryClientConfigurationStore();
|
||||
builder.Services.AddTransient<IDynamicClientRegistrationRequestProcessor, CustomClientRegistrationProcessor>();
|
||||
|
||||
builder.Services.AddDistributedMemoryCache();
|
||||
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ public class DynamicClientRegistrationRequestProcessor : IDynamicClientRegistrat
|
|||
protected virtual async Task<IStepResult> AddClientSecret(
|
||||
DynamicClientRegistrationContext context)
|
||||
{
|
||||
if (context.Client.ClientSecrets.Count == 0)
|
||||
if (context.Client.ClientSecrets.Count == 0 && context.Request.TokenEndpointAuthenticationMethod != "none")
|
||||
{
|
||||
var (secret, plainText) = await GenerateSecret(context);
|
||||
context.Items["secret"] = secret;
|
||||
|
|
|
|||
|
|
@ -129,6 +129,12 @@ public class DynamicClientRegistrationValidator : IDynamicClientRegistrationVali
|
|||
|
||||
if (context.Request.GrantTypes.Contains(OidcConstants.GrantTypes.ClientCredentials))
|
||||
{
|
||||
if (context.Request.RequireClientSecret is false ||
|
||||
context.Request.TokenEndpointAuthenticationMethod == "none")
|
||||
{
|
||||
return StepResult.Failure("client secret is required for client credentials grant type");
|
||||
}
|
||||
|
||||
context.Client.AllowedGrantTypes.Add(GrantType.ClientCredentials);
|
||||
}
|
||||
if (context.Request.GrantTypes.Contains(OidcConstants.GrantTypes.AuthorizationCode))
|
||||
|
|
@ -482,6 +488,11 @@ public class DynamicClientRegistrationValidator : IDynamicClientRegistrationVali
|
|||
{
|
||||
context.Client.RequireClientSecret = context.Request.RequireClientSecret.Value;
|
||||
}
|
||||
else if (context.Request.TokenEndpointAuthenticationMethod == "none")
|
||||
{
|
||||
context.Client.RequireClientSecret = false;
|
||||
}
|
||||
|
||||
return StepResult.Success();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -236,11 +236,10 @@ public static class IdentityServerBuilderExtensionsCore
|
|||
builder.Services.AddSingleton<IDiagnosticEntry, ClientInfoDiagnosticEntry>();
|
||||
builder.Services.AddSingleton<ResourceLoadedTracker>();
|
||||
builder.Services.AddSingleton<IDiagnosticEntry, ResourceInfoDiagnosticEntry>();
|
||||
builder.Services.AddSingleton(serviceProvider => new DiagnosticSummary(
|
||||
builder.Services.AddSingleton<DiagnosticSummary>();
|
||||
builder.Services.AddSingleton(serviceProvider => new DiagnosticDataService(
|
||||
DateTime.UtcNow,
|
||||
serviceProvider.GetServices<IDiagnosticEntry>(),
|
||||
serviceProvider.GetRequiredService<IdentityServerOptions>(),
|
||||
serviceProvider.GetRequiredService<ILoggerFactory>()));
|
||||
serviceProvider.GetServices<IDiagnosticEntry>()));
|
||||
builder.Services.AddHostedService<DiagnosticHostedService>();
|
||||
|
||||
return builder;
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ internal static class Constants
|
|||
OidcConstants.PromptModes.Consent,
|
||||
OidcConstants.PromptModes.SelectAccount,
|
||||
// Create not in here by default -- it's added if customer sets the CreateAccountUrl user interaction option
|
||||
//OidcConstants.PromptModes.Create,
|
||||
//OidcConstants.PromptModes.Create,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -222,6 +222,7 @@ internal static class Constants
|
|||
public const string IdentityServerBasePath = "idsvr:IdentityServerBasePath";
|
||||
public const string SignOutCalled = "idsvr:IdentityServerSignOutCalled";
|
||||
public const string DetectedExpiredUserSession = "idsvr:IdentityServerDetectedExpiredUserSession";
|
||||
public const string BackChannlLogoutTriggered = "idsvr:IdentityServerBackChannlLogoutTriggered";
|
||||
}
|
||||
|
||||
public static class TokenTypeHints
|
||||
|
|
|
|||
|
|
@ -223,6 +223,19 @@ public class AuthorizeHttpWriter : IHttpResponseWriter<AuthorizeResult>
|
|||
await uiLocalesService.StoreUiLocalesForRedirectAsync(response.Request?.UiLocales);
|
||||
}
|
||||
|
||||
var errorModel = await CreateErrorMessage(response, context);
|
||||
|
||||
var message = new Message<ErrorMessage>(errorModel, _clock.UtcNow.UtcDateTime);
|
||||
var id = await _errorMessageStore.WriteAsync(message);
|
||||
|
||||
var errorUrl = _options.UserInteraction.ErrorUrl;
|
||||
|
||||
var url = errorUrl.AddQueryString(_options.UserInteraction.ErrorIdParameter, id);
|
||||
context.Response.Redirect(_urls.GetAbsoluteUrl(url));
|
||||
}
|
||||
|
||||
protected virtual Task<ErrorMessage> CreateErrorMessage(AuthorizeResponse response, HttpContext context)
|
||||
{
|
||||
var errorModel = new ErrorMessage
|
||||
{
|
||||
ActivityId = System.Diagnostics.Activity.Current?.Id,
|
||||
|
|
@ -234,12 +247,6 @@ public class AuthorizeHttpWriter : IHttpResponseWriter<AuthorizeResult>
|
|||
ClientId = response.Request?.ClientId
|
||||
};
|
||||
|
||||
var message = new Message<ErrorMessage>(errorModel, _clock.UtcNow.UtcDateTime);
|
||||
var id = await _errorMessageStore.WriteAsync(message);
|
||||
|
||||
var errorUrl = _options.UserInteraction.ErrorUrl;
|
||||
|
||||
var url = errorUrl.AddQueryString(_options.UserInteraction.ErrorIdParameter, id);
|
||||
context.Response.Redirect(_urls.GetAbsoluteUrl(url));
|
||||
return Task.FromResult(errorModel);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,14 @@ public static class HttpContextExtensions
|
|||
|
||||
internal static bool GetSignOutCalled(this HttpContext context) => context.Items.ContainsKey(Constants.EnvironmentKeys.SignOutCalled);
|
||||
|
||||
internal static void SetBackChannelLogoutTriggered(this HttpContext context)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(context);
|
||||
context.Items[Constants.EnvironmentKeys.BackChannlLogoutTriggered] = "true";
|
||||
}
|
||||
|
||||
internal static bool GetBackChannelLogoutTriggered(this HttpContext context) => context.Items.ContainsKey(Constants.EnvironmentKeys.BackChannlLogoutTriggered);
|
||||
|
||||
internal static void SetExpiredUserSession(this HttpContext context, UserSession userSession)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(context);
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using System.Security.Claims;
|
|||
using Duende.IdentityModel;
|
||||
using Duende.IdentityServer.Configuration.DependencyInjection;
|
||||
using Duende.IdentityServer.Extensions;
|
||||
using Duende.IdentityServer.Models;
|
||||
using Duende.IdentityServer.Services;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
|
@ -27,6 +28,8 @@ internal class IdentityServerAuthenticationService : IAuthenticationService
|
|||
private readonly IAuthenticationSchemeProvider _schemes;
|
||||
private readonly IClock _clock;
|
||||
private readonly IUserSession _session;
|
||||
private readonly IIssuerNameService _issuerNameService;
|
||||
private readonly ISessionCoordinationService _sessionCoordinationService;
|
||||
private readonly ILogger<IdentityServerAuthenticationService> _logger;
|
||||
|
||||
public IdentityServerAuthenticationService(
|
||||
|
|
@ -34,6 +37,8 @@ internal class IdentityServerAuthenticationService : IAuthenticationService
|
|||
IAuthenticationSchemeProvider schemes,
|
||||
IClock clock,
|
||||
IUserSession session,
|
||||
IIssuerNameService issuerNameService,
|
||||
ISessionCoordinationService sessionCoordinationService,
|
||||
ILogger<IdentityServerAuthenticationService> logger)
|
||||
{
|
||||
_inner = decorator.Instance;
|
||||
|
|
@ -41,6 +46,8 @@ internal class IdentityServerAuthenticationService : IAuthenticationService
|
|||
_schemes = schemes;
|
||||
_clock = clock;
|
||||
_session = session;
|
||||
_issuerNameService = issuerNameService;
|
||||
_sessionCoordinationService = sessionCoordinationService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
|
|
@ -77,6 +84,38 @@ internal class IdentityServerAuthenticationService : IAuthenticationService
|
|||
{
|
||||
// this sets a flag used by middleware to do post-signout work.
|
||||
context.SetSignOutCalled();
|
||||
|
||||
if (!context.GetBackChannelLogoutTriggered())
|
||||
{
|
||||
// Note: it is important the work for triggering back-channel logout
|
||||
// is inside the Response.OnStarting event. Otherwise, in some conditions
|
||||
// the request will never complete.
|
||||
// See: https://github.com/DuendeArchive/IdentityServer4/issues/4644
|
||||
context.Response.OnStarting(async () =>
|
||||
{
|
||||
_logger.LogDebug("SignOutCalled set; processing post-signout session cleanup.");
|
||||
|
||||
// back channel logout
|
||||
var user = await _session.GetUserAsync();
|
||||
if (user != null)
|
||||
{
|
||||
var session = new UserSession
|
||||
{
|
||||
SubjectId = user.GetSubjectId(),
|
||||
SessionId = await _session.GetSessionIdAsync(),
|
||||
DisplayName = user.GetDisplayName(),
|
||||
ClientIds = (await _session.GetClientListAsync()).ToList(),
|
||||
Issuer = await _issuerNameService.GetCurrentAsync()
|
||||
};
|
||||
await _sessionCoordinationService.ProcessLogoutAsync(session);
|
||||
}
|
||||
|
||||
// this clears our session id cookie so JS clients can detect the user has signed out
|
||||
await _session.RemoveSessionIdCookieAsync();
|
||||
});
|
||||
|
||||
context.SetBackChannelLogoutTriggered();
|
||||
}
|
||||
}
|
||||
|
||||
await _inner.SignOutAsync(context, scheme, properties);
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ using Duende.IdentityServer.Events;
|
|||
using Duende.IdentityServer.Extensions;
|
||||
using Duende.IdentityServer.Licensing.V2;
|
||||
using Duende.IdentityServer.Logging;
|
||||
using Duende.IdentityServer.Models;
|
||||
using Duende.IdentityServer.Services;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
|
|
@ -61,28 +60,6 @@ public class IdentityServerMiddleware
|
|||
|
||||
context.Response.OnStarting(async () =>
|
||||
{
|
||||
if (context.GetSignOutCalled())
|
||||
{
|
||||
_sanitizedLogger.LogDebug("SignOutCalled set; processing post-signout session cleanup.");
|
||||
|
||||
// this clears our session id cookie so JS clients can detect the user has signed out
|
||||
await userSession.RemoveSessionIdCookieAsync();
|
||||
|
||||
var user = await userSession.GetUserAsync();
|
||||
if (user != null)
|
||||
{
|
||||
var session = new UserSession
|
||||
{
|
||||
SubjectId = user.GetSubjectId(),
|
||||
SessionId = await userSession.GetSessionIdAsync(),
|
||||
DisplayName = user.GetDisplayName(),
|
||||
ClientIds = (await userSession.GetClientListAsync()).ToList(),
|
||||
Issuer = await issuerNameService.GetCurrentAsync()
|
||||
};
|
||||
await sessionCoordinationService.ProcessLogoutAsync(session);
|
||||
}
|
||||
}
|
||||
|
||||
if (context.TryGetExpiredUserSession(out var expiredUserSession))
|
||||
{
|
||||
_sanitizedLogger.LogDebug("Detected expired session removed; processing post-expiration cleanup.");
|
||||
|
|
|
|||
|
|
@ -1,36 +1,21 @@
|
|||
// Copyright (c) Duende Software. All rights reserved.
|
||||
// See LICENSE in the project root for license information.
|
||||
|
||||
using System.Buffers;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using Duende.IdentityServer.Configuration;
|
||||
using Duende.IdentityServer.Services;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Duende.IdentityServer.Licensing.V2.Diagnostics;
|
||||
|
||||
internal class DiagnosticSummary(DateTime serverStartTime, IEnumerable<IDiagnosticEntry> entries, IdentityServerOptions options, ILoggerFactory loggerFactory)
|
||||
internal class DiagnosticSummary(DiagnosticDataService diagnosticDataService, IdentityServerOptions options, ILoggerFactory loggerFactory)
|
||||
{
|
||||
private readonly ILogger _logger = loggerFactory.CreateLogger("Duende.IdentityServer.Diagnostics.Summary");
|
||||
|
||||
public async Task PrintSummary()
|
||||
{
|
||||
var bufferWriter = new ArrayBufferWriter<byte>();
|
||||
await using var writer = new Utf8JsonWriter(bufferWriter, new JsonWriterOptions { Indented = false });
|
||||
|
||||
writer.WriteStartObject();
|
||||
|
||||
var diagnosticContext = new DiagnosticContext(serverStartTime, DateTime.UtcNow);
|
||||
foreach (var diagnosticEntry in entries)
|
||||
{
|
||||
await diagnosticEntry.WriteAsync(diagnosticContext, writer);
|
||||
}
|
||||
|
||||
writer.WriteEndObject();
|
||||
|
||||
await writer.FlushAsync();
|
||||
|
||||
var span = bufferWriter.WrittenSpan;
|
||||
var jsonMemory = await diagnosticDataService.GetJsonBytesAsync();
|
||||
var span = jsonMemory.Span;
|
||||
|
||||
using var diagnosticActivity = Tracing.DiagnosticsActivitySource.StartActivity("DiagnosticSummary");
|
||||
var chunkSize = options.Diagnostics.ChunkSize;
|
||||
|
|
@ -47,7 +32,7 @@ internal class DiagnosticSummary(DateTime serverStartTime, IEnumerable<IDiagnost
|
|||
}
|
||||
else
|
||||
{
|
||||
_logger.DiagnosticSummaryLogged(1, 1, Encoding.UTF8.GetString(bufferWriter.WrittenSpan));
|
||||
_logger.DiagnosticSummaryLogged(1, 1, Encoding.UTF8.GetString(span));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
// Copyright (c) Duende Software. All rights reserved.
|
||||
// See LICENSE in the project root for license information.
|
||||
|
||||
|
||||
#nullable enable
|
||||
|
||||
using System.Buffers;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using Duende.IdentityServer.Licensing.V2.Diagnostics;
|
||||
|
||||
namespace Duende.IdentityServer.Services;
|
||||
|
||||
public class DiagnosticDataService
|
||||
{
|
||||
private readonly DateTime _serverStartTime;
|
||||
private readonly IEnumerable<IDiagnosticEntry> _entries;
|
||||
|
||||
internal DiagnosticDataService(DateTime serverStartTime, IEnumerable<IDiagnosticEntry> entries)
|
||||
{
|
||||
_serverStartTime = serverStartTime;
|
||||
_entries = entries;
|
||||
}
|
||||
|
||||
public async Task<ReadOnlyMemory<byte>> GetJsonBytesAsync()
|
||||
{
|
||||
var bufferWriter = new ArrayBufferWriter<byte>();
|
||||
await using var writer = new Utf8JsonWriter(bufferWriter, new JsonWriterOptions { Indented = false });
|
||||
|
||||
writer.WriteStartObject();
|
||||
|
||||
var diagnosticContext = new DiagnosticContext(_serverStartTime, DateTime.UtcNow);
|
||||
foreach (var diagnosticEntry in _entries)
|
||||
{
|
||||
await diagnosticEntry.WriteAsync(diagnosticContext, writer);
|
||||
}
|
||||
|
||||
writer.WriteEndObject();
|
||||
|
||||
await writer.FlushAsync();
|
||||
|
||||
return bufferWriter.WrittenMemory;
|
||||
}
|
||||
|
||||
public async Task<string> GetJsonStringAsync()
|
||||
{
|
||||
var bytes = await GetJsonBytesAsync();
|
||||
return Encoding.UTF8.GetString(bytes.Span);
|
||||
}
|
||||
}
|
||||
|
|
@ -39,4 +39,28 @@ public class DynamicClientRegistrationTests : ConfigurationIntegrationTestBase
|
|||
newClient.ClientSecrets.Count.ShouldBe(1);
|
||||
newClient.ClientSecrets.Single().Value.ShouldBe(response.ClientSecret.Sha256());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task request_for_public_client_does_not_require_client_secret()
|
||||
{
|
||||
IdentityServerHost.ApiScopes.Add(new ApiScope("api1"));
|
||||
|
||||
var request = new DynamicClientRegistrationRequest
|
||||
{
|
||||
RedirectUris = new[] { new Uri("https://example.com/callback") },
|
||||
GrantTypes = new[] { "authorization_code" },
|
||||
ClientName = "test",
|
||||
ClientUri = new Uri("https://example.com"),
|
||||
DefaultMaxAge = 10000,
|
||||
Scope = "api1 openid profile",
|
||||
TokenEndpointAuthenticationMethod = "none"
|
||||
};
|
||||
var httpResponse = await ConfigurationHost.HttpClient!.PostAsJsonAsync("/connect/dcr", request);
|
||||
|
||||
var response = await httpResponse.Content.ReadFromJsonAsync<DynamicClientRegistrationResponse>();
|
||||
response.ShouldNotBeNull();
|
||||
response.ClientSecret.ShouldBeNull();
|
||||
response.RequireClientSecret.ShouldNotBeNull();
|
||||
response.RequireClientSecret.Value.ShouldBeFalse();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -113,4 +113,34 @@ public class DynamicClientRegistrationValidationTests : ConfigurationIntegration
|
|||
var error = await response.Content.ReadFromJsonAsync<DynamicClientRegistrationError>();
|
||||
error?.Error.ShouldBe("invalid_client_metadata");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task client_credentials_and_do_not_require_client_secret_should_fail()
|
||||
{
|
||||
var response = await ConfigurationHost.HttpClient!.PostAsJsonAsync("/connect/dcr",
|
||||
new DynamicClientRegistrationRequest
|
||||
{
|
||||
GrantTypes = { "client_credentials" },
|
||||
RequireClientSecret = false
|
||||
});
|
||||
response.StatusCode.ShouldBe(HttpStatusCode.BadRequest);
|
||||
|
||||
var error = await response.Content.ReadFromJsonAsync<DynamicClientRegistrationError>();
|
||||
error?.Error.ShouldBe("invalid_client_metadata");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task client_credentials_and_token_endpoint_auth_method_none_should_fail()
|
||||
{
|
||||
var response = await ConfigurationHost.HttpClient!.PostAsJsonAsync("/connect/dcr",
|
||||
new DynamicClientRegistrationRequest
|
||||
{
|
||||
GrantTypes = { "client_credentials" },
|
||||
TokenEndpointAuthenticationMethod = "none"
|
||||
});
|
||||
response.StatusCode.ShouldBe(HttpStatusCode.BadRequest);
|
||||
|
||||
var error = await response.Content.ReadFromJsonAsync<DynamicClientRegistrationError>();
|
||||
error?.Error.ShouldBe("invalid_client_metadata");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
using System.Text.Json;
|
||||
using Duende.IdentityServer.Configuration;
|
||||
using Duende.IdentityServer.Licensing.V2.Diagnostics;
|
||||
using Duende.IdentityServer.Services;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
|
|
@ -24,7 +25,8 @@ public class DiagnosticHostedServiceTests
|
|||
secondDiagnosticEntry,
|
||||
thirdDiagnosticEntry
|
||||
};
|
||||
var diagnosticSummary = new DiagnosticSummary(DateTime.UtcNow, entries, new IdentityServerOptions(), new StubLoggerFactory(diagnosticSummaryLogger));
|
||||
var diagnosticService = new DiagnosticDataService(DateTime.UtcNow, entries);
|
||||
var diagnosticSummary = new DiagnosticSummary(diagnosticService, new IdentityServerOptions(), new StubLoggerFactory(diagnosticSummaryLogger));
|
||||
|
||||
var options = Options.Create(new IdentityServerOptions());
|
||||
var logger = new NullLogger<DiagnosticHostedService>();
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using System.Text.Json;
|
|||
using Duende.IdentityServer.Configuration;
|
||||
using Duende.IdentityServer.Events;
|
||||
using Duende.IdentityServer.Licensing.V2.Diagnostics;
|
||||
using Duende.IdentityServer.Services;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Logging.Testing;
|
||||
|
||||
|
|
@ -25,7 +26,8 @@ public class DiagnosticSummaryTests
|
|||
secondDiagnosticEntry,
|
||||
thirdDiagnosticEntry
|
||||
};
|
||||
var summary = new DiagnosticSummary(DateTime.UtcNow, entries, new IdentityServerOptions(), new StubLoggerFactory(logger));
|
||||
var diagnosticService = new DiagnosticDataService(DateTime.UtcNow, entries);
|
||||
var summary = new DiagnosticSummary(diagnosticService, new IdentityServerOptions(), new StubLoggerFactory(logger));
|
||||
|
||||
await summary.PrintSummary();
|
||||
|
||||
|
|
@ -42,7 +44,8 @@ public class DiagnosticSummaryTests
|
|||
|
||||
var logger = new FakeLogger<DiagnosticSummary>();
|
||||
var diagnosticEntry = new LongDiagnosticEntry { OutputLength = chunkSize * 2 };
|
||||
var summary = new DiagnosticSummary(DateTime.UtcNow, [diagnosticEntry], options, new StubLoggerFactory(logger));
|
||||
var diagnosticService = new DiagnosticDataService(DateTime.UtcNow, [diagnosticEntry]);
|
||||
var summary = new DiagnosticSummary(diagnosticService, options, new StubLoggerFactory(logger));
|
||||
|
||||
await summary.PrintSummary();
|
||||
|
||||
|
|
@ -61,7 +64,9 @@ public class DiagnosticSummaryTests
|
|||
|
||||
var logger = new FakeLogger<DiagnosticSummary>();
|
||||
var diagnosticEntry = new LongDiagnosticEntry { OutputLength = 2, OutputCharacter = '€' };
|
||||
var summary = new DiagnosticSummary(DateTime.UtcNow, [diagnosticEntry], options, new StubLoggerFactory(logger));
|
||||
var diagnosticService = new DiagnosticDataService(DateTime.UtcNow, [diagnosticEntry]);
|
||||
var summary = new DiagnosticSummary(diagnosticService, options, new StubLoggerFactory(logger));
|
||||
|
||||
|
||||
await summary.PrintSummary();
|
||||
|
||||
|
|
@ -76,7 +81,9 @@ public class DiagnosticSummaryTests
|
|||
|
||||
var logger = new FakeLogger<DiagnosticSummary>();
|
||||
var diagnosticEntry = new LongDiagnosticEntry { OutputLength = options.Diagnostics.ChunkSize * 2 };
|
||||
var summary = new DiagnosticSummary(DateTime.UtcNow, [diagnosticEntry], options, new StubLoggerFactory(logger));
|
||||
var diagnosticService = new DiagnosticDataService(DateTime.UtcNow, [diagnosticEntry]);
|
||||
var summary = new DiagnosticSummary(diagnosticService, options, new StubLoggerFactory(logger));
|
||||
|
||||
|
||||
await summary.PrintSummary();
|
||||
foreach (var entry in logger.Collector.GetSnapshot())
|
||||
|
|
@ -91,7 +98,8 @@ public class DiagnosticSummaryTests
|
|||
var options = new IdentityServerOptions();
|
||||
var logger = new FakeLogger<DiagnosticSummary>();
|
||||
var diagnosticEntry = new LongDiagnosticEntry { OutputLength = 100000 };
|
||||
var summary = new DiagnosticSummary(DateTime.UtcNow, [diagnosticEntry], options, new StubLoggerFactory(logger));
|
||||
var diagnosticService = new DiagnosticDataService(DateTime.UtcNow, [diagnosticEntry]);
|
||||
var summary = new DiagnosticSummary(diagnosticService, options, new StubLoggerFactory(logger));
|
||||
|
||||
await summary.PrintSummary();
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,272 @@
|
|||
// Copyright (c) Duende Software. All rights reserved.
|
||||
// See LICENSE in the project root for license information.
|
||||
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using Duende.IdentityServer.Licensing.V2.Diagnostics;
|
||||
using Duende.IdentityServer.Services;
|
||||
|
||||
namespace IdentityServer.UnitTests.Services;
|
||||
|
||||
public class DiagnosticDataServiceTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task GetJsonBytesAsync_WithNoEntries_ShouldReturnEmptyJsonObject()
|
||||
{
|
||||
var serverStartTime = DateTime.UtcNow.AddMinutes(-5);
|
||||
var entries = new List<IDiagnosticEntry>();
|
||||
var service = new DiagnosticDataService(serverStartTime, entries);
|
||||
|
||||
var result = await service.GetJsonBytesAsync();
|
||||
|
||||
var json = Encoding.UTF8.GetString(result.Span);
|
||||
json.ShouldBe("{}");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetJsonBytesAsync_WithSingleEntry_ShouldReturnValidJson()
|
||||
{
|
||||
var serverStartTime = DateTime.UtcNow.AddMinutes(-5);
|
||||
var entries = new List<IDiagnosticEntry>
|
||||
{
|
||||
new TestDiagnosticEntry("TestProperty", "TestValue")
|
||||
};
|
||||
var service = new DiagnosticDataService(serverStartTime, entries);
|
||||
|
||||
var result = await service.GetJsonBytesAsync();
|
||||
|
||||
var json = Encoding.UTF8.GetString(result.Span);
|
||||
var jsonDoc = JsonDocument.Parse(json);
|
||||
jsonDoc.RootElement.GetProperty("TestProperty").GetString().ShouldBe("TestValue");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetJsonBytesAsync_WithMultipleEntries_ShouldIncludeAllEntries()
|
||||
{
|
||||
var serverStartTime = DateTime.UtcNow.AddMinutes(-5);
|
||||
var entries = new List<IDiagnosticEntry>
|
||||
{
|
||||
new TestDiagnosticEntry("Property1", "Value1"),
|
||||
new TestDiagnosticEntry("Property2", "Value2"),
|
||||
new TestDiagnosticEntry("Property3", "Value3")
|
||||
};
|
||||
var service = new DiagnosticDataService(serverStartTime, entries);
|
||||
|
||||
var result = await service.GetJsonBytesAsync();
|
||||
|
||||
var json = Encoding.UTF8.GetString(result.Span);
|
||||
var jsonDoc = JsonDocument.Parse(json);
|
||||
jsonDoc.RootElement.GetProperty("Property1").GetString().ShouldBe("Value1");
|
||||
jsonDoc.RootElement.GetProperty("Property2").GetString().ShouldBe("Value2");
|
||||
jsonDoc.RootElement.GetProperty("Property3").GetString().ShouldBe("Value3");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetJsonBytesAsync_ShouldPassCorrectDiagnosticContext()
|
||||
{
|
||||
var serverStartTime = DateTime.UtcNow.AddMinutes(-5);
|
||||
var capturedContext = new TestDiagnosticEntry.ContextCapture();
|
||||
var entries = new List<IDiagnosticEntry>
|
||||
{
|
||||
new TestDiagnosticEntry("TestProperty", "TestValue", capturedContext)
|
||||
};
|
||||
var service = new DiagnosticDataService(serverStartTime, entries);
|
||||
|
||||
await service.GetJsonBytesAsync();
|
||||
|
||||
capturedContext.Context.ShouldNotBeNull();
|
||||
capturedContext.Context.ServerStartTime.ShouldBe(serverStartTime);
|
||||
capturedContext.Context.CurrentSeverTime.ShouldBeGreaterThanOrEqualTo(serverStartTime);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetJsonBytesAsync_ShouldProduceCompactJson()
|
||||
{
|
||||
var serverStartTime = DateTime.UtcNow.AddMinutes(-5);
|
||||
var entries = new List<IDiagnosticEntry>
|
||||
{
|
||||
new TestDiagnosticEntry("Property1", "Value1"),
|
||||
new TestDiagnosticEntry("Property2", "Value2")
|
||||
};
|
||||
var service = new DiagnosticDataService(serverStartTime, entries);
|
||||
|
||||
var result = await service.GetJsonBytesAsync();
|
||||
|
||||
var json = Encoding.UTF8.GetString(result.Span);
|
||||
json.ShouldNotContain("\n");
|
||||
json.ShouldNotContain("\r");
|
||||
json.ShouldNotContain(" ");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetJsonStringAsync_WithNoEntries_ShouldReturnEmptyJsonObject()
|
||||
{
|
||||
var serverStartTime = DateTime.UtcNow.AddMinutes(-5);
|
||||
var entries = new List<IDiagnosticEntry>();
|
||||
var service = new DiagnosticDataService(serverStartTime, entries);
|
||||
|
||||
var result = await service.GetJsonStringAsync();
|
||||
|
||||
result.ShouldBe("{}");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetJsonStringAsync_WithSingleEntry_ShouldReturnValidJson()
|
||||
{
|
||||
var serverStartTime = DateTime.UtcNow.AddMinutes(-5);
|
||||
var entries = new List<IDiagnosticEntry>
|
||||
{
|
||||
new TestDiagnosticEntry("TestProperty", "TestValue")
|
||||
};
|
||||
var service = new DiagnosticDataService(serverStartTime, entries);
|
||||
|
||||
var result = await service.GetJsonStringAsync();
|
||||
|
||||
var jsonDoc = JsonDocument.Parse(result);
|
||||
jsonDoc.RootElement.GetProperty("TestProperty").GetString().ShouldBe("TestValue");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetJsonStringAsync_WithMultipleEntries_ShouldIncludeAllEntries()
|
||||
{
|
||||
var serverStartTime = DateTime.UtcNow.AddMinutes(-5);
|
||||
var entries = new List<IDiagnosticEntry>
|
||||
{
|
||||
new TestDiagnosticEntry("Property1", "Value1"),
|
||||
new TestDiagnosticEntry("Property2", "Value2"),
|
||||
new TestDiagnosticEntry("Property3", "Value3")
|
||||
};
|
||||
var service = new DiagnosticDataService(serverStartTime, entries);
|
||||
|
||||
var result = await service.GetJsonStringAsync();
|
||||
|
||||
var jsonDoc = JsonDocument.Parse(result);
|
||||
jsonDoc.RootElement.GetProperty("Property1").GetString().ShouldBe("Value1");
|
||||
jsonDoc.RootElement.GetProperty("Property2").GetString().ShouldBe("Value2");
|
||||
jsonDoc.RootElement.GetProperty("Property3").GetString().ShouldBe("Value3");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetJsonStringAsync_ShouldReturnUtf8EncodedString()
|
||||
{
|
||||
var serverStartTime = DateTime.UtcNow.AddMinutes(-5);
|
||||
var entries = new List<IDiagnosticEntry>
|
||||
{
|
||||
new TestDiagnosticEntry("Property", "Value with émojis 🎉")
|
||||
};
|
||||
var service = new DiagnosticDataService(serverStartTime, entries);
|
||||
|
||||
var result = await service.GetJsonStringAsync();
|
||||
|
||||
var jsonDoc = JsonDocument.Parse(result);
|
||||
jsonDoc.RootElement.GetProperty("Property").GetString().ShouldBe("Value with émojis 🎉");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetJsonStringAsync_ShouldMatchGetJsonBytesAsync()
|
||||
{
|
||||
var serverStartTime = DateTime.UtcNow.AddMinutes(-5);
|
||||
var entries = new List<IDiagnosticEntry>
|
||||
{
|
||||
new TestDiagnosticEntry("Property1", "Value1"),
|
||||
new TestDiagnosticEntry("Property2", "Value2")
|
||||
};
|
||||
var service = new DiagnosticDataService(serverStartTime, entries);
|
||||
|
||||
var stringResult = await service.GetJsonStringAsync();
|
||||
var bytesResult = await service.GetJsonBytesAsync();
|
||||
var stringFromBytes = Encoding.UTF8.GetString(bytesResult.Span);
|
||||
|
||||
stringResult.ShouldBe(stringFromBytes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetJsonBytesAsync_WithComplexEntry_ShouldWriteNestedObjects()
|
||||
{
|
||||
var serverStartTime = DateTime.UtcNow.AddMinutes(-5);
|
||||
var entries = new List<IDiagnosticEntry>
|
||||
{
|
||||
new ComplexTestDiagnosticEntry()
|
||||
};
|
||||
var service = new DiagnosticDataService(serverStartTime, entries);
|
||||
|
||||
var result = await service.GetJsonBytesAsync();
|
||||
|
||||
var json = Encoding.UTF8.GetString(result.Span);
|
||||
var jsonDoc = JsonDocument.Parse(json);
|
||||
var complex = jsonDoc.RootElement.GetProperty("ComplexData");
|
||||
complex.GetProperty("StringValue").GetString().ShouldBe("test");
|
||||
complex.GetProperty("NumberValue").GetInt32().ShouldBe(42);
|
||||
complex.GetProperty("BoolValue").GetBoolean().ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetJsonBytesAsync_WithAsyncEntry_ShouldHandleAsyncWrites()
|
||||
{
|
||||
var serverStartTime = DateTime.UtcNow.AddMinutes(-5);
|
||||
var entries = new List<IDiagnosticEntry>
|
||||
{
|
||||
new AsyncTestDiagnosticEntry()
|
||||
};
|
||||
var service = new DiagnosticDataService(serverStartTime, entries);
|
||||
|
||||
var result = await service.GetJsonBytesAsync();
|
||||
|
||||
var json = Encoding.UTF8.GetString(result.Span);
|
||||
var jsonDoc = JsonDocument.Parse(json);
|
||||
jsonDoc.RootElement.GetProperty("AsyncData").GetString().ShouldBe("async value");
|
||||
}
|
||||
|
||||
// Test helper classes
|
||||
private class TestDiagnosticEntry : IDiagnosticEntry
|
||||
{
|
||||
private readonly string _propertyName;
|
||||
private readonly string _propertyValue;
|
||||
private readonly ContextCapture _contextCapture;
|
||||
|
||||
public TestDiagnosticEntry(string propertyName, string propertyValue, ContextCapture contextCapture = null)
|
||||
{
|
||||
_propertyName = propertyName;
|
||||
_propertyValue = propertyValue;
|
||||
_contextCapture = contextCapture;
|
||||
}
|
||||
|
||||
public Task WriteAsync(DiagnosticContext context, Utf8JsonWriter writer)
|
||||
{
|
||||
if (_contextCapture != null)
|
||||
{
|
||||
_contextCapture.Context = context;
|
||||
}
|
||||
writer.WriteString(_propertyName, _propertyValue);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public class ContextCapture
|
||||
{
|
||||
public DiagnosticContext Context { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
private class ComplexTestDiagnosticEntry : IDiagnosticEntry
|
||||
{
|
||||
public Task WriteAsync(DiagnosticContext context, Utf8JsonWriter writer)
|
||||
{
|
||||
writer.WritePropertyName("ComplexData");
|
||||
writer.WriteStartObject();
|
||||
writer.WriteString("StringValue", "test");
|
||||
writer.WriteNumber("NumberValue", 42);
|
||||
writer.WriteBoolean("BoolValue", true);
|
||||
writer.WriteEndObject();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
private class AsyncTestDiagnosticEntry : IDiagnosticEntry
|
||||
{
|
||||
public async Task WriteAsync(DiagnosticContext context, Utf8JsonWriter writer)
|
||||
{
|
||||
await Task.Delay(1);
|
||||
writer.WriteString("AsyncData", "async value");
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue