diff --git a/samples/JwtBearerSample/Startup.cs b/samples/JwtBearerSample/Startup.cs index 02611de8c4..ac599bc57f 100644 --- a/samples/JwtBearerSample/Startup.cs +++ b/samples/JwtBearerSample/Startup.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; @@ -15,6 +16,8 @@ namespace JwtBearerSample { public Startup(IHostingEnvironment env) { + Environment = env; + var builder = new ConfigurationBuilder() .SetBasePath(env.ContentRootPath); @@ -30,6 +33,8 @@ namespace JwtBearerSample public IConfiguration Configuration { get; set; } + public IHostingEnvironment Environment { get; set; } + // Shared between users in memory public IList Todos { get; } = new List(); @@ -68,7 +73,23 @@ namespace JwtBearerSample { // You also need to update /wwwroot/app/scripts/app.js Authority = Configuration["jwt:authority"], - Audience = Configuration["jwt:audience"] + Audience = Configuration["jwt:audience"], + Events = new JwtBearerEvents() + { + OnAuthenticationFailed = c => + { + c.HandleResponse(); + + c.Response.StatusCode = 500; + c.Response.ContentType = "text/plain"; + if (Environment.IsDevelopment()) + { + // Debug only, in production do not share exceptions with the remote host. + return c.Response.WriteAsync(c.Exception.ToString()); + } + return c.Response.WriteAsync("An error occurred processing your authentication."); + } + } }); // [Authorize] would usually handle this diff --git a/samples/OpenIdConnectSample/Startup.cs b/samples/OpenIdConnectSample/Startup.cs index 37b753102b..90ed3db25d 100644 --- a/samples/OpenIdConnectSample/Startup.cs +++ b/samples/OpenIdConnectSample/Startup.cs @@ -20,6 +20,8 @@ namespace OpenIdConnectSample { public Startup(IHostingEnvironment env) { + Environment = env; + var builder = new ConfigurationBuilder() .SetBasePath(env.ContentRootPath); @@ -35,6 +37,8 @@ namespace OpenIdConnectSample public IConfiguration Configuration { get; set; } + public IHostingEnvironment Environment { get; set; } + public void ConfigureServices(IServiceCollection services) { services.AddAuthentication(sharedOptions => @@ -75,8 +79,24 @@ namespace OpenIdConnectSample ClientId = Configuration["oidc:clientid"], ClientSecret = Configuration["oidc:clientsecret"], // for code flow Authority = Configuration["oidc:authority"], - ResponseType = OpenIdConnectResponseType.Code, - GetClaimsFromUserInfoEndpoint = true + ResponseType = OpenIdConnectResponseType.CodeIdToken, + GetClaimsFromUserInfoEndpoint = true, + Events = new OpenIdConnectEvents() + { + OnAuthenticationFailed = c => + { + c.HandleResponse(); + + c.Response.StatusCode = 500; + c.Response.ContentType = "text/plain"; + if (Environment.IsDevelopment()) + { + // Debug only, in production do not share exceptions with the remote host. + return c.Response.WriteAsync(c.Exception.ToString()); + } + return c.Response.WriteAsync("An error occurred processing your authentication."); + } + } }); app.Run(async context => diff --git a/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/OpenIdConnectHandler.cs b/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/OpenIdConnectHandler.cs index 1e5eda4707..9cc4fc2cd4 100644 --- a/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/OpenIdConnectHandler.cs +++ b/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/OpenIdConnectHandler.cs @@ -578,7 +578,7 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect tokenEndpointResponse = await RedeemAuthorizationCodeAsync(tokenEndpointRequest); } - var tokenResponseReceivedContext = await RunTokenResponseReceivedEventAsync(authorizationResponse, tokenEndpointResponse, properties); + var tokenResponseReceivedContext = await RunTokenResponseReceivedEventAsync(authorizationResponse, tokenEndpointResponse, properties, ticket); if (tokenResponseReceivedContext.CheckEventResult(out result)) { return result; @@ -1038,13 +1038,15 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect private async Task RunTokenResponseReceivedEventAsync( OpenIdConnectMessage message, OpenIdConnectMessage tokenEndpointResponse, - AuthenticationProperties properties) + AuthenticationProperties properties, + AuthenticationTicket ticket) { Logger.TokenResponseReceived(); var eventContext = new TokenResponseReceivedContext(Context, Options, properties) { ProtocolMessage = message, - TokenEndpointResponse = tokenEndpointResponse + TokenEndpointResponse = tokenEndpointResponse, + Ticket = ticket }; await Options.Events.TokenResponseReceived(eventContext); diff --git a/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/OpenIdConnectOptions.cs b/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/OpenIdConnectOptions.cs index 12bc1c03d8..f0b26f75b2 100644 --- a/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/OpenIdConnectOptions.cs +++ b/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/OpenIdConnectOptions.cs @@ -86,6 +86,7 @@ namespace Microsoft.AspNetCore.Builder /// /// Boolean to set whether the middleware should go to user info endpoint to retrieve additional claims or not after creating an identity from id_token received from token endpoint. + /// The default is 'false'. /// public bool GetClaimsFromUserInfoEndpoint { get; set; } diff --git a/src/Microsoft.AspNetCore.Authentication/AuthenticateResult.cs b/src/Microsoft.AspNetCore.Authentication/AuthenticateResult.cs index fb97931cbb..28f116d3d8 100644 --- a/src/Microsoft.AspNetCore.Authentication/AuthenticateResult.cs +++ b/src/Microsoft.AspNetCore.Authentication/AuthenticateResult.cs @@ -33,6 +33,12 @@ namespace Microsoft.AspNetCore.Authentication /// public Exception Failure { get; private set; } + /// + /// Indicates that stage of authentication was directly handled by user intervention and no + /// further processing should be attempted. + /// + public bool Handled { get; private set; } + /// /// Indicates that this stage of authentication was skipped by user intervention. /// @@ -47,6 +53,11 @@ namespace Microsoft.AspNetCore.Authentication return new AuthenticateResult() { Ticket = ticket }; } + public static AuthenticateResult Handle() + { + return new AuthenticateResult() { Handled = true }; + } + public static AuthenticateResult Skip() { return new AuthenticateResult() { Skipped = true }; diff --git a/src/Microsoft.AspNetCore.Authentication/AuthenticationHandler.cs b/src/Microsoft.AspNetCore.Authentication/AuthenticationHandler.cs index 5e9a7c2381..ae32424ebd 100644 --- a/src/Microsoft.AspNetCore.Authentication/AuthenticationHandler.cs +++ b/src/Microsoft.AspNetCore.Authentication/AuthenticationHandler.cs @@ -58,6 +58,8 @@ namespace Microsoft.AspNetCore.Authentication protected TOptions Options { get; private set; } + protected AuthenticateResult InitializeResult { get; private set; } + /// /// Initialize is called once per request to contextualize this instance with appropriate state. /// @@ -101,12 +103,18 @@ namespace Microsoft.AspNetCore.Authentication if (ShouldHandleScheme(AuthenticationManager.AutomaticScheme, Options.AutomaticAuthenticate)) { - var result = await HandleAuthenticateOnceAsync(); - if (result?.Failure != null) + InitializeResult = await HandleAuthenticateOnceAsync(); + if (InitializeResult?.Skipped == true || InitializeResult?.Handled == true) { - Logger.AuthenticationSchemeNotAuthenticatedWithFailure(Options.AuthenticationScheme, result.Failure.Message); + return; } - var ticket = result?.Ticket; + + if (InitializeResult?.Failure != null) + { + Logger.AuthenticationSchemeNotAuthenticatedWithFailure(Options.AuthenticationScheme, InitializeResult.Failure.Message); + } + + var ticket = InitializeResult?.Ticket; if (ticket?.Principal != null) { Context.User = SecurityHelper.MergeUserPrincipal(Context.User, ticket.Principal); @@ -179,6 +187,10 @@ namespace Microsoft.AspNetCore.Authentication /// pipeline. public virtual Task HandleRequestAsync() { + if (InitializeResult?.Handled == true) + { + return Task.FromResult(true); + } return Task.FromResult(false); } @@ -250,7 +262,7 @@ namespace Microsoft.AspNetCore.Authentication /// /// Used to ensure HandleAuthenticateAsync is only invoked once safely. The subsequent /// calls will return the same authentication result. Any exceptions will be converted - /// into a failed authenticatoin result containing the exception. + /// into a failed authentication result containing the exception. /// protected async Task HandleAuthenticateOnceSafeAsync() { diff --git a/src/Microsoft.AspNetCore.Authentication/Events/BaseControlContext.cs b/src/Microsoft.AspNetCore.Authentication/Events/BaseControlContext.cs index 24f3ba8e53..4039a05609 100644 --- a/src/Microsoft.AspNetCore.Authentication/Events/BaseControlContext.cs +++ b/src/Microsoft.AspNetCore.Authentication/Events/BaseControlContext.cs @@ -51,7 +51,14 @@ namespace Microsoft.AspNetCore.Authentication { if (HandledResponse) { - result = AuthenticateResult.Success(Ticket); + if (Ticket == null) + { + result = AuthenticateResult.Handle(); + } + else + { + result = AuthenticateResult.Success(Ticket); + } return true; } else if (Skipped) diff --git a/src/Microsoft.AspNetCore.Authentication/RemoteAuthenticationHandler.cs b/src/Microsoft.AspNetCore.Authentication/RemoteAuthenticationHandler.cs index 862193db15..1e41fb0b50 100644 --- a/src/Microsoft.AspNetCore.Authentication/RemoteAuthenticationHandler.cs +++ b/src/Microsoft.AspNetCore.Authentication/RemoteAuthenticationHandler.cs @@ -43,6 +43,10 @@ namespace Microsoft.AspNetCore.Authentication { exception = new InvalidOperationException("Invalid return state, unable to redirect."); } + else if (authResult.Handled) + { + return true; + } else if (authResult.Skipped) { return false; diff --git a/test/Microsoft.AspNetCore.Authentication.Test/JwtBearer/JwtBearerMiddlewareTests.cs b/test/Microsoft.AspNetCore.Authentication.Test/JwtBearer/JwtBearerMiddlewareTests.cs index fea01b36fe..c0d2ddba5b 100644 --- a/test/Microsoft.AspNetCore.Authentication.Test/JwtBearer/JwtBearerMiddlewareTests.cs +++ b/test/Microsoft.AspNetCore.Authentication.Test/JwtBearer/JwtBearerMiddlewareTests.cs @@ -429,6 +429,39 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer Assert.Equal(string.Empty, response.ResponseText); } + [Fact] + public async Task EventOnMessageReceivedHandled_NoMoreEventsExecuted() + { + var server = CreateServer(new JwtBearerOptions + { + Events = new JwtBearerEvents() + { + OnMessageReceived = context => + { + context.HandleResponse(); + context.Response.StatusCode = StatusCodes.Status202Accepted; + return Task.FromResult(0); + }, + OnTokenValidated = context => + { + throw new NotImplementedException(); + }, + OnAuthenticationFailed = context => + { + throw new NotImplementedException(context.Exception.ToString()); + }, + OnChallenge = context => + { + throw new NotImplementedException(); + }, + } + }); + + var response = await SendAsync(server, "http://example.com/checkforerrors", "Bearer Token"); + Assert.Equal(HttpStatusCode.Accepted, response.Response.StatusCode); + Assert.Equal(string.Empty, response.ResponseText); + } + [Fact] public async Task EventOnTokenValidatedSkipped_NoMoreEventsExecuted() { @@ -460,6 +493,38 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer Assert.Equal(string.Empty, response.ResponseText); } + [Fact] + public async Task EventOnTokenValidatedHandled_NoMoreEventsExecuted() + { + var options = new JwtBearerOptions + { + Events = new JwtBearerEvents() + { + OnTokenValidated = context => + { + context.HandleResponse(); + context.Response.StatusCode = StatusCodes.Status202Accepted; + return Task.FromResult(0); + }, + OnAuthenticationFailed = context => + { + throw new NotImplementedException(context.Exception.ToString()); + }, + OnChallenge = context => + { + throw new NotImplementedException(); + }, + } + }; + options.SecurityTokenValidators.Clear(); + options.SecurityTokenValidators.Add(new BlobTokenValidator("JWT")); + var server = CreateServer(options); + + var response = await SendAsync(server, "http://example.com/checkforerrors", "Bearer Token"); + Assert.Equal(HttpStatusCode.Accepted, response.Response.StatusCode); + Assert.Equal(string.Empty, response.ResponseText); + } + [Fact] public async Task EventOnAuthenticationFailedSkipped_NoMoreEventsExecuted() { @@ -491,6 +556,38 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer Assert.Equal(string.Empty, response.ResponseText); } + [Fact] + public async Task EventOnAuthenticationFailedHandled_NoMoreEventsExecuted() + { + var options = new JwtBearerOptions + { + Events = new JwtBearerEvents() + { + OnTokenValidated = context => + { + throw new Exception("Test Exception"); + }, + OnAuthenticationFailed = context => + { + context.HandleResponse(); + context.Response.StatusCode = StatusCodes.Status202Accepted; + return Task.FromResult(0); + }, + OnChallenge = context => + { + throw new NotImplementedException(); + }, + } + }; + options.SecurityTokenValidators.Clear(); + options.SecurityTokenValidators.Add(new BlobTokenValidator("JWT")); + var server = CreateServer(options); + + var response = await SendAsync(server, "http://example.com/checkforerrors", "Bearer Token"); + Assert.Equal(HttpStatusCode.Accepted, response.Response.StatusCode); + Assert.Equal(string.Empty, response.ResponseText); + } + [Fact] public async Task EventOnChallengeSkipped_ResponseNotModified() { @@ -512,6 +609,28 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer Assert.Equal(string.Empty, response.ResponseText); } + [Fact] + public async Task EventOnChallengeHandled_ResponseNotModified() + { + var server = CreateServer(new JwtBearerOptions + { + Events = new JwtBearerEvents() + { + OnChallenge = context => + { + context.HandleResponse(); + context.Response.StatusCode = StatusCodes.Status202Accepted; + return Task.FromResult(0); + }, + } + }); + + var response = await SendAsync(server, "http://example.com/unauthorized", "Bearer Token"); + Assert.Equal(HttpStatusCode.Accepted, response.Response.StatusCode); + Assert.Empty(response.Response.Headers.WwwAuthenticate); + Assert.Equal(string.Empty, response.ResponseText); + } + class InvalidTokenValidator : ISecurityTokenValidator { public InvalidTokenValidator() diff --git a/test/Microsoft.AspNetCore.Authentication.Test/OpenIdConnect/OpenIdConnectEventTests.cs b/test/Microsoft.AspNetCore.Authentication.Test/OpenIdConnect/OpenIdConnectEventTests.cs new file mode 100644 index 0000000000..a212af649d --- /dev/null +++ b/test/Microsoft.AspNetCore.Authentication.Test/OpenIdConnect/OpenIdConnectEventTests.cs @@ -0,0 +1,1534 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Security.Claims; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Authentication.OpenIdConnect; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Authentication; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.IdentityModel.Protocols.OpenIdConnect; +using Microsoft.IdentityModel.Tokens; +using Xunit; + +namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect +{ + public class OpenIdConnectEventTests + { + private readonly Func MessageNotImpl = context => { throw new NotImplementedException("Message"); }; + private readonly Func TokenNotImpl = context => { throw new NotImplementedException("Token"); }; + private readonly Func CodeNotImpl = context => { throw new NotImplementedException("Code"); }; + private readonly Func TokenResponseNotImpl = context => { throw new NotImplementedException("TokenResponse"); }; + private readonly Func UserNotImpl = context => { throw new NotImplementedException("User"); }; + private readonly Func FailedNotImpl = context => { throw new NotImplementedException("Failed", context.Exception); }; + private readonly Func TicketNotImpl = context => { throw new NotImplementedException("Ticket"); }; + private readonly Func FailureNotImpl = context => { throw new NotImplementedException("Failure", context.Failure); }; + private readonly Func RedirectNotImpl = context => { throw new NotImplementedException("Redirect"); }; + private readonly Func RemoteSignOutNotImpl = context => { throw new NotImplementedException("Remote"); }; + private readonly RequestDelegate AppNotImpl = context => { throw new NotImplementedException("App"); }; + + [Fact] + public async Task OnMessageReceived_Skipped_NoMoreEventsRun() + { + var messageReceived = false; + var server = CreateServer(new OpenIdConnectEvents() + { + OnMessageReceived = context => + { + messageReceived = true; + context.SkipToNextMiddleware(); + return Task.FromResult(0); + }, + OnTokenValidated = TokenNotImpl, + OnAuthorizationCodeReceived = CodeNotImpl, + OnTokenResponseReceived = TokenResponseNotImpl, + OnUserInformationReceived = UserNotImpl, + OnAuthenticationFailed = FailedNotImpl, + OnTicketReceived = TicketNotImpl, + OnRemoteFailure = FailureNotImpl, + + OnRedirectToIdentityProvider = RedirectNotImpl, + OnRedirectToIdentityProviderForSignOut = RedirectNotImpl, + OnRemoteSignOut = RemoteSignOutNotImpl, + }, + context => + { + return context.Response.WriteAsync(context.Request.Path); + }); + + var response = await PostAsync(server, "signin-oidc", ""); + + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("/signin-oidc", await response.Content.ReadAsStringAsync()); + Assert.True(messageReceived); + } + + [Fact] + public async Task OnMessageReceived_Handled_NoMoreEventsRun() + { + var messageReceived = false; + var server = CreateServer(new OpenIdConnectEvents() + { + OnMessageReceived = context => + { + messageReceived = true; + context.HandleResponse(); + context.Response.StatusCode = StatusCodes.Status202Accepted; + return Task.FromResult(0); + }, + OnTokenValidated = TokenNotImpl, + OnAuthorizationCodeReceived = CodeNotImpl, + OnTokenResponseReceived = TokenResponseNotImpl, + OnUserInformationReceived = UserNotImpl, + OnAuthenticationFailed = FailedNotImpl, + OnRemoteFailure = FailureNotImpl, + OnTicketReceived = TicketNotImpl, + + OnRedirectToIdentityProvider = RedirectNotImpl, + OnRedirectToIdentityProviderForSignOut = RedirectNotImpl, + OnRemoteSignOut = RemoteSignOutNotImpl, + }, + AppNotImpl); + + var response = await PostAsync(server, "signin-oidc", ""); + + Assert.Equal(HttpStatusCode.Accepted, response.StatusCode); + Assert.Equal("", await response.Content.ReadAsStringAsync()); + Assert.True(messageReceived); + } + + [Fact] + public async Task OnTokenValidated_Skipped_NoMoreEventsRun() + { + var messageReceived = false; + var tokenValidated = false; + var server = CreateServer(new OpenIdConnectEvents() + { + OnMessageReceived = context => + { + messageReceived = true; + return Task.FromResult(0); + }, + OnTokenValidated = context => + { + tokenValidated = true; + context.SkipToNextMiddleware(); + return Task.FromResult(0); + }, + OnAuthorizationCodeReceived = CodeNotImpl, + OnTokenResponseReceived = TokenResponseNotImpl, + OnUserInformationReceived = UserNotImpl, + OnAuthenticationFailed = FailedNotImpl, + OnRemoteFailure = FailureNotImpl, + OnTicketReceived = TicketNotImpl, + + OnRedirectToIdentityProvider = RedirectNotImpl, + OnRedirectToIdentityProviderForSignOut = RedirectNotImpl, + OnRemoteSignOut = RemoteSignOutNotImpl, + }, + context => + { + return context.Response.WriteAsync(context.Request.Path); + }); + + var response = await PostAsync(server, "signin-oidc", "id_token=my_id_token&state=protected_state"); + + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("/signin-oidc", await response.Content.ReadAsStringAsync()); + Assert.True(messageReceived); + Assert.True(tokenValidated); + } + + [Fact] + public async Task OnTokenValidated_HandledWithoutTicket_NoMoreEventsRun() + { + var messageReceived = false; + var tokenValidated = false; + var server = CreateServer(new OpenIdConnectEvents() + { + OnMessageReceived = context => + { + messageReceived = true; + return Task.FromResult(0); + }, + OnTokenValidated = context => + { + tokenValidated = true; + context.HandleResponse(); + context.Ticket = null; + context.Response.StatusCode = StatusCodes.Status202Accepted; + return Task.FromResult(0); + }, + OnAuthorizationCodeReceived = CodeNotImpl, + OnTokenResponseReceived = TokenResponseNotImpl, + OnUserInformationReceived = UserNotImpl, + OnAuthenticationFailed = FailedNotImpl, + OnRemoteFailure = FailureNotImpl, + OnTicketReceived = TicketNotImpl, + + OnRedirectToIdentityProvider = RedirectNotImpl, + OnRedirectToIdentityProviderForSignOut = RedirectNotImpl, + OnRemoteSignOut = RemoteSignOutNotImpl, + }, + AppNotImpl); + + var response = await PostAsync(server, "signin-oidc", "id_token=my_id_token&state=protected_state"); + + Assert.Equal(HttpStatusCode.Accepted, response.StatusCode); + Assert.Equal("", await response.Content.ReadAsStringAsync()); + Assert.True(messageReceived); + Assert.True(tokenValidated); + } + + // TODO: Do any other events depend on the presence of the ticket? It's strange we have to double handle this event. + [Fact] + public async Task OnTokenValidated_HandledWithTicket_SkipToTicketReceived() + { + var messageReceived = false; + var tokenValidated = false; + var ticketReceived = false; + var server = CreateServer(new OpenIdConnectEvents() + { + OnMessageReceived = context => + { + messageReceived = true; + return Task.FromResult(0); + }, + OnTokenValidated = context => + { + tokenValidated = true; + context.HandleResponse(); + // context.Ticket = null; + return Task.FromResult(0); + }, + OnAuthorizationCodeReceived = CodeNotImpl, + OnTokenResponseReceived = TokenResponseNotImpl, + OnUserInformationReceived = UserNotImpl, + OnAuthenticationFailed = FailedNotImpl, + OnRemoteFailure = FailureNotImpl, + OnTicketReceived = context => + { + ticketReceived = true; + context.HandleResponse(); + context.Response.StatusCode = StatusCodes.Status202Accepted; + return Task.FromResult(0); + }, + + OnRedirectToIdentityProvider = RedirectNotImpl, + OnRedirectToIdentityProviderForSignOut = RedirectNotImpl, + OnRemoteSignOut = RemoteSignOutNotImpl, + }, + AppNotImpl); + + var response = await PostAsync(server, "signin-oidc", "id_token=my_id_token&state=protected_state"); + + Assert.Equal(HttpStatusCode.Accepted, response.StatusCode); + Assert.Equal("", await response.Content.ReadAsStringAsync()); + Assert.True(messageReceived); + Assert.True(tokenValidated); + Assert.True(ticketReceived); + } + + [Fact] + public async Task OnAuthorizationCodeReceived_Skipped_NoMoreEventsRun() + { + var messageReceived = false; + var tokenValidated = false; + var codeReceived = false; + var server = CreateServer(new OpenIdConnectEvents() + { + OnMessageReceived = context => + { + messageReceived = true; + return Task.FromResult(0); + }, + OnTokenValidated = context => + { + tokenValidated = true; + return Task.FromResult(0); + }, + OnAuthorizationCodeReceived = context => + { + codeReceived = true; + context.SkipToNextMiddleware(); + return Task.FromResult(0); + }, + OnTokenResponseReceived = TokenResponseNotImpl, + OnUserInformationReceived = UserNotImpl, + OnAuthenticationFailed = FailedNotImpl, + OnRemoteFailure = FailureNotImpl, + OnTicketReceived = TicketNotImpl, + + OnRedirectToIdentityProvider = RedirectNotImpl, + OnRedirectToIdentityProviderForSignOut = RedirectNotImpl, + OnRemoteSignOut = RemoteSignOutNotImpl, + }, + context => + { + return context.Response.WriteAsync(context.Request.Path); + }); + + var response = await PostAsync(server, "signin-oidc", "id_token=my_id_token&state=protected_state&code=my_code"); + + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("/signin-oidc", await response.Content.ReadAsStringAsync()); + Assert.True(messageReceived); + Assert.True(tokenValidated); + Assert.True(codeReceived); + } + + [Fact] + public async Task OnAuthorizationCodeReceived_HandledWithoutTicket_NoMoreEventsRun() + { + var messageReceived = false; + var tokenValidated = false; + var codeReceived = false; + var server = CreateServer(new OpenIdConnectEvents() + { + OnMessageReceived = context => + { + messageReceived = true; + return Task.FromResult(0); + }, + OnTokenValidated = context => + { + tokenValidated = true; + return Task.FromResult(0); + }, + OnAuthorizationCodeReceived = context => + { + codeReceived = true; + context.HandleResponse(); + context.Ticket = null; + context.Response.StatusCode = StatusCodes.Status202Accepted; + return Task.FromResult(0); + }, + OnTokenResponseReceived = TokenResponseNotImpl, + OnUserInformationReceived = UserNotImpl, + OnAuthenticationFailed = FailedNotImpl, + OnRemoteFailure = FailureNotImpl, + OnTicketReceived = TicketNotImpl, + + OnRedirectToIdentityProvider = RedirectNotImpl, + OnRedirectToIdentityProviderForSignOut = RedirectNotImpl, + OnRemoteSignOut = RemoteSignOutNotImpl, + }, + AppNotImpl); + + var response = await PostAsync(server, "signin-oidc", "id_token=my_id_token&state=protected_state&code=my_code"); + + Assert.Equal(HttpStatusCode.Accepted, response.StatusCode); + Assert.Equal("", await response.Content.ReadAsStringAsync()); + Assert.True(messageReceived); + Assert.True(tokenValidated); + Assert.True(codeReceived); + } + + [Fact] + public async Task OnAuthorizationCodeReceived_HandledWithTicket_SkipToTicketReceived() + { + var messageReceived = false; + var tokenValidated = false; + var codeReceived = false; + var ticketReceived = false; + var server = CreateServer(new OpenIdConnectEvents() + { + OnMessageReceived = context => + { + messageReceived = true; + return Task.FromResult(0); + }, + OnTokenValidated = context => + { + tokenValidated = true; + return Task.FromResult(0); + }, + OnAuthorizationCodeReceived = context => + { + codeReceived = true; + context.HandleResponse(); + // context.Ticket = null; + return Task.FromResult(0); + }, + OnTokenResponseReceived = TokenResponseNotImpl, + OnUserInformationReceived = UserNotImpl, + OnAuthenticationFailed = FailedNotImpl, + OnRemoteFailure = FailureNotImpl, + OnTicketReceived = context => + { + ticketReceived = true; + context.HandleResponse(); + context.Response.StatusCode = StatusCodes.Status202Accepted; + return Task.FromResult(0); + }, + + OnRedirectToIdentityProvider = RedirectNotImpl, + OnRedirectToIdentityProviderForSignOut = RedirectNotImpl, + OnRemoteSignOut = RemoteSignOutNotImpl, + }, + AppNotImpl); + + var response = await PostAsync(server, "signin-oidc", "id_token=my_id_token&state=protected_state&code=my_code"); + + Assert.Equal(HttpStatusCode.Accepted, response.StatusCode); + Assert.Equal("", await response.Content.ReadAsStringAsync()); + Assert.True(messageReceived); + Assert.True(tokenValidated); + Assert.True(codeReceived); + Assert.True(ticketReceived); + } + + [Fact] + public async Task OnTokenResponseReceived_Skipped_NoMoreEventsRun() + { + var messageReceived = false; + var tokenValidated = false; + var codeReceived = false; + var tokenResponseReceived = false; + var server = CreateServer(new OpenIdConnectEvents() + { + OnMessageReceived = context => + { + messageReceived = true; + return Task.FromResult(0); + }, + OnTokenValidated = context => + { + tokenValidated = true; + return Task.FromResult(0); + }, + OnAuthorizationCodeReceived = context => + { + codeReceived = true; + return Task.FromResult(0); + }, + OnTokenResponseReceived = context => + { + tokenResponseReceived = true; + context.SkipToNextMiddleware(); + return Task.FromResult(0); + }, + OnUserInformationReceived = UserNotImpl, + OnAuthenticationFailed = FailedNotImpl, + OnRemoteFailure = FailureNotImpl, + OnTicketReceived = TicketNotImpl, + + OnRedirectToIdentityProvider = RedirectNotImpl, + OnRedirectToIdentityProviderForSignOut = RedirectNotImpl, + OnRemoteSignOut = RemoteSignOutNotImpl, + }, + context => + { + return context.Response.WriteAsync(context.Request.Path); + }); + + var response = await PostAsync(server, "signin-oidc", "id_token=my_id_token&state=protected_state&code=my_code"); + + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("/signin-oidc", await response.Content.ReadAsStringAsync()); + Assert.True(messageReceived); + Assert.True(tokenValidated); + Assert.True(codeReceived); + Assert.True(tokenResponseReceived); + } + + [Fact] + public async Task OnTokenResponseReceived_HandledWithoutTicket_NoMoreEventsRun() + { + var messageReceived = false; + var tokenValidated = false; + var codeReceived = false; + var tokenResponseReceived = false; + var server = CreateServer(new OpenIdConnectEvents() + { + OnMessageReceived = context => + { + messageReceived = true; + return Task.FromResult(0); + }, + OnTokenValidated = context => + { + tokenValidated = true; + return Task.FromResult(0); + }, + OnAuthorizationCodeReceived = context => + { + codeReceived = true; + return Task.FromResult(0); + }, + OnTokenResponseReceived = context => + { + tokenResponseReceived = true; + context.Ticket = null; + context.HandleResponse(); + context.Response.StatusCode = StatusCodes.Status202Accepted; + return Task.FromResult(0); + }, + OnUserInformationReceived = UserNotImpl, + OnAuthenticationFailed = FailedNotImpl, + OnRemoteFailure = FailureNotImpl, + OnTicketReceived = TicketNotImpl, + + OnRedirectToIdentityProvider = RedirectNotImpl, + OnRedirectToIdentityProviderForSignOut = RedirectNotImpl, + OnRemoteSignOut = RemoteSignOutNotImpl, + }, + AppNotImpl); + + var response = await PostAsync(server, "signin-oidc", "id_token=my_id_token&state=protected_state&code=my_code"); + + Assert.Equal(HttpStatusCode.Accepted, response.StatusCode); + Assert.Equal("", await response.Content.ReadAsStringAsync()); + Assert.True(messageReceived); + Assert.True(tokenValidated); + Assert.True(codeReceived); + Assert.True(tokenResponseReceived); + } + + [Fact] + public async Task OnTokenResponseReceived_HandledWithTicket_SkipToTicketReceived() + { + var messageReceived = false; + var tokenValidated = false; + var codeReceived = false; + var ticketReceived = false; + var tokenResponseReceived = false; + var server = CreateServer(new OpenIdConnectEvents() + { + OnMessageReceived = context => + { + messageReceived = true; + return Task.FromResult(0); + }, + OnTokenValidated = context => + { + tokenValidated = true; + return Task.FromResult(0); + }, + OnAuthorizationCodeReceived = context => + { + codeReceived = true; + return Task.FromResult(0); + }, + OnTokenResponseReceived = context => + { + tokenResponseReceived = true; + // context.Ticket = null; + context.HandleResponse(); + return Task.FromResult(0); + }, + OnUserInformationReceived = UserNotImpl, + OnAuthenticationFailed = FailedNotImpl, + OnRemoteFailure = FailureNotImpl, + OnTicketReceived = context => + { + ticketReceived = true; + context.HandleResponse(); + context.Response.StatusCode = StatusCodes.Status202Accepted; + return Task.FromResult(0); + }, + + OnRedirectToIdentityProvider = RedirectNotImpl, + OnRedirectToIdentityProviderForSignOut = RedirectNotImpl, + OnRemoteSignOut = RemoteSignOutNotImpl, + }, + AppNotImpl); + + var response = await PostAsync(server, "signin-oidc", "id_token=my_id_token&state=protected_state&code=my_code"); + + Assert.Equal(HttpStatusCode.Accepted, response.StatusCode); + Assert.Equal("", await response.Content.ReadAsStringAsync()); + Assert.True(messageReceived); + Assert.True(tokenValidated); + Assert.True(codeReceived); + Assert.True(tokenResponseReceived); + Assert.True(ticketReceived); + } + + [Fact] + public async Task OnTokenValidatedBackchannel_Skipped_NoMoreEventsRun() + { + var messageReceived = false; + var codeReceived = false; + var tokenResponseReceived = false; + var tokenValidated = false; + var server = CreateServer(new OpenIdConnectEvents() + { + OnMessageReceived = context => + { + messageReceived = true; + return Task.FromResult(0); + }, + OnAuthorizationCodeReceived = context => + { + codeReceived = true; + return Task.FromResult(0); + }, + OnTokenResponseReceived = context => + { + tokenResponseReceived = true; + return Task.FromResult(0); + }, + OnTokenValidated = context => + { + tokenValidated = true; + context.SkipToNextMiddleware(); + return Task.FromResult(0); + }, + OnUserInformationReceived = UserNotImpl, + OnAuthenticationFailed = FailedNotImpl, + OnRemoteFailure = FailureNotImpl, + OnTicketReceived = TicketNotImpl, + + OnRedirectToIdentityProvider = RedirectNotImpl, + OnRedirectToIdentityProviderForSignOut = RedirectNotImpl, + OnRemoteSignOut = RemoteSignOutNotImpl, + }, + context => + { + return context.Response.WriteAsync(context.Request.Path); + }); + + var response = await PostAsync(server, "signin-oidc", "state=protected_state&code=my_code"); + + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("/signin-oidc", await response.Content.ReadAsStringAsync()); + Assert.True(messageReceived); + Assert.True(codeReceived); + Assert.True(tokenResponseReceived); + Assert.True(tokenValidated); + } + + [Fact] + public async Task OnTokenValidatedBackchannel_HandledWithoutTicket_NoMoreEventsRun() + { + var messageReceived = false; + var codeReceived = false; + var tokenResponseReceived = false; + var tokenValidated = false; + var server = CreateServer(new OpenIdConnectEvents() + { + OnMessageReceived = context => + { + messageReceived = true; + return Task.FromResult(0); + }, + OnAuthorizationCodeReceived = context => + { + codeReceived = true; + return Task.FromResult(0); + }, + OnTokenResponseReceived = context => + { + tokenResponseReceived = true; + return Task.FromResult(0); + }, + OnTokenValidated = context => + { + tokenValidated = true; + context.Ticket = null; + context.HandleResponse(); + context.Response.StatusCode = StatusCodes.Status202Accepted; + return Task.FromResult(0); + }, + OnUserInformationReceived = UserNotImpl, + OnAuthenticationFailed = FailedNotImpl, + OnRemoteFailure = FailureNotImpl, + OnTicketReceived = TicketNotImpl, + + OnRedirectToIdentityProvider = RedirectNotImpl, + OnRedirectToIdentityProviderForSignOut = RedirectNotImpl, + OnRemoteSignOut = RemoteSignOutNotImpl, + }, + AppNotImpl); + + var response = await PostAsync(server, "signin-oidc", "state=protected_state&code=my_code"); + + Assert.Equal(HttpStatusCode.Accepted, response.StatusCode); + Assert.Equal("", await response.Content.ReadAsStringAsync()); + Assert.True(messageReceived); + Assert.True(codeReceived); + Assert.True(tokenResponseReceived); + Assert.True(tokenValidated); + } + + [Fact] + public async Task OnTokenValidatedBackchannel_HandledWithTicket_SkipToTicketReceived() + { + var messageReceived = false; + var codeReceived = false; + var ticketReceived = false; + var tokenResponseReceived = false; + var tokenValidated = false; + var server = CreateServer(new OpenIdConnectEvents() + { + OnMessageReceived = context => + { + messageReceived = true; + return Task.FromResult(0); + }, + OnAuthorizationCodeReceived = context => + { + codeReceived = true; + return Task.FromResult(0); + }, + OnTokenResponseReceived = context => + { + tokenResponseReceived = true; + return Task.FromResult(0); + }, + OnTokenValidated = context => + { + tokenValidated = true; + // context.Ticket = null; + context.HandleResponse(); + return Task.FromResult(0); + }, + OnUserInformationReceived = UserNotImpl, + OnAuthenticationFailed = FailedNotImpl, + OnRemoteFailure = FailureNotImpl, + OnTicketReceived = context => + { + ticketReceived = true; + context.HandleResponse(); + context.Response.StatusCode = StatusCodes.Status202Accepted; + return Task.FromResult(0); + }, + + OnRedirectToIdentityProvider = RedirectNotImpl, + OnRedirectToIdentityProviderForSignOut = RedirectNotImpl, + OnRemoteSignOut = RemoteSignOutNotImpl, + }, + AppNotImpl); + + var response = await PostAsync(server, "signin-oidc", "state=protected_state&code=my_code"); + + Assert.Equal(HttpStatusCode.Accepted, response.StatusCode); + Assert.Equal("", await response.Content.ReadAsStringAsync()); + Assert.True(messageReceived); + Assert.True(codeReceived); + Assert.True(tokenResponseReceived); + Assert.True(tokenValidated); + Assert.True(ticketReceived); + } + + [Fact] + public async Task OnUserInformationReceived_Skipped_NoMoreEventsRun() + { + var messageReceived = false; + var tokenValidated = false; + var codeReceived = false; + var tokenResponseReceived = false; + var userInfoReceived = false; + var server = CreateServer(new OpenIdConnectEvents() + { + OnMessageReceived = context => + { + messageReceived = true; + return Task.FromResult(0); + }, + OnTokenValidated = context => + { + tokenValidated = true; + return Task.FromResult(0); + }, + OnAuthorizationCodeReceived = context => + { + codeReceived = true; + return Task.FromResult(0); + }, + OnTokenResponseReceived = context => + { + tokenResponseReceived = true; + return Task.FromResult(0); + }, + OnUserInformationReceived = context => + { + userInfoReceived = true; + context.SkipToNextMiddleware(); + return Task.FromResult(0); + }, + OnAuthenticationFailed = FailedNotImpl, + OnRemoteFailure = FailureNotImpl, + OnTicketReceived = TicketNotImpl, + + OnRedirectToIdentityProvider = RedirectNotImpl, + OnRedirectToIdentityProviderForSignOut = RedirectNotImpl, + OnRemoteSignOut = RemoteSignOutNotImpl, + }, + context => + { + return context.Response.WriteAsync(context.Request.Path); + }); + + var response = await PostAsync(server, "signin-oidc", "id_token=my_id_token&state=protected_state&code=my_code"); + + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("/signin-oidc", await response.Content.ReadAsStringAsync()); + Assert.True(messageReceived); + Assert.True(tokenValidated); + Assert.True(codeReceived); + Assert.True(tokenResponseReceived); + Assert.True(userInfoReceived); + } + + [Fact] + public async Task OnUserInformationReceived_HandledWithoutTicket_NoMoreEventsRun() + { + var messageReceived = false; + var tokenValidated = false; + var codeReceived = false; + var tokenResponseReceived = false; + var userInfoReceived = false; + var server = CreateServer(new OpenIdConnectEvents() + { + OnMessageReceived = context => + { + messageReceived = true; + return Task.FromResult(0); + }, + OnTokenValidated = context => + { + tokenValidated = true; + return Task.FromResult(0); + }, + OnAuthorizationCodeReceived = context => + { + codeReceived = true; + return Task.FromResult(0); + }, + OnTokenResponseReceived = context => + { + tokenResponseReceived = true; + return Task.FromResult(0); + }, + OnUserInformationReceived = context => + { + userInfoReceived = true; + context.Ticket = null; + context.HandleResponse(); + context.Response.StatusCode = StatusCodes.Status202Accepted; + return Task.FromResult(0); + }, + OnAuthenticationFailed = FailedNotImpl, + OnRemoteFailure = FailureNotImpl, + OnTicketReceived = TicketNotImpl, + + OnRedirectToIdentityProvider = RedirectNotImpl, + OnRedirectToIdentityProviderForSignOut = RedirectNotImpl, + OnRemoteSignOut = RemoteSignOutNotImpl, + }, + AppNotImpl); + + var response = await PostAsync(server, "signin-oidc", "id_token=my_id_token&state=protected_state&code=my_code"); + + Assert.Equal(HttpStatusCode.Accepted, response.StatusCode); + Assert.Equal("", await response.Content.ReadAsStringAsync()); + Assert.True(messageReceived); + Assert.True(tokenValidated); + Assert.True(codeReceived); + Assert.True(tokenResponseReceived); + Assert.True(userInfoReceived); + } + + [Fact] + public async Task OnUserInformationReceived_HandledWithTicket_SkipToTicketReceived() + { + var messageReceived = false; + var tokenValidated = false; + var codeReceived = false; + var ticketReceived = false; + var tokenResponseReceived = false; + var userInfoReceived = false; + var server = CreateServer(new OpenIdConnectEvents() + { + OnMessageReceived = context => + { + messageReceived = true; + return Task.FromResult(0); + }, + OnTokenValidated = context => + { + tokenValidated = true; + return Task.FromResult(0); + }, + OnAuthorizationCodeReceived = context => + { + codeReceived = true; + return Task.FromResult(0); + }, + OnTokenResponseReceived = context => + { + tokenResponseReceived = true; + return Task.FromResult(0); + }, + OnUserInformationReceived = context => + { + userInfoReceived = true; + // context.Ticket = null; + context.HandleResponse(); + return Task.FromResult(0); + }, + OnAuthenticationFailed = FailedNotImpl, + OnRemoteFailure = FailureNotImpl, + OnTicketReceived = context => + { + ticketReceived = true; + context.HandleResponse(); + context.Response.StatusCode = StatusCodes.Status202Accepted; + return Task.FromResult(0); + }, + + OnRedirectToIdentityProvider = RedirectNotImpl, + OnRedirectToIdentityProviderForSignOut = RedirectNotImpl, + OnRemoteSignOut = RemoteSignOutNotImpl, + }, + AppNotImpl); + + var response = await PostAsync(server, "signin-oidc", "id_token=my_id_token&state=protected_state&code=my_code"); + + Assert.Equal(HttpStatusCode.Accepted, response.StatusCode); + Assert.Equal("", await response.Content.ReadAsStringAsync()); + Assert.True(messageReceived); + Assert.True(tokenValidated); + Assert.True(codeReceived); + Assert.True(tokenResponseReceived); + Assert.True(userInfoReceived); + Assert.True(ticketReceived); + } + + [Fact] + public async Task OnAuthenticationFailed_Skipped_NoMoreEventsRun() + { + var messageReceived = false; + var tokenValidated = false; + var codeReceived = false; + var tokenResponseReceived = false; + var userInfoReceived = false; + var authFailed = false; + var server = CreateServer(new OpenIdConnectEvents() + { + OnMessageReceived = context => + { + messageReceived = true; + return Task.FromResult(0); + }, + OnTokenValidated = context => + { + tokenValidated = true; + return Task.FromResult(0); + }, + OnAuthorizationCodeReceived = context => + { + codeReceived = true; + return Task.FromResult(0); + }, + OnTokenResponseReceived = context => + { + tokenResponseReceived = true; + return Task.FromResult(0); + }, + OnUserInformationReceived = context => + { + userInfoReceived = true; + throw new NotImplementedException("TestException"); + }, + OnAuthenticationFailed = context => + { + authFailed = true; + Assert.Equal("TestException", context.Exception.Message); + context.SkipToNextMiddleware(); + return Task.FromResult(0); + }, + OnRemoteFailure = FailureNotImpl, + OnTicketReceived = TicketNotImpl, + + OnRedirectToIdentityProvider = RedirectNotImpl, + OnRedirectToIdentityProviderForSignOut = RedirectNotImpl, + OnRemoteSignOut = RemoteSignOutNotImpl, + }, + context => + { + return context.Response.WriteAsync(context.Request.Path); + }); + + var response = await PostAsync(server, "signin-oidc", "id_token=my_id_token&state=protected_state&code=my_code"); + + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("/signin-oidc", await response.Content.ReadAsStringAsync()); + Assert.True(messageReceived); + Assert.True(tokenValidated); + Assert.True(codeReceived); + Assert.True(tokenResponseReceived); + Assert.True(userInfoReceived); + Assert.True(authFailed); + } + + [Fact] + public async Task OnAuthenticationFailed_HandledWithoutTicket_NoMoreEventsRun() + { + var messageReceived = false; + var tokenValidated = false; + var codeReceived = false; + var tokenResponseReceived = false; + var userInfoReceived = false; + var authFailed = false; + var server = CreateServer(new OpenIdConnectEvents() + { + OnMessageReceived = context => + { + messageReceived = true; + return Task.FromResult(0); + }, + OnTokenValidated = context => + { + tokenValidated = true; + return Task.FromResult(0); + }, + OnAuthorizationCodeReceived = context => + { + codeReceived = true; + return Task.FromResult(0); + }, + OnTokenResponseReceived = context => + { + tokenResponseReceived = true; + return Task.FromResult(0); + }, + OnUserInformationReceived = context => + { + userInfoReceived = true; + throw new NotImplementedException("TestException"); + }, + OnAuthenticationFailed = context => + { + authFailed = true; + Assert.Equal("TestException", context.Exception.Message); + Assert.Null(context.Ticket); + context.HandleResponse(); + context.Response.StatusCode = StatusCodes.Status202Accepted; + return Task.FromResult(0); + }, + OnRemoteFailure = FailureNotImpl, + OnTicketReceived = TicketNotImpl, + + OnRedirectToIdentityProvider = RedirectNotImpl, + OnRedirectToIdentityProviderForSignOut = RedirectNotImpl, + OnRemoteSignOut = RemoteSignOutNotImpl, + }, + AppNotImpl); + + var response = await PostAsync(server, "signin-oidc", "id_token=my_id_token&state=protected_state&code=my_code"); + + Assert.Equal(HttpStatusCode.Accepted, response.StatusCode); + Assert.Equal("", await response.Content.ReadAsStringAsync()); + Assert.True(messageReceived); + Assert.True(tokenValidated); + Assert.True(codeReceived); + Assert.True(tokenResponseReceived); + Assert.True(userInfoReceived); + Assert.True(authFailed); + } + + [Fact] + public async Task OnAuthenticationFailed_HandledWithTicket_SkipToTicketReceived() + { + var messageReceived = false; + var tokenValidated = false; + var codeReceived = false; + var ticketReceived = false; + var tokenResponseReceived = false; + var userInfoReceived = false; + var authFailed = false; + var server = CreateServer(new OpenIdConnectEvents() + { + OnMessageReceived = context => + { + messageReceived = true; + return Task.FromResult(0); + }, + OnTokenValidated = context => + { + tokenValidated = true; + return Task.FromResult(0); + }, + OnAuthorizationCodeReceived = context => + { + codeReceived = true; + return Task.FromResult(0); + }, + OnTokenResponseReceived = context => + { + tokenResponseReceived = true; + return Task.FromResult(0); + }, + OnUserInformationReceived = context => + { + userInfoReceived = true; + throw new NotImplementedException("TestException"); + }, + OnAuthenticationFailed = context => + { + authFailed = true; + Assert.Equal("TestException", context.Exception.Message); + Assert.Null(context.Ticket); + + var claims = new[] + { + new Claim(ClaimTypes.NameIdentifier, "Bob le Magnifique"), + new Claim(ClaimTypes.Email, "bob@contoso.com"), + new Claim(ClaimsIdentity.DefaultNameClaimType, "bob") + }; + + context.Ticket = new AuthenticationTicket( + new ClaimsPrincipal(new ClaimsIdentity(claims, context.Options.AuthenticationScheme)), + new AuthenticationProperties(), context.Options.AuthenticationScheme); + + context.HandleResponse(); + return Task.FromResult(0); + }, + OnRemoteFailure = FailureNotImpl, + OnTicketReceived = context => + { + ticketReceived = true; + context.HandleResponse(); + context.Response.StatusCode = StatusCodes.Status202Accepted; + return Task.FromResult(0); + }, + + OnRedirectToIdentityProvider = RedirectNotImpl, + OnRedirectToIdentityProviderForSignOut = RedirectNotImpl, + OnRemoteSignOut = RemoteSignOutNotImpl, + }, + AppNotImpl); + + var response = await PostAsync(server, "signin-oidc", "id_token=my_id_token&state=protected_state&code=my_code"); + + Assert.Equal(HttpStatusCode.Accepted, response.StatusCode); + Assert.Equal("", await response.Content.ReadAsStringAsync()); + Assert.True(messageReceived); + Assert.True(tokenValidated); + Assert.True(codeReceived); + Assert.True(tokenResponseReceived); + Assert.True(userInfoReceived); + Assert.True(authFailed); + Assert.True(ticketReceived); + } + + [Fact] + public async Task OnRemoteFailure_Skipped_NoMoreEventsRun() + { + var messageReceived = false; + var tokenValidated = false; + var codeReceived = false; + var tokenResponseReceived = false; + var userInfoReceived = false; + var authFailed = false; + var remoteFailure = false; + var server = CreateServer(new OpenIdConnectEvents() + { + OnMessageReceived = context => + { + messageReceived = true; + return Task.FromResult(0); + }, + OnTokenValidated = context => + { + tokenValidated = true; + return Task.FromResult(0); + }, + OnAuthorizationCodeReceived = context => + { + codeReceived = true; + return Task.FromResult(0); + }, + OnTokenResponseReceived = context => + { + tokenResponseReceived = true; + return Task.FromResult(0); + }, + OnUserInformationReceived = context => + { + userInfoReceived = true; + throw new NotImplementedException("TestException"); + }, + OnAuthenticationFailed = context => + { + authFailed = true; + Assert.Equal("TestException", context.Exception.Message); + return Task.FromResult(0); + }, + OnRemoteFailure = context => + { + remoteFailure = true; + Assert.Equal("TestException", context.Failure.Message); + context.SkipToNextMiddleware(); + return Task.FromResult(0); + }, + OnTicketReceived = TicketNotImpl, + + OnRedirectToIdentityProvider = RedirectNotImpl, + OnRedirectToIdentityProviderForSignOut = RedirectNotImpl, + OnRemoteSignOut = RemoteSignOutNotImpl, + }, + context => + { + return context.Response.WriteAsync(context.Request.Path); + }); + + var response = await PostAsync(server, "signin-oidc", "id_token=my_id_token&state=protected_state&code=my_code"); + + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("/signin-oidc", await response.Content.ReadAsStringAsync()); + Assert.True(messageReceived); + Assert.True(tokenValidated); + Assert.True(codeReceived); + Assert.True(tokenResponseReceived); + Assert.True(userInfoReceived); + Assert.True(authFailed); + Assert.True(remoteFailure); + } + + [Fact] + public async Task OnRemoteFailure_Handled_NoMoreEventsRun() + { + var messageReceived = false; + var tokenValidated = false; + var codeReceived = false; + var tokenResponseReceived = false; + var userInfoReceived = false; + var authFailed = false; + var remoteFailure = false; + var server = CreateServer(new OpenIdConnectEvents() + { + OnMessageReceived = context => + { + messageReceived = true; + return Task.FromResult(0); + }, + OnTokenValidated = context => + { + tokenValidated = true; + return Task.FromResult(0); + }, + OnAuthorizationCodeReceived = context => + { + codeReceived = true; + return Task.FromResult(0); + }, + OnTokenResponseReceived = context => + { + tokenResponseReceived = true; + return Task.FromResult(0); + }, + OnUserInformationReceived = context => + { + userInfoReceived = true; + throw new NotImplementedException("TestException"); + }, + OnAuthenticationFailed = context => + { + authFailed = true; + Assert.Equal("TestException", context.Exception.Message); + return Task.FromResult(0); + }, + OnRemoteFailure = context => + { + remoteFailure = true; + Assert.Equal("TestException", context.Failure.Message); + context.HandleResponse(); + context.Response.StatusCode = StatusCodes.Status202Accepted; + return Task.FromResult(0); + }, + OnTicketReceived = TicketNotImpl, + + OnRedirectToIdentityProvider = RedirectNotImpl, + OnRedirectToIdentityProviderForSignOut = RedirectNotImpl, + OnRemoteSignOut = RemoteSignOutNotImpl, + }, + AppNotImpl); + + var response = await PostAsync(server, "signin-oidc", "id_token=my_id_token&state=protected_state&code=my_code"); + + Assert.Equal(HttpStatusCode.Accepted, response.StatusCode); + Assert.Equal("", await response.Content.ReadAsStringAsync()); + Assert.True(messageReceived); + Assert.True(tokenValidated); + Assert.True(codeReceived); + Assert.True(tokenResponseReceived); + Assert.True(userInfoReceived); + Assert.True(authFailed); + Assert.True(remoteFailure); + } + + [Fact] + public async Task OnTicketReceived_Skipped_NoMoreEventsRun() + { + var messageReceived = false; + var tokenValidated = false; + var codeReceived = false; + var tokenResponseReceived = false; + var userInfoReceived = false; + var ticektReceived = false; + var server = CreateServer(new OpenIdConnectEvents() + { + OnMessageReceived = context => + { + messageReceived = true; + return Task.FromResult(0); + }, + OnTokenValidated = context => + { + tokenValidated = true; + return Task.FromResult(0); + }, + OnAuthorizationCodeReceived = context => + { + codeReceived = true; + return Task.FromResult(0); + }, + OnTokenResponseReceived = context => + { + tokenResponseReceived = true; + return Task.FromResult(0); + }, + OnUserInformationReceived = context => + { + userInfoReceived = true; + return Task.FromResult(0); + }, + OnAuthenticationFailed = FailedNotImpl, + OnRemoteFailure = FailureNotImpl, + OnTicketReceived = context => + { + ticektReceived = true; + context.SkipToNextMiddleware(); + return Task.FromResult(0); + }, + + OnRedirectToIdentityProvider = RedirectNotImpl, + OnRedirectToIdentityProviderForSignOut = RedirectNotImpl, + OnRemoteSignOut = RemoteSignOutNotImpl, + }, + context => + { + return context.Response.WriteAsync(context.Request.Path); + }); + + var response = await PostAsync(server, "signin-oidc", "id_token=my_id_token&state=protected_state&code=my_code"); + + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("/signin-oidc", await response.Content.ReadAsStringAsync()); + Assert.True(messageReceived); + Assert.True(tokenValidated); + Assert.True(codeReceived); + Assert.True(tokenResponseReceived); + Assert.True(userInfoReceived); + Assert.True(ticektReceived); + } + + [Fact] + public async Task OnTicketReceived_Handled_NoMoreEventsRun() + { + var messageReceived = false; + var tokenValidated = false; + var codeReceived = false; + var tokenResponseReceived = false; + var userInfoReceived = false; + var ticektReceived = false; + var server = CreateServer(new OpenIdConnectEvents() + { + OnMessageReceived = context => + { + messageReceived = true; + return Task.FromResult(0); + }, + OnTokenValidated = context => + { + tokenValidated = true; + return Task.FromResult(0); + }, + OnAuthorizationCodeReceived = context => + { + codeReceived = true; + return Task.FromResult(0); + }, + OnTokenResponseReceived = context => + { + tokenResponseReceived = true; + return Task.FromResult(0); + }, + OnUserInformationReceived = context => + { + userInfoReceived = true; + return Task.FromResult(0); + }, + OnAuthenticationFailed = FailedNotImpl, + OnRemoteFailure = FailureNotImpl, + OnTicketReceived = context => + { + ticektReceived = true; + context.HandleResponse(); + context.Response.StatusCode = StatusCodes.Status202Accepted; + return Task.FromResult(0); + }, + + OnRedirectToIdentityProvider = RedirectNotImpl, + OnRedirectToIdentityProviderForSignOut = RedirectNotImpl, + OnRemoteSignOut = RemoteSignOutNotImpl, + }, + AppNotImpl); + + var response = await PostAsync(server, "signin-oidc", "id_token=my_id_token&state=protected_state&code=my_code"); + + Assert.Equal(HttpStatusCode.Accepted, response.StatusCode); + Assert.Equal("", await response.Content.ReadAsStringAsync()); + Assert.True(messageReceived); + Assert.True(tokenValidated); + Assert.True(codeReceived); + Assert.True(tokenResponseReceived); + Assert.True(userInfoReceived); + Assert.True(ticektReceived); + } + + private TestServer CreateServer(OpenIdConnectEvents events, RequestDelegate appCode) + { + var builder = new WebHostBuilder() + .ConfigureServices(services => + { + services.AddAuthentication(); + }) + .Configure(app => + { + app.UseCookieAuthentication(); + app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions() + { + Events = events, + SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme, + ClientId = "ClientId", + GetClaimsFromUserInfoEndpoint = true, + Configuration = new OpenIdConnectConfiguration() + { + TokenEndpoint = "http://testhost/tokens", + UserInfoEndpoint = "http://testhost/user", + }, + StateDataFormat = new TestStateDataFormat(), + SecurityTokenValidator = new TestTokenValidator(), + ProtocolValidator = new TestProtocolValidator(), + BackchannelHttpHandler = new TestBackchannel(), + }); + app.Run(appCode); + }); + + return new TestServer(builder); + } + + private Task PostAsync(TestServer server, string path, string form) + { + var client = server.CreateClient(); + var cookie = ".AspNetCore.Correlation." + OpenIdConnectDefaults.AuthenticationScheme + ".corrilationId=N"; + client.DefaultRequestHeaders.Add("Cookie", cookie); + return client.PostAsync("signin-oidc", + new StringContent(form, Encoding.ASCII, "application/x-www-form-urlencoded")); + } + + private class TestStateDataFormat : ISecureDataFormat + { + private AuthenticationProperties Data { get; set; } + + public string Protect(AuthenticationProperties data) + { + throw new NotImplementedException(); + } + + public string Protect(AuthenticationProperties data, string purpose) + { + throw new NotImplementedException(); + } + + public AuthenticationProperties Unprotect(string protectedText) + { + Assert.Equal("protected_state", protectedText); + return new AuthenticationProperties(new Dictionary() + { + { ".xsrf", "corrilationId" }, + { OpenIdConnectDefaults.RedirectUriForCodePropertiesKey, "redirect_uri" } + }); + } + + public AuthenticationProperties Unprotect(string protectedText, string purpose) + { + throw new NotImplementedException(); + } + } + + private class TestTokenValidator : ISecurityTokenValidator + { + public bool CanValidateToken => true; + + public int MaximumTokenSizeInBytes + { + get { return 1024; } + set { throw new NotImplementedException(); } + } + + public bool CanReadToken(string securityToken) + { + Assert.Equal("my_id_token", securityToken); + return true; + } + + public ClaimsPrincipal ValidateToken(string securityToken, TokenValidationParameters validationParameters, out SecurityToken validatedToken) + { + Assert.Equal("my_id_token", securityToken); + validatedToken = new JwtSecurityToken(); + return new ClaimsPrincipal(new ClaimsIdentity("customAuthType")); + } + } + + private class TestProtocolValidator : OpenIdConnectProtocolValidator + { + public override void ValidateAuthenticationResponse(OpenIdConnectProtocolValidationContext validationContext) + { + } + + public override void ValidateTokenResponse(OpenIdConnectProtocolValidationContext validationContext) + { + } + + public override void ValidateUserInfoResponse(OpenIdConnectProtocolValidationContext validationContext) + { + } + } + + private class TestBackchannel : HttpMessageHandler + { + protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + if (string.Equals("/tokens", request.RequestUri.AbsolutePath, StringComparison.Ordinal)) + { + return Task.FromResult(new HttpResponseMessage() { Content = + new StringContent("{ \"id_token\": \"my_id_token\", \"access_token\": \"my_access_token\" }", Encoding.ASCII, "application/json") }); + } + if (string.Equals("/user", request.RequestUri.AbsolutePath, StringComparison.Ordinal)) + { + return Task.FromResult(new HttpResponseMessage() { Content = new StringContent("{ }", Encoding.ASCII, "application/json") }); + } + + throw new NotImplementedException(request.RequestUri.ToString()); + } + } + } +}