Add VirtualSchemes
This commit is contained in:
parent
bd8ecd0268
commit
72e1cb1385
|
|
@ -25,18 +25,10 @@ namespace Microsoft.AspNetCore.Authentication
|
|||
/// </summary>
|
||||
public virtual IServiceCollection Services { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Adds a <see cref="AuthenticationScheme"/> which can be used by <see cref="IAuthenticationService"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TOptions">The <see cref="AuthenticationSchemeOptions"/> type to configure the handler."/>.</typeparam>
|
||||
/// <typeparam name="THandler">The <see cref="AuthenticationHandler{TOptions}"/> used to handle this scheme.</typeparam>
|
||||
/// <param name="authenticationScheme">The name of this scheme.</param>
|
||||
/// <param name="displayName">The display name of this scheme.</param>
|
||||
/// <param name="configureOptions">Used to configure the scheme options.</param>
|
||||
/// <returns>The builder.</returns>
|
||||
public virtual AuthenticationBuilder AddScheme<TOptions, THandler>(string authenticationScheme, string displayName, Action<TOptions> configureOptions)
|
||||
where TOptions : AuthenticationSchemeOptions, new()
|
||||
where THandler : AuthenticationHandler<TOptions>
|
||||
|
||||
private AuthenticationBuilder AddSchemeHelper<TOptions, THandler>(string authenticationScheme, string displayName, Action<TOptions> configureOptions)
|
||||
where TOptions : class, new()
|
||||
where THandler : class, IAuthenticationHandler
|
||||
{
|
||||
Services.Configure<AuthenticationOptions>(o =>
|
||||
{
|
||||
|
|
@ -53,6 +45,20 @@ namespace Microsoft.AspNetCore.Authentication
|
|||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a <see cref="AuthenticationScheme"/> which can be used by <see cref="IAuthenticationService"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TOptions">The <see cref="AuthenticationSchemeOptions"/> type to configure the handler."/>.</typeparam>
|
||||
/// <typeparam name="THandler">The <see cref="AuthenticationHandler{TOptions}"/> used to handle this scheme.</typeparam>
|
||||
/// <param name="authenticationScheme">The name of this scheme.</param>
|
||||
/// <param name="displayName">The display name of this scheme.</param>
|
||||
/// <param name="configureOptions">Used to configure the scheme options.</param>
|
||||
/// <returns>The builder.</returns>
|
||||
public virtual AuthenticationBuilder AddScheme<TOptions, THandler>(string authenticationScheme, string displayName, Action<TOptions> configureOptions)
|
||||
where TOptions : AuthenticationSchemeOptions, new()
|
||||
where THandler : AuthenticationHandler<TOptions>
|
||||
=> AddSchemeHelper<TOptions, THandler>(authenticationScheme, displayName, configureOptions);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a <see cref="AuthenticationScheme"/> which can be used by <see cref="IAuthenticationService"/>.
|
||||
/// </summary>
|
||||
|
|
@ -84,6 +90,17 @@ namespace Microsoft.AspNetCore.Authentication
|
|||
return AddScheme<TOptions, THandler>(authenticationScheme, displayName, configureOptions: configureOptions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a <see cref="VirtualAuthenticationHandler"/> based authentication handler which can be used to
|
||||
/// redirect to other authentication schemes.
|
||||
/// </summary>
|
||||
/// <param name="authenticationScheme">The name of this scheme.</param>
|
||||
/// <param name="displayName">The display name of this scheme.</param>
|
||||
/// <param name="configureOptions">Used to configure the scheme options.</param>
|
||||
/// <returns>The builder.</returns>
|
||||
public virtual AuthenticationBuilder AddVirtualScheme(string authenticationScheme, string displayName, Action<VirtualSchemeOptions> configureOptions)
|
||||
=> AddSchemeHelper<VirtualSchemeOptions, VirtualAuthenticationHandler>(authenticationScheme, displayName, configureOptions);
|
||||
|
||||
// Used to ensure that there's always a default sign in scheme that's not itself
|
||||
private class EnsureSignInScheme<TOptions> : IPostConfigureOptions<TOptions> where TOptions : RemoteAuthenticationOptions
|
||||
{
|
||||
|
|
|
|||
|
|
@ -22,12 +22,12 @@ namespace Microsoft.AspNetCore.Authentication
|
|||
|
||||
protected HttpRequest Request
|
||||
{
|
||||
get { return Context.Request; }
|
||||
get => Context.Request;
|
||||
}
|
||||
|
||||
protected HttpResponse Response
|
||||
{
|
||||
get { return Context.Response; }
|
||||
get => Context.Response;
|
||||
}
|
||||
|
||||
protected PathString OriginalPath => Context.Features.Get<IAuthenticationFeature>()?.OriginalPath ?? Request.Path;
|
||||
|
|
@ -52,10 +52,7 @@ namespace Microsoft.AspNetCore.Authentication
|
|||
|
||||
protected string CurrentUri
|
||||
{
|
||||
get
|
||||
{
|
||||
return Request.Scheme + "://" + Request.Host + Request.PathBase + Request.Path + Request.QueryString;
|
||||
}
|
||||
get => Request.Scheme + "://" + Request.Host + Request.PathBase + Request.Path + Request.QueryString;
|
||||
}
|
||||
|
||||
protected AuthenticationHandler(IOptionsMonitor<TOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
|
||||
|
|
@ -116,15 +113,10 @@ namespace Microsoft.AspNetCore.Authentication
|
|||
/// Called after options/events have been initialized for the handler to finish initializing itself.
|
||||
/// </summary>
|
||||
/// <returns>A task</returns>
|
||||
protected virtual Task InitializeHandlerAsync()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
protected virtual Task InitializeHandlerAsync() => Task.CompletedTask;
|
||||
|
||||
protected string BuildRedirectUri(string targetPath)
|
||||
{
|
||||
return Request.Scheme + "://" + Request.Host + OriginalPathBase + targetPath;
|
||||
}
|
||||
=> Request.Scheme + "://" + Request.Host + OriginalPathBase + targetPath;
|
||||
|
||||
public async Task<AuthenticateResult> AuthenticateAsync()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,71 @@
|
|||
// 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.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.Authentication
|
||||
{
|
||||
/// <summary>
|
||||
/// Forwards calls to another authentication scheme.
|
||||
/// </summary>
|
||||
public class VirtualAuthenticationHandler : IAuthenticationHandler, IAuthenticationSignInHandler
|
||||
{
|
||||
protected IOptionsMonitor<VirtualSchemeOptions> OptionsMonitor { get; }
|
||||
public AuthenticationScheme Scheme { get; private set; }
|
||||
public VirtualSchemeOptions Options { get; private set; }
|
||||
protected HttpContext Context { get; private set; }
|
||||
|
||||
public VirtualAuthenticationHandler(IOptionsMonitor<VirtualSchemeOptions> options)
|
||||
{
|
||||
OptionsMonitor = options;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the handler, resolve the options and validate them.
|
||||
/// </summary>
|
||||
/// <param name="scheme"></param>
|
||||
/// <param name="context"></param>
|
||||
/// <returns>A Task.</returns>
|
||||
public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context)
|
||||
{
|
||||
if (scheme == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(scheme));
|
||||
}
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
Scheme = scheme;
|
||||
Context = context;
|
||||
|
||||
Options = OptionsMonitor.Get(Scheme.Name) ?? new VirtualSchemeOptions();
|
||||
Options.Validate();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected virtual string ResolveTarget(string scheme)
|
||||
=> scheme ?? Options.DefaultSelector?.Invoke(Context) ?? Options.Default;
|
||||
|
||||
public virtual Task<AuthenticateResult> AuthenticateAsync()
|
||||
=> Context.AuthenticateAsync(ResolveTarget(Options.Authenticate));
|
||||
|
||||
public virtual Task SignInAsync(ClaimsPrincipal user, AuthenticationProperties properties)
|
||||
=> Context.SignInAsync(ResolveTarget(Options.SignIn), user, properties);
|
||||
|
||||
public virtual Task SignOutAsync(AuthenticationProperties properties)
|
||||
=> Context.SignOutAsync(ResolveTarget(Options.SignOut), properties);
|
||||
|
||||
public virtual Task ChallengeAsync(AuthenticationProperties properties)
|
||||
=> Context.ChallengeAsync(ResolveTarget(Options.Challenge), properties);
|
||||
|
||||
public virtual Task ForbidAsync(AuthenticationProperties properties)
|
||||
=> Context.ForbidAsync(ResolveTarget(Options.Forbid), properties);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
// 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 Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Authentication
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to redirect authentication methods to another scheme
|
||||
/// </summary>
|
||||
public class VirtualSchemeOptions
|
||||
{
|
||||
public string Default { get; set; }
|
||||
|
||||
public string Authenticate { get; set; }
|
||||
public string Challenge { get; set; }
|
||||
public string Forbid { get; set; }
|
||||
public string SignIn { get; set; }
|
||||
public string SignOut { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Used to select a default scheme to target based on the request.
|
||||
/// </summary>
|
||||
public Func<HttpContext, string> DefaultSelector { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Check that the options are valid. Should throw an exception if things are not ok.
|
||||
/// </summary>
|
||||
public virtual void Validate() { }
|
||||
}
|
||||
}
|
||||
|
|
@ -990,7 +990,7 @@ namespace Microsoft.AspNetCore.Authentication.Google
|
|||
var res = context.Response;
|
||||
if (req.Path == new PathString("/challenge"))
|
||||
{
|
||||
await context.ChallengeAsync("Google");
|
||||
await context.ChallengeAsync();
|
||||
}
|
||||
else if (req.Path == new PathString("/challengeFacebook"))
|
||||
{
|
||||
|
|
@ -1061,19 +1061,19 @@ namespace Microsoft.AspNetCore.Authentication.Google
|
|||
.ConfigureServices(services =>
|
||||
{
|
||||
services.AddTransient<IClaimsTransformation, ClaimsTransformer>();
|
||||
services.AddAuthentication(o =>
|
||||
{
|
||||
o.DefaultScheme = TestExtensions.CookieAuthenticationScheme;
|
||||
o.DefaultChallengeScheme = GoogleDefaults.AuthenticationScheme;
|
||||
});
|
||||
services.AddAuthentication()
|
||||
services.AddAuthentication("Auth")
|
||||
.AddVirtualScheme("Auth", "Auth", o =>
|
||||
{
|
||||
o.Default = TestExtensions.CookieAuthenticationScheme;
|
||||
o.Challenge = GoogleDefaults.AuthenticationScheme;
|
||||
})
|
||||
.AddCookie(TestExtensions.CookieAuthenticationScheme)
|
||||
.AddGoogle(configureOptions)
|
||||
.AddFacebook(o =>
|
||||
{
|
||||
o.AppId = "Test AppId";
|
||||
o.AppSecret = "Test AppSecrent";
|
||||
});
|
||||
{
|
||||
o.AppId = "Test AppId";
|
||||
o.AppSecret = "Test AppSecrent";
|
||||
});
|
||||
});
|
||||
return new TestServer(builder);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,525 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.TestHost;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Authentication
|
||||
{
|
||||
public class VirtualHandlerTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task CanDispatch()
|
||||
{
|
||||
var server = CreateServer(services =>
|
||||
{
|
||||
services.AddAuthentication(o =>
|
||||
{
|
||||
o.AddScheme<TestHandler>("auth1", "auth1");
|
||||
o.AddScheme<TestHandler>("auth2", "auth2");
|
||||
o.AddScheme<TestHandler>("auth3", "auth3");
|
||||
})
|
||||
.AddVirtualScheme("policy1", "policy1", p =>
|
||||
{
|
||||
p.Default = "auth1";
|
||||
})
|
||||
.AddVirtualScheme("policy2", "policy2", p =>
|
||||
{
|
||||
p.Authenticate = "auth2";
|
||||
});
|
||||
});
|
||||
|
||||
var transaction = await server.SendAsync("http://example.com/auth/policy1");
|
||||
Assert.Equal("auth1", transaction.FindClaimValue(ClaimTypes.NameIdentifier, "auth1"));
|
||||
|
||||
transaction = await server.SendAsync("http://example.com/auth/auth1");
|
||||
Assert.Equal("auth1", transaction.FindClaimValue(ClaimTypes.NameIdentifier, "auth1"));
|
||||
|
||||
transaction = await server.SendAsync("http://example.com/auth/auth2");
|
||||
Assert.Equal("auth2", transaction.FindClaimValue(ClaimTypes.NameIdentifier, "auth2"));
|
||||
|
||||
transaction = await server.SendAsync("http://example.com/auth/auth3");
|
||||
Assert.Equal("auth3", transaction.FindClaimValue(ClaimTypes.NameIdentifier, "auth3"));
|
||||
|
||||
transaction = await server.SendAsync("http://example.com/auth/policy2");
|
||||
Assert.Equal("auth2", transaction.FindClaimValue(ClaimTypes.NameIdentifier, "auth2"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DefaultTargetSelectorWinsOverDefaultTarget()
|
||||
{
|
||||
var services = new ServiceCollection().AddOptions();
|
||||
|
||||
services.AddAuthentication(o =>
|
||||
{
|
||||
o.AddScheme<TestHandler>("auth1", "auth1");
|
||||
o.AddScheme<TestHandler2>("auth2", "auth2");
|
||||
})
|
||||
.AddVirtualScheme("forward", "forward", p => {
|
||||
p.Default = "auth2";
|
||||
p.DefaultSelector = ctx => "auth1";
|
||||
});
|
||||
|
||||
var handler1 = new TestHandler();
|
||||
services.AddSingleton(handler1);
|
||||
var handler2 = new TestHandler2();
|
||||
services.AddSingleton(handler2);
|
||||
|
||||
var sp = services.BuildServiceProvider();
|
||||
var context = new DefaultHttpContext();
|
||||
context.RequestServices = sp;
|
||||
|
||||
Assert.Equal(0, handler1.AuthenticateCount);
|
||||
Assert.Equal(0, handler1.ForbidCount);
|
||||
Assert.Equal(0, handler1.ChallengeCount);
|
||||
Assert.Equal(0, handler1.SignInCount);
|
||||
Assert.Equal(0, handler1.SignOutCount);
|
||||
Assert.Equal(0, handler2.AuthenticateCount);
|
||||
Assert.Equal(0, handler2.ForbidCount);
|
||||
Assert.Equal(0, handler2.ChallengeCount);
|
||||
Assert.Equal(0, handler2.SignInCount);
|
||||
Assert.Equal(0, handler2.SignOutCount);
|
||||
|
||||
await context.AuthenticateAsync("forward");
|
||||
Assert.Equal(1, handler1.AuthenticateCount);
|
||||
Assert.Equal(0, handler2.AuthenticateCount);
|
||||
|
||||
await context.ForbidAsync("forward");
|
||||
Assert.Equal(1, handler1.ForbidCount);
|
||||
Assert.Equal(0, handler2.ForbidCount);
|
||||
|
||||
await context.ChallengeAsync("forward");
|
||||
Assert.Equal(1, handler1.ChallengeCount);
|
||||
Assert.Equal(0, handler2.ChallengeCount);
|
||||
|
||||
await context.SignOutAsync("forward");
|
||||
Assert.Equal(1, handler1.SignOutCount);
|
||||
Assert.Equal(0, handler2.SignOutCount);
|
||||
|
||||
await context.SignInAsync("forward", new ClaimsPrincipal());
|
||||
Assert.Equal(1, handler1.SignInCount);
|
||||
Assert.Equal(0, handler2.SignInCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task NullDefaultTargetSelectorFallsBacktoDefaultTarget()
|
||||
{
|
||||
var services = new ServiceCollection().AddOptions();
|
||||
|
||||
services.AddAuthentication(o =>
|
||||
{
|
||||
o.AddScheme<TestHandler>("auth1", "auth1");
|
||||
o.AddScheme<TestHandler2>("auth2", "auth2");
|
||||
})
|
||||
.AddVirtualScheme("forward", "forward", p => {
|
||||
p.Default = "auth1";
|
||||
p.DefaultSelector = ctx => null;
|
||||
});
|
||||
|
||||
var handler1 = new TestHandler();
|
||||
services.AddSingleton(handler1);
|
||||
var handler2 = new TestHandler2();
|
||||
services.AddSingleton(handler2);
|
||||
|
||||
var sp = services.BuildServiceProvider();
|
||||
var context = new DefaultHttpContext();
|
||||
context.RequestServices = sp;
|
||||
|
||||
Assert.Equal(0, handler1.AuthenticateCount);
|
||||
Assert.Equal(0, handler1.ForbidCount);
|
||||
Assert.Equal(0, handler1.ChallengeCount);
|
||||
Assert.Equal(0, handler1.SignInCount);
|
||||
Assert.Equal(0, handler1.SignOutCount);
|
||||
Assert.Equal(0, handler2.AuthenticateCount);
|
||||
Assert.Equal(0, handler2.ForbidCount);
|
||||
Assert.Equal(0, handler2.ChallengeCount);
|
||||
Assert.Equal(0, handler2.SignInCount);
|
||||
Assert.Equal(0, handler2.SignOutCount);
|
||||
|
||||
await context.AuthenticateAsync("forward");
|
||||
Assert.Equal(1, handler1.AuthenticateCount);
|
||||
Assert.Equal(0, handler2.AuthenticateCount);
|
||||
|
||||
await context.ForbidAsync("forward");
|
||||
Assert.Equal(1, handler1.ForbidCount);
|
||||
Assert.Equal(0, handler2.ForbidCount);
|
||||
|
||||
await context.ChallengeAsync("forward");
|
||||
Assert.Equal(1, handler1.ChallengeCount);
|
||||
Assert.Equal(0, handler2.ChallengeCount);
|
||||
|
||||
await context.SignOutAsync("forward");
|
||||
Assert.Equal(1, handler1.SignOutCount);
|
||||
Assert.Equal(0, handler2.SignOutCount);
|
||||
|
||||
await context.SignInAsync("forward", new ClaimsPrincipal());
|
||||
Assert.Equal(1, handler1.SignInCount);
|
||||
Assert.Equal(0, handler2.SignInCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SpecificTargetAlwaysWinsOverDefaultTarget()
|
||||
{
|
||||
var services = new ServiceCollection().AddOptions();
|
||||
|
||||
services.AddAuthentication(o =>
|
||||
{
|
||||
o.AddScheme<TestHandler>("auth1", "auth1");
|
||||
o.AddScheme<TestHandler2>("auth2", "auth2");
|
||||
})
|
||||
.AddVirtualScheme("forward", "forward", p => {
|
||||
p.Default = "auth2";
|
||||
p.DefaultSelector = ctx => "auth2";
|
||||
p.Authenticate = "auth1";
|
||||
p.SignIn = "auth1";
|
||||
p.SignOut = "auth1";
|
||||
p.Forbid = "auth1";
|
||||
p.Challenge = "auth1";
|
||||
});
|
||||
|
||||
var handler1 = new TestHandler();
|
||||
services.AddSingleton(handler1);
|
||||
var handler2 = new TestHandler2();
|
||||
services.AddSingleton(handler2);
|
||||
|
||||
var sp = services.BuildServiceProvider();
|
||||
var context = new DefaultHttpContext();
|
||||
context.RequestServices = sp;
|
||||
|
||||
Assert.Equal(0, handler1.AuthenticateCount);
|
||||
Assert.Equal(0, handler1.ForbidCount);
|
||||
Assert.Equal(0, handler1.ChallengeCount);
|
||||
Assert.Equal(0, handler1.SignInCount);
|
||||
Assert.Equal(0, handler1.SignOutCount);
|
||||
Assert.Equal(0, handler2.AuthenticateCount);
|
||||
Assert.Equal(0, handler2.ForbidCount);
|
||||
Assert.Equal(0, handler2.ChallengeCount);
|
||||
Assert.Equal(0, handler2.SignInCount);
|
||||
Assert.Equal(0, handler2.SignOutCount);
|
||||
|
||||
await context.AuthenticateAsync("forward");
|
||||
Assert.Equal(1, handler1.AuthenticateCount);
|
||||
Assert.Equal(0, handler2.AuthenticateCount);
|
||||
|
||||
await context.ForbidAsync("forward");
|
||||
Assert.Equal(1, handler1.ForbidCount);
|
||||
Assert.Equal(0, handler2.ForbidCount);
|
||||
|
||||
await context.ChallengeAsync("forward");
|
||||
Assert.Equal(1, handler1.ChallengeCount);
|
||||
Assert.Equal(0, handler2.ChallengeCount);
|
||||
|
||||
await context.SignOutAsync("forward");
|
||||
Assert.Equal(1, handler1.SignOutCount);
|
||||
Assert.Equal(0, handler2.SignOutCount);
|
||||
|
||||
await context.SignInAsync("forward", new ClaimsPrincipal());
|
||||
Assert.Equal(1, handler1.SignInCount);
|
||||
Assert.Equal(0, handler2.SignInCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task VirtualSchemeTargetsForwardWithDefaultTarget()
|
||||
{
|
||||
var services = new ServiceCollection().AddOptions();
|
||||
|
||||
services.AddAuthentication(o =>
|
||||
{
|
||||
o.AddScheme<TestHandler>("auth1", "auth1");
|
||||
o.AddScheme<TestHandler2>("auth2", "auth2");
|
||||
})
|
||||
.AddVirtualScheme("forward", "forward", p => p.Default = "auth1");
|
||||
|
||||
var handler1 = new TestHandler();
|
||||
services.AddSingleton(handler1);
|
||||
var handler2 = new TestHandler2();
|
||||
services.AddSingleton(handler2);
|
||||
|
||||
var sp = services.BuildServiceProvider();
|
||||
var context = new DefaultHttpContext();
|
||||
context.RequestServices = sp;
|
||||
|
||||
Assert.Equal(0, handler1.AuthenticateCount);
|
||||
Assert.Equal(0, handler1.ForbidCount);
|
||||
Assert.Equal(0, handler1.ChallengeCount);
|
||||
Assert.Equal(0, handler1.SignInCount);
|
||||
Assert.Equal(0, handler1.SignOutCount);
|
||||
Assert.Equal(0, handler2.AuthenticateCount);
|
||||
Assert.Equal(0, handler2.ForbidCount);
|
||||
Assert.Equal(0, handler2.ChallengeCount);
|
||||
Assert.Equal(0, handler2.SignInCount);
|
||||
Assert.Equal(0, handler2.SignOutCount);
|
||||
|
||||
await context.AuthenticateAsync("forward");
|
||||
Assert.Equal(1, handler1.AuthenticateCount);
|
||||
Assert.Equal(0, handler2.AuthenticateCount);
|
||||
|
||||
await context.ForbidAsync("forward");
|
||||
Assert.Equal(1, handler1.ForbidCount);
|
||||
Assert.Equal(0, handler2.ForbidCount);
|
||||
|
||||
await context.ChallengeAsync("forward");
|
||||
Assert.Equal(1, handler1.ChallengeCount);
|
||||
Assert.Equal(0, handler2.ChallengeCount);
|
||||
|
||||
await context.SignOutAsync("forward");
|
||||
Assert.Equal(1, handler1.SignOutCount);
|
||||
Assert.Equal(0, handler2.SignOutCount);
|
||||
|
||||
await context.SignInAsync("forward", new ClaimsPrincipal());
|
||||
Assert.Equal(1, handler1.SignInCount);
|
||||
Assert.Equal(0, handler2.SignInCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task VirtualSchemeTargetsOverrideDefaultTarget()
|
||||
{
|
||||
var services = new ServiceCollection().AddOptions();
|
||||
|
||||
services.AddAuthentication(o =>
|
||||
{
|
||||
o.AddScheme<TestHandler>("auth1", "auth1");
|
||||
o.AddScheme<TestHandler2>("auth2", "auth2");
|
||||
})
|
||||
.AddVirtualScheme("forward", "forward", p =>
|
||||
{
|
||||
p.Default = "auth1";
|
||||
p.Challenge = "auth2";
|
||||
p.SignIn = "auth2";
|
||||
});
|
||||
|
||||
var handler1 = new TestHandler();
|
||||
services.AddSingleton(handler1);
|
||||
var handler2 = new TestHandler2();
|
||||
services.AddSingleton(handler2);
|
||||
|
||||
var sp = services.BuildServiceProvider();
|
||||
var context = new DefaultHttpContext();
|
||||
context.RequestServices = sp;
|
||||
|
||||
Assert.Equal(0, handler1.AuthenticateCount);
|
||||
Assert.Equal(0, handler1.ForbidCount);
|
||||
Assert.Equal(0, handler1.ChallengeCount);
|
||||
Assert.Equal(0, handler1.SignInCount);
|
||||
Assert.Equal(0, handler1.SignOutCount);
|
||||
Assert.Equal(0, handler2.AuthenticateCount);
|
||||
Assert.Equal(0, handler2.ForbidCount);
|
||||
Assert.Equal(0, handler2.ChallengeCount);
|
||||
Assert.Equal(0, handler2.SignInCount);
|
||||
Assert.Equal(0, handler2.SignOutCount);
|
||||
|
||||
await context.AuthenticateAsync("forward");
|
||||
Assert.Equal(1, handler1.AuthenticateCount);
|
||||
Assert.Equal(0, handler2.AuthenticateCount);
|
||||
|
||||
await context.ForbidAsync("forward");
|
||||
Assert.Equal(1, handler1.ForbidCount);
|
||||
Assert.Equal(0, handler2.ForbidCount);
|
||||
|
||||
await context.ChallengeAsync("forward");
|
||||
Assert.Equal(0, handler1.ChallengeCount);
|
||||
Assert.Equal(1, handler2.ChallengeCount);
|
||||
|
||||
await context.SignOutAsync("forward");
|
||||
Assert.Equal(1, handler1.SignOutCount);
|
||||
Assert.Equal(0, handler2.SignOutCount);
|
||||
|
||||
await context.SignInAsync("forward", new ClaimsPrincipal());
|
||||
Assert.Equal(0, handler1.SignInCount);
|
||||
Assert.Equal(1, handler2.SignInCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanDynamicTargetBasedOnQueryString()
|
||||
{
|
||||
var server = CreateServer(services =>
|
||||
{
|
||||
services.AddAuthentication(o =>
|
||||
{
|
||||
o.AddScheme<TestHandler>("auth1", "auth1");
|
||||
o.AddScheme<TestHandler>("auth2", "auth2");
|
||||
o.AddScheme<TestHandler>("auth3", "auth3");
|
||||
})
|
||||
.AddVirtualScheme("dynamic", "dynamic", p =>
|
||||
{
|
||||
p.DefaultSelector = c => c.Request.QueryString.Value.Substring(1);
|
||||
});
|
||||
});
|
||||
|
||||
var transaction = await server.SendAsync("http://example.com/auth/dynamic?auth1");
|
||||
Assert.Equal("auth1", transaction.FindClaimValue(ClaimTypes.NameIdentifier, "auth1"));
|
||||
transaction = await server.SendAsync("http://example.com/auth/dynamic?auth2");
|
||||
Assert.Equal("auth2", transaction.FindClaimValue(ClaimTypes.NameIdentifier, "auth2"));
|
||||
transaction = await server.SendAsync("http://example.com/auth/dynamic?auth3");
|
||||
Assert.Equal("auth3", transaction.FindClaimValue(ClaimTypes.NameIdentifier, "auth3"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TargetsDefaultSchemeByDefault()
|
||||
{
|
||||
var server = CreateServer(services =>
|
||||
{
|
||||
services.AddAuthentication(o =>
|
||||
{
|
||||
o.DefaultScheme = "default";
|
||||
o.AddScheme<TestHandler>("default", "default");
|
||||
})
|
||||
.AddVirtualScheme("virtual", "virtual", p => { });
|
||||
});
|
||||
|
||||
var transaction = await server.SendAsync("http://example.com/auth/virtual");
|
||||
Assert.Equal("default", transaction.FindClaimValue(ClaimTypes.NameIdentifier, "default"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TargetsDefaultSchemeThrowsWithNoDefault()
|
||||
{
|
||||
var server = CreateServer(services =>
|
||||
{
|
||||
services.AddAuthentication(o =>
|
||||
{
|
||||
o.AddScheme<TestHandler>("default", "default");
|
||||
})
|
||||
.AddVirtualScheme("virtual", "virtual", p => { });
|
||||
});
|
||||
|
||||
var error = await Assert.ThrowsAsync<InvalidOperationException>(() => server.SendAsync("http://example.com/auth/virtual"));
|
||||
Assert.Contains("No authenticationScheme was specified", error.Message);
|
||||
}
|
||||
|
||||
private class TestHandler : IAuthenticationSignInHandler
|
||||
{
|
||||
public AuthenticationScheme Scheme { get; set; }
|
||||
public int SignInCount { get; set; }
|
||||
public int SignOutCount { get; set; }
|
||||
public int ForbidCount { get; set; }
|
||||
public int ChallengeCount { get; set; }
|
||||
public int AuthenticateCount { get; set; }
|
||||
|
||||
public Task<AuthenticateResult> AuthenticateAsync()
|
||||
{
|
||||
AuthenticateCount++;
|
||||
var principal = new ClaimsPrincipal();
|
||||
var id = new ClaimsIdentity();
|
||||
id.AddClaim(new Claim(ClaimTypes.NameIdentifier, Scheme.Name, ClaimValueTypes.String, Scheme.Name));
|
||||
principal.AddIdentity(id);
|
||||
return Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(principal, new AuthenticationProperties(), Scheme.Name)));
|
||||
}
|
||||
|
||||
public Task ChallengeAsync(AuthenticationProperties properties)
|
||||
{
|
||||
ChallengeCount++;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task ForbidAsync(AuthenticationProperties properties)
|
||||
{
|
||||
ForbidCount++;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context)
|
||||
{
|
||||
Scheme = scheme;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task SignInAsync(ClaimsPrincipal user, AuthenticationProperties properties)
|
||||
{
|
||||
SignInCount++;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task SignOutAsync(AuthenticationProperties properties)
|
||||
{
|
||||
SignOutCount++;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
private class TestHandler2 : IAuthenticationSignInHandler
|
||||
{
|
||||
public AuthenticationScheme Scheme { get; set; }
|
||||
public int SignInCount { get; set; }
|
||||
public int SignOutCount { get; set; }
|
||||
public int ForbidCount { get; set; }
|
||||
public int ChallengeCount { get; set; }
|
||||
public int AuthenticateCount { get; set; }
|
||||
|
||||
public Task<AuthenticateResult> AuthenticateAsync()
|
||||
{
|
||||
AuthenticateCount++;
|
||||
var principal = new ClaimsPrincipal();
|
||||
var id = new ClaimsIdentity();
|
||||
id.AddClaim(new Claim(ClaimTypes.NameIdentifier, Scheme.Name, ClaimValueTypes.String, Scheme.Name));
|
||||
principal.AddIdentity(id);
|
||||
return Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(principal, new AuthenticationProperties(), Scheme.Name)));
|
||||
}
|
||||
|
||||
public Task ChallengeAsync(AuthenticationProperties properties)
|
||||
{
|
||||
ChallengeCount++;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task ForbidAsync(AuthenticationProperties properties)
|
||||
{
|
||||
ForbidCount++;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context)
|
||||
{
|
||||
Scheme = scheme;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task SignInAsync(ClaimsPrincipal user, AuthenticationProperties properties)
|
||||
{
|
||||
SignInCount++;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task SignOutAsync(AuthenticationProperties properties)
|
||||
{
|
||||
SignOutCount++;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
private static TestServer CreateServer(Action<IServiceCollection> configure = null, string defaultScheme = null)
|
||||
{
|
||||
var builder = new WebHostBuilder()
|
||||
.Configure(app =>
|
||||
{
|
||||
app.UseAuthentication();
|
||||
app.Use(async (context, next) =>
|
||||
{
|
||||
var req = context.Request;
|
||||
var res = context.Response;
|
||||
if (req.Path.StartsWithSegments(new PathString("/auth"), out var remainder))
|
||||
{
|
||||
var name = (remainder.Value.Length > 0) ? remainder.Value.Substring(1) : null;
|
||||
var result = await context.AuthenticateAsync(name);
|
||||
res.Describe(result?.Ticket?.Principal);
|
||||
}
|
||||
else
|
||||
{
|
||||
await next();
|
||||
}
|
||||
});
|
||||
})
|
||||
.ConfigureServices(services =>
|
||||
{
|
||||
configure?.Invoke(services);
|
||||
});
|
||||
return new TestServer(builder);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue