Browse Source

Merge pull request #25878 from abpframework/auto-merge/rel-10-6/4733

Merge branch dev with rel-10.6
pull/25880/head
Volosoft Agent 1 month ago
committed by GitHub
parent
commit
f2e71faa52
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 2
      docs/en/release-info/migration-guides/abp-8-2-blazor-web-app.md
  2. 29
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AntiForgery/AbpAntiforgery.cs
  3. 206
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/AntiForgery/AbpAntiforgery_Tests.cs
  4. 2
      templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/MyProjectNameModule.cs
  5. 2
      templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/MyProjectNameModule.cs
  6. 2
      templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server.Mongo/MyProjectNameHostModule.cs
  7. 2
      templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/MyProjectNameHostModule.cs
  8. 2
      templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/MyProjectNameBlazorModule.cs
  9. 2
      templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/MyProjectNameBlazorModule.cs
  10. 2
      templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered/MyProjectNameBlazorModule.cs
  11. 2
      templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp/MyProjectNameBlazorModule.cs
  12. 2
      templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/MyProjectNameBlazorHostModule.cs

2
docs/en/release-info/migration-guides/abp-8-2-blazor-web-app.md

@ -86,8 +86,8 @@ public override void OnApplicationInitialization(ApplicationInitializationContex
var app = context.GetApplicationBuilder(); var app = context.GetApplicationBuilder();
// ... // ...
app.UseAntiforgery();
app.UseAuthorization(); app.UseAuthorization();
app.UseAntiforgery();
app.UseConfiguredEndpoints(builder => app.UseConfiguredEndpoints(builder =>
{ {

29
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AntiForgery/AbpAntiforgery.cs

@ -1,7 +1,10 @@
using System; using System;
using System.Security.Claims;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Antiforgery; using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features.Authentication;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
@ -67,6 +70,8 @@ public class AbpAntiforgery : IAntiforgery
} }
var normalizer = httpContext.RequestServices.GetRequiredService<IAbpAntiForgeryClaimsPrincipalNormalizer>(); var normalizer = httpContext.RequestServices.GetRequiredService<IAbpAntiForgeryClaimsPrincipalNormalizer>();
var authenticateResultFeature = httpContext.Features.Get<IAuthenticateResultFeature>();
var originalResult = authenticateResultFeature?.AuthenticateResult;
var originalPrincipal = httpContext.User; var originalPrincipal = httpContext.User;
httpContext.User = normalizer.Normalize(originalPrincipal); httpContext.User = normalizer.Normalize(originalPrincipal);
try try
@ -75,7 +80,7 @@ public class AbpAntiforgery : IAntiforgery
} }
finally finally
{ {
httpContext.User = originalPrincipal; RestoreAuthenticationState(httpContext, authenticateResultFeature, originalResult, originalPrincipal);
} }
} }
@ -87,6 +92,8 @@ public class AbpAntiforgery : IAntiforgery
} }
var normalizer = httpContext.RequestServices.GetRequiredService<IAbpAntiForgeryClaimsPrincipalNormalizer>(); var normalizer = httpContext.RequestServices.GetRequiredService<IAbpAntiForgeryClaimsPrincipalNormalizer>();
var authenticateResultFeature = httpContext.Features.Get<IAuthenticateResultFeature>();
var originalResult = authenticateResultFeature?.AuthenticateResult;
var originalPrincipal = httpContext.User; var originalPrincipal = httpContext.User;
httpContext.User = normalizer.Normalize(originalPrincipal); httpContext.User = normalizer.Normalize(originalPrincipal);
try try
@ -95,7 +102,25 @@ public class AbpAntiforgery : IAntiforgery
} }
finally finally
{ {
httpContext.User = originalPrincipal; RestoreAuthenticationState(httpContext, authenticateResultFeature, originalResult, originalPrincipal);
}
}
protected virtual void RestoreAuthenticationState(
HttpContext httpContext,
IAuthenticateResultFeature? authenticateResultFeature,
AuthenticateResult? originalResult,
ClaimsPrincipal originalPrincipal)
{
// Assigning HttpContext.User drops the AuthenticateResult on the built-in feature, so restoring the
// principal alone would leave downstream consumers (SignalR connection expiration, idle checks, ...)
// without the authentication properties. IAuthenticateResultFeature does not require an implementation
// to keep the two in sync, so restore both: the principal first, then the result.
httpContext.User = originalPrincipal;
if (originalResult != null && authenticateResultFeature != null)
{
authenticateResultFeature.AuthenticateResult = originalResult;
} }
} }
} }

