diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Uow/AbpAspNetCoreUnitOfWorkOptions.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Uow/AbpAspNetCoreUnitOfWorkOptions.cs index 34ba5f29c6..891b6cacf1 100644 --- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Uow/AbpAspNetCoreUnitOfWorkOptions.cs +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Uow/AbpAspNetCoreUnitOfWorkOptions.cs @@ -29,13 +29,10 @@ public class AbpAspNetCoreUnitOfWorkOptions public bool CompleteUnitOfWorkOnResponseStarting { get; set; } = false; /// - /// Absolute request path prefixes (matched by segment) that opt-in to - /// even when it is globally disabled (for example - /// "/connect" matches "/connect/token" but not "/connections"). Each prefix is matched against both - /// the request path and the path base + request path, so an endpoint configured with the path base - /// still matches. A trailing slash is normalized; blank, non-absolute, and root ("/") entries are - /// ignored - use to enable it for every request - /// handled by the middleware. + /// Request path prefixes that opt-in to even when + /// it is globally disabled. A request whose path starts with one of these values (for example + /// "/connect") is included, matched like . Use + /// to enable it for every request handled by the middleware. /// public List CompleteUnitOfWorkOnResponseStartingUrls { get; } = new List(); } diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Uow/AbpUnitOfWorkMiddleware.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Uow/AbpUnitOfWorkMiddleware.cs index 211a5be2cf..61ac3cc5fa 100644 --- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Uow/AbpUnitOfWorkMiddleware.cs +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Uow/AbpUnitOfWorkMiddleware.cs @@ -70,35 +70,9 @@ public class AbpUnitOfWorkMiddleware : AbpMiddlewareBase, ITransientDependency private bool ShouldCompleteOnResponseStarting(HttpContext context) { - if (_options.CompleteUnitOfWorkOnResponseStarting) - { - return true; - } - - foreach (var url in _options.CompleteUnitOfWorkOnResponseStartingUrls) - { - if (string.IsNullOrWhiteSpace(url)) - { - continue; - } - - // Normalize a trailing slash ("/connect/" behaves like "/connect") and ignore non-absolute entries. - var prefix = url.TrimEnd('/'); - if (!prefix.StartsWith("/", StringComparison.Ordinal)) - { - continue; - } - - // Match both the request path and the path base + path, so an absolute endpoint that includes - // the path base still matches when the path base is stripped from Request.Path. - if (context.Request.Path.StartsWithSegments(prefix, StringComparison.OrdinalIgnoreCase) || - context.Request.PathBase.Add(context.Request.Path).StartsWithSegments(prefix, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - } - - return false; + return _options.CompleteUnitOfWorkOnResponseStarting || + (context.Request.Path.Value != null && + _options.CompleteUnitOfWorkOnResponseStartingUrls.Any(x => context.Request.Path.Value.StartsWith(x, StringComparison.OrdinalIgnoreCase))); } protected async override Task ShouldSkipAsync(HttpContext context, RequestDelegate next) diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcTestModule.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcTestModule.cs index 02663eae90..4339104b30 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcTestModule.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcTestModule.cs @@ -164,9 +164,6 @@ public class AbpAspNetCoreMvcTestModule : AbpModule app.UseStaticFiles(); app.UseAbpRequestLocalization(); app.UseAbpSecurityHeaders(); - // Moves the "/pathbase-test" prefix into Request.PathBase so a unit of work opt-in test can - // exercise path base matching; a no-op for every other request. - app.UsePathBase("/pathbase-test"); app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkMiddleware_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkMiddleware_Tests.cs index 26f81ef3a0..283a7ad699 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkMiddleware_Tests.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkMiddleware_Tests.cs @@ -119,34 +119,4 @@ public class UnitOfWorkMiddleware_Tests : AspNetCoreMvcTestBase var result = await GetResponseAsStringAsync("/api/unitofwork-test/CommitBeforeResponseFlush"); result.ShouldBe("first:completed"); } - - [Fact] - public async Task Opt_In_Url_With_A_Non_Segment_Prefix_Should_Not_Match() - { - Options.CompleteUnitOfWorkOnResponseStartingUrls.Add("/api/unitofwork-test/Commit"); - - var result = await GetResponseAsStringAsync("/api/unitofwork-test/CommitBeforeResponseFlush"); - result.ShouldBe("first:not-completed"); - } - - [Fact] - public async Task Blank_Or_Malformed_Opt_In_Urls_Are_Ignored() - { - Options.CompleteUnitOfWorkOnResponseStartingUrls.Add(""); - Options.CompleteUnitOfWorkOnResponseStartingUrls.Add(" "); - Options.CompleteUnitOfWorkOnResponseStartingUrls.Add("api/no-leading-slash"); - - var result = await GetResponseAsStringAsync("/api/unitofwork-test/CommitBeforeResponseFlush"); - result.ShouldBe("first:not-completed"); - } - - [Fact] - public async Task Opt_In_Url_Including_The_Path_Base_Matches() - { - // The request path is "/api/..." (the path base is stripped), so this only matches via path base + path. - Options.CompleteUnitOfWorkOnResponseStartingUrls.Add("/pathbase-test/api/unitofwork-test/CommitBeforeResponseFlush"); - - var result = await GetResponseAsStringAsync("/pathbase-test/api/unitofwork-test/CommitBeforeResponseFlush"); - result.ShouldBe("first:completed"); - } } diff --git a/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/AbpOpenIddictAspNetCoreModule.cs b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/AbpOpenIddictAspNetCoreModule.cs index edaa453b2e..ea03453c52 100644 --- a/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/AbpOpenIddictAspNetCoreModule.cs +++ b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/AbpOpenIddictAspNetCoreModule.cs @@ -1,10 +1,7 @@ -using System; -using System.Collections.Generic; -using System.Linq; +using System.Collections.Generic; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; using OpenIddict.Abstractions; using OpenIddict.Server; using Volo.Abp.AspNetCore.MultiTenancy; @@ -51,40 +48,12 @@ public class AbpOpenIddictAspNetCoreModule : AbpModule options.RemoveClientIdClaim(); }); - // Complete data written while processing OpenIddict requests before the response starts. - // Derived from the configured OpenIddict server endpoint paths (including the device endpoint). - context.Services.AddOptions() - .Configure>((uowOptions, serverOptions) => - { - foreach (var path in GetServerEndpointPaths(serverOptions.Value)) - { - if (!uowOptions.CompleteUnitOfWorkOnResponseStartingUrls.Contains(path)) - { - uowOptions.CompleteUnitOfWorkOnResponseStartingUrls.Add(path); - } - } - }); - } - - private static IEnumerable GetServerEndpointPaths(OpenIddictServerOptions serverOptions) - { - var endpoints = serverOptions.TokenEndpointUris - .Concat(serverOptions.AuthorizationEndpointUris) - .Concat(serverOptions.DeviceAuthorizationEndpointUris) - .Concat(serverOptions.PushedAuthorizationEndpointUris) - .Concat(serverOptions.EndSessionEndpointUris) - .Concat(serverOptions.RevocationEndpointUris) - .Concat(serverOptions.EndUserVerificationEndpointUris); - - foreach (var uri in endpoints) + // Complete tokens/authorizations/sessions written while processing OpenIddict requests before the response starts. + Configure(options => { - // Normalize the (usually relative) endpoint URI to an absolute path, e.g. "connect/token" -> "/connect/token". - var path = uri.IsAbsoluteUri ? uri.AbsolutePath : "/" + uri.OriginalString.RemovePreFix("./").TrimStart('/'); - if (path.Length > 1) - { - yield return path; - } - } + options.CompleteUnitOfWorkOnResponseStartingUrls.AddIfNotContains("/connect"); + options.CompleteUnitOfWorkOnResponseStartingUrls.AddIfNotContains("/device"); + }); } private void AddOpenIddictServer(IServiceCollection services) diff --git a/modules/openiddict/test/Volo.Abp.OpenIddict.AspNetCore.Tests/Volo/Abp/OpenIddict/Integration/OpenIddictTokenEndpoint_Integration_Tests.cs b/modules/openiddict/test/Volo.Abp.OpenIddict.AspNetCore.Tests/Volo/Abp/OpenIddict/Integration/OpenIddictTokenEndpoint_Integration_Tests.cs index 483e64694b..cf11cffdf3 100644 --- a/modules/openiddict/test/Volo.Abp.OpenIddict.AspNetCore.Tests/Volo/Abp/OpenIddict/Integration/OpenIddictTokenEndpoint_Integration_Tests.cs +++ b/modules/openiddict/test/Volo.Abp.OpenIddict.AspNetCore.Tests/Volo/Abp/OpenIddict/Integration/OpenIddictTokenEndpoint_Integration_Tests.cs @@ -34,7 +34,7 @@ public class OpenIddictTokenEndpoint_Integration_Tests : AbpWebApplicationFactor [Fact] public async Task Token_Row_Is_Committed_Before_The_Connect_Token_Response_Is_Sent() { - // The OpenIddict module opts its endpoint paths (including "/connect/token") in by default. + // The OpenIddict module opts "/connect" in by default. var response = await RequestTokenAsync(); response.StatusCode.ShouldBe(HttpStatusCode.OK);