#955 Print available scheme names in exception message for invalid scheme (#961)

This commit is contained in:
Chris Ross 2017-11-16 09:17:57 -08:00 committed by GitHub
parent 231f3a44b1
commit 476333771c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 177 additions and 15 deletions

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Linq;
using System.Security.Claims; using System.Security.Claims;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
@ -62,7 +63,7 @@ namespace Microsoft.AspNetCore.Authentication
var handler = await Handlers.GetHandlerAsync(context, scheme); var handler = await Handlers.GetHandlerAsync(context, scheme);
if (handler == null) if (handler == null)
{ {
throw new InvalidOperationException($"No authentication handler is configured to authenticate for the scheme: {scheme}"); throw await CreateMissingHandlerException(scheme);
} }
var result = await handler.AuthenticateAsync(); var result = await handler.AuthenticateAsync();
@ -96,7 +97,7 @@ namespace Microsoft.AspNetCore.Authentication
var handler = await Handlers.GetHandlerAsync(context, scheme); var handler = await Handlers.GetHandlerAsync(context, scheme);
if (handler == null) if (handler == null)
{ {
throw new InvalidOperationException($"No authentication handler is configured to handle the scheme: {scheme}"); throw await CreateMissingHandlerException(scheme);
} }
await handler.ChallengeAsync(properties); await handler.ChallengeAsync(properties);
@ -124,7 +125,7 @@ namespace Microsoft.AspNetCore.Authentication
var handler = await Handlers.GetHandlerAsync(context, scheme); var handler = await Handlers.GetHandlerAsync(context, scheme);
if (handler == null) if (handler == null)
{ {
throw new InvalidOperationException($"No authentication handler is configured to handle the scheme: {scheme}"); throw await CreateMissingHandlerException(scheme);
} }
await handler.ForbidAsync(properties); await handler.ForbidAsync(properties);
@ -155,13 +156,19 @@ namespace Microsoft.AspNetCore.Authentication
} }
} }
var handler = await Handlers.GetHandlerAsync(context, scheme) as IAuthenticationSignInHandler; var handler = await Handlers.GetHandlerAsync(context, scheme);
if (handler == null) if (handler == null)
{ {
throw new InvalidOperationException($"No IAuthenticationSignInHandler is configured to handle sign in for the scheme: {scheme}"); throw await CreateMissingSignInHandlerException(scheme);
} }
await handler.SignInAsync(principal, properties); var signInHandler = handler as IAuthenticationSignInHandler;
if (signInHandler == null)
{
throw await CreateMismatchedSignInHandlerException(scheme, handler);
}
await signInHandler.SignInAsync(principal, properties);
} }
/// <summary> /// <summary>
@ -183,13 +190,114 @@ namespace Microsoft.AspNetCore.Authentication
} }
} }
var handler = await Handlers.GetHandlerAsync(context, scheme) as IAuthenticationSignOutHandler; var handler = await Handlers.GetHandlerAsync(context, scheme);
if (handler == null) if (handler == null)
{ {
throw new InvalidOperationException($"No IAuthenticationSignOutHandler is configured to handle sign out for the scheme: {scheme}"); throw await CreateMissingSignOutHandlerException(scheme);
} }
await handler.SignOutAsync(properties); var signOutHandler = handler as IAuthenticationSignOutHandler;
if (signOutHandler == null)
{
throw await CreateMismatchedSignOutHandlerException(scheme, handler);
}
await signOutHandler.SignOutAsync(properties);
}
private async Task<Exception> CreateMissingHandlerException(string scheme)
{
var schemes = string.Join(", ", (await Schemes.GetAllSchemesAsync()).Select(sch => sch.Name));
var footer = $" Did you forget to call AddAuthentication().Add[SomeAuthHandler](\"{scheme}\",...)?";
if (string.IsNullOrEmpty(schemes))
{
return new InvalidOperationException(
$"No authentication handlers are registered." + footer);
}
return new InvalidOperationException(
$"No authentication handler is registered for the scheme '{scheme}'. The registered schemes are: {schemes}." + footer);
}
private async Task<string> GetAllSignInSchemeNames()
{
return string.Join(", ", (await Schemes.GetAllSchemesAsync())
.Where(sch => typeof(IAuthenticationSignInHandler).IsAssignableFrom(sch.HandlerType))
.Select(sch => sch.Name));
}
private async Task<Exception> CreateMissingSignInHandlerException(string scheme)
{
var schemes = await GetAllSignInSchemeNames();
// CookieAuth is the only implementation of sign-in.
var footer = $" Did you forget to call AddAuthentication().AddCookies(\"{scheme}\",...)?";
if (string.IsNullOrEmpty(schemes))
{
return new InvalidOperationException(
$"No sign-in authentication handlers are registered." + footer);
}
return new InvalidOperationException(
$"No sign-in authentication handler is registered for the scheme '{scheme}'. The registered sign-in schemes are: {schemes}." + footer);
}
private async Task<Exception> CreateMismatchedSignInHandlerException(string scheme, IAuthenticationHandler handler)
{
var schemes = await GetAllSignInSchemeNames();
var mismatchError = $"The authentication handler registered for scheme '{scheme}' is '{handler.GetType().Name}' which cannot be used for SignInAsync. ";
if (string.IsNullOrEmpty(schemes))
{
// CookieAuth is the only implementation of sign-in.
return new InvalidOperationException(mismatchError
+ $"Did you intended to call AddAuthentication().AddCookies(\"Cookies\") and SignInAsync(\"Cookies\",...)?");
}
return new InvalidOperationException(mismatchError + $"The registered sign-in schemes are: {schemes}.");
}
private async Task<string> GetAllSignOutSchemeNames()
{
return string.Join(", ", (await Schemes.GetAllSchemesAsync())
.Where(sch => typeof(IAuthenticationSignOutHandler).IsAssignableFrom(sch.HandlerType))
.Select(sch => sch.Name));
}
private async Task<Exception> CreateMissingSignOutHandlerException(string scheme)
{
var schemes = await GetAllSignOutSchemeNames();
var footer = $" Did you forget to call AddAuthentication().AddCookies(\"{scheme}\",...)?";
if (string.IsNullOrEmpty(schemes))
{
// CookieAuth is the most common implementation of sign-out, but OpenIdConnect and WsFederation also support it.
return new InvalidOperationException($"No sign-out authentication handlers are registered." + footer);
}
return new InvalidOperationException(
$"No sign-out authentication handler is registered for the scheme '{scheme}'. The registered sign-out schemes are: {schemes}." + footer);
}
private async Task<Exception> CreateMismatchedSignOutHandlerException(string scheme, IAuthenticationHandler handler)
{
var schemes = await GetAllSignOutSchemeNames();
var mismatchError = $"The authentication handler registered for scheme '{scheme}' is '{handler.GetType().Name}' which cannot be used for {nameof(SignOutAsync)}. ";
if (string.IsNullOrEmpty(schemes))
{
// CookieAuth is the most common implementation of sign-out, but OpenIdConnect and WsFederation also support it.
return new InvalidOperationException(mismatchError
+ $"Did you intended to call AddAuthentication().AddCookies(\"Cookies\") and {nameof(SignOutAsync)}(\"Cookies\",...)?");
}
return new InvalidOperationException(mismatchError + $"The registered sign-out schemes are: {schemes}.");
} }
} }
} }