206
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/AntiForgery/AbpAntiforgery_Tests.cs

@ -2,7 +2,9 @@ using System;
using System.Security.Claims; using System.Security.Claims;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Antiforgery; using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features.Authentication;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Shouldly; using Shouldly;
using Volo.Abp.Security.Claims; using Volo.Abp.Security.Claims;
@ -14,6 +16,7 @@ public class AbpAntiforgery_Tests
{ {
private const string BearerIssuer = "https://localhost:44361/"; private const string BearerIssuer = "https://localhost:44361/";
private const string UserId = "3a0e6f1c-1111-2222-3333-444455556666"; private const string UserId = "3a0e6f1c-1111-2222-3333-444455556666";
private static readonly DateTimeOffset ExpiresUtc = new(2030, 1, 2, 3, 4, 5, TimeSpan.Zero);
[Fact] [Fact]
public Task GetAndStoreTokens_should_normalize_the_user_and_restore_it() => public Task GetAndStoreTokens_should_normalize_the_user_and_restore_it() =>
@ -45,6 +48,109 @@ public class AbpAntiforgery_Tests
(antiforgery, httpContext) => { antiforgery.SetCookieTokenAndHeader(httpContext); return Task.CompletedTask; }, (antiforgery, httpContext) => { antiforgery.SetCookieTokenAndHeader(httpContext); return Task.CompletedTask; },
inner => inner.UserSeenBySetCookieTokenAndHeader); inner => inner.UserSeenBySetCookieTokenAndHeader);
[Fact]
public Task GetAndStoreTokens_should_preserve_the_authenticate_result() =>
Should_preserve_the_authenticate_result(
(antiforgery, httpContext) => { antiforgery.GetAndStoreTokens(httpContext); return Task.CompletedTask; });
[Fact]
public Task GetTokens_should_preserve_the_authenticate_result() =>
Should_preserve_the_authenticate_result(
(antiforgery, httpContext) => { antiforgery.GetTokens(httpContext); return Task.CompletedTask; });
[Fact]
public Task IsRequestValidAsync_should_preserve_the_authenticate_result() =>
Should_preserve_the_authenticate_result(
(antiforgery, httpContext) => antiforgery.IsRequestValidAsync(httpContext));
[Fact]
public Task ValidateRequestAsync_should_preserve_the_authenticate_result() =>
Should_preserve_the_authenticate_result(
(antiforgery, httpContext) => antiforgery.ValidateRequestAsync(httpContext));
[Fact]
public Task SetCookieTokenAndHeader_should_preserve_the_authenticate_result() =>
Should_preserve_the_authenticate_result(
(antiforgery, httpContext) => { antiforgery.SetCookieTokenAndHeader(httpContext); return Task.CompletedTask; });
[Fact]
public async Task Should_preserve_the_authenticate_result_when_the_inner_antiforgery_throws()
{
var inner = new RecordingAntiforgery { ThrowOnValidateRequest = true };
var antiforgery = new AbpAntiforgery(inner, CreateOptions(normalize: true));
var original = CreatePrincipal(BearerIssuer);
var httpContext = CreateHttpContext(original, withNormalizer: true, CreateSuccessResult(original));
await Should.ThrowAsync<AntiforgeryValidationException>(
() => antiforgery.ValidateRequestAsync(httpContext));
AssertResultPreserved(httpContext, original);
}
[Fact]
public void Should_restore_both_when_the_feature_does_not_sync_the_user()
{
// IAuthenticateResultFeature does not require an implementation to keep the principal and the result
// in sync; ASP.NET Core itself reuses such a feature. Restoring only the result would leave the
// normalized principal on HttpContext.User.
var inner = new RecordingAntiforgery();
var antiforgery = new AbpAntiforgery(inner, CreateOptions(normalize: true));
var original = CreatePrincipal(BearerIssuer);
var originalResult = CreateSuccessResult(original);
var httpContext = CreateHttpContext(original, withNormalizer: true);
httpContext.Features.Set<IAuthenticateResultFeature>(
new DecoupledAuthenticateResultFeature { AuthenticateResult = originalResult });
antiforgery.GetAndStoreTokens(httpContext);
httpContext.User.ShouldBeSameAs(original);
httpContext.Features.Get<IAuthenticateResultFeature>()!.AuthenticateResult.ShouldBeSameAs(originalResult);
}
[Fact]
public void Should_restore_the_original_authenticate_result_instance()
{
var inner = new RecordingAntiforgery();
var antiforgery = new AbpAntiforgery(inner, CreateOptions(normalize: true));
var original = CreatePrincipal(BearerIssuer);
var originalResult = CreateSuccessResult(original);
var httpContext = CreateHttpContext(original, withNormalizer: true, originalResult);
antiforgery.GetAndStoreTokens(httpContext);
httpContext.Features.Get<IAuthenticateResultFeature>()!.AuthenticateResult.ShouldBeSameAs(originalResult);
}
[Fact]
public void Should_restore_the_user_when_there_is_no_authenticate_result_feature()
{
var inner = new RecordingAntiforgery();
var antiforgery = new AbpAntiforgery(inner, CreateOptions(normalize: true));
var original = CreatePrincipal(BearerIssuer);
var httpContext = CreateHttpContext(original, withNormalizer: true);
antiforgery.GetAndStoreTokens(httpContext);
httpContext.Features.Get<IAuthenticateResultFeature>().ShouldBeNull();
httpContext.User.ShouldBeSameAs(original);
}
[Fact]
public void Should_restore_the_user_when_the_authenticate_result_is_null()
{
var inner = new RecordingAntiforgery();
var antiforgery = new AbpAntiforgery(inner, CreateOptions(normalize: true));
var original = CreatePrincipal(BearerIssuer);
var httpContext = CreateHttpContext(original, withNormalizer: true, CreateSuccessResult(original));
httpContext.Features.Get<IAuthenticateResultFeature>()!.AuthenticateResult = null;
httpContext.User = original;
antiforgery.GetAndStoreTokens(httpContext);
httpContext.User.ShouldBeSameAs(original);
httpContext.Features.Get<IAuthenticateResultFeature>()!.AuthenticateResult.ShouldBeNull();
}
[Fact] [Fact]
public void Should_delegate_the_result_to_the_inner_antiforgery() public void Should_delegate_the_result_to_the_inner_antiforgery()
{ {
@ -103,13 +209,48 @@ public class AbpAntiforgery_Tests
httpContext.User.ShouldBeSameAs(original); httpContext.User.ShouldBeSameAs(original);
} }
private static async Task Should_preserve_the_authenticate_result(Func<IAntiforgery, HttpContext, Task> invoke)
{
var inner = new RecordingAntiforgery();
var antiforgery = new AbpAntiforgery(inner, CreateOptions(normalize: true));
var original = CreatePrincipal(BearerIssuer);
var httpContext = CreateHttpContext(original, withNormalizer: true, CreateSuccessResult(original));
await invoke(antiforgery, httpContext);
AssertResultPreserved(httpContext, original);
}
private static void AssertResultPreserved(HttpContext httpContext, ClaimsPrincipal original)
{
var result = httpContext.Features.Get<IAuthenticateResultFeature>()!.AuthenticateResult;
result.ShouldNotBeNull();
result.Properties!.IsPersistent.ShouldBeTrue();
result.Properties.ExpiresUtc.ShouldBe(ExpiresUtc);
httpContext.User.ShouldBeSameAs(original);
}
private static AuthenticateResult CreateSuccessResult(ClaimsPrincipal principal)
{
var properties = new AuthenticationProperties
{
IsPersistent = true,
ExpiresUtc = ExpiresUtc
};
return AuthenticateResult.Success(new AuthenticationTicket(principal, properties, "TestScheme"));
}
private static Microsoft.Extensions.Options.IOptions<AbpAntiForgeryOptions> CreateOptions(bool normalize) private static Microsoft.Extensions.Options.IOptions<AbpAntiForgeryOptions> CreateOptions(bool normalize)
{ {
return Microsoft.Extensions.Options.Options.Create( return Microsoft.Extensions.Options.Options.Create(
new AbpAntiForgeryOptions { NormalizeUserIdClaimIssuer = normalize }); new AbpAntiForgeryOptions { NormalizeUserIdClaimIssuer = normalize });
} }
private static HttpContext CreateHttpContext(ClaimsPrincipal user, bool withNormalizer) private static HttpContext CreateHttpContext(
ClaimsPrincipal user,
bool withNormalizer,
AuthenticateResult? authenticateResult = null)
{ {
var services = new ServiceCollection(); var services = new ServiceCollection();
if (withNormalizer) if (withNormalizer)
@ -117,11 +258,63 @@ public class AbpAntiforgery_Tests
services.AddTransient<IAbpAntiForgeryClaimsPrincipalNormalizer, AbpAntiForgeryClaimsPrincipalNormalizer>(); services.AddTransient<IAbpAntiForgeryClaimsPrincipalNormalizer, AbpAntiForgeryClaimsPrincipalNormalizer>();
} }
return new DefaultHttpContext var httpContext = new DefaultHttpContext
{ {
User = user,
RequestServices = services.BuildServiceProvider() RequestServices = services.BuildServiceProvider()
}; };
if (authenticateResult != null)
{
var features = new TestAuthenticationFeatures(authenticateResult);
httpContext.Features.Set<IHttpAuthenticationFeature>(features);
httpContext.Features.Set<IAuthenticateResultFeature>(features);
}
else
{
httpContext.User = user;
}
return httpContext;
}
// An IAuthenticateResultFeature that does not touch HttpContext.User, like the one ASP.NET Core
// reuses in AuthorizationMiddlewareTests.
private sealed class DecoupledAuthenticateResultFeature : IAuthenticateResultFeature
{
public AuthenticateResult? AuthenticateResult { get; set; }
}
// Mirrors the framework's internal AuthenticationFeatures: assigning User drops the AuthenticateResult,
// which is the behaviour AbpAntiforgery has to compensate for when it restores the principal.
private sealed class TestAuthenticationFeatures : IAuthenticateResultFeature, IHttpAuthenticationFeature
{
private ClaimsPrincipal? _user;
private AuthenticateResult? _result;
public TestAuthenticationFeatures(AuthenticateResult result)
{
AuthenticateResult = result;
}
public AuthenticateResult? AuthenticateResult
{
get => _result;
set
{
_result = value;
_user = _result?.Principal;
}
}
public ClaimsPrincipal? User
{
get => _user;
set
{
_user = value;
_result = null;
}
}
} }
private static ClaimsPrincipal CreatePrincipal(string userIdClaimIssuer) private static ClaimsPrincipal CreatePrincipal(string userIdClaimIssuer)
@ -145,6 +338,8 @@ public class AbpAntiforgery_Tests
public ClaimsPrincipal? UserSeenByValidateRequest { get; private set; } public ClaimsPrincipal? UserSeenByValidateRequest { get; private set; }
public ClaimsPrincipal? UserSeenBySetCookieTokenAndHeader { get; private set; } public ClaimsPrincipal? UserSeenBySetCookieTokenAndHeader { get; private set; }
public bool ThrowOnValidateRequest { get; set; }
public AntiforgeryTokenSet GetAndStoreTokens(HttpContext httpContext) public AntiforgeryTokenSet GetAndStoreTokens(HttpContext httpContext)
{ {
UserSeenByGetAndStoreTokens = httpContext.User; UserSeenByGetAndStoreTokens = httpContext.User;
@ -166,6 +361,11 @@ public class AbpAntiforgery_Tests
public Task ValidateRequestAsync(HttpContext httpContext) public Task ValidateRequestAsync(HttpContext httpContext)
{ {
UserSeenByValidateRequest = httpContext.User; UserSeenByValidateRequest = httpContext.User;
if (ThrowOnValidateRequest)
{
throw new AntiforgeryValidationException("test");
}
return Task.CompletedTask; return Task.CompletedTask;
} }

2
templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/MyProjectNameModule.cs

@ -361,8 +361,8 @@ public class MyProjectNameModule : AbpModule
app.UseUnitOfWork(); app.UseUnitOfWork();
app.UseDynamicClaims(); app.UseDynamicClaims();
app.UseAntiforgery();
app.UseAuthorization(); app.UseAuthorization();
app.UseAntiforgery();
app.UseSwagger(); app.UseSwagger();
app.UseAbpSwaggerUI(options => app.UseAbpSwaggerUI(options =>

2
templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/MyProjectNameModule.cs

@ -378,8 +378,8 @@ public class MyProjectNameModule : AbpModule
app.UseUnitOfWork(); app.UseUnitOfWork();
app.UseDynamicClaims(); app.UseDynamicClaims();
app.UseAntiforgery();
app.UseAuthorization(); app.UseAuthorization();
app.UseAntiforgery();
app.UseSwagger(); app.UseSwagger();
app.UseAbpSwaggerUI(options => app.UseAbpSwaggerUI(options =>

2
templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server.Mongo/MyProjectNameHostModule.cs

@ -316,8 +316,8 @@ public class MyProjectNameHostModule : AbpModule
app.UseUnitOfWork(); app.UseUnitOfWork();
app.UseDynamicClaims(); app.UseDynamicClaims();
app.UseAntiforgery();
app.UseAuthorization(); app.UseAuthorization();
app.UseAntiforgery();
app.UseSwagger(); app.UseSwagger();
app.UseAbpSwaggerUI(options => app.UseAbpSwaggerUI(options =>

2
templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/MyProjectNameHostModule.cs

@ -335,8 +335,8 @@ public class MyProjectNameHostModule : AbpModule
app.UseUnitOfWork(); app.UseUnitOfWork();
app.UseDynamicClaims(); app.UseDynamicClaims();
app.UseAntiforgery();
app.UseAuthorization(); app.UseAuthorization();
app.UseAntiforgery();
app.UseSwagger(); app.UseSwagger();
app.UseAbpSwaggerUI(options => app.UseAbpSwaggerUI(options =>

2
templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/MyProjectNameBlazorModule.cs

@ -353,8 +353,8 @@ public class MyProjectNameBlazorModule : AbpModule
app.UseMultiTenancy(); app.UseMultiTenancy();
} }
app.UseDynamicClaims(); app.UseDynamicClaims();
app.UseAntiforgery();
app.UseAuthorization(); app.UseAuthorization();
app.UseAntiforgery();
app.UseSwagger(); app.UseSwagger();
app.UseAbpSwaggerUI(options => app.UseAbpSwaggerUI(options =>
{ {

2
templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/MyProjectNameBlazorModule.cs

@ -266,8 +266,8 @@ public class MyProjectNameBlazorModule : AbpModule
} }
app.UseUnitOfWork(); app.UseUnitOfWork();
app.UseDynamicClaims(); app.UseDynamicClaims();
app.UseAntiforgery();
app.UseAuthorization(); app.UseAuthorization();
app.UseAntiforgery();
app.UseSwagger(); app.UseSwagger();
app.UseAbpSwaggerUI(options => app.UseAbpSwaggerUI(options =>

2
templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered/MyProjectNameBlazorModule.cs

@ -360,8 +360,8 @@ public class MyProjectNameBlazorModule : AbpModule
app.UseMultiTenancy(); app.UseMultiTenancy();
} }
app.UseDynamicClaims(); app.UseDynamicClaims();
app.UseAntiforgery();
app.UseAuthorization(); app.UseAuthorization();
app.UseAntiforgery();
app.UseSwagger(); app.UseSwagger();
app.UseAbpSwaggerUI(options => app.UseAbpSwaggerUI(options =>
{ {

2
templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp/MyProjectNameBlazorModule.cs

@ -274,8 +274,8 @@ public class MyProjectNameBlazorModule : AbpModule
} }
app.UseUnitOfWork(); app.UseUnitOfWork();
app.UseDynamicClaims(); app.UseDynamicClaims();
app.UseAntiforgery();
app.UseAuthorization(); app.UseAuthorization();
app.UseAntiforgery();
app.UseSwagger(); app.UseSwagger();
app.UseAbpSwaggerUI(options => app.UseAbpSwaggerUI(options =>

2
templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/MyProjectNameBlazorHostModule.cs

@ -259,8 +259,8 @@ public class MyProjectNameBlazorHostModule : AbpModule
} }
app.UseUnitOfWork(); app.UseUnitOfWork();
app.UseAntiforgery();
app.UseAuthorization(); app.UseAuthorization();
app.UseAntiforgery();
app.UseSwagger(); app.UseSwagger();
app.UseAbpSwaggerUI(options => app.UseAbpSwaggerUI(options =>
{ {

Loading…
Cancel
Save