Auth API changes (Async, ChallengeBehavior)

This commit is contained in:
Hao Kung 2015-06-25 17:03:10 -07:00
parent 641a7fb82b
commit 5fe8037281
13 changed files with 139 additions and 169 deletions

View File

@ -1,44 +0,0 @@
// 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.Security.Claims;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Http.Authentication
{
/// <summary>
/// Acts as the return value from calls to the IAuthenticationManager's AuthenticeAsync methods.
/// </summary>
public class AuthenticationResult
{
/// <summary>
/// Create an instance of the result object
/// </summary>
/// <param name="identity">Assigned to Identity. May be null.</param>
/// <param name="properties">Assigned to Properties. Contains extra information carried along with the identity.</param>
/// <param name="description">Assigned to Description. Contains information describing the authentication provider.</param>
public AuthenticationResult(ClaimsPrincipal principal, [NotNull] AuthenticationProperties properties, [NotNull] AuthenticationDescription description)
{
Principal = principal;
Properties = properties;
Description = description;
}
/// <summary>
/// Contains the claims that were authenticated by the given AuthenticationScheme. If the authentication
/// scheme was not successful the Identity property will be null.
/// </summary>
public ClaimsPrincipal Principal { get; private set; }
/// <summary>
/// Contains extra values that were provided with the original SignIn call.
/// </summary>
public AuthenticationProperties Properties { get; private set; }
/// <summary>
/// Contains description properties for the middleware authentication type in general. Does not
/// vary per request.
/// </summary>
public AuthenticationDescription Description { get; private set; }
}
}

View File

@ -4,6 +4,8 @@
using System.Collections.Generic;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNet.Http.Features.Authentication;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Http.Authentication
{
@ -11,41 +13,61 @@ namespace Microsoft.AspNet.Http.Authentication
{
public abstract IEnumerable<AuthenticationDescription> GetAuthenticationSchemes();
public abstract AuthenticationResult Authenticate(string authenticationScheme);
public abstract Task AuthenticateAsync([NotNull] AuthenticateContext context);
public abstract Task<AuthenticationResult> AuthenticateAsync(string authenticationScheme);
public virtual void Challenge()
public virtual async Task<ClaimsPrincipal> AuthenticateAsync([NotNull] string authenticationScheme)
{
Challenge(authenticationScheme: null, properties: null);
var context = new AuthenticateContext(authenticationScheme);
await AuthenticateAsync(context);
return context.Principal;
}
public virtual void Challenge(AuthenticationProperties properties)
public virtual Task ChallengeAsync()
{
Challenge(authenticationScheme: null, properties: properties);
return ChallengeAsync(properties: null);
}
public virtual void Challenge(string authenticationScheme)
public virtual Task ChallengeAsync(AuthenticationProperties properties)
{
Challenge(authenticationScheme: authenticationScheme, properties: null);
return ChallengeAsync(authenticationScheme: string.Empty, properties: properties);
}
public abstract void Challenge(string authenticationScheme, AuthenticationProperties properties);
public void SignIn(string authenticationScheme, ClaimsPrincipal principal)
public virtual Task ChallengeAsync([NotNull] string authenticationScheme)
{
SignIn(authenticationScheme, principal, properties: null);
return ChallengeAsync(authenticationScheme: authenticationScheme, properties: null);
}
public abstract void SignIn(string authenticationScheme, ClaimsPrincipal principal, AuthenticationProperties properties);
public virtual void SignOut()
// Leave it up to authentication handler to do the right thing for the challenge
public virtual Task ChallengeAsync([NotNull] string authenticationScheme, AuthenticationProperties properties)
{
SignOut(authenticationScheme: null, properties: null);
return ChallengeAsync(authenticationScheme, properties, ChallengeBehavior.Automatic);
}
public abstract void SignOut(string authenticationScheme);
public virtual Task SignInAsync([NotNull] string authenticationScheme, [NotNull] ClaimsPrincipal principal)
{
return SignInAsync(authenticationScheme, principal, properties: null);
}
public abstract void SignOut(string authenticationScheme, AuthenticationProperties properties);
public virtual Task ForbidAsync([NotNull] string authenticationScheme)
{
return ForbidAsync(authenticationScheme, properties: null);
}
// Deny access (typically a 403)
public virtual Task ForbidAsync([NotNull] string authenticationScheme, AuthenticationProperties properties)
{
return ChallengeAsync(authenticationScheme, properties, ChallengeBehavior.Forbidden);
}
public abstract Task ChallengeAsync([NotNull] string authenticationScheme, AuthenticationProperties properties, ChallengeBehavior behavior);
public abstract Task SignInAsync([NotNull] string authenticationScheme, [NotNull] ClaimsPrincipal principal, AuthenticationProperties properties);
public virtual Task SignOutAsync([NotNull] string authenticationScheme)
{
return SignOutAsync(authenticationScheme, properties: null);
}
public abstract Task SignOutAsync([NotNull] string authenticationScheme, AuthenticationProperties properties);
}
}

