From 7eee09758e0f32d57831f3b174899c8c0aaf1e06 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 24 Nov 2020 09:49:58 +1100 Subject: [PATCH 001/202] Ensure there's explicit scopes used for all service access in the Examine indexing logic --- .../Services/Implement/PublicAccessService.cs | 6 +- .../ContentValueSetValidator.cs | 47 ++++++++++--- src/Umbraco.Web/Search/ExamineComponent.cs | 67 ++++++++++++------- 3 files changed, 79 insertions(+), 41 deletions(-) diff --git a/src/Umbraco.Core/Services/Implement/PublicAccessService.cs b/src/Umbraco.Core/Services/Implement/PublicAccessService.cs index ab9ea64292..06570da725 100644 --- a/src/Umbraco.Core/Services/Implement/PublicAccessService.cs +++ b/src/Umbraco.Core/Services/Implement/PublicAccessService.cs @@ -62,12 +62,10 @@ namespace Umbraco.Core.Services.Implement //start with the deepest id ids.Reverse(); - using (var scope = ScopeProvider.CreateScope()) + using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { //This will retrieve from cache! - var entries = _publicAccessRepository.GetMany().ToArray(); - - scope.Complete(); + var entries = _publicAccessRepository.GetMany().ToList(); foreach (var id in ids) { var found = entries.FirstOrDefault(x => x.ProtectedNodeId == id); diff --git a/src/Umbraco.Examine/ContentValueSetValidator.cs b/src/Umbraco.Examine/ContentValueSetValidator.cs index 9555566c53..f702e8197d 100644 --- a/src/Umbraco.Examine/ContentValueSetValidator.cs +++ b/src/Umbraco.Examine/ContentValueSetValidator.cs @@ -4,7 +4,9 @@ using System.Linq; using Examine; using Examine.LuceneEngine.Providers; using Umbraco.Core; +using Umbraco.Core.Composing; using Umbraco.Core.Models; +using Umbraco.Core.Scoping; using Umbraco.Core.Services; namespace Umbraco.Examine @@ -15,9 +17,9 @@ namespace Umbraco.Examine public class ContentValueSetValidator : ValueSetValidator, IContentValueSetValidator { private readonly IPublicAccessService _publicAccessService; - + private readonly IScopeProvider _scopeProvider; private const string PathKey = "path"; - private static readonly IEnumerable ValidCategories = new[] {IndexTypes.Content, IndexTypes.Media}; + private static readonly IEnumerable ValidCategories = new[] { IndexTypes.Content, IndexTypes.Media }; protected override IEnumerable ValidIndexCategories => ValidCategories; public bool PublishedValuesOnly { get; } @@ -53,25 +55,38 @@ namespace Umbraco.Examine public bool ValidateProtectedContent(string path, string category) { - if (category == IndexTypes.Content - && !SupportProtectedContent - //if the service is null we can't look this up so we'll return false - && (_publicAccessService == null || _publicAccessService.IsProtected(path))) + if (category == IndexTypes.Content && !SupportProtectedContent) { - return false; + //if the service is null we can't look this up so we'll return false + if (_publicAccessService == null || _scopeProvider == null) + { + return false; + } + + // explicit scope since we may be in a background thread + using (_scopeProvider.CreateScope(autoComplete: true)) + { + if (_publicAccessService.IsProtected(path)) + { + return false; + } + } } return true; } + // used for tests public ContentValueSetValidator(bool publishedValuesOnly, int? parentId = null, IEnumerable includeItemTypes = null, IEnumerable excludeItemTypes = null) - : this(publishedValuesOnly, true, null, parentId, includeItemTypes, excludeItemTypes) + : this(publishedValuesOnly, true, null, null, parentId, includeItemTypes, excludeItemTypes) { } public ContentValueSetValidator(bool publishedValuesOnly, bool supportProtectedContent, - IPublicAccessService publicAccessService, int? parentId = null, + IPublicAccessService publicAccessService, + IScopeProvider scopeProvider, + int? parentId = null, IEnumerable includeItemTypes = null, IEnumerable excludeItemTypes = null) : base(includeItemTypes, excludeItemTypes, null, null) { @@ -79,6 +94,16 @@ namespace Umbraco.Examine SupportProtectedContent = supportProtectedContent; ParentId = parentId; _publicAccessService = publicAccessService; + _scopeProvider = scopeProvider; + } + + [Obsolete("Use the ctor with all parameters instead")] + public ContentValueSetValidator(bool publishedValuesOnly, bool supportProtectedContent, + IPublicAccessService publicAccessService, int? parentId = null, + IEnumerable includeItemTypes = null, IEnumerable excludeItemTypes = null) + : this(publishedValuesOnly, supportProtectedContent, publicAccessService, Current.ScopeProvider, + parentId, includeItemTypes, excludeItemTypes) + { } public override ValueSetValidationResult Validate(ValueSet valueSet) @@ -103,7 +128,7 @@ namespace Umbraco.Examine && variesByCulture.Count > 0 && variesByCulture[0].Equals("y")) { //so this valueset is for a content that varies by culture, now check for non-published cultures and remove those values - foreach(var publishField in valueSet.Values.Where(x => x.Key.StartsWith($"{UmbracoExamineIndex.PublishedFieldName}_")).ToList()) + foreach (var publishField in valueSet.Values.Where(x => x.Key.StartsWith($"{UmbracoExamineIndex.PublishedFieldName}_")).ToList()) { if (publishField.Value.Count <= 0 || !publishField.Value[0].Equals("y")) { @@ -134,7 +159,7 @@ namespace Umbraco.Examine || !ValidateProtectedContent(path, valueSet.Category)) return ValueSetValidationResult.Filtered; - return isFiltered ? ValueSetValidationResult.Filtered: ValueSetValidationResult.Valid; + return isFiltered ? ValueSetValidationResult.Filtered : ValueSetValidationResult.Valid; } } } diff --git a/src/Umbraco.Web/Search/ExamineComponent.cs b/src/Umbraco.Web/Search/ExamineComponent.cs index c9d7b7cf56..bb3f30d4cf 100644 --- a/src/Umbraco.Web/Search/ExamineComponent.cs +++ b/src/Umbraco.Web/Search/ExamineComponent.cs @@ -629,22 +629,27 @@ namespace Umbraco.Web.Search // perform the ValueSet lookup on a background thread examineComponent._indexItemTaskRunner.Add(new SimpleTask(() => { - // for content we have a different builder for published vs unpublished - // we don't want to build more value sets than is needed so we'll lazily build 2 one for published one for non-published - var builders = new Dictionary>> + // Background thread, wrap the whole thing in an explicit scope since we know + // DB services are used within this logic. + using (examineComponent._scopeProvider.CreateScope(autoComplete: true)) { - [true] = new Lazy>(() => examineComponent._publishedContentValueSetBuilder.GetValueSets(content).ToList()), - [false] = new Lazy>(() => examineComponent._contentValueSetBuilder.GetValueSets(content).ToList()) - }; + // for content we have a different builder for published vs unpublished + // we don't want to build more value sets than is needed so we'll lazily build 2 one for published one for non-published + var builders = new Dictionary>> + { + [true] = new Lazy>(() => examineComponent._publishedContentValueSetBuilder.GetValueSets(content).ToList()), + [false] = new Lazy>(() => examineComponent._contentValueSetBuilder.GetValueSets(content).ToList()) + }; - foreach (var index in examineComponent._examineManager.Indexes.OfType() - //filter the indexers - .Where(x => isPublished || !x.PublishedValuesOnly) - .Where(x => x.EnableDefaultEventHandler)) - { - var valueSet = builders[index.PublishedValuesOnly].Value; - index.IndexItems(valueSet); - } + foreach (var index in examineComponent._examineManager.Indexes.OfType() + //filter the indexers + .Where(x => isPublished || !x.PublishedValuesOnly) + .Where(x => x.EnableDefaultEventHandler)) + { + var valueSet = builders[index.PublishedValuesOnly].Value; + index.IndexItems(valueSet); + } + } })); } } @@ -675,14 +680,19 @@ namespace Umbraco.Web.Search // perform the ValueSet lookup on a background thread examineComponent._indexItemTaskRunner.Add(new SimpleTask(() => { - var valueSet = examineComponent._mediaValueSetBuilder.GetValueSets(media).ToList(); - - foreach (var index in examineComponent._examineManager.Indexes.OfType() - //filter the indexers - .Where(x => isPublished || !x.PublishedValuesOnly) - .Where(x => x.EnableDefaultEventHandler)) + // Background thread, wrap the whole thing in an explicit scope since we know + // DB services are used within this logic. + using (examineComponent._scopeProvider.CreateScope(autoComplete: true)) { - index.IndexItems(valueSet); + var valueSet = examineComponent._mediaValueSetBuilder.GetValueSets(media).ToList(); + + foreach (var index in examineComponent._examineManager.Indexes.OfType() + //filter the indexers + .Where(x => isPublished || !x.PublishedValuesOnly) + .Where(x => x.EnableDefaultEventHandler)) + { + index.IndexItems(valueSet); + } } })); } @@ -712,12 +722,17 @@ namespace Umbraco.Web.Search // perform the ValueSet lookup on a background thread examineComponent._indexItemTaskRunner.Add(new SimpleTask(() => { - var valueSet = examineComponent._memberValueSetBuilder.GetValueSets(member).ToList(); - foreach (var index in examineComponent._examineManager.Indexes.OfType() - //filter the indexers - .Where(x => x.EnableDefaultEventHandler)) + // Background thread, wrap the whole thing in an explicit scope since we know + // DB services are used within this logic. + using (examineComponent._scopeProvider.CreateScope(autoComplete: true)) { - index.IndexItems(valueSet); + var valueSet = examineComponent._memberValueSetBuilder.GetValueSets(member).ToList(); + foreach (var index in examineComponent._examineManager.Indexes.OfType() + //filter the indexers + .Where(x => x.EnableDefaultEventHandler)) + { + index.IndexItems(valueSet); + } } })); } From 348ae22d91f72336e6bcbc7bcb2be63bf0355eca Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 14 Dec 2020 12:37:30 +1100 Subject: [PATCH 002/202] Fixing tests --- .../UmbracoContentValueSetValidatorTests.cs | 81 +++++++++++++++---- 1 file changed, 66 insertions(+), 15 deletions(-) diff --git a/src/Umbraco.Tests/UmbracoExamine/UmbracoContentValueSetValidatorTests.cs b/src/Umbraco.Tests/UmbracoExamine/UmbracoContentValueSetValidatorTests.cs index 8bdb0c71c7..330529a5db 100644 --- a/src/Umbraco.Tests/UmbracoExamine/UmbracoContentValueSetValidatorTests.cs +++ b/src/Umbraco.Tests/UmbracoExamine/UmbracoContentValueSetValidatorTests.cs @@ -8,6 +8,7 @@ using Umbraco.Core; using Umbraco.Core.Models; using System; using System.Linq; +using Umbraco.Core.Scoping; namespace Umbraco.Tests.UmbracoExamine { @@ -17,10 +18,14 @@ namespace Umbraco.Tests.UmbracoExamine [Test] public void Invalid_Category() { - var validator = new ContentValueSetValidator(false, true, Mock.Of()); + var validator = new ContentValueSetValidator( + false, + true, + Mock.Of(), + Mock.Of()); var result = validator.Validate(ValueSet.FromObject("555", IndexTypes.Content, new { hello = "world", path = "-1,555" })); - Assert.AreEqual(ValueSetValidationResult.Valid, result); + Assert.AreEqual(ValueSetValidationResult.Valid, result); result = validator.Validate(ValueSet.FromObject("777", IndexTypes.Media, new { hello = "world", path = "-1,555" })); Assert.AreEqual(ValueSetValidationResult.Valid, result); @@ -33,7 +38,11 @@ namespace Umbraco.Tests.UmbracoExamine [Test] public void Must_Have_Path() { - var validator = new ContentValueSetValidator(false, true, Mock.Of()); + var validator = new ContentValueSetValidator( + false, + true, + Mock.Of(), + Mock.Of()); var result = validator.Validate(ValueSet.FromObject("555", IndexTypes.Content, new { hello = "world" })); Assert.AreEqual(ValueSetValidationResult.Failed, result); @@ -45,7 +54,12 @@ namespace Umbraco.Tests.UmbracoExamine [Test] public void Parent_Id() { - var validator = new ContentValueSetValidator(false, true, Mock.Of(), 555); + var validator = new ContentValueSetValidator( + false, + true, + Mock.Of(), + Mock.Of(), + 555); var result = validator.Validate(ValueSet.FromObject("555", IndexTypes.Content, new { hello = "world", path = "-1,555" })); Assert.AreEqual(ValueSetValidationResult.Filtered, result); @@ -63,7 +77,9 @@ namespace Umbraco.Tests.UmbracoExamine [Test] public void Inclusion_Field_List() { - var validator = new ValueSetValidator(null, null, + var validator = new ValueSetValidator( + null, + null, new[] { "hello", "world" }, null); @@ -79,7 +95,9 @@ namespace Umbraco.Tests.UmbracoExamine [Test] public void Exclusion_Field_List() { - var validator = new ValueSetValidator(null, null, + var validator = new ValueSetValidator( + null, + null, null, new[] { "hello", "world" }); @@ -95,7 +113,9 @@ namespace Umbraco.Tests.UmbracoExamine [Test] public void Inclusion_Exclusion_Field_List() { - var validator = new ValueSetValidator(null, null, + var validator = new ValueSetValidator( + null, + null, new[] { "hello", "world" }, new[] { "world" }); @@ -111,7 +131,11 @@ namespace Umbraco.Tests.UmbracoExamine [Test] public void Inclusion_Type_List() { - var validator = new ContentValueSetValidator(false, true, Mock.Of(), + var validator = new ContentValueSetValidator( + false, + true, + Mock.Of(), + Mock.Of(), includeItemTypes: new List { "include-content" }); var result = validator.Validate(ValueSet.FromObject("555", IndexTypes.Content, "test-content", new { hello = "world", path = "-1,555" })); @@ -127,7 +151,11 @@ namespace Umbraco.Tests.UmbracoExamine [Test] public void Exclusion_Type_List() { - var validator = new ContentValueSetValidator(false, true, Mock.Of(), + var validator = new ContentValueSetValidator( + false, + true, + Mock.Of(), + Mock.Of(), excludeItemTypes: new List { "exclude-content" }); var result = validator.Validate(ValueSet.FromObject("555", IndexTypes.Content, "test-content", new { hello = "world", path = "-1,555" })); @@ -143,7 +171,11 @@ namespace Umbraco.Tests.UmbracoExamine [Test] public void Inclusion_Exclusion_Type_List() { - var validator = new ContentValueSetValidator(false, true, Mock.Of(), + var validator = new ContentValueSetValidator( + false, + true, + Mock.Of(), + Mock.Of(), includeItemTypes: new List { "include-content", "exclude-content" }, excludeItemTypes: new List { "exclude-content" }); @@ -163,7 +195,11 @@ namespace Umbraco.Tests.UmbracoExamine [Test] public void Recycle_Bin_Content() { - var validator = new ContentValueSetValidator(true, false, Mock.Of()); + var validator = new ContentValueSetValidator( + true, + false, + Mock.Of(), + Mock.Of()); var result = validator.Validate(ValueSet.FromObject("555", IndexTypes.Content, new { hello = "world", path = "-1,-20,555" })); Assert.AreEqual(ValueSetValidationResult.Failed, result); @@ -187,7 +223,11 @@ namespace Umbraco.Tests.UmbracoExamine [Test] public void Recycle_Bin_Media() { - var validator = new ContentValueSetValidator(true, false, Mock.Of()); + var validator = new ContentValueSetValidator( + true, + false, + Mock.Of(), + Mock.Of()); var result = validator.Validate(ValueSet.FromObject("555", IndexTypes.Media, new { hello = "world", path = "-1,-21,555" })); Assert.AreEqual(ValueSetValidationResult.Filtered, result); @@ -203,7 +243,11 @@ namespace Umbraco.Tests.UmbracoExamine [Test] public void Published_Only() { - var validator = new ContentValueSetValidator(true, true, Mock.Of()); + var validator = new ContentValueSetValidator( + true, + true, + Mock.Of(), + Mock.Of()); var result = validator.Validate(ValueSet.FromObject("555", IndexTypes.Content, new { hello = "world", path = "-1,555" })); Assert.AreEqual(ValueSetValidationResult.Failed, result); @@ -230,7 +274,10 @@ namespace Umbraco.Tests.UmbracoExamine [Test] public void Published_Only_With_Variants() { - var validator = new ContentValueSetValidator(true, true, Mock.Of()); + var validator = new ContentValueSetValidator(true, + true, + Mock.Of(), + Mock.Of()); var result = validator.Validate(new ValueSet("555", IndexTypes.Content, new Dictionary @@ -288,7 +335,11 @@ namespace Umbraco.Tests.UmbracoExamine .Returns(Attempt.Succeed(new PublicAccessEntry(Guid.NewGuid(), 555, 444, 333, Enumerable.Empty()))); publicAccessService.Setup(x => x.IsProtected("-1,777")) .Returns(Attempt.Fail()); - var validator = new ContentValueSetValidator(false, false, publicAccessService.Object); + var validator = new ContentValueSetValidator( + false, + false, + publicAccessService.Object, + Mock.Of()); var result = validator.Validate(ValueSet.FromObject("555", IndexTypes.Content, new { hello = "world", path = "-1,555" })); Assert.AreEqual(ValueSetValidationResult.Filtered, result); From 55124fff81b077770ea9d4ca1b36c6fedbf65b29 Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Sun, 7 Feb 2021 02:50:20 +0100 Subject: [PATCH 003/202] Update icons in umb-tree-search-results component (#9757) * Avoid use of ng-if * Green class is not necessary * Ensure button element use full width * Left align text inside button element --- .../src/less/components/tree/umb-tree.less | 3 ++- .../components/tree/umb-tree-search-results.html | 13 +++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less index 8234618393..6ab663a8b2 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less @@ -132,7 +132,6 @@ body.touch .umb-tree { .umb-tree .umb-search-group { position: inherit; display: inherit; - list-style: none; h6 { @@ -155,6 +154,8 @@ body.touch .umb-tree { &-link { display: block; + width: 100%; + text-align: left; } &-name { diff --git a/src/Umbraco.Web.UI.Client/src/views/components/tree/umb-tree-search-results.html b/src/Umbraco.Web.UI.Client/src/views/components/tree/umb-tree-search-results.html index 1264d68c76..f472251624 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/tree/umb-tree-search-results.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/tree/umb-tree-search-results.html @@ -7,13 +7,14 @@
    • -
      - -
      - - - -
      +
      + + +
      + + + + +
      +
      diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index 97fc223cc3..bc2f4c0c1e 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -268,6 +268,7 @@ Statistics Title (optional) Alternative text (optional) + Caption (optional) Type Unpublish Unpublished diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml index 1456cce9f0..94645eaa85 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -272,6 +272,7 @@ Statistics Title (optional) Alternative text (optional) + Caption (optional) Type Unpublish Draft diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml index 75377f54c9..88c6458a31 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml @@ -263,6 +263,7 @@ Statistiques Titre (optionnel) Texte alternatif (optionnel) + Légende (optionnel) Type Dépublier Dépublié From 3b3d55ca263af995251db360de701442b0ae4c5d Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 9 Feb 2021 13:43:28 +1100 Subject: [PATCH 013/202] Fixes issue with broken caches used for user permissions Calculating start nodes is expensive and this is supposed to be cached but the cache was not working. --- src/Umbraco.Core/Cache/CacheKeys.cs | 5 + .../Models/Identity/IdentityMapDefinition.cs | 15 +- src/Umbraco.Core/Models/Membership/User.cs | 8 +- src/Umbraco.Core/Models/UserExtensions.cs | 172 +++++++++--------- .../Security/BackOfficeUserStore.cs | 22 ++- .../Security/ContentPermissionsHelper.cs | 21 ++- .../Models/UserExtensionsTests.cs | 3 +- .../Controllers/ContentControllerUnitTests.cs | 27 +-- .../Controllers/MediaControllerUnitTests.cs | 15 +- .../UserEditorAuthorizationHelperTests.cs | 37 ++-- src/Umbraco.Web/Cache/UserCacheRefresher.cs | 7 + src/Umbraco.Web/Editors/ContentController.cs | 2 +- src/Umbraco.Web/Editors/EntityController.cs | 8 +- .../Filters/ContentSaveValidationAttribute.cs | 11 +- .../MediaItemSaveValidationAttribute.cs | 9 +- .../UserGroupAuthorizationAttribute.cs | 3 +- .../UserGroupEditorAuthorizationHelper.cs | 9 +- src/Umbraco.Web/Editors/MediaController.cs | 11 +- .../Editors/UserEditorAuthorizationHelper.cs | 13 +- .../Editors/UserGroupsController.cs | 6 +- src/Umbraco.Web/Editors/UsersController.cs | 6 +- .../Models/Mapping/ContentMapDefinition.cs | 22 ++- .../Models/Mapping/UserMapDefinition.cs | 8 +- src/Umbraco.Web/Search/UmbracoTreeSearcher.cs | 38 +++- .../Trees/ContentTreeController.cs | 4 +- .../Trees/ContentTreeControllerBase.cs | 12 +- src/Umbraco.Web/Trees/MediaTreeController.cs | 4 +- .../Filters/AdminUsersAuthorizeAttribute.cs | 2 +- .../CheckIfUserTicketDataIsStaleAttribute.cs | 4 +- ...EnsureUserPermissionForContentAttribute.cs | 4 +- .../EnsureUserPermissionForMediaAttribute.cs | 1 + .../FilterAllowedOutgoingContentAttribute.cs | 34 ++-- .../FilterAllowedOutgoingMediaAttribute.cs | 2 +- 33 files changed, 325 insertions(+), 220 deletions(-) diff --git a/src/Umbraco.Core/Cache/CacheKeys.cs b/src/Umbraco.Core/Cache/CacheKeys.cs index b8ee0e97c4..0e9a9a3862 100644 --- a/src/Umbraco.Core/Cache/CacheKeys.cs +++ b/src/Umbraco.Core/Cache/CacheKeys.cs @@ -12,5 +12,10 @@ public const string MacroContentCacheKey = "macroContent_"; // used in MacroRenderers public const string MacroFromAliasCacheKey = "macroFromAlias_"; + + public const string UserAllContentStartNodesPrefix = "AllContentStartNodes"; + public const string UserAllMediaStartNodesPrefix = "AllMediaStartNodes"; + public const string UserMediaStartNodePathsPrefix = "MediaStartNodePaths"; + public const string UserContentStartNodePathsPrefix = "ContentStartNodePaths"; } } diff --git a/src/Umbraco.Core/Models/Identity/IdentityMapDefinition.cs b/src/Umbraco.Core/Models/Identity/IdentityMapDefinition.cs index 57e1c9ee5c..47d33f3014 100644 --- a/src/Umbraco.Core/Models/Identity/IdentityMapDefinition.cs +++ b/src/Umbraco.Core/Models/Identity/IdentityMapDefinition.cs @@ -1,4 +1,5 @@ using System; +using Umbraco.Core.Cache; using Umbraco.Core.Configuration; using Umbraco.Core.Mapping; using Umbraco.Core.Models.Membership; @@ -11,7 +12,9 @@ namespace Umbraco.Core.Models.Identity private readonly ILocalizedTextService _textService; private readonly IEntityService _entityService; private readonly IGlobalSettings _globalSettings; + private readonly AppCaches _appCaches; + [Obsolete("Use constructor specifying all dependencies")] public IdentityMapDefinition(ILocalizedTextService textService, IEntityService entityService, IGlobalSettings globalSettings) { _textService = textService; @@ -19,6 +22,14 @@ namespace Umbraco.Core.Models.Identity _globalSettings = globalSettings; } + public IdentityMapDefinition(ILocalizedTextService textService, IEntityService entityService, IGlobalSettings globalSettings, AppCaches appCaches) + { + _textService = textService; + _entityService = entityService; + _globalSettings = globalSettings; + _appCaches = appCaches; + } + public void DefineMaps(UmbracoMapper mapper) { mapper.Define( @@ -46,8 +57,8 @@ namespace Umbraco.Core.Models.Identity target.Groups = source.Groups.ToArray(); */ - target.CalculatedMediaStartNodeIds = source.CalculateMediaStartNodeIds(_entityService); - target.CalculatedContentStartNodeIds = source.CalculateContentStartNodeIds(_entityService); + target.CalculatedMediaStartNodeIds = source.CalculateMediaStartNodeIds(_entityService, _appCaches); + target.CalculatedContentStartNodeIds = source.CalculateContentStartNodeIds(_entityService, _appCaches); target.Email = source.Email; target.UserName = source.Username; target.LastPasswordChangeDateUtc = source.LastPasswordChangeDate.ToUniversalTime(); diff --git a/src/Umbraco.Core/Models/Membership/User.cs b/src/Umbraco.Core/Models/Membership/User.cs index 3d071b0a18..dc39463925 100644 --- a/src/Umbraco.Core/Models/Membership/User.cs +++ b/src/Umbraco.Core/Models/Membership/User.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using System.Runtime.Serialization; using Umbraco.Core.Composing; @@ -384,11 +385,10 @@ namespace Umbraco.Core.Models.Membership #endregion - /// - /// This is used as an internal cache for this entity - specifically for calculating start nodes so we don't re-calculated all of the time - /// [IgnoreDataMember] [DoNotClone] + [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("This should not be used, it's currently used for only a single edge case - should probably be removed for netcore")] internal IDictionary AdditionalData { get @@ -402,6 +402,8 @@ namespace Umbraco.Core.Models.Membership [IgnoreDataMember] [DoNotClone] + [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("Not used, will be removed in future versions")] internal object AdditionalDataLock => _additionalDataLock; protected override void PerformDeepClone(object clone) diff --git a/src/Umbraco.Core/Models/UserExtensions.cs b/src/Umbraco.Core/Models/UserExtensions.cs index 5be66bac47..fdb833a821 100644 --- a/src/Umbraco.Core/Models/UserExtensions.cs +++ b/src/Umbraco.Core/Models/UserExtensions.cs @@ -150,48 +150,40 @@ namespace Umbraco.Core.Models } } - internal static bool HasContentRootAccess(this IUser user, IEntityService entityService) - { - return ContentPermissionsHelper.HasPathAccess(Constants.System.RootString, user.CalculateContentStartNodeIds(entityService), Constants.System.RecycleBinContent); - } + internal static bool HasContentRootAccess(this IUser user, IEntityService entityService, AppCaches appCaches) + => ContentPermissionsHelper.HasPathAccess(Constants.System.RootString, user.CalculateContentStartNodeIds(entityService, appCaches), Constants.System.RecycleBinContent); - internal static bool HasContentBinAccess(this IUser user, IEntityService entityService) - { - return ContentPermissionsHelper.HasPathAccess(Constants.System.RecycleBinContentString, user.CalculateContentStartNodeIds(entityService), Constants.System.RecycleBinContent); - } + internal static bool HasContentBinAccess(this IUser user, IEntityService entityService, AppCaches appCaches) + => ContentPermissionsHelper.HasPathAccess(Constants.System.RecycleBinContentString, user.CalculateContentStartNodeIds(entityService, appCaches), Constants.System.RecycleBinContent); - internal static bool HasMediaRootAccess(this IUser user, IEntityService entityService) - { - return ContentPermissionsHelper.HasPathAccess(Constants.System.RootString, user.CalculateMediaStartNodeIds(entityService), Constants.System.RecycleBinMedia); - } + internal static bool HasMediaRootAccess(this IUser user, IEntityService entityService, AppCaches appCaches) + => ContentPermissionsHelper.HasPathAccess(Constants.System.RootString, user.CalculateMediaStartNodeIds(entityService, appCaches), Constants.System.RecycleBinMedia); - internal static bool HasMediaBinAccess(this IUser user, IEntityService entityService) - { - return ContentPermissionsHelper.HasPathAccess(Constants.System.RecycleBinMediaString, user.CalculateMediaStartNodeIds(entityService), Constants.System.RecycleBinMedia); - } + internal static bool HasMediaBinAccess(this IUser user, IEntityService entityService, AppCaches appCaches) + => ContentPermissionsHelper.HasPathAccess(Constants.System.RecycleBinMediaString, user.CalculateMediaStartNodeIds(entityService, appCaches), Constants.System.RecycleBinMedia); - internal static bool HasPathAccess(this IUser user, IContent content, IEntityService entityService) + internal static bool HasPathAccess(this IUser user, IContent content, IEntityService entityService, AppCaches appCaches) { if (content == null) throw new ArgumentNullException(nameof(content)); - return ContentPermissionsHelper.HasPathAccess(content.Path, user.CalculateContentStartNodeIds(entityService), Constants.System.RecycleBinContent); + return ContentPermissionsHelper.HasPathAccess(content.Path, user.CalculateContentStartNodeIds(entityService, appCaches), Constants.System.RecycleBinContent); } - internal static bool HasPathAccess(this IUser user, IMedia media, IEntityService entityService) + internal static bool HasPathAccess(this IUser user, IMedia media, IEntityService entityService, AppCaches appCaches) { if (media == null) throw new ArgumentNullException(nameof(media)); - return ContentPermissionsHelper.HasPathAccess(media.Path, user.CalculateMediaStartNodeIds(entityService), Constants.System.RecycleBinMedia); + return ContentPermissionsHelper.HasPathAccess(media.Path, user.CalculateMediaStartNodeIds(entityService, appCaches), Constants.System.RecycleBinMedia); } - internal static bool HasContentPathAccess(this IUser user, IUmbracoEntity entity, IEntityService entityService) + internal static bool HasContentPathAccess(this IUser user, IUmbracoEntity entity, IEntityService entityService, AppCaches appCaches) { if (entity == null) throw new ArgumentNullException(nameof(entity)); - return ContentPermissionsHelper.HasPathAccess(entity.Path, user.CalculateContentStartNodeIds(entityService), Constants.System.RecycleBinContent); + return ContentPermissionsHelper.HasPathAccess(entity.Path, user.CalculateContentStartNodeIds(entityService, appCaches), Constants.System.RecycleBinContent); } - internal static bool HasMediaPathAccess(this IUser user, IUmbracoEntity entity, IEntityService entityService) + internal static bool HasMediaPathAccess(this IUser user, IUmbracoEntity entity, IEntityService entityService, AppCaches appCaches) { if (entity == null) throw new ArgumentNullException(nameof(entity)); - return ContentPermissionsHelper.HasPathAccess(entity.Path, user.CalculateMediaStartNodeIds(entityService), Constants.System.RecycleBinMedia); + return ContentPermissionsHelper.HasPathAccess(entity.Path, user.CalculateMediaStartNodeIds(entityService, appCaches), Constants.System.RecycleBinMedia); } /// @@ -204,84 +196,92 @@ namespace Umbraco.Core.Models return user.Groups != null && user.Groups.Any(x => x.Alias == Constants.Security.SensitiveDataGroupAlias); } - // calc. start nodes, combining groups' and user's, and excluding what's in the bin + [Obsolete("Use the overload specifying all parameters instead")] public static int[] CalculateContentStartNodeIds(this IUser user, IEntityService entityService) - { - const string cacheKey = "AllContentStartNodes"; - //try to look them up from cache so we don't recalculate - var valuesInUserCache = FromUserCache(user, cacheKey); - if (valuesInUserCache != null) return valuesInUserCache; + => CalculateContentStartNodeIds(user, entityService, Current.AppCaches); - var gsn = user.Groups.Where(x => x.StartContentId.HasValue).Select(x => x.StartContentId.Value).Distinct().ToArray(); - var usn = user.StartContentIds; - var vals = CombineStartNodes(UmbracoObjectTypes.Document, gsn, usn, entityService); - ToUserCache(user, cacheKey, vals); - return vals; + /// + /// Calculate start nodes, combining groups' and user's, and excluding what's in the bin + /// + /// + /// + /// + /// + public static int[] CalculateContentStartNodeIds(this IUser user, IEntityService entityService, AppCaches appCaches) + { + var cacheKey = CacheKeys.UserAllContentStartNodesPrefix + user.Id; + var runtimeCache = appCaches.IsolatedCaches.GetOrCreate(); + var result = runtimeCache.GetCacheItem(cacheKey, () => + { + var gsn = user.Groups.Where(x => x.StartContentId.HasValue).Select(x => x.StartContentId.Value).Distinct().ToArray(); + var usn = user.StartContentIds; + var vals = CombineStartNodes(UmbracoObjectTypes.Document, gsn, usn, entityService); + return vals; + }, TimeSpan.FromMinutes(2), true); + + return result; } - // calc. start nodes, combining groups' and user's, and excluding what's in the bin + [Obsolete("Use the overload specifying all parameters instead")] public static int[] CalculateMediaStartNodeIds(this IUser user, IEntityService entityService) - { - const string cacheKey = "AllMediaStartNodes"; - //try to look them up from cache so we don't recalculate - var valuesInUserCache = FromUserCache(user, cacheKey); - if (valuesInUserCache != null) return valuesInUserCache; + => CalculateMediaStartNodeIds(user, entityService, Current.AppCaches); - var gsn = user.Groups.Where(x => x.StartMediaId.HasValue).Select(x => x.StartMediaId.Value).Distinct().ToArray(); - var usn = user.StartMediaIds; - var vals = CombineStartNodes(UmbracoObjectTypes.Media, gsn, usn, entityService); - ToUserCache(user, cacheKey, vals); - return vals; + /// + /// Calculate start nodes, combining groups' and user's, and excluding what's in the bin + /// + /// + /// + /// + /// + public static int[] CalculateMediaStartNodeIds(this IUser user, IEntityService entityService, AppCaches appCaches) + { + var cacheKey = CacheKeys.UserAllMediaStartNodesPrefix + user.Id; + var runtimeCache = appCaches.IsolatedCaches.GetOrCreate(); + var result = runtimeCache.GetCacheItem(cacheKey, () => + { + var gsn = user.Groups.Where(x => x.StartMediaId.HasValue).Select(x => x.StartMediaId.Value).Distinct().ToArray(); + var usn = user.StartMediaIds; + var vals = CombineStartNodes(UmbracoObjectTypes.Media, gsn, usn, entityService); + return vals; + }, TimeSpan.FromMinutes(2), true); + + return result; } + [Obsolete("Use the overload specifying all parameters instead")] public static string[] GetMediaStartNodePaths(this IUser user, IEntityService entityService) - { - const string cacheKey = "MediaStartNodePaths"; - //try to look them up from cache so we don't recalculate - var valuesInUserCache = FromUserCache(user, cacheKey); - if (valuesInUserCache != null) return valuesInUserCache; + => GetMediaStartNodePaths(user, entityService, Current.AppCaches); - var startNodeIds = user.CalculateMediaStartNodeIds(entityService); - var vals = entityService.GetAllPaths(UmbracoObjectTypes.Media, startNodeIds).Select(x => x.Path).ToArray(); - ToUserCache(user, cacheKey, vals); - return vals; + public static string[] GetMediaStartNodePaths(this IUser user, IEntityService entityService, AppCaches appCaches) + { + var cacheKey = CacheKeys.UserMediaStartNodePathsPrefix + user.Id; + var runtimeCache = appCaches.IsolatedCaches.GetOrCreate(); + var result = runtimeCache.GetCacheItem(cacheKey, () => + { + var startNodeIds = user.CalculateMediaStartNodeIds(entityService, appCaches); + var vals = entityService.GetAllPaths(UmbracoObjectTypes.Media, startNodeIds).Select(x => x.Path).ToArray(); + return vals; + }, TimeSpan.FromMinutes(2), true); + + return result; } + [Obsolete("Use the overload specifying all parameters instead")] public static string[] GetContentStartNodePaths(this IUser user, IEntityService entityService) + => GetContentStartNodePaths(user, entityService, Current.AppCaches); + + public static string[] GetContentStartNodePaths(this IUser user, IEntityService entityService, AppCaches appCaches) { - const string cacheKey = "ContentStartNodePaths"; - //try to look them up from cache so we don't recalculate - var valuesInUserCache = FromUserCache(user, cacheKey); - if (valuesInUserCache != null) return valuesInUserCache; - - var startNodeIds = user.CalculateContentStartNodeIds(entityService); - var vals = entityService.GetAllPaths(UmbracoObjectTypes.Document, startNodeIds).Select(x => x.Path).ToArray(); - ToUserCache(user, cacheKey, vals); - return vals; - } - - private static T FromUserCache(IUser user, string cacheKey) - where T: class - { - if (!(user is User entityUser)) return null; - - lock (entityUser.AdditionalDataLock) + var cacheKey = CacheKeys.UserContentStartNodePathsPrefix + user.Id; + var runtimeCache = appCaches.IsolatedCaches.GetOrCreate(); + var result = runtimeCache.GetCacheItem(cacheKey, () => { - return entityUser.AdditionalData.TryGetValue(cacheKey, out var allContentStartNodes) - ? allContentStartNodes as T - : null; - } - } + var startNodeIds = user.CalculateContentStartNodeIds(entityService, appCaches); + var vals = entityService.GetAllPaths(UmbracoObjectTypes.Document, startNodeIds).Select(x => x.Path).ToArray(); + return vals; + }, TimeSpan.FromMinutes(2), true); - private static void ToUserCache(IUser user, string cacheKey, T vals) - where T: class - { - if (!(user is User entityUser)) return; - - lock (entityUser.AdditionalDataLock) - { - entityUser.AdditionalData[cacheKey] = vals; - } + return result; } private static bool StartsWithPath(string test, string path) diff --git a/src/Umbraco.Core/Security/BackOfficeUserStore.cs b/src/Umbraco.Core/Security/BackOfficeUserStore.cs index 7df328b5b7..fe1673bca6 100644 --- a/src/Umbraco.Core/Security/BackOfficeUserStore.cs +++ b/src/Umbraco.Core/Security/BackOfficeUserStore.cs @@ -5,6 +5,8 @@ using System.Linq; using System.Threading.Tasks; using System.Web.Security; using Microsoft.AspNet.Identity; +using Umbraco.Core.Cache; +using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.Mapping; using Umbraco.Core.Models; @@ -38,20 +40,26 @@ namespace Umbraco.Core.Security private readonly IExternalLoginService _externalLoginService; private readonly IGlobalSettings _globalSettings; private readonly UmbracoMapper _mapper; + private readonly AppCaches _appCaches; private bool _disposed = false; + [Obsolete("Use the constructor specifying all dependencies")] public BackOfficeUserStore(IUserService userService, IMemberTypeService memberTypeService, IEntityService entityService, IExternalLoginService externalLoginService, IGlobalSettings globalSettings, MembershipProviderBase usersMembershipProvider, UmbracoMapper mapper) + : this(userService, memberTypeService, entityService, externalLoginService, globalSettings, usersMembershipProvider, mapper, Current.AppCaches) { } + + public BackOfficeUserStore(IUserService userService, IMemberTypeService memberTypeService, IEntityService entityService, IExternalLoginService externalLoginService, IGlobalSettings globalSettings, MembershipProviderBase usersMembershipProvider, UmbracoMapper mapper, AppCaches appCaches) { + if (userService == null) throw new ArgumentNullException("userService"); + if (usersMembershipProvider == null) throw new ArgumentNullException("usersMembershipProvider"); + if (externalLoginService == null) throw new ArgumentNullException("externalLoginService"); + _userService = userService; _memberTypeService = memberTypeService; _entityService = entityService; _externalLoginService = externalLoginService; - _globalSettings = globalSettings; - if (userService == null) throw new ArgumentNullException("userService"); - if (usersMembershipProvider == null) throw new ArgumentNullException("usersMembershipProvider"); - if (externalLoginService == null) throw new ArgumentNullException("externalLoginService"); + _globalSettings = globalSettings; _mapper = mapper; - + _appCaches = appCaches; _userService = userService; _externalLoginService = externalLoginService; @@ -775,8 +783,8 @@ namespace Umbraco.Core.Security } //we should re-set the calculated start nodes - identityUser.CalculatedMediaStartNodeIds = user.CalculateMediaStartNodeIds(_entityService); - identityUser.CalculatedContentStartNodeIds = user.CalculateContentStartNodeIds(_entityService); + identityUser.CalculatedMediaStartNodeIds = user.CalculateMediaStartNodeIds(_entityService, _appCaches); + identityUser.CalculatedContentStartNodeIds = user.CalculateContentStartNodeIds(_entityService, _appCaches); //reset all changes identityUser.ResetDirtyProperties(false); diff --git a/src/Umbraco.Core/Security/ContentPermissionsHelper.cs b/src/Umbraco.Core/Security/ContentPermissionsHelper.cs index 1a329fcdcb..8c3a138f6a 100644 --- a/src/Umbraco.Core/Security/ContentPermissionsHelper.cs +++ b/src/Umbraco.Core/Security/ContentPermissionsHelper.cs @@ -4,6 +4,7 @@ using System.Globalization; using System.Linq; using System.Text; using System.Threading.Tasks; +using Umbraco.Core.Cache; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Core.Models.Membership; @@ -25,6 +26,7 @@ namespace Umbraco.Core.Security IUser user, IUserService userService, IEntityService entityService, + AppCaches appCaches, params char[] permissionsToCheck) { if (user == null) throw new ArgumentNullException("user"); @@ -33,7 +35,7 @@ namespace Umbraco.Core.Security if (content == null) return ContentAccess.NotFound; - var hasPathAccess = user.HasPathAccess(content, entityService); + var hasPathAccess = user.HasPathAccess(content, entityService, appCaches); if (hasPathAccess == false) return ContentAccess.Denied; @@ -52,6 +54,7 @@ namespace Umbraco.Core.Security IUser user, IUserService userService, IEntityService entityService, + AppCaches appCaches, params char[] permissionsToCheck) { if (user == null) throw new ArgumentNullException("user"); @@ -60,7 +63,7 @@ namespace Umbraco.Core.Security if (entity == null) return ContentAccess.NotFound; - var hasPathAccess = user.HasContentPathAccess(entity, entityService); + var hasPathAccess = user.HasContentPathAccess(entity, entityService, appCaches); if (hasPathAccess == false) return ContentAccess.Denied; @@ -89,6 +92,7 @@ namespace Umbraco.Core.Security IUser user, IUserService userService, IEntityService entityService, + AppCaches appCaches, out IUmbracoEntity entity, params char[] permissionsToCheck) { @@ -100,16 +104,16 @@ namespace Umbraco.Core.Security entity = null; if (nodeId == Constants.System.Root) - hasPathAccess = user.HasContentRootAccess(entityService); + hasPathAccess = user.HasContentRootAccess(entityService, appCaches); else if (nodeId == Constants.System.RecycleBinContent) - hasPathAccess = user.HasContentBinAccess(entityService); + hasPathAccess = user.HasContentBinAccess(entityService, appCaches); if (hasPathAccess.HasValue) return hasPathAccess.Value ? ContentAccess.Granted : ContentAccess.Denied; entity = entityService.Get(nodeId, UmbracoObjectTypes.Document); if (entity == null) return ContentAccess.NotFound; - hasPathAccess = user.HasContentPathAccess(entity, entityService); + hasPathAccess = user.HasContentPathAccess(entity, entityService, appCaches); if (hasPathAccess == false) return ContentAccess.Denied; @@ -140,6 +144,7 @@ namespace Umbraco.Core.Security IUserService userService, IContentService contentService, IEntityService entityService, + AppCaches appCaches, out IContent contentItem, params char[] permissionsToCheck) { @@ -152,16 +157,16 @@ namespace Umbraco.Core.Security contentItem = null; if (nodeId == Constants.System.Root) - hasPathAccess = user.HasContentRootAccess(entityService); + hasPathAccess = user.HasContentRootAccess(entityService, appCaches); else if (nodeId == Constants.System.RecycleBinContent) - hasPathAccess = user.HasContentBinAccess(entityService); + hasPathAccess = user.HasContentBinAccess(entityService, appCaches); if (hasPathAccess.HasValue) return hasPathAccess.Value ? ContentAccess.Granted : ContentAccess.Denied; contentItem = contentService.GetById(nodeId); if (contentItem == null) return ContentAccess.NotFound; - hasPathAccess = user.HasPathAccess(contentItem, entityService); + hasPathAccess = user.HasPathAccess(contentItem, entityService, appCaches); if (hasPathAccess == false) return ContentAccess.Denied; diff --git a/src/Umbraco.Tests/Models/UserExtensionsTests.cs b/src/Umbraco.Tests/Models/UserExtensionsTests.cs index bf76031b59..18ece0c841 100644 --- a/src/Umbraco.Tests/Models/UserExtensionsTests.cs +++ b/src/Umbraco.Tests/Models/UserExtensionsTests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using Moq; using NUnit.Framework; +using Umbraco.Core.Cache; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Core.Models.Membership; @@ -34,7 +35,7 @@ namespace Umbraco.Tests.Models .Setup(x => x.GetAllPaths(It.IsAny(), It.IsAny())) .Returns((type, ids) => new[] { new TreeEntityPath { Id = startNodeId, Path = startNodePath } }); - Assert.AreEqual(outcome, user.HasPathAccess(content, esmock.Object)); + Assert.AreEqual(outcome, user.HasPathAccess(content, esmock.Object, AppCaches.Disabled)); } [TestCase("", "1", "1")] // single user start, top level diff --git a/src/Umbraco.Tests/Web/Controllers/ContentControllerUnitTests.cs b/src/Umbraco.Tests/Web/Controllers/ContentControllerUnitTests.cs index f55a4e593b..26aea30c26 100644 --- a/src/Umbraco.Tests/Web/Controllers/ContentControllerUnitTests.cs +++ b/src/Umbraco.Tests/Web/Controllers/ContentControllerUnitTests.cs @@ -2,6 +2,7 @@ using System.Web.Http; using Moq; using NUnit.Framework; +using Umbraco.Core.Cache; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Core.Models.Membership; @@ -34,7 +35,7 @@ namespace Umbraco.Tests.Web.Controllers var userService = userServiceMock.Object; //act - var result = ContentPermissionsHelper.CheckPermissions(1234, user, userService, contentService, entityService, out var foundContent); + var result = ContentPermissionsHelper.CheckPermissions(1234, user, userService, contentService, entityService, AppCaches.Disabled, out var foundContent); //assert Assert.AreEqual(ContentPermissionsHelper.ContentAccess.Granted, result); @@ -62,7 +63,7 @@ namespace Umbraco.Tests.Web.Controllers var entityService = entityServiceMock.Object; //act - var result = ContentPermissionsHelper.CheckPermissions(1234, user, userService, contentService, entityService, out var foundContent, new[] { 'F' }); + var result = ContentPermissionsHelper.CheckPermissions(1234, user, userService, contentService, entityService, AppCaches.Disabled, out var foundContent, new[] { 'F' }); //assert Assert.AreEqual(ContentPermissionsHelper.ContentAccess.NotFound, result); @@ -93,7 +94,7 @@ namespace Umbraco.Tests.Web.Controllers var entityService = entityServiceMock.Object; //act - var result = ContentPermissionsHelper.CheckPermissions(1234, user, userService, contentService, entityService, out var foundContent, new[] { 'F' }); + var result = ContentPermissionsHelper.CheckPermissions(1234, user, userService, contentService, entityService, AppCaches.Disabled, out var foundContent, new[] { 'F' }); //assert Assert.AreEqual(ContentPermissionsHelper.ContentAccess.Denied, result); @@ -124,7 +125,7 @@ namespace Umbraco.Tests.Web.Controllers var entityService = entityServiceMock.Object; //act - var result = ContentPermissionsHelper.CheckPermissions(1234, user, userService, contentService, entityService, out var foundContent, new[] { 'F' }); + var result = ContentPermissionsHelper.CheckPermissions(1234, user, userService, contentService, entityService, AppCaches.Disabled, out var foundContent, new[] { 'F' }); //assert Assert.AreEqual(ContentPermissionsHelper.ContentAccess.Denied, result); @@ -156,7 +157,7 @@ namespace Umbraco.Tests.Web.Controllers var entityService = entityServiceMock.Object; //act - var result = ContentPermissionsHelper.CheckPermissions(1234, user, userService, contentService, entityService, out var foundContent, new[] { 'F' }); + var result = ContentPermissionsHelper.CheckPermissions(1234, user, userService, contentService, entityService, AppCaches.Disabled, out var foundContent, new[] { 'F' }); //assert Assert.AreEqual(ContentPermissionsHelper.ContentAccess.Granted, result); @@ -178,7 +179,7 @@ namespace Umbraco.Tests.Web.Controllers var entityService = entityServiceMock.Object; //act - var result = ContentPermissionsHelper.CheckPermissions(-1, user, userService, contentService, entityService, out var foundContent); + var result = ContentPermissionsHelper.CheckPermissions(-1, user, userService, contentService, entityService, AppCaches.Disabled, out var foundContent); //assert Assert.AreEqual(ContentPermissionsHelper.ContentAccess.Granted, result); @@ -200,7 +201,7 @@ namespace Umbraco.Tests.Web.Controllers var entityService = entityServiceMock.Object; //act - var result = ContentPermissionsHelper.CheckPermissions(-20, user, userService, contentService, entityService, out var foundContent); + var result = ContentPermissionsHelper.CheckPermissions(-20, user, userService, contentService, entityService, AppCaches.Disabled, out var foundContent); //assert Assert.AreEqual(ContentPermissionsHelper.ContentAccess.Granted, result); @@ -224,7 +225,7 @@ namespace Umbraco.Tests.Web.Controllers var entityService = entityServiceMock.Object; //act - var result = ContentPermissionsHelper.CheckPermissions(-20, user, userService, contentService, entityService, out var foundContent); + var result = ContentPermissionsHelper.CheckPermissions(-20, user, userService, contentService, entityService, AppCaches.Disabled, out var foundContent); //assert Assert.AreEqual(ContentPermissionsHelper.ContentAccess.Denied, result); @@ -248,7 +249,7 @@ namespace Umbraco.Tests.Web.Controllers var entityService = entityServiceMock.Object; //act - var result = ContentPermissionsHelper.CheckPermissions(-1, user, userService, contentService, entityService, out var foundContent); + var result = ContentPermissionsHelper.CheckPermissions(-1, user, userService, contentService, entityService, AppCaches.Disabled, out var foundContent); //assert Assert.AreEqual(ContentPermissionsHelper.ContentAccess.Denied, result); @@ -278,7 +279,7 @@ namespace Umbraco.Tests.Web.Controllers //act - var result = ContentPermissionsHelper.CheckPermissions(-1, user, userService, contentService, entityService, out var foundContent, new[] { 'A' }); + var result = ContentPermissionsHelper.CheckPermissions(-1, user, userService, contentService, entityService, AppCaches.Disabled, out var foundContent, new[] { 'A' }); //assert Assert.AreEqual(ContentPermissionsHelper.ContentAccess.Granted, result); @@ -306,7 +307,7 @@ namespace Umbraco.Tests.Web.Controllers var contentService = contentServiceMock.Object; //act - var result = ContentPermissionsHelper.CheckPermissions(-1, user, userService, contentService, entityService, out var foundContent, new[] { 'B' }); + var result = ContentPermissionsHelper.CheckPermissions(-1, user, userService, contentService, entityService, AppCaches.Disabled, out var foundContent, new[] { 'B' }); //assert Assert.AreEqual(ContentPermissionsHelper.ContentAccess.Denied, result); @@ -336,7 +337,7 @@ namespace Umbraco.Tests.Web.Controllers var contentService = contentServiceMock.Object; //act - var result = ContentPermissionsHelper.CheckPermissions(-20, user, userService, contentService, entityService, out var foundContent, new[] { 'A' }); + var result = ContentPermissionsHelper.CheckPermissions(-20, user, userService, contentService, entityService, AppCaches.Disabled, out var foundContent, new[] { 'A' }); //assert Assert.AreEqual(ContentPermissionsHelper.ContentAccess.Granted, result); @@ -364,7 +365,7 @@ namespace Umbraco.Tests.Web.Controllers var contentService = contentServiceMock.Object; //act - var result = ContentPermissionsHelper.CheckPermissions(-20, user, userService, contentService, entityService, out var foundContent, new[] { 'B' }); + var result = ContentPermissionsHelper.CheckPermissions(-20, user, userService, contentService, entityService, AppCaches.Disabled, out var foundContent, new[] { 'B' }); //assert Assert.AreEqual(ContentPermissionsHelper.ContentAccess.Denied, result); diff --git a/src/Umbraco.Tests/Web/Controllers/MediaControllerUnitTests.cs b/src/Umbraco.Tests/Web/Controllers/MediaControllerUnitTests.cs index f409d81a2d..c347d81288 100644 --- a/src/Umbraco.Tests/Web/Controllers/MediaControllerUnitTests.cs +++ b/src/Umbraco.Tests/Web/Controllers/MediaControllerUnitTests.cs @@ -2,6 +2,7 @@ using System.Web.Http; using Moq; using NUnit.Framework; +using Umbraco.Core.Cache; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Core.Models.Membership; @@ -31,7 +32,7 @@ namespace Umbraco.Tests.Web.Controllers var entityService = entityServiceMock.Object; //act - var result = MediaController.CheckPermissions(new Dictionary(), user, mediaService, entityService, 1234); + var result = MediaController.CheckPermissions(new Dictionary(), user, mediaService, entityService, AppCaches.Disabled, 1234); //assert Assert.IsTrue(result); @@ -54,7 +55,7 @@ namespace Umbraco.Tests.Web.Controllers var entityService = entityServiceMock.Object; //act/assert - Assert.Throws(() => MediaController.CheckPermissions(new Dictionary(), user, mediaService, entityService, 1234)); + Assert.Throws(() => MediaController.CheckPermissions(new Dictionary(), user, mediaService, entityService, AppCaches.Disabled, 1234)); } [Test] @@ -77,7 +78,7 @@ namespace Umbraco.Tests.Web.Controllers var entityService = entityServiceMock.Object; //act - var result = MediaController.CheckPermissions(new Dictionary(), user, mediaService, entityService, 1234); + var result = MediaController.CheckPermissions(new Dictionary(), user, mediaService, entityService, AppCaches.Disabled, 1234); //assert Assert.IsFalse(result); @@ -97,7 +98,7 @@ namespace Umbraco.Tests.Web.Controllers var entityService = entityServiceMock.Object; //act - var result = MediaController.CheckPermissions(new Dictionary(), user, mediaService, entityService, -1); + var result = MediaController.CheckPermissions(new Dictionary(), user, mediaService, entityService, AppCaches.Disabled, -1); //assert Assert.IsTrue(result); @@ -119,7 +120,7 @@ namespace Umbraco.Tests.Web.Controllers var entityService = entityServiceMock.Object; //act - var result = MediaController.CheckPermissions(new Dictionary(), user, mediaService, entityService, -1); + var result = MediaController.CheckPermissions(new Dictionary(), user, mediaService, entityService, AppCaches.Disabled, -1); //assert Assert.IsFalse(result); @@ -139,7 +140,7 @@ namespace Umbraco.Tests.Web.Controllers var entityService = entityServiceMock.Object; //act - var result = MediaController.CheckPermissions(new Dictionary(), user, mediaService, entityService, -21); + var result = MediaController.CheckPermissions(new Dictionary(), user, mediaService, entityService, AppCaches.Disabled, -21); //assert Assert.IsTrue(result); @@ -161,7 +162,7 @@ namespace Umbraco.Tests.Web.Controllers var entityService = entityServiceMock.Object; //act - var result = MediaController.CheckPermissions(new Dictionary(), user, mediaService, entityService, -21); + var result = MediaController.CheckPermissions(new Dictionary(), user, mediaService, entityService, AppCaches.Disabled, -21); //assert Assert.IsFalse(result); diff --git a/src/Umbraco.Tests/Web/Controllers/UserEditorAuthorizationHelperTests.cs b/src/Umbraco.Tests/Web/Controllers/UserEditorAuthorizationHelperTests.cs index 04694b21ee..4478b59085 100644 --- a/src/Umbraco.Tests/Web/Controllers/UserEditorAuthorizationHelperTests.cs +++ b/src/Umbraco.Tests/Web/Controllers/UserEditorAuthorizationHelperTests.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using Moq; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Core.Models.Membership; @@ -32,7 +33,8 @@ namespace Umbraco.Tests.Web.Controllers contentService.Object, mediaService.Object, userService.Object, - entityService.Object); + entityService.Object, + AppCaches.Disabled); var result = authHelper.IsAuthorized(currentUser, savingUser, new int[0], new int[0], new string[0]); @@ -54,7 +56,8 @@ namespace Umbraco.Tests.Web.Controllers contentService.Object, mediaService.Object, userService.Object, - entityService.Object); + entityService.Object, + AppCaches.Disabled); var result = authHelper.IsAuthorized(currentUser, savingUser, new int[0], new int[0], new string[0]); @@ -79,7 +82,8 @@ namespace Umbraco.Tests.Web.Controllers contentService.Object, mediaService.Object, userService.Object, - entityService.Object); + entityService.Object, + AppCaches.Disabled); var result = authHelper.IsAuthorized(currentUser, savingUser, new int[0], new int[0], new[] {"FunGroup"}); @@ -104,7 +108,8 @@ namespace Umbraco.Tests.Web.Controllers contentService.Object, mediaService.Object, userService.Object, - entityService.Object); + entityService.Object, + AppCaches.Disabled); var result = authHelper.IsAuthorized(currentUser, savingUser, new int[0], new int[0], new[] { "test" }); @@ -141,7 +146,8 @@ namespace Umbraco.Tests.Web.Controllers contentService.Object, mediaService.Object, userService.Object, - entityService.Object); + entityService.Object, + AppCaches.Disabled); //adding 5555 which currentUser has access to since it's a child of 9876 ... adding is still ok even though currentUser doesn't have access to 1234 var result = authHelper.IsAuthorized(currentUser, savingUser, new[] { 1234, 5555 }, new int[0], new string[0]); @@ -179,7 +185,8 @@ namespace Umbraco.Tests.Web.Controllers contentService.Object, mediaService.Object, userService.Object, - entityService.Object); + entityService.Object, + AppCaches.Disabled); //removing 4567 start node even though currentUser doesn't have acces to it ... removing is ok var result = authHelper.IsAuthorized(currentUser, savingUser, new[] { 1234 }, new int[0], new string[0]); @@ -217,7 +224,8 @@ namespace Umbraco.Tests.Web.Controllers contentService.Object, mediaService.Object, userService.Object, - entityService.Object); + entityService.Object, + AppCaches.Disabled); //adding 1234 but currentUser doesn't have access to it ... nope var result = authHelper.IsAuthorized(currentUser, savingUser, new []{1234}, new int[0], new string[0]); @@ -255,7 +263,8 @@ namespace Umbraco.Tests.Web.Controllers contentService.Object, mediaService.Object, userService.Object, - entityService.Object); + entityService.Object, + AppCaches.Disabled); //adding 5555 which currentUser has access to since it's a child of 9876 ... ok var result = authHelper.IsAuthorized(currentUser, savingUser, new[] { 5555 }, new int[0], new string[0]); @@ -293,7 +302,8 @@ namespace Umbraco.Tests.Web.Controllers contentService.Object, mediaService.Object, userService.Object, - entityService.Object); + entityService.Object, + AppCaches.Disabled); //adding 1234 but currentUser doesn't have access to it ... nope var result = authHelper.IsAuthorized(currentUser, savingUser, new int[0], new[] {1234}, new string[0]); @@ -331,7 +341,8 @@ namespace Umbraco.Tests.Web.Controllers contentService.Object, mediaService.Object, userService.Object, - entityService.Object); + entityService.Object, + AppCaches.Disabled); //adding 5555 which currentUser has access to since it's a child of 9876 ... ok var result = authHelper.IsAuthorized(currentUser, savingUser, new int[0], new[] { 5555 }, new string[0]); @@ -369,7 +380,8 @@ namespace Umbraco.Tests.Web.Controllers contentService.Object, mediaService.Object, userService.Object, - entityService.Object); + entityService.Object, + AppCaches.Disabled); //adding 5555 which currentUser has access to since it's a child of 9876 ... adding is still ok even though currentUser doesn't have access to 1234 var result = authHelper.IsAuthorized(currentUser, savingUser, new int[0], new[] { 1234, 5555 }, new string[0]); @@ -407,7 +419,8 @@ namespace Umbraco.Tests.Web.Controllers contentService.Object, mediaService.Object, userService.Object, - entityService.Object); + entityService.Object, + AppCaches.Disabled); //removing 4567 start node even though currentUser doesn't have acces to it ... removing is ok var result = authHelper.IsAuthorized(currentUser, savingUser, new int[0], new[] { 1234 }, new string[0]); diff --git a/src/Umbraco.Web/Cache/UserCacheRefresher.cs b/src/Umbraco.Web/Cache/UserCacheRefresher.cs index 922a9df385..ce2cbbf754 100644 --- a/src/Umbraco.Web/Cache/UserCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/UserCacheRefresher.cs @@ -42,7 +42,14 @@ namespace Umbraco.Web.Cache { var userCache = AppCaches.IsolatedCaches.Get(); if (userCache) + { userCache.Result.Clear(RepositoryCacheKeys.GetKey(id)); + userCache.Result.ClearByKey(CacheKeys.UserContentStartNodePathsPrefix + id); + userCache.Result.ClearByKey(CacheKeys.UserMediaStartNodePathsPrefix + id); + userCache.Result.ClearByKey(CacheKeys.UserAllContentStartNodesPrefix + id); + userCache.Result.ClearByKey(CacheKeys.UserAllMediaStartNodesPrefix + id); + } + base.Remove(id); } diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 6f85a08751..7093edf23d 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -1168,7 +1168,7 @@ namespace Umbraco.Web.Editors //if this item's path has already been denied or if the user doesn't have access to it, add to the deny list if (denied.Any(x => c.Path.StartsWith($"{x.Path},")) || (ContentPermissionsHelper.CheckPermissions(c, - Security.CurrentUser, Services.UserService, Services.EntityService, + Security.CurrentUser, Services.UserService, Services.EntityService, AppCaches, ActionPublish.ActionLetter) == ContentPermissionsHelper.ContentAccess.Denied)) { denied.Add(c); diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index 65d9305906..456e0e20f2 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -670,9 +670,9 @@ namespace Umbraco.Web.Editors switch (type) { case UmbracoEntityTypes.Document: - return Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService); + return Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService, AppCaches); case UmbracoEntityTypes.Media: - return Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService); + return Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService, AppCaches); default: return Array.Empty(); } @@ -811,10 +811,10 @@ namespace Umbraco.Web.Editors switch (entityType) { case UmbracoEntityTypes.Document: - aids = Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService); + aids = Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService, AppCaches); break; case UmbracoEntityTypes.Media: - aids = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService); + aids = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService, AppCaches); break; } diff --git a/src/Umbraco.Web/Editors/Filters/ContentSaveValidationAttribute.cs b/src/Umbraco.Web/Editors/Filters/ContentSaveValidationAttribute.cs index fbce2d0414..c0e190989a 100644 --- a/src/Umbraco.Web/Editors/Filters/ContentSaveValidationAttribute.cs +++ b/src/Umbraco.Web/Editors/Filters/ContentSaveValidationAttribute.cs @@ -7,6 +7,7 @@ using System.Web.Http; using System.Web.Http.Controllers; using System.Web.Http.Filters; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Security; @@ -29,11 +30,12 @@ namespace Umbraco.Web.Editors.Filters private readonly IContentService _contentService; private readonly IUserService _userService; private readonly IEntityService _entityService; + private readonly AppCaches _appCaches; - 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, Current.Services.TextService, Current.Services.ContentService, Current.Services.UserService, Current.Services.EntityService, Current.AppCaches) { } - public ContentSaveValidationAttribute(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, ILocalizedTextService textService, IContentService contentService, IUserService userService, IEntityService entityService) + public ContentSaveValidationAttribute(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, ILocalizedTextService textService, IContentService contentService, IUserService userService, IEntityService entityService, AppCaches appCaches) { _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor)); @@ -41,6 +43,7 @@ namespace Umbraco.Web.Editors.Filters _contentService = contentService ?? throw new ArgumentNullException(nameof(contentService)); _userService = userService ?? throw new ArgumentNullException(nameof(userService)); _entityService = entityService ?? throw new ArgumentNullException(nameof(entityService)); + _appCaches = appCaches; } public override void OnActionExecuting(HttpActionContext actionContext) @@ -195,13 +198,13 @@ namespace Umbraco.Web.Editors.Filters accessResult = ContentPermissionsHelper.CheckPermissions( contentToCheck, webSecurity.CurrentUser, - _userService, _entityService, permissionToCheck.ToArray()); + _userService, _entityService, _appCaches, permissionToCheck.ToArray()); } else { accessResult = ContentPermissionsHelper.CheckPermissions( contentIdToCheck, webSecurity.CurrentUser, - _userService, _contentService, _entityService, + _userService, _contentService, _entityService, _appCaches, out contentToCheck, permissionToCheck.ToArray()); if (contentToCheck != null) diff --git a/src/Umbraco.Web/Editors/Filters/MediaItemSaveValidationAttribute.cs b/src/Umbraco.Web/Editors/Filters/MediaItemSaveValidationAttribute.cs index 449ef95675..af973d0662 100644 --- a/src/Umbraco.Web/Editors/Filters/MediaItemSaveValidationAttribute.cs +++ b/src/Umbraco.Web/Editors/Filters/MediaItemSaveValidationAttribute.cs @@ -4,6 +4,7 @@ using System.Net.Http; using System.Web.Http.Controllers; using System.Web.Http.Filters; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Services; @@ -23,18 +24,20 @@ namespace Umbraco.Web.Editors.Filters private readonly ILocalizedTextService _textService; private readonly IMediaService _mediaService; private readonly IEntityService _entityService; + private readonly AppCaches _appCaches; - public MediaItemSaveValidationAttribute() : this(Current.Logger, Current.UmbracoContextAccessor, Current.Services.TextService, Current.Services.MediaService, Current.Services.EntityService) + public MediaItemSaveValidationAttribute() : this(Current.Logger, Current.UmbracoContextAccessor, Current.Services.TextService, Current.Services.MediaService, Current.Services.EntityService, Current.AppCaches) { } - public MediaItemSaveValidationAttribute(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, ILocalizedTextService textService, IMediaService mediaService, IEntityService entityService) + public MediaItemSaveValidationAttribute(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, ILocalizedTextService textService, IMediaService mediaService, IEntityService entityService, AppCaches appCaches) { _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor)); _textService = textService ?? throw new ArgumentNullException(nameof(textService)); _mediaService = mediaService ?? throw new ArgumentNullException(nameof(mediaService)); _entityService = entityService ?? throw new ArgumentNullException(nameof(entityService)); + _appCaches = appCaches; } public override void OnActionExecuting(HttpActionContext actionContext) @@ -91,7 +94,7 @@ namespace Umbraco.Web.Editors.Filters if (MediaController.CheckPermissions( actionContext.Request.Properties, _umbracoContextAccessor.UmbracoContext.Security.CurrentUser, - _mediaService, _entityService, + _mediaService, _entityService, _appCaches, contentIdToCheck, contentToCheck) == false) { actionContext.Response = actionContext.Request.CreateUserNoAccessResponse(); diff --git a/src/Umbraco.Web/Editors/Filters/UserGroupAuthorizationAttribute.cs b/src/Umbraco.Web/Editors/Filters/UserGroupAuthorizationAttribute.cs index e1d6626055..e94098db82 100644 --- a/src/Umbraco.Web/Editors/Filters/UserGroupAuthorizationAttribute.cs +++ b/src/Umbraco.Web/Editors/Filters/UserGroupAuthorizationAttribute.cs @@ -54,7 +54,8 @@ namespace Umbraco.Web.Editors.Filters Current.Services.UserService, Current.Services.ContentService, Current.Services.MediaService, - Current.Services.EntityService); + Current.Services.EntityService, + Current.AppCaches); return authHelper.AuthorizeGroupAccess(currentUser, intIds); } } diff --git a/src/Umbraco.Web/Editors/Filters/UserGroupEditorAuthorizationHelper.cs b/src/Umbraco.Web/Editors/Filters/UserGroupEditorAuthorizationHelper.cs index 985c42bbbf..ea403758d0 100644 --- a/src/Umbraco.Web/Editors/Filters/UserGroupEditorAuthorizationHelper.cs +++ b/src/Umbraco.Web/Editors/Filters/UserGroupEditorAuthorizationHelper.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; using Umbraco.Core.Services; @@ -13,13 +14,15 @@ namespace Umbraco.Web.Editors.Filters private readonly IContentService _contentService; private readonly IMediaService _mediaService; private readonly IEntityService _entityService; + private readonly AppCaches _appCaches; - public UserGroupEditorAuthorizationHelper(IUserService userService, IContentService contentService, IMediaService mediaService, IEntityService entityService) + public UserGroupEditorAuthorizationHelper(IUserService userService, IContentService contentService, IMediaService mediaService, IEntityService entityService, AppCaches appCaches) { _userService = userService; _contentService = contentService; _mediaService = mediaService; _entityService = entityService; + _appCaches = appCaches; } /// @@ -111,7 +114,7 @@ namespace Umbraco.Web.Editors.Filters var content = _contentService.GetById(proposedContentStartId.Value); if (content != null) { - if (currentUser.HasPathAccess(content, _entityService) == false) + if (currentUser.HasPathAccess(content, _entityService, _appCaches) == false) return Attempt.Fail("Current user doesn't have access to the content path " + content.Path); } } @@ -121,7 +124,7 @@ namespace Umbraco.Web.Editors.Filters var media = _mediaService.GetById(proposedMediaStartId.Value); if (media != null) { - if (currentUser.HasPathAccess(media, _entityService) == false) + if (currentUser.HasPathAccess(media, _entityService, _appCaches) == false) return Attempt.Fail("Current user doesn't have access to the media path " + media.Path); } } diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index dfe6939552..022512e2a4 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -239,7 +239,7 @@ namespace Umbraco.Web.Editors protected int[] UserStartNodes { - get { return _userStartNodes ?? (_userStartNodes = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService)); } + get { return _userStartNodes ?? (_userStartNodes = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService, AppCaches)); } } /// @@ -835,6 +835,7 @@ namespace Umbraco.Web.Editors Security.CurrentUser, Services.MediaService, Services.EntityService, + AppCaches, intParentId) == false) { throw new HttpResponseException(Request.CreateResponse( @@ -919,7 +920,7 @@ namespace Umbraco.Web.Editors /// The content to lookup, if the contentItem is not specified /// Specifies the already resolved content item to check against, setting this ignores the nodeId /// - internal static bool CheckPermissions(IDictionary storage, IUser user, IMediaService mediaService, IEntityService entityService, int nodeId, IMedia media = null) + internal static bool CheckPermissions(IDictionary storage, IUser user, IMediaService mediaService, IEntityService entityService, AppCaches appCaches, int nodeId, IMedia media = null) { if (storage == null) throw new ArgumentNullException("storage"); if (user == null) throw new ArgumentNullException("user"); @@ -940,10 +941,10 @@ namespace Umbraco.Web.Editors } var hasPathAccess = (nodeId == Constants.System.Root) - ? user.HasMediaRootAccess(entityService) + ? user.HasMediaRootAccess(entityService, appCaches) : (nodeId == Constants.System.RecycleBinMedia) - ? user.HasMediaBinAccess(entityService) - : user.HasPathAccess(media, entityService); + ? user.HasMediaBinAccess(entityService, appCaches) + : user.HasPathAccess(media, entityService, appCaches); return hasPathAccess; } diff --git a/src/Umbraco.Web/Editors/UserEditorAuthorizationHelper.cs b/src/Umbraco.Web/Editors/UserEditorAuthorizationHelper.cs index 320580aaf9..f666b6d5a3 100644 --- a/src/Umbraco.Web/Editors/UserEditorAuthorizationHelper.cs +++ b/src/Umbraco.Web/Editors/UserEditorAuthorizationHelper.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; using Umbraco.Core.Security; @@ -14,13 +15,15 @@ namespace Umbraco.Web.Editors private readonly IMediaService _mediaService; private readonly IUserService _userService; private readonly IEntityService _entityService; + private readonly AppCaches _appCaches; - public UserEditorAuthorizationHelper(IContentService contentService, IMediaService mediaService, IUserService userService, IEntityService entityService) + public UserEditorAuthorizationHelper(IContentService contentService, IMediaService mediaService, IUserService userService, IEntityService entityService, AppCaches appCaches) { _contentService = contentService; _mediaService = mediaService; _userService = userService; _entityService = entityService; + _appCaches = appCaches; } /// @@ -114,7 +117,7 @@ namespace Umbraco.Web.Editors { if (contentId == Constants.System.Root) { - var hasAccess = ContentPermissionsHelper.HasPathAccess("-1", currentUser.CalculateContentStartNodeIds(_entityService), Constants.System.RecycleBinContent); + var hasAccess = ContentPermissionsHelper.HasPathAccess("-1", currentUser.CalculateContentStartNodeIds(_entityService, _appCaches), Constants.System.RecycleBinContent); if (hasAccess == false) return Attempt.Fail("The current user does not have access to the content root"); } @@ -122,7 +125,7 @@ namespace Umbraco.Web.Editors { var content = _contentService.GetById(contentId); if (content == null) continue; - var hasAccess = currentUser.HasPathAccess(content, _entityService); + var hasAccess = currentUser.HasPathAccess(content, _entityService, _appCaches); if (hasAccess == false) return Attempt.Fail("The current user does not have access to the content path " + content.Path); } @@ -135,7 +138,7 @@ namespace Umbraco.Web.Editors { if (mediaId == Constants.System.Root) { - var hasAccess = ContentPermissionsHelper.HasPathAccess("-1", currentUser.CalculateMediaStartNodeIds(_entityService), Constants.System.RecycleBinMedia); + var hasAccess = ContentPermissionsHelper.HasPathAccess("-1", currentUser.CalculateMediaStartNodeIds(_entityService, _appCaches), Constants.System.RecycleBinMedia); if (hasAccess == false) return Attempt.Fail("The current user does not have access to the media root"); } @@ -143,7 +146,7 @@ namespace Umbraco.Web.Editors { var media = _mediaService.GetById(mediaId); if (media == null) continue; - var hasAccess = currentUser.HasPathAccess(media, _entityService); + var hasAccess = currentUser.HasPathAccess(media, _entityService, _appCaches); if (hasAccess == false) return Attempt.Fail("The current user does not have access to the media path " + media.Path); } diff --git a/src/Umbraco.Web/Editors/UserGroupsController.cs b/src/Umbraco.Web/Editors/UserGroupsController.cs index b081ca6137..77f7a305af 100644 --- a/src/Umbraco.Web/Editors/UserGroupsController.cs +++ b/src/Umbraco.Web/Editors/UserGroupsController.cs @@ -28,7 +28,11 @@ namespace Umbraco.Web.Editors //authorize that the user has access to save this user group var authHelper = new UserGroupEditorAuthorizationHelper( - Services.UserService, Services.ContentService, Services.MediaService, Services.EntityService); + Services.UserService, + Services.ContentService, + Services.MediaService, + Services.EntityService, + AppCaches); var isAuthorized = authHelper.AuthorizeGroupAccess(Security.CurrentUser, userGroupSave.Alias); if (isAuthorized == false) diff --git a/src/Umbraco.Web/Editors/UsersController.cs b/src/Umbraco.Web/Editors/UsersController.cs index b022e6f27a..21a32ef12f 100644 --- a/src/Umbraco.Web/Editors/UsersController.cs +++ b/src/Umbraco.Web/Editors/UsersController.cs @@ -304,7 +304,7 @@ namespace Umbraco.Web.Editors CheckUniqueEmail(userSave.Email, null); //Perform authorization here to see if the current user can actually save this user with the info being requested - var authHelper = new UserEditorAuthorizationHelper(Services.ContentService, Services.MediaService, Services.UserService, Services.EntityService); + var authHelper = new UserEditorAuthorizationHelper(Services.ContentService, Services.MediaService, Services.UserService, Services.EntityService, AppCaches); var canSaveUser = authHelper.IsAuthorized(Security.CurrentUser, null, null, null, userSave.UserGroups); if (canSaveUser == false) { @@ -398,7 +398,7 @@ namespace Umbraco.Web.Editors } //Perform authorization here to see if the current user can actually save this user with the info being requested - var authHelper = new UserEditorAuthorizationHelper(Services.ContentService, Services.MediaService, Services.UserService, Services.EntityService); + var authHelper = new UserEditorAuthorizationHelper(Services.ContentService, Services.MediaService, Services.UserService, Services.EntityService, AppCaches); var canSaveUser = authHelper.IsAuthorized(Security.CurrentUser, user, null, null, userSave.UserGroups); if (canSaveUser == false) { @@ -573,7 +573,7 @@ namespace Umbraco.Web.Editors throw new HttpResponseException(HttpStatusCode.NotFound); //Perform authorization here to see if the current user can actually save this user with the info being requested - var authHelper = new UserEditorAuthorizationHelper(Services.ContentService, Services.MediaService, Services.UserService, Services.EntityService); + var authHelper = new UserEditorAuthorizationHelper(Services.ContentService, Services.MediaService, Services.UserService, Services.EntityService, AppCaches); var canSaveUser = authHelper.IsAuthorized(Security.CurrentUser, found, userSave.StartContentIds, userSave.StartMediaIds, userSave.UserGroups); if (canSaveUser == false) { diff --git a/src/Umbraco.Web/Models/Mapping/ContentMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/ContentMapDefinition.cs index 983172f8e1..ec3367d4d3 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentMapDefinition.cs @@ -2,6 +2,8 @@ using System.Collections.Generic; using System.Linq; using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Composing; using Umbraco.Core.Logging; using Umbraco.Core.Mapping; using Umbraco.Core.Models; @@ -29,14 +31,25 @@ namespace Umbraco.Web.Models.Mapping private readonly ILogger _logger; private readonly IUserService _userService; private readonly IEntityService _entityService; + private readonly AppCaches _appCaches; private readonly TabsAndPropertiesMapper _tabsAndPropertiesMapper; private readonly ContentSavedStateMapper _stateMapper; private readonly ContentBasicSavedStateMapper _basicStateMapper; private readonly ContentVariantMapper _contentVariantMapper; - public ContentMapDefinition(CommonMapper commonMapper, ILocalizedTextService localizedTextService, IContentService contentService, IContentTypeService contentTypeService, - IFileService fileService, IUmbracoContextAccessor umbracoContextAccessor, IPublishedRouter publishedRouter, ILocalizationService localizationService, ILogger logger, - IUserService userService, IEntityService entityService) + public ContentMapDefinition( + CommonMapper commonMapper, + ILocalizedTextService localizedTextService, + IContentService contentService, + IContentTypeService contentTypeService, + IFileService fileService, + IUmbracoContextAccessor umbracoContextAccessor, + IPublishedRouter publishedRouter, + ILocalizationService localizationService, + ILogger logger, + IUserService userService, + IEntityService entityService, + AppCaches appCaches) { _commonMapper = commonMapper; _localizedTextService = localizedTextService; @@ -49,6 +62,7 @@ namespace Umbraco.Web.Models.Mapping _logger = logger; _userService = userService; _entityService = entityService; + _appCaches = appCaches; _tabsAndPropertiesMapper = new TabsAndPropertiesMapper(localizedTextService); _stateMapper = new ContentSavedStateMapper(); _basicStateMapper = new ContentBasicSavedStateMapper(); @@ -238,7 +252,7 @@ namespace Umbraco.Web.Models.Mapping // false here. if (context.HasItems && context.Items.TryGetValue("CurrentUser", out var usr) && usr is IUser currentUser) { - userStartNodes = currentUser.CalculateContentStartNodeIds(_entityService); + userStartNodes = currentUser.CalculateContentStartNodeIds(_entityService, _appCaches); if (!userStartNodes.Contains(Constants.System.Root)) { // return false if this is the user's actual start node, the node will be rendered in the tree diff --git a/src/Umbraco.Web/Models/Mapping/UserMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/UserMapDefinition.cs index aa158799cb..6c58309f57 100644 --- a/src/Umbraco.Web/Models/Mapping/UserMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/UserMapDefinition.cs @@ -273,8 +273,8 @@ namespace Umbraco.Web.Models.Mapping { target.AvailableCultures = _textService.GetSupportedCultures().ToDictionary(x => x.Name, x => x.DisplayName); target.Avatars = source.GetUserAvatarUrls(_appCaches.RuntimeCache); - target.CalculatedStartContentIds = GetStartNodes(source.CalculateContentStartNodeIds(_entityService), UmbracoObjectTypes.Document, "content/contentRoot", context); - target.CalculatedStartMediaIds = GetStartNodes(source.CalculateMediaStartNodeIds(_entityService), UmbracoObjectTypes.Media, "media/mediaRoot", context); + target.CalculatedStartContentIds = GetStartNodes(source.CalculateContentStartNodeIds(_entityService, _appCaches), UmbracoObjectTypes.Document, "content/contentRoot", context); + target.CalculatedStartMediaIds = GetStartNodes(source.CalculateMediaStartNodeIds(_entityService, _appCaches), UmbracoObjectTypes.Media, "media/mediaRoot", context); target.CreateDate = source.CreateDate; target.Culture = source.GetUserCulture(_textService, _globalSettings).ToString(); target.Email = source.Email; @@ -327,8 +327,8 @@ namespace Umbraco.Web.Models.Mapping target.Email = source.Email; target.EmailHash = source.Email.ToLowerInvariant().Trim().GenerateHash(); target.Name = source.Name; - target.StartContentIds = source.CalculateContentStartNodeIds(_entityService); - target.StartMediaIds = source.CalculateMediaStartNodeIds(_entityService); + target.StartContentIds = source.CalculateContentStartNodeIds(_entityService, _appCaches); + target.StartMediaIds = source.CalculateMediaStartNodeIds(_entityService, _appCaches); target.UserId = source.Id; //we need to map the legacy UserType diff --git a/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs b/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs index a22e2a6f6a..6d777287d6 100644 --- a/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs +++ b/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs @@ -5,6 +5,8 @@ using System.Text; using System.Text.RegularExpressions; using Examine; using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Composing; using Umbraco.Core.Mapping; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; @@ -30,14 +32,29 @@ namespace Umbraco.Web.Search private readonly UmbracoMapper _mapper; private readonly ISqlContext _sqlContext; private readonly IUmbracoTreeSearcherFields _umbracoTreeSearcherFields; + private readonly AppCaches _appCaches; - - public UmbracoTreeSearcher(IExamineManager examineManager, + [Obsolete("Use constructor specifying all dependencies instead")] + public UmbracoTreeSearcher( + IExamineManager examineManager, UmbracoContext umbracoContext, ILocalizationService languageService, IEntityService entityService, UmbracoMapper mapper, - ISqlContext sqlContext,IUmbracoTreeSearcherFields umbracoTreeSearcherFields) + ISqlContext sqlContext, + IUmbracoTreeSearcherFields umbracoTreeSearcherFields) + : this(examineManager, umbracoContext, languageService, entityService, mapper, sqlContext, umbracoTreeSearcherFields, Current.AppCaches) + { } + + public UmbracoTreeSearcher( + IExamineManager examineManager, + UmbracoContext umbracoContext, + ILocalizationService languageService, + IEntityService entityService, + UmbracoMapper mapper, + ISqlContext sqlContext, + IUmbracoTreeSearcherFields umbracoTreeSearcherFields, + AppCaches appCaches) { _examineManager = examineManager ?? throw new ArgumentNullException(nameof(examineManager)); _umbracoContext = umbracoContext; @@ -46,6 +63,7 @@ namespace Umbraco.Web.Search _mapper = mapper; _sqlContext = sqlContext; _umbracoTreeSearcherFields = umbracoTreeSearcherFields; + _appCaches = appCaches; } /// @@ -112,13 +130,13 @@ namespace Umbraco.Web.Search case UmbracoEntityTypes.Media: type = "media"; fields.AddRange(_umbracoTreeSearcherFields.GetBackOfficeMediaFields()); - var allMediaStartNodes = _umbracoContext.Security.CurrentUser.CalculateMediaStartNodeIds(_entityService); + var allMediaStartNodes = _umbracoContext.Security.CurrentUser.CalculateMediaStartNodeIds(_entityService, _appCaches); AppendPath(sb, UmbracoObjectTypes.Media, allMediaStartNodes, searchFrom, ignoreUserStartNodes, _entityService); break; case UmbracoEntityTypes.Document: type = "content"; fields.AddRange(_umbracoTreeSearcherFields.GetBackOfficeDocumentFields()); - var allContentStartNodes = _umbracoContext.Security.CurrentUser.CalculateContentStartNodeIds(_entityService); + var allContentStartNodes = _umbracoContext.Security.CurrentUser.CalculateContentStartNodeIds(_entityService, _appCaches); AppendPath(sb, UmbracoObjectTypes.Document, allContentStartNodes, searchFrom, ignoreUserStartNodes, _entityService); break; default: @@ -462,11 +480,13 @@ namespace Umbraco.Web.Search var defaultLang = _languageService.GetDefaultLanguageIsoCode(); foreach (var result in results) { - var entity = _mapper.Map(result, context => { - if(culture != null) { - context.SetCulture(culture); - } + var entity = _mapper.Map(result, context => + { + if (culture != null) + { + context.SetCulture(culture); } + } ); var intId = entity.Id.TryConvertTo(); diff --git a/src/Umbraco.Web/Trees/ContentTreeController.cs b/src/Umbraco.Web/Trees/ContentTreeController.cs index 663af43643..4bf5be4008 100644 --- a/src/Umbraco.Web/Trees/ContentTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTreeController.cs @@ -47,7 +47,7 @@ namespace Umbraco.Web.Trees private int[] _userStartNodes; protected override int[] UserStartNodes - => _userStartNodes ?? (_userStartNodes = Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService)); + => _userStartNodes ?? (_userStartNodes = Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService, AppCaches)); public ContentTreeController(UmbracoTreeSearcher treeSearcher, ActionCollection actions, IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper) : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper) { @@ -165,7 +165,7 @@ namespace Umbraco.Web.Trees } //if the user has no path access for this node, all they can do is refresh - if (!Security.CurrentUser.HasContentPathAccess(item, Services.EntityService)) + if (!Security.CurrentUser.HasContentPathAccess(item, Services.EntityService, AppCaches)) { var menu = new MenuItemCollection(); menu.Items.Add(new RefreshNode(Services.TextService, true)); diff --git a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs index 95de72b7bf..afb38dea07 100644 --- a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs @@ -126,12 +126,12 @@ namespace Umbraco.Web.Trees switch (RecycleBinId) { case Constants.System.RecycleBinMedia: - startNodeIds = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService); - startNodePaths = Security.CurrentUser.GetMediaStartNodePaths(Services.EntityService); + startNodeIds = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService, AppCaches); + startNodePaths = Security.CurrentUser.GetMediaStartNodePaths(Services.EntityService, AppCaches); break; case Constants.System.RecycleBinContent: - startNodeIds = Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService); - startNodePaths = Security.CurrentUser.GetContentStartNodePaths(Services.EntityService); + startNodeIds = Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService, AppCaches); + startNodePaths = Security.CurrentUser.GetContentStartNodePaths(Services.EntityService, AppCaches); break; default: throw new NotSupportedException("Path access is only determined on content or media"); @@ -291,8 +291,8 @@ namespace Umbraco.Web.Trees { if (entity == null) return false; return RecycleBinId == Constants.System.RecycleBinContent - ? Security.CurrentUser.HasContentPathAccess(entity, Services.EntityService) - : Security.CurrentUser.HasMediaPathAccess(entity, Services.EntityService); + ? Security.CurrentUser.HasContentPathAccess(entity, Services.EntityService, AppCaches) + : Security.CurrentUser.HasMediaPathAccess(entity, Services.EntityService, AppCaches); } /// diff --git a/src/Umbraco.Web/Trees/MediaTreeController.cs b/src/Umbraco.Web/Trees/MediaTreeController.cs index df44a809c9..2ad3c2af0a 100644 --- a/src/Umbraco.Web/Trees/MediaTreeController.cs +++ b/src/Umbraco.Web/Trees/MediaTreeController.cs @@ -50,7 +50,7 @@ namespace Umbraco.Web.Trees private int[] _userStartNodes; protected override int[] UserStartNodes - => _userStartNodes ?? (_userStartNodes = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService)); + => _userStartNodes ?? (_userStartNodes = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService, AppCaches)); /// /// Creates a tree node for a content item based on an UmbracoEntity @@ -117,7 +117,7 @@ namespace Umbraco.Web.Trees } //if the user has no path access for this node, all they can do is refresh - if (!Security.CurrentUser.HasMediaPathAccess(item, Services.EntityService)) + if (!Security.CurrentUser.HasMediaPathAccess(item, Services.EntityService, AppCaches)) { menu.Items.Add(new RefreshNode(Services.TextService, true)); return menu; diff --git a/src/Umbraco.Web/WebApi/Filters/AdminUsersAuthorizeAttribute.cs b/src/Umbraco.Web/WebApi/Filters/AdminUsersAuthorizeAttribute.cs index 96226622b0..3494a5c0c7 100644 --- a/src/Umbraco.Web/WebApi/Filters/AdminUsersAuthorizeAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/AdminUsersAuthorizeAttribute.cs @@ -50,7 +50,7 @@ namespace Umbraco.Web.WebApi.Filters if (userIds.Length == 0) return base.IsAuthorized(actionContext); var users = Current.Services.UserService.GetUsersById(userIds); - var authHelper = new UserEditorAuthorizationHelper(Current.Services.ContentService, Current.Services.MediaService, Current.Services.UserService, Current.Services.EntityService); + var authHelper = new UserEditorAuthorizationHelper(Current.Services.ContentService, Current.Services.MediaService, Current.Services.UserService, Current.Services.EntityService, Current.AppCaches); return users.All(user => authHelper.IsAuthorized(Current.UmbracoContext.Security.CurrentUser, user, null, null, null) != false); } } diff --git a/src/Umbraco.Web/WebApi/Filters/CheckIfUserTicketDataIsStaleAttribute.cs b/src/Umbraco.Web/WebApi/Filters/CheckIfUserTicketDataIsStaleAttribute.cs index 2a57ec10b2..3bbee7ca41 100644 --- a/src/Umbraco.Web/WebApi/Filters/CheckIfUserTicketDataIsStaleAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/CheckIfUserTicketDataIsStaleAttribute.cs @@ -85,12 +85,12 @@ namespace Umbraco.Web.WebApi.Filters () => user.Groups.Select(x => x.Alias).UnsortedSequenceEqual(identity.Roles) == false, () => { - var startContentIds = UserExtensions.CalculateContentStartNodeIds(user, Current.Services.EntityService); + var startContentIds = UserExtensions.CalculateContentStartNodeIds(user, Current.Services.EntityService, Current.AppCaches); return startContentIds.UnsortedSequenceEqual(identity.StartContentNodes) == false; }, () => { - var startMediaIds = UserExtensions.CalculateMediaStartNodeIds(user, Current.Services.EntityService); + var startMediaIds = UserExtensions.CalculateMediaStartNodeIds(user, Current.Services.EntityService, Current.AppCaches); return startMediaIds.UnsortedSequenceEqual(identity.StartMediaNodes) == false; } }; diff --git a/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs b/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs index 28f09b46b7..fcf8cf1dd6 100644 --- a/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs @@ -115,11 +115,13 @@ namespace Umbraco.Web.WebApi.Filters nodeId = _nodeId.Value; } - var permissionResult = ContentPermissionsHelper.CheckPermissions(nodeId, + var permissionResult = ContentPermissionsHelper.CheckPermissions( + nodeId, Current.UmbracoContext.Security.CurrentUser, Current.Services.UserService, Current.Services.ContentService, Current.Services.EntityService, + Current.AppCaches, out var contentItem, _permissionToCheck.HasValue ? new[] { _permissionToCheck.Value } : null); diff --git a/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForMediaAttribute.cs b/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForMediaAttribute.cs index 60e2889fd5..ef99aa4778 100644 --- a/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForMediaAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForMediaAttribute.cs @@ -124,6 +124,7 @@ namespace Umbraco.Web.WebApi.Filters Current.UmbracoContext.Security.CurrentUser, Current.Services.MediaService, Current.Services.EntityService, + Current.AppCaches, nodeId)) { base.OnActionExecuting(actionContext); diff --git a/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingContentAttribute.cs b/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingContentAttribute.cs index 31e0b22ce1..23512aa144 100644 --- a/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingContentAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingContentAttribute.cs @@ -9,7 +9,7 @@ using Umbraco.Core; using Umbraco.Web.Composing; using Umbraco.Core.Models; using Umbraco.Web.Actions; - +using Umbraco.Core.Cache; namespace Umbraco.Web.WebApi.Filters { @@ -21,52 +21,48 @@ namespace Umbraco.Web.WebApi.Filters { private readonly IUserService _userService; private readonly IEntityService _entityService; + private readonly AppCaches _appCaches; private readonly char _permissionToCheck; public FilterAllowedOutgoingContentAttribute(Type outgoingType) - : this(outgoingType, Current.Services.UserService, Current.Services.EntityService) + : this(outgoingType, ActionBrowse.ActionLetter, string.Empty, Current.Services.UserService, Current.Services.EntityService, Current.AppCaches) { - _permissionToCheck = ActionBrowse.ActionLetter; } public FilterAllowedOutgoingContentAttribute(Type outgoingType, char permissionToCheck) - : this(outgoingType, Current.Services.UserService, Current.Services.EntityService) + : this(outgoingType, permissionToCheck, string.Empty, Current.Services.UserService, Current.Services.EntityService, Current.AppCaches) { - _permissionToCheck = permissionToCheck; } public FilterAllowedOutgoingContentAttribute(Type outgoingType, string propertyName) - : this(outgoingType, propertyName, Current.Services.UserService, Current.Services.EntityService) + : this(outgoingType, ActionBrowse.ActionLetter, propertyName, Current.Services.UserService, Current.Services.EntityService, Current.AppCaches) { - _permissionToCheck = ActionBrowse.ActionLetter; } public FilterAllowedOutgoingContentAttribute(Type outgoingType, IUserService userService, IEntityService entityService) - : base(outgoingType) + : this(outgoingType, ActionBrowse.ActionLetter, string.Empty, userService, entityService, Current.AppCaches) { - _userService = userService ?? throw new ArgumentNullException(nameof(userService)); - _entityService = entityService ?? throw new ArgumentNullException(nameof(entityService)); - _permissionToCheck = ActionBrowse.ActionLetter; } public FilterAllowedOutgoingContentAttribute(Type outgoingType, char permissionToCheck, IUserService userService, IEntityService entityService) - : base(outgoingType) + : this(outgoingType, permissionToCheck, string.Empty, userService, entityService, Current.AppCaches) { - _userService = userService ?? throw new ArgumentNullException(nameof(userService)); - _entityService = entityService ?? throw new ArgumentNullException(nameof(entityService)); - _userService = userService; - _entityService = entityService; - _permissionToCheck = permissionToCheck; } public FilterAllowedOutgoingContentAttribute(Type outgoingType, string propertyName, IUserService userService, IEntityService entityService) + : this(outgoingType, ActionBrowse.ActionLetter, propertyName, userService, entityService, Current.AppCaches) + { + } + + private FilterAllowedOutgoingContentAttribute(Type outgoingType, char permissionToCheck, string propertyName, IUserService userService, IEntityService entityService, AppCaches appCaches) : base(outgoingType, propertyName) { _userService = userService ?? throw new ArgumentNullException(nameof(userService)); _entityService = entityService ?? throw new ArgumentNullException(nameof(entityService)); + _appCaches = appCaches; _userService = userService; _entityService = entityService; - _permissionToCheck = ActionBrowse.ActionLetter; + _permissionToCheck = permissionToCheck; } protected override void FilterItems(IUser user, IList items) @@ -78,7 +74,7 @@ namespace Umbraco.Web.WebApi.Filters protected override int[] GetUserStartNodes(IUser user) { - return user.CalculateContentStartNodeIds(_entityService); + return user.CalculateContentStartNodeIds(_entityService, _appCaches); } protected override int RecycleBinId diff --git a/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs b/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs index 21dc60e6cc..5e308bd3c1 100644 --- a/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs @@ -39,7 +39,7 @@ namespace Umbraco.Web.WebApi.Filters protected virtual int[] GetUserStartNodes(IUser user) { - return user.CalculateMediaStartNodeIds(Current.Services.EntityService); + return user.CalculateMediaStartNodeIds(Current.Services.EntityService, Current.AppCaches); } protected virtual int RecycleBinId => Constants.System.RecycleBinMedia; From 6b4f99394d952deb347e1d8c6ac2ade947f52fc6 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 9 Feb 2021 15:30:39 +1100 Subject: [PATCH 014/202] Fix mini profiler so that it actually profiles the Content APIs in the back office --- src/Umbraco.Web/Logging/WebProfiler.cs | 10 +++++++--- src/Umbraco.Web/Logging/WebProfilerProvider.cs | 6 +++++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web/Logging/WebProfiler.cs b/src/Umbraco.Web/Logging/WebProfiler.cs index 512edb2296..740afba6a7 100755 --- a/src/Umbraco.Web/Logging/WebProfiler.cs +++ b/src/Umbraco.Web/Logging/WebProfiler.cs @@ -26,12 +26,16 @@ namespace Umbraco.Web.Logging _provider = new WebProfilerProvider(); //see https://miniprofiler.com/dotnet/AspDotNet - MiniProfiler.Configure(new MiniProfilerOptions + var options = new MiniProfilerOptions { SqlFormatter = new SqlServerFormatter(), - StackMaxLength = 5000, + StackMaxLength = 5000, ProfilerProvider = _provider - }); + }; + // this is a default path and by default it performs a 'contains' check which will match our content controller + // (and probably other requests) and ignore them. + options.IgnoredPaths.Remove("/content/"); + MiniProfiler.Configure(options); } public void UmbracoApplicationBeginRequest(object sender, EventArgs e) diff --git a/src/Umbraco.Web/Logging/WebProfilerProvider.cs b/src/Umbraco.Web/Logging/WebProfilerProvider.cs index dbb81cf232..f38a606745 100755 --- a/src/Umbraco.Web/Logging/WebProfilerProvider.cs +++ b/src/Umbraco.Web/Logging/WebProfilerProvider.cs @@ -85,7 +85,11 @@ namespace Umbraco.Web.Logging public override MiniProfiler Start(string profilerName, MiniProfilerBaseOptions options) { var first = Interlocked.Exchange(ref _first, 1) == 0; - if (first == false) return base.Start(profilerName, options); + if (first == false) + { + var profiler = base.Start(profilerName, options); + return profiler; + } _startupProfiler = new MiniProfiler("StartupProfiler", options); CurrentProfiler = _startupProfiler; From 4a10c9971767e2031ae68c3380f70ce7cd99ea8c Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Tue, 9 Feb 2021 13:17:28 +0100 Subject: [PATCH 015/202] Adds CoC enforcement document --- .github/CODE_OF_CONDUCT.md | 2 + .github/CODE_OF_CONDUCT_ENFORCEMENT.md | 57 ++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 .github/CODE_OF_CONDUCT_ENFORCEMENT.md diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md index 7a81989037..cdb57cd7c9 100644 --- a/.github/CODE_OF_CONDUCT.md +++ b/.github/CODE_OF_CONDUCT.md @@ -43,6 +43,8 @@ Community leaders (e.g. Meetup & festival organizers, moderators, maintainers, . Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. +Specific enforcement steps are listed in the [Code of Conduct Enforcement Guidelines](https://github.com/umbraco/Umbraco-CMS/blob/v8/contrib/.github/CODE_OF_CONDUCT_ENFORCEMENT.md) document which is an appendix of this document, updated and maintained by the Code of Conduct Team. + ## Scope This Code of Conduct applies within all community spaces and events supported by Umbraco HQ or using the Umbraco name. It also applies when an individual is officially representing the community in public spaces. diff --git a/.github/CODE_OF_CONDUCT_ENFORCEMENT.md b/.github/CODE_OF_CONDUCT_ENFORCEMENT.md new file mode 100644 index 0000000000..ac2e812528 --- /dev/null +++ b/.github/CODE_OF_CONDUCT_ENFORCEMENT.md @@ -0,0 +1,57 @@ +# Umbraco Code of Conduct Enforcement guidelines - Consequence Ladder + +These are the steps followed by the [Umbraco Code of Conduct Team](https://github.com/umbraco/Umbraco-CMS/blob/v8/contrib/.github/CODE_OF_CONDUCT.md) when we respond to an issue or incident brought to our attention by a community member. + +This is an appendix to the Code of Conduct and is updated and maintained by the Code of Conduct Team. + +To make sure that all reports will be reviewed and investigated promptly and fairly, as highlighted in the Umbraco Code of Conduct, we are following [Mozilla’s Consequence Ladder approach](https://github.com/mozilla/inclusion/blob/master/code-of-conduct-enforcement/consequence-ladder.md). + +This approach helps the Team enforce the Code of Conduct in a structured manner and can be used as a way of communicating escalation. Each time the Team takes an action (warning, ban) the individual is made aware of future consequences. The Team can either follow the order of the levels in the ladder or decide to jump levels. When needed, the team can go directly to a permanent ban. + +**Level 0: No Action** +Recommendations do not indicate a violation of the Code of Conduct. + +**Level 1: Simple Warning Issued** +A private, written warning from the Code of Conduct Team, with clarity of violation, consequences of continued behavior. + +**Level 2: Warning** +A private, written warning from the Code of Conduct Team, with clarity of violation, consequences of continued behavior. Additionally: + +* Communication of next-level consequences if behaviors are repeated (according to this ladder). + +**Level 3: Warning + Mandatory Cooling Off Period (Access Retained)** +A private warning from the Code of Conduct Team, with clarity of violation, consequences of continued behavior. Additionally: + +* Request to avoid interaction on community messaging platforms (public forums, Our, commenting on issues). + * This includes avoiding any interactions in any Umbraco channels, spaces/offices, as well as external channels like social media (e.g. Twitter, Facebook, LinkedIn). For example, 'following/liking/retweeting' would be considered a violation of these terms, and consequence would escalate according to his ladder. +* Require they do not interact with others in the report, or those who they suspect are involved in the report. +* Suggestions for 'out of office' type of message on platforms, to reduce curiosity, or suspicion among those not involved. + +**Level 4: Temporary Ban (Access Revoked)** +Private communication of ban from the Code of Conduct Team, with clarity of violation, consequences of continued behavior. Additionally: + +* 3-6 months imposed break. +* All accounts deactivated, or blocked during this time (Our, HQ Slack if applicable). +* Require to avoid interaction on community messaging platforms (public forums, Our, commenting on issues). + * This includes avoiding any interactions in any Umbraco channels, spaces/offices, as well as external channels like social media (e.g. Twitter, Facebook, LinkedIn). For example, 'following/liking/retweeting' would be considered a violation of these terms, and consequence would escalate according to his ladder. +* All community leadership roles (e.g. Community Teams, Meetup/festival organizer, Commit right on Github..) suspended. (onboarding/reapplication required outside of this process) +* No attendance at Umbraco events during the ban period. +* Not allowed to enter Umbraco HQ offices during the ban period. +* Permission to use the MVP title, if applicable, is revoked during this ban period. +* The community leaders running events and other initiatives are informed of the ban. + +**Level 5: Permanent Ban** +Private communication of ban from the Code of Conduct Team, with clarity of violation, consequences of continued behavior. Additionally: + +* All accounts deactivated permanently. +* No attendance at Umbraco events going forward. +* Not allowed to enter Umbraco HQ offices permanently. +* All community leadership roles (e.g. Community Teams, Meetup/festival organizer, Commit right on Github..) permanently suspended. +* Permission to use the MVP title, if applicable, revoked. +* The community leaders running events and other initiatives are informed of the ban. + + +Sources: +* [Mozilla Code of Conduct - Enforcement Consequence Ladder](https://github.com/mozilla/inclusion/blob/master/code-of-conduct-enforcement/consequence-ladder.md) +* [Drupal Conflict Resolution Policy and Process](https://www.drupal.org/conflict-resolution) +* [Django Code of Conduct - Enforcement Manual](https://www.djangoproject.com/conduct/enforcement-manual/) From 6694690a1114694c5c94d512758adca79dbceeae Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Tue, 9 Feb 2021 13:20:32 +0100 Subject: [PATCH 016/202] Duh forgot to save latest version --- .github/CODE_OF_CONDUCT_ENFORCEMENT.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/CODE_OF_CONDUCT_ENFORCEMENT.md b/.github/CODE_OF_CONDUCT_ENFORCEMENT.md index ac2e812528..2bb45644c2 100644 --- a/.github/CODE_OF_CONDUCT_ENFORCEMENT.md +++ b/.github/CODE_OF_CONDUCT_ENFORCEMENT.md @@ -1,4 +1,4 @@ -# Umbraco Code of Conduct Enforcement guidelines - Consequence Ladder +# Umbraco Code of Conduct Enforcement guidelines - Consequence Ladder These are the steps followed by the [Umbraco Code of Conduct Team](https://github.com/umbraco/Umbraco-CMS/blob/v8/contrib/.github/CODE_OF_CONDUCT.md) when we respond to an issue or incident brought to our attention by a community member. @@ -17,23 +17,23 @@ A private, written warning from the Code of Conduct Team, with clarity of violat **Level 2: Warning** A private, written warning from the Code of Conduct Team, with clarity of violation, consequences of continued behavior. Additionally: -* Communication of next-level consequences if behaviors are repeated (according to this ladder). +* Communication of next-level consequences if behaviors are repeated (according to this ladder). **Level 3: Warning + Mandatory Cooling Off Period (Access Retained)** A private warning from the Code of Conduct Team, with clarity of violation, consequences of continued behavior. Additionally: * Request to avoid interaction on community messaging platforms (public forums, Our, commenting on issues). - * This includes avoiding any interactions in any Umbraco channels, spaces/offices, as well as external channels like social media (e.g. Twitter, Facebook, LinkedIn). For example, 'following/liking/retweeting' would be considered a violation of these terms, and consequence would escalate according to his ladder. + * This includes avoiding any interactions in any Umbraco channels, spaces/offices, as well as external channels like social media (e.g. Twitter, Facebook, LinkedIn). For example, 'following/liking/retweeting' would be considered a violation of these terms, and consequence would escalate according to this ladder. * Require they do not interact with others in the report, or those who they suspect are involved in the report. * Suggestions for 'out of office' type of message on platforms, to reduce curiosity, or suspicion among those not involved. **Level 4: Temporary Ban (Access Revoked)** -Private communication of ban from the Code of Conduct Team, with clarity of violation, consequences of continued behavior. Additionally: +Private communication of ban from the Code of Conduct Team, with clarity of violation, consequences of continued behavior. Additionally: * 3-6 months imposed break. * All accounts deactivated, or blocked during this time (Our, HQ Slack if applicable). * Require to avoid interaction on community messaging platforms (public forums, Our, commenting on issues). - * This includes avoiding any interactions in any Umbraco channels, spaces/offices, as well as external channels like social media (e.g. Twitter, Facebook, LinkedIn). For example, 'following/liking/retweeting' would be considered a violation of these terms, and consequence would escalate according to his ladder. + * This includes avoiding any interactions in any Umbraco channels, spaces/offices, as well as external channels like social media (e.g. Twitter, Facebook, LinkedIn). For example, 'following/liking/retweeting' would be considered a violation of these terms, and consequence would escalate according to this ladder. * All community leadership roles (e.g. Community Teams, Meetup/festival organizer, Commit right on Github..) suspended. (onboarding/reapplication required outside of this process) * No attendance at Umbraco events during the ban period. * Not allowed to enter Umbraco HQ offices during the ban period. From deb38d3f79dbdd945a356e5d949ffbdd77b8c37b Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Tue, 9 Feb 2021 14:42:55 +0100 Subject: [PATCH 017/202] Clarification of the CoC --- .github/CODE_OF_CONDUCT.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md index cdb57cd7c9..e97b03e7e3 100644 --- a/.github/CODE_OF_CONDUCT.md +++ b/.github/CODE_OF_CONDUCT.md @@ -60,6 +60,8 @@ Or alternatively, you can reach out directly to any of the team members behind t The review process is done with full respect for the privacy and security of the reporter of any incident. +People with a conflict of interest should exclude themselves or if necessary be excluded by the other team members. + ## Enforcement Guidelines Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: From e9429cfe44507e028ab714c9e2bc1740c746e004 Mon Sep 17 00:00:00 2001 From: Jakob Bagterp <25110864+jakob-bagterp@users.noreply.github.com> Date: Thu, 4 Feb 2021 23:54:01 +0100 Subject: [PATCH 018/202] Separate remove button and label for settings and styles in Grid Layout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It's inconvenient that the click area for the remove button laps over the label of settings and styles in the Grid Layout property editor. Simply as the editor may remove the style/setting by accident – and especially since there isn't an undo option. Known issue: Linebreak of long text labels doesn't appear pretty, but this is cosmetic and can be fixed. --- .../src/views/propertyeditors/grid/grid.prevalues.html | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.prevalues.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.prevalues.html index 9bf32675e3..f1f1a90ef4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.prevalues.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.prevalues.html @@ -117,11 +117,10 @@
    • - + {{configValue.label}}
    @@ -145,12 +144,10 @@
  • - - + {{style.label}}
  • From 230bf1052cde249587d4ce06a2fb23859094f414 Mon Sep 17 00:00:00 2001 From: Marc Goodson Date: Wed, 10 Feb 2021 23:25:38 +0000 Subject: [PATCH 019/202] V8/feature/super doctype create menu (#9174) * WIP - added options from superdoctypecreate package resolve merge conflict in create html file * fiddling with the wording but it will never be perfect * looks like ng-if should be ng-show * Update data-elements for uniqueness (I'm not sure what these are used for!) * pass 'iscomposition' on the querystring when choosing the 'composition' option from the menu Why? - in the future we could hide the 'Compositions...' button when choosing to create a composition * Remove PostCreateCollection implementation now the Create Collection is removed in this PR from the DocType Create menu resolve merge conflict in Remove PostCreateCollection implementation now the Create Collection is removed in this PR from the DocType Create menu * Add translations * Put back PostSave method. It's useful. Co-authored-by: Kenn Jacobsen Co-authored-by: Nathan Woulfe --- .../common/resources/contenttype.resource.js | 35 +------ .../views/documenttypes/create.controller.js | 75 ++++---------- .../src/views/documenttypes/create.html | 99 ++++++------------- src/Umbraco.Web.UI/Umbraco/config/lang/da.xml | 10 ++ src/Umbraco.Web.UI/Umbraco/config/lang/en.xml | 10 ++ .../Umbraco/config/lang/en_us.xml | 10 ++ .../Editors/ContentTypeController.cs | 86 +++------------- 7 files changed, 96 insertions(+), 229 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js index 01b6360d07..6acf702546 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js @@ -511,41 +511,8 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca $http.post(umbRequestHelper.getApiUrl("contentTypeApiBaseUrl", "PostCreateContainer", { parentId: parentId, name: encodeURIComponent(name) })), 'Failed to create a folder under parent id ' + parentId); - }, - /** - * @ngdoc method - * @name umbraco.resources.contentTypeResource#createCollection - * @methodOf umbraco.resources.contentTypeResource - * - * @description - * Create a collection of a content types - * - * ##usage - *
    -        * contentTypeResource.createCollection(1244,"testcollectionname",true,"collectionItemName",true,"icon-name","icon-name")
    -        *    .then(function() {
    -        *       Do stuff..
    -        *    });
    -        * 
    - * - * @param {Int} parentId the ID of the parent content type underneath which to create the collection - * @param {String} collectionName the name of the collection - * @param {Boolean} collectionCreateTemplate true/false to specify whether to create a default template for the collection - * @param {String} collectionItemName the name of the collection item - * @param {Boolean} collectionItemCreateTemplate true/false to specify whether to create a default template for the collection item - * @param {String} collectionIcon the icon for the collection - * @param {String} collectionItemIcon the icon for the collection item - * @returns {Promise} resourcePromise object. - * - */ - createCollection: function (parentId, collectionName, collectionCreateTemplate, collectionItemName, collectionItemCreateTemplate, collectionIcon, collectionItemIcon) { - - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("contentTypeApiBaseUrl", "PostCreateCollection", { parentId: parentId, collectionName: collectionName, collectionCreateTemplate: collectionCreateTemplate, collectionItemName: collectionItemName, collectionItemCreateTemplate: collectionItemCreateTemplate, collectionIcon: collectionIcon, collectionItemIcon: collectionItemIcon})), - 'Failed to create collection under ' + parentId); - - }, + }, /** * @ngdoc method diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.controller.js b/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.controller.js index 71eee085ee..23173a404d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.controller.js @@ -11,8 +11,7 @@ function DocumentTypesCreateController($scope, $location, navigationService, con $scope.model = { allowCreateFolder: $scope.currentNode.parentId === null || $scope.currentNode.nodeType === "container", folderName: "", - creatingFolder: false, - creatingDoctypeCollection: false + creatingFolder: false }; var disableTemplates = Umbraco.Sys.ServerVariables.features.disabledFeatures.disableTemplates; @@ -24,12 +23,6 @@ function DocumentTypesCreateController($scope, $location, navigationService, con $scope.model.creatingFolder = true; }; - $scope.showCreateDocTypeCollection = function () { - $scope.model.creatingDoctypeCollection = true; - $scope.model.collectionCreateTemplate = !$scope.model.disableTemplates; - $scope.model.collectionItemCreateTemplate = !$scope.model.disableTemplates; - }; - $scope.createContainer = function () { if (formHelper.submitForm({ scope: $scope, formCtrl: $scope.createFolderForm })) { @@ -58,55 +51,7 @@ function DocumentTypesCreateController($scope, $location, navigationService, con }); } - }; - - $scope.createCollection = function () { - - if (formHelper.submitForm({ scope: $scope, formCtrl: this.createDoctypeCollectionForm, statusMessage: "Creating Doctype Collection..." })) { - - // see if we can find matching icons - var collectionIcon = "icon-folders", collectionItemIcon = "icon-document"; - iconHelper.getIcons().then(function (icons) { - - for (var i = 0; i < icons.length; i++) { - // for matching we'll require a full match for collection, partial match for item - if (icons[i].substring(5) == $scope.model.collectionName.toLowerCase()) { - collectionIcon = icons[i]; - } else if (icons[i].substring(5).indexOf($scope.model.collectionItemName.toLowerCase()) > -1) { - collectionItemIcon = icons[i]; - } - } - - contentTypeResource.createCollection(node.id, $scope.model.collectionName, $scope.model.collectionCreateTemplate, $scope.model.collectionItemName, $scope.model.collectionItemCreateTemplate, collectionIcon, collectionItemIcon) - .then(function (collectionData) { - - navigationService.hideMenu(); - $location.search('create', null); - $location.search('notemplate', null); - - formHelper.resetForm({ scope: $scope, formCtrl: this.createDoctypeCollectionForm }); - - var section = appState.getSectionState("currentSection"); - - // redirect to the item id - $location.path("/" + section + "/documenttypes/edit/" + collectionData.containerId); - - }, function (err) { - - formHelper.resetForm({ scope: $scope, formCtrl: this.createDoctypeCollectionForm, hasErrors: true }); - $scope.error = err; - - //show any notifications - if (Utilities.isArray(err.data.notifications)) { - for (var i = 0; i < err.data.notifications.length; i++) { - notificationsService.showNotification(err.data.notifications[i]); - } - } - }); - }); - } - - }; + }; // Disabling logic for creating document type with template if disableTemplates is set to true if (!disableTemplates) { @@ -125,6 +70,22 @@ function DocumentTypesCreateController($scope, $location, navigationService, con navigationService.hideMenu(); }; + $scope.createComposition = function () { + $location.search('create', null); + $location.search('notemplate', null); + $location.search('iscomposition', null); + $location.path("/settings/documenttypes/edit/" + node.id).search("create", "true").search("notemplate", "true").search("iscomposition", "true"); + navigationService.hideMenu(); + }; + + $scope.createElement = function () { + $location.search('create', null); + $location.search('notemplate', null); + $location.search('iselement', null); + $location.path("/settings/documenttypes/edit/" + node.id).search("create", "true").search("notemplate", "true").search("iselement", "true"); + navigationService.hideMenu(); + }; + $scope.close = function() { const showMenu = true; navigationService.hideDialog(showMenu); diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html b/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html index 4ebb96c09b..208b4eaeda 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html @@ -1,16 +1,15 @@
    - diff --git a/src/Umbraco.Web.UI.Client/src/views/membertypes/create.html b/src/Umbraco.Web.UI.Client/src/views/membertypes/create.html index 46b51b5f34..673b90ef85 100644 --- a/src/Umbraco.Web.UI.Client/src/views/membertypes/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/membertypes/create.html @@ -1,47 +1,53 @@ -