Cookies now always redirects to Login/AccessDenied Paths

This commit is contained in:
Hao Kung 2015-08-11 16:50:20 -07:00
parent b1013ed976
commit b883920bef
24 changed files with 215 additions and 172 deletions

View File

@ -28,5 +28,19 @@ namespace Microsoft.AspNet.Builder
Name = optionsName
});
}
/// <summary>
/// Adds a cookie-based authentication middleware to your web application pipeline.
/// </summary>
/// <param name="app">The IApplicationBuilder passed to your configuration method</param>
/// <param name="configureOptions">Used to configure the options for the middleware</param>
/// <param name="optionsName">The name of the options class that controls the middleware behavior, null will use the default options</param>
/// <returns>The original app parameter</returns>
public static IApplicationBuilder UseCookieAuthentication([NotNull] this IApplicationBuilder app, IOptions<CookieAuthenticationOptions> options)
{
return app.UseMiddleware<CookieAuthenticationMiddleware>(options,
new ConfigureOptions<CookieAuthenticationOptions>(o => { }));
}
}
}

View File

@ -22,19 +22,26 @@ namespace Microsoft.AspNet.Authentication.Cookies
public const string CookiePrefix = ".AspNet.";
/// <summary>
/// The default value used by UseApplicationSignInCookie for the
/// The default value used by CookieAuthenticationMiddleware for the
/// CookieAuthenticationOptions.LoginPath
/// </summary>
[SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Login", Justification = "By design")]
public static readonly PathString LoginPath = new PathString("/Account/Login");
/// <summary>
/// The default value used by UseApplicationSignInCookie for the
/// The default value used by CookieAuthenticationMiddleware for the
/// CookieAuthenticationOptions.LogoutPath
/// </summary>
[SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Logout", Justification = "By design")]
public static readonly PathString LogoutPath = new PathString("/Account/Logout");
/// <summary>
/// The default value used by CookieAuthenticationMiddleware for the
/// CookieAuthenticationOptions.AccessDeniedPath
/// </summary>
[SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Login", Justification = "By design")]
public static readonly PathString AccessDeniedPath = new PathString("/Account/AccessDenied");
/// <summary>
/// The default value of the CookieAuthenticationOptions.ReturnUrlParameter
/// </summary>

View File

@ -376,12 +376,6 @@ namespace Microsoft.AspNet.Authentication.Cookies
protected override Task<bool> HandleForbiddenAsync(ChallengeContext context)
{
// HandleForbidden by redirecting to AccessDeniedPath if set
if (!Options.AccessDeniedPath.HasValue)
{
return base.HandleForbiddenAsync(context);
}
try
{
var accessDeniedUri =
@ -409,11 +403,6 @@ namespace Microsoft.AspNet.Authentication.Cookies
protected override Task<bool> HandleUnauthorizedAsync([NotNull] ChallengeContext context)
{
if (!Options.LoginPath.HasValue)
{
return base.HandleUnauthorizedAsync(context);
}
var redirectUri = new AuthenticationProperties(context.Properties).RedirectUri;
try
{

View File

@ -40,6 +40,18 @@ namespace Microsoft.AspNet.Authentication.Cookies
{
Options.CookieManager = new ChunkingCookieManager(urlEncoder);
}
if (!Options.LoginPath.HasValue)
{
Options.LoginPath = CookieAuthenticationDefaults.LoginPath;
}
if (!Options.LogoutPath.HasValue)
{
Options.LogoutPath = CookieAuthenticationDefaults.LogoutPath;
}
if (!Options.AccessDeniedPath.HasValue)
{
Options.AccessDeniedPath = CookieAuthenticationDefaults.AccessDeniedPath;
}
}
protected override AuthenticationHandler<CookieAuthenticationOptions> CreateHandler()

View File

@ -88,9 +88,6 @@ namespace Microsoft.AspNet.Authentication.Cookies
/// to the LoginPath as a query string parameter named by the ReturnUrlParameter. Once a request to the
/// LoginPath grants a new SignIn identity, the ReturnUrlParameter value is used to redirect the browser back
/// to the url which caused the original unauthorized status code.
///
/// If the LoginPath is null or empty, the middleware will not look for 401 Unauthorized status codes, and it will
/// not redirect automatically when a login occurs.
/// </summary>
[SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Login", Justification = "By design")]
public PathString LoginPath { get; set; }
@ -104,9 +101,6 @@ namespace Microsoft.AspNet.Authentication.Cookies
/// <summary>
/// The AccessDeniedPath property informs the middleware that it should change an outgoing 403 Forbidden status
/// code into a 302 redirection onto the given path.
///
/// If the AccessDeniedPath is null or empty, the middleware will not look for 403 Forbidden status codes, and it will
/// not redirect
/// </summary>
public PathString AccessDeniedPath { get; set; }

View File

@ -38,11 +38,11 @@ namespace Microsoft.AspNet.Authentication.Facebook
ConfigureOptions<FacebookAuthenticationOptions> configureOptions = null)
: base(next, dataProtectionProvider, loggerFactory, encoder, sharedOptions, options, configureOptions)
{
if (string.IsNullOrWhiteSpace(Options.AppId))
if (string.IsNullOrEmpty(Options.AppId))
{
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, nameof(Options.AppId)));
}
if (string.IsNullOrWhiteSpace(Options.AppSecret))
if (string.IsNullOrEmpty(Options.AppSecret))
{
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, nameof(Options.AppSecret)));
}

View File

@ -49,7 +49,7 @@ namespace Microsoft.AspNet.Authentication.MicrosoftAccount
}
var email = MicrosoftAccountAuthenticationHelper.GetEmail(payload);
if (!string.IsNullOrWhiteSpace(email))
if (!string.IsNullOrEmpty(email))
{
identity.AddClaim(new Claim(ClaimTypes.Email, email, ClaimValueTypes.String, Options.ClaimsIssuer));
}

View File

@ -116,7 +116,7 @@ namespace Microsoft.AspNet.Authentication.OAuth
var tokens = await ExchangeCodeAsync(code, BuildRedirectUri(Options.CallbackPath));
if (string.IsNullOrWhiteSpace(tokens.AccessToken))
if (string.IsNullOrEmpty(tokens.AccessToken))
{
Logger.LogWarning("Access token was not found");
return new AuthenticationTicket(properties, Options.AuthenticationScheme);
@ -279,7 +279,7 @@ namespace Microsoft.AspNet.Authentication.OAuth
{
var correlationKey = Constants.CorrelationPrefix + Options.AuthenticationScheme;
var correlationCookie = Request.Cookies[correlationKey];
if (string.IsNullOrWhiteSpace(correlationCookie))
if (string.IsNullOrEmpty(correlationCookie))
{
Logger.LogWarning("{0} cookie not found.", correlationKey);
return false;
@ -312,4 +312,4 @@ namespace Microsoft.AspNet.Authentication.OAuth
return true;
}
}
}
}

View File

@ -38,27 +38,27 @@ namespace Microsoft.AspNet.Authentication.OAuth
: base(next, options, loggerFactory, encoder, configureOptions)
{
// todo: review error handling
if (string.IsNullOrWhiteSpace(Options.AuthenticationScheme))
if (string.IsNullOrEmpty(Options.AuthenticationScheme))
{
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, nameof(Options.AuthenticationScheme)));
}
if (string.IsNullOrWhiteSpace(Options.ClientId))
if (string.IsNullOrEmpty(Options.ClientId))
{
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, nameof(Options.ClientId)));
}
if (string.IsNullOrWhiteSpace(Options.ClientSecret))
if (string.IsNullOrEmpty(Options.ClientSecret))
{
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, nameof(Options.ClientSecret)));
}
if (string.IsNullOrWhiteSpace(Options.AuthorizationEndpoint))
if (string.IsNullOrEmpty(Options.AuthorizationEndpoint))
{
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, nameof(Options.AuthorizationEndpoint)));
}
if (string.IsNullOrWhiteSpace(Options.TokenEndpoint))
if (string.IsNullOrEmpty(Options.TokenEndpoint))
{
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, nameof(Options.TokenEndpoint)));
}

View File

@ -97,7 +97,7 @@ namespace Microsoft.AspNet.Authentication.OAuthBearer
var validationParameters = Options.TokenValidationParameters.Clone();
if (_configuration != null)
{
if (validationParameters.ValidIssuer == null && !string.IsNullOrWhiteSpace(_configuration.Issuer))
if (validationParameters.ValidIssuer == null && !string.IsNullOrEmpty(_configuration.Issuer))
{
validationParameters.ValidIssuer = _configuration.Issuer;
}

View File

@ -48,7 +48,7 @@ namespace Microsoft.AspNet.Authentication.OAuthBearer
Options.SecurityTokenValidators = new List<ISecurityTokenValidator> { new JwtSecurityTokenHandler() };
}
if (string.IsNullOrWhiteSpace(Options.TokenValidationParameters.ValidAudience) && !string.IsNullOrWhiteSpace(Options.Audience))
if (string.IsNullOrEmpty(Options.TokenValidationParameters.ValidAudience) && !string.IsNullOrEmpty(Options.Audience))
{
Options.TokenValidationParameters.ValidAudience = Options.Audience;
}
@ -59,9 +59,9 @@ namespace Microsoft.AspNet.Authentication.OAuthBearer
{
Options.ConfigurationManager = new StaticConfigurationManager<OpenIdConnectConfiguration>(Options.Configuration);
}
else if (!(string.IsNullOrWhiteSpace(Options.MetadataAddress) && string.IsNullOrWhiteSpace(Options.Authority)))
else if (!(string.IsNullOrEmpty(Options.MetadataAddress) && string.IsNullOrEmpty(Options.Authority)))
{
if (string.IsNullOrWhiteSpace(Options.MetadataAddress) && !string.IsNullOrWhiteSpace(Options.Authority))
if (string.IsNullOrEmpty(Options.MetadataAddress) && !string.IsNullOrEmpty(Options.Authority))
{
Options.MetadataAddress = Options.Authority;
if (!Options.MetadataAddress.EndsWith("/", StringComparison.Ordinal))

View File

@ -251,7 +251,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
// response_mode=query (explicit or not) and a response_type containing id_token
// or token are not considered as a safe combination and MUST be rejected.
// See http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#Security
if (!string.IsNullOrWhiteSpace(message.IdToken) || !string.IsNullOrWhiteSpace(message.Token))
if (!string.IsNullOrEmpty(message.IdToken) || !string.IsNullOrEmpty(message.Token))
{
Logger.LogError("An OpenID Connect response cannot contain an identity token " +
"or an access token when using response_mode=query");
@ -260,7 +260,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
}
// assumption: if the ContentType is "application/x-www-form-urlencoded" it should be safe to read as it is small.
else if (string.Equals(Request.Method, "POST", StringComparison.OrdinalIgnoreCase)
&& !string.IsNullOrWhiteSpace(Request.ContentType)
&& !string.IsNullOrEmpty(Request.ContentType)
// May have media/type; charset=utf-8, allow partial match.
&& Request.ContentType.StartsWith("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase)
&& Request.Body.CanRead)
@ -643,7 +643,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
{
// assume a well formed query string: <a=b&>OpenIdConnectAuthenticationDefaults.AuthenticationPropertiesKey=kasjd;fljasldkjflksdj<&c=d>
var startIndex = 0;
if (string.IsNullOrWhiteSpace(state) || (startIndex = state.IndexOf(OpenIdConnectAuthenticationDefaults.AuthenticationPropertiesKey, StringComparison.Ordinal)) == -1)
if (string.IsNullOrEmpty(state) || (startIndex = state.IndexOf(OpenIdConnectAuthenticationDefaults.AuthenticationPropertiesKey, StringComparison.Ordinal)) == -1)
{
return null;
}
@ -934,7 +934,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
}
// Redirect back to the original secured resource, if any.
if (!string.IsNullOrWhiteSpace(ticket.Properties.RedirectUri))
if (!string.IsNullOrEmpty(ticket.Properties.RedirectUri))
{
Response.Redirect(ticket.Properties.RedirectUri);
return true;

View File

@ -100,7 +100,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
Options.Notifications = new OpenIdConnectAuthenticationNotifications();
}
if (string.IsNullOrWhiteSpace(Options.TokenValidationParameters.ValidAudience) && !string.IsNullOrWhiteSpace(Options.ClientId))
if (string.IsNullOrEmpty(Options.TokenValidationParameters.ValidAudience) && !string.IsNullOrEmpty(Options.ClientId))
{
Options.TokenValidationParameters.ValidAudience = Options.ClientId;
}
@ -116,9 +116,9 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
{
Options.ConfigurationManager = new StaticConfigurationManager<OpenIdConnectConfiguration>(Options.Configuration);
}
else if (!(string.IsNullOrWhiteSpace(Options.MetadataAddress) && string.IsNullOrWhiteSpace(Options.Authority)))
else if (!(string.IsNullOrEmpty(Options.MetadataAddress) && string.IsNullOrEmpty(Options.Authority)))
{
if (string.IsNullOrWhiteSpace(Options.MetadataAddress) && !string.IsNullOrWhiteSpace(Options.Authority))
if (string.IsNullOrEmpty(Options.MetadataAddress) && !string.IsNullOrEmpty(Options.Authority))
{
Options.MetadataAddress = Options.Authority;
if (!Options.MetadataAddress.EndsWith("/", StringComparison.Ordinal))

View File

@ -62,7 +62,7 @@ namespace Microsoft.AspNet.Authentication.Twitter
properties = requestToken.Properties;
var returnedToken = query.Get("oauth_token");
if (string.IsNullOrWhiteSpace(returnedToken))
if (string.IsNullOrEmpty(returnedToken))
{
Logger.LogWarning("Missing oauth_token");
return new AuthenticationTicket(properties, Options.AuthenticationScheme);
@ -75,7 +75,7 @@ namespace Microsoft.AspNet.Authentication.Twitter
}
var oauthVerifier = query.Get("oauth_verifier");
if (string.IsNullOrWhiteSpace(oauthVerifier))
if (string.IsNullOrEmpty(oauthVerifier))
{
Logger.LogWarning("Missing or blank oauth_verifier");
return new AuthenticationTicket(properties, Options.AuthenticationScheme);

View File

@ -42,11 +42,11 @@ namespace Microsoft.AspNet.Authentication.Twitter
ConfigureOptions<TwitterAuthenticationOptions> configureOptions = null)
: base(next, options, loggerFactory, encoder, configureOptions)
{
if (string.IsNullOrWhiteSpace(Options.ConsumerSecret))
if (string.IsNullOrEmpty(Options.ConsumerSecret))
{
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, nameof(Options.ConsumerSecret)));
}
if (string.IsNullOrWhiteSpace(Options.ConsumerKey))
if (string.IsNullOrEmpty(Options.ConsumerKey))
{
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, nameof(Options.ConsumerKey)));
}

View File

@ -14,11 +14,11 @@ namespace Microsoft.AspNet.Authentication
/// <summary>
/// Base class for the per-request work performed by most authentication middleware.
/// </summary>
public abstract class AuthenticationHandler : IAuthenticationHandler
/// <typeparam name="TOptions">Specifies which type for of AuthenticationOptions property</typeparam>
public abstract class AuthenticationHandler<TOptions> : IAuthenticationHandler where TOptions : AuthenticationOptions
{
private Task<AuthenticationTicket> _authenticateTask;
private bool _finishCalled;
private AuthenticationOptions _baseOptions;
protected bool SignInAccepted { get; set; }
protected bool SignOutAccepted { get; set; }
@ -44,11 +44,6 @@ namespace Microsoft.AspNet.Authentication
protected IUrlEncoder UrlEncoder { get; private set; }
internal AuthenticationOptions BaseOptions
{
get { return _baseOptions; }
}
public IAuthenticationHandler PriorHandler { get; set; }
protected string CurrentUri
@ -59,9 +54,18 @@ namespace Microsoft.AspNet.Authentication
}
}
protected async Task BaseInitializeAsync([NotNull] AuthenticationOptions options, [NotNull] HttpContext context, [NotNull] ILogger logger, [NotNull] IUrlEncoder encoder)
protected TOptions Options { get; private set; }
/// <summary>
/// Initialize is called once per request to contextualize this instance with appropriate state.
/// </summary>
/// <param name="options">The original options passed by the application control behavior</param>
/// <param name="context">The utility object to observe the current request and response</param>
/// <param name="logger">The logging factory used to create loggers</param>
/// <returns>async completion</returns>
public async Task InitializeAsync([NotNull] TOptions options, [NotNull] HttpContext context, [NotNull] ILogger logger, [NotNull] IUrlEncoder encoder)
{
_baseOptions = options;
Options = options;
Context = context;
OriginalPathBase = Request.PathBase;
OriginalPath = Request.Path;
@ -90,7 +94,7 @@ namespace Microsoft.AspNet.Authentication
private static async Task OnStartingCallback(object state)
{
var handler = (AuthenticationHandler)state;
var handler = (AuthenticationHandler<TOptions>)state;
await handler.FinishResponseOnce();
}
@ -115,9 +119,9 @@ namespace Microsoft.AspNet.Authentication
private async Task HandleAutomaticChallengeIfNeeded()
{
if (!ChallengeCalled && BaseOptions.AutomaticAuthentication && Response.StatusCode == 401)
if (!ChallengeCalled && Options.AutomaticAuthentication && Response.StatusCode == 401)
{
await HandleUnauthorizedAsync(new ChallengeContext(BaseOptions.AuthenticationScheme));
await HandleUnauthorizedAsync(new ChallengeContext(Options.AuthenticationScheme));
}
}
@ -152,7 +156,7 @@ namespace Microsoft.AspNet.Authentication
public void GetDescriptions(DescribeSchemesContext describeContext)
{
describeContext.Accept(BaseOptions.Description.Items);
describeContext.Accept(Options.Description.Items);
if (PriorHandler != null)
{
@ -162,8 +166,8 @@ namespace Microsoft.AspNet.Authentication
public bool ShouldHandleScheme(string authenticationScheme)
{
return string.Equals(BaseOptions.AuthenticationScheme, authenticationScheme, StringComparison.Ordinal) ||
(BaseOptions.AutomaticAuthentication && string.IsNullOrEmpty(authenticationScheme));
return string.Equals(Options.AuthenticationScheme, authenticationScheme, StringComparison.Ordinal) ||
(Options.AutomaticAuthentication && string.IsNullOrEmpty(authenticationScheme));
}
public async Task AuthenticateAsync(AuthenticateContext context)
@ -174,7 +178,7 @@ namespace Microsoft.AspNet.Authentication
var ticket = await HandleAuthenticateOnceAsync();
if (ticket?.Principal != null)
{
context.Authenticated(ticket.Principal, ticket.Properties.Items, BaseOptions.Description.Items);
context.Authenticated(ticket.Principal, ticket.Properties.Items, Options.Description.Items);
}
else
{

View File

@ -1,32 +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.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.Framework.Logging;
using Microsoft.Framework.WebEncoders;
namespace Microsoft.AspNet.Authentication
{
/// <summary>
/// Base class for the per-request work performed by most authentication middleware.
/// </summary>
/// <typeparam name="TOptions">Specifies which type for of AuthenticationOptions property</typeparam>
public abstract class AuthenticationHandler<TOptions> : AuthenticationHandler where TOptions : AuthenticationOptions
{
protected TOptions Options { get; private set; }
/// <summary>
/// Initialize is called once per request to contextualize this instance with appropriate state.
/// </summary>
/// <param name="options">The original options passed by the application control behavior</param>
/// <param name="context">The utility object to observe the current request and response</param>
/// <param name="logger">The logging factory used to create loggers</param>
/// <returns>async completion</returns>
public Task Initialize(TOptions options, HttpContext context, ILogger logger, IUrlEncoder encoder)
{
Options = options;
return BaseInitializeAsync(options, context, logger, encoder);
}
}
}

View File

@ -55,7 +55,7 @@ namespace Microsoft.AspNet.Authentication
public async Task Invoke(HttpContext context)
{
var handler = CreateHandler();
await handler.Initialize(Options, context, Logger, UrlEncoder);
await handler.InitializeAsync(Options, context, Logger, UrlEncoder);
try
{
if (!await handler.InvokeAsync())

View File

@ -18,6 +18,12 @@ namespace Microsoft.Framework.DependencyInjection
return services;
}
public static IServiceCollection AddAuthentication([NotNull] this IServiceCollection services, [NotNull] Action<SharedAuthenticationOptions> configureOptions)
{
services.Configure(configureOptions);
return services.AddAuthentication();
}
public static IServiceCollection ConfigureClaimsTransformation([NotNull] this IServiceCollection services, [NotNull] Action<ClaimsTransformationOptions> configure)
{
return services.Configure(configure);

View File

@ -55,7 +55,7 @@ namespace Microsoft.AspNet.Authentication
foreach (var chainElement in chain.ChainElements)
{
string subjectKeyIdentifier = GetSubjectKeyIdentifier(chainElement.Certificate);
if (string.IsNullOrWhiteSpace(subjectKeyIdentifier))
if (string.IsNullOrEmpty(subjectKeyIdentifier))
{
continue;
}

View File

@ -3,9 +3,11 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Features.Authentication;
using Microsoft.AspNet.Http.Internal;
using Microsoft.Framework.Logging;
using Moq;
using Xunit;
namespace Microsoft.AspNet.Authentication
@ -13,12 +15,12 @@ namespace Microsoft.AspNet.Authentication
public class AuthenticationHandlerFacts
{
[Fact]
public void ShouldHandleSchemeAreDeterminedOnlyByMatchingAuthenticationScheme()
public async Task ShouldHandleSchemeAreDeterminedOnlyByMatchingAuthenticationScheme()
{
var handler = new TestHandler("Alpha");
var handler = await TestHandler.Create("Alpha");
var passiveNoMatch = handler.ShouldHandleScheme("Beta");
handler = new TestHandler("Alpha");
handler = await TestHandler.Create("Alpha");
var passiveWithMatch = handler.ShouldHandleScheme("Alpha");
Assert.False(passiveNoMatch);
@ -26,44 +28,44 @@ namespace Microsoft.AspNet.Authentication
}
[Fact]
public void AutomaticHandlerInAutomaticModeHandlesEmptyChallenges()
public async Task AutomaticHandlerInAutomaticModeHandlesEmptyChallenges()
{
var handler = new TestAutoHandler("ignored", true);
var handler = await TestAutoHandler.Create("ignored", true);
Assert.True(handler.ShouldHandleScheme(""));
}
[Fact]
public void AutomaticHandlerHandlesNullScheme()
public async Task AutomaticHandlerHandlesNullScheme()
{
var handler = new TestAutoHandler("ignored", true);
var handler = await TestAutoHandler.Create("ignored", true);
Assert.True(handler.ShouldHandleScheme(null));
}
[Fact]
public void AutomaticHandlerIgnoresWhitespaceScheme()
public async Task AutomaticHandlerIgnoresWhitespaceScheme()
{
var handler = new TestAutoHandler("ignored", true);
var handler = await TestAutoHandler.Create("ignored", true);
Assert.False(handler.ShouldHandleScheme(" "));
}
[Fact]
public void AutomaticHandlerShouldHandleSchemeWhenSchemeMatches()
public async Task AutomaticHandlerShouldHandleSchemeWhenSchemeMatches()
{
var handler = new TestAutoHandler("Alpha", true);
var handler = await TestAutoHandler.Create("Alpha", true);
Assert.True(handler.ShouldHandleScheme("Alpha"));
}
[Fact]
public void AutomaticHandlerShouldNotHandleChallengeWhenSchemeDoesNotMatches()
public async Task AutomaticHandlerShouldNotHandleChallengeWhenSchemeDoesNotMatches()
{
var handler = new TestAutoHandler("Dog", true);
var handler = await TestAutoHandler.Create("Dog", true);
Assert.False(handler.ShouldHandleScheme("Alpha"));
}
[Fact]
public void AutomaticHandlerShouldNotHandleChallengeWhenSchemesNotEmpty()
public async Task AutomaticHandlerShouldNotHandleChallengeWhenSchemesNotEmpty()
{
var handler = new TestAutoHandler(null, true);
var handler = await TestAutoHandler.Create(null, true);
Assert.False(handler.ShouldHandleScheme("Alpha"));
}
@ -72,22 +74,31 @@ namespace Microsoft.AspNet.Authentication
[InlineData("")]
public async Task AuthHandlerAuthenticateCachesTicket(string scheme)
{
var handler = new CountHandler(scheme);
var handler = await CountHandler.Create(scheme);
var context = new AuthenticateContext(scheme);
await handler.AuthenticateAsync(context);
await handler.AuthenticateAsync(context);
Assert.Equal(1, handler.AuthCount);
}
private class CountHandler : AuthenticationHandler<AuthenticationOptions>
private class CountOptions : AuthenticationOptions { }
private class CountHandler : AuthenticationHandler<CountOptions>
{
public int AuthCount = 0;
public CountHandler(string scheme)
private CountHandler() { }
public static async Task<CountHandler> Create(string scheme)
{
Initialize(new TestOptions(), new DefaultHttpContext(), new LoggerFactory().CreateLogger("TestHandler"), Framework.WebEncoders.UrlEncoder.Default);
Options.AuthenticationScheme = scheme;
Options.AutomaticAuthentication = true;
var handler = new CountHandler();
var context = new Mock<HttpContext>();
context.Setup(c => c.Request).Returns(new Mock<HttpRequest>().Object);
context.Setup(c => c.Response).Returns(new Mock<HttpResponse>().Object);
await handler.InitializeAsync(new CountOptions(), context.Object, new LoggerFactory().CreateLogger("CountHandler"), Framework.WebEncoders.UrlEncoder.Default);
handler.Options.AuthenticationScheme = scheme;
handler.Options.AutomaticAuthentication = true;
return handler;
}
protected override Task<AuthenticationTicket> HandleAuthenticateAsync()
@ -98,17 +109,24 @@ namespace Microsoft.AspNet.Authentication
}
private class TestHandler : AuthenticationHandler<AuthenticationOptions>
private class TestHandler : AuthenticationHandler<TestOptions>
{
public TestHandler(string scheme)
private TestHandler() { }
public static async Task<TestHandler> Create(string scheme)
{
Initialize(new TestOptions(), new DefaultHttpContext(), new LoggerFactory().CreateLogger("TestHandler"), Framework.WebEncoders.UrlEncoder.Default);
Options.AuthenticationScheme = scheme;
var handler = new TestHandler();
var context = new Mock<HttpContext>();
context.Setup(c => c.Request).Returns(new Mock<HttpRequest>().Object);
context.Setup(c => c.Response).Returns(new Mock<HttpResponse>().Object);
await handler.InitializeAsync(new TestOptions(), context.Object, new LoggerFactory().CreateLogger("TestHandler"), Framework.WebEncoders.UrlEncoder.Default);
handler.Options.AuthenticationScheme = scheme;
return handler;
}
protected override Task<AuthenticationTicket> HandleAuthenticateAsync()
{
throw new NotImplementedException();
return Task.FromResult<AuthenticationTicket>(null);
}
}
@ -124,16 +142,23 @@ namespace Microsoft.AspNet.Authentication
private class TestAutoHandler : AuthenticationHandler<TestAutoOptions>
{
public TestAutoHandler(string scheme, bool auto)
private TestAutoHandler() { }
public static async Task<TestAutoHandler> Create(string scheme, bool auto)
{
Initialize(new TestAutoOptions(), new DefaultHttpContext(), new LoggerFactory().CreateLogger("TestHandler"), Framework.WebEncoders.UrlEncoder.Default);
Options.AuthenticationScheme = scheme;
Options.AutomaticAuthentication = auto;
var handler = new TestAutoHandler();
var context = new Mock<HttpContext>();
context.Setup(c => c.Request).Returns(new Mock<HttpRequest>().Object);
context.Setup(c => c.Response).Returns(new Mock<HttpResponse>().Object);
await handler.InitializeAsync(new TestAutoOptions(), context.Object, new LoggerFactory().CreateLogger("TestAutoHandler"), Framework.WebEncoders.UrlEncoder.Default);
handler.Options.AuthenticationScheme = scheme;
handler.Options.AutomaticAuthentication = auto;
return handler;
}
protected override Task<AuthenticationTicket> HandleAuthenticateAsync()
{
throw new NotImplementedException();
return Task.FromResult<AuthenticationTicket>(null);
}
}
}

View File

@ -57,26 +57,16 @@ namespace Microsoft.AspNet.Authentication.Cookies
}
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task ProtectedCustomRequestShouldRedirectToCustomRedirectUri(bool auto)
[Fact]
public async Task ProtectedCustomRequestShouldRedirectToCustomRedirectUri()
{
var server = CreateServer(options =>
{
options.LoginPath = new PathString("/login");
options.AutomaticAuthentication = auto;
});
var server = CreateServer(options => options.AutomaticAuthentication = true);
var transaction = await SendAsync(server, "http://example.com/protected/CustomRedirect");
// REVIEW: Now when Cookies are not in auto, noone handles the challenge so the Status stays OK, is that reasonable??
transaction.Response.StatusCode.ShouldBe(auto ? HttpStatusCode.Redirect : HttpStatusCode.OK);
if (auto)
{
var location = transaction.Response.Headers.Location;
location.ToString().ShouldBe("http://example.com/login?ReturnUrl=%2FCustomRedirect");
}
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.Redirect);
var location = transaction.Response.Headers.Location;
location.ToString().ShouldBe("http://example.com/Account/Login?ReturnUrl=%2FCustomRedirect");
}
private Task SignInAsAlice(HttpContext context)
@ -588,7 +578,7 @@ namespace Microsoft.AspNet.Authentication.Cookies
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task CookieTurns401To403WithCookie(bool automatic)
public async Task CookieTurnsChallengeIntoForbidWithCookie(bool automatic)
{
var clock = new TestClock();
var server = CreateServer(options =>
@ -603,7 +593,9 @@ namespace Microsoft.AspNet.Authentication.Cookies
var url = "http://example.com/challenge";
var transaction2 = await SendAsync(server, url, transaction1.CookieNameValue);
transaction2.Response.StatusCode.ShouldBe(HttpStatusCode.Forbidden);
transaction2.Response.StatusCode.ShouldBe(HttpStatusCode.Redirect);
var location = transaction2.Response.Headers.Location;
location.LocalPath.ShouldBe("/Account/AccessDenied");
}
[Theory]
@ -614,9 +606,7 @@ namespace Microsoft.AspNet.Authentication.Cookies
var clock = new TestClock();
var server = CreateServer(options =>
{
options.LoginPath = new PathString("/login");
options.AutomaticAuthentication = automatic;
options.AccessDeniedPath = new PathString("/accessdenied");
options.SystemClock = clock;
},
SignInAsAlice);
@ -626,13 +616,13 @@ namespace Microsoft.AspNet.Authentication.Cookies
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.Redirect);
var location = transaction.Response.Headers.Location;
location.LocalPath.ShouldBe("/login");
location.LocalPath.ShouldBe("/Account/Login");
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task CookieForbidTurns401To403WithoutCookie(bool automatic)
public async Task CookieForbidRedirectsWithoutCookie(bool automatic)
{
var clock = new TestClock();
var server = CreateServer(options =>
@ -645,7 +635,9 @@ namespace Microsoft.AspNet.Authentication.Cookies
var url = "http://example.com/forbid";
var transaction = await SendAsync(server, url);
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.Forbidden);
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.Redirect);
var location = transaction.Response.Headers.Location;
location.LocalPath.ShouldBe("/Account/AccessDenied");
}
[Fact]
@ -670,19 +662,20 @@ namespace Microsoft.AspNet.Authentication.Cookies
}
[Fact]
public async Task CookieChallengeDoesNothingIfNotAuthenticated()
public async Task CookieChallengeRedirectsWithLoginPath()
{
var clock = new TestClock();
var server = CreateServer(options =>
{
options.SystemClock = clock;
options.LoginPath = new PathString("/page");
});
var transaction1 = await SendAsync(server, "http://example.com/testpath");
var transaction2 = await SendAsync(server, "http://example.com/challenge", transaction1.CookieNameValue);
transaction2.Response.StatusCode.ShouldBe(HttpStatusCode.Unauthorized);
transaction2.Response.StatusCode.ShouldBe(HttpStatusCode.Redirect);
}
[Fact]
@ -692,13 +685,14 @@ namespace Microsoft.AspNet.Authentication.Cookies
var server = CreateServer(options =>
{
options.SystemClock = clock;
options.LoginPath = new PathString("/page");
});
var transaction1 = await SendAsync(server, "http://example.com/testpath");
var transaction2 = await SendAsync(server, "http://example.com/unauthorized", transaction1.CookieNameValue);
transaction2.Response.StatusCode.ShouldBe(HttpStatusCode.Unauthorized);
transaction2.Response.StatusCode.ShouldBe(HttpStatusCode.Redirect);
}
[Fact]
@ -720,6 +714,53 @@ namespace Microsoft.AspNet.Authentication.Cookies
location.Query.ShouldBe("?ReturnUrl=%2F");
}
[Fact]
public async Task ChallengeDoesNotSet401OnUnauthorized()
{
var server = TestServer.Create(app =>
{
app.UseCookieAuthentication();
app.Run(async context => {
await Assert.ThrowsAsync<InvalidOperationException>(() => context.Authentication.ChallengeAsync());
});
}, services => services.AddAuthentication());
var transaction = await server.SendAsync("http://example.com");
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.OK);
}
/* [Fact]
public async Task UseCookieWithInstanceDoesntUseSharedOptions()
{
var server = TestServer.Create(app =>
{
app.UseCookieAuthentication(options => options.CookieName = "One");
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.Run(context => context.Authentication.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(new ClaimsIdentity())));
}, services => services.AddAuthentication());
var transaction = await server.SendAsync("http://example.com");
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.OK);
Assert.True(transaction.SetCookie[0].StartsWith(".AspNet.Cookies="));
}
[Fact]
public async Task UseCookieWithOutInstanceDoesUseSharedOptions()
{
var server = TestServer.Create(app =>
{
app.UseCookieAuthentication(options => options.CookieName = "One");
app.UseCookieAuthentication(options => options.AuthenticationScheme = "Two");
app.Run(context => context.Authentication.SignInAsync("Two", new ClaimsPrincipal(new ClaimsIdentity())));
}, services => services.AddAuthentication());
var transaction = await server.SendAsync("http://example.com");
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.OK);
Assert.True(transaction.SetCookie[0].StartsWith("One="));
}*/
[Fact]
public async Task MapWithSignInOnlyRedirectToReturnUrlOnLoginPath()
{
@ -870,6 +911,7 @@ namespace Microsoft.AspNet.Authentication.Cookies
var server = TestServer.Create(app =>
{
app.UseCookieAuthentication(configureOptions);
// app.UseCookieAuthentication(new CookieAuthenticationOptions { AuthenticationScheme = "Cookie2" });
if (claimsTransform != null)
{
@ -961,17 +1003,13 @@ namespace Microsoft.AspNet.Authentication.Cookies
}
}
private static async Task<Transaction> SendAsync(TestServer server, string uri, string cookieHeader = null, bool ajaxRequest = false)
private static async Task<Transaction> SendAsync(TestServer server, string uri, string cookieHeader = null)
{
var request = new HttpRequestMessage(HttpMethod.Get, uri);
if (!string.IsNullOrEmpty(cookieHeader))
{
request.Headers.Add("Cookie", cookieHeader);
}
if (ajaxRequest)
{
request.Headers.Add("X-Requested-With", "XMLHttpRequest");
}
var transaction = new Transaction
{
Request = request,

View File

@ -34,7 +34,7 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
AuthenticationProperties ISecureDataFormat<AuthenticationProperties>.Unprotect(string protectedText)
{
if (string.IsNullOrWhiteSpace(protectedText))
if (string.IsNullOrEmpty(protectedText))
{
return null;
}

View File

@ -27,15 +27,6 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
return await base.HandleUnauthorizedAsync(context);
}
protected override Task HandleSignInAsync(SignInContext context)
{
return Task.FromResult(0);
}
protected override Task HandleSignOutAsync(SignOutContext context)
{
return Task.FromResult(0);
}
protected override async Task<OpenIdConnectTokenEndpointResponse> RedeemAuthorizationCodeAsync(string authorizationCode, string redirectUri)
{
var jsonResponse = new JObject();
@ -53,10 +44,5 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
claimsIdentity.AddClaim(new Claim("test claim", "test value"));
return new AuthenticationTicket(new ClaimsPrincipal(claimsIdentity), ticket.Properties, ticket.AuthenticationScheme);
}
//public override bool ShouldHandleScheme(string authenticationScheme)
//{
// return true;
//}
}
}