View File

@ -3,11 +3,20 @@
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Http
{
public abstract class HttpResponse
{
private static readonly Func<object, Task> _callbackDelegate = callback => ((Func<Task>)callback)();
private static readonly Func<object, Task> _disposeDelegate = disposable =>
{
((IDisposable)disposable).Dispose();
return Task.FromResult(0);
};
public abstract HttpContext HttpContext { get; }
public abstract int StatusCode { get; set; }
@ -24,14 +33,17 @@ namespace Microsoft.AspNet.Http
public abstract bool HasStarted { get; }
public abstract void OnResponseStarting(Action<object> callback, object state);
public abstract void OnStarting([NotNull] Func<object, Task> callback, object state);
public abstract void OnResponseCompleted(Action<object> callback, object state);
public virtual void OnStarting([NotNull] Func<Task> callback) => OnStarting(_callbackDelegate, callback);
public virtual void Redirect(string location)
{
Redirect(location, permanent: false);
}
public abstract void OnCompleted([NotNull] Func<object, Task> callback, object state);
public virtual void OnCompletedDispose([NotNull] IDisposable disposable) => OnCompleted(_disposeDelegate, disposable);
public virtual void OnCompleted([NotNull] Func<Task> callback) => OnCompleted(_callbackDelegate, callback);
public virtual void Redirect(string location) => Redirect(location, permanent: false);
public abstract void Redirect(string location, bool permanent);
}

View File

@ -0,0 +1,12 @@
// 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.
namespace Microsoft.AspNet.Http.Features.Authentication
{
public enum ChallengeBehavior
{
Automatic,
Unauthorized,
Forbidden
}
}

View File

@ -3,19 +3,27 @@
using System;
using System.Collections.Generic;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Http.Features.Authentication
{
public class ChallengeContext
{
public ChallengeContext(string authenticationScheme, IDictionary<string, string> properties)
public ChallengeContext([NotNull] string authenticationScheme) : this(authenticationScheme, properties: null, behavior: ChallengeBehavior.Automatic)
{
}
public ChallengeContext([NotNull] string authenticationScheme, IDictionary<string, string> properties, ChallengeBehavior behavior)
{
AuthenticationScheme = authenticationScheme;
Properties = properties ?? new Dictionary<string, string>(StringComparer.Ordinal);
Behavior = behavior;
}
public string AuthenticationScheme { get; }
public ChallengeBehavior Behavior { get; }
public IDictionary<string, string> Properties { get; }
public bool Accepted { get; private set; }

View File

@ -9,14 +9,12 @@ namespace Microsoft.AspNet.Http.Features.Authentication
{
void GetDescriptions(DescribeSchemesContext context);
void Authenticate(AuthenticateContext context);
Task AuthenticateAsync(AuthenticateContext context);
void Challenge(ChallengeContext context);
Task ChallengeAsync(ChallengeContext context);
void SignIn(SignInContext context);
Task SignInAsync(SignInContext context);
void SignOut(SignOutContext context);
Task SignOutAsync(SignOutContext context);
}
}

View File

@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
namespace Microsoft.AspNet.Http.Features
{
@ -14,7 +15,7 @@ namespace Microsoft.AspNet.Http.Features
IDictionary<string, string[]> Headers { get; set; }
Stream Body { get; set; }
bool HasStarted { get; }
void OnResponseStarting(Action<object> callback, object state);
void OnResponseCompleted(Action<object> callback, object state);
void OnStarting(Func<object, Task> callback, object state);
void OnCompleted(Func<object, Task> callback, object state);
}
}

View File

@ -48,66 +48,29 @@ namespace Microsoft.AspNet.Http.Authentication.Internal
return describeContext.Results.Select(description => new AuthenticationDescription(description));
}
public override AuthenticationResult Authenticate([NotNull] string authenticationScheme)
public override async Task AuthenticateAsync([NotNull] AuthenticateContext context)
{
var handler = HttpAuthenticationFeature.Handler;
var authenticateContext = new AuthenticateContext(authenticationScheme);
if (handler != null)
{
handler.Authenticate(authenticateContext);
await handler.AuthenticateAsync(context);
}
if (!authenticateContext.Accepted)
if (!context.Accepted)
{
throw new InvalidOperationException($"The following authentication scheme was not accepted: {authenticationScheme}");
throw new InvalidOperationException($"The following authentication scheme was not accepted: {context.AuthenticationScheme}");
}
if (authenticateContext.Principal == null)
{
return null;
}
return new AuthenticationResult(authenticateContext.Principal,
new AuthenticationProperties(authenticateContext.Properties),
new AuthenticationDescription(authenticateContext.Description));
}
public override async Task<AuthenticationResult> AuthenticateAsync([NotNull] string authenticationScheme)
public override async Task ChallengeAsync([NotNull] string authenticationScheme, AuthenticationProperties properties, ChallengeBehavior behavior)
{
var handler = HttpAuthenticationFeature.Handler;
var authenticateContext = new AuthenticateContext(authenticationScheme);
var challengeContext = new ChallengeContext(authenticationScheme, properties?.Items, behavior);
if (handler != null)
{
await handler.AuthenticateAsync(authenticateContext);
}
// Verify all types ack'd
if (!authenticateContext.Accepted)
{
throw new InvalidOperationException($"The following authentication scheme was not accepted: {authenticationScheme}");
}
if (authenticateContext.Principal == null)
{
return null;
}
return new AuthenticationResult(authenticateContext.Principal,
new AuthenticationProperties(authenticateContext.Properties),
new AuthenticationDescription(authenticateContext.Description));
}
public override void Challenge(string authenticationScheme, AuthenticationProperties properties)
{
HttpResponseFeature.StatusCode = 401;
var handler = HttpAuthenticationFeature.Handler;
var challengeContext = new ChallengeContext(authenticationScheme, properties?.Items);
if (handler != null)
{
handler.Challenge(challengeContext);
await handler.ChallengeAsync(challengeContext);
}
// The default Challenge with no scheme is always accepted
@ -117,43 +80,36 @@ namespace Microsoft.AspNet.Http.Authentication.Internal
}
}
public override void SignIn([NotNull] string authenticationScheme, [NotNull] ClaimsPrincipal principal, AuthenticationProperties properties)
public override async Task SignInAsync([NotNull] string authenticationScheme, [NotNull] ClaimsPrincipal principal, AuthenticationProperties properties)
{
var handler = HttpAuthenticationFeature.Handler;
var signInContext = new SignInContext(authenticationScheme, principal, properties?.Items);
if (handler != null)
{
handler.SignIn(signInContext);
await handler.SignInAsync(signInContext);
}
// Verify all types ack'd
if (!signInContext.Accepted)
{
throw new InvalidOperationException($"The following authentication scheme was not accepted: {authenticationScheme}");
}
}
public override void SignOut(string authenticationScheme, AuthenticationProperties properties)
public override async Task SignOutAsync([NotNull] string authenticationScheme, AuthenticationProperties properties)
{
var handler = HttpAuthenticationFeature.Handler;
var signOutContext = new SignOutContext(authenticationScheme, properties?.Items);
if (handler != null)
{
handler.SignOut(signOutContext);
await handler.SignOutAsync(signOutContext);
}
// Verify all types ack'd
if (!string.IsNullOrWhiteSpace(authenticationScheme) && !signOutContext.Accepted)
{
throw new InvalidOperationException($"The following authentication scheme was not accepted: {authenticationScheme}");
}
}
public override void SignOut(string authenticationScheme)
{
SignOut(authenticationScheme, properties: null);
}
}
}

