From 6d40204290fe4ce056e2098cd60fb4f5b3214add Mon Sep 17 00:00:00 2001 From: Petr Moskor Date: Thu, 3 Apr 2025 10:09:24 +0200 Subject: [PATCH 1/2] Added JwtBearerOptions.EventsType support - Added a constructor to `JwtBearerEventsWrapperBase` for flexible inner service handling. - Implemented `InnerResolved` method to resolve inner `JwtBearerEvents` from the service provider. - Updated `JwtBearerOptionsExtensions` to dynamically set the `Events` property based on `EventsType`. - Enhanced `QueryStringJwtBearerEventsWrapper` with new constructors and validation checks. - Introduced new unit tests for `QueryStringJwtBearerEventsTypeWrapper` and `QueryStringJwtBearerEventsWrapperBaseTests`. - Refactored existing tests to align with the new structure and validate event wrapper behavior. --- .../JwtBearerEventsWrapperBase.cs | 34 +++- .../JwtBearerOptionsExtensions.cs | 35 +++- .../QueryStringJwtBearerEventsWrapper.cs | 50 +++++ .../JwtBearerEventsWrapperBaseTests.cs | 7 + .../JwtBearerOptionsExtensionsTests.cs | 44 +++++ .../JwtBearerOptionsPostConfigurationTests.cs | 4 +- ...ryStringJwtBearerEventsTypeWrapperTests.cs | 147 ++++++++++++++ ...ryStringJwtBearerEventsWrapperBaseTests.cs | 187 ++++++++++++++++++ .../QueryStringJwtBearerEventsWrapperTests.cs | 179 +---------------- 9 files changed, 500 insertions(+), 187 deletions(-) create mode 100644 test/Invio.Extensions.Authentication.JwtBearer.Tests/QueryStringJwtBearerEventsTypeWrapperTests.cs create mode 100644 test/Invio.Extensions.Authentication.JwtBearer.Tests/QueryStringJwtBearerEventsWrapperBaseTests.cs diff --git a/src/Invio.Extensions.Authentication.JwtBearer/JwtBearerEventsWrapperBase.cs b/src/Invio.Extensions.Authentication.JwtBearer/JwtBearerEventsWrapperBase.cs index 6d53ecb..81f80eb 100644 --- a/src/Invio.Extensions.Authentication.JwtBearer/JwtBearerEventsWrapperBase.cs +++ b/src/Invio.Extensions.Authentication.JwtBearer/JwtBearerEventsWrapperBase.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.Extensions.DependencyInjection; namespace Invio.Extensions.Authentication.JwtBearer { @@ -14,9 +15,10 @@ namespace Invio.Extensions.Authentication.JwtBearer { /// true wrappers to implement their desired functionality without having /// to provide distracting invocations to the wrapped . /// - public abstract class JwtBearerEventsWrapperBase : JwtBearerEvents { - - private JwtBearerEvents inner { get; } + public abstract class JwtBearerEventsWrapperBase : JwtBearerEvents + { + private JwtBearerEvents inner; + private readonly Type innerType; /// /// Creates a implementation which @@ -37,19 +39,34 @@ protected JwtBearerEventsWrapperBase(JwtBearerEvents inner) { this.inner = inner; } + /// + /// Creates a implementation which + /// does nothing but call the injected wrapped implementation by default. + /// + /// + /// The base service type that will be resolved from the request services + /// and then invoked after the inheriting wrapper performs its side effects. + /// + /// + /// Thrown when is null. + /// + protected JwtBearerEventsWrapperBase(Type innerType) { + this.innerType = innerType ?? throw new ArgumentNullException(nameof(innerType)); + } + /// /// Invoked if exceptions are thrown during request processing. /// The exceptions will be re-thrown after this event unless suppressed. /// public override Task AuthenticationFailed(AuthenticationFailedContext context) { - return inner.AuthenticationFailed(context); + return InnerResolved(context).AuthenticationFailed(context); } /// /// Invoked when a protocol message is first received. /// public override Task MessageReceived(MessageReceivedContext context) { - return inner.MessageReceived(context); + return InnerResolved(context).MessageReceived(context); } /// @@ -57,16 +74,19 @@ public override Task MessageReceived(MessageReceivedContext context) { /// and a ClaimsIdentity has been generated. /// public override Task TokenValidated(TokenValidatedContext context) { - return inner.TokenValidated(context); + return InnerResolved(context).TokenValidated(context); } /// /// Invoked before a challenge is sent back to the caller. /// public override Task Challenge(JwtBearerChallengeContext context) { - return inner.Challenge(context); + return InnerResolved(context).Challenge(context); } + private JwtBearerEvents InnerResolved(BaseContext context) => + inner ?? + (inner = (JwtBearerEvents)context.HttpContext.RequestServices.GetRequiredService(innerType)); } } diff --git a/src/Invio.Extensions.Authentication.JwtBearer/JwtBearerOptionsExtensions.cs b/src/Invio.Extensions.Authentication.JwtBearer/JwtBearerOptionsExtensions.cs index e28be6d..2eeccd0 100644 --- a/src/Invio.Extensions.Authentication.JwtBearer/JwtBearerOptionsExtensions.cs +++ b/src/Invio.Extensions.Authentication.JwtBearer/JwtBearerOptionsExtensions.cs @@ -28,10 +28,18 @@ public static JwtBearerOptions AddQueryStringAuthentication( throw new ArgumentNullException(nameof(options)); } - options.Events = - new QueryStringJwtBearerEventsWrapper( - options.Events ?? new JwtBearerEvents() - ); + if (options.EventsType == null) { + options.Events = + new QueryStringJwtBearerEventsWrapper( + options.Events ?? new JwtBearerEvents() + ); + } else { + options.Events = + new QueryStringJwtBearerEventsWrapper( + options.EventsType + ); + options.EventsType = null; + } return options; } @@ -61,11 +69,20 @@ public static JwtBearerOptions AddQueryStringAuthentication( throw new ArgumentNullException(nameof(options)); } - options.Events = - new QueryStringJwtBearerEventsWrapper( - options.Events ?? new JwtBearerEvents(), - queryStringParameterName - ); + if (options.EventsType == null) { + options.Events = + new QueryStringJwtBearerEventsWrapper( + options.Events ?? new JwtBearerEvents(), + queryStringParameterName + ); + } else { + options.Events = + new QueryStringJwtBearerEventsWrapper( + options.EventsType, + queryStringParameterName + ); + options.EventsType = null; + } return options; } diff --git a/src/Invio.Extensions.Authentication.JwtBearer/QueryStringJwtBearerEventsWrapper.cs b/src/Invio.Extensions.Authentication.JwtBearer/QueryStringJwtBearerEventsWrapper.cs index b5dbc8f..ce4915b 100644 --- a/src/Invio.Extensions.Authentication.JwtBearer/QueryStringJwtBearerEventsWrapper.cs +++ b/src/Invio.Extensions.Authentication.JwtBearer/QueryStringJwtBearerEventsWrapper.cs @@ -58,6 +58,20 @@ public class QueryStringJwtBearerEventsWrapper : JwtBearerEventsWrapperBase { public QueryStringJwtBearerEventsWrapper(JwtBearerEvents inner) : this(inner, DefaultQueryStringParameterName) {} + /// + /// Wraps an instance of with a behavior + /// that checks for a token in the query string with a name of "bearer". + /// + /// + /// A base service type implementation of + /// that will gain this additional query string inspection behavior. + /// + /// + /// Thrown when is null. + /// + public QueryStringJwtBearerEventsWrapper(Type innerType) : + this(innerType, DefaultQueryStringParameterName) {} + /// /// Wraps an instance of with a behavior /// checks for a token in the query string with a name specified in the @@ -94,6 +108,42 @@ public QueryStringJwtBearerEventsWrapper(JwtBearerEvents inner, string queryStri this.QueryStringParameterName = queryStringParameterName; } + /// + /// Wraps an instance of with a behavior + /// checks for a token in the query string with a name specified in the + /// parameter. + /// + /// + /// A base service type implementation of + /// that will gain this additional query string inspection behavior. + /// + /// + /// The name of the query string parameter that will be sought from requests + /// in order to extract a token. + /// + /// + /// Thrown when or + /// is null. + /// + /// + /// Thrown when is an invalid name + /// for a query string parameter. + /// + public QueryStringJwtBearerEventsWrapper(Type innerType, string queryStringParameterName) : + base(innerType) { + + if (queryStringParameterName == null) { + throw new ArgumentNullException(nameof(queryStringParameterName)); + } else if (String.IsNullOrWhiteSpace(queryStringParameterName)) { + throw new ArgumentException( + $"The '{nameof(queryStringParameterName)}' cannot be null or whitespace.", + nameof(queryStringParameterName) + ); + } + + this.QueryStringParameterName = queryStringParameterName; + } + /// /// Checks the web request for the in /// the request's query string. If it is found, it fetches the token diff --git a/test/Invio.Extensions.Authentication.JwtBearer.Tests/JwtBearerEventsWrapperBaseTests.cs b/test/Invio.Extensions.Authentication.JwtBearer.Tests/JwtBearerEventsWrapperBaseTests.cs index 11ec394..bf1b481 100644 --- a/test/Invio.Extensions.Authentication.JwtBearer.Tests/JwtBearerEventsWrapperBaseTests.cs +++ b/test/Invio.Extensions.Authentication.JwtBearer.Tests/JwtBearerEventsWrapperBaseTests.cs @@ -17,6 +17,7 @@ public async Task AuthenticationFailed_Wraps() { var inner = Mock.Of(); var wrapper = this.CreateJwtBearerEvents(inner); var context = new DefaultAuthenticationFailedContext(); + SetupContext(context, inner); // Act @@ -35,6 +36,7 @@ public async Task MessageReceived_Wraps() { var inner = Mock.Of(); var wrapper = this.CreateJwtBearerEvents(inner); var context = new DefaultMessageReceivedContext(); + SetupContext(context, inner); // Act @@ -53,6 +55,7 @@ public async Task TokenValidated() { var inner = Mock.Of(); var wrapper = this.CreateJwtBearerEvents(inner); var context = new DefaultTokenValidatedContext(); + SetupContext(context, inner); // Act @@ -71,6 +74,7 @@ public async Task Challenge_Wraps() { var inner = Mock.Of(); var wrapper = this.CreateJwtBearerEvents(inner); var context = new DefaultJwtBearerChallengeContext(); + SetupContext(context, inner); // Act @@ -81,6 +85,9 @@ public async Task Challenge_Wraps() { Mock.Get(inner).Verify(events => events.Challenge(context)); } + protected virtual void SetupContext(BaseContext context, + JwtBearerEvents inner) {} + protected abstract JwtBearerEvents CreateJwtBearerEvents(JwtBearerEvents inner); } diff --git a/test/Invio.Extensions.Authentication.JwtBearer.Tests/JwtBearerOptionsExtensionsTests.cs b/test/Invio.Extensions.Authentication.JwtBearer.Tests/JwtBearerOptionsExtensionsTests.cs index 29f20be..60b048b 100644 --- a/test/Invio.Extensions.Authentication.JwtBearer.Tests/JwtBearerOptionsExtensionsTests.cs +++ b/test/Invio.Extensions.Authentication.JwtBearer.Tests/JwtBearerOptionsExtensionsTests.cs @@ -48,6 +48,30 @@ public static void AddQueryStringAuthentication_DefaultQueryStringParameter() { ); } + [Fact] + public static void AddQueryStringAuthentication_DefaultQueryStringParameter_CustomEventsType() { + + // Arrange + + var options = new JwtBearerOptions { EventsType = typeof(JwtBearerEvents) }; + + // Act + + options.AddQueryStringAuthentication(); + + // Assert + + Assert.NotNull(options.Events); + var events = Assert.IsType(options.Events); + + Assert.Equal( + QueryStringJwtBearerEventsWrapper.DefaultQueryStringParameterName, + events.QueryStringParameterName + ); + + Assert.Null(options.EventsType); + } + [Fact] public static void AddQueryStringAuthentication_CustomQueryStringParameter_NullOptions() { @@ -85,6 +109,26 @@ public static void AddQueryStringAuthentication_CustomQueryStringParameter() { Assert.Equal(queryStringParameterName, events.QueryStringParameterName); } + [Fact] + public static void AddQueryStringAuthentication_CustomQueryStringParameter_CustomEventsType() { + + // Arrange + + var options = new JwtBearerOptions { EventsType = typeof(JwtBearerEvents) }; + const string queryStringParameterName = "query-string-parameter-name"; + + // Act + + options.AddQueryStringAuthentication(queryStringParameterName); + + // Assert + + Assert.NotNull(options.Events); + var events = Assert.IsType(options.Events); + Assert.Equal(queryStringParameterName, events.QueryStringParameterName); + Assert.Null(options.EventsType); + } + } } \ No newline at end of file diff --git a/test/Invio.Extensions.Authentication.JwtBearer.Tests/JwtBearerOptionsPostConfigurationTests.cs b/test/Invio.Extensions.Authentication.JwtBearer.Tests/JwtBearerOptionsPostConfigurationTests.cs index ffb3e6a..cfea105 100644 --- a/test/Invio.Extensions.Authentication.JwtBearer.Tests/JwtBearerOptionsPostConfigurationTests.cs +++ b/test/Invio.Extensions.Authentication.JwtBearer.Tests/JwtBearerOptionsPostConfigurationTests.cs @@ -51,7 +51,8 @@ public static IEnumerable PostConfigure_AppliesEventsWrapper_MemberDat get { var tuples = ImmutableList.Create<(string, JwtBearerOptions)>( ("name", new JwtBearerOptions { Events = null }), - (null, new JwtBearerOptions { Events = new JwtBearerEvents() }) + (null, new JwtBearerOptions { Events = new JwtBearerEvents() }), + ("name", new JwtBearerOptions { EventsType = typeof(JwtBearerEvents) }) ); return tuples.Select(tuple => new object[] { tuple.Item1, tuple.Item2 }); @@ -77,6 +78,7 @@ public void PostConfigure_AppliesEventsWrapper( var typed = Assert.IsType(bearerOptions.Events); Assert.Equal(options.QueryStringParameterName, typed.QueryStringParameterName); + Assert.Null(bearerOptions.EventsType); } private IPostConfigureOptions CreatePostConfiguration( diff --git a/test/Invio.Extensions.Authentication.JwtBearer.Tests/QueryStringJwtBearerEventsTypeWrapperTests.cs b/test/Invio.Extensions.Authentication.JwtBearer.Tests/QueryStringJwtBearerEventsTypeWrapperTests.cs new file mode 100644 index 0000000..41ff12f --- /dev/null +++ b/test/Invio.Extensions.Authentication.JwtBearer.Tests/QueryStringJwtBearerEventsTypeWrapperTests.cs @@ -0,0 +1,147 @@ +using System; +using System.Threading.Tasks; +using Invio.Xunit; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace Invio.Extensions.Authentication.JwtBearer { + + [UnitTest] + public sealed class QueryStringJwtBearerEventsTypeWrapperTests : QueryStringJwtBearerEventsWrapperBaseTests { + + [Fact] + public void Constructor_DefaultQueryStringParameterName_NullInner() { + + // Arrange + + Type innerType = null; + + // Act + + var exception = Record.Exception( + () => new QueryStringJwtBearerEventsWrapper(innerType) + ); + + // Assert + + Assert.IsType(exception); + } + + [Fact] + public void Constructor_CustomQueryStringParameterName_NullInner() { + + // Arrange + + Type innerType = null; + + // Act + + var exception = Record.Exception( + () => new QueryStringJwtBearerEventsWrapper(innerType, "myCustomQueryStringParameterName") + ); + + // Assert + + Assert.IsType(exception); + } + + [Fact] + public void Constructor_CustomQueryStringParameterName_NullParameterName() { + + // Arrange + + var innerType = typeof(JwtBearerEvents); + + // Act + + var exception = Record.Exception( + () => new QueryStringJwtBearerEventsWrapper(innerType, null) + ); + + // Assert + + Assert.IsType(exception); + } + + [Theory] + [InlineData("")] + [InlineData(" ")] + public void Constructor_WithCustomQueryStringParameterName_InvalidParameterName( + string queryStringParameterName) { + + // Arrange + + var innerType = typeof(JwtBearerEvents); + + // Act + + var exception = Record.Exception( + () => new QueryStringJwtBearerEventsWrapper(innerType, queryStringParameterName) + ); + + // Assert + + Assert.IsType(exception); + + Assert.Equal( + "The 'queryStringParameterName' cannot be null or whitespace." + + Environment.NewLine + "Parameter name: queryStringParameterName", + exception.Message + ); + } + + [Fact] + public async Task MessageReceived_NullServiceProvider() { + + // Arrange + + var inner = new JwtBearerEvents(); + var events = this.CreateJwtBearerEvents(inner); + var context = new DefaultMessageReceivedContext(); + context.HttpContext.RequestServices = null; + + // Act + + var exception = await Record.ExceptionAsync( + () => events.MessageReceived(context) + ); + + // Assert + + Assert.IsType(exception); + } + + protected override void SetupContext(BaseContext context, + JwtBearerEvents inner) { + + context.HttpContext.RequestServices = CreateServiceProvider(inner); + } + + protected override JwtBearerEvents CreateJwtBearerEvents(JwtBearerEvents inner) { + return this.CreateQueryStringJwtBearerEvents(inner); + } + + protected override QueryStringJwtBearerEventsWrapper CreateQueryStringJwtBearerEvents( + JwtBearerEvents inner) { + + return new QueryStringJwtBearerEventsWrapper(typeof(JwtBearerEvents)); + } + + protected override QueryStringJwtBearerEventsWrapper CreateQueryStringJwtBearerEvents( + JwtBearerEvents inner, string queryStringParameterName) { + + return new QueryStringJwtBearerEventsWrapper(typeof(JwtBearerEvents), queryStringParameterName); + } + + private static IServiceProvider CreateServiceProvider(JwtBearerEvents inner) { + + var services = new ServiceCollection(); + services.AddSingleton(inner); + return services.BuildServiceProvider(); + } + + } + +} diff --git a/test/Invio.Extensions.Authentication.JwtBearer.Tests/QueryStringJwtBearerEventsWrapperBaseTests.cs b/test/Invio.Extensions.Authentication.JwtBearer.Tests/QueryStringJwtBearerEventsWrapperBaseTests.cs new file mode 100644 index 0000000..5b48fa9 --- /dev/null +++ b/test/Invio.Extensions.Authentication.JwtBearer.Tests/QueryStringJwtBearerEventsWrapperBaseTests.cs @@ -0,0 +1,187 @@ +using System; +using System.Net; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Http; +using Moq; +using Xunit; + +namespace Invio.Extensions.Authentication.JwtBearer { + + public abstract class QueryStringJwtBearerEventsWrapperBaseTests : JwtBearerEventsWrapperBaseTests { + + [Fact] + public async Task MessageReceived_NullContext() { + + // Arrange + + var inner = new JwtBearerEvents(); + var events = this.CreateJwtBearerEvents(inner); + + // Act + + var exception = await Record.ExceptionAsync( + () => events.MessageReceived(null) + ); + + // Assert + + Assert.IsType(exception); + } + + [Theory] + [InlineData(null)] + [InlineData("?")] + [InlineData("?another_token=foo")] + public async Task MessageReceived_NoQueryStringParameterName(String queryString) { + + // Arrange + + var inner = Mock.Of(); + var events = this.CreateJwtBearerEvents(inner); + var context = new DefaultMessageReceivedContext(); + context.Request.QueryString = new QueryString(queryString); + SetupContext(context, inner); + + // Act + + await events.MessageReceived(context); + + // Assert + + Assert.Null(context.Token); + Assert.Null(context.Result); + Mock.Get(inner).Verify(e => e.MessageReceived(context)); + } + + [Theory] + [InlineData("")] + [InlineData("=")] + [InlineData("=%20")] + public async Task MessageReceived_WhiteSpaceToken(String value) { + + // Arrange + + var inner = Mock.Of(); + var events = this.CreateQueryStringJwtBearerEvents(inner); + var context = new DefaultMessageReceivedContext(); + var queryString = $"?{events.QueryStringParameterName}{value}"; + + context.Request.QueryString = new QueryString(queryString); + SetupContext(context, inner); + + // Act + + await events.MessageReceived(context); + + // Assert + + Assert.Null(context.Token); + + Assert.NotNull(context.Result); + Assert.Equal( + $"The '{events.QueryStringParameterName}' query string parameter was " + + "defined, but a value to represent the token was not included.", + context.Result.Failure?.Message + ); + + Assert.Equal((int)HttpStatusCode.Unauthorized, context.Response.StatusCode); + Mock.Get(inner).Verify(e => e.MessageReceived(context)); + } + + [Fact] + public async Task MessageReceived_MoreThanOneToken() { + + // Arrange + + var inner = Mock.Of(); + var events = this.CreateQueryStringJwtBearerEvents(inner); + var context = new DefaultMessageReceivedContext(); + var queryString = $"?{events.QueryStringParameterName}=foo&{events.QueryStringParameterName}=bar"; + + context.Request.QueryString = new QueryString(queryString); + SetupContext(context, inner); + + // Act + + await events.MessageReceived(context); + + // Assert + + Assert.Null(context.Token); + + Assert.NotNull(context.Result?.Failure); + Assert.Equal( + $"Only one '{events.QueryStringParameterName}' query string parameter " + + "can be defined. However, 2 were included in the request.", + context.Result.Failure.Message + ); + + Assert.Equal((int)HttpStatusCode.Unauthorized, context.Response.StatusCode); + Mock.Get(inner).Verify(e => e.MessageReceived(context)); + } + + [Fact] + public async Task MessageReceived_ValidDefaultToken() { + + // Arrange + + var inner = Mock.Of(); + var events = this.CreateQueryStringJwtBearerEvents(inner); + var context = new DefaultMessageReceivedContext(); + + const string token = "foo"; + var queryString = + $"?{QueryStringJwtBearerEventsWrapper.DefaultQueryStringParameterName}={token}"; + + context.Request.QueryString = new QueryString(queryString); + SetupContext(context, inner); + + // Act + + await events.MessageReceived(context); + + // Assert + + Assert.Equal(token, context.Token); + Assert.Null(context.Result); + Mock.Get(inner).Verify(e => e.MessageReceived(context)); + } + + [Theory] + [InlineData("bearer")] + [InlineData("id-token")] + [InlineData("with space")] + public async Task MessageReceived_ValidCustomToken(string customQueryStringParameterName) { + + // Arrange + + var inner = Mock.Of(); + var events = CreateQueryStringJwtBearerEvents(inner, customQueryStringParameterName); + var context = new DefaultMessageReceivedContext(); + + const string token = "foo"; + var queryString = $"?{customQueryStringParameterName}={token}"; + + context.Request.QueryString = new QueryString(queryString); + SetupContext(context, inner); + + // Act + + await events.MessageReceived(context); + + // Assert + + Assert.Equal(token, context.Token); + Assert.Null(context.Result); + Mock.Get(inner).Verify(e => e.MessageReceived(context)); + } + + protected abstract QueryStringJwtBearerEventsWrapper CreateQueryStringJwtBearerEvents( + JwtBearerEvents inner); + + protected abstract QueryStringJwtBearerEventsWrapper CreateQueryStringJwtBearerEvents( + JwtBearerEvents inner, string queryStringParameterName); + + } +} \ No newline at end of file diff --git a/test/Invio.Extensions.Authentication.JwtBearer.Tests/QueryStringJwtBearerEventsWrapperTests.cs b/test/Invio.Extensions.Authentication.JwtBearer.Tests/QueryStringJwtBearerEventsWrapperTests.cs index 3320d51..2c4b18b 100644 --- a/test/Invio.Extensions.Authentication.JwtBearer.Tests/QueryStringJwtBearerEventsWrapperTests.cs +++ b/test/Invio.Extensions.Authentication.JwtBearer.Tests/QueryStringJwtBearerEventsWrapperTests.cs @@ -1,17 +1,12 @@ using System; -using System.Net; -using System.Threading.Tasks; using Invio.Xunit; -using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.JwtBearer; -using Microsoft.AspNetCore.Http; -using Moq; using Xunit; namespace Invio.Extensions.Authentication.JwtBearer { [UnitTest] - public sealed class QueryStringJwtBearerEventsWrapperTests : JwtBearerEventsWrapperBaseTests { + public sealed class QueryStringJwtBearerEventsWrapperTests : QueryStringJwtBearerEventsWrapperBaseTests { [Fact] public void Constructor_DefaultQueryStringParameterName_NullInner() { @@ -88,184 +83,28 @@ public void Constructor_WithCustomQueryStringParameterName_InvalidParameterName( Assert.IsType(exception); Assert.Equal( - $"The 'queryStringParameterName' cannot be null or whitespace." + + "The 'queryStringParameterName' cannot be null or whitespace." + Environment.NewLine + "Parameter name: queryStringParameterName", exception.Message ); } - [Fact] - public async Task MessageReceived_NullContext() { - - // Arrange - - var inner = new JwtBearerEvents(); - var events = this.CreateJwtBearerEvents(inner); - - // Act - - var exception = await Record.ExceptionAsync( - () => events.MessageReceived(null) - ); - - // Assert - - Assert.IsType(exception); - } - - [Theory] - [InlineData(null)] - [InlineData("?")] - [InlineData("?another_token=foo")] - public async Task MessageReceived_NoQueryStringParameterName(String queryString) { - - // Arrange - - var inner = Mock.Of(); - var events = this.CreateJwtBearerEvents(inner); - var context = new DefaultMessageReceivedContext(); - context.Request.QueryString = new QueryString(queryString); - - // Act - - await events.MessageReceived(context); - - // Assert - - Assert.Null(context.Token); - Assert.Null(context.Result); - Mock.Get(inner).Verify(e => e.MessageReceived(context)); - } - - [Theory] - [InlineData("")] - [InlineData("=")] - [InlineData("=%20")] - public async Task MessageReceived_WhiteSpaceToken(String value) { - - // Arrange - - var inner = Mock.Of(); - var events = this.CreateQueryStringJwtBearerEvents(inner); - var context = new DefaultMessageReceivedContext(); - var queryString = $"?{events.QueryStringParameterName}{value}"; - - context.Request.QueryString = new QueryString(queryString); - - // Act - - await events.MessageReceived(context); - - // Assert - - Assert.Null(context.Token); - - Assert.NotNull(context.Result); - Assert.Equal( - $"The '{events.QueryStringParameterName}' query string parameter was " + - $"defined, but a value to represent the token was not included.", - context.Result.Failure?.Message - ); - - Assert.Equal((int)HttpStatusCode.Unauthorized, context.Response.StatusCode); - Mock.Get(inner).Verify(e => e.MessageReceived(context)); - } - - [Fact] - public async Task MessageReceived_MoreThanOneToken() { - - // Arrange - - var inner = Mock.Of(); - var events = this.CreateQueryStringJwtBearerEvents(inner); - var context = new DefaultMessageReceivedContext(); - var queryString = $"?{events.QueryStringParameterName}=foo&{events.QueryStringParameterName}=bar"; - - context.Request.QueryString = new QueryString(queryString); - - // Act - - await events.MessageReceived(context); - - // Assert - - Assert.Null(context.Token); - - Assert.NotNull(context.Result?.Failure); - Assert.Equal( - $"Only one '{events.QueryStringParameterName}' query string parameter " + - $"can be defined. However, 2 were included in the request.", - context.Result.Failure.Message - ); - - Assert.Equal((int)HttpStatusCode.Unauthorized, context.Response.StatusCode); - Mock.Get(inner).Verify(e => e.MessageReceived(context)); - } - - [Fact] - public async Task MessageReceived_ValidDefaultToken() { - - // Arrange - - var inner = Mock.Of(); - var events = this.CreateQueryStringJwtBearerEvents(inner); - var context = new DefaultMessageReceivedContext(); - - const string token = "foo"; - var queryString = - $"?{QueryStringJwtBearerEventsWrapper.DefaultQueryStringParameterName}={token}"; - - context.Request.QueryString = new QueryString(queryString); - - // Act - - await events.MessageReceived(context); - - // Assert - - Assert.Equal(token, context.Token); - Assert.Null(context.Result); - Mock.Get(inner).Verify(e => e.MessageReceived(context)); - } - - [Theory] - [InlineData("bearer")] - [InlineData("id-token")] - [InlineData("with space")] - public async Task MessageReceived_ValidCustomToken(string customQueryStringParameterName) { - - // Arrange - - var inner = Mock.Of(); - var events = new QueryStringJwtBearerEventsWrapper(inner, customQueryStringParameterName); - var context = new DefaultMessageReceivedContext(); - - const string token = "foo"; - var queryString = $"?{customQueryStringParameterName}={token}"; - - context.Request.QueryString = new QueryString(queryString); - - // Act - - await events.MessageReceived(context); - - // Assert - - Assert.Equal(token, context.Token); - Assert.Null(context.Result); - Mock.Get(inner).Verify(e => e.MessageReceived(context)); - } - protected override JwtBearerEvents CreateJwtBearerEvents(JwtBearerEvents inner) { return this.CreateQueryStringJwtBearerEvents(inner); } - private QueryStringJwtBearerEventsWrapper CreateQueryStringJwtBearerEvents( + protected override QueryStringJwtBearerEventsWrapper CreateQueryStringJwtBearerEvents( JwtBearerEvents inner) { return new QueryStringJwtBearerEventsWrapper(inner); } + protected override QueryStringJwtBearerEventsWrapper CreateQueryStringJwtBearerEvents( + JwtBearerEvents inner, string queryStringParameterName) { + + return new QueryStringJwtBearerEventsWrapper(inner, queryStringParameterName); + } + } } From d6a2ee46e8e98723c641b095fed0ce7fbf2c0b26 Mon Sep 17 00:00:00 2001 From: Petr Moskor Date: Thu, 3 Apr 2025 10:15:41 +0200 Subject: [PATCH 2/2] Fixed formatting of JwtBearerEventsWrapperBase class --- .../JwtBearerEventsWrapperBase.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Invio.Extensions.Authentication.JwtBearer/JwtBearerEventsWrapperBase.cs b/src/Invio.Extensions.Authentication.JwtBearer/JwtBearerEventsWrapperBase.cs index 81f80eb..3b08629 100644 --- a/src/Invio.Extensions.Authentication.JwtBearer/JwtBearerEventsWrapperBase.cs +++ b/src/Invio.Extensions.Authentication.JwtBearer/JwtBearerEventsWrapperBase.cs @@ -15,8 +15,7 @@ namespace Invio.Extensions.Authentication.JwtBearer { /// true wrappers to implement their desired functionality without having /// to provide distracting invocations to the wrapped . /// - public abstract class JwtBearerEventsWrapperBase : JwtBearerEvents - { + public abstract class JwtBearerEventsWrapperBase : JwtBearerEvents { private JwtBearerEvents inner; private readonly Type innerType;