Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 0 additions & 20 deletions src/Http/Http.Abstractions/src/Metadata/ApiEndpointMetadata.cs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.AspNetCore.Http.Metadata;

/// <summary>
/// Metadata that indicates the endpoint should disable cookie-based authentication redirects.
/// When present, authentication handlers should prefer returning status codes over browser redirects.
/// </summary>
internal sealed class DisableCookieRedirectMetadata : IDisableCookieRedirectMetadata
{
/// <summary>
/// Singleton instance of <see cref="DisableCookieRedirectMetadata"/>.
/// </summary>
public static readonly DisableCookieRedirectMetadata Instance = new();

private DisableCookieRedirectMetadata()
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.AspNetCore.Http.Metadata;

/// <summary>
/// Metadata that indicates the endpoint should allow cookie-based authentication redirects.
/// This is normally the default behavior, but it exists to override <see cref="IDisableCookieRedirectMetadata"/> no matter the order.
/// When present, the cookie authentication handler will prefer browser login or access denied redirects over 401 and 403 status codes.
/// </summary>
public interface IAllowCookieRedirectMetadata
{
}
12 changes: 0 additions & 12 deletions src/Http/Http.Abstractions/src/Metadata/IApiEndpointMetadata.cs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.AspNetCore.Http.Metadata;

/// <summary>
/// Metadata that indicates the endpoint should disable cookie-based authentication redirects
/// typically because it is intended for API clients rather than direct browser navigation.
///
/// <see cref="IAllowCookieRedirectMetadata"/> overrides this no matter the order.
///
/// When present and not overridden, the cookie authentication handler will prefer using
/// 401 and 403 status codes over redirecting to the login or access denied paths.
/// </summary>
public interface IDisableCookieRedirectMetadata
{
}
3 changes: 2 additions & 1 deletion src/Http/Http.Abstractions/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#nullable enable
Microsoft.AspNetCore.Http.Metadata.IApiEndpointMetadata
Microsoft.AspNetCore.Http.Metadata.IAllowCookieRedirectMetadata
Microsoft.AspNetCore.Http.Metadata.IDisableCookieRedirectMetadata
Microsoft.AspNetCore.Http.Metadata.IDisableValidationMetadata
Microsoft.AspNetCore.Http.ProducesResponseTypeMetadata.Description.get -> string?
Microsoft.AspNetCore.Http.ProducesResponseTypeMetadata.Description.set -> void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context)

if (hasJsonBody || hasResponseMetadata)
{
codeWriter.WriteLine(RequestDelegateGeneratorSources.ApiEndpointMetadataClass);
codeWriter.WriteLine(RequestDelegateGeneratorSources.DisableCookieRedirectMetadataClass);
}

