From af382e5e71c5fa672bc29066958c8530b03e501d Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Mon, 8 Jun 2020 13:14:23 +0200 Subject: [PATCH] Migrated section controller + LanguageTreeController, ApplciationTreeController. Updated AngularJsonOnlyConfigurationAttribute to be a TypeFilterAttribute, to allow injeciton into the filter. Note: Left TODO about checking authorization of proxied trees.. --- .../Controllers/AuthenticationController.cs | 2 +- .../Controllers/BackOfficeServerVariables.cs | 16 +- .../Controllers/CodeFileController.cs | 1 + .../Controllers}/SectionController.cs | 78 +++++---- .../UmbracoAuthorizedJsonController.cs | 2 +- .../Extensions/CompositionExtensions.cs | 29 ++++ .../Runtime/BackOfficeComposer.cs | 17 +- .../Trees/ApplicationTreeController.cs | 157 +++++++++++------- .../Trees/LanguageTreeController.cs | 25 ++- .../Trees/TreeCollectionBuilder.cs | 1 + .../Trees/TreeController.cs | 11 +- .../Trees/TreeControllerBase.cs | 6 +- .../Trees/UrlHelperExtensions.cs | 4 +- .../Extensions/FormCollectionExtensions.cs | 3 +- .../AngularJsonOnlyConfigurationAttribute.cs | 47 +++--- .../Install/InstallApiController.cs | 4 +- .../Runtime/AspNetCoreComposer.cs | 13 -- src/Umbraco.Web/CompositionExtensions.cs | 10 -- .../Editors/BackOfficeServerVariables.cs | 11 +- src/Umbraco.Web/Umbraco.Web.csproj | 4 - 20 files changed, 250 insertions(+), 191 deletions(-) rename src/{Umbraco.Web/Editors => Umbraco.Web.BackOffice/Controllers}/SectionController.cs (53%) create mode 100644 src/Umbraco.Web.BackOffice/Extensions/CompositionExtensions.cs rename src/{Umbraco.Web => Umbraco.Web.BackOffice}/Trees/ApplicationTreeController.cs (68%) rename src/{Umbraco.Web => Umbraco.Web.BackOffice}/Trees/LanguageTreeController.cs (69%) rename src/{Umbraco.Web => Umbraco.Web.BackOffice}/Trees/TreeCollectionBuilder.cs (98%) diff --git a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs index 3b2d51bfdf..9e6691ee48 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs @@ -22,7 +22,7 @@ namespace Umbraco.Web.BackOffice.Controllers { [PluginController(Constants.Web.Mvc.BackOfficeApiArea)] // TODO: Maybe this could be applied with our Application Model conventions //[ValidationFilter] // TODO: I don't actually think this is required with our custom Application Model conventions applied - [TypeFilter(typeof(AngularJsonOnlyConfigurationAttribute))] // TODO: This could be applied with our Application Model conventions + [AngularJsonOnlyConfiguration] // TODO: This could be applied with our Application Model conventions [IsBackOffice] // TODO: This could be applied with our Application Model conventions public class AuthenticationController : UmbracoApiControllerBase { diff --git a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs index af5fa5b563..e761e13bd4 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs @@ -183,14 +183,14 @@ namespace Umbraco.Web.BackOffice.Controllers "imagesApiBaseUrl", _linkGenerator.GetUmbracoApiServiceBaseUrl( controller => controller.GetBigThumbnail("")) }, - //{ - // "sectionApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( - // controller => controller.GetSections()) - //}, - //{ - // "treeApplicationApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( - // controller => controller.GetApplicationTrees(null, null, null, TreeUse.None)) - //}, + { + "sectionApiBaseUrl", _linkGenerator.GetUmbracoApiServiceBaseUrl( + controller => controller.GetSections()) + }, + { + "treeApplicationApiBaseUrl", _linkGenerator.GetUmbracoApiServiceBaseUrl( + controller => controller.GetApplicationTrees(null, null, null, TreeUse.None)) + }, //{ // "contentTypeApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( // controller => controller.GetAllowedChildren(0)) diff --git a/src/Umbraco.Web.BackOffice/Controllers/CodeFileController.cs b/src/Umbraco.Web.BackOffice/Controllers/CodeFileController.cs index 56a4ca7def..a02220774d 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/CodeFileController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/CodeFileController.cs @@ -14,6 +14,7 @@ using Umbraco.Core.Models; using Umbraco.Core.Services; using Umbraco.Core.Strings; using Umbraco.Core.Strings.Css; +using Umbraco.Extensions; using Umbraco.Web.Models.ContentEditing; using Stylesheet = Umbraco.Core.Models.Stylesheet; using StylesheetRule = Umbraco.Web.Models.ContentEditing.StylesheetRule; diff --git a/src/Umbraco.Web/Editors/SectionController.cs b/src/Umbraco.Web.BackOffice/Controllers/SectionController.cs similarity index 53% rename from src/Umbraco.Web/Editors/SectionController.cs rename to src/Umbraco.Web.BackOffice/Controllers/SectionController.cs index f352d9154f..eb1694c34f 100644 --- a/src/Umbraco.Web/Editors/SectionController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/SectionController.cs @@ -1,62 +1,70 @@ using System.Collections.Generic; -using Umbraco.Web.Mvc; using System.Linq; -using Umbraco.Core; -using Umbraco.Core.Cache; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.Persistence; -using Umbraco.Core.Services; -using Umbraco.Core.Strings; -using Umbraco.Web.Trees; -using Section = Umbraco.Web.Models.ContentEditing.Section; -using Umbraco.Web.Models.Trees; -using Umbraco.Web.Services; +using Microsoft.AspNetCore.Mvc.Controllers; using Umbraco.Core.Mapping; -using Umbraco.Web.Routing; +using Umbraco.Core.Models; +using Umbraco.Core.Services; +using Umbraco.Web.BackOffice.Controllers; +using Umbraco.Web.Common.Attributes; +using Umbraco.Web.Models.ContentEditing; +using Umbraco.Web.Models.Trees; +using Umbraco.Web.Security; +using Umbraco.Web.Services; +using Umbraco.Web.Trees; namespace Umbraco.Web.Editors { /// - /// The API controller used for using the list of sections + /// The API controller used for using the list of sections /// [PluginController("UmbracoApi")] public class SectionController : UmbracoAuthorizedJsonController { + private readonly IControllerFactory _controllerFactory; private readonly IDashboardService _dashboardService; + private readonly ILocalizedTextService _localizedTextService; private readonly ISectionService _sectionService; private readonly ITreeService _treeService; + private readonly UmbracoMapper _umbracoMapper; + private readonly IWebSecurity _webSecurity; - public SectionController(IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, - IDashboardService dashboardService, ISectionService sectionService, ITreeService treeService, IShortStringHelper shortStringHelper, UmbracoMapper umbracoMapper, IPublishedUrlProvider publishedUrlProvider) - : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, shortStringHelper, umbracoMapper, publishedUrlProvider) + public SectionController( + IWebSecurity webSecurity, + ILocalizedTextService localizedTextService, + IDashboardService dashboardService, ISectionService sectionService, ITreeService treeService, + UmbracoMapper umbracoMapper, IControllerFactory controllerFactory) { + _webSecurity = webSecurity; + _localizedTextService = localizedTextService; _dashboardService = dashboardService; _sectionService = sectionService; _treeService = treeService; + _umbracoMapper = umbracoMapper; + _controllerFactory = controllerFactory; } public IEnumerable
GetSections() { - var sections = _sectionService.GetAllowedSections(Security.GetUserId().ResultOr(0)); + var sections = _sectionService.GetAllowedSections(_webSecurity.GetUserId().ResultOr(0)); - var sectionModels = sections.Select(Mapper.Map
).ToArray(); + var sectionModels = sections.Select(_umbracoMapper.Map
).ToArray(); // this is a bit nasty since we'll be proxying via the app tree controller but we sort of have to do that // since tree's by nature are controllers and require request contextual data - var appTreeController = new ApplicationTreeController(GlobalSettings, UmbracoContextAccessor, SqlContext, Services, AppCaches, Logger, RuntimeState, _treeService, _sectionService, Mapper, PublishedUrlProvider) - { - ControllerContext = ControllerContext - }; + var appTreeController = + new ApplicationTreeController(_treeService, _sectionService, _localizedTextService, _controllerFactory) + { + ControllerContext = ControllerContext + }; - var dashboards = _dashboardService.GetDashboards(Security.CurrentUser); + var dashboards = _dashboardService.GetDashboards(_webSecurity.CurrentUser); //now we can add metadata for each section so that the UI knows if there's actually anything at all to render for //a dashboard for a given section, then the UI can deal with it accordingly (i.e. redirect to the first tree) foreach (var section in sectionModels) { - var hasDashboards = dashboards.TryGetValue(section.Alias, out var dashboardsForSection) && dashboardsForSection.Any(); + var hasDashboards = dashboards.TryGetValue(section.Alias, out var dashboardsForSection) && + dashboardsForSection.Any(); if (hasDashboards) continue; // get the first tree in the section and get its root node route path @@ -68,7 +76,7 @@ namespace Umbraco.Web.Editors } /// - /// Returns the first non root/group node's route path + /// Returns the first non root/group node's route path /// /// /// @@ -77,30 +85,28 @@ namespace Umbraco.Web.Editors if (!rootNode.IsContainer || !rootNode.ContainsTrees) return rootNode.RoutePath; - foreach(var node in rootNode.Children) + foreach (var node in rootNode.Children) { if (node is TreeRootNode groupRoot) - return GetRoutePathForFirstTree(groupRoot);//recurse to get the first tree in the group - else - return node.RoutePath; + return GetRoutePathForFirstTree(groupRoot); //recurse to get the first tree in the group + return node.RoutePath; } return string.Empty; } /// - /// Returns all the sections that the user has access to + /// Returns all the sections that the user has access to /// /// public IEnumerable
GetAllSections() { var sections = _sectionService.GetSections(); - var mapped = sections.Select(Mapper.Map
); - if (Security.CurrentUser.IsAdmin()) + var mapped = sections.Select(_umbracoMapper.Map
); + if (_webSecurity.CurrentUser.IsAdmin()) return mapped; - return mapped.Where(x => Security.CurrentUser.AllowedSections.Contains(x.Alias)).ToArray(); + return mapped.Where(x => _webSecurity.CurrentUser.AllowedSections.Contains(x.Alias)).ToArray(); } - } } diff --git a/src/Umbraco.Web.BackOffice/Controllers/UmbracoAuthorizedJsonController.cs b/src/Umbraco.Web.BackOffice/Controllers/UmbracoAuthorizedJsonController.cs index 6a68651a50..2c1b106aa3 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/UmbracoAuthorizedJsonController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/UmbracoAuthorizedJsonController.cs @@ -13,7 +13,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// methods that are not called by Angular or don't contain a valid csrf header will NOT work. /// [TypeFilter(typeof(ValidateAngularAntiForgeryTokenAttribute))] - [TypeFilter(typeof(AngularJsonOnlyConfigurationAttribute))] // TODO: This could be applied with our Application Model conventions + [AngularJsonOnlyConfiguration] // TODO: This could be applied with our Application Model conventions public abstract class UmbracoAuthorizedJsonController : UmbracoAuthorizedApiController { diff --git a/src/Umbraco.Web.BackOffice/Extensions/CompositionExtensions.cs b/src/Umbraco.Web.BackOffice/Extensions/CompositionExtensions.cs new file mode 100644 index 0000000000..5cc481c018 --- /dev/null +++ b/src/Umbraco.Web.BackOffice/Extensions/CompositionExtensions.cs @@ -0,0 +1,29 @@ +using Umbraco.Core.Composing; +using Umbraco.Web.Trees; + +// the namespace here is intentional - although defined in Umbraco.Web assembly, +// this class should be visible when using Umbraco.Core.Components, alongside +// Umbraco.Core's own CompositionExtensions class + +// ReSharper disable once CheckNamespace +namespace Umbraco.Extensions +{ + /// + /// Provides extension methods to the class. + /// + public static class WebCompositionExtensions + { + #region Collection Builders + + /// + /// Gets the back office tree collection builder + /// + /// + /// + public static TreeCollectionBuilder Trees(this Composition composition) + => composition.WithCollectionBuilder(); + + #endregion + + } +} diff --git a/src/Umbraco.Web.BackOffice/Runtime/BackOfficeComposer.cs b/src/Umbraco.Web.BackOffice/Runtime/BackOfficeComposer.cs index b13cb4a192..c29ae43dbd 100644 --- a/src/Umbraco.Web.BackOffice/Runtime/BackOfficeComposer.cs +++ b/src/Umbraco.Web.BackOffice/Runtime/BackOfficeComposer.cs @@ -1,23 +1,34 @@ -using Microsoft.AspNetCore.Identity; -using System.Linq; +using System.Linq; using Umbraco.Core; -using Umbraco.Core.BackOffice; using Umbraco.Core.Composing; using Umbraco.Extensions; using Umbraco.Web.BackOffice.Controllers; using Umbraco.Web.BackOffice.Routing; using Umbraco.Web.BackOffice.Security; +using Umbraco.Web.BackOffice.Trees; +using Umbraco.Web.Common.Runtime; namespace Umbraco.Web.BackOffice.Runtime { + [ComposeBefore(typeof(ICoreComposer))] + [ComposeAfter(typeof(AspNetCoreComposer))] public class BackOfficeComposer : IComposer { public void Compose(Composition composition) { + + composition.RegisterUnique(); composition.RegisterUnique(); composition.RegisterUnique(); + + // register back office trees + // the collection builder only accepts types inheriting from TreeControllerBase + // and will filter out those that are not attributed with TreeAttribute + var umbracoApiControllerTypes = composition.TypeLoader.GetUmbracoApiControllers().ToList(); + composition.Trees() + .AddTreeControllers(umbracoApiControllerTypes.Where(x => typeof(TreeControllerBase).IsAssignableFrom(x))); } } } diff --git a/src/Umbraco.Web/Trees/ApplicationTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/ApplicationTreeController.cs similarity index 68% rename from src/Umbraco.Web/Trees/ApplicationTreeController.cs rename to src/Umbraco.Web.BackOffice/Trees/ApplicationTreeController.cs index d815f76b40..7728ee017a 100644 --- a/src/Umbraco.Web/Trees/ApplicationTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/ApplicationTreeController.cs @@ -3,25 +3,25 @@ using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; -using System.Net.Http.Formatting; +using System.Reflection; using System.Threading.Tasks; -using System.Web.Http; -using System.Web.Http.Controllers; -using System.Web.Http.Routing; -using System.Web.Mvc; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.Primitives; using Umbraco.Core; -using Umbraco.Core.Cache; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Mapping; -using Umbraco.Core.Persistence; using Umbraco.Core.Services; +using Umbraco.Extensions; +using Umbraco.Web.BackOffice.Controllers; +using Umbraco.Web.BackOffice.Trees; +using Umbraco.Web.Common.Attributes; +using Umbraco.Web.Common.Exceptions; +using Umbraco.Web.Common.Filters; +using Umbraco.Web.Common.ModelBinders; using Umbraco.Web.Models.Trees; -using Umbraco.Web.Mvc; -using Umbraco.Web.Routing; using Umbraco.Web.Services; -using Umbraco.Web.WebApi; -using Umbraco.Web.WebApi.Filters; using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.Trees @@ -35,15 +35,21 @@ namespace Umbraco.Web.Trees { private readonly ITreeService _treeService; private readonly ISectionService _sectionService; + private readonly ILocalizedTextService _localizedTextService; + private readonly IControllerFactory _controllerFactory; - public ApplicationTreeController(IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, - ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, - IRuntimeState runtimeState, ITreeService treeService, ISectionService sectionService, UmbracoMapper umbracoMapper, IPublishedUrlProvider publishedUrlProvider) - : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoMapper, publishedUrlProvider) - { + public ApplicationTreeController( + ITreeService treeService, + ISectionService sectionService, + ILocalizedTextService localizedTextService, + IControllerFactory controllerFactory + ) + { _treeService = treeService; _sectionService = sectionService; - } + _localizedTextService = localizedTextService; + _controllerFactory = controllerFactory; + } /// /// Returns the tree nodes for an application @@ -53,7 +59,7 @@ namespace Umbraco.Web.Trees /// /// Tree use. /// - public async Task GetApplicationTrees(string application, string tree, [System.Web.Http.ModelBinding.ModelBinder(typeof(HttpQueryStringModelBinder))]FormDataCollection queryStrings, TreeUse use = TreeUse.Main) + public async Task GetApplicationTrees(string application, string tree, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormCollection queryStrings, TreeUse use = TreeUse.Main) { application = application.CleanForXss(); @@ -72,7 +78,7 @@ namespace Umbraco.Web.Trees { //if there are no trees defined for this section but the section is defined then we can have a simple //full screen section without trees - var name = Services.TextService.Localize("sections/" + application); + var name = _localizedTextService.Localize("sections/" + application); return TreeRootNode.CreateSingleTreeRoot(Constants.System.RootString, null, null, name, TreeNodeCollection.Empty, true); } @@ -105,7 +111,7 @@ namespace Umbraco.Web.Trees nodes.Add(node); } - var name = Services.TextService.Localize("sections/" + application); + var name = _localizedTextService.Localize("sections/" + application); if (nodes.Count > 0) { @@ -140,7 +146,7 @@ namespace Umbraco.Web.Trees var name = groupName.IsNullOrWhiteSpace() ? "thirdPartyGroup" : groupName; var groupRootNode = TreeRootNode.CreateGroupNode(nodes, application); - groupRootNode.Name = Services.TextService.Localize("treeHeaders/" + name); + groupRootNode.Name = _localizedTextService.Localize("treeHeaders/" + name); treeRootNodes.Add(groupRootNode); } @@ -154,7 +160,7 @@ namespace Umbraco.Web.Trees /// Returns null if the root node could not be obtained due to an HttpResponseException, /// which probably indicates that the user isn't authorized to view that tree. /// - private async Task TryGetRootNode(Tree tree, FormDataCollection querystring) + private async Task TryGetRootNode(Tree tree, FormCollection querystring) { if (tree == null) throw new ArgumentNullException(nameof(tree)); @@ -174,7 +180,7 @@ namespace Umbraco.Web.Trees /// /// Get the tree root node of a tree. /// - private async Task GetTreeRootNode(Tree tree, int id, FormDataCollection querystring) + private async Task GetTreeRootNode(Tree tree, int id, FormCollection querystring) { if (tree == null) throw new ArgumentNullException(nameof(tree)); @@ -203,7 +209,7 @@ namespace Umbraco.Web.Trees /// /// Gets the root node of a tree. /// - private async Task GetRootNode(Tree tree, FormDataCollection querystring) + private async Task GetRootNode(Tree tree, FormCollection querystring) { if (tree == null) throw new ArgumentNullException(nameof(tree)); @@ -217,16 +223,16 @@ namespace Umbraco.Web.Trees /// /// Get the child nodes of a tree node. /// - private async Task GetChildren(Tree tree, int id, FormDataCollection querystring) + private async Task GetChildren(Tree tree, int id, FormCollection querystring) { if (tree == null) throw new ArgumentNullException(nameof(tree)); // the method we proxy has an 'id' parameter which is *not* in the querystring, // we need to add it for the proxy to work (else, it does not find the method, // when trying to run auth filters etc). - var d = querystring?.ToDictionary(x => x.Key, x => x.Value) ?? new Dictionary(); - d["id"] = null; - var proxyQuerystring = new FormDataCollection(d); + var d = querystring?.ToDictionary(x => x.Key, x => x.Value) ?? new Dictionary(); + d["id"] = StringValues.Empty; + var proxyQuerystring = new FormCollection(d); var controller = (TreeController) await GetApiControllerProxy(tree.TreeControllerType, "GetNodes", proxyQuerystring); return controller.GetNodes(id.ToInvariantString(), querystring); @@ -244,49 +250,74 @@ namespace Umbraco.Web.Trees /// and context etc. so it can execute the specified . Runs the authorization /// filters for that action, to ensure that the user has permission to execute it. /// - private async Task GetApiControllerProxy(Type controllerType, string action, FormDataCollection querystring) + private async Task GetApiControllerProxy(Type controllerType, string action, FormCollection querystring) { // note: this is all required in order to execute the auth-filters for the sub request, we // need to "trick" web-api into thinking that it is actually executing the proxied controller. - var context = ControllerContext; - - // get the controller - var controller = (ApiController) DependencyResolver.Current.GetService(controllerType) - ?? throw new Exception($"Failed to create controller of type {controllerType.FullName}."); - - // create the proxy URL for the controller action - var proxyUrl = context.Request.RequestUri.GetLeftPart(UriPartial.Authority) - + context.Request.GetUrlHelper().GetUmbracoApiService(action, controllerType) - + "?" + querystring.ToQueryString(); // create proxy route data specifying the action & controller to execute - var proxyRoute = new HttpRouteData( - context.RouteData.Route, - new HttpRouteValueDictionary(new { action, controller = ControllerExtensions.GetControllerName(controllerType) })); - - // create a proxy request - var proxyRequest = new HttpRequestMessage(HttpMethod.Get, proxyUrl); - - // create a proxy controller context - var proxyContext = new HttpControllerContext(context.Configuration, proxyRoute, proxyRequest) + var routeData = new RouteData(new RouteValueDictionary() { - ControllerDescriptor = new HttpControllerDescriptor(context.ControllerDescriptor.Configuration, ControllerExtensions.GetControllerName(controllerType), controllerType), - RequestContext = context.RequestContext, - Controller = controller - }; + ["action"] = action, + ["controller"] = controllerType.Name.Substring(0,controllerType.Name.Length-10) // remove controller part of name; - // wire everything - controller.ControllerContext = proxyContext; - controller.Request = proxyContext.Request; - controller.RequestContext.RouteData = proxyRoute; + }); - // auth - var authResult = await controller.ControllerContext.InvokeAuthorizationFiltersForRequest(); - if (authResult != null) - throw new HttpResponseException(authResult); + + var controllerContext = new ControllerContext( + new ActionContext( + HttpContext, + routeData, + new ControllerActionDescriptor() + { + ControllerTypeInfo = controllerType.GetTypeInfo() + } + )); + + var controller = (TreeController) _controllerFactory.CreateController(controllerContext); + + + //TODO Refactor trees or reimplement this hacks to check authentication. + //https://dev.azure.com/umbraco/D-Team%20Tracker/_workitems/edit/3694 + + // var context = ControllerContext; + // + // // get the controller + // var controller = (TreeController) DependencyResolver.Current.GetService(controllerType) + // ?? throw new Exception($"Failed to create controller of type {controllerType.FullName}."); + // + // // create the proxy URL for the controller action + // var proxyUrl = HttpContext.Request.RequestUri.GetLeftPart(UriPartial.Authority) + // + HttpContext.Request.GetUrlHelper().GetUmbracoApiService(action, controllerType) + // + "?" + querystring.ToQueryString(); + // + // + // + // // create a proxy request + // var proxyRequest = new HttpRequestMessage(HttpMethod.Get, proxyUrl); + // + // // create a proxy controller context + // var proxyContext = new HttpControllerContext(context.Configuration, proxyRoute, proxyRequest) + // { + // ControllerDescriptor = new HttpControllerDescriptor(context.ControllerDescriptor.Configuration, ControllerExtensions.GetControllerName(controllerType), controllerType), + // RequestContext = context.RequestContext, + // Controller = controller + // }; + // + // // wire everything + // controller.ControllerContext = proxyContext; + // controller.Request = proxyContext.Request; + // controller.RequestContext.RouteData = proxyRoute; + // + // // auth + // var authResult = await controller.ControllerContext.InvokeAuthorizationFiltersForRequest(); + // if (authResult != null) + // throw new HttpResponseException(authResult); return controller; } + + } } diff --git a/src/Umbraco.Web/Trees/LanguageTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/LanguageTreeController.cs similarity index 69% rename from src/Umbraco.Web/Trees/LanguageTreeController.cs rename to src/Umbraco.Web.BackOffice/Trees/LanguageTreeController.cs index 944cbd103c..d34c26b07d 100644 --- a/src/Umbraco.Web/Trees/LanguageTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/LanguageTreeController.cs @@ -1,8 +1,10 @@ -using System.Net.Http.Formatting; -using Umbraco.Core; +using Microsoft.AspNetCore.Http; +using Umbraco.Core.Services; +using Umbraco.Web.BackOffice.Filters; +using Umbraco.Web.BackOffice.Trees; +using Umbraco.Web.Common.Attributes; using Umbraco.Web.Models.Trees; -using Umbraco.Web.Mvc; -using Umbraco.Web.WebApi.Filters; +using Umbraco.Web.WebApi; using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.Trees @@ -13,13 +15,20 @@ namespace Umbraco.Web.Trees [CoreTree] public class LanguageTreeController : TreeController { - protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) + + public LanguageTreeController( + ILocalizedTextService textService, + UmbracoApiControllerTypeCollection umbracoApiControllerTypeCollection) + : base(textService, umbracoApiControllerTypeCollection) + { + } + protected override TreeNodeCollection GetTreeNodes(string id, FormCollection queryStrings) { //We don't have any child nodes & only use the root node to load a custom UI return new TreeNodeCollection(); } - protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings) + protected override MenuItemCollection GetMenuForNode(string id, FormCollection queryStrings) { //We don't have any menu item options (such as create/delete/reload) & only use the root node to load a custom UI return null; @@ -29,7 +38,7 @@ namespace Umbraco.Web.Trees /// Helper method to create a root model for a tree /// /// - protected override TreeNode CreateRootNode(FormDataCollection queryStrings) + protected override TreeNode CreateRootNode(FormCollection queryStrings) { var root = base.CreateRootNode(queryStrings); @@ -41,5 +50,7 @@ namespace Umbraco.Web.Trees return root; } + + } } diff --git a/src/Umbraco.Web/Trees/TreeCollectionBuilder.cs b/src/Umbraco.Web.BackOffice/Trees/TreeCollectionBuilder.cs similarity index 98% rename from src/Umbraco.Web/Trees/TreeCollectionBuilder.cs rename to src/Umbraco.Web.BackOffice/Trees/TreeCollectionBuilder.cs index d324ad93b2..fb1b642bc8 100644 --- a/src/Umbraco.Web/Trees/TreeCollectionBuilder.cs +++ b/src/Umbraco.Web.BackOffice/Trees/TreeCollectionBuilder.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using Umbraco.Core; using Umbraco.Core.Composing; +using Umbraco.Web.BackOffice.Trees; namespace Umbraco.Web.Trees { diff --git a/src/Umbraco.Web.BackOffice/Trees/TreeController.cs b/src/Umbraco.Web.BackOffice/Trees/TreeController.cs index b28cf5843c..9a342f5519 100644 --- a/src/Umbraco.Web.BackOffice/Trees/TreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/TreeController.cs @@ -4,6 +4,7 @@ using Microsoft.Extensions.DependencyInjection; using Umbraco.Core; using Umbraco.Core.Services; using Umbraco.Web.Trees; +using Umbraco.Web.WebApi; namespace Umbraco.Web.BackOffice.Trees { @@ -18,14 +19,8 @@ namespace Umbraco.Web.BackOffice.Trees private readonly ILocalizedTextService _textService; - protected TreeController() - { - var serviceProvider = HttpContext.RequestServices; - _textService = serviceProvider.GetService(); - _treeAttribute = GetTreeAttribute(); - } - - protected TreeController(ILocalizedTextService textService) + protected TreeController(ILocalizedTextService textService, UmbracoApiControllerTypeCollection umbracoApiControllerTypeCollection) + : base(umbracoApiControllerTypeCollection) { _textService = textService ?? throw new ArgumentNullException(nameof(textService)); _treeAttribute = GetTreeAttribute(); diff --git a/src/Umbraco.Web.BackOffice/Trees/TreeControllerBase.cs b/src/Umbraco.Web.BackOffice/Trees/TreeControllerBase.cs index ab0dacfe9a..15130002df 100644 --- a/src/Umbraco.Web.BackOffice/Trees/TreeControllerBase.cs +++ b/src/Umbraco.Web.BackOffice/Trees/TreeControllerBase.cs @@ -7,6 +7,7 @@ using Umbraco.Core.Events; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence; +using Umbraco.Extensions; using Umbraco.Web.BackOffice.Controllers; using Umbraco.Web.Common.Filters; using Umbraco.Web.Common.ModelBinders; @@ -22,15 +23,16 @@ namespace Umbraco.Web.BackOffice.Trees /// /// Developers should generally inherit from TreeController. /// - [TypeFilter(typeof(AngularJsonOnlyConfigurationAttribute))] + [AngularJsonOnlyConfiguration] public abstract class TreeControllerBase : UmbracoAuthorizedApiController, ITree { // TODO: Need to set this, but from where? // Presumably not injecting as this will be a base controller for package/solution developers. private readonly UmbracoApiControllerTypeCollection _apiControllers; - protected TreeControllerBase() + protected TreeControllerBase(UmbracoApiControllerTypeCollection apiControllers) { + _apiControllers = apiControllers; } /// diff --git a/src/Umbraco.Web.BackOffice/Trees/UrlHelperExtensions.cs b/src/Umbraco.Web.BackOffice/Trees/UrlHelperExtensions.cs index eef45976f0..d822688202 100644 --- a/src/Umbraco.Web.BackOffice/Trees/UrlHelperExtensions.cs +++ b/src/Umbraco.Web.BackOffice/Trees/UrlHelperExtensions.cs @@ -5,11 +5,11 @@ using System.Web; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Core; -using Umbraco.Extensions; +using Umbraco.Web.BackOffice.Trees; using Umbraco.Web.Common.Extensions; using Umbraco.Web.WebApi; -namespace Umbraco.Web.BackOffice.Trees +namespace Umbraco.Extensions { public static class UrlHelperExtensions { diff --git a/src/Umbraco.Web.Common/Extensions/FormCollectionExtensions.cs b/src/Umbraco.Web.Common/Extensions/FormCollectionExtensions.cs index 099c2416fc..59b29ffa9b 100644 --- a/src/Umbraco.Web.Common/Extensions/FormCollectionExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/FormCollectionExtensions.cs @@ -4,9 +4,8 @@ using System.Linq; using System.Text; using Microsoft.AspNetCore.Http; using Umbraco.Core; -using Umbraco.Web.Common.Extensions; -namespace Umbraco.Web.Common.Extensions +namespace Umbraco.Extensions { public static class FormCollectionExtensions { diff --git a/src/Umbraco.Web.Common/Filters/AngularJsonOnlyConfigurationAttribute.cs b/src/Umbraco.Web.Common/Filters/AngularJsonOnlyConfigurationAttribute.cs index 192b5e7df0..0eabbf0f54 100644 --- a/src/Umbraco.Web.Common/Filters/AngularJsonOnlyConfigurationAttribute.cs +++ b/src/Umbraco.Web.Common/Filters/AngularJsonOnlyConfigurationAttribute.cs @@ -1,12 +1,8 @@ -using System.Buffers; -using System.Collections.Generic; -using System.Text.Json; + +using System.Buffers; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; -using Microsoft.AspNetCore.Mvc.Formatters; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; -using Newtonsoft.Json; using Umbraco.Web.Common.Formatters; namespace Umbraco.Web.Common.Filters @@ -14,29 +10,40 @@ namespace Umbraco.Web.Common.Filters /// /// Applying this attribute to any controller will ensure that it only contains one json formatter compatible with the angular json vulnerability prevention. /// - public class AngularJsonOnlyConfigurationAttribute : ActionFilterAttribute + public class AngularJsonOnlyConfigurationAttribute : TypeFilterAttribute { - private readonly IOptions _mvcNewtonsoftJsonOptions; - private readonly ArrayPool _arrayPool; - private readonly IOptions _options; - - public AngularJsonOnlyConfigurationAttribute(IOptions mvcNewtonsoftJsonOptions, ArrayPool arrayPool, IOptions options) + public AngularJsonOnlyConfigurationAttribute() : base(typeof(AngularJsonOnlyConfigurationFilter)) { - _mvcNewtonsoftJsonOptions = mvcNewtonsoftJsonOptions; - _arrayPool = arrayPool; - _options = options; } - public override void OnResultExecuting(ResultExecutingContext context) + private class AngularJsonOnlyConfigurationFilter : IResultFilter { - if (context.Result is ObjectResult objectResult) + private readonly IOptions _mvcNewtonsoftJsonOptions; + private readonly ArrayPool _arrayPool; + private readonly IOptions _options; + + public AngularJsonOnlyConfigurationFilter(IOptions mvcNewtonsoftJsonOptions, ArrayPool arrayPool, IOptions options) { - objectResult.Formatters.Clear(); - objectResult.Formatters.Add(new AngularJsonMediaTypeFormatter(_mvcNewtonsoftJsonOptions.Value.SerializerSettings, _arrayPool, _options.Value)); + _mvcNewtonsoftJsonOptions = mvcNewtonsoftJsonOptions; + _arrayPool = arrayPool; + _options = options; } - base.OnResultExecuting(context); + public void OnResultExecuted(ResultExecutedContext context) + { + + } + + public void OnResultExecuting(ResultExecutingContext context) + { + if (context.Result is ObjectResult objectResult) + { + objectResult.Formatters.Clear(); + objectResult.Formatters.Add(new AngularJsonMediaTypeFormatter(_mvcNewtonsoftJsonOptions.Value.SerializerSettings, _arrayPool, _options.Value)); + } + } } } + } diff --git a/src/Umbraco.Web.Common/Install/InstallApiController.cs b/src/Umbraco.Web.Common/Install/InstallApiController.cs index c171b09fa8..207b8f49c3 100644 --- a/src/Umbraco.Web.Common/Install/InstallApiController.cs +++ b/src/Umbraco.Web.Common/Install/InstallApiController.cs @@ -22,7 +22,7 @@ namespace Umbraco.Web.Common.Install [UmbracoApiController] [TypeFilter(typeof(HttpResponseExceptionFilter))] - [TypeFilter(typeof(AngularJsonOnlyConfigurationAttribute))] + [AngularJsonOnlyConfiguration] [InstallAuthorize] [Area(Umbraco.Core.Constants.Web.Mvc.InstallArea)] public class InstallApiController : ControllerBase @@ -95,7 +95,7 @@ namespace Umbraco.Web.Common.Install /// /// Installs. - /// + /// public async Task PostPerformInstall(InstallInstructions installModel) { if (installModel == null) throw new ArgumentNullException(nameof(installModel)); diff --git a/src/Umbraco.Web.Common/Runtime/AspNetCoreComposer.cs b/src/Umbraco.Web.Common/Runtime/AspNetCoreComposer.cs index 62b3b7f34c..0dad8053c6 100644 --- a/src/Umbraco.Web.Common/Runtime/AspNetCoreComposer.cs +++ b/src/Umbraco.Web.Common/Runtime/AspNetCoreComposer.cs @@ -18,10 +18,8 @@ using Umbraco.Web.Common.Install; using Umbraco.Extensions; using System.Linq; using Umbraco.Web.Common.Controllers; -using System; using Umbraco.Web.Common.Middleware; using Umbraco.Web.Common.ModelBinding; -using Umbraco.Web.Search; using Umbraco.Web.Security; using Umbraco.Web.Trees; @@ -83,13 +81,6 @@ namespace Umbraco.Web.Common.Runtime composition.WithCollectionBuilder() .Add(umbracoApiControllerTypes); - // register back office trees - // the collection builder only accepts types inheriting from TreeControllerBase - // and will filter out those that are not attributed with TreeAttribute - // composition.Trees() - // .AddTreeControllers(umbracoApiControllerTypes.Where(x => typeof(TreeControllerBase).IsAssignableFrom(x))); - composition.RegisterUnique(); //TODO replace with collection builder above - composition.RegisterUnique(); @@ -98,10 +89,6 @@ namespace Umbraco.Web.Common.Runtime composition.RegisterUnique(); composition.RegisterUnique(); - - - - } } } diff --git a/src/Umbraco.Web/CompositionExtensions.cs b/src/Umbraco.Web/CompositionExtensions.cs index 29a892f1d5..85b7b68c35 100644 --- a/src/Umbraco.Web/CompositionExtensions.cs +++ b/src/Umbraco.Web/CompositionExtensions.cs @@ -41,16 +41,6 @@ namespace Umbraco.Web - - /// - /// Gets the back office tree collection builder - /// - /// - /// - public static TreeCollectionBuilder Trees(this Composition composition) - => composition.WithCollectionBuilder(); - - #endregion #region Uniques diff --git a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs index 38458c932b..e4fe697e20 100644 --- a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs +++ b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs @@ -172,14 +172,7 @@ namespace Umbraco.Web.Editors // "imagesApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( // controller => controller.GetBigThumbnail("")) // }, - { - "sectionApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( - controller => controller.GetSections()) - }, - { - "treeApplicationApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( - controller => controller.GetApplicationTrees(null, null, null, TreeUse.None)) - }, + { "contentTypeApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( controller => controller.GetAllowedChildren(0)) @@ -196,7 +189,7 @@ namespace Umbraco.Web.Editors "macroApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( controller => controller.Create(null)) }, - + { "currentUserApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( controller => controller.PostChangePassword(null)) diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 1f52f73315..815dbb9507 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -202,7 +202,6 @@ - @@ -343,7 +342,6 @@ - @@ -374,7 +372,6 @@ - @@ -384,7 +381,6 @@ -