Merge remote-tracking branch 'origin/netcore/netcore' into netcore/feature/get-remaining-seconds
# Conflicts: # src/Umbraco.Web/Editors/AuthenticationController.cs
This commit is contained in:
126
src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs
Normal file
126
src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs
Normal file
@@ -0,0 +1,126 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Services.Implement;
|
||||
using Umbraco.Web.PropertyEditors;
|
||||
|
||||
namespace Umbraco.Web.Compose
|
||||
{
|
||||
public class NestedContentPropertyComponent : IComponent
|
||||
{
|
||||
public void Initialize()
|
||||
{
|
||||
ContentService.Copying += ContentService_Copying;
|
||||
ContentService.Saving += ContentService_Saving;
|
||||
}
|
||||
|
||||
private void ContentService_Copying(IContentService sender, CopyEventArgs<IContent> e)
|
||||
{
|
||||
// When a content node contains nested content property
|
||||
// Check if the copied node contains a nested content
|
||||
var nestedContentProps = e.Copy.Properties.Where(x => x.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.Aliases.NestedContent);
|
||||
UpdateNestedContentProperties(nestedContentProps, false);
|
||||
}
|
||||
|
||||
private void ContentService_Saving(IContentService sender, ContentSavingEventArgs e)
|
||||
{
|
||||
// One or more content nodes could be saved in a bulk publish
|
||||
foreach (var entity in e.SavedEntities)
|
||||
{
|
||||
// When a content node contains nested content property
|
||||
// Check if the copied node contains a nested content
|
||||
var nestedContentProps = entity.Properties.Where(x => x.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.Aliases.NestedContent);
|
||||
UpdateNestedContentProperties(nestedContentProps, true);
|
||||
}
|
||||
}
|
||||
|
||||
public void Terminate()
|
||||
{
|
||||
ContentService.Copying -= ContentService_Copying;
|
||||
ContentService.Saving -= ContentService_Saving;
|
||||
}
|
||||
|
||||
private void UpdateNestedContentProperties(IEnumerable<IProperty> nestedContentProps, bool onlyMissingKeys)
|
||||
{
|
||||
// Each NC Property on a doctype
|
||||
foreach (var nestedContentProp in nestedContentProps)
|
||||
{
|
||||
// A NC Prop may have one or more values due to cultures
|
||||
var propVals = nestedContentProp.Values;
|
||||
foreach (var cultureVal in propVals)
|
||||
{
|
||||
// Remove keys from published value & any nested NC's
|
||||
var updatedPublishedVal = CreateNestedContentKeys(cultureVal.PublishedValue?.ToString(), onlyMissingKeys);
|
||||
cultureVal.PublishedValue = updatedPublishedVal;
|
||||
|
||||
// Remove keys from edited/draft value & any nested NC's
|
||||
var updatedEditedVal = CreateNestedContentKeys(cultureVal.EditedValue?.ToString(), onlyMissingKeys);
|
||||
cultureVal.EditedValue = updatedEditedVal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// internal for tests
|
||||
internal string CreateNestedContentKeys(string rawJson, bool onlyMissingKeys, Func<Guid> createGuid = null)
|
||||
{
|
||||
// used so we can test nicely
|
||||
if (createGuid == null)
|
||||
createGuid = () => Guid.NewGuid();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(rawJson) || !rawJson.DetectIsJson())
|
||||
return rawJson;
|
||||
|
||||
// Parse JSON
|
||||
var complexEditorValue = JToken.Parse(rawJson);
|
||||
|
||||
UpdateNestedContentKeysRecursively(complexEditorValue, onlyMissingKeys, createGuid);
|
||||
|
||||
return complexEditorValue.ToString();
|
||||
}
|
||||
|
||||
private void UpdateNestedContentKeysRecursively(JToken json, bool onlyMissingKeys, Func<Guid> createGuid)
|
||||
{
|
||||
// check if this is NC
|
||||
var isNestedContent = json.SelectTokens($"$..['{NestedContentPropertyEditor.ContentTypeAliasPropertyKey}']", false).Any();
|
||||
|
||||
// select all values (flatten)
|
||||
var allProperties = json.SelectTokens("$..*").OfType<JValue>().Select(x => x.Parent as JProperty).WhereNotNull().ToList();
|
||||
foreach (var prop in allProperties)
|
||||
{
|
||||
if (prop.Name == NestedContentPropertyEditor.ContentTypeAliasPropertyKey)
|
||||
{
|
||||
// get it's sibling 'key' property
|
||||
var ncKeyVal = prop.Parent["key"] as JValue;
|
||||
// TODO: This bool seems odd, if the key is null, shouldn't we fill it in regardless of onlyMissingKeys?
|
||||
if ((onlyMissingKeys && ncKeyVal == null) || (!onlyMissingKeys && ncKeyVal != null))
|
||||
{
|
||||
// create or replace
|
||||
prop.Parent["key"] = createGuid().ToString();
|
||||
}
|
||||
}
|
||||
else if (!isNestedContent || prop.Name != "key")
|
||||
{
|
||||
// this is an arbitrary property that could contain a nested complex editor
|
||||
var propVal = prop.Value?.ToString();
|
||||
// check if this might contain a nested NC
|
||||
if (!propVal.IsNullOrWhiteSpace() && propVal.DetectIsJson() && propVal.InvariantContains(NestedContentPropertyEditor.ContentTypeAliasPropertyKey))
|
||||
{
|
||||
// recurse
|
||||
var parsed = JToken.Parse(propVal);
|
||||
UpdateNestedContentKeysRecursively(parsed, onlyMissingKeys, createGuid);
|
||||
// set the value to the updated one
|
||||
prop.Value = parsed.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
9
src/Umbraco.Web/Compose/NestedContentPropertyComposer.cs
Normal file
9
src/Umbraco.Web/Compose/NestedContentPropertyComposer.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Composing;
|
||||
|
||||
namespace Umbraco.Web.Compose
|
||||
{
|
||||
[RuntimeLevel(MinLevel = RuntimeLevel.Run)]
|
||||
public class NestedContentPropertyComposer : ComponentComposer<NestedContentPropertyComponent>, ICoreComposer
|
||||
{ }
|
||||
}
|
||||
@@ -41,16 +41,6 @@ namespace Umbraco.Web
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the back office tree collection builder
|
||||
/// </summary>
|
||||
/// <param name="composition"></param>
|
||||
/// <returns></returns>
|
||||
public static TreeCollectionBuilder Trees(this Composition composition)
|
||||
=> composition.WithCollectionBuilder<TreeCollectionBuilder>();
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region Uniques
|
||||
|
||||
@@ -88,7 +88,7 @@ namespace Umbraco.Web.Editors
|
||||
[WebApi.UmbracoAuthorize(requireApproval: false)]
|
||||
public IDictionary<string, object> GetPasswordConfig(int userId)
|
||||
{
|
||||
return _passwordConfiguration.GetConfiguration(userId != UmbracoContext.Security.CurrentUser.Id);
|
||||
return _passwordConfiguration.GetConfiguration(userId != Security.CurrentUser.Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -169,7 +169,7 @@ namespace Umbraco.Web.Editors
|
||||
[SetAngularAntiForgeryTokens]
|
||||
public UserDetail GetCurrentInvitedUser()
|
||||
{
|
||||
var user = UmbracoContext.Security.CurrentUser;
|
||||
var user = Security.CurrentUser;
|
||||
|
||||
if (user.IsApproved)
|
||||
{
|
||||
@@ -193,7 +193,7 @@ namespace Umbraco.Web.Editors
|
||||
[ValidateAngularAntiForgeryToken]
|
||||
public async Task<Dictionary<string, string>> GetCurrentUserLinkedLogins()
|
||||
{
|
||||
var identityUser = await UserManager.FindByIdAsync(UmbracoContext.Security.GetUserId().ResultOr(0).ToString());
|
||||
var identityUser = await UserManager.FindByIdAsync(Security.GetUserId().ResultOr(0).ToString());
|
||||
return identityUser.Logins.ToDictionary(x => x.LoginProvider, x => x.ProviderKey);
|
||||
}
|
||||
|
||||
|
||||
@@ -172,14 +172,7 @@ namespace Umbraco.Web.Editors
|
||||
// "imagesApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<ImagesController>(
|
||||
// controller => controller.GetBigThumbnail(""))
|
||||
// },
|
||||
{
|
||||
"sectionApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<SectionController>(
|
||||
controller => controller.GetSections())
|
||||
},
|
||||
{
|
||||
"treeApplicationApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<ApplicationTreeController>(
|
||||
controller => controller.GetApplicationTrees(null, null, null, TreeUse.None))
|
||||
},
|
||||
|
||||
{
|
||||
"contentTypeApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<ContentTypeController>(
|
||||
controller => controller.GetAllowedChildren(0))
|
||||
@@ -196,7 +189,7 @@ namespace Umbraco.Web.Editors
|
||||
"macroApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<MacrosController>(
|
||||
controller => controller.Create(null))
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
"currentUserApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<CurrentUserController>(
|
||||
controller => controller.PostChangePassword(null))
|
||||
@@ -250,10 +243,10 @@ namespace Umbraco.Web.Editors
|
||||
"memberGroupApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<MemberGroupController>(
|
||||
controller => controller.GetAllGroups())
|
||||
},
|
||||
{
|
||||
"updateCheckApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<UpdateCheckController>(
|
||||
controller => controller.GetCheck())
|
||||
},
|
||||
// {
|
||||
// "updateCheckApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<UpdateCheckController>(
|
||||
// controller => controller.GetCheck())
|
||||
// },
|
||||
// {
|
||||
// "templateApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<TemplateController>(
|
||||
// controller => controller.GetById(0))
|
||||
@@ -296,10 +289,10 @@ namespace Umbraco.Web.Editors
|
||||
// "publishedStatusBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<PublishedStatusController>(
|
||||
// controller => controller.GetPublishedStatusUrl())
|
||||
// },
|
||||
{
|
||||
"dictionaryApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<DictionaryController>(
|
||||
controller => controller.DeleteById(int.MaxValue))
|
||||
},
|
||||
// {
|
||||
// "dictionaryApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<DictionaryController>(
|
||||
// controller => controller.DeleteById(int.MaxValue))
|
||||
// },
|
||||
// {
|
||||
// "publishedSnapshotCacheStatusBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<PublishedSnapshotCacheStatusController>(
|
||||
// controller => controller.GetStatus())
|
||||
@@ -330,10 +323,10 @@ namespace Umbraco.Web.Editors
|
||||
// "webProfilingBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<WebProfilingController>(
|
||||
// controller => controller.GetStatus())
|
||||
// },
|
||||
{
|
||||
"tinyMceApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<TinyMceController>(
|
||||
controller => controller.UploadImage())
|
||||
},
|
||||
// {
|
||||
// "tinyMceApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<TinyMceController>(
|
||||
// controller => controller.UploadImage())
|
||||
// },
|
||||
//TODO Reintroduce
|
||||
// {
|
||||
// "imageUrlGeneratorApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<ImageUrlGeneratorController>(
|
||||
|
||||
@@ -1,264 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Web.Http;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Strings;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Umbraco.Web.Mvc;
|
||||
using Umbraco.Web.Routing;
|
||||
using Umbraco.Web.WebApi;
|
||||
using Umbraco.Web.WebApi.Filters;
|
||||
using Constants = Umbraco.Core.Constants;
|
||||
|
||||
namespace Umbraco.Web.Editors
|
||||
{
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// The API controller used for editing dictionary items
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The security for this controller is defined to allow full CRUD access to dictionary if the user has access to either:
|
||||
/// Dictionary
|
||||
/// </remarks>
|
||||
[PluginController("UmbracoApi")]
|
||||
[UmbracoTreeAuthorize(Constants.Trees.Dictionary)]
|
||||
[EnableOverrideAuthorization]
|
||||
public class DictionaryController : BackOfficeNotificationsController
|
||||
{
|
||||
public DictionaryController(
|
||||
IGlobalSettings globalSettings,
|
||||
IUmbracoContextAccessor umbracoContextAccessor,
|
||||
ISqlContext sqlContext,
|
||||
ServiceContext services,
|
||||
AppCaches appCaches,
|
||||
IProfilingLogger logger,
|
||||
IRuntimeState runtimeState,
|
||||
IShortStringHelper shortStringHelper,
|
||||
UmbracoMapper umbracoMapper,
|
||||
IPublishedUrlProvider publishedUrlProvider)
|
||||
: base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, shortStringHelper, umbracoMapper, publishedUrlProvider)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a data type with a given ID
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns><see cref="HttpResponseMessage"/></returns>
|
||||
[HttpDelete]
|
||||
[HttpPost]
|
||||
public HttpResponseMessage DeleteById(int id)
|
||||
{
|
||||
var foundDictionary = Services.LocalizationService.GetDictionaryItemById(id);
|
||||
|
||||
if (foundDictionary == null)
|
||||
throw new HttpResponseException(HttpStatusCode.NotFound);
|
||||
|
||||
var foundDictionaryDescendants = Services.LocalizationService.GetDictionaryItemDescendants(foundDictionary.Key);
|
||||
|
||||
foreach (var dictionaryItem in foundDictionaryDescendants)
|
||||
{
|
||||
Services.LocalizationService.Delete(dictionaryItem, Security.CurrentUser.Id);
|
||||
}
|
||||
|
||||
Services.LocalizationService.Delete(foundDictionary, Security.CurrentUser.Id);
|
||||
|
||||
return Request.CreateResponse(HttpStatusCode.OK);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new dictionary item
|
||||
/// </summary>
|
||||
/// <param name="parentId">
|
||||
/// The parent id.
|
||||
/// </param>
|
||||
/// <param name="key">
|
||||
/// The key.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// The <see cref="HttpResponseMessage"/>.
|
||||
/// </returns>
|
||||
[HttpPost]
|
||||
public HttpResponseMessage Create(int parentId, string key)
|
||||
{
|
||||
if (string.IsNullOrEmpty(key))
|
||||
return Request
|
||||
.CreateNotificationValidationErrorResponse("Key can not be empty."); // TODO: translate
|
||||
|
||||
if (Services.LocalizationService.DictionaryItemExists(key))
|
||||
{
|
||||
var message = Services.TextService.Localize(
|
||||
"dictionaryItem/changeKeyError",
|
||||
Security.CurrentUser.GetUserCulture(Services.TextService, GlobalSettings),
|
||||
new Dictionary<string, string> { { "0", key } });
|
||||
return Request.CreateNotificationValidationErrorResponse(message);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Guid? parentGuid = null;
|
||||
|
||||
if (parentId > 0)
|
||||
parentGuid = Services.LocalizationService.GetDictionaryItemById(parentId).Key;
|
||||
|
||||
var item = Services.LocalizationService.CreateDictionaryItemWithIdentity(
|
||||
key,
|
||||
parentGuid,
|
||||
string.Empty);
|
||||
|
||||
|
||||
return Request.CreateResponse(HttpStatusCode.OK, item.Id);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(GetType(), ex, "Error creating dictionary with {Name} under {ParentId}", key, parentId);
|
||||
return Request.CreateNotificationValidationErrorResponse("Error creating dictionary item");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a dictionary item by id
|
||||
/// </summary>
|
||||
/// <param name="id">
|
||||
/// The id.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// The <see cref="DictionaryDisplay"/>.
|
||||
/// </returns>
|
||||
/// <exception cref="HttpResponseException">
|
||||
/// Returns a not found response when dictionary item does not exist
|
||||
/// </exception>
|
||||
public DictionaryDisplay GetById(int id)
|
||||
{
|
||||
var dictionary = Services.LocalizationService.GetDictionaryItemById(id);
|
||||
|
||||
if (dictionary == null)
|
||||
throw new HttpResponseException(HttpStatusCode.NotFound);
|
||||
|
||||
return Mapper.Map<IDictionaryItem, DictionaryDisplay>(dictionary);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves a dictionary item
|
||||
/// </summary>
|
||||
/// <param name="dictionary">
|
||||
/// The dictionary.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// The <see cref="DictionaryDisplay"/>.
|
||||
/// </returns>
|
||||
public DictionaryDisplay PostSave(DictionarySave dictionary)
|
||||
{
|
||||
var dictionaryItem =
|
||||
Services.LocalizationService.GetDictionaryItemById(int.Parse(dictionary.Id.ToString()));
|
||||
|
||||
if (dictionaryItem == null)
|
||||
throw new HttpResponseException(Request.CreateNotificationValidationErrorResponse("Dictionary item does not exist"));
|
||||
|
||||
var userCulture = Security.CurrentUser.GetUserCulture(Services.TextService, GlobalSettings);
|
||||
|
||||
if (dictionary.NameIsDirty)
|
||||
{
|
||||
// if the name (key) has changed, we need to check if the new key does not exist
|
||||
var dictionaryByKey = Services.LocalizationService.GetDictionaryItemByKey(dictionary.Name);
|
||||
|
||||
if (dictionaryByKey != null && dictionaryItem.Id != dictionaryByKey.Id)
|
||||
{
|
||||
|
||||
var message = Services.TextService.Localize(
|
||||
"dictionaryItem/changeKeyError",
|
||||
userCulture,
|
||||
new Dictionary<string, string> { { "0", dictionary.Name } });
|
||||
ModelState.AddModelError("Name", message);
|
||||
throw new HttpResponseException(Request.CreateValidationErrorResponse(ModelState));
|
||||
}
|
||||
|
||||
dictionaryItem.ItemKey = dictionary.Name;
|
||||
}
|
||||
|
||||
foreach (var translation in dictionary.Translations)
|
||||
{
|
||||
Services.LocalizationService.AddOrUpdateDictionaryValue(dictionaryItem,
|
||||
Services.LocalizationService.GetLanguageById(translation.LanguageId), translation.Translation);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Services.LocalizationService.Save(dictionaryItem);
|
||||
|
||||
var model = Mapper.Map<IDictionaryItem, DictionaryDisplay>(dictionaryItem);
|
||||
|
||||
model.Notifications.Add(new BackOfficeNotification(
|
||||
Services.TextService.Localize("speechBubbles/dictionaryItemSaved", userCulture), string.Empty,
|
||||
NotificationStyle.Success));
|
||||
|
||||
return model;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(GetType(), ex, "Error saving dictionary with {Name} under {ParentId}", dictionary.Name, dictionary.ParentId);
|
||||
throw new HttpResponseException(Request.CreateNotificationValidationErrorResponse("Something went wrong saving dictionary"));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a list with all dictionary items
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The <see cref="IEnumerable{T}"/>.
|
||||
/// </returns>
|
||||
public IEnumerable<DictionaryOverviewDisplay> GetList()
|
||||
{
|
||||
var list = new List<DictionaryOverviewDisplay>();
|
||||
|
||||
const int level = 0;
|
||||
|
||||
foreach (var dictionaryItem in Services.LocalizationService.GetRootDictionaryItems().OrderBy(ItemSort()))
|
||||
{
|
||||
var item = Mapper.Map<IDictionaryItem, DictionaryOverviewDisplay>(dictionaryItem);
|
||||
item.Level = 0;
|
||||
list.Add(item);
|
||||
|
||||
GetChildItemsForList(dictionaryItem, level + 1, list);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get child items for list.
|
||||
/// </summary>
|
||||
/// <param name="dictionaryItem">
|
||||
/// The dictionary item.
|
||||
/// </param>
|
||||
/// <param name="level">
|
||||
/// The level.
|
||||
/// </param>
|
||||
/// <param name="list">
|
||||
/// The list.
|
||||
/// </param>
|
||||
private void GetChildItemsForList(IDictionaryItem dictionaryItem, int level, ICollection<DictionaryOverviewDisplay> list)
|
||||
{
|
||||
foreach (var childItem in Services.LocalizationService.GetDictionaryItemChildren(dictionaryItem.Key).OrderBy(ItemSort()))
|
||||
{
|
||||
var item = Mapper.Map<IDictionaryItem, DictionaryOverviewDisplay>(childItem);
|
||||
item.Level = level;
|
||||
list.Add(item);
|
||||
|
||||
GetChildItemsForList(childItem, level + 1, list);
|
||||
}
|
||||
}
|
||||
|
||||
private static Func<IDictionaryItem, string> ItemSort() => item => item.ItemKey;
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ using Umbraco.Core.Models;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Umbraco.Web.Security;
|
||||
|
||||
namespace Umbraco.Web.Editors.Filters
|
||||
{
|
||||
@@ -18,13 +19,13 @@ namespace Umbraco.Web.Editors.Filters
|
||||
/// </summary>
|
||||
internal abstract class ContentModelValidator
|
||||
{
|
||||
protected IUmbracoContextAccessor UmbracoContextAccessor { get; }
|
||||
protected IWebSecurity WebSecurity { get; }
|
||||
protected ILogger Logger { get; }
|
||||
|
||||
protected ContentModelValidator(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor)
|
||||
protected ContentModelValidator(ILogger logger, IWebSecurity webSecurity)
|
||||
{
|
||||
Logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
UmbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor));
|
||||
WebSecurity = webSecurity ?? throw new ArgumentNullException(nameof(webSecurity));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +46,7 @@ namespace Umbraco.Web.Editors.Filters
|
||||
{
|
||||
private readonly ILocalizedTextService _textService;
|
||||
|
||||
protected ContentModelValidator(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, ILocalizedTextService textService) : base(logger, umbracoContextAccessor)
|
||||
protected ContentModelValidator(ILogger logger, IWebSecurity webSecurity, ILocalizedTextService textService) : base(logger, webSecurity)
|
||||
{
|
||||
_textService = textService ?? throw new ArgumentNullException(nameof(textService));
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Umbraco.Web.Security;
|
||||
|
||||
namespace Umbraco.Web.Editors.Filters
|
||||
{
|
||||
@@ -10,7 +11,7 @@ namespace Umbraco.Web.Editors.Filters
|
||||
/// </summary>
|
||||
internal class ContentSaveModelValidator : ContentModelValidator<IContent, ContentItemSave, ContentVariantSave>
|
||||
{
|
||||
public ContentSaveModelValidator(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, ILocalizedTextService textService) : base(logger, umbracoContextAccessor, textService)
|
||||
public ContentSaveModelValidator(ILogger logger, IWebSecurity webSecurity, ILocalizedTextService textService) : base(logger, webSecurity, textService)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,19 +25,19 @@ namespace Umbraco.Web.Editors.Filters
|
||||
internal sealed class ContentSaveValidationAttribute : ActionFilterAttribute
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
|
||||
private readonly IWebSecurity _webSecurity;
|
||||
private readonly ILocalizedTextService _textService;
|
||||
private readonly IContentService _contentService;
|
||||
private readonly IUserService _userService;
|
||||
private readonly IEntityService _entityService;
|
||||
|
||||
public ContentSaveValidationAttribute(): this(Current.Logger, Current.UmbracoContextAccessor, Current.Services.TextService, Current.Services.ContentService, Current.Services.UserService, Current.Services.EntityService)
|
||||
public ContentSaveValidationAttribute(): this(Current.Logger, Current.UmbracoContextAccessor.UmbracoContext.Security, Current.Services.TextService, Current.Services.ContentService, Current.Services.UserService, Current.Services.EntityService)
|
||||
{ }
|
||||
|
||||
public ContentSaveValidationAttribute(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, ILocalizedTextService textService, IContentService contentService, IUserService userService, IEntityService entityService)
|
||||
public ContentSaveValidationAttribute(ILogger logger, IWebSecurity webSecurity, ILocalizedTextService textService, IContentService contentService, IUserService userService, IEntityService entityService)
|
||||
{
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor));
|
||||
_webSecurity = webSecurity ?? throw new ArgumentNullException(nameof(webSecurity));
|
||||
_textService = textService ?? throw new ArgumentNullException(nameof(textService));
|
||||
_contentService = contentService ?? throw new ArgumentNullException(nameof(contentService));
|
||||
_userService = userService ?? throw new ArgumentNullException(nameof(userService));
|
||||
@@ -47,11 +47,11 @@ namespace Umbraco.Web.Editors.Filters
|
||||
public override void OnActionExecuting(HttpActionContext actionContext)
|
||||
{
|
||||
var model = (ContentItemSave)actionContext.ActionArguments["contentItem"];
|
||||
var contentItemValidator = new ContentSaveModelValidator(_logger, _umbracoContextAccessor, _textService);
|
||||
var contentItemValidator = new ContentSaveModelValidator(_logger, _webSecurity, _textService);
|
||||
|
||||
if (!ValidateAtLeastOneVariantIsBeingSaved(model, actionContext)) return;
|
||||
if (!contentItemValidator.ValidateExistingContent(model, actionContext)) return;
|
||||
if (!ValidateUserAccess(model, actionContext, _umbracoContextAccessor.UmbracoContext.Security)) return;
|
||||
if (!ValidateUserAccess(model, actionContext, _webSecurity)) return;
|
||||
|
||||
//validate for each variant that is being updated
|
||||
foreach (var variant in model.Variants.Where(x => x.Save))
|
||||
|
||||
@@ -9,6 +9,7 @@ using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Composing;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Umbraco.Web.Security;
|
||||
using Umbraco.Web.WebApi;
|
||||
|
||||
namespace Umbraco.Web.Editors.Filters
|
||||
@@ -19,19 +20,19 @@ namespace Umbraco.Web.Editors.Filters
|
||||
internal class MediaItemSaveValidationAttribute : ActionFilterAttribute
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
|
||||
private readonly IWebSecurity _webSecurity;
|
||||
private readonly ILocalizedTextService _textService;
|
||||
private readonly IMediaService _mediaService;
|
||||
private readonly IEntityService _entityService;
|
||||
|
||||
public MediaItemSaveValidationAttribute() : this(Current.Logger, Current.UmbracoContextAccessor, Current.Services.TextService, Current.Services.MediaService, Current.Services.EntityService)
|
||||
public MediaItemSaveValidationAttribute() : this(Current.Logger, Current.UmbracoContextAccessor.UmbracoContext.Security, Current.Services.TextService, Current.Services.MediaService, Current.Services.EntityService)
|
||||
{
|
||||
}
|
||||
|
||||
public MediaItemSaveValidationAttribute(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, ILocalizedTextService textService, IMediaService mediaService, IEntityService entityService)
|
||||
public MediaItemSaveValidationAttribute(ILogger logger, IWebSecurity webSecurity, ILocalizedTextService textService, IMediaService mediaService, IEntityService entityService)
|
||||
{
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor));
|
||||
_webSecurity = webSecurity ?? throw new ArgumentNullException(nameof(webSecurity));
|
||||
_textService = textService ?? throw new ArgumentNullException(nameof(textService));
|
||||
_mediaService = mediaService ?? throw new ArgumentNullException(nameof(mediaService));
|
||||
_entityService = entityService ?? throw new ArgumentNullException(nameof(entityService));
|
||||
@@ -40,7 +41,7 @@ namespace Umbraco.Web.Editors.Filters
|
||||
public override void OnActionExecuting(HttpActionContext actionContext)
|
||||
{
|
||||
var model = (MediaItemSave)actionContext.ActionArguments["contentItem"];
|
||||
var contentItemValidator = new MediaSaveModelValidator(_logger, _umbracoContextAccessor, _textService);
|
||||
var contentItemValidator = new MediaSaveModelValidator(_logger, _webSecurity, _textService);
|
||||
|
||||
if (ValidateUserAccess(model, actionContext))
|
||||
{
|
||||
@@ -90,7 +91,7 @@ namespace Umbraco.Web.Editors.Filters
|
||||
|
||||
if (MediaController.CheckPermissions(
|
||||
actionContext.Request.Properties,
|
||||
_umbracoContextAccessor.UmbracoContext.Security.CurrentUser,
|
||||
_webSecurity.CurrentUser,
|
||||
_mediaService, _entityService,
|
||||
contentIdToCheck, contentToCheck) == false)
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Umbraco.Web.Security;
|
||||
|
||||
namespace Umbraco.Web.Editors.Filters
|
||||
{
|
||||
@@ -10,7 +11,7 @@ namespace Umbraco.Web.Editors.Filters
|
||||
/// </summary>
|
||||
internal class MediaSaveModelValidator : ContentModelValidator<IMedia, MediaItemSave, IContentProperties<ContentPropertyBasic>>
|
||||
{
|
||||
public MediaSaveModelValidator(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, ILocalizedTextService textService) : base(logger, umbracoContextAccessor, textService)
|
||||
public MediaSaveModelValidator(ILogger logger, IWebSecurity webSecurity, ILocalizedTextService textService) : base(logger, webSecurity, textService)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Strings;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Umbraco.Web.Security;
|
||||
|
||||
namespace Umbraco.Web.Editors.Filters
|
||||
{
|
||||
@@ -25,12 +26,12 @@ namespace Umbraco.Web.Editors.Filters
|
||||
|
||||
public MemberSaveModelValidator(
|
||||
ILogger logger,
|
||||
IUmbracoContextAccessor umbracoContextAccessor,
|
||||
IWebSecurity webSecurity,
|
||||
ILocalizedTextService textService,
|
||||
IMemberTypeService memberTypeService,
|
||||
IMemberService memberService,
|
||||
IShortStringHelper shortStringHelper)
|
||||
: base(logger, umbracoContextAccessor, textService)
|
||||
: base(logger, webSecurity, textService)
|
||||
{
|
||||
_memberTypeService = memberTypeService ?? throw new ArgumentNullException(nameof(memberTypeService));
|
||||
_memberService = memberService ?? throw new ArgumentNullException(nameof(memberService));
|
||||
@@ -101,7 +102,7 @@ namespace Umbraco.Web.Editors.Filters
|
||||
|
||||
//if the user doesn't have access to sensitive values, then we need to validate the incoming properties to check
|
||||
//if a sensitive value is being submitted.
|
||||
if (UmbracoContextAccessor.UmbracoContext.Security.CurrentUser.HasAccessToSensitiveData() == false)
|
||||
if (WebSecurity.CurrentUser.HasAccessToSensitiveData() == false)
|
||||
{
|
||||
var contentType = _memberTypeService.Get(model.PersistedContent.ContentTypeId);
|
||||
var sensitiveProperties = contentType
|
||||
|
||||
@@ -6,6 +6,7 @@ using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Strings;
|
||||
using Umbraco.Web.Composing;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Umbraco.Web.Security;
|
||||
|
||||
namespace Umbraco.Web.Editors.Filters
|
||||
{
|
||||
@@ -15,20 +16,20 @@ namespace Umbraco.Web.Editors.Filters
|
||||
internal class MemberSaveValidationAttribute : ActionFilterAttribute
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
|
||||
private readonly IWebSecurity _webSecurity;
|
||||
private readonly ILocalizedTextService _textService;
|
||||
private readonly IMemberTypeService _memberTypeService;
|
||||
private readonly IMemberService _memberService;
|
||||
private readonly IShortStringHelper _shortStringHelper;
|
||||
|
||||
public MemberSaveValidationAttribute()
|
||||
: this(Current.Logger, Current.UmbracoContextAccessor, Current.Services.TextService, Current.Services.MemberTypeService, Current.Services.MemberService, Current.ShortStringHelper)
|
||||
: this(Current.Logger, Current.UmbracoContextAccessor.UmbracoContext.Security, Current.Services.TextService, Current.Services.MemberTypeService, Current.Services.MemberService, Current.ShortStringHelper)
|
||||
{ }
|
||||
|
||||
public MemberSaveValidationAttribute(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, ILocalizedTextService textService, IMemberTypeService memberTypeService, IMemberService memberService, IShortStringHelper shortStringHelper)
|
||||
public MemberSaveValidationAttribute(ILogger logger, IWebSecurity webSecurity, ILocalizedTextService textService, IMemberTypeService memberTypeService, IMemberService memberService, IShortStringHelper shortStringHelper)
|
||||
{
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor));
|
||||
_webSecurity = webSecurity ?? throw new ArgumentNullException(nameof(webSecurity));
|
||||
_textService = textService ?? throw new ArgumentNullException(nameof(textService));
|
||||
_memberTypeService = memberTypeService ?? throw new ArgumentNullException(nameof(memberTypeService));
|
||||
_memberService = memberService ?? throw new ArgumentNullException(nameof(memberService));
|
||||
@@ -38,7 +39,7 @@ namespace Umbraco.Web.Editors.Filters
|
||||
public override void OnActionExecuting(HttpActionContext actionContext)
|
||||
{
|
||||
var model = (MemberSave)actionContext.ActionArguments["contentItem"];
|
||||
var contentItemValidator = new MemberSaveModelValidator(_logger, _umbracoContextAccessor,_textService, _memberTypeService, _memberService, _shortStringHelper);
|
||||
var contentItemValidator = new MemberSaveModelValidator(_logger, _webSecurity, _textService, _memberTypeService, _memberService, _shortStringHelper);
|
||||
//now do each validation step
|
||||
if (contentItemValidator.ValidateExistingContent(model, actionContext))
|
||||
if (contentItemValidator.ValidateProperties(model, model, actionContext))
|
||||
|
||||
@@ -136,7 +136,7 @@ namespace Umbraco.Web.Editors
|
||||
var ctId = Convert.ToInt32(contentTypeSave.Id);
|
||||
var ct = ctId > 0 ? Services.MemberTypeService.Get(ctId) : null;
|
||||
|
||||
if (UmbracoContext.Security.CurrentUser.HasAccessToSensitiveData() == false)
|
||||
if (Security.CurrentUser.HasAccessToSensitiveData() == false)
|
||||
{
|
||||
//We need to validate if any properties on the contentTypeSave have had their IsSensitiveValue changed,
|
||||
//and if so, we need to check if the current user has access to sensitive values. If not, we have to return an error
|
||||
|
||||
@@ -1,106 +0,0 @@
|
||||
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 Umbraco.Core.Mapping;
|
||||
using Umbraco.Web.Routing;
|
||||
|
||||
namespace Umbraco.Web.Editors
|
||||
{
|
||||
/// <summary>
|
||||
/// The API controller used for using the list of sections
|
||||
/// </summary>
|
||||
[PluginController("UmbracoApi")]
|
||||
public class SectionController : UmbracoAuthorizedJsonController
|
||||
{
|
||||
private readonly IDashboardService _dashboardService;
|
||||
private readonly ISectionService _sectionService;
|
||||
private readonly ITreeService _treeService;
|
||||
|
||||
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)
|
||||
{
|
||||
_dashboardService = dashboardService;
|
||||
_sectionService = sectionService;
|
||||
_treeService = treeService;
|
||||
}
|
||||
|
||||
public IEnumerable<Section> GetSections()
|
||||
{
|
||||
var sections = _sectionService.GetAllowedSections(Security.GetUserId().ResultOr(0));
|
||||
|
||||
var sectionModels = sections.Select(Mapper.Map<Section>).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 dashboards = _dashboardService.GetDashboards(Security.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();
|
||||
if (hasDashboards) continue;
|
||||
|
||||
// get the first tree in the section and get its root node route path
|
||||
var sectionRoot = appTreeController.GetApplicationTrees(section.Alias, null, null).Result;
|
||||
section.RoutePath = GetRoutePathForFirstTree(sectionRoot);
|
||||
}
|
||||
|
||||
return sectionModels;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the first non root/group node's route path
|
||||
/// </summary>
|
||||
/// <param name="rootNode"></param>
|
||||
/// <returns></returns>
|
||||
private string GetRoutePathForFirstTree(TreeRootNode rootNode)
|
||||
{
|
||||
if (!rootNode.IsContainer || !rootNode.ContainsTrees)
|
||||
return rootNode.RoutePath;
|
||||
|
||||
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 string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all the sections that the user has access to
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<Section> GetAllSections()
|
||||
{
|
||||
var sections = _sectionService.GetSections();
|
||||
var mapped = sections.Select(Mapper.Map<Section>);
|
||||
if (Security.CurrentUser.IsAdmin())
|
||||
return mapped;
|
||||
|
||||
return mapped.Where(x => Security.CurrentUser.AllowedSections.Contains(x.Alias)).ToArray();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web.Http;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Configuration.UmbracoSettings;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Strings;
|
||||
using Umbraco.Web.Mvc;
|
||||
using Umbraco.Web.Routing;
|
||||
using Umbraco.Web.WebApi;
|
||||
using Umbraco.Web.WebApi.Filters;
|
||||
using Constants = Umbraco.Core.Constants;
|
||||
|
||||
namespace Umbraco.Web.Editors
|
||||
{
|
||||
[PluginController("UmbracoApi")]
|
||||
[UmbracoApplicationAuthorize(
|
||||
Constants.Applications.Content,
|
||||
Constants.Applications.Media,
|
||||
Constants.Applications.Members)]
|
||||
public class TinyMceController : UmbracoAuthorizedApiController
|
||||
{
|
||||
private readonly IIOHelper _ioHelper;
|
||||
private readonly IShortStringHelper _shortStringHelper;
|
||||
private readonly IContentSettings _contentSettings;
|
||||
|
||||
public TinyMceController(
|
||||
IGlobalSettings globalSettings,
|
||||
IUmbracoContextAccessor umbracoContextAccessor,
|
||||
ISqlContext sqlContext,
|
||||
ServiceContext services,
|
||||
AppCaches appCaches,
|
||||
IProfilingLogger logger,
|
||||
IRuntimeState runtimeState,
|
||||
UmbracoMapper umbracoMapper,
|
||||
IShortStringHelper shortStringHelper,
|
||||
IContentSettings contentSettings,
|
||||
IIOHelper ioHelper,
|
||||
IPublishedUrlProvider publishedUrlProvider)
|
||||
: base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoMapper, publishedUrlProvider)
|
||||
{
|
||||
_shortStringHelper = shortStringHelper ?? throw new ArgumentNullException(nameof(shortStringHelper));
|
||||
_contentSettings = contentSettings ?? throw new ArgumentNullException(nameof(contentSettings));
|
||||
_ioHelper = ioHelper ?? throw new ArgumentNullException(nameof(ioHelper));
|
||||
}
|
||||
|
||||
|
||||
[HttpPost]
|
||||
public async Task<HttpResponseMessage> UploadImage()
|
||||
{
|
||||
if (Request.Content.IsMimeMultipartContent() == false)
|
||||
{
|
||||
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
|
||||
}
|
||||
|
||||
// Create an unique folder path to help with concurrent users to avoid filename clash
|
||||
var imageTempPath = _ioHelper.MapPath(Constants.SystemDirectories.TempImageUploads + "/" + Guid.NewGuid().ToString());
|
||||
|
||||
// Temp folderpath (Files come in as bodypart & will need to move/saved into imgTempPath
|
||||
var folderPath = _ioHelper.MapPath(Constants.SystemDirectories.TempFileUploads);
|
||||
|
||||
// Ensure image temp path exists
|
||||
if(Directory.Exists(imageTempPath) == false)
|
||||
{
|
||||
Directory.CreateDirectory(imageTempPath);
|
||||
}
|
||||
|
||||
// File uploaded will be saved as bodypart into TEMP folder
|
||||
var provider = new MultipartFormDataStreamProvider(folderPath);
|
||||
var result = await Request.Content.ReadAsMultipartAsync(provider);
|
||||
|
||||
// Must have a file
|
||||
if (result.FileData.Count == 0)
|
||||
{
|
||||
return Request.CreateResponse(HttpStatusCode.NotFound);
|
||||
}
|
||||
|
||||
// Should only have one file
|
||||
if (result.FileData.Count > 1)
|
||||
{
|
||||
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Only one file can be uploaded at a time");
|
||||
}
|
||||
|
||||
// Really we should only have one file per request to this endpoint
|
||||
var file = result.FileData[0];
|
||||
var fileName = file.Headers.ContentDisposition.FileName.Trim(new[] { '\"' }).TrimEnd();
|
||||
var safeFileName = fileName.ToSafeFileName(_shortStringHelper);
|
||||
var ext = safeFileName.Substring(safeFileName.LastIndexOf('.') + 1).ToLower();
|
||||
|
||||
if (_contentSettings.IsFileAllowedForUpload(ext) == false || _contentSettings.ImageFileTypes.Contains(ext) == false)
|
||||
{
|
||||
// Throw some error - to say can't upload this IMG type
|
||||
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, "This is not an image filetype extension that is approved");
|
||||
}
|
||||
|
||||
//var mediaItemName = fileName.ToFriendlyName();
|
||||
var currentFile = file.LocalFileName;
|
||||
var newFilePath = imageTempPath + Path.DirectorySeparatorChar + safeFileName;
|
||||
var relativeNewFilePath = _ioHelper.GetRelativePath(newFilePath);
|
||||
|
||||
try
|
||||
{
|
||||
// Move the file from bodypart to a real filename
|
||||
// This is what we return from this API so RTE updates img src path
|
||||
// Until we fully persist & save the media item when persisting
|
||||
// If we find <img data-temp-img /> data attribute
|
||||
File.Move(currentFile, newFilePath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// IOException, PathTooLong, DirectoryNotFound, UnathorizedAccess
|
||||
Logger.Error<TinyMceController>(ex, "Error when trying to move {CurrentFilePath} to {NewFilePath}", currentFile, newFilePath);
|
||||
return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, $"Error when trying to move {currentFile} to {newFilePath}", ex);
|
||||
}
|
||||
|
||||
return Request.CreateResponse(HttpStatusCode.OK, new { tmpLocation = relativeNewFilePath });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web.Http.Filters;
|
||||
using Semver;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Web.Composing;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Models;
|
||||
using Umbraco.Web.Mvc;
|
||||
|
||||
namespace Umbraco.Web.Editors
|
||||
{
|
||||
[PluginController("UmbracoApi")]
|
||||
public class UpdateCheckController : UmbracoAuthorizedJsonController
|
||||
{
|
||||
private readonly IUpgradeService _upgradeService;
|
||||
private readonly IUmbracoVersion _umbracoVersion;
|
||||
|
||||
public UpdateCheckController() { }
|
||||
|
||||
public UpdateCheckController(IUpgradeService upgradeService, IUmbracoVersion umbracoVersion)
|
||||
{
|
||||
_upgradeService = upgradeService;
|
||||
_umbracoVersion = umbracoVersion;
|
||||
}
|
||||
|
||||
[UpdateCheckResponseFilter]
|
||||
public async Task<UpgradeCheckResponse> GetCheck()
|
||||
{
|
||||
var updChkCookie = Request.Headers.GetCookies("UMB_UPDCHK").FirstOrDefault();
|
||||
var updateCheckCookie = updChkCookie != null ? updChkCookie["UMB_UPDCHK"].Value : "";
|
||||
if (GlobalSettings.VersionCheckPeriod > 0 && string.IsNullOrEmpty(updateCheckCookie) && Security.CurrentUser.IsAdmin())
|
||||
{
|
||||
try
|
||||
{
|
||||
var version = new SemVersion(_umbracoVersion.Current.Major, _umbracoVersion.Current.Minor,
|
||||
_umbracoVersion.Current.Build, _umbracoVersion.Comment);
|
||||
var result = await _upgradeService.CheckUpgrade(version);
|
||||
|
||||
return new UpgradeCheckResponse(result.UpgradeType, result.Comment, result.UpgradeUrl, _umbracoVersion);
|
||||
}
|
||||
catch
|
||||
{
|
||||
//We don't want to crash due to this
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the cookie response if it was successful
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A filter is required because we are returning an object from the get method and not an HttpResponseMessage
|
||||
/// </remarks>
|
||||
internal class UpdateCheckResponseFilter : ActionFilterAttribute
|
||||
{
|
||||
public override void OnActionExecuted(HttpActionExecutedContext context)
|
||||
{
|
||||
if (context.Response == null) return;
|
||||
|
||||
var objectContent = context.Response.Content as ObjectContent;
|
||||
if (objectContent == null || objectContent.Value == null) return;
|
||||
|
||||
//there is a result, set the outgoing cookie
|
||||
var cookie = new CookieHeaderValue("UMB_UPDCHK", "1")
|
||||
{
|
||||
Path = "/",
|
||||
Expires = DateTimeOffset.Now.AddDays(Current.Configs.Global().VersionCheckPeriod),
|
||||
HttpOnly = true,
|
||||
Secure = Current.Configs.Global().UseHttps
|
||||
};
|
||||
context.Response.Headers.AddCookies(new[] { cookie });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -92,7 +92,7 @@ namespace Umbraco.Web.Editors
|
||||
/// <returns></returns>
|
||||
public string[] GetCurrentUserAvatarUrls()
|
||||
{
|
||||
var urls = UmbracoContext.Security.CurrentUser.GetUserAvatarUrls(AppCaches.RuntimeCache, _mediaFileSystem, _imageUrlGenerator);
|
||||
var urls = Security.CurrentUser.GetUserAvatarUrls(AppCaches.RuntimeCache, _mediaFileSystem, _imageUrlGenerator);
|
||||
if (urls == null)
|
||||
throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Could not access Gravatar endpoint"));
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ using Umbraco.Core;
|
||||
|
||||
namespace Umbraco.Web
|
||||
{
|
||||
|
||||
// Migrated to .NET Core (as FormCollectionExtensions)
|
||||
public static class FormDataCollectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -25,7 +25,10 @@ namespace Umbraco.Web.Mvc
|
||||
|
||||
//we have no choice but to instantiate the controller
|
||||
var instance = factory.CreateController(requestContext, controllerName);
|
||||
return instance?.GetType();
|
||||
var controllerType = instance?.GetType();
|
||||
factory.ReleaseController(instance);
|
||||
|
||||
return controllerType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +82,10 @@ namespace Umbraco.Web.Mvc
|
||||
|
||||
//we have no choice but to instantiate the controller
|
||||
var instance = factory.CreateController(requestContext, controllerName);
|
||||
return instance?.GetType();
|
||||
var controllerType = instance?.GetType();
|
||||
factory.ReleaseController(instance);
|
||||
|
||||
return controllerType;
|
||||
}
|
||||
|
||||
return GetControllerType(requestContext, controllerName);
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Web.Mvc;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Web.Composing;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Web.Security;
|
||||
|
||||
namespace Umbraco.Web.Mvc
|
||||
{
|
||||
@@ -12,25 +13,23 @@ namespace Umbraco.Web.Mvc
|
||||
public sealed class UmbracoAuthorizeAttribute : AuthorizeAttribute
|
||||
{
|
||||
// see note in HttpInstallAuthorizeAttribute
|
||||
private readonly IUmbracoContext _umbracoContext;
|
||||
private readonly IWebSecurity _webSecurity;
|
||||
private readonly IRuntimeState _runtimeState;
|
||||
private readonly string _redirectUrl;
|
||||
|
||||
private IRuntimeState RuntimeState => _runtimeState ?? Current.RuntimeState;
|
||||
|
||||
private IUmbracoContext UmbracoContext => _umbracoContext ?? Current.UmbracoContext;
|
||||
private IWebSecurity WebSecurity => _webSecurity ?? Current.UmbracoContext.Security;
|
||||
|
||||
/// <summary>
|
||||
/// THIS SHOULD BE ONLY USED FOR UNIT TESTS
|
||||
/// </summary>
|
||||
/// <param name="umbracoContext"></param>
|
||||
/// <param name="webSecurity"></param>
|
||||
/// <param name="runtimeState"></param>
|
||||
public UmbracoAuthorizeAttribute(IUmbracoContext umbracoContext, IRuntimeState runtimeState)
|
||||
public UmbracoAuthorizeAttribute(IWebSecurity webSecurity, IRuntimeState runtimeState)
|
||||
{
|
||||
if (umbracoContext == null) throw new ArgumentNullException(nameof(umbracoContext));
|
||||
if (runtimeState == null) throw new ArgumentNullException(nameof(runtimeState));
|
||||
_umbracoContext = umbracoContext;
|
||||
_runtimeState = runtimeState;
|
||||
_webSecurity = webSecurity ?? throw new ArgumentNullException(nameof(webSecurity));
|
||||
_runtimeState = runtimeState ?? throw new ArgumentNullException(nameof(runtimeState));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -75,7 +74,7 @@ namespace Umbraco.Web.Mvc
|
||||
// otherwise we need to ensure that a user is logged in
|
||||
return RuntimeState.Level == RuntimeLevel.Install
|
||||
|| RuntimeState.Level == RuntimeLevel.Upgrade
|
||||
|| UmbracoContext.Security.ValidateCurrentUser();
|
||||
|| WebSecurity.ValidateCurrentUser();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
||||
@@ -1,292 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Formatting;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web.Http;
|
||||
using System.Web.Http.Controllers;
|
||||
using System.Web.Http.Routing;
|
||||
using System.Web.Mvc;
|
||||
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.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
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to return tree root nodes
|
||||
/// </summary>
|
||||
[AngularJsonOnlyConfiguration]
|
||||
[PluginController("UmbracoTrees")]
|
||||
public class ApplicationTreeController : UmbracoAuthorizedApiController
|
||||
{
|
||||
private readonly ITreeService _treeService;
|
||||
private readonly ISectionService _sectionService;
|
||||
|
||||
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)
|
||||
{
|
||||
_treeService = treeService;
|
||||
_sectionService = sectionService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the tree nodes for an application
|
||||
/// </summary>
|
||||
/// <param name="application">The application to load tree for</param>
|
||||
/// <param name="tree">An optional single tree alias, if specified will only load the single tree for the request app</param>
|
||||
/// <param name="queryStrings"></param>
|
||||
/// <param name="use">Tree use.</param>
|
||||
/// <returns></returns>
|
||||
public async Task<TreeRootNode> GetApplicationTrees(string application, string tree, [System.Web.Http.ModelBinding.ModelBinder(typeof(HttpQueryStringModelBinder))]FormDataCollection queryStrings, TreeUse use = TreeUse.Main)
|
||||
{
|
||||
application = application.CleanForXss();
|
||||
|
||||
if (string.IsNullOrEmpty(application))
|
||||
throw new HttpResponseException(HttpStatusCode.NotFound);
|
||||
|
||||
var section = _sectionService.GetByAlias(application);
|
||||
if (section == null)
|
||||
throw new HttpResponseException(HttpStatusCode.NotFound);
|
||||
|
||||
//find all tree definitions that have the current application alias
|
||||
var groupedTrees = _treeService.GetBySectionGrouped(application, use);
|
||||
var allTrees = groupedTrees.Values.SelectMany(x => x).ToList();
|
||||
|
||||
if (allTrees.Count == 0)
|
||||
{
|
||||
//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);
|
||||
return TreeRootNode.CreateSingleTreeRoot(Constants.System.RootString, null, null, name, TreeNodeCollection.Empty, true);
|
||||
}
|
||||
|
||||
// handle request for a specific tree / or when there is only one tree
|
||||
if (!tree.IsNullOrWhiteSpace() || allTrees.Count == 1)
|
||||
{
|
||||
var t = tree.IsNullOrWhiteSpace()
|
||||
? allTrees[0]
|
||||
: allTrees.FirstOrDefault(x => x.TreeAlias == tree);
|
||||
|
||||
if (t == null)
|
||||
throw new HttpResponseException(HttpStatusCode.NotFound);
|
||||
|
||||
var treeRootNode = await GetTreeRootNode(t, Constants.System.Root, queryStrings);
|
||||
if (treeRootNode != null)
|
||||
return treeRootNode;
|
||||
|
||||
throw new HttpResponseException(HttpStatusCode.NotFound);
|
||||
}
|
||||
|
||||
// handle requests for all trees
|
||||
// for only 1 group
|
||||
if (groupedTrees.Count == 1)
|
||||
{
|
||||
var nodes = new TreeNodeCollection();
|
||||
foreach (var t in allTrees)
|
||||
{
|
||||
var node = await TryGetRootNode(t, queryStrings);
|
||||
if (node != null)
|
||||
nodes.Add(node);
|
||||
}
|
||||
|
||||
var name = Services.TextService.Localize("sections/" + application);
|
||||
|
||||
if (nodes.Count > 0)
|
||||
{
|
||||
var treeRootNode = TreeRootNode.CreateMultiTreeRoot(nodes);
|
||||
treeRootNode.Name = name;
|
||||
return treeRootNode;
|
||||
}
|
||||
|
||||
// otherwise it's a section with all empty trees, aka a fullscreen section
|
||||
// todo is this true? what if we just failed to TryGetRootNode on all of them? SD: Yes it's true but we should check the result of TryGetRootNode and throw?
|
||||
return TreeRootNode.CreateSingleTreeRoot(Constants.System.RootString, null, null, name, TreeNodeCollection.Empty, true);
|
||||
}
|
||||
|
||||
// for many groups
|
||||
var treeRootNodes = new List<TreeRootNode>();
|
||||
foreach (var (groupName, trees) in groupedTrees)
|
||||
{
|
||||
var nodes = new TreeNodeCollection();
|
||||
foreach (var t in trees)
|
||||
{
|
||||
var node = await TryGetRootNode(t, queryStrings);
|
||||
if (node != null)
|
||||
nodes.Add(node);
|
||||
}
|
||||
|
||||
if (nodes.Count == 0)
|
||||
continue;
|
||||
|
||||
// no name => third party
|
||||
// use localization key treeHeaders/thirdPartyGroup
|
||||
// todo this is an odd convention
|
||||
var name = groupName.IsNullOrWhiteSpace() ? "thirdPartyGroup" : groupName;
|
||||
|
||||
var groupRootNode = TreeRootNode.CreateGroupNode(nodes, application);
|
||||
groupRootNode.Name = Services.TextService.Localize("treeHeaders/" + name);
|
||||
treeRootNodes.Add(groupRootNode);
|
||||
}
|
||||
|
||||
return TreeRootNode.CreateGroupedMultiTreeRoot(new TreeNodeCollection(treeRootNodes.OrderBy(x => x.Name)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get the root node of a tree.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>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.</para>
|
||||
/// </remarks>
|
||||
private async Task<TreeNode> TryGetRootNode(Tree tree, FormDataCollection querystring)
|
||||
{
|
||||
if (tree == null) throw new ArgumentNullException(nameof(tree));
|
||||
|
||||
try
|
||||
{
|
||||
return await GetRootNode(tree, querystring);
|
||||
}
|
||||
catch (HttpResponseException)
|
||||
{
|
||||
// if this occurs its because the user isn't authorized to view that tree,
|
||||
// in this case since we are loading multiple trees we will just return
|
||||
// null so that it's not added to the list.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the tree root node of a tree.
|
||||
/// </summary>
|
||||
private async Task<TreeRootNode> GetTreeRootNode(Tree tree, int id, FormDataCollection querystring)
|
||||
{
|
||||
if (tree == null) throw new ArgumentNullException(nameof(tree));
|
||||
|
||||
var children = await GetChildren(tree, id, querystring);
|
||||
var rootNode = await GetRootNode(tree, querystring);
|
||||
|
||||
var sectionRoot = TreeRootNode.CreateSingleTreeRoot(
|
||||
Constants.System.RootString,
|
||||
rootNode.ChildNodesUrl,
|
||||
rootNode.MenuUrl,
|
||||
rootNode.Name,
|
||||
children,
|
||||
tree.IsSingleNodeTree);
|
||||
|
||||
// assign the route path based on the root node, this means it will route there when the
|
||||
// section is navigated to and no dashboards will be available for this section
|
||||
sectionRoot.RoutePath = rootNode.RoutePath;
|
||||
sectionRoot.Path = rootNode.Path;
|
||||
|
||||
foreach (var d in rootNode.AdditionalData)
|
||||
sectionRoot.AdditionalData[d.Key] = d.Value;
|
||||
|
||||
return sectionRoot;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the root node of a tree.
|
||||
/// </summary>
|
||||
private async Task<TreeNode> GetRootNode(Tree tree, FormDataCollection querystring)
|
||||
{
|
||||
if (tree == null) throw new ArgumentNullException(nameof(tree));
|
||||
|
||||
var controller = (TreeController) await GetApiControllerProxy(tree.TreeControllerType, "GetRootNode", querystring);
|
||||
var rootNode = controller.GetRootNode(querystring);
|
||||
if (rootNode == null)
|
||||
throw new InvalidOperationException($"Failed to get root node for tree \"{tree.TreeAlias}\".");
|
||||
return rootNode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the child nodes of a tree node.
|
||||
/// </summary>
|
||||
private async Task<TreeNodeCollection> GetChildren(Tree tree, int id, FormDataCollection 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<string, string>();
|
||||
d["id"] = null;
|
||||
var proxyQuerystring = new FormDataCollection(d);
|
||||
|
||||
var controller = (TreeController) await GetApiControllerProxy(tree.TreeControllerType, "GetNodes", proxyQuerystring);
|
||||
return controller.GetNodes(id.ToInvariantString(), querystring);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a proxy to a controller for a specified action.
|
||||
/// </summary>
|
||||
/// <param name="controllerType">The type of the controller.</param>
|
||||
/// <param name="action">The action.</param>
|
||||
/// <param name="querystring">The querystring.</param>
|
||||
/// <returns>An instance of the controller.</returns>
|
||||
/// <remarks>
|
||||
/// <para>Creates an instance of the <paramref name="controllerType"/> and initializes it with a route
|
||||
/// and context etc. so it can execute the specified <paramref name="action"/>. Runs the authorization
|
||||
/// filters for that action, to ensure that the user has permission to execute it.</para>
|
||||
/// </remarks>
|
||||
private async Task<object> GetApiControllerProxy(Type controllerType, string action, FormDataCollection 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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
using System.Net.Http.Formatting;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Web.Models.Trees;
|
||||
using Umbraco.Web.Mvc;
|
||||
using Umbraco.Web.WebApi.Filters;
|
||||
using Constants = Umbraco.Core.Constants;
|
||||
|
||||
namespace Umbraco.Web.Trees
|
||||
{
|
||||
[UmbracoTreeAuthorize(Constants.Trees.Languages)]
|
||||
[Tree(Constants.Applications.Settings, Constants.Trees.Languages, SortOrder = 11, TreeGroup = Constants.Trees.Groups.Settings)]
|
||||
[PluginController("UmbracoTrees")]
|
||||
[CoreTree]
|
||||
public class LanguageTreeController : TreeController
|
||||
{
|
||||
protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection 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)
|
||||
{
|
||||
//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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method to create a root model for a tree
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected override TreeNode CreateRootNode(FormDataCollection queryStrings)
|
||||
{
|
||||
var root = base.CreateRootNode(queryStrings);
|
||||
|
||||
//this will load in a custom UI instead of the dashboard for the root node
|
||||
root.RoutePath = $"{Constants.Applications.Settings}/{Constants.Trees.Languages}/overview";
|
||||
root.Icon = "icon-globe";
|
||||
root.HasChildren = false;
|
||||
root.MenuUrl = null;
|
||||
|
||||
return root;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ using Umbraco.Web.Models.Trees;
|
||||
|
||||
namespace Umbraco.Web.Trees
|
||||
{
|
||||
// Migrated to .NET Core
|
||||
public class MenuRenderingEventArgs : TreeRenderingEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace Umbraco.Web.Trees
|
||||
/// <summary>
|
||||
/// Identifies a section tree.
|
||||
/// </summary>
|
||||
/// // Migrated to .NET Core
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
|
||||
public class TreeAttribute : Attribute, ITree
|
||||
{
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Composing;
|
||||
|
||||
namespace Umbraco.Web.Trees
|
||||
{
|
||||
/// <summary>
|
||||
/// Builds a <see cref="TreeCollection"/>.
|
||||
/// </summary>
|
||||
public class TreeCollectionBuilder : ICollectionBuilder<TreeCollection, Tree>
|
||||
{
|
||||
private readonly List<Tree> _trees = new List<Tree>();
|
||||
|
||||
public TreeCollection CreateCollection(IFactory factory) => new TreeCollection(_trees);
|
||||
|
||||
public void RegisterWith(IRegister register) => register.Register(CreateCollection, Lifetime.Singleton);
|
||||
|
||||
/// <summary>
|
||||
/// Registers a custom tree definition
|
||||
/// </summary>
|
||||
/// <param name="treeDefinition"></param>
|
||||
/// <remarks>
|
||||
/// This is useful if a developer wishes to have a single tree controller for different tree aliases. In this case the tree controller
|
||||
/// cannot be decorated with the TreeAttribute (since then it will be auto-registered).
|
||||
/// </remarks>
|
||||
public void AddTree(Tree treeDefinition)
|
||||
{
|
||||
if (treeDefinition == null) throw new ArgumentNullException(nameof(treeDefinition));
|
||||
_trees.Add(treeDefinition);
|
||||
}
|
||||
|
||||
public void AddTreeController<TController>()
|
||||
where TController : TreeControllerBase
|
||||
=> AddTreeController(typeof(TController));
|
||||
|
||||
public void AddTreeController(Type controllerType)
|
||||
{
|
||||
if (!typeof(TreeControllerBase).IsAssignableFrom(controllerType))
|
||||
throw new ArgumentException($"Type {controllerType} does not inherit from {typeof(TreeControllerBase).FullName}.");
|
||||
|
||||
// not all TreeControllerBase are meant to be used here,
|
||||
// ignore those that don't have the attribute
|
||||
|
||||
var attribute = controllerType.GetCustomAttribute<TreeAttribute>(false);
|
||||
if (attribute == null) return;
|
||||
var tree = new Tree(attribute.SortOrder, attribute.SectionAlias, attribute.TreeGroup, attribute.TreeAlias, attribute.TreeTitle, attribute.TreeUse, controllerType, attribute.IsSingleNodeTree);
|
||||
_trees.Add(tree);
|
||||
}
|
||||
|
||||
public void AddTreeControllers(IEnumerable<Type> controllerTypes)
|
||||
{
|
||||
foreach (var controllerType in controllerTypes)
|
||||
AddTreeController(controllerType);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ namespace Umbraco.Web.Trees
|
||||
/// <summary>
|
||||
/// The base controller for all tree requests
|
||||
/// </summary>
|
||||
/// // Migrated to .NET Core
|
||||
public abstract class TreeController : TreeControllerBase
|
||||
{
|
||||
private static readonly ConcurrentDictionary<Type, TreeAttribute> TreeAttributeCache = new ConcurrentDictionary<Type, TreeAttribute>();
|
||||
|
||||
@@ -27,6 +27,7 @@ namespace Umbraco.Web.Trees
|
||||
/// <remarks>
|
||||
/// Developers should generally inherit from TreeController.
|
||||
/// </remarks>
|
||||
/// Migrated to .NET Core
|
||||
[AngularJsonOnlyConfiguration]
|
||||
public abstract class TreeControllerBase : UmbracoAuthorizedApiController, ITree
|
||||
{
|
||||
|
||||
@@ -3,6 +3,7 @@ using Umbraco.Web.Models.Trees;
|
||||
|
||||
namespace Umbraco.Web.Trees
|
||||
{
|
||||
// Migrated to .NET Core
|
||||
public class TreeNodeRenderingEventArgs : TreeRenderingEventArgs
|
||||
{
|
||||
public TreeNode Node { get; private set; }
|
||||
|
||||
@@ -3,6 +3,7 @@ using Umbraco.Web.Models.Trees;
|
||||
|
||||
namespace Umbraco.Web.Trees
|
||||
{
|
||||
// Migrated to .NET Core
|
||||
public class TreeNodesRenderingEventArgs : TreeRenderingEventArgs
|
||||
{
|
||||
public TreeNodeCollection Nodes { get; private set; }
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
/// <summary>
|
||||
/// Common query string parameters used for tree query strings
|
||||
/// </summary>
|
||||
/// Migrated to .NET Core
|
||||
internal struct TreeQueryStringParameters
|
||||
{
|
||||
public const string Use = "use";
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Net.Http.Formatting;
|
||||
|
||||
namespace Umbraco.Web.Trees
|
||||
{
|
||||
// Migrated to .NET Core
|
||||
public class TreeRenderingEventArgs : EventArgs
|
||||
{
|
||||
public FormDataCollection QueryStrings { get; private set; }
|
||||
|
||||
@@ -8,6 +8,7 @@ using Umbraco.Core;
|
||||
|
||||
namespace Umbraco.Web.Trees
|
||||
{
|
||||
// Migrated to .NET Core
|
||||
public static class UrlHelperExtensions
|
||||
{
|
||||
public static string GetTreeUrl(this UrlHelper urlHelper, Type treeType, string nodeId, FormDataCollection queryStrings)
|
||||
|
||||
@@ -144,6 +144,7 @@
|
||||
<Compile Include="Compose\AuditEventsComposer.cs" />
|
||||
<Compile Include="Compose\BackOfficeUserAuditEventsComponent.cs" />
|
||||
<Compile Include="Compose\BackOfficeUserAuditEventsComposer.cs" />
|
||||
<Compile Include="Compose\NestedContentPropertyComponent.cs" />
|
||||
<Compile Include="Composing\LightInject\LightInjectContainer.cs" />
|
||||
<Compile Include="Macros\MacroRenderer.cs" />
|
||||
<Compile Include="Macros\MemberUserKeyProvider.cs" />
|
||||
@@ -159,7 +160,6 @@
|
||||
<Compile Include="Editors\Filters\ContentSaveModelValidator.cs" />
|
||||
<Compile Include="Editors\Filters\MediaSaveModelValidator.cs" />
|
||||
<Compile Include="Editors\MacrosController.cs" />
|
||||
<Compile Include="Editors\TinyMceController.cs" />
|
||||
<Compile Include="HttpContextAccessorExtensions.cs" />
|
||||
<Compile Include="HttpContextExtensions.cs" />
|
||||
<Compile Include="ImageCropperTemplateCoreExtensions.cs" />
|
||||
@@ -185,6 +185,7 @@
|
||||
<Compile Include="AspNet\AspNetHttpContextAccessor.cs" />
|
||||
<Compile Include="AspNet\AspNetIpResolver.cs" />
|
||||
<Compile Include="AspNet\AspNetPasswordHasher.cs" />
|
||||
<Compile Include="Compose\NestedContentPropertyComposer.cs" />
|
||||
<Compile Include="RoutableDocumentFilter.cs" />
|
||||
<Compile Include="Runtime\AspNetUmbracoBootPermissionChecker.cs" />
|
||||
<Compile Include="Security\BackOfficeSignInManager.cs" />
|
||||
@@ -201,7 +202,6 @@
|
||||
<Compile Include="Security\UserAwarePasswordHasher.cs" />
|
||||
<Compile Include="StringExtensions.cs" />
|
||||
<Compile Include="Trees\ITreeNodeController.cs" />
|
||||
<Compile Include="Trees\TreeCollectionBuilder.cs" />
|
||||
<Compile Include="UmbracoContext.cs" />
|
||||
<Compile Include="UmbracoContextFactory.cs" />
|
||||
<Compile Include="Mvc\UmbracoVirtualNodeByUdiRouteHandler.cs" />
|
||||
@@ -231,7 +231,6 @@
|
||||
<Compile Include="WebApi\HttpActionContextExtensions.cs" />
|
||||
<Compile Include="WebApi\SerializeVersionAttribute.cs" />
|
||||
<Compile Include="WebApi\TrimModelBinder.cs" />
|
||||
<Compile Include="Editors\DictionaryController.cs" />
|
||||
<Compile Include="Editors\Filters\MemberSaveModelValidator.cs" />
|
||||
<Compile Include="Editors\FromJsonPathAttribute.cs" />
|
||||
<Compile Include="Editors\Filters\IsCurrentUserModelFilterAttribute.cs" />
|
||||
@@ -330,7 +329,6 @@
|
||||
<Compile Include="Mvc\UmbracoViewPage.cs" />
|
||||
<Compile Include="Editors\MacroRenderingController.cs" />
|
||||
<Compile Include="Editors\MemberTypeController.cs" />
|
||||
<Compile Include="Editors\UpdateCheckController.cs" />
|
||||
<Compile Include="Editors\EntityController.cs" />
|
||||
<Compile Include="Editors\MemberController.cs" />
|
||||
<Compile Include="Editors\CurrentUserController.cs" />
|
||||
@@ -342,7 +340,6 @@
|
||||
<Compile Include="Security\UmbracoBackOfficeCookieAuthOptions.cs" />
|
||||
<Compile Include="Trees\DataTypeTreeController.cs" />
|
||||
<Compile Include="Trees\FileSystemTreeController.cs" />
|
||||
<Compile Include="Trees\LanguageTreeController.cs" />
|
||||
<Compile Include="Trees\MemberTreeController.cs" />
|
||||
<Compile Include="Trees\MenuRenderingEventArgs.cs" />
|
||||
<Compile Include="Trees\TemplatesTreeController.cs" />
|
||||
@@ -373,7 +370,6 @@
|
||||
<Compile Include="Models\RegisterModel.cs" />
|
||||
<Compile Include="Editors\MediaTypeController.cs" />
|
||||
<Compile Include="Security\MembershipHelper.cs" />
|
||||
<Compile Include="Editors\SectionController.cs" />
|
||||
<Compile Include="Editors\UmbracoAuthorizedJsonController.cs" />
|
||||
<Compile Include="HttpCookieExtensions.cs" />
|
||||
<Compile Include="FormDataCollectionExtensions.cs" />
|
||||
@@ -383,7 +379,6 @@
|
||||
<Compile Include="Trees\TreeNodeRenderingEventArgs.cs" />
|
||||
<Compile Include="Trees\TreeNodesRenderingEventArgs.cs" />
|
||||
<Compile Include="Trees\TreeQueryStringParameters.cs" />
|
||||
<Compile Include="Trees\ApplicationTreeController.cs" />
|
||||
<Compile Include="Editors\BackOfficeController.cs" />
|
||||
<Compile Include="Security\Providers\MembersMembershipProvider.cs" />
|
||||
<Compile Include="Security\Providers\MembersRoleProvider.cs" />
|
||||
|
||||
@@ -19,24 +19,22 @@ namespace Umbraco.Web.WebApi
|
||||
internal static bool Enable = true;
|
||||
|
||||
// TODO: inject!
|
||||
private readonly IUmbracoContext _umbracoContext;
|
||||
private readonly IWebSecurity _webSecurity;
|
||||
private readonly IRuntimeState _runtimeState;
|
||||
|
||||
private IRuntimeState RuntimeState => _runtimeState ?? Current.RuntimeState;
|
||||
|
||||
private IUmbracoContext UmbracoContext => _umbracoContext ?? Current.UmbracoContext;
|
||||
private IWebSecurity WebSecurity => _webSecurity ?? Current.UmbracoContext.Security;
|
||||
|
||||
/// <summary>
|
||||
/// THIS SHOULD BE ONLY USED FOR UNIT TESTS
|
||||
/// </summary>
|
||||
/// <param name="umbracoContext"></param>
|
||||
/// <param name="webSecurity"></param>
|
||||
/// <param name="runtimeState"></param>
|
||||
public UmbracoAuthorizeAttribute(IUmbracoContext umbracoContext, IRuntimeState runtimeState)
|
||||
public UmbracoAuthorizeAttribute(IWebSecurity webSecurity, IRuntimeState runtimeState)
|
||||
{
|
||||
if (umbracoContext == null) throw new ArgumentNullException(nameof(umbracoContext));
|
||||
if (runtimeState == null) throw new ArgumentNullException(nameof(runtimeState));
|
||||
_umbracoContext = umbracoContext;
|
||||
_runtimeState = runtimeState;
|
||||
_webSecurity = webSecurity ?? throw new ArgumentNullException(nameof(webSecurity));
|
||||
_runtimeState = runtimeState ?? throw new ArgumentNullException(nameof(runtimeState));
|
||||
}
|
||||
|
||||
public UmbracoAuthorizeAttribute() : this(true)
|
||||
@@ -60,7 +58,7 @@ namespace Umbraco.Web.WebApi
|
||||
// otherwise we need to ensure that a user is logged in
|
||||
return RuntimeState.Level == RuntimeLevel.Install
|
||||
|| RuntimeState.Level == RuntimeLevel.Upgrade
|
||||
|| UmbracoContext.Security.ValidateCurrentUser(false, _requireApproval) == ValidateRequestAttempt.Success;
|
||||
|| WebSecurity.ValidateCurrentUser(false, _requireApproval) == ValidateRequestAttempt.Success;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user