mirror of
https://github.com/DuendeSoftware/products
synced 2026-05-24 09:28:24 +00:00
Merge pull request #2348 from DuendeSoftware/ev/bff/4.1.1
Merge 2347 into 4.1.x
This commit is contained in:
commit
66544530c6
7 changed files with 128 additions and 13 deletions
|
|
@ -1,7 +1,9 @@
|
|||
// Copyright (c) Duende Software. All rights reserved.
|
||||
// See LICENSE in the project root for license information.
|
||||
|
||||
using Duende.Bff.AccessTokenManagement;
|
||||
using Duende.Bff.Configuration;
|
||||
using Duende.Bff.DynamicFrontends.Internal;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
|
@ -12,7 +14,7 @@ internal class BffConfigureCookieOptions(
|
|||
TimeProvider timeProvider,
|
||||
IOptions<BffConfiguration> bffConfiguration,
|
||||
IOptions<BffOptions> bffOptions,
|
||||
CurrentFrontendAccessor currentFrontendAccessor
|
||||
FrontendSelector frontendSelector
|
||||
) : IConfigureNamedOptions<CookieAuthenticationOptions>
|
||||
{
|
||||
private readonly BffOptions _bffOptions = bffOptions.Value;
|
||||
|
|
@ -23,8 +25,10 @@ internal class BffConfigureCookieOptions(
|
|||
{
|
||||
// Normally, this is added by AuthenticationBuilder.PostConfigureAuthenticationSchemeOptions
|
||||
// but this is private API, so we need to do it ourselves.
|
||||
|
||||
var schemeName = Scheme.ParseOrDefault(name);
|
||||
options.TimeProvider = timeProvider;
|
||||
if (currentFrontendAccessor.TryGet(out var frontEnd))
|
||||
if (frontendSelector.TryGetFrontendByCookieScheme(schemeName, out var frontEnd))
|
||||
{
|
||||
if (frontEnd.MatchingCriteria.MatchingPath != null)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using Duende.Bff.AccessTokenManagement;
|
||||
using Duende.Bff.Configuration;
|
||||
using Duende.Bff.DynamicFrontends.Internal;
|
||||
using Duende.Bff.Internal;
|
||||
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
|
@ -11,7 +12,7 @@ namespace Duende.Bff.DynamicFrontends;
|
|||
|
||||
internal class BffConfigureOpenIdConnectOptions(
|
||||
TimeProvider timeProvider,
|
||||
CurrentFrontendAccessor currentFrontendAccessor,
|
||||
FrontendSelector frontendSelector,
|
||||
ActiveOpenIdConnectAuthenticationScheme activeOpenIdConnectScheme,
|
||||
IOptions<BffConfiguration> bffConfiguration,
|
||||
IOptions<BffOptions> bffOptions
|
||||
|
|
@ -21,7 +22,8 @@ internal class BffConfigureOpenIdConnectOptions(
|
|||
|
||||
public void Configure(string? name, OpenIdConnectOptions options)
|
||||
{
|
||||
if (!activeOpenIdConnectScheme.ShouldConfigureScheme(Scheme.ParseOrDefault(name)))
|
||||
var schemeName = Scheme.ParseOrDefault(name);
|
||||
if (!activeOpenIdConnectScheme.ShouldConfigureScheme(schemeName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
@ -45,7 +47,7 @@ internal class BffConfigureOpenIdConnectOptions(
|
|||
|
||||
// See if there is a frontend selected
|
||||
// If so, apply the frontend's OpenID Connect options
|
||||
if (!currentFrontendAccessor.TryGet(out var frontEnd))
|
||||
if (!frontendSelector.TryGetFrontendByOidcScheme(schemeName, out var frontEnd))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// See LICENSE in the project root for license information.
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Duende.Bff.AccessTokenManagement;
|
||||
using Duende.Bff.Otel;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
|
@ -11,6 +12,8 @@ namespace Duende.Bff.DynamicFrontends.Internal;
|
|||
internal class BffIndex
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly Dictionary<Scheme, BffFrontend> _frontendsByOidcScheme = new();
|
||||
private readonly Dictionary<Scheme, BffFrontend> _frontendsByCookieScheme = new();
|
||||
private readonly Dictionary<HostString, PathTrie<BffFrontend>> _perHostHeader = new();
|
||||
private readonly PathTrie<BffFrontend> _perPath = new();
|
||||
private BffFrontend? _defaultFrontend;
|
||||
|
|
@ -24,8 +27,30 @@ internal class BffIndex
|
|||
}
|
||||
}
|
||||
|
||||
public bool TryGetFrontendByOidcScheme(Scheme? scheme, [NotNullWhen(true)] out BffFrontend? frontend)
|
||||
{
|
||||
if (scheme == null)
|
||||
{
|
||||
frontend = null;
|
||||
return false;
|
||||
}
|
||||
return _frontendsByOidcScheme.TryGetValue(scheme.Value, out frontend);
|
||||
}
|
||||
|
||||
public bool TryGetFrontendByCookieScheme(Scheme? scheme, [NotNullWhen(true)] out BffFrontend? frontend)
|
||||
{
|
||||
if (scheme == null)
|
||||
{
|
||||
frontend = null;
|
||||
return false;
|
||||
}
|
||||
return _frontendsByCookieScheme.TryGetValue(scheme.Value, out frontend);
|
||||
}
|
||||
|
||||
public void AddFrontend(BffFrontend frontend)
|
||||
{
|
||||
_frontendsByOidcScheme.Add(frontend.OidcSchemeName, frontend);
|
||||
_frontendsByCookieScheme.Add(frontend.CookieSchemeName, frontend);
|
||||
var frontendMatchingCriteria = frontend.MatchingCriteria;
|
||||
|
||||
if (!_registeredCriteria.TryAdd(frontendMatchingCriteria, frontend.Name))
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// See LICENSE in the project root for license information.
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Duende.Bff.AccessTokenManagement;
|
||||
using Duende.Bff.Otel;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
|
@ -45,4 +46,10 @@ internal class FrontendSelector
|
|||
|
||||
return _bffIndex.TryMatch(request, out selectedFrontend);
|
||||
}
|
||||
|
||||
public bool TryGetFrontendByOidcScheme(Scheme? scheme, [NotNullWhen(true)] out BffFrontend? frontend) =>
|
||||
_bffIndex.TryGetFrontendByOidcScheme(scheme, out frontend);
|
||||
|
||||
public bool TryGetFrontendByCookieScheme(Scheme? scheme, [NotNullWhen(true)] out BffFrontend? frontend) =>
|
||||
_bffIndex.TryGetFrontendByCookieScheme(scheme, out frontend);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
// See LICENSE in the project root for license information.
|
||||
|
||||
using Duende.Bff.AccessTokenManagement;
|
||||
using Duende.Bff.DynamicFrontends;
|
||||
using Duende.Bff.DynamicFrontends.Internal;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
|
|
@ -11,7 +11,7 @@ namespace Duende.Bff.Internal;
|
|||
/// <summary>
|
||||
/// Centralizes the logic for determining if the cookie authentication scheme should be configured based on the currently selected frontend and the default authentication scheme.
|
||||
/// </summary>
|
||||
internal sealed class ActiveCookieAuthenticationScheme(CurrentFrontendAccessor currentFrontendAccessor, IOptions<AuthenticationOptions> authOptions)
|
||||
internal sealed class ActiveCookieAuthenticationScheme(FrontendSelector frontendSelector, IOptions<AuthenticationOptions> authOptions)
|
||||
{
|
||||
private readonly Scheme? _defaultAuthenticationScheme = Scheme.ParseOrDefault(authOptions.Value.DefaultAuthenticateScheme ?? authOptions.Value.DefaultScheme);
|
||||
|
||||
|
|
@ -25,6 +25,6 @@ internal sealed class ActiveCookieAuthenticationScheme(CurrentFrontendAccessor c
|
|||
// Either the currently selected scheme is the default scheme
|
||||
_defaultAuthenticationScheme == schemeName ||
|
||||
|
||||
// Or it's the correct scheme for the currently selected frontend
|
||||
(currentFrontendAccessor.TryGet(out var frontend) && schemeName == frontend.CookieSchemeName);
|
||||
// Or it's actually a scheme for a specific frontend.
|
||||
(frontendSelector.TryGetFrontendByCookieScheme(schemeName, out _));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
// See LICENSE in the project root for license information.
|
||||
|
||||
using Duende.Bff.AccessTokenManagement;
|
||||
using Duende.Bff.DynamicFrontends;
|
||||
using Duende.Bff.DynamicFrontends.Internal;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
|
|
@ -11,10 +11,10 @@ namespace Duende.Bff.Internal;
|
|||
/// <summary>
|
||||
/// Centralizes the logic for determining if the OpenID Connect authentication scheme should be configured based on the currently selected frontend and the default authentication scheme.
|
||||
/// </summary>
|
||||
/// <param name="currentFrontendAccessor"></param>
|
||||
/// <param name="frontendSelector"></param>
|
||||
/// <param name="authOptions"></param>
|
||||
internal sealed class ActiveOpenIdConnectAuthenticationScheme(
|
||||
CurrentFrontendAccessor currentFrontendAccessor,
|
||||
FrontendSelector frontendSelector,
|
||||
IOptions<AuthenticationOptions> authOptions)
|
||||
{
|
||||
private readonly Scheme? _defaultAuthenticationScheme = Scheme.ParseOrDefault(authOptions.Value.DefaultChallengeScheme ?? authOptions.Value.DefaultScheme);
|
||||
|
|
@ -29,6 +29,6 @@ internal sealed class ActiveOpenIdConnectAuthenticationScheme(
|
|||
// Either the currently selected scheme is the default scheme
|
||||
(_defaultAuthenticationScheme == schemeName)
|
||||
// Or it's the correct scheme for the currently selected frontend
|
||||
|| (currentFrontendAccessor.TryGet(out var frontend) && schemeName == frontend.OidcSchemeName);
|
||||
|| frontendSelector.TryGetFrontendByOidcScheme(schemeName, out _);
|
||||
|
||||
}
|
||||
|
|
|
|||
77
bff/test/Bff.Tests/BffOptionsConfigurationTests.cs
Normal file
77
bff/test/Bff.Tests/BffOptionsConfigurationTests.cs
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
// Copyright (c) Duende Software. All rights reserved.
|
||||
// See LICENSE in the project root for license information.
|
||||
|
||||
using System.Net;
|
||||
using Duende.Bff.Tests.TestFramework;
|
||||
using Duende.Bff.Tests.TestInfra;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Duende.Bff.Tests;
|
||||
|
||||
public class BffOptionsConfigurationTests(ITestOutputHelper output) : BffTestBase(output)
|
||||
{
|
||||
[Theory]
|
||||
[MemberData(nameof(AllSetups))]
|
||||
public async Task calls_outside_http_context_can_get_oidc_configuration(BffSetupType setup)
|
||||
{
|
||||
Bff.OnConfigureApp += app =>
|
||||
{
|
||||
app.Map(The.Path, c => ApiHost.ReturnApiCallDetails(c, () => HttpStatusCode.OK))
|
||||
.RequireAuthorization()
|
||||
.AsBffApiEndpoint();
|
||||
};
|
||||
|
||||
await ConfigureBff(setup);
|
||||
|
||||
// this retrieves the openid connect options outside the http context. This shouldn't
|
||||
// normally happen but AzureAppConfigurationRefreshMiddleware can cause this.
|
||||
// when this happens, this call would fail with No HTTP Context available,
|
||||
// but also all subsequent requests, because IOptionsCache caches this.
|
||||
var opt = Bff.Resolve<IOptionsMonitor<OpenIdConnectOptions>>();
|
||||
opt.Get(Some.BffFrontend().OidcSchemeName);
|
||||
|
||||
await Bff.BrowserClient.Login();
|
||||
|
||||
ApiCallDetails apiResult = await Bff.BrowserClient.CallBffHostApi(
|
||||
url: Bff.Url(The.Path)
|
||||
);
|
||||
|
||||
apiResult.Method.ShouldBe(HttpMethod.Get);
|
||||
apiResult.Path.ShouldBe(The.Path);
|
||||
apiResult.Sub.ShouldBe(The.Sub);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(AllSetups))]
|
||||
public async Task calls_outside_http_context_can_get_cookie_configuration(BffSetupType setup)
|
||||
{
|
||||
Bff.OnConfigureApp += app =>
|
||||
{
|
||||
app.Map(The.Path, c => ApiHost.ReturnApiCallDetails(c, () => HttpStatusCode.OK))
|
||||
.RequireAuthorization()
|
||||
.AsBffApiEndpoint();
|
||||
};
|
||||
|
||||
await ConfigureBff(setup);
|
||||
|
||||
// this retrieves the cookie connect options outside the http context. This shouldn't
|
||||
// normally happen but AzureAppConfigurationRefreshMiddleware can cause this.
|
||||
// when this happens, this call would fail with No HTTP Context available,
|
||||
// but also all subsequent requests, because IOptionsCache caches this.
|
||||
var opt = Bff.Resolve<IOptionsMonitor<CookieAuthenticationOptions>>();
|
||||
opt.Get(Some.BffFrontend().CookieSchemeName);
|
||||
|
||||
await Bff.BrowserClient.Login();
|
||||
|
||||
ApiCallDetails apiResult = await Bff.BrowserClient.CallBffHostApi(
|
||||
url: Bff.Url(The.Path)
|
||||
);
|
||||
|
||||
apiResult.Method.ShouldBe(HttpMethod.Get);
|
||||
apiResult.Path.ShouldBe(The.Path);
|
||||
apiResult.Sub.ShouldBe(The.Sub);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue