Fixes: U4-4010 Member and user session gets mixed - fixes how we detect a back office request + unit tests and added new IsBackOffice attribute

This commit is contained in:
Shannon
2014-01-15 13:17:38 +11:00
parent 75a42fd0d6
commit 3ef9635406
10 changed files with 148 additions and 29 deletions

View File

@@ -11,24 +11,97 @@ namespace Umbraco.Core
/// </summary>
public static class UriExtensions
{
/// <summary>
/// Checks if the current uri is a back office request
/// </summary>
/// <param name="url"></param>
/// <param name="applicationPath">
/// The current application path or VirtualPath
/// </param>
/// <returns></returns>
internal static bool IsBackOfficeRequest(this Uri url)
/// <remarks>
/// There are some special routes we need to check to properly determine this:
///
/// If any route has an extension in the path like .aspx = back office
///
/// These are def back office:
/// /Umbraco/RestServices = back office
/// /Umbraco/BackOffice = back office
/// /Umbraco/UmbracpApi = back office
/// /Umbraco/UmbracoTrees = back office
/// If it's not any of the above, and there's no extension then we cannot determine if it's back office or front-end
/// so we can only assume that it is not back office. This will occur if people use an UmbracoApiController for the backoffice
/// but do not inherit from UmbracoAuthorizedApiController and do not use [IsBackOffice] attribute.
///
/// These are def front-end:
/// /Umbraco/Surface = front-end
/// /Umbraco/Api = front-end
/// But if we've got this far we'll just have to assume it's front-end anyways.
///
/// </remarks>
internal static bool IsBackOfficeRequest(this Uri url, string applicationPath)
{
var authority = url.GetLeftPart(UriPartial.Authority);
var afterAuthority = url.GetLeftPart(UriPartial.Query)
.TrimStart(authority)
.TrimStart("/");
applicationPath = applicationPath ?? string.Empty;
var fullUrlPath = url.AbsolutePath.TrimStart(new[] {'/'});
var appPath = applicationPath.TrimStart(new[] {'/'});
var urlPath = fullUrlPath.TrimStart(appPath).EnsureStartsWith('/');
//check if this is in the umbraco back office
return afterAuthority.InvariantStartsWith(GlobalSettings.Path.TrimStart("/"));
var isUmbracoPath = urlPath.InvariantStartsWith(GlobalSettings.Path.EnsureStartsWith('/'));
//if not, then def not back office
if (isUmbracoPath == false) return false;
//if its the normal /umbraco path
if (urlPath.InvariantEquals("/" + GlobalSettings.UmbracoMvcArea)
|| urlPath.InvariantEquals("/" + GlobalSettings.UmbracoMvcArea + "/"))
{
return true;
}
//check for a file extension
var extension = Path.GetExtension(url.LocalPath);
//has an extension, def back office
if (extension.IsNullOrWhiteSpace() == false) return true;
//check for special case asp.net calls like:
// /umbraco/webservices/legacyAjaxCalls.asmx/js which will return a null file extension but are still considered extension'd requests
if (urlPath.InvariantContains(".asmx/")
|| urlPath.InvariantContains(".aspx/")
|| urlPath.InvariantContains(".ashx/")
|| urlPath.InvariantContains(".axd/")
|| urlPath.InvariantContains(".svc/"))
{
return true;
}
//check for special back office paths
if (urlPath.InvariantStartsWith("/" + GlobalSettings.UmbracoMvcArea + "/UmbracoApi/")
|| urlPath.InvariantStartsWith("/" + GlobalSettings.UmbracoMvcArea + "/UmbracoTrees/")
|| urlPath.InvariantStartsWith("/" + GlobalSettings.UmbracoMvcArea + "/BackOffice/")
|| urlPath.InvariantStartsWith("/" + GlobalSettings.UmbracoMvcArea + "/RestServices/"))
{
return true;
}
//check for special front-end paths
if (urlPath.InvariantStartsWith("/" + GlobalSettings.UmbracoMvcArea + "/Surface/")
|| urlPath.InvariantStartsWith("/" + GlobalSettings.UmbracoMvcArea + "/Api/"))
{
return false;
}
//if its none of the above, we will have to try to detect if it's a PluginController route, we can detect this by
// checking how many parts the route has, for example, all PluginController routes will be routed like
// Umbraco/MYPLUGINAREA/MYCONTROLLERNAME/{action}/{id}
// so if the path contains at a minimum 3 parts: Umbraco + MYPLUGINAREA + MYCONTROLLERNAME then we will have to assume it is a
// plugin controller for the front-end.
if (urlPath.Split(new[] {'/'}, StringSplitOptions.RemoveEmptyEntries).Length >= 3)
{
return false;
}
//if its anything else we can assume it's back office
return true;
}
/// <summary>

View File

@@ -10,20 +10,35 @@ namespace Umbraco.Tests
[TestFixture]
public class UriExtensionsTests
{
[TestCase("http://www.domain.com/umbraco", true)]
[TestCase("http://www.domain.com/Umbraco/", true)]
[TestCase("http://www.domain.com/umbraco/default.aspx", true)]
[TestCase("http://www.domain.com/umbraco/test/test", true)]
[TestCase("http://www.domain.com/Umbraco/test/test.aspx", true)]
[TestCase("http://www.domain.com/umbraco/test/test.js", true)]
[TestCase("http://www.domain.com/umbrac", false)]
[TestCase("http://www.domain.com/test", false)]
[TestCase("http://www.domain.com/test/umbraco", false)]
[TestCase("http://www.domain.com/test/umbraco.aspx", false)]
public void Is_Back_Office_Request(string input, bool expected)
[TestCase("http://www.domain.com/umbraco", "", true)]
[TestCase("http://www.domain.com/Umbraco/", "", true)]
[TestCase("http://www.domain.com/umbraco/default.aspx", "", true)]
[TestCase("http://www.domain.com/umbraco/test/test", "", false)]
[TestCase("http://www.domain.com/umbraco/test/test/test", "", false)]
[TestCase("http://www.domain.com/Umbraco/test/test.aspx", "", true)]
[TestCase("http://www.domain.com/umbraco/test/test.js", "", true)]
[TestCase("http://www.domain.com/umbrac", "", false)]
[TestCase("http://www.domain.com/test", "", false)]
[TestCase("http://www.domain.com/test/umbraco", "", false)]
[TestCase("http://www.domain.com/test/umbraco.aspx", "", false)]
[TestCase("http://www.domain.com/Umbraco/restServices/blah", "", true)]
[TestCase("http://www.domain.com/Umbraco/Backoffice/blah", "", true)]
[TestCase("http://www.domain.com/Umbraco/umbracoapi/blah", "", true)]
[TestCase("http://www.domain.com/Umbraco/umbracotrees/blah", "", true)]
[TestCase("http://www.domain.com/Umbraco/anything", "", true)]
[TestCase("http://www.domain.com/Umbraco/anything/", "", true)]
[TestCase("http://www.domain.com/Umbraco/surface/blah", "", false)]
[TestCase("http://www.domain.com/umbraco/api/blah", "", false)]
[TestCase("http://www.domain.com/myvdir/umbraco/api/blah", "myvdir", false)]
[TestCase("http://www.domain.com/MyVdir/umbraco/api/blah", "/myvdir", false)]
[TestCase("http://www.domain.com/MyVdir/Umbraco/", "myvdir", true)]
[TestCase("http://www.domain.com/MyVdir/Umbraco/restServices/blah", "/myvdir", true)]
[TestCase("http://www.domain.com/umbraco/webservices/legacyAjaxCalls.asmx/js", "", true)]
[TestCase("http://www.domain.com/umbraco/test/legacyAjaxCalls.ashx?some=query&blah=js", "", true)]
public void Is_Back_Office_Request(string input, string virtualPath, bool expected)
{
var source = new Uri(input);
Assert.AreEqual(expected, source.IsBackOfficeRequest());
var source = new Uri(input);
Assert.AreEqual(expected, source.IsBackOfficeRequest(virtualPath));
}
[TestCase("http://www.domain.com/install", true)]

View File

@@ -3,6 +3,7 @@ using System.Collections.Concurrent;
using System.Web.Mvc;
using Umbraco.Core;
using Umbraco.Core.Services;
using Umbraco.Web.WebApi;
namespace Umbraco.Web.Mvc
{
@@ -86,14 +87,17 @@ namespace Umbraco.Web.Mvc
return MetadataStorage.GetOrAdd(type, type1 =>
{
var attribute = type.GetCustomAttribute<PluginControllerAttribute>(false);
var pluginAttribute = type.GetCustomAttribute<PluginControllerAttribute>(false);
//check if any inherited class of this type contains the IsBackOffice attribute
var backOfficeAttribute = type.GetCustomAttribute<IsBackOfficeAttribute>(true);
var meta = new PluginControllerMetadata()
{
AreaName = attribute == null ? null : attribute.AreaName,
AreaName = pluginAttribute == null ? null : pluginAttribute.AreaName,
ControllerName = ControllerExtensions.GetControllerName(type),
ControllerNamespace = type.Namespace,
ControllerType = type
ControllerType = type,
IsBackOffice = backOfficeAttribute != null
};
MetadataStorage.TryAdd(type, meta);

View File

@@ -11,5 +11,11 @@ namespace Umbraco.Web.Mvc
internal string ControllerName { get; set; }
internal string ControllerNamespace { get; set; }
internal string AreaName { get; set; }
/// <summary>
/// This is determined by another attribute [IsBackOffice] which slightly modifies the route path
/// allowing us to determine if it is indeed a back office request or not
/// </summary>
internal bool IsBackOffice { get; set; }
}
}

View File

@@ -55,7 +55,7 @@ namespace Umbraco.Web.Strategies
}
static void UmbracoModuleRouteAttempt(object sender, Routing.RoutableAttemptEventArgs e)
static void UmbracoModuleRouteAttempt(object sender, RoutableAttemptEventArgs e)
{
if (e.HttpContext.Request == null || e.HttpContext.Request.Url == null) return;
@@ -78,7 +78,7 @@ namespace Umbraco.Web.Strategies
if (e.Outcome == EnsureRoutableOutcome.NotDocumentRequest)
{
//check if this is in the umbraco back office
if (e.HttpContext.Request.Url.IsBackOfficeRequest())
if (e.HttpContext.Request.Url.IsBackOfficeRequest(HttpRuntime.AppDomainAppVirtualPath))
{
//yup it's a back office request!
using (var lck = new UpgradeableReadLock(Locker))

View File

@@ -457,6 +457,7 @@
<Compile Include="umbraco.presentation\umbraco\Trees\TranslateTreeNames.cs" />
<Compile Include="UrlHelperExtensions.cs" />
<Compile Include="UrlHelperRenderExtensions.cs" />
<Compile Include="WebApi\IsBackOfficeAttribute.cs" />
<Compile Include="WebApi\MemberAuthorizeAttribute.cs" />
<Compile Include="WebApi\UmbracoApiController.cs" />
<Compile Include="WebApi\UmbracoApiControllerResolver.cs" />

View File

@@ -139,7 +139,7 @@ namespace Umbraco.Web
if (http.Request.Url.IsClientSideRequest())
return;
if (app.Request.Url.IsBackOfficeRequest() || app.Request.Url.IsInstallerRequest())
if (app.Request.Url.IsBackOfficeRequest(HttpRuntime.AppDomainAppVirtualPath) || app.Request.Url.IsInstallerRequest())
{
var ticket = http.GetUmbracoAuthTicket();
if (ticket != null)

View File

@@ -0,0 +1,13 @@
using System;
namespace Umbraco.Web.WebApi
{
/// <summary>
/// When applied to an api controller it will be routed to the /Umbraco/BackOffice prefix route so we can determine if it
/// is a back office route or not.
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public sealed class IsBackOfficeAttribute : Attribute
{
}
}

View File

@@ -7,6 +7,7 @@ using umbraco.BusinessLogic;
namespace Umbraco.Web.WebApi
{
[IsBackOffice]
[UmbracoAuthorize]
public abstract class UmbracoAuthorizedApiController : UmbracoApiController
{

View File

@@ -231,9 +231,15 @@ namespace Umbraco.Web
private void RouteLocalApiController(Type controller, string umbracoPath)
{
var meta = PluginController.GetMetadata(controller);
//url to match
var routePath = meta.IsBackOffice == false
? umbracoPath + "/Api/" + meta.ControllerName + "/{action}/{id}"
: umbracoPath + "/BackOffice/Api/" + meta.ControllerName + "/{action}/{id}";
var route = RouteTable.Routes.MapHttpRoute(
string.Format("umbraco-{0}-{1}", "api", meta.ControllerName),
umbracoPath + "/Api/" + meta.ControllerName + "/{action}/{id}", //url to match
routePath,
new {controller = meta.ControllerName, id = UrlParameter.Optional});
//web api routes don't set the data tokens object
if (route.DataTokens == null)