mirror of
https://github.com/DuendeSoftware/products
synced 2026-05-24 09:28:24 +00:00
Rewriting the tests to use new test setup (#2052)
* refactor IAccessTokenRetrieverTests * refactoring tests * ManagementBasePathTests * LogoutEndpointTests
This commit is contained in:
parent
2189d8d874
commit
e47064fa1b
27 changed files with 350 additions and 198 deletions
|
|
@ -98,7 +98,7 @@ app.UseAntiforgery();
|
|||
|
||||
app.MapBffManagementEndpoints();
|
||||
|
||||
app.MapRemoteBffApiEndpoint("/remote-apis/user-token", "https://localhost:5010")
|
||||
app.MapRemoteBffApiEndpoint("/remote-apis/user-token", new Uri("https://localhost:5010"))
|
||||
.WithAccessToken(RequiredTokenType.User);
|
||||
|
||||
app.MapRazorComponents<App>()
|
||||
|
|
|
|||
|
|
@ -184,22 +184,22 @@ internal static class Extensions
|
|||
private static void MapRemoteUrls(IEndpointRouteBuilder app)
|
||||
{
|
||||
// On this path, we use a client credentials token
|
||||
app.MapRemoteBffApiEndpoint("/api/client-token", "https://localhost:5011")
|
||||
app.MapRemoteBffApiEndpoint("/api/client-token", new Uri("https://localhost:5011"))
|
||||
.WithAccessToken(RequiredTokenType.Client);
|
||||
|
||||
// On this path, we use a user token if logged in, and fall back to a client credentials token if not
|
||||
app.MapRemoteBffApiEndpoint("/api/user-or-client-token", "https://localhost:5011")
|
||||
app.MapRemoteBffApiEndpoint("/api/user-or-client-token", new Uri("https://localhost:5011"))
|
||||
.WithAccessToken(RequiredTokenType.UserOrClient);
|
||||
|
||||
// On this path, we make anonymous requests
|
||||
app.MapRemoteBffApiEndpoint("/api/anonymous", "https://localhost:5011");
|
||||
app.MapRemoteBffApiEndpoint("/api/anonymous", new Uri("https://localhost:5011"));
|
||||
|
||||
// On this path, we use the client token only if the user is logged in
|
||||
app.MapRemoteBffApiEndpoint("/api/optional-user-token", "https://localhost:5011")
|
||||
app.MapRemoteBffApiEndpoint("/api/optional-user-token", new Uri("https://localhost:5011"))
|
||||
.WithAccessToken(RequiredTokenType.UserOrNone);
|
||||
|
||||
// On this path, we require the user token
|
||||
app.MapRemoteBffApiEndpoint("/api/user-token", "https://localhost:5011")
|
||||
app.MapRemoteBffApiEndpoint("/api/user-token", new Uri("https://localhost:5011"))
|
||||
.WithAccessToken();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ internal static class Extensions
|
|||
// all calls to /api/* will be forwarded to the remote API
|
||||
// user or client access token will be attached in API call
|
||||
// user access token will be managed automatically using the refresh token
|
||||
app.MapRemoteBffApiEndpoint("/api", "https://localhost:5010")
|
||||
app.MapRemoteBffApiEndpoint("/api", new Uri("https://localhost:5010"))
|
||||
.WithAccessToken(RequiredTokenType.UserOrClient);
|
||||
|
||||
return app;
|
||||
|
|
|
|||
|
|
@ -128,33 +128,33 @@ internal static class Extensions
|
|||
//////////////////////////////////////
|
||||
|
||||
// On this path, we use a client credentials token
|
||||
app.MapRemoteBffApiEndpoint("/api/client-token", "https://localhost:5010")
|
||||
app.MapRemoteBffApiEndpoint("/api/client-token", new Uri("https://localhost:5010"))
|
||||
.WithAccessToken(RequiredTokenType.Client);
|
||||
|
||||
// On this path, we use a user token if logged in, and fall back to a client credentials token if not
|
||||
app.MapRemoteBffApiEndpoint("/api/user-or-client-token", "https://localhost:5010")
|
||||
app.MapRemoteBffApiEndpoint("/api/user-or-client-token", new Uri("https://localhost:5010"))
|
||||
.WithAccessToken(RequiredTokenType.UserOrClient);
|
||||
|
||||
// On this path, we make anonymous requests
|
||||
app.MapRemoteBffApiEndpoint("/api/anonymous", "https://localhost:5010");
|
||||
app.MapRemoteBffApiEndpoint("/api/anonymous", new Uri("https://localhost:5010"));
|
||||
|
||||
// On this path, we use the client token only if the user is logged in
|
||||
app.MapRemoteBffApiEndpoint("/api/optional-user-token", "https://localhost:5010")
|
||||
app.MapRemoteBffApiEndpoint("/api/optional-user-token", new Uri("https://localhost:5010"))
|
||||
.WithAccessToken(RequiredTokenType.UserOrNone);
|
||||
|
||||
// On this path, we require the user token
|
||||
app.MapRemoteBffApiEndpoint("/api/user-token", "https://localhost:5010")
|
||||
app.MapRemoteBffApiEndpoint("/api/user-token", new Uri("https://localhost:5010"))
|
||||
.WithAccessToken();
|
||||
|
||||
// On this path, we perform token exchange to impersonate a different user
|
||||
// before making the api request
|
||||
app.MapRemoteBffApiEndpoint("/api/impersonation", "https://localhost:5010")
|
||||
app.MapRemoteBffApiEndpoint("/api/impersonation", new Uri("https://localhost:5010"))
|
||||
.WithAccessToken()
|
||||
.WithAccessTokenRetriever<ImpersonationAccessTokenRetriever>();
|
||||
|
||||
// On this path, we obtain an audience constrained token and invoke
|
||||
// a different api that requires such a token
|
||||
app.MapRemoteBffApiEndpoint("/api/audience-constrained", "https://localhost:5012")
|
||||
app.MapRemoteBffApiEndpoint("/api/audience-constrained", new Uri("https://localhost:5012"))
|
||||
.WithAccessToken()
|
||||
.WithUserAccessTokenParameter(new BffUserAccessTokenParameters { Resource = Resource.Parse("urn:isolated-api") });
|
||||
|
||||
|
|
|
|||
|
|
@ -51,11 +51,11 @@ public class BffHost : Host
|
|||
|
||||
app.MapBffManagementEndpoints();
|
||||
|
||||
app.MapRemoteBffApiEndpoint("/allow_anon", apiUri.ToString());
|
||||
app.MapRemoteBffApiEndpoint("/client_token", apiUri.ToString())
|
||||
app.MapRemoteBffApiEndpoint("/allow_anon", apiUri);
|
||||
app.MapRemoteBffApiEndpoint("/client_token", apiUri)
|
||||
.WithAccessToken(RequiredTokenType.Client);
|
||||
|
||||
app.MapRemoteBffApiEndpoint("/user_token", apiUri.ToString())
|
||||
app.MapRemoteBffApiEndpoint("/user_token", apiUri)
|
||||
.WithAccessToken(RequiredTokenType.User);
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ public static class RouteBuilderExtensions
|
|||
public static IEndpointConventionBuilder MapRemoteBffApiEndpoint(
|
||||
this IEndpointRouteBuilder endpoints,
|
||||
PathString localPath,
|
||||
string apiAddress,
|
||||
Uri apiAddress,
|
||||
Action<TransformBuilderContext>? yarpTransformBuilder = null)
|
||||
{
|
||||
endpoints.CheckLicense();
|
||||
|
|
@ -45,11 +45,12 @@ public static class RouteBuilderExtensions
|
|||
|
||||
return endpoints.MapForwarder(
|
||||
pattern: localPath.Add("/{**catch-all}").Value!,
|
||||
destinationPrefix: apiAddress,
|
||||
destinationPrefix: apiAddress.ToString(),
|
||||
configureTransform: context =>
|
||||
{
|
||||
yarpTransformBuilder(context);
|
||||
})
|
||||
.WithMetadata(new BffRemoteApiEndpointMetadata());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,15 @@ internal class OpenIdConnectCallbackMiddleware(RequestDelegate next,
|
|||
return;
|
||||
}
|
||||
}
|
||||
if (context.Request.Path.StartsWithSegments(options.SignedOutCallbackPath))
|
||||
{
|
||||
var handlers = context.RequestServices.GetRequiredService<IAuthenticationHandlerProvider>();
|
||||
if (await handlers.GetHandlerAsync(context, frontend.OidcSchemeName) is IAuthenticationRequestHandler handler)
|
||||
{
|
||||
await handler.HandleRequestAsync();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
await next(context);
|
||||
}
|
||||
|
|
|
|||
0
bff/src/Bff/Endpoints/Internal/IUserEndpoint.cs
Normal file
0
bff/src/Bff/Endpoints/Internal/IUserEndpoint.cs
Normal file
|
|
@ -44,7 +44,7 @@ public class DPoPTestsWithManualAuthentication : BffTestBase, IAsyncLifetime
|
|||
Bff.OnConfigureBff += bff => bff.AddRemoteApis();
|
||||
Bff.OnConfigureEndpoints += endpoints =>
|
||||
{
|
||||
endpoints.MapRemoteBffApiEndpoint(The.Path, Api.Url().ToString())
|
||||
endpoints.MapRemoteBffApiEndpoint(The.Path, Api.Url())
|
||||
.WithAccessToken(RequiredTokenType.Client)
|
||||
;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ public class DpopRemoteEndpointTests : BffTestBase, IAsyncLifetime
|
|||
Bff.OnConfigureBff += bff => bff.AddRemoteApis();
|
||||
Bff.OnConfigureEndpoints += endpoints =>
|
||||
{
|
||||
endpoints.MapRemoteBffApiEndpoint(The.Path, Api.Url().ToString())
|
||||
endpoints.MapRemoteBffApiEndpoint(The.Path, Api.Url())
|
||||
.WithAccessToken(RequiredTokenType.Client)
|
||||
;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,19 +2,21 @@
|
|||
// See LICENSE in the project root for license information.
|
||||
|
||||
using System.Net;
|
||||
using Duende.Bff.Configuration;
|
||||
using Duende.Bff.Tests.TestHosts;
|
||||
using System.Security.Claims;
|
||||
using Duende.Bff.Tests.TestInfra;
|
||||
using Duende.IdentityModel;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Duende.Bff.Tests.Endpoints.Management;
|
||||
|
||||
public class LogoutEndpointTests(ITestOutputHelper output) : BffIntegrationTestBase(output)
|
||||
public class LogoutEndpointTests(ITestOutputHelper output) : BffTestBase(output)
|
||||
{
|
||||
[Fact]
|
||||
public async Task logout_endpoint_should_allow_anonymous()
|
||||
[Theory]
|
||||
[MemberData(nameof(AllSetups))]
|
||||
public async Task logout_endpoint_should_allow_anonymous(BffSetupType setup)
|
||||
{
|
||||
ConfigureBff(setup);
|
||||
Bff.OnConfigureServices += svcs =>
|
||||
{
|
||||
svcs.AddAuthorization(opts =>
|
||||
|
|
@ -25,134 +27,155 @@ public class LogoutEndpointTests(ITestOutputHelper output) : BffIntegrationTestB
|
|||
.Build();
|
||||
});
|
||||
};
|
||||
await Bff.InitializeAsync();
|
||||
|
||||
await InitializeAsync();
|
||||
|
||||
var response = await Bff.BrowserClient.GetAsync(Bff.Url("/bff/logout"));
|
||||
response.StatusCode.ShouldNotBe(HttpStatusCode.Unauthorized);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task logout_endpoint_should_signout()
|
||||
[Theory]
|
||||
[MemberData(nameof(AllSetups))]
|
||||
public async Task logout_endpoint_should_signout(BffSetupType setup)
|
||||
{
|
||||
await Bff.BffLoginAsync("alice", "sid123");
|
||||
ConfigureBff(setup);
|
||||
await InitializeAsync();
|
||||
|
||||
await Bff.BffLogoutAsync("sid123");
|
||||
await Bff.BrowserClient.Login();
|
||||
|
||||
(await Bff.GetIsUserLoggedInAsync()).ShouldBeFalse();
|
||||
var response = await Bff.BrowserClient.Logout();
|
||||
response.StatusCode.ShouldBe(HttpStatusCode.OK);
|
||||
response.RequestMessage!.RequestUri.ShouldBe(Bff.Url());
|
||||
|
||||
(await Bff.BrowserClient.GetIsUserLoggedInAsync()).ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task logout_endpoint_for_authenticated_should_require_sid()
|
||||
[Theory]
|
||||
[MemberData(nameof(AllSetups))]
|
||||
public async Task logout_endpoint_for_authenticated_should_require_sid(BffSetupType setup)
|
||||
{
|
||||
await Bff.BffLoginAsync("alice", "sid123");
|
||||
ConfigureBff(setup);
|
||||
await InitializeAsync();
|
||||
await Bff.BrowserClient.Login();
|
||||
|
||||
var problem = await Bff.BrowserClient.GetAsync(Bff.Url("/bff/logout"))
|
||||
.ShouldBeProblem();
|
||||
|
||||
problem.Errors.ShouldContainKey(JwtClaimTypes.SessionId);
|
||||
|
||||
(await Bff.GetIsUserLoggedInAsync()).ShouldBeTrue();
|
||||
(await Bff.BrowserClient.GetIsUserLoggedInAsync()).ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task logout_endpoint_for_authenticated_when_require_option_is_false_should_not_require_sid()
|
||||
[Theory]
|
||||
[MemberData(nameof(AllSetups))]
|
||||
public async Task logout_endpoint_for_authenticated_when_require_option_is_false_should_not_require_sid(BffSetupType setup)
|
||||
{
|
||||
await Bff.BffLoginAsync("alice", "sid123");
|
||||
ConfigureBff(setup);
|
||||
await InitializeAsync();
|
||||
await Bff.BrowserClient.Login();
|
||||
|
||||
Bff.BffOptions.RequireLogoutSessionId = false;
|
||||
Bff.BrowserClient.RedirectHandler.AutoFollowRedirects = false;
|
||||
|
||||
var response = await Bff.BrowserClient.GetAsync(Bff.Url("/bff/logout"));
|
||||
response.StatusCode.ShouldBe(HttpStatusCode.Redirect); // endsession
|
||||
response.Headers.Location!.ToString().ToLowerInvariant().ShouldStartWith(IdentityServer.Url("/connect/endsession"));
|
||||
response.Headers.Location!.ToString().ToLowerInvariant().ShouldStartWith(IdentityServer.Url("/connect/endsession").ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task logout_endpoint_for_authenticated_user_without_sid_should_succeed()
|
||||
[Theory]
|
||||
[MemberData(nameof(AllSetups))]
|
||||
public async Task logout_endpoint_for_authenticated_user_without_sid_should_succeed(BffSetupType setup)
|
||||
{
|
||||
// workaround for RevokeUserRefreshTokenAsync throwing when no RT in session
|
||||
Bff.OnConfigureServices += svcs =>
|
||||
|
||||
// Workaround to place a session cookie in the BFF without a session id claim.
|
||||
Bff.OnConfigureEndpoints += endpoints =>
|
||||
{
|
||||
svcs.Configure<BffOptions>(options =>
|
||||
endpoints.MapGet("/__signin", async ctx =>
|
||||
{
|
||||
options.RevokeRefreshTokenOnLogout = false;
|
||||
var props = new AuthenticationProperties();
|
||||
await ctx.SignInAsync(new ClaimsPrincipal(new ClaimsIdentity([new Claim(JwtClaimTypes.Subject, The.Sub)], "test", "name", "role")), props);
|
||||
|
||||
ctx.Response.StatusCode = 204;
|
||||
});
|
||||
};
|
||||
await Bff.InitializeAsync();
|
||||
|
||||
await Bff.IssueSessionCookieAsync("alice");
|
||||
ConfigureBff(setup);
|
||||
await InitializeAsync();
|
||||
await Bff.BrowserClient.GetAsync("__signin");
|
||||
|
||||
// workaround for RevokeUserRefreshTokenAsync throwing when no RT in session
|
||||
Bff.BffOptions.RevokeRefreshTokenOnLogout = false;
|
||||
|
||||
Bff.BrowserClient.RedirectHandler.AutoFollowRedirects = false;
|
||||
|
||||
var response = await Bff.BrowserClient.GetAsync(Bff.Url("/bff/logout"));
|
||||
response.StatusCode.ShouldBe(HttpStatusCode.Redirect); // endsession
|
||||
response.Headers.Location!.ToString().ToLowerInvariant().ShouldStartWith(IdentityServer.Url("/connect/endsession"));
|
||||
response.Headers.Location!.ToString().ToLowerInvariant().ShouldStartWith(IdentityServer.Url("/connect/endsession").ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task logout_endpoint_for_anonymous_user_without_sid_should_succeed()
|
||||
[Theory]
|
||||
[MemberData(nameof(AllSetups))]
|
||||
public async Task logout_endpoint_for_anonymous_user_without_sid_should_succeed(BffSetupType setup)
|
||||
{
|
||||
ConfigureBff(setup);
|
||||
await InitializeAsync();
|
||||
|
||||
Bff.BrowserClient.RedirectHandler.AutoFollowRedirects = false;
|
||||
|
||||
var response = await Bff.BrowserClient.GetAsync(Bff.Url("/bff/logout"));
|
||||
response.StatusCode.ShouldBe(HttpStatusCode.Redirect); // endsession
|
||||
response.Headers.Location!.ToString().ToLowerInvariant().ShouldStartWith(IdentityServer.Url("/connect/endsession"));
|
||||
response.Headers.Location!.ToString().ToLowerInvariant().ShouldStartWith(IdentityServer.Url("/connect/endsession").ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task logout_endpoint_should_redirect_to_external_signout_and_return_to_root()
|
||||
[Theory]
|
||||
[MemberData(nameof(AllSetups))]
|
||||
public async Task can_logout_twice(BffSetupType setup)
|
||||
{
|
||||
await Bff.BffLoginAsync("alice", "sid123");
|
||||
ConfigureBff(setup);
|
||||
await InitializeAsync();
|
||||
await Bff.BrowserClient.Login();
|
||||
|
||||
await Bff.BffLogoutAsync("sid123");
|
||||
var sid = await Bff.BrowserClient.GetSid();
|
||||
await Bff.BrowserClient.Logout(sid)
|
||||
.CheckHttpStatusCode();
|
||||
|
||||
Bff.BrowserClient.CurrentUri
|
||||
.ShouldNotBeNull()
|
||||
.ToString()
|
||||
.ToLowerInvariant()
|
||||
.ShouldBe(Bff.Url("/"));
|
||||
Bff.BrowserClient.RedirectHandler.AutoFollowRedirects = false;
|
||||
var response = await Bff.BrowserClient.Logout(sid);
|
||||
|
||||
(await Bff.GetIsUserLoggedInAsync()).ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task can_logout_twice()
|
||||
{
|
||||
await Bff.BffLoginAsync("alice", "sid123");
|
||||
|
||||
await Bff.BffLogoutAsync("sid123");
|
||||
|
||||
var response = await Bff.BrowserClient.GetAsync(Bff.Url("/bff/logout") + "?sid=123");
|
||||
response.StatusCode.ShouldBe(HttpStatusCode.Redirect); // endsession
|
||||
response.Headers.Location!.ToString().ToLowerInvariant().ShouldStartWith(IdentityServer.Url("/connect/endsession"));
|
||||
response.Headers.Location!.ToString().ToLowerInvariant().ShouldStartWith(IdentityServer.Url("/connect/endsession").ToString());
|
||||
|
||||
|
||||
(await Bff.GetIsUserLoggedInAsync()).ShouldBeFalse();
|
||||
(await Bff.BrowserClient.GetIsUserLoggedInAsync()).ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task logout_endpoint_should_accept_returnUrl()
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(AllSetups))]
|
||||
public async Task logout_endpoint_should_accept_returnUrl(BffSetupType setup)
|
||||
{
|
||||
await Bff.BffLoginAsync("alice", "sid123");
|
||||
Bff.OnConfigureEndpoints += endpoints => endpoints.MapGet("/foo", () => "foo'd you");
|
||||
|
||||
var response = await Bff.BrowserClient.GetAsync(Bff.Url("/bff/logout") + "?sid=sid123&returnUrl=/foo");
|
||||
response.StatusCode.ShouldBe(HttpStatusCode.Redirect); // endsession
|
||||
response.Headers.Location!.ToString().ToLowerInvariant().ShouldStartWith(IdentityServer.Url("/connect/endsession"));
|
||||
ConfigureBff(setup);
|
||||
await InitializeAsync();
|
||||
await Bff.BrowserClient.Login();
|
||||
|
||||
response = await IdentityServer.BrowserClient.GetAsync(response.Headers.Location!.ToString());
|
||||
response.StatusCode.ShouldBe(HttpStatusCode.Redirect); // logout
|
||||
response.Headers.Location!.ToString().ToLowerInvariant().ShouldStartWith(IdentityServer.Url("/account/logout"));
|
||||
var response = await Bff.BrowserClient.Logout(returnUrl: new Uri("/foo", UriKind.Relative))
|
||||
.CheckHttpStatusCode();
|
||||
|
||||
response = await IdentityServer.BrowserClient.GetAsync(response.Headers.Location!.ToString());
|
||||
response.StatusCode.ShouldBe(HttpStatusCode.Redirect); // post logout redirect uri
|
||||
response.Headers.Location!.ToString().ToLowerInvariant().ShouldStartWith(Bff.Url("/signout-callback-oidc"));
|
||||
response.RequestMessage!.RequestUri.ShouldBe(Bff.Url("/foo"));
|
||||
|
||||
response = await Bff.BrowserClient.GetAsync(response.Headers.Location!.ToString());
|
||||
response.StatusCode.ShouldBe(HttpStatusCode.Redirect); // root
|
||||
response.Headers.Location!.ToString().ToLowerInvariant().ShouldBe("/foo");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task logout_endpoint_should_reject_non_local_returnUrl()
|
||||
[Theory]
|
||||
[MemberData(nameof(AllSetups))]
|
||||
public async Task logout_endpoint_should_reject_non_local_returnUrl(BffSetupType setup)
|
||||
{
|
||||
await Bff.BffLoginAsync("alice", "sid123");
|
||||
ConfigureBff(setup);
|
||||
await InitializeAsync();
|
||||
await Bff.BrowserClient.Login();
|
||||
|
||||
var problem = await Bff.BrowserClient.GetAsync(Bff.Url("/bff/logout") + "?sid=sid123&returnUrl=https://foo")
|
||||
var problem = await Bff.BrowserClient.Logout(returnUrl: new Uri("https://foo"))
|
||||
.ShouldBeProblem();
|
||||
|
||||
problem.Errors.ShouldContainKey(Constants.RequestParameters.ReturnUrl);
|
||||
|
|
|
|||
|
|
@ -3,23 +3,24 @@
|
|||
|
||||
using System.Net;
|
||||
using Duende.Bff.Configuration;
|
||||
using Duende.Bff.Tests.TestHosts;
|
||||
using Duende.Bff.Tests.TestInfra;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Duende.Bff.Tests.Endpoints.Management;
|
||||
|
||||
public class ManagementBasePathTests(ITestOutputHelper output) : BffIntegrationTestBase(output)
|
||||
public class ManagementBasePathTests(ITestOutputHelper output) : BffTestBase(output)
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(Constants.ManagementEndpoints.Login)]
|
||||
[InlineData(Constants.ManagementEndpoints.Logout)]
|
||||
[InlineData(Constants.ManagementEndpoints.Login, HttpStatusCode.Redirect)]
|
||||
[InlineData(Constants.ManagementEndpoints.Logout, HttpStatusCode.Redirect)]
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
[InlineData(Constants.ManagementEndpoints.SilentLogin)]
|
||||
[InlineData(Constants.ManagementEndpoints.SilentLogin, HttpStatusCode.Redirect)]
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
[InlineData(Constants.ManagementEndpoints.SilentLoginCallback)]
|
||||
[InlineData(Constants.ManagementEndpoints.User)]
|
||||
public async Task custom_ManagementBasePath_should_affect_basepath(string path)
|
||||
[InlineData(Constants.ManagementEndpoints.SilentLoginCallback, HttpStatusCode.OK)]
|
||||
[InlineData(Constants.ManagementEndpoints.User, HttpStatusCode.Unauthorized)]
|
||||
public async Task custom_ManagementBasePath_should_affect_basepath(string path, HttpStatusCode expectedStatusCode)
|
||||
{
|
||||
SetupDefaultBffAuthentication();
|
||||
Bff.OnConfigureServices += svcs =>
|
||||
{
|
||||
svcs.Configure<BffOptions>(options =>
|
||||
|
|
@ -27,13 +28,26 @@ public class ManagementBasePathTests(ITestOutputHelper output) : BffIntegrationT
|
|||
options.ManagementBasePath = new PathString("/{path:regex(^[a-zA-Z\\d-]+$)}/bff");
|
||||
});
|
||||
};
|
||||
await Bff.InitializeAsync();
|
||||
await InitializeAsync();
|
||||
|
||||
var req = new HttpRequestMessage(HttpMethod.Get, Bff.Url("/custom/bff" + path));
|
||||
// Don't follow the redirects, becuase otherwise we might folow a redirect flow that ends up in a 404
|
||||
Bff.BrowserClient.RedirectHandler.AutoFollowRedirects = false;
|
||||
|
||||
|
||||
// Make sure the 'original path' doesn't work
|
||||
await VerifyRoute(path, HttpStatusCode.NotFound);
|
||||
|
||||
// but the custom path does work
|
||||
await VerifyRoute(path, expectedStatusCode, "/custom/bff");
|
||||
}
|
||||
|
||||
private async Task VerifyRoute(string path, HttpStatusCode expectedStatusCode, string? prefix = null)
|
||||
{
|
||||
var req = new HttpRequestMessage(HttpMethod.Get, Bff.Url(prefix + path));
|
||||
req.Headers.Add("x-csrf", "1");
|
||||
|
||||
var response = await Bff.BrowserClient.SendAsync(req);
|
||||
response.StatusCode.ShouldBe(expectedStatusCode);
|
||||
|
||||
response.StatusCode.ShouldNotBe(HttpStatusCode.NotFound);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,25 +4,47 @@
|
|||
using System.Net;
|
||||
using System.Security.Claims;
|
||||
using Duende.Bff.Configuration;
|
||||
using Duende.Bff.Tests.TestHosts;
|
||||
using Duende.Bff.Tests.TestInfra;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Duende.Bff.Tests.Endpoints.Management;
|
||||
|
||||
public class UserEndpointTests(ITestOutputHelper output) : BffIntegrationTestBase(output)
|
||||
public class UserEndpointTests : BffTestBase, IAsyncLifetime
|
||||
{
|
||||
private List<Claim> ClaimsToAdd = [];
|
||||
|
||||
public UserEndpointTests(ITestOutputHelper output) : base(output)
|
||||
{
|
||||
SetupDefaultBffAuthentication(ClaimsToAdd);
|
||||
|
||||
Bff.OnConfigureEndpoints += endpoints =>
|
||||
{
|
||||
// Setup a login endpoint that allows you to simulate signing in as a specific
|
||||
// user in the BFF.
|
||||
endpoints.MapGet("/__signin", async ctx =>
|
||||
{
|
||||
var props = new AuthenticationProperties();
|
||||
await ctx.SignInAsync(UserToSignIn!, props);
|
||||
|
||||
ctx.Response.StatusCode = 204;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
public ClaimsPrincipal? UserToSignIn { get; set; }
|
||||
|
||||
[Fact]
|
||||
public async Task user_endpoint_for_authenticated_user_should_return_claims()
|
||||
{
|
||||
await Bff.IssueSessionCookieAsync(
|
||||
new Claim("sub", "alice"),
|
||||
new Claim("foo", "foo1"),
|
||||
new Claim("foo", "foo2"));
|
||||
ClaimsToAdd.Add(new Claim("foo", "foo1"));
|
||||
ClaimsToAdd.Add(new Claim("foo", "foo2"));
|
||||
await Bff.BrowserClient.Login();
|
||||
|
||||
var data = await Bff.CallUserEndpointAsync();
|
||||
var data = await Bff.BrowserClient.CallUserEndpointAsync();
|
||||
|
||||
data.Count.ShouldBe(5);
|
||||
data.First(d => d.Type == "sub").Value.GetString().ShouldBe("alice");
|
||||
data.First(d => d.Type == "sub").Value.GetString().ShouldBe(The.Sub);
|
||||
|
||||
var foos = data.Where(d => d.Type == "foo");
|
||||
foos.Count().ShouldBe(2);
|
||||
|
|
@ -30,17 +52,20 @@ public class UserEndpointTests(ITestOutputHelper output) : BffIntegrationTestBas
|
|||
foos.Skip(1).First().Value.GetString().ShouldBe("foo2");
|
||||
|
||||
data.First(d => d.Type == Constants.ClaimTypes.SessionExpiresIn).Value.GetInt32().ShouldBePositive();
|
||||
data.First(d => d.Type == Constants.ClaimTypes.LogoutUrl).Value.GetString().ShouldBe("/bff/logout");
|
||||
data.First(d => d.Type == Constants.ClaimTypes.LogoutUrl).Value.GetString().ShouldStartWith("/bff/logout?sid=");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task user_endpoint_for_authenticated_user_with_sid_should_return_claims_including_logout()
|
||||
{
|
||||
await Bff.IssueSessionCookieAsync(
|
||||
UserToSignIn = new ClaimsPrincipal(new ClaimsIdentity([
|
||||
new Claim("sub", "alice"),
|
||||
new Claim("sid", "123"));
|
||||
new Claim("sid", "123"),
|
||||
], "test", "name", "role"));
|
||||
|
||||
var data = await Bff.CallUserEndpointAsync();
|
||||
await Bff.BrowserClient.GetAsync("/__signin");
|
||||
|
||||
var data = await Bff.BrowserClient.CallUserEndpointAsync();
|
||||
|
||||
data.Count.ShouldBe(4);
|
||||
data.First(d => d.Type == "sub").Value.GetString().ShouldBe("alice");
|
||||
|
|
@ -52,7 +77,7 @@ public class UserEndpointTests(ITestOutputHelper output) : BffIntegrationTestBas
|
|||
[Fact]
|
||||
public async Task user_endpoint_for_authenticated_user_without_csrf_header_should_fail()
|
||||
{
|
||||
await Bff.IssueSessionCookieAsync(new Claim("sub", "alice"), new Claim("foo", "foo1"), new Claim("foo", "foo2"));
|
||||
await Bff.BrowserClient.IssueSessionCookieAsync(new Claim("sub", "alice"), new Claim("foo", "foo1"), new Claim("foo", "foo2"));
|
||||
|
||||
var req = new HttpRequestMessage(HttpMethod.Get, Bff.Url("/bff/user"));
|
||||
var response = await Bff.BrowserClient.SendAsync(req);
|
||||
|
|
@ -73,9 +98,12 @@ public class UserEndpointTests(ITestOutputHelper output) : BffIntegrationTestBas
|
|||
[Fact]
|
||||
public async Task when_configured_user_endpoint_for_unauthenticated_user_should_return_200_and_empty()
|
||||
{
|
||||
Bff.BffOptions.AnonymousSessionResponse = AnonymousSessionResponse.Response200;
|
||||
|
||||
var data = await Bff.CallUserEndpointAsync();
|
||||
var options = Bff.Resolve<IOptions<BffOptions>>();
|
||||
|
||||
options.Value.AnonymousSessionResponse = AnonymousSessionResponse.Response200;
|
||||
|
||||
var data = await Bff.BrowserClient.CallUserEndpointAsync();
|
||||
data.ShouldBeEmpty();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ public class ApiAndBffUseForwardedHeaders : BffTestBase, IAsyncLifetime
|
|||
|
||||
Bff.OnConfigureEndpoints += endpoints =>
|
||||
{
|
||||
endpoints.MapRemoteBffApiEndpoint(The.Path, Api.Url().ToString());
|
||||
endpoints.MapRemoteBffApiEndpoint(The.Path, Api.Url());
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ public class ApiUseForwardedHeaders : BffTestBase
|
|||
|
||||
Bff.OnConfigureEndpoints += endpoints =>
|
||||
{
|
||||
endpoints.MapRemoteBffApiEndpoint(The.Path, Api.Url().ToString());
|
||||
endpoints.MapRemoteBffApiEndpoint(The.Path, Api.Url());
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
using Duende.Bff.AccessTokenManagement;
|
||||
using Duende.Bff.Internal;
|
||||
using Duende.Bff.Tests.TestHosts;
|
||||
using Duende.Bff.Tests.TestInfra;
|
||||
using Duende.Bff.Yarp;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Xunit.Abstractions;
|
||||
|
|
@ -13,26 +13,52 @@ namespace Duende.Bff.Tests;
|
|||
/// <summary>
|
||||
/// These tests prove that you can use a custom IAccessTokenRetriever and that the context is populated correctly.
|
||||
/// </summary>
|
||||
public class IAccessTokenRetriever_Extensibility_tests : BffIntegrationTestBase
|
||||
public class IAccessTokenRetriever_Extensibility_tests : BffTestBase
|
||||
{
|
||||
private ContextCapturingAccessTokenRetriever _customAccessTokenReceiver { get; } = new(NullLogger<DefaultAccessTokenRetriever>.Instance);
|
||||
|
||||
public IAccessTokenRetriever_Extensibility_tests(ITestOutputHelper output) : base(output)
|
||||
{
|
||||
IdentityServer.AddClient(The.ClientId, Bff.Url());
|
||||
Bff.OnConfigureBff += bff => bff
|
||||
.WithDefaultOpenIdConnectOptions(The.DefaultOpenIdConnectConfiguration)
|
||||
.AddRemoteApis();
|
||||
|
||||
Bff.OnConfigureServices += services =>
|
||||
{
|
||||
services.AddSingleton(_customAccessTokenReceiver);
|
||||
};
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task When_calling_custom_endpoint_then_AccessTokenRetrievalContext_has_api_address_and_localpath()
|
||||
{
|
||||
Bff.OnConfigureEndpoints += endpoints =>
|
||||
{
|
||||
endpoints.MapRemoteBffApiEndpoint("/custom", Api.Url("/some/path"))
|
||||
.WithAccessToken()
|
||||
.WithAccessTokenRetriever<ContextCapturingAccessTokenRetriever>();
|
||||
};
|
||||
|
||||
await InitializeAsync();
|
||||
await Bff.BrowserClient.Login();
|
||||
|
||||
await Bff.BrowserClient.CallBffHostApi(Bff.Url("/custom"));
|
||||
|
||||
var usedContext = _customAccessTokenReceiver.UsedContext.ShouldNotBeNull();
|
||||
|
||||
usedContext.Metadata.TokenType.ShouldBe(RequiredTokenType.User);
|
||||
|
||||
usedContext.ApiAddress.ShouldBe(Api.Url("/some/path"));
|
||||
usedContext.LocalPath.ToString().ShouldBe("/custom");
|
||||
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task When_calling_sub_custom_endpoint_then_AccessTokenRetrievalContext_has_api_address_and_localpath()
|
||||
{
|
||||
Bff.OnConfigure += app =>
|
||||
{
|
||||
app.UseEndpoints((endpoints) =>
|
||||
{
|
||||
endpoints.MapRemoteBffApiEndpoint("/custom", Api.Url("/some/path"))
|
||||
.WithAccessToken()
|
||||
.WithAccessTokenRetriever<ContextCapturingAccessTokenRetriever>();
|
||||
|
||||
});
|
||||
|
||||
app.Map("/subPath",
|
||||
subPath =>
|
||||
|
|
@ -47,34 +73,13 @@ public class IAccessTokenRetriever_Extensibility_tests : BffIntegrationTestBase
|
|||
});
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task When_calling_custom_endpoint_then_AccessTokenRetrievalContext_has_api_address_and_localpath()
|
||||
{
|
||||
await Bff.BffLoginAsync("alice");
|
||||
|
||||
await Bff.BrowserClient.CallBffHostApi(Bff.Url("/custom"));
|
||||
|
||||
var usedContext = _customAccessTokenReceiver.UsedContext.ShouldNotBeNull();
|
||||
|
||||
usedContext.Metadata.TokenType.ShouldBe(RequiredTokenType.User);
|
||||
|
||||
usedContext.ApiAddress.ShouldBe(new Uri(Api.Url("/some/path")));
|
||||
usedContext.LocalPath.ToString().ShouldBe("/custom");
|
||||
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task When_calling_sub_custom_endpoint_then_AccessTokenRetrievalContext_has_api_address_and_localpath()
|
||||
{
|
||||
await Bff.BffLoginAsync("alice");
|
||||
|
||||
await InitializeAsync();
|
||||
await Bff.BrowserClient.Login();
|
||||
await Bff.BrowserClient.CallBffHostApi(Bff.Url("/subPath/custom_within_subpath"));
|
||||
|
||||
var usedContext = _customAccessTokenReceiver.UsedContext.ShouldNotBeNull();
|
||||
|
||||
usedContext.ApiAddress.ShouldBe(new Uri(Api.Url("/some/path")));
|
||||
usedContext.ApiAddress.ShouldBe(Api.Url("/some/path"));
|
||||
usedContext.LocalPath.ToString().ShouldBe("/custom_within_subpath");
|
||||
|
||||
}
|
||||
|
|
@ -94,9 +99,10 @@ public class IAccessTokenRetriever_Extensibility_tests : BffIntegrationTestBase
|
|||
UsedContext = context;
|
||||
if (context.Metadata.TokenType.HasValue)
|
||||
{
|
||||
return await context.HttpContext.GetManagedAccessToken(
|
||||
var managedAccessToken = await context.HttpContext.GetManagedAccessToken(
|
||||
requiredTokenType: context.Metadata.TokenType.Value,
|
||||
context.UserTokenRequestParameters, ct: ct);
|
||||
return managedAccessToken;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@
|
|||
}
|
||||
public static class RouteBuilderExtensions
|
||||
{
|
||||
public static Microsoft.AspNetCore.Builder.IEndpointConventionBuilder MapRemoteBffApiEndpoint(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, Microsoft.AspNetCore.Http.PathString localPath, string apiAddress, System.Action<Yarp.ReverseProxy.Transforms.Builder.TransformBuilderContext>? yarpTransformBuilder = null) { }
|
||||
public static Microsoft.AspNetCore.Builder.IEndpointConventionBuilder MapRemoteBffApiEndpoint(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, Microsoft.AspNetCore.Http.PathString localPath, System.Uri apiAddress, System.Action<Yarp.ReverseProxy.Transforms.Builder.TransformBuilderContext>? yarpTransformBuilder = null) { }
|
||||
}
|
||||
public sealed class UserAccessTokenParameters : System.IEquatable<Duende.Bff.Yarp.UserAccessTokenParameters>
|
||||
{
|
||||
|
|
|
|||
|
|
@ -407,45 +407,45 @@ public class BffHost : GenericHost
|
|||
.RequireAuthorization("AlwaysFail");
|
||||
|
||||
endpoints.MapRemoteBffApiEndpoint(
|
||||
"/api_user", _apiHost.Url())
|
||||
"/api_user", new Uri(_apiHost.Url()))
|
||||
.WithAccessToken();
|
||||
|
||||
endpoints.MapRemoteBffApiEndpoint(
|
||||
"/api_user_no_csrf", _apiHost.Url())
|
||||
"/api_user_no_csrf", new Uri(_apiHost.Url()))
|
||||
.SkipAntiforgery()
|
||||
.WithAccessToken();
|
||||
|
||||
endpoints.MapRemoteBffApiEndpoint(
|
||||
"/api_client", _apiHost.Url())
|
||||
"/api_client", new Uri(_apiHost.Url()))
|
||||
.WithAccessToken(RequiredTokenType.Client);
|
||||
|
||||
endpoints.MapRemoteBffApiEndpoint(
|
||||
"/api_user_or_client", _apiHost.Url())
|
||||
"/api_user_or_client", new Uri(_apiHost.Url()))
|
||||
.WithAccessToken(RequiredTokenType.UserOrClient);
|
||||
|
||||
endpoints.MapRemoteBffApiEndpoint(
|
||||
"/api_unauthenticated", _apiHost.Url() + "return_unauthenticated")
|
||||
"/api_unauthenticated", new Uri(_apiHost.Url() + "return_unauthenticated"))
|
||||
.WithAccessToken(RequiredTokenType.UserOrClient);
|
||||
|
||||
|
||||
endpoints.MapRemoteBffApiEndpoint(
|
||||
"/api_forbidden", _apiHost.Url() + "return_forbidden")
|
||||
"/api_forbidden", new Uri(_apiHost.Url() + "return_forbidden"))
|
||||
.WithAccessToken(RequiredTokenType.UserOrClient);
|
||||
|
||||
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
endpoints.MapRemoteBffApiEndpoint(
|
||||
"/api_user_or_anon", _apiHost.Url())
|
||||
"/api_user_or_anon", new Uri(_apiHost.Url()))
|
||||
.WithOptionalUserAccessToken();
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
|
||||
endpoints.MapRemoteBffApiEndpoint(
|
||||
"/api_anon_only", _apiHost.Url());
|
||||
"/api_anon_only", new Uri(_apiHost.Url()));
|
||||
|
||||
// Add a custom transform. This transform just copies the request headers
|
||||
// which allows the tests to see if this custom transform works
|
||||
endpoints.MapRemoteBffApiEndpoint(
|
||||
"/api_custom_transform", _apiHost.Url(),
|
||||
"/api_custom_transform", new Uri(_apiHost.Url()),
|
||||
c =>
|
||||
{
|
||||
c.CopyRequestHeaders = true;
|
||||
|
|
@ -453,12 +453,12 @@ public class BffHost : GenericHost
|
|||
});
|
||||
|
||||
endpoints.MapRemoteBffApiEndpoint(
|
||||
"/api_with_access_token_retriever", _apiHost.Url())
|
||||
"/api_with_access_token_retriever", new Uri(_apiHost.Url()))
|
||||
.WithAccessToken(RequiredTokenType.UserOrClient)
|
||||
.WithAccessTokenRetriever<TestAccessTokenRetriever>();
|
||||
|
||||
endpoints.MapRemoteBffApiEndpoint(
|
||||
"/api_with_access_token_retrieval_that_fails", _apiHost.Url())
|
||||
"/api_with_access_token_retrieval_that_fails", new Uri(_apiHost.Url()))
|
||||
.WithAccessToken(RequiredTokenType.UserOrClient)
|
||||
.WithAccessTokenRetriever<FailureAccessTokenRetriever>();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -142,7 +142,7 @@ public class BffHostUsingResourceNamedTokens : GenericHost
|
|||
endpoints.MapBffManagementEndpoints();
|
||||
|
||||
endpoints.MapRemoteBffApiEndpoint(
|
||||
"/api_user_with_useraccesstokenparameters_having_stored_named_token", _apiHost.Url())
|
||||
"/api_user_with_useraccesstokenparameters_having_stored_named_token", new Uri(_apiHost.Url()))
|
||||
.WithUserAccessTokenParameter(new BffUserAccessTokenParameters
|
||||
{
|
||||
SignInScheme = Scheme.Parse("cookie"),
|
||||
|
|
@ -152,7 +152,7 @@ public class BffHostUsingResourceNamedTokens : GenericHost
|
|||
.WithAccessToken();
|
||||
|
||||
endpoints.MapRemoteBffApiEndpoint(
|
||||
"/api_user_with_useraccesstokenparameters_having_not_stored_named_token", _apiHost.Url())
|
||||
"/api_user_with_useraccesstokenparameters_having_not_stored_named_token", new Uri(_apiHost.Url()))
|
||||
.WithUserAccessTokenParameter(new BffUserAccessTokenParameters
|
||||
{
|
||||
SignInScheme = Scheme.Parse("cookie"),
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ public static class CookieContainerExtensions
|
|||
}
|
||||
}
|
||||
|
||||
public class BffHttpClient(RedirectHandler handler, CookieContainer cookies) : HttpClient(handler), IHttpClient<BffHttpClient>
|
||||
public class BffHttpClient(RedirectHandler handler, CookieContainer cookies, IdentityServerTestHost identityServer) : HttpClient(handler)
|
||||
{
|
||||
public CookieContainer Cookies { get; } = cookies;
|
||||
|
||||
|
|
@ -30,8 +30,20 @@ public class BffHttpClient(RedirectHandler handler, CookieContainer cookies) : H
|
|||
.CheckHttpStatusCode(expectedStatusCode);
|
||||
|
||||
|
||||
public static BffHttpClient Build(RedirectHandler handler, CookieContainer cookies) => new(handler, cookies);
|
||||
|
||||
public async Task<List<JsonRecord>> CallUserEndpointAsync()
|
||||
{
|
||||
var req = new HttpRequestMessage(HttpMethod.Get, "/bff/user");
|
||||
req.Headers.Add("x-csrf", "1");
|
||||
|
||||
var response = await SendAsync(req);
|
||||
|
||||
response.StatusCode.ShouldBe(HttpStatusCode.OK);
|
||||
response.Content.Headers.ContentType?.MediaType.ShouldBe("application/json");
|
||||
|
||||
var json = await response.Content.ReadAsStringAsync();
|
||||
return JsonSerializer.Deserialize<List<JsonRecord>>(json, TestSerializerOptions.Default) ?? [];
|
||||
}
|
||||
|
||||
internal Task<TestBrowserClient.BffHostResponse> CallBffHostApi(
|
||||
PathString path,
|
||||
|
|
@ -115,23 +127,44 @@ public class BffHttpClient(RedirectHandler handler, CookieContainer cookies) : H
|
|||
host.PropsToSignIn.Items.Add("session_id", sid);
|
||||
}
|
||||
|
||||
await IssueSessionCookieAsync(host, new Claim("sub", sub));
|
||||
await IssueSessionCookieAsync(new Claim("sub", sub));
|
||||
}
|
||||
|
||||
public async Task IssueSessionCookieAsync(IdentityServerTestHost host, params Claim[] claims)
|
||||
public async Task IssueSessionCookieAsync(params Claim[] claims)
|
||||
{
|
||||
var previousUser = host.UserToSignIn;
|
||||
var previousUser = identityServer.UserToSignIn;
|
||||
try
|
||||
{
|
||||
host.UserToSignIn = new ClaimsPrincipal(new ClaimsIdentity(claims, "test", "name", "role"));
|
||||
var response = await GetAsync(host.Url("__signin"));
|
||||
identityServer.UserToSignIn = new ClaimsPrincipal(new ClaimsIdentity(claims, "test", "name", "role"));
|
||||
var response = await GetAsync(identityServer.Url("__signin"));
|
||||
response.StatusCode.ShouldBe(HttpStatusCode.NoContent);
|
||||
}
|
||||
finally
|
||||
{
|
||||
host.UserToSignIn = previousUser;
|
||||
identityServer.UserToSignIn = previousUser;
|
||||
}
|
||||
|
||||
}
|
||||
public async Task RevokeIdentityServerSession(Uri url) => await GetAsync(new Uri(url, "__signout")).CheckHttpStatusCode(HttpStatusCode.NoContent);
|
||||
|
||||
public async Task<HttpResponseMessage> Logout(string? sid = null, Uri? returnUrl = null)
|
||||
{
|
||||
sid ??= await GetSid();
|
||||
|
||||
var returnParams = returnUrl == null ? null : $"&returnUrl={Uri.EscapeDataString(returnUrl.ToString())}";
|
||||
|
||||
var req = new HttpRequestMessage(HttpMethod.Get, "/bff/logout?sid=" + sid + returnParams);
|
||||
req.Headers.Add("x-csrf", "1");
|
||||
return await SendAsync(req);
|
||||
}
|
||||
|
||||
public async Task<string> GetSid()
|
||||
{
|
||||
var claims = await CallUserEndpointAsync();
|
||||
|
||||
var sidClaim = claims.FirstOrDefault(c => c.Type == "sid")?.Value;
|
||||
sidClaim.ShouldNotBeNull();
|
||||
var sid = sidClaim.Value.ToString();
|
||||
return sid;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright (c) Duende Software. All rights reserved.
|
||||
// See LICENSE in the project root for license information.
|
||||
|
||||
using System.Security.Claims;
|
||||
using Duende.Bff.DynamicFrontends;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
|
||||
|
|
@ -39,7 +40,7 @@ public abstract class BffTestBase : IAsyncDisposable
|
|||
|
||||
|
||||
Api = new ApiHost(Context, IdentityServer);
|
||||
Bff = new BffTestHost(Context);
|
||||
Bff = new BffTestHost(Context, IdentityServer);
|
||||
Cdn = new CdnHost(Context);
|
||||
IdentityServer.AddClient(DefaultOidcClient.ClientId, Bff.Url());
|
||||
Some = Context.Some;
|
||||
|
|
@ -188,5 +189,28 @@ public abstract class BffTestBase : IAsyncDisposable
|
|||
yield return [BffSetupType.ManuallyConfiguredBff];
|
||||
}
|
||||
|
||||
protected void SetupDefaultBffAuthentication(IEnumerable<Claim>? claimsToAdd = null)
|
||||
{
|
||||
Bff.OnConfigureBff += bff => bff
|
||||
.WithDefaultOpenIdConnectOptions(opt =>
|
||||
{
|
||||
if (claimsToAdd != null)
|
||||
{
|
||||
opt.Events.OnTokenValidated = context =>
|
||||
{
|
||||
// Add custom claims to the identity
|
||||
var identity = (ClaimsIdentity)context.Principal!.Identity!;
|
||||
foreach (var claim in claimsToAdd)
|
||||
{
|
||||
identity.AddClaim(claim);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}; The.DefaultOpenIdConnectConfiguration(opt);
|
||||
}
|
||||
});
|
||||
|
||||
AddOrUpdateFrontend(Some.BffFrontend());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,17 +1,20 @@
|
|||
// Copyright (c) Duende Software. All rights reserved.
|
||||
// See LICENSE in the project root for license information.
|
||||
|
||||
using System.Net;
|
||||
using Duende.Bff.Configuration;
|
||||
using Duende.Bff.DynamicFrontends;
|
||||
using Duende.Bff.DynamicFrontends.Internal;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Yarp.ReverseProxy.Forwarder;
|
||||
|
||||
namespace Duende.Bff.Tests.TestInfra;
|
||||
|
||||
public class BffTestHost(TestHostContext context) : TestHost(context, new Uri("https://bff"))
|
||||
public class BffTestHost(TestHostContext context, IdentityServerTestHost identityServer) : TestHost(context, new Uri("https://bff"))
|
||||
{
|
||||
public readonly string DefaultRootResponse = "Default response from root";
|
||||
private BffHttpClient _browserClient = null!;
|
||||
public BffOptions BffOptions => Resolve<IOptions<BffOptions>>().Value;
|
||||
|
||||
/// <summary>
|
||||
/// Should a default response for "/" be mapped?
|
||||
|
|
@ -25,7 +28,17 @@ public class BffTestHost(TestHostContext context) : TestHost(context, new Uri("h
|
|||
|
||||
public override void Initialize()
|
||||
{
|
||||
BrowserClient = Internet.BuildHttpClient<BffHttpClient>(Url());
|
||||
var cookieContainer = new CookieContainer();
|
||||
var cookieHandler = new CookieHandler(Internet, cookieContainer);
|
||||
var redirectHandler = new RedirectHandler(WriteOutput)
|
||||
{
|
||||
InnerHandler = cookieHandler
|
||||
};
|
||||
BrowserClient = new BffHttpClient(redirectHandler, cookieContainer, identityServer)
|
||||
{
|
||||
BaseAddress = Url()
|
||||
};
|
||||
|
||||
OnConfigureServices += services =>
|
||||
{
|
||||
if (EnableBackChannelHandler)
|
||||
|
|
@ -46,15 +59,6 @@ public class BffTestHost(TestHostContext context) : TestHost(context, new Uri("h
|
|||
OnConfigureBff(builder);
|
||||
};
|
||||
|
||||
OnConfigure += app =>
|
||||
{
|
||||
app.UseRouting();
|
||||
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
app.UseBff();
|
||||
};
|
||||
OnConfigureEndpoints += endpoints =>
|
||||
{
|
||||
if (MapGetForRoot)
|
||||
|
|
@ -67,6 +71,17 @@ public class BffTestHost(TestHostContext context) : TestHost(context, new Uri("h
|
|||
};
|
||||
}
|
||||
|
||||
protected override void ConfigureApp(IApplicationBuilder app)
|
||||
{
|
||||
app.UseRouting();
|
||||
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
app.UseBff();
|
||||
base.ConfigureApp(app);
|
||||
}
|
||||
|
||||
public BffHttpClient BrowserClient
|
||||
{
|
||||
get => _browserClient ?? throw new InvalidOperationException("Not yet initialized");
|
||||
|
|
|
|||
|
|
@ -142,8 +142,7 @@ public class IdentityServerTestHost : TestHost
|
|||
ClientSecrets = { new Secret(clientSecret.Sha256()) },
|
||||
AllowedGrantTypes = GrantTypes.CodeAndClientCredentials,
|
||||
RedirectUris = [redirectUri.ToString()],
|
||||
PostLogoutRedirectUris = ["/"], // not implemented
|
||||
BackChannelLogoutUri = "/", // not implemented
|
||||
PostLogoutRedirectUris = [new Uri(baseUri, "signout-callback-oidc").ToString()],
|
||||
AllowOfflineAccess = true,
|
||||
AllowedScopes = options.Scope.Any()
|
||||
? options.Scope
|
||||
|
|
|
|||
|
|
@ -33,11 +33,11 @@ public class SimulatedInternet : DelegatingHandler
|
|||
|
||||
public T BuildHttpClient<T>(Uri baseUrl) where T : HttpClient, IHttpClient<T>
|
||||
{
|
||||
var handler = new RedirectHandler(_outputWriter);
|
||||
var recirectHandler = new RedirectHandler(_outputWriter);
|
||||
var cookieContainer = new CookieContainer();
|
||||
handler.InnerHandler = new CookieHandler(this, cookieContainer);
|
||||
recirectHandler.InnerHandler = new CookieHandler(this, cookieContainer);
|
||||
|
||||
var client = T.Build(handler, cookieContainer);
|
||||
var client = T.Build(recirectHandler, cookieContainer);
|
||||
client.BaseAddress = baseUrl;
|
||||
return client;
|
||||
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@ public class TestData
|
|||
opt.Scope.Add("openid");
|
||||
opt.Scope.Add("profile");
|
||||
opt.Scope.Add(Scope);
|
||||
opt.SignedOutRedirectUri = "/";
|
||||
};
|
||||
|
||||
DefaultOpenIdConnectConfiguration(OpenIdConnectOptions);
|
||||
|
|
|
|||
|
|
@ -7,15 +7,14 @@ namespace Duende.Bff.Tests.TestInfra;
|
|||
|
||||
public class TestHost(TestHostContext context, Uri baseAddress) : IAsyncDisposable
|
||||
{
|
||||
public TestHost(TestHostContext context) : this(context, new("https://server"))
|
||||
{
|
||||
}
|
||||
|
||||
internal TestDataBuilder Some => context.Some;
|
||||
public TestData The => context.The;
|
||||
|
||||
protected SimulatedInternet Internet => context.Internet;
|
||||
|
||||
protected void WriteOutput(string output) => context.WriteOutput(output);
|
||||
|
||||
IServiceProvider? _appServices = null!;
|
||||
|
||||
public TestServer Server { get; private set; } = null!;
|
||||
|
|
|
|||
0
start-influxdb-grafana.ps1
Normal file
0
start-influxdb-grafana.ps1
Normal file
Loading…
Reference in a new issue