View File

@ -3,6 +3,7 @@
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNet.FeatureModel;
using Microsoft.AspNet.Http.Features;
using Microsoft.AspNet.Http.Features.Internal;
@ -94,14 +95,14 @@ namespace Microsoft.AspNet.Http.Internal
get { return HttpResponseFeature.HasStarted; }
}
public override void OnResponseStarting(Action<object> callback, object state)
public override void OnStarting(Func<object, Task> callback, object state)
{
HttpResponseFeature.OnResponseStarting(callback, state);
HttpResponseFeature.OnStarting(callback, state);
}
public override void OnResponseCompleted(Action<object> callback, object state)
public override void OnCompleted(Func<object, Task> callback, object state)
{
HttpResponseFeature.OnResponseCompleted(callback, state);
HttpResponseFeature.OnCompleted(callback, state);
}
public override void Redirect(string location, bool permanent)

View File

@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
namespace Microsoft.AspNet.Http.Features.Internal
{
@ -29,14 +30,14 @@ namespace Microsoft.AspNet.Http.Features.Internal
get { return false; }
}
public void OnResponseStarting(Action<object> callback, object state)
public void OnStarting(Func<object, Task> callback, object state)
{
throw new NotSupportedException();
throw new NotImplementedException();
}
public void OnResponseCompleted(Action<object> callback, object state)
public void OnCompleted(Func<object, Task> callback, object state)
{
throw new NotSupportedException();
throw new NotImplementedException();
}
}
}

