diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IWebRoutingSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IWebRoutingSection.cs index 9eb6d02aa7..c59d63d42e 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IWebRoutingSection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IWebRoutingSection.cs @@ -8,6 +8,8 @@ bool DisableAlternativeTemplates { get; } + bool ValidateAlternativeTemplates { get; } + bool DisableFindContentByIdPath { get; } bool DisableRedirectUrlTracking { get; } diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/WebRoutingElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/WebRoutingElement.cs index 82f5d46b28..ed133e5f61 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/WebRoutingElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/WebRoutingElement.cs @@ -1,4 +1,5 @@ -using System.Configuration; +using System; +using System.Configuration; namespace Umbraco.Core.Configuration.UmbracoSettings { @@ -21,6 +22,13 @@ namespace Umbraco.Core.Configuration.UmbracoSettings { get { return (bool) base["disableAlternativeTemplates"]; } } + + [ConfigurationProperty("validateAlternativeTemplates", DefaultValue = "false")] + public bool ValidateAlternativeTemplates + { + get { return (bool)base["validateAlternativeTemplates"]; } + } + [ConfigurationProperty("disableFindContentByIdPath", DefaultValue = "false")] public bool DisableFindContentByIdPath { diff --git a/src/Umbraco.Core/Models/ContentType.cs b/src/Umbraco.Core/Models/ContentType.cs index 568d80ccb7..549ccb24e8 100644 --- a/src/Umbraco.Core/Models/ContentType.cs +++ b/src/Umbraco.Core/Models/ContentType.cs @@ -102,6 +102,28 @@ namespace Umbraco.Core.Models } } + /// + /// Determines if AllowedTemplates contains templateId + /// + /// The template id to check + /// True if AllowedTemplates contains the templateId else False + public bool IsAllowedTemplate(int templateId) + { + var allowedTemplates = AllowedTemplates ?? new ITemplate[0]; + return allowedTemplates.Any(t => t.Id == templateId); + } + + /// + /// Determines if AllowedTemplates contains templateId + /// + /// The template alias to check + /// True if AllowedTemplates contains the templateAlias else False + public bool IsAllowedTemplate(string templateAlias) + { + var allowedTemplates = AllowedTemplates ?? new ITemplate[0]; + return allowedTemplates.Any(t => t.Alias.Equals(templateAlias, StringComparison.InvariantCultureIgnoreCase)); + } + /// /// Sets the default template for the ContentType /// diff --git a/src/Umbraco.Core/Models/IContentType.cs b/src/Umbraco.Core/Models/IContentType.cs index 766a8eec81..356a5acccd 100644 --- a/src/Umbraco.Core/Models/IContentType.cs +++ b/src/Umbraco.Core/Models/IContentType.cs @@ -17,6 +17,20 @@ namespace Umbraco.Core.Models /// IEnumerable AllowedTemplates { get; set; } + /// + /// Determines if AllowedTemplates contains templateId + /// + /// The template id to check + /// True if AllowedTemplates contains the templateId else False + bool IsAllowedTemplate(int templateId); + + /// + /// Determines if AllowedTemplates contains templateId + /// + /// The template alias to check + /// True if AllowedTemplates contains the templateAlias else False + bool IsAllowedTemplate(string templateAlias); + /// /// Sets the default template for the ContentType /// diff --git a/src/Umbraco.Tests/Configurations/UmbracoSettings/WebRoutingElementDefaultTests.cs b/src/Umbraco.Tests/Configurations/UmbracoSettings/WebRoutingElementDefaultTests.cs index 1e568c608e..4c77fff5bc 100644 --- a/src/Umbraco.Tests/Configurations/UmbracoSettings/WebRoutingElementDefaultTests.cs +++ b/src/Umbraco.Tests/Configurations/UmbracoSettings/WebRoutingElementDefaultTests.cs @@ -23,6 +23,12 @@ namespace Umbraco.Tests.Configurations.UmbracoSettings Assert.IsTrue(SettingsSection.WebRouting.DisableAlternativeTemplates == false); } + [Test] + public void ValidateAlternativeTemplates() + { + Assert.IsTrue(SettingsSection.WebRouting.ValidateAlternativeTemplates == false); + } + [Test] public void DisableFindContentByIdPath() { diff --git a/src/Umbraco.Web.UI/config/umbracoSettings.Release.config b/src/Umbraco.Web.UI/config/umbracoSettings.Release.config index 27f5fc97c7..88d350225b 100644 --- a/src/Umbraco.Web.UI/config/umbracoSettings.Release.config +++ b/src/Umbraco.Web.UI/config/umbracoSettings.Release.config @@ -152,6 +152,12 @@ will make Umbraco render the content on the current page with the template you requested, for example: http://mysite.com/about-us/?altTemplate=Home and http://mysite.com/about-us/Home would render the "About Us" page with a template with the alias Home. Setting this setting to true stops that behavior + @validateAlternativeTemplates + By default you can add a altTemplate querystring or append a template name to the current URL which + will make Umbraco render the content on the current page with the template you requested, for example: + http://mysite.com/about-us/?altTemplate=Home and http://mysite.com/about-us/Home would render the + "About Us" page with a template with the alias Home. Setting this setting to true will ensure that + only templates that have been permitted on the document type will be allowed @disableFindContentByIdPath By default you can call any content Id in the url and show the content with that id, for example: http://mysite.com/1092 or http://mysite.com/1092.aspx would render the content with id 1092. Setting @@ -163,7 +169,7 @@ --> diff --git a/src/Umbraco.Web.UI/config/umbracoSettings.config b/src/Umbraco.Web.UI/config/umbracoSettings.config index 2b2fdf2674..aa8225b082 100644 --- a/src/Umbraco.Web.UI/config/umbracoSettings.config +++ b/src/Umbraco.Web.UI/config/umbracoSettings.config @@ -304,7 +304,13 @@ By default you can add a altTemplate querystring or append a template name to the current URL which will make Umbraco render the content on the current page with the template you requested, for example: http://mysite.com/about-us/?altTemplate=Home and http://mysite.com/about-us/Home would render the - "About Us" page with a template with the alias Home. Setting this setting to true stops that behavior + "About Us" page with a template with the alias Home. Setting this setting to true stops that behavior + @validateAlternativeTemplates + By default you can add a altTemplate querystring or append a template name to the current URL which + will make Umbraco render the content on the current page with the template you requested, for example: + http://mysite.com/about-us/?altTemplate=Home and http://mysite.com/about-us/Home would render the + "About Us" page with a template with the alias Home. Setting this setting to true will ensure that + only templates that have been permitted on the document type will be allowed @disableFindContentByIdPath By default you can call any content Id in the url and show the content with that id, for example: http://mysite.com/1092 or http://mysite.com/1092.aspx would render the content with id 1092. Setting @@ -316,7 +322,7 @@ --> diff --git a/src/Umbraco.Web/PublishedContentExtensions.cs b/src/Umbraco.Web/PublishedContentExtensions.cs index f1dd008e38..c80f41d9fc 100644 --- a/src/Umbraco.Web/PublishedContentExtensions.cs +++ b/src/Umbraco.Web/PublishedContentExtensions.cs @@ -6,11 +6,12 @@ using System.Globalization; using System.Linq; using System.Web; using Examine.LuceneEngine.SearchCriteria; +using Umbraco.Core; +using Umbraco.Core.Configuration; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Services; using Umbraco.Web.Models; -using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Web.Routing; using ContentType = umbraco.cms.businesslogic.ContentType; @@ -130,6 +131,32 @@ namespace Umbraco.Web { var template = ApplicationContext.Current.Services.FileService.GetTemplate(content.TemplateId); return template == null ? string.Empty : template.Alias; + } + + public static bool IsAllowedTemplate(this IPublishedContent content, int templateId) + { + if (UmbracoConfig.For.UmbracoSettings().WebRouting.DisableAlternativeTemplates == true) + return content.TemplateId == templateId; + + if (content.TemplateId != templateId && UmbracoConfig.For.UmbracoSettings().WebRouting.ValidateAlternativeTemplates == true) + { + var publishedContentContentType = ApplicationContext.Current.Services.ContentTypeService.GetContentType(content.ContentType.Id); + if (publishedContentContentType == null) + throw new NullReferenceException("No content type returned for published content (contentType='" + content.ContentType.Id + "')"); + + return publishedContentContentType.IsAllowedTemplate(templateId); + } + + return true; + } + public static bool IsAllowedTemplate(this IPublishedContent content, string templateAlias) + { + var template = ApplicationContext.Current.Services.FileService.GetTemplate(templateAlias); + var isAllowedTemplate = (template != null) ? + content.IsAllowedTemplate(template.Id) : + false; + + return isAllowedTemplate; } #endregion diff --git a/src/Umbraco.Web/Routing/ContentFinderByNiceUrlAndTemplate.cs b/src/Umbraco.Web/Routing/ContentFinderByNiceUrlAndTemplate.cs index 246b109dea..f19886c4f2 100644 --- a/src/Umbraco.Web/Routing/ContentFinderByNiceUrlAndTemplate.cs +++ b/src/Umbraco.Web/Routing/ContentFinderByNiceUrlAndTemplate.cs @@ -22,6 +22,8 @@ namespace Umbraco.Web.Routing /// If successful, also assigns the template. public override bool TryFindContent(PublishedContentRequest docRequest) { + const string tracePrefix = "ContentFinderByNiceUrlAndTemplate: "; + IPublishedContent node = null; string path = docRequest.Uri.GetAbsolutePathDecoded(); @@ -42,8 +44,16 @@ namespace Umbraco.Web.Routing var route = docRequest.HasDomain ? (docRequest.Domain.RootNodeId.ToString() + path) : path; node = FindContent(docRequest, route); - if (UmbracoConfig.For.UmbracoSettings().WebRouting.DisableAlternativeTemplates == false && node != null) + if (node.IsAllowedTemplate(template.Id)) + { docRequest.TemplateModel = template; + } + else + { + LogHelper.Warn("Configuration settings prevent template \"{0}\" from showing for node \"{1}\"", () => templateAlias, () => node.Id); + docRequest.PublishedContent = null; + node = null; + } } else { diff --git a/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs b/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs index afbfe1c4f6..217f407f02 100644 --- a/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs +++ b/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs @@ -11,6 +11,7 @@ using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; using Umbraco.Core.Logging; +using Umbraco.Core.Models; using Umbraco.Core.Security; using umbraco; @@ -651,9 +652,8 @@ namespace Umbraco.Web.Routing // only if the published content is the initial once, else the alternate template // does not apply // + optionnally, apply the alternate template on internal redirects - var useAltTemplate = _webRoutingSection.DisableAlternativeTemplates == false - && (_pcr.IsInitialPublishedContent - || (_webRoutingSection.InternalRedirectPreservesTemplate && _pcr.IsInternalRedirectPublishedContent)); + var useAltTemplate = _pcr.IsInitialPublishedContent + || (_webRoutingSection.InternalRedirectPreservesTemplate && _pcr.IsInternalRedirectPublishedContent); string altTemplate = useAltTemplate ? _routingContext.UmbracoContext.HttpContext.Request[Constants.Conventions.Url.AltTemplate] : null; @@ -675,20 +675,11 @@ namespace Umbraco.Web.Routing var templateId = _pcr.PublishedContent.TemplateId; - if (templateId > 0) - { - ProfilingLogger.Logger.Debug("{0}Look for template id={1}", () => tracePrefix, () => templateId); - var template = ApplicationContext.Current.Services.FileService.GetTemplate(templateId); - if (template == null) - throw new InvalidOperationException("The template with Id " + templateId + " does not exist, the page cannot render"); - _pcr.TemplateModel = template; - ProfilingLogger.Logger.Debug("{0}Got template id={1} alias=\"{2}\"", () => tracePrefix, () => template.Id, () => template.Alias); - } - else - { - ProfilingLogger.Logger.Debug("{0}No specified template.", () => tracePrefix); - } - } + // This code was moved to GetTemplateModel to allow the same functionality on a failed altTemplate (U4-8550) + // The only change is a diffent logger prefix and null will be set to _pcr.TemplateModel if the templateId is <= 0 + // rather than no set taking place + _pcr.TemplateModel = GetTemplateModel(_pcr.PublishedContent.TemplateId); + } else { // we have an alternate template specified. lookup the template with that alias @@ -701,16 +692,19 @@ namespace Umbraco.Web.Routing ProfilingLogger.Logger.Debug("{0}Has a template already, but also an alternate template.", () => tracePrefix); ProfilingLogger.Logger.Debug("{0}Look for alternate template alias=\"{1}\"", () => tracePrefix, () => altTemplate); - var template = ApplicationContext.Current.Services.FileService.GetTemplate(altTemplate); - if (template != null) - { - _pcr.TemplateModel = template; - ProfilingLogger.Logger.Debug("{0}Got template id={1} alias=\"{2}\"", () => tracePrefix, () => template.Id, () => template.Alias); - } - else - { - ProfilingLogger.Logger.Debug("{0}The template with alias=\"{1}\" does not exist, ignoring.", () => tracePrefix, () => altTemplate); - } + if (_pcr.PublishedContent.IsAllowedTemplate(altTemplate)) + { + var template = ApplicationContext.Current.Services.FileService.GetTemplate(altTemplate); + if (template != null) + _pcr.TemplateModel = template; + else + ProfilingLogger.Logger.Debug("{0}The template with alias=\"{1}\" does not exist, ignoring.", () => tracePrefix, () => altTemplate); + } + else + { + LogHelper.Warn("{0}Configuration settings prevent template \"{1}\" from showing for node \"{2}\"", () => tracePrefix, () => altTemplate, () => _pcr.PublishedContent.Id); + _pcr.TemplateModel = GetTemplateModel(_pcr.PublishedContent.TemplateId); + } } if (_pcr.HasTemplate == false) @@ -732,11 +726,31 @@ namespace Umbraco.Web.Routing } } - /// - /// Follows external redirection through umbracoRedirect document property. - /// - /// As per legacy, if the redirect does not work, we just ignore it. - private void FollowExternalRedirect() + private ITemplate GetTemplateModel(int templateId) + { + const string tracePrefix = "GetTemplateModel: "; + + if (templateId > 0) + { + ProfilingLogger.Logger.Debug("{0}Look for template id={1}", () => tracePrefix, () => templateId); + var template = ApplicationContext.Current.Services.FileService.GetTemplate(templateId); + if (template == null) + throw new InvalidOperationException("The template with Id " + templateId + " does not exist, the page cannot render"); + ProfilingLogger.Logger.Debug("{0}Got template id={1} alias=\"{2}\"", () => tracePrefix, () => template.Id, () => template.Alias); + return template; + } + else + { + ProfilingLogger.Logger.Debug("{0}No specified template.", () => tracePrefix); + } + return null; + } + + /// + /// Follows external redirection through umbracoRedirect document property. + /// + /// As per legacy, if the redirect does not work, we just ignore it. + private void FollowExternalRedirect() { if (_pcr.HasPublishedContent == false) return;