massively simplifies the cookie handling, we don't use our own and just use the defaults, the trick to not validating everything is to use the cookie path. This does mean that each clientside request will also be validated but there's no way to override this behavior in identity currently, the cookie handler is internal so unless we copy/paste all of it's code can't do much about that.
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Web;
|
||||
using Microsoft.AspNet.Identity;
|
||||
using Microsoft.AspNet.Identity.Owin;
|
||||
using Microsoft.Owin;
|
||||
using Microsoft.Owin.Extensions;
|
||||
using Microsoft.Owin.Security.Cookies;
|
||||
using Owin;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration;
|
||||
@@ -51,47 +53,28 @@ namespace Umbraco.Web.Security.Identity
|
||||
{
|
||||
if (app == null) throw new ArgumentNullException("app");
|
||||
|
||||
app.Use(typeof (UmbracoBackOfficeAuthenticationMiddleware),
|
||||
//ctor params
|
||||
app,
|
||||
new UmbracoBackOfficeCookieAuthenticationOptions(
|
||||
|
||||
app.UseCookieAuthentication(new UmbracoBackOfficeCookieAuthenticationOptions(
|
||||
UmbracoConfig.For.UmbracoSettings().Security,
|
||||
GlobalSettings.TimeOutInMinutes,
|
||||
GlobalSettings.UseSSL),
|
||||
LoggerResolver.Current.Logger);
|
||||
GlobalSettings.UseSSL,
|
||||
GlobalSettings.Path)
|
||||
{
|
||||
//Provider = new CookieAuthenticationProvider
|
||||
//{
|
||||
// // Enables the application to validate the security stamp when the user
|
||||
// // logs in. This is a security feature which is used when you
|
||||
// // change a password or add an external login to your account.
|
||||
// OnValidateIdentity = SecurityStampValidator
|
||||
// .OnValidateIdentity<UmbracoMembersUserManager<UmbracoApplicationUser>, UmbracoApplicationUser, int>(
|
||||
// TimeSpan.FromMinutes(30),
|
||||
// (manager, user) => user.GenerateUserIdentityAsync(manager),
|
||||
// identity => identity.GetUserId<int>())
|
||||
//}
|
||||
});
|
||||
|
||||
app.UseStageMarker(PipelineStage.Authenticate);
|
||||
return app;
|
||||
}
|
||||
|
||||
//This is a fix for OWIN mem leak!
|
||||
//http://stackoverflow.com/questions/24378856/memory-leak-in-owin-appbuilderextensions/24819543#24819543
|
||||
private class OwinContextDisposal<T1, T2> : IDisposable
|
||||
where T1 : IDisposable
|
||||
where T2 : IDisposable
|
||||
{
|
||||
private readonly List<IDisposable> _disposables = new List<IDisposable>();
|
||||
private bool _disposed = false;
|
||||
|
||||
public OwinContextDisposal(IOwinContext owinContext)
|
||||
{
|
||||
if (HttpContext.Current == null) return;
|
||||
|
||||
_disposables.Add(owinContext.Get<T1>());
|
||||
_disposables.Add(owinContext.Get<T2>());
|
||||
|
||||
HttpContext.Current.DisposeOnPipelineCompleted(this);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
foreach (var disposable in _disposables)
|
||||
{
|
||||
disposable.Dispose();
|
||||
}
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,236 +0,0 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Security.Principal;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using System.Web.Security;
|
||||
using Microsoft.Owin;
|
||||
using Microsoft.Owin.Security;
|
||||
using Microsoft.Owin.Security.Cookies;
|
||||
using Microsoft.Owin.Security.Infrastructure;
|
||||
using Newtonsoft.Json;
|
||||
using Umbraco.Core.Configuration.UmbracoSettings;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Logging;
|
||||
|
||||
namespace Umbraco.Web.Security.Identity
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to allow normal Umbraco back office authentication to work
|
||||
/// </summary>
|
||||
public class UmbracoBackOfficeAuthenticationHandler : AuthenticationHandler<UmbracoBackOfficeCookieAuthenticationOptions>
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private bool _shouldRenew;
|
||||
private DateTimeOffset _renewIssuedUtc;
|
||||
private DateTimeOffset _renewExpiresUtc;
|
||||
|
||||
public UmbracoBackOfficeAuthenticationHandler(ILogger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if we should authentication the request (i.e. is back office) and if so gets the forms auth ticket in the request
|
||||
/// and returns an AuthenticationTicket based on that.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// It's worth noting that the UmbracoModule still executes and performs the authentication, however this also needs to execute
|
||||
/// so that it assigns the new Principal object on the OWIN request:
|
||||
/// http://brockallen.com/2013/10/27/host-authentication-and-web-api-with-owin-and-active-vs-passive-authentication-middleware/
|
||||
/// </remarks>
|
||||
protected override async Task<AuthenticationTicket> AuthenticateCoreAsync()
|
||||
{
|
||||
if (ShouldAuthRequest())
|
||||
{
|
||||
var ticket = GetAuthTicket(Request);
|
||||
|
||||
if (ticket == null)
|
||||
{
|
||||
_logger.Warn<UmbracoBackOfficeAuthenticationHandler>(@"Unprotect ticket failed");
|
||||
return null;
|
||||
}
|
||||
|
||||
DateTimeOffset currentUtc = Options.SystemClock.UtcNow;
|
||||
DateTimeOffset? issuedUtc = ticket.Properties.IssuedUtc;
|
||||
DateTimeOffset? expiresUtc = ticket.Properties.ExpiresUtc;
|
||||
|
||||
if (expiresUtc != null && expiresUtc.Value < currentUtc)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (issuedUtc != null && expiresUtc != null && Options.SlidingExpiration)
|
||||
{
|
||||
TimeSpan timeElapsed = currentUtc.Subtract(issuedUtc.Value);
|
||||
TimeSpan timeRemaining = expiresUtc.Value.Subtract(currentUtc);
|
||||
|
||||
if (timeRemaining < timeElapsed)
|
||||
{
|
||||
_shouldRenew = true;
|
||||
_renewIssuedUtc = currentUtc;
|
||||
TimeSpan timeSpan = expiresUtc.Value.Subtract(issuedUtc.Value);
|
||||
_renewExpiresUtc = currentUtc.Add(timeSpan);
|
||||
}
|
||||
}
|
||||
|
||||
var context = new CookieValidateIdentityContext(Context, ticket, Options);
|
||||
|
||||
await Options.Provider.ValidateIdentity(context);
|
||||
|
||||
return new AuthenticationTicket(context.Identity, context.Properties);
|
||||
}
|
||||
|
||||
return await Task.FromResult<AuthenticationTicket>(null);
|
||||
}
|
||||
|
||||
protected override async Task ApplyResponseGrantAsync()
|
||||
{
|
||||
AuthenticationResponseGrant signin = Helper.LookupSignIn(Options.AuthenticationType);
|
||||
bool shouldSignin = signin != null;
|
||||
AuthenticationResponseRevoke signout = Helper.LookupSignOut(Options.AuthenticationType, Options.AuthenticationMode);
|
||||
bool shouldSignout = signout != null;
|
||||
|
||||
if (shouldSignin || shouldSignout || _shouldRenew)
|
||||
{
|
||||
var cookieOptions = new CookieOptions
|
||||
{
|
||||
Domain = Options.CookieDomain,
|
||||
HttpOnly = Options.CookieHttpOnly,
|
||||
Path = Options.CookiePath ?? "/",
|
||||
};
|
||||
if (Options.CookieSecure == CookieSecureOption.SameAsRequest)
|
||||
{
|
||||
cookieOptions.Secure = Request.IsSecure;
|
||||
}
|
||||
else
|
||||
{
|
||||
cookieOptions.Secure = Options.CookieSecure == CookieSecureOption.Always;
|
||||
}
|
||||
|
||||
if (shouldSignin)
|
||||
{
|
||||
var context = new CookieResponseSignInContext(
|
||||
Context,
|
||||
Options,
|
||||
Options.AuthenticationType,
|
||||
signin.Identity,
|
||||
signin.Properties);
|
||||
|
||||
DateTimeOffset issuedUtc = Options.SystemClock.UtcNow;
|
||||
DateTimeOffset expiresUtc = issuedUtc.Add(Options.ExpireTimeSpan);
|
||||
|
||||
context.Properties.IssuedUtc = issuedUtc;
|
||||
context.Properties.ExpiresUtc = expiresUtc;
|
||||
|
||||
Options.Provider.ResponseSignIn(context);
|
||||
|
||||
if (context.Properties.IsPersistent)
|
||||
{
|
||||
cookieOptions.Expires = expiresUtc.ToUniversalTime().DateTime;
|
||||
}
|
||||
|
||||
var model = new AuthenticationTicket(context.Identity, context.Properties);
|
||||
string cookieValue = Options.TicketDataFormat.Protect(model);
|
||||
|
||||
Response.Cookies.Append(
|
||||
Options.CookieName,
|
||||
cookieValue,
|
||||
cookieOptions);
|
||||
}
|
||||
else if (shouldSignout)
|
||||
{
|
||||
Response.Cookies.Delete(
|
||||
Options.CookieName,
|
||||
cookieOptions);
|
||||
}
|
||||
else if (_shouldRenew)
|
||||
{
|
||||
AuthenticationTicket model = await AuthenticateAsync();
|
||||
|
||||
model.Properties.IssuedUtc = _renewIssuedUtc;
|
||||
model.Properties.ExpiresUtc = _renewExpiresUtc;
|
||||
|
||||
string cookieValue = Options.TicketDataFormat.Protect(model);
|
||||
|
||||
if (model.Properties.IsPersistent)
|
||||
{
|
||||
cookieOptions.Expires = _renewExpiresUtc.ToUniversalTime().DateTime;
|
||||
}
|
||||
|
||||
Response.Cookies.Append(
|
||||
Options.CookieName,
|
||||
cookieValue,
|
||||
cookieOptions);
|
||||
}
|
||||
|
||||
//Response.Headers.Set(
|
||||
// HeaderNameCacheControl,
|
||||
// HeaderValueNoCache);
|
||||
|
||||
//Response.Headers.Set(
|
||||
// HeaderNamePragma,
|
||||
// HeaderValueNoCache);
|
||||
|
||||
//Response.Headers.Set(
|
||||
// HeaderNameExpires,
|
||||
// HeaderValueMinusOne);
|
||||
|
||||
bool shouldLoginRedirect = shouldSignin && Options.LoginPath.HasValue && Request.Path == Options.LoginPath;
|
||||
bool shouldLogoutRedirect = shouldSignout && Options.LogoutPath.HasValue && Request.Path == Options.LogoutPath;
|
||||
|
||||
if ((shouldLoginRedirect || shouldLogoutRedirect) && Response.StatusCode == 200)
|
||||
{
|
||||
IReadableStringCollection query = Request.Query;
|
||||
string redirectUri = query.Get(Options.ReturnUrlParameter);
|
||||
if (!string.IsNullOrWhiteSpace(redirectUri)
|
||||
//&& IsHostRelative(redirectUri)
|
||||
)
|
||||
{
|
||||
var redirectContext = new CookieApplyRedirectContext(Context, Options, redirectUri);
|
||||
Options.Provider.ApplyRedirect(redirectContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool ShouldAuthRequest()
|
||||
{
|
||||
var httpContext = Context.HttpContextFromOwinContext();
|
||||
|
||||
// do not process if client-side request
|
||||
if (httpContext.Request.Url.IsClientSideRequest())
|
||||
return false;
|
||||
|
||||
return UmbracoModule.ShouldAuthenticateRequest(httpContext.Request, Request.Uri);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the current FormsAuth ticket in the request
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <returns></returns>
|
||||
private AuthenticationTicket GetAuthTicket(IOwinRequest request)
|
||||
{
|
||||
if (request == null) throw new ArgumentNullException("request");
|
||||
|
||||
var formsCookie = request.Cookies[Options.CookieName];
|
||||
if (string.IsNullOrWhiteSpace(formsCookie))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
//get the ticket
|
||||
try
|
||||
{
|
||||
return Options.TicketDataFormat.Unprotect(formsCookie);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
using Microsoft.Owin;
|
||||
using Microsoft.Owin.Security.Infrastructure;
|
||||
using Owin;
|
||||
using Umbraco.Core.Configuration.UmbracoSettings;
|
||||
using Umbraco.Core.Logging;
|
||||
|
||||
namespace Umbraco.Web.Security.Identity
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to enable the normal Umbraco back office authentication to operate
|
||||
/// </summary>
|
||||
public class UmbracoBackOfficeAuthenticationMiddleware : AuthenticationMiddleware<UmbracoBackOfficeCookieAuthenticationOptions>
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public UmbracoBackOfficeAuthenticationMiddleware(
|
||||
OwinMiddleware next,
|
||||
IAppBuilder app,
|
||||
UmbracoBackOfficeCookieAuthenticationOptions options,
|
||||
ILogger logger)
|
||||
: base(next, options)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
protected override AuthenticationHandler<UmbracoBackOfficeCookieAuthenticationOptions> CreateHandler()
|
||||
{
|
||||
return new UmbracoBackOfficeAuthenticationHandler(_logger);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Owin;
|
||||
using Microsoft.Owin.Security.Cookies;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Configuration.UmbracoSettings;
|
||||
|
||||
@@ -13,22 +14,32 @@ namespace Umbraco.Web.Security.Identity
|
||||
public sealed class UmbracoBackOfficeCookieAuthenticationOptions : CookieAuthenticationOptions
|
||||
{
|
||||
public UmbracoBackOfficeCookieAuthenticationOptions()
|
||||
: this(UmbracoConfig.For.UmbracoSettings().Security, GlobalSettings.TimeOutInMinutes, GlobalSettings.UseSSL)
|
||||
: this(UmbracoConfig.For.UmbracoSettings().Security, GlobalSettings.TimeOutInMinutes, GlobalSettings.UseSSL, GlobalSettings.Path)
|
||||
{
|
||||
}
|
||||
|
||||
public UmbracoBackOfficeCookieAuthenticationOptions(ISecuritySection securitySection, int loginTimeoutMinutes, bool forceSsl)
|
||||
public UmbracoBackOfficeCookieAuthenticationOptions(
|
||||
ISecuritySection securitySection,
|
||||
int loginTimeoutMinutes,
|
||||
bool forceSsl,
|
||||
string umbracoPath,
|
||||
bool useLegacyFormsAuthDataFormat = true)
|
||||
{
|
||||
AuthenticationType = "UmbracoBackOffice";
|
||||
|
||||
TicketDataFormat = new FormsAuthenticationSecureDataFormat(loginTimeoutMinutes);
|
||||
if (useLegacyFormsAuthDataFormat)
|
||||
{
|
||||
//If this is not explicitly set it will fall back to the default automatically
|
||||
TicketDataFormat = new FormsAuthenticationSecureDataFormat(loginTimeoutMinutes);
|
||||
}
|
||||
|
||||
CookieDomain = securitySection.AuthCookieDomain;
|
||||
CookieName = securitySection.AuthCookieName;
|
||||
CookieHttpOnly = true;
|
||||
CookieSecure = forceSsl ? CookieSecureOption.Always : CookieSecureOption.SameAsRequest;
|
||||
CookiePath = "/";
|
||||
LoginPath = new PathString("/umbraco/login"); //TODO: ??
|
||||
|
||||
//Ensure the cookie path is set so that it isn't transmitted for anything apart from requests to the back office
|
||||
CookiePath = umbracoPath.EnsureStartsWith('/');
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -543,8 +543,6 @@
|
||||
<Compile Include="Security\Identity\AppBuilderExtensions.cs" />
|
||||
<Compile Include="Security\Identity\FormsAuthenticationSecureDataFormat.cs" />
|
||||
<Compile Include="Security\Identity\OwinExtensions.cs" />
|
||||
<Compile Include="Security\Identity\UmbracoBackOfficeAuthenticationHandler.cs" />
|
||||
<Compile Include="Security\Identity\UmbracoBackOfficeAuthenticationMiddleware.cs" />
|
||||
<Compile Include="Security\Identity\UmbracoBackOfficeCookieAuthenticationOptions.cs" />
|
||||
<Compile Include="Scheduling\TaskAndFactoryExtensions.cs" />
|
||||
<Compile Include="Strategies\Migrations\ClearCsrfCookiesAfterUpgrade.cs" />
|
||||
|
||||
Reference in New Issue
Block a user