From 29e59ebc69d67f676ee9412dc9c89af7b8d0a09a Mon Sep 17 00:00:00 2001 From: Joe DeCock Date: Mon, 27 Nov 2023 15:24:31 -0600 Subject: [PATCH] Add test using resource parameters --- test/Tests/Framework/ApiHost.cs | 3 +- test/Tests/Framework/AppHost.cs | 16 ++++- test/Tests/Framework/IdentityServerHost.cs | 6 ++ test/Tests/UserTokenManagementTests.cs | 80 ++++++++++++++++++++++ 4 files changed, 103 insertions(+), 2 deletions(-) diff --git a/test/Tests/Framework/ApiHost.cs b/test/Tests/Framework/ApiHost.cs index 4f3a286..ea12cd8 100644 --- a/test/Tests/Framework/ApiHost.cs +++ b/test/Tests/Framework/ApiHost.cs @@ -14,11 +14,12 @@ public class ApiHost : GenericHost private readonly IdentityServerHost _identityServerHost; public event Action ApiInvoked = ctx => { }; - public ApiHost(IdentityServerHost identityServerHost, string scope, string baseAddress = "https://api") + public ApiHost(IdentityServerHost identityServerHost, string scope, string baseAddress = "https://api", string resource = "urn:api") : base(baseAddress) { _identityServerHost = identityServerHost; _identityServerHost.ApiScopes.Add(new ApiScope(scope)); + _identityServerHost.ApiResources.Add(new ApiResource(resource)); OnConfigureServices += ConfigureServices; OnConfigure += Configure; diff --git a/test/Tests/Framework/AppHost.cs b/test/Tests/Framework/AppHost.cs index 9af39c4..5300c11 100644 --- a/test/Tests/Framework/AppHost.cs +++ b/test/Tests/Framework/AppHost.cs @@ -7,6 +7,8 @@ using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Http; using RichardSzalay.MockHttp; +using Duende.AccessTokenManagement.OpenIdConnect; +using Microsoft.AspNetCore.Mvc; namespace Duende.AccessTokenManagement.Tests; @@ -92,7 +94,10 @@ private void ConfigureServices(IServiceCollection services) }); services.AddDistributedMemoryCache(); - services.AddOpenIdConnectAccessTokenManagement(); + services.AddOpenIdConnectAccessTokenManagement(opt => + { + opt.UseChallengeSchemeScopedTokens = true; + }); } @@ -122,6 +127,15 @@ await context.ChallengeAsync(new AuthenticationProperties var token = await context.GetUserAccessTokenAsync(); await context.Response.WriteAsJsonAsync(token); }); + + endpoints.MapGet("/user_token_with_resource/{resource}", async (string resource, HttpContext context) => + { + var token = await context.GetUserAccessTokenAsync(new UserTokenRequestParameters + { + Resource = resource + }); + await context.Response.WriteAsJsonAsync(token); + }); endpoints.MapGet("/client_token", async context => { diff --git a/test/Tests/Framework/IdentityServerHost.cs b/test/Tests/Framework/IdentityServerHost.cs index df48d45..c547708 100644 --- a/test/Tests/Framework/IdentityServerHost.cs +++ b/test/Tests/Framework/IdentityServerHost.cs @@ -31,6 +31,11 @@ public IdentityServerHost(string baseAddress = "https://identityserver") }; public List ApiScopes { get; set; } = new(); + public List ApiResources { get; set; } = new() + { + new ApiResource("urn:api1"), + new ApiResource("urn:api2") + }; private void ConfigureServices(IServiceCollection services) { @@ -47,6 +52,7 @@ private void ConfigureServices(IServiceCollection services) }) .AddInMemoryClients(Clients) .AddInMemoryIdentityResources(IdentityResources) + .AddInMemoryApiResources(ApiResources) .AddInMemoryApiScopes(ApiScopes); } diff --git a/test/Tests/UserTokenManagementTests.cs b/test/Tests/UserTokenManagementTests.cs index e6cf89f..0d797d8 100644 --- a/test/Tests/UserTokenManagementTests.cs +++ b/test/Tests/UserTokenManagementTests.cs @@ -269,4 +269,84 @@ public async Task Short_token_lifetime_should_trigger_refresh() token.RefreshToken.ShouldBe("refreshed2_refresh_token"); token.Expiration.ShouldNotBe(DateTimeOffset.MaxValue); } + + [Fact] + public async Task Resources_get_distinct_tokens() + { + var mockHttp = new MockHttpMessageHandler(); + AppHost.IdentityServerHttpHandler = mockHttp; + + // no resource specified + var initialTokenResponse = new + { + id_token = IdentityServerHost.CreateIdToken("1", "web"), + access_token = "access_token_without_resource", + token_type = "token_type", + expires_in = 3600, + refresh_token = "initial_refresh_token", + }; + mockHttp.When("/connect/token") + .WithFormData("grant_type", "authorization_code") + .Respond("application/json", JsonSerializer.Serialize(initialTokenResponse)); + + // resource 1 specified + var resource1TokenResponse = new + { + access_token = "urn:api1_access_token", + token_type = "token_type1", + expires_in = 3600, + refresh_token = "initial_refresh_token", + }; + mockHttp.When("/connect/token") + .WithFormData("grant_type", "refresh_token") + .WithFormData("resource", "urn:api1") + .Respond("application/json", JsonSerializer.Serialize(resource1TokenResponse)); + + // resource 2 specified + var resource2TokenResponse = new + { + access_token = "urn:api2_access_token", + token_type = "token_type1", + expires_in = 3600, + refresh_token = "initial_refresh_token", + }; + mockHttp.When("/connect/token") + .WithFormData("grant_type", "refresh_token") + .WithFormData("resource", "urn:api2") + .Respond("application/json", JsonSerializer.Serialize(resource2TokenResponse)); + + // setup host + await AppHost.InitializeAsync(); + await AppHost.LoginAsync("alice"); + + // first request - no resource + var response = await AppHost.BrowserClient.GetAsync(AppHost.Url("/user_token")); + var token = await response.Content.ReadFromJsonAsync(); + + token.ShouldNotBeNull(); + token.IsError.ShouldBeFalse(); + token.AccessToken.ShouldBe("access_token_without_resource"); + token.RefreshToken.ShouldBe("initial_refresh_token"); + token.Expiration.ShouldNotBe(DateTimeOffset.MaxValue); + + // second request - with resource api1 + response = await AppHost.BrowserClient.GetAsync(AppHost.Url("/user_token_with_resource/urn:api1")); + token = await response.Content.ReadFromJsonAsync(); + + token.ShouldNotBeNull(); + token.IsError.ShouldBeFalse(); + token.AccessToken.ShouldBe("urn:api1_access_token"); + token.RefreshToken.ShouldBe("initial_refresh_token"); // This doesn't change with resources! + token.Expiration.ShouldNotBe(DateTimeOffset.MaxValue); + + // third request - with resource api2 + response = await AppHost.BrowserClient.GetAsync(AppHost.Url("/user_token_with_resource/urn:api2")); + token = await response.Content.ReadFromJsonAsync(); + + token.ShouldNotBeNull(); + token.IsError.ShouldBeFalse(); + token.AccessToken.ShouldBe("urn:api2_access_token"); + token.RefreshToken.ShouldBe("initial_refresh_token"); + token.Expiration.ShouldNotBe(DateTimeOffset.MaxValue); + } } \ No newline at end of file