Merge remote-tracking branch 'origin/netcore/dev' into netcore/feature/merge-v8-05032021
# Conflicts: # src/Umbraco.Infrastructure/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs # src/Umbraco.Infrastructure/Search/ExamineComponent.cs
This commit is contained in:
261
src/Umbraco.Web.Common/Cache/HttpContextRequestAppCache.cs
Normal file
261
src/Umbraco.Web.Common/Cache/HttpContextRequestAppCache.cs
Normal file
@@ -0,0 +1,261 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Umbraco.Cms.Core.Events;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Core.Cache
|
||||
{
|
||||
/// <summary>
|
||||
/// Implements a <see cref="IAppCache"/> on top of <see cref="IHttpContextAccessor"/>
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>The HttpContext is not thread safe and no part of it is which means we need to include our own thread
|
||||
/// safety mechanisms. This relies on notifications: <see cref="UmbracoRequestBegin"/> and <see cref="UmbracoRequestEnd"/>
|
||||
/// in order to facilitate the correct locking and releasing allocations.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public class HttpContextRequestAppCache : FastDictionaryAppCacheBase, IRequestCache
|
||||
{
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpRequestAppCache"/> class with a context, for unit tests!
|
||||
/// </summary>
|
||||
public HttpContextRequestAppCache(IHttpContextAccessor httpContextAccessor) => _httpContextAccessor = httpContextAccessor;
|
||||
|
||||
public bool IsAvailable => TryGetContextItems(out _);
|
||||
|
||||
private bool TryGetContextItems(out IDictionary<object, object> items)
|
||||
{
|
||||
items = _httpContextAccessor.HttpContext?.Items;
|
||||
return items != null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object Get(string key, Func<object> factory)
|
||||
{
|
||||
//no place to cache so just return the callback result
|
||||
if (!TryGetContextItems(out var items))
|
||||
{
|
||||
return factory();
|
||||
}
|
||||
|
||||
key = GetCacheKey(key);
|
||||
|
||||
Lazy<object> result;
|
||||
|
||||
try
|
||||
{
|
||||
EnterWriteLock();
|
||||
result = items[key] as Lazy<object>; // null if key not found
|
||||
|
||||
// cannot create value within the lock, so if result.IsValueCreated is false, just
|
||||
// do nothing here - means that if creation throws, a race condition could cause
|
||||
// more than one thread to reach the return statement below and throw - accepted.
|
||||
|
||||
if (result == null || SafeLazy.GetSafeLazyValue(result, true) == null) // get non-created as NonCreatedValue & exceptions as null
|
||||
{
|
||||
result = SafeLazy.GetSafeLazy(factory);
|
||||
items[key] = result;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
ExitWriteLock();
|
||||
}
|
||||
|
||||
// using GetSafeLazy and GetSafeLazyValue ensures that we don't cache
|
||||
// exceptions (but try again and again) and silently eat them - however at
|
||||
// some point we have to report them - so need to re-throw here
|
||||
|
||||
// this does not throw anymore
|
||||
//return result.Value;
|
||||
|
||||
var value = result.Value; // will not throw (safe lazy)
|
||||
if (value is SafeLazy.ExceptionHolder eh)
|
||||
{
|
||||
eh.Exception.Throw(); // throw once!
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public bool Set(string key, object value)
|
||||
{
|
||||
//no place to cache so just return the callback result
|
||||
if (!TryGetContextItems(out var items))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
key = GetCacheKey(key);
|
||||
try
|
||||
{
|
||||
|
||||
EnterWriteLock();
|
||||
items[key] = SafeLazy.GetSafeLazy(() => value);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ExitWriteLock();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Remove(string key)
|
||||
{
|
||||
//no place to cache so just return the callback result
|
||||
if (!TryGetContextItems(out var items))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
key = GetCacheKey(key);
|
||||
try
|
||||
{
|
||||
|
||||
EnterWriteLock();
|
||||
items.Remove(key);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ExitWriteLock();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#region Entries
|
||||
|
||||
protected override IEnumerable<KeyValuePair<object, object>> GetDictionaryEntries()
|
||||
{
|
||||
const string prefix = CacheItemPrefix + "-";
|
||||
|
||||
if (!TryGetContextItems(out var items))
|
||||
return Enumerable.Empty<KeyValuePair<object, object>>();
|
||||
|
||||
return items.Cast<KeyValuePair<object, object>>()
|
||||
.Where(x => x.Key is string s && s.StartsWith(prefix));
|
||||
}
|
||||
|
||||
protected override void RemoveEntry(string key)
|
||||
{
|
||||
if (!TryGetContextItems(out var items))
|
||||
return;
|
||||
|
||||
items.Remove(key);
|
||||
}
|
||||
|
||||
protected override object GetEntry(string key)
|
||||
{
|
||||
return !TryGetContextItems(out var items) ? null : items[key];
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Lock
|
||||
|
||||
protected override void EnterReadLock()
|
||||
{
|
||||
object locker = GetLock();
|
||||
if (locker == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Monitor.Enter(locker);
|
||||
}
|
||||
|
||||
protected override void EnterWriteLock()
|
||||
{
|
||||
object locker = GetLock();
|
||||
if (locker == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Monitor.Enter(locker);
|
||||
}
|
||||
|
||||
protected override void ExitReadLock()
|
||||
{
|
||||
object locker = GetLock();
|
||||
if (locker == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (Monitor.IsEntered(locker))
|
||||
{
|
||||
Monitor.Exit(locker);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ExitWriteLock()
|
||||
{
|
||||
object locker = GetLock();
|
||||
if (locker == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (Monitor.IsEntered(locker))
|
||||
{
|
||||
Monitor.Exit(locker);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
|
||||
{
|
||||
if (!TryGetContextItems(out IDictionary<object, object> items))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<object, object> item in items)
|
||||
{
|
||||
yield return new KeyValuePair<string, object>(item.Key.ToString(), item.Value);
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
/// <summary>
|
||||
/// Ensures and returns the current lock
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private object GetLock()
|
||||
{
|
||||
HttpContext httpContext = _httpContextAccessor.HttpContext;
|
||||
if (httpContext == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
RequestLock requestLock = httpContext.Features.Get<RequestLock>();
|
||||
if (requestLock != null)
|
||||
{
|
||||
return requestLock.SyncRoot;
|
||||
}
|
||||
|
||||
IFeatureCollection features = httpContext.Features;
|
||||
|
||||
lock (httpContext)
|
||||
{
|
||||
requestLock = new RequestLock();
|
||||
features.Set(requestLock);
|
||||
return requestLock.SyncRoot;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used as Scoped instance to allow locking within a request
|
||||
/// </summary>
|
||||
private class RequestLock
|
||||
{
|
||||
public object SyncRoot { get; } = new object();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -54,6 +54,7 @@ using Umbraco.Cms.Web.Common.Security;
|
||||
using Umbraco.Cms.Web.Common.Templates;
|
||||
using Umbraco.Cms.Web.Common.UmbracoContext;
|
||||
using Umbraco.Core.Events;
|
||||
using static Umbraco.Cms.Core.Cache.HttpContextRequestAppCache;
|
||||
using IHostingEnvironment = Umbraco.Cms.Core.Hosting.IHostingEnvironment;
|
||||
|
||||
namespace Umbraco.Extensions
|
||||
@@ -86,15 +87,18 @@ namespace Umbraco.Extensions
|
||||
|
||||
IHostingEnvironment tempHostingEnvironment = GetTemporaryHostingEnvironment(webHostEnvironment, config);
|
||||
|
||||
var loggingDir = tempHostingEnvironment.MapPathContentRoot(Cms.Core.Constants.SystemDirectories.LogFiles);
|
||||
var loggingDir = tempHostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.LogFiles);
|
||||
var loggingConfig = new LoggingConfiguration(loggingDir);
|
||||
|
||||
services.AddLogger(tempHostingEnvironment, loggingConfig, config);
|
||||
|
||||
// Manually create and register the HttpContextAccessor. In theory this should not be registered
|
||||
// again by the user but if that is the case it's not the end of the world since HttpContextAccessor
|
||||
// is just based on AsyncLocal, see https://github.com/dotnet/aspnetcore/blob/main/src/Http/Http/src/HttpContextAccessor.cs
|
||||
IHttpContextAccessor httpContextAccessor = new HttpContextAccessor();
|
||||
services.AddSingleton(httpContextAccessor);
|
||||
|
||||
var requestCache = new GenericDictionaryRequestAppCache(() => httpContextAccessor.HttpContext?.Items);
|
||||
var requestCache = new HttpContextRequestAppCache(httpContextAccessor);
|
||||
var appCaches = AppCaches.Create(requestCache);
|
||||
services.AddUnique(appCaches);
|
||||
|
||||
@@ -243,7 +247,6 @@ namespace Umbraco.Extensions
|
||||
builder.Services.AddUmbracoImageSharp(builder.Config);
|
||||
|
||||
// AspNetCore specific services
|
||||
builder.Services.AddUnique<IHttpContextAccessor, HttpContextAccessor>();
|
||||
builder.Services.AddUnique<IRequestAccessor, AspNetCoreRequestAccessor>();
|
||||
builder.AddNotificationHandler<UmbracoRequestBegin, AspNetCoreRequestAccessor>();
|
||||
|
||||
@@ -267,10 +270,8 @@ namespace Umbraco.Extensions
|
||||
// register the umbraco context factory
|
||||
|
||||
builder.Services.AddUnique<IUmbracoContextFactory, UmbracoContextFactory>();
|
||||
builder.Services.AddUnique<IBackOfficeSecurityFactory, BackOfficeSecurityFactory>();
|
||||
builder.Services.AddUnique<IBackOfficeSecurityAccessor, HybridBackofficeSecurityAccessor>();
|
||||
builder.AddNotificationHandler<UmbracoRoutedRequest, UmbracoWebsiteSecurityFactory>();
|
||||
builder.Services.AddUnique<IUmbracoWebsiteSecurityAccessor, HybridUmbracoWebsiteSecurityAccessor>();
|
||||
builder.Services.AddUnique<IBackOfficeSecurityAccessor, BackOfficeSecurityAccessor>();
|
||||
builder.Services.AddUnique<IUmbracoWebsiteSecurityAccessor, UmbracoWebsiteSecurityAccessor>();
|
||||
|
||||
var umbracoApiControllerTypes = builder.TypeLoader.GetUmbracoApiControllers().ToList();
|
||||
builder.WithCollectionBuilder<UmbracoApiControllerTypeCollectionBuilder>()
|
||||
@@ -290,6 +291,8 @@ namespace Umbraco.Extensions
|
||||
builder.Services.AddSingleton<ContentModelBinder>();
|
||||
|
||||
builder.Services.AddScoped<UmbracoHelper>();
|
||||
builder.Services.AddScoped<IBackOfficeSecurity, BackOfficeSecurity>();
|
||||
builder.Services.AddScoped<IUmbracoWebsiteSecurity, UmbracoWebsiteSecurity>();
|
||||
|
||||
builder.AddHttpClients();
|
||||
|
||||
|
||||
@@ -1,13 +1,27 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Security.Claims;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
|
||||
namespace Umbraco.Extensions
|
||||
{
|
||||
public static class HttpContextExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the value in the request form or query string for the key
|
||||
/// </summary>
|
||||
public static string GetRequestValue(this HttpContext context, string key)
|
||||
{
|
||||
HttpRequest request = context.Request;
|
||||
if (!request.HasFormContentType)
|
||||
{
|
||||
return request.Query[key];
|
||||
}
|
||||
|
||||
string value = request.Form[key];
|
||||
return value ?? request.Query[key];
|
||||
}
|
||||
|
||||
public static void SetPrincipalForRequest(this HttpContext context, ClaimsPrincipal principal)
|
||||
{
|
||||
context.User = principal;
|
||||
|
||||
@@ -4,12 +4,14 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Extensions;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.Events;
|
||||
using Umbraco.Cms.Core.Hosting;
|
||||
using Umbraco.Cms.Core.Logging;
|
||||
using Umbraco.Cms.Core.Scoping;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Web;
|
||||
using Umbraco.Cms.Infrastructure.PublishedCache;
|
||||
@@ -36,7 +38,6 @@ namespace Umbraco.Cms.Web.Common.Middleware
|
||||
|
||||
private readonly IUmbracoContextFactory _umbracoContextFactory;
|
||||
private readonly IRequestCache _requestCache;
|
||||
private readonly IBackOfficeSecurityFactory _backofficeSecurityFactory;
|
||||
private readonly PublishedSnapshotServiceEventHandler _publishedSnapshotServiceEventHandler;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly IHostingEnvironment _hostingEnvironment;
|
||||
@@ -52,7 +53,6 @@ namespace Umbraco.Cms.Web.Common.Middleware
|
||||
ILogger<UmbracoRequestMiddleware> logger,
|
||||
IUmbracoContextFactory umbracoContextFactory,
|
||||
IRequestCache requestCache,
|
||||
IBackOfficeSecurityFactory backofficeSecurityFactory,
|
||||
PublishedSnapshotServiceEventHandler publishedSnapshotServiceEventHandler,
|
||||
IEventAggregator eventAggregator,
|
||||
IProfiler profiler,
|
||||
@@ -61,7 +61,6 @@ namespace Umbraco.Cms.Web.Common.Middleware
|
||||
_logger = logger;
|
||||
_umbracoContextFactory = umbracoContextFactory;
|
||||
_requestCache = requestCache;
|
||||
_backofficeSecurityFactory = backofficeSecurityFactory;
|
||||
_publishedSnapshotServiceEventHandler = publishedSnapshotServiceEventHandler;
|
||||
_eventAggregator = eventAggregator;
|
||||
_hostingEnvironment = hostingEnvironment;
|
||||
@@ -84,11 +83,6 @@ namespace Umbraco.Cms.Web.Common.Middleware
|
||||
|
||||
EnsureContentCacheInitialized();
|
||||
|
||||
// TODO: This dependency chain is broken and needs to be fixed.
|
||||
// This is required to be called before EnsureUmbracoContext else the UmbracoContext's IBackOfficeSecurity instance is null
|
||||
// This is ugly Temporal Coupling which also means that developers can no longer just use IUmbracoContextFactory the
|
||||
// way it was intended.
|
||||
_backofficeSecurityFactory.EnsureBackOfficeSecurity();
|
||||
UmbracoContextReference umbracoContextReference = _umbracoContextFactory.EnsureUmbracoContext();
|
||||
|
||||
Uri currentApplicationUrl = GetApplicationUrlFromCurrentRequest(context.Request);
|
||||
@@ -133,10 +127,11 @@ namespace Umbraco.Cms.Web.Common.Middleware
|
||||
|
||||
try
|
||||
{
|
||||
DisposeRequestCacheItems(_logger, _requestCache, context.Request);
|
||||
DisposeHttpContextItems(context.Request);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Dispose the umbraco context reference which will in turn dispose the UmbracoContext itself.
|
||||
umbracoContextReference.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -159,9 +154,9 @@ namespace Umbraco.Cms.Web.Common.Middleware
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Any object that is in the HttpContext.Items collection that is IDisposable will get disposed on the end of the request
|
||||
/// Dispose some request scoped objects that we are maintaining the lifecycle for.
|
||||
/// </summary>
|
||||
private static void DisposeRequestCacheItems(ILogger<UmbracoRequestMiddleware> logger, IRequestCache requestCache, HttpRequest request)
|
||||
private void DisposeHttpContextItems(HttpRequest request)
|
||||
{
|
||||
// do not process if client-side request
|
||||
if (request.IsClientSideRequest())
|
||||
@@ -169,37 +164,9 @@ namespace Umbraco.Cms.Web.Common.Middleware
|
||||
return;
|
||||
}
|
||||
|
||||
// get a list of keys to dispose
|
||||
var keys = new HashSet<string>();
|
||||
foreach (var i in requestCache)
|
||||
{
|
||||
if (i.Value is IDisposeOnRequestEnd || i.Key is IDisposeOnRequestEnd)
|
||||
{
|
||||
keys.Add(i.Key);
|
||||
}
|
||||
}
|
||||
|
||||
// dispose each item and key that was found as disposable.
|
||||
foreach (var k in keys)
|
||||
{
|
||||
try
|
||||
{
|
||||
requestCache.Get(k).DisposeIfDisposable();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError("Could not dispose item with key " + k, ex);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
k.DisposeIfDisposable();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError("Could not dispose item key " + k, ex);
|
||||
}
|
||||
}
|
||||
// ensure this is disposed by DI at the end of the request
|
||||
IHttpScopeReference httpScopeReference = request.HttpContext.RequestServices.GetRequiredService<IHttpScopeReference>();
|
||||
httpScopeReference.Register();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -774,9 +774,10 @@ namespace Umbraco.Cms.Web.Common.ModelsBuilder
|
||||
}
|
||||
|
||||
public void Stop(bool immediate)
|
||||
{
|
||||
{
|
||||
_watcher.EnableRaisingEvents = false;
|
||||
_watcher.Dispose();
|
||||
_locker.Dispose();
|
||||
_hostingLifetime.UnregisterObject(this);
|
||||
}
|
||||
|
||||
|
||||
@@ -69,9 +69,10 @@ namespace Umbraco.Cms.Web.Common.ModelsBuilder
|
||||
/// This is used so that when new PureLive models are built, the entire razor stack is re-constructed so all razor
|
||||
/// caches and assembly references, etc... are cleared.
|
||||
/// </remarks>
|
||||
internal class RefreshingRazorViewEngine : IRazorViewEngine
|
||||
internal class RefreshingRazorViewEngine : IRazorViewEngine, IDisposable
|
||||
{
|
||||
private IRazorViewEngine _current;
|
||||
private bool _disposedValue;
|
||||
private readonly PureLiveModelFactory _pureLiveModelFactory;
|
||||
private readonly Func<IRazorViewEngine> _defaultRazorViewEngineFactory;
|
||||
private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim();
|
||||
@@ -172,5 +173,24 @@ namespace Umbraco.Cms.Web.Common.ModelsBuilder
|
||||
_locker.ExitReadLock();
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_locker.Dispose();
|
||||
}
|
||||
|
||||
_disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
|
||||
namespace Umbraco.Cms.Web.Common.Security
|
||||
{
|
||||
public class BackOfficeSecurityAccessor : IBackOfficeSecurityAccessor
|
||||
{
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BackOfficeSecurityAccessor"/> class.
|
||||
/// </summary>
|
||||
public BackOfficeSecurityAccessor(IHttpContextAccessor httpContextAccessor) => _httpContextAccessor = httpContextAccessor;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current <see cref="IBackOfficeSecurity"/> object.
|
||||
/// </summary>
|
||||
public IBackOfficeSecurity BackOfficeSecurity
|
||||
=> _httpContextAccessor.HttpContext?.RequestServices?.GetService<IBackOfficeSecurity>();
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models.Membership;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
|
||||
namespace Umbraco.Cms.Web.Common.Security
|
||||
{
|
||||
// TODO: This is only for the back office, does it need to be in common? YES currently UmbracoContext has an transitive dependency on this which needs to be fixed/reviewed.
|
||||
|
||||
public class BackOfficeSecurityFactory: IBackOfficeSecurityFactory
|
||||
{
|
||||
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
|
||||
private readonly IUserService _userService;
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
|
||||
public BackOfficeSecurityFactory(
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
|
||||
IUserService userService,
|
||||
IHttpContextAccessor httpContextAccessor)
|
||||
{
|
||||
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
|
||||
_userService = userService;
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
}
|
||||
|
||||
public void EnsureBackOfficeSecurity()
|
||||
{
|
||||
if (_backOfficeSecurityAccessor.BackOfficeSecurity is null)
|
||||
{
|
||||
_backOfficeSecurityAccessor.BackOfficeSecurity = new BackOfficeSecurity(_userService, _httpContextAccessor);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
|
||||
namespace Umbraco.Cms.Web.Common.Security
|
||||
{
|
||||
|
||||
public class UmbracoWebsiteSecurityAccessor : IUmbracoWebsiteSecurityAccessor
|
||||
{
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UmbracoWebsiteSecurityAccessor"/> class.
|
||||
/// </summary>
|
||||
public UmbracoWebsiteSecurityAccessor(IHttpContextAccessor httpContextAccessor) => _httpContextAccessor = httpContextAccessor;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="IUmbracoWebsiteSecurity"/> object.
|
||||
/// </summary>
|
||||
public IUmbracoWebsiteSecurity WebsiteSecurity
|
||||
=> _httpContextAccessor.HttpContext?.RequestServices?.GetService<IUmbracoWebsiteSecurity>();
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Umbraco.Cms.Core.Events;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Strings;
|
||||
|
||||
namespace Umbraco.Cms.Web.Common.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// Ensures that the <see cref="IUmbracoWebsiteSecurity"/> is populated on a front-end request
|
||||
/// </summary>
|
||||
internal sealed class UmbracoWebsiteSecurityFactory : INotificationHandler<UmbracoRoutedRequest>
|
||||
{
|
||||
private readonly IUmbracoWebsiteSecurityAccessor _umbracoWebsiteSecurityAccessor;
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
private readonly IMemberService _memberService;
|
||||
private readonly IMemberTypeService _memberTypeService;
|
||||
private readonly IShortStringHelper _shortStringHelper;
|
||||
|
||||
public UmbracoWebsiteSecurityFactory(
|
||||
IUmbracoWebsiteSecurityAccessor umbracoWebsiteSecurityAccessor,
|
||||
IHttpContextAccessor httpContextAccessor,
|
||||
IMemberService memberService,
|
||||
IMemberTypeService memberTypeService,
|
||||
IShortStringHelper shortStringHelper)
|
||||
{
|
||||
_umbracoWebsiteSecurityAccessor = umbracoWebsiteSecurityAccessor;
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
_memberService = memberService;
|
||||
_memberTypeService = memberTypeService;
|
||||
_shortStringHelper = shortStringHelper;
|
||||
}
|
||||
|
||||
public void Handle(UmbracoRoutedRequest notification)
|
||||
{
|
||||
if (_umbracoWebsiteSecurityAccessor.WebsiteSecurity is null)
|
||||
{
|
||||
_umbracoWebsiteSecurityAccessor.WebsiteSecurity = new UmbracoWebsiteSecurity(
|
||||
_httpContextAccessor,
|
||||
_memberService,
|
||||
_memberTypeService,
|
||||
_shortStringHelper);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,12 +21,12 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="5.0.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="5.0.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="5.0.3" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="5.0.3" />
|
||||
<PackageReference Include="MiniProfiler.AspNetCore.Mvc" Version="4.2.22" />
|
||||
<PackageReference Include="NETStandard.Library" Version="2.0.3" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="3.4.0" />
|
||||
<PackageReference Include="SixLabors.ImageSharp.Web" Version="1.0.1" />
|
||||
<PackageReference Include="SixLabors.ImageSharp.Web" Version="1.0.2" />
|
||||
<PackageReference Include="Smidge" Version="3.2.0" />
|
||||
<PackageReference Include="Smidge.Nuglify" Version="3.2.0" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Extensions;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Hosting;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.PublishedCache;
|
||||
using Umbraco.Cms.Core.Routing;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Web;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
@@ -13,17 +14,17 @@ namespace Umbraco.Cms.Web.Common.UmbracoContext
|
||||
/// <summary>
|
||||
/// Class that encapsulates Umbraco information of a specific HTTP request
|
||||
/// </summary>
|
||||
public class UmbracoContext : DisposableObjectSlim, IDisposeOnRequestEnd, IUmbracoContext
|
||||
public class UmbracoContext : DisposableObjectSlim, IUmbracoContext
|
||||
{
|
||||
private readonly IHostingEnvironment _hostingEnvironment;
|
||||
private readonly UriUtility _uriUtility;
|
||||
private readonly ICookieManager _cookieManager;
|
||||
private readonly IRequestAccessor _requestAccessor;
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
private readonly Lazy<IPublishedSnapshot> _publishedSnapshot;
|
||||
private string _previewToken;
|
||||
private bool? _previewing;
|
||||
private readonly IBackOfficeSecurity _backofficeSecurity;
|
||||
private readonly UmbracoRequestPaths _umbracoRequestPaths;
|
||||
private Uri _requestUrl;
|
||||
private Uri _originalRequestUrl;
|
||||
private Uri _cleanedUmbracoUrl;
|
||||
|
||||
@@ -33,13 +34,12 @@ namespace Umbraco.Cms.Web.Common.UmbracoContext
|
||||
// warn: does *not* manage setting any IUmbracoContextAccessor
|
||||
internal UmbracoContext(
|
||||
IPublishedSnapshotService publishedSnapshotService,
|
||||
IBackOfficeSecurity backofficeSecurity,
|
||||
UmbracoRequestPaths umbracoRequestPaths,
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
IVariationContextAccessor variationContextAccessor,
|
||||
UriUtility uriUtility,
|
||||
ICookieManager cookieManager,
|
||||
IRequestAccessor requestAccessor)
|
||||
IHttpContextAccessor httpContextAccessor)
|
||||
{
|
||||
if (publishedSnapshotService == null)
|
||||
{
|
||||
@@ -50,11 +50,9 @@ namespace Umbraco.Cms.Web.Common.UmbracoContext
|
||||
_uriUtility = uriUtility;
|
||||
_hostingEnvironment = hostingEnvironment;
|
||||
_cookieManager = cookieManager;
|
||||
_requestAccessor = requestAccessor;
|
||||
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
ObjectCreated = DateTime.Now;
|
||||
UmbracoRequestId = Guid.NewGuid();
|
||||
_backofficeSecurity = backofficeSecurity ?? throw new ArgumentNullException(nameof(backofficeSecurity));
|
||||
_umbracoRequestPaths = umbracoRequestPaths;
|
||||
|
||||
// beware - we cannot expect a current user here, so detecting preview mode must be a lazy thing
|
||||
@@ -72,6 +70,11 @@ namespace Umbraco.Cms.Web.Common.UmbracoContext
|
||||
/// </remarks>
|
||||
internal Guid UmbracoRequestId { get; }
|
||||
|
||||
// lazily get/create a Uri for the current request
|
||||
private Uri RequestUrl => _requestUrl ??= _httpContextAccessor.HttpContext is null
|
||||
? null
|
||||
: new Uri(_httpContextAccessor.HttpContext.Request.GetEncodedUrl());
|
||||
|
||||
/// <inheritdoc/>
|
||||
// set the urls lazily, no need to allocate until they are needed...
|
||||
// NOTE: The request will not be available during app startup so we can only set this to an absolute URL of localhost, this
|
||||
@@ -79,7 +82,7 @@ namespace Umbraco.Cms.Web.Common.UmbracoContext
|
||||
// 'could' still generate URLs during startup BUT any domain driven URL generation will not work because it is NOT possible to get
|
||||
// the current domain during application startup.
|
||||
// see: http://issues.umbraco.org/issue/U4-1890
|
||||
public Uri OriginalRequestUrl => _originalRequestUrl ?? (_originalRequestUrl = _requestAccessor.GetRequestUrl() ?? new Uri("http://localhost"));
|
||||
public Uri OriginalRequestUrl => _originalRequestUrl ?? (_originalRequestUrl = RequestUrl ?? new Uri("http://localhost"));
|
||||
|
||||
/// <inheritdoc/>
|
||||
// set the urls lazily, no need to allocate until they are needed...
|
||||
@@ -106,8 +109,8 @@ namespace Umbraco.Cms.Web.Common.UmbracoContext
|
||||
/// <inheritdoc/>
|
||||
public bool IsDebug => // NOTE: the request can be null during app startup!
|
||||
_hostingEnvironment.IsDebugMode
|
||||
&& (string.IsNullOrEmpty(_requestAccessor.GetRequestValue("umbdebugshowtrace")) == false
|
||||
|| string.IsNullOrEmpty(_requestAccessor.GetRequestValue("umbdebug")) == false
|
||||
&& (string.IsNullOrEmpty(_httpContextAccessor.HttpContext.GetRequestValue("umbdebugshowtrace")) == false
|
||||
|| string.IsNullOrEmpty(_httpContextAccessor.HttpContext.GetRequestValue("umbdebug")) == false
|
||||
|| string.IsNullOrEmpty(_cookieManager.GetCookieValue("UMB-DEBUG")) == false);
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -140,10 +143,9 @@ namespace Umbraco.Cms.Web.Common.UmbracoContext
|
||||
|
||||
private void DetectPreviewMode()
|
||||
{
|
||||
Uri requestUrl = _requestAccessor.GetRequestUrl();
|
||||
if (requestUrl != null
|
||||
&& _umbracoRequestPaths.IsBackOfficeRequest(requestUrl.AbsolutePath) == false
|
||||
&& _backofficeSecurity.CurrentUser != null)
|
||||
if (RequestUrl != null
|
||||
&& _umbracoRequestPaths.IsBackOfficeRequest(RequestUrl.AbsolutePath) == false
|
||||
&& _httpContextAccessor.HttpContext?.GetCurrentIdentity() != null)
|
||||
{
|
||||
var previewToken = _cookieManager.GetCookieValue(Core.Constants.Web.PreviewCookieName); // may be null or empty
|
||||
_previewToken = previewToken.IsNullOrWhiteSpace() ? null : previewToken;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Hosting;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.PublishedCache;
|
||||
using Umbraco.Cms.Core.Routing;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Web;
|
||||
|
||||
namespace Umbraco.Cms.Web.Common.UmbracoContext
|
||||
@@ -18,12 +18,10 @@ namespace Umbraco.Cms.Web.Common.UmbracoContext
|
||||
private readonly IPublishedSnapshotService _publishedSnapshotService;
|
||||
private readonly IVariationContextAccessor _variationContextAccessor;
|
||||
private readonly IDefaultCultureAccessor _defaultCultureAccessor;
|
||||
|
||||
private readonly UmbracoRequestPaths _umbracoRequestPaths;
|
||||
private readonly IHostingEnvironment _hostingEnvironment;
|
||||
private readonly ICookieManager _cookieManager;
|
||||
private readonly IRequestAccessor _requestAccessor;
|
||||
private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor;
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
private readonly UriUtility _uriUtility;
|
||||
|
||||
/// <summary>
|
||||
@@ -38,8 +36,7 @@ namespace Umbraco.Cms.Web.Common.UmbracoContext
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
UriUtility uriUtility,
|
||||
ICookieManager cookieManager,
|
||||
IRequestAccessor requestAccessor,
|
||||
IBackOfficeSecurityAccessor backofficeSecurityAccessor)
|
||||
IHttpContextAccessor httpContextAccessor)
|
||||
{
|
||||
_umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor));
|
||||
_publishedSnapshotService = publishedSnapshotService ?? throw new ArgumentNullException(nameof(publishedSnapshotService));
|
||||
@@ -49,8 +46,7 @@ namespace Umbraco.Cms.Web.Common.UmbracoContext
|
||||
_hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment));
|
||||
_uriUtility = uriUtility ?? throw new ArgumentNullException(nameof(uriUtility));
|
||||
_cookieManager = cookieManager ?? throw new ArgumentNullException(nameof(cookieManager));
|
||||
_requestAccessor = requestAccessor ?? throw new ArgumentNullException(nameof(requestAccessor));
|
||||
_backofficeSecurityAccessor = backofficeSecurityAccessor ?? throw new ArgumentNullException(nameof(backofficeSecurityAccessor));
|
||||
_httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
|
||||
}
|
||||
|
||||
private IUmbracoContext CreateUmbracoContext()
|
||||
@@ -75,13 +71,12 @@ namespace Umbraco.Cms.Web.Common.UmbracoContext
|
||||
|
||||
return new UmbracoContext(
|
||||
_publishedSnapshotService,
|
||||
_backofficeSecurityAccessor.BackOfficeSecurity,
|
||||
_umbracoRequestPaths,
|
||||
_hostingEnvironment,
|
||||
_variationContextAccessor,
|
||||
_uriUtility,
|
||||
_cookieManager,
|
||||
_requestAccessor);
|
||||
_httpContextAccessor);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
Reference in New Issue
Block a user