Isolated more web usages - Membership and Request stuff
- Moved a few more files
This commit is contained in:
24
src/Umbraco.Web/AspNet/AspNetRequestAccessor.cs
Normal file
24
src/Umbraco.Web/AspNet/AspNetRequestAccessor.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using Umbraco.Core.Request;
|
||||
|
||||
namespace Umbraco.Web.AspNet
|
||||
{
|
||||
public class AspNetRequestAccessor : IRequestAccessor
|
||||
{
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
|
||||
public AspNetRequestAccessor(IHttpContextAccessor httpContextAccessor)
|
||||
{
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
}
|
||||
|
||||
public string GetRequestValue(string name)
|
||||
{
|
||||
return _httpContextAccessor.GetRequiredHttpContext().Request[name];
|
||||
}
|
||||
|
||||
public string GetQueryStringValue(string name)
|
||||
{
|
||||
return _httpContextAccessor.GetRequiredHttpContext().Request.QueryString[name];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
using System.Web;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Net;
|
||||
|
||||
namespace Umbraco.Web
|
||||
{
|
||||
internal class AspNetSessionIdResolver : ISessionIdResolver
|
||||
{
|
||||
public string SessionId => HttpContext.Current?.Session?.SessionID;
|
||||
}
|
||||
}
|
||||
26
src/Umbraco.Web/AspNet/AspNetSessionManager.cs
Normal file
26
src/Umbraco.Web/AspNet/AspNetSessionManager.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using System.Web;
|
||||
using Umbraco.Core.Session;
|
||||
using Umbraco.Net;
|
||||
|
||||
namespace Umbraco.Web.AspNet
|
||||
{
|
||||
public class AspNetSessionManager: ISessionManager, ISessionIdResolver
|
||||
{
|
||||
|
||||
public AspNetSessionManager()
|
||||
{
|
||||
}
|
||||
|
||||
public object GetSessionValue(string sessionName)
|
||||
{
|
||||
return HttpContext.Current.Session[sessionName];
|
||||
}
|
||||
|
||||
public void SetSessionValue(string sessionName, object value)
|
||||
{
|
||||
HttpContext.Current.Session[sessionName] = value;
|
||||
}
|
||||
|
||||
public string SessionId => HttpContext.Current?.Session?.SessionID;
|
||||
}
|
||||
}
|
||||
49
src/Umbraco.Web/Macros/MacroRenderer.cs
Executable file → Normal file
49
src/Umbraco.Web/Macros/MacroRenderer.cs
Executable file → Normal file
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@@ -12,10 +11,11 @@ using Umbraco.Core.Events;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Macros;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
using Umbraco.Core.Request;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Security;
|
||||
using Umbraco.Core.Session;
|
||||
|
||||
namespace Umbraco.Web.Macros
|
||||
{
|
||||
@@ -29,10 +29,23 @@ namespace Umbraco.Web.Macros
|
||||
private readonly IMacroService _macroService;
|
||||
private readonly IIOHelper _ioHelper;
|
||||
private readonly ICookieManager _cookieManager;
|
||||
private readonly IUserService _userService;
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
private readonly IMemberUserKeyProvider _memberUserKeyProvider;
|
||||
private readonly ISessionManager _sessionManager;
|
||||
private readonly IRequestAccessor _requestAccessor;
|
||||
|
||||
public MacroRenderer(IProfilingLogger plogger, IUmbracoContextAccessor umbracoContextAccessor, IContentSection contentSection, ILocalizedTextService textService, AppCaches appCaches, IMacroService macroService, IUserService userService, IHttpContextAccessor httpContextAccessor, IIOHelper ioHelper, ICookieManager cookieManager)
|
||||
|
||||
public MacroRenderer(
|
||||
IProfilingLogger plogger,
|
||||
IUmbracoContextAccessor umbracoContextAccessor,
|
||||
IContentSection contentSection,
|
||||
ILocalizedTextService textService,
|
||||
AppCaches appCaches,
|
||||
IMacroService macroService,
|
||||
IIOHelper ioHelper,
|
||||
ICookieManager cookieManager,
|
||||
IMemberUserKeyProvider memberUserKeyProvider,
|
||||
ISessionManager sessionManager,
|
||||
IRequestAccessor requestAccessor)
|
||||
{
|
||||
_plogger = plogger ?? throw new ArgumentNullException(nameof(plogger));
|
||||
_umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor));
|
||||
@@ -42,8 +55,9 @@ namespace Umbraco.Web.Macros
|
||||
_macroService = macroService ?? throw new ArgumentNullException(nameof(macroService));
|
||||
_ioHelper = ioHelper ?? throw new ArgumentNullException(nameof(ioHelper));
|
||||
_cookieManager = cookieManager;
|
||||
_userService = userService ?? throw new ArgumentNullException(nameof(userService));
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
_memberUserKeyProvider = memberUserKeyProvider;
|
||||
_sessionManager = sessionManager;
|
||||
_requestAccessor = requestAccessor;
|
||||
}
|
||||
|
||||
#region MacroContent cache
|
||||
@@ -63,12 +77,9 @@ namespace Umbraco.Web.Macros
|
||||
{
|
||||
object key = 0;
|
||||
|
||||
if (_httpContextAccessor.HttpContext?.User?.Identity?.IsAuthenticated ?? false)
|
||||
if (_umbracoContextAccessor.UmbracoContext.Security.IsAuthenticated())
|
||||
{
|
||||
//ugh, membershipproviders :(
|
||||
var provider = MembershipProviderExtensions.GetMembersMembershipProvider();
|
||||
var member = MembershipProviderExtensions.GetCurrentUser(provider);
|
||||
key = member?.ProviderUserKey ?? 0;
|
||||
key = _memberUserKeyProvider.GetMemberProviderUserKey() ?? 0;
|
||||
}
|
||||
|
||||
id.AppendFormat("m{0}-", key);
|
||||
@@ -127,10 +138,8 @@ namespace Umbraco.Web.Macros
|
||||
// do not cache if it should cache by member and there's not member
|
||||
if (model.CacheByMember)
|
||||
{
|
||||
var provider = MembershipProviderExtensions.GetMembersMembershipProvider();
|
||||
var member = MembershipProviderExtensions.GetCurrentUser(provider);
|
||||
var key = member?.ProviderUserKey;
|
||||
if (key == null) return;
|
||||
var key = _memberUserKeyProvider.GetMemberProviderUserKey();
|
||||
if (key is null) return;
|
||||
}
|
||||
|
||||
// remember when we cache the content
|
||||
@@ -364,8 +373,6 @@ namespace Umbraco.Web.Macros
|
||||
return attributeValue;
|
||||
}
|
||||
|
||||
var context = _httpContextAccessor.HttpContext;
|
||||
|
||||
foreach (var token in tokens)
|
||||
{
|
||||
var isToken = token.Length > 4 && token[0] == '[' && token[token.Length - 1] == ']' && validTypes.Contains(token[1]);
|
||||
@@ -383,10 +390,10 @@ namespace Umbraco.Web.Macros
|
||||
switch (type)
|
||||
{
|
||||
case '@':
|
||||
attributeValue = context?.Request[name];
|
||||
attributeValue = _requestAccessor.GetRequestValue(name);
|
||||
break;
|
||||
case '%':
|
||||
attributeValue = context?.Session[name]?.ToString();
|
||||
attributeValue = _sessionManager.GetSessionValue(name)?.ToString();
|
||||
if (string.IsNullOrEmpty(attributeValue))
|
||||
attributeValue = _cookieManager.GetCookieValue(name);
|
||||
break;
|
||||
|
||||
17
src/Umbraco.Web/Macros/MemberUserKeyProvider.cs
Normal file
17
src/Umbraco.Web/Macros/MemberUserKeyProvider.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Web.Security;
|
||||
|
||||
namespace Umbraco.Web.Macros
|
||||
{
|
||||
internal class MemberUserKeyProvider : IMemberUserKeyProvider
|
||||
{
|
||||
public object GetMemberProviderUserKey()
|
||||
{
|
||||
//ugh, membershipproviders :(
|
||||
var provider = MembershipProviderExtensions.GetMembersMembershipProvider();
|
||||
var member = MembershipProviderExtensions.GetCurrentUser(provider);
|
||||
|
||||
return member?.ProviderUserKey;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Configuration.UmbracoSettings;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Umbraco.Web.Routing
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides an implementation of <see cref="IContentFinder"/> that handles page identifiers.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>Handles <c>/1234</c> where <c>1234</c> is the identified of a document.</para>
|
||||
/// </remarks>
|
||||
public class ContentFinderByIdPath : IContentFinder
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
private readonly IWebRoutingSection _webRoutingSection;
|
||||
|
||||
public ContentFinderByIdPath(IWebRoutingSection webRoutingSection, ILogger logger, IHttpContextAccessor httpContextAccessor)
|
||||
{
|
||||
_webRoutingSection = webRoutingSection ?? throw new System.ArgumentNullException(nameof(webRoutingSection));
|
||||
_logger = logger ?? throw new System.ArgumentNullException(nameof(logger));
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to find and assign an Umbraco document to a <c>PublishedRequest</c>.
|
||||
/// </summary>
|
||||
/// <param name="frequest">The <c>PublishedRequest</c>.</param>
|
||||
/// <returns>A value indicating whether an Umbraco document was found and assigned.</returns>
|
||||
public bool TryFindContent(IPublishedRequest frequest)
|
||||
{
|
||||
|
||||
if (frequest.UmbracoContext != null && frequest.UmbracoContext.InPreviewMode == false
|
||||
&& _webRoutingSection.DisableFindContentByIdPath)
|
||||
return false;
|
||||
|
||||
IPublishedContent node = null;
|
||||
var path = frequest.Uri.GetAbsolutePathDecoded();
|
||||
|
||||
var nodeId = -1;
|
||||
if (path != "/") // no id if "/"
|
||||
{
|
||||
var noSlashPath = path.Substring(1);
|
||||
|
||||
if (int.TryParse(noSlashPath, out nodeId) == false)
|
||||
nodeId = -1;
|
||||
|
||||
if (nodeId > 0)
|
||||
{
|
||||
_logger.Debug<ContentFinderByIdPath>("Id={NodeId}", nodeId);
|
||||
node = frequest.UmbracoContext.Content.GetById(nodeId);
|
||||
|
||||
if (node != null)
|
||||
{
|
||||
var httpContext = _httpContextAccessor.GetRequiredHttpContext();
|
||||
//if we have a node, check if we have a culture in the query string
|
||||
if (httpContext.Request.QueryString.ContainsKey("culture"))
|
||||
{
|
||||
//we're assuming it will match a culture, if an invalid one is passed in, an exception will throw (there is no TryGetCultureInfo method), i think this is ok though
|
||||
frequest.Culture = CultureInfo.GetCultureInfo(httpContext.Request.QueryString["culture"]);
|
||||
}
|
||||
|
||||
frequest.PublishedContent = node;
|
||||
_logger.Debug<ContentFinderByIdPath>("Found node with id={PublishedContentId}", frequest.PublishedContent.Id);
|
||||
}
|
||||
else
|
||||
{
|
||||
nodeId = -1; // trigger message below
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (nodeId == -1)
|
||||
_logger.Debug<ContentFinderByIdPath>("Not a node id");
|
||||
|
||||
return node != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
namespace Umbraco.Web.Routing
|
||||
{
|
||||
/// <summary>
|
||||
/// This looks up a document by checking for the umbPageId of a request/query string
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is used by library.RenderTemplate and also some of the macro rendering functionality like in
|
||||
/// macroResultWrapper.aspx
|
||||
/// </remarks>
|
||||
public class ContentFinderByPageIdQuery : IContentFinder
|
||||
{
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
|
||||
public ContentFinderByPageIdQuery(IHttpContextAccessor httpContextAccessor)
|
||||
{
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
}
|
||||
|
||||
public bool TryFindContent(IPublishedRequest frequest)
|
||||
{
|
||||
int pageId;
|
||||
if (int.TryParse(_httpContextAccessor.GetRequiredHttpContext().Request["umbPageID"], out pageId))
|
||||
{
|
||||
var doc = frequest.UmbracoContext.Content.GetById(pageId);
|
||||
|
||||
if (doc != null)
|
||||
{
|
||||
frequest.PublishedContent = doc;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,772 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Web.Composing;
|
||||
using Umbraco.Core.Configuration.UmbracoSettings;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Macros;
|
||||
using Umbraco.Web.Security;
|
||||
|
||||
namespace Umbraco.Web.Routing
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides the default <see cref="IPublishedRouter"/> implementation.
|
||||
/// </summary>
|
||||
public class PublishedRouter : IPublishedRouter
|
||||
{
|
||||
private readonly IWebRoutingSection _webRoutingSection;
|
||||
private readonly ContentFinderCollection _contentFinders;
|
||||
private readonly IContentLastChanceFinder _contentLastChanceFinder;
|
||||
private readonly ServiceContext _services;
|
||||
private readonly IProfilingLogger _profilingLogger;
|
||||
private readonly IVariationContextAccessor _variationContextAccessor;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IUmbracoSettingsSection _umbracoSettingsSection;
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
private readonly IPublishedUrlProvider _publishedUrlProvider;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PublishedRouter"/> class.
|
||||
/// </summary>
|
||||
public PublishedRouter(
|
||||
IWebRoutingSection webRoutingSection,
|
||||
ContentFinderCollection contentFinders,
|
||||
IContentLastChanceFinder contentLastChanceFinder,
|
||||
IVariationContextAccessor variationContextAccessor,
|
||||
ServiceContext services,
|
||||
IProfilingLogger proflog,
|
||||
IUmbracoSettingsSection umbracoSettingsSection,
|
||||
IHttpContextAccessor httpContextAccessor,
|
||||
IPublishedUrlProvider publishedUrlProvider)
|
||||
{
|
||||
_webRoutingSection = webRoutingSection ?? throw new ArgumentNullException(nameof(webRoutingSection));
|
||||
_contentFinders = contentFinders ?? throw new ArgumentNullException(nameof(contentFinders));
|
||||
_contentLastChanceFinder = contentLastChanceFinder ?? throw new ArgumentNullException(nameof(contentLastChanceFinder));
|
||||
_services = services ?? throw new ArgumentNullException(nameof(services));
|
||||
_profilingLogger = proflog ?? throw new ArgumentNullException(nameof(proflog));
|
||||
_variationContextAccessor = variationContextAccessor ?? throw new ArgumentNullException(nameof(variationContextAccessor));
|
||||
_logger = proflog;
|
||||
_umbracoSettingsSection = umbracoSettingsSection ?? throw new ArgumentNullException(nameof(umbracoSettingsSection));
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
_publishedUrlProvider = publishedUrlProvider;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IPublishedRequest CreateRequest(IUmbracoContext umbracoContext, Uri uri = null)
|
||||
{
|
||||
return new PublishedRequest(this, umbracoContext, _umbracoSettingsSection, uri ?? umbracoContext.CleanedUmbracoUrl);
|
||||
}
|
||||
|
||||
#region Request
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryRouteRequest(IPublishedRequest request)
|
||||
{
|
||||
// disabled - is it going to change the routing?
|
||||
//_pcr.OnPreparing();
|
||||
|
||||
FindDomain(request);
|
||||
if (request.IsRedirect) return false;
|
||||
if (request.HasPublishedContent) return true;
|
||||
FindPublishedContent(request);
|
||||
if (request.IsRedirect) return false;
|
||||
|
||||
// don't handle anything - we just want to ensure that we find the content
|
||||
//HandlePublishedContent();
|
||||
//FindTemplate();
|
||||
//FollowExternalRedirect();
|
||||
//HandleWildcardDomains();
|
||||
|
||||
// disabled - we just want to ensure that we find the content
|
||||
//_pcr.OnPrepared();
|
||||
|
||||
return request.HasPublishedContent;
|
||||
}
|
||||
|
||||
private void SetVariationContext(string culture)
|
||||
{
|
||||
var variationContext = _variationContextAccessor.VariationContext;
|
||||
if (variationContext != null && variationContext.Culture == culture) return;
|
||||
_variationContextAccessor.VariationContext = new VariationContext(culture);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool PrepareRequest(IPublishedRequest request)
|
||||
{
|
||||
// note - at that point the original legacy module did something do handle IIS custom 404 errors
|
||||
// ie pages looking like /anything.aspx?404;/path/to/document - I guess the reason was to support
|
||||
// "directory urls" without having to do wildcard mapping to ASP.NET on old IIS. This is a pain
|
||||
// to maintain and probably not used anymore - removed as of 06/2012. @zpqrtbnk.
|
||||
//
|
||||
// to trigger Umbraco's not-found, one should configure IIS and/or ASP.NET custom 404 errors
|
||||
// so that they point to a non-existing page eg /redirect-404.aspx
|
||||
// TODO: SD: We need more information on this for when we release 4.10.0 as I'm not sure what this means.
|
||||
|
||||
// trigger the Preparing event - at that point anything can still be changed
|
||||
// the idea is that it is possible to change the uri
|
||||
//
|
||||
request.OnPreparing();
|
||||
|
||||
//find domain
|
||||
FindDomain(request);
|
||||
|
||||
// if request has been flagged to redirect then return
|
||||
// whoever called us is in charge of actually redirecting
|
||||
if (request.IsRedirect)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// set the culture on the thread - once, so it's set when running document lookups
|
||||
Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = request.Culture;
|
||||
SetVariationContext(request.Culture.Name);
|
||||
|
||||
//find the published content if it's not assigned. This could be manually assigned with a custom route handler, or
|
||||
// with something like EnsurePublishedContentRequestAttribute or UmbracoVirtualNodeRouteHandler. Those in turn call this method
|
||||
// to setup the rest of the pipeline but we don't want to run the finders since there's one assigned.
|
||||
if (request.PublishedContent == null)
|
||||
{
|
||||
// find the document & template
|
||||
FindPublishedContentAndTemplate(request);
|
||||
}
|
||||
|
||||
// handle wildcard domains
|
||||
HandleWildcardDomains(request);
|
||||
|
||||
// set the culture on the thread -- again, 'cos it might have changed due to a finder or wildcard domain
|
||||
Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = request.Culture;
|
||||
SetVariationContext(request.Culture.Name);
|
||||
|
||||
// trigger the Prepared event - at that point it is still possible to change about anything
|
||||
// even though the request might be flagged for redirection - we'll redirect _after_ the event
|
||||
//
|
||||
// also, OnPrepared() will make the PublishedRequest readonly, so nothing can change
|
||||
//
|
||||
request.OnPrepared();
|
||||
|
||||
// we don't take care of anything so if the content has changed, it's up to the user
|
||||
// to find out the appropriate template
|
||||
|
||||
//complete the PCR and assign the remaining values
|
||||
return ConfigureRequest(request);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called by PrepareRequest once everything has been discovered, resolved and assigned to the PCR. This method
|
||||
/// finalizes the PCR with the values assigned.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// Returns false if the request was not successfully configured
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// This method logic has been put into it's own method in case developers have created a custom PCR or are assigning their own values
|
||||
/// but need to finalize it themselves.
|
||||
/// </remarks>
|
||||
public bool ConfigureRequest(IPublishedRequest frequest)
|
||||
{
|
||||
if (frequest.HasPublishedContent == false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// set the culture on the thread -- again, 'cos it might have changed in the event handler
|
||||
Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = frequest.Culture;
|
||||
SetVariationContext(frequest.Culture.Name);
|
||||
|
||||
// if request has been flagged to redirect, or has no content to display,
|
||||
// then return - whoever called us is in charge of actually redirecting
|
||||
if (frequest.IsRedirect || frequest.HasPublishedContent == false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// we may be 404 _and_ have a content
|
||||
|
||||
// can't go beyond that point without a PublishedContent to render
|
||||
// it's ok not to have a template, in order to give MVC a chance to hijack routes
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void UpdateRequestToNotFound(IPublishedRequest request)
|
||||
{
|
||||
// clear content
|
||||
var content = request.PublishedContent;
|
||||
request.PublishedContent = null;
|
||||
|
||||
HandlePublishedContent(request); // will go 404
|
||||
FindTemplate(request);
|
||||
|
||||
// if request has been flagged to redirect then return
|
||||
// whoever called us is in charge of redirecting
|
||||
if (request.IsRedirect)
|
||||
return;
|
||||
|
||||
if (request.HasPublishedContent == false)
|
||||
{
|
||||
// means the engine could not find a proper document to handle 404
|
||||
// restore the saved content so we know it exists
|
||||
request.PublishedContent = content;
|
||||
return;
|
||||
}
|
||||
|
||||
if (request.HasTemplate == false)
|
||||
{
|
||||
// means we may have a document, but we have no template
|
||||
// at that point there isn't much we can do and there is no point returning
|
||||
// to Mvc since Mvc can't do much either
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Domain
|
||||
|
||||
/// <summary>
|
||||
/// Finds the site root (if any) matching the http request, and updates the PublishedRequest accordingly.
|
||||
/// </summary>
|
||||
/// <returns>A value indicating whether a domain was found.</returns>
|
||||
internal bool FindDomain(IPublishedRequest request)
|
||||
{
|
||||
const string tracePrefix = "FindDomain: ";
|
||||
|
||||
// note - we are not handling schemes nor ports here.
|
||||
|
||||
_logger.Debug<PublishedRouter>("{TracePrefix}Uri={RequestUri}", tracePrefix, request.Uri);
|
||||
|
||||
var domainsCache = request.UmbracoContext.PublishedSnapshot.Domains;
|
||||
var domains = domainsCache.GetAll(includeWildcards: false).ToList();
|
||||
|
||||
// determines whether a domain corresponds to a published document, since some
|
||||
// domains may exist but on a document that has been unpublished - as a whole - or
|
||||
// that is not published for the domain's culture - in which case the domain does
|
||||
// not apply
|
||||
bool IsPublishedContentDomain(Domain domain)
|
||||
{
|
||||
// just get it from content cache - optimize there, not here
|
||||
var domainDocument = request.UmbracoContext.PublishedSnapshot.Content.GetById(domain.ContentId);
|
||||
|
||||
// not published - at all
|
||||
if (domainDocument == null)
|
||||
return false;
|
||||
|
||||
// invariant - always published
|
||||
if (!domainDocument.ContentType.VariesByCulture())
|
||||
return true;
|
||||
|
||||
// variant, ensure that the culture corresponding to the domain's language is published
|
||||
return domainDocument.Cultures.ContainsKey(domain.Culture.Name);
|
||||
}
|
||||
|
||||
domains = domains.Where(IsPublishedContentDomain).ToList();
|
||||
|
||||
var defaultCulture = domainsCache.DefaultCulture;
|
||||
|
||||
// try to find a domain matching the current request
|
||||
var domainAndUri = DomainUtilities.SelectDomain(domains, request.Uri, defaultCulture: defaultCulture);
|
||||
|
||||
// handle domain - always has a contentId and a culture
|
||||
if (domainAndUri != null)
|
||||
{
|
||||
// matching an existing domain
|
||||
_logger.Debug<PublishedRouter>("{TracePrefix}Matches domain={Domain}, rootId={RootContentId}, culture={Culture}", tracePrefix, domainAndUri.Name, domainAndUri.ContentId, domainAndUri.Culture);
|
||||
|
||||
request.Domain = domainAndUri;
|
||||
request.Culture = domainAndUri.Culture;
|
||||
|
||||
// canonical? not implemented at the moment
|
||||
// if (...)
|
||||
// {
|
||||
// _pcr.RedirectUrl = "...";
|
||||
// return true;
|
||||
// }
|
||||
}
|
||||
else
|
||||
{
|
||||
// not matching any existing domain
|
||||
_logger.Debug<PublishedRouter>("{TracePrefix}Matches no domain", tracePrefix);
|
||||
|
||||
request.Culture = defaultCulture == null ? CultureInfo.CurrentUICulture : new CultureInfo(defaultCulture);
|
||||
}
|
||||
|
||||
_logger.Debug<PublishedRouter>("{TracePrefix}Culture={CultureName}", tracePrefix, request.Culture.Name);
|
||||
|
||||
return request.Domain != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks for wildcard domains in the path and updates <c>Culture</c> accordingly.
|
||||
/// </summary>
|
||||
internal void HandleWildcardDomains(IPublishedRequest request)
|
||||
{
|
||||
const string tracePrefix = "HandleWildcardDomains: ";
|
||||
|
||||
if (request.HasPublishedContent == false)
|
||||
return;
|
||||
|
||||
var nodePath = request.PublishedContent.Path;
|
||||
_logger.Debug<PublishedRouter>("{TracePrefix}Path={NodePath}", tracePrefix, nodePath);
|
||||
var rootNodeId = request.HasDomain ? request.Domain.ContentId : (int?)null;
|
||||
var domain = DomainUtilities.FindWildcardDomainInPath(request.UmbracoContext.PublishedSnapshot.Domains.GetAll(true), nodePath, rootNodeId);
|
||||
|
||||
// always has a contentId and a culture
|
||||
if (domain != null)
|
||||
{
|
||||
request.Culture = domain.Culture;
|
||||
_logger.Debug<PublishedRouter>("{TracePrefix}Got domain on node {DomainContentId}, set culture to {CultureName}", tracePrefix, domain.ContentId, request.Culture.Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Debug<PublishedRouter>("{TracePrefix}No match.", tracePrefix);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Rendering engine
|
||||
|
||||
internal bool FindTemplateRenderingEngineInDirectory(DirectoryInfo directory, string alias, string[] extensions)
|
||||
{
|
||||
if (directory == null || directory.Exists == false)
|
||||
return false;
|
||||
|
||||
var pos = alias.IndexOf('/');
|
||||
if (pos > 0)
|
||||
{
|
||||
// recurse
|
||||
var subdir = directory.GetDirectories(alias.Substring(0, pos)).FirstOrDefault();
|
||||
alias = alias.Substring(pos + 1);
|
||||
return subdir != null && FindTemplateRenderingEngineInDirectory(subdir, alias, extensions);
|
||||
}
|
||||
|
||||
// look here
|
||||
return directory.GetFiles().Any(f => extensions.Any(e => f.Name.InvariantEquals(alias + e)));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Document and template
|
||||
|
||||
/// <inheritdoc />
|
||||
public ITemplate GetTemplate(string alias)
|
||||
{
|
||||
return _services.FileService.GetTemplate(alias);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the Umbraco document (if any) matching the request, and updates the PublishedRequest accordingly.
|
||||
/// </summary>
|
||||
/// <returns>A value indicating whether a document and template were found.</returns>
|
||||
private void FindPublishedContentAndTemplate(IPublishedRequest request)
|
||||
{
|
||||
_logger.Debug<PublishedRouter>("FindPublishedContentAndTemplate: Path={UriAbsolutePath}", request.Uri.AbsolutePath);
|
||||
|
||||
// run the document finders
|
||||
FindPublishedContent(request);
|
||||
|
||||
// if request has been flagged to redirect then return
|
||||
// whoever called us is in charge of actually redirecting
|
||||
// -- do not process anything any further --
|
||||
if (request.IsRedirect)
|
||||
return;
|
||||
|
||||
// not handling umbracoRedirect here but after LookupDocument2
|
||||
// so internal redirect, 404, etc has precedence over redirect
|
||||
|
||||
// handle not-found, redirects, access...
|
||||
HandlePublishedContent(request);
|
||||
|
||||
// find a template
|
||||
FindTemplate(request);
|
||||
|
||||
// handle umbracoRedirect
|
||||
FollowExternalRedirect(request);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to find the document matching the request, by running the IPublishedContentFinder instances.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">There is no finder collection.</exception>
|
||||
internal void FindPublishedContent(IPublishedRequest request)
|
||||
{
|
||||
const string tracePrefix = "FindPublishedContent: ";
|
||||
|
||||
// look for the document
|
||||
// the first successful finder, if any, will set this.PublishedContent, and may also set this.Template
|
||||
// some finders may implement caching
|
||||
|
||||
using (_profilingLogger.DebugDuration<PublishedRouter>(
|
||||
$"{tracePrefix}Begin finders",
|
||||
$"{tracePrefix}End finders, {(request.HasPublishedContent ? "a document was found" : "no document was found")}"))
|
||||
{
|
||||
//iterate but return on first one that finds it
|
||||
var found = _contentFinders.Any(finder =>
|
||||
{
|
||||
_logger.Debug<PublishedRouter>("Finder {ContentFinderType}", finder.GetType().FullName);
|
||||
return finder.TryFindContent(request);
|
||||
});
|
||||
}
|
||||
|
||||
// indicate that the published content (if any) we have at the moment is the
|
||||
// one that was found by the standard finders before anything else took place.
|
||||
request.SetIsInitialPublishedContent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles the published content (if any).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Handles "not found", internal redirects, access validation...
|
||||
/// things that must be handled in one place because they can create loops
|
||||
/// </remarks>
|
||||
private void HandlePublishedContent(IPublishedRequest request)
|
||||
{
|
||||
// because these might loop, we have to have some sort of infinite loop detection
|
||||
int i = 0, j = 0;
|
||||
const int maxLoop = 8;
|
||||
do
|
||||
{
|
||||
_logger.Debug<PublishedRouter>("HandlePublishedContent: Loop {LoopCounter}", i);
|
||||
|
||||
// handle not found
|
||||
if (request.HasPublishedContent == false)
|
||||
{
|
||||
request.Is404 = true;
|
||||
_logger.Debug<PublishedRouter>("HandlePublishedContent: No document, try last chance lookup");
|
||||
|
||||
// if it fails then give up, there isn't much more that we can do
|
||||
if (_contentLastChanceFinder.TryFindContent(request) == false)
|
||||
{
|
||||
_logger.Debug<PublishedRouter>("HandlePublishedContent: Failed to find a document, give up");
|
||||
break;
|
||||
}
|
||||
|
||||
_logger.Debug<PublishedRouter>("HandlePublishedContent: Found a document");
|
||||
}
|
||||
|
||||
// follow internal redirects as long as it's not running out of control ie infinite loop of some sort
|
||||
j = 0;
|
||||
while (FollowInternalRedirects(request) && j++ < maxLoop)
|
||||
{ }
|
||||
if (j == maxLoop) // we're running out of control
|
||||
break;
|
||||
|
||||
// ensure access
|
||||
if (request.HasPublishedContent)
|
||||
EnsurePublishedContentAccess(request);
|
||||
|
||||
// loop while we don't have page, ie the redirect or access
|
||||
// got us to nowhere and now we need to run the notFoundLookup again
|
||||
// as long as it's not running out of control ie infinite loop of some sort
|
||||
|
||||
} while (request.HasPublishedContent == false && i++ < maxLoop);
|
||||
|
||||
if (i == maxLoop || j == maxLoop)
|
||||
{
|
||||
_logger.Debug<PublishedRouter>("HandlePublishedContent: Looks like we are running into an infinite loop, abort");
|
||||
request.PublishedContent = null;
|
||||
}
|
||||
|
||||
_logger.Debug<PublishedRouter>("HandlePublishedContent: End");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Follows internal redirections through the <c>umbracoInternalRedirectId</c> document property.
|
||||
/// </summary>
|
||||
/// <returns>A value indicating whether redirection took place and led to a new published document.</returns>
|
||||
/// <remarks>
|
||||
/// <para>Redirecting to a different site root and/or culture will not pick the new site root nor the new culture.</para>
|
||||
/// <para>As per legacy, if the redirect does not work, we just ignore it.</para>
|
||||
/// </remarks>
|
||||
private bool FollowInternalRedirects(IPublishedRequest request)
|
||||
{
|
||||
if (request.PublishedContent == null)
|
||||
throw new InvalidOperationException("There is no PublishedContent.");
|
||||
|
||||
// don't try to find a redirect if the property doesn't exist
|
||||
if (request.PublishedContent.HasProperty(Constants.Conventions.Content.InternalRedirectId) == false)
|
||||
return false;
|
||||
|
||||
var redirect = false;
|
||||
var valid = false;
|
||||
IPublishedContent internalRedirectNode = null;
|
||||
var internalRedirectId = request.PublishedContent.Value(Constants.Conventions.Content.InternalRedirectId, defaultValue: -1);
|
||||
|
||||
if (internalRedirectId > 0)
|
||||
{
|
||||
// try and get the redirect node from a legacy integer ID
|
||||
valid = true;
|
||||
internalRedirectNode = request.UmbracoContext.Content.GetById(internalRedirectId);
|
||||
}
|
||||
else
|
||||
{
|
||||
var udiInternalRedirectId = request.PublishedContent.Value<GuidUdi>(Constants.Conventions.Content.InternalRedirectId);
|
||||
if (udiInternalRedirectId != null)
|
||||
{
|
||||
// try and get the redirect node from a UDI Guid
|
||||
valid = true;
|
||||
internalRedirectNode = request.UmbracoContext.Content.GetById(udiInternalRedirectId.Guid);
|
||||
}
|
||||
}
|
||||
|
||||
if (valid == false)
|
||||
{
|
||||
// bad redirect - log and display the current page (legacy behavior)
|
||||
_logger.Debug<PublishedRouter>("FollowInternalRedirects: Failed to redirect to id={InternalRedirectId}: value is not an int nor a GuidUdi.",
|
||||
request.PublishedContent.GetProperty(Constants.Conventions.Content.InternalRedirectId).GetSourceValue());
|
||||
}
|
||||
|
||||
if (internalRedirectNode == null)
|
||||
{
|
||||
_logger.Debug<PublishedRouter>("FollowInternalRedirects: Failed to redirect to id={InternalRedirectId}: no such published document.",
|
||||
request.PublishedContent.GetProperty(Constants.Conventions.Content.InternalRedirectId).GetSourceValue());
|
||||
}
|
||||
else if (internalRedirectId == request.PublishedContent.Id)
|
||||
{
|
||||
// redirect to self
|
||||
_logger.Debug<PublishedRouter>("FollowInternalRedirects: Redirecting to self, ignore");
|
||||
}
|
||||
else
|
||||
{
|
||||
request.SetInternalRedirectPublishedContent(internalRedirectNode); // don't use .PublishedContent here
|
||||
redirect = true;
|
||||
_logger.Debug<PublishedRouter>("FollowInternalRedirects: Redirecting to id={InternalRedirectId}", internalRedirectId);
|
||||
}
|
||||
|
||||
return redirect;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that access to current node is permitted.
|
||||
/// </summary>
|
||||
/// <remarks>Redirecting to a different site root and/or culture will not pick the new site root nor the new culture.</remarks>
|
||||
private void EnsurePublishedContentAccess(IPublishedRequest request)
|
||||
{
|
||||
if (request.PublishedContent == null)
|
||||
throw new InvalidOperationException("There is no PublishedContent.");
|
||||
|
||||
var path = request.PublishedContent.Path;
|
||||
|
||||
var publicAccessAttempt = _services.PublicAccessService.IsProtected(path);
|
||||
|
||||
if (publicAccessAttempt)
|
||||
{
|
||||
_logger.Debug<PublishedRouter>("EnsurePublishedContentAccess: Page is protected, check for access");
|
||||
|
||||
var membershipHelper = Current.Factory.GetInstance<MembershipHelper>();
|
||||
|
||||
if (membershipHelper.IsLoggedIn() == false)
|
||||
{
|
||||
_logger.Debug<PublishedRouter>("EnsurePublishedContentAccess: Not logged in, redirect to login page");
|
||||
|
||||
var loginPageId = publicAccessAttempt.Result.LoginNodeId;
|
||||
|
||||
if (loginPageId != request.PublishedContent.Id)
|
||||
request.PublishedContent = request.UmbracoContext.PublishedSnapshot.Content.GetById(loginPageId);
|
||||
}
|
||||
else if (_services.PublicAccessService.HasAccess(request.PublishedContent.Id, _services.ContentService, membershipHelper.CurrentUserName, membershipHelper.GetCurrentUserRoles()) == false)
|
||||
{
|
||||
_logger.Debug<PublishedRouter>("EnsurePublishedContentAccess: Current member has not access, redirect to error page");
|
||||
var errorPageId = publicAccessAttempt.Result.NoAccessNodeId;
|
||||
if (errorPageId != request.PublishedContent.Id)
|
||||
request.PublishedContent = request.UmbracoContext.PublishedSnapshot.Content.GetById(errorPageId);
|
||||
}
|
||||
else
|
||||
{
|
||||
// grab the current member
|
||||
var member = membershipHelper.GetCurrentMember();
|
||||
// if the member has the "approved" and/or "locked out" properties, make sure they're correctly set before allowing access
|
||||
var memberIsActive = true;
|
||||
if (member != null)
|
||||
{
|
||||
if (member.HasProperty(Constants.Conventions.Member.IsApproved) == false)
|
||||
memberIsActive = member.Value<bool>(Constants.Conventions.Member.IsApproved);
|
||||
|
||||
if (member.HasProperty(Constants.Conventions.Member.IsLockedOut) == false)
|
||||
memberIsActive = member.Value<bool>(Constants.Conventions.Member.IsLockedOut) == false;
|
||||
}
|
||||
|
||||
if (memberIsActive == false)
|
||||
{
|
||||
_logger.Debug<PublishedRouter>(
|
||||
"Current member is either unapproved or locked out, redirect to error page");
|
||||
var errorPageId = publicAccessAttempt.Result.NoAccessNodeId;
|
||||
if (errorPageId != request.PublishedContent.Id)
|
||||
request.PublishedContent =
|
||||
request.UmbracoContext.PublishedSnapshot.Content.GetById(errorPageId);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Debug<PublishedRouter>("Current member has access");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Debug<PublishedRouter>("EnsurePublishedContentAccess: Page is not protected");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds a template for the current node, if any.
|
||||
/// </summary>
|
||||
private void FindTemplate(IPublishedRequest request)
|
||||
{
|
||||
// NOTE: at the moment there is only 1 way to find a template, and then ppl must
|
||||
// use the Prepared event to change the template if they wish. Should we also
|
||||
// implement an ITemplateFinder logic?
|
||||
|
||||
if (request.PublishedContent == null)
|
||||
{
|
||||
request.TemplateModel = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// read the alternate template alias, from querystring, form, cookie or server vars,
|
||||
// only if the published content is the initial once, else the alternate template
|
||||
// does not apply
|
||||
// + optionally, apply the alternate template on internal redirects
|
||||
var useAltTemplate = request.IsInitialPublishedContent
|
||||
|| (_webRoutingSection.InternalRedirectPreservesTemplate && request.IsInternalRedirectPublishedContent);
|
||||
var altTemplate = useAltTemplate
|
||||
? _httpContextAccessor.GetRequiredHttpContext().Request[Constants.Conventions.Url.AltTemplate]
|
||||
: null;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(altTemplate))
|
||||
{
|
||||
// we don't have an alternate template specified. use the current one if there's one already,
|
||||
// which can happen if a content lookup also set the template (LookupByNiceUrlAndTemplate...),
|
||||
// else lookup the template id on the document then lookup the template with that id.
|
||||
|
||||
if (request.HasTemplate)
|
||||
{
|
||||
_logger.Debug<IPublishedRequest>("FindTemplate: Has a template already, and no alternate template.");
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: When we remove the need for a database for templates, then this id should be irrelevant,
|
||||
// not sure how were going to do this nicely.
|
||||
|
||||
// TODO: We need to limit altTemplate to only allow templates that are assigned to the current document type!
|
||||
// if the template isn't assigned to the document type we should log a warning and return 404
|
||||
|
||||
var templateId = request.PublishedContent.TemplateId;
|
||||
request.TemplateModel = GetTemplateModel(templateId);
|
||||
}
|
||||
else
|
||||
{
|
||||
// we have an alternate template specified. lookup the template with that alias
|
||||
// this means the we override any template that a content lookup might have set
|
||||
// so /path/to/page/template1?altTemplate=template2 will use template2
|
||||
|
||||
// ignore if the alias does not match - just trace
|
||||
|
||||
if (request.HasTemplate)
|
||||
_logger.Debug<PublishedRouter>("FindTemplate: Has a template already, but also an alternative template.");
|
||||
_logger.Debug<PublishedRouter>("FindTemplate: Look for alternative template alias={AltTemplate}", altTemplate);
|
||||
|
||||
// IsAllowedTemplate deals both with DisableAlternativeTemplates and ValidateAlternativeTemplates settings
|
||||
if (request.PublishedContent.IsAllowedTemplate(altTemplate))
|
||||
{
|
||||
// allowed, use
|
||||
var template = _services.FileService.GetTemplate(altTemplate);
|
||||
|
||||
if (template != null)
|
||||
{
|
||||
request.TemplateModel = template;
|
||||
_logger.Debug<PublishedRouter>("FindTemplate: Got alternative template id={TemplateId} alias={TemplateAlias}", template.Id, template.Alias);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Debug<PublishedRouter>("FindTemplate: The alternative template with alias={AltTemplate} does not exist, ignoring.", altTemplate);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Warn<PublishedRouter>("FindTemplate: Alternative template {TemplateAlias} is not allowed on node {NodeId}, ignoring.", altTemplate, request.PublishedContent.Id);
|
||||
|
||||
// no allowed, back to default
|
||||
var templateId = request.PublishedContent.TemplateId;
|
||||
request.TemplateModel = GetTemplateModel(templateId);
|
||||
}
|
||||
}
|
||||
|
||||
if (request.HasTemplate == false)
|
||||
{
|
||||
_logger.Debug<PublishedRouter>("FindTemplate: No template was found.");
|
||||
|
||||
// initial idea was: if we're not already 404 and UmbracoSettings.HandleMissingTemplateAs404 is true
|
||||
// then reset _pcr.Document to null to force a 404.
|
||||
//
|
||||
// but: because we want to let MVC hijack routes even though no template is defined, we decide that
|
||||
// a missing template is OK but the request will then be forwarded to MVC, which will need to take
|
||||
// care of everything.
|
||||
//
|
||||
// so, don't set _pcr.Document to null here
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Debug<PublishedRouter>("FindTemplate: Running with template id={TemplateId} alias={TemplateAlias}", request.TemplateModel.Id, request.TemplateModel.Alias);
|
||||
}
|
||||
}
|
||||
|
||||
private ITemplate GetTemplateModel(int? templateId)
|
||||
{
|
||||
if (templateId.HasValue == false || templateId.Value == default)
|
||||
{
|
||||
_logger.Debug<PublishedRouter>("GetTemplateModel: No template.");
|
||||
return null;
|
||||
}
|
||||
|
||||
_logger.Debug<PublishedRouter>("GetTemplateModel: Get template id={TemplateId}", templateId);
|
||||
|
||||
if (templateId == null)
|
||||
throw new InvalidOperationException("The template is not set, the page cannot render.");
|
||||
|
||||
var template = _services.FileService.GetTemplate(templateId.Value);
|
||||
if (template == null)
|
||||
throw new InvalidOperationException("The template with Id " + templateId + " does not exist, the page cannot render.");
|
||||
_logger.Debug<PublishedRouter>("GetTemplateModel: Got template id={TemplateId} alias={TemplateAlias}", template.Id, template.Alias);
|
||||
return template;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Follows external redirection through <c>umbracoRedirect</c> document property.
|
||||
/// </summary>
|
||||
/// <remarks>As per legacy, if the redirect does not work, we just ignore it.</remarks>
|
||||
private void FollowExternalRedirect(IPublishedRequest request)
|
||||
{
|
||||
if (request.HasPublishedContent == false) return;
|
||||
|
||||
// don't try to find a redirect if the property doesn't exist
|
||||
if (request.PublishedContent.HasProperty(Constants.Conventions.Content.Redirect) == false)
|
||||
return;
|
||||
|
||||
var redirectId = request.PublishedContent.Value(Constants.Conventions.Content.Redirect, defaultValue: -1);
|
||||
var redirectUrl = "#";
|
||||
if (redirectId > 0)
|
||||
{
|
||||
redirectUrl = _publishedUrlProvider.GetUrl(redirectId);
|
||||
}
|
||||
else
|
||||
{
|
||||
// might be a UDI instead of an int Id
|
||||
var redirectUdi = request.PublishedContent.Value<GuidUdi>(Constants.Conventions.Content.Redirect);
|
||||
if (redirectUdi != null)
|
||||
redirectUrl = _publishedUrlProvider.GetUrl(redirectUdi.Guid);
|
||||
}
|
||||
if (redirectUrl != "#")
|
||||
request.SetRedirect(redirectUrl);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,163 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Configuration.UmbracoSettings;
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Services.Implement;
|
||||
using Umbraco.Web.PublishedCache;
|
||||
|
||||
namespace Umbraco.Web.Routing
|
||||
{
|
||||
/// Implements an Application Event Handler for managing redirect urls tracking.
|
||||
/// <para>when content is renamed or moved, we want to create a permanent 301 redirect from it's old url</para>
|
||||
/// <para>
|
||||
/// not managing domains because we don't know how to do it - changing domains => must create a higher level
|
||||
/// strategy using rewriting rules probably
|
||||
/// </para>
|
||||
/// <para>recycle bin = moving to and from does nothing: to = the node is gone, where would we redirect? from = same</para>
|
||||
public sealed class RedirectTrackingComponent : IComponent
|
||||
{
|
||||
private const string _eventStateKey = "Umbraco.Web.Redirects.RedirectTrackingEventHandler";
|
||||
|
||||
private readonly IUmbracoSettingsSection _umbracoSettings;
|
||||
private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor;
|
||||
private readonly IRedirectUrlService _redirectUrlService;
|
||||
|
||||
public RedirectTrackingComponent(IUmbracoSettingsSection umbracoSettings, IPublishedSnapshotAccessor publishedSnapshotAccessor, IRedirectUrlService redirectUrlService)
|
||||
{
|
||||
_umbracoSettings = umbracoSettings;
|
||||
_publishedSnapshotAccessor = publishedSnapshotAccessor;
|
||||
_redirectUrlService = redirectUrlService;
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
// don't let the event handlers kick in if Redirect Tracking is turned off in the config
|
||||
if (_umbracoSettings.WebRouting.DisableRedirectUrlTracking) return;
|
||||
|
||||
ContentService.Publishing += ContentService_Publishing;
|
||||
ContentService.Published += ContentService_Published;
|
||||
ContentService.Moving += ContentService_Moving;
|
||||
ContentService.Moved += ContentService_Moved;
|
||||
|
||||
// kill all redirects once a content is deleted
|
||||
//ContentService.Deleted += ContentService_Deleted;
|
||||
// BUT, doing it here would prevent content deletion due to FK
|
||||
// so the rows are actually deleted by the ContentRepository (see GetDeleteClauses)
|
||||
|
||||
// rolled back items have to be published, so publishing will take care of that
|
||||
}
|
||||
|
||||
public void Terminate()
|
||||
{ }
|
||||
|
||||
private void ContentService_Publishing(IContentService sender, PublishEventArgs<IContent> args)
|
||||
{
|
||||
var oldRoutes = GetOldRoutes(args.EventState);
|
||||
foreach (var entity in args.PublishedEntities)
|
||||
{
|
||||
StoreOldRoute(entity, oldRoutes);
|
||||
}
|
||||
}
|
||||
|
||||
private void ContentService_Published(IContentService sender, ContentPublishedEventArgs args)
|
||||
{
|
||||
var oldRoutes = GetOldRoutes(args.EventState);
|
||||
CreateRedirects(oldRoutes);
|
||||
}
|
||||
|
||||
private void ContentService_Moving(IContentService sender, MoveEventArgs<IContent> args)
|
||||
{
|
||||
var oldRoutes = GetOldRoutes(args.EventState);
|
||||
foreach (var info in args.MoveInfoCollection)
|
||||
{
|
||||
StoreOldRoute(info.Entity, oldRoutes);
|
||||
}
|
||||
}
|
||||
|
||||
private void ContentService_Moved(IContentService sender, MoveEventArgs<IContent> args)
|
||||
{
|
||||
var oldRoutes = GetOldRoutes(args.EventState);
|
||||
CreateRedirects(oldRoutes);
|
||||
}
|
||||
|
||||
private OldRoutesDictionary GetOldRoutes(IDictionary<string, object> eventState)
|
||||
{
|
||||
if (! eventState.ContainsKey(_eventStateKey))
|
||||
{
|
||||
eventState[_eventStateKey] = new OldRoutesDictionary();
|
||||
}
|
||||
|
||||
return eventState[_eventStateKey] as OldRoutesDictionary;
|
||||
}
|
||||
|
||||
private void StoreOldRoute(IContent entity, OldRoutesDictionary oldRoutes)
|
||||
{
|
||||
var contentCache = _publishedSnapshotAccessor.PublishedSnapshot.Content;
|
||||
var entityContent = contentCache.GetById(entity.Id);
|
||||
if (entityContent == null) return;
|
||||
|
||||
// get the default affected cultures by going up the tree until we find the first culture variant entity (default to no cultures)
|
||||
var defaultCultures = entityContent.AncestorsOrSelf()?.FirstOrDefault(a => a.Cultures.Any())?.Cultures.Keys.ToArray()
|
||||
?? new[] { (string)null };
|
||||
foreach (var x in entityContent.DescendantsOrSelf())
|
||||
{
|
||||
// if this entity defines specific cultures, use those instead of the default ones
|
||||
var cultures = x.Cultures.Any() ? x.Cultures.Keys : defaultCultures;
|
||||
|
||||
foreach (var culture in cultures)
|
||||
{
|
||||
var route = contentCache.GetRouteById(x.Id, culture);
|
||||
if (IsNotRoute(route)) continue;
|
||||
oldRoutes[new ContentIdAndCulture(x.Id, culture)] = new ContentKeyAndOldRoute(x.Key, route);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateRedirects(OldRoutesDictionary oldRoutes)
|
||||
{
|
||||
var contentCache = _publishedSnapshotAccessor.PublishedSnapshot.Content;
|
||||
|
||||
foreach (var oldRoute in oldRoutes)
|
||||
{
|
||||
var newRoute = contentCache.GetRouteById(oldRoute.Key.ContentId, oldRoute.Key.Culture);
|
||||
if (IsNotRoute(newRoute) || oldRoute.Value.OldRoute == newRoute) continue;
|
||||
_redirectUrlService.Register(oldRoute.Value.OldRoute, oldRoute.Value.ContentKey, oldRoute.Key.Culture);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsNotRoute(string route)
|
||||
{
|
||||
// null if content not found
|
||||
// err/- if collision or anomaly or ...
|
||||
return route == null || route.StartsWith("err/");
|
||||
}
|
||||
|
||||
private class ContentIdAndCulture : Tuple<int, string>
|
||||
{
|
||||
public ContentIdAndCulture(int contentId, string culture) : base(contentId, culture)
|
||||
{
|
||||
}
|
||||
|
||||
public int ContentId => Item1;
|
||||
public string Culture => Item2;
|
||||
}
|
||||
|
||||
private class ContentKeyAndOldRoute : Tuple<Guid, string>
|
||||
{
|
||||
public ContentKeyAndOldRoute(Guid contentKey, string oldRoute) : base(contentKey, oldRoute)
|
||||
{
|
||||
}
|
||||
|
||||
public Guid ContentKey => Item1;
|
||||
public string OldRoute => Item2;
|
||||
}
|
||||
|
||||
private class OldRoutesDictionary : Dictionary<ContentIdAndCulture, ContentKeyAndOldRoute>
|
||||
{ }
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Composing;
|
||||
|
||||
namespace Umbraco.Web.Routing
|
||||
{
|
||||
/// <summary>
|
||||
/// Implements an Application Event Handler for managing redirect urls tracking.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>when content is renamed or moved, we want to create a permanent 301 redirect from it's old url</para>
|
||||
/// <para>not managing domains because we don't know how to do it - changing domains => must create a higher level strategy using rewriting rules probably</para>
|
||||
/// <para>recycle bin = moving to and from does nothing: to = the node is gone, where would we redirect? from = same</para>
|
||||
/// </remarks>
|
||||
[RuntimeLevel(MinLevel = RuntimeLevel.Run)]
|
||||
public class RedirectTrackingComposer : ComponentComposer<RedirectTrackingComponent>, ICoreComposer
|
||||
{ }
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
using System.Web.Security;
|
||||
using Examine;
|
||||
using Microsoft.AspNet.SignalR;
|
||||
@@ -7,13 +6,11 @@ using Umbraco.Core;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Cookie;
|
||||
using Umbraco.Core.Dashboards;
|
||||
using Umbraco.Core.Dictionary;
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Core.Hosting;
|
||||
using Umbraco.Core.Install;
|
||||
using Umbraco.Core.Migrations.PostMigrations;
|
||||
using Umbraco.Core.Models.Identity;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
using Umbraco.Core.PropertyEditors.ValueConverters;
|
||||
using Umbraco.Core.Runtime;
|
||||
@@ -49,6 +46,9 @@ using Current = Umbraco.Web.Composing.Current;
|
||||
using Umbraco.Web.PropertyEditors;
|
||||
using Umbraco.Examine;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Request;
|
||||
using Umbraco.Core.Session;
|
||||
using Umbraco.Web.AspNet;
|
||||
using Umbraco.Web.Models;
|
||||
|
||||
namespace Umbraco.Web.Runtime
|
||||
@@ -64,7 +64,14 @@ namespace Umbraco.Web.Runtime
|
||||
|
||||
composition.Register<UmbracoInjectedModule>();
|
||||
composition.Register<IIpResolver, AspNetIpResolver>();
|
||||
composition.Register<ISessionIdResolver, AspNetSessionIdResolver>();
|
||||
|
||||
|
||||
composition.Register<AspNetSessionManager>(Lifetime.Singleton);
|
||||
composition.Register<ISessionIdResolver>(factory => factory.GetInstance<AspNetSessionManager>(), Lifetime.Singleton);
|
||||
composition.Register<ISessionManager>(factory => factory.GetInstance<AspNetSessionManager>(), Lifetime.Singleton);
|
||||
|
||||
composition.Register<IRequestAccessor, AspNetRequestAccessor>();
|
||||
|
||||
composition.Register<IHostingEnvironment, AspNetHostingEnvironment>();
|
||||
composition.Register<IBackOfficeInfo, AspNetBackOfficeInfo>();
|
||||
composition.Register<IPasswordHasher, AspNetPasswordHasher>();
|
||||
@@ -73,6 +80,7 @@ namespace Umbraco.Web.Runtime
|
||||
composition.RegisterUnique<IHttpContextAccessor, AspNetHttpContextAccessor>(); // required for hybrid accessors
|
||||
composition.RegisterUnique<ICookieManager, AspNetCookieManager>();
|
||||
|
||||
|
||||
composition.ComposeWebMappingProfiles();
|
||||
|
||||
//register the install components
|
||||
@@ -85,6 +93,8 @@ namespace Umbraco.Web.Runtime
|
||||
composition.Register(factory => Roles.Enabled ? Roles.Provider : new MembersRoleProvider(factory.GetInstance<IMemberService>()));
|
||||
composition.Register<MembershipHelper>(Lifetime.Request);
|
||||
composition.Register<IPublishedMemberCache>(factory => factory.GetInstance<IUmbracoContext>().PublishedSnapshot.Members);
|
||||
composition.RegisterUnique<IMemberUserKeyProvider, MemberUserKeyProvider>();
|
||||
composition.RegisterUnique<IPublicAccessChecker, PublicAccessChecker>();
|
||||
|
||||
// register accessors for cultures
|
||||
composition.RegisterUnique<IDefaultCultureAccessor, DefaultCultureAccessor>();
|
||||
@@ -115,6 +125,7 @@ namespace Umbraco.Web.Runtime
|
||||
|
||||
composition.RegisterUnique<ITemplateRenderer, TemplateRenderer>();
|
||||
composition.RegisterUnique<IMacroRenderer, MacroRenderer>();
|
||||
|
||||
composition.RegisterUnique<IUmbracoComponentRenderer, UmbracoComponentRenderer>();
|
||||
|
||||
composition.RegisterUnique<HtmlLocalLinkParser>();
|
||||
@@ -125,6 +136,7 @@ namespace Umbraco.Web.Runtime
|
||||
// register the umbraco helper - this is Transient! very important!
|
||||
// also, if not level.Run, we cannot really use the helper (during upgrade...)
|
||||
// so inject a "void" helper (not exactly pretty but...)
|
||||
if (composition.RuntimeState.Level == RuntimeLevel.Run)
|
||||
if (composition.RuntimeState.Level == RuntimeLevel.Run)
|
||||
composition.Register<UmbracoHelper>(factory =>
|
||||
{
|
||||
|
||||
54
src/Umbraco.Web/Security/PublicAccessChecker.cs
Normal file
54
src/Umbraco.Web/Security/PublicAccessChecker.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Services;
|
||||
|
||||
namespace Umbraco.Web.Security
|
||||
{
|
||||
public class PublicAccessChecker : IPublicAccessChecker
|
||||
{
|
||||
//TODO: This is lazy to avoid circular dependency. We don't care right now because all membership is going to be changed.
|
||||
private readonly Lazy<MembershipHelper> _membershipHelper;
|
||||
private readonly IPublicAccessService _publicAccessService;
|
||||
private readonly IContentService _contentService;
|
||||
|
||||
public PublicAccessChecker(Lazy<MembershipHelper> membershipHelper, IPublicAccessService publicAccessService, IContentService contentService)
|
||||
{
|
||||
_membershipHelper = membershipHelper;
|
||||
_publicAccessService = publicAccessService;
|
||||
_contentService = contentService;
|
||||
}
|
||||
|
||||
public PublicAccessStatus HasMemberAccessToContent(int publishedContentId)
|
||||
{
|
||||
var membershipHelper = _membershipHelper.Value;
|
||||
|
||||
if (membershipHelper.IsLoggedIn() == false)
|
||||
{
|
||||
return PublicAccessStatus.NotLoggedIn;
|
||||
}
|
||||
|
||||
var username = membershipHelper.CurrentUserName;
|
||||
var userRoles = membershipHelper.GetCurrentUserRoles();
|
||||
|
||||
if (_publicAccessService.HasAccess(publishedContentId, _contentService, username, userRoles) == false)
|
||||
{
|
||||
return PublicAccessStatus.AccessDenied;
|
||||
}
|
||||
|
||||
var member = membershipHelper.GetCurrentMember();
|
||||
|
||||
if (member.HasProperty(Constants.Conventions.Member.IsApproved) == false)
|
||||
{
|
||||
return PublicAccessStatus.NotApproved;
|
||||
}
|
||||
|
||||
if (member.HasProperty(Constants.Conventions.Member.IsLockedOut) &&
|
||||
member.Value<bool>(Constants.Conventions.Member.IsApproved))
|
||||
{
|
||||
return PublicAccessStatus.LockedOut;
|
||||
}
|
||||
|
||||
return PublicAccessStatus.AccessAccepted;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -134,6 +134,8 @@
|
||||
<Compile Include="AppBuilderExtensions.cs" />
|
||||
<Compile Include="AreaRegistrationContextExtensions.cs" />
|
||||
<Compile Include="AspNet\AspNetHostingEnvironment.cs" />
|
||||
<Compile Include="AspNet\AspNetRequestAccessor.cs" />
|
||||
<Compile Include="AspNet\AspNetSessionManager.cs" />
|
||||
<Compile Include="AspNet\FrameworkMarchal.cs" />
|
||||
<Compile Include="Cache\WebCachingAppCache.cs" />
|
||||
<Compile Include="Compose\AuditEventsComponent.cs" />
|
||||
@@ -166,6 +168,8 @@
|
||||
<Compile Include="Logging\WebProfilerComponent.cs" />
|
||||
<Compile Include="Logging\WebProfilerComposer.cs" />
|
||||
<Compile Include="Logging\WebProfilerProvider.cs" />
|
||||
<Compile Include="Macros\MacroRenderer.cs" />
|
||||
<Compile Include="Macros\MemberUserKeyProvider.cs" />
|
||||
<Compile Include="Models\Identity\BackOfficeIdentityUser.cs" />
|
||||
<Compile Include="Models\Identity\IdentityMapDefinition.cs" />
|
||||
<Compile Include="Models\Identity\IdentityUser.cs" />
|
||||
@@ -185,7 +189,6 @@
|
||||
<Compile Include="AspNet\AspNetHttpContextAccessor.cs" />
|
||||
<Compile Include="AspNet\AspNetIpResolver.cs" />
|
||||
<Compile Include="AspNet\AspNetPasswordHasher.cs" />
|
||||
<Compile Include="AspNet\AspNetSessionIdResolver.cs" />
|
||||
<Compile Include="Profiling\WebProfilingController.cs" />
|
||||
<Compile Include="RoutableDocumentFilter.cs" />
|
||||
<Compile Include="Runtime\AspNetUmbracoBootPermissionChecker.cs" />
|
||||
@@ -200,6 +203,7 @@
|
||||
<Compile Include="Security\MembershipProviderBase.cs" />
|
||||
<Compile Include="Security\MembershipProviderExtensions.cs" />
|
||||
<Compile Include="Security\PasswordSecurity.cs" />
|
||||
<Compile Include="Security\PublicAccessChecker.cs" />
|
||||
<Compile Include="Security\UmbracoBackOfficeIdentity.cs" />
|
||||
<Compile Include="Security\UmbracoEmailMessage.cs" />
|
||||
<Compile Include="Security\UmbracoMembershipProviderBase.cs" />
|
||||
@@ -214,7 +218,6 @@
|
||||
<Compile Include="ViewDataExtensions.cs" />
|
||||
<Compile Include="WebApi\Filters\AdminUsersAuthorizeAttribute.cs" />
|
||||
<Compile Include="WebApi\Filters\OnlyLocalRequestsAttribute.cs" />
|
||||
<Compile Include="Routing\RedirectTrackingComposer.cs" />
|
||||
<Compile Include="Runtime\WebInitialComposer.cs" />
|
||||
<Compile Include="Security\ActiveDirectoryBackOfficeUserPasswordChecker.cs" />
|
||||
<Compile Include="Security\BackOfficeClaimsIdentityFactory.cs" />
|
||||
@@ -274,7 +277,6 @@
|
||||
<Compile Include="Editors\MemberGroupController.cs" />
|
||||
<Compile Include="Composing\CompositionExtensions\Controllers.cs" />
|
||||
<Compile Include="HealthCheck\HealthCheckController.cs" />
|
||||
<Compile Include="Macros\MacroRenderer.cs" />
|
||||
<Compile Include="HtmlHelperBackOfficeExtensions.cs" />
|
||||
<Compile Include="Composing\ModuleInjector.cs" />
|
||||
<Compile Include="Mvc\FilteredControllerFactoryCollection.cs" />
|
||||
@@ -323,7 +325,6 @@
|
||||
<Compile Include="Mvc\UmbracoRequireHttpsAttribute.cs" />
|
||||
<Compile Include="Mvc\ValidateMvcAngularAntiForgeryTokenAttribute.cs" />
|
||||
<Compile Include="OwinMiddlewareConfiguredEventArgs.cs" />
|
||||
<Compile Include="Routing\RedirectTrackingComponent.cs" />
|
||||
<Compile Include="Editors\RedirectUrlManagementController.cs" />
|
||||
<Compile Include="DefaultEventMessagesFactory.cs" />
|
||||
<Compile Include="Security\ExternalSignInAutoLinkOptions.cs" />
|
||||
@@ -529,8 +530,6 @@
|
||||
<Compile Include="Mvc\PluginControllerMetadata.cs" />
|
||||
<Compile Include="Mvc\UmbracoPageResult.cs" />
|
||||
<Compile Include="RouteCollectionExtensions.cs" />
|
||||
<Compile Include="Routing\ContentFinderByPageIdQuery.cs" />
|
||||
<Compile Include="Routing\PublishedRouter.cs" />
|
||||
<Compile Include="Compose\DatabaseServerRegistrarAndMessengerComponent.cs" />
|
||||
<Compile Include="Templates\TemplateRenderer.cs" />
|
||||
<Compile Include="Trees\PartialViewMacrosTreeController.cs" />
|
||||
@@ -556,7 +555,6 @@
|
||||
<Compile Include="WebApi\Filters\UmbracoUserTimeoutFilterAttribute.cs" />
|
||||
<Compile Include="Runtime\WebRuntime.cs" />
|
||||
<Compile Include="Mvc\ControllerExtensions.cs" />
|
||||
<Compile Include="Routing\ContentFinderByIdPath.cs" />
|
||||
<Compile Include="TypeLoaderExtensions.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs">
|
||||
<SubType>Code</SubType>
|
||||
|
||||
@@ -11,6 +11,7 @@ using Umbraco.Core.Hosting;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Logging.Serilog;
|
||||
using Umbraco.Web.AspNet;
|
||||
using Umbraco.Web.Hosting;
|
||||
using Current = Umbraco.Web.Composing.Current;
|
||||
|
||||
@@ -39,7 +40,7 @@ namespace Umbraco.Web
|
||||
var ioHelper = new IOHelper(hostingEnvironment);
|
||||
var configs = configFactory.Create(ioHelper);
|
||||
|
||||
var logger = SerilogLogger.CreateWithDefaultConfiguration(hostingEnvironment, new AspNetSessionIdResolver(), () => _factory?.GetInstance<IRequestCache>(), coreDebug, ioHelper, new FrameworkMarchal());
|
||||
var logger = SerilogLogger.CreateWithDefaultConfiguration(hostingEnvironment, new AspNetSessionManager(), () => _factory?.GetInstance<IRequestCache>(), coreDebug, ioHelper, new FrameworkMarchal());
|
||||
var backOfficeInfo = new AspNetBackOfficeInfo(configs.Global(), ioHelper, configs.Settings(), logger);
|
||||
var profiler = new LogProfiler(logger);
|
||||
Umbraco.Composing.Current.Initialize(logger, configs, ioHelper, hostingEnvironment, backOfficeInfo, profiler);
|
||||
|
||||
Reference in New Issue
Block a user