diff --git a/src/Umbraco.Web.BackOffice/Controllers/CodeFileController.cs b/src/Umbraco.Web.BackOffice/Controllers/CodeFileController.cs index cfb4d94ad4..63a7330c29 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/CodeFileController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/CodeFileController.cs @@ -30,7 +30,7 @@ namespace Umbraco.Web.BackOffice.Controllers // ref: https://www.exceptionnotfound.net/the-asp-net-web-api-exception-handling-pipeline-a-guided-tour/ [PluginController("UmbracoApi")] //[PrefixlessBodyModelValidator] - [UmbracoApplicationAuthorize(Core.Constants.Applications.Settings)] + [TypeFilter(typeof(UmbracoApplicationAuthorizeAttribute), Arguments = new object[]{new string []{Constants.Applications.Settings}})] public class CodeFileController : BackOfficeNotificationsController { private readonly IIOHelper _ioHelper; diff --git a/src/Umbraco.Web/Editors/DataTypeController.cs b/src/Umbraco.Web.BackOffice/Controllers/DataTypeController.cs similarity index 63% rename from src/Umbraco.Web/Editors/DataTypeController.cs rename to src/Umbraco.Web.BackOffice/Controllers/DataTypeController.cs index 19ad546b2d..8cc5d884bd 100644 --- a/src/Umbraco.Web/Editors/DataTypeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/DataTypeController.cs @@ -3,28 +3,21 @@ using System.Collections.Generic; using System.Data; using System.Linq; using System.Net; -using System.Web.Http; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; -using Umbraco.Core.Strings; using Umbraco.Web.Models.ContentEditing; -using Umbraco.Web.Mvc; -using Umbraco.Web.WebApi; using Umbraco.Web.WebApi.Filters; using System.Net.Http; using System.Text; -using Umbraco.Core.Cache; -using Umbraco.Web.Composing; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence; using Constants = Umbraco.Core.Constants; using Umbraco.Core.Mapping; -using System.Web.Http.Controllers; +using Microsoft.AspNetCore.Mvc; using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Web.Routing; +using Umbraco.Web.BackOffice.Filters; +using Umbraco.Web.Common.Attributes; +using Umbraco.Web.Common.Exceptions; namespace Umbraco.Web.Editors { @@ -37,45 +30,44 @@ namespace Umbraco.Web.Editors /// Content Types, Member Types or Media Types ... and of course to Data Types /// [PluginController("UmbracoApi")] - [UmbracoTreeAuthorize(Constants.Trees.DataTypes, Constants.Trees.DocumentTypes, Constants.Trees.MediaTypes, Constants.Trees.MemberTypes)] - [EnableOverrideAuthorization] - [DataTypeControllerConfiguration] + [TypeFilter(typeof(UmbracoTreeAuthorizeAttribute), Arguments = new object[]{new string[]{Constants.Trees.DataTypes, Constants.Trees.DocumentTypes, Constants.Trees.MediaTypes, Constants.Trees.MemberTypes}})] public class DataTypeController : BackOfficeNotificationsController { private readonly PropertyEditorCollection _propertyEditors; + private readonly IDataTypeService _dataTypeService; private readonly IContentSettings _contentSettings; + private readonly UmbracoMapper _umbracoMapper; + private readonly PropertyEditorCollection _propertyEditorCollection; + private readonly IContentTypeService _contentTypeService; + private readonly IMediaTypeService _mediaTypeService; + private readonly IMemberTypeService _memberTypeService; + private readonly ILocalizedTextService _localizedTextService; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; public DataTypeController( PropertyEditorCollection propertyEditors, - IGlobalSettings globalSettings, - IUmbracoContextAccessor umbracoContextAccessor, - ISqlContext sqlContext, - ServiceContext services, - AppCaches appCaches, - IProfilingLogger logger, - IRuntimeState runtimeState, - IShortStringHelper shortStringHelper, - UmbracoMapper umbracoMapper, + IDataTypeService dataTypeService, IContentSettings contentSettings, - IPublishedUrlProvider publishedUrlProvider) - : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, shortStringHelper, umbracoMapper, publishedUrlProvider) - { - _propertyEditors = propertyEditors; + UmbracoMapper umbracoMapper, + PropertyEditorCollection propertyEditorCollection, + IContentTypeService contentTypeService, + IMediaTypeService mediaTypeService, + IMemberTypeService memberTypeService, + ILocalizedTextService localizedTextService, + IUmbracoContextAccessor umbracoContextAccessor) + { + _propertyEditors = propertyEditors ?? throw new ArgumentNullException(nameof(propertyEditors)); + _dataTypeService = dataTypeService ?? throw new ArgumentNullException(nameof(dataTypeService)); _contentSettings = contentSettings ?? throw new ArgumentNullException(nameof(contentSettings)); - } + _umbracoMapper = umbracoMapper ?? throw new ArgumentNullException(nameof(umbracoMapper)); + _propertyEditorCollection = propertyEditorCollection ?? throw new ArgumentNullException(nameof(propertyEditorCollection)); + _contentTypeService = contentTypeService ?? throw new ArgumentNullException(nameof(contentTypeService)); + _mediaTypeService = mediaTypeService ?? throw new ArgumentNullException(nameof(mediaTypeService)); + _memberTypeService = memberTypeService ?? throw new ArgumentNullException(nameof(memberTypeService)); + _localizedTextService = localizedTextService ?? throw new ArgumentNullException(nameof(localizedTextService)); + _umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor)); + } - /// - /// Configures this controller with a custom action selector - /// - private class DataTypeControllerConfigurationAttribute : Attribute, IControllerConfiguration - { - public void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor) - { - controllerSettings.Services.Replace(typeof(IHttpActionSelector), new ParameterSwapControllerActionSelector( - new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetById", "id", typeof(int), typeof(Guid), typeof(Udi)) - )); - } - } /// /// Gets data type by name @@ -84,8 +76,8 @@ namespace Umbraco.Web.Editors /// public DataTypeDisplay GetByName(string name) { - var dataType = Services.DataTypeService.GetDataType(name); - return dataType == null ? null : Mapper.Map(dataType); + var dataType = _dataTypeService.GetDataType(name); + return dataType == null ? null : _umbracoMapper.Map(dataType); } /// @@ -95,12 +87,12 @@ namespace Umbraco.Web.Editors /// public DataTypeDisplay GetById(int id) { - var dataType = Services.DataTypeService.GetDataType(id); + var dataType = _dataTypeService.GetDataType(id); if (dataType == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } - return Mapper.Map(dataType); + return _umbracoMapper.Map(dataType); } /// @@ -110,12 +102,12 @@ namespace Umbraco.Web.Editors /// public DataTypeDisplay GetById(Guid id) { - var dataType = Services.DataTypeService.GetDataType(id); + var dataType = _dataTypeService.GetDataType(id); if (dataType == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } - return Mapper.Map(dataType); + return _umbracoMapper.Map(dataType); } /// @@ -129,12 +121,12 @@ namespace Umbraco.Web.Editors if (guidUdi == null) throw new HttpResponseException(HttpStatusCode.NotFound); - var dataType = Services.DataTypeService.GetDataType(guidUdi.Guid); + var dataType = _dataTypeService.GetDataType(guidUdi.Guid); if (dataType == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } - return Mapper.Map(dataType); + return _umbracoMapper.Map(dataType); } /// @@ -144,17 +136,17 @@ namespace Umbraco.Web.Editors /// [HttpDelete] [HttpPost] - public HttpResponseMessage DeleteById(int id) + public IActionResult DeleteById(int id) { - var foundType = Services.DataTypeService.GetDataType(id); + var foundType = _dataTypeService.GetDataType(id); if (foundType == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } + var currentUser = _umbracoContextAccessor.GetRequiredUmbracoContext().Security.CurrentUser; + _dataTypeService.Delete(foundType, currentUser.Id); - Services.DataTypeService.Delete(foundType, Security.CurrentUser.Id); - - return Request.CreateResponse(HttpStatusCode.OK); + return Ok(); } public DataTypeDisplay GetEmpty(int parentId) @@ -162,7 +154,7 @@ namespace Umbraco.Web.Editors // cannot create an "empty" data type, so use something by default. var editor = _propertyEditors[Constants.PropertyEditors.Aliases.Label]; var dt = new DataType(editor, parentId); - return Mapper.Map(dt); + return _umbracoMapper.Map(dt); } /// @@ -172,13 +164,13 @@ namespace Umbraco.Web.Editors /// a DataTypeDisplay public DataTypeDisplay GetCustomListView(string contentTypeAlias) { - var dt = Services.DataTypeService.GetDataType(Constants.Conventions.DataTypes.ListViewPrefix + contentTypeAlias); + var dt = _dataTypeService.GetDataType(Constants.Conventions.DataTypes.ListViewPrefix + contentTypeAlias); if (dt == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } - return Mapper.Map(dt); + return _umbracoMapper.Map(dt); } /// @@ -188,17 +180,17 @@ namespace Umbraco.Web.Editors /// public DataTypeDisplay PostCreateCustomListView(string contentTypeAlias) { - var dt = Services.DataTypeService.GetDataType(Constants.Conventions.DataTypes.ListViewPrefix + contentTypeAlias); + var dt = _dataTypeService.GetDataType(Constants.Conventions.DataTypes.ListViewPrefix + contentTypeAlias); //if it doesn't exist yet, we will create it. if (dt == null) { var editor = _propertyEditors[Constants.PropertyEditors.Aliases.ListView]; dt = new DataType(editor) { Name = Constants.Conventions.DataTypes.ListViewPrefix + contentTypeAlias }; - Services.DataTypeService.Save(dt); + _dataTypeService.Save(dt); } - return Mapper.Map(dt); + return _umbracoMapper.Map(dt); } /// @@ -218,11 +210,11 @@ namespace Umbraco.Web.Editors if (dataTypeId == -1) { //this is a new data type, so just return the field editors with default values - return Mapper.Map>(propEd); + return _umbracoMapper.Map>(propEd); } //we have a data type associated - var dataType = Services.DataTypeService.GetDataType(dataTypeId); + var dataType = _dataTypeService.GetDataType(dataTypeId); if (dataType == null) { throw new HttpResponseException(HttpStatusCode.NotFound); @@ -234,11 +226,11 @@ namespace Umbraco.Web.Editors if (dataType.EditorAlias == editorAlias) { //this is the currently assigned pre-value editor, return with values. - return Mapper.Map>(dataType); + return _umbracoMapper.Map>(dataType); } //these are new pre-values, so just return the field editors with default values - return Mapper.Map>(propEd); + return _umbracoMapper.Map>(propEd); } /// @@ -248,20 +240,23 @@ namespace Umbraco.Web.Editors /// [HttpDelete] [HttpPost] - public HttpResponseMessage DeleteContainer(int id) + public IActionResult DeleteContainer(int id) { - Services.DataTypeService.DeleteContainer(id, Security.CurrentUser.Id); - return Request.CreateResponse(HttpStatusCode.OK); + var currentUser = _umbracoContextAccessor.GetRequiredUmbracoContext().Security.CurrentUser; + _dataTypeService.DeleteContainer(id, currentUser.Id); + + return Ok(); } - public HttpResponseMessage PostCreateContainer(int parentId, string name) + public IActionResult PostCreateContainer(int parentId, string name) { - var result = Services.DataTypeService.CreateContainer(parentId, name, Security.CurrentUser.Id); + var currentUser = _umbracoContextAccessor.GetRequiredUmbracoContext().Security.CurrentUser; + var result = _dataTypeService.CreateContainer(parentId, name, currentUser.Id); return result - ? Request.CreateResponse(HttpStatusCode.OK, result.Result) //return the id - : Request.CreateNotificationValidationErrorResponse(result.Exception.Message); + ? Ok(result.Result) //return the id + : throw HttpResponseException.CreateNotificationValidationErrorResponse(result.Exception.Message); } /// @@ -269,8 +264,8 @@ namespace Umbraco.Web.Editors /// /// /// - [DataTypeValidate] - public DataTypeDisplay PostSave(DataTypeSave dataType) + [TypeFilter(typeof(DataTypeValidateAttribute))] + public IActionResult PostSave(DataTypeSave dataType) { //If we've made it here, then everything has been wired up and validated by the attribute @@ -286,21 +281,23 @@ namespace Umbraco.Web.Editors dataType.PersistedDataType.Configuration = configuration; + var currentUser = _umbracoContextAccessor.GetRequiredUmbracoContext().Security.CurrentUser; // save the data type try { - Services.DataTypeService.Save(dataType.PersistedDataType, Security.CurrentUser.Id); + + _dataTypeService.Save(dataType.PersistedDataType, currentUser.Id); } catch (DuplicateNameException ex) { ModelState.AddModelError("Name", ex.Message); - throw new HttpResponseException(Request.CreateValidationErrorResponse(ModelState)); + throw HttpResponseException.CreateValidationErrorResponse(ModelState); } // map back to display model, and return - var display = Mapper.Map(dataType.PersistedDataType); - display.AddSuccessNotification(Services.TextService.Localize("speechBubbles/dataTypeSaved"), ""); - return display; + var display = _umbracoMapper.Map(dataType.PersistedDataType); + display.AddSuccessNotification(_localizedTextService.Localize("speechBubbles/dataTypeSaved"), ""); + return Ok(display); } /// @@ -308,46 +305,45 @@ namespace Umbraco.Web.Editors /// /// /// - public HttpResponseMessage PostMove(MoveOrCopy move) + public IActionResult PostMove(MoveOrCopy move) { - var toMove = Services.DataTypeService.GetDataType(move.Id); + var toMove = _dataTypeService.GetDataType(move.Id); if (toMove == null) { - return Request.CreateResponse(HttpStatusCode.NotFound); + return NotFound(); } - var result = Services.DataTypeService.Move(toMove, move.ParentId); + var result = _dataTypeService.Move(toMove, move.ParentId); if (result.Success) { - var response = Request.CreateResponse(HttpStatusCode.OK); - response.Content = new StringContent(toMove.Path, Encoding.UTF8, "text/plain"); - return response; + return Content(toMove.Path,"text/plain", Encoding.UTF8); } switch (result.Result.Result) { case MoveOperationStatusType.FailedParentNotFound: - return Request.CreateResponse(HttpStatusCode.NotFound); + return NotFound(); case MoveOperationStatusType.FailedCancelledByEvent: //returning an object of INotificationModel will ensure that any pending // notification messages are added to the response. - return Request.CreateValidationErrorResponse(new SimpleNotificationModel()); + throw HttpResponseException.CreateValidationErrorResponse(new SimpleNotificationModel()); case MoveOperationStatusType.FailedNotAllowedByPath: var notificationModel = new SimpleNotificationModel(); - notificationModel.AddErrorNotification(Services.TextService.Localize("moveOrCopy/notAllowedByPath"), ""); - return Request.CreateValidationErrorResponse(notificationModel); + notificationModel.AddErrorNotification(_localizedTextService.Localize("moveOrCopy/notAllowedByPath"), ""); + throw HttpResponseException.CreateValidationErrorResponse(notificationModel); default: throw new ArgumentOutOfRangeException(); } } - public HttpResponseMessage PostRenameContainer(int id, string name) + public IActionResult PostRenameContainer(int id, string name) { - var result = Services.DataTypeService.RenameContainer(id, name, Security.CurrentUser.Id); + var currentUser = _umbracoContextAccessor.GetRequiredUmbracoContext().Security.CurrentUser; + var result = _dataTypeService.RenameContainer(id, name, currentUser.Id); return result - ? Request.CreateResponse(HttpStatusCode.OK, result.Result) - : Request.CreateNotificationValidationErrorResponse(result.Exception.Message); + ? Ok(result.Result) + : throw HttpResponseException.CreateNotificationValidationErrorResponse(result.Exception.Message); } /// @@ -358,7 +354,7 @@ namespace Umbraco.Web.Editors public DataTypeReferences GetReferences(int id) { var result = new DataTypeReferences(); - var usages = Services.DataTypeService.GetReferences(id); + var usages = _dataTypeService.GetReferences(id); foreach(var groupOfEntityType in usages.GroupBy(x => x.Key.EntityType)) { @@ -366,11 +362,11 @@ namespace Umbraco.Web.Editors var guidsAndPropertyAliases = groupOfEntityType.ToDictionary(i => ((GuidUdi)i.Key).Guid, i => i.Value); if (groupOfEntityType.Key == ObjectTypes.GetUdiType(UmbracoObjectTypes.DocumentType)) - result.DocumentTypes = GetContentTypeUsages(Services.ContentTypeService.GetAll(guidsAndPropertyAliases.Keys), guidsAndPropertyAliases); + result.DocumentTypes = GetContentTypeUsages(_contentTypeService.GetAll(guidsAndPropertyAliases.Keys), guidsAndPropertyAliases); else if (groupOfEntityType.Key == ObjectTypes.GetUdiType(UmbracoObjectTypes.MediaType)) - result.MediaTypes = GetContentTypeUsages(Services.MediaTypeService.GetAll(guidsAndPropertyAliases.Keys), guidsAndPropertyAliases); + result.MediaTypes = GetContentTypeUsages(_mediaTypeService.GetAll(guidsAndPropertyAliases.Keys), guidsAndPropertyAliases); else if (groupOfEntityType.Key == ObjectTypes.GetUdiType(UmbracoObjectTypes.MemberType)) - result.MemberTypes = GetContentTypeUsages(Services.MemberTypeService.GetAll(guidsAndPropertyAliases.Keys), guidsAndPropertyAliases); + result.MemberTypes = GetContentTypeUsages(_memberTypeService.GetAll(guidsAndPropertyAliases.Keys), guidsAndPropertyAliases); } return result; @@ -412,14 +408,13 @@ namespace Umbraco.Web.Editors /// /// Permission is granted to this method if the user has access to any of these sections: Content, media, settings, developer, members /// - [UmbracoApplicationAuthorize( - Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members, - Constants.Applications.Settings, Constants.Applications.Packages)] + [TypeFilter(typeof(UmbracoApplicationAuthorizeAttribute), Arguments = new object[]{new string []{Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members, + Constants.Applications.Settings, Constants.Applications.Packages}})] public IEnumerable GetAll() { - return Services.DataTypeService + return _dataTypeService .GetAll() - .Select(Mapper.Map).Where(x => x.IsSystemDataType == false); + .Select(_umbracoMapper.Map).Where(x => x.IsSystemDataType == false); } /// @@ -429,17 +424,16 @@ namespace Umbraco.Web.Editors /// /// Permission is granted to this method if the user has access to any of these sections: Content, media, settings, developer, members /// - [UmbracoTreeAuthorize( - Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members, - Constants.Applications.Settings, Constants.Applications.Packages)] + [TypeFilter(typeof(UmbracoTreeAuthorizeAttribute), Arguments = new object[]{new string[]{Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members, + Constants.Applications.Settings, Constants.Applications.Packages}})] public IDictionary> GetGroupedDataTypes() { - var dataTypes = Services.DataTypeService + var dataTypes = _dataTypeService .GetAll() - .Select(Mapper.Map) + .Select(_umbracoMapper.Map) .ToArray(); - var propertyEditors = Current.PropertyEditors.ToArray(); + var propertyEditors =_propertyEditorCollection.ToArray(); foreach (var dataType in dataTypes) { @@ -462,20 +456,20 @@ namespace Umbraco.Web.Editors /// /// Permission is granted to this method if the user has access to any of these sections: Content, media, settings, developer, members /// - [UmbracoTreeAuthorize( - Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members, - Constants.Applications.Settings, Constants.Applications.Packages)] + [TypeFilter(typeof(UmbracoTreeAuthorizeAttribute), Arguments = new object[]{new string[]{Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members, + Constants.Applications.Settings, Constants.Applications.Packages}})] + public IDictionary> GetGroupedPropertyEditors() { var datatypes = new List(); var showDeprecatedPropertyEditors = _contentSettings.ShowDeprecatedPropertyEditors; - var propertyEditors = Current.PropertyEditors + var propertyEditors =_propertyEditorCollection .Where(x=>x.IsDeprecated == false || showDeprecatedPropertyEditors); foreach (var propertyEditor in propertyEditors) { var hasPrevalues = propertyEditor.GetConfigurationEditor().Fields.Any(); - var basic = Mapper.Map(propertyEditor); + var basic = _umbracoMapper.Map(propertyEditor); basic.HasPrevalues = hasPrevalues; datatypes.Add(basic); } @@ -495,14 +489,14 @@ namespace Umbraco.Web.Editors /// /// Permission is granted to this method if the user has access to any of these sections: Content, media, settings, developer, members /// - [UmbracoTreeAuthorize( - Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members, - Constants.Applications.Settings, Constants.Applications.Packages)] + [TypeFilter(typeof(UmbracoTreeAuthorizeAttribute), Arguments = new object[]{new string[]{Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members, + Constants.Applications.Settings, Constants.Applications.Packages}})] + public IEnumerable GetAllPropertyEditors() { - return Current.PropertyEditors + return _propertyEditorCollection .OrderBy(x => x.Name) - .Select(Mapper.Map); + .Select(_umbracoMapper.Map); } #endregion } diff --git a/src/Umbraco.Web/Editors/Filters/DataTypeValidateAttribute.cs b/src/Umbraco.Web.BackOffice/Filters/DataTypeValidateAttribute.cs similarity index 60% rename from src/Umbraco.Web/Editors/Filters/DataTypeValidateAttribute.cs rename to src/Umbraco.Web.BackOffice/Filters/DataTypeValidateAttribute.cs index efdcf93fff..efd45f3a6e 100644 --- a/src/Umbraco.Web/Editors/Filters/DataTypeValidateAttribute.cs +++ b/src/Umbraco.Web.BackOffice/Filters/DataTypeValidateAttribute.cs @@ -1,16 +1,15 @@ using System; using System.Linq; using System.Net; -using System.Net.Http; -using System.Web.Http.Controllers; -using System.Web.Http.Filters; +using Microsoft.AspNetCore.Mvc.Filters; using Umbraco.Core; -using Umbraco.Web.Composing; +using Umbraco.Core.Mapping; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; +using Umbraco.Web.Common.ActionsResults; +using Umbraco.Web.Common.Exceptions; using Umbraco.Web.Models.ContentEditing; -using Umbraco.Web.WebApi; namespace Umbraco.Web.Editors { @@ -19,38 +18,36 @@ namespace Umbraco.Web.Editors /// internal sealed class DataTypeValidateAttribute : ActionFilterAttribute { - public IDataTypeService DataTypeService { get; } + private readonly IDataTypeService _dataTypeService; + private readonly PropertyEditorCollection _propertyEditorCollection; + private readonly UmbracoMapper _umbracoMapper; - public PropertyEditorCollection PropertyEditors { get; } - - public DataTypeValidateAttribute() - : this(Current.Factory.GetInstance(), Current.Factory.GetInstance()) - { - } /// /// For use in unit tests. Not possible to use as attribute ctor. /// /// - /// - public DataTypeValidateAttribute(IDataTypeService dataTypeService, PropertyEditorCollection propertyEditors) + /// + /// + public DataTypeValidateAttribute(IDataTypeService dataTypeService, PropertyEditorCollection propertyEditorCollection, UmbracoMapper umbracoMapper) { - DataTypeService = dataTypeService; - PropertyEditors = propertyEditors; + _dataTypeService = dataTypeService ?? throw new ArgumentNullException(nameof(dataTypeService)); + _propertyEditorCollection = propertyEditorCollection ?? throw new ArgumentNullException(nameof(propertyEditorCollection)); + _umbracoMapper = umbracoMapper ?? throw new ArgumentNullException(nameof(umbracoMapper)); } - public override void OnActionExecuting(HttpActionContext actionContext) + public override void OnActionExecuting(ActionExecutingContext context) { - var dataType = (DataTypeSave) actionContext.ActionArguments["dataType"]; + var dataType = (DataTypeSave) context.ActionArguments["dataType"]; dataType.Name = dataType.Name.CleanForXss('[', ']', '(', ')', ':'); dataType.Alias = dataType.Alias == null ? dataType.Name : dataType.Alias.CleanForXss('[', ']', '(', ')', ':'); // get the property editor, ensuring that it exits - if (!PropertyEditors.TryGet(dataType.EditorAlias, out var propertyEditor)) + if (!_propertyEditorCollection.TryGet(dataType.EditorAlias, out var propertyEditor)) { var message = $"Property editor \"{dataType.EditorAlias}\" was not found."; - actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.NotFound, message); + context.Result = new UmbracoProblemResult(message, HttpStatusCode.NotFound); return; } @@ -62,25 +59,25 @@ namespace Umbraco.Web.Editors switch (dataType.Action) { case ContentSaveAction.Save: - persisted = DataTypeService.GetDataType(Convert.ToInt32(dataType.Id)); + persisted = _dataTypeService.GetDataType(Convert.ToInt32(dataType.Id)); if (persisted == null) { var message = $"Data type with id {dataType.Id} was not found."; - actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.NotFound, message); + context.Result = new UmbracoProblemResult(message, HttpStatusCode.NotFound); return; } // map the model to the persisted instance - Current.Mapper.Map(dataType, persisted); + _umbracoMapper.Map(dataType, persisted); break; case ContentSaveAction.SaveNew: // create the persisted model from mapping the saved model - persisted = Current.Mapper.Map(dataType); + persisted = _umbracoMapper.Map(dataType); ((DataType) persisted).ResetIdentity(); break; default: - actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.NotFound, new ArgumentOutOfRangeException()); + context.Result = new UmbracoProblemResult($"Data type action {dataType.Action} was not found.", HttpStatusCode.NotFound); return; } @@ -98,13 +95,13 @@ namespace Umbraco.Web.Editors // run each IValueValidator (with null valueType and dataTypeConfiguration: not relevant here) foreach (var validator in editorField.Validators) foreach (var result in validator.Validate(field.Value, null, null)) - actionContext.ModelState.AddValidationError(result, "Properties", field.Key); + context.ModelState.AddModelError(field.Key,result.ErrorMessage); } - if (actionContext.ModelState.IsValid == false) + if (context.ModelState.IsValid == false) { // if it is not valid, do not continue and return the model state - actionContext.Response = actionContext.Request.CreateValidationErrorResponse(actionContext.ModelState); + throw HttpResponseException.CreateValidationErrorResponse(context.ModelState); } } } diff --git a/src/Umbraco.Web.BackOffice/Filters/UmbracoApplicationAuthorizeAttribute.cs b/src/Umbraco.Web.BackOffice/Filters/UmbracoApplicationAuthorizeAttribute.cs index 88a4c4f8ff..2a9f88c0be 100644 --- a/src/Umbraco.Web.BackOffice/Filters/UmbracoApplicationAuthorizeAttribute.cs +++ b/src/Umbraco.Web.BackOffice/Filters/UmbracoApplicationAuthorizeAttribute.cs @@ -14,6 +14,7 @@ namespace Umbraco.Web.BackOffice.Filters /// internal static bool Enable = true; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; private readonly string[] _appNames; /// @@ -22,29 +23,29 @@ namespace Umbraco.Web.BackOffice.Filters /// /// If the user has access to any of the specified apps, they will be authorized. /// - public UmbracoApplicationAuthorizeAttribute(params string[] appName) + public UmbracoApplicationAuthorizeAttribute(IUmbracoContextAccessor umbracoContextAccessor, params string[] appName) { + _umbracoContextAccessor = umbracoContextAccessor; _appNames = appName; } public void OnAuthorization(AuthorizationFilterContext context) { - var umbracoContextAccessor = context.HttpContext.RequestServices.GetRequiredService(); - if (!IsAuthorized(umbracoContextAccessor)) + if (!IsAuthorized()) { context.Result = new ForbidResult(); } } - private bool IsAuthorized(IUmbracoContextAccessor umbracoContextAccessor) + private bool IsAuthorized() { if (Enable == false) { return true; } - var umbracoContext = umbracoContextAccessor.GetRequiredUmbracoContext(); + var umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); var authorized = umbracoContext.Security.CurrentUser != null && _appNames.Any(app => umbracoContext.Security.UserHasSectionAccess( app, umbracoContext.Security.CurrentUser)); diff --git a/src/Umbraco.Web.BackOffice/Filters/UmbracoTreeAuthorizeAttribute.cs b/src/Umbraco.Web.BackOffice/Filters/UmbracoTreeAuthorizeAttribute.cs new file mode 100644 index 0000000000..9a8239b637 --- /dev/null +++ b/src/Umbraco.Web.BackOffice/Filters/UmbracoTreeAuthorizeAttribute.cs @@ -0,0 +1,71 @@ +using System.Linq; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using Umbraco.Core; +using Umbraco.Web.Services; + +namespace Umbraco.Web.BackOffice.Filters +{ + /// + /// Ensures that the current user has access to the application for which the specified tree(s) belongs + /// + /// + /// This would allow a tree to be moved between sections + /// + public sealed class UmbracoTreeAuthorizeAttribute : IAuthorizationFilter + { + /// + /// Can be used by unit tests to enable/disable this filter + /// + internal static bool Enable = true; + + private readonly ITreeService _treeService; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + private readonly string[] _treeAliases; + + /// + /// Constructor to set authorization to be based on a tree alias for which application security will be applied + /// + /// + /// + /// If the user has access to the application that the treeAlias is specified in, they will be authorized. + /// Multiple trees may be specified. + /// + /// + public UmbracoTreeAuthorizeAttribute(ITreeService treeService, IUmbracoContextAccessor umbracoContextAccessor, params string[] treeAliases) + { + _treeService = treeService; + _umbracoContextAccessor = umbracoContextAccessor; + _treeAliases = treeAliases; + } + + private bool IsAuthorized() + { + if (Enable == false) + { + return true; + } + + var apps = _treeAliases.Select(x => _treeService + .GetByAlias(x)) + .WhereNotNull() + .Select(x => x.SectionAlias) + .Distinct() + .ToArray(); + + var umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); + return umbracoContext.Security.CurrentUser != null + && apps.Any(app => umbracoContext.Security.UserHasSectionAccess( + app, umbracoContext.Security.CurrentUser)); + } + + public void OnAuthorization(AuthorizationFilterContext context) + { + if (!IsAuthorized()) + { + + context.Result = new ForbidResult(); + } + } + } +} diff --git a/src/Umbraco.Web.BackOffice/HealthCheck/HealthCheckController.cs b/src/Umbraco.Web.BackOffice/HealthCheck/HealthCheckController.cs index db2b72f989..00de2db0b6 100644 --- a/src/Umbraco.Web.BackOffice/HealthCheck/HealthCheckController.cs +++ b/src/Umbraco.Web.BackOffice/HealthCheck/HealthCheckController.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using Microsoft.AspNetCore.Mvc; +using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Web.Editors; using Umbraco.Core.Configuration.HealthChecks; @@ -12,7 +13,7 @@ namespace Umbraco.Web.HealthCheck /// /// The API controller used to display the health check info and execute any actions /// - [UmbracoApplicationAuthorize(Core.Constants.Applications.Settings)] + [TypeFilter(typeof(UmbracoApplicationAuthorizeAttribute), Arguments = new object[]{new string []{Constants.Applications.Settings}})] public class HealthCheckController : UmbracoAuthorizedJsonController { private readonly HealthCheckCollection _checks; diff --git a/src/Umbraco.Web.BackOffice/Profiling/WebProfilingController.cs b/src/Umbraco.Web.BackOffice/Profiling/WebProfilingController.cs index a053c28d42..ba55dcb51c 100644 --- a/src/Umbraco.Web.BackOffice/Profiling/WebProfilingController.cs +++ b/src/Umbraco.Web.BackOffice/Profiling/WebProfilingController.cs @@ -1,4 +1,6 @@ -using Umbraco.Core.Hosting; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Core; +using Umbraco.Core.Hosting; using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.Editors; @@ -8,7 +10,7 @@ namespace Umbraco.Web.BackOffice.Profiling /// /// The API controller used to display the state of the web profiler /// - [UmbracoApplicationAuthorizeAttribute(Core.Constants.Applications.Settings)] + [TypeFilter(typeof(UmbracoApplicationAuthorizeAttribute), Arguments = new object[]{new string []{Constants.Applications.Settings}})] public class WebProfilingController : UmbracoAuthorizedJsonController { private readonly IHostingEnvironment _hosting; diff --git a/src/Umbraco.Web.Common/Runtime/AspNetCoreComposer.cs b/src/Umbraco.Web.Common/Runtime/AspNetCoreComposer.cs index babf6cb80d..5ea29cef1d 100644 --- a/src/Umbraco.Web.Common/Runtime/AspNetCoreComposer.cs +++ b/src/Umbraco.Web.Common/Runtime/AspNetCoreComposer.cs @@ -21,6 +21,8 @@ using Umbraco.Web.Common.Controllers; using System; using Umbraco.Web.Common.Middleware; using Umbraco.Web.Common.ModelBinding; +using Umbraco.Web.Search; +using Umbraco.Web.Trees; namespace Umbraco.Web.Common.Runtime { @@ -79,6 +81,14 @@ 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(); composition.RegisterUnique(); @@ -86,6 +96,10 @@ namespace Umbraco.Web.Common.Runtime composition.RegisterUnique(); composition.RegisterUnique(); + + + + } } } diff --git a/src/Umbraco.Web.UI.NetCore/Umbraco.Web.UI.NetCore.csproj b/src/Umbraco.Web.UI.NetCore/Umbraco.Web.UI.NetCore.csproj index 483e38780e..8f7b78b128 100644 --- a/src/Umbraco.Web.UI.NetCore/Umbraco.Web.UI.NetCore.csproj +++ b/src/Umbraco.Web.UI.NetCore/Umbraco.Web.UI.NetCore.csproj @@ -60,6 +60,7 @@ Designer + diff --git a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs index f79865c881..a509ecfd29 100644 --- a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs +++ b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs @@ -208,10 +208,10 @@ namespace Umbraco.Web.Editors "entityApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( controller => controller.GetById(0, UmbracoEntityTypes.Media)) }, - { - "dataTypeApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( - controller => controller.GetById(0)) - }, + // { + // "dataTypeApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + // controller => controller.GetById(0)) + // }, //TODO Reintroduce // { // "dashboardApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( diff --git a/src/Umbraco.Web/Runtime/WebInitialComposer.cs b/src/Umbraco.Web/Runtime/WebInitialComposer.cs index a427669ed9..7f437c9378 100644 --- a/src/Umbraco.Web/Runtime/WebInitialComposer.cs +++ b/src/Umbraco.Web/Runtime/WebInitialComposer.cs @@ -70,7 +70,7 @@ namespace Umbraco.Web.Runtime //we need to eagerly scan controller types since they will need to be routed composition.WithCollectionBuilder() .Add(composition.TypeLoader.GetSurfaceControllers()); - + // add all known factories, devs can then modify this list on application // startup either by binding to events or in their own global.asax @@ -83,13 +83,7 @@ namespace Umbraco.Web.Runtime // register preview SignalR hub composition.RegisterUnique(_ => GlobalHost.ConnectionManager.GetHubContext()); - var umbracoApiControllerTypes = composition.TypeLoader.GetUmbracoApiControllers().ToList(); - // 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))); } diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index f8e73bb361..6687660dc6 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -152,11 +152,11 @@ + - @@ -339,7 +339,6 @@ - @@ -389,7 +388,6 @@ - diff --git a/src/Umbraco.Web/WebApi/Filters/UmbracoTreeAuthorizeAttribute.cs b/src/Umbraco.Web/WebApi/Filters/UmbracoTreeAuthorizeAttribute.cs index 1bea963ee1..07829648c3 100644 --- a/src/Umbraco.Web/WebApi/Filters/UmbracoTreeAuthorizeAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/UmbracoTreeAuthorizeAttribute.cs @@ -5,6 +5,7 @@ using System.Web.Http.Filters; using Umbraco.Core; using Umbraco.Web.Composing; +//MOVED to netcore namespace Umbraco.Web.WebApi.Filters { ///