View File

@ -12,6 +12,51 @@ namespace Microsoft.AspNetCore.Authentication
{ {
public class AuthenticationServiceTests public class AuthenticationServiceTests
{ {
[Fact]
public async Task AuthenticateThrowsForSchemeMismatch()
{
var services = new ServiceCollection().AddOptions().AddAuthenticationCore(o =>
{
o.AddScheme<BaseHandler>("base", "whatever");
}).BuildServiceProvider();
var context = new DefaultHttpContext();
context.RequestServices = services;
await context.AuthenticateAsync("base");
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => context.AuthenticateAsync("missing"));
Assert.Contains("base", ex.Message);
}
[Fact]
public async Task ChallengeThrowsForSchemeMismatch()
{
var services = new ServiceCollection().AddOptions().AddAuthenticationCore(o =>
{
o.AddScheme<BaseHandler>("base", "whatever");
}).BuildServiceProvider();
var context = new DefaultHttpContext();
context.RequestServices = services;
await context.ChallengeAsync("base");
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => context.ChallengeAsync("missing"));
Assert.Contains("base", ex.Message);
}
[Fact]
public async Task ForbidThrowsForSchemeMismatch()
{
var services = new ServiceCollection().AddOptions().AddAuthenticationCore(o =>
{
o.AddScheme<BaseHandler>("base", "whatever");
}).BuildServiceProvider();
var context = new DefaultHttpContext();
context.RequestServices = services;
await context.ForbidAsync("base");
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => context.ForbidAsync("missing"));
Assert.Contains("base", ex.Message);
}
[Fact] [Fact]
public async Task CanOnlySignInIfSupported() public async Task CanOnlySignInIfSupported()
{ {
@ -26,9 +71,13 @@ namespace Microsoft.AspNetCore.Authentication
context.RequestServices = services; context.RequestServices = services;
await context.SignInAsync("uber", new ClaimsPrincipal(), null); await context.SignInAsync("uber", new ClaimsPrincipal(), null);
await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignInAsync("base", new ClaimsPrincipal(), null)); var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignInAsync("base", new ClaimsPrincipal(), null));
Assert.Contains("uber", ex.Message);
Assert.Contains("signin", ex.Message);
await context.SignInAsync("signin", new ClaimsPrincipal(), null); await context.SignInAsync("signin", new ClaimsPrincipal(), null);
await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignInAsync("signout", new ClaimsPrincipal(), null)); ex = await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignInAsync("signout", new ClaimsPrincipal(), null));
Assert.Contains("uber", ex.Message);
Assert.Contains("signin", ex.Message);
} }
[Fact] [Fact]
@ -45,7 +94,9 @@ namespace Microsoft.AspNetCore.Authentication
context.RequestServices = services; context.RequestServices = services;
await context.SignOutAsync("uber"); await context.SignOutAsync("uber");
await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignOutAsync("base")); var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignOutAsync("base"));
Assert.Contains("uber", ex.Message);
Assert.Contains("signout", ex.Message);
await context.SignOutAsync("signout"); await context.SignOutAsync("signout");
await context.SignOutAsync("signin"); await context.SignOutAsync("signin");
} }
@ -64,8 +115,10 @@ namespace Microsoft.AspNetCore.Authentication
await context.AuthenticateAsync(); await context.AuthenticateAsync();
await context.ChallengeAsync(); await context.ChallengeAsync();
await context.ForbidAsync(); await context.ForbidAsync();
await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignOutAsync()); var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignOutAsync());
await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignInAsync(new ClaimsPrincipal())); Assert.Contains("cannot be used for SignOutAsync", ex.Message);
ex = await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignInAsync(new ClaimsPrincipal()));
Assert.Contains("cannot be used for SignInAsync", ex.Message);
} }
[Fact] [Fact]
@ -119,7 +172,8 @@ namespace Microsoft.AspNetCore.Authentication
await context.ChallengeAsync(); await context.ChallengeAsync();
await context.ForbidAsync(); await context.ForbidAsync();
await context.SignOutAsync(); await context.SignOutAsync();
await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignInAsync(new ClaimsPrincipal())); var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignInAsync(new ClaimsPrincipal()));
Assert.Contains("cannot be used for SignInAsync", ex.Message);
} }
[Fact] [Fact]