Replaced usages of HttpContext.Items with usages of IRequestCache

This commit is contained in:
Bjarke Berg
2020-02-13 07:46:49 +01:00
parent 6f32470fdf
commit 85afde0ea1
24 changed files with 152 additions and 140 deletions
@@ -1,4 +1,5 @@
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Text.RegularExpressions;
@@ -101,5 +102,9 @@ namespace Umbraco.Core.Cache
var compiled = new Regex(regex, RegexOptions.Compiled);
_items.RemoveAll(kvp => compiled.IsMatch(kvp.Key));
}
public IEnumerator<KeyValuePair<string, object>> GetEnumerator() => _items.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
}
@@ -171,5 +171,20 @@ namespace Umbraco.Core.Cache
}
#endregion
public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
{
if (!TryGetContextItems(out var items))
{
yield break;
}
foreach (DictionaryEntry item in items)
{
yield return new KeyValuePair<string, object>(item.Key.ToString(), item.Value);
}
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
}
@@ -1,6 +1,8 @@
using System.Collections.Generic;
namespace Umbraco.Core.Cache
{
public interface IRequestCache : IAppCache
public interface IRequestCache : IAppCache, IEnumerable<KeyValuePair<string, object>>
{
bool Set(string key, object value);
bool Remove(string key);
@@ -1,4 +1,5 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
@@ -84,5 +85,9 @@ namespace Umbraco.Core.Cache
/// <inheritdoc />
public virtual void ClearByRegex(string regex)
{ }
public IEnumerator<KeyValuePair<string, object>> GetEnumerator() => new Dictionary<string, object>().GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
}
@@ -439,11 +439,6 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache
return (IPublishedContent) appCache.Get(key, () => (new XmlPublishedContent(node, isPreviewing, appCache, contentTypeCache, variationContextAccessor)).CreateModel(Current.PublishedModelFactory));
}
public static void ClearRequest()
{
Current.AppCaches.RequestCache.ClearByKey(CacheKeyPrefix);
}
private const string CacheKeyPrefix = "CONTENTCACHE_XMLPUBLISHEDCONTENT_";
}
}
@@ -42,7 +42,8 @@ namespace Umbraco.Tests.Routing
logger,
null, // FIXME: PublishedRouter complexities...
Mock.Of<IUmbracoContextFactory>(),
new RoutableDocumentFilter(globalSettings, IOHelper)
new RoutableDocumentFilter(globalSettings, IOHelper),
AppCaches.RequestCache
);
runtime.Level = RuntimeLevel.Run;
@@ -8,7 +8,6 @@ using Moq;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Web.Composing;
using Umbraco.Tests.TestHelpers;
using Umbraco.Tests.Testing;
using Umbraco.Tests.Testing.Objects.Accessors;
using Umbraco.Web;
@@ -39,7 +38,7 @@ namespace Umbraco.Tests.Security
var runtime = Mock.Of<IRuntimeState>(x => x.Level == RuntimeLevel.Install);
var mgr = new BackOfficeCookieManager(
Mock.Of<IUmbracoContextAccessor>(accessor => accessor.UmbracoContext == umbracoContext), runtime, TestObjects.GetGlobalSettings(), TestHelper.IOHelper);
Mock.Of<IUmbracoContextAccessor>(accessor => accessor.UmbracoContext == umbracoContext), runtime, TestObjects.GetGlobalSettings(), IOHelper, AppCaches.RequestCache);
var result = mgr.ShouldAuthenticateRequest(Mock.Of<IOwinContext>(), new Uri("http://localhost/umbraco"));
@@ -58,7 +57,7 @@ namespace Umbraco.Tests.Security
new TestVariationContextAccessor(), IOHelper);
var runtime = Mock.Of<IRuntimeState>(x => x.Level == RuntimeLevel.Run);
var mgr = new BackOfficeCookieManager(Mock.Of<IUmbracoContextAccessor>(accessor => accessor.UmbracoContext == umbCtx), runtime, TestObjects.GetGlobalSettings(), TestHelper.IOHelper);
var mgr = new BackOfficeCookieManager(Mock.Of<IUmbracoContextAccessor>(accessor => accessor.UmbracoContext == umbCtx), runtime, TestObjects.GetGlobalSettings(), IOHelper, AppCaches.RequestCache);
var request = new Mock<OwinRequest>();
request.Setup(owinRequest => owinRequest.Uri).Returns(new Uri("http://localhost/umbraco"));
@@ -29,6 +29,7 @@ namespace Umbraco.Tests.TestHelpers
protected override void Compose()
{
base.Compose();
base.Compose();
Composition.RegisterUnique<IPublishedValueFallback, PublishedValueFallback>();
Composition.RegisterUnique<IProfilingLogger, ProfilingLogger>();
@@ -28,22 +28,14 @@ namespace Umbraco.Web
public class BatchedDatabaseServerMessenger : DatabaseServerMessenger
{
private readonly IUmbracoDatabaseFactory _databaseFactory;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IRequestCache _requestCache;
public BatchedDatabaseServerMessenger(
IRuntimeState runtime,
IUmbracoDatabaseFactory databaseFactory,
IScopeProvider scopeProvider,
ISqlContext sqlContext,
IProfilingLogger proflog,
DatabaseServerMessengerOptions options,
IHostingEnvironment hostingEnvironment,
CacheRefresherCollection cacheRefreshers,
IHttpContextAccessor httpContextAccessor)
IRuntimeState runtime, IUmbracoDatabaseFactory databaseFactory, IScopeProvider scopeProvider, ISqlContext sqlContext, IProfilingLogger proflog, DatabaseServerMessengerOptions options, IHostingEnvironment hostingEnvironment, CacheRefresherCollection cacheRefreshers, IRequestCache requestCache)
: base(runtime, scopeProvider, sqlContext, proflog, true, options, hostingEnvironment, cacheRefreshers)
{
_databaseFactory = databaseFactory;
_httpContextAccessor = httpContextAccessor;
_requestCache = requestCache;
}
// invoked by DatabaseServerRegistrarAndMessengerComponent
@@ -112,24 +104,18 @@ namespace Umbraco.Web
protected ICollection<RefreshInstructionEnvelope> GetBatch(bool create)
{
// try get the http context from the UmbracoContext, we do this because in the case we are launching an async
// thread and we know that the cache refreshers will execute, we will ensure the UmbracoContext and therefore we
// can get the http context from it
var httpContext = (_httpContextAccessor.HttpContext)
// if this is null, it could be that an async thread is calling this method that we weren't aware of and the UmbracoContext
// wasn't ensured at the beginning of the thread. We can try to see if the HttpContext.Current is available which might be
// the case if the asp.net synchronization context has kicked in
?? (HttpContext.Current == null ? null : new HttpContextWrapper(HttpContext.Current));
// if no context was found, return null - we cannot not batch
if (httpContext == null) return null;
var key = typeof (BatchedDatabaseServerMessenger).Name;
if (!_requestCache.IsAvailable) return null;
// no thread-safety here because it'll run in only 1 thread (request) at a time
var batch = (ICollection<RefreshInstructionEnvelope>)httpContext.Items[key];
var batch = (ICollection<RefreshInstructionEnvelope>)_requestCache.Get(key);
if (batch == null && create)
httpContext.Items[key] = batch = new List<RefreshInstructionEnvelope>();
{
batch = new List<RefreshInstructionEnvelope>();
_requestCache.Set(key, batch);
}
return batch;
}
-14
View File
@@ -4,20 +4,6 @@ namespace Umbraco.Core
{
public static class HttpContextExtensions
{
public static T GetContextItem<T>(this HttpContextBase httpContext, string key)
{
if (httpContext == null) return default(T);
if (httpContext.Items[key] == null) return default(T);
var val = httpContext.Items[key].TryConvertTo<T>();
if (val) return val.Result;
return default(T);
}
public static T GetContextItem<T>(this HttpContext httpContext, string key)
{
return new HttpContextWrapper(httpContext).GetContextItem<T>(key);
}
public static string GetCurrentRequestIpAddress(this HttpContextBase httpContext)
{
if (httpContext == null)
@@ -1,31 +1,29 @@
using System;
using Umbraco.Core.Cache;
namespace Umbraco.Web
{
internal class HttpContextUmbracoContextAccessor : IUmbracoContextAccessor
{
private readonly IRequestCache _requestCache;
private const string HttpContextItemKey = "Umbraco.Web.UmbracoContext";
private readonly IHttpContextAccessor _httpContextAccessor;
public HttpContextUmbracoContextAccessor(IHttpContextAccessor httpContextAccessor)
public HttpContextUmbracoContextAccessor(IRequestCache requestCache)
{
_httpContextAccessor = httpContextAccessor;
_requestCache = requestCache;
}
public IUmbracoContext UmbracoContext
{
get
{
var httpContext = _httpContextAccessor.HttpContext;
if (httpContext == null) throw new Exception("oops:httpContext");
return (IUmbracoContext) httpContext.Items[HttpContextItemKey];
if (!_requestCache.IsAvailable) throw new InvalidOperationException("No request cache available");
return (IUmbracoContext) _requestCache.Get(HttpContextItemKey);
}
set
{
var httpContext = _httpContextAccessor.HttpContext;
if (httpContext == null) throw new Exception("oops:httpContext");
httpContext.Items[HttpContextItemKey] = value;
if (!_requestCache.IsAvailable) throw new InvalidOperationException("No request cache available");
_requestCache.Set(HttpContextItemKey, value);
}
}
}
+16 -10
View File
@@ -1,6 +1,7 @@
using System;
using System.Runtime.Remoting.Messaging;
using Umbraco.Core;
using Umbraco.Core.Cache;
namespace Umbraco.Web
{
@@ -16,12 +17,14 @@ namespace Umbraco.Web
public abstract class HybridAccessorBase<T>
where T : class
{
private readonly IRequestCache _requestCache;
// ReSharper disable StaticMemberInGenericType
private static readonly object Locker = new object();
private static bool _registered;
// ReSharper restore StaticMemberInGenericType
private readonly IHttpContextAccessor _httpContextAccessor;
// private readonly IHttpContextAccessor _httpContextAccessor;
protected abstract string ItemKey { get; }
@@ -50,9 +53,9 @@ namespace Umbraco.Web
}
}
protected HybridAccessorBase(IHttpContextAccessor httpContextAccessor)
protected HybridAccessorBase(IRequestCache requestCache)
{
_httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
_requestCache = requestCache ?? throw new ArgumentNullException(nameof(requestCache));
lock (Locker)
{
@@ -81,20 +84,23 @@ namespace Umbraco.Web
{
get
{
var httpContext = _httpContextAccessor.HttpContext;
if (httpContext == null) return NonContextValue;
return (T) httpContext.Items[ItemKey];
if (!_requestCache.IsAvailable)
{
return NonContextValue;
}
return (T) _requestCache.Get(ItemKey);
}
set
{
var httpContext = _httpContextAccessor.HttpContext;
if (httpContext == null)
if (!_requestCache.IsAvailable)
{
NonContextValue = value;
}
else if (value == null)
httpContext.Items.Remove(ItemKey);
_requestCache.Remove(ItemKey);
else
httpContext.Items[ItemKey] = value;
_requestCache.Set(ItemKey, value);
}
}
}
@@ -1,4 +1,5 @@
using Umbraco.Core.Events;
using Umbraco.Core.Cache;
using Umbraco.Core.Events;
namespace Umbraco.Web
{
@@ -6,8 +7,8 @@ namespace Umbraco.Web
{
protected override string ItemKey => "Umbraco.Core.Events.HybridEventMessagesAccessor";
public HybridEventMessagesAccessor(IHttpContextAccessor httpContextAccessor)
: base(httpContextAccessor)
public HybridEventMessagesAccessor(IRequestCache requestCache)
: base(requestCache)
{ }
public EventMessages EventMessages
@@ -1,4 +1,6 @@
namespace Umbraco.Web
using Umbraco.Core.Cache;
namespace Umbraco.Web
{
/// <summary>
/// Implements a hybrid <see cref="IUmbracoContextAccessor"/>.
@@ -8,8 +10,8 @@
/// <summary>
/// Initializes a new instance of the <see cref="HybridUmbracoContextAccessor"/> class.
/// </summary>
public HybridUmbracoContextAccessor(IHttpContextAccessor httpContextAccessor)
: base(httpContextAccessor)
public HybridUmbracoContextAccessor(IRequestCache requestCache)
: base(requestCache)
{ }
/// <inheritdoc />
@@ -1,4 +1,5 @@
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.Cache;
using Umbraco.Core.Models.PublishedContent;
namespace Umbraco.Web.Models.PublishedContent
{
@@ -7,22 +8,22 @@ namespace Umbraco.Web.Models.PublishedContent
/// </summary>
public class HttpContextVariationContextAccessor : IVariationContextAccessor
{
public const string ContextKey = "Umbraco.Web.Models.PublishedContent.DefaultVariationContextAccessor";
public readonly IHttpContextAccessor HttpContextAccessor;
private readonly IRequestCache _requestCache;
private const string _contextKey = "Umbraco.Web.Models.PublishedContent.DefaultVariationContextAccessor";
/// <summary>
/// Initializes a new instance of the <see cref="HttpContextVariationContextAccessor"/> class.
/// </summary>
public HttpContextVariationContextAccessor(IHttpContextAccessor httpContextAccessor)
public HttpContextVariationContextAccessor(IRequestCache requestCache)
{
HttpContextAccessor = httpContextAccessor;
_requestCache = requestCache;
}
/// <inheritdoc />
public VariationContext VariationContext
{
get => (VariationContext) HttpContextAccessor.HttpContext?.Items[ContextKey];
set => HttpContextAccessor.HttpContext.Items[ContextKey] = value;
get => (VariationContext) _requestCache.Get(_contextKey);
set => _requestCache.Set(_contextKey, value);
}
}
}
@@ -1,4 +1,5 @@
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.Cache;
using Umbraco.Core.Models.PublishedContent;
namespace Umbraco.Web.Models.PublishedContent
{
@@ -7,8 +8,8 @@ namespace Umbraco.Web.Models.PublishedContent
/// </summary>
internal class HybridVariationContextAccessor : HybridAccessorBase<VariationContext>, IVariationContextAccessor
{
public HybridVariationContextAccessor(IHttpContextAccessor httpContextAccessor)
: base(httpContextAccessor)
public HybridVariationContextAccessor(IRequestCache requestCache)
: base(requestCache)
{ }
/// <inheritdoc />
@@ -23,4 +24,4 @@ namespace Umbraco.Web.Models.PublishedContent
set => Value = value;
}
}
}
}
@@ -11,6 +11,7 @@ using Microsoft.Owin.Security.DataHandler;
using Microsoft.Owin.Security.DataProtection;
using Owin;
using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.IO;
@@ -153,9 +154,10 @@ namespace Umbraco.Web.Security
IGlobalSettings globalSettings,
ISecuritySection securitySection,
IIOHelper ioHelper,
IRequestCache requestCache,
IUmbracoSettingsSection umbracoSettingsSection)
{
return app.UseUmbracoBackOfficeCookieAuthentication(umbracoContextAccessor, runtimeState, userService, globalSettings, securitySection, ioHelper, PipelineStage.Authenticate, umbracoSettingsSection);
return app.UseUmbracoBackOfficeCookieAuthentication(umbracoContextAccessor, runtimeState, userService, globalSettings, securitySection, ioHelper, requestCache, PipelineStage.Authenticate, umbracoSettingsSection);
}
/// <summary>
@@ -179,11 +181,12 @@ namespace Umbraco.Web.Security
IGlobalSettings globalSettings,
ISecuritySection securitySection,
IIOHelper ioHelper,
IRequestCache requestCache,
PipelineStage stage,
IUmbracoSettingsSection umbracoSettingsSection)
{
//Create the default options and provider
var authOptions = app.CreateUmbracoCookieAuthOptions(umbracoContextAccessor, globalSettings, runtimeState, securitySection, ioHelper);
var authOptions = app.CreateUmbracoCookieAuthOptions(umbracoContextAccessor, globalSettings, runtimeState, securitySection, ioHelper, requestCache);
authOptions.Provider = new BackOfficeCookieAuthenticationProvider(userService, runtimeState, globalSettings, ioHelper, umbracoSettingsSection)
{
@@ -198,7 +201,7 @@ namespace Umbraco.Web.Security
};
return app.UseUmbracoBackOfficeCookieAuthentication(umbracoContextAccessor, runtimeState, globalSettings, securitySection, ioHelper, authOptions, stage);
return app.UseUmbracoBackOfficeCookieAuthentication(umbracoContextAccessor, runtimeState, globalSettings, securitySection, ioHelper, requestCache, authOptions, stage);
}
/// <summary>
@@ -215,7 +218,8 @@ namespace Umbraco.Web.Security
/// Configurable pipeline stage
/// </param>
/// <returns></returns>
public static IAppBuilder UseUmbracoBackOfficeCookieAuthentication(this IAppBuilder app, IUmbracoContextAccessor umbracoContextAccessor, IRuntimeState runtimeState, IGlobalSettings globalSettings, ISecuritySection securitySection, IIOHelper ioHelper, CookieAuthenticationOptions cookieOptions, PipelineStage stage)
public static IAppBuilder UseUmbracoBackOfficeCookieAuthentication(this IAppBuilder app, IUmbracoContextAccessor umbracoContextAccessor, IRuntimeState runtimeState, IGlobalSettings globalSettings,
ISecuritySection securitySection, IIOHelper ioHelper, IRequestCache requestCache, CookieAuthenticationOptions cookieOptions, PipelineStage stage)
{
if (app == null) throw new ArgumentNullException(nameof(app));
if (runtimeState == null) throw new ArgumentNullException(nameof(runtimeState));
@@ -225,7 +229,7 @@ namespace Umbraco.Web.Security
if (cookieOptions.Provider is BackOfficeCookieAuthenticationProvider == false)
throw new ArgumentException($"cookieOptions.Provider must be of type {typeof(BackOfficeCookieAuthenticationProvider)}.", nameof(cookieOptions));
app.UseUmbracoBackOfficeCookieAuthenticationInternal(cookieOptions, runtimeState, stage);
app.UseUmbracoBackOfficeCookieAuthenticationInternal(cookieOptions, runtimeState, requestCache, stage);
//don't apply if app is not ready
if (runtimeState.Level != RuntimeLevel.Upgrade && runtimeState.Level != RuntimeLevel.Run) return app;
@@ -233,7 +237,7 @@ namespace Umbraco.Web.Security
var cookieAuthOptions = app.CreateUmbracoCookieAuthOptions(
umbracoContextAccessor, globalSettings, runtimeState, securitySection,
//This defines the explicit path read cookies from for this middleware
ioHelper, new[] {$"{globalSettings.Path}/backoffice/UmbracoApi/Authentication/GetRemainingTimeoutSeconds"});
ioHelper, requestCache, new[] {$"{globalSettings.Path}/backoffice/UmbracoApi/Authentication/GetRemainingTimeoutSeconds"});
cookieAuthOptions.Provider = cookieOptions.Provider;
//This is a custom middleware, we need to return the user's remaining logged in seconds
@@ -279,7 +283,7 @@ namespace Umbraco.Web.Security
});
}
private static void UseUmbracoBackOfficeCookieAuthenticationInternal(this IAppBuilder app, CookieAuthenticationOptions options, IRuntimeState runtimeState, PipelineStage stage)
private static void UseUmbracoBackOfficeCookieAuthenticationInternal(this IAppBuilder app, CookieAuthenticationOptions options, IRuntimeState runtimeState, IRequestCache requestCache, PipelineStage stage)
{
if (app == null) throw new ArgumentNullException(nameof(app));
if (runtimeState == null) throw new ArgumentNullException(nameof(runtimeState));
@@ -290,7 +294,7 @@ namespace Umbraco.Web.Security
if (runtimeState.Level == RuntimeLevel.Upgrade || runtimeState.Level == RuntimeLevel.Run)
{
//Then our custom middlewares
app.Use(typeof(ForceRenewalCookieAuthenticationMiddleware), app, options, Current.UmbracoContextAccessor);
app.Use(typeof(ForceRenewalCookieAuthenticationMiddleware), app, options, Current.UmbracoContextAccessor, requestCache);
app.Use(typeof(FixWindowsAuthMiddlware));
}
@@ -311,9 +315,9 @@ namespace Umbraco.Web.Security
/// <remarks>
/// By default this will be configured to execute on PipelineStage.Authenticate
/// </remarks>
public static IAppBuilder UseUmbracoBackOfficeExternalCookieAuthentication(this IAppBuilder app, IUmbracoContextAccessor umbracoContextAccessor, IRuntimeState runtimeState,IGlobalSettings globalSettings, IIOHelper ioHelper)
public static IAppBuilder UseUmbracoBackOfficeExternalCookieAuthentication(this IAppBuilder app, IUmbracoContextAccessor umbracoContextAccessor, IRuntimeState runtimeState,IGlobalSettings globalSettings, IIOHelper ioHelper, IRequestCache requestCache)
{
return app.UseUmbracoBackOfficeExternalCookieAuthentication(umbracoContextAccessor, runtimeState, globalSettings, ioHelper, PipelineStage.Authenticate);
return app.UseUmbracoBackOfficeExternalCookieAuthentication(umbracoContextAccessor, runtimeState, globalSettings, ioHelper, requestCache, PipelineStage.Authenticate);
}
/// <summary>
@@ -329,7 +333,7 @@ namespace Umbraco.Web.Security
/// <returns></returns>
public static IAppBuilder UseUmbracoBackOfficeExternalCookieAuthentication(this IAppBuilder app,
IUmbracoContextAccessor umbracoContextAccessor, IRuntimeState runtimeState,
IGlobalSettings globalSettings, IIOHelper ioHelper, PipelineStage stage)
IGlobalSettings globalSettings, IIOHelper ioHelper, IRequestCache requestCache, PipelineStage stage)
{
if (app == null) throw new ArgumentNullException(nameof(app));
if (runtimeState == null) throw new ArgumentNullException(nameof(runtimeState));
@@ -342,7 +346,7 @@ namespace Umbraco.Web.Security
CookieName = Constants.Security.BackOfficeExternalCookieName,
ExpireTimeSpan = TimeSpan.FromMinutes(5),
//Custom cookie manager so we can filter requests
CookieManager = new BackOfficeCookieManager(umbracoContextAccessor, runtimeState, globalSettings, ioHelper),
CookieManager = new BackOfficeCookieManager(umbracoContextAccessor, runtimeState, globalSettings, ioHelper, requestCache),
CookiePath = "/",
CookieSecure = globalSettings.UseHttps ? CookieSecureOption.Always : CookieSecureOption.SameAsRequest,
CookieHttpOnly = true,
@@ -369,9 +373,9 @@ namespace Umbraco.Web.Security
/// <remarks>
/// By default this will be configured to execute on PipelineStage.PostAuthenticate
/// </remarks>
public static IAppBuilder UseUmbracoPreviewAuthentication(this IAppBuilder app, IUmbracoContextAccessor umbracoContextAccessor, IRuntimeState runtimeState, IGlobalSettings globalSettings, ISecuritySection securitySettings, IIOHelper ioHelper)
public static IAppBuilder UseUmbracoPreviewAuthentication(this IAppBuilder app, IUmbracoContextAccessor umbracoContextAccessor, IRuntimeState runtimeState, IGlobalSettings globalSettings, ISecuritySection securitySettings, IIOHelper ioHelper, IRequestCache requestCache)
{
return app.UseUmbracoPreviewAuthentication(umbracoContextAccessor, runtimeState, globalSettings, securitySettings, ioHelper, PipelineStage.PostAuthenticate);
return app.UseUmbracoPreviewAuthentication(umbracoContextAccessor, runtimeState, globalSettings, securitySettings, ioHelper, requestCache, PipelineStage.PostAuthenticate);
}
/// <summary>
@@ -389,11 +393,11 @@ namespace Umbraco.Web.Security
/// This ensures that during a preview request that the back office use is also Authenticated and that the back office Identity
/// is added as a secondary identity to the current IPrincipal so it can be used to Authorize the previewed document.
/// </remarks>
public static IAppBuilder UseUmbracoPreviewAuthentication(this IAppBuilder app, IUmbracoContextAccessor umbracoContextAccessor, IRuntimeState runtimeState, IGlobalSettings globalSettings, ISecuritySection securitySettings, IIOHelper ioHelper, PipelineStage stage)
public static IAppBuilder UseUmbracoPreviewAuthentication(this IAppBuilder app, IUmbracoContextAccessor umbracoContextAccessor, IRuntimeState runtimeState, IGlobalSettings globalSettings, ISecuritySection securitySettings, IIOHelper ioHelper, IRequestCache requestCache, PipelineStage stage)
{
if (runtimeState.Level != RuntimeLevel.Run) return app;
var authOptions = app.CreateUmbracoCookieAuthOptions(umbracoContextAccessor, globalSettings, runtimeState, securitySettings, ioHelper);
var authOptions = app.CreateUmbracoCookieAuthOptions(umbracoContextAccessor, globalSettings, runtimeState, securitySettings, ioHelper, requestCache);
app.Use(typeof(PreviewAuthenticationMiddleware), authOptions, Current.Configs.Global(), ioHelper);
// This middleware must execute at least on PostAuthentication, by default it is on Authorize
@@ -424,7 +428,7 @@ namespace Umbraco.Web.Security
/// <returns></returns>
public static UmbracoBackOfficeCookieAuthOptions CreateUmbracoCookieAuthOptions(this IAppBuilder app,
IUmbracoContextAccessor umbracoContextAccessor,
IGlobalSettings globalSettings, IRuntimeState runtimeState, ISecuritySection securitySettings, IIOHelper ioHelper, string[] explicitPaths = null)
IGlobalSettings globalSettings, IRuntimeState runtimeState, ISecuritySection securitySettings, IIOHelper ioHelper, IRequestCache requestCache, string[] explicitPaths = null)
{
//this is how aspnet wires up the default AuthenticationTicket protector so we'll use the same code
var ticketDataFormat = new TicketDataFormat(
@@ -439,7 +443,8 @@ namespace Umbraco.Web.Security
globalSettings,
runtimeState,
ticketDataFormat,
ioHelper);
ioHelper,
requestCache);
return authOptions;
}
@@ -169,18 +169,6 @@ namespace Umbraco.Web.Security
return true;
}
/// <summary>
/// This will force ticket renewal in the OWIN pipeline
/// </summary>
/// <param name="http"></param>
/// <returns></returns>
internal static bool RenewUmbracoAuthTicket(this HttpContext http)
{
if (http == null) throw new ArgumentNullException("http");
http.Items[Constants.Security.ForceReAuthFlag] = true;
return true;
}
/// <summary>
/// returns the number of seconds the user has until their auth session times out
/// </summary>
@@ -5,6 +5,7 @@ using System.Web;
using Microsoft.Owin;
using Microsoft.Owin.Infrastructure;
using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Configuration;
using Umbraco.Core.IO;
using Umbraco.Core.Security;
@@ -24,19 +25,21 @@ namespace Umbraco.Web.Security
private readonly IRuntimeState _runtime;
private readonly IGlobalSettings _globalSettings;
private readonly IIOHelper _ioHelper;
private readonly IRequestCache _requestCache;
private readonly string[] _explicitPaths;
private readonly string _getRemainingSecondsPath;
public BackOfficeCookieManager(IUmbracoContextAccessor umbracoContextAccessor, IRuntimeState runtime, IGlobalSettings globalSettings, IIOHelper ioHelper)
: this(umbracoContextAccessor, runtime, globalSettings, ioHelper,null)
public BackOfficeCookieManager(IUmbracoContextAccessor umbracoContextAccessor, IRuntimeState runtime, IGlobalSettings globalSettings, IIOHelper ioHelper, IRequestCache requestCache)
: this(umbracoContextAccessor, runtime, globalSettings, ioHelper,requestCache, null)
{ }
public BackOfficeCookieManager(IUmbracoContextAccessor umbracoContextAccessor, IRuntimeState runtime, IGlobalSettings globalSettings, IIOHelper ioHelper, IEnumerable<string> explicitPaths)
public BackOfficeCookieManager(IUmbracoContextAccessor umbracoContextAccessor, IRuntimeState runtime, IGlobalSettings globalSettings, IIOHelper ioHelper, IRequestCache requestCache, IEnumerable<string> explicitPaths)
{
_umbracoContextAccessor = umbracoContextAccessor;
_runtime = runtime;
_globalSettings = globalSettings;
_ioHelper = ioHelper;
_requestCache = requestCache;
_explicitPaths = explicitPaths?.ToArray();
_getRemainingSecondsPath = $"{globalSettings.Path}/backoffice/UmbracoApi/Authentication/GetRemainingTimeoutSeconds";
}
@@ -89,8 +92,6 @@ namespace Umbraco.Web.Security
return false;
var request = owinContext.Request;
var httpContext = owinContext.TryGetHttpContext();
//check the explicit paths
if (_explicitPaths != null)
{
@@ -102,7 +103,7 @@ namespace Umbraco.Web.Security
if (//check the explicit flag
(checkForceAuthTokens && owinContext.Get<bool?>(Constants.Security.ForceReAuthFlag) != null)
|| (checkForceAuthTokens && httpContext.Success && httpContext.Result.Items[Constants.Security.ForceReAuthFlag] != null)
|| (checkForceAuthTokens && _requestCache.IsAvailable && _requestCache.Get(Constants.Security.ForceReAuthFlag) != null)
//check back office
|| request.Uri.IsBackOfficeRequest(HttpRuntime.AppDomainAppVirtualPath, _globalSettings, _ioHelper)
//check installer
@@ -3,6 +3,7 @@ using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.Infrastructure;
using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Security;
namespace Umbraco.Web.Security
@@ -13,10 +14,12 @@ namespace Umbraco.Web.Security
internal class ForceRenewalCookieAuthenticationHandler : AuthenticationHandler<CookieAuthenticationOptions>
{
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
private readonly IRequestCache _requestCache;
public ForceRenewalCookieAuthenticationHandler(IUmbracoContextAccessor umbracoContextAccessor)
public ForceRenewalCookieAuthenticationHandler(IUmbracoContextAccessor umbracoContextAccessor, IRequestCache requestCache)
{
_umbracoContextAccessor = umbracoContextAccessor;
_requestCache = requestCache;
}
/// <summary>
@@ -68,9 +71,8 @@ namespace Umbraco.Web.Security
//This is auth'd normally, so OWIN will naturally take care of the cookie renewal
if (normalAuthUrl) return Task.FromResult(0);
var httpCtx = Context.TryGetHttpContext();
//check for the special flag in either the owin or http context
var shouldRenew = Context.Get<bool?>(Constants.Security.ForceReAuthFlag) != null || (httpCtx.Success && httpCtx.Result.Items[Constants.Security.ForceReAuthFlag] != null);
var shouldRenew = Context.Get<bool?>(Constants.Security.ForceReAuthFlag) != null || (_requestCache.IsAvailable && _requestCache.Get(Constants.Security.ForceReAuthFlag) != null);
if (shouldRenew)
{
@@ -2,6 +2,7 @@
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.Infrastructure;
using Owin;
using Umbraco.Core.Cache;
namespace Umbraco.Web.Security
{
@@ -11,19 +12,22 @@ namespace Umbraco.Web.Security
internal class ForceRenewalCookieAuthenticationMiddleware : CookieAuthenticationMiddleware
{
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
private readonly IRequestCache _requestCache;
public ForceRenewalCookieAuthenticationMiddleware(
OwinMiddleware next,
IAppBuilder app,
UmbracoBackOfficeCookieAuthOptions options,
IUmbracoContextAccessor umbracoContextAccessor) : base(next, app, options)
IUmbracoContextAccessor umbracoContextAccessor,
IRequestCache requestCache) : base(next, app, options)
{
_umbracoContextAccessor = umbracoContextAccessor;
_requestCache = requestCache;
}
protected override AuthenticationHandler<CookieAuthenticationOptions> CreateHandler()
{
return new ForceRenewalCookieAuthenticationHandler(_umbracoContextAccessor);
return new ForceRenewalCookieAuthenticationHandler(_umbracoContextAccessor, _requestCache);
}
}
}
@@ -3,6 +3,7 @@ using Microsoft.Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.IO;
@@ -23,7 +24,8 @@ namespace Umbraco.Web.Security
IGlobalSettings globalSettings,
IRuntimeState runtimeState,
ISecureDataFormat<AuthenticationTicket> secureDataFormat,
IIOHelper ioHelper)
IIOHelper ioHelper,
IRequestCache requestCache)
{
var secureDataFormat1 = secureDataFormat ?? throw new ArgumentNullException(nameof(secureDataFormat));
LoginTimeoutMinutes = globalSettings.TimeOutInMinutes;
@@ -40,7 +42,7 @@ namespace Umbraco.Web.Security
TicketDataFormat = new UmbracoSecureDataFormat(LoginTimeoutMinutes, secureDataFormat1);
//Custom cookie manager so we can filter requests
CookieManager = new BackOfficeCookieManager(umbracoContextAccessor, runtimeState, globalSettings, ioHelper, explicitPaths);
CookieManager = new BackOfficeCookieManager(umbracoContextAccessor, runtimeState, globalSettings, ioHelper, requestCache, explicitPaths);
}
/// <summary>
+5 -3
View File
@@ -2,6 +2,7 @@
using Microsoft.Owin;
using Owin;
using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.IO;
@@ -34,6 +35,7 @@ namespace Umbraco.Web
protected UmbracoMapper Mapper => Current.Mapper;
protected IIpResolver IpResolver => Current.IpResolver;
protected IIOHelper IOHelper => Current.IOHelper;
protected IRequestCache RequestCache => Current.AppCaches.RequestCache;
/// <summary>
/// Main startup method
@@ -102,9 +104,9 @@ namespace Umbraco.Web
// Ensure owin is configured for Umbraco back office authentication.
// Front-end OWIN cookie configuration must be declared after this code.
app
.UseUmbracoBackOfficeCookieAuthentication(UmbracoContextAccessor, RuntimeState, Services.UserService, GlobalSettings, UmbracoSettings.Security, IOHelper, PipelineStage.Authenticate, UmbracoSettings)
.UseUmbracoBackOfficeExternalCookieAuthentication(UmbracoContextAccessor, RuntimeState, GlobalSettings, IOHelper, PipelineStage.Authenticate)
.UseUmbracoPreviewAuthentication(UmbracoContextAccessor, RuntimeState, GlobalSettings, UmbracoSettings.Security, IOHelper, PipelineStage.Authorize);
.UseUmbracoBackOfficeCookieAuthentication(UmbracoContextAccessor, RuntimeState, Services.UserService, GlobalSettings, UmbracoSettings.Security, IOHelper, RequestCache, PipelineStage.Authenticate, UmbracoSettings)
.UseUmbracoBackOfficeExternalCookieAuthentication(UmbracoContextAccessor, RuntimeState, GlobalSettings, IOHelper, RequestCache, PipelineStage.Authenticate)
.UseUmbracoPreviewAuthentication(UmbracoContextAccessor, RuntimeState, GlobalSettings, UmbracoSettings.Security, IOHelper, RequestCache, PipelineStage.Authorize);
}
public static event EventHandler<OwinMiddlewareConfiguredEventArgs> MiddlewareConfigured;
+12 -8
View File
@@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Web;
using System.Web.Routing;
using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Configuration;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
@@ -38,6 +39,7 @@ namespace Umbraco.Web
private readonly IPublishedRouter _publishedRouter;
private readonly IUmbracoContextFactory _umbracoContextFactory;
private readonly RoutableDocumentFilter _routableDocumentLookup;
private readonly IRequestCache _requestCache;
public UmbracoInjectedModule(
IGlobalSettings globalSettings,
@@ -45,7 +47,8 @@ namespace Umbraco.Web
ILogger logger,
IPublishedRouter publishedRouter,
IUmbracoContextFactory umbracoContextFactory,
RoutableDocumentFilter routableDocumentLookup)
RoutableDocumentFilter routableDocumentLookup,
IRequestCache requestCache)
{
_globalSettings = globalSettings;
_runtime = runtime;
@@ -53,6 +56,7 @@ namespace Umbraco.Web
_publishedRouter = publishedRouter;
_umbracoContextFactory = umbracoContextFactory;
_routableDocumentLookup = routableDocumentLookup;
_requestCache = requestCache;
}
#region HttpModule event handlers
@@ -305,15 +309,15 @@ namespace Umbraco.Web
/// Any object that is in the HttpContext.Items collection that is IDisposable will get disposed on the end of the request
/// </summary>
/// <param name="http"></param>
private void DisposeHttpContextItems(HttpContext http)
private void DisposeRequestCacheItems(HttpContext http, IRequestCache requestCache)
{
// do not process if client-side request
if (http.Request.Url.IsClientSideRequest())
return;
//get a list of keys to dispose
var keys = new HashSet<object>();
foreach (DictionaryEntry i in http.Items)
var keys = new HashSet<string>();
foreach (var i in requestCache)
{
if (i.Value is IDisposeOnRequestEnd || i.Key is IDisposeOnRequestEnd)
{
@@ -325,7 +329,7 @@ namespace Umbraco.Web
{
try
{
http.Items[k].DisposeIfDisposable();
requestCache.Get(k).DisposeIfDisposable();
}
catch (Exception ex)
{
@@ -376,7 +380,7 @@ namespace Umbraco.Web
{
var httpContext = ((HttpApplication) sender).Context;
LogHttpRequest.TryGetCurrentHttpRequestId(out var httpRequestId, Current.AppCaches.RequestCache);
LogHttpRequest.TryGetCurrentHttpRequestId(out var httpRequestId, _requestCache);
_logger.Verbose<UmbracoModule>("Begin request [{HttpRequestId}]: {RequestUrl}", httpRequestId, httpContext.Request.Url);
BeginRequest(new HttpContextWrapper(httpContext));
@@ -421,14 +425,14 @@ namespace Umbraco.Web
if (Current.UmbracoContext != null && Current.UmbracoContext.IsFrontEndUmbracoRequest)
{
LogHttpRequest.TryGetCurrentHttpRequestId(out var httpRequestId, Current.AppCaches.RequestCache);
LogHttpRequest.TryGetCurrentHttpRequestId(out var httpRequestId, _requestCache);
_logger.Verbose<UmbracoModule>("End Request [{HttpRequestId}]: {RequestUrl} ({RequestDuration}ms)", httpRequestId, httpContext.Request.Url, DateTime.Now.Subtract(Current.UmbracoContext.ObjectCreated).TotalMilliseconds);
}
UmbracoModule.OnEndRequest(this, new UmbracoRequestEventArgs(Current.UmbracoContext, new HttpContextWrapper(httpContext)));
DisposeHttpContextItems(httpContext);
DisposeRequestCacheItems(httpContext, _requestCache);
};
}