if (hasFormBody || hasJsonBody || hasResponseMetadata)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -493,18 +493,18 @@ public AntiforgeryMetadata(bool requiresValidation)
}
""";

public static string ApiEndpointMetadataClass = """
file sealed class ApiEndpointMetadata : IApiEndpointMetadata
public static string DisableCookieRedirectMetadataClass = """
file sealed class DisableCookieRedirectMetadata : IDisableCookieRedirectMetadata
{
public static readonly ApiEndpointMetadata Instance = new();
public static readonly DisableCookieRedirectMetadata Instance = new();

private ApiEndpointMetadata()
private DisableCookieRedirectMetadata()
{
}

public static void AddApiEndpointMetadataIfMissing(EndpointBuilder builder)
public static void AddMetadataIfMissing(EndpointBuilder builder)
{
if (!builder.Metadata.Any(m => m is IApiEndpointMetadata))
if (!builder.Metadata.Any(m => m is IDisableCookieRedirectMetadata))
{
builder.Metadata.Add(Instance);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ private static void EmitBuiltinResponseTypeMetadata(this Endpoint endpoint, Code
else if (response.ResponseType is { } responseType)
{
codeWriter.WriteLine($$"""options.EndpointBuilder.Metadata.Add(new ProducesResponseTypeMetadata(statusCode: StatusCodes.Status200OK, type: typeof({{responseType.ToDisplayString(EmitterConstants.DisplayFormatWithoutNullability)}}), contentTypes: GeneratedMetadataConstants.JsonContentType));""");
codeWriter.WriteLine("ApiEndpointMetadata.AddApiEndpointMetadataIfMissing(options.EndpointBuilder);");
codeWriter.WriteLine("DisableCookieRedirectMetadata.AddMetadataIfMissing(options.EndpointBuilder);");
}
}

Expand Down Expand Up @@ -336,15 +336,15 @@ public static void EmitJsonAcceptsMetadata(this Endpoint endpoint, CodeWriter co
codeWriter.WriteLine("if (!serviceProviderIsService.IsService(type))");
codeWriter.StartBlock();
codeWriter.WriteLine("options.EndpointBuilder.Metadata.Add(new AcceptsMetadata(type: type, isOptional: isOptional, contentTypes: GeneratedMetadataConstants.JsonContentType));");
codeWriter.WriteLine("options.EndpointBuilder.Metadata.Add(ApiEndpointMetadata.Instance);");
codeWriter.WriteLine("options.EndpointBuilder.Metadata.Add(DisableCookieRedirectMetadata.Instance);");
codeWriter.WriteLine("break;");
codeWriter.EndBlock();
codeWriter.EndBlock();
}
else
{
codeWriter.WriteLine("options.EndpointBuilder.Metadata.Add(new AcceptsMetadata(contentTypes: GeneratedMetadataConstants.JsonContentType));");
codeWriter.WriteLine("options.EndpointBuilder.Metadata.Add(ApiEndpointMetadata.Instance);");
codeWriter.WriteLine("options.EndpointBuilder.Metadata.Add(DisableCookieRedirectMetadata.Instance);");
}
}

Expand Down
16 changes: 16 additions & 0 deletions src/Http/Http.Extensions/src/AllowCookieRedirectAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.AspNetCore.Http.Metadata;

namespace Microsoft.AspNetCore.Http;

/// <summary>
/// Specifies that cookie-based authentication redirects are allowed for an endpoint.
/// This is normally the default behavior, but it exists to override <see cref="IDisableCookieRedirectMetadata"/> no matter the order.
/// When present, the cookie authentication handler will prefer browser login or access denied redirects over 401 and 403 status codes.
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public sealed class AllowCookieRedirectAttribute : Attribute, IAllowCookieRedirectMetadata
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Metadata;

namespace Microsoft.AspNetCore.Builder;

/// <summary>
/// Cookie redirect extension methods for <see cref="IEndpointConventionBuilder"/>.
/// </summary>
public static class CookieRedirectEndpointConventionBuilderExtensions
{
private static readonly AllowCookieRedirectAttribute _allowCookieRedirectAttribute = new();

/// <summary>
/// Specifies that cookie-based authentication redirects are disabled for an endpoint using <see cref="IDisableCookieRedirectMetadata"/>.
/// When present and not overridden by <see cref="AllowCookieRedirect"/> or <see cref="IAllowCookieRedirectMetadata"/>,
/// the cookie authentication handler will prefer using 401 and 403 status codes over redirecting to the login or access denied paths.
/// </summary>
/// <typeparam name="TBuilder">The type of endpoint convention builder.</typeparam>
/// <param name="builder">The endpoint convention builder.</param>
/// <returns>The original convention builder parameter.</returns>
public static TBuilder DisableCookieRedirect<TBuilder>(this TBuilder builder) where TBuilder : IEndpointConventionBuilder
{
builder.Add(b => b.Metadata.Add(DisableCookieRedirectMetadata.Instance));
return builder;
}

/// <summary>
/// Specifies that cookie-based authentication redirects are allowed for an endpoint using <see cref="IAllowCookieRedirectMetadata"/>.
/// This is normally the default behavior, but it exists to override <see cref="IDisableCookieRedirectMetadata"/> no matter the order.
/// When present, the cookie authentication handler will prefer browser login or access denied redirects over 401 and 403 status codes.
/// </summary>
/// <typeparam name="TBuilder">The type of endpoint convention builder.</typeparam>
/// <param name="builder">The endpoint convention builder.</param>
/// <returns>The original convention builder parameter.</returns>
public static TBuilder AllowCookieRedirect<TBuilder>(this TBuilder builder) where TBuilder : IEndpointConventionBuilder
{
builder.Add(b => b.Metadata.Add(_allowCookieRedirectAttribute));
return builder;
}
}
5 changes: 5 additions & 0 deletions src/Http/Http.Extensions/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
#nullable enable
Microsoft.AspNetCore.Builder.CookieRedirectEndpointConventionBuilderExtensions
Microsoft.AspNetCore.Http.AllowCookieRedirectAttribute
Microsoft.AspNetCore.Http.AllowCookieRedirectAttribute.AllowCookieRedirectAttribute() -> void
static Microsoft.AspNetCore.Builder.CookieRedirectEndpointConventionBuilderExtensions.AllowCookieRedirect<TBuilder>(this TBuilder builder) -> TBuilder
static Microsoft.AspNetCore.Builder.CookieRedirectEndpointConventionBuilderExtensions.DisableCookieRedirect<TBuilder>(this TBuilder builder) -> TBuilder
4 changes: 2 additions & 2 deletions src/Http/Http.Extensions/src/RequestDelegateFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,7 @@ private static Expression[] CreateArgumentsAndInferMetadata(MethodInfo methodInf
// When present, authentication handlers should prefer returning status codes over browser redirects.
if (factoryContext.JsonRequestBodyParameter is not null)
{
factoryContext.EndpointBuilder.Metadata.Add(ApiEndpointMetadata.Instance);
factoryContext.EndpointBuilder.Metadata.Add(DisableCookieRedirectMetadata.Instance);
}

PopulateBuiltInResponseTypeMetadata(methodInfo.ReturnType, factoryContext);
Expand Down Expand Up @@ -1062,7 +1062,7 @@ private static void PopulateBuiltInResponseTypeMetadata(Type returnType, Request
{
// Since this endpoint responds with JSON, we assume its an API endpoint not intended for browser navigation,
// but we don't want to bother adding this metadata twice if we've already inferred it based on the expected JSON request body.
builder.Metadata.Add(ApiEndpointMetadata.Instance);
builder.Metadata.Add(DisableCookieRedirectMetadata.Instance);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http.Metadata;

namespace Microsoft.AspNetCore.Http.Extensions.Tests;

public class CookieRedirectEndpointConventionBuilderExtensionsTests
{
[Fact]
public void DisableCookieRedirect_AddsMetadata()
{
// Arrange
var builder = new TestEndpointConventionBuilder();

// Act
builder.DisableCookieRedirect();

// Assert
Assert.IsAssignableFrom<IDisableCookieRedirectMetadata>(Assert.Single(builder.Metadata));
}

[Fact]
public void AllowCookieRedirect_AddsMetadata()
{
// Arrange
var builder = new TestEndpointConventionBuilder();

// Act
builder.AllowCookieRedirect();

// Assert
Assert.IsAssignableFrom<IAllowCookieRedirectMetadata>(Assert.Single(builder.Metadata));
}

[Fact]
public void DisableCookieRedirect_ReturnsBuilder()
{
// Arrange
var builder = new TestEndpointConventionBuilder();

// Act
var result = builder.DisableCookieRedirect();

// Assert
Assert.Same(builder, result);
}

[Fact]
public void AllowCookieRedirect_ReturnsBuilder()
{
// Arrange
var builder = new TestEndpointConventionBuilder();

// Act
var result = builder.AllowCookieRedirect();

// Assert
Assert.Same(builder, result);
}

private sealed class TestEndpointConventionBuilder : EndpointBuilder, IEndpointConventionBuilder
{
public void Add(Action<EndpointBuilder> convention)
{
convention(this);
}

public override Endpoint Build() => throw new NotImplementedException();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2825,8 +2825,8 @@ public void Create_CombinesAllMetadata_InCorrectOrder()
m => Assert.True(m is AcceptsMetadata am && am.RequestType == typeof(AddsCustomParameterMetadata)),
// Inferred ParameterBinding metadata
m => Assert.True(m is IParameterBindingMetadata { Name: "param1" }),
// Inferred IApiEndpointMetadata from RDF for complex request and response type
m => Assert.True(m is IApiEndpointMetadata),
// Inferred IDisableCookieRedirectMetadata from RDF for complex request and response type
m => Assert.True(m is IDisableCookieRedirectMetadata),
// Inferred ProducesResponseTypeMetadata from RDF for complex type
m => Assert.Equal(typeof(CountsDefaultEndpointMetadataPoco), ((IProducesResponseTypeMetadata)m).Type),
// Metadata provided by parameters implementing IEndpointParameterMetadataProvider
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,17 +225,17 @@ namespace Microsoft.AspNetCore.Http.Generated

}

file sealed class ApiEndpointMetadata : IApiEndpointMetadata
file sealed class DisableCookieRedirectMetadata : IDisableCookieRedirectMetadata
{
public static readonly ApiEndpointMetadata Instance = new();
public static readonly DisableCookieRedirectMetadata Instance = new();

private ApiEndpointMetadata()
private DisableCookieRedirectMetadata()
{
}

public static void AddApiEndpointMetadataIfMissing(EndpointBuilder builder)
public static void AddMetadataIfMissing(EndpointBuilder builder)
{
if (!builder.Metadata.Any(m => m is IApiEndpointMetadata))
if (!builder.Metadata.Any(m => m is IDisableCookieRedirectMetadata))
{
builder.Metadata.Add(Instance);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -363,17 +363,17 @@ namespace Microsoft.AspNetCore.Http.Generated

}

file sealed class ApiEndpointMetadata : IApiEndpointMetadata
file sealed class DisableCookieRedirectMetadata : IDisableCookieRedirectMetadata
{
public static readonly ApiEndpointMetadata Instance = new();
public static readonly DisableCookieRedirectMetadata Instance = new();

private ApiEndpointMetadata()
private DisableCookieRedirectMetadata()
{
}

public static void AddApiEndpointMetadataIfMissing(EndpointBuilder builder)
public static void AddMetadataIfMissing(EndpointBuilder builder)
{
if (!builder.Metadata.Any(m => m is IApiEndpointMetadata))
if (!builder.Metadata.Any(m => m is IDisableCookieRedirectMetadata))
{
builder.Metadata.Add(Instance);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2225,17 +2225,17 @@ namespace Microsoft.AspNetCore.Http.Generated

}

file sealed class ApiEndpointMetadata : IApiEndpointMetadata
file sealed class DisableCookieRedirectMetadata : IDisableCookieRedirectMetadata
{
public static readonly ApiEndpointMetadata Instance = new();
public static readonly DisableCookieRedirectMetadata Instance = new();

private ApiEndpointMetadata()
private DisableCookieRedirectMetadata()
{
}

public static void AddApiEndpointMetadataIfMissing(EndpointBuilder builder)
public static void AddMetadataIfMissing(EndpointBuilder builder)
{
if (!builder.Metadata.Any(m => m is IApiEndpointMetadata))
if (!builder.Metadata.Any(m => m is IDisableCookieRedirectMetadata))
{
builder.Metadata.Add(Instance);
}
Expand Down
Loading
Loading