View File

@ -62,7 +62,15 @@ namespace Microsoft.AspNet.Owin
{ OwinConstants.ResponseReasonPhrase, new FeatureMap<IHttpResponseFeature>(feature => feature.ReasonPhrase, (feature, value) => feature.ReasonPhrase = Convert.ToString(value)) },
{ OwinConstants.ResponseHeaders, new FeatureMap<IHttpResponseFeature>(feature => feature.Headers, (feature, value) => feature.Headers = (IDictionary<string, string[]>)value) },
{ OwinConstants.ResponseBody, new FeatureMap<IHttpResponseFeature>(feature => feature.Body, () => Stream.Null, (feature, value) => feature.Body = (Stream)value) },
{ OwinConstants.CommonKeys.OnSendingHeaders, new FeatureMap<IHttpResponseFeature>(feature => new Action<Action<object>, object>(feature.OnResponseStarting)) },
{ OwinConstants.CommonKeys.OnSendingHeaders, new FeatureMap<IHttpResponseFeature>(
feature => new Action<Action<object>, object>((cb, state) => {
feature.OnStarting(s =>
{
cb(s);
return Task.FromResult(0);
}, state);
}))
},
{ OwinConstants.CommonKeys.LocalPort, new FeatureMap<IHttpConnectionFeature>(feature => feature.LocalPort.ToString(CultureInfo.InvariantCulture),
(feature, value) => feature.LocalPort = Convert.ToInt32(value, CultureInfo.InvariantCulture)) },

View File

@ -147,17 +147,19 @@ namespace Microsoft.AspNet.Owin
get { return _headersSent; }
}
void IHttpResponseFeature.OnResponseStarting(Action<object> callback, object state)
void IHttpResponseFeature.OnStarting(Func<object, Task> callback, object state)
{
var register = Prop<Action<Action<object>, object>>(OwinConstants.CommonKeys.OnSendingHeaders);
if (register == null)
{
throw new NotSupportedException(OwinConstants.CommonKeys.OnSendingHeaders);
}
register(callback, state);
// Need to block on the callback since we can't change the OWIN signature to be async
register(s => callback(s).GetAwaiter().GetResult(), state);
}
void IHttpResponseFeature.OnResponseCompleted(Action<object> callback, object state)
void IHttpResponseFeature.OnCompleted(Func<object, Task> callback, object state)
{
throw new NotSupportedException();
}

View File

@ -18,50 +18,46 @@ namespace Microsoft.AspNet.Http.Authentication.Internal
public async Task AuthenticateWithNoAuthMiddlewareThrows()
{
var context = CreateContext();
Assert.Throws<InvalidOperationException>(() => context.Authentication.Authenticate("Foo"));
await Assert.ThrowsAsync<InvalidOperationException>(async () => await context.Authentication.AuthenticateAsync("Foo"));
}
[Fact]
public void ChallengeWithNoAuthMiddlewareMayThrow()
public async Task ChallengeWithNoAuthMiddlewareMayThrow()
{
var context = CreateContext();
context.Authentication.Challenge();
Assert.Equal(401, context.Response.StatusCode);
Assert.Throws<InvalidOperationException>(() => context.Authentication.Challenge("Foo"));
await context.Authentication.ChallengeAsync();
Assert.Equal(200, context.Response.StatusCode);
await Assert.ThrowsAsync<InvalidOperationException>(() => context.Authentication.ChallengeAsync("Foo"));
}
[Fact]
public void SignInWithNoAuthMiddlewareThrows()
public async Task SignInWithNoAuthMiddlewareThrows()
{
var context = CreateContext();
Assert.Throws<InvalidOperationException>(() => context.Authentication.SignIn("Foo", new ClaimsPrincipal()));
await Assert.ThrowsAsync<InvalidOperationException>(() => context.Authentication.SignInAsync("Foo", new ClaimsPrincipal()));
}
[Fact]
public void SignOutWithNoAuthMiddlewareMayThrow()
public async Task SignOutWithNoAuthMiddlewareMayThrow()
{
var context = CreateContext();
context.Authentication.SignOut();
Assert.Throws<InvalidOperationException>(() => context.Authentication.SignOut("Foo"));
await Assert.ThrowsAsync<InvalidOperationException>(() => context.Authentication.SignOutAsync("Foo"));
}
[Fact]
public void SignInOutIn()
public async Task SignInOutIn()
{
var context = CreateContext();
var handler = new AuthHandler();
context.SetFeature<IHttpAuthenticationFeature>(new HttpAuthenticationFeature() { Handler = handler });
var user = new ClaimsPrincipal();
context.Authentication.SignIn("ignored", user);
await context.Authentication.SignInAsync("ignored", user);
Assert.True(handler.SignedIn);
context.Authentication.SignOut("ignored");
await context.Authentication.SignOutAsync("ignored");
Assert.False(handler.SignedIn);
context.Authentication.SignIn("ignored", user);
await context.Authentication.SignInAsync("ignored", user);
Assert.True(handler.SignedIn);
context.Authentication.SignOut("ignored", new AuthenticationProperties() { RedirectUri = "~/logout" });
await context.Authentication.SignOutAsync("ignored", new AuthenticationProperties() { RedirectUri = "~/logout" });
Assert.False(handler.SignedIn);
}
@ -69,17 +65,12 @@ namespace Microsoft.AspNet.Http.Authentication.Internal
{
public bool SignedIn { get; set; }
public void Authenticate(AuthenticateContext context)
{
throw new NotImplementedException();
}
public Task AuthenticateAsync(AuthenticateContext context)
{
throw new NotImplementedException();
}
public void Challenge(ChallengeContext context)
public Task ChallengeAsync(ChallengeContext context)
{
throw new NotImplementedException();
}
@ -89,16 +80,18 @@ namespace Microsoft.AspNet.Http.Authentication.Internal
throw new NotImplementedException();
}
public void SignIn(SignInContext context)
public Task SignInAsync(SignInContext context)
{
SignedIn = true;
context.Accept();
return Task.FromResult(0);
}
public void SignOut(SignOutContext context)
public Task SignOutAsync(SignOutContext context)
{
SignedIn = false;
context.Accept();
return Task.FromResult(0);
}
}