| File: Integration\MockOpenIdAuthority.cs | Web Access |
| Project: src\tests\Aspire.Dashboard.Tests\Aspire.Dashboard.Tests.csproj (Aspire.Dashboard.Tests) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Globalization; using System.Net; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting.Server.Features; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.Tokens; using Xunit; namespace Aspire.Dashboard.Tests.Integration; internal static class MockOpenIdAuthority { /// <summary> /// Creates a mock authority (identity provider) that will handle the basics of the protocol, for use in automated tests. /// </summary> public static async Task<Authority> CreateAsync() { var webHost = new WebHostBuilder() .ConfigureServices(services => services.AddRouting()) .UseKestrel(options => { // Bind to loopback on a random available port options.Listen(IPAddress.Loopback, 0); }) .Configure(app => { // Based on code from https://github.com/dotnet/aspnetcore/blob/3f99d45b0b7d8f0427a3d98acc63098694613362/src/Components/test/testassets/Components.TestServer/RemoteAuthenticationStartup.cs#L37-L94 app.UseRouting(); app.UseEndpoints(endpoints => { var issuer = ""; var lastCode = ""; var jwtHandler = new JsonWebTokenHandler(); endpoints.MapGet( ".well-known/openid-configuration", (HttpRequest request, [FromHeader] string host) => { issuer = $"{(request.IsHttps ? "https" : "http")}://{host}"; return Results.Json(new { issuer, authorization_endpoint = $"{issuer}/authorize", token_endpoint = $"{issuer}/token", }); }); endpoints.MapGet( "authorize", (string redirect_uri, string? state, string? prompt, bool? preservedExtraQueryParams) => { // Require interaction so silent sign-in does not skip RedirectToLogin.razor. if (prompt == "none") { return Results.Redirect($"{redirect_uri}?error=interaction_required&state={state}"); } // Verify that the extra query parameters added by RedirectToLogin.razor are preserved. if (preservedExtraQueryParams != true) { return Results.Redirect($"{redirect_uri}?error=invalid_request&error_description=extraQueryParams%20not%20preserved&state={state}"); } lastCode = Random.Shared.Next().ToString(CultureInfo.InvariantCulture); return Results.Redirect($"{redirect_uri}?code={lastCode}&state={state}"); }); endpoints.MapPost( "token", ([FromForm] string code) => { if (string.IsNullOrEmpty(lastCode) && code != lastCode) { return Results.BadRequest("Bad code"); } return Results.Json(new { token_type = "Bearer", scope = "openid profile", expires_in = 3600, id_token = jwtHandler.CreateToken(new SecurityTokenDescriptor { Issuer = issuer, Audience = "s6BhdRkqt3", Claims = new Dictionary<string, object> { ["sub"] = "248289761001", ["name"] = "Jane Doe", }, }), }); }).DisableAntiforgery(); }); }) .ConfigureLogging(logging => { // Log to the console logging.AddConsole(); }) .Build(); await webHost.StartAsync(); return new Authority(webHost, url: GetBoundAddress()); string GetBoundAddress() { var serverAddress = webHost.ServerFeatures.Get<IServerAddressesFeature>(); Assert.NotNull(serverAddress); var authorityUrl = serverAddress.Addresses.First().Replace("127.0.0.1", "localhost"); Assert.StartsWith("http://localhost", authorityUrl); return authorityUrl; } } public sealed class Authority(IWebHost webHost, string url) : IAsyncDisposable { public string Url => url; public async ValueTask DisposeAsync() { await webHost.StopAsync(); webHost.Dispose(); } } }