Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand All @@ -15,8 +16,8 @@ namespace Invio.Extensions.Authentication.JwtBearer {
/// to provide distracting invocations to the wrapped <see cref="JwtBearerEvents" />.
/// </remarks>
public abstract class JwtBearerEventsWrapperBase : JwtBearerEvents {

private JwtBearerEvents inner { get; }
private JwtBearerEvents inner;
private readonly Type innerType;

/// <summary>
/// Creates a <see cref="JwtBearerEvents" /> implementation which
Expand All @@ -37,36 +38,54 @@ protected JwtBearerEventsWrapperBase(JwtBearerEvents inner) {
this.inner = inner;
}

/// <summary>
/// Creates a <see cref="JwtBearerEvents" /> implementation which
/// does nothing but call the injected wrapped implementation by default.
/// </summary>
/// <param name="innerType">
/// The base service type that will be resolved from the request services
/// and then invoked after the inheriting wrapper performs its side effects.
/// </param>
/// <exception cref="ArgumentNullException">
/// Thrown when <paramref name="innerType" /> is null.
/// </exception>
protected JwtBearerEventsWrapperBase(Type innerType) {
this.innerType = innerType ?? throw new ArgumentNullException(nameof(innerType));
}

/// <summary>
/// Invoked if exceptions are thrown during request processing.
/// The exceptions will be re-thrown after this event unless suppressed.
/// </summary>
public override Task AuthenticationFailed(AuthenticationFailedContext context) {
return inner.AuthenticationFailed(context);
return InnerResolved(context).AuthenticationFailed(context);
}

/// <summary>
/// Invoked when a protocol message is first received.
/// </summary>
public override Task MessageReceived(MessageReceivedContext context) {
return inner.MessageReceived(context);
return InnerResolved(context).MessageReceived(context);
}

/// <summary>
/// Invoked after the security token has passed validation
/// and a ClaimsIdentity has been generated.
/// </summary>
public override Task TokenValidated(TokenValidatedContext context) {
return inner.TokenValidated(context);
return InnerResolved(context).TokenValidated(context);
}

/// <summary>
/// Invoked before a challenge is sent back to the caller.
/// </summary>
public override Task Challenge(JwtBearerChallengeContext context) {
return inner.Challenge(context);
return InnerResolved(context).Challenge(context);
}

private JwtBearerEvents InnerResolved(BaseContext<JwtBearerOptions> context) =>
inner ??
(inner = (JwtBearerEvents)context.HttpContext.RequestServices.GetRequiredService(innerType));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,20 @@ public class QueryStringJwtBearerEventsWrapper : JwtBearerEventsWrapperBase {
public QueryStringJwtBearerEventsWrapper(JwtBearerEvents inner) :
this(inner, DefaultQueryStringParameterName) {}

/// <summary>
/// Wraps an instance of <see cref="JwtBearerEvents" /> with a behavior
/// that checks for a token in the query string with a name of "bearer".
/// </summary>
/// <param name="innerType">
/// A base service type implementation of <see cref="JwtBearerEvents" />
/// that will gain this additional query string inspection behavior.
/// </param>
/// <exception cref="ArgumentNullException">
/// Thrown when <paramref name="innerType" /> is null.
/// </exception>
public QueryStringJwtBearerEventsWrapper(Type innerType) :
this(innerType, DefaultQueryStringParameterName) {}

/// <summary>
/// Wraps an instance of <see cref="JwtBearerEvents" /> with a behavior
/// checks for a token in the query string with a name specified in the
Expand Down Expand Up @@ -94,6 +108,42 @@ public QueryStringJwtBearerEventsWrapper(JwtBearerEvents inner, string queryStri
this.QueryStringParameterName = queryStringParameterName;
}

/// <summary>
/// Wraps an instance of <see cref="JwtBearerEvents" /> with a behavior
/// checks for a token in the query string with a name specified in the
/// <paramref name="queryStringParameterName" /> parameter.
/// </summary>
/// <param name="innerType">
/// A base service type implementation of <see cref="JwtBearerEvents" />
/// that will gain this additional query string inspection behavior.
/// </param>
/// <param name="queryStringParameterName">
/// The name of the query string parameter that will be sought from requests
/// in order to extract a token.
/// </param>
/// <exception cref="ArgumentNullException">
/// Thrown when <paramref name="innerType" /> or
/// <paramref name="queryStringParameterName" /> is null.
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown when <paramref name="queryStringParameterName" /> is an invalid name
/// for a query string parameter.
/// </exception>
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;
}

/// <summary>
/// Checks the web request for the <see cref="QueryStringParameterName" /> in
/// the request's query string. If it is found, it fetches the token
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public async Task AuthenticationFailed_Wraps() {
var inner = Mock.Of<JwtBearerEvents>();
var wrapper = this.CreateJwtBearerEvents(inner);
var context = new DefaultAuthenticationFailedContext();
SetupContext(context, inner);

// Act

Expand All @@ -35,6 +36,7 @@ public async Task MessageReceived_Wraps() {
var inner = Mock.Of<JwtBearerEvents>();
var wrapper = this.CreateJwtBearerEvents(inner);
var context = new DefaultMessageReceivedContext();
SetupContext(context, inner);

// Act

Expand All @@ -53,6 +55,7 @@ public async Task TokenValidated() {
var inner = Mock.Of<JwtBearerEvents>();
var wrapper = this.CreateJwtBearerEvents(inner);
var context = new DefaultTokenValidatedContext();
SetupContext(context, inner);

// Act

Expand All @@ -71,6 +74,7 @@ public async Task Challenge_Wraps() {
var inner = Mock.Of<JwtBearerEvents>();
var wrapper = this.CreateJwtBearerEvents(inner);
var context = new DefaultJwtBearerChallengeContext();
SetupContext(context, inner);

// Act

Expand All @@ -81,6 +85,9 @@ public async Task Challenge_Wraps() {
Mock.Get(inner).Verify(events => events.Challenge(context));
}

protected virtual void SetupContext(BaseContext<JwtBearerOptions> context,
JwtBearerEvents inner) {}

protected abstract JwtBearerEvents CreateJwtBearerEvents(JwtBearerEvents inner);

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<QueryStringJwtBearerEventsWrapper>(options.Events);

Assert.Equal(
QueryStringJwtBearerEventsWrapper.DefaultQueryStringParameterName,
events.QueryStringParameterName
);

Assert.Null(options.EventsType);
}

[Fact]
public static void AddQueryStringAuthentication_CustomQueryStringParameter_NullOptions() {

Expand Down Expand Up @@ -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<QueryStringJwtBearerEventsWrapper>(options.Events);
Assert.Equal(queryStringParameterName, events.QueryStringParameterName);
Assert.Null(options.EventsType);
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ public static IEnumerable<object[]> 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 });
Expand All @@ -77,6 +78,7 @@ public void PostConfigure_AppliesEventsWrapper(

var typed = Assert.IsType<QueryStringJwtBearerEventsWrapper>(bearerOptions.Events);
Assert.Equal(options.QueryStringParameterName, typed.QueryStringParameterName);
Assert.Null(bearerOptions.EventsType);
}

private IPostConfigureOptions<JwtBearerOptions> CreatePostConfiguration(
Expand Down
Loading