diff --git a/src/Microsoft.AspNet.Http.Abstractions/Authentication/AuthenticateResult.cs b/src/Microsoft.AspNet.Http.Abstractions/Authentication/AuthenticateResult.cs deleted file mode 100644 index 28060a24e2..0000000000 --- a/src/Microsoft.AspNet.Http.Abstractions/Authentication/AuthenticateResult.cs +++ /dev/null @@ -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 -{ - /// - /// Acts as the return value from calls to the IAuthenticationManager's AuthenticeAsync methods. - /// - public class AuthenticationResult - { - /// - /// Create an instance of the result object - /// - /// Assigned to Identity. May be null. - /// Assigned to Properties. Contains extra information carried along with the identity. - /// Assigned to Description. Contains information describing the authentication provider. - public AuthenticationResult(ClaimsPrincipal principal, [NotNull] AuthenticationProperties properties, [NotNull] AuthenticationDescription description) - { - Principal = principal; - Properties = properties; - Description = description; - } - - /// - /// Contains the claims that were authenticated by the given AuthenticationScheme. If the authentication - /// scheme was not successful the Identity property will be null. - /// - public ClaimsPrincipal Principal { get; private set; } - - /// - /// Contains extra values that were provided with the original SignIn call. - /// - public AuthenticationProperties Properties { get; private set; } - - /// - /// Contains description properties for the middleware authentication type in general. Does not - /// vary per request. - /// - public AuthenticationDescription Description { get; private set; } - } -} diff --git a/src/Microsoft.AspNet.Http.Abstractions/Authentication/AuthenticationManager.cs b/src/Microsoft.AspNet.Http.Abstractions/Authentication/AuthenticationManager.cs index 331c4c8fe1..e10e081717 100644 --- a/src/Microsoft.AspNet.Http.Abstractions/Authentication/AuthenticationManager.cs +++ b/src/Microsoft.AspNet.Http.Abstractions/Authentication/AuthenticationManager.cs @@ -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 GetAuthenticationSchemes(); - public abstract AuthenticationResult Authenticate(string authenticationScheme); + public abstract Task AuthenticateAsync([NotNull] AuthenticateContext context); - public abstract Task AuthenticateAsync(string authenticationScheme); - - public virtual void Challenge() + public virtual async Task 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); } } diff --git a/src/Microsoft.AspNet.Http.Abstractions/HttpResponse.cs b/src/Microsoft.AspNet.Http.Abstractions/HttpResponse.cs index a0a92fb823..914e9604c9 100644 --- a/src/Microsoft.AspNet.Http.Abstractions/HttpResponse.cs +++ b/src/Microsoft.AspNet.Http.Abstractions/HttpResponse.cs @@ -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 _callbackDelegate = callback => ((Func)callback)(); + private static readonly Func _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 callback, object state); + public abstract void OnStarting([NotNull] Func callback, object state); - public abstract void OnResponseCompleted(Action callback, object state); + public virtual void OnStarting([NotNull] Func callback) => OnStarting(_callbackDelegate, callback); - public virtual void Redirect(string location) - { - Redirect(location, permanent: false); - } + public abstract void OnCompleted([NotNull] Func callback, object state); + + public virtual void OnCompletedDispose([NotNull] IDisposable disposable) => OnCompleted(_disposeDelegate, disposable); + + public virtual void OnCompleted([NotNull] Func callback) => OnCompleted(_callbackDelegate, callback); + + public virtual void Redirect(string location) => Redirect(location, permanent: false); public abstract void Redirect(string location, bool permanent); } diff --git a/src/Microsoft.AspNet.Http.Features/Authentication/ChallengeBehavior.cs b/src/Microsoft.AspNet.Http.Features/Authentication/ChallengeBehavior.cs new file mode 100644 index 0000000000..1be42f700b --- /dev/null +++ b/src/Microsoft.AspNet.Http.Features/Authentication/ChallengeBehavior.cs @@ -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 + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Http.Features/Authentication/ChallengeContext.cs b/src/Microsoft.AspNet.Http.Features/Authentication/ChallengeContext.cs index 592086e83c..5dfcd16914 100644 --- a/src/Microsoft.AspNet.Http.Features/Authentication/ChallengeContext.cs +++ b/src/Microsoft.AspNet.Http.Features/Authentication/ChallengeContext.cs @@ -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 properties) + public ChallengeContext([NotNull] string authenticationScheme) : this(authenticationScheme, properties: null, behavior: ChallengeBehavior.Automatic) + { + } + + public ChallengeContext([NotNull] string authenticationScheme, IDictionary properties, ChallengeBehavior behavior) { AuthenticationScheme = authenticationScheme; Properties = properties ?? new Dictionary(StringComparer.Ordinal); + Behavior = behavior; } public string AuthenticationScheme { get; } + public ChallengeBehavior Behavior { get; } + public IDictionary Properties { get; } public bool Accepted { get; private set; } diff --git a/src/Microsoft.AspNet.Http.Features/Authentication/IAuthenticationHandler.cs b/src/Microsoft.AspNet.Http.Features/Authentication/IAuthenticationHandler.cs index 9f551b8bdd..155cd87380 100644 --- a/src/Microsoft.AspNet.Http.Features/Authentication/IAuthenticationHandler.cs +++ b/src/Microsoft.AspNet.Http.Features/Authentication/IAuthenticationHandler.cs @@ -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); } } diff --git a/src/Microsoft.AspNet.Http.Features/IHttpResponseFeature.cs b/src/Microsoft.AspNet.Http.Features/IHttpResponseFeature.cs index 9057a9991f..d530718b17 100644 --- a/src/Microsoft.AspNet.Http.Features/IHttpResponseFeature.cs +++ b/src/Microsoft.AspNet.Http.Features/IHttpResponseFeature.cs @@ -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 Headers { get; set; } Stream Body { get; set; } bool HasStarted { get; } - void OnResponseStarting(Action callback, object state); - void OnResponseCompleted(Action callback, object state); + void OnStarting(Func callback, object state); + void OnCompleted(Func callback, object state); } } diff --git a/src/Microsoft.AspNet.Http/Authentication/DefaultAuthenticationManager.cs b/src/Microsoft.AspNet.Http/Authentication/DefaultAuthenticationManager.cs index 9f534a6ada..526dfea4f3 100644 --- a/src/Microsoft.AspNet.Http/Authentication/DefaultAuthenticationManager.cs +++ b/src/Microsoft.AspNet.Http/Authentication/DefaultAuthenticationManager.cs @@ -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 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); - } } } diff --git a/src/Microsoft.AspNet.Http/DefaultHttpResponse.cs b/src/Microsoft.AspNet.Http/DefaultHttpResponse.cs index 2f38ac9cbe..cf3b006945 100644 --- a/src/Microsoft.AspNet.Http/DefaultHttpResponse.cs +++ b/src/Microsoft.AspNet.Http/DefaultHttpResponse.cs @@ -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 callback, object state) + public override void OnStarting(Func callback, object state) { - HttpResponseFeature.OnResponseStarting(callback, state); + HttpResponseFeature.OnStarting(callback, state); } - public override void OnResponseCompleted(Action callback, object state) + public override void OnCompleted(Func callback, object state) { - HttpResponseFeature.OnResponseCompleted(callback, state); + HttpResponseFeature.OnCompleted(callback, state); } public override void Redirect(string location, bool permanent) diff --git a/src/Microsoft.AspNet.Http/Features/HttpResponseFeature.cs b/src/Microsoft.AspNet.Http/Features/HttpResponseFeature.cs index 5e32a938be..84f8196f93 100644 --- a/src/Microsoft.AspNet.Http/Features/HttpResponseFeature.cs +++ b/src/Microsoft.AspNet.Http/Features/HttpResponseFeature.cs @@ -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 callback, object state) + public void OnStarting(Func callback, object state) { - throw new NotSupportedException(); + throw new NotImplementedException(); } - public void OnResponseCompleted(Action callback, object state) + public void OnCompleted(Func callback, object state) { - throw new NotSupportedException(); + throw new NotImplementedException(); } } } diff --git a/src/Microsoft.AspNet.Owin/OwinEnvironment.cs b/src/Microsoft.AspNet.Owin/OwinEnvironment.cs index 5d1f95119c..5c231eb0e8 100644 --- a/src/Microsoft.AspNet.Owin/OwinEnvironment.cs +++ b/src/Microsoft.AspNet.Owin/OwinEnvironment.cs @@ -62,7 +62,15 @@ namespace Microsoft.AspNet.Owin { OwinConstants.ResponseReasonPhrase, new FeatureMap(feature => feature.ReasonPhrase, (feature, value) => feature.ReasonPhrase = Convert.ToString(value)) }, { OwinConstants.ResponseHeaders, new FeatureMap(feature => feature.Headers, (feature, value) => feature.Headers = (IDictionary)value) }, { OwinConstants.ResponseBody, new FeatureMap(feature => feature.Body, () => Stream.Null, (feature, value) => feature.Body = (Stream)value) }, - { OwinConstants.CommonKeys.OnSendingHeaders, new FeatureMap(feature => new Action, object>(feature.OnResponseStarting)) }, + { OwinConstants.CommonKeys.OnSendingHeaders, new FeatureMap( + feature => new Action, object>((cb, state) => { + feature.OnStarting(s => + { + cb(s); + return Task.FromResult(0); + }, state); + })) + }, { OwinConstants.CommonKeys.LocalPort, new FeatureMap(feature => feature.LocalPort.ToString(CultureInfo.InvariantCulture), (feature, value) => feature.LocalPort = Convert.ToInt32(value, CultureInfo.InvariantCulture)) }, diff --git a/src/Microsoft.AspNet.Owin/OwinFeatureCollection.cs b/src/Microsoft.AspNet.Owin/OwinFeatureCollection.cs index b7e6598383..b718d36097 100644 --- a/src/Microsoft.AspNet.Owin/OwinFeatureCollection.cs +++ b/src/Microsoft.AspNet.Owin/OwinFeatureCollection.cs @@ -147,17 +147,19 @@ namespace Microsoft.AspNet.Owin get { return _headersSent; } } - void IHttpResponseFeature.OnResponseStarting(Action callback, object state) + void IHttpResponseFeature.OnStarting(Func callback, object state) { var register = Prop, 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 callback, object state) + void IHttpResponseFeature.OnCompleted(Func callback, object state) { throw new NotSupportedException(); } diff --git a/test/Microsoft.AspNet.Http.Tests/Authentication/DefaultAuthenticationManagerTests.cs b/test/Microsoft.AspNet.Http.Tests/Authentication/DefaultAuthenticationManagerTests.cs index 652f3a1091..2871c27ec3 100644 --- a/test/Microsoft.AspNet.Http.Tests/Authentication/DefaultAuthenticationManagerTests.cs +++ b/test/Microsoft.AspNet.Http.Tests/Authentication/DefaultAuthenticationManagerTests.cs @@ -18,50 +18,46 @@ namespace Microsoft.AspNet.Http.Authentication.Internal public async Task AuthenticateWithNoAuthMiddlewareThrows() { var context = CreateContext(); - Assert.Throws(() => context.Authentication.Authenticate("Foo")); await Assert.ThrowsAsync(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(() => context.Authentication.Challenge("Foo")); + await context.Authentication.ChallengeAsync(); + Assert.Equal(200, context.Response.StatusCode); + await Assert.ThrowsAsync(() => context.Authentication.ChallengeAsync("Foo")); } [Fact] - public void SignInWithNoAuthMiddlewareThrows() + public async Task SignInWithNoAuthMiddlewareThrows() { var context = CreateContext(); - Assert.Throws(() => context.Authentication.SignIn("Foo", new ClaimsPrincipal())); + await Assert.ThrowsAsync(() => context.Authentication.SignInAsync("Foo", new ClaimsPrincipal())); } [Fact] - public void SignOutWithNoAuthMiddlewareMayThrow() + public async Task SignOutWithNoAuthMiddlewareMayThrow() { var context = CreateContext(); - context.Authentication.SignOut(); - - Assert.Throws(() => context.Authentication.SignOut("Foo")); + await Assert.ThrowsAsync(() => context.Authentication.SignOutAsync("Foo")); } [Fact] - public void SignInOutIn() + public async Task SignInOutIn() { var context = CreateContext(); var handler = new AuthHandler(); context.SetFeature(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); } }