From c2ed6a629bcc46af66c5a7a578281115b27d05f9 Mon Sep 17 00:00:00 2001 From: Matthew Care Date: Fri, 15 Oct 2021 23:22:01 +0100 Subject: [PATCH 001/141] Update request handler settings `CharCollection` didn't map correctly from the config, updated to an array so that it does Add logic to concatenate user and default replacements, replacing defaults with user defined if present. Added additional option to disable the default replacements --- .../Models/RequestHandlerSettings.cs | 114 ++++++++++-------- 1 file changed, 62 insertions(+), 52 deletions(-) diff --git a/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs b/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs index ee223b36c6..79dabf9da0 100644 --- a/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.ComponentModel; +using System.Linq; using Umbraco.Cms.Core.Configuration.UmbracoSettings; using Umbraco.Extensions; @@ -16,33 +17,34 @@ namespace Umbraco.Cms.Core.Configuration.Models { internal const bool StaticAddTrailingSlash = true; internal const string StaticConvertUrlsToAscii = "try"; + internal const bool StaticEnableDefaultCharReplacements = true; - internal static readonly CharItem[] DefaultCharCollection = + internal static readonly CharacterReplacement[] DefaultCharCollection = { - new CharItem { Char = " ", Replacement = "-" }, - new CharItem { Char = "\"", Replacement = string.Empty }, - new CharItem { Char = "'", Replacement = string.Empty }, - new CharItem { Char = "%", Replacement = string.Empty }, - new CharItem { Char = ".", Replacement = string.Empty }, - new CharItem { Char = ";", Replacement = string.Empty }, - new CharItem { Char = "/", Replacement = string.Empty }, - new CharItem { Char = "\\", Replacement = string.Empty }, - new CharItem { Char = ":", Replacement = string.Empty }, - new CharItem { Char = "#", Replacement = string.Empty }, - new CharItem { Char = "+", Replacement = "plus" }, - new CharItem { Char = "*", Replacement = "star" }, - new CharItem { Char = "&", Replacement = string.Empty }, - new CharItem { Char = "?", Replacement = string.Empty }, - new CharItem { Char = "æ", Replacement = "ae" }, - new CharItem { Char = "ä", Replacement = "ae" }, - new CharItem { Char = "ø", Replacement = "oe" }, - new CharItem { Char = "ö", Replacement = "oe" }, - new CharItem { Char = "å", Replacement = "aa" }, - new CharItem { Char = "ü", Replacement = "ue" }, - new CharItem { Char = "ß", Replacement = "ss" }, - new CharItem { Char = "|", Replacement = "-" }, - new CharItem { Char = "<", Replacement = string.Empty }, - new CharItem { Char = ">", Replacement = string.Empty } + new () { Char = " ", Replacement = "-" }, + new () { Char = "\"", Replacement = string.Empty }, + new () { Char = "'", Replacement = string.Empty }, + new () { Char = "%", Replacement = string.Empty }, + new () { Char = ".", Replacement = string.Empty }, + new () { Char = ";", Replacement = string.Empty }, + new () { Char = "/", Replacement = string.Empty }, + new () { Char = "\\", Replacement = string.Empty }, + new () { Char = ":", Replacement = string.Empty }, + new () { Char = "#", Replacement = string.Empty }, + new () { Char = "+", Replacement = "plus" }, + new () { Char = "*", Replacement = "star" }, + new () { Char = "&", Replacement = string.Empty }, + new () { Char = "?", Replacement = string.Empty }, + new () { Char = "æ", Replacement = "ae" }, + new () { Char = "ä", Replacement = "ae" }, + new () { Char = "ø", Replacement = "oe" }, + new () { Char = "ö", Replacement = "oe" }, + new () { Char = "å", Replacement = "aa" }, + new () { Char = "ü", Replacement = "ue" }, + new () { Char = "ß", Replacement = "ss" }, + new () { Char = "|", Replacement = "-" }, + new () { Char = "<", Replacement = string.Empty }, + new () { Char = ">", Replacement = string.Empty } }; /// @@ -67,41 +69,49 @@ namespace Umbraco.Cms.Core.Configuration.Models /// public bool ShouldTryConvertUrlsToAscii => ConvertUrlsToAscii.InvariantEquals("try"); - // We need to special handle ":", as this character is special in keys - - // TODO: implement from configuration - - //// var collection = _configuration.GetSection(Prefix + "CharCollection").GetChildren() - //// .Select(x => new CharItem() - //// { - //// Char = x.GetValue("Char"), - //// Replacement = x.GetValue("Replacement"), - //// }).ToArray(); - - //// if (collection.Any() || _configuration.GetSection("Prefix").GetChildren().Any(x => - //// x.Key.Equals("CharCollection", StringComparison.OrdinalIgnoreCase))) - //// { - //// return collection; - //// } - - //// return DefaultCharCollection; + /// + /// Disable all default character replacements + /// + [DefaultValue(StaticEnableDefaultCharReplacements)] + public bool EnableDefaultCharReplacements { get; set; } = StaticEnableDefaultCharReplacements; /// - /// Gets or sets a value for the default character collection for replacements. + /// Add additional character replacements, or override defaults /// - /// WB-TODO - public IEnumerable CharCollection { get; set; } = DefaultCharCollection; + public CharacterReplacement[] CharCollection { get; set; } /// - /// Defines a character replacement. + /// Get concatenated user and default character replacements + /// taking into account /// - public class CharItem : IChar + public IEnumerable GetCharReplacements() { - /// - public string Char { get; set; } + // TODO We need to special handle ":", as this character is special in keys - /// - public string Replacement { get; set; } + if (!EnableDefaultCharReplacements) + { + return CharCollection; + } + + if (CharCollection == null || !CharCollection.Any()) + { + return DefaultCharCollection; + } + + foreach (var defaultReplacement in DefaultCharCollection) + { + foreach (var userReplacement in CharCollection) + { + if (userReplacement.Char == defaultReplacement.Char) + { + defaultReplacement.Replacement = userReplacement.Replacement; + } + } + } + + var mergedCollections = DefaultCharCollection.Union(CharCollection, new CharacterReplacementEqualityComparer()); + + return mergedCollections; } } } From 1ee4e379e797ef95e579c0c72476e3ac98898a7f Mon Sep 17 00:00:00 2001 From: Matthew Care Date: Fri, 15 Oct 2021 23:23:07 +0100 Subject: [PATCH 002/141] Update classes Update class names, and location --- .../UmbracoSettings/CharacterReplacement.cs | 15 +++++++ .../CharacterReplacementEqualityComparer.cs | 40 +++++++++++++++++++ .../Configuration/UmbracoSettings/IChar.cs | 8 ---- 3 files changed, 55 insertions(+), 8 deletions(-) create mode 100644 src/Umbraco.Core/Configuration/UmbracoSettings/CharacterReplacement.cs create mode 100644 src/Umbraco.Core/Configuration/UmbracoSettings/CharacterReplacementEqualityComparer.cs delete mode 100644 src/Umbraco.Core/Configuration/UmbracoSettings/IChar.cs diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/CharacterReplacement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/CharacterReplacement.cs new file mode 100644 index 0000000000..ed2f97dba9 --- /dev/null +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/CharacterReplacement.cs @@ -0,0 +1,15 @@ +namespace Umbraco.Cms.Core.Configuration.UmbracoSettings +{ + public class CharacterReplacement + { + /// + /// The character to replace + /// + public string Char { get; set; } + + /// + /// The replacement character + /// + public string Replacement { get; set; } + } +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/CharacterReplacementEqualityComparer.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/CharacterReplacementEqualityComparer.cs new file mode 100644 index 0000000000..b7dbf1cd16 --- /dev/null +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/CharacterReplacementEqualityComparer.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; + +namespace Umbraco.Cms.Core.Configuration.UmbracoSettings +{ + public class CharacterReplacementEqualityComparer : IEqualityComparer + { + public bool Equals(CharacterReplacement x, CharacterReplacement y) + { + if (ReferenceEquals(x, y)) + { + return true; + } + + if (x is null) + { + return false; + } + + if (y is null) + { + return false; + } + + if (x.GetType() != y.GetType()) + { + return false; + } + + return x.Char == y.Char && x.Replacement == y.Replacement; + } + + public int GetHashCode(CharacterReplacement obj) + { + unchecked + { + return ((obj.Char != null ? obj.Char.GetHashCode() : 0) * 397) ^ (obj.Replacement != null ? obj.Replacement.GetHashCode() : 0); + } + } + } +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IChar.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IChar.cs deleted file mode 100644 index 4073a12149..0000000000 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IChar.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Umbraco.Cms.Core.Configuration.UmbracoSettings -{ - public interface IChar - { - string Char { get; } - string Replacement { get; } - } -} From 12e89a01ba52ae3ea74c3b0de3dfe05d1fe13983 Mon Sep 17 00:00:00 2001 From: Matthew Care Date: Fri, 15 Oct 2021 23:23:56 +0100 Subject: [PATCH 003/141] Update usage Update string helper to use new method that uses user defined replacements --- src/Umbraco.Core/Strings/DefaultShortStringHelperConfig.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Strings/DefaultShortStringHelperConfig.cs b/src/Umbraco.Core/Strings/DefaultShortStringHelperConfig.cs index cf5e71a568..287d33dd58 100644 --- a/src/Umbraco.Core/Strings/DefaultShortStringHelperConfig.cs +++ b/src/Umbraco.Core/Strings/DefaultShortStringHelperConfig.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Umbraco.Cms.Core.Configuration.Models; @@ -60,7 +60,7 @@ namespace Umbraco.Cms.Core.Strings /// The short string helper. public DefaultShortStringHelperConfig WithDefault(RequestHandlerSettings requestHandlerSettings) { - UrlReplaceCharacters = requestHandlerSettings.CharCollection + UrlReplaceCharacters = requestHandlerSettings.GetCharReplacements() .Where(x => string.IsNullOrEmpty(x.Char) == false) .ToDictionary(x => x.Char, x => x.Replacement); From 25b1c3c0788eb1dbbd068aeef9e5a67741d50498 Mon Sep 17 00:00:00 2001 From: Matthew Care Date: Fri, 15 Oct 2021 23:24:21 +0100 Subject: [PATCH 004/141] Add tests Add new tests for the request handler settings, and fix other tests --- .../Models/RequestHandlerSettingsTests.cs | 90 +++++++++++++++++++ ...faultShortStringHelperTestsWithoutSetup.cs | 7 +- 2 files changed, 94 insertions(+), 3 deletions(-) create mode 100644 src/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/RequestHandlerSettingsTests.cs diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/RequestHandlerSettingsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/RequestHandlerSettingsTests.cs new file mode 100644 index 0000000000..c16d9b4897 --- /dev/null +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/RequestHandlerSettingsTests.cs @@ -0,0 +1,90 @@ +using System.Linq; +using NUnit.Framework; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Configuration.UmbracoSettings; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Configuration.Models +{ + [TestFixture] + public class RequestHandlerSettingsTests + { + [Test] + public void Given_CharCollection_With_DefaultEnabled_MergesCollection() + { + var userCollection = new CharacterReplacement[] + { + new() { Char = "test", Replacement = "replace" }, + new() { Char = "test2", Replacement = "replace2" } + }; + + + var settings = new RequestHandlerSettings { CharCollection = userCollection }; + var actual = settings.GetCharReplacements().ToList(); + + var expectedCollection = RequestHandlerSettings.DefaultCharCollection.ToList(); + expectedCollection.AddRange(userCollection); + + Assert.AreEqual(expectedCollection.Count, actual.Count); + Assert.That(actual, Is.EquivalentTo(expectedCollection)); + } + + [Test] + public void Given_CharCollection_With_DefaultDisabled_ReturnsUserCollection() + { + var userCollection = new CharacterReplacement[] + { + new() { Char = "test", Replacement = "replace" }, + new() { Char = "test2", Replacement = "replace2" } + }; + + var settings = new RequestHandlerSettings { CharCollection = userCollection, EnableDefaultCharReplacements = false }; + var actual = settings.GetCharReplacements().ToList(); + + Assert.AreEqual(userCollection.Length, actual.Count); + Assert.That(actual, Is.EquivalentTo(userCollection)); + } + + [Test] + public void Given_CharCollection_That_OverridesDefaultValues_ReturnsReplacements() + { + var userCollection = new CharacterReplacement[] + { + new() { Char = "%", Replacement = "percent" }, + new() { Char = ".", Replacement = "dot" } + }; + + var settings = new RequestHandlerSettings { CharCollection = userCollection }; + var actual = settings.GetCharReplacements().ToList(); + + Assert.AreEqual(RequestHandlerSettings.DefaultCharCollection.Length, actual.Count); + + Assert.That(actual, Has.Exactly(1).Matches(x => x.Char == "%" && x.Replacement == "percent")); + Assert.That(actual, Has.Exactly(1).Matches(x => x.Char == "." && x.Replacement == "dot")); + Assert.That(actual, Has.Exactly(0).Matches(x => x.Char == "%" && x.Replacement == string.Empty)); + Assert.That(actual, Has.Exactly(0).Matches(x => x.Char == "." && x.Replacement == string.Empty)); + } + + [Test] + public void Given_CharCollection_That_OverridesDefaultValues_And_ContainsNew_ReturnsMergedWithReplacements() + { + var userCollection = new CharacterReplacement[] + { + new() { Char = "%", Replacement = "percent" }, + new() { Char = ".", Replacement = "dot" }, + new() {Char = "new", Replacement = "new"} + }; + + var settings = new RequestHandlerSettings { CharCollection = userCollection }; + var actual = settings.GetCharReplacements().ToList(); + + // Add 1 to the length, because we're expecting to only add one new one + Assert.AreEqual(RequestHandlerSettings.DefaultCharCollection.Length + 1, actual.Count); + + Assert.That(actual, Has.Exactly(1).Matches(x => x.Char == "%" && x.Replacement == "percent")); + Assert.That(actual, Has.Exactly(1).Matches(x => x.Char == "." && x.Replacement == "dot")); + Assert.That(actual, Has.Exactly(1).Matches(x => x.Char == "new" && x.Replacement == "new")); + Assert.That(actual, Has.Exactly(0).Matches(x => x.Char == "%" && x.Replacement == string.Empty)); + Assert.That(actual, Has.Exactly(0).Matches(x => x.Char == "." && x.Replacement == string.Empty)); + } + } +} diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/ShortStringHelper/DefaultShortStringHelperTestsWithoutSetup.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/ShortStringHelper/DefaultShortStringHelperTestsWithoutSetup.cs index 6f9ee481cc..5aa8198e07 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/ShortStringHelper/DefaultShortStringHelperTestsWithoutSetup.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/ShortStringHelper/DefaultShortStringHelperTestsWithoutSetup.cs @@ -1,6 +1,7 @@ // Copyright (c) Umbraco. // See LICENSE for more details. +using System; using System.Diagnostics; using System.Linq; using System.Text; @@ -19,7 +20,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.ShortStringHelper { var requestHandlerSettings = new RequestHandlerSettings() { - CharCollection = Enumerable.Empty(), + CharCollection = Array.Empty(), ConvertUrlsToAscii = "false" }; @@ -45,7 +46,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.ShortStringHelper { var requestHandlerSettings = new RequestHandlerSettings() { - CharCollection = Enumerable.Empty(), + CharCollection = Array.Empty(), ConvertUrlsToAscii = "false" }; @@ -339,7 +340,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.ShortStringHelper { var requestHandlerSettings = new RequestHandlerSettings() { - CharCollection = Enumerable.Empty(), + CharCollection = Array.Empty(), ConvertUrlsToAscii = "false" }; From 98474e111062b48f6d344b165503f98f8704438a Mon Sep 17 00:00:00 2001 From: Matthew Care Date: Fri, 15 Oct 2021 23:52:26 +0100 Subject: [PATCH 005/141] Fix broken test Update tests to disable the default replacements --- .../DefaultShortStringHelperTestsWithoutSetup.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/ShortStringHelper/DefaultShortStringHelperTestsWithoutSetup.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/ShortStringHelper/DefaultShortStringHelperTestsWithoutSetup.cs index 5aa8198e07..ee7999cac1 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/ShortStringHelper/DefaultShortStringHelperTestsWithoutSetup.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/ShortStringHelper/DefaultShortStringHelperTestsWithoutSetup.cs @@ -21,6 +21,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.ShortStringHelper var requestHandlerSettings = new RequestHandlerSettings() { CharCollection = Array.Empty(), + EnableDefaultCharReplacements = false, ConvertUrlsToAscii = "false" }; @@ -47,6 +48,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.ShortStringHelper var requestHandlerSettings = new RequestHandlerSettings() { CharCollection = Array.Empty(), + EnableDefaultCharReplacements = false, ConvertUrlsToAscii = "false" }; @@ -341,6 +343,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.ShortStringHelper var requestHandlerSettings = new RequestHandlerSettings() { CharCollection = Array.Empty(), + EnableDefaultCharReplacements = false, ConvertUrlsToAscii = "false" }; From 5580f8003500f6587ae2bd04e2762b7a3eb1d7e9 Mon Sep 17 00:00:00 2001 From: Maarten Mensink Date: Fri, 10 Dec 2021 10:33:35 +0100 Subject: [PATCH 006/141] fix incorrect camelcasing closes #11752 --- src/Umbraco.Web.BackOffice/Trees/ContentTypeTreeController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.BackOffice/Trees/ContentTypeTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/ContentTypeTreeController.cs index ecc5b78a51..27eb5b7f6e 100644 --- a/src/Umbraco.Web.BackOffice/Trees/ContentTypeTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/ContentTypeTreeController.cs @@ -118,7 +118,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees // root actions menu.Items.Add(LocalizedTextService, opensDialog: true); - menu.Items.Add(new MenuItem("importDocumentType", LocalizedTextService) + menu.Items.Add(new MenuItem("importdocumenttype", LocalizedTextService) { Icon = "page-up", SeparatorBefore = true, From bbbfb99a39eb83e9811ab0c7dbeaa77e0c987301 Mon Sep 17 00:00:00 2001 From: Maarten Mensink Date: Fri, 10 Dec 2021 11:26:51 +0100 Subject: [PATCH 007/141] update key for importdocumenttype --- src/Umbraco.Web.UI/umbraco/config/lang/cs.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/cy.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/da.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/de.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/es.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/fr.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/he.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/it.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/ja.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/ko.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/nb.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/nl.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/pl.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/pt.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/ru.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/sv.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/tr.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/zh.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/zh_tw.xml | 2 +- 21 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/cs.xml b/src/Umbraco.Web.UI/umbraco/config/lang/cs.xml index af701cd5e3..aab9329656 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/cs.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/cs.xml @@ -19,7 +19,7 @@ Vyprázdnit koš Aktivovat Exportovat typ dokumentu - Importovat typ dokumentu + Importovat typ dokumentu Importovat balíček Editovat na stránce Odhlásit diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/cy.xml b/src/Umbraco.Web.UI/umbraco/config/lang/cy.xml index 0692d01e7a..67815470cb 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/cy.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/cy.xml @@ -21,7 +21,7 @@ Gwagu bin ailgylchu Galluogi Allforio Math o Ddogfen - Mewnforio Math o Ddogfen + Mewnforio Math o Ddogfen Mewnforio Pecyn Golygu mewn Cynfas Gadael diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml index 1f50e3ebca..34444242d2 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml @@ -21,7 +21,7 @@ Tøm papirkurv Aktivér Eksportér dokumenttype - Importér dokumenttype + Importér dokumenttype Importér pakke Redigér i Canvas Log af diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/de.xml b/src/Umbraco.Web.UI/umbraco/config/lang/de.xml index 8f2ba350d0..2a456410f2 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/de.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/de.xml @@ -19,7 +19,7 @@ Papierkorb leeren Aktivieren Dokumenttyp exportieren - Dokumenttyp importieren + Dokumenttyp importieren Paket importieren 'Canvas'-Modus starten Abmelden diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index b1da14db12..81f7358d31 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -20,7 +20,7 @@ Empty recycle bin Enable Export Document Type - Import Document Type + Import Document Type Import Package Edit in Canvas Exit 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 4f0bab5991..0fa8678272 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -21,7 +21,7 @@ Empty recycle bin Enable Export Document Type - Import Document Type + Import Document Type Import Package Edit in Canvas Exit diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/es.xml b/src/Umbraco.Web.UI/umbraco/config/lang/es.xml index df78683aca..f1b9f640fd 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/es.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/es.xml @@ -18,7 +18,7 @@ Vaciar Papelera Activar Exportar Documento (tipo) - Importar Documento (tipo) + Importar Documento (tipo) Importar Paquete Editar en vivo Cerrar sesión diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/fr.xml b/src/Umbraco.Web.UI/umbraco/config/lang/fr.xml index f134a3b56b..5aa7ca4fed 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/fr.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/fr.xml @@ -19,7 +19,7 @@ Vider la corbeille Activer Exporter le type de document - Importer un type de document + Importer un type de document Importer un package Editer dans Canvas Déconnexion diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/he.xml b/src/Umbraco.Web.UI/umbraco/config/lang/he.xml index 9ee8bbf014..e70cf053ac 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/he.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/he.xml @@ -15,7 +15,7 @@ נטרל רוקן סל מיחזור ייצא סוג קובץ - ייבא סוג מסמך + ייבא סוג מסמך ייבא חבילה ערוך במצב "קנבס" יציאה diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/it.xml b/src/Umbraco.Web.UI/umbraco/config/lang/it.xml index a0d89bff2d..de12bc9fb3 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/it.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/it.xml @@ -21,7 +21,7 @@ Svuota il cestino Abilita Esporta il tipo di documento - Importa il tipo di documento + Importa il tipo di documento Importa il pacchetto Modifica in Area di Lavoro Uscita diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/ja.xml b/src/Umbraco.Web.UI/umbraco/config/lang/ja.xml index 4b98adad26..7ad2521f76 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/ja.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/ja.xml @@ -16,7 +16,7 @@ 無効 ごみ箱を空にする ドキュメントタイプの書出 - ドキュメントタイプの読込 + ドキュメントタイプの読込 パッケージの読み込み ライブ編集 ログアウト diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/ko.xml b/src/Umbraco.Web.UI/umbraco/config/lang/ko.xml index 792dd6700c..5ad8ed2a96 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/ko.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/ko.xml @@ -15,7 +15,7 @@ 비활성 휴지통 비우기 추출 문서 유형 - 등록 문서 유형 + 등록 문서 유형 패키지 등록 캔버스 내용 편집 종료 diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/nb.xml b/src/Umbraco.Web.UI/umbraco/config/lang/nb.xml index 1c47969189..ca62ab10d2 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/nb.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/nb.xml @@ -16,7 +16,7 @@ Deaktiver Tøm papirkurv Eksporter dokumenttype - Importer dokumenttype + Importer dokumenttype Importer pakke Rediger i Canvas Logg av diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml b/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml index cd675f7056..23678534d2 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml @@ -21,7 +21,7 @@ Prullenbak leegmaken Inschakelen Documenttype exporteren - Documenttype importeren + Documenttype importeren Package importeren Aanpassen in Canvas Afsluiten diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/pl.xml b/src/Umbraco.Web.UI/umbraco/config/lang/pl.xml index dfbc324df6..acca411f42 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/pl.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/pl.xml @@ -18,7 +18,7 @@ Opróżnij kosz Aktywuj Eksportuj typ dokumentu - Importuj typ dokumentu + Importuj typ dokumentu Importuj zbiór Edytuj na stronie Wyjście diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/pt.xml b/src/Umbraco.Web.UI/umbraco/config/lang/pt.xml index 542b03abc1..7d6ed8dec9 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/pt.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/pt.xml @@ -15,7 +15,7 @@ Desabilitar Esvaziar Lixeira Exportar Tipo de Documento - Importar Tipo de Documento + Importar Tipo de Documento Importar Pacote Editar na Tela Sair diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml b/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml index 9c1d9e12fb..5de2647217 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml @@ -21,7 +21,7 @@ Включить Экспорт Экспортировать - Импортировать + Импортировать Импортировать пакет Править на месте Выйти diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/sv.xml b/src/Umbraco.Web.UI/umbraco/config/lang/sv.xml index e0e2235ae9..f954b5d6fa 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/sv.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/sv.xml @@ -22,7 +22,7 @@ Avaktivera Töm papperskorgen Exportera dokumenttyp - Importera dokumenttyp + Importera dokumenttyp Importera paket Redigera i Canvas Logga ut diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/tr.xml b/src/Umbraco.Web.UI/umbraco/config/lang/tr.xml index 58c0f7f94b..4f3f6575c5 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/tr.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/tr.xml @@ -20,7 +20,7 @@ Geri dönüşüm kutusunu boşalt Etkinleştir Belge Türünü Dışa Aktar - Belge Türünü İçe Aktar + Belge Türünü İçe Aktar Paketi İçe Aktar Kanvas'ta Düzenle Çıkış diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/zh.xml b/src/Umbraco.Web.UI/umbraco/config/lang/zh.xml index 423e5cca36..474f8ed229 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/zh.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/zh.xml @@ -16,7 +16,7 @@ 禁用 清空回收站 导出文档类型 - 导入文档类型 + 导入文档类型 导入扩展包 实时编辑模式 退出 diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/zh_tw.xml b/src/Umbraco.Web.UI/umbraco/config/lang/zh_tw.xml index 8d5cf16de2..1a41cdc1f9 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/zh_tw.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/zh_tw.xml @@ -16,7 +16,7 @@ 禁用 清空回收站 匯出文檔類型 - 導入文檔類型 + 導入文檔類型 導入擴展包 即時編輯模式 退出 From d9a4c50a73d9743ad9453ddb389aa6d549e864e2 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Tue, 14 Dec 2021 09:23:55 +0100 Subject: [PATCH 008/141] Refactored message related methods to allow provision of an HttpContext, and used this in DistributedCacheBinder to ensure messages created are flushed from the same context. --- .../BatchedDatabaseServerMessenger.cs | 27 ++++++++++---- .../Cache/DistributedCacheBinder.cs | 35 +++++++++++++++++-- 2 files changed, 53 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Web/BatchedDatabaseServerMessenger.cs b/src/Umbraco.Web/BatchedDatabaseServerMessenger.cs index f89f62eb3d..a02cd2c128 100644 --- a/src/Umbraco.Web/BatchedDatabaseServerMessenger.cs +++ b/src/Umbraco.Web/BatchedDatabaseServerMessenger.cs @@ -68,9 +68,11 @@ namespace Umbraco.Web BatchMessage(refresher, messageType, idsA, arrayType, json); } - public void FlushBatch() + public void FlushBatch() => FlushBatch(null); + + internal void FlushBatch(HttpContextBase httpContext) { - var batch = GetBatch(false); + var batch = httpContext != null ? GetBatch(false, httpContext) : GetBatch(false); if (batch == null) return; var instructions = batch.SelectMany(x => x.Instructions).ToArray(); @@ -83,9 +85,9 @@ namespace Umbraco.Web { WriteInstructions(scope, instructionsBatch); } + scope.Complete(); } - } private void WriteInstructions(IScope scope, IEnumerable instructions) @@ -111,10 +113,15 @@ namespace Umbraco.Web // the case if the asp.net synchronization context has kicked in ?? (HttpContext.Current == null ? null : new HttpContextWrapper(HttpContext.Current)); - // if no context was found, return null - we cannot not batch + // if no context was found, return null - we cannot batch if (httpContext == null) return null; - var key = typeof (BatchedDatabaseServerMessenger).Name; + return GetBatch(create, httpContext); + } + + protected ICollection GetBatch(bool create, HttpContextBase httpContext) + { + var key = typeof(BatchedDatabaseServerMessenger).Name; // no thread-safety here because it'll run in only 1 thread (request) at a time var batch = (ICollection)httpContext.Items[key]; @@ -128,9 +135,17 @@ namespace Umbraco.Web MessageType messageType, IEnumerable ids = null, Type idType = null, + string json = null) => BatchMessage(refresher, messageType, null, ids, idType, json); + + protected void BatchMessage( + ICacheRefresher refresher, + MessageType messageType, + HttpContextBase httpContext, + IEnumerable ids = null, + Type idType = null, string json = null) { - var batch = GetBatch(true); + var batch = httpContext != null ? GetBatch(true, httpContext) : GetBatch(true); var instructions = RefreshInstruction.GetInstructions(refresher, messageType, ids, idType, json); // batch if we can, else write to DB immediately diff --git a/src/Umbraco.Web/Cache/DistributedCacheBinder.cs b/src/Umbraco.Web/Cache/DistributedCacheBinder.cs index bfb1a01a69..5987d425a5 100644 --- a/src/Umbraco.Web/Cache/DistributedCacheBinder.cs +++ b/src/Umbraco.Web/Cache/DistributedCacheBinder.cs @@ -9,6 +9,7 @@ using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Services; using Umbraco.Core.Services.Changes; +using Umbraco.Core.Sync; namespace Umbraco.Web.Cache { @@ -21,10 +22,12 @@ namespace Umbraco.Web.Cache private readonly DistributedCache _distributedCache; private readonly IUmbracoContextFactory _umbracoContextFactory; private readonly ILogger _logger; + private readonly BatchedDatabaseServerMessenger _serverMessenger; /// /// Initializes a new instance of the class. /// + [Obsolete("Please use the constructor accepting an instance of IServerMessenger. This constructor will be removed in a future version.")] public DistributedCacheBinder(DistributedCache distributedCache, IUmbracoContextFactory umbracoContextFactory, ILogger logger) { _distributedCache = distributedCache; @@ -32,6 +35,15 @@ namespace Umbraco.Web.Cache _umbracoContextFactory = umbracoContextFactory; } + /// + /// Initializes a new instance of the class. + /// + public DistributedCacheBinder(DistributedCache distributedCache, IUmbracoContextFactory umbracoContextFactory, ILogger logger, IServerMessenger serverMessenger) + : this(distributedCache, umbracoContextFactory, logger) + { + _serverMessenger = serverMessenger as BatchedDatabaseServerMessenger; + } + // internal for tests internal static MethodInfo FindHandler(IEventDefinition eventDefinition) { @@ -42,7 +54,6 @@ namespace Umbraco.Web.Cache private static readonly Lazy CandidateHandlers = new Lazy(() => { - return typeof(DistributedCacheBinder) .GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) .Select(x => @@ -66,9 +77,9 @@ namespace Umbraco.Web.Cache { // Ensure we run with an UmbracoContext, because this may run in a background task, // yet developers may be using the 'current' UmbracoContext in the event handlers. - using (_umbracoContextFactory.EnsureUmbracoContext()) + using (var umbracoContextReference = _umbracoContextFactory.EnsureUmbracoContext()) { - // When it comes to content types types, a change to any single one will trigger a reload of the content and media caches. + // When it comes to content types, a change to any single one will trigger a reload of the content and media caches. // We can reduce the impact of that by grouping the events to invoke just one per type, providing a collection of the individual arguments. var groupedEvents = GetGroupedEventList(events); foreach (var e in groupedEvents) @@ -84,6 +95,24 @@ namespace Umbraco.Web.Cache handler.Invoke(this, new[] { e.Sender, e.Args }); } + + // Handled events may be triggering messages to be sent for load balanced servers to refresh their caches. + // When the state changes that initiate the events are handled outside of an Umbraco request and rather in a + // background task, we'll have ensured an Umbraco context, but using a newly created HttpContext. + // + // An example of this is when using an Umbraco Deploy content transfer operation + // (see: https://github.com/umbraco/Umbraco.Deploy.Issues/issues/90). + // + // This will be used in the event handlers, and when the methods on BatchedDatabaseServerMessenger are called, + // they'll be using this "ensured" HttpContext, populating a batch of message stored in HttpContext.Items. + // When the FlushBatch method is called on the end of an Umbraco request (via the event handler wired up in + // DatabaseServerRegistrarAndMessengerComponent), this will use the HttpContext associated with the request, + // which will be a different one, and so won't have the batch stored in it's HttpContext.Items. + // + // As such by making an explicit call here, and providing the ensured HttpContext that will have had it's + // Items dictionary populated with the batch of messages, we'll make sure the batch is flushed, and the + // database instructions written. + _serverMessenger?.FlushBatch(umbracoContextReference.UmbracoContext.HttpContext); } } From a69499a13664fac9e5976825fc37690e51d2c373 Mon Sep 17 00:00:00 2001 From: Mole Date: Thu, 16 Dec 2021 09:10:55 +0100 Subject: [PATCH 009/141] Bump versions --- .../UmbracoPackage/.template.config/template.json | 2 +- .../UmbracoProject/.template.config/template.json | 2 +- src/Directory.Build.props | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/build/templates/UmbracoPackage/.template.config/template.json b/build/templates/UmbracoPackage/.template.config/template.json index b69b63dd1c..1a4dd16fd7 100644 --- a/build/templates/UmbracoPackage/.template.config/template.json +++ b/build/templates/UmbracoPackage/.template.config/template.json @@ -24,7 +24,7 @@ "version": { "type": "parameter", "datatype": "string", - "defaultValue": "9.2.0-rc", + "defaultValue": "9.3.0-rc", "description": "The version of Umbraco to load using NuGet", "replaces": "UMBRACO_VERSION_FROM_TEMPLATE" }, diff --git a/build/templates/UmbracoProject/.template.config/template.json b/build/templates/UmbracoProject/.template.config/template.json index 6ec0babd70..fd41de8d1c 100644 --- a/build/templates/UmbracoProject/.template.config/template.json +++ b/build/templates/UmbracoProject/.template.config/template.json @@ -57,7 +57,7 @@ "version": { "type": "parameter", "datatype": "string", - "defaultValue": "9.2.0-rc", + "defaultValue": "9.3.0-rc", "description": "The version of Umbraco to load using NuGet", "replaces": "UMBRACO_VERSION_FROM_TEMPLATE" }, diff --git a/src/Directory.Build.props b/src/Directory.Build.props index fed70283e7..995c8afebd 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -3,10 +3,10 @@ - 9.2.0 - 9.2.0 - 9.2.0-rc - 9.2.0 + 9.3.0 + 9.3.0 + 9.3.0-rc + 9.3.0 9.0 en-US Umbraco CMS From 7006461ba27102597bc7d9b9c0fbf34ea5e2c180 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 16 Dec 2021 16:30:18 +0100 Subject: [PATCH 010/141] Revert "Added notifications toggle to user groups (#10450)" This reverts commit 4c6d4b9326c094f2e66f627083ccf875a01188a2. --- .../Migrations/Install/DatabaseDataCreator.cs | 6 ++--- .../Migrations/Upgrade/UmbracoPlan.cs | 5 +--- .../AddDefaultForNotificationsToggle.cs | 15 ------------ src/Umbraco.Core/Umbraco.Core.csproj | 1 - src/Umbraco.Web.UI/Umbraco/config/lang/da.xml | 1 - src/Umbraco.Web.UI/Umbraco/config/lang/en.xml | 1 - .../Umbraco/config/lang/en_us.xml | 1 - src/Umbraco.Web/Actions/ActionNotify.cs | 24 ------------------- .../Trees/ContentTreeController.cs | 7 +++++- src/Umbraco.Web/Umbraco.Web.csproj | 1 - 10 files changed, 10 insertions(+), 52 deletions(-) delete mode 100644 src/Umbraco.Core/Migrations/Upgrade/V_8_18_0/AddDefaultForNotificationsToggle.cs delete mode 100644 src/Umbraco.Web/Actions/ActionNotify.cs diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs index d9cc22d26d..bd7a96f6e7 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs @@ -188,9 +188,9 @@ namespace Umbraco.Core.Migrations.Install private void CreateUserGroupData() { - _database.Insert(Constants.DatabaseSchema.Tables.UserGroup, "id", false, new UserGroupDto { Id = 1, StartMediaId = -1, StartContentId = -1, Alias = Constants.Security.AdminGroupAlias, Name = "Administrators", DefaultPermissions = "CADMOSKTPIURZ:5F7ïN", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-medal" }); - _database.Insert(Constants.DatabaseSchema.Tables.UserGroup, "id", false, new UserGroupDto { Id = 2, StartMediaId = -1, StartContentId = -1, Alias = Constants.Security.WriterGroupAlias, Name = "Writers", DefaultPermissions = "CAH:FN", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-edit" }); - _database.Insert(Constants.DatabaseSchema.Tables.UserGroup, "id", false, new UserGroupDto { Id = 3, StartMediaId = -1, StartContentId = -1, Alias = Constants.Security.EditorGroupAlias, Name = "Editors", DefaultPermissions = "CADMOSKTPUZ:5FïN", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-tools" }); + _database.Insert(Constants.DatabaseSchema.Tables.UserGroup, "id", false, new UserGroupDto { Id = 1, StartMediaId = -1, StartContentId = -1, Alias = Constants.Security.AdminGroupAlias, Name = "Administrators", DefaultPermissions = "CADMOSKTPIURZ:5F7ï", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-medal" }); + _database.Insert(Constants.DatabaseSchema.Tables.UserGroup, "id", false, new UserGroupDto { Id = 2, StartMediaId = -1, StartContentId = -1, Alias = Constants.Security.WriterGroupAlias, Name = "Writers", DefaultPermissions = "CAH:F", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-edit" }); + _database.Insert(Constants.DatabaseSchema.Tables.UserGroup, "id", false, new UserGroupDto { Id = 3, StartMediaId = -1, StartContentId = -1, Alias = Constants.Security.EditorGroupAlias, Name = "Editors", DefaultPermissions = "CADMOSKTPUZ:5Fï", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-tools" }); _database.Insert(Constants.DatabaseSchema.Tables.UserGroup, "id", false, new UserGroupDto { Id = 4, StartMediaId = -1, StartContentId = -1, Alias = Constants.Security.TranslatorGroupAlias, Name = "Translators", DefaultPermissions = "AF", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-globe" }); _database.Insert(Constants.DatabaseSchema.Tables.UserGroup, "id", false, new UserGroupDto { Id = 5, StartMediaId = -1, StartContentId = -1, Alias = Constants.Security.SensitiveDataGroupAlias, Name = "Sensitive data", DefaultPermissions = "", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-lock" }); } diff --git a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs index 895fd1946b..a557c7e78a 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs @@ -211,11 +211,8 @@ namespace Umbraco.Core.Migrations.Upgrade // to 8.17.0 To("{153865E9-7332-4C2A-9F9D-F20AEE078EC7}"); - // to 8.18.0 - To("{8BAF5E6C-DCB7-41AE-824F-4215AE4F1F98}"); - To("{AD3D3B7F-8E74-45A4-85DB-7FFAD57F9243}"); - //FINAL + To("{8BAF5E6C-DCB7-41AE-824F-4215AE4F1F98}"); } } } diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_18_0/AddDefaultForNotificationsToggle.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_18_0/AddDefaultForNotificationsToggle.cs deleted file mode 100644 index 0173600584..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_18_0/AddDefaultForNotificationsToggle.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Umbraco.Core.Migrations.Upgrade.V_8_18_0 -{ - public class AddDefaultForNotificationsToggle : MigrationBase - { - public AddDefaultForNotificationsToggle(IMigrationContext context) : base(context) - { - } - - public override void Migrate() - { - var updateSQL = Sql($"UPDATE {Constants.DatabaseSchema.Tables.UserGroup} SET userGroupDefaultPermissions = userGroupDefaultPermissions + 'N' WHERE userGroupAlias IN ('admin', 'writer', 'editor')"); - Execute.Sql(updateSQL.SQL).Do(); - } - } -} diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index e6012c9e97..632031a2e6 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -151,7 +151,6 @@ - diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml index 07752530fa..2dfec4523c 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml @@ -85,7 +85,6 @@ Tillad adgang til at oversætte en node Tillad adgang til at gemme en node Tillad adgang til at oprette en indholdsskabelon - Tillad adgang til at oprette notificeringer for noder Indhold diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index 6d29c9adcd..ef63b4f292 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -82,7 +82,6 @@ Allow access to translate a node Allow access to save a node Allow access to create a Content Template - Allow access to setup notifications for content nodes Content 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 6c75d4e04a..e0560ec507 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -83,7 +83,6 @@ Allow access to translate a node Allow access to save a node Allow access to create a Content Template - Allow access to setup notifications for content nodes Content diff --git a/src/Umbraco.Web/Actions/ActionNotify.cs b/src/Umbraco.Web/Actions/ActionNotify.cs deleted file mode 100644 index a8f6a4c2a2..0000000000 --- a/src/Umbraco.Web/Actions/ActionNotify.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Umbraco.Core; - -namespace Umbraco.Web.Actions -{ - public class ActionNotify : IAction - { - public char Letter => 'N'; - - public bool ShowInNotifier => false; - - public bool CanBePermissionAssigned => true; - - public string Icon => "megaphone"; - - public string Alias => "notify"; - - public string Category => Constants.Conventions.PermissionCategories.ContentCategory; - } -} diff --git a/src/Umbraco.Web/Trees/ContentTreeController.cs b/src/Umbraco.Web/Trees/ContentTreeController.cs index e6b4f45d22..c2c938249e 100644 --- a/src/Umbraco.Web/Trees/ContentTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTreeController.cs @@ -242,7 +242,12 @@ namespace Umbraco.Web.Trees if (EmailSender.CanSendRequiredEmail) { - AddActionNode(item, menu, true, opensDialog: true); + menu.Items.Add(new MenuItem("notify", Services.TextService) + { + Icon = "megaphone", + SeparatorBefore = true, + OpensDialog = true + }); } if((item is DocumentEntitySlim documentEntity && documentEntity.IsContainer) == false) diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index fb2ce5dfc1..5c84e65514 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -136,7 +136,6 @@ Properties\SolutionInfo.cs - From 0ba51f87eaf086bbbf79815ae8a3a25c24106a94 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Mon, 20 Dec 2021 08:45:11 +0100 Subject: [PATCH 011/141] Move member properties to Member Content App (V9 merge regression) (#11768) * Fix regression after merging to v9 * Update test to align with removed member properties --- .../Mapping/MemberTabsAndPropertiesMapper.cs | 34 +++++-------------- .../Controllers/MemberControllerUnitTests.cs | 14 -------- 2 files changed, 9 insertions(+), 39 deletions(-) diff --git a/src/Umbraco.Core/Models/Mapping/MemberTabsAndPropertiesMapper.cs b/src/Umbraco.Core/Models/Mapping/MemberTabsAndPropertiesMapper.cs index d61e32d88a..d8ac8d635d 100644 --- a/src/Umbraco.Core/Models/Mapping/MemberTabsAndPropertiesMapper.cs +++ b/src/Umbraco.Core/Models/Mapping/MemberTabsAndPropertiesMapper.cs @@ -65,14 +65,11 @@ namespace Umbraco.Cms.Core.Models.Mapping var resolved = base.Map(source, context); - // This is kind of a hack because a developer is supposed to be allowed to set their property editor - would have been much easier - // if we just had all of the membership provider fields on the member table :( - // TODO: But is there a way to map the IMember.IsLockedOut to the property ? i dunno. + // IMember.IsLockedOut can't be set to true, so make it readonly when that's the case (you can only unlock) var isLockedOutProperty = resolved.SelectMany(x => x.Properties).FirstOrDefault(x => x.Alias == Constants.Conventions.Member.IsLockedOut); if (isLockedOutProperty?.Value != null && isLockedOutProperty.Value.ToString() != "1") { - isLockedOutProperty.View = "readonlyvalue"; - isLockedOutProperty.Value = _localizedTextService.Localize("general", "no"); + isLockedOutProperty.Readonly = true; } return resolved; @@ -191,20 +188,6 @@ namespace Umbraco.Cms.Core.Models.Mapping { var properties = new List { - new ContentPropertyDisplay - { - Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}id", - Label = _localizedTextService.Localize("general","id"), - Value = new List {member.Id.ToString(), member.Key.ToString()}, - View = "idwithguid" - }, - new ContentPropertyDisplay - { - Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}doctype", - Label = _localizedTextService.Localize("content","membertype"), - Value = _localizedTextService.UmbracoDictionaryTranslate(CultureDictionary, member.ContentType.Name), - View = _propertyEditorCollection[Constants.PropertyEditors.Aliases.Label].GetValueEditor().View - }, GetLoginProperty(member, _localizedTextService), new ContentPropertyDisplay { @@ -212,7 +195,7 @@ namespace Umbraco.Cms.Core.Models.Mapping Label = _localizedTextService.Localize("general","email"), Value = member.Email, View = "email", - Validation = {Mandatory = true} + Validation = { Mandatory = true } }, new ContentPropertyDisplay { @@ -221,12 +204,10 @@ namespace Umbraco.Cms.Core.Models.Mapping Value = new Dictionary { // TODO: why ignoreCase, what are we doing here?! - {"newPassword", member.GetAdditionalDataValueIgnoreCase("NewPassword", null)}, + { "newPassword", member.GetAdditionalDataValueIgnoreCase("NewPassword", null) } }, - // TODO: Hard coding this because the changepassword doesn't necessarily need to be a resolvable (real) property editor View = "changepassword", - // Initialize the dictionary with the configuration from the default membership provider - Config = GetPasswordConfig(member) + Config = GetPasswordConfig(member) // Initialize the dictionary with the configuration from the default membership provider }, new ContentPropertyDisplay { @@ -234,7 +215,10 @@ namespace Umbraco.Cms.Core.Models.Mapping Label = _localizedTextService.Localize("content","membergroup"), Value = GetMemberGroupValue(member.Username), View = "membergroups", - Config = new Dictionary {{"IsRequired", true}} + Config = new Dictionary + { + { "IsRequired", true } + } } }; diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/MemberControllerUnitTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/MemberControllerUnitTests.cs index da5175f272..069e94f732 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/MemberControllerUnitTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/MemberControllerUnitTests.cs @@ -618,20 +618,6 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers Id = 77, Properties = new List() { - new ContentPropertyDisplay() - { - Alias = "_umb_id", - View = "idwithguid", - Value = new [] - { - "123", - "guid" - } - }, - new ContentPropertyDisplay() - { - Alias = "_umb_doctype" - }, new ContentPropertyDisplay() { Alias = "_umb_login" From 4e6d09b6268da683f38755d6603ce1da7a9d6175 Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska Date: Mon, 20 Dec 2021 14:37:18 +0100 Subject: [PATCH 012/141] Adds a new Health Check --- .../Security/UmbracoApplicationUrlCheck.cs | 101 ++++++++++++++++++ src/Umbraco.Web/Umbraco.Web.csproj | 1 + 2 files changed, 102 insertions(+) create mode 100644 src/Umbraco.Web/HealthCheck/Checks/Security/UmbracoApplicationUrlCheck.cs diff --git a/src/Umbraco.Web/HealthCheck/Checks/Security/UmbracoApplicationUrlCheck.cs b/src/Umbraco.Web/HealthCheck/Checks/Security/UmbracoApplicationUrlCheck.cs new file mode 100644 index 0000000000..f5e571a86a --- /dev/null +++ b/src/Umbraco.Web/HealthCheck/Checks/Security/UmbracoApplicationUrlCheck.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using Umbraco.Core; +using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.IO; +using Umbraco.Core.Services; +using Umbraco.Web.HealthCheck.Checks.Config; + +namespace Umbraco.Web.HealthCheck.Checks.Security +{ + [HealthCheck( + "6708CA45-E96E-40B8-A40A-0607C1CA7F28", + "Application URL Configuration", + Description = "Checks if the Umbraco application URL is configured for your site.", + Group = "Security")] + public class UmbracoApplicationUrlCheck : HealthCheck + { + private readonly ILocalizedTextService _textService; + private readonly IRuntimeState _runtime; + private readonly IUmbracoSettingsSection _settings; + + private const string SetApplicationUrlAction = "setApplicationUrl"; + + public UmbracoApplicationUrlCheck(ILocalizedTextService textService, IRuntimeState runtime, IUmbracoSettingsSection settings) + { + _textService = textService; + _runtime = runtime; + _settings = settings; + } + + /// + /// Executes the action and returns its status + /// + /// + /// + public override HealthCheckStatus ExecuteAction(HealthCheckAction action) + { + switch (action.Alias) + { + case SetApplicationUrlAction: + return SetUmbracoApplicationUrl(); + default: + throw new InvalidOperationException("UmbracoApplicationUrlCheck action requested is either not executable or does not exist"); + } + } + + public override IEnumerable GetStatus() + { + //return the statuses + return new[] { CheckUmbracoApplicationUrl() }; + } + + private HealthCheckStatus CheckUmbracoApplicationUrl() + { + var urlConfigured = !_settings.WebRouting.UmbracoApplicationUrl.IsNullOrWhiteSpace(); + var actions = new List(); + + string resultMessage = _textService.Localize("healthcheck", "umbracoApplicationUrlCheckResult", new[] { urlConfigured ? string.Empty : "not" }); + StatusResultType resultType = urlConfigured ? StatusResultType.Success : StatusResultType.Warning; + + if (urlConfigured == false) + { + actions.Add(new HealthCheckAction(SetApplicationUrlAction, Id) + { + Name = _textService.Localize("healthcheck", "umbracoApplicationUrlConfigureButton"), + Description = _textService.Localize("healthcheck", "umbracoApplicationUrlConfigureDescription") + }); + } + + return new HealthCheckStatus(resultMessage) + { + ResultType = resultType, + Actions = actions + }; + } + + private HealthCheckStatus SetUmbracoApplicationUrl() + { + var configFilePath = IOHelper.MapPath("~/config/umbracoSettings.config"); + const string xPath = "/settings/web.routing/@umbracoApplicationUrl"; + var configurationService = new ConfigurationService(configFilePath, xPath, _textService); + var urlValue = _runtime.ApplicationUrl.ToString(); + var updateConfigFile = configurationService.UpdateConfigFile(urlValue); + + if (updateConfigFile.Success) + { + return + new HealthCheckStatus(_textService.Localize("healthcheck", "umbracoApplicationUrlConfigureSuccess", new[] { urlValue })) + { + ResultType = StatusResultType.Success + }; + } + + return + new HealthCheckStatus(_textService.Localize("healthcheck", "umbracoApplicationUrlConfigureError", new[] { updateConfigFile.Result })) + { + ResultType = StatusResultType.Error + }; + } + } +} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 5c84e65514..89317e8f99 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -190,6 +190,7 @@ + From bcabf0599576b6397bbd7598391b2127c693c084 Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska Date: Mon, 20 Dec 2021 14:37:42 +0100 Subject: [PATCH 013/141] Adds new translation --- src/Umbraco.Web.UI/Umbraco/config/lang/en.xml | 5 +++++ src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index ef63b4f292..8b16c80a25 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -2182,6 +2182,11 @@ To manage your website, simply open the Umbraco backoffice and start adding cont + The 'umbracoApplicationUrl' option is %0% set in your umbracoSettings.config file. + Set Umbraco application URL in Config + Adds a value to the 'umbracoApplicationUrl' option of umbracoSettings.config to prevent configuring insecure endpoint as the hostname of your Umbraco application. + %0% in your umbracoSettings.config file.]]> + Could not update the value of 'umbracoApplicationUrl' option in your umbracoSettings.config file. Error: %0% %0%.]]> %0%. If they aren't being written to no action need be taken.]]> X-Frame-Options used to control whether a site can be IFRAMEd by another was found.]]> 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 e0560ec507..9ce74e904b 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -2219,6 +2219,11 @@ To manage your website, simply open the Umbraco backoffice and start adding cont + The 'umbracoApplicationUrl' option is %0% set in your umbracoSettings.config file. + Set Umbraco application URL in Config + Adds a value to the 'umbracoApplicationUrl' option of umbracoSettings.config to prevent configuring insecure endpoint as the hostname of your Umbraco application. + %0% in your umbracoSettings.config file.]]> + Could not update the value of 'umbracoApplicationUrl' option in your umbracoSettings.config file. Error: %0% %0%.]]> %0%. If they aren't being written to no action need be taken.]]> X-Frame-Options used to control whether a site can be IFRAMEd by another was found.]]> From 68fdf6521e3caf8ef9d5dc3073ccf36d0c295a17 Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska Date: Mon, 20 Dec 2021 15:21:12 +0100 Subject: [PATCH 014/141] Being more descriptive --- src/Umbraco.Web.UI/Umbraco/config/lang/en.xml | 3 ++- .../Umbraco/config/lang/en_us.xml | 3 ++- .../Security/UmbracoApplicationUrlCheck.cs | 18 +++++++++++++----- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index 8b16c80a25..2b62c6ba21 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -2182,7 +2182,8 @@ To manage your website, simply open the Umbraco backoffice and start adding cont - The 'umbracoApplicationUrl' option is %0% set in your umbracoSettings.config file. + %0% in your umbracoSettings.config file.]]> + The 'umbracoApplicationUrl' option is not set in your umbracoSettings.config file. Set Umbraco application URL in Config Adds a value to the 'umbracoApplicationUrl' option of umbracoSettings.config to prevent configuring insecure endpoint as the hostname of your Umbraco application. %0% in your umbracoSettings.config file.]]> 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 9ce74e904b..79121d015d 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -2219,7 +2219,8 @@ To manage your website, simply open the Umbraco backoffice and start adding cont - The 'umbracoApplicationUrl' option is %0% set in your umbracoSettings.config file. + %0% in your umbracoSettings.config file.]]> + The 'umbracoApplicationUrl' option is not set in your umbracoSettings.config file. Set Umbraco application URL in Config Adds a value to the 'umbracoApplicationUrl' option of umbracoSettings.config to prevent configuring insecure endpoint as the hostname of your Umbraco application. %0% in your umbracoSettings.config file.]]> diff --git a/src/Umbraco.Web/HealthCheck/Checks/Security/UmbracoApplicationUrlCheck.cs b/src/Umbraco.Web/HealthCheck/Checks/Security/UmbracoApplicationUrlCheck.cs index f5e571a86a..bc14f43235 100644 --- a/src/Umbraco.Web/HealthCheck/Checks/Security/UmbracoApplicationUrlCheck.cs +++ b/src/Umbraco.Web/HealthCheck/Checks/Security/UmbracoApplicationUrlCheck.cs @@ -52,20 +52,28 @@ namespace Umbraco.Web.HealthCheck.Checks.Security private HealthCheckStatus CheckUmbracoApplicationUrl() { - var urlConfigured = !_settings.WebRouting.UmbracoApplicationUrl.IsNullOrWhiteSpace(); + var url = _settings.WebRouting.UmbracoApplicationUrl; + + string resultMessage; + StatusResultType resultType; var actions = new List(); - string resultMessage = _textService.Localize("healthcheck", "umbracoApplicationUrlCheckResult", new[] { urlConfigured ? string.Empty : "not" }); - StatusResultType resultType = urlConfigured ? StatusResultType.Success : StatusResultType.Warning; - - if (urlConfigured == false) + if (url.IsNullOrWhiteSpace()) { + resultMessage = _textService.Localize("healthcheck", "umbracoApplicationUrlCheckResultFalse"); + resultType = StatusResultType.Warning; + actions.Add(new HealthCheckAction(SetApplicationUrlAction, Id) { Name = _textService.Localize("healthcheck", "umbracoApplicationUrlConfigureButton"), Description = _textService.Localize("healthcheck", "umbracoApplicationUrlConfigureDescription") }); } + else + { + resultMessage = _textService.Localize("healthcheck", "umbracoApplicationUrlCheckResultTrue", new[] { url }); + resultType = StatusResultType.Success; + } return new HealthCheckStatus(resultMessage) { From 7a5f72b5eb0b5b2bf8ec40ddad3060263b2dc4da Mon Sep 17 00:00:00 2001 From: Mole Date: Tue, 21 Dec 2021 07:23:08 +0100 Subject: [PATCH 015/141] Use current request for emails (#11775) * Use current request for emails * Fix tests --- src/Umbraco.Core/Sync/ApplicationUrlHelper.cs | 17 ++++++++++ .../AuthenticationControllerTests.cs | 4 ++- .../Web/Controllers/UsersControllerTests.cs | 13 ++++--- .../Editors/AuthenticationController.cs | 19 +++++++++-- src/Umbraco.Web/Editors/UsersController.cs | 34 +++++++++++++------ 5 files changed, 69 insertions(+), 18 deletions(-) diff --git a/src/Umbraco.Core/Sync/ApplicationUrlHelper.cs b/src/Umbraco.Core/Sync/ApplicationUrlHelper.cs index 52af734f1c..d934e24575 100644 --- a/src/Umbraco.Core/Sync/ApplicationUrlHelper.cs +++ b/src/Umbraco.Core/Sync/ApplicationUrlHelper.cs @@ -102,5 +102,22 @@ namespace Umbraco.Core.Sync return url.TrimEnd(Constants.CharArrays.ForwardSlash); } + + /// + /// Will get the application URL from configuration, if none is specified will fall back to URL from request. + /// + /// + /// + /// + /// + public static Uri GetApplicationUriUncached( + HttpRequestBase request, + IUmbracoSettingsSection umbracoSettingsSection) + { + var settingUrl = umbracoSettingsSection.WebRouting.UmbracoApplicationUrl; + return string.IsNullOrEmpty(settingUrl) + ? new Uri(request.Url, IOHelper.ResolveUrl(SystemDirectories.Umbraco)) + : new Uri(settingUrl); + } } } diff --git a/src/Umbraco.Tests/Web/Controllers/AuthenticationControllerTests.cs b/src/Umbraco.Tests/Web/Controllers/AuthenticationControllerTests.cs index 3d264663b5..9bd9ee73ed 100644 --- a/src/Umbraco.Tests/Web/Controllers/AuthenticationControllerTests.cs +++ b/src/Umbraco.Tests/Web/Controllers/AuthenticationControllerTests.cs @@ -16,6 +16,7 @@ using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; +using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Persistence; @@ -82,7 +83,8 @@ namespace Umbraco.Tests.Web.Controllers Factory.GetInstance(), Factory.GetInstance(), Factory.GetInstance(), - helper); + helper, + Factory.GetInstance()); return usersController; } diff --git a/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs b/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs index 85dd303432..b9289c1392 100644 --- a/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs +++ b/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs @@ -14,6 +14,7 @@ using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; +using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; @@ -84,7 +85,8 @@ namespace Umbraco.Tests.Web.Controllers Factory.GetInstance(), Factory.GetInstance(), Factory.GetInstance(), - helper); + helper, + Factory.GetInstance()); return usersController; } @@ -148,7 +150,8 @@ namespace Umbraco.Tests.Web.Controllers Factory.GetInstance(), Factory.GetInstance(), Factory.GetInstance(), - helper); + helper, + Factory.GetInstance()); return usersController; } @@ -183,7 +186,8 @@ namespace Umbraco.Tests.Web.Controllers Factory.GetInstance(), Factory.GetInstance(), Factory.GetInstance(), - helper); + helper, + Factory.GetInstance()); return usersController; } @@ -253,7 +257,8 @@ namespace Umbraco.Tests.Web.Controllers Factory.GetInstance(), Factory.GetInstance(), Factory.GetInstance(), - helper); + helper, + Factory.GetInstance()); return usersController; } diff --git a/src/Umbraco.Web/Editors/AuthenticationController.cs b/src/Umbraco.Web/Editors/AuthenticationController.cs index 3ecc6b64a4..54612377e0 100644 --- a/src/Umbraco.Web/Editors/AuthenticationController.cs +++ b/src/Umbraco.Web/Editors/AuthenticationController.cs @@ -27,6 +27,8 @@ using Umbraco.Web.Composing; using IUser = Umbraco.Core.Models.Membership.IUser; using Umbraco.Web.Editors.Filters; using Microsoft.Owin.Security; +using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.Sync; namespace Umbraco.Web.Editors { @@ -40,12 +42,23 @@ namespace Umbraco.Web.Editors [DisableBrowserCache] public class AuthenticationController : UmbracoApiController { + private readonly IUmbracoSettingsSection _umbracoSettingsSection; private BackOfficeUserManager _userManager; private BackOfficeSignInManager _signInManager; - public AuthenticationController(IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper) + public AuthenticationController( + IGlobalSettings globalSettings, + IUmbracoContextAccessor umbracoContextAccessor, + ISqlContext sqlContext, + ServiceContext services, + AppCaches appCaches, + IProfilingLogger logger, + IRuntimeState runtimeState, + UmbracoHelper umbracoHelper, + IUmbracoSettingsSection umbracoSettingsSection) : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper, Current.Mapper) { + _umbracoSettingsSection = umbracoSettingsSection; } protected BackOfficeUserManager UserManager => _userManager @@ -552,8 +565,8 @@ namespace Umbraco.Web.Editors r = code }); - // Construct full URL using configured application URL (which will fall back to request) - var applicationUri = Current.RuntimeState.ApplicationUrl; + // Construct full URL using configured application URL (which will fall back to current request) + var applicationUri = ApplicationUrlHelper.GetApplicationUriUncached(http.Request, _umbracoSettingsSection); var callbackUri = new Uri(applicationUri, action); return callbackUri.ToString(); } diff --git a/src/Umbraco.Web/Editors/UsersController.cs b/src/Umbraco.Web/Editors/UsersController.cs index dda0dfc933..4bfd72854f 100644 --- a/src/Umbraco.Web/Editors/UsersController.cs +++ b/src/Umbraco.Web/Editors/UsersController.cs @@ -17,6 +17,7 @@ using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; +using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models; @@ -27,6 +28,7 @@ using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Security; using Umbraco.Core.Services; +using Umbraco.Core.Sync; using Umbraco.Web.Editors.Filters; using Umbraco.Web.Models; using Umbraco.Web.Models.ContentEditing; @@ -46,9 +48,21 @@ namespace Umbraco.Web.Editors [IsCurrentUserModelFilter] public class UsersController : UmbracoAuthorizedJsonController { - public UsersController(IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper) + private readonly IUmbracoSettingsSection _umbracoSettingsSection; + + public UsersController( + IGlobalSettings globalSettings, + IUmbracoContextAccessor umbracoContextAccessor, + ISqlContext sqlContext, + ServiceContext services, + AppCaches appCaches, + IProfilingLogger logger, + IRuntimeState runtimeState, + UmbracoHelper umbracoHelper, + IUmbracoSettingsSection umbracoSettingsSection) : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper) { + _umbracoSettingsSection = umbracoSettingsSection; } /// @@ -390,7 +404,7 @@ namespace Umbraco.Web.Editors user = CheckUniqueEmail(userSave.Email, u => u.LastLoginDate != default || u.EmailConfirmedDate.HasValue); var userMgr = TryGetOwinContext().Result.GetBackOfficeUserManager(); - + if (!EmailSender.CanSendRequiredEmail && !userMgr.HasSendingUserInviteEventHandler) { throw new HttpResponseException( @@ -462,12 +476,12 @@ namespace Umbraco.Web.Editors Email = userSave.Email, Username = userSave.Username }; - } + } } else { //send the email - await SendUserInviteEmailAsync(display, Security.CurrentUser.Name, Security.CurrentUser.Email, user, userSave.Message); + await SendUserInviteEmailAsync(display, Security.CurrentUser.Name, Security.CurrentUser.Email, user, userSave.Message); } display.AddSuccessNotification(Services.TextService.Localize("speechBubbles", "resendInviteHeader"), Services.TextService.Localize("speechBubbles", "resendInviteSuccess", new[] { user.Name })); @@ -525,9 +539,9 @@ namespace Umbraco.Web.Editors invite = inviteToken }); - // Construct full URL using configured application URL (which will fall back to request) - var applicationUri = RuntimeState.ApplicationUrl; - var inviteUri = new Uri(applicationUri, action); + // Construct full URL will use the value in settings if specified, otherwise will use the current request URL + var requestUrl = ApplicationUrlHelper.GetApplicationUriUncached(http.Request, _umbracoSettingsSection); + var inviteUri = new Uri(requestUrl, action); var emailSubject = Services.TextService.Localize("user", "inviteEmailCopySubject", //Ensure the culture of the found user is used for the email! @@ -622,7 +636,7 @@ namespace Umbraco.Web.Editors if (Current.Configs.Settings().Security.UsernameIsEmail && found.Username == found.Email && userSave.Username != userSave.Email) { userSave.Username = userSave.Email; - } + } if (hasErrors) throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState)); @@ -647,13 +661,13 @@ namespace Umbraco.Web.Editors } /// - /// + /// /// /// /// public async Task> PostChangePassword(ChangingPasswordModel changingPasswordModel) { - changingPasswordModel = changingPasswordModel ?? throw new ArgumentNullException(nameof(changingPasswordModel)); + changingPasswordModel = changingPasswordModel ?? throw new ArgumentNullException(nameof(changingPasswordModel)); if (ModelState.IsValid == false) { From 2b2e703e30014006bab7dd1f7d76aec7a81f2e55 Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska Date: Tue, 21 Dec 2021 08:57:28 +0100 Subject: [PATCH 016/141] Implement #11776 for v9 --- .../Security/UmbracoApplicationUrlCheck.cs | 68 +++++++++++++++++++ src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 2 + .../umbraco/config/lang/en_us.xml | 2 + 3 files changed, 72 insertions(+) create mode 100644 src/Umbraco.Core/HealthChecks/Checks/Security/UmbracoApplicationUrlCheck.cs diff --git a/src/Umbraco.Core/HealthChecks/Checks/Security/UmbracoApplicationUrlCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Security/UmbracoApplicationUrlCheck.cs new file mode 100644 index 0000000000..44b10ba0e3 --- /dev/null +++ b/src/Umbraco.Core/HealthChecks/Checks/Security/UmbracoApplicationUrlCheck.cs @@ -0,0 +1,68 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Services; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Core.HealthChecks.Checks.Security +{ + [HealthCheck( + "6708CA45-E96E-40B8-A40A-0607C1CA7F28", + "Application URL Configuration", + Description = "Checks if the Umbraco application URL is configured for your site.", + Group = "Security")] + public class UmbracoApplicationUrlCheck : HealthCheck + { + private readonly ILocalizedTextService _textService; + private readonly IOptionsMonitor _webRoutingSettings; + + public UmbracoApplicationUrlCheck(ILocalizedTextService textService, IOptionsMonitor webRoutingSettings) + { + _textService = textService; + _webRoutingSettings = webRoutingSettings; + } + + /// + /// Executes the action and returns its status + /// + public override HealthCheckStatus ExecuteAction(HealthCheckAction action) => throw new InvalidOperationException("UmbracoApplicationUrlCheck has no executable actions"); + + /// + /// Get the status for this health check + /// + public override Task> GetStatus() => + Task.FromResult(CheckUmbracoApplicationUrl().Yield()); + + private HealthCheckStatus CheckUmbracoApplicationUrl() + { + var url = _webRoutingSettings.CurrentValue.UmbracoApplicationUrl; + + string resultMessage; + StatusResultType resultType; + var success = false; + + if (url.IsNullOrWhiteSpace()) + { + resultMessage = _textService.Localize("healthcheck", "umbracoApplicationUrlCheckResultFalse"); + resultType = StatusResultType.Warning; + } + else + { + resultMessage = _textService.Localize("healthcheck", "umbracoApplicationUrlCheckResultTrue", new[] { url }); + resultType = StatusResultType.Success; + success = true; + } + + return new HealthCheckStatus(resultMessage) + { + ResultType = resultType, + ReadMoreLink = success ? null : Constants.HealthChecks.DocumentationLinks.Security.UmbracoApplicationUrlCheck + }; + } + } +} diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index 468a0cc735..4411209cd5 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -2290,6 +2290,8 @@ To manage your website, simply open the Umbraco backoffice and start adding cont + %0%.]]> + The appSetting 'Umbraco:CMS:WebRouting:UmbracoApplicationUrl' is not set. X-Frame-Options used to control whether a site can be IFRAMEd by another was found.]]> 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 aef05ca973..5a17eafbb2 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -2372,6 +2372,8 @@ To manage your website, simply open the Umbraco backoffice and start adding cont + %0%.]]> + The appSetting 'Umbraco:CMS:WebRouting:UmbracoApplicationUrl' is not set. X-Frame-Options used to control whether a site can be IFRAMEd by another was found.]]> From cc8ea0e8ebb5154d369ea59b9b27fe2b8f54390b Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska Date: Tue, 21 Dec 2021 08:58:13 +0100 Subject: [PATCH 017/141] Adding UmbracoApplicationUrlCheck "Read more" URL --- src/Umbraco.Core/Constants-HealthChecks.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Core/Constants-HealthChecks.cs b/src/Umbraco.Core/Constants-HealthChecks.cs index 5770bd07e4..5a8ea401cb 100644 --- a/src/Umbraco.Core/Constants-HealthChecks.cs +++ b/src/Umbraco.Core/Constants-HealthChecks.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core +namespace Umbraco.Cms.Core { /// /// Defines constants. @@ -20,15 +20,16 @@ public const string CompilationDebugCheck = "https://umbra.co/healthchecks-compilation-debug"; } + public static class Configuration { public const string MacroErrorsCheck = "https://umbra.co/healthchecks-macro-errors"; public const string TrySkipIisCustomErrorsCheck = "https://umbra.co/healthchecks-skip-iis-custom-errors"; public const string NotificationEmailCheck = "https://umbra.co/healthchecks-notification-email"; } + public static class FolderAndFilePermissionsCheck { - public const string FileWriting = "https://umbra.co/healthchecks-file-writing"; public const string FolderCreation = "https://umbra.co/healthchecks-folder-creation"; public const string FileWritingForPackages = "https://umbra.co/healthchecks-file-writing-for-packages"; @@ -37,7 +38,7 @@ public static class Security { - + public const string UmbracoApplicationUrlCheck = "https://umbra.co/healthchecks-umbraco-application-url"; public const string ClickJackingCheck = "https://umbra.co/healthchecks-click-jacking"; public const string HstsCheck = "https://umbra.co/healthchecks-hsts"; public const string NoSniffCheck = "https://umbra.co/healthchecks-no-sniff"; @@ -46,7 +47,6 @@ public static class HttpsCheck { - public const string CheckIfCurrentSchemeIsHttps = "https://umbra.co/healthchecks-https-request"; public const string CheckHttpsConfigurationSetting = "https://umbra.co/healthchecks-https-config"; public const string CheckForValidCertificate = "https://umbra.co/healthchecks-valid-certificate"; From 8a8b1e3247e6a79e8ca60e6a883305d2a2038bb5 Mon Sep 17 00:00:00 2001 From: Mole Date: Tue, 21 Dec 2021 10:30:19 +0100 Subject: [PATCH 018/141] Rename CharacterReplacement back to CharItem Since the class is public renaming this would be a breaking change, since if anyone is directly referring to the class it would break --- .../CharacterReplacement.cs => Models/CharItem.cs} | 4 ++-- .../Configuration/Models/RequestHandlerSettings.cs | 6 +++--- .../CharacterReplacementEqualityComparer.cs | 7 ++++--- .../DefaultShortStringHelperTestsWithoutSetup.cs | 6 +++--- 4 files changed, 12 insertions(+), 11 deletions(-) rename src/Umbraco.Core/Configuration/{UmbracoSettings/CharacterReplacement.cs => Models/CharItem.cs} (74%) diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/CharacterReplacement.cs b/src/Umbraco.Core/Configuration/Models/CharItem.cs similarity index 74% rename from src/Umbraco.Core/Configuration/UmbracoSettings/CharacterReplacement.cs rename to src/Umbraco.Core/Configuration/Models/CharItem.cs index ed2f97dba9..9a1178a1bf 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/CharacterReplacement.cs +++ b/src/Umbraco.Core/Configuration/Models/CharItem.cs @@ -1,6 +1,6 @@ -namespace Umbraco.Cms.Core.Configuration.UmbracoSettings +namespace Umbraco.Cms.Core.Configuration.Models { - public class CharacterReplacement + public class CharItem { /// /// The character to replace diff --git a/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs b/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs index 79dabf9da0..105712396d 100644 --- a/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs @@ -19,7 +19,7 @@ namespace Umbraco.Cms.Core.Configuration.Models internal const string StaticConvertUrlsToAscii = "try"; internal const bool StaticEnableDefaultCharReplacements = true; - internal static readonly CharacterReplacement[] DefaultCharCollection = + internal static readonly CharItem[] DefaultCharCollection = { new () { Char = " ", Replacement = "-" }, new () { Char = "\"", Replacement = string.Empty }, @@ -78,13 +78,13 @@ namespace Umbraco.Cms.Core.Configuration.Models /// /// Add additional character replacements, or override defaults /// - public CharacterReplacement[] CharCollection { get; set; } + public CharItem[] CharCollection { get; set; } /// /// Get concatenated user and default character replacements /// taking into account /// - public IEnumerable GetCharReplacements() + public IEnumerable GetCharReplacements() { // TODO We need to special handle ":", as this character is special in keys diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/CharacterReplacementEqualityComparer.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/CharacterReplacementEqualityComparer.cs index b7dbf1cd16..7a830e9655 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/CharacterReplacementEqualityComparer.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/CharacterReplacementEqualityComparer.cs @@ -1,10 +1,11 @@ using System.Collections.Generic; +using Umbraco.Cms.Core.Configuration.Models; namespace Umbraco.Cms.Core.Configuration.UmbracoSettings { - public class CharacterReplacementEqualityComparer : IEqualityComparer + public class CharacterReplacementEqualityComparer : IEqualityComparer { - public bool Equals(CharacterReplacement x, CharacterReplacement y) + public bool Equals(CharItem x, CharItem y) { if (ReferenceEquals(x, y)) { @@ -29,7 +30,7 @@ namespace Umbraco.Cms.Core.Configuration.UmbracoSettings return x.Char == y.Char && x.Replacement == y.Replacement; } - public int GetHashCode(CharacterReplacement obj) + public int GetHashCode(CharItem obj) { unchecked { diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/ShortStringHelper/DefaultShortStringHelperTestsWithoutSetup.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/ShortStringHelper/DefaultShortStringHelperTestsWithoutSetup.cs index ee7999cac1..b686aee278 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/ShortStringHelper/DefaultShortStringHelperTestsWithoutSetup.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/ShortStringHelper/DefaultShortStringHelperTestsWithoutSetup.cs @@ -20,7 +20,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.ShortStringHelper { var requestHandlerSettings = new RequestHandlerSettings() { - CharCollection = Array.Empty(), + CharCollection = Array.Empty(), EnableDefaultCharReplacements = false, ConvertUrlsToAscii = "false" }; @@ -47,7 +47,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.ShortStringHelper { var requestHandlerSettings = new RequestHandlerSettings() { - CharCollection = Array.Empty(), + CharCollection = Array.Empty(), EnableDefaultCharReplacements = false, ConvertUrlsToAscii = "false" }; @@ -342,7 +342,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.ShortStringHelper { var requestHandlerSettings = new RequestHandlerSettings() { - CharCollection = Array.Empty(), + CharCollection = Array.Empty(), EnableDefaultCharReplacements = false, ConvertUrlsToAscii = "false" }; From 338bbdd3825f89b9c5e6835313c8012be2b80b9a Mon Sep 17 00:00:00 2001 From: Mole Date: Tue, 21 Dec 2021 10:36:25 +0100 Subject: [PATCH 019/141] Reintroduce IChar interface Removing a public interface is a breaking change, in case someone is implementing the interface for some reason. --- src/Umbraco.Core/Configuration/Models/CharItem.cs | 4 +++- .../Configuration/Models/RequestHandlerSettings.cs | 6 +++--- .../CharacterReplacementEqualityComparer.cs | 6 +++--- src/Umbraco.Core/Configuration/UmbracoSettings/IChar.cs | 9 +++++++++ 4 files changed, 18 insertions(+), 7 deletions(-) create mode 100644 src/Umbraco.Core/Configuration/UmbracoSettings/IChar.cs diff --git a/src/Umbraco.Core/Configuration/Models/CharItem.cs b/src/Umbraco.Core/Configuration/Models/CharItem.cs index 9a1178a1bf..e269e0a83e 100644 --- a/src/Umbraco.Core/Configuration/Models/CharItem.cs +++ b/src/Umbraco.Core/Configuration/Models/CharItem.cs @@ -1,6 +1,8 @@ +using Umbraco.Cms.Core.Configuration.UmbracoSettings; + namespace Umbraco.Cms.Core.Configuration.Models { - public class CharItem + public class CharItem : IChar { /// /// The character to replace diff --git a/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs b/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs index 105712396d..3c76af7252 100644 --- a/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs @@ -78,13 +78,13 @@ namespace Umbraco.Cms.Core.Configuration.Models /// /// Add additional character replacements, or override defaults /// - public CharItem[] CharCollection { get; set; } + public IEnumerable CharCollection { get; set; } /// /// Get concatenated user and default character replacements /// taking into account /// - public IEnumerable GetCharReplacements() + public IEnumerable GetCharReplacements() { // TODO We need to special handle ":", as this character is special in keys @@ -109,7 +109,7 @@ namespace Umbraco.Cms.Core.Configuration.Models } } - var mergedCollections = DefaultCharCollection.Union(CharCollection, new CharacterReplacementEqualityComparer()); + var mergedCollections = DefaultCharCollection.Union(CharCollection, new CharacterReplacementEqualityComparer()); return mergedCollections; } diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/CharacterReplacementEqualityComparer.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/CharacterReplacementEqualityComparer.cs index 7a830e9655..a916febb93 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/CharacterReplacementEqualityComparer.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/CharacterReplacementEqualityComparer.cs @@ -3,9 +3,9 @@ using Umbraco.Cms.Core.Configuration.Models; namespace Umbraco.Cms.Core.Configuration.UmbracoSettings { - public class CharacterReplacementEqualityComparer : IEqualityComparer + public class CharacterReplacementEqualityComparer : IEqualityComparer { - public bool Equals(CharItem x, CharItem y) + public bool Equals(IChar x, IChar y) { if (ReferenceEquals(x, y)) { @@ -30,7 +30,7 @@ namespace Umbraco.Cms.Core.Configuration.UmbracoSettings return x.Char == y.Char && x.Replacement == y.Replacement; } - public int GetHashCode(CharItem obj) + public int GetHashCode(IChar obj) { unchecked { diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IChar.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IChar.cs new file mode 100644 index 0000000000..61e840245c --- /dev/null +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IChar.cs @@ -0,0 +1,9 @@ +namespace Umbraco.Cms.Core.Configuration.UmbracoSettings +{ + public interface IChar + { + string Char { get; } + + string Replacement { get; } + } +} From d77e198d3144f21eb48034eafd63b9c7b4251ba0 Mon Sep 17 00:00:00 2001 From: Mole Date: Tue, 21 Dec 2021 10:42:54 +0100 Subject: [PATCH 020/141] Move unit test to the new location --- .../Models/RequestHandlerSettingsTests.cs | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) rename {src => tests}/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/RequestHandlerSettingsTests.cs (57%) diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/RequestHandlerSettingsTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/RequestHandlerSettingsTests.cs similarity index 57% rename from src/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/RequestHandlerSettingsTests.cs rename to tests/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/RequestHandlerSettingsTests.cs index c16d9b4897..9f2368d462 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/RequestHandlerSettingsTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/RequestHandlerSettingsTests.cs @@ -11,7 +11,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Configuration.Models [Test] public void Given_CharCollection_With_DefaultEnabled_MergesCollection() { - var userCollection = new CharacterReplacement[] + var userCollection = new CharItem[] { new() { Char = "test", Replacement = "replace" }, new() { Char = "test2", Replacement = "replace2" } @@ -31,10 +31,10 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Configuration.Models [Test] public void Given_CharCollection_With_DefaultDisabled_ReturnsUserCollection() { - var userCollection = new CharacterReplacement[] + var userCollection = new CharItem[] { - new() { Char = "test", Replacement = "replace" }, - new() { Char = "test2", Replacement = "replace2" } + new () { Char = "test", Replacement = "replace" }, + new () { Char = "test2", Replacement = "replace2" } }; var settings = new RequestHandlerSettings { CharCollection = userCollection, EnableDefaultCharReplacements = false }; @@ -47,10 +47,10 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Configuration.Models [Test] public void Given_CharCollection_That_OverridesDefaultValues_ReturnsReplacements() { - var userCollection = new CharacterReplacement[] + var userCollection = new CharItem[] { - new() { Char = "%", Replacement = "percent" }, - new() { Char = ".", Replacement = "dot" } + new () { Char = "%", Replacement = "percent" }, + new () { Char = ".", Replacement = "dot" } }; var settings = new RequestHandlerSettings { CharCollection = userCollection }; @@ -58,20 +58,20 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Configuration.Models Assert.AreEqual(RequestHandlerSettings.DefaultCharCollection.Length, actual.Count); - Assert.That(actual, Has.Exactly(1).Matches(x => x.Char == "%" && x.Replacement == "percent")); - Assert.That(actual, Has.Exactly(1).Matches(x => x.Char == "." && x.Replacement == "dot")); - Assert.That(actual, Has.Exactly(0).Matches(x => x.Char == "%" && x.Replacement == string.Empty)); - Assert.That(actual, Has.Exactly(0).Matches(x => x.Char == "." && x.Replacement == string.Empty)); + Assert.That(actual, Has.Exactly(1).Matches(x => x.Char == "%" && x.Replacement == "percent")); + Assert.That(actual, Has.Exactly(1).Matches(x => x.Char == "." && x.Replacement == "dot")); + Assert.That(actual, Has.Exactly(0).Matches(x => x.Char == "%" && x.Replacement == string.Empty)); + Assert.That(actual, Has.Exactly(0).Matches(x => x.Char == "." && x.Replacement == string.Empty)); } [Test] public void Given_CharCollection_That_OverridesDefaultValues_And_ContainsNew_ReturnsMergedWithReplacements() { - var userCollection = new CharacterReplacement[] + var userCollection = new CharItem[] { - new() { Char = "%", Replacement = "percent" }, - new() { Char = ".", Replacement = "dot" }, - new() {Char = "new", Replacement = "new"} + new () { Char = "%", Replacement = "percent" }, + new () { Char = ".", Replacement = "dot" }, + new () { Char = "new", Replacement = "new" } }; var settings = new RequestHandlerSettings { CharCollection = userCollection }; @@ -80,11 +80,11 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Configuration.Models // Add 1 to the length, because we're expecting to only add one new one Assert.AreEqual(RequestHandlerSettings.DefaultCharCollection.Length + 1, actual.Count); - Assert.That(actual, Has.Exactly(1).Matches(x => x.Char == "%" && x.Replacement == "percent")); - Assert.That(actual, Has.Exactly(1).Matches(x => x.Char == "." && x.Replacement == "dot")); - Assert.That(actual, Has.Exactly(1).Matches(x => x.Char == "new" && x.Replacement == "new")); - Assert.That(actual, Has.Exactly(0).Matches(x => x.Char == "%" && x.Replacement == string.Empty)); - Assert.That(actual, Has.Exactly(0).Matches(x => x.Char == "." && x.Replacement == string.Empty)); + Assert.That(actual, Has.Exactly(1).Matches(x => x.Char == "%" && x.Replacement == "percent")); + Assert.That(actual, Has.Exactly(1).Matches(x => x.Char == "." && x.Replacement == "dot")); + Assert.That(actual, Has.Exactly(1).Matches(x => x.Char == "new" && x.Replacement == "new")); + Assert.That(actual, Has.Exactly(0).Matches(x => x.Char == "%" && x.Replacement == string.Empty)); + Assert.That(actual, Has.Exactly(0).Matches(x => x.Char == "." && x.Replacement == string.Empty)); } } } From 4240f89534a0c123e12407e9bdfd7c76c4c2f602 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Tue, 21 Dec 2021 11:15:57 +0100 Subject: [PATCH 021/141] Adds migration back for v9 for #10450 (#11769) --- .../Migrations/Upgrade/UmbracoPlan.cs | 2 +- .../V_9_2_0/AddDefaultForNotificationsToggle.cs | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_2_0/AddDefaultForNotificationsToggle.cs diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs index 257fee9967..ff9e86335f 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs @@ -267,7 +267,7 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade // TO 9.2.0 To("{0571C395-8F0B-44E9-8E3F-47BDD08D817B}"); - + To("{AD3D3B7F-8E74-45A4-85DB-7FFAD57F9243}"); } } } diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_2_0/AddDefaultForNotificationsToggle.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_2_0/AddDefaultForNotificationsToggle.cs new file mode 100644 index 0000000000..3bc62ab42e --- /dev/null +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_2_0/AddDefaultForNotificationsToggle.cs @@ -0,0 +1,17 @@ +using Umbraco.Cms.Core; + +namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_9_2_0 +{ + public class AddDefaultForNotificationsToggle : MigrationBase + { + public AddDefaultForNotificationsToggle(IMigrationContext context) + : base(context) + { } + + protected override void Migrate() + { + var updateSQL = Sql($"UPDATE {Constants.DatabaseSchema.Tables.UserGroup} SET userGroupDefaultPermissions = userGroupDefaultPermissions + 'N' WHERE userGroupAlias IN ('admin', 'writer', 'editor')"); + Execute.Sql(updateSQL.SQL).Do(); + } + } +} From 6147375a3e7d133c196c13216bc966018dfcd817 Mon Sep 17 00:00:00 2001 From: Mole Date: Tue, 21 Dec 2021 11:56:33 +0100 Subject: [PATCH 022/141] Return CharItem instead of IChar from CharCollection On second thought this is not a breaking change since CharItem implements IChar, so you could still do something lke IEnumerable chars = requestHandlerSettings.CharCollection --- .../Configuration/Models/RequestHandlerSettings.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs b/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs index 3c76af7252..c27ad63092 100644 --- a/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs @@ -78,13 +78,13 @@ namespace Umbraco.Cms.Core.Configuration.Models /// /// Add additional character replacements, or override defaults /// - public IEnumerable CharCollection { get; set; } + public IEnumerable CharCollection { get; set; } /// /// Get concatenated user and default character replacements /// taking into account /// - public IEnumerable GetCharReplacements() + public IEnumerable GetCharReplacements() { // TODO We need to special handle ":", as this character is special in keys @@ -109,7 +109,7 @@ namespace Umbraco.Cms.Core.Configuration.Models } } - var mergedCollections = DefaultCharCollection.Union(CharCollection, new CharacterReplacementEqualityComparer()); + var mergedCollections = DefaultCharCollection.Union(CharCollection, new CharacterReplacementEqualityComparer()); return mergedCollections; } From 72d9f56478c996eda346fe48e25631a0fc8afa5f Mon Sep 17 00:00:00 2001 From: Mole Date: Tue, 21 Dec 2021 12:48:35 +0100 Subject: [PATCH 023/141] V9: Use current request for emails (#11778) * Use request url for email * Fixed potential null ref exceptions Co-authored-by: Bjarke Berg --- .../Controllers/AuthenticationController.cs | 56 ++++++++++- .../Controllers/UsersController.cs | 92 ++++++++++++++++--- .../Extensions/HttpRequestExtensions.cs | 31 ++++++- 3 files changed, 160 insertions(+), 19 deletions(-) diff --git a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs index f159011d80..30ad3f75ae 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs @@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Cms.Core; @@ -29,6 +30,7 @@ using Umbraco.Cms.Web.Common.ActionsResults; using Umbraco.Cms.Web.Common.Attributes; using Umbraco.Cms.Web.Common.Authorization; using Umbraco.Cms.Web.Common.Controllers; +using Umbraco.Cms.Web.Common.DependencyInjection; using Umbraco.Cms.Web.Common.Filters; using Umbraco.Cms.Web.Common.Models; using Umbraco.Extensions; @@ -71,9 +73,11 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers private readonly LinkGenerator _linkGenerator; private readonly IBackOfficeExternalLoginProviders _externalAuthenticationOptions; private readonly IBackOfficeTwoFactorOptions _backOfficeTwoFactorOptions; + private readonly IHttpContextAccessor _httpContextAccessor; + private readonly WebRoutingSettings _webRoutingSettings; // TODO: We need to review all _userManager.Raise calls since many/most should be on the usermanager or signinmanager, very few should be here - + [ActivatorUtilitiesConstructor] public AuthenticationController( IBackOfficeSecurityAccessor backofficeSecurityAccessor, IBackOfficeUserManager backOfficeUserManager, @@ -91,7 +95,9 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers IHostingEnvironment hostingEnvironment, LinkGenerator linkGenerator, IBackOfficeExternalLoginProviders externalAuthenticationOptions, - IBackOfficeTwoFactorOptions backOfficeTwoFactorOptions) + IBackOfficeTwoFactorOptions backOfficeTwoFactorOptions, + IHttpContextAccessor httpContextAccessor, + IOptions webRoutingSettings) { _backofficeSecurityAccessor = backofficeSecurityAccessor; _userManager = backOfficeUserManager; @@ -110,6 +116,50 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers _linkGenerator = linkGenerator; _externalAuthenticationOptions = externalAuthenticationOptions; _backOfficeTwoFactorOptions = backOfficeTwoFactorOptions; + _httpContextAccessor = httpContextAccessor; + _webRoutingSettings = webRoutingSettings.Value; + } + + [Obsolete("Use constructor that also takes IHttpAccessor and IOptions, scheduled for removal in V11")] + public AuthenticationController( + IBackOfficeSecurityAccessor backofficeSecurityAccessor, + IBackOfficeUserManager backOfficeUserManager, + IBackOfficeSignInManager signInManager, + IUserService userService, + ILocalizedTextService textService, + IUmbracoMapper umbracoMapper, + IOptions globalSettings, + IOptions securitySettings, + ILogger logger, + IIpResolver ipResolver, + IOptions passwordConfiguration, + IEmailSender emailSender, + ISmsSender smsSender, + IHostingEnvironment hostingEnvironment, + LinkGenerator linkGenerator, + IBackOfficeExternalLoginProviders externalAuthenticationOptions, + IBackOfficeTwoFactorOptions backOfficeTwoFactorOptions) + : this( + backofficeSecurityAccessor, + backOfficeUserManager, + signInManager, + userService, + textService, + umbracoMapper, + globalSettings, + securitySettings, + logger, + ipResolver, + passwordConfiguration, + emailSender, + smsSender, + hostingEnvironment, + linkGenerator, + externalAuthenticationOptions, + backOfficeTwoFactorOptions, + StaticServiceProvider.Instance.GetRequiredService(), + StaticServiceProvider.Instance.GetRequiredService>()) + { } /// @@ -629,7 +679,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers }); // Construct full URL using configured application URL (which will fall back to request) - var applicationUri = _hostingEnvironment.ApplicationMainUrl; + Uri applicationUri = _httpContextAccessor.GetRequiredHttpContext().Request.GetApplicationUri(_webRoutingSettings); var callbackUri = new Uri(applicationUri, action); return callbackUri.ToString(); } diff --git a/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs b/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs index 79e7838110..72377c0670 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs @@ -13,6 +13,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using MimeKit; @@ -42,6 +43,7 @@ using Umbraco.Cms.Web.BackOffice.Security; using Umbraco.Cms.Web.Common.ActionsResults; using Umbraco.Cms.Web.Common.Attributes; using Umbraco.Cms.Web.Common.Authorization; +using Umbraco.Cms.Web.Common.DependencyInjection; using Umbraco.Cms.Web.Common.Security; using Umbraco.Extensions; using Constants = Umbraco.Cms.Core.Constants; @@ -75,7 +77,10 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers private readonly UserEditorAuthorizationHelper _userEditorAuthorizationHelper; private readonly IPasswordChanger _passwordChanger; private readonly ILogger _logger; + private readonly IHttpContextAccessor _httpContextAccessor; + private readonly WebRoutingSettings _webRoutingSettings; + [ActivatorUtilitiesConstructor] public UsersController( MediaFileManager mediaFileManager, IOptions contentSettings, @@ -96,7 +101,9 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers LinkGenerator linkGenerator, IBackOfficeExternalLoginProviders externalLogins, UserEditorAuthorizationHelper userEditorAuthorizationHelper, - IPasswordChanger passwordChanger) + IPasswordChanger passwordChanger, + IHttpContextAccessor httpContextAccessor, + IOptions webRoutingSettings) { _mediaFileManager = mediaFileManager; _contentSettings = contentSettings.Value; @@ -119,6 +126,55 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers _userEditorAuthorizationHelper = userEditorAuthorizationHelper; _passwordChanger = passwordChanger; _logger = _loggerFactory.CreateLogger(); + _httpContextAccessor = httpContextAccessor; + _webRoutingSettings = webRoutingSettings.Value; + } + + [Obsolete("Use constructor that also takes IHttpAccessor and IOptions, scheduled for removal in V11")] + public UsersController( + MediaFileManager mediaFileManager, + IOptions contentSettings, + IHostingEnvironment hostingEnvironment, + ISqlContext sqlContext, + IImageUrlGenerator imageUrlGenerator, + IOptions securitySettings, + IEmailSender emailSender, + IBackOfficeSecurityAccessor backofficeSecurityAccessor, + AppCaches appCaches, + IShortStringHelper shortStringHelper, + IUserService userService, + ILocalizedTextService localizedTextService, + IUmbracoMapper umbracoMapper, + IOptions globalSettings, + IBackOfficeUserManager backOfficeUserManager, + ILoggerFactory loggerFactory, + LinkGenerator linkGenerator, + IBackOfficeExternalLoginProviders externalLogins, + UserEditorAuthorizationHelper userEditorAuthorizationHelper, + IPasswordChanger passwordChanger) + : this(mediaFileManager, + contentSettings, + hostingEnvironment, + sqlContext, + imageUrlGenerator, + securitySettings, + emailSender, + backofficeSecurityAccessor, + appCaches, + shortStringHelper, + userService, + localizedTextService, + umbracoMapper, + globalSettings, + backOfficeUserManager, + loggerFactory, + linkGenerator, + externalLogins, + userEditorAuthorizationHelper, + passwordChanger, + StaticServiceProvider.Instance.GetRequiredService(), + StaticServiceProvider.Instance.GetRequiredService>()) + { } /// @@ -421,20 +477,25 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers /// public async Task> PostInviteUser(UserInvite userSave) { - if (userSave == null) throw new ArgumentNullException("userSave"); + if (userSave == null) + { + throw new ArgumentNullException("userSave"); + } if (userSave.Message.IsNullOrWhiteSpace()) + { ModelState.AddModelError("Message", "Message cannot be empty"); + } IUser user; if (_securitySettings.UsernameIsEmail) { - //ensure it's the same + // ensure it's the same userSave.Username = userSave.Email; } else { - //first validate the username if we're showing it + // first validate the username if we're showing it var userResult = CheckUniqueUsername(userSave.Username, u => u.LastLoginDate != default || u.EmailConfirmedDate.HasValue); if (!(userResult.Result is null)) { @@ -443,6 +504,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers user = userResult.Value; } + user = CheckUniqueEmail(userSave.Email, u => u.LastLoginDate != default || u.EmailConfirmedDate.HasValue); if (ModelState.IsValid == false) @@ -455,7 +517,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers return ValidationProblem("No Email server is configured"); } - //Perform authorization here to see if the current user can actually save this user with the info being requested + // Perform authorization here to see if the current user can actually save this user with the info being requested var canSaveUser = _userEditorAuthorizationHelper.IsAuthorized(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser, user, null, null, userSave.UserGroups); if (canSaveUser == false) { @@ -464,8 +526,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers if (user == null) { - //we want to create the user with the UserManager, this ensures the 'empty' (special) password - //format is applied without us having to duplicate that logic + // we want to create the user with the UserManager, this ensures the 'empty' (special) password + // format is applied without us having to duplicate that logic var identityUser = BackOfficeIdentityUser.CreateNew(_globalSettings, userSave.Username, userSave.Email, _globalSettings.DefaultUILanguage); identityUser.Name = userSave.Name; @@ -475,21 +537,21 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers return ValidationProblem(created.Errors.ToErrorMessage()); } - //now re-look the user back up + // now re-look the user back up user = _userService.GetByEmail(userSave.Email); } - //map the save info over onto the user + // map the save info over onto the user user = _umbracoMapper.Map(userSave, user); - //ensure the invited date is set + // ensure the invited date is set user.InvitedDate = DateTime.Now; - //Save the updated user (which will process the user groups too) + // Save the updated user (which will process the user groups too) _userService.Save(user); var display = _umbracoMapper.Map(user); - //send the email + // send the email await SendUserInviteEmailAsync(display, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Name, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Email, user, userSave.Message); display.AddSuccessNotification(_localizedTextService.Localize("speechBubbles","resendInviteHeader"), _localizedTextService.Localize("speechBubbles","resendInviteSuccess", new[] { user.Name })); @@ -544,14 +606,14 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers }); // Construct full URL using configured application URL (which will fall back to request) - var applicationUri = _hostingEnvironment.ApplicationMainUrl; + Uri applicationUri = _httpContextAccessor.GetRequiredHttpContext().Request.GetApplicationUri(_webRoutingSettings); var inviteUri = new Uri(applicationUri, action); var emailSubject = _localizedTextService.Localize("user","inviteEmailCopySubject", - //Ensure the culture of the found user is used for the email! + // Ensure the culture of the found user is used for the email! UmbracoUserExtensions.GetUserCulture(to.Language, _localizedTextService, _globalSettings)); var emailBody = _localizedTextService.Localize("user","inviteEmailCopyFormat", - //Ensure the culture of the found user is used for the email! + // Ensure the culture of the found user is used for the email! UmbracoUserExtensions.GetUserCulture(to.Language, _localizedTextService, _globalSettings), new[] { userDisplay.Name, from, message, inviteUri.ToString(), senderEmail }); diff --git a/src/Umbraco.Web.Common/Extensions/HttpRequestExtensions.cs b/src/Umbraco.Web.Common/Extensions/HttpRequestExtensions.cs index c7c2bb3115..2aeb2555eb 100644 --- a/src/Umbraco.Web.Common/Extensions/HttpRequestExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/HttpRequestExtensions.cs @@ -1,9 +1,12 @@ -using System.IO; +using System; +using System.IO; using System.Net; using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Extensions; using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Routing; namespace Umbraco.Extensions @@ -107,5 +110,31 @@ namespace Umbraco.Extensions return result; } } + + /// + /// Gets the application URI, will use the one specified in settings if present + /// + public static Uri GetApplicationUri(this HttpRequest request, WebRoutingSettings routingSettings) + { + if (request == null) + { + throw new ArgumentNullException(nameof(request)); + } + + if (routingSettings == null) + { + throw new ArgumentNullException(nameof(routingSettings)); + } + + if (string.IsNullOrEmpty(routingSettings.UmbracoApplicationUrl)) + { + var requestUri = new Uri(request.GetDisplayUrl()); + + // Create a new URI with the relative uri as /, this ensures that only the base path is returned. + return new Uri(requestUri, "/"); + } + + return new Uri(routingSettings.UmbracoApplicationUrl); + } } } From be53ccf1fd088d638cdd0ce2be2100f9402d7365 Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska Date: Tue, 21 Dec 2021 08:57:28 +0100 Subject: [PATCH 024/141] Implement #11776 for v9 --- .../Security/UmbracoApplicationUrlCheck.cs | 68 +++++++++++++++++++ src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 2 + .../umbraco/config/lang/en_us.xml | 2 + 3 files changed, 72 insertions(+) create mode 100644 src/Umbraco.Core/HealthChecks/Checks/Security/UmbracoApplicationUrlCheck.cs diff --git a/src/Umbraco.Core/HealthChecks/Checks/Security/UmbracoApplicationUrlCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Security/UmbracoApplicationUrlCheck.cs new file mode 100644 index 0000000000..44b10ba0e3 --- /dev/null +++ b/src/Umbraco.Core/HealthChecks/Checks/Security/UmbracoApplicationUrlCheck.cs @@ -0,0 +1,68 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Services; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Core.HealthChecks.Checks.Security +{ + [HealthCheck( + "6708CA45-E96E-40B8-A40A-0607C1CA7F28", + "Application URL Configuration", + Description = "Checks if the Umbraco application URL is configured for your site.", + Group = "Security")] + public class UmbracoApplicationUrlCheck : HealthCheck + { + private readonly ILocalizedTextService _textService; + private readonly IOptionsMonitor _webRoutingSettings; + + public UmbracoApplicationUrlCheck(ILocalizedTextService textService, IOptionsMonitor webRoutingSettings) + { + _textService = textService; + _webRoutingSettings = webRoutingSettings; + } + + /// + /// Executes the action and returns its status + /// + public override HealthCheckStatus ExecuteAction(HealthCheckAction action) => throw new InvalidOperationException("UmbracoApplicationUrlCheck has no executable actions"); + + /// + /// Get the status for this health check + /// + public override Task> GetStatus() => + Task.FromResult(CheckUmbracoApplicationUrl().Yield()); + + private HealthCheckStatus CheckUmbracoApplicationUrl() + { + var url = _webRoutingSettings.CurrentValue.UmbracoApplicationUrl; + + string resultMessage; + StatusResultType resultType; + var success = false; + + if (url.IsNullOrWhiteSpace()) + { + resultMessage = _textService.Localize("healthcheck", "umbracoApplicationUrlCheckResultFalse"); + resultType = StatusResultType.Warning; + } + else + { + resultMessage = _textService.Localize("healthcheck", "umbracoApplicationUrlCheckResultTrue", new[] { url }); + resultType = StatusResultType.Success; + success = true; + } + + return new HealthCheckStatus(resultMessage) + { + ResultType = resultType, + ReadMoreLink = success ? null : Constants.HealthChecks.DocumentationLinks.Security.UmbracoApplicationUrlCheck + }; + } + } +} diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index 468a0cc735..4411209cd5 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -2290,6 +2290,8 @@ To manage your website, simply open the Umbraco backoffice and start adding cont + %0%.]]> + The appSetting 'Umbraco:CMS:WebRouting:UmbracoApplicationUrl' is not set. X-Frame-Options used to control whether a site can be IFRAMEd by another was found.]]> 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 aef05ca973..5a17eafbb2 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -2372,6 +2372,8 @@ To manage your website, simply open the Umbraco backoffice and start adding cont + %0%.]]> + The appSetting 'Umbraco:CMS:WebRouting:UmbracoApplicationUrl' is not set. X-Frame-Options used to control whether a site can be IFRAMEd by another was found.]]> From 0ea5f6a02c2497c69446bf3e65e4e54b24d7093e Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska Date: Tue, 21 Dec 2021 08:58:13 +0100 Subject: [PATCH 025/141] Adding UmbracoApplicationUrlCheck "Read more" URL --- src/Umbraco.Core/Constants-HealthChecks.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Core/Constants-HealthChecks.cs b/src/Umbraco.Core/Constants-HealthChecks.cs index 5770bd07e4..5a8ea401cb 100644 --- a/src/Umbraco.Core/Constants-HealthChecks.cs +++ b/src/Umbraco.Core/Constants-HealthChecks.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core +namespace Umbraco.Cms.Core { /// /// Defines constants. @@ -20,15 +20,16 @@ public const string CompilationDebugCheck = "https://umbra.co/healthchecks-compilation-debug"; } + public static class Configuration { public const string MacroErrorsCheck = "https://umbra.co/healthchecks-macro-errors"; public const string TrySkipIisCustomErrorsCheck = "https://umbra.co/healthchecks-skip-iis-custom-errors"; public const string NotificationEmailCheck = "https://umbra.co/healthchecks-notification-email"; } + public static class FolderAndFilePermissionsCheck { - public const string FileWriting = "https://umbra.co/healthchecks-file-writing"; public const string FolderCreation = "https://umbra.co/healthchecks-folder-creation"; public const string FileWritingForPackages = "https://umbra.co/healthchecks-file-writing-for-packages"; @@ -37,7 +38,7 @@ public static class Security { - + public const string UmbracoApplicationUrlCheck = "https://umbra.co/healthchecks-umbraco-application-url"; public const string ClickJackingCheck = "https://umbra.co/healthchecks-click-jacking"; public const string HstsCheck = "https://umbra.co/healthchecks-hsts"; public const string NoSniffCheck = "https://umbra.co/healthchecks-no-sniff"; @@ -46,7 +47,6 @@ public static class HttpsCheck { - public const string CheckIfCurrentSchemeIsHttps = "https://umbra.co/healthchecks-https-request"; public const string CheckHttpsConfigurationSetting = "https://umbra.co/healthchecks-https-config"; public const string CheckForValidCertificate = "https://umbra.co/healthchecks-valid-certificate"; From 80cda3364b09c3241e6284761dc416e79ef8fe71 Mon Sep 17 00:00:00 2001 From: Mole Date: Tue, 21 Dec 2021 13:35:30 +0100 Subject: [PATCH 026/141] Make CharCollection return the merged result Since this is the way it's been previously, we cannot change the behaviour of this. Unfortunately due to how the config binding works, we have to make sure it returns null the very first time it's called by the framework to ensure that overrides actually gets registered. --- .../Models/RequestHandlerSettings.cs | 37 +++++++++++++++---- .../Strings/DefaultShortStringHelperConfig.cs | 2 +- .../Models/RequestHandlerSettingsTests.cs | 12 +++--- 3 files changed, 36 insertions(+), 15 deletions(-) diff --git a/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs b/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs index c27ad63092..4894fb7125 100644 --- a/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs @@ -75,32 +75,53 @@ namespace Umbraco.Cms.Core.Configuration.Models [DefaultValue(StaticEnableDefaultCharReplacements)] public bool EnableDefaultCharReplacements { get; set; } = StaticEnableDefaultCharReplacements; + private IEnumerable _charCollection; + /// /// Add additional character replacements, or override defaults /// - public IEnumerable CharCollection { get; set; } + public IEnumerable CharCollection + { + get + { + // This is pretty ugly, but necessary. + // Essentially the config binding will only run properly if we return null the first time this is invoked. + // Otherwise whatever we return will just be used and user specific bindings won't overwrite the existing ones. + // However the next time this get is invoked, for instance in DefaultShortStringHelper we want to actually run the GetCharReplacements + // To make sure that the default bindings is used if configured to do so. + // Therefore we set _charCollection to be something, and still return null, to trick dotnet to actually read the config. + if (_charCollection is null) + { + _charCollection = Enumerable.Empty(); + return null; + } + + return GetCharReplacements(); + } + + set => _charCollection = value; + } /// /// Get concatenated user and default character replacements /// taking into account /// - public IEnumerable GetCharReplacements() + private IEnumerable GetCharReplacements() { // TODO We need to special handle ":", as this character is special in keys - if (!EnableDefaultCharReplacements) { - return CharCollection; + return _charCollection ?? Enumerable.Empty(); } - if (CharCollection == null || !CharCollection.Any()) + if (_charCollection == null || !_charCollection.Any()) { return DefaultCharCollection; } - foreach (var defaultReplacement in DefaultCharCollection) + foreach (CharItem defaultReplacement in DefaultCharCollection) { - foreach (var userReplacement in CharCollection) + foreach (CharItem userReplacement in _charCollection) { if (userReplacement.Char == defaultReplacement.Char) { @@ -109,7 +130,7 @@ namespace Umbraco.Cms.Core.Configuration.Models } } - var mergedCollections = DefaultCharCollection.Union(CharCollection, new CharacterReplacementEqualityComparer()); + IEnumerable mergedCollections = DefaultCharCollection.Union(_charCollection, new CharacterReplacementEqualityComparer()); return mergedCollections; } diff --git a/src/Umbraco.Core/Strings/DefaultShortStringHelperConfig.cs b/src/Umbraco.Core/Strings/DefaultShortStringHelperConfig.cs index 287d33dd58..273d7a6562 100644 --- a/src/Umbraco.Core/Strings/DefaultShortStringHelperConfig.cs +++ b/src/Umbraco.Core/Strings/DefaultShortStringHelperConfig.cs @@ -60,7 +60,7 @@ namespace Umbraco.Cms.Core.Strings /// The short string helper. public DefaultShortStringHelperConfig WithDefault(RequestHandlerSettings requestHandlerSettings) { - UrlReplaceCharacters = requestHandlerSettings.GetCharReplacements() + UrlReplaceCharacters = requestHandlerSettings.CharCollection .Where(x => string.IsNullOrEmpty(x.Char) == false) .ToDictionary(x => x.Char, x => x.Replacement); diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/RequestHandlerSettingsTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/RequestHandlerSettingsTests.cs index 9f2368d462..d681d4a70e 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/RequestHandlerSettingsTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/RequestHandlerSettingsTests.cs @@ -13,13 +13,13 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Configuration.Models { var userCollection = new CharItem[] { - new() { Char = "test", Replacement = "replace" }, - new() { Char = "test2", Replacement = "replace2" } + new () { Char = "test", Replacement = "replace" }, + new () { Char = "test2", Replacement = "replace2" } }; var settings = new RequestHandlerSettings { CharCollection = userCollection }; - var actual = settings.GetCharReplacements().ToList(); + var actual = settings.CharCollection.ToList(); var expectedCollection = RequestHandlerSettings.DefaultCharCollection.ToList(); expectedCollection.AddRange(userCollection); @@ -38,7 +38,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Configuration.Models }; var settings = new RequestHandlerSettings { CharCollection = userCollection, EnableDefaultCharReplacements = false }; - var actual = settings.GetCharReplacements().ToList(); + var actual = settings.CharCollection.ToList(); Assert.AreEqual(userCollection.Length, actual.Count); Assert.That(actual, Is.EquivalentTo(userCollection)); @@ -54,7 +54,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Configuration.Models }; var settings = new RequestHandlerSettings { CharCollection = userCollection }; - var actual = settings.GetCharReplacements().ToList(); + var actual = settings.CharCollection.ToList(); Assert.AreEqual(RequestHandlerSettings.DefaultCharCollection.Length, actual.Count); @@ -75,7 +75,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Configuration.Models }; var settings = new RequestHandlerSettings { CharCollection = userCollection }; - var actual = settings.GetCharReplacements().ToList(); + var actual = settings.CharCollection.ToList(); // Add 1 to the length, because we're expecting to only add one new one Assert.AreEqual(RequestHandlerSettings.DefaultCharCollection.Length + 1, actual.Count); From 76cf5037e53b526977ff94083ac341a8e7dd9909 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 21 Dec 2021 14:02:49 +0100 Subject: [PATCH 027/141] v9: Move local xml package files to database instead (#11654) * Move package to database * Added migration and implemented new repository * Updated migrations to use proper xml convert * Fixed save function and renamed createDate to update date * Updated dependencyInjection * Updated UmbracoPlan.cs * Apply suggestions from code review Co-authored-by: Elitsa Marinovska <21998037+elit0451@users.noreply.github.com> * Added File check * Tried using same context as create table * Fix DTO * Fix GetById and local package saving * Fix images when migrating * Implement deletion of all local files * Only delete local repo file, not file snapshots * Remove static package path and use the one we save * Update package repo to export package and remove check for ids when exporting * Minor fixes * Fix so that you can download package after creating * Update savePackage method to export package afterwards Co-authored-by: Nikolaj Geisle Co-authored-by: Elitsa Marinovska Co-authored-by: Elitsa Marinovska <21998037+elit0451@users.noreply.github.com> --- .../Extensions/ContentExtensions.cs | 2 +- .../Packaging/PackagesRepository.cs | 9 + .../Persistence/Constants-DatabaseSchema.cs | 2 + .../UmbracoBuilder.Services.cs | 8 +- .../Install/DatabaseSchemaCreator.cs | 3 +- .../Migrations/Upgrade/UmbracoPlan.cs | 1 + .../Upgrade/V_9_2_0/MovePackageXMLToDb.cs | 72 ++ .../Dtos/CreatedPackageSchemaDto.cs | 38 + .../CreatedPackageSchemaRepository.cs | 758 ++++++++++++++++++ .../Controllers/PackageController.cs | 10 +- .../Packaging/CreatedPackageSchemaTests.cs | 62 ++ 11 files changed, 953 insertions(+), 12 deletions(-) create mode 100644 src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_2_0/MovePackageXMLToDb.cs create mode 100644 src/Umbraco.Infrastructure/Persistence/Dtos/CreatedPackageSchemaDto.cs create mode 100644 src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs create mode 100644 tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Packaging/CreatedPackageSchemaTests.cs diff --git a/src/Umbraco.Core/Extensions/ContentExtensions.cs b/src/Umbraco.Core/Extensions/ContentExtensions.cs index daca62926a..67c483a4d6 100644 --- a/src/Umbraco.Core/Extensions/ContentExtensions.cs +++ b/src/Umbraco.Core/Extensions/ContentExtensions.cs @@ -367,7 +367,7 @@ namespace Umbraco.Extensions /// to generate xml for /// /// Xml representation of the passed in - internal static XElement ToDeepXml(this IContent content, IEntityXmlSerializer serializer) + public static XElement ToDeepXml(this IContent content, IEntityXmlSerializer serializer) { return serializer.Serialize(content, false, true); } diff --git a/src/Umbraco.Core/Packaging/PackagesRepository.cs b/src/Umbraco.Core/Packaging/PackagesRepository.cs index 2ab24fa593..331034e787 100644 --- a/src/Umbraco.Core/Packaging/PackagesRepository.cs +++ b/src/Umbraco.Core/Packaging/PackagesRepository.cs @@ -21,6 +21,7 @@ namespace Umbraco.Cms.Core.Packaging /// /// Manages the storage of installed/created package definitions /// + [Obsolete("Packages have now been moved to the database instead of local files, please use CreatedPackageSchemaRepository instead")] public class PackagesRepository : ICreatedPackagesRepository { private readonly IContentService _contentService; @@ -744,5 +745,13 @@ namespace Umbraco.Cms.Core.Packaging var packagesXml = XDocument.Load(packagesFile); return packagesXml; } + + public void DeleteLocalRepositoryFiles() + { + var packagesFile = _hostingEnvironment.MapPathContentRoot(CreatedPackagesFile); + File.Delete(packagesFile); + var packagesFolder = _hostingEnvironment.MapPathContentRoot(_packagesFolderPath); + Directory.Delete(packagesFolder); + } } } diff --git a/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs b/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs index 680eee5ba2..37560b4c0a 100644 --- a/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs +++ b/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs @@ -81,6 +81,8 @@ namespace Umbraco.Cms.Core public const string UserLogin = TableNamePrefix + "UserLogin"; public const string LogViewerQuery = TableNamePrefix + "LogViewerQuery"; + + public const string CreatedPackageSchema = TableNamePrefix + "CreatedPackageSchema"; } } } diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs index 661ed93292..debe476c49 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs @@ -15,6 +15,8 @@ using Umbraco.Cms.Core.Routing; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services.Implement; using Umbraco.Cms.Infrastructure.Packaging; +using Umbraco.Cms.Infrastructure.Persistence; +using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; using Umbraco.Cms.Infrastructure.Services.Implement; using Umbraco.Extensions; @@ -74,16 +76,14 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection builder.Services.AddUnique(); builder.Services.AddUnique(); - builder.Services.AddUnique(factory => CreatePackageRepository(factory, "createdPackages.config")); + builder.Services.AddUnique(factory => CreatePackageRepository(factory, "createdPackages.config")); + builder.Services.AddUnique(); builder.Services.AddUnique(); builder.Services.AddUnique(); return builder; } - /// - /// Creates an instance of PackagesRepository for either the ICreatedPackagesRepository or the IInstalledPackagesRepository - /// private static PackagesRepository CreatePackageRepository(IServiceProvider factory, string packageRepoFileName) => new PackagesRepository( factory.GetRequiredService(), diff --git a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseSchemaCreator.cs b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseSchemaCreator.cs index d9ebb8bf75..8fb9767eb7 100644 --- a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseSchemaCreator.cs +++ b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseSchemaCreator.cs @@ -78,7 +78,8 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install typeof(ContentScheduleDto), typeof(LogViewerQueryDto), typeof(ContentVersionCleanupPolicyDto), - typeof(UserGroup2NodeDto) + typeof(UserGroup2NodeDto), + typeof(CreatedPackageSchemaDto) }; private readonly IUmbracoDatabase _database; diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs index ff9e86335f..2b6f2fe6d6 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs @@ -268,6 +268,7 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade // TO 9.2.0 To("{0571C395-8F0B-44E9-8E3F-47BDD08D817B}"); To("{AD3D3B7F-8E74-45A4-85DB-7FFAD57F9243}"); + To("{A2F22F17-5870-4179-8A8D-2362AA4A0A5F}"); } } } diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_2_0/MovePackageXMLToDb.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_2_0/MovePackageXMLToDb.cs new file mode 100644 index 0000000000..e4a4af0cbb --- /dev/null +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_2_0/MovePackageXMLToDb.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml; +using System.Xml.Linq; +using Umbraco.Cms.Core.Packaging; +using Umbraco.Cms.Infrastructure.Persistence; +using Umbraco.Cms.Infrastructure.Persistence.Dtos; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_9_2_0 +{ + public class MovePackageXMLToDb : MigrationBase + { + private readonly PackagesRepository _packagesRepository; + private readonly PackageDefinitionXmlParser _xmlParser; + + /// + /// Initializes a new instance of the class. + /// + public MovePackageXMLToDb(IMigrationContext context, PackagesRepository packagesRepository) + : base(context) + { + _packagesRepository = packagesRepository; + _xmlParser = new PackageDefinitionXmlParser(); + } + + private void CreateDatabaseTable() + { + // Add CreatedPackage table in database if it doesn't exist + IEnumerable tables = SqlSyntax.GetTablesInSchema(Context.Database); + if (!tables.InvariantContains(CreatedPackageSchemaDto.TableName)) + { + Create.Table().Do(); + } + } + + private void MigrateCreatedPackageFilesToDb() + { + // Load data from file + IEnumerable packages = _packagesRepository.GetAll(); + var createdPackageDtos = new List(); + foreach (PackageDefinition package in packages) + { + // Create dto from xmlDocument + var dto = new CreatedPackageSchemaDto() + { + Name = package.Name, + Value = _xmlParser.ToXml(package).ToString(), + UpdateDate = DateTime.Now, + PackageId = Guid.NewGuid() + }; + createdPackageDtos.Add(dto); + } + + _packagesRepository.DeleteLocalRepositoryFiles(); + if (createdPackageDtos.Any()) + { + // Insert dto into CreatedPackage table + Database.InsertBulk(createdPackageDtos); + } + } + + /// + protected override void Migrate() + { + CreateDatabaseTable(); + MigrateCreatedPackageFilesToDb(); + } + } +} diff --git a/src/Umbraco.Infrastructure/Persistence/Dtos/CreatedPackageSchemaDto.cs b/src/Umbraco.Infrastructure/Persistence/Dtos/CreatedPackageSchemaDto.cs new file mode 100644 index 0000000000..37e6fd8d8d --- /dev/null +++ b/src/Umbraco.Infrastructure/Persistence/Dtos/CreatedPackageSchemaDto.cs @@ -0,0 +1,38 @@ +using System; +using NPoco; +using Umbraco.Cms.Infrastructure.Persistence.DatabaseAnnotations; +using Umbraco.Cms.Infrastructure.Persistence.DatabaseModelDefinitions; + +namespace Umbraco.Cms.Infrastructure.Persistence.Dtos +{ + [TableName(TableName)] + [ExplicitColumns] + [PrimaryKey("id")] + public class CreatedPackageSchemaDto + { + public const string TableName = Cms.Core.Constants.DatabaseSchema.Tables.CreatedPackageSchema; + + [Column("id")] + [PrimaryKeyColumn] + public int Id { get; set; } + + [Column("name")] + [Length(255)] + [NullSetting(NullSetting = NullSettings.NotNull)] + [Index(IndexTypes.UniqueNonClustered, ForColumns = "name", Name = "IX_" + TableName + "_Name")] + public string Name { get; set; } + + [Column("value")] + [SpecialDbType(SpecialDbTypes.NVARCHARMAX)] + [NullSetting(NullSetting = NullSettings.NotNull)] + public string Value { get; set; } + + [Column("updateDate")] + [Constraint(Default = SystemMethods.CurrentDateTime)] + public DateTime UpdateDate { get; set; } + + [Column("packageId")] + [NullSetting(NullSetting = NullSettings.NotNull)] + public Guid PackageId { get; set; } + } +} diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs new file mode 100644 index 0000000000..0c4f876bb1 --- /dev/null +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs @@ -0,0 +1,758 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Globalization; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Xml.Linq; +using Microsoft.Extensions.Options; +using NPoco; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Hosting; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Packaging; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.Persistence.Dtos; +using Umbraco.Extensions; +using File = System.IO.File; + +namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement +{ + /// + public class CreatedPackageSchemaRepository : ICreatedPackagesRepository + { + private readonly PackageDefinitionXmlParser _xmlParser; + private readonly IUmbracoDatabase _umbracoDatabase; + private readonly IHostingEnvironment _hostingEnvironment; + private readonly FileSystems _fileSystems; + private readonly IEntityXmlSerializer _serializer; + private readonly IDataTypeService _dataTypeService; + private readonly ILocalizationService _localizationService; + private readonly IFileService _fileService; + private readonly IMediaService _mediaService; + private readonly IMediaTypeService _mediaTypeService; + private readonly IContentService _contentService; + private readonly MediaFileManager _mediaFileManager; + private readonly IMacroService _macroService; + private readonly IContentTypeService _contentTypeService; + private readonly string _tempFolderPath; + private readonly string _mediaFolderPath; + + /// + /// Initializes a new instance of the class. + /// + public CreatedPackageSchemaRepository( + IUmbracoDatabase umbracoDatabase, + IHostingEnvironment hostingEnvironment, + IOptions globalSettings, + FileSystems fileSystems, + IEntityXmlSerializer serializer, + IDataTypeService dataTypeService, + ILocalizationService localizationService, + IFileService fileService, + IMediaService mediaService, + IMediaTypeService mediaTypeService, + IContentService contentService, + MediaFileManager mediaFileManager, + IMacroService macroService, + IContentTypeService contentTypeService, + string mediaFolderPath = null, + string tempFolderPath = null) + { + _umbracoDatabase = umbracoDatabase; + _hostingEnvironment = hostingEnvironment; + _fileSystems = fileSystems; + _serializer = serializer; + _dataTypeService = dataTypeService; + _localizationService = localizationService; + _fileService = fileService; + _mediaService = mediaService; + _mediaTypeService = mediaTypeService; + _contentService = contentService; + _mediaFileManager = mediaFileManager; + _macroService = macroService; + _contentTypeService = contentTypeService; + _xmlParser = new PackageDefinitionXmlParser(); + _mediaFolderPath = mediaFolderPath ?? globalSettings.Value.UmbracoMediaPath + "/created-packages"; + _tempFolderPath = + tempFolderPath ?? Constants.SystemDirectories.TempData.EnsureEndsWith('/') + "PackageFiles"; + } + + public IEnumerable GetAll() + { + Sql query = new Sql(_umbracoDatabase.SqlContext) + .Select() + .From() + .OrderBy(x => x.Id); + + var packageDefinitions = new List(); + + List xmlSchemas = _umbracoDatabase.Fetch(query); + foreach (CreatedPackageSchemaDto packageSchema in xmlSchemas) + { + var packageDefinition = _xmlParser.ToPackageDefinition(XElement.Parse(packageSchema.Value)); + packageDefinition.Id = packageSchema.Id; + packageDefinition.Name = packageSchema.Name; + packageDefinition.PackageId = packageSchema.PackageId; + packageDefinitions.Add(packageDefinition); + } + + return packageDefinitions; + } + + public PackageDefinition GetById(int id) + { + Sql query = new Sql(_umbracoDatabase.SqlContext) + .Select() + .From() + .Where(x => x.Id == id); + List schemaDtos = _umbracoDatabase.Fetch(query); + + if (schemaDtos.IsCollectionEmpty()) + { + return null; + } + + var packageSchema = schemaDtos.First(); + var packageDefinition = _xmlParser.ToPackageDefinition(XElement.Parse(packageSchema.Value)); + packageDefinition.Id = packageSchema.Id; + packageDefinition.Name = packageSchema.Name; + packageDefinition.PackageId = packageSchema.PackageId; + return packageDefinition; + } + + public void Delete(int id) + { + // Delete package snapshot + var packageDef = GetById(id); + if (File.Exists(packageDef.PackagePath)) + { + File.Delete(packageDef.PackagePath); + } + + Sql query = new Sql(_umbracoDatabase.SqlContext) + .Delete() + .Where(x => x.Id == id); + + _umbracoDatabase.Delete(query); + } + + public bool SavePackage(PackageDefinition definition) + { + if (definition == null) + { + throw new NullReferenceException("PackageDefinition cannot be null when saving"); + } + + if (definition.Name == null || string.IsNullOrEmpty(definition.Name) || definition.PackagePath == null) + { + return false; + } + + // Ensure it's valid + ValidatePackage(definition); + + + if (definition.Id == default) + { + // Create dto from definition + var dto = new CreatedPackageSchemaDto() + { + Name = definition.Name, + Value = _xmlParser.ToXml(definition).ToString(), + UpdateDate = DateTime.Now, + PackageId = Guid.NewGuid() + }; + + // Set the ids, we have to save in database first to get the Id + definition.PackageId = dto.PackageId; + var result = _umbracoDatabase.Insert(dto); + var decimalResult = result.SafeCast(); + definition.Id = decimal.ToInt32(decimalResult); + } + + // Save snapshot locally, we do this to the updated packagePath + ExportPackage(definition); + // Create dto from definition + var updatedDto = new CreatedPackageSchemaDto() + { + Name = definition.Name, + Value = _xmlParser.ToXml(definition).ToString(), + Id = definition.Id, + PackageId = definition.PackageId, + UpdateDate = DateTime.Now + }; + _umbracoDatabase.Update(updatedDto); + + return true; + } + + public string ExportPackage(PackageDefinition definition) + { + + // Ensure it's valid + ValidatePackage(definition); + + // Create a folder for building this package + var temporaryPath = + _hostingEnvironment.MapPathContentRoot(_tempFolderPath.EnsureEndsWith('/') + Guid.NewGuid()); + if (Directory.Exists(temporaryPath) == false) + { + Directory.CreateDirectory(temporaryPath); + } + + try + { + // Init package file + XDocument compiledPackageXml = CreateCompiledPackageXml(out XElement root); + + // Info section + root.Add(GetPackageInfoXml(definition)); + + PackageDocumentsAndTags(definition, root); + PackageDocumentTypes(definition, root); + PackageMediaTypes(definition, root); + PackageTemplates(definition, root); + PackageStylesheets(definition, root); + PackageStaticFiles(definition.Scripts, root, "Scripts", "Script", _fileSystems.ScriptsFileSystem); + PackageStaticFiles(definition.PartialViews, root, "PartialViews", "View", + _fileSystems.PartialViewsFileSystem); + PackageMacros(definition, root); + PackageDictionaryItems(definition, root); + PackageLanguages(definition, root); + PackageDataTypes(definition, root); + Dictionary mediaFiles = PackageMedia(definition, root); + + string fileName; + string tempPackagePath; + if (mediaFiles.Count > 0) + { + fileName = "package.zip"; + tempPackagePath = Path.Combine(temporaryPath, fileName); + using (FileStream fileStream = File.OpenWrite(tempPackagePath)) + using (var archive = new ZipArchive(fileStream, ZipArchiveMode.Create, true)) + { + ZipArchiveEntry packageXmlEntry = archive.CreateEntry("package.xml"); + using (Stream entryStream = packageXmlEntry.Open()) + { + compiledPackageXml.Save(entryStream); + } + + foreach (KeyValuePair mediaFile in mediaFiles) + { + var entryPath = $"media{mediaFile.Key.EnsureStartsWith('/')}"; + ZipArchiveEntry mediaEntry = archive.CreateEntry(entryPath); + using (Stream entryStream = mediaEntry.Open()) + using (mediaFile.Value) + { + mediaFile.Value.Seek(0, SeekOrigin.Begin); + mediaFile.Value.CopyTo(entryStream); + } + } + } + } + else + { + fileName = "package.xml"; + tempPackagePath = Path.Combine(temporaryPath, fileName); + + using (FileStream fileStream = File.OpenWrite(tempPackagePath)) + { + compiledPackageXml.Save(fileStream); + } + } + + var directoryName = + _hostingEnvironment.MapPathWebRoot( + Path.Combine(_mediaFolderPath, definition.Name.Replace(' ', '_'))); + + if (Directory.Exists(directoryName) == false) + { + Directory.CreateDirectory(directoryName); + } + + var finalPackagePath = Path.Combine(directoryName, fileName); + + if (File.Exists(finalPackagePath)) + { + File.Delete(finalPackagePath); + } + + if (File.Exists(finalPackagePath.Replace("zip", "xml"))) + { + File.Delete(finalPackagePath.Replace("zip", "xml")); + } + + File.Move(tempPackagePath, finalPackagePath); + + definition.PackagePath = finalPackagePath; + + return finalPackagePath; + } + finally + { + // Clean up + Directory.Delete(temporaryPath, true); + } + } + + private XDocument CreateCompiledPackageXml(out XElement root) + { + root = new XElement("umbPackage"); + var compiledPackageXml = new XDocument(root); + return compiledPackageXml; + } + + private void ValidatePackage(PackageDefinition definition) + { + // Ensure it's valid + var context = new ValidationContext(definition, serviceProvider: null, items: null); + var results = new List(); + var isValid = Validator.TryValidateObject(definition, context, results); + if (!isValid) + { + throw new InvalidOperationException("Validation failed, there is invalid data on the model: " + + string.Join(", ", results.Select(x => x.ErrorMessage))); + } + } + + private void PackageDataTypes(PackageDefinition definition, XContainer root) + { + var dataTypes = new XElement("DataTypes"); + foreach (var dtId in definition.DataTypes) + { + if (!int.TryParse(dtId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var outInt)) + { + continue; + } + + IDataType dataType = _dataTypeService.GetDataType(outInt); + if (dataType == null) + { + continue; + } + + dataTypes.Add(_serializer.Serialize(dataType)); + } + + root.Add(dataTypes); + } + + private void PackageLanguages(PackageDefinition definition, XContainer root) + { + var languages = new XElement("Languages"); + foreach (var langId in definition.Languages) + { + if (!int.TryParse(langId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var outInt)) + { + continue; + } + + ILanguage lang = _localizationService.GetLanguageById(outInt); + if (lang == null) + { + continue; + } + + languages.Add(_serializer.Serialize(lang)); + } + + root.Add(languages); + } + + private void PackageDictionaryItems(PackageDefinition definition, XContainer root) + { + var rootDictionaryItems = new XElement("DictionaryItems"); + var items = new Dictionary(); + + foreach (var dictionaryId in definition.DictionaryItems) + { + if (!int.TryParse(dictionaryId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var outInt)) + { + continue; + } + + IDictionaryItem di = _localizationService.GetDictionaryItemById(outInt); + + if (di == null) + { + continue; + } + + items[di.Key] = (di, _serializer.Serialize(di, false)); + } + + // organize them in hierarchy ... + var itemCount = items.Count; + var processed = new Dictionary(); + while (processed.Count < itemCount) + { + foreach (Guid key in items.Keys.ToList()) + { + (IDictionaryItem dictionaryItem, XElement serializedDictionaryValue) = items[key]; + + if (!dictionaryItem.ParentId.HasValue) + { + // if it has no parent, its definitely just at the root + AppendDictionaryElement(rootDictionaryItems, items, processed, key, serializedDictionaryValue); + } + else + { + if (processed.ContainsKey(dictionaryItem.ParentId.Value)) + { + // we've processed this parent element already so we can just append this xml child to it + AppendDictionaryElement(processed[dictionaryItem.ParentId.Value], items, processed, key, + serializedDictionaryValue); + } + else if (items.ContainsKey(dictionaryItem.ParentId.Value)) + { + // we know the parent exists in the dictionary but + // we haven't processed it yet so we'll leave it for the next loop + continue; + } + else + { + // in this case, the parent of this item doesn't exist in our collection, we have no + // choice but to add it to the root. + AppendDictionaryElement(rootDictionaryItems, items, processed, key, + serializedDictionaryValue); + } + } + } + } + + root.Add(rootDictionaryItems); + + static void AppendDictionaryElement(XElement rootDictionaryItems, + Dictionary items, + Dictionary processed, Guid key, XElement serializedDictionaryValue) + { + // track it + processed.Add(key, serializedDictionaryValue); + + // append it + rootDictionaryItems.Add(serializedDictionaryValue); + + // remove it so its not re-processed + items.Remove(key); + } + } + + private void PackageMacros(PackageDefinition definition, XContainer root) + { + var packagedMacros = new List(); + var macros = new XElement("Macros"); + foreach (var macroId in definition.Macros) + { + if (!int.TryParse(macroId, NumberStyles.Integer, CultureInfo.InvariantCulture, out int outInt)) + { + continue; + } + + XElement macroXml = GetMacroXml(outInt, out IMacro macro); + if (macroXml == null) + { + continue; + } + + macros.Add(macroXml); + packagedMacros.Add(macro); + } + + root.Add(macros); + + // Get the partial views for macros and package those (exclude views outside of the default directory, e.g. App_Plugins\*\Views) + IEnumerable views = packagedMacros + .Where(x => x.MacroSource.StartsWith(Constants.SystemDirectories.MacroPartials)) + .Select(x => + x.MacroSource.Substring(Constants.SystemDirectories.MacroPartials.Length).Replace('/', '\\')); + PackageStaticFiles(views, root, "MacroPartialViews", "View", _fileSystems.MacroPartialsFileSystem); + } + + private void PackageStylesheets(PackageDefinition definition, XContainer root) + { + var stylesheetsXml = new XElement("Stylesheets"); + foreach (var stylesheet in definition.Stylesheets) + { + if (stylesheet.IsNullOrWhiteSpace()) + { + continue; + } + + XElement xml = GetStylesheetXml(stylesheet, true); + if (xml != null) + { + stylesheetsXml.Add(xml); + } + } + + root.Add(stylesheetsXml); + } + + private void PackageStaticFiles( + IEnumerable filePaths, + XContainer root, + string containerName, + string elementName, + IFileSystem fileSystem) + { + var scriptsXml = new XElement(containerName); + foreach (var file in filePaths) + { + if (file.IsNullOrWhiteSpace()) + { + continue; + } + + if (!fileSystem.FileExists(file)) + { + throw new InvalidOperationException("No file found with path " + file); + } + + using (Stream stream = fileSystem.OpenFile(file)) + using (var reader = new StreamReader(stream)) + { + var fileContents = reader.ReadToEnd(); + scriptsXml.Add( + new XElement( + elementName, + new XAttribute("path", file), + new XCData(fileContents))); + } + } + + root.Add(scriptsXml); + } + + private void PackageTemplates(PackageDefinition definition, XContainer root) + { + var templatesXml = new XElement("Templates"); + foreach (var templateId in definition.Templates) + { + if (!int.TryParse(templateId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var outInt)) + { + continue; + } + + ITemplate template = _fileService.GetTemplate(outInt); + if (template == null) + { + continue; + } + + templatesXml.Add(_serializer.Serialize(template)); + } + + root.Add(templatesXml); + } + + private void PackageDocumentTypes(PackageDefinition definition, XContainer root) + { + var contentTypes = new HashSet(); + var docTypesXml = new XElement("DocumentTypes"); + foreach (var dtId in definition.DocumentTypes) + { + if (!int.TryParse(dtId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var outInt)) + { + continue; + } + + IContentType contentType = _contentTypeService.Get(outInt); + if (contentType == null) + { + continue; + } + + AddDocumentType(contentType, contentTypes); + } + + foreach (IContentType contentType in contentTypes) + { + docTypesXml.Add(_serializer.Serialize(contentType)); + } + + root.Add(docTypesXml); + } + + private void PackageMediaTypes(PackageDefinition definition, XContainer root) + { + var mediaTypes = new HashSet(); + var mediaTypesXml = new XElement("MediaTypes"); + foreach (var mediaTypeId in definition.MediaTypes) + { + if (!int.TryParse(mediaTypeId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var outInt)) + { + continue; + } + + IMediaType mediaType = _mediaTypeService.Get(outInt); + if (mediaType == null) + { + continue; + } + + AddMediaType(mediaType, mediaTypes); + } + + foreach (IMediaType mediaType in mediaTypes) + { + mediaTypesXml.Add(_serializer.Serialize(mediaType)); + } + + root.Add(mediaTypesXml); + } + + private void PackageDocumentsAndTags(PackageDefinition definition, XContainer root) + { + // Documents and tags + if (string.IsNullOrEmpty(definition.ContentNodeId) == false && int.TryParse(definition.ContentNodeId, + NumberStyles.Integer, CultureInfo.InvariantCulture, out var contentNodeId)) + { + if (contentNodeId > 0) + { + // load content from umbraco. + IContent content = _contentService.GetById(contentNodeId); + if (content != null) + { + var contentXml = definition.ContentLoadChildNodes + ? content.ToDeepXml(_serializer) + : content.ToXml(_serializer); + + // Create the Documents/DocumentSet node + + root.Add( + new XElement( + "Documents", + new XElement( + "DocumentSet", + new XAttribute("importMode", "root"), + contentXml))); + } + } + } + } + + private Dictionary PackageMedia(PackageDefinition definition, XElement root) + { + var mediaStreams = new Dictionary(); + + // callback that occurs on each serialized media item + void OnSerializedMedia(IMedia media, XElement xmlMedia) + { + // get the media file path and store that separately in the XML. + // the media file path is different from the URL and is specifically + // extracted using the property editor for this media file and the current media file system. + Stream mediaStream = _mediaFileManager.GetFile(media, out var mediaFilePath); + if (mediaStream != null) + { + xmlMedia.Add(new XAttribute("mediaFilePath", mediaFilePath)); + + // add the stream to our outgoing stream + mediaStreams.Add(mediaFilePath, mediaStream); + } + } + + IEnumerable medias = _mediaService.GetByIds(definition.MediaUdis); + + var mediaXml = new XElement( + "MediaItems", + medias.Select(media => + { + XElement serializedMedia = _serializer.Serialize( + media, + definition.MediaLoadChildNodes, + OnSerializedMedia); + + return new XElement("MediaSet", serializedMedia); + })); + + root.Add(mediaXml); + + return mediaStreams; + } + + /// + /// Gets a macros xml node + /// + private XElement GetMacroXml(int macroId, out IMacro macro) + { + macro = _macroService.GetById(macroId); + if (macro == null) + { + return null; + } + + XElement xml = _serializer.Serialize(macro); + return xml; + } + + /// + /// Converts a umbraco stylesheet to a package xml node + /// + /// The path of the stylesheet. + /// if set to true [include properties]. + private XElement GetStylesheetXml(string path, bool includeProperties) + { + if (string.IsNullOrWhiteSpace(path)) + { + throw new ArgumentException("Value cannot be null or whitespace.", nameof(path)); + } + + IStylesheet stylesheet = _fileService.GetStylesheet(path); + if (stylesheet == null) + { + return null; + } + + return _serializer.Serialize(stylesheet, includeProperties); + } + + private void AddDocumentType(IContentType dt, HashSet dtl) + { + if (dt.ParentId > 0) + { + IContentType parent = _contentTypeService.Get(dt.ParentId); + if (parent != null) + { + AddDocumentType(parent, dtl); + } + } + + if (!dtl.Contains(dt)) + { + dtl.Add(dt); + } + } + + private void AddMediaType(IMediaType mediaType, HashSet mediaTypes) + { + if (mediaType.ParentId > 0) + { + IMediaType parent = _mediaTypeService.Get(mediaType.ParentId); + if (parent != null) + { + AddMediaType(parent, mediaTypes); + } + } + + if (!mediaTypes.Contains(mediaType)) + { + mediaTypes.Add(mediaType); + } + } + + private static XElement GetPackageInfoXml(PackageDefinition definition) + { + var info = new XElement("info"); + + // Package info + var package = new XElement("package"); + package.Add(new XElement("name", definition.Name)); + info.Add(package); + return info; + } + } +} diff --git a/src/Umbraco.Web.BackOffice/Controllers/PackageController.cs b/src/Umbraco.Web.BackOffice/Controllers/PackageController.cs index 813682c70e..6312b935f9 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/PackageController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/PackageController.cs @@ -69,7 +69,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers if (ModelState.IsValid == false) return ValidationProblem(ModelState); - //save it + // Save it if (!_packagingService.SaveCreatedPackage(model)) { return ValidationProblem( @@ -78,9 +78,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers : $"The package with id {model.Id} was not found"); } - _packagingService.ExportCreatedPackage(model); - - //the packagePath will be on the model + // The packagePath will be on the model return model; } @@ -112,11 +110,11 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers return ValidationErrorResult.CreateNotificationValidationErrorResult( $"Package migration failed on package {packageName} with error: {ex.Message}. Check log for full details."); - } + } } [HttpGet] - public IActionResult DownloadCreatedPackage(int id) + public IActionResult DownloadCreatedPackage(int id) { var package = _packagingService.GetCreatedPackageById(id); if (package == null) diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Packaging/CreatedPackageSchemaTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Packaging/CreatedPackageSchemaTests.cs new file mode 100644 index 0000000000..6a5ee88426 --- /dev/null +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Packaging/CreatedPackageSchemaTests.cs @@ -0,0 +1,62 @@ +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using Umbraco.Cms.Core.Packaging; +using Umbraco.Cms.Tests.Common.Testing; +using Umbraco.Cms.Tests.Integration.Testing; + +namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Packaging +{ + [TestFixture] + [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] + public class CreatedPackageSchemaTests : UmbracoIntegrationTest + { + private ICreatedPackagesRepository CreatedPackageSchemaRepository => + GetRequiredService(); + + [Test] + public void PackagesRepository_Can_Save_PackageDefinition() + { + var packageDefinition = new PackageDefinition() + { + Name = "NewPack", DocumentTypes = new List() { "Root" } + }; + var result = CreatedPackageSchemaRepository.SavePackage(packageDefinition); + Assert.IsTrue(result); + } + + [Test] + public void PackageRepository_GetAll_Returns_All_PackageDefinitions() + { + var packageDefinitionList = new List() + { + new () { Name = "PackOne" }, + new () { Name = "PackTwo" }, + new () { Name = "PackThree" } + }; + foreach (PackageDefinition packageDefinition in packageDefinitionList) + { + CreatedPackageSchemaRepository.SavePackage(packageDefinition); + } + + var loadedPackageDefinitions = CreatedPackageSchemaRepository.GetAll().ToList(); + CollectionAssert.IsNotEmpty(loadedPackageDefinitions); + CollectionAssert.AllItemsAreUnique(loadedPackageDefinitions); + Assert.AreEqual(loadedPackageDefinitions.Count, 3); + } + + [Test] + public void PackageRepository_Can_Update_Package() + { + var packageDefinition = new PackageDefinition() { Name = "TestPackage" }; + CreatedPackageSchemaRepository.SavePackage(packageDefinition); + + packageDefinition.Name = "UpdatedName"; + CreatedPackageSchemaRepository.SavePackage(packageDefinition); + var result = CreatedPackageSchemaRepository.GetAll().ToList(); + + Assert.AreEqual(result.Count, 1); + Assert.AreEqual(result.FirstOrDefault()?.Name, "UpdatedName"); + } + } +} From 040116c004d5d16d553914193729b3e70f9216b7 Mon Sep 17 00:00:00 2001 From: Mole Date: Tue, 21 Dec 2021 14:48:56 +0100 Subject: [PATCH 028/141] Remove TODO With some testing this seems to be a none-issue, since we don't operate on the keys themselves --- src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs b/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs index 4894fb7125..a38f152f3b 100644 --- a/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs @@ -108,7 +108,6 @@ namespace Umbraco.Cms.Core.Configuration.Models /// private IEnumerable GetCharReplacements() { - // TODO We need to special handle ":", as this character is special in keys if (!EnableDefaultCharReplacements) { return _charCollection ?? Enumerable.Empty(); From c6d28f01a8d2afba588b265529bc5549d4c5dd29 Mon Sep 17 00:00:00 2001 From: Mole Date: Tue, 21 Dec 2021 15:15:53 +0100 Subject: [PATCH 029/141] Check for null in DefaultShortStringHelperConfig.WithDefault --- .../Strings/DefaultShortStringHelperConfig.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Strings/DefaultShortStringHelperConfig.cs b/src/Umbraco.Core/Strings/DefaultShortStringHelperConfig.cs index 273d7a6562..d5bab4afb1 100644 --- a/src/Umbraco.Core/Strings/DefaultShortStringHelperConfig.cs +++ b/src/Umbraco.Core/Strings/DefaultShortStringHelperConfig.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Configuration.UmbracoSettings; using Umbraco.Extensions; namespace Umbraco.Cms.Core.Strings @@ -60,7 +61,18 @@ namespace Umbraco.Cms.Core.Strings /// The short string helper. public DefaultShortStringHelperConfig WithDefault(RequestHandlerSettings requestHandlerSettings) { - UrlReplaceCharacters = requestHandlerSettings.CharCollection + // CharCollection could potentially be null if not invoked first by the framework, for instance in tests, so ensure that it's initialized. + IEnumerable charCollection = requestHandlerSettings.CharCollection; + if (charCollection is null) + { + charCollection = requestHandlerSettings.CharCollection; + if (charCollection is null) + { + throw new ArgumentNullException(nameof(requestHandlerSettings.CharCollection)); + } + } + + UrlReplaceCharacters = charCollection .Where(x => string.IsNullOrEmpty(x.Char) == false) .ToDictionary(x => x.Char, x => x.Replacement); From 1af09c7f1345484d6cf1c91172935ad6d781e99a Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Tue, 21 Dec 2021 15:19:55 +0100 Subject: [PATCH 030/141] Updates Forms and adds Deploy JSON schema. (#11739) --- src/JsonSchema/AppSettings.cs | 8 +++++++- src/JsonSchema/JsonSchema.csproj | 3 ++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/JsonSchema/AppSettings.cs b/src/JsonSchema/AppSettings.cs index 8db2bbb8c7..a9b70fc4a1 100644 --- a/src/JsonSchema/AppSettings.cs +++ b/src/JsonSchema/AppSettings.cs @@ -1,7 +1,9 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Deploy.Core.Configuration.DeployConfiguration; +using Umbraco.Deploy.Core.Configuration.DeployProjectConfiguration; using Umbraco.Forms.Core.Configuration; using SecuritySettings = Umbraco.Cms.Core.Configuration.Models.SecuritySettings; @@ -82,6 +84,7 @@ namespace JsonSchema public BasicAuthSettings BasicAuth { get; set; } public PackageMigrationSettings PackageMigration { get; set; } + public LegacyPasswordMigrationSettings LegacyPasswordMigration { get; set; } } @@ -116,6 +119,9 @@ namespace JsonSchema /// public class DeployDefinition { + public DeploySettings Settings { get; set; } + + public DeployProjectConfig Project { get; set; } } } } diff --git a/src/JsonSchema/JsonSchema.csproj b/src/JsonSchema/JsonSchema.csproj index 84de67b657..c7cbbe1320 100644 --- a/src/JsonSchema/JsonSchema.csproj +++ b/src/JsonSchema/JsonSchema.csproj @@ -10,7 +10,8 @@ - + + From 53e5a25df87ef8b8bce1f5cd32d8f5d652b582c9 Mon Sep 17 00:00:00 2001 From: Mole Date: Wed, 22 Dec 2021 11:29:04 +0100 Subject: [PATCH 031/141] Remove ugly CharCollection hack By adding a new configuration and mapping the old CharCollection to that, we can get around having to return null the first time, by obsoleting the old one and redirecting to the new GetCharReplacements method --- .../Models/RequestHandlerSettings.cs | 62 +++---------------- .../UmbracoBuilder.Configuration.cs | 20 ++++++ .../RequestHandlerSettingsExtension.cs | 44 +++++++++++++ .../Strings/DefaultShortStringHelperConfig.cs | 11 +--- .../Models/RequestHandlerSettingsTests.cs | 18 +++--- 5 files changed, 81 insertions(+), 74 deletions(-) create mode 100644 src/Umbraco.Core/Extensions/RequestHandlerSettingsExtension.cs diff --git a/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs b/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs index a38f152f3b..051c31dc26 100644 --- a/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs @@ -1,9 +1,9 @@ // Copyright (c) Umbraco. // See LICENSE for more details. +using System; using System.Collections.Generic; using System.ComponentModel; -using System.Linq; using Umbraco.Cms.Core.Configuration.UmbracoSettings; using Umbraco.Extensions; @@ -75,63 +75,15 @@ namespace Umbraco.Cms.Core.Configuration.Models [DefaultValue(StaticEnableDefaultCharReplacements)] public bool EnableDefaultCharReplacements { get; set; } = StaticEnableDefaultCharReplacements; - private IEnumerable _charCollection; + /// + /// Add additional character replacements, or override defaults + /// + [Obsolete("Use the GetCharReplacements extension method in the Umbraco.Extensions namespace instead. Scheduled for removal in V11")] + public IEnumerable CharCollection { get; set; } = DefaultCharCollection; /// /// Add additional character replacements, or override defaults /// - public IEnumerable CharCollection - { - get - { - // This is pretty ugly, but necessary. - // Essentially the config binding will only run properly if we return null the first time this is invoked. - // Otherwise whatever we return will just be used and user specific bindings won't overwrite the existing ones. - // However the next time this get is invoked, for instance in DefaultShortStringHelper we want to actually run the GetCharReplacements - // To make sure that the default bindings is used if configured to do so. - // Therefore we set _charCollection to be something, and still return null, to trick dotnet to actually read the config. - if (_charCollection is null) - { - _charCollection = Enumerable.Empty(); - return null; - } - - return GetCharReplacements(); - } - - set => _charCollection = value; - } - - /// - /// Get concatenated user and default character replacements - /// taking into account - /// - private IEnumerable GetCharReplacements() - { - if (!EnableDefaultCharReplacements) - { - return _charCollection ?? Enumerable.Empty(); - } - - if (_charCollection == null || !_charCollection.Any()) - { - return DefaultCharCollection; - } - - foreach (CharItem defaultReplacement in DefaultCharCollection) - { - foreach (CharItem userReplacement in _charCollection) - { - if (userReplacement.Char == defaultReplacement.Char) - { - defaultReplacement.Replacement = userReplacement.Replacement; - } - } - } - - IEnumerable mergedCollections = DefaultCharCollection.Union(_charCollection, new CharacterReplacementEqualityComparer()); - - return mergedCollections; - } + public IEnumerable UserDefinedCharCollection { get; set; } } } diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs index 6ef87464e8..8043698b6e 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Generic; using System.Reflection; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; @@ -76,6 +78,24 @@ namespace Umbraco.Cms.Core.DependencyInjection .AddUmbracoOptions() .AddUmbracoOptions(); + builder.Services.Configure(options => + { + var userDefinedReplacements = new List(); + IEnumerable config = builder.Config + .GetSection( + $"{Constants.Configuration.ConfigRequestHandler}:{nameof(RequestHandlerSettings.CharCollection)}") + .GetChildren(); + + foreach (IConfigurationSection section in config) + { + var @char = section.GetValue(nameof(CharItem.Char)); + var replacement = section.GetValue(nameof(CharItem.Replacement)); + userDefinedReplacements.Add(new CharItem { Char = @char, Replacement = replacement }); + } + + options.UserDefinedCharCollection = userDefinedReplacements; + }); + return builder; } } diff --git a/src/Umbraco.Core/Extensions/RequestHandlerSettingsExtension.cs b/src/Umbraco.Core/Extensions/RequestHandlerSettingsExtension.cs new file mode 100644 index 0000000000..d54d00188a --- /dev/null +++ b/src/Umbraco.Core/Extensions/RequestHandlerSettingsExtension.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; +using System.Linq; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Configuration.UmbracoSettings; + +namespace Umbraco.Extensions +{ + /// + /// Get concatenated user and default character replacements + /// taking into account + /// + public static class RequestHandlerSettingsExtension + { + public static IEnumerable GetCharReplacements(this RequestHandlerSettings requestHandlerSettings) + { + if (!requestHandlerSettings.EnableDefaultCharReplacements) + { + return requestHandlerSettings.UserDefinedCharCollection ?? Enumerable.Empty(); + } + + if (requestHandlerSettings.UserDefinedCharCollection == null || !requestHandlerSettings.UserDefinedCharCollection.Any()) + { + return RequestHandlerSettings.DefaultCharCollection; + } + + foreach (CharItem defaultReplacement in RequestHandlerSettings.DefaultCharCollection) + { + foreach (CharItem userReplacement in requestHandlerSettings.UserDefinedCharCollection) + { + if (userReplacement.Char == defaultReplacement.Char) + { + defaultReplacement.Replacement = userReplacement.Replacement; + } + } + } + + IEnumerable mergedCollections = + RequestHandlerSettings.DefaultCharCollection.Union( + requestHandlerSettings.UserDefinedCharCollection, new CharacterReplacementEqualityComparer()); + + return mergedCollections; + } + } +} diff --git a/src/Umbraco.Core/Strings/DefaultShortStringHelperConfig.cs b/src/Umbraco.Core/Strings/DefaultShortStringHelperConfig.cs index d5bab4afb1..b0f0a9b003 100644 --- a/src/Umbraco.Core/Strings/DefaultShortStringHelperConfig.cs +++ b/src/Umbraco.Core/Strings/DefaultShortStringHelperConfig.cs @@ -61,16 +61,7 @@ namespace Umbraco.Cms.Core.Strings /// The short string helper. public DefaultShortStringHelperConfig WithDefault(RequestHandlerSettings requestHandlerSettings) { - // CharCollection could potentially be null if not invoked first by the framework, for instance in tests, so ensure that it's initialized. - IEnumerable charCollection = requestHandlerSettings.CharCollection; - if (charCollection is null) - { - charCollection = requestHandlerSettings.CharCollection; - if (charCollection is null) - { - throw new ArgumentNullException(nameof(requestHandlerSettings.CharCollection)); - } - } + IEnumerable charCollection = requestHandlerSettings.GetCharReplacements(); UrlReplaceCharacters = charCollection .Where(x => string.IsNullOrEmpty(x.Char) == false) diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/RequestHandlerSettingsTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/RequestHandlerSettingsTests.cs index d681d4a70e..f159ecbc85 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/RequestHandlerSettingsTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/RequestHandlerSettingsTests.cs @@ -1,7 +1,7 @@ using System.Linq; using NUnit.Framework; using Umbraco.Cms.Core.Configuration.Models; -using Umbraco.Cms.Core.Configuration.UmbracoSettings; +using Umbraco.Extensions; namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Configuration.Models { @@ -18,8 +18,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Configuration.Models }; - var settings = new RequestHandlerSettings { CharCollection = userCollection }; - var actual = settings.CharCollection.ToList(); + var settings = new RequestHandlerSettings { UserDefinedCharCollection = userCollection }; + var actual = settings.GetCharReplacements().ToList(); var expectedCollection = RequestHandlerSettings.DefaultCharCollection.ToList(); expectedCollection.AddRange(userCollection); @@ -37,8 +37,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Configuration.Models new () { Char = "test2", Replacement = "replace2" } }; - var settings = new RequestHandlerSettings { CharCollection = userCollection, EnableDefaultCharReplacements = false }; - var actual = settings.CharCollection.ToList(); + var settings = new RequestHandlerSettings { UserDefinedCharCollection = userCollection, EnableDefaultCharReplacements = false }; + var actual = settings.GetCharReplacements().ToList(); Assert.AreEqual(userCollection.Length, actual.Count); Assert.That(actual, Is.EquivalentTo(userCollection)); @@ -53,8 +53,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Configuration.Models new () { Char = ".", Replacement = "dot" } }; - var settings = new RequestHandlerSettings { CharCollection = userCollection }; - var actual = settings.CharCollection.ToList(); + var settings = new RequestHandlerSettings { UserDefinedCharCollection = userCollection }; + var actual = settings.GetCharReplacements().ToList(); Assert.AreEqual(RequestHandlerSettings.DefaultCharCollection.Length, actual.Count); @@ -74,8 +74,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Configuration.Models new () { Char = "new", Replacement = "new" } }; - var settings = new RequestHandlerSettings { CharCollection = userCollection }; - var actual = settings.CharCollection.ToList(); + var settings = new RequestHandlerSettings { UserDefinedCharCollection = userCollection }; + var actual = settings.GetCharReplacements().ToList(); // Add 1 to the length, because we're expecting to only add one new one Assert.AreEqual(RequestHandlerSettings.DefaultCharCollection.Length + 1, actual.Count); From bc6d8b5ecee275b77942714a0075b50a2412ba8d Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Wed, 22 Dec 2021 13:03:38 +0100 Subject: [PATCH 032/141] v9: Throw error on duplicate routes (#11774) * Add conflicting route service and check * Added testclass * Cleanup * Update src/Umbraco.Web.BackOffice/Services/ConflictingRouteService.cs Co-authored-by: Elitsa Marinovska <21998037+elit0451@users.noreply.github.com> * Implemented out variable Co-authored-by: Elitsa Marinovska Co-authored-by: Elitsa Marinovska <21998037+elit0451@users.noreply.github.com> --- .../Services/IConflictingRouteService.cs | 7 ++++ .../Runtime/RuntimeState.cs | 34 +++++++++++++++++- .../UmbracoBuilderExtensions.cs | 1 + .../Services/ConflictingRouteService.cs | 35 +++++++++++++++++++ .../Testing/TestConflictingRouteService.cs | 14 ++++++++ .../Testing/UmbracoIntegrationTest.cs | 3 ++ 6 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 src/Umbraco.Core/Services/IConflictingRouteService.cs create mode 100644 src/Umbraco.Web.BackOffice/Services/ConflictingRouteService.cs create mode 100644 tests/Umbraco.Tests.Integration/Testing/TestConflictingRouteService.cs diff --git a/src/Umbraco.Core/Services/IConflictingRouteService.cs b/src/Umbraco.Core/Services/IConflictingRouteService.cs new file mode 100644 index 0000000000..04d81d7f88 --- /dev/null +++ b/src/Umbraco.Core/Services/IConflictingRouteService.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Cms.Core.Services +{ + public interface IConflictingRouteService + { + public bool HasConflictingRoutes(out string controllerName); + } +} diff --git a/src/Umbraco.Infrastructure/Runtime/RuntimeState.cs b/src/Umbraco.Infrastructure/Runtime/RuntimeState.cs index 8eab08bfee..73b6692e3a 100644 --- a/src/Umbraco.Infrastructure/Runtime/RuntimeState.cs +++ b/src/Umbraco.Infrastructure/Runtime/RuntimeState.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Threading; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Cms.Core; @@ -12,6 +13,7 @@ using Umbraco.Cms.Core.Semver; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Infrastructure.Migrations.Upgrade; using Umbraco.Cms.Infrastructure.Persistence; +using Umbraco.Cms.Web.Common.DependencyInjection; using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.Runtime @@ -30,6 +32,7 @@ namespace Umbraco.Cms.Infrastructure.Runtime private readonly ILogger _logger; private readonly PendingPackageMigrations _packageMigrationState; private readonly Dictionary _startupState = new Dictionary(); + private readonly IConflictingRouteService _conflictingRouteService; /// /// The initial @@ -51,6 +54,25 @@ namespace Umbraco.Cms.Infrastructure.Runtime IUmbracoDatabaseFactory databaseFactory, ILogger logger, PendingPackageMigrations packageMigrationState) + : this( + globalSettings, + unattendedSettings, + umbracoVersion, + databaseFactory, + logger, + packageMigrationState, + StaticServiceProvider.Instance.GetRequiredService()) + { + } + + public RuntimeState( + IOptions globalSettings, + IOptions unattendedSettings, + IUmbracoVersion umbracoVersion, + IUmbracoDatabaseFactory databaseFactory, + ILogger logger, + PendingPackageMigrations packageMigrationState, + IConflictingRouteService conflictingRouteService) { _globalSettings = globalSettings; _unattendedSettings = unattendedSettings; @@ -58,9 +80,9 @@ namespace Umbraco.Cms.Infrastructure.Runtime _databaseFactory = databaseFactory; _logger = logger; _packageMigrationState = packageMigrationState; + _conflictingRouteService = conflictingRouteService; } - /// public Version Version => _umbracoVersion.Version; @@ -101,6 +123,16 @@ namespace Umbraco.Cms.Infrastructure.Runtime return; } + // Check if we have multiple controllers with the same name. + if (_conflictingRouteService.HasConflictingRoutes(out string controllerName)) + { + Level = RuntimeLevel.BootFailed; + Reason = RuntimeLevelReason.BootFailedOnException; + BootFailedException = new BootFailedException($"Conflicting routes, you cannot have multiple controllers with the same name: {controllerName}"); + + return; + } + // Check the database state, whether we can connect or if it's in an upgrade or empty state, etc... switch (GetUmbracoDatabaseState(_databaseFactory)) diff --git a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs index aeb329efb4..7a15d640e6 100644 --- a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs @@ -114,6 +114,7 @@ namespace Umbraco.Extensions }); builder.Services.AddUnique(); + builder.Services.AddUnique(); builder.Services.AddUnique(); return builder; diff --git a/src/Umbraco.Web.BackOffice/Services/ConflictingRouteService.cs b/src/Umbraco.Web.BackOffice/Services/ConflictingRouteService.cs new file mode 100644 index 0000000000..2951ace9e1 --- /dev/null +++ b/src/Umbraco.Web.BackOffice/Services/ConflictingRouteService.cs @@ -0,0 +1,35 @@ +using System; +using System.Linq; +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Web.Common.Controllers; + +namespace Umbraco.Cms.Web.BackOffice.Services +{ + public class ConflictingRouteService : IConflictingRouteService + { + private readonly TypeLoader _typeLoader; + + /// + /// Initializes a new instance of the class. + /// + public ConflictingRouteService(TypeLoader typeLoader) => _typeLoader = typeLoader; + + /// + public bool HasConflictingRoutes(out string controllerName) + { + var controllers = _typeLoader.GetTypes().ToList(); + foreach (Type controller in controllers) + { + if (controllers.Count(x => x.Name == controller.Name) > 1) + { + controllerName = controller.Name; + return true; + } + } + + controllerName = string.Empty; + return false; + } + } +} diff --git a/tests/Umbraco.Tests.Integration/Testing/TestConflictingRouteService.cs b/tests/Umbraco.Tests.Integration/Testing/TestConflictingRouteService.cs new file mode 100644 index 0000000000..c61cbb8a1d --- /dev/null +++ b/tests/Umbraco.Tests.Integration/Testing/TestConflictingRouteService.cs @@ -0,0 +1,14 @@ +using System; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Tests.Integration.Testing +{ + public class TestConflictingRouteService : IConflictingRouteService + { + public bool HasConflictingRoutes(out string controllername) + { + controllername = string.Empty; + return false; + } + } +} diff --git a/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs b/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs index 314a614b49..fb505e6b83 100644 --- a/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs +++ b/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs @@ -201,6 +201,9 @@ namespace Umbraco.Cms.Tests.Integration.Testing IWebHostEnvironment webHostEnvironment = TestHelper.GetWebHostEnvironment(); services.AddRequiredNetCoreServices(TestHelper, webHostEnvironment); + // We register this service because we need it for IRuntimeState, if we don't this breaks 900 tests + services.AddSingleton(); + // Add it! Core.Hosting.IHostingEnvironment hostingEnvironment = TestHelper.GetHostingEnvironment(); TypeLoader typeLoader = services.AddTypeLoader( From 04d20269ab51addc0f127750a9ddfe4109690b21 Mon Sep 17 00:00:00 2001 From: Mole Date: Wed, 22 Dec 2021 13:02:13 +0100 Subject: [PATCH 033/141] Merge CharCollection and UserDefinedCharCollection Otherwise only CharCollection would work, and that's obsolete, now you can use either, but UserDefinedCharCollection takes priority --- .../UmbracoBuilder.Configuration.cs | 19 +---- .../RequestHandlerSettingsExtension.cs | 74 ++++++++++++++++--- 2 files changed, 65 insertions(+), 28 deletions(-) diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs index 8043698b6e..d1a8542688 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Configuration.Models.Validation; +using Umbraco.Extensions; namespace Umbraco.Cms.Core.DependencyInjection { @@ -78,23 +79,7 @@ namespace Umbraco.Cms.Core.DependencyInjection .AddUmbracoOptions() .AddUmbracoOptions(); - builder.Services.Configure(options => - { - var userDefinedReplacements = new List(); - IEnumerable config = builder.Config - .GetSection( - $"{Constants.Configuration.ConfigRequestHandler}:{nameof(RequestHandlerSettings.CharCollection)}") - .GetChildren(); - - foreach (IConfigurationSection section in config) - { - var @char = section.GetValue(nameof(CharItem.Char)); - var replacement = section.GetValue(nameof(CharItem.Replacement)); - userDefinedReplacements.Add(new CharItem { Char = @char, Replacement = replacement }); - } - - options.UserDefinedCharCollection = userDefinedReplacements; - }); + builder.Services.Configure(options => options.MergeReplacements(builder.Config)); return builder; } diff --git a/src/Umbraco.Core/Extensions/RequestHandlerSettingsExtension.cs b/src/Umbraco.Core/Extensions/RequestHandlerSettingsExtension.cs index d54d00188a..e9e6618f8c 100644 --- a/src/Umbraco.Core/Extensions/RequestHandlerSettingsExtension.cs +++ b/src/Umbraco.Core/Extensions/RequestHandlerSettingsExtension.cs @@ -1,5 +1,7 @@ using System.Collections.Generic; using System.Linq; +using Microsoft.Extensions.Configuration; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Configuration.UmbracoSettings; @@ -11,34 +13,84 @@ namespace Umbraco.Extensions /// public static class RequestHandlerSettingsExtension { + /// + /// Get concatenated user and default character replacements + /// taking into account + /// public static IEnumerable GetCharReplacements(this RequestHandlerSettings requestHandlerSettings) { - if (!requestHandlerSettings.EnableDefaultCharReplacements) + if (requestHandlerSettings.EnableDefaultCharReplacements is false) { return requestHandlerSettings.UserDefinedCharCollection ?? Enumerable.Empty(); } - if (requestHandlerSettings.UserDefinedCharCollection == null || !requestHandlerSettings.UserDefinedCharCollection.Any()) + if (requestHandlerSettings.UserDefinedCharCollection == null || requestHandlerSettings.UserDefinedCharCollection.Any() is false) { return RequestHandlerSettings.DefaultCharCollection; } - foreach (CharItem defaultReplacement in RequestHandlerSettings.DefaultCharCollection) + return MergeUnique(requestHandlerSettings.UserDefinedCharCollection, RequestHandlerSettings.DefaultCharCollection); + } + + /// + /// Merges CharCollection and UserDefinedCharCollection, prioritizing UserDefinedCharCollection + /// + internal static void MergeReplacements(this RequestHandlerSettings requestHandlerSettings, IConfiguration configuration) + { + string sectionKey = $"{Constants.Configuration.ConfigRequestHandler}:"; + + IEnumerable charCollection = GetReplacements( + configuration, + $"{sectionKey}{nameof(RequestHandlerSettings.CharCollection)}"); + + IEnumerable userDefinedCharCollection = GetReplacements( + configuration, + $"{sectionKey}{nameof(requestHandlerSettings.UserDefinedCharCollection)}"); + + IEnumerable mergedCollection = MergeUnique(userDefinedCharCollection, charCollection); + + requestHandlerSettings.UserDefinedCharCollection = mergedCollection; + } + + private static IEnumerable GetReplacements(IConfiguration configuration, string key) + { + var replacements = new List(); + IEnumerable config = configuration.GetSection(key).GetChildren(); + + foreach (IConfigurationSection section in config) { - foreach (CharItem userReplacement in requestHandlerSettings.UserDefinedCharCollection) + var @char = section.GetValue(nameof(CharItem.Char)); + var replacement = section.GetValue(nameof(CharItem.Replacement)); + replacements.Add(new CharItem { Char = @char, Replacement = replacement }); + } + + return replacements; + } + + /// + /// Merges two IEnumerable of CharItem without any duplicates, items in priorityReplacements will override those in alternativeReplacements + /// + private static IEnumerable MergeUnique( + IEnumerable priorityReplacements, + IEnumerable alternativeReplacements) + { + var priorityReplacementsList = priorityReplacements.ToList(); + var alternativeReplacementsList = alternativeReplacements.ToList(); + + foreach (CharItem alternativeReplacement in alternativeReplacementsList) + { + foreach (CharItem priorityReplacement in priorityReplacementsList) { - if (userReplacement.Char == defaultReplacement.Char) + if (priorityReplacement.Char == alternativeReplacement.Char) { - defaultReplacement.Replacement = userReplacement.Replacement; + alternativeReplacement.Replacement = priorityReplacement.Replacement; } } } - IEnumerable mergedCollections = - RequestHandlerSettings.DefaultCharCollection.Union( - requestHandlerSettings.UserDefinedCharCollection, new CharacterReplacementEqualityComparer()); - - return mergedCollections; + return priorityReplacementsList.Union( + alternativeReplacementsList, + new CharacterReplacementEqualityComparer()); } } } From fad0c948a15ed01fcca479b42c6b21a4b11be8ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Kottal?= Date: Tue, 21 Dec 2021 13:51:35 +0100 Subject: [PATCH 034/141] Makes the width of .login-overlay__logo dynamic --- src/Umbraco.Web.UI.Client/src/less/pages/login.less | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/pages/login.less b/src/Umbraco.Web.UI.Client/src/less/pages/login.less index cf49af526b..2763a879ea 100644 --- a/src/Umbraco.Web.UI.Client/src/less/pages/login.less +++ b/src/Umbraco.Web.UI.Client/src/less/pages/login.less @@ -30,11 +30,14 @@ position: absolute; top: 22px; left: 25px; - width: 30px; height: 30px; z-index: 1; } +.login-overlay__logo > img { + max-height:100%; +} + .login-overlay .umb-modalcolumn { background: none; border: none; From 025db57b039345d50e94a29acef782bcb0554489 Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Tue, 28 Dec 2021 14:50:19 +0100 Subject: [PATCH 035/141] Fix issue with missing URLs shown in redirect management --- src/Umbraco.Core/Routing/DefaultUrlProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Routing/DefaultUrlProvider.cs b/src/Umbraco.Core/Routing/DefaultUrlProvider.cs index ef7a29afaf..ae2c3d7f3a 100644 --- a/src/Umbraco.Core/Routing/DefaultUrlProvider.cs +++ b/src/Umbraco.Core/Routing/DefaultUrlProvider.cs @@ -154,7 +154,7 @@ namespace Umbraco.Cms.Core.Routing : DomainUtilities.DomainForNode(umbracoContext.PublishedSnapshot.Domains, _siteDomainMapper, int.Parse(route.Substring(0, pos), CultureInfo.InvariantCulture), current, culture); var defaultCulture = _localizationService.GetDefaultLanguageIsoCode(); - if (domainUri is not null || culture == defaultCulture || culture is null) + if (domainUri is not null || culture == defaultCulture || string.IsNullOrEmpty(culture)) { var url = AssembleUrl(domainUri, path, current, mode).ToString(); return UrlInfo.Url(url, culture); From be8e7b285bb978158e16ce746002b99c6a137379 Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Tue, 28 Dec 2021 14:22:03 +0100 Subject: [PATCH 036/141] Fix incorrect styling of actions in tags editor --- .../src/less/components/umb-tags-editor.less | 36 ++++++++++--------- .../components/tags/umb-tags-editor.html | 1 + 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-tags-editor.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-tags-editor.less index 213807e685..2d41cbe6f2 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-tags-editor.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-tags-editor.less @@ -1,4 +1,4 @@ -.umb-tags-editor { +.umb-tags-editor { border: @inputBorder solid 1px; padding: 5px; min-height: 54px; @@ -14,24 +14,26 @@ position: relative; user-select: all; + > .btn-icon { + color: @white; + padding: 0; + position: relative; + cursor: pointer; + padding-left: 2px; + font-size: 15px; + right: -5px; + bottom: -1px; + user-select: none; + } + .umb_confirm-action { - > .btn-icon { - color: @white; - padding: 0; - position: relative; - cursor: pointer; - padding-left: 2px; - font-size: 15px; - right: -5px; - bottom: -1px; - user-select: none; - } - - .umb_confirm-action__overlay.-left { - top: 8px; - left: auto; - right: 15px; + &__overlay { + &.-left { + top: 8px; + left: auto; + right: 15px; + } } } } diff --git a/src/Umbraco.Web.UI.Client/src/views/components/tags/umb-tags-editor.html b/src/Umbraco.Web.UI.Client/src/views/components/tags/umb-tags-editor.html index e8f77d09a5..7277ff63c2 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/tags/umb-tags-editor.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/tags/umb-tags-editor.html @@ -13,6 +13,7 @@ From 84fea8f9537c37c8ed5ee7601465e4ee5ea1df0f Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Mon, 3 Jan 2022 15:07:44 +0100 Subject: [PATCH 037/141] Fix assignDomain to handle case sensitive operating systems (#11784) --- src/Umbraco.Core/Actions/ActionAssignDomain.cs | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/cs.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/cy.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/da.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/de.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/es.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/fr.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/he.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/it.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/ja.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/ko.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/nb.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/nl.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/pl.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/pt.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/ru.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/sv.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/tr.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/zh.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/zh_tw.xml | 2 +- 22 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/Umbraco.Core/Actions/ActionAssignDomain.cs b/src/Umbraco.Core/Actions/ActionAssignDomain.cs index e03e2de81c..6340a03082 100644 --- a/src/Umbraco.Core/Actions/ActionAssignDomain.cs +++ b/src/Umbraco.Core/Actions/ActionAssignDomain.cs @@ -8,7 +8,7 @@ public const char ActionLetter = 'I'; public char Letter => ActionLetter; - public string Alias => "assignDomain"; + public string Alias => "assigndomain"; public string Category => Constants.Conventions.PermissionCategories.AdministrationCategory; public string Icon => "home"; public bool ShowInNotifier => false; diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/cs.xml b/src/Umbraco.Web.UI/umbraco/config/lang/cs.xml index af701cd5e3..a90aa33355 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/cs.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/cs.xml @@ -60,7 +60,7 @@ Ostatní - Povolit přístup k přiřazování kultury a názvů hostitelů + Povolit přístup k přiřazování kultury a názvů hostitelů Povolit přístup k zobrazení protokolu historie uzlu Povolit přístup k zobrazení uzlu Povolit přístup ke změně typu dokumentu daného uzlu diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/cy.xml b/src/Umbraco.Web.UI/umbraco/config/lang/cy.xml index 0692d01e7a..60ff3ffdb1 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/cy.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/cy.xml @@ -5,7 +5,7 @@ https://www.method4.co.uk/ - Diwylliannau ac Enwau Gwesteia + Diwylliannau ac Enwau Gwesteia Trywydd Archwilio Dewis Nod Newid Math o Ddogfen diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml index 27901846a9..1f6dcff88f 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml @@ -5,7 +5,7 @@ https://our.umbraco.com/documentation/Extending-Umbraco/Language-Files - Tilføj domæne + Tilføj domæne Revisionsspor Gennemse elementer Skift Dokument Type diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/de.xml b/src/Umbraco.Web.UI/umbraco/config/lang/de.xml index 8f2ba350d0..0cabf29497 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/de.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/de.xml @@ -5,7 +5,7 @@ https://our.umbraco.com/documentation/Extending-Umbraco/Language-Files - Kulturen und Hostnamen + Kulturen und Hostnamen Protokoll Durchsuchen Dokumenttyp ändern diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index 4411209cd5..47c104b822 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -5,7 +5,7 @@ https://our.umbraco.com/documentation/Extending-Umbraco/Language-Files - Culture and Hostnames + Culture and Hostnames Audit Trail Browse Node Change Document Type 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 5a17eafbb2..e4d2784c8c 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -5,7 +5,7 @@ https://our.umbraco.com/documentation/Extending-Umbraco/Language-Files - Culture and Hostnames + Culture and Hostnames Audit Trail Browse Node Change Document Type diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/es.xml b/src/Umbraco.Web.UI/umbraco/config/lang/es.xml index df78683aca..d99548f5c5 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/es.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/es.xml @@ -5,7 +5,7 @@ https://our.umbraco.com/documentation/Extending-Umbraco/Language-Files - Administrar dominios + Administrar dominios Historial Nodo de Exploración Cambiar tipo de documento diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/fr.xml b/src/Umbraco.Web.UI/umbraco/config/lang/fr.xml index 4681818c47..68dc28c99b 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/fr.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/fr.xml @@ -5,7 +5,7 @@ https://our.umbraco.com/documentation/Extending-Umbraco/Language-Files - Culture et noms d'hôte + Culture et noms d'hôte Informations d'audit Parcourir Changer le type de document diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/he.xml b/src/Umbraco.Web.UI/umbraco/config/lang/he.xml index 9ee8bbf014..b99890282e 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/he.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/he.xml @@ -5,7 +5,7 @@ https://our.umbraco.com/documentation/Extending-Umbraco/Language-Files - נהל שמות מתחם + נהל שמות מתחם מעקב ביקורות צפה בתוכן העתק diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/it.xml b/src/Umbraco.Web.UI/umbraco/config/lang/it.xml index a0d89bff2d..7e20c0b266 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/it.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/it.xml @@ -5,7 +5,7 @@ https://our.umbraco.com/documentation/Extending-Umbraco/Language-Files - Gestisci hostnames + Gestisci hostnames Audit Trail Sfoglia Cambia tipo di documento diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/ja.xml b/src/Umbraco.Web.UI/umbraco/config/lang/ja.xml index 4b98adad26..142f7d055b 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/ja.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/ja.xml @@ -5,7 +5,7 @@ https://our.umbraco.com/documentation/Extending-Umbraco/Language-Files - ドメインの割り当て + ドメインの割り当て 動作記録 ノードの参照 ドキュメントタイプの変更 diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/ko.xml b/src/Umbraco.Web.UI/umbraco/config/lang/ko.xml index 792dd6700c..c340f3f30a 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/ko.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/ko.xml @@ -5,7 +5,7 @@ https://our.umbraco.com/documentation/Extending-Umbraco/Language-Files - 호스트명 관리 + 호스트명 관리 감사 추적 노드 탐색 복사 diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/nb.xml b/src/Umbraco.Web.UI/umbraco/config/lang/nb.xml index 1c47969189..44fad5d2ec 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/nb.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/nb.xml @@ -5,7 +5,7 @@ https://our.umbraco.com/documentation/Extending-Umbraco/Language-Files - Angi domene + Angi domene Revisjoner Bla gjennom Skift dokumenttype diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml b/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml index f830c3368d..0c24b119c9 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml @@ -5,7 +5,7 @@ https://our.umbraco.com/documentation/Extending-Umbraco/Language-Files - Beheer domeinnamen + Beheer domeinnamen Documentgeschiedenis Node bekijken Documenttype wijzigen diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/pl.xml b/src/Umbraco.Web.UI/umbraco/config/lang/pl.xml index dfbc324df6..712b695f77 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/pl.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/pl.xml @@ -5,7 +5,7 @@ https://our.umbraco.com/documentation/Extending-Umbraco/Language-Files - Zarządzanie hostami + Zarządzanie hostami Historia zmian Przeglądaj węzeł Zmień typ dokumentu diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/pt.xml b/src/Umbraco.Web.UI/umbraco/config/lang/pt.xml index 542b03abc1..eac232e851 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/pt.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/pt.xml @@ -5,7 +5,7 @@ https://our.umbraco.com/documentation/Extending-Umbraco/Language-Files - Gerenciar hostnames + Gerenciar hostnames Caminho de Auditoria Navegar o Nó Copiar diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml b/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml index 9c1d9e12fb..5c18fbc682 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml @@ -5,7 +5,7 @@ https://our.umbraco.com/documentation/Extending-Umbraco/Language-Files - Языки и домены + Языки и домены История исправлений Просмотреть Изменить тип документа diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/sv.xml b/src/Umbraco.Web.UI/umbraco/config/lang/sv.xml index e0e2235ae9..dda58366d8 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/sv.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/sv.xml @@ -8,7 +8,7 @@ Innehåll - Hantera domännamn + Hantera domännamn Hantera versioner Surfa på sidan Ändra dokumenttyp diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/tr.xml b/src/Umbraco.Web.UI/umbraco/config/lang/tr.xml index 58c0f7f94b..66d097b1e9 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/tr.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/tr.xml @@ -5,7 +5,7 @@ https://our.umbraco.com/documentation/Extending-Umbraco/Language-Files - Kültür ve Ana Bilgisayar Adları + Kültür ve Ana Bilgisayar Adları Denetim Yolu Düğüme Göz At Belge Türünü Değiştir diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/zh.xml b/src/Umbraco.Web.UI/umbraco/config/lang/zh.xml index ba51488f9f..8c78154b62 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/zh.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/zh.xml @@ -5,7 +5,7 @@ https://our.umbraco.com/documentation/Extending-Umbraco/Language-Files - 管理主机名 + 管理主机名 跟踪审计 浏览节点 改变文档类型 diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/zh_tw.xml b/src/Umbraco.Web.UI/umbraco/config/lang/zh_tw.xml index 8d5cf16de2..992a7ba55b 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/zh_tw.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/zh_tw.xml @@ -5,7 +5,7 @@ https://our.umbraco.com/documentation/Extending-Umbraco/Language-Files - 管理主機名稱 + 管理主機名稱 跟蹤審計 流覽節點 改變文檔類型 From 421faf8d43237d93bfc9e2a77b64b902126998ae Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Mon, 3 Jan 2022 15:07:44 +0100 Subject: [PATCH 038/141] Fix assignDomain to handle case sensitive operating systems (#11784) --- src/Umbraco.Core/Actions/ActionAssignDomain.cs | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/cs.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/cy.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/da.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/de.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/es.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/fr.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/he.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/it.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/ja.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/ko.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/nb.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/nl.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/pl.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/pt.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/ru.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/sv.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/tr.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/zh.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/zh_tw.xml | 2 +- 22 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/Umbraco.Core/Actions/ActionAssignDomain.cs b/src/Umbraco.Core/Actions/ActionAssignDomain.cs index e03e2de81c..6340a03082 100644 --- a/src/Umbraco.Core/Actions/ActionAssignDomain.cs +++ b/src/Umbraco.Core/Actions/ActionAssignDomain.cs @@ -8,7 +8,7 @@ public const char ActionLetter = 'I'; public char Letter => ActionLetter; - public string Alias => "assignDomain"; + public string Alias => "assigndomain"; public string Category => Constants.Conventions.PermissionCategories.AdministrationCategory; public string Icon => "home"; public bool ShowInNotifier => false; diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/cs.xml b/src/Umbraco.Web.UI/umbraco/config/lang/cs.xml index af701cd5e3..a90aa33355 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/cs.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/cs.xml @@ -60,7 +60,7 @@ Ostatní - Povolit přístup k přiřazování kultury a názvů hostitelů + Povolit přístup k přiřazování kultury a názvů hostitelů Povolit přístup k zobrazení protokolu historie uzlu Povolit přístup k zobrazení uzlu Povolit přístup ke změně typu dokumentu daného uzlu diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/cy.xml b/src/Umbraco.Web.UI/umbraco/config/lang/cy.xml index 0692d01e7a..60ff3ffdb1 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/cy.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/cy.xml @@ -5,7 +5,7 @@ https://www.method4.co.uk/ - Diwylliannau ac Enwau Gwesteia + Diwylliannau ac Enwau Gwesteia Trywydd Archwilio Dewis Nod Newid Math o Ddogfen diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml index 27901846a9..1f6dcff88f 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml @@ -5,7 +5,7 @@ https://our.umbraco.com/documentation/Extending-Umbraco/Language-Files - Tilføj domæne + Tilføj domæne Revisionsspor Gennemse elementer Skift Dokument Type diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/de.xml b/src/Umbraco.Web.UI/umbraco/config/lang/de.xml index 8f2ba350d0..0cabf29497 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/de.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/de.xml @@ -5,7 +5,7 @@ https://our.umbraco.com/documentation/Extending-Umbraco/Language-Files - Kulturen und Hostnamen + Kulturen und Hostnamen Protokoll Durchsuchen Dokumenttyp ändern diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index 4411209cd5..47c104b822 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -5,7 +5,7 @@ https://our.umbraco.com/documentation/Extending-Umbraco/Language-Files - Culture and Hostnames + Culture and Hostnames Audit Trail Browse Node Change Document Type 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 5a17eafbb2..e4d2784c8c 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -5,7 +5,7 @@ https://our.umbraco.com/documentation/Extending-Umbraco/Language-Files - Culture and Hostnames + Culture and Hostnames Audit Trail Browse Node Change Document Type diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/es.xml b/src/Umbraco.Web.UI/umbraco/config/lang/es.xml index df78683aca..d99548f5c5 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/es.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/es.xml @@ -5,7 +5,7 @@ https://our.umbraco.com/documentation/Extending-Umbraco/Language-Files - Administrar dominios + Administrar dominios Historial Nodo de Exploración Cambiar tipo de documento diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/fr.xml b/src/Umbraco.Web.UI/umbraco/config/lang/fr.xml index 4681818c47..68dc28c99b 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/fr.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/fr.xml @@ -5,7 +5,7 @@ https://our.umbraco.com/documentation/Extending-Umbraco/Language-Files - Culture et noms d'hôte + Culture et noms d'hôte Informations d'audit Parcourir Changer le type de document diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/he.xml b/src/Umbraco.Web.UI/umbraco/config/lang/he.xml index 9ee8bbf014..b99890282e 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/he.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/he.xml @@ -5,7 +5,7 @@ https://our.umbraco.com/documentation/Extending-Umbraco/Language-Files - נהל שמות מתחם + נהל שמות מתחם מעקב ביקורות צפה בתוכן העתק diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/it.xml b/src/Umbraco.Web.UI/umbraco/config/lang/it.xml index a0d89bff2d..7e20c0b266 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/it.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/it.xml @@ -5,7 +5,7 @@ https://our.umbraco.com/documentation/Extending-Umbraco/Language-Files - Gestisci hostnames + Gestisci hostnames Audit Trail Sfoglia Cambia tipo di documento diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/ja.xml b/src/Umbraco.Web.UI/umbraco/config/lang/ja.xml index 4b98adad26..142f7d055b 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/ja.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/ja.xml @@ -5,7 +5,7 @@ https://our.umbraco.com/documentation/Extending-Umbraco/Language-Files - ドメインの割り当て + ドメインの割り当て 動作記録 ノードの参照 ドキュメントタイプの変更 diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/ko.xml b/src/Umbraco.Web.UI/umbraco/config/lang/ko.xml index 792dd6700c..c340f3f30a 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/ko.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/ko.xml @@ -5,7 +5,7 @@ https://our.umbraco.com/documentation/Extending-Umbraco/Language-Files - 호스트명 관리 + 호스트명 관리 감사 추적 노드 탐색 복사 diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/nb.xml b/src/Umbraco.Web.UI/umbraco/config/lang/nb.xml index 1c47969189..44fad5d2ec 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/nb.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/nb.xml @@ -5,7 +5,7 @@ https://our.umbraco.com/documentation/Extending-Umbraco/Language-Files - Angi domene + Angi domene Revisjoner Bla gjennom Skift dokumenttype diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml b/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml index f830c3368d..0c24b119c9 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml @@ -5,7 +5,7 @@ https://our.umbraco.com/documentation/Extending-Umbraco/Language-Files - Beheer domeinnamen + Beheer domeinnamen Documentgeschiedenis Node bekijken Documenttype wijzigen diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/pl.xml b/src/Umbraco.Web.UI/umbraco/config/lang/pl.xml index dfbc324df6..712b695f77 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/pl.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/pl.xml @@ -5,7 +5,7 @@ https://our.umbraco.com/documentation/Extending-Umbraco/Language-Files - Zarządzanie hostami + Zarządzanie hostami Historia zmian Przeglądaj węzeł Zmień typ dokumentu diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/pt.xml b/src/Umbraco.Web.UI/umbraco/config/lang/pt.xml index 542b03abc1..eac232e851 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/pt.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/pt.xml @@ -5,7 +5,7 @@ https://our.umbraco.com/documentation/Extending-Umbraco/Language-Files - Gerenciar hostnames + Gerenciar hostnames Caminho de Auditoria Navegar o Nó Copiar diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml b/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml index 9c1d9e12fb..5c18fbc682 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml @@ -5,7 +5,7 @@ https://our.umbraco.com/documentation/Extending-Umbraco/Language-Files - Языки и домены + Языки и домены История исправлений Просмотреть Изменить тип документа diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/sv.xml b/src/Umbraco.Web.UI/umbraco/config/lang/sv.xml index e0e2235ae9..dda58366d8 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/sv.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/sv.xml @@ -8,7 +8,7 @@ Innehåll - Hantera domännamn + Hantera domännamn Hantera versioner Surfa på sidan Ändra dokumenttyp diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/tr.xml b/src/Umbraco.Web.UI/umbraco/config/lang/tr.xml index 58c0f7f94b..66d097b1e9 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/tr.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/tr.xml @@ -5,7 +5,7 @@ https://our.umbraco.com/documentation/Extending-Umbraco/Language-Files - Kültür ve Ana Bilgisayar Adları + Kültür ve Ana Bilgisayar Adları Denetim Yolu Düğüme Göz At Belge Türünü Değiştir diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/zh.xml b/src/Umbraco.Web.UI/umbraco/config/lang/zh.xml index ba51488f9f..8c78154b62 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/zh.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/zh.xml @@ -5,7 +5,7 @@ https://our.umbraco.com/documentation/Extending-Umbraco/Language-Files - 管理主机名 + 管理主机名 跟踪审计 浏览节点 改变文档类型 diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/zh_tw.xml b/src/Umbraco.Web.UI/umbraco/config/lang/zh_tw.xml index 8d5cf16de2..992a7ba55b 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/zh_tw.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/zh_tw.xml @@ -5,7 +5,7 @@ https://our.umbraco.com/documentation/Extending-Umbraco/Language-Files - 管理主機名稱 + 管理主機名稱 跟蹤審計 流覽節點 改變文檔類型 From bbfa975096adf2f10bfdc7c661f316f934dfc602 Mon Sep 17 00:00:00 2001 From: Mole Date: Mon, 3 Jan 2022 15:36:52 +0100 Subject: [PATCH 039/141] Bump versions to non-rc --- build/templates/UmbracoPackage/.template.config/template.json | 2 +- build/templates/UmbracoProject/.template.config/template.json | 2 +- src/Directory.Build.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build/templates/UmbracoPackage/.template.config/template.json b/build/templates/UmbracoPackage/.template.config/template.json index b69b63dd1c..6a6f0fb9ab 100644 --- a/build/templates/UmbracoPackage/.template.config/template.json +++ b/build/templates/UmbracoPackage/.template.config/template.json @@ -24,7 +24,7 @@ "version": { "type": "parameter", "datatype": "string", - "defaultValue": "9.2.0-rc", + "defaultValue": "9.2.0", "description": "The version of Umbraco to load using NuGet", "replaces": "UMBRACO_VERSION_FROM_TEMPLATE" }, diff --git a/build/templates/UmbracoProject/.template.config/template.json b/build/templates/UmbracoProject/.template.config/template.json index 6ec0babd70..d3c3d1a79f 100644 --- a/build/templates/UmbracoProject/.template.config/template.json +++ b/build/templates/UmbracoProject/.template.config/template.json @@ -57,7 +57,7 @@ "version": { "type": "parameter", "datatype": "string", - "defaultValue": "9.2.0-rc", + "defaultValue": "9.2.0", "description": "The version of Umbraco to load using NuGet", "replaces": "UMBRACO_VERSION_FROM_TEMPLATE" }, diff --git a/src/Directory.Build.props b/src/Directory.Build.props index fed70283e7..8d923733fa 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -5,7 +5,7 @@ 9.2.0 9.2.0 - 9.2.0-rc + 9.2.0 9.2.0 9.0 en-US From 65c0d8fceca475d8a77ffb9289c4217b2bfeb3a6 Mon Sep 17 00:00:00 2001 From: Mole Date: Tue, 21 Dec 2021 12:48:35 +0100 Subject: [PATCH 040/141] V9: Use current request for emails (#11778) * Use request url for email * Fixed potential null ref exceptions Co-authored-by: Bjarke Berg --- .../Controllers/AuthenticationController.cs | 56 ++++++++++- .../Controllers/UsersController.cs | 92 ++++++++++++++++--- .../Extensions/HttpRequestExtensions.cs | 31 ++++++- 3 files changed, 160 insertions(+), 19 deletions(-) diff --git a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs index f159011d80..30ad3f75ae 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs @@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Cms.Core; @@ -29,6 +30,7 @@ using Umbraco.Cms.Web.Common.ActionsResults; using Umbraco.Cms.Web.Common.Attributes; using Umbraco.Cms.Web.Common.Authorization; using Umbraco.Cms.Web.Common.Controllers; +using Umbraco.Cms.Web.Common.DependencyInjection; using Umbraco.Cms.Web.Common.Filters; using Umbraco.Cms.Web.Common.Models; using Umbraco.Extensions; @@ -71,9 +73,11 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers private readonly LinkGenerator _linkGenerator; private readonly IBackOfficeExternalLoginProviders _externalAuthenticationOptions; private readonly IBackOfficeTwoFactorOptions _backOfficeTwoFactorOptions; + private readonly IHttpContextAccessor _httpContextAccessor; + private readonly WebRoutingSettings _webRoutingSettings; // TODO: We need to review all _userManager.Raise calls since many/most should be on the usermanager or signinmanager, very few should be here - + [ActivatorUtilitiesConstructor] public AuthenticationController( IBackOfficeSecurityAccessor backofficeSecurityAccessor, IBackOfficeUserManager backOfficeUserManager, @@ -91,7 +95,9 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers IHostingEnvironment hostingEnvironment, LinkGenerator linkGenerator, IBackOfficeExternalLoginProviders externalAuthenticationOptions, - IBackOfficeTwoFactorOptions backOfficeTwoFactorOptions) + IBackOfficeTwoFactorOptions backOfficeTwoFactorOptions, + IHttpContextAccessor httpContextAccessor, + IOptions webRoutingSettings) { _backofficeSecurityAccessor = backofficeSecurityAccessor; _userManager = backOfficeUserManager; @@ -110,6 +116,50 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers _linkGenerator = linkGenerator; _externalAuthenticationOptions = externalAuthenticationOptions; _backOfficeTwoFactorOptions = backOfficeTwoFactorOptions; + _httpContextAccessor = httpContextAccessor; + _webRoutingSettings = webRoutingSettings.Value; + } + + [Obsolete("Use constructor that also takes IHttpAccessor and IOptions, scheduled for removal in V11")] + public AuthenticationController( + IBackOfficeSecurityAccessor backofficeSecurityAccessor, + IBackOfficeUserManager backOfficeUserManager, + IBackOfficeSignInManager signInManager, + IUserService userService, + ILocalizedTextService textService, + IUmbracoMapper umbracoMapper, + IOptions globalSettings, + IOptions securitySettings, + ILogger logger, + IIpResolver ipResolver, + IOptions passwordConfiguration, + IEmailSender emailSender, + ISmsSender smsSender, + IHostingEnvironment hostingEnvironment, + LinkGenerator linkGenerator, + IBackOfficeExternalLoginProviders externalAuthenticationOptions, + IBackOfficeTwoFactorOptions backOfficeTwoFactorOptions) + : this( + backofficeSecurityAccessor, + backOfficeUserManager, + signInManager, + userService, + textService, + umbracoMapper, + globalSettings, + securitySettings, + logger, + ipResolver, + passwordConfiguration, + emailSender, + smsSender, + hostingEnvironment, + linkGenerator, + externalAuthenticationOptions, + backOfficeTwoFactorOptions, + StaticServiceProvider.Instance.GetRequiredService(), + StaticServiceProvider.Instance.GetRequiredService>()) + { } /// @@ -629,7 +679,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers }); // Construct full URL using configured application URL (which will fall back to request) - var applicationUri = _hostingEnvironment.ApplicationMainUrl; + Uri applicationUri = _httpContextAccessor.GetRequiredHttpContext().Request.GetApplicationUri(_webRoutingSettings); var callbackUri = new Uri(applicationUri, action); return callbackUri.ToString(); } diff --git a/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs b/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs index 79e7838110..72377c0670 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs @@ -13,6 +13,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using MimeKit; @@ -42,6 +43,7 @@ using Umbraco.Cms.Web.BackOffice.Security; using Umbraco.Cms.Web.Common.ActionsResults; using Umbraco.Cms.Web.Common.Attributes; using Umbraco.Cms.Web.Common.Authorization; +using Umbraco.Cms.Web.Common.DependencyInjection; using Umbraco.Cms.Web.Common.Security; using Umbraco.Extensions; using Constants = Umbraco.Cms.Core.Constants; @@ -75,7 +77,10 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers private readonly UserEditorAuthorizationHelper _userEditorAuthorizationHelper; private readonly IPasswordChanger _passwordChanger; private readonly ILogger _logger; + private readonly IHttpContextAccessor _httpContextAccessor; + private readonly WebRoutingSettings _webRoutingSettings; + [ActivatorUtilitiesConstructor] public UsersController( MediaFileManager mediaFileManager, IOptions contentSettings, @@ -96,7 +101,9 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers LinkGenerator linkGenerator, IBackOfficeExternalLoginProviders externalLogins, UserEditorAuthorizationHelper userEditorAuthorizationHelper, - IPasswordChanger passwordChanger) + IPasswordChanger passwordChanger, + IHttpContextAccessor httpContextAccessor, + IOptions webRoutingSettings) { _mediaFileManager = mediaFileManager; _contentSettings = contentSettings.Value; @@ -119,6 +126,55 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers _userEditorAuthorizationHelper = userEditorAuthorizationHelper; _passwordChanger = passwordChanger; _logger = _loggerFactory.CreateLogger(); + _httpContextAccessor = httpContextAccessor; + _webRoutingSettings = webRoutingSettings.Value; + } + + [Obsolete("Use constructor that also takes IHttpAccessor and IOptions, scheduled for removal in V11")] + public UsersController( + MediaFileManager mediaFileManager, + IOptions contentSettings, + IHostingEnvironment hostingEnvironment, + ISqlContext sqlContext, + IImageUrlGenerator imageUrlGenerator, + IOptions securitySettings, + IEmailSender emailSender, + IBackOfficeSecurityAccessor backofficeSecurityAccessor, + AppCaches appCaches, + IShortStringHelper shortStringHelper, + IUserService userService, + ILocalizedTextService localizedTextService, + IUmbracoMapper umbracoMapper, + IOptions globalSettings, + IBackOfficeUserManager backOfficeUserManager, + ILoggerFactory loggerFactory, + LinkGenerator linkGenerator, + IBackOfficeExternalLoginProviders externalLogins, + UserEditorAuthorizationHelper userEditorAuthorizationHelper, + IPasswordChanger passwordChanger) + : this(mediaFileManager, + contentSettings, + hostingEnvironment, + sqlContext, + imageUrlGenerator, + securitySettings, + emailSender, + backofficeSecurityAccessor, + appCaches, + shortStringHelper, + userService, + localizedTextService, + umbracoMapper, + globalSettings, + backOfficeUserManager, + loggerFactory, + linkGenerator, + externalLogins, + userEditorAuthorizationHelper, + passwordChanger, + StaticServiceProvider.Instance.GetRequiredService(), + StaticServiceProvider.Instance.GetRequiredService>()) + { } /// @@ -421,20 +477,25 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers /// public async Task> PostInviteUser(UserInvite userSave) { - if (userSave == null) throw new ArgumentNullException("userSave"); + if (userSave == null) + { + throw new ArgumentNullException("userSave"); + } if (userSave.Message.IsNullOrWhiteSpace()) + { ModelState.AddModelError("Message", "Message cannot be empty"); + } IUser user; if (_securitySettings.UsernameIsEmail) { - //ensure it's the same + // ensure it's the same userSave.Username = userSave.Email; } else { - //first validate the username if we're showing it + // first validate the username if we're showing it var userResult = CheckUniqueUsername(userSave.Username, u => u.LastLoginDate != default || u.EmailConfirmedDate.HasValue); if (!(userResult.Result is null)) { @@ -443,6 +504,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers user = userResult.Value; } + user = CheckUniqueEmail(userSave.Email, u => u.LastLoginDate != default || u.EmailConfirmedDate.HasValue); if (ModelState.IsValid == false) @@ -455,7 +517,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers return ValidationProblem("No Email server is configured"); } - //Perform authorization here to see if the current user can actually save this user with the info being requested + // Perform authorization here to see if the current user can actually save this user with the info being requested var canSaveUser = _userEditorAuthorizationHelper.IsAuthorized(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser, user, null, null, userSave.UserGroups); if (canSaveUser == false) { @@ -464,8 +526,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers if (user == null) { - //we want to create the user with the UserManager, this ensures the 'empty' (special) password - //format is applied without us having to duplicate that logic + // we want to create the user with the UserManager, this ensures the 'empty' (special) password + // format is applied without us having to duplicate that logic var identityUser = BackOfficeIdentityUser.CreateNew(_globalSettings, userSave.Username, userSave.Email, _globalSettings.DefaultUILanguage); identityUser.Name = userSave.Name; @@ -475,21 +537,21 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers return ValidationProblem(created.Errors.ToErrorMessage()); } - //now re-look the user back up + // now re-look the user back up user = _userService.GetByEmail(userSave.Email); } - //map the save info over onto the user + // map the save info over onto the user user = _umbracoMapper.Map(userSave, user); - //ensure the invited date is set + // ensure the invited date is set user.InvitedDate = DateTime.Now; - //Save the updated user (which will process the user groups too) + // Save the updated user (which will process the user groups too) _userService.Save(user); var display = _umbracoMapper.Map(user); - //send the email + // send the email await SendUserInviteEmailAsync(display, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Name, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Email, user, userSave.Message); display.AddSuccessNotification(_localizedTextService.Localize("speechBubbles","resendInviteHeader"), _localizedTextService.Localize("speechBubbles","resendInviteSuccess", new[] { user.Name })); @@ -544,14 +606,14 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers }); // Construct full URL using configured application URL (which will fall back to request) - var applicationUri = _hostingEnvironment.ApplicationMainUrl; + Uri applicationUri = _httpContextAccessor.GetRequiredHttpContext().Request.GetApplicationUri(_webRoutingSettings); var inviteUri = new Uri(applicationUri, action); var emailSubject = _localizedTextService.Localize("user","inviteEmailCopySubject", - //Ensure the culture of the found user is used for the email! + // Ensure the culture of the found user is used for the email! UmbracoUserExtensions.GetUserCulture(to.Language, _localizedTextService, _globalSettings)); var emailBody = _localizedTextService.Localize("user","inviteEmailCopyFormat", - //Ensure the culture of the found user is used for the email! + // Ensure the culture of the found user is used for the email! UmbracoUserExtensions.GetUserCulture(to.Language, _localizedTextService, _globalSettings), new[] { userDisplay.Name, from, message, inviteUri.ToString(), senderEmail }); diff --git a/src/Umbraco.Web.Common/Extensions/HttpRequestExtensions.cs b/src/Umbraco.Web.Common/Extensions/HttpRequestExtensions.cs index c7c2bb3115..2aeb2555eb 100644 --- a/src/Umbraco.Web.Common/Extensions/HttpRequestExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/HttpRequestExtensions.cs @@ -1,9 +1,12 @@ -using System.IO; +using System; +using System.IO; using System.Net; using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Extensions; using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Routing; namespace Umbraco.Extensions @@ -107,5 +110,31 @@ namespace Umbraco.Extensions return result; } } + + /// + /// Gets the application URI, will use the one specified in settings if present + /// + public static Uri GetApplicationUri(this HttpRequest request, WebRoutingSettings routingSettings) + { + if (request == null) + { + throw new ArgumentNullException(nameof(request)); + } + + if (routingSettings == null) + { + throw new ArgumentNullException(nameof(routingSettings)); + } + + if (string.IsNullOrEmpty(routingSettings.UmbracoApplicationUrl)) + { + var requestUri = new Uri(request.GetDisplayUrl()); + + // Create a new URI with the relative uri as /, this ensures that only the base path is returned. + return new Uri(requestUri, "/"); + } + + return new Uri(routingSettings.UmbracoApplicationUrl); + } } } From 763cb70e677ac0c85557b19b5df09eccfa1b9dfb Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Mon, 20 Dec 2021 08:45:11 +0100 Subject: [PATCH 041/141] Move member properties to Member Content App (V9 merge regression) (#11768) * Fix regression after merging to v9 * Update test to align with removed member properties --- .../Mapping/MemberTabsAndPropertiesMapper.cs | 34 +++++-------------- .../Controllers/MemberControllerUnitTests.cs | 14 -------- 2 files changed, 9 insertions(+), 39 deletions(-) diff --git a/src/Umbraco.Core/Models/Mapping/MemberTabsAndPropertiesMapper.cs b/src/Umbraco.Core/Models/Mapping/MemberTabsAndPropertiesMapper.cs index d61e32d88a..d8ac8d635d 100644 --- a/src/Umbraco.Core/Models/Mapping/MemberTabsAndPropertiesMapper.cs +++ b/src/Umbraco.Core/Models/Mapping/MemberTabsAndPropertiesMapper.cs @@ -65,14 +65,11 @@ namespace Umbraco.Cms.Core.Models.Mapping var resolved = base.Map(source, context); - // This is kind of a hack because a developer is supposed to be allowed to set their property editor - would have been much easier - // if we just had all of the membership provider fields on the member table :( - // TODO: But is there a way to map the IMember.IsLockedOut to the property ? i dunno. + // IMember.IsLockedOut can't be set to true, so make it readonly when that's the case (you can only unlock) var isLockedOutProperty = resolved.SelectMany(x => x.Properties).FirstOrDefault(x => x.Alias == Constants.Conventions.Member.IsLockedOut); if (isLockedOutProperty?.Value != null && isLockedOutProperty.Value.ToString() != "1") { - isLockedOutProperty.View = "readonlyvalue"; - isLockedOutProperty.Value = _localizedTextService.Localize("general", "no"); + isLockedOutProperty.Readonly = true; } return resolved; @@ -191,20 +188,6 @@ namespace Umbraco.Cms.Core.Models.Mapping { var properties = new List { - new ContentPropertyDisplay - { - Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}id", - Label = _localizedTextService.Localize("general","id"), - Value = new List {member.Id.ToString(), member.Key.ToString()}, - View = "idwithguid" - }, - new ContentPropertyDisplay - { - Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}doctype", - Label = _localizedTextService.Localize("content","membertype"), - Value = _localizedTextService.UmbracoDictionaryTranslate(CultureDictionary, member.ContentType.Name), - View = _propertyEditorCollection[Constants.PropertyEditors.Aliases.Label].GetValueEditor().View - }, GetLoginProperty(member, _localizedTextService), new ContentPropertyDisplay { @@ -212,7 +195,7 @@ namespace Umbraco.Cms.Core.Models.Mapping Label = _localizedTextService.Localize("general","email"), Value = member.Email, View = "email", - Validation = {Mandatory = true} + Validation = { Mandatory = true } }, new ContentPropertyDisplay { @@ -221,12 +204,10 @@ namespace Umbraco.Cms.Core.Models.Mapping Value = new Dictionary { // TODO: why ignoreCase, what are we doing here?! - {"newPassword", member.GetAdditionalDataValueIgnoreCase("NewPassword", null)}, + { "newPassword", member.GetAdditionalDataValueIgnoreCase("NewPassword", null) } }, - // TODO: Hard coding this because the changepassword doesn't necessarily need to be a resolvable (real) property editor View = "changepassword", - // Initialize the dictionary with the configuration from the default membership provider - Config = GetPasswordConfig(member) + Config = GetPasswordConfig(member) // Initialize the dictionary with the configuration from the default membership provider }, new ContentPropertyDisplay { @@ -234,7 +215,10 @@ namespace Umbraco.Cms.Core.Models.Mapping Label = _localizedTextService.Localize("content","membergroup"), Value = GetMemberGroupValue(member.Username), View = "membergroups", - Config = new Dictionary {{"IsRequired", true}} + Config = new Dictionary + { + { "IsRequired", true } + } } }; diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/MemberControllerUnitTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/MemberControllerUnitTests.cs index da5175f272..069e94f732 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/MemberControllerUnitTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/MemberControllerUnitTests.cs @@ -618,20 +618,6 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers Id = 77, Properties = new List() { - new ContentPropertyDisplay() - { - Alias = "_umb_id", - View = "idwithguid", - Value = new [] - { - "123", - "guid" - } - }, - new ContentPropertyDisplay() - { - Alias = "_umb_doctype" - }, new ContentPropertyDisplay() { Alias = "_umb_login" From a54c5bb21d4b9af4d984e1c7d0d45a20693ff3d4 Mon Sep 17 00:00:00 2001 From: Mole Date: Tue, 4 Jan 2022 10:11:34 +0100 Subject: [PATCH 042/141] V8: Merge package telemetry from V9 (#11785) * Merge Telemetry classes from V9 * Use TelemetryService in ReportSiteTask * Migrate tests --- .../CompositionExtensions/Services.cs | 3 + src/Umbraco.Core/Manifest/ManifestParser.cs | 2 +- src/Umbraco.Core/Manifest/PackageManifest.cs | 35 ++++++++ .../Telemetry/ITelemetryService.cs | 15 ++++ .../Telemetry/Models/PackageTelemetry.cs | 28 +++++++ .../Telemetry/Models/TelemetryReportData.cs | 34 ++++++++ .../Telemetry/TelemetryService.cs | 81 +++++++++++++++++++ src/Umbraco.Core/Umbraco.Core.csproj | 4 + .../Manifest/ManifestParserTests.cs | 22 +++++ src/Umbraco.Web/Telemetry/ReportSiteTask.cs | 34 +++----- .../Telemetry/TelemetryComponent.cs | 9 ++- 11 files changed, 239 insertions(+), 28 deletions(-) create mode 100644 src/Umbraco.Core/Telemetry/ITelemetryService.cs create mode 100644 src/Umbraco.Core/Telemetry/Models/PackageTelemetry.cs create mode 100644 src/Umbraco.Core/Telemetry/Models/TelemetryReportData.cs create mode 100644 src/Umbraco.Core/Telemetry/TelemetryService.cs diff --git a/src/Umbraco.Core/Composing/CompositionExtensions/Services.cs b/src/Umbraco.Core/Composing/CompositionExtensions/Services.cs index 4f9a953212..e912f7281c 100644 --- a/src/Umbraco.Core/Composing/CompositionExtensions/Services.cs +++ b/src/Umbraco.Core/Composing/CompositionExtensions/Services.cs @@ -8,6 +8,7 @@ using Umbraco.Core.Logging; using Umbraco.Core.Packaging; using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; +using Umbraco.Core.Telemetry; namespace Umbraco.Core.Composing.CompositionExtensions { @@ -79,6 +80,8 @@ namespace Umbraco.Core.Composing.CompositionExtensions factory.GetInstance(), factory.GetInstance(), new DirectoryInfo(IOHelper.GetRootDirectorySafe()))); + composition.RegisterUnique(); + return composition; } diff --git a/src/Umbraco.Core/Manifest/ManifestParser.cs b/src/Umbraco.Core/Manifest/ManifestParser.cs index 9bbb0875d8..a9ce06e8da 100644 --- a/src/Umbraco.Core/Manifest/ManifestParser.cs +++ b/src/Umbraco.Core/Manifest/ManifestParser.cs @@ -67,7 +67,7 @@ namespace Umbraco.Core.Manifest /// /// Gets all manifests. /// - private IEnumerable GetManifests() + internal IEnumerable GetManifests() { var manifests = new List(); diff --git a/src/Umbraco.Core/Manifest/PackageManifest.cs b/src/Umbraco.Core/Manifest/PackageManifest.cs index e50eb69467..cadd661e28 100644 --- a/src/Umbraco.Core/Manifest/PackageManifest.cs +++ b/src/Umbraco.Core/Manifest/PackageManifest.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using Newtonsoft.Json; using Umbraco.Core.PropertyEditors; @@ -9,6 +10,28 @@ namespace Umbraco.Core.Manifest /// public class PackageManifest { + private string _packageName; + + [JsonProperty("name")] + public string PackageName + { + get + { + if (string.IsNullOrWhiteSpace(_packageName) is false) + { + return _packageName; + } + + if (string.IsNullOrWhiteSpace(Source) is false) + { + _packageName = Path.GetFileName(Path.GetDirectoryName(Source)); + } + + return _packageName; + } + set => _packageName = value; + } + /// /// Gets the source path of the manifest. /// @@ -66,5 +89,17 @@ namespace Umbraco.Core.Manifest /// [JsonProperty("sections")] public ManifestSection[] Sections { get; set; } = Array.Empty(); + + /// + /// Gets or sets the version of the package + /// + [JsonProperty("version")] + public string Version { get; set; } = string.Empty; + + /// + /// Gets or sets a value indicating whether telemetry is allowed + /// + [JsonProperty("allowPackageTelemetry")] + public bool AllowPackageTelemetry { get; set; } = true; } } diff --git a/src/Umbraco.Core/Telemetry/ITelemetryService.cs b/src/Umbraco.Core/Telemetry/ITelemetryService.cs new file mode 100644 index 0000000000..f4ca3736f6 --- /dev/null +++ b/src/Umbraco.Core/Telemetry/ITelemetryService.cs @@ -0,0 +1,15 @@ +using Umbraco.Core.Telemetry.Models; + +namespace Umbraco.Core.Telemetry +{ + /// + /// Service which gathers the data for telemetry reporting + /// + public interface ITelemetryService + { + /// + /// Try and get the + /// + bool TryGetTelemetryReportData(out TelemetryReportData telemetryReportData); + } +} diff --git a/src/Umbraco.Core/Telemetry/Models/PackageTelemetry.cs b/src/Umbraco.Core/Telemetry/Models/PackageTelemetry.cs new file mode 100644 index 0000000000..a86c4c4fa2 --- /dev/null +++ b/src/Umbraco.Core/Telemetry/Models/PackageTelemetry.cs @@ -0,0 +1,28 @@ +using System; +using System.Runtime.Serialization; + +namespace Umbraco.Core.Telemetry.Models +{ + /// + /// Serializable class containing information about an installed package. + /// + [Serializable] + [DataContract(Name = "packageTelemetry")] + public class PackageTelemetry + { + /// + /// Gets or sets the name of the installed package. + /// + [DataMember(Name = "name")] + public string Name { get; set; } + + /// + /// Gets or sets the version of the installed package. + /// + /// + /// This may be an empty string if no version is specified, or if package telemetry has been restricted. + /// + [DataMember(Name = "version")] + public string Version { get; set; } + } +} diff --git a/src/Umbraco.Core/Telemetry/Models/TelemetryReportData.cs b/src/Umbraco.Core/Telemetry/Models/TelemetryReportData.cs new file mode 100644 index 0000000000..560bd1dcfe --- /dev/null +++ b/src/Umbraco.Core/Telemetry/Models/TelemetryReportData.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace Umbraco.Core.Telemetry.Models +{ + /// + /// Serializable class containing telemetry information. + /// + [DataContract] + public class TelemetryReportData + { + /// + /// Gets or sets a random GUID to prevent an instance posting multiple times pr. day. + /// + [DataMember(Name = "id")] + public Guid Id { get; set; } + + /// + /// Gets or sets the Umbraco CMS version. + /// + [DataMember(Name = "version")] + public string Version { get; set; } + + /// + /// Gets or sets an enumerable containing information about packages. + /// + /// + /// Contains only the name and version of the packages, unless no version is specified. + /// + [DataMember(Name = "packages")] + public IEnumerable Packages { get; set; } + } +} diff --git a/src/Umbraco.Core/Telemetry/TelemetryService.cs b/src/Umbraco.Core/Telemetry/TelemetryService.cs new file mode 100644 index 0000000000..a1b1f39ecd --- /dev/null +++ b/src/Umbraco.Core/Telemetry/TelemetryService.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using Umbraco.Core.Configuration; +using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.Manifest; +using Umbraco.Core.Telemetry.Models; + +namespace Umbraco.Core.Telemetry +{ + /// + internal class TelemetryService : ITelemetryService + { + private readonly IUmbracoSettingsSection _settings; + private readonly ManifestParser _manifestParser; + + /// + /// Initializes a new instance of the class. + /// + public TelemetryService( + ManifestParser manifestParser, + IUmbracoSettingsSection settings) + { + _manifestParser = manifestParser; + _settings = settings; + } + + /// + public bool TryGetTelemetryReportData(out TelemetryReportData telemetryReportData) + { + if (TryGetTelemetryId(out Guid telemetryId) is false) + { + telemetryReportData = null; + return false; + } + + telemetryReportData = new TelemetryReportData + { + Id = telemetryId, + Version = UmbracoVersion.SemanticVersion.ToSemanticString(), + Packages = GetPackageTelemetry() + }; + return true; + } + + private bool TryGetTelemetryId(out Guid telemetryId) + { + // Parse telemetry string as a GUID & verify its a GUID and not some random string + // since users may have messed with or decided to empty the app setting or put in something random + if (Guid.TryParse(_settings.BackOffice.Id, out var parsedTelemetryId) is false) + { + telemetryId = Guid.Empty; + return false; + } + + telemetryId = parsedTelemetryId; + return true; + } + + private IEnumerable GetPackageTelemetry() + { + List packages = new (); + var manifests = _manifestParser.GetManifests(); + + foreach (var manifest in manifests) + { + if (manifest.AllowPackageTelemetry is false) + { + continue; + } + + packages.Add(new PackageTelemetry + { + Name = manifest.PackageName, + Version = manifest.Version ?? string.Empty + }); + } + + return packages; + } + } +} diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 632031a2e6..6729930174 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -396,6 +396,10 @@ + + + + diff --git a/src/Umbraco.Tests/Manifest/ManifestParserTests.cs b/src/Umbraco.Tests/Manifest/ManifestParserTests.cs index 6b140e3757..26c031c7aa 100644 --- a/src/Umbraco.Tests/Manifest/ManifestParserTests.cs +++ b/src/Umbraco.Tests/Manifest/ManifestParserTests.cs @@ -443,5 +443,27 @@ javascript: ['~/test.js',/*** some note about stuff asd09823-4**09234*/ '~/test2 Assert.AreEqual("Content", manifest.Sections[0].Name); Assert.AreEqual("World", manifest.Sections[1].Name); } + + [Test] + public void CanParseManifest_Version() + { + const string json = @"{""name"": ""VersionPackage"", ""version"": ""1.0.0""}"; + PackageManifest manifest = _parser.ParseManifest(json); + + Assert.Multiple(() => + { + Assert.AreEqual("VersionPackage", manifest.PackageName); + Assert.AreEqual("1.0.0", manifest.Version); + }); + } + + [Test] + public void CanParseManifest_TrackingAllowed() + { + const string json = @"{""allowPackageTelemetry"": false }"; + PackageManifest manifest = _parser.ParseManifest(json); + + Assert.IsFalse(manifest.AllowPackageTelemetry); + } } } diff --git a/src/Umbraco.Web/Telemetry/ReportSiteTask.cs b/src/Umbraco.Web/Telemetry/ReportSiteTask.cs index 24ac7cbf3a..78d4b24ab6 100644 --- a/src/Umbraco.Web/Telemetry/ReportSiteTask.cs +++ b/src/Umbraco.Web/Telemetry/ReportSiteTask.cs @@ -1,14 +1,11 @@ using Newtonsoft.Json; using System; using System.Net.Http; -using System.Runtime.Serialization; using System.Text; using System.Threading; using System.Threading.Tasks; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; +using Umbraco.Core.Telemetry; using Umbraco.Web.Scheduling; namespace Umbraco.Web.Telemetry @@ -17,14 +14,19 @@ namespace Umbraco.Web.Telemetry { private readonly IProfilingLogger _logger; private static HttpClient _httpClient; - private readonly IUmbracoSettingsSection _settings; + private readonly ITelemetryService _telemetryService; - public ReportSiteTask(IBackgroundTaskRunner runner, int delayBeforeWeStart, int howOftenWeRepeat, IProfilingLogger logger, IUmbracoSettingsSection settings) + public ReportSiteTask( + IBackgroundTaskRunner runner, + int delayBeforeWeStart, + int howOftenWeRepeat, + IProfilingLogger logger, + ITelemetryService telemetryService) : base(runner, delayBeforeWeStart, howOftenWeRepeat) { _logger = logger; _httpClient = new HttpClient(); - _settings = settings; + _telemetryService = telemetryService; } /// @@ -34,12 +36,9 @@ namespace Umbraco.Web.Telemetry /// A value indicating whether to repeat the task. public override async Task PerformRunAsync(CancellationToken token) { - // Try & get a value stored in umbracoSettings.config on the backoffice XML element ID attribute - var backofficeIdentifierRaw = _settings.BackOffice.Id; - // Parse as a GUID & verify its a GUID and not some random string // In case of users may have messed or decided to empty the file contents or put in something random - if (Guid.TryParse(backofficeIdentifierRaw, out var telemetrySiteIdentifier) == false) + if (_telemetryService.TryGetTelemetryReportData(out var telemetryReportData) is false) { // Some users may have decided to mess with the XML attribute and put in something else // Stop repeating this task (no need to keep checking) @@ -61,8 +60,7 @@ namespace Umbraco.Web.Telemetry using (var request = new HttpRequestMessage(HttpMethod.Post, "installs/")) { - var postData = new TelemetryReportData { Id = telemetrySiteIdentifier, Version = UmbracoVersion.SemanticVersion.ToSemanticString() }; - request.Content = new StringContent(JsonConvert.SerializeObject(postData), Encoding.UTF8, "application/json"); //CONTENT-TYPE header + request.Content = new StringContent(JsonConvert.SerializeObject(telemetryReportData), Encoding.UTF8, "application/json"); //CONTENT-TYPE header // Set a low timeout - no need to use a larger default timeout for this POST request _httpClient.Timeout = new TimeSpan(0, 0, 1); @@ -86,15 +84,5 @@ namespace Umbraco.Web.Telemetry } public override bool IsAsync => true; - - [DataContract] - private class TelemetryReportData - { - [DataMember(Name = "id")] - public Guid Id { get; set; } - - [DataMember(Name = "version")] - public string Version { get; set; } - } } } diff --git a/src/Umbraco.Web/Telemetry/TelemetryComponent.cs b/src/Umbraco.Web/Telemetry/TelemetryComponent.cs index 1ae9ad9764..c3d29f72ca 100644 --- a/src/Umbraco.Web/Telemetry/TelemetryComponent.cs +++ b/src/Umbraco.Web/Telemetry/TelemetryComponent.cs @@ -1,6 +1,7 @@ using Umbraco.Core.Composing; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; +using Umbraco.Core.Telemetry; using Umbraco.Web.Scheduling; namespace Umbraco.Web.Telemetry @@ -8,13 +9,13 @@ namespace Umbraco.Web.Telemetry public class TelemetryComponent : IComponent { private readonly IProfilingLogger _logger; - private readonly IUmbracoSettingsSection _settings; + private readonly ITelemetryService _telemetryService; private BackgroundTaskRunner _telemetryReporterRunner; - public TelemetryComponent(IProfilingLogger logger, IUmbracoSettingsSection settings) + public TelemetryComponent(IProfilingLogger logger, IUmbracoSettingsSection settings, ITelemetryService telemetryService) { _logger = logger; - _settings = settings; + _telemetryService = telemetryService; } public void Initialize() @@ -26,7 +27,7 @@ namespace Umbraco.Web.Telemetry const int howOftenWeRepeat = 60 * 1000 * 60 * 24; // 60 * 1000 * 60 * 24 = 24hrs (86400000) // As soon as we add our task to the runner it will start to run (after its delay period) - var task = new ReportSiteTask(_telemetryReporterRunner, delayBeforeWeStart, howOftenWeRepeat, _logger, _settings); + var task = new ReportSiteTask(_telemetryReporterRunner, delayBeforeWeStart, howOftenWeRepeat, _logger, _telemetryService); _telemetryReporterRunner.TryAdd(task); } From 75bb8051bff84e3cc2e9da2379d696f02d7a8845 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Wed, 5 Jan 2022 11:11:27 +0100 Subject: [PATCH 043/141] Prune Image Cropper and Media Picker (v3) values (#11805) * Clean up redundant/default Umbraco.ImageCropper data * Fix ToString() and add HasCrops() method * Re-use crop/focal point pruning for Umbraco.MediaPicker3 * Fix ImageCropperTest Co-authored-by: Elitsa Marinovska --- .../ValueConverters/ImageCropperValue.cs | 88 ++++++++++++++----- .../PropertyEditors/ImageCropperTest.cs | 6 +- .../ImageCropperPropertyValueEditor.cs | 39 +++++--- .../MediaPicker3PropertyEditor.cs | 81 ++++++++++++++++- 4 files changed, 174 insertions(+), 40 deletions(-) diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/ImageCropperValue.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/ImageCropperValue.cs index f2151778d9..555c198f7d 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/ImageCropperValue.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/ImageCropperValue.cs @@ -1,12 +1,11 @@ using System; using System.Collections.Generic; using System.ComponentModel; -using System.Globalization; using System.Linq; using System.Runtime.Serialization; -using System.Text; using System.Web; using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using Umbraco.Core.Composing; using Umbraco.Core.Models; using Umbraco.Core.Serialization; @@ -18,14 +17,14 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters /// [JsonConverter(typeof(NoTypeConverterJsonConverter))] [TypeConverter(typeof(ImageCropperValueTypeConverter))] - [DataContract(Name="imageCropDataSet")] + [DataContract(Name = "imageCropDataSet")] public class ImageCropperValue : IHtmlString, IEquatable { /// /// Gets or sets the value source image. /// - [DataMember(Name="src")] - public string Src { get; set;} + [DataMember(Name = "src")] + public string Src { get; set; } /// /// Gets or sets the value focal point. @@ -41,9 +40,7 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters /// public override string ToString() - { - return Crops != null ? (Crops.Any() ? JsonConvert.SerializeObject(this) : Src) : string.Empty; - } + => HasCrops() || HasFocalPoint() ? JsonConvert.SerializeObject(this, Formatting.None) : Src; /// public string ToHtmlString() => Src; @@ -134,13 +131,19 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters /// /// public bool HasFocalPoint() - => FocalPoint != null && (FocalPoint.Left != 0.5m || FocalPoint.Top != 0.5m); + => FocalPoint is ImageCropperFocalPoint focalPoint && (focalPoint.Left != 0.5m || focalPoint.Top != 0.5m); + + /// + /// Determines whether the value has crops. + /// + public bool HasCrops() + => Crops is IEnumerable crops && crops.Any(); /// /// Determines whether the value has a specified crop. /// public bool HasCrop(string alias) - => Crops != null && Crops.Any(x => x.Alias == alias); + => Crops is IEnumerable crops && crops.Any(x => x.Alias == alias); /// /// Determines whether the value has a source image. @@ -179,6 +182,51 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters }; } + /// + /// Removes redundant crop data/default focal point. + /// + /// The image cropper value. + /// + /// The cleaned up value. + /// + public static void Prune(JObject value) + { + if (value is null) throw new ArgumentNullException(nameof(value)); + + if (value.TryGetValue("crops", out var crops)) + { + if (crops.HasValues) + { + foreach (var crop in crops.Values().ToList()) + { + if (crop.TryGetValue("coordinates", out var coordinates) == false || coordinates.HasValues == false) + { + // Remove crop without coordinates + crop.Remove(); + continue; + } + + // Width/height are already stored in the crop configuration + crop.Remove("width"); + crop.Remove("height"); + } + } + + if (crops.HasValues == false) + { + // Remove empty crops + value.Remove("crops"); + } + } + + if (value.TryGetValue("focalPoint", out var focalPoint) && + (focalPoint.HasValues == false || (focalPoint.Value("top") == 0.5m && focalPoint.Value("left") == 0.5m))) + { + // Remove empty/default focal point + value.Remove("focalPoint"); + } + } + #region IEquatable /// @@ -212,8 +260,8 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters // properties are, practically, readonly // ReSharper disable NonReadonlyMemberInGetHashCode var hashCode = Src?.GetHashCode() ?? 0; - hashCode = (hashCode*397) ^ (FocalPoint?.GetHashCode() ?? 0); - hashCode = (hashCode*397) ^ (Crops?.GetHashCode() ?? 0); + hashCode = (hashCode * 397) ^ (FocalPoint?.GetHashCode() ?? 0); + hashCode = (hashCode * 397) ^ (Crops?.GetHashCode() ?? 0); return hashCode; // ReSharper restore NonReadonlyMemberInGetHashCode } @@ -258,7 +306,7 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters { // properties are, practically, readonly // ReSharper disable NonReadonlyMemberInGetHashCode - return (Left.GetHashCode()*397) ^ Top.GetHashCode(); + return (Left.GetHashCode() * 397) ^ Top.GetHashCode(); // ReSharper restore NonReadonlyMemberInGetHashCode } } @@ -312,9 +360,9 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters // properties are, practically, readonly // ReSharper disable NonReadonlyMemberInGetHashCode var hashCode = Alias?.GetHashCode() ?? 0; - hashCode = (hashCode*397) ^ Width; - hashCode = (hashCode*397) ^ Height; - hashCode = (hashCode*397) ^ (Coordinates?.GetHashCode() ?? 0); + hashCode = (hashCode * 397) ^ Width; + hashCode = (hashCode * 397) ^ Height; + hashCode = (hashCode * 397) ^ (Coordinates?.GetHashCode() ?? 0); return hashCode; // ReSharper restore NonReadonlyMemberInGetHashCode } @@ -339,7 +387,7 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters public decimal Y2 { get; set; } #region IEquatable - + /// public bool Equals(ImageCropperCropCoordinates other) => ReferenceEquals(this, other) || Equals(this, other); @@ -369,9 +417,9 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters // properties are, practically, readonly // ReSharper disable NonReadonlyMemberInGetHashCode var hashCode = X1.GetHashCode(); - hashCode = (hashCode*397) ^ Y1.GetHashCode(); - hashCode = (hashCode*397) ^ X2.GetHashCode(); - hashCode = (hashCode*397) ^ Y2.GetHashCode(); + hashCode = (hashCode * 397) ^ Y1.GetHashCode(); + hashCode = (hashCode * 397) ^ X2.GetHashCode(); + hashCode = (hashCode * 397) ^ Y2.GetHashCode(); return hashCode; // ReSharper restore NonReadonlyMemberInGetHashCode } diff --git a/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs b/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs index c40708770e..eed45b0d27 100644 --- a/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs +++ b/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs @@ -29,13 +29,13 @@ namespace Umbraco.Tests.PropertyEditors { private const string CropperJson1 = "{\"focalPoint\": {\"left\": 0.96,\"top\": 0.80827067669172936},\"src\": \"/media/1005/img_0671.jpg\",\"crops\": [{\"alias\":\"thumb\",\"width\": 100,\"height\": 100,\"coordinates\": {\"x1\": 0.58729977382575338,\"y1\": 0.055768992440203169,\"x2\": 0,\"y2\": 0.32457553600198386}}]}"; private const string CropperJson2 = "{\"focalPoint\": {\"left\": 0.98,\"top\": 0.80827067669172936},\"src\": \"/media/1005/img_0672.jpg\",\"crops\": [{\"alias\":\"thumb\",\"width\": 100,\"height\": 100,\"coordinates\": {\"x1\": 0.58729977382575338,\"y1\": 0.055768992440203169,\"x2\": 0,\"y2\": 0.32457553600198386}}]}"; - private const string CropperJson3 = "{\"focalPoint\": {\"left\": 0.98,\"top\": 0.80827067669172936},\"src\": \"/media/1005/img_0672.jpg\",\"crops\": []}"; + private const string CropperJson3 = "{\"focalPoint\": {\"left\": 0.5,\"top\": 0.5},\"src\": \"/media/1005/img_0672.jpg\",\"crops\": []}"; private const string MediaPath = "/media/1005/img_0671.jpg"; [Test] public void CanConvertImageCropperDataSetSrcToString() { - //cropperJson3 - has not crops + //cropperJson3 - has no crops var cropperValue = CropperJson3.DeserializeImageCropperValue(); var serialized = cropperValue.TryConvertTo(); Assert.IsTrue(serialized.Success); @@ -45,7 +45,7 @@ namespace Umbraco.Tests.PropertyEditors [Test] public void CanConvertImageCropperDataSetJObject() { - //cropperJson3 - has not crops + //cropperJson3 - has no crops var cropperValue = CropperJson3.DeserializeImageCropperValue(); var serialized = cropperValue.TryConvertTo(); Assert.IsTrue(serialized.Success); diff --git a/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyValueEditor.cs b/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyValueEditor.cs index 4aac8f54aa..8e13d1bb5a 100644 --- a/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyValueEditor.cs @@ -1,6 +1,6 @@ -using Newtonsoft.Json.Linq; -using System; +using System; using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using Umbraco.Core; using Umbraco.Core.IO; using Umbraco.Core.Logging; @@ -66,31 +66,42 @@ namespace Umbraco.Web.PropertyEditors /// public override object FromEditor(ContentPropertyData editorValue, object currentValue) { - // get the current path + // Get the current path var currentPath = string.Empty; try { var svalue = currentValue as string; var currentJson = string.IsNullOrWhiteSpace(svalue) ? null : JObject.Parse(svalue); - if (currentJson != null && currentJson["src"] != null) - currentPath = currentJson["src"].Value(); + if (currentJson != null && currentJson.TryGetValue("src", out var src)) + { + currentPath = src.Value(); + } } catch (Exception ex) { - // for some reason the value is invalid so continue as if there was no value there + // For some reason the value is invalid, so continue as if there was no value there _logger.Warn(ex, "Could not parse current db value to a JObject."); } + if (string.IsNullOrWhiteSpace(currentPath) == false) currentPath = _mediaFileSystem.GetRelativePath(currentPath); - // get the new json and path - JObject editorJson = null; + // Get the new JSON and file path var editorFile = string.Empty; - if (editorValue.Value != null) + if (editorValue.Value is JObject editorJson) { - editorJson = editorValue.Value as JObject; - if (editorJson != null && editorJson["src"] != null) + // Populate current file + if (editorJson["src"] != null) + { editorFile = editorJson["src"].Value(); + } + + // Clean up redundant/default data + ImageCropperValue.Prune(editorJson); + } + else + { + editorJson = null; } // ensure we have the required guids @@ -118,7 +129,7 @@ namespace Umbraco.Web.PropertyEditors return null; // clear } - return editorJson?.ToString(); // unchanged + return editorJson?.ToString(Formatting.None); // unchanged } // process the file @@ -135,7 +146,8 @@ namespace Umbraco.Web.PropertyEditors // update json and return if (editorJson == null) return null; editorJson["src"] = filepath == null ? string.Empty : _mediaFileSystem.GetUrl(filepath); - return editorJson.ToString(); + + return editorJson.ToString(Formatting.None); } private string ProcessFile(ContentPropertyData editorValue, ContentPropertyFile file, string currentPath, Guid cuid, Guid puid) @@ -160,7 +172,6 @@ namespace Umbraco.Web.PropertyEditors return filepath; } - public override string ConvertDbToString(PropertyType propertyType, object value, IDataTypeService dataTypeService) { if (value == null || string.IsNullOrEmpty(value.ToString())) diff --git a/src/Umbraco.Web/PropertyEditors/MediaPicker3PropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MediaPicker3PropertyEditor.cs index 43d190e173..5f1b319e01 100644 --- a/src/Umbraco.Web/PropertyEditors/MediaPicker3PropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MediaPicker3PropertyEditor.cs @@ -1,8 +1,9 @@ -using Newtonsoft.Json; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Models; @@ -50,7 +51,36 @@ namespace Umbraco.Web.PropertyEditors { var value = property.GetValue(culture, segment); - return Deserialize(value); + var dtos = Deserialize(value).ToList(); + + var dataType = dataTypeService.GetDataType(property.PropertyType.DataTypeId); + if (dataType?.Configuration != null) + { + var configuration = dataType.ConfigurationAs(); + + foreach (var dto in dtos) + { + dto.ApplyConfiguration(configuration); + } + } + + return dtos; + } + + public override object FromEditor(ContentPropertyData editorValue, object currentValue) + { + if (editorValue.Value is JArray dtos) + { + // Clean up redundant/default data + foreach (var dto in dtos.Values()) + { + MediaWithCropsDto.Prune(dto); + } + + return dtos.ToString(Formatting.None); + } + + return base.FromEditor(editorValue, currentValue); } public IEnumerable GetReferences(object value) @@ -117,6 +147,51 @@ namespace Umbraco.Web.PropertyEditors [DataMember(Name = "focalPoint")] public ImageCropperValue.ImageCropperFocalPoint FocalPoint { get; set; } + + /// + /// Applies the configuration to ensure only valid crops are kept and have the correct width/height. + /// + /// The configuration. + public void ApplyConfiguration(MediaPicker3Configuration configuration) + { + var crops = new List(); + + var configuredCrops = configuration?.Crops; + if (configuredCrops != null) + { + foreach (var configuredCrop in configuredCrops) + { + var crop = Crops?.FirstOrDefault(x => x.Alias == configuredCrop.Alias); + + crops.Add(new ImageCropperValue.ImageCropperCrop + { + Alias = configuredCrop.Alias, + Width = configuredCrop.Width, + Height = configuredCrop.Height, + Coordinates = crop?.Coordinates + }); + } + } + + Crops = crops; + + if (configuration?.EnableLocalFocalPoint == false) + { + FocalPoint = null; + } + } + + /// + /// Removes redundant crop data/default focal point. + /// + /// The media with crops DTO. + /// + /// The cleaned up value. + /// + /// + /// Because the DTO uses the same JSON keys as the image cropper value for crops and focal point, we can re-use the prune method. + /// + public static void Prune(JObject value) => ImageCropperValue.Prune(value); } } } From dfc3e56eb79965117c4ebfb489d9e71779878b39 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Wed, 5 Jan 2022 13:16:07 +0100 Subject: [PATCH 044/141] Check if we're in debug and set IncludeErrorPolicy accordingly --- src/Umbraco.Web/WebApi/EnableDetailedErrorsAttribute.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web/WebApi/EnableDetailedErrorsAttribute.cs b/src/Umbraco.Web/WebApi/EnableDetailedErrorsAttribute.cs index a62df6b5e4..3df6ffb47b 100644 --- a/src/Umbraco.Web/WebApi/EnableDetailedErrorsAttribute.cs +++ b/src/Umbraco.Web/WebApi/EnableDetailedErrorsAttribute.cs @@ -1,4 +1,5 @@ -using System.Web.Http; +using System.Web; +using System.Web.Http; using System.Web.Http.Controllers; using System.Web.Http.Filters; @@ -11,7 +12,7 @@ namespace Umbraco.Web.WebApi { public override void OnActionExecuting(HttpActionContext actionContext) { - actionContext.ControllerContext.Configuration.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always; + actionContext.ControllerContext.Configuration.IncludeErrorDetailPolicy = HttpContext.Current.IsDebuggingEnabled ? IncludeErrorDetailPolicy.Always : IncludeErrorDetailPolicy.Default; } } } From 642c216f944b507a234ee8c09a9c8b5cdd180dbe Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Thu, 6 Jan 2022 13:35:24 +0100 Subject: [PATCH 045/141] Serve Media and App_Plugins using WebRootFileProvider (and allow changing the physical media path) (#11783) * Allow changing UmbracoMediaPath to an absolute path. Also ensure Imagesharp are handing requests outside of the wwwroot folder. * Let UmbracoMediaUrl fallback to UmbracoMediaPath when empty * Add FileSystemFileProvider to expose an IFileSystem as IFileProvider * Replace IUmbracoMediaFileProvider with IFileProviderFactory implementation * Fix issue resolving relative paths when media URL has changed * Remove FileSystemFileProvider and require explicitly implementing IFileProviderFactory * Update tests (UnauthorizedAccessException isn't thrown anymore for rooted files) * Update test to use UmbracoMediaUrl * Add UmbracoMediaPhysicalRootPath global setting * Remove MediaFileManagerImageProvider and use composited file providers * Move CreateFileProvider to IFileSystem extension method * Add rooted path tests Co-authored-by: Ronald Barendse --- .../Configuration/Models/GlobalSettings.cs | 31 ++++++++++------ .../Constants-SystemDirectories.cs | 2 ++ .../UmbracoBuilder.Configuration.cs | 26 +++++++++----- src/Umbraco.Core/IO/FileSystemExtensions.cs | 20 +++++++++++ src/Umbraco.Core/IO/IFileProviderFactory.cs | 18 ++++++++++ src/Umbraco.Core/IO/MediaFileManager.cs | 35 +++++++++++-------- src/Umbraco.Core/IO/PhysicalFileSystem.cs | 18 ++++++---- src/Umbraco.Core/IO/ShadowWrapper.cs | 8 +++-- .../Packaging/PackagesRepository.cs | 2 +- .../Runtime/EssentialDirectoryCreator.cs | 2 +- src/Umbraco.Core/Umbraco.Core.csproj | 3 +- .../UmbracoBuilder.FileSystems.cs | 2 +- .../Install/FilePermissionHelper.cs | 4 +-- .../CreatedPackageSchemaRepository.cs | 2 +- .../UmbracoApplicationBuilder.cs | 30 +++++++++++++--- .../ImageSharpConfigurationOptions.cs | 4 +-- .../UmbracoBuilder.ImageSharp.cs | 1 + .../ApplicationBuilderExtensions.cs | 31 ++++++++-------- .../Extensions/FileProviderExtensions.cs | 19 ++++++++++ .../PartialViewRepositoryTests.cs | 29 +++++++++------ .../Repositories/ScriptRepositoryTest.cs | 15 +++++--- .../Repositories/StylesheetRepositoryTest.cs | 17 ++++++--- .../Scoping/ScopeFileSystemsTests.cs | 6 ++-- .../TestHelpers/TestHelper.cs | 4 +-- 24 files changed, 233 insertions(+), 96 deletions(-) create mode 100644 src/Umbraco.Core/IO/IFileProviderFactory.cs create mode 100644 src/Umbraco.Web.Common/Extensions/FileProviderExtensions.cs diff --git a/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs b/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs index 97fb91b0ec..7e3e1a2700 100644 --- a/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs @@ -31,21 +31,19 @@ namespace Umbraco.Cms.Core.Configuration.Models internal const bool StaticSanitizeTinyMce = false; /// - /// Gets or sets a value for the reserved URLs. - /// It must end with a comma + /// Gets or sets a value for the reserved URLs (must end with a comma). /// [DefaultValue(StaticReservedUrls)] public string ReservedUrls { get; set; } = StaticReservedUrls; /// - /// Gets or sets a value for the reserved paths. - /// It must end with a comma + /// Gets or sets a value for the reserved paths (must end with a comma). /// [DefaultValue(StaticReservedPaths)] public string ReservedPaths { get; set; } = StaticReservedPaths; /// - /// Gets or sets a value for the timeout + /// Gets or sets a value for the back-office login timeout. /// [DefaultValue(StaticTimeOut)] public TimeSpan TimeOut { get; set; } = TimeSpan.Parse(StaticTimeOut); @@ -104,11 +102,19 @@ namespace Umbraco.Cms.Core.Configuration.Models public string UmbracoScriptsPath { get; set; } = StaticUmbracoScriptsPath; /// - /// Gets or sets a value for the Umbraco media path. + /// Gets or sets a value for the Umbraco media request path. /// [DefaultValue(StaticUmbracoMediaPath)] public string UmbracoMediaPath { get; set; } = StaticUmbracoMediaPath; + /// + /// Gets or sets a value for the physical Umbraco media root path (falls back to when empty). + /// + /// + /// If the value is a virtual path, it's resolved relative to the webroot. + /// + public string UmbracoMediaPhysicalRootPath { get; set; } + /// /// Gets or sets a value indicating whether to install the database when it is missing. /// @@ -131,6 +137,9 @@ namespace Umbraco.Cms.Core.Configuration.Models /// public string MainDomLock { get; set; } = string.Empty; + /// + /// Gets or sets the telemetry ID. + /// public string Id { get; set; } = string.Empty; /// @@ -164,19 +173,19 @@ namespace Umbraco.Cms.Core.Configuration.Models /// public bool IsPickupDirectoryLocationConfigured => !string.IsNullOrWhiteSpace(Smtp?.PickupDirectoryLocation); - /// Gets a value indicating whether TinyMCE scripting sanitization should be applied + /// + /// Gets a value indicating whether TinyMCE scripting sanitization should be applied. /// [DefaultValue(StaticSanitizeTinyMce)] public bool SanitizeTinyMce => StaticSanitizeTinyMce; /// - /// An int value representing the time in milliseconds to lock the database for a write operation + /// Gets a value representing the time in milliseconds to lock the database for a write operation. /// /// - /// The default value is 5000 milliseconds + /// The default value is 5000 milliseconds. /// - /// The timeout in milliseconds. [DefaultValue(StaticSqlWriteLockTimeOut)] public TimeSpan SqlWriteLockTimeOut { get; } = TimeSpan.Parse(StaticSqlWriteLockTimeOut); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Constants-SystemDirectories.cs b/src/Umbraco.Core/Constants-SystemDirectories.cs index 80b49781ec..bf34aab989 100644 --- a/src/Umbraco.Core/Constants-SystemDirectories.cs +++ b/src/Umbraco.Core/Constants-SystemDirectories.cs @@ -43,6 +43,8 @@ namespace Umbraco.Cms.Core public const string AppPlugins = "/App_Plugins"; public static string AppPluginIcons => "/Backoffice/Icons"; + public const string CreatedPackages = "/created-packages"; + public const string MvcViews = "~/Views"; diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs index 6ef87464e8..ce2e4f2304 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs @@ -13,23 +13,25 @@ namespace Umbraco.Cms.Core.DependencyInjection public static partial class UmbracoBuilderExtensions { - private static IUmbracoBuilder AddUmbracoOptions(this IUmbracoBuilder builder) + private static IUmbracoBuilder AddUmbracoOptions(this IUmbracoBuilder builder, Action> configure = null) where TOptions : class { var umbracoOptionsAttribute = typeof(TOptions).GetCustomAttribute(); - if (umbracoOptionsAttribute is null) { - throw new ArgumentException("typeof(TOptions) do not have the UmbracoOptionsAttribute"); + throw new ArgumentException($"{typeof(TOptions)} do not have the UmbracoOptionsAttribute."); } - - builder.Services.AddOptions() - .Bind(builder.Config.GetSection(umbracoOptionsAttribute.ConfigurationKey), - o => o.BindNonPublicProperties = umbracoOptionsAttribute.BindNonPublicProperties) + var optionsBuilder = builder.Services.AddOptions() + .Bind( + builder.Config.GetSection(umbracoOptionsAttribute.ConfigurationKey), + o => o.BindNonPublicProperties = umbracoOptionsAttribute.BindNonPublicProperties + ) .ValidateDataAnnotations(); - return builder; + configure?.Invoke(optionsBuilder); + + return builder; } /// @@ -52,7 +54,13 @@ namespace Umbraco.Cms.Core.DependencyInjection .AddUmbracoOptions() .AddUmbracoOptions() .AddUmbracoOptions() - .AddUmbracoOptions() + .AddUmbracoOptions(optionsBuilder => optionsBuilder.PostConfigure(options => + { + if (string.IsNullOrEmpty(options.UmbracoMediaPhysicalRootPath)) + { + options.UmbracoMediaPhysicalRootPath = options.UmbracoMediaPath; + } + })) .AddUmbracoOptions() .AddUmbracoOptions() .AddUmbracoOptions() diff --git a/src/Umbraco.Core/IO/FileSystemExtensions.cs b/src/Umbraco.Core/IO/FileSystemExtensions.cs index 23be195e4b..c95d37e1c3 100644 --- a/src/Umbraco.Core/IO/FileSystemExtensions.cs +++ b/src/Umbraco.Core/IO/FileSystemExtensions.cs @@ -3,6 +3,7 @@ using System.IO; using System.Security.Cryptography; using System.Text; using System.Threading; +using Microsoft.Extensions.FileProviders; using Umbraco.Cms.Core.IO; namespace Umbraco.Extensions @@ -87,5 +88,24 @@ namespace Umbraco.Extensions } fs.DeleteFile(tempFile); } + + /// + /// Creates a new from the file system. + /// + /// The file system. + /// When this method returns, contains an created from the file system. + /// + /// true if the was successfully created; otherwise, false. + /// + public static bool TryCreateFileProvider(this IFileSystem fileSystem, out IFileProvider fileProvider) + { + fileProvider = fileSystem switch + { + IFileProviderFactory fileProviderFactory => fileProviderFactory.Create(), + _ => null + }; + + return fileProvider != null; + } } } diff --git a/src/Umbraco.Core/IO/IFileProviderFactory.cs b/src/Umbraco.Core/IO/IFileProviderFactory.cs new file mode 100644 index 0000000000..742467ccc8 --- /dev/null +++ b/src/Umbraco.Core/IO/IFileProviderFactory.cs @@ -0,0 +1,18 @@ +using Microsoft.Extensions.FileProviders; + +namespace Umbraco.Cms.Core.IO +{ + /// + /// Factory for creating instances. + /// + public interface IFileProviderFactory + { + /// + /// Creates a new instance. + /// + /// + /// The newly created instance (or null if not supported). + /// + IFileProvider Create(); + } +} diff --git a/src/Umbraco.Core/IO/MediaFileManager.cs b/src/Umbraco.Core/IO/MediaFileManager.cs index 96680d3f84..c769b9801e 100644 --- a/src/Umbraco.Core/IO/MediaFileManager.cs +++ b/src/Umbraco.Core/IO/MediaFileManager.cs @@ -8,7 +8,6 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.Strings; using Umbraco.Extensions; @@ -22,13 +21,22 @@ namespace Umbraco.Cms.Core.IO private readonly IShortStringHelper _shortStringHelper; private readonly IServiceProvider _serviceProvider; private MediaUrlGeneratorCollection _mediaUrlGenerators; - private readonly ContentSettings _contentSettings; - /// - /// Gets the media filesystem. - /// - public IFileSystem FileSystem { get; } + public MediaFileManager( + IFileSystem fileSystem, + IMediaPathScheme mediaPathScheme, + ILogger logger, + IShortStringHelper shortStringHelper, + IServiceProvider serviceProvider) + { + _mediaPathScheme = mediaPathScheme; + _logger = logger; + _shortStringHelper = shortStringHelper; + _serviceProvider = serviceProvider; + FileSystem = fileSystem; + } + [Obsolete("Use the ctr that doesn't include unused parameters.")] public MediaFileManager( IFileSystem fileSystem, IMediaPathScheme mediaPathScheme, @@ -36,14 +44,13 @@ namespace Umbraco.Cms.Core.IO IShortStringHelper shortStringHelper, IServiceProvider serviceProvider, IOptions contentSettings) - { - _mediaPathScheme = mediaPathScheme; - _logger = logger; - _shortStringHelper = shortStringHelper; - _serviceProvider = serviceProvider; - _contentSettings = contentSettings.Value; - FileSystem = fileSystem; - } + : this(fileSystem, mediaPathScheme, logger, shortStringHelper, serviceProvider) + { } + + /// + /// Gets the media filesystem. + /// + public IFileSystem FileSystem { get; } /// /// Delete media files. diff --git a/src/Umbraco.Core/IO/PhysicalFileSystem.cs b/src/Umbraco.Core/IO/PhysicalFileSystem.cs index db10cae416..3da09a499c 100644 --- a/src/Umbraco.Core/IO/PhysicalFileSystem.cs +++ b/src/Umbraco.Core/IO/PhysicalFileSystem.cs @@ -3,14 +3,17 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; +using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Hosting; using Umbraco.Extensions; namespace Umbraco.Cms.Core.IO { - public interface IPhysicalFileSystem : IFileSystem {} - public class PhysicalFileSystem : IPhysicalFileSystem + public interface IPhysicalFileSystem : IFileSystem + { } + + public class PhysicalFileSystem : IPhysicalFileSystem, IFileProviderFactory { private readonly IIOHelper _ioHelper; private readonly ILogger _logger; @@ -28,7 +31,7 @@ namespace Umbraco.Cms.Core.IO // eg "" or "/Views" or "/Media" or "//Media" in case of a virtual path private readonly string _rootUrl; - public PhysicalFileSystem(IIOHelper ioHelper,IHostingEnvironment hostingEnvironment, ILogger logger, string rootPath, string rootUrl) + public PhysicalFileSystem(IIOHelper ioHelper, IHostingEnvironment hostingEnvironment, ILogger logger, string rootPath, string rootUrl) { _ioHelper = ioHelper ?? throw new ArgumentNullException(nameof(ioHelper)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); @@ -270,7 +273,7 @@ namespace Umbraco.Cms.Core.IO return path.Substring(_rootUrl.Length).TrimStart(Constants.CharArrays.ForwardSlash); // unchanged - what else? - return path; + return path.TrimStart(Constants.CharArrays.ForwardSlash); } /// @@ -285,7 +288,7 @@ namespace Umbraco.Cms.Core.IO public string GetFullPath(string path) { // normalize - var opath = path; + var originalPath = path; path = EnsureDirectorySeparatorChar(path); // FIXME: this part should go! @@ -318,7 +321,7 @@ namespace Umbraco.Cms.Core.IO // nothing prevents us to reach the file, security-wise, yet it is outside // this filesystem's root - throw - throw new UnauthorizedAccessException($"File original: [{opath}] full: [{path}] is outside this filesystem's root."); + throw new UnauthorizedAccessException($"File original: [{originalPath}] full: [{path}] is outside this filesystem's root."); } /// @@ -450,6 +453,9 @@ namespace Umbraco.Cms.Core.IO } } + /// + public IFileProvider Create() => new PhysicalFileProvider(_rootPath); + #endregion } } diff --git a/src/Umbraco.Core/IO/ShadowWrapper.cs b/src/Umbraco.Core/IO/ShadowWrapper.cs index cda61cf7b5..7bf315a575 100644 --- a/src/Umbraco.Core/IO/ShadowWrapper.cs +++ b/src/Umbraco.Core/IO/ShadowWrapper.cs @@ -1,14 +1,15 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; +using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Hosting; using Umbraco.Extensions; namespace Umbraco.Cms.Core.IO { - internal class ShadowWrapper : IFileSystem + internal class ShadowWrapper : IFileSystem, IFileProviderFactory { private static readonly string ShadowFsPath = Constants.SystemDirectories.TempData.EnsureEndsWith('/') + "ShadowFs"; @@ -220,5 +221,8 @@ namespace Umbraco.Cms.Core.IO { FileSystem.AddFile(path, physicalPath, overrideIfExists, copy); } + + /// + public IFileProvider Create() => _innerFileSystem.TryCreateFileProvider(out IFileProvider fileProvider) ? fileProvider : null; } } diff --git a/src/Umbraco.Core/Packaging/PackagesRepository.cs b/src/Umbraco.Core/Packaging/PackagesRepository.cs index 331034e787..36b7a5d5d5 100644 --- a/src/Umbraco.Core/Packaging/PackagesRepository.cs +++ b/src/Umbraco.Core/Packaging/PackagesRepository.cs @@ -93,7 +93,7 @@ namespace Umbraco.Cms.Core.Packaging _tempFolderPath = tempFolderPath ?? Constants.SystemDirectories.TempData.EnsureEndsWith('/') + "PackageFiles"; _packagesFolderPath = packagesFolderPath ?? Constants.SystemDirectories.Packages; - _mediaFolderPath = mediaFolderPath ?? globalSettings.Value.UmbracoMediaPath + "/created-packages"; + _mediaFolderPath = mediaFolderPath ?? Path.Combine(globalSettings.Value.UmbracoMediaPhysicalRootPath, Constants.SystemDirectories.CreatedPackages); _parser = new PackageDefinitionXmlParser(); _mediaService = mediaService; diff --git a/src/Umbraco.Core/Runtime/EssentialDirectoryCreator.cs b/src/Umbraco.Core/Runtime/EssentialDirectoryCreator.cs index a9564712c3..6c45e4d969 100644 --- a/src/Umbraco.Core/Runtime/EssentialDirectoryCreator.cs +++ b/src/Umbraco.Core/Runtime/EssentialDirectoryCreator.cs @@ -25,7 +25,7 @@ namespace Umbraco.Cms.Core.Runtime // ensure we have some essential directories // every other component can then initialize safely _ioHelper.EnsurePathExists(_hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.Data)); - _ioHelper.EnsurePathExists(_hostingEnvironment.MapPathWebRoot(_globalSettings.UmbracoMediaPath)); + _ioHelper.EnsurePathExists(_hostingEnvironment.MapPathWebRoot(_globalSettings.UmbracoMediaPhysicalRootPath)); _ioHelper.EnsurePathExists(_hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.MvcViews)); _ioHelper.EnsurePathExists(_hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.PartialViews)); _ioHelper.EnsurePathExists(_hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.MacroPartials)); diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index f36d7a8ee5..ee83d0677a 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 @@ -17,6 +17,7 @@ + diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.FileSystems.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.FileSystems.cs index 6582cfb0c6..f66991fb68 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.FileSystems.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.FileSystems.cs @@ -49,7 +49,7 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection ILogger logger = factory.GetRequiredService>(); GlobalSettings globalSettings = factory.GetRequiredService>().Value; - var rootPath = hostingEnvironment.MapPathWebRoot(globalSettings.UmbracoMediaPath); + var rootPath = hostingEnvironment.MapPathWebRoot(globalSettings.UmbracoMediaPhysicalRootPath); var rootUrl = hostingEnvironment.ToAbsolute(globalSettings.UmbracoMediaPath); return new PhysicalFileSystem(ioHelper, hostingEnvironment, logger, rootPath, rootUrl); }); diff --git a/src/Umbraco.Infrastructure/Install/FilePermissionHelper.cs b/src/Umbraco.Infrastructure/Install/FilePermissionHelper.cs index 0ad2271d7e..1d228ebf98 100644 --- a/src/Umbraco.Infrastructure/Install/FilePermissionHelper.cs +++ b/src/Umbraco.Infrastructure/Install/FilePermissionHelper.cs @@ -44,7 +44,7 @@ namespace Umbraco.Cms.Infrastructure.Install hostingEnvironment.MapPathWebRoot(_globalSettings.UmbracoCssPath), hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.Config), hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.Data), - hostingEnvironment.MapPathWebRoot(_globalSettings.UmbracoMediaPath), + hostingEnvironment.MapPathWebRoot(_globalSettings.UmbracoMediaPhysicalRootPath), hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.Preview) }; _packagesPermissionsDirs = new[] @@ -70,7 +70,7 @@ namespace Umbraco.Cms.Infrastructure.Install EnsureFiles(_permissionFiles, out errors); report[FilePermissionTest.FileWriting] = errors.ToList(); - EnsureCanCreateSubDirectory(_hostingEnvironment.MapPathWebRoot(_globalSettings.UmbracoMediaPath), out errors); + EnsureCanCreateSubDirectory(_hostingEnvironment.MapPathWebRoot(_globalSettings.UmbracoMediaPhysicalRootPath), out errors); report[FilePermissionTest.MediaFolderCreation] = errors.ToList(); return report.Sum(x => x.Value.Count()) == 0; diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs index 0c4f876bb1..be1a31c2c9 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs @@ -76,7 +76,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement _macroService = macroService; _contentTypeService = contentTypeService; _xmlParser = new PackageDefinitionXmlParser(); - _mediaFolderPath = mediaFolderPath ?? globalSettings.Value.UmbracoMediaPath + "/created-packages"; + _mediaFolderPath = mediaFolderPath ?? Path.Combine(globalSettings.Value.UmbracoMediaPhysicalRootPath, Constants.SystemDirectories.CreatedPackages); _tempFolderPath = tempFolderPath ?? Constants.SystemDirectories.TempData.EnsureEndsWith('/') + "PackageFiles"; } diff --git a/src/Umbraco.Web.Common/ApplicationBuilder/UmbracoApplicationBuilder.cs b/src/Umbraco.Web.Common/ApplicationBuilder/UmbracoApplicationBuilder.cs index 9d30551071..58f66a6fb8 100644 --- a/src/Umbraco.Web.Common/ApplicationBuilder/UmbracoApplicationBuilder.cs +++ b/src/Umbraco.Web.Common/ApplicationBuilder/UmbracoApplicationBuilder.cs @@ -1,10 +1,16 @@ using System; +using Dazinator.Extensions.FileProviders.PrependBasePath; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Options; using SixLabors.ImageSharp.Web.DependencyInjection; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Services; using Umbraco.Extensions; +using IHostingEnvironment = Umbraco.Cms.Core.Hosting.IHostingEnvironment; namespace Umbraco.Cms.Web.Common.ApplicationBuilder { @@ -22,12 +28,14 @@ namespace Umbraco.Cms.Web.Common.ApplicationBuilder { AppBuilder = appBuilder ?? throw new ArgumentNullException(nameof(appBuilder)); ApplicationServices = appBuilder.ApplicationServices; - RuntimeState = appBuilder.ApplicationServices.GetRequiredService(); + RuntimeState = appBuilder.ApplicationServices.GetRequiredService(); _umbracoPipelineStartupOptions = ApplicationServices.GetRequiredService>(); } public IServiceProvider ApplicationServices { get; } + public IRuntimeState RuntimeState { get; } + public IApplicationBuilder AppBuilder { get; } /// @@ -78,18 +86,32 @@ namespace Umbraco.Cms.Web.Common.ApplicationBuilder } /// - /// Registers the default required middleware to run Umbraco + /// Registers the default required middleware to run Umbraco. /// - /// public void RegisterDefaultRequiredMiddleware() { UseUmbracoCoreMiddleware(); AppBuilder.UseStatusCodePages(); - // Important we handle image manipulations before the static files, otherwise the querystring is just ignored. + // Important we handle image manipulations before the static files, otherwise the querystring is just ignored. AppBuilder.UseImageSharp(); + + // Get media file provider and request path/URL + var mediaFileManager = AppBuilder.ApplicationServices.GetRequiredService(); + if (mediaFileManager.FileSystem.TryCreateFileProvider(out IFileProvider mediaFileProvider)) + { + GlobalSettings globalSettings = AppBuilder.ApplicationServices.GetRequiredService>().Value; + IHostingEnvironment hostingEnvironment = AppBuilder.ApplicationServices.GetService(); + string mediaRequestPath = hostingEnvironment.ToAbsolute(globalSettings.UmbracoMediaPath); + + // Configure custom file provider for media + IWebHostEnvironment webHostEnvironment = AppBuilder.ApplicationServices.GetService(); + webHostEnvironment.WebRootFileProvider = webHostEnvironment.WebRootFileProvider.ConcatComposite(new PrependBasePathFileProvider(mediaRequestPath, mediaFileProvider)); + } + AppBuilder.UseStaticFiles(); + AppBuilder.UseUmbracoPluginsStaticFiles(); // UseRouting adds endpoint routing middleware, this means that middlewares registered after this one diff --git a/src/Umbraco.Web.Common/DependencyInjection/ImageSharpConfigurationOptions.cs b/src/Umbraco.Web.Common/DependencyInjection/ImageSharpConfigurationOptions.cs index 628345dcd6..f8897e522c 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/ImageSharpConfigurationOptions.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/ImageSharpConfigurationOptions.cs @@ -7,7 +7,7 @@ namespace Umbraco.Cms.Web.Common.DependencyInjection /// /// Configures the ImageSharp middleware options to use the registered configuration. /// - /// + /// public sealed class ImageSharpConfigurationOptions : IConfigureOptions { /// @@ -22,7 +22,7 @@ namespace Umbraco.Cms.Web.Common.DependencyInjection public ImageSharpConfigurationOptions(Configuration configuration) => _configuration = configuration; /// - /// Invoked to configure a instance. + /// Invoked to configure an instance. /// /// The options instance to configure. public void Configure(ImageSharpMiddlewareOptions options) => options.Configuration = _configuration; diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs index 4d621d348c..30331fd812 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs @@ -74,6 +74,7 @@ namespace Umbraco.Extensions .Configure(options => options.CacheFolder = builder.BuilderHostingEnvironment.MapPathContentRoot(imagingSettings.Cache.CacheFolder)) .AddProcessor(); + // Configure middleware to use the registered/shared ImageSharp configuration builder.Services.AddTransient, ImageSharpConfigurationOptions>(); return builder.Services; diff --git a/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs b/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs index cc035d116b..fc6a2904df 100644 --- a/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs @@ -1,18 +1,20 @@ using System; using System.IO; +using Dazinator.Extensions.FileProviders.PrependBasePath; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Serilog.Context; using StackExchange.Profiling; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration.Models; -using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.Logging.Serilog.Enrichers; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Web.Common.ApplicationBuilder; using Umbraco.Cms.Web.Common.Middleware; using Umbraco.Cms.Web.Common.Plugins; +using IHostingEnvironment = Umbraco.Cms.Core.Hosting.IHostingEnvironment; namespace Umbraco.Extensions { @@ -94,7 +96,8 @@ namespace Umbraco.Extensions throw new ArgumentNullException(nameof(app)); } - if (!app.UmbracoCanBoot()) return app; + if (!app.UmbracoCanBoot()) + return app; app.UseMiddleware(); @@ -109,25 +112,21 @@ namespace Umbraco.Extensions public static IApplicationBuilder UseUmbracoPluginsStaticFiles(this IApplicationBuilder app) { var hostingEnvironment = app.ApplicationServices.GetRequiredService(); - var umbracoPluginSettings = app.ApplicationServices.GetRequiredService>(); var pluginFolder = hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.AppPlugins); - - // Ensure the plugin folder exists - Directory.CreateDirectory(pluginFolder); - - var fileProvider = new UmbracoPluginPhysicalFileProvider( - pluginFolder, - umbracoPluginSettings); - - app.UseStaticFiles(new StaticFileOptions + if (Directory.Exists(pluginFolder)) { - FileProvider = fileProvider, - RequestPath = Constants.SystemDirectories.AppPlugins - }); + var umbracoPluginSettings = app.ApplicationServices.GetRequiredService>(); + + var pluginFileProvider = new UmbracoPluginPhysicalFileProvider( + pluginFolder, + umbracoPluginSettings); + + IWebHostEnvironment webHostEnvironment = app.ApplicationServices.GetService(); + webHostEnvironment.WebRootFileProvider = webHostEnvironment.WebRootFileProvider.ConcatComposite(new PrependBasePathFileProvider(Constants.SystemDirectories.AppPlugins, pluginFileProvider)); + } return app; } } - } diff --git a/src/Umbraco.Web.Common/Extensions/FileProviderExtensions.cs b/src/Umbraco.Web.Common/Extensions/FileProviderExtensions.cs new file mode 100644 index 0000000000..35a2882bdd --- /dev/null +++ b/src/Umbraco.Web.Common/Extensions/FileProviderExtensions.cs @@ -0,0 +1,19 @@ +using System.Linq; +using Microsoft.Extensions.FileProviders; + +namespace Umbraco.Extensions +{ + internal static class FileProviderExtensions + { + public static IFileProvider ConcatComposite(this IFileProvider fileProvider, params IFileProvider[] fileProviders) + { + var existingFileProviders = fileProvider switch + { + CompositeFileProvider compositeFileProvider => compositeFileProvider.FileProviders, + _ => new[] { fileProvider } + }; + + return new CompositeFileProvider(existingFileProviders.Concat(fileProviders)); + } + } +} diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/PartialViewRepositoryTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/PartialViewRepositoryTests.cs index 02709f7f84..4c6204ee4e 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/PartialViewRepositoryTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/PartialViewRepositoryTests.cs @@ -53,7 +53,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos { var repository = new PartialViewRepository(fileSystems); - var partialView = new PartialView(PartialViewType.PartialView, "test-path-1.cshtml") { Content = "// partialView" }; + IPartialView partialView = new PartialView(PartialViewType.PartialView, "test-path-1.cshtml") { Content = "// partialView" }; repository.Save(partialView); Assert.IsTrue(_fileSystem.FileExists("test-path-1.cshtml")); Assert.AreEqual("test-path-1.cshtml", partialView.Path); @@ -62,10 +62,10 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos partialView = new PartialView(PartialViewType.PartialView, "path-2/test-path-2.cshtml") { Content = "// partialView" }; repository.Save(partialView); Assert.IsTrue(_fileSystem.FileExists("path-2/test-path-2.cshtml")); - Assert.AreEqual("path-2\\test-path-2.cshtml".Replace("\\", $"{Path.DirectorySeparatorChar}"), partialView.Path); // fixed in 7.3 - 7.2.8 does not update the path + Assert.AreEqual("path-2\\test-path-2.cshtml".Replace("\\", $"{Path.DirectorySeparatorChar}"), partialView.Path); Assert.AreEqual("/Views/Partials/path-2/test-path-2.cshtml", partialView.VirtualPath); - partialView = (PartialView)repository.Get("path-2/test-path-2.cshtml"); + partialView = repository.Get("path-2/test-path-2.cshtml"); Assert.IsNotNull(partialView); Assert.AreEqual("path-2\\test-path-2.cshtml".Replace("\\", $"{Path.DirectorySeparatorChar}"), partialView.Path); Assert.AreEqual("/Views/Partials/path-2/test-path-2.cshtml", partialView.VirtualPath); @@ -76,26 +76,33 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos Assert.AreEqual("path-2\\test-path-3.cshtml".Replace("\\", $"{Path.DirectorySeparatorChar}"), partialView.Path); Assert.AreEqual("/Views/Partials/path-2/test-path-3.cshtml", partialView.VirtualPath); - partialView = (PartialView)repository.Get("path-2/test-path-3.cshtml"); + partialView = repository.Get("path-2/test-path-3.cshtml"); Assert.IsNotNull(partialView); Assert.AreEqual("path-2\\test-path-3.cshtml".Replace("\\", $"{Path.DirectorySeparatorChar}"), partialView.Path); Assert.AreEqual("/Views/Partials/path-2/test-path-3.cshtml", partialView.VirtualPath); - partialView = (PartialView)repository.Get("path-2\\test-path-3.cshtml"); + partialView = repository.Get("path-2\\test-path-3.cshtml"); Assert.IsNotNull(partialView); Assert.AreEqual("path-2\\test-path-3.cshtml".Replace("\\", $"{Path.DirectorySeparatorChar}"), partialView.Path); Assert.AreEqual("/Views/Partials/path-2/test-path-3.cshtml", partialView.VirtualPath); - partialView = new PartialView(PartialViewType.PartialView, "\\test-path-4.cshtml") { Content = "// partialView" }; - Assert.Throws(() => // fixed in 7.3 - 7.2.8 used to strip the \ + partialView = new PartialView(PartialViewType.PartialView, "..\\test-path-4.cshtml") { Content = "// partialView" }; + Assert.Throws(() => repository.Save(partialView)); - partialView = (PartialView)repository.Get("missing.cshtml"); + partialView = new PartialView(PartialViewType.PartialView, "\\test-path-5.cshtml") { Content = "// partialView" }; + repository.Save(partialView); + + partialView = repository.Get("\\test-path-5.cshtml"); + Assert.IsNotNull(partialView); + Assert.AreEqual("test-path-5.cshtml", partialView.Path); + Assert.AreEqual("/Views/Partials/test-path-5.cshtml", partialView.VirtualPath); + + partialView = repository.Get("missing.cshtml"); Assert.IsNull(partialView); - // fixed in 7.3 - 7.2.8 used to... - Assert.Throws(() => partialView = (PartialView)repository.Get("\\test-path-4.cshtml")); - Assert.Throws(() => partialView = (PartialView)repository.Get("../../packages.config")); + Assert.Throws(() => partialView = repository.Get("..\\test-path-4.cshtml")); + Assert.Throws(() => partialView = repository.Get("../../packages.config")); } } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ScriptRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ScriptRepositoryTest.cs index 28f9a9eff1..4721af14e1 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ScriptRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ScriptRepositoryTest.cs @@ -303,15 +303,22 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos Assert.AreEqual("path-2\\test-path-3.js".Replace("\\", $"{Path.DirectorySeparatorChar}"), script.Path); Assert.AreEqual("/scripts/path-2/test-path-3.js", script.VirtualPath); - script = new Script("\\test-path-4.js") { Content = "// script" }; - Assert.Throws(() => // fixed in 7.3 - 7.2.8 used to strip the \ + script = new Script("..\\test-path-4.js") { Content = "// script" }; + Assert.Throws(() => repository.Save(script)); + script = new Script("\\test-path-5.js") { Content = "// script" }; + repository.Save(script); + + script = repository.Get("\\test-path-5.js"); + Assert.IsNotNull(script); + Assert.AreEqual("test-path-5.js", script.Path); + Assert.AreEqual("/scripts/test-path-5.js", script.VirtualPath); + script = repository.Get("missing.js"); Assert.IsNull(script); - // fixed in 7.3 - 7.2.8 used to... - Assert.Throws(() => script = repository.Get("\\test-path-4.js")); + Assert.Throws(() => script = repository.Get("..\\test-path-4.js")); Assert.Throws(() => script = repository.Get("../packages.config")); } } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/StylesheetRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/StylesheetRepositoryTest.cs index d9bde0bca2..a32638ed4d 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/StylesheetRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/StylesheetRepositoryTest.cs @@ -275,7 +275,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos repository.Save(stylesheet); Assert.IsTrue(_fileSystem.FileExists("path-2/test-path-2.css")); - Assert.AreEqual("path-2\\test-path-2.css".Replace("\\", $"{Path.DirectorySeparatorChar}"), stylesheet.Path); // fixed in 7.3 - 7.2.8 does not update the path + Assert.AreEqual("path-2\\test-path-2.css".Replace("\\", $"{Path.DirectorySeparatorChar}"), stylesheet.Path); Assert.AreEqual("/css/path-2/test-path-2.css", stylesheet.VirtualPath); stylesheet = repository.Get("path-2/test-path-2.css"); @@ -300,17 +300,24 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos Assert.AreEqual("path-2\\test-path-3.css".Replace("\\", $"{Path.DirectorySeparatorChar}"), stylesheet.Path); Assert.AreEqual("/css/path-2/test-path-3.css", stylesheet.VirtualPath); - stylesheet = new Stylesheet("\\test-path-4.css") { Content = "body { color:#000; } .bold {font-weight:bold;}" }; - Assert.Throws(() => // fixed in 7.3 - 7.2.8 used to strip the \ + stylesheet = new Stylesheet("..\\test-path-4.css") { Content = "body { color:#000; } .bold {font-weight:bold;}" }; + Assert.Throws(() => repository.Save(stylesheet)); - // fixed in 7.3 - 7.2.8 used to throw + stylesheet = new Stylesheet("\\test-path-5.css") { Content = "body { color:#000; } .bold {font-weight:bold;}" }; + repository.Save(stylesheet); + + stylesheet = repository.Get("\\test-path-5.css"); + Assert.IsNotNull(stylesheet); + Assert.AreEqual("test-path-5.css", stylesheet.Path); + Assert.AreEqual("/css/test-path-5.css", stylesheet.VirtualPath); + stylesheet = repository.Get("missing.css"); Assert.IsNull(stylesheet); // #7713 changes behaviour to return null when outside the filesystem // to accomodate changing the CSS path and not flooding the backoffice with errors - stylesheet = repository.Get("\\test-path-4.css"); // outside the filesystem, does not exist + stylesheet = repository.Get("..\\test-path-4.css"); // outside the filesystem, does not exist Assert.IsNull(stylesheet); stylesheet = repository.Get("../packages.config"); // outside the filesystem, exists diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopeFileSystemsTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopeFileSystemsTests.cs index e30d7bbf55..7ea8e65eda 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopeFileSystemsTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopeFileSystemsTests.cs @@ -48,7 +48,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Scoping [Test] public void MediaFileManager_does_not_write_to_physical_file_system_when_scoped_if_scope_does_not_complete() { - string rootPath = HostingEnvironment.MapPathWebRoot(GlobalSettings.UmbracoMediaPath); + string rootPath = HostingEnvironment.MapPathWebRoot(GlobalSettings.UmbracoMediaPhysicalRootPath); string rootUrl = HostingEnvironment.ToAbsolute(GlobalSettings.UmbracoMediaPath); var physMediaFileSystem = new PhysicalFileSystem(IOHelper, HostingEnvironment, GetRequiredService>(), rootPath, rootUrl); MediaFileManager mediaFileManager = MediaFileManager; @@ -77,7 +77,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Scoping [Test] public void MediaFileManager_writes_to_physical_file_system_when_scoped_and_scope_is_completed() { - string rootPath = HostingEnvironment.MapPathWebRoot(GlobalSettings.UmbracoMediaPath); + string rootPath = HostingEnvironment.MapPathWebRoot(GlobalSettings.UmbracoMediaPhysicalRootPath); string rootUrl = HostingEnvironment.ToAbsolute(GlobalSettings.UmbracoMediaPath); var physMediaFileSystem = new PhysicalFileSystem(IOHelper, HostingEnvironment, GetRequiredService>(), rootPath, rootUrl); MediaFileManager mediaFileManager = MediaFileManager; @@ -108,7 +108,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Scoping [Test] public void MultiThread() { - string rootPath = HostingEnvironment.MapPathWebRoot(GlobalSettings.UmbracoMediaPath); + string rootPath = HostingEnvironment.MapPathWebRoot(GlobalSettings.UmbracoMediaPhysicalRootPath); string rootUrl = HostingEnvironment.ToAbsolute(GlobalSettings.UmbracoMediaPath); var physMediaFileSystem = new PhysicalFileSystem(IOHelper, HostingEnvironment, GetRequiredService>(), rootPath, rootUrl); MediaFileManager mediaFileManager = MediaFileManager; diff --git a/tests/Umbraco.Tests.UnitTests/TestHelpers/TestHelper.cs b/tests/Umbraco.Tests.UnitTests/TestHelpers/TestHelper.cs index 8dee7db12d..ba89c9775e 100644 --- a/tests/Umbraco.Tests.UnitTests/TestHelpers/TestHelper.cs +++ b/tests/Umbraco.Tests.UnitTests/TestHelpers/TestHelper.cs @@ -134,9 +134,9 @@ namespace Umbraco.Cms.Tests.UnitTests.TestHelpers /// public static string MapPathForTestFiles(string relativePath) => s_testHelperInternal.MapPathForTestFiles(relativePath); - public static void InitializeContentDirectories() => CreateDirectories(new[] { Constants.SystemDirectories.MvcViews, new GlobalSettings().UmbracoMediaPath, Constants.SystemDirectories.AppPlugins }); + public static void InitializeContentDirectories() => CreateDirectories(new[] { Constants.SystemDirectories.MvcViews, new GlobalSettings().UmbracoMediaPhysicalRootPath, Constants.SystemDirectories.AppPlugins }); - public static void CleanContentDirectories() => CleanDirectories(new[] { Constants.SystemDirectories.MvcViews, new GlobalSettings().UmbracoMediaPath }); + public static void CleanContentDirectories() => CleanDirectories(new[] { Constants.SystemDirectories.MvcViews, new GlobalSettings().UmbracoMediaPhysicalRootPath }); public static void CreateDirectories(string[] directories) { From 187cac469e4e8193057a682c49d80e07246684bb Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Fri, 7 Jan 2022 08:55:40 +0100 Subject: [PATCH 046/141] Add package tests (#11824) --- .../cypress/integration/Packages/packages.ts | 161 ++++++++++++++++++ .../cypress/plugins/index.js | 10 +- .../cypress/support/commands.js | 1 + .../package-lock.json | 12 +- .../Umbraco.Tests.AcceptanceTest/package.json | 3 +- .../tsconfig.json | 3 +- 6 files changed, 184 insertions(+), 6 deletions(-) create mode 100644 tests/Umbraco.Tests.AcceptanceTest/cypress/integration/Packages/packages.ts diff --git a/tests/Umbraco.Tests.AcceptanceTest/cypress/integration/Packages/packages.ts b/tests/Umbraco.Tests.AcceptanceTest/cypress/integration/Packages/packages.ts new file mode 100644 index 0000000000..80250502e6 --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/cypress/integration/Packages/packages.ts @@ -0,0 +1,161 @@ +/// +import { + ContentBuilder, + DocumentTypeBuilder + } from 'umbraco-cypress-testhelpers'; + +context('Packages', () => { + const packageName = "TestPackage"; + const rootDocTypeName = "Test document type"; + const nodeName = "1) Home"; + + beforeEach(() => { + cy.umbracoLogin(Cypress.env('username'), Cypress.env('password'), false); + }); + + function CreatePackage(contentId){ + const newPackage = { + id: 0, + packageGuid: "00000000-0000-0000-0000-000000000000", + name: "TestPackage", + packagePath: "", + contentLoadChildNodes: false, + contentNodeId: contentId, + macros: [], + languages: [], + dictionaryItems: [], + templates: [], + partialViews: [], + documentTypes: [], + mediaTypes: [], + stylesheets: [], + scripts: [], + dataTypes: [], + mediaUdis: [], + mediaLoadChildNodes: false + } + const url = "https://localhost:44331/umbraco/backoffice/umbracoapi/package/PostSavePackage"; + cy.umbracoApiRequest(url, 'POST', newPackage); + } + + function CreateSimplePackage(){ + + const rootDocType = new DocumentTypeBuilder() + .withName(rootDocTypeName) + .withAllowAsRoot(true) + .build(); + + cy.saveDocumentType(rootDocType).then((generatedRootDocType) => { + const rootDocTypeAlias = generatedRootDocType["alias"]; + + const rootContentNode = new ContentBuilder() + .withContentTypeAlias(rootDocTypeAlias) + .withAction("saveNew") + .addVariant() + .withName(nodeName) + .withSave(true) + .done() + .build(); + cy.saveContent(rootContentNode).then((generatedContent) => { + CreatePackage(generatedContent.Id); + }); + }); + } + + it('Creates a simple package', () => { + + cy.umbracoEnsurePackageNameNotExists(packageName); + cy.deleteAllContent(); + cy.umbracoEnsureDocumentTypeNameNotExists(rootDocTypeName); + + const rootDocType = new DocumentTypeBuilder() + .withName(rootDocTypeName) + .withAllowAsRoot(true) + .build(); + + cy.saveDocumentType(rootDocType).then((generatedRootDocType) => { + const rootDocTypeAlias = generatedRootDocType["alias"]; + + const rootContentNode = new ContentBuilder() + .withContentTypeAlias(rootDocTypeAlias) + .withAction("saveNew") + .addVariant() + .withName(nodeName) + .withSave(true) + .done() + .build(); + cy.saveContent(rootContentNode); + }); + + // Navigate to create package section + cy.umbracoSection('packages'); + cy.contains('Created').click(); + cy.contains('Create package').click(); + + // Fill out package creation form + cy.get('#headerName').should('be.visible'); + cy.wait(1000); + cy.get('#headerName').type(packageName); + cy.get('.controls > .umb-node-preview-add').click(); + cy.get('.umb-tree-item__label').first().click(); + cy.contains('Create').click(); + + // Navigate pack to packages and Assert the file is created + cy.umbracoSection('packages'); + cy.contains('Created').click(); + cy.contains(packageName).should('be.visible'); + + // Cleanup + cy.umbracoEnsurePackageNameNotExists(packageName); + cy.deleteAllContent(); + cy.umbracoEnsureDocumentTypeNameNotExists(rootDocTypeName); + }); + + it('Delete package', () => { + + // Ensure cleanup before test + cy.deleteAllContent(); + cy.umbracoEnsureDocumentTypeNameNotExists(rootDocTypeName); + cy.umbracoEnsurePackageNameNotExists(packageName); + + CreateSimplePackage(); + + // Navigate to create package section + cy.umbracoSection('packages'); + cy.contains('Created').click(); + cy.contains('Delete').click(); + cy.contains('Yes, delete').click(); + + // Assert + cy.contains('TestPackage').should('not.exist'); + + // Cleanup + cy.deleteAllContent(); + cy.umbracoEnsureDocumentTypeNameNotExists(rootDocTypeName); + cy.umbracoEnsurePackageNameNotExists(packageName); + }); + + it('Download package', () => { + + // Ensure cleanup before test + cy.deleteAllContent(); + cy.umbracoEnsureDocumentTypeNameNotExists(rootDocTypeName); + cy.umbracoEnsurePackageNameNotExists(packageName); + + CreateSimplePackage(); + + // Navigate to package and download + cy.umbracoSection('packages'); + cy.contains('Created').click(); + cy.contains('TestPackage').click(); + cy.contains('Download').click(); + + // Assert + cy.verifyDownload('package.xml'); + + // Cleanup + cy.deleteAllContent(); + cy.umbracoEnsureDocumentTypeNameNotExists(rootDocTypeName); + cy.umbracoEnsurePackageNameNotExists(packageName); + }); +}); \ No newline at end of file diff --git a/tests/Umbraco.Tests.AcceptanceTest/cypress/plugins/index.js b/tests/Umbraco.Tests.AcceptanceTest/cypress/plugins/index.js index 51b79a1fef..30988b82cf 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/cypress/plugins/index.js +++ b/tests/Umbraco.Tests.AcceptanceTest/cypress/plugins/index.js @@ -12,6 +12,7 @@ // This function is called when a project is opened or re-opened (e.g. due to // the project's config changing) const del = require('del') +const { isFileExist } = require('cy-verify-downloads'); /** * @type {Cypress.PluginConfig} @@ -25,6 +26,7 @@ module.exports = (on, config) => { config.baseUrl = baseUrl; } + on('task', { isFileExist }) on('after:spec', (spec, results) => { if(results.stats.failures === 0 && results.video) { // `del()` returns a promise, so it's important to return it to ensure @@ -33,5 +35,11 @@ module.exports = (on, config) => { } }) + +on('task', { + isFileExist +}); + + return config; -} +} \ No newline at end of file diff --git a/tests/Umbraco.Tests.AcceptanceTest/cypress/support/commands.js b/tests/Umbraco.Tests.AcceptanceTest/cypress/support/commands.js index 4a2a6d31a2..5056c05036 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/cypress/support/commands.js +++ b/tests/Umbraco.Tests.AcceptanceTest/cypress/support/commands.js @@ -27,6 +27,7 @@ import {Command} from 'umbraco-cypress-testhelpers'; import {Chainable} from './chainable'; import { JsonHelper } from 'umbraco-cypress-testhelpers'; +require('cy-verify-downloads').addCustomCommand(); new Chainable(); new Command().registerCypressCommands(); diff --git a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json index a4636d990a..7ada1d9fb7 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json +++ b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json @@ -453,6 +453,12 @@ "which": "^2.0.1" } }, + "cy-verify-downloads": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/cy-verify-downloads/-/cy-verify-downloads-0.0.5.tgz", + "integrity": "sha512-aRK7VvKG5rmDJK4hjZ27KM2oOOz0cMO7z/j4zX8qCc4ffXZS1XRJkofUY0w5u6MCB/wUsNMs03VuvkeR2tNPoQ==", + "dev": true + }, "cycle": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz", @@ -1622,9 +1628,9 @@ "integrity": "sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q==" }, "umbraco-cypress-testhelpers": { - "version": "1.0.0-beta-62", - "resolved": "https://registry.npmjs.org/umbraco-cypress-testhelpers/-/umbraco-cypress-testhelpers-1.0.0-beta-62.tgz", - "integrity": "sha512-fVjXBdotb2TZrhaWq/HtD1cy+7scHcldJL+HRHeyYtwvUp368lUiAMrx0y4TOZTwBOJ898yyl8yTEPASfAX2pQ==", + "version": "1.0.0-beta-63", + "resolved": "https://registry.npmjs.org/umbraco-cypress-testhelpers/-/umbraco-cypress-testhelpers-1.0.0-beta-63.tgz", + "integrity": "sha512-X+DHWktfB+WBb7YrxvpneVfS1PATx2zPYMdkeZTmtoQEeyGxXA9fW6P712/AUbyGAhRhH+46t4cAINdWJxItug==", "dev": true, "requires": { "camelize": "^1.0.0", diff --git a/tests/Umbraco.Tests.AcceptanceTest/package.json b/tests/Umbraco.Tests.AcceptanceTest/package.json index a44877e703..a95a71020f 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/package.json +++ b/tests/Umbraco.Tests.AcceptanceTest/package.json @@ -10,10 +10,11 @@ "devDependencies": { "cross-env": "^7.0.2", "cypress": "8.4.1", + "cy-verify-downloads": "0.0.5", "del": "^6.0.0", "ncp": "^2.0.0", "prompt": "^1.2.0", - "umbraco-cypress-testhelpers": "^1.0.0-beta-62" + "umbraco-cypress-testhelpers": "^1.0.0-beta-63" }, "dependencies": { "typescript": "^3.9.2" diff --git a/tests/Umbraco.Tests.AcceptanceTest/tsconfig.json b/tests/Umbraco.Tests.AcceptanceTest/tsconfig.json index 6cb05bfcc7..96178bfc54 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tsconfig.json +++ b/tests/Umbraco.Tests.AcceptanceTest/tsconfig.json @@ -14,7 +14,8 @@ "target": "es5", "types": [ - "cypress" + "cypress", + "cy-verify-downloads" ], "lib": [ "es5", From 2155062678e5366d26330ab20262dd11f6ab02af Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Mon, 10 Jan 2022 09:32:29 +0100 Subject: [PATCH 047/141] Prune/remove indentation from JSON property values (#11806) * Use Formatting.None for JSON property data in default value editor * Use Formatting.None for JSON property data in custom value editors * Use Formatting.None for JSON property data in Nested Content and Block List * Use Formatting.None for JSON property tags * Use Formatting.None for JSON configuration data * Use Formatting.None in custom JSON converter * Ensure empty tags and complex editor values are not stored * Fix NestedContentPropertyComponentTests * Do not store empty property data * Use Formatting.None and don't store configured crops (without coordinates) * Fix JSON deserialization of tags value --- .../Models/PropertyTagsExtensions.cs | 19 +- ...omplexPropertyEditorContentEventHandler.cs | 8 +- .../PropertyEditors/ConfigurationEditor.cs | 2 + .../PropertyEditors/DataValueEditor.cs | 16 +- .../Serialization/JsonToStringConverter.cs | 3 +- .../BlockEditorComponentTests.cs | 3 +- .../NestedContentPropertyComponentTests.cs | 417 +++++++++--------- .../Compose/BlockEditorComponent.cs | 2 +- .../Compose/NestedContentPropertyComponent.cs | 10 +- .../BlockEditorPropertyEditor.cs | 2 +- .../ColorPickerConfigurationEditor.cs | 2 +- .../PropertyEditors/GridPropertyEditor.cs | 2 +- .../ImageCropperPropertyEditor.cs | 23 +- .../ImageCropperPropertyValueEditor.cs | 4 + .../MultiUrlPickerValueEditor.cs | 14 +- .../MultipleTextStringPropertyEditor.cs | 2 +- .../PropertyEditors/MultipleValueEditor.cs | 8 +- .../NestedContentPropertyEditor.cs | 9 +- .../PropertyEditors/RichTextPropertyEditor.cs | 2 +- .../PropertyEditors/TagsPropertyEditor.cs | 2 +- 20 files changed, 281 insertions(+), 269 deletions(-) diff --git a/src/Umbraco.Core/Models/PropertyTagsExtensions.cs b/src/Umbraco.Core/Models/PropertyTagsExtensions.cs index 63cf870221..c97bf4c66a 100644 --- a/src/Umbraco.Core/Models/PropertyTagsExtensions.cs +++ b/src/Umbraco.Core/Models/PropertyTagsExtensions.cs @@ -68,11 +68,13 @@ namespace Umbraco.Core.Models switch (storageType) { case TagsStorageType.Csv: - property.SetValue(string.Join(delimiter.ToString(), currentTags.Union(trimmedTags)), culture); // csv string + property.SetValue(string.Join(delimiter.ToString(), currentTags.Union(trimmedTags)).NullOrWhiteSpaceAsNull(), culture); // csv string break; case TagsStorageType.Json: - property.SetValue(JsonConvert.SerializeObject(currentTags.Union(trimmedTags).ToArray()), culture); // json array + var updatedTags = currentTags.Union(trimmedTags).ToArray(); + var updatedValue = updatedTags.Length == 0 ? null : JsonConvert.SerializeObject(updatedTags, Formatting.None); + property.SetValue(updatedValue, culture); // json array break; } } @@ -81,11 +83,12 @@ namespace Umbraco.Core.Models switch (storageType) { case TagsStorageType.Csv: - property.SetValue(string.Join(delimiter.ToString(), trimmedTags), culture); // csv string + property.SetValue(string.Join(delimiter.ToString(), trimmedTags).NullOrWhiteSpaceAsNull(), culture); // csv string break; case TagsStorageType.Json: - property.SetValue(JsonConvert.SerializeObject(trimmedTags), culture); // json array + var updatedValue = trimmedTags.Length == 0 ? null : JsonConvert.SerializeObject(trimmedTags, Formatting.None); + property.SetValue(updatedValue, culture); // json array break; } } @@ -121,11 +124,13 @@ namespace Umbraco.Core.Models switch (storageType) { case TagsStorageType.Csv: - property.SetValue(string.Join(delimiter.ToString(), currentTags.Except(trimmedTags)), culture); // csv string + property.SetValue(string.Join(delimiter.ToString(), currentTags.Except(trimmedTags)).NullOrWhiteSpaceAsNull(), culture); // csv string break; case TagsStorageType.Json: - property.SetValue(JsonConvert.SerializeObject(currentTags.Except(trimmedTags).ToArray()), culture); // json array + var updatedTags = currentTags.Except(trimmedTags).ToArray(); + var updatedValue = updatedTags.Length == 0 ? null : JsonConvert.SerializeObject(updatedTags, Formatting.None); + property.SetValue(updatedValue, culture); // json array break; } } @@ -157,7 +162,7 @@ namespace Umbraco.Core.Models case TagsStorageType.Json: try { - return JsonConvert.DeserializeObject(value).Select(x => x.ToString().Trim()); + return JsonConvert.DeserializeObject(value).Select(x => x.Trim()); } catch (JsonException) { diff --git a/src/Umbraco.Core/PropertyEditors/ComplexPropertyEditorContentEventHandler.cs b/src/Umbraco.Core/PropertyEditors/ComplexPropertyEditorContentEventHandler.cs index 2b819d4555..f0876acb9b 100644 --- a/src/Umbraco.Core/PropertyEditors/ComplexPropertyEditorContentEventHandler.cs +++ b/src/Umbraco.Core/PropertyEditors/ComplexPropertyEditorContentEventHandler.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using Umbraco.Core.Events; using Umbraco.Core.Models; using Umbraco.Core.Services; @@ -59,11 +61,13 @@ namespace Umbraco.Core.PropertyEditors foreach (var cultureVal in propVals) { // Remove keys from published value & any nested properties - var updatedPublishedVal = _formatPropertyValue(cultureVal.PublishedValue?.ToString(), onlyMissingKeys); + var publishedValue = cultureVal.PublishedValue is JToken jsonPublishedValue ? jsonPublishedValue.ToString(Formatting.None) : cultureVal.PublishedValue?.ToString(); + var updatedPublishedVal = _formatPropertyValue(publishedValue, onlyMissingKeys).NullOrWhiteSpaceAsNull(); cultureVal.PublishedValue = updatedPublishedVal; // Remove keys from edited/draft value & any nested properties - var updatedEditedVal = _formatPropertyValue(cultureVal.EditedValue?.ToString(), onlyMissingKeys); + var editedValue = cultureVal.EditedValue is JToken jsonEditedValue ? jsonEditedValue.ToString(Formatting.None) : cultureVal.EditedValue?.ToString(); + var updatedEditedVal = _formatPropertyValue(editedValue, onlyMissingKeys).NullOrWhiteSpaceAsNull(); cultureVal.EditedValue = updatedEditedVal; } } diff --git a/src/Umbraco.Core/PropertyEditors/ConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/ConfigurationEditor.cs index 8151753a43..82a23847a3 100644 --- a/src/Umbraco.Core/PropertyEditors/ConfigurationEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/ConfigurationEditor.cs @@ -126,6 +126,8 @@ namespace Umbraco.Core.PropertyEditors /// public static JsonSerializerSettings ConfigurationJsonSettings { get; } = new JsonSerializerSettings { + Formatting = Formatting.None, + NullValueHandling = NullValueHandling.Ignore, ContractResolver = new ConfigurationCustomContractResolver(), Converters = new List(new[]{new FuzzyBooleanConverter()}) }; diff --git a/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs b/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs index fbcd5ec440..2484e8f830 100644 --- a/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs @@ -145,8 +145,18 @@ namespace Umbraco.Core.PropertyEditors /// internal Attempt TryConvertValueToCrlType(object value) { - if (value is JValue) - value = value.ToString(); + if (value is JToken jsonValue) + { + if (jsonValue is JContainer && jsonValue.HasValues == false) + { + // Empty JSON array/object + value = null; + } + else + { + value = jsonValue.ToString(Formatting.None); + } + } //this is a custom check to avoid any errors, if it's a string and it's empty just make it null if (value is string s && string.IsNullOrWhiteSpace(s)) @@ -187,6 +197,7 @@ namespace Umbraco.Core.PropertyEditors default: throw new ArgumentOutOfRangeException(); } + return value.TryConvertTo(valueType); } @@ -222,6 +233,7 @@ namespace Umbraco.Core.PropertyEditors Current.Logger.Warn("The value {EditorValue} cannot be converted to the type {StorageTypeValue}", editorValue.Value, ValueTypes.ToStorageType(ValueType)); return null; } + return result.Result; } diff --git a/src/Umbraco.Core/Serialization/JsonToStringConverter.cs b/src/Umbraco.Core/Serialization/JsonToStringConverter.cs index 08c9a44d00..26d73b60ef 100644 --- a/src/Umbraco.Core/Serialization/JsonToStringConverter.cs +++ b/src/Umbraco.Core/Serialization/JsonToStringConverter.cs @@ -20,9 +20,10 @@ namespace Umbraco.Core.Serialization { return reader.Value; } + // Load JObject from stream JObject jObject = JObject.Load(reader); - return jObject.ToString(); + return jObject.ToString(Formatting.None); } public override bool CanConvert(Type objectType) diff --git a/src/Umbraco.Tests/PropertyEditors/BlockEditorComponentTests.cs b/src/Umbraco.Tests/PropertyEditors/BlockEditorComponentTests.cs index bfd8b8c77b..3fce47b718 100644 --- a/src/Umbraco.Tests/PropertyEditors/BlockEditorComponentTests.cs +++ b/src/Umbraco.Tests/PropertyEditors/BlockEditorComponentTests.cs @@ -14,8 +14,7 @@ namespace Umbraco.Tests.PropertyEditors private readonly JsonSerializerSettings _serializerSettings = new JsonSerializerSettings { Formatting = Formatting.None, - NullValueHandling = NullValueHandling.Ignore, - + NullValueHandling = NullValueHandling.Ignore }; private const string _contentGuid1 = "036ce82586a64dfba2d523a99ed80f58"; diff --git a/src/Umbraco.Tests/PropertyEditors/NestedContentPropertyComponentTests.cs b/src/Umbraco.Tests/PropertyEditors/NestedContentPropertyComponentTests.cs index 5b7e220123..75c4403e2b 100644 --- a/src/Umbraco.Tests/PropertyEditors/NestedContentPropertyComponentTests.cs +++ b/src/Umbraco.Tests/PropertyEditors/NestedContentPropertyComponentTests.cs @@ -1,10 +1,7 @@ -using Newtonsoft.Json; +using System; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using NUnit.Framework; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Umbraco.Web.Compose; namespace Umbraco.Tests.PropertyEditors @@ -13,6 +10,11 @@ namespace Umbraco.Tests.PropertyEditors [TestFixture] public class NestedContentPropertyComponentTests { + private static void AreEqualJson(string expected, string actual) + { + Assert.AreEqual(JToken.Parse(expected), JToken.Parse(actual)); + } + [Test] public void Invalid_Json() { @@ -29,17 +31,18 @@ namespace Umbraco.Tests.PropertyEditors Func guidFactory = () => guids[guidCounter++]; var json = @"[ - {""key"":""04a6dba8-813c-4144-8aca-86a3f24ebf08"",""name"":""Item 1"",""ncContentTypeAlias"":""nested"",""text"":""woot""}, - {""key"":""d8e214d8-c5a5-4b45-9b51-4050dd47f5fa"",""name"":""Item 2"",""ncContentTypeAlias"":""nested"",""text"":""zoot""} -]"; + {""key"":""04a6dba8-813c-4144-8aca-86a3f24ebf08"",""name"":""Item 1"",""ncContentTypeAlias"":""nested"",""text"":""woot""}, + {""key"":""d8e214d8-c5a5-4b45-9b51-4050dd47f5fa"",""name"":""Item 2"",""ncContentTypeAlias"":""nested"",""text"":""zoot""} + ]"; + var expected = json .Replace("04a6dba8-813c-4144-8aca-86a3f24ebf08", guids[0].ToString()) .Replace("d8e214d8-c5a5-4b45-9b51-4050dd47f5fa", guids[1].ToString()); var component = new NestedContentPropertyComponent(); - var result = component.CreateNestedContentKeys(json, false, guidFactory); + var actual = component.CreateNestedContentKeys(json, false, guidFactory); - Assert.AreEqual(JsonConvert.DeserializeObject(expected).ToString(), JsonConvert.DeserializeObject(result).ToString()); + AreEqualJson(expected, actual); } [Test] @@ -50,29 +53,27 @@ namespace Umbraco.Tests.PropertyEditors Func guidFactory = () => guids[guidCounter++]; var json = @"[{ - ""key"": ""04a6dba8-813c-4144-8aca-86a3f24ebf08"", - ""name"": ""Item 1"", - ""ncContentTypeAlias"": ""text"", - ""text"": ""woot"" - }, { - ""key"": ""d8e214d8-c5a5-4b45-9b51-4050dd47f5fa"", - ""name"": ""Item 2"", - ""ncContentTypeAlias"": ""list"", - ""text"": ""zoot"", - ""subItems"": [{ - ""key"": ""dccf550c-3a05-469e-95e1-a8f560f788c2"", - ""name"": ""Item 1"", - ""ncContentTypeAlias"": ""text"", - ""text"": ""woot"" - }, { - ""key"": ""fbde4288-8382-4e13-8933-ed9c160de050"", - ""name"": ""Item 2"", - ""ncContentTypeAlias"": ""text"", - ""text"": ""zoot"" - } - ] - } -]"; + ""key"": ""04a6dba8-813c-4144-8aca-86a3f24ebf08"", + ""name"": ""Item 1"", + ""ncContentTypeAlias"": ""text"", + ""text"": ""woot"" + }, { + ""key"": ""d8e214d8-c5a5-4b45-9b51-4050dd47f5fa"", + ""name"": ""Item 2"", + ""ncContentTypeAlias"": ""list"", + ""text"": ""zoot"", + ""subItems"": [{ + ""key"": ""dccf550c-3a05-469e-95e1-a8f560f788c2"", + ""name"": ""Item 1"", + ""ncContentTypeAlias"": ""text"", + ""text"": ""woot"" + }, { + ""key"": ""fbde4288-8382-4e13-8933-ed9c160de050"", + ""name"": ""Item 2"", + ""ncContentTypeAlias"": ""text"", + ""text"": ""zoot"" + }] + }]"; var expected = json .Replace("04a6dba8-813c-4144-8aca-86a3f24ebf08", guids[0].ToString()) @@ -81,9 +82,9 @@ namespace Umbraco.Tests.PropertyEditors .Replace("fbde4288-8382-4e13-8933-ed9c160de050", guids[3].ToString()); var component = new NestedContentPropertyComponent(); - var result = component.CreateNestedContentKeys(json, false, guidFactory); + var actual = component.CreateNestedContentKeys(json, false, guidFactory); - Assert.AreEqual(JsonConvert.DeserializeObject(expected).ToString(), JsonConvert.DeserializeObject(result).ToString()); + AreEqualJson(expected, actual); } [Test] @@ -95,7 +96,8 @@ namespace Umbraco.Tests.PropertyEditors // we need to ensure the escaped json is consistent with how it will be re-escaped after parsing // and this is how to do that, the result will also include quotes around it. - var subJsonEscaped = JsonConvert.ToString(JsonConvert.DeserializeObject(@"[{ + var subJsonEscaped = JsonConvert.ToString(JToken.Parse(@" + [{ ""key"": ""dccf550c-3a05-469e-95e1-a8f560f788c2"", ""name"": ""Item 1"", ""ncContentTypeAlias"": ""text"", @@ -105,22 +107,20 @@ namespace Umbraco.Tests.PropertyEditors ""name"": ""Item 2"", ""ncContentTypeAlias"": ""text"", ""text"": ""zoot"" - } - ]").ToString()); + }]").ToString(Formatting.None)); var json = @"[{ - ""key"": ""04a6dba8-813c-4144-8aca-86a3f24ebf08"", - ""name"": ""Item 1"", - ""ncContentTypeAlias"": ""text"", - ""text"": ""woot"" - }, { - ""key"": ""d8e214d8-c5a5-4b45-9b51-4050dd47f5fa"", - ""name"": ""Item 2"", - ""ncContentTypeAlias"": ""list"", - ""text"": ""zoot"", - ""subItems"":" + subJsonEscaped + @" - } -]"; + ""key"": ""04a6dba8-813c-4144-8aca-86a3f24ebf08"", + ""name"": ""Item 1"", + ""ncContentTypeAlias"": ""text"", + ""text"": ""woot"" + }, { + ""key"": ""d8e214d8-c5a5-4b45-9b51-4050dd47f5fa"", + ""name"": ""Item 2"", + ""ncContentTypeAlias"": ""list"", + ""text"": ""zoot"", + ""subItems"":" + subJsonEscaped + @" + }]"; var expected = json .Replace("04a6dba8-813c-4144-8aca-86a3f24ebf08", guids[0].ToString()) @@ -129,9 +129,9 @@ namespace Umbraco.Tests.PropertyEditors .Replace("fbde4288-8382-4e13-8933-ed9c160de050", guids[3].ToString()); var component = new NestedContentPropertyComponent(); - var result = component.CreateNestedContentKeys(json, false, guidFactory); + var actual = component.CreateNestedContentKeys(json, false, guidFactory); - Assert.AreEqual(JsonConvert.DeserializeObject(expected).ToString(), JsonConvert.DeserializeObject(result).ToString()); + AreEqualJson(expected, actual); } [Test] @@ -143,7 +143,7 @@ namespace Umbraco.Tests.PropertyEditors // we need to ensure the escaped json is consistent with how it will be re-escaped after parsing // and this is how to do that, the result will also include quotes around it. - var subJsonEscaped = JsonConvert.ToString(JsonConvert.DeserializeObject(@"[{ + var subJsonEscaped = JsonConvert.ToString(JToken.Parse(@"[{ ""key"": ""dccf550c-3a05-469e-95e1-a8f560f788c2"", ""name"": ""Item 1"", ""ncContentTypeAlias"": ""text"", @@ -153,79 +153,74 @@ namespace Umbraco.Tests.PropertyEditors ""name"": ""Item 2"", ""ncContentTypeAlias"": ""text"", ""text"": ""zoot"" - } - ]").ToString()); + }]").ToString(Formatting.None)); // Complex editor such as the grid var complexEditorJsonEscaped = @"{ - ""name"": ""1 column layout"", - ""sections"": [ - { - ""grid"": ""12"", - ""rows"": [ - { - ""name"": ""Article"", - ""id"": ""b4f6f651-0de3-ef46-e66a-464f4aaa9c57"", - ""areas"": [ - { - ""grid"": ""4"", - ""controls"": [ + ""name"": ""1 column layout"", + ""sections"": [ { - ""value"": ""I am quote"", - ""editor"": { - ""alias"": ""quote"", - ""view"": ""textstring"" - }, - ""styles"": null, - ""config"": null - }], - ""styles"": null, - ""config"": null - }, - { - ""grid"": ""8"", - ""controls"": [ - { - ""value"": ""Header"", - ""editor"": { - ""alias"": ""headline"", - ""view"": ""textstring"" - }, - ""styles"": null, - ""config"": null - }, - { - ""value"": " + subJsonEscaped + @", - ""editor"": { - ""alias"": ""madeUpNestedContent"", - ""view"": ""madeUpNestedContentInGrid"" - }, - ""styles"": null, - ""config"": null - }], - ""styles"": null, - ""config"": null - }], - ""styles"": null, - ""config"": null - }] - }] -}"; - + ""grid"": ""12"", + ""rows"": [ + { + ""name"": ""Article"", + ""id"": ""b4f6f651-0de3-ef46-e66a-464f4aaa9c57"", + ""areas"": [ + { + ""grid"": ""4"", + ""controls"": [{ + ""value"": ""I am quote"", + ""editor"": { + ""alias"": ""quote"", + ""view"": ""textstring"" + }, + ""styles"": null, + ""config"": null + }], + ""styles"": null, + ""config"": null + }, + { + ""grid"": ""8"", + ""controls"": [{ + ""value"": ""Header"", + ""editor"": { + ""alias"": ""headline"", + ""view"": ""textstring"" + }, + ""styles"": null, + ""config"": null + }, + { + ""value"": " + subJsonEscaped + @", + ""editor"": { + ""alias"": ""madeUpNestedContent"", + ""view"": ""madeUpNestedContentInGrid"" + }, + ""styles"": null, + ""config"": null + }], + ""styles"": null, + ""config"": null + }], + ""styles"": null, + ""config"": null + }] + }] + }"; var json = @"[{ - ""key"": ""04a6dba8-813c-4144-8aca-86a3f24ebf08"", - ""name"": ""Item 1"", - ""ncContentTypeAlias"": ""text"", - ""text"": ""woot"" - }, { - ""key"": ""d8e214d8-c5a5-4b45-9b51-4050dd47f5fa"", - ""name"": ""Item 2"", - ""ncContentTypeAlias"": ""list"", - ""text"": ""zoot"", - ""subItems"":" + complexEditorJsonEscaped + @" - } -]"; + ""key"": ""04a6dba8-813c-4144-8aca-86a3f24ebf08"", + ""name"": ""Item 1"", + ""ncContentTypeAlias"": ""text"", + ""text"": ""woot"" + }, { + ""key"": ""d8e214d8-c5a5-4b45-9b51-4050dd47f5fa"", + ""name"": ""Item 2"", + ""ncContentTypeAlias"": ""list"", + ""text"": ""zoot"", + ""subItems"":" + complexEditorJsonEscaped + @" + }]"; var expected = json .Replace("04a6dba8-813c-4144-8aca-86a3f24ebf08", guids[0].ToString()) @@ -234,12 +229,11 @@ namespace Umbraco.Tests.PropertyEditors .Replace("fbde4288-8382-4e13-8933-ed9c160de050", guids[3].ToString()); var component = new NestedContentPropertyComponent(); - var result = component.CreateNestedContentKeys(json, false, guidFactory); + var actual = component.CreateNestedContentKeys(json, false, guidFactory); - Assert.AreEqual(JsonConvert.DeserializeObject(expected).ToString(), JsonConvert.DeserializeObject(result).ToString()); + AreEqualJson(expected, actual); } - [Test] public void No_Nesting_Generates_Keys_For_Missing_Items() { @@ -248,18 +242,18 @@ namespace Umbraco.Tests.PropertyEditors Func guidFactory = () => guids[guidCounter++]; var json = @"[ - {""key"":""04a6dba8-813c-4144-8aca-86a3f24ebf08"",""name"":""Item 1 my key wont change"",""ncContentTypeAlias"":""nested"",""text"":""woot""}, - {""name"":""Item 2 was copied and has no key prop"",""ncContentTypeAlias"":""nested"",""text"":""zoot""} -]"; + {""key"":""04a6dba8-813c-4144-8aca-86a3f24ebf08"",""name"":""Item 1 my key wont change"",""ncContentTypeAlias"":""nested"",""text"":""woot""}, + {""name"":""Item 2 was copied and has no key prop"",""ncContentTypeAlias"":""nested"",""text"":""zoot""} + ]"; var component = new NestedContentPropertyComponent(); var result = component.CreateNestedContentKeys(json, true, guidFactory); // Ensure the new GUID is put in a key into the JSON - Assert.IsTrue(JsonConvert.DeserializeObject(result).ToString().Contains(guids[0].ToString())); + Assert.IsTrue(result.Contains(guids[0].ToString())); // Ensure that the original key is NOT changed/modified & still exists - Assert.IsTrue(JsonConvert.DeserializeObject(result).ToString().Contains("04a6dba8-813c-4144-8aca-86a3f24ebf08")); + Assert.IsTrue(result.Contains("04a6dba8-813c-4144-8aca-86a3f24ebf08")); } [Test] @@ -271,7 +265,7 @@ namespace Umbraco.Tests.PropertyEditors // we need to ensure the escaped json is consistent with how it will be re-escaped after parsing // and this is how to do that, the result will also include quotes around it. - var subJsonEscaped = JsonConvert.ToString(JsonConvert.DeserializeObject(@"[{ + var subJsonEscaped = JsonConvert.ToString(JToken.Parse(@"[{ ""name"": ""Item 1"", ""ncContentTypeAlias"": ""text"", ""text"": ""woot"" @@ -279,29 +273,27 @@ namespace Umbraco.Tests.PropertyEditors ""name"": ""Nested Item 2 was copied and has no key"", ""ncContentTypeAlias"": ""text"", ""text"": ""zoot"" - } - ]").ToString()); + }]").ToString(Formatting.None)); var json = @"[{ - ""name"": ""Item 1 was copied and has no key"", - ""ncContentTypeAlias"": ""text"", - ""text"": ""woot"" - }, { - ""key"": ""d8e214d8-c5a5-4b45-9b51-4050dd47f5fa"", - ""name"": ""Item 2"", - ""ncContentTypeAlias"": ""list"", - ""text"": ""zoot"", - ""subItems"":" + subJsonEscaped + @" - } -]"; + ""name"": ""Item 1 was copied and has no key"", + ""ncContentTypeAlias"": ""text"", + ""text"": ""woot"" + }, { + ""key"": ""d8e214d8-c5a5-4b45-9b51-4050dd47f5fa"", + ""name"": ""Item 2"", + ""ncContentTypeAlias"": ""list"", + ""text"": ""zoot"", + ""subItems"":" + subJsonEscaped + @" + }]"; var component = new NestedContentPropertyComponent(); var result = component.CreateNestedContentKeys(json, true, guidFactory); // Ensure the new GUID is put in a key into the JSON for each item - Assert.IsTrue(JsonConvert.DeserializeObject(result).ToString().Contains(guids[0].ToString())); - Assert.IsTrue(JsonConvert.DeserializeObject(result).ToString().Contains(guids[1].ToString())); - Assert.IsTrue(JsonConvert.DeserializeObject(result).ToString().Contains(guids[2].ToString())); + Assert.IsTrue(result.Contains(guids[0].ToString())); + Assert.IsTrue(result.Contains(guids[1].ToString())); + Assert.IsTrue(result.Contains(guids[2].ToString())); } [Test] @@ -313,7 +305,7 @@ namespace Umbraco.Tests.PropertyEditors // we need to ensure the escaped json is consistent with how it will be re-escaped after parsing // and this is how to do that, the result will also include quotes around it. - var subJsonEscaped = JsonConvert.ToString(JsonConvert.DeserializeObject(@"[{ + var subJsonEscaped = JsonConvert.ToString(JToken.Parse(@"[{ ""key"": ""dccf550c-3a05-469e-95e1-a8f560f788c2"", ""name"": ""Item 1"", ""ncContentTypeAlias"": ""text"", @@ -322,85 +314,80 @@ namespace Umbraco.Tests.PropertyEditors ""name"": ""Nested Item 2 was copied and has no key"", ""ncContentTypeAlias"": ""text"", ""text"": ""zoot"" - } - ]").ToString()); + }]").ToString(Formatting.None)); // Complex editor such as the grid var complexEditorJsonEscaped = @"{ - ""name"": ""1 column layout"", - ""sections"": [ - { - ""grid"": ""12"", - ""rows"": [ - { - ""name"": ""Article"", - ""id"": ""b4f6f651-0de3-ef46-e66a-464f4aaa9c57"", - ""areas"": [ - { - ""grid"": ""4"", - ""controls"": [ + ""name"": ""1 column layout"", + ""sections"": [ { - ""value"": ""I am quote"", - ""editor"": { - ""alias"": ""quote"", - ""view"": ""textstring"" - }, - ""styles"": null, - ""config"": null - }], - ""styles"": null, - ""config"": null - }, - { - ""grid"": ""8"", - ""controls"": [ - { - ""value"": ""Header"", - ""editor"": { - ""alias"": ""headline"", - ""view"": ""textstring"" - }, - ""styles"": null, - ""config"": null - }, - { - ""value"": " + subJsonEscaped + @", - ""editor"": { - ""alias"": ""madeUpNestedContent"", - ""view"": ""madeUpNestedContentInGrid"" - }, - ""styles"": null, - ""config"": null - }], - ""styles"": null, - ""config"": null - }], - ""styles"": null, - ""config"": null - }] - }] -}"; - + ""grid"": ""12"", + ""rows"": [ + { + ""name"": ""Article"", + ""id"": ""b4f6f651-0de3-ef46-e66a-464f4aaa9c57"", + ""areas"": [ + { + ""grid"": ""4"", + ""controls"": [{ + ""value"": ""I am quote"", + ""editor"": { + ""alias"": ""quote"", + ""view"": ""textstring"" + }, + ""styles"": null, + ""config"": null + }], + ""styles"": null, + ""config"": null + }, + { + ""grid"": ""8"", + ""controls"": [{ + ""value"": ""Header"", + ""editor"": { + ""alias"": ""headline"", + ""view"": ""textstring"" + }, + ""styles"": null, + ""config"": null + }, + { + ""value"": " + subJsonEscaped + @", + ""editor"": { + ""alias"": ""madeUpNestedContent"", + ""view"": ""madeUpNestedContentInGrid"" + }, + ""styles"": null, + ""config"": null + }], + ""styles"": null, + ""config"": null + }], + ""styles"": null, + ""config"": null + }] + }] + }"; var json = @"[{ - ""key"": ""04a6dba8-813c-4144-8aca-86a3f24ebf08"", - ""name"": ""Item 1"", - ""ncContentTypeAlias"": ""text"", - ""text"": ""woot"" - }, { - ""name"": ""Item 2 was copied and has no key"", - ""ncContentTypeAlias"": ""list"", - ""text"": ""zoot"", - ""subItems"":" + complexEditorJsonEscaped + @" - } -]"; + ""key"": ""04a6dba8-813c-4144-8aca-86a3f24ebf08"", + ""name"": ""Item 1"", + ""ncContentTypeAlias"": ""text"", + ""text"": ""woot"" + }, { + ""name"": ""Item 2 was copied and has no key"", + ""ncContentTypeAlias"": ""list"", + ""text"": ""zoot"", + ""subItems"":" + complexEditorJsonEscaped + @" + }]"; var component = new NestedContentPropertyComponent(); var result = component.CreateNestedContentKeys(json, true, guidFactory); // Ensure the new GUID is put in a key into the JSON for each item - Assert.IsTrue(JsonConvert.DeserializeObject(result).ToString().Contains(guids[0].ToString())); - Assert.IsTrue(JsonConvert.DeserializeObject(result).ToString().Contains(guids[1].ToString())); + Assert.IsTrue(result.Contains(guids[0].ToString())); + Assert.IsTrue(result.Contains(guids[1].ToString())); } } } diff --git a/src/Umbraco.Web/Compose/BlockEditorComponent.cs b/src/Umbraco.Web/Compose/BlockEditorComponent.cs index ac92aa6918..a2dd772cf7 100644 --- a/src/Umbraco.Web/Compose/BlockEditorComponent.cs +++ b/src/Umbraco.Web/Compose/BlockEditorComponent.cs @@ -63,7 +63,7 @@ namespace Umbraco.Web.Compose UpdateBlockListRecursively(blockListValue, createGuid); - return JsonConvert.SerializeObject(blockListValue.BlockValue); + return JsonConvert.SerializeObject(blockListValue.BlockValue, Formatting.None); } private void UpdateBlockListRecursively(BlockEditorData blockListData, Func createGuid) diff --git a/src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs b/src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs index 633e814bd9..3abf962f4d 100644 --- a/src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs +++ b/src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs @@ -1,14 +1,10 @@ using System; -using System.Collections.Generic; using System.Linq; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Umbraco.Core; using Umbraco.Core.Composing; -using Umbraco.Core.Events; -using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Services; -using Umbraco.Core.Services.Implement; using Umbraco.Web.PropertyEditors; namespace Umbraco.Web.Compose @@ -47,7 +43,7 @@ namespace Umbraco.Web.Compose UpdateNestedContentKeysRecursively(complexEditorValue, onlyMissingKeys, createGuid); - return complexEditorValue.ToString(); + return complexEditorValue.ToString(Formatting.None); } private void UpdateNestedContentKeysRecursively(JToken json, bool onlyMissingKeys, Func createGuid) @@ -80,7 +76,7 @@ namespace Umbraco.Web.Compose var parsed = JToken.Parse(propVal); UpdateNestedContentKeysRecursively(parsed, onlyMissingKeys, createGuid); // set the value to the updated one - prop.Value = parsed.ToString(); + prop.Value = parsed.ToString(Formatting.None); } } } diff --git a/src/Umbraco.Web/PropertyEditors/BlockEditorPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/BlockEditorPropertyEditor.cs index 8d50792b71..fab0115d70 100644 --- a/src/Umbraco.Web/PropertyEditors/BlockEditorPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/BlockEditorPropertyEditor.cs @@ -234,7 +234,7 @@ namespace Umbraco.Web.PropertyEditors MapBlockItemData(blockEditorData.BlockValue.SettingsData); // return json - return JsonConvert.SerializeObject(blockEditorData.BlockValue); + return JsonConvert.SerializeObject(blockEditorData.BlockValue, Formatting.None); } #endregion diff --git a/src/Umbraco.Web/PropertyEditors/ColorPickerConfigurationEditor.cs b/src/Umbraco.Web/PropertyEditors/ColorPickerConfigurationEditor.cs index a4d894c551..f163fa898c 100644 --- a/src/Umbraco.Web/PropertyEditors/ColorPickerConfigurationEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ColorPickerConfigurationEditor.cs @@ -130,7 +130,7 @@ namespace Umbraco.Web.PropertyEditors if (id >= nextId) nextId = id + 1; var label = item.Property("label")?.Value?.Value(); - value = JsonConvert.SerializeObject(new { value, label }); + value = JsonConvert.SerializeObject(new { value, label }, Formatting.None); output.Items.Add(new ValueListConfiguration.ValueListItem { Id = id, Value = value }); } diff --git a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs index f9eacd9e73..6f919868f7 100644 --- a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs @@ -142,7 +142,7 @@ namespace Umbraco.Web.PropertyEditors } // Convert back to raw JSON for persisting - return JsonConvert.SerializeObject(grid); + return JsonConvert.SerializeObject(grid, Formatting.None); } /// diff --git a/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs index e66af480f8..0c70bae0c5 100644 --- a/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs @@ -1,16 +1,14 @@ -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using System; +using System; using System.Collections.Generic; using System.Linq; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using Umbraco.Core; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; using Umbraco.Core.Logging; -using Umbraco.Core.Media; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; -using Umbraco.Core.PropertyEditors.ValueConverters; using Umbraco.Core.Services; using Umbraco.Web.Media; @@ -170,7 +168,7 @@ namespace Umbraco.Web.PropertyEditors var sourcePath = _mediaFileSystem.GetRelativePath(src); var copyPath = _mediaFileSystem.CopyFile(args.Copy, property.PropertyType, sourcePath); jo["src"] = _mediaFileSystem.GetUrl(copyPath); - args.Copy.SetValue(property.Alias, jo.ToString(), propertyValue.Culture, propertyValue.Segment); + args.Copy.SetValue(property.Alias, jo.ToString(Formatting.None), propertyValue.Culture, propertyValue.Segment); isUpdated = true; } } @@ -241,17 +239,12 @@ namespace Umbraco.Web.PropertyEditors // it can happen when an image is uploaded via the folder browser, in which case // the property value will be the file source eg '/media/23454/hello.jpg' and we // are fixing that anomaly here - does not make any sense at all but... bah... - - var dt = _dataTypeService.GetDataType(property.PropertyType.DataTypeId); - var config = dt?.ConfigurationAs(); src = svalue; - var json = new - { - src = svalue, - crops = config == null ? Array.Empty() : config.Crops - }; - property.SetValue(JsonConvert.SerializeObject(json), pvalue.Culture, pvalue.Segment); + property.SetValue(JsonConvert.SerializeObject(new + { + src = svalue + }, Formatting.None), pvalue.Culture, pvalue.Segment); } else { diff --git a/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyValueEditor.cs b/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyValueEditor.cs index 8e13d1bb5a..e6c6040325 100644 --- a/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyValueEditor.cs @@ -190,6 +190,10 @@ namespace Umbraco.Web.PropertyEditors { src = val, crops = crops + }, new JsonSerializerSettings() + { + Formatting = Formatting.None, + NullValueHandling = NullValueHandling.Ignore }); } } diff --git a/src/Umbraco.Web/PropertyEditors/MultiUrlPickerValueEditor.cs b/src/Umbraco.Web/PropertyEditors/MultiUrlPickerValueEditor.cs index aae691f624..69c8b26c6f 100644 --- a/src/Umbraco.Web/PropertyEditors/MultiUrlPickerValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MultiUrlPickerValueEditor.cs @@ -123,22 +123,26 @@ namespace Umbraco.Web.PropertyEditors private static readonly JsonSerializerSettings LinkDisplayJsonSerializerSettings = new JsonSerializerSettings { + Formatting = Formatting.None, NullValueHandling = NullValueHandling.Ignore }; public override object FromEditor(ContentPropertyData editorValue, object currentValue) { var value = editorValue.Value?.ToString(); - if (string.IsNullOrEmpty(value)) { - return string.Empty; + return null; } try { + var links = JsonConvert.DeserializeObject>(value); + if (links.Count == 0) + return null; + return JsonConvert.SerializeObject( - from link in JsonConvert.DeserializeObject>(value) + from link in links select new MultiUrlPickerValueEditor.LinkDto { Name = link.Name, @@ -146,8 +150,8 @@ namespace Umbraco.Web.PropertyEditors Target = link.Target, Udi = link.Udi, Url = link.Udi == null ? link.Url : null, // only save the URL for external links - }, LinkDisplayJsonSerializerSettings - ); + }, + LinkDisplayJsonSerializerSettings); } catch (Exception ex) { diff --git a/src/Umbraco.Web/PropertyEditors/MultipleTextStringPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MultipleTextStringPropertyEditor.cs index fa82bc555c..99a57500cc 100644 --- a/src/Umbraco.Web/PropertyEditors/MultipleTextStringPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MultipleTextStringPropertyEditor.cs @@ -60,7 +60,7 @@ namespace Umbraco.Web.PropertyEditors public override object FromEditor(ContentPropertyData editorValue, object currentValue) { var asArray = editorValue.Value as JArray; - if (asArray == null) + if (asArray == null || asArray.HasValues == false) { return null; } diff --git a/src/Umbraco.Web/PropertyEditors/MultipleValueEditor.cs b/src/Umbraco.Web/PropertyEditors/MultipleValueEditor.cs index bbeaff184e..e7123b2147 100644 --- a/src/Umbraco.Web/PropertyEditors/MultipleValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MultipleValueEditor.cs @@ -49,14 +49,18 @@ namespace Umbraco.Web.PropertyEditors public override object FromEditor(Core.Models.Editors.ContentPropertyData editorValue, object currentValue) { var json = editorValue.Value as JArray; - if (json == null) + if (json == null || json.HasValues == false) { return null; } var values = json.Select(item => item.Value()).ToArray(); + if (values.Length == 0) + { + return null; + } - return JsonConvert.SerializeObject(values); + return JsonConvert.SerializeObject(values, Formatting.None); } } } diff --git a/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs index b0eeacacd9..2569ab5688 100644 --- a/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs @@ -103,7 +103,7 @@ namespace Umbraco.Web.PropertyEditors var rows = _nestedContentValues.GetPropertyValues(propertyValue); if (rows.Count == 0) - return string.Empty; + return null; foreach (var row in rows.ToList()) { @@ -134,7 +134,7 @@ namespace Umbraco.Web.PropertyEditors } } - return JsonConvert.SerializeObject(rows).ToXmlString(); + return JsonConvert.SerializeObject(rows, Formatting.None).ToXmlString(); } #endregion @@ -229,7 +229,7 @@ namespace Umbraco.Web.PropertyEditors var rows = _nestedContentValues.GetPropertyValues(editorValue.Value); if (rows.Count == 0) - return string.Empty; + return null; foreach (var row in rows.ToList()) { @@ -254,8 +254,9 @@ namespace Umbraco.Web.PropertyEditors } // return json - return JsonConvert.SerializeObject(rows); + return JsonConvert.SerializeObject(rows, Formatting.None); } + #endregion public IEnumerable GetReferences(object value) diff --git a/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs index 42777f11ad..2d698835b0 100644 --- a/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs @@ -142,7 +142,7 @@ namespace Umbraco.Web.PropertyEditors var editorValueWithMediaUrlsRemoved = _imageSourceParser.RemoveImageSources(parseAndSavedTempImages); var parsed = MacroTagParser.FormatRichTextContentForPersistence(editorValueWithMediaUrlsRemoved); - return parsed; + return parsed.NullOrWhiteSpaceAsNull(); } /// diff --git a/src/Umbraco.Web/PropertyEditors/TagsPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/TagsPropertyEditor.cs index 6066bf7dfb..41e22541c8 100644 --- a/src/Umbraco.Web/PropertyEditors/TagsPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/TagsPropertyEditor.cs @@ -52,7 +52,7 @@ namespace Umbraco.Web.PropertyEditors if (editorValue.Value is JArray json) { - return json.Select(x => x.Value()); + return json.HasValues ? json.Select(x => x.Value()) : null; } if (string.IsNullOrWhiteSpace(value) == false) From a952d17e7423a0b43344c5aa79478b0424fb0ffb Mon Sep 17 00:00:00 2001 From: Paul Johnson Date: Mon, 10 Jan 2022 15:31:46 +0000 Subject: [PATCH 048/141] Make using scoped services in notification handlers less painful. (#11733) * Add failing test to demo issue with handlers + scoped dependencies. * Make using scoped services in notification handlers less painful. * Update comments for accuracy. --- .../Events/EventAggregator.Notifications.cs | 61 ++++++++++++++-- .../Implementations/TestHelper.cs | 6 ++ .../UmbracoTestServerTestBase.cs | 11 ++- .../Events/EventAggregatorTests.cs | 71 +++++++++++++++++++ 4 files changed, 143 insertions(+), 6 deletions(-) create mode 100644 tests/Umbraco.Tests.Integration/Umbraco.Core/Events/EventAggregatorTests.cs diff --git a/src/Umbraco.Core/Events/EventAggregator.Notifications.cs b/src/Umbraco.Core/Events/EventAggregator.Notifications.cs index 58ccb06ed0..44eac93641 100644 --- a/src/Umbraco.Core/Events/EventAggregator.Notifications.cs +++ b/src/Umbraco.Core/Events/EventAggregator.Notifications.cs @@ -7,6 +7,8 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Notifications; namespace Umbraco.Cms.Core.Events @@ -84,17 +86,56 @@ namespace Umbraco.Cms.Core.Events internal class NotificationAsyncHandlerWrapperImpl : NotificationAsyncHandlerWrapper where TNotification : INotification { + /// + /// + /// Background - During v9 build we wanted an in-process message bus to facilitate removal of the old static event handlers.
+ /// Instead of taking a dependency on MediatR we (the community) implemented our own using MediatR as inspiration. + ///
+ /// + /// + /// Some things worth knowing about MediatR. + /// + /// All handlers are by default registered with transient lifetime, but can easily depend on services with state. + /// Both the Mediatr instance and its handler resolver are registered transient and as such it is always possible to depend on scoped services in a handler. + /// + /// + /// + /// + /// Our EventAggregator started out registered with a transient lifetime but later (before initial release) the registration was changed to singleton, presumably + /// because there are a lot of singleton services in Umbraco which like to publish notifications and it's a pain to use scoped services from a singleton. + ///
+ /// The problem with a singleton EventAggregator is it forces handlers to create a service scope and service locate any scoped services + /// they wish to make use of e.g. a unit of work (think entity framework DBContext). + ///
+ /// + /// + /// Moving forwards it probably makes more sense to register EventAggregator transient but doing so now would mean an awful lot of service location to avoid breaking changes. + ///
+ /// For now we can do the next best thing which is to create a scope for each published notification, thus enabling the transient handlers to take a dependency on a scoped service. + ///
+ /// + /// + /// Did discuss using HttpContextAccessor/IScopedServiceProvider to enable sharing of scopes when publisher has http context, + /// but decided against because it's inconsistent with what happens in background threads and will just cause confusion. + /// + ///
public override Task HandleAsync( INotification notification, CancellationToken cancellationToken, ServiceFactory serviceFactory, Func>, INotification, CancellationToken, Task> publish) { - IEnumerable> handlers = serviceFactory - .GetInstances>() + // Create a new service scope from which to resolve handlers and ensure it's disposed when it goes out of scope. + // TODO: go back to using ServiceFactory to resolve + IServiceScopeFactory scopeFactory = serviceFactory.GetInstance(); + using IServiceScope scope = scopeFactory.CreateScope(); + IServiceProvider container = scope.ServiceProvider; + + IEnumerable> handlers = container + .GetServices>() .Select(x => new Func( (theNotification, theToken) => - x.HandleAsync((TNotification)theNotification, theToken))); + x.HandleAsync((TNotification)theNotification, theToken))); return publish(handlers, notification, cancellationToken); } @@ -103,13 +144,23 @@ namespace Umbraco.Cms.Core.Events internal class NotificationHandlerWrapperImpl : NotificationHandlerWrapper where TNotification : INotification { + /// + /// See remarks on for explanation on + /// what's going on with the IServiceProvider stuff here. + /// public override void Handle( INotification notification, ServiceFactory serviceFactory, Action>, INotification> publish) { - IEnumerable> handlers = serviceFactory - .GetInstances>() + // Create a new service scope from which to resolve handlers and ensure it's disposed when it goes out of scope. + // TODO: go back to using ServiceFactory to resolve + IServiceScopeFactory scopeFactory = serviceFactory.GetInstance(); + using IServiceScope scope = scopeFactory.CreateScope(); + IServiceProvider container = scope.ServiceProvider; + + IEnumerable> handlers = container + .GetServices>() .Select(x => new Action( (theNotification) => x.Handle((TNotification)theNotification))); diff --git a/tests/Umbraco.Tests.Integration/Implementations/TestHelper.cs b/tests/Umbraco.Tests.Integration/Implementations/TestHelper.cs index a9bed62993..f8afe1d6ae 100644 --- a/tests/Umbraco.Tests.Integration/Implementations/TestHelper.cs +++ b/tests/Umbraco.Tests.Integration/Implementations/TestHelper.cs @@ -60,6 +60,11 @@ namespace Umbraco.Cms.Tests.Integration.Implementations _ipResolver = new AspNetCoreIpResolver(_httpContextAccessor); string contentRoot = Assembly.GetExecutingAssembly().GetRootDirectorySafe(); + + // The mock for IWebHostEnvironment has caused a good few issues. + // We can UseContentRoot, UseWebRoot etc on the host builder instead. + // possibly going down rabbit holes though as would need to cleanup all usages of + // GetHostingEnvironment & GetWebHostEnvironment. var hostEnvironment = new Mock(); // This must be the assembly name for the WebApplicationFactory to work. @@ -68,6 +73,7 @@ namespace Umbraco.Cms.Tests.Integration.Implementations hostEnvironment.Setup(x => x.ContentRootFileProvider).Returns(() => new PhysicalFileProvider(contentRoot)); hostEnvironment.Setup(x => x.WebRootPath).Returns(() => WorkingDirectory); hostEnvironment.Setup(x => x.WebRootFileProvider).Returns(() => new PhysicalFileProvider(WorkingDirectory)); + hostEnvironment.Setup(x => x.EnvironmentName).Returns("Tests"); // We also need to expose it as the obsolete interface since netcore's WebApplicationFactory casts it. hostEnvironment.As(); diff --git a/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs b/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs index 2d140e4336..bd9418bdb6 100644 --- a/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs +++ b/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs @@ -87,7 +87,16 @@ namespace Umbraco.Cms.Tests.Integration.TestServerTest // call startup builder.Configure(app => Configure(app)); - }).UseEnvironment(Environments.Development); + }) + .UseDefaultServiceProvider(cfg => + { + // These default to true *if* WebHostEnvironment.EnvironmentName == Development + // When running tests, EnvironmentName used to be null on the mock that we register into services. + // Enable opt in for tests so that validation occurs regardless of environment name. + // Would be nice to have this on for UmbracoIntegrationTest also but requires a lot more effort to resolve issues. + cfg.ValidateOnBuild = true; + cfg.ValidateScopes = true; + }); return builder; } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Events/EventAggregatorTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Events/EventAggregatorTests.cs new file mode 100644 index 0000000000..6446bf542a --- /dev/null +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Events/EventAggregatorTests.cs @@ -0,0 +1,71 @@ +using System; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; +using NUnit.Framework; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Tests.Integration.TestServerTest; + +namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Events +{ + [TestFixture] + public class EventAggregatorTests : UmbracoTestServerTestBase + { + public override void ConfigureServices(IServiceCollection services) + { + base.ConfigureServices(services); + services.AddScoped(); + services.AddTransient, EventAggregatorTestNotificationHandler>(); + } + + [Test] + public async Task Publish_HandlerWithScopedDependency_DoesNotThrow() + { + HttpResponseMessage result = await Client.GetAsync("/test-handler-with-scoped-services"); + Assert.AreEqual(HttpStatusCode.OK, result.StatusCode); + } + } + + public class EventAggregatorTestsController : Controller + { + private readonly IEventAggregator _eventAggregator; + + public EventAggregatorTestsController(IEventAggregator eventAggregator) => _eventAggregator = eventAggregator; + + [HttpGet("test-handler-with-scoped-services")] + public async Task Test() + { + var notification = new EventAggregatorTestNotification(); + await _eventAggregator.PublishAsync(notification); + + if (!notification.Mutated) + { + throw new ApplicationException("Doesn't work"); + } + + return Ok(); + } + } + + public class EventAggregatorTestScopedService + { + } + + public class EventAggregatorTestNotification : INotification + { + public bool Mutated { get; set; } + } + + public class EventAggregatorTestNotificationHandler : INotificationHandler + { + private readonly EventAggregatorTestScopedService _scopedService; + + public EventAggregatorTestNotificationHandler(EventAggregatorTestScopedService scopedService) => _scopedService = scopedService; + + // Mutation proves that the handler runs despite depending on scoped service. + public void Handle(EventAggregatorTestNotification notification) => notification.Mutated = true; + } +} From d9bdd8196c7e06d50411205ac4f1178ff69b6d60 Mon Sep 17 00:00:00 2001 From: Paul Johnson Date: Mon, 10 Jan 2022 16:03:33 +0000 Subject: [PATCH 049/141] Misc/obsolete redundant extension (#11838) * Mark AddUnique obsolete. * Remove all internal usages of AddUnique. --- .../ServiceCollectionExtensions.cs | 2 ++ .../DependencyInjection/UmbracoBuilder.cs | 24 +++++++++---------- .../UmbracoBuilder.Examine.cs | 2 +- .../UmbracoBuilder.FileSystems.cs | 2 +- .../UmbracoBuilder.Installer.cs | 2 +- .../UmbracoBuilder.Services.cs | 8 +++---- .../UmbracoBuilderExtensions.cs | 12 +++++----- .../UmbracoBuilderExtensions.cs | 18 +++++++------- 8 files changed, 36 insertions(+), 34 deletions(-) diff --git a/src/Umbraco.Core/DependencyInjection/ServiceCollectionExtensions.cs b/src/Umbraco.Core/DependencyInjection/ServiceCollectionExtensions.cs index cec1cbb4eb..871a0bbe02 100644 --- a/src/Umbraco.Core/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Umbraco.Core/DependencyInjection/ServiceCollectionExtensions.cs @@ -24,6 +24,8 @@ namespace Umbraco.Extensions services.AddUnique(factory => (TImplementing)factory.GetRequiredService()); } + // TODO(V11): Remove this function. + [Obsolete("This method is functionally equivalent to AddSingleton() please use that instead.")] public static void AddUnique(this IServiceCollection services) where TImplementing : class => services.Replace(ServiceDescriptor.Singleton()); diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs index 1af25e16e8..eacd615830 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs @@ -171,7 +171,7 @@ namespace Umbraco.Cms.Core.DependencyInjection Services.AddSingleton(); - Services.AddUnique(); + Services.AddSingleton(); // by default, register a noop factory Services.AddUnique(); @@ -179,7 +179,7 @@ namespace Umbraco.Cms.Core.DependencyInjection Services.AddUnique(); Services.AddSingleton(f => f.GetRequiredService().CreateDictionary()); - Services.AddUnique(); + Services.AddSingleton(); Services.AddUnique(); Services.AddUnique(); @@ -194,14 +194,14 @@ namespace Umbraco.Cms.Core.DependencyInjection Services.AddUnique(); Services.AddUnique(); - Services.AddUnique(); - Services.AddUnique(); - Services.AddUnique(); + Services.AddSingleton(); + Services.AddSingleton(); + Services.AddSingleton(); // register properties fallback Services.AddUnique(); - Services.AddUnique(); + Services.AddSingleton(); // register published router Services.AddUnique(); @@ -226,13 +226,13 @@ namespace Umbraco.Cms.Core.DependencyInjection // let's use an hybrid accessor that can fall back to a ThreadStatic context. Services.AddUnique(); - Services.AddUnique(); - Services.AddUnique(); - Services.AddUnique(); - Services.AddUnique(); + Services.AddSingleton(); + Services.AddSingleton(); + Services.AddSingleton(); + Services.AddSingleton(); - Services.AddUnique(); - Services.AddUnique(); + Services.AddSingleton(); + Services.AddSingleton(); // register a server registrar, by default it's the db registrar Services.AddUnique(f => diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Examine.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Examine.cs index d061a4372c..da31a8df39 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Examine.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Examine.cs @@ -50,7 +50,7 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection false)); builder.Services.AddUnique, MediaValueSetBuilder>(); builder.Services.AddUnique, MemberValueSetBuilder>(); - builder.Services.AddUnique(); + builder.Services.AddSingleton(); builder.AddNotificationHandler(); builder.AddNotificationHandler(); diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.FileSystems.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.FileSystems.cs index f66991fb68..ccb515182e 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.FileSystems.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.FileSystems.cs @@ -34,7 +34,7 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection internal static IUmbracoBuilder AddFileSystems(this IUmbracoBuilder builder) { // register FileSystems, which manages all filesystems - builder.Services.AddUnique(); + builder.Services.AddSingleton(); // register the scheme for media paths builder.Services.AddUnique(); diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Installer.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Installer.cs index 990b158ef3..e0958bfdb7 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Installer.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Installer.cs @@ -27,7 +27,7 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection builder.Services.AddScoped(); builder.Services.AddTransient(); - builder.Services.AddUnique(); + builder.Services.AddSingleton(); builder.Services.AddTransient(); diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs index debe476c49..7d74ff13c8 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs @@ -30,7 +30,7 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection internal static IUmbracoBuilder AddServices(this IUmbracoBuilder builder) { // register the service context - builder.Services.AddUnique(); + builder.Services.AddSingleton(); // register the special idk map builder.Services.AddUnique(); @@ -74,11 +74,11 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection builder.Services.AddUnique(); - builder.Services.AddUnique(); - builder.Services.AddUnique(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); builder.Services.AddUnique(factory => CreatePackageRepository(factory, "createdPackages.config")); builder.Services.AddUnique(); - builder.Services.AddUnique(); + builder.Services.AddSingleton(); builder.Services.AddUnique(); return builder; diff --git a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs index 7a15d640e6..2c801e963b 100644 --- a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs @@ -82,12 +82,12 @@ namespace Umbraco.Extensions { builder.Services.AddSingleton(); builder.Services.ConfigureOptions(); - builder.Services.AddUnique(); - builder.Services.AddUnique(); - builder.Services.AddUnique(); - builder.Services.AddUnique(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); builder.AddNotificationAsyncHandler(); - builder.Services.AddUnique(); + builder.Services.AddSingleton(); builder.Services.AddScoped(); builder.Services.AddScoped(); @@ -115,7 +115,7 @@ namespace Umbraco.Extensions builder.Services.AddUnique(); builder.Services.AddUnique(); - builder.Services.AddUnique(); + builder.Services.AddSingleton(); return builder; } diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs index 97a9f3e087..247e364ba4 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs @@ -148,7 +148,7 @@ namespace Umbraco.Extensions // Add supported databases builder.AddUmbracoSqlServerSupport(); builder.AddUmbracoSqlCeSupport(); - builder.Services.AddUnique(); + builder.Services.AddSingleton(); // Must be added here because DbProviderFactories is netstandard 2.1 so cannot exist in Infra for now builder.Services.AddSingleton(factory => new DbProviderFactoryCreator( @@ -200,7 +200,7 @@ namespace Umbraco.Extensions ///
public static IUmbracoBuilder AddUmbracoProfiler(this IUmbracoBuilder builder) { - builder.Services.AddUnique(); + builder.Services.AddSingleton(); builder.Services.AddMiniProfiler(options => { @@ -286,7 +286,7 @@ namespace Umbraco.Extensions builder.Services.AddSmidgeInMemory(false); // it will be enabled based on config/cachebuster builder.Services.AddUnique(); - builder.Services.AddUnique(); + builder.Services.AddSingleton(); builder.Services.AddTransient(); builder.Services.ConfigureOptions(); @@ -332,7 +332,7 @@ namespace Umbraco.Extensions builder.Services.AddUnique(); builder.Services.AddUnique(); - builder.Services.AddUnique(); + builder.Services.AddSingleton(); // register the umbraco context factory @@ -343,12 +343,12 @@ namespace Umbraco.Extensions builder.WithCollectionBuilder() .Add(umbracoApiControllerTypes); - builder.Services.AddUnique(); - builder.Services.AddUnique(); - builder.Services.AddUnique(); - builder.Services.AddUnique(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); - builder.Services.AddUnique(); + builder.Services.AddSingleton(); builder.Services.AddUnique(); builder.Services.AddUnique(); From 8d1fa717ba663708382131b9b7791d40737df414 Mon Sep 17 00:00:00 2001 From: Paul Johnson Date: Mon, 10 Jan 2022 16:03:33 +0000 Subject: [PATCH 050/141] cherry-pick Misc/obsolete redundant extension (#11838) * Mark AddUnique obsolete. * Remove all internal usages of AddUnique. --- .../ServiceCollectionExtensions.cs | 2 ++ .../DependencyInjection/UmbracoBuilder.cs | 24 +++++++++---------- .../UmbracoBuilder.Examine.cs | 2 +- .../UmbracoBuilder.FileSystems.cs | 2 +- .../UmbracoBuilder.Installer.cs | 2 +- .../UmbracoBuilder.Services.cs | 8 +++---- .../UmbracoBuilderExtensions.cs | 12 +++++----- .../UmbracoBuilderExtensions.cs | 18 +++++++------- 8 files changed, 36 insertions(+), 34 deletions(-) diff --git a/src/Umbraco.Core/DependencyInjection/ServiceCollectionExtensions.cs b/src/Umbraco.Core/DependencyInjection/ServiceCollectionExtensions.cs index cec1cbb4eb..871a0bbe02 100644 --- a/src/Umbraco.Core/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Umbraco.Core/DependencyInjection/ServiceCollectionExtensions.cs @@ -24,6 +24,8 @@ namespace Umbraco.Extensions services.AddUnique(factory => (TImplementing)factory.GetRequiredService()); } + // TODO(V11): Remove this function. + [Obsolete("This method is functionally equivalent to AddSingleton() please use that instead.")] public static void AddUnique(this IServiceCollection services) where TImplementing : class => services.Replace(ServiceDescriptor.Singleton()); diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs index 1af25e16e8..eacd615830 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs @@ -171,7 +171,7 @@ namespace Umbraco.Cms.Core.DependencyInjection Services.AddSingleton(); - Services.AddUnique(); + Services.AddSingleton(); // by default, register a noop factory Services.AddUnique(); @@ -179,7 +179,7 @@ namespace Umbraco.Cms.Core.DependencyInjection Services.AddUnique(); Services.AddSingleton(f => f.GetRequiredService().CreateDictionary()); - Services.AddUnique(); + Services.AddSingleton(); Services.AddUnique(); Services.AddUnique(); @@ -194,14 +194,14 @@ namespace Umbraco.Cms.Core.DependencyInjection Services.AddUnique(); Services.AddUnique(); - Services.AddUnique(); - Services.AddUnique(); - Services.AddUnique(); + Services.AddSingleton(); + Services.AddSingleton(); + Services.AddSingleton(); // register properties fallback Services.AddUnique(); - Services.AddUnique(); + Services.AddSingleton(); // register published router Services.AddUnique(); @@ -226,13 +226,13 @@ namespace Umbraco.Cms.Core.DependencyInjection // let's use an hybrid accessor that can fall back to a ThreadStatic context. Services.AddUnique(); - Services.AddUnique(); - Services.AddUnique(); - Services.AddUnique(); - Services.AddUnique(); + Services.AddSingleton(); + Services.AddSingleton(); + Services.AddSingleton(); + Services.AddSingleton(); - Services.AddUnique(); - Services.AddUnique(); + Services.AddSingleton(); + Services.AddSingleton(); // register a server registrar, by default it's the db registrar Services.AddUnique(f => diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Examine.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Examine.cs index d061a4372c..da31a8df39 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Examine.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Examine.cs @@ -50,7 +50,7 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection false)); builder.Services.AddUnique, MediaValueSetBuilder>(); builder.Services.AddUnique, MemberValueSetBuilder>(); - builder.Services.AddUnique(); + builder.Services.AddSingleton(); builder.AddNotificationHandler(); builder.AddNotificationHandler(); diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.FileSystems.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.FileSystems.cs index 6582cfb0c6..8454bbdf31 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.FileSystems.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.FileSystems.cs @@ -34,7 +34,7 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection internal static IUmbracoBuilder AddFileSystems(this IUmbracoBuilder builder) { // register FileSystems, which manages all filesystems - builder.Services.AddUnique(); + builder.Services.AddSingleton(); // register the scheme for media paths builder.Services.AddUnique(); diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Installer.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Installer.cs index 990b158ef3..e0958bfdb7 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Installer.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Installer.cs @@ -27,7 +27,7 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection builder.Services.AddScoped(); builder.Services.AddTransient(); - builder.Services.AddUnique(); + builder.Services.AddSingleton(); builder.Services.AddTransient(); diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs index 661ed93292..1558f929ee 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs @@ -28,7 +28,7 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection internal static IUmbracoBuilder AddServices(this IUmbracoBuilder builder) { // register the service context - builder.Services.AddUnique(); + builder.Services.AddSingleton(); // register the special idk map builder.Services.AddUnique(); @@ -72,10 +72,10 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection builder.Services.AddUnique(); - builder.Services.AddUnique(); - builder.Services.AddUnique(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); builder.Services.AddUnique(factory => CreatePackageRepository(factory, "createdPackages.config")); - builder.Services.AddUnique(); + builder.Services.AddSingleton(); builder.Services.AddUnique(); return builder; diff --git a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs index aeb329efb4..705b47d806 100644 --- a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs @@ -82,12 +82,12 @@ namespace Umbraco.Extensions { builder.Services.AddSingleton(); builder.Services.ConfigureOptions(); - builder.Services.AddUnique(); - builder.Services.AddUnique(); - builder.Services.AddUnique(); - builder.Services.AddUnique(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); builder.AddNotificationAsyncHandler(); - builder.Services.AddUnique(); + builder.Services.AddSingleton(); builder.Services.AddScoped(); builder.Services.AddScoped(); @@ -114,7 +114,7 @@ namespace Umbraco.Extensions }); builder.Services.AddUnique(); - builder.Services.AddUnique(); + builder.Services.AddSingleton(); return builder; } diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs index 97a9f3e087..247e364ba4 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs @@ -148,7 +148,7 @@ namespace Umbraco.Extensions // Add supported databases builder.AddUmbracoSqlServerSupport(); builder.AddUmbracoSqlCeSupport(); - builder.Services.AddUnique(); + builder.Services.AddSingleton(); // Must be added here because DbProviderFactories is netstandard 2.1 so cannot exist in Infra for now builder.Services.AddSingleton(factory => new DbProviderFactoryCreator( @@ -200,7 +200,7 @@ namespace Umbraco.Extensions ///
public static IUmbracoBuilder AddUmbracoProfiler(this IUmbracoBuilder builder) { - builder.Services.AddUnique(); + builder.Services.AddSingleton(); builder.Services.AddMiniProfiler(options => { @@ -286,7 +286,7 @@ namespace Umbraco.Extensions builder.Services.AddSmidgeInMemory(false); // it will be enabled based on config/cachebuster builder.Services.AddUnique(); - builder.Services.AddUnique(); + builder.Services.AddSingleton(); builder.Services.AddTransient(); builder.Services.ConfigureOptions(); @@ -332,7 +332,7 @@ namespace Umbraco.Extensions builder.Services.AddUnique(); builder.Services.AddUnique(); - builder.Services.AddUnique(); + builder.Services.AddSingleton(); // register the umbraco context factory @@ -343,12 +343,12 @@ namespace Umbraco.Extensions builder.WithCollectionBuilder() .Add(umbracoApiControllerTypes); - builder.Services.AddUnique(); - builder.Services.AddUnique(); - builder.Services.AddUnique(); - builder.Services.AddUnique(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); - builder.Services.AddUnique(); + builder.Services.AddSingleton(); builder.Services.AddUnique(); builder.Services.AddUnique(); From ae2ff856fbefe93860477765bb5167a9fb1d0c20 Mon Sep 17 00:00:00 2001 From: Paul Johnson Date: Tue, 11 Jan 2022 16:59:31 +0000 Subject: [PATCH 051/141] Remove IEventDispatcher, it's no longer used. --- src/Umbraco.Core/Events/IEventDispatcher.cs | 98 ---- .../Events/PassThroughEventDispatcher.cs | 60 --- .../Events/QueuingEventDispatcher.cs | 43 -- .../Events/QueuingEventDispatcherBase.cs | 344 ------------- src/Umbraco.Infrastructure/Scoping/IScope.cs | 5 - .../Scoping/IScopeProvider.cs | 4 - src/Umbraco.Infrastructure/Scoping/Scope.cs | 31 +- .../Scoping/ScopeProvider.cs | 8 +- .../UmbracoExamine/ExamineBaseTest.cs | 1 - .../Scoping/ScopedRepositoryTests.cs | 15 +- .../PublishedSnapshotServiceTestBase.cs | 1 - .../Scoping/ScopeEventDispatcherTests.cs | 452 ------------------ .../HostedServices/LogScrubberTests.cs | 2 +- .../Mapping/MappingTests.cs | 1 - .../Migrations/MigrationTests.cs | 2 - .../Security/MemberUserStoreTests.cs | 2 +- .../Controllers/MemberControllerUnitTests.cs | 1 - 17 files changed, 8 insertions(+), 1062 deletions(-) delete mode 100644 src/Umbraco.Core/Events/IEventDispatcher.cs delete mode 100644 src/Umbraco.Core/Events/PassThroughEventDispatcher.cs delete mode 100644 src/Umbraco.Core/Events/QueuingEventDispatcher.cs delete mode 100644 src/Umbraco.Core/Events/QueuingEventDispatcherBase.cs delete mode 100644 tests/Umbraco.Tests.UnitTests/Umbraco.Core/Scoping/ScopeEventDispatcherTests.cs diff --git a/src/Umbraco.Core/Events/IEventDispatcher.cs b/src/Umbraco.Core/Events/IEventDispatcher.cs deleted file mode 100644 index 84e522761c..0000000000 --- a/src/Umbraco.Core/Events/IEventDispatcher.cs +++ /dev/null @@ -1,98 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Umbraco.Cms.Core.Events -{ - /// - /// Dispatches events from within a scope. - /// - /// - /// The name of the event is auto-magically discovered by matching the sender type, args type, and - /// eventHandler type. If the match is not unique, then the name parameter must be used to specify the - /// name in an explicit way. - /// What happens when an event is dispatched depends on the scope settings. It can be anything from - /// "trigger immediately" to "just ignore". Refer to the scope documentation for more details. - /// - public interface IEventDispatcher - { - // not sure about the Dispatch & DispatchCancelable signatures at all for now - // nor about the event name thing, etc - but let's keep it like this - - /// - /// Dispatches a cancelable event. - /// - /// The event handler. - /// The object that raised the event. - /// The event data. - /// The optional name of the event. - /// A value indicating whether the cancelable event was cancelled. - /// See general remarks on the interface. - bool DispatchCancelable(EventHandler eventHandler, object sender, CancellableEventArgs args, string name = null); - - /// - /// Dispatches a cancelable event. - /// - /// The event handler. - /// The object that raised the event. - /// The event data. - /// The optional name of the event. - /// A value indicating whether the cancelable event was cancelled. - /// See general remarks on the interface. - bool DispatchCancelable(EventHandler eventHandler, object sender, TArgs args, string name = null) - where TArgs : CancellableEventArgs; - - /// - /// Dispatches a cancelable event. - /// - /// The event handler. - /// The object that raised the event. - /// The event data. - /// The optional name of the event. - /// A value indicating whether the cancelable event was cancelled. - /// See general remarks on the interface. - bool DispatchCancelable(TypedEventHandler eventHandler, TSender sender, TArgs args, string name = null) - where TArgs : CancellableEventArgs; - - /// - /// Dispatches an event. - /// - /// The event handler. - /// The object that raised the event. - /// The event data. - /// The optional name of the event. - /// See general remarks on the interface. - void Dispatch(EventHandler eventHandler, object sender, EventArgs args, string name = null); - - /// - /// Dispatches an event. - /// - /// The event handler. - /// The object that raised the event. - /// The event data. - /// The optional name of the event. - /// See general remarks on the interface. - void Dispatch(EventHandler eventHandler, object sender, TArgs args, string name = null); - - /// - /// Dispatches an event. - /// - /// The event handler. - /// The object that raised the event. - /// The event data. - /// The optional name of the event. - /// See general remarks on the interface. - void Dispatch(TypedEventHandler eventHandler, TSender sender, TArgs args, string name = null); - - /// - /// Notifies the dispatcher that the scope is exiting. - /// - /// A value indicating whether the scope completed. - void ScopeExit(bool completed); - - /// - /// Gets the collected events. - /// - /// The collected events. - IEnumerable GetEvents(EventDefinitionFilter filter); - } -} diff --git a/src/Umbraco.Core/Events/PassThroughEventDispatcher.cs b/src/Umbraco.Core/Events/PassThroughEventDispatcher.cs deleted file mode 100644 index 0b2e72cc7f..0000000000 --- a/src/Umbraco.Core/Events/PassThroughEventDispatcher.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Umbraco.Cms.Core.Events -{ - /// - /// An IEventDispatcher that immediately raise all events. - /// - /// This means that events will be raised during the scope transaction, - /// whatever happens, and the transaction could roll back in the end. - internal class PassThroughEventDispatcher : IEventDispatcher - { - public bool DispatchCancelable(EventHandler eventHandler, object sender, CancellableEventArgs args, string eventName = null) - { - if (eventHandler == null) return args.Cancel; - eventHandler(sender, args); - return args.Cancel; - } - - public bool DispatchCancelable(EventHandler eventHandler, object sender, TArgs args, string eventName = null) - where TArgs : CancellableEventArgs - { - if (eventHandler == null) return args.Cancel; - eventHandler(sender, args); - return args.Cancel; - } - - public bool DispatchCancelable(TypedEventHandler eventHandler, TSender sender, TArgs args, string eventName = null) - where TArgs : CancellableEventArgs - { - if (eventHandler == null) return args.Cancel; - eventHandler(sender, args); - return args.Cancel; - } - - public void Dispatch(EventHandler eventHandler, object sender, EventArgs args, string eventName = null) - { - eventHandler?.Invoke(sender, args); - } - - public void Dispatch(EventHandler eventHandler, object sender, TArgs args, string eventName = null) - { - eventHandler?.Invoke(sender, args); - } - - public void Dispatch(TypedEventHandler eventHandler, TSender sender, TArgs args, string eventName = null) - { - eventHandler?.Invoke(sender, args); - } - - public IEnumerable GetEvents(EventDefinitionFilter filter) - { - return Enumerable.Empty(); - } - - public void ScopeExit(bool completed) - { } - } -} diff --git a/src/Umbraco.Core/Events/QueuingEventDispatcher.cs b/src/Umbraco.Core/Events/QueuingEventDispatcher.cs deleted file mode 100644 index e79cd67cd8..0000000000 --- a/src/Umbraco.Core/Events/QueuingEventDispatcher.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) Umbraco. -// See LICENSE for more details. - -using Umbraco.Cms.Core.IO; - -namespace Umbraco.Cms.Core.Events -{ - /// - /// An IEventDispatcher that queues events, and raise them when the scope - /// exits and has been completed. - /// - public class QueuingEventDispatcher : QueuingEventDispatcherBase - { - private readonly MediaFileManager _mediaFileManager; - public QueuingEventDispatcher(MediaFileManager mediaFileManager) - : base(true) - { - _mediaFileManager = mediaFileManager; - } - - protected override void ScopeExitCompleted() - { - // processing only the last instance of each event... - // this is probably far from perfect, because if eg a content is saved in a list - // and then as a single content, the two events will probably not be de-duplicated, - // but it's better than nothing - - foreach (var e in GetEvents(EventDefinitionFilter.LastIn)) - { - e.RaiseEvent(); - - // separating concerns means that this should probably not be here, - // but then where should it be (without making things too complicated)? - var delete = e.Args as IDeletingMediaFilesEventArgs; - if (delete != null && delete.MediaFilesToDelete.Count > 0) - _mediaFileManager.DeleteMediaFiles(delete.MediaFilesToDelete); - } - } - - - - } -} diff --git a/src/Umbraco.Core/Events/QueuingEventDispatcherBase.cs b/src/Umbraco.Core/Events/QueuingEventDispatcherBase.cs deleted file mode 100644 index 784390ebe7..0000000000 --- a/src/Umbraco.Core/Events/QueuingEventDispatcherBase.cs +++ /dev/null @@ -1,344 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Cms.Core.Collections; -using Umbraco.Cms.Core.Composing; -using Umbraco.Cms.Core.Models.Entities; -using Umbraco.Extensions; - -namespace Umbraco.Cms.Core.Events -{ - /// - /// An IEventDispatcher that queues events. - /// - /// - /// Can raise, or ignore, cancelable events, depending on option. - /// Implementations must override ScopeExitCompleted to define what - /// to do with the events when the scope exits and has been completed. - /// If the scope exits without being completed, events are ignored. - /// - public abstract class QueuingEventDispatcherBase : IEventDispatcher - { - //events will be enlisted in the order they are raised - private List _events; - private readonly bool _raiseCancelable; - - protected QueuingEventDispatcherBase(bool raiseCancelable) - { - _raiseCancelable = raiseCancelable; - } - - private List Events => _events ?? (_events = new List()); - - public bool DispatchCancelable(EventHandler eventHandler, object sender, CancellableEventArgs args, string eventName = null) - { - if (eventHandler == null) return args.Cancel; - if (_raiseCancelable == false) return args.Cancel; - eventHandler(sender, args); - return args.Cancel; - } - - public bool DispatchCancelable(EventHandler eventHandler, object sender, TArgs args, string eventName = null) - where TArgs : CancellableEventArgs - { - if (eventHandler == null) return args.Cancel; - if (_raiseCancelable == false) return args.Cancel; - eventHandler(sender, args); - return args.Cancel; - } - - public bool DispatchCancelable(TypedEventHandler eventHandler, TSender sender, TArgs args, string eventName = null) - where TArgs : CancellableEventArgs - { - if (eventHandler == null) return args.Cancel; - if (_raiseCancelable == false) return args.Cancel; - eventHandler(sender, args); - return args.Cancel; - } - - public void Dispatch(EventHandler eventHandler, object sender, EventArgs args, string eventName = null) - { - if (eventHandler == null) return; - Events.Add(new EventDefinition(eventHandler, sender, args, eventName)); - } - - public void Dispatch(EventHandler eventHandler, object sender, TArgs args, string eventName = null) - { - if (eventHandler == null) return; - Events.Add(new EventDefinition(eventHandler, sender, args, eventName)); - } - - public void Dispatch(TypedEventHandler eventHandler, TSender sender, TArgs args, string eventName = null) - { - if (eventHandler == null) return; - Events.Add(new EventDefinition(eventHandler, sender, args, eventName)); - } - - public IEnumerable GetEvents(EventDefinitionFilter filter) - { - if (_events == null) - return Enumerable.Empty(); - - IReadOnlyList events; - switch (filter) - { - case EventDefinitionFilter.All: - events = _events; - break; - case EventDefinitionFilter.FirstIn: - var l1 = new OrderedHashSet(); - foreach (var e in _events) - l1.Add(e); - events = l1; - break; - case EventDefinitionFilter.LastIn: - var l2 = new OrderedHashSet(keepOldest: false); - foreach (var e in _events) - l2.Add(e); - events = l2; - break; - default: - throw new ArgumentOutOfRangeException("filter", filter, null); - } - - return FilterSupersededAndUpdateToLatestEntity(events); - } - - private class EventDefinitionInfos - { - public IEventDefinition EventDefinition { get; set; } - public Type[] SupersedeTypes { get; set; } - } - - // this is way too convoluted, the supersede attribute is used only on DeleteEventargs to specify - // that it supersedes save, publish, move and copy - BUT - publish event args is also used for - // unpublishing and should NOT be superseded - so really it should not be managed at event args - // level but at event level - // - // what we want is: - // if an entity is deleted, then all Saved, Moved, Copied, Published events prior to this should - // not trigger for the entity - and even though, does it make any sense? making a copy of an entity - // should ... trigger? - // - // not going to refactor it all - we probably want to *always* trigger event but tell people that - // due to scopes, they should not expected eg a saved entity to still be around - however, now, - // going to write a ugly condition to deal with U4-10764 - - // iterates over the events (latest first) and filter out any events or entities in event args that are included - // in more recent events that Supersede previous ones. For example, If an Entity has been Saved and then Deleted, we don't want - // to raise the Saved event (well actually we just don't want to include it in the args for that saved event) - internal static IEnumerable FilterSupersededAndUpdateToLatestEntity(IReadOnlyList events) - { - // keeps the 'latest' entity and associated event data - var entities = new List>(); - - // collects the event definitions - // collects the arguments in result, that require their entities to be updated - var result = new List(); - var resultArgs = new List(); - - // eagerly fetch superseded arg types for each arg type - var argTypeSuperceeding = events.Select(x => x.Args.GetType()) - .Distinct() - .ToDictionary(x => x, x => x.GetCustomAttributes(false).Select(y => y.SupersededEventArgsType).ToArray()); - - // iterate over all events and filter - // - // process the list in reverse, because events are added in the order they are raised and we want to keep - // the latest (most recent) entities and filter out what is not relevant anymore (too old), eg if an entity - // is Deleted after being Saved, we want to filter out the Saved event - for (var index = events.Count - 1; index >= 0; index--) - { - var def = events[index]; - - var infos = new EventDefinitionInfos - { - EventDefinition = def, - SupersedeTypes = argTypeSuperceeding[def.Args.GetType()] - }; - - var args = def.Args as CancellableObjectEventArgs; - if (args == null) - { - // not a cancellable event arg, include event definition in result - result.Add(def); - } - else - { - // event object can either be a single object or an enumerable of objects - // try to get as an enumerable, get null if it's not - var eventObjects = TypeHelper.CreateGenericEnumerableFromObject(args.EventObject); - if (eventObjects == null) - { - // single object, cast as an IEntity - // if cannot cast, cannot filter, nothing - just include event definition in result - var eventEntity = args.EventObject as IEntity; - if (eventEntity == null) - { - result.Add(def); - continue; - } - - // look for this entity in superseding event args - // found = must be removed (ie not added), else track - if (IsSuperceeded(eventEntity, infos, entities) == false) - { - // track - entities.Add(Tuple.Create(eventEntity, infos)); - - // track result arguments - // include event definition in result - resultArgs.Add(args); - result.Add(def); - } - } - else - { - // enumerable of objects - var toRemove = new List(); - foreach (var eventObject in eventObjects) - { - // extract the event object, cast as an IEntity - // if cannot cast, cannot filter, nothing to do - just leave it in the list & continue - var eventEntity = eventObject as IEntity; - if (eventEntity == null) - continue; - - // look for this entity in superseding event args - // found = must be removed, else track - if (IsSuperceeded(eventEntity, infos, entities)) - toRemove.Add(eventEntity); - else - entities.Add(Tuple.Create(eventEntity, infos)); - } - - // remove superseded entities - foreach (var entity in toRemove) - eventObjects.Remove(entity); - - // if there are still entities in the list, keep the event definition - if (eventObjects.Count > 0) - { - if (toRemove.Count > 0) - { - // re-assign if changed - args.EventObject = eventObjects; - } - - // track result arguments - // include event definition in result - resultArgs.Add(args); - result.Add(def); - } - } - } - } - - // go over all args in result, and update them with the latest instanceof each entity - UpdateToLatestEntities(entities, resultArgs); - - // reverse, since we processed the list in reverse - result.Reverse(); - - return result; - } - - // edits event args to use the latest instance of each entity - private static void UpdateToLatestEntities(IEnumerable> entities, IEnumerable args) - { - // get the latest entities - // ordered hash set + keepOldest will keep the latest inserted entity (in case of duplicates) - var latestEntities = new OrderedHashSet(keepOldest: true); - foreach (var entity in entities.OrderByDescending(entity => entity.Item1.UpdateDate)) - latestEntities.Add(entity.Item1); - - foreach (var arg in args) - { - // event object can either be a single object or an enumerable of objects - // try to get as an enumerable, get null if it's not - var eventObjects = TypeHelper.CreateGenericEnumerableFromObject(arg.EventObject); - if (eventObjects == null) - { - // single object - // look for a more recent entity for that object, and replace if any - // works by "equalling" entities ie the more recent one "equals" this one (though different object) - var foundEntity = latestEntities.FirstOrDefault(x => Equals(x, arg.EventObject)); - if (foundEntity != null) - arg.EventObject = foundEntity; - } - else - { - // enumerable of objects - // same as above but for each object - var updated = false; - for (var i = 0; i < eventObjects.Count; i++) - { - var foundEntity = latestEntities.FirstOrDefault(x => Equals(x, eventObjects[i])); - if (foundEntity == null) continue; - eventObjects[i] = foundEntity; - updated = true; - } - - if (updated) - arg.EventObject = eventObjects; - } - } - } - - // determines if a given entity, appearing in a given event definition, should be filtered out, - // considering the entities that have already been visited - an entity is filtered out if it - // appears in another even definition, which supersedes this event definition. - private static bool IsSuperceeded(IEntity entity, EventDefinitionInfos infos, List> entities) - { - //var argType = meta.EventArgsType; - var argType = infos.EventDefinition.Args.GetType(); - - // look for other instances of the same entity, coming from an event args that supersedes other event args, - // ie is marked with the attribute, and is not this event args (cannot supersede itself) - var superceeding = entities - .Where(x => x.Item2.SupersedeTypes.Length > 0 // has the attribute - && x.Item2.EventDefinition.Args.GetType() != argType // is not the same - && Equals(x.Item1, entity)) // same entity - .ToArray(); - - // first time we see this entity = not filtered - if (superceeding.Length == 0) - return false; - - // delete event args does NOT supersedes 'unpublished' event - if (argType.IsGenericType && argType.GetGenericTypeDefinition() == typeof(PublishEventArgs<>) && infos.EventDefinition.EventName == "Unpublished") - return false; - - // found occurrences, need to determine if this event args is superseded - if (argType.IsGenericType) - { - // generic, must compare type arguments - var supercededBy = superceeding.FirstOrDefault(x => - x.Item2.SupersedeTypes.Any(y => - // superseding a generic type which has the same generic type definition - // (but ... no matter the generic type parameters? could be different?) - y.IsGenericTypeDefinition && y == argType.GetGenericTypeDefinition() - // or superceeding a non-generic type which is ... (but... how is this ever possible? argType *is* generic? - || y.IsGenericTypeDefinition == false && y == argType)); - return supercededBy != null; - } - else - { - // non-generic, can compare types 1:1 - var supercededBy = superceeding.FirstOrDefault(x => - x.Item2.SupersedeTypes.Any(y => y == argType)); - return supercededBy != null; - } - } - - public void ScopeExit(bool completed) - { - if (_events == null) return; - if (completed) - ScopeExitCompleted(); - _events.Clear(); - } - - protected abstract void ScopeExitCompleted(); - } -} diff --git a/src/Umbraco.Infrastructure/Scoping/IScope.cs b/src/Umbraco.Infrastructure/Scoping/IScope.cs index 372e329a71..fb96a0f8a2 100644 --- a/src/Umbraco.Infrastructure/Scoping/IScope.cs +++ b/src/Umbraco.Infrastructure/Scoping/IScope.cs @@ -25,11 +25,6 @@ namespace Umbraco.Cms.Core.Scoping /// EventMessages Messages { get; } - /// - /// Gets the scope event dispatcher. - /// - IEventDispatcher Events { get; } - /// /// Gets the scope notification publisher /// diff --git a/src/Umbraco.Infrastructure/Scoping/IScopeProvider.cs b/src/Umbraco.Infrastructure/Scoping/IScopeProvider.cs index d1f82d2189..816f42aa98 100644 --- a/src/Umbraco.Infrastructure/Scoping/IScopeProvider.cs +++ b/src/Umbraco.Infrastructure/Scoping/IScopeProvider.cs @@ -18,7 +18,6 @@ namespace Umbraco.Cms.Core.Scoping /// /// The transaction isolation level. /// The repositories cache mode. - /// An optional events dispatcher. /// An optional notification publisher. /// A value indicating whether to scope the filesystems. /// A value indicating whether this scope should always be registered in the call context. @@ -35,7 +34,6 @@ namespace Umbraco.Cms.Core.Scoping IScope CreateScope( IsolationLevel isolationLevel = IsolationLevel.Unspecified, RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified, - IEventDispatcher eventDispatcher = null, IScopedNotificationPublisher scopedNotificationPublisher = null, bool? scopeFileSystems = null, bool callContext = false, @@ -47,7 +45,6 @@ namespace Umbraco.Cms.Core.Scoping /// A detached scope. /// The transaction isolation level. /// The repositories cache mode. - /// An optional events dispatcher. /// An option notification publisher. /// A value indicating whether to scope the filesystems. /// @@ -58,7 +55,6 @@ namespace Umbraco.Cms.Core.Scoping IScope CreateDetachedScope( IsolationLevel isolationLevel = IsolationLevel.Unspecified, RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified, - IEventDispatcher eventDispatcher = null, IScopedNotificationPublisher scopedNotificationPublisher = null, bool? scopeFileSystems = null); diff --git a/src/Umbraco.Infrastructure/Scoping/Scope.cs b/src/Umbraco.Infrastructure/Scoping/Scope.cs index 2383e8eb92..8f4cd9e8bd 100644 --- a/src/Umbraco.Infrastructure/Scoping/Scope.cs +++ b/src/Umbraco.Infrastructure/Scoping/Scope.cs @@ -39,7 +39,6 @@ namespace Umbraco.Cms.Core.Scoping private IUmbracoDatabase _database; private bool _disposed; - private IEventDispatcher _eventDispatcher; private ICompletable _fscope; private IsolatedCaches _isolatedCaches; @@ -68,7 +67,6 @@ namespace Umbraco.Cms.Core.Scoping bool detachable, IsolationLevel isolationLevel = IsolationLevel.Unspecified, RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified, - IEventDispatcher eventDispatcher = null, IScopedNotificationPublisher notificationPublisher = null, bool? scopeFileSystems = null, bool callContext = false, @@ -83,7 +81,6 @@ namespace Umbraco.Cms.Core.Scoping _isolationLevel = isolationLevel; _repositoryCacheMode = repositoryCacheMode; - _eventDispatcher = eventDispatcher; _notificationPublisher = notificationPublisher; _scopeFileSystem = scopeFileSystems; _callContext = callContext; @@ -141,12 +138,6 @@ namespace Umbraco.Cms.Core.Scoping nameof(repositoryCacheMode)); } - // cannot specify a dispatcher! - if (_eventDispatcher != null) - { - throw new ArgumentException("Value cannot be specified on nested scope.", nameof(eventDispatcher)); - } - // Only the outermost scope can specify the notification publisher if (_notificationPublisher != null) { @@ -187,13 +178,12 @@ namespace Umbraco.Cms.Core.Scoping IScopeContext scopeContext, IsolationLevel isolationLevel = IsolationLevel.Unspecified, RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified, - IEventDispatcher eventDispatcher = null, IScopedNotificationPublisher scopedNotificationPublisher = null, bool? scopeFileSystems = null, bool callContext = false, bool autoComplete = false) : this(scopeProvider, coreDebugSettings, mediaFileManager, eventAggregator, logger, fileSystems, null, - scopeContext, detachable, isolationLevel, repositoryCacheMode, eventDispatcher, + scopeContext, detachable, isolationLevel, repositoryCacheMode, scopedNotificationPublisher, scopeFileSystems, callContext, autoComplete) { } @@ -209,13 +199,12 @@ namespace Umbraco.Cms.Core.Scoping Scope parent, IsolationLevel isolationLevel = IsolationLevel.Unspecified, RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified, - IEventDispatcher eventDispatcher = null, IScopedNotificationPublisher notificationPublisher = null, bool? scopeFileSystems = null, bool callContext = false, bool autoComplete = false) : this(scopeProvider, coreDebugSettings, mediaFileManager, eventAggregator, logger, fileSystems, parent, - null, false, isolationLevel, repositoryCacheMode, eventDispatcher, notificationPublisher, + null, false, isolationLevel, repositoryCacheMode, notificationPublisher, scopeFileSystems, callContext, autoComplete) { } @@ -455,21 +444,6 @@ namespace Umbraco.Cms.Core.Scoping } } - /// - public IEventDispatcher Events - { - get - { - EnsureNotDisposed(); - if (ParentScope != null) - { - return ParentScope.Events; - } - - return _eventDispatcher ??= new QueuingEventDispatcher(_mediaFileManager); - } - } - public IScopedNotificationPublisher Notifications { get @@ -857,7 +831,6 @@ namespace Umbraco.Cms.Core.Scoping // deal with events if (onException == false) { - _eventDispatcher?.ScopeExit(completed); _notificationPublisher?.ScopeExit(completed); } }, () => diff --git a/src/Umbraco.Infrastructure/Scoping/ScopeProvider.cs b/src/Umbraco.Infrastructure/Scoping/ScopeProvider.cs index 4cc354cbad..9806b81d51 100644 --- a/src/Umbraco.Infrastructure/Scoping/ScopeProvider.cs +++ b/src/Umbraco.Infrastructure/Scoping/ScopeProvider.cs @@ -381,10 +381,9 @@ namespace Umbraco.Cms.Core.Scoping public IScope CreateDetachedScope( IsolationLevel isolationLevel = IsolationLevel.Unspecified, RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified, - IEventDispatcher eventDispatcher = null, IScopedNotificationPublisher scopedNotificationPublisher = null, bool? scopeFileSystems = null) - => new Scope(this, _coreDebugSettings, _mediaFileManager, _eventAggregator, _loggerFactory.CreateLogger(), _fileSystems, true, null, isolationLevel, repositoryCacheMode, eventDispatcher, scopedNotificationPublisher, scopeFileSystems); + => new Scope(this, _coreDebugSettings, _mediaFileManager, _eventAggregator, _loggerFactory.CreateLogger(), _fileSystems, true, null, isolationLevel, repositoryCacheMode, scopedNotificationPublisher, scopeFileSystems); /// public void AttachScope(IScope other, bool callContext = false) @@ -453,7 +452,6 @@ namespace Umbraco.Cms.Core.Scoping public IScope CreateScope( IsolationLevel isolationLevel = IsolationLevel.Unspecified, RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified, - IEventDispatcher eventDispatcher = null, IScopedNotificationPublisher notificationPublisher = null, bool? scopeFileSystems = null, bool callContext = false, @@ -464,7 +462,7 @@ namespace Umbraco.Cms.Core.Scoping { IScopeContext ambientContext = AmbientContext; ScopeContext newContext = ambientContext == null ? new ScopeContext() : null; - var scope = new Scope(this, _coreDebugSettings, _mediaFileManager, _eventAggregator, _loggerFactory.CreateLogger(), _fileSystems, false, newContext, isolationLevel, repositoryCacheMode, eventDispatcher, notificationPublisher, scopeFileSystems, callContext, autoComplete); + var scope = new Scope(this, _coreDebugSettings, _mediaFileManager, _eventAggregator, _loggerFactory.CreateLogger(), _fileSystems, false, newContext, isolationLevel, repositoryCacheMode, notificationPublisher, scopeFileSystems, callContext, autoComplete); // assign only if scope creation did not throw! PushAmbientScope(scope); if (newContext != null) @@ -474,7 +472,7 @@ namespace Umbraco.Cms.Core.Scoping return scope; } - var nested = new Scope(this, _coreDebugSettings, _mediaFileManager, _eventAggregator, _loggerFactory.CreateLogger(), _fileSystems, ambientScope, isolationLevel, repositoryCacheMode, eventDispatcher, notificationPublisher, scopeFileSystems, callContext, autoComplete); + var nested = new Scope(this, _coreDebugSettings, _mediaFileManager, _eventAggregator, _loggerFactory.CreateLogger(), _fileSystems, ambientScope, isolationLevel, repositoryCacheMode, notificationPublisher, scopeFileSystems, callContext, autoComplete); PushAmbientScope(nested); return nested; } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineBaseTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineBaseTest.cs index a2a82d57fb..bc1c6b8f02 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineBaseTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineBaseTest.cs @@ -88,7 +88,6 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Examine.Lucene.UmbracoExamine scopeProviderMock.Setup(x => x.CreateScope( It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopedRepositoryTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopedRepositoryTests.cs index b05257712c..0c0508e523 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopedRepositoryTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopedRepositoryTests.cs @@ -318,20 +318,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Scoping public static string GetCacheIdKey(object id) => $"{GetCacheTypeKey()}{id}"; public static string GetCacheTypeKey() => $"uRepo_{typeof(T).Name}_"; - - public class PassiveEventDispatcher : QueuingEventDispatcherBase - { - public PassiveEventDispatcher() - : base(false) - { - } - - protected override void ScopeExitCompleted() - { - // do nothing - } - } - + public class LocalServerMessenger : ServerMessengerBase { public LocalServerMessenger() diff --git a/tests/Umbraco.Tests.UnitTests/TestHelpers/PublishedSnapshotServiceTestBase.cs b/tests/Umbraco.Tests.UnitTests/TestHelpers/PublishedSnapshotServiceTestBase.cs index 4cf6ffdc9e..cfe3830970 100644 --- a/tests/Umbraco.Tests.UnitTests/TestHelpers/PublishedSnapshotServiceTestBase.cs +++ b/tests/Umbraco.Tests.UnitTests/TestHelpers/PublishedSnapshotServiceTestBase.cs @@ -230,7 +230,6 @@ namespace Umbraco.Cms.Tests.UnitTests.TestHelpers .Setup(x => x.CreateScope( It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Scoping/ScopeEventDispatcherTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Scoping/ScopeEventDispatcherTests.cs deleted file mode 100644 index 5cb21cac3c..0000000000 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Scoping/ScopeEventDispatcherTests.cs +++ /dev/null @@ -1,452 +0,0 @@ -using System; -using System.Linq; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; -using Microsoft.Extensions.Options; -using Moq; -using NUnit.Framework; -using Umbraco.Cms.Core.Cache; -using Umbraco.Cms.Core.Configuration.Models; -using Umbraco.Cms.Core.Events; -using Umbraco.Cms.Core.Hosting; -using Umbraco.Cms.Core.IO; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.PropertyEditors; -using Umbraco.Cms.Core.Scoping; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Strings; -using Umbraco.Cms.Infrastructure.Persistence; -using Umbraco.Cms.Tests.Common; -using Umbraco.Cms.Tests.Common.Builders; - -namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Scoping -{ - [TestFixture] - public class ScopeEventDispatcherTests - { - [SetUp] - public void Setup() - { - // remove all handlers first - DoThing1 = null; - DoThing2 = null; - DoThing3 = null; - } - - - - [TestCase(false, true, true)] - [TestCase(false, true, false)] - [TestCase(false, false, true)] - [TestCase(false, false, false)] - [TestCase(true, true, true)] - [TestCase(true, true, false)] - [TestCase(true, false, true)] - [TestCase(true, false, false)] - public void EventsHandling(bool passive, bool cancel, bool complete) - { - var counter1 = 0; - var counter2 = 0; - - DoThing1 += (sender, args) => { counter1++; if (cancel) args.Cancel = true; }; - DoThing2 += (sender, args) => { counter2++; }; - - var scopeProvider = GetScopeProvider(NullLoggerFactory.Instance); - using (var scope = scopeProvider.CreateScope(eventDispatcher: passive ? new PassiveEventDispatcher() : null)) - { - var cancelled = scope.Events.DispatchCancelable(DoThing1, this, new SaveEventArgs("test")); - if (cancelled == false) - scope.Events.Dispatch(DoThing2, this, new SaveEventArgs(0)); - if (complete) - scope.Complete(); - } - - var expected1 = passive ? 0 : 1; - Assert.AreEqual(expected1, counter1); - - int expected2; - if (passive) - expected2 = 0; - else - expected2 = cancel ? 0 : (complete ? 1 : 0); - - Assert.AreEqual(expected2, counter2); - } - - private ScopeProvider GetScopeProvider(NullLoggerFactory instance) - { - var fileSystems = new FileSystems( - instance, - Mock.Of(), - Options.Create(new GlobalSettings()), - Mock.Of()); - - var mediaFileManager = new MediaFileManager( - Mock.Of(), - Mock.Of(), - instance.CreateLogger(), - Mock.Of(), - Mock.Of(), - Options.Create(new ContentSettings())); - - return new ScopeProvider( - Mock.Of(), - fileSystems, - new TestOptionsMonitor(new CoreDebugSettings()), - mediaFileManager, - Mock.Of>(), - instance, - Mock.Of(), - Mock.Of() - ); - } - - [Test] - public void QueueEvents() - { - DoThing1 += OnDoThingFail; - DoThing2 += OnDoThingFail; - DoThing3 += OnDoThingFail; - - var scopeProvider = GetScopeProvider(NullLoggerFactory.Instance); - using (var scope = scopeProvider.CreateScope(eventDispatcher: new PassiveEventDispatcher())) - { - scope.Events.Dispatch(DoThing1, this, new SaveEventArgs("test")); - scope.Events.Dispatch(DoThing2, this, new SaveEventArgs(0)); - scope.Events.Dispatch(DoThing3, this, new SaveEventArgs(0)); - - // events have been queued - Assert.AreEqual(3, scope.Events.GetEvents(EventDefinitionFilter.All).Count()); - - var events = scope.Events.GetEvents(EventDefinitionFilter.All).ToArray(); - - var knownNames = new[] { "DoThing1", "DoThing2", "DoThing3" }; - var knownArgTypes = new[] { typeof(SaveEventArgs), typeof(SaveEventArgs), typeof(SaveEventArgs) }; - - for (var i = 0; i < events.Length; i++) - { - Assert.AreEqual(knownNames[i], events[i].EventName); - Assert.AreEqual(knownArgTypes[i], events[i].Args.GetType()); - } - } - } - - [Test] - public void SupersededEvents() - { - DoSaveForContent += OnDoThingFail; - DoDeleteForContent += OnDoThingFail; - DoForTestArgs += OnDoThingFail; - DoForTestArgs2 += OnDoThingFail; - - var contentType = ContentTypeBuilder.CreateBasicContentType(); - - var content1 = ContentBuilder.CreateBasicContent(contentType); - content1.Id = 123; - - var content2 = ContentBuilder.CreateBasicContent(contentType); - content2.Id = 456; - - var content3 = ContentBuilder.CreateBasicContent(contentType); - content3.Id = 789; - - var scopeProvider = GetScopeProvider(NullLoggerFactory.Instance); - using (var scope = scopeProvider.CreateScope(eventDispatcher: new PassiveEventDispatcher())) - { - - //content1 will be filtered from the args - scope.Events.Dispatch(DoSaveForContent, this, new SaveEventArgs(new[] { content1, content3 })); - scope.Events.Dispatch(DoDeleteForContent, this, new DeleteEventArgs(content1), "DoDeleteForContent"); - scope.Events.Dispatch(DoSaveForContent, this, new SaveEventArgs(content2)); - //this entire event will be filtered - scope.Events.Dispatch(DoForTestArgs, this, new TestEventArgs(content1)); - scope.Events.Dispatch(DoForTestArgs2, this, new TestEventArgs2(content1)); - - // events have been queued - var events = scope.Events.GetEvents(EventDefinitionFilter.All).ToArray(); - Assert.AreEqual(4, events.Length); - - Assert.AreEqual(typeof(SaveEventArgs), events[0].Args.GetType()); - Assert.AreEqual(1, ((SaveEventArgs)events[0].Args).SavedEntities.Count()); - Assert.AreEqual(content3.Id, ((SaveEventArgs)events[0].Args).SavedEntities.First().Id); - - Assert.AreEqual(typeof(DeleteEventArgs), events[1].Args.GetType()); - Assert.AreEqual(content1.Id, ((DeleteEventArgs)events[1].Args).DeletedEntities.First().Id); - - Assert.AreEqual(typeof(SaveEventArgs), events[2].Args.GetType()); - Assert.AreEqual(content2.Id, ((SaveEventArgs)events[2].Args).SavedEntities.First().Id); - - Assert.AreEqual(typeof(TestEventArgs2), events[3].Args.GetType()); - } - } - - [Test] - public void SupersededEvents2() - { - Test_Unpublished += OnDoThingFail; - Test_Deleted += OnDoThingFail; - - var contentService = Mock.Of(); - var content = Mock.Of(); - - var scopeProvider = GetScopeProvider(NullLoggerFactory.Instance); - using (var scope = scopeProvider.CreateScope(eventDispatcher: new PassiveEventDispatcher())) - { - scope.Events.Dispatch(Test_Unpublished, contentService, new PublishEventArgs(new[] { content }), "Unpublished"); - scope.Events.Dispatch(Test_Deleted, contentService, new DeleteEventArgs(new[] { content }), "Deleted"); - - // see U4-10764 - var events = scope.Events.GetEvents(EventDefinitionFilter.All).ToArray(); - Assert.AreEqual(2, events.Length); - } - } - - /// - /// This will test that when we track events that before we Get the events we normalize all of the - /// event entities to be the latest one (most current) found amongst the event so that there is - /// no 'stale' entities in any of the args - /// - [Test] - public void LatestEntities() - { - DoSaveForContent += OnDoThingFail; - - var now = DateTime.Now; - var contentType = ContentTypeBuilder.CreateBasicContentType(); - var content1 = ContentBuilder.CreateBasicContent(contentType); - content1.Id = 123; - content1.UpdateDate = now.AddMinutes(1); - var content2 = ContentBuilder.CreateBasicContent(contentType); - content2.Id = 123; - content2.UpdateDate = now.AddMinutes(2); - var content3 = ContentBuilder.CreateBasicContent(contentType); - content3.Id = 123; - content3.UpdateDate = now.AddMinutes(3); - - var scopeProvider = GetScopeProvider(NullLoggerFactory.Instance); - using (var scope = scopeProvider.CreateScope(eventDispatcher: new PassiveEventDispatcher())) - { - scope.Events.Dispatch(DoSaveForContent, this, new SaveEventArgs(content1)); - scope.Events.Dispatch(DoSaveForContent, this, new SaveEventArgs(content2)); - scope.Events.Dispatch(DoSaveForContent, this, new SaveEventArgs(content3)); - - // events have been queued - var events = scope.Events.GetEvents(EventDefinitionFilter.All).ToArray(); - Assert.AreEqual(3, events.Length); - - foreach (var t in events) - { - var args = (SaveEventArgs)t.Args; - foreach (var entity in args.SavedEntities) - { - Assert.AreEqual(content3, entity); - Assert.IsTrue(object.ReferenceEquals(content3, entity)); - } - } - } - } - - [Test] - public void FirstIn() - { - DoSaveForContent += OnDoThingFail; - - var now = DateTime.Now; - var contentType = ContentTypeBuilder.CreateBasicContentType(); - var content1 = ContentBuilder.CreateBasicContent(contentType); - content1.Id = 123; - content1.UpdateDate = now.AddMinutes(1); - var content2 = ContentBuilder.CreateBasicContent(contentType); - content2.Id = 123; - content1.UpdateDate = now.AddMinutes(2); - var content3 = ContentBuilder.CreateBasicContent(contentType); - content3.Id = 123; - content1.UpdateDate = now.AddMinutes(3); - - var scopeProvider = GetScopeProvider(NullLoggerFactory.Instance); - using (var scope = scopeProvider.CreateScope(eventDispatcher: new PassiveEventDispatcher())) - { - scope.Events.Dispatch(DoSaveForContent, this, new SaveEventArgs(content1)); - scope.Events.Dispatch(DoSaveForContent, this, new SaveEventArgs(content2)); - scope.Events.Dispatch(DoSaveForContent, this, new SaveEventArgs(content3)); - - // events have been queued - var events = scope.Events.GetEvents(EventDefinitionFilter.FirstIn).ToArray(); - Assert.AreEqual(1, events.Length); - Assert.AreEqual(content1, ((SaveEventArgs)events[0].Args).SavedEntities.First()); - Assert.IsTrue(object.ReferenceEquals(content1, ((SaveEventArgs)events[0].Args).SavedEntities.First())); - Assert.AreEqual(content1.UpdateDate, ((SaveEventArgs)events[0].Args).SavedEntities.First().UpdateDate); - } - } - - [Test] - public void LastIn() - { - DoSaveForContent += OnDoThingFail; - - var now = DateTime.Now; - var contentType = ContentTypeBuilder.CreateBasicContentType(); - var content1 = ContentBuilder.CreateBasicContent(contentType); - content1.Id = 123; - content1.UpdateDate = now.AddMinutes(1); - var content2 = ContentBuilder.CreateBasicContent(contentType); - content2.Id = 123; - content2.UpdateDate = now.AddMinutes(2); - var content3 = ContentBuilder.CreateBasicContent(contentType); - content3.Id = 123; - content3.UpdateDate = now.AddMinutes(3); - - var scopeProvider = GetScopeProvider(NullLoggerFactory.Instance); - using (var scope = scopeProvider.CreateScope(eventDispatcher: new PassiveEventDispatcher())) - { - scope.Events.Dispatch(DoSaveForContent, this, new SaveEventArgs(content1)); - scope.Events.Dispatch(DoSaveForContent, this, new SaveEventArgs(content2)); - scope.Events.Dispatch(DoSaveForContent, this, new SaveEventArgs(content3)); - - // events have been queued - var events = scope.Events.GetEvents(EventDefinitionFilter.LastIn).ToArray(); - Assert.AreEqual(1, events.Length); - Assert.AreEqual(content3, ((SaveEventArgs)events[0].Args).SavedEntities.First()); - Assert.IsTrue(object.ReferenceEquals(content3, ((SaveEventArgs)events[0].Args).SavedEntities.First())); - Assert.AreEqual(content3.UpdateDate, ((SaveEventArgs)events[0].Args).SavedEntities.First().UpdateDate); - } - } - - [TestCase(true)] - [TestCase(false)] - public void EventsDispatching_Passive(bool complete) - { - DoThing1 += OnDoThingFail; - DoThing2 += OnDoThingFail; - DoThing3 += OnDoThingFail; - - var scopeProvider = GetScopeProvider(NullLoggerFactory.Instance); - using (var scope = scopeProvider.CreateScope(eventDispatcher: new PassiveEventDispatcher())) - { - scope.Events.Dispatch(DoThing1, this, new SaveEventArgs("test")); - scope.Events.Dispatch(DoThing2, this, new SaveEventArgs(0)); - scope.Events.Dispatch(DoThing3, this, new SaveEventArgs(0)); - - // events have been queued - Assert.AreEqual(3, scope.Events.GetEvents(EventDefinitionFilter.All).Count()); - - if (complete) - scope.Complete(); - } - - // no event has been raised (else OnDoThingFail would have failed) - } - - [TestCase(true)] - [TestCase(false)] - public void EventsDispatching_Scope(bool complete) - { - var counter = 0; - IScope ambientScope = null; - IScopeContext ambientContext = null; - Guid value = Guid.Empty; - - var scopeProvider = GetScopeProvider(NullLoggerFactory.Instance) as ScopeProvider; - - DoThing1 += (sender, args) => { counter++; }; - DoThing2 += (sender, args) => { counter++; }; - DoThing3 += (sender, args) => - { - ambientScope = scopeProvider.AmbientScope; - ambientContext = scopeProvider.AmbientContext; - value = scopeProvider.Context.Enlist("value", Guid.NewGuid, (c, o) => { }); - counter++; - }; - - Guid guid; - using (var scope = scopeProvider.CreateScope()) - { - Assert.IsNotNull(scopeProvider.AmbientContext); - guid = scopeProvider.Context.Enlist("value", Guid.NewGuid, (c, o) => { }); - - scope.Events.Dispatch(DoThing1, this, new SaveEventArgs("test")); - scope.Events.Dispatch(DoThing2, this, new SaveEventArgs(0)); - scope.Events.Dispatch(DoThing3, this, new SaveEventArgs(0)); - - // events have been queued - Assert.AreEqual(3, scope.Events.GetEvents(EventDefinitionFilter.All).Count()); - Assert.AreEqual(0, counter); - - if (complete) - scope.Complete(); - } - - if (complete) - { - // events have been raised - Assert.AreEqual(3, counter); - Assert.IsNull(ambientScope); // scope was gone - Assert.IsNotNull(ambientContext); // but not context - Assert.AreEqual(guid, value); // so we got the same value! - } - else - { - // else, no event has been raised - Assert.AreEqual(0, counter); - } - - // everything's gone - Assert.IsNull(scopeProvider.AmbientScope); - Assert.IsNull(scopeProvider.AmbientContext); - } - - private static void OnDoThingFail(object sender, EventArgs eventArgs) - { - Assert.Fail(); - } - - public static event EventHandler> DoSaveForContent; - public static event EventHandler> DoDeleteForContent; - public static event EventHandler DoForTestArgs; - public static event EventHandler DoForTestArgs2; - public static event EventHandler> DoThing1; - public static event EventHandler> DoThing2; - - public static event TypedEventHandler> DoThing3; - - public static event TypedEventHandler> Test_Unpublished; - public static event TypedEventHandler> Test_Deleted; - - public class TestEventArgs : CancellableObjectEventArgs - { - public TestEventArgs(object eventObject) : base(eventObject) - { - } - - public object MyEventObject - { - get { return EventObject; } - } - } - - [SupersedeEvent(typeof(TestEventArgs))] - public class TestEventArgs2 : CancellableObjectEventArgs - { - public TestEventArgs2(object eventObject) : base(eventObject) - { - } - - public object MyEventObject - { - get { return EventObject; } - } - } - - public class PassiveEventDispatcher : QueuingEventDispatcherBase - { - public PassiveEventDispatcher() - : base(false) - { } - - protected override void ScopeExitCompleted() - { - // do nothing - } - } - } -} diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/LogScrubberTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/LogScrubberTests.cs index 055224e32f..fc61f90150 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/LogScrubberTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/LogScrubberTests.cs @@ -77,7 +77,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.HostedServices var mockScope = new Mock(); var mockScopeProvider = new Mock(); mockScopeProvider - .Setup(x => x.CreateScope(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(x => x.CreateScope(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(mockScope.Object); var mockLogger = new Mock>(); var mockProfilingLogger = new Mock(); diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Mapping/MappingTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Mapping/MappingTests.cs index 9c8f9da75d..f707464443 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Mapping/MappingTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Mapping/MappingTests.cs @@ -29,7 +29,6 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Mapping scopeMock.Setup(x => x.CreateScope( It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/MigrationTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/MigrationTests.cs index 988262cfd5..7dd2b8be2b 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/MigrationTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/MigrationTests.cs @@ -28,7 +28,6 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Migrations public IScope CreateScope( IsolationLevel isolationLevel = IsolationLevel.Unspecified, RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified, - IEventDispatcher eventDispatcher = null, IScopedNotificationPublisher notificationPublisher = null, bool? scopeFileSystems = null, bool callContext = false, @@ -37,7 +36,6 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Migrations public IScope CreateDetachedScope( IsolationLevel isolationLevel = IsolationLevel.Unspecified, RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified, - IEventDispatcher eventDispatcher = null, IScopedNotificationPublisher notificationPublisher = null, bool? scopeFileSystems = null) => throw new NotImplementedException(); diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberUserStoreTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberUserStoreTests.cs index db58239e5f..d7c770558d 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberUserStoreTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberUserStoreTests.cs @@ -29,7 +29,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Security var mockScope = new Mock(); var mockScopeProvider = new Mock(); mockScopeProvider - .Setup(x => x.CreateScope(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(x => x.CreateScope(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(mockScope.Object); return new MemberUserStore( diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/MemberControllerUnitTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/MemberControllerUnitTests.cs index da5175f272..315efb5890 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/MemberControllerUnitTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/MemberControllerUnitTests.cs @@ -523,7 +523,6 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers var scopeProvider = Mock.Of(x => x.CreateScope( It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), From 18a3133536ffed06c23c78890e468f337f2215a0 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Wed, 12 Jan 2022 12:32:53 +0100 Subject: [PATCH 052/141] v9: Fix max allowed content length on iis & kestrel (#11802) * Update web.config * Change web.config to align with v8 default values * Adjust kestrel options to align with v8 * Add better comment * added web.config to root * change web.config to 30mb * delete obsolete comment * No reason to have web.config to just have it default * Add back ConfigureIISServerOptions.cs * Add obsolete comment, can't link to documentation yet as it doesn't exist * Apply suggestions from code review Co-authored-by: Mole * Add link to documentation Co-authored-by: Mole --- .../DependencyInjection/UmbracoBuilderExtensions.cs | 1 - .../Security/ConfigureIISServerOptions.cs | 10 ++++++++-- .../Security/ConfigureKestrelServerOptions.cs | 4 ++-- src/Umbraco.Web.UI.Client/src/web.config | 2 +- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs index 247e364ba4..819076bbb8 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs @@ -109,7 +109,6 @@ namespace Umbraco.Extensions var appCaches = AppCaches.Create(requestCache); services.ConfigureOptions(); - services.ConfigureOptions(); services.ConfigureOptions(); IProfiler profiler = GetWebProfiler(config); diff --git a/src/Umbraco.Web.Common/Security/ConfigureIISServerOptions.cs b/src/Umbraco.Web.Common/Security/ConfigureIISServerOptions.cs index 4141669c1c..1c1460432f 100644 --- a/src/Umbraco.Web.Common/Security/ConfigureIISServerOptions.cs +++ b/src/Umbraco.Web.Common/Security/ConfigureIISServerOptions.cs @@ -1,18 +1,24 @@ +using System; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; namespace Umbraco.Cms.Web.Common.Security { + [Obsolete("This class is obsolete, as this does not configure your Maximum request length, see https://our.umbraco.com/documentation/Reference/V9-Config/MaximumUploadSizeSettings/ for information about configuring maximum request length")] public class ConfigureIISServerOptions : IConfigureOptions { private readonly IOptions _runtimeSettings; - public ConfigureIISServerOptions(IOptions runtimeSettings) => _runtimeSettings = runtimeSettings; + public ConfigureIISServerOptions(IOptions runtimeSettings) => + _runtimeSettings = runtimeSettings; + public void Configure(IISServerOptions options) { // convert from KB to bytes - options.MaxRequestBodySize = _runtimeSettings.Value.MaxRequestLength.HasValue ? _runtimeSettings.Value.MaxRequestLength.Value * 1024 : uint.MaxValue; // ~4GB is the max supported value for IIS and IIS express. + options.MaxRequestBodySize = _runtimeSettings.Value.MaxRequestLength.HasValue + ? _runtimeSettings.Value.MaxRequestLength.Value * 1024 + : uint.MaxValue; // ~4GB is the max supported value for IIS and IIS express. } } } diff --git a/src/Umbraco.Web.Common/Security/ConfigureKestrelServerOptions.cs b/src/Umbraco.Web.Common/Security/ConfigureKestrelServerOptions.cs index c11e0d8814..59ec330700 100644 --- a/src/Umbraco.Web.Common/Security/ConfigureKestrelServerOptions.cs +++ b/src/Umbraco.Web.Common/Security/ConfigureKestrelServerOptions.cs @@ -11,8 +11,8 @@ namespace Umbraco.Cms.Web.Common.Security public ConfigureKestrelServerOptions(IOptions runtimeSettings) => _runtimeSettings = runtimeSettings; public void Configure(KestrelServerOptions options) { - // convert from KB to bytes - options.Limits.MaxRequestBodySize = _runtimeSettings.Value.MaxRequestLength.HasValue ? _runtimeSettings.Value.MaxRequestLength.Value * 1024 : long.MaxValue; + // convert from KB to bytes, 52428800 bytes (50 MB) is the same as in the IIS settings + options.Limits.MaxRequestBodySize = _runtimeSettings.Value.MaxRequestLength.HasValue ? _runtimeSettings.Value.MaxRequestLength.Value * 1024 : 52428800; } } } diff --git a/src/Umbraco.Web.UI.Client/src/web.config b/src/Umbraco.Web.UI.Client/src/web.config index 6903c39608..6d14a9bab7 100644 --- a/src/Umbraco.Web.UI.Client/src/web.config +++ b/src/Umbraco.Web.UI.Client/src/web.config @@ -5,4 +5,4 @@ - \ No newline at end of file + From eb4acba08118b74a09d7bb23fcdc86d312f75e56 Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Wed, 12 Jan 2022 12:35:48 +0100 Subject: [PATCH 053/141] Set default tree group for core trees (#11801) Co-authored-by: Elitsa Marinovska --- src/Umbraco.Core/Extensions/TypeExtensions.cs | 10 ++++++++-- .../Trees/TreeCollectionBuilder.cs | 6 ++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Extensions/TypeExtensions.cs b/src/Umbraco.Core/Extensions/TypeExtensions.cs index 67a6dd1dce..c5bc99cae8 100644 --- a/src/Umbraco.Core/Extensions/TypeExtensions.cs +++ b/src/Umbraco.Core/Extensions/TypeExtensions.cs @@ -430,10 +430,16 @@ namespace Umbraco.Extensions where T : Attribute { if (type == null) return Enumerable.Empty(); - return type.GetCustomAttributes(typeof (T), inherited).OfType(); + return type.GetCustomAttributes(typeof(T), inherited).OfType(); } - /// + public static bool HasCustomAttribute(this Type type, bool inherit) + where T : Attribute + { + return type.GetCustomAttribute(inherit) != null; + } + + /// /// Tries to return a value based on a property name for an object but ignores case sensitivity /// /// diff --git a/src/Umbraco.Web.BackOffice/Trees/TreeCollectionBuilder.cs b/src/Umbraco.Web.BackOffice/Trees/TreeCollectionBuilder.cs index d8d8afe13a..08f6d7b400 100644 --- a/src/Umbraco.Web.BackOffice/Trees/TreeCollectionBuilder.cs +++ b/src/Umbraco.Web.BackOffice/Trees/TreeCollectionBuilder.cs @@ -48,6 +48,12 @@ namespace Umbraco.Cms.Web.BackOffice.Trees var attribute = controllerType.GetCustomAttribute(false); if (attribute == null) return; + + bool isCoreTree = controllerType.HasCustomAttribute(false); + + // Use section as tree group if core tree, so it isn't grouped by empty key and thus end up in "Third Party" tree group if adding custom tree nodes in other groups, e.g. "Settings" tree group. + attribute.TreeGroup = attribute.TreeGroup ?? (isCoreTree ? attribute.SectionAlias : attribute.TreeGroup); + var tree = new Tree(attribute.SortOrder, attribute.SectionAlias, attribute.TreeGroup, attribute.TreeAlias, attribute.TreeTitle, attribute.TreeUse, controllerType, attribute.IsSingleNodeTree); _trees.Add(tree); } From baba7ffed9e59362c50fa9b28e286dbe38b430cb Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 12 Jan 2022 13:54:14 +0100 Subject: [PATCH 054/141] Bugfix - Take ufprt from form data if the request has form content type, otherwise fallback to use the query (#11845) --- .../Routing/UmbracoRouteValueTransformer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs b/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs index 717a6b490a..9106c3ed09 100644 --- a/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs +++ b/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs @@ -130,7 +130,7 @@ namespace Umbraco.Cms.Web.Website.Routing IPublishedRequest publishedRequest = await RouteRequestAsync(umbracoContext); - umbracoRouteValues = await _routeValuesFactory.CreateAsync(httpContext, publishedRequest); + umbracoRouteValues = await _routeValuesFactory.CreateAsync(httpContext, publishedRequest); // now we need to do some public access checks umbracoRouteValues = await _publicAccessRequestHandler.RewriteForPublishedContentAccessAsync(httpContext, umbracoRouteValues); @@ -202,8 +202,8 @@ namespace Umbraco.Cms.Web.Website.Routing } // if it is a POST/GET then a value must be in the request - if (!httpContext.Request.Query.TryGetValue("ufprt", out StringValues encodedVal) - && (!httpContext.Request.HasFormContentType || !httpContext.Request.Form.TryGetValue("ufprt", out encodedVal))) + if ((!httpContext.Request.HasFormContentType || !httpContext.Request.Form.TryGetValue("ufprt", out StringValues encodedVal)) + && !httpContext.Request.Query.TryGetValue("ufprt", out encodedVal)) { return null; } From 52f5269e011558ceba0ed44ec23f9aed40205b54 Mon Sep 17 00:00:00 2001 From: Paul Johnson Date: Thu, 13 Jan 2022 08:48:38 +0000 Subject: [PATCH 055/141] Update AddUnique to support multiple lifetimes. (#11841) --- .../ServiceCollectionExtensions.cs | 70 ++++++++++++++----- 1 file changed, 54 insertions(+), 16 deletions(-) diff --git a/src/Umbraco.Core/DependencyInjection/ServiceCollectionExtensions.cs b/src/Umbraco.Core/DependencyInjection/ServiceCollectionExtensions.cs index 871a0bbe02..5cb9a7137f 100644 --- a/src/Umbraco.Core/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Umbraco.Core/DependencyInjection/ServiceCollectionExtensions.cs @@ -7,50 +7,88 @@ namespace Umbraco.Extensions { public static class ServiceCollectionExtensions { - public static void AddUnique(this IServiceCollection services) + /// + /// Adds a service of type with an implementation type of to the specified . + /// + /// + /// Removes all previous registrations for the type . + /// + public static void AddUnique( + this IServiceCollection services, + ServiceLifetime lifetime = ServiceLifetime.Singleton) where TService : class where TImplementing : class, TService - => services.Replace(ServiceDescriptor.Singleton()); + { + services.RemoveAll(); + services.Add(ServiceDescriptor.Describe(typeof(TService), typeof(TImplementing), lifetime)); + } /// - /// Registers a singleton instance against multiple interfaces. + /// Adds services of types & with a shared implementation type of to the specified . /// - public static void AddMultipleUnique(this IServiceCollection services) + /// + /// Removes all previous registrations for the types & . + /// + public static void AddMultipleUnique( + this IServiceCollection services, + ServiceLifetime lifetime = ServiceLifetime.Singleton) where TService1 : class where TService2 : class where TImplementing : class, TService1, TService2 { - services.AddUnique(); - services.AddUnique(factory => (TImplementing)factory.GetRequiredService()); + services.AddUnique(lifetime); + services.AddUnique(factory => (TImplementing)factory.GetRequiredService(), lifetime); } // TODO(V11): Remove this function. [Obsolete("This method is functionally equivalent to AddSingleton() please use that instead.")] public static void AddUnique(this IServiceCollection services) where TImplementing : class - => services.Replace(ServiceDescriptor.Singleton()); + { + services.RemoveAll(); + services.AddSingleton(); + } /// - /// Registers a unique service with an implementation factory. + /// Adds a service of type with an implementation factory method to the specified . /// - /// Unique services have one single implementation, and a Singleton lifetime. - public static void AddUnique(this IServiceCollection services, Func factory) + /// + /// Removes all previous registrations for the type . + /// + public static void AddUnique( + this IServiceCollection services, + Func factory, + ServiceLifetime lifetime = ServiceLifetime.Singleton) where TService : class - => services.Replace(ServiceDescriptor.Singleton(factory)); + { + services.RemoveAll(); + services.Add(ServiceDescriptor.Describe(typeof(TService), factory, lifetime)); + } /// - /// Registers a unique service with an implementing instance. + /// Adds a singleton service of the type specified by to the specified . /// - /// Unique services have one single implementation, and a Singleton lifetime. + /// + /// Removes all previous registrations for the type specified by . + /// public static void AddUnique(this IServiceCollection services, Type serviceType, object instance) - => services.Replace(ServiceDescriptor.Singleton(serviceType, instance)); + { + services.RemoveAll(serviceType); + services.AddSingleton(serviceType, instance); + } /// - /// Registers a unique service with an implementing instance. + /// Adds a singleton service of type to the specified . /// + /// + /// Removes all previous registrations for the type type . + /// public static void AddUnique(this IServiceCollection services, TService instance) where TService : class - => services.Replace(ServiceDescriptor.Singleton(instance)); + { + services.RemoveAll(); + services.AddSingleton(instance); + } internal static IServiceCollection AddLazySupport(this IServiceCollection services) { From 71ce0383301156c7e86f0637f0c2658e8691d64e Mon Sep 17 00:00:00 2001 From: Paul Johnson Date: Thu, 13 Jan 2022 09:24:30 +0000 Subject: [PATCH 056/141] Scope no longer uses MediaFileManager. Deleting media via services results in a published MediaDeletedNotification via scope.Notifications, these are fired on Scope.Complete() and picked up by the registered handlers. --- src/Umbraco.Core/Events/DeleteEventArgs.cs | 202 ------------------ .../Events/IDeletingMediaFilesEventArgs.cs | 9 - src/Umbraco.Infrastructure/Scoping/Scope.cs | 9 +- .../Scoping/ScopeProvider.cs | 10 +- .../Umbraco.Core/Components/ComponentTests.cs | 2 +- .../ScopedNotificationPublisherTests.cs | 1 - .../Scoping/ScopeUnitTests.cs | 1 - 7 files changed, 7 insertions(+), 227 deletions(-) delete mode 100644 src/Umbraco.Core/Events/DeleteEventArgs.cs delete mode 100644 src/Umbraco.Core/Events/IDeletingMediaFilesEventArgs.cs diff --git a/src/Umbraco.Core/Events/DeleteEventArgs.cs b/src/Umbraco.Core/Events/DeleteEventArgs.cs deleted file mode 100644 index 0a3f76eeb8..0000000000 --- a/src/Umbraco.Core/Events/DeleteEventArgs.cs +++ /dev/null @@ -1,202 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Umbraco.Cms.Core.Events -{ - [SupersedeEvent(typeof(SaveEventArgs<>))] - [SupersedeEvent(typeof(PublishEventArgs<>))] - [SupersedeEvent(typeof(MoveEventArgs<>))] - [SupersedeEvent(typeof(CopyEventArgs<>))] - public class DeleteEventArgs : CancellableEnumerableObjectEventArgs, IEquatable>, IDeletingMediaFilesEventArgs - { - /// - /// Constructor accepting multiple entities that are used in the delete operation - /// - /// - /// - /// - public DeleteEventArgs(IEnumerable eventObject, bool canCancel, EventMessages eventMessages) : base(eventObject, canCancel, eventMessages) - { - MediaFilesToDelete = new List(); - } - - /// - /// Constructor accepting multiple entities that are used in the delete operation - /// - /// - /// - public DeleteEventArgs(IEnumerable eventObject, EventMessages eventMessages) : base(eventObject, eventMessages) - { - MediaFilesToDelete = new List(); - } - - /// - /// Constructor accepting a single entity instance - /// - /// - /// - public DeleteEventArgs(TEntity eventObject, EventMessages eventMessages) - : base(new List { eventObject }, eventMessages) - { - MediaFilesToDelete = new List(); - } - - /// - /// Constructor accepting a single entity instance - /// - /// - /// - /// - public DeleteEventArgs(TEntity eventObject, bool canCancel, EventMessages eventMessages) - : base(new List { eventObject }, canCancel, eventMessages) - { - MediaFilesToDelete = new List(); - } - - /// - /// Constructor accepting multiple entities that are used in the delete operation - /// - /// - /// - public DeleteEventArgs(IEnumerable eventObject, bool canCancel) : base(eventObject, canCancel) - { - MediaFilesToDelete = new List(); - } - - /// - /// Constructor accepting multiple entities that are used in the delete operation - /// - /// - public DeleteEventArgs(IEnumerable eventObject) : base(eventObject) - { - MediaFilesToDelete = new List(); - } - - /// - /// Constructor accepting a single entity instance - /// - /// - public DeleteEventArgs(TEntity eventObject) - : base(new List { eventObject }) - { - MediaFilesToDelete = new List(); - } - - /// - /// Constructor accepting a single entity instance - /// - /// - /// - public DeleteEventArgs(TEntity eventObject, bool canCancel) - : base(new List { eventObject }, canCancel) - { - MediaFilesToDelete = new List(); - } - - /// - /// Returns all entities that were deleted during the operation - /// - public IEnumerable DeletedEntities - { - get => EventObject; - set => EventObject = value; - } - - /// - /// A list of media files that can be added to during a deleted operation for which Umbraco will ensure are removed - /// - public List MediaFilesToDelete { get; private set; } - - public bool Equals(DeleteEventArgs other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return base.Equals(other) && MediaFilesToDelete.SequenceEqual(other.MediaFilesToDelete); - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((DeleteEventArgs) obj); - } - - public override int GetHashCode() - { - unchecked - { - return (base.GetHashCode() * 397) ^ MediaFilesToDelete.GetHashCode(); - } - } - - public static bool operator ==(DeleteEventArgs left, DeleteEventArgs right) - { - return Equals(left, right); - } - - public static bool operator !=(DeleteEventArgs left, DeleteEventArgs right) - { - return !Equals(left, right); - } - } - - public class DeleteEventArgs : CancellableEventArgs, IEquatable - { - public DeleteEventArgs(int id, bool canCancel, EventMessages eventMessages) - : base(canCancel, eventMessages) - { - Id = id; - } - - public DeleteEventArgs(int id, bool canCancel) - : base(canCancel) - { - Id = id; - } - - public DeleteEventArgs(int id) - { - Id = id; - } - - /// - /// Gets the Id of the object being deleted. - /// - public int Id { get; private set; } - - public bool Equals(DeleteEventArgs other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return base.Equals(other) && Id == other.Id; - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((DeleteEventArgs) obj); - } - - public override int GetHashCode() - { - unchecked - { - return (base.GetHashCode() * 397) ^ Id; - } - } - - public static bool operator ==(DeleteEventArgs left, DeleteEventArgs right) - { - return Equals(left, right); - } - - public static bool operator !=(DeleteEventArgs left, DeleteEventArgs right) - { - return !Equals(left, right); - } - } -} diff --git a/src/Umbraco.Core/Events/IDeletingMediaFilesEventArgs.cs b/src/Umbraco.Core/Events/IDeletingMediaFilesEventArgs.cs deleted file mode 100644 index 9a6a4357e0..0000000000 --- a/src/Umbraco.Core/Events/IDeletingMediaFilesEventArgs.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Collections.Generic; - -namespace Umbraco.Cms.Core.Events -{ - public interface IDeletingMediaFilesEventArgs - { - List MediaFilesToDelete { get; } - } -} diff --git a/src/Umbraco.Infrastructure/Scoping/Scope.cs b/src/Umbraco.Infrastructure/Scoping/Scope.cs index 8f4cd9e8bd..3f9f6fad4c 100644 --- a/src/Umbraco.Infrastructure/Scoping/Scope.cs +++ b/src/Umbraco.Infrastructure/Scoping/Scope.cs @@ -29,7 +29,6 @@ namespace Umbraco.Cms.Core.Scoping private readonly IsolationLevel _isolationLevel; private readonly object _lockQueueLocker = new(); private readonly ILogger _logger; - private readonly MediaFileManager _mediaFileManager; private readonly RepositoryCacheMode _repositoryCacheMode; private readonly bool? _scopeFileSystem; @@ -58,7 +57,6 @@ namespace Umbraco.Cms.Core.Scoping private Scope( ScopeProvider scopeProvider, CoreDebugSettings coreDebugSettings, - MediaFileManager mediaFileManager, IEventAggregator eventAggregator, ILogger logger, FileSystems fileSystems, @@ -74,7 +72,6 @@ namespace Umbraco.Cms.Core.Scoping { _scopeProvider = scopeProvider; _coreDebugSettings = coreDebugSettings; - _mediaFileManager = mediaFileManager; _eventAggregator = eventAggregator; _logger = logger; Context = scopeContext; @@ -170,7 +167,6 @@ namespace Umbraco.Cms.Core.Scoping public Scope( ScopeProvider scopeProvider, CoreDebugSettings coreDebugSettings, - MediaFileManager mediaFileManager, IEventAggregator eventAggregator, ILogger logger, FileSystems fileSystems, @@ -182,7 +178,7 @@ namespace Umbraco.Cms.Core.Scoping bool? scopeFileSystems = null, bool callContext = false, bool autoComplete = false) - : this(scopeProvider, coreDebugSettings, mediaFileManager, eventAggregator, logger, fileSystems, null, + : this(scopeProvider, coreDebugSettings, eventAggregator, logger, fileSystems, null, scopeContext, detachable, isolationLevel, repositoryCacheMode, scopedNotificationPublisher, scopeFileSystems, callContext, autoComplete) { @@ -192,7 +188,6 @@ namespace Umbraco.Cms.Core.Scoping public Scope( ScopeProvider scopeProvider, CoreDebugSettings coreDebugSettings, - MediaFileManager mediaFileManager, IEventAggregator eventAggregator, ILogger logger, FileSystems fileSystems, @@ -203,7 +198,7 @@ namespace Umbraco.Cms.Core.Scoping bool? scopeFileSystems = null, bool callContext = false, bool autoComplete = false) - : this(scopeProvider, coreDebugSettings, mediaFileManager, eventAggregator, logger, fileSystems, parent, + : this(scopeProvider, coreDebugSettings, eventAggregator, logger, fileSystems, parent, null, false, isolationLevel, repositoryCacheMode, notificationPublisher, scopeFileSystems, callContext, autoComplete) { diff --git a/src/Umbraco.Infrastructure/Scoping/ScopeProvider.cs b/src/Umbraco.Infrastructure/Scoping/ScopeProvider.cs index 9806b81d51..43a3f304b3 100644 --- a/src/Umbraco.Infrastructure/Scoping/ScopeProvider.cs +++ b/src/Umbraco.Infrastructure/Scoping/ScopeProvider.cs @@ -29,19 +29,17 @@ namespace Umbraco.Cms.Core.Scoping private readonly IRequestCache _requestCache; private readonly FileSystems _fileSystems; private CoreDebugSettings _coreDebugSettings; - private readonly MediaFileManager _mediaFileManager; private static readonly AsyncLocal> s_scopeStack = new AsyncLocal>(); private static readonly AsyncLocal> s_scopeContextStack = new AsyncLocal>(); private static readonly string s_scopeItemKey = typeof(Scope).FullName; private static readonly string s_contextItemKey = typeof(ScopeProvider).FullName; private readonly IEventAggregator _eventAggregator; - public ScopeProvider(IUmbracoDatabaseFactory databaseFactory, FileSystems fileSystems, IOptionsMonitor coreDebugSettings, MediaFileManager mediaFileManager, ILogger logger, ILoggerFactory loggerFactory, IRequestCache requestCache, IEventAggregator eventAggregator) + public ScopeProvider(IUmbracoDatabaseFactory databaseFactory, FileSystems fileSystems, IOptionsMonitor coreDebugSettings, ILogger logger, ILoggerFactory loggerFactory, IRequestCache requestCache, IEventAggregator eventAggregator) { DatabaseFactory = databaseFactory; _fileSystems = fileSystems; _coreDebugSettings = coreDebugSettings.CurrentValue; - _mediaFileManager = mediaFileManager; _logger = logger; _loggerFactory = loggerFactory; _requestCache = requestCache; @@ -383,7 +381,7 @@ namespace Umbraco.Cms.Core.Scoping RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified, IScopedNotificationPublisher scopedNotificationPublisher = null, bool? scopeFileSystems = null) - => new Scope(this, _coreDebugSettings, _mediaFileManager, _eventAggregator, _loggerFactory.CreateLogger(), _fileSystems, true, null, isolationLevel, repositoryCacheMode, scopedNotificationPublisher, scopeFileSystems); + => new Scope(this, _coreDebugSettings, _eventAggregator, _loggerFactory.CreateLogger(), _fileSystems, true, null, isolationLevel, repositoryCacheMode, scopedNotificationPublisher, scopeFileSystems); /// public void AttachScope(IScope other, bool callContext = false) @@ -462,7 +460,7 @@ namespace Umbraco.Cms.Core.Scoping { IScopeContext ambientContext = AmbientContext; ScopeContext newContext = ambientContext == null ? new ScopeContext() : null; - var scope = new Scope(this, _coreDebugSettings, _mediaFileManager, _eventAggregator, _loggerFactory.CreateLogger(), _fileSystems, false, newContext, isolationLevel, repositoryCacheMode, notificationPublisher, scopeFileSystems, callContext, autoComplete); + var scope = new Scope(this, _coreDebugSettings, _eventAggregator, _loggerFactory.CreateLogger(), _fileSystems, false, newContext, isolationLevel, repositoryCacheMode, notificationPublisher, scopeFileSystems, callContext, autoComplete); // assign only if scope creation did not throw! PushAmbientScope(scope); if (newContext != null) @@ -472,7 +470,7 @@ namespace Umbraco.Cms.Core.Scoping return scope; } - var nested = new Scope(this, _coreDebugSettings, _mediaFileManager, _eventAggregator, _loggerFactory.CreateLogger(), _fileSystems, ambientScope, isolationLevel, repositoryCacheMode, notificationPublisher, scopeFileSystems, callContext, autoComplete); + var nested = new Scope(this, _coreDebugSettings, _eventAggregator, _loggerFactory.CreateLogger(), _fileSystems, ambientScope, isolationLevel, repositoryCacheMode, notificationPublisher, scopeFileSystems, callContext, autoComplete); PushAmbientScope(nested); return nested; } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs index 2fb4ce2f21..95462fd08f 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs @@ -69,7 +69,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Components Mock.Of(), Options.Create(new ContentSettings())); IEventAggregator eventAggregator = Mock.Of(); - var scopeProvider = new ScopeProvider(f, fs, new TestOptionsMonitor(coreDebug), mediaFileManager, loggerFactory.CreateLogger(), loggerFactory, NoAppCache.Instance, eventAggregator); + var scopeProvider = new ScopeProvider(f, fs, new TestOptionsMonitor(coreDebug), loggerFactory.CreateLogger(), loggerFactory, NoAppCache.Instance, eventAggregator); mock.Setup(x => x.GetService(typeof(ILogger))).Returns(logger); mock.Setup(x => x.GetService(typeof(ILogger))).Returns(loggerFactory.CreateLogger); diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Scoping/ScopedNotificationPublisherTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Scoping/ScopedNotificationPublisherTests.cs index 5e6b3fe382..6b90d7cb79 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Scoping/ScopedNotificationPublisherTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Scoping/ScopedNotificationPublisherTests.cs @@ -94,7 +94,6 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Scoping Mock.Of(), fileSystems, new TestOptionsMonitor(new CoreDebugSettings()), - mediaFileManager, loggerFactory.CreateLogger(), loggerFactory, Mock.Of(), diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Scoping/ScopeUnitTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Scoping/ScopeUnitTests.cs index cc4532580a..a6d723f7e8 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Scoping/ScopeUnitTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Scoping/ScopeUnitTests.cs @@ -61,7 +61,6 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Scoping databaseFactory.Object, fileSystems, new TestOptionsMonitor(new CoreDebugSettings()), - mediaFileManager, loggerFactory.CreateLogger(), loggerFactory, Mock.Of(), From 3258a676c03d5a5d2e56f6f46fe9c23db952e0ee Mon Sep 17 00:00:00 2001 From: Paul Johnson Date: Thu, 13 Jan 2022 09:29:38 +0000 Subject: [PATCH 057/141] ScopeProvider can create its own logger if required. --- src/Umbraco.Infrastructure/Scoping/ScopeProvider.cs | 10 ++++++++-- .../Umbraco.Core/Components/ComponentTests.cs | 4 ++-- .../Scoping/ScopedNotificationPublisherTests.cs | 1 - .../Umbraco.Infrastructure/Scoping/ScopeUnitTests.cs | 1 - 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Infrastructure/Scoping/ScopeProvider.cs b/src/Umbraco.Infrastructure/Scoping/ScopeProvider.cs index 43a3f304b3..1633da53df 100644 --- a/src/Umbraco.Infrastructure/Scoping/ScopeProvider.cs +++ b/src/Umbraco.Infrastructure/Scoping/ScopeProvider.cs @@ -35,12 +35,18 @@ namespace Umbraco.Cms.Core.Scoping private static readonly string s_contextItemKey = typeof(ScopeProvider).FullName; private readonly IEventAggregator _eventAggregator; - public ScopeProvider(IUmbracoDatabaseFactory databaseFactory, FileSystems fileSystems, IOptionsMonitor coreDebugSettings, ILogger logger, ILoggerFactory loggerFactory, IRequestCache requestCache, IEventAggregator eventAggregator) + public ScopeProvider( + IUmbracoDatabaseFactory databaseFactory, + FileSystems fileSystems, + IOptionsMonitor coreDebugSettings, + ILoggerFactory loggerFactory, + IRequestCache requestCache, + IEventAggregator eventAggregator) { DatabaseFactory = databaseFactory; _fileSystems = fileSystems; _coreDebugSettings = coreDebugSettings.CurrentValue; - _logger = logger; + _logger = loggerFactory.CreateLogger(); _loggerFactory = loggerFactory; _requestCache = requestCache; _eventAggregator = eventAggregator; diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs index 95462fd08f..152808d6f3 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs @@ -44,7 +44,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Components { // FIXME: use IUmbracoDatabaseFactory vs UmbracoDatabaseFactory, clean it all up! var mock = new Mock(); - NullLoggerFactory loggerFactory = NullLoggerFactory.Instance; + ILoggerFactory loggerFactory = NullLoggerFactory.Instance; ILogger logger = loggerFactory.CreateLogger("GenericLogger"); var globalSettings = new GlobalSettings(); var connectionStrings = new ConnectionStrings(); @@ -69,7 +69,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Components Mock.Of(), Options.Create(new ContentSettings())); IEventAggregator eventAggregator = Mock.Of(); - var scopeProvider = new ScopeProvider(f, fs, new TestOptionsMonitor(coreDebug), loggerFactory.CreateLogger(), loggerFactory, NoAppCache.Instance, eventAggregator); + var scopeProvider = new ScopeProvider(f, fs, new TestOptionsMonitor(coreDebug), loggerFactory, NoAppCache.Instance, eventAggregator); mock.Setup(x => x.GetService(typeof(ILogger))).Returns(logger); mock.Setup(x => x.GetService(typeof(ILogger))).Returns(loggerFactory.CreateLogger); diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Scoping/ScopedNotificationPublisherTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Scoping/ScopedNotificationPublisherTests.cs index 6b90d7cb79..6d614b62e9 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Scoping/ScopedNotificationPublisherTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Scoping/ScopedNotificationPublisherTests.cs @@ -94,7 +94,6 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Scoping Mock.Of(), fileSystems, new TestOptionsMonitor(new CoreDebugSettings()), - loggerFactory.CreateLogger(), loggerFactory, Mock.Of(), eventAggregatorMock.Object diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Scoping/ScopeUnitTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Scoping/ScopeUnitTests.cs index a6d723f7e8..6ef176ceef 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Scoping/ScopeUnitTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Scoping/ScopeUnitTests.cs @@ -61,7 +61,6 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Scoping databaseFactory.Object, fileSystems, new TestOptionsMonitor(new CoreDebugSettings()), - loggerFactory.CreateLogger(), loggerFactory, Mock.Of(), Mock.Of()); From d543dccb2726a42d4ac80f840a1dd33b2e440ee8 Mon Sep 17 00:00:00 2001 From: Paul Johnson Date: Thu, 13 Jan 2022 09:56:08 +0000 Subject: [PATCH 058/141] Attempt to make Scope.RobustExit clearer. --- src/Umbraco.Infrastructure/Scoping/Scope.cs | 49 ++++++++++++++------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/src/Umbraco.Infrastructure/Scoping/Scope.cs b/src/Umbraco.Infrastructure/Scoping/Scope.cs index 3f9f6fad4c..4c47200227 100644 --- a/src/Umbraco.Infrastructure/Scoping/Scope.cs +++ b/src/Umbraco.Infrastructure/Scoping/Scope.cs @@ -809,7 +809,7 @@ namespace Umbraco.Cms.Core.Scoping completed = false; } - TryFinally(() => + void HandleScopedFileSystems() { if (_scopeFileSystem == true) { @@ -821,14 +821,17 @@ namespace Umbraco.Cms.Core.Scoping _fscope.Dispose(); _fscope = null; } - }, () => + } + + void HandleScopedNotifications() { - // deal with events if (onException == false) { _notificationPublisher?.ScopeExit(completed); } - }, () => + } + + void HandleScopeContext() { // if *we* created it, then get rid of it if (_scopeProvider.AmbientContext == Context) @@ -843,7 +846,9 @@ namespace Umbraco.Cms.Core.Scoping _scopeProvider.PopAmbientScopeContext(); } } - }, () => + } + + void HandleDetachedScopes() { if (Detachable) { @@ -865,25 +870,35 @@ namespace Umbraco.Cms.Core.Scoping OrigScope = null; OrigContext = null; } - }); + } + + TryFinally( + HandleScopedFileSystems, + HandleScopedNotifications, + HandleScopeContext, + HandleDetachedScopes + ); } - private static void TryFinally(params Action[] actions) => TryFinally(0, actions); - - private static void TryFinally(int index, Action[] actions) + private static void TryFinally(params Action[] actions) { - if (index == actions.Length) + var exceptions = new List(); + + foreach (Action action in actions) { - return; + try + { + action(); + } + catch (Exception ex) + { + exceptions.Add(ex); + } } - try + if (exceptions.Any()) { - actions[index](); - } - finally - { - TryFinally(index + 1, actions); + throw new AggregateException(exceptions); } } From 8719010e8aabdc865a13e0af1cbb2e0d24c950ab Mon Sep 17 00:00:00 2001 From: Paul Johnson Date: Thu, 13 Jan 2022 10:07:08 +0000 Subject: [PATCH 059/141] Remove unused property from Scope --- src/Umbraco.Infrastructure/Scoping/IScope.cs | 6 +--- src/Umbraco.Infrastructure/Scoping/Scope.cs | 33 +------------------- 2 files changed, 2 insertions(+), 37 deletions(-) diff --git a/src/Umbraco.Infrastructure/Scoping/IScope.cs b/src/Umbraco.Infrastructure/Scoping/IScope.cs index fb96a0f8a2..8027e10606 100644 --- a/src/Umbraco.Infrastructure/Scoping/IScope.cs +++ b/src/Umbraco.Infrastructure/Scoping/IScope.cs @@ -20,11 +20,6 @@ namespace Umbraco.Cms.Core.Scoping /// ISqlContext SqlContext { get; } - /// - /// Gets the scope event messages. - /// - EventMessages Messages { get; } - /// /// Gets the scope notification publisher /// @@ -74,6 +69,7 @@ namespace Umbraco.Cms.Core.Scoping void ReadLock(TimeSpan timeout, int lockId); void EagerWriteLock(params int[] lockIds); + void EagerWriteLock(TimeSpan timeout, int lockId); void EagerReadLock(TimeSpan timeout, int lockId); diff --git a/src/Umbraco.Infrastructure/Scoping/Scope.cs b/src/Umbraco.Infrastructure/Scoping/Scope.cs index 4c47200227..e0f947b4aa 100644 --- a/src/Umbraco.Infrastructure/Scoping/Scope.cs +++ b/src/Umbraco.Infrastructure/Scoping/Scope.cs @@ -41,7 +41,6 @@ namespace Umbraco.Cms.Core.Scoping private ICompletable _fscope; private IsolatedCaches _isolatedCaches; - private EventMessages _messages; private IScopedNotificationPublisher _notificationPublisher; private StackQueue<(LockType lockType, TimeSpan timeout, Guid instanceId, int lockId)> _queuedLocks; @@ -205,6 +204,7 @@ namespace Umbraco.Cms.Core.Scoping } internal Dictionary> ReadLocks => _readLocksDictionary; + internal Dictionary> WriteLocks => _writeLocksDictionary; // a value indicating whether to force call-context @@ -295,15 +295,6 @@ namespace Umbraco.Cms.Core.Scoping } } - public EventMessages MessagesOrNull - { - get - { - EnsureNotDisposed(); - return ParentScope == null ? _messages : ParentScope.MessagesOrNull; - } - } - // true if Umbraco.CoreDebugSettings.LogUncompletedScope appSetting is set to "true" private bool LogUncompletedScopes => _coreDebugSettings.LogIncompletedScopes; @@ -417,28 +408,6 @@ namespace Umbraco.Cms.Core.Scoping } } - /// - public EventMessages Messages - { - get - { - EnsureNotDisposed(); - if (ParentScope != null) - { - return ParentScope.Messages; - } - - return _messages ??= new EventMessages(); - - // TODO: event messages? - // this may be a problem: the messages collection will be cleared at the end of the scope - // how shall we process it in controllers etc? if we don't want the global factory from v7? - // it'd need to be captured by the controller - // - // + rename // EventMessages = ServiceMessages or something - } - } - public IScopedNotificationPublisher Notifications { get From 81d68f48ad1b515ae14d1d5b32902a30ffaf6ae2 Mon Sep 17 00:00:00 2001 From: Paul Johnson Date: Thu, 13 Jan 2022 11:31:25 +0000 Subject: [PATCH 060/141] v10 misc - Removed some redundant event handling code. (#11842) * Removed some redundant event handling code. * Cleanup Co-authored-by: Elitsa Marinovska --- .../PublishedSnapshotServiceEventHandler.cs | 50 +------------------ .../Middleware/UmbracoRequestMiddleware.cs | 24 --------- .../ContentTypeServiceVariantsTests.cs | 13 ----- 3 files changed, 1 insertion(+), 86 deletions(-) diff --git a/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotServiceEventHandler.cs b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotServiceEventHandler.cs index 81c9710ad6..e2f8f57bbe 100644 --- a/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotServiceEventHandler.cs +++ b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotServiceEventHandler.cs @@ -1,12 +1,9 @@ using System; -using System.Collections.Generic; using System.Linq; -using Umbraco.Cms.Core; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.PublishedCache; -using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services.Changes; using Umbraco.Cms.Infrastructure.PublishedCache.Persistence; using Umbraco.Extensions; @@ -17,7 +14,6 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache /// Subscribes to Umbraco events to ensure nucache remains consistent with the source data /// public class PublishedSnapshotServiceEventHandler : - IDisposable, INotificationHandler, INotificationHandler, INotificationHandler, @@ -28,47 +24,18 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache INotificationHandler, INotificationHandler { - private readonly IRuntimeState _runtime; - private bool _disposedValue; private readonly IPublishedSnapshotService _publishedSnapshotService; private readonly INuCacheContentService _publishedContentService; - private readonly IContentService _contentService; - private readonly IMediaService _mediaService; /// /// Initializes a new instance of the class. /// public PublishedSnapshotServiceEventHandler( - IRuntimeState runtime, IPublishedSnapshotService publishedSnapshotService, - INuCacheContentService publishedContentService, - IContentService contentService, - IMediaService mediaService) + INuCacheContentService publishedContentService) { - _runtime = runtime; _publishedSnapshotService = publishedSnapshotService; _publishedContentService = publishedContentService; - _contentService = contentService; - _mediaService = mediaService; - } - - /// - /// Binds to the Umbraco events - /// - /// Returns true if binding occurred - public bool Initialize() - { - // however, the cache is NOT available until we are configured, because loading - // content (and content types) from database cannot be consistent (see notes in "Handle - // Notifications" region), so - // - notifications will be ignored - // - trying to obtain a published snapshot from the service will throw - if (_runtime.Level != RuntimeLevel.Run) - { - return false; - } - - return true; } // note: if the service is not ready, ie _isReady is false, then we still handle repository events, @@ -136,20 +103,5 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache _publishedSnapshotService.Rebuild(contentTypeIds: Array.Empty()); } } - - protected virtual void Dispose(bool disposing) - { - if (!_disposedValue) - { - _disposedValue = true; - } - } - - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); - GC.SuppressFinalize(this); - } } } diff --git a/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs b/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs index 2b83d1e33a..9df64c64ad 100644 --- a/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs +++ b/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; @@ -20,7 +19,6 @@ using Umbraco.Cms.Core.Routing; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Web; -using Umbraco.Cms.Infrastructure.PublishedCache; using Umbraco.Cms.Infrastructure.WebAssets; using Umbraco.Cms.Web.Common.Profiler; using Umbraco.Extensions; @@ -45,7 +43,6 @@ namespace Umbraco.Cms.Web.Common.Middleware private readonly IUmbracoContextFactory _umbracoContextFactory; private readonly IRequestCache _requestCache; - private readonly PublishedSnapshotServiceEventHandler _publishedSnapshotServiceEventHandler; private readonly IEventAggregator _eventAggregator; private readonly IHostingEnvironment _hostingEnvironment; private readonly UmbracoRequestPaths _umbracoRequestPaths; @@ -56,10 +53,6 @@ namespace Umbraco.Cms.Web.Common.Middleware private SmidgeOptions _smidgeOptions; private readonly WebProfiler _profiler; - private static bool s_cacheInitialized; - private static bool s_cacheInitializedFlag = false; - private static object s_cacheInitializedLock = new object(); - #pragma warning disable IDE0044 // Add readonly modifier private static bool s_firstBackOfficeRequest; private static bool s_firstBackOfficeReqestFlag; @@ -73,7 +66,6 @@ namespace Umbraco.Cms.Web.Common.Middleware ILogger logger, IUmbracoContextFactory umbracoContextFactory, IRequestCache requestCache, - PublishedSnapshotServiceEventHandler publishedSnapshotServiceEventHandler, IEventAggregator eventAggregator, IProfiler profiler, IHostingEnvironment hostingEnvironment, @@ -87,7 +79,6 @@ namespace Umbraco.Cms.Web.Common.Middleware _logger = logger; _umbracoContextFactory = umbracoContextFactory; _requestCache = requestCache; - _publishedSnapshotServiceEventHandler = publishedSnapshotServiceEventHandler; _eventAggregator = eventAggregator; _hostingEnvironment = hostingEnvironment; _umbracoRequestPaths = umbracoRequestPaths; @@ -117,8 +108,6 @@ namespace Umbraco.Cms.Web.Common.Middleware // Also MiniProfiler.Current becomes null if it is handled by the event aggregator due to async/await _profiler?.UmbracoApplicationBeginRequest(context, _runtimeState.Level); - EnsureContentCacheInitialized(); - _variationContextAccessor.VariationContext ??= new VariationContext(_defaultCultureAccessor.DefaultCulture); UmbracoContextReference umbracoContextReference = _umbracoContextFactory.EnsureUmbracoContext(); @@ -230,18 +219,5 @@ namespace Umbraco.Cms.Web.Common.Middleware IHttpScopeReference httpScopeReference = request.HttpContext.RequestServices.GetRequiredService(); httpScopeReference.Register(); } - - /// - /// Initializes the content cache one time - /// - private void EnsureContentCacheInitialized() => LazyInitializer.EnsureInitialized( - ref s_cacheInitialized, - ref s_cacheInitializedFlag, - ref s_cacheInitializedLock, - () => - { - _publishedSnapshotServiceEventHandler.Initialize(); - return true; - }); } } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentTypeServiceVariantsTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentTypeServiceVariantsTests.cs index 04617ae5f3..7e22e22dd6 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentTypeServiceVariantsTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentTypeServiceVariantsTests.cs @@ -4,19 +4,16 @@ using System; using System.Linq; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; using NPoco; using NUnit.Framework; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; using Umbraco.Cms.Infrastructure.Persistence; using Umbraco.Cms.Infrastructure.Persistence.Dtos; -using Umbraco.Cms.Infrastructure.PublishedCache; using Umbraco.Cms.Tests.Common.Builders; using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Cms.Tests.Integration.Testing; @@ -40,14 +37,6 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services private ILocalizationService LocalizationService => GetRequiredService(); - protected override void BeforeHostStart(IHost host) - { - base.BeforeHostStart(host); - - // Ensure that the events are bound on each test - PublishedSnapshotServiceEventHandler eventBinder = host.Services.GetRequiredService(); - eventBinder.Initialize(); - } protected override void CustomTestSetup(IUmbracoBuilder builder) { @@ -57,8 +46,6 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services { options.NuCacheSerializerType = NuCacheSerializerType.JSON; }); - - } private void AssertJsonStartsWith(int id, string expected) From 0eed6412359f9359f42b3cc0725d4764bb4429dc Mon Sep 17 00:00:00 2001 From: Mole Date: Thu, 13 Jan 2022 13:12:44 +0100 Subject: [PATCH 061/141] Delete temp document type file if validation fails (#11836) Co-authored-by: Elitsa Marinovska --- .../Editors/ContentTypeController.cs | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Web/Editors/ContentTypeController.cs b/src/Umbraco.Web/Editors/ContentTypeController.cs index 62787fbedb..f425599d99 100644 --- a/src/Umbraco.Web/Editors/ContentTypeController.cs +++ b/src/Umbraco.Web/Editors/ContentTypeController.cs @@ -284,9 +284,9 @@ namespace Umbraco.Web.Editors public DocumentTypeDisplay PostSave(DocumentTypeSave contentTypeSave) { - //Before we send this model into this saving/mapping pipeline, we need to do some cleanup on variations. - //If the doc type does not allow content variations, we need to update all of it's property types to not allow this either - //else we may end up with ysods. I'm unsure if the service level handles this but we'll make sure it is updated here + //Before we send this model into this saving/mapping pipeline, we need to do some cleanup on variations. + //If the doc type does not allow content variations, we need to update all of it's property types to not allow this either + //else we may end up with ysods. I'm unsure if the service level handles this but we'll make sure it is updated here if (!contentTypeSave.AllowCultureVariant) { foreach (var prop in contentTypeSave.Groups.SelectMany(x => x.Properties)) @@ -301,16 +301,16 @@ namespace Umbraco.Web.Editors saveContentType: type => Services.ContentTypeService.Save(type), beforeCreateNew: ctSave => { - //create a default template if it doesn't exist -but only if default template is == to the content type + //create a default template if it doesn't exist -but only if default template is == to the content type if (ctSave.DefaultTemplate.IsNullOrWhiteSpace() == false && ctSave.DefaultTemplate == ctSave.Alias) { var template = CreateTemplateForContentType(ctSave.Alias, ctSave.Name); - // If the alias has been manually updated before the first save, - // make sure to also update the first allowed template, as the - // name will come back as a SafeAlias of the document type name, - // not as the actual document type alias. - // For more info: http://issues.umbraco.org/issue/U4-11059 + // If the alias has been manually updated before the first save, + // make sure to also update the first allowed template, as the + // name will come back as a SafeAlias of the document type name, + // not as the actual document type alias. + // For more info: http://issues.umbraco.org/issue/U4-11059 if (ctSave.DefaultTemplate != template.Alias) { var allowedTemplates = ctSave.AllowedTemplates.ToArray(); @@ -319,7 +319,7 @@ namespace Umbraco.Web.Editors ctSave.AllowedTemplates = allowedTemplates; } - //make sure the template alias is set on the default and allowed template so we can map it back + //make sure the template alias is set on the default and allowed template so we can map it back ctSave.DefaultTemplate = template.Alias; } @@ -611,6 +611,8 @@ namespace Umbraco.Web.Editors } else { + // Cleanup the temp file + System.IO.File.Delete(destFileName); model.Notifications.Add(new Notification( Services.TextService.Localize("speechBubbles", "operationFailedHeader"), Services.TextService.Localize("media", "disallowedFileType"), @@ -619,6 +621,8 @@ namespace Umbraco.Web.Editors } else { + // Cleanup the temp file + System.IO.File.Delete(result.FileData[0].LocalFileName); model.Notifications.Add(new Notification( Services.TextService.Localize("speechBubbles", "operationFailedHeader"), Services.TextService.Localize("media", "invalidFileName"), From 3b51850e56d51cb301787a82ab010e1a749b1c64 Mon Sep 17 00:00:00 2001 From: Paul Johnson Date: Thu, 13 Jan 2022 15:28:12 +0000 Subject: [PATCH 062/141] IUmbracoDatabase shouldn't be registered singleton. --- .../DependencyInjection/UmbracoBuilder.CoreServices.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs index 6781623fe7..b979ce239d 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs @@ -68,7 +68,6 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection .AddLogging(); builder.Services.AddSingleton(); - builder.Services.AddSingleton(factory => factory.GetRequiredService().CreateDatabase()); builder.Services.AddSingleton(factory => factory.GetRequiredService().SqlContext); builder.NPocoMappers().Add(); builder.PackageMigrationPlans().Add(() => builder.TypeLoader.GetPackageMigrationPlans()); From 4a477c7378db825e9c66aa5d1c3dad06c07650b9 Mon Sep 17 00:00:00 2001 From: Paul Johnson Date: Thu, 13 Jan 2022 15:48:40 +0000 Subject: [PATCH 063/141] Expose IQuery from ScopeProvider. So that it can be used without requiring visibility of ISqlContext. --- src/Umbraco.Infrastructure/Scoping/IScopeProvider.cs | 3 +++ src/Umbraco.Infrastructure/Scoping/ScopeProvider.cs | 3 +++ .../Services/Implement/RepositoryService.cs | 6 ++++-- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Infrastructure/Scoping/IScopeProvider.cs b/src/Umbraco.Infrastructure/Scoping/IScopeProvider.cs index 816f42aa98..51b7ba874a 100644 --- a/src/Umbraco.Infrastructure/Scoping/IScopeProvider.cs +++ b/src/Umbraco.Infrastructure/Scoping/IScopeProvider.cs @@ -1,5 +1,6 @@ using System.Data; using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Infrastructure.Persistence; #if DEBUG_SCOPES @@ -87,6 +88,8 @@ namespace Umbraco.Cms.Core.Scoping /// ISqlContext SqlContext { get; } + IQuery CreateQuery(); + #if DEBUG_SCOPES IEnumerable ScopeInfos { get; } diff --git a/src/Umbraco.Infrastructure/Scoping/ScopeProvider.cs b/src/Umbraco.Infrastructure/Scoping/ScopeProvider.cs index 1633da53df..944c547236 100644 --- a/src/Umbraco.Infrastructure/Scoping/ScopeProvider.cs +++ b/src/Umbraco.Infrastructure/Scoping/ScopeProvider.cs @@ -11,6 +11,7 @@ using Umbraco.Extensions; using System.Collections.Generic; using System.Collections.Concurrent; using System.Threading; +using Umbraco.Cms.Core.Persistence.Querying; #if DEBUG_SCOPES using System.Linq; @@ -60,6 +61,8 @@ namespace Umbraco.Cms.Core.Scoping public ISqlContext SqlContext => DatabaseFactory.SqlContext; + public IQuery CreateQuery() => SqlContext.Query(); + #region Context private void MoveHttpContextScopeToCallContext() diff --git a/src/Umbraco.Infrastructure/Services/Implement/RepositoryService.cs b/src/Umbraco.Infrastructure/Services/Implement/RepositoryService.cs index b1e60b73f4..e529616961 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/RepositoryService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/RepositoryService.cs @@ -1,4 +1,4 @@ -using System; +using System; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Persistence.Querying; @@ -12,7 +12,9 @@ namespace Umbraco.Cms.Core.Services.Implement public abstract class RepositoryService : IService { protected IEventMessagesFactory EventMessagesFactory { get; } + protected IScopeProvider ScopeProvider { get; } + protected ILoggerFactory LoggerFactory { get; } protected RepositoryService(IScopeProvider provider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory) @@ -22,6 +24,6 @@ namespace Umbraco.Cms.Core.Services.Implement LoggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); } - protected IQuery Query() => ScopeProvider.SqlContext.Query(); + protected IQuery Query() => ScopeProvider.CreateQuery(); } } From 022042a0ce20acf4d4ac58340c2389cd7db0d65c Mon Sep 17 00:00:00 2001 From: Paul Johnson Date: Thu, 13 Jan 2022 17:44:39 +0000 Subject: [PATCH 064/141] EntityService should delegate more to repository. --- .../Repositories/IEntityRepository.cs | 3 +- .../Implement/EntityRepository.cs | 32 +++++++++++++ .../Services/Implement/EntityService.cs | 48 ++----------------- 3 files changed, 39 insertions(+), 44 deletions(-) diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/IEntityRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/IEntityRepository.cs index a0bfdd8a53..bc4f82b02b 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/IEntityRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/IEntityRepository.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using NPoco; using Umbraco.Cms.Core.Models; @@ -36,6 +36,7 @@ namespace Umbraco.Cms.Core.Persistence.Repositories UmbracoObjectTypes GetObjectType(int id); UmbracoObjectTypes GetObjectType(Guid key); + int ReserveId(Guid key); IEnumerable GetAllPaths(Guid objectType, params int[] ids); IEnumerable GetAllPaths(Guid objectType, params Guid[] keys); diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepository.cs index f1b9c77d0a..83cb31774d 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepository.cs @@ -251,6 +251,38 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement return ObjectTypes.GetUmbracoObjectType(Database.ExecuteScalar(sql)); } + public int ReserveId(Guid key) + { + NodeDto node; + + Sql sql = SqlContext.Sql() + .Select() + .From() + .Where(x => x.UniqueId == key && x.NodeObjectType == Cms.Core.Constants.ObjectTypes.IdReservation); + + node = Database.SingleOrDefault(sql); + if (node != null) + throw new InvalidOperationException("An identifier has already been reserved for this Udi."); + + node = new NodeDto + { + UniqueId = key, + Text = "RESERVED.ID", + NodeObjectType = Cms.Core.Constants.ObjectTypes.IdReservation, + + CreateDate = DateTime.Now, + UserId = null, + ParentId = -1, + Level = 1, + Path = "-1", + SortOrder = 0, + Trashed = false + }; + Database.Insert(node); + + return node.NodeId; + } + public bool Exists(Guid key) { var sql = Sql().SelectCount().From().Where(x => x.UniqueId == key); diff --git a/src/Umbraco.Infrastructure/Services/Implement/EntityService.cs b/src/Umbraco.Infrastructure/Services/Implement/EntityService.cs index 0cbcc8d729..34eb122c73 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/EntityService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/EntityService.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; @@ -398,13 +398,7 @@ namespace Umbraco.Cms.Core.Services.Implement { using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { - var sql = scope.SqlContext.Sql() - .Select(x => x.NodeObjectType) - .From() - .Where(x => x.NodeId == id); - - var guid = scope.Database.ExecuteScalar(sql); - return ObjectTypes.GetUmbracoObjectType(guid); + return _entityRepository.GetObjectType(id); } } @@ -413,13 +407,7 @@ namespace Umbraco.Cms.Core.Services.Implement { using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { - var sql = scope.SqlContext.Sql() - .Select(x => x.NodeObjectType) - .From() - .Where(x => x.UniqueId == key); - - var guid = scope.Database.ExecuteScalar(sql); - return ObjectTypes.GetUmbracoObjectType(guid); + return _entityRepository.GetObjectType(key); } } @@ -483,36 +471,10 @@ namespace Umbraco.Cms.Core.Services.Implement /// public int ReserveId(Guid key) { - NodeDto node; - using (var scope = ScopeProvider.CreateScope()) + using (ScopeProvider.CreateScope(autoComplete: true)) { - var sql = scope.SqlContext.Sql() - .Select() - .From() - .Where(x => x.UniqueId == key && x.NodeObjectType == Cms.Core.Constants.ObjectTypes.IdReservation); - - node = scope.Database.SingleOrDefault(sql); - if (node != null) - throw new InvalidOperationException("An identifier has already been reserved for this Udi."); - - node = new NodeDto - { - UniqueId = key, - Text = "RESERVED.ID", - NodeObjectType = Cms.Core.Constants.ObjectTypes.IdReservation, - - CreateDate = DateTime.Now, - UserId = null, - ParentId = -1, - Level = 1, - Path = "-1", - SortOrder = 0, - Trashed = false - }; - scope.Database.Insert(node); - scope.Complete(); + return _entityRepository.ReserveId(key); } - return node.NodeId; } } } From eb841a7b980f562475e7e5adb88aac5423bdec89 Mon Sep 17 00:00:00 2001 From: Paul Johnson Date: Thu, 13 Jan 2022 17:44:11 +0000 Subject: [PATCH 065/141] Remove database properties from IScope --- .../Scoping/IScope.cs | 11 --- .../Scoping/IScopeProvider.cs | 5 +- .../Cache/DefaultRepositoryCachePolicy.cs | 1 + .../Cache/FullDataSetRepositoryCachePolicy.cs | 3 +- .../Cache/RepositoryCachePolicyBase.cs | 3 +- .../SingleItemsOnlyRepositoryCachePolicy.cs | 3 +- .../UmbracoBuilder.CoreServices.cs | 5 +- .../Migrations/Install/DatabaseBuilder.cs | 16 ++-- .../Migrations/MigrationPlanExecutor.cs | 6 +- .../Implement/AuditEntryRepository.cs | 1 + .../Repositories/Implement/AuditRepository.cs | 2 +- .../Implement/CacheInstructionRepository.cs | 4 +- .../Implement/ConsentRepository.cs | 2 +- .../Implement/ContentRepositoryBase.cs | 2 +- .../Implement/ContentTypeCommonRepository.cs | 3 +- .../Implement/ContentTypeRepository.cs | 2 +- .../Implement/ContentTypeRepositoryBase.cs | 4 +- .../Implement/DataTypeContainerRepository.cs | 4 +- .../Implement/DataTypeRepository.cs | 2 +- .../Implement/DictionaryRepository.cs | 2 +- .../Implement/DocumentBlueprintRepository.cs | 2 +- .../Implement/DocumentRepository.cs | 1 + .../DocumentTypeContainerRepository.cs | 4 +- .../Implement/DocumentVersionRepository.cs | 1 + .../Implement/DomainRepository.cs | 1 + .../Implement/EntityContainerRepository.cs | 2 +- .../Implement/EntityRepository.cs | 2 +- .../Implement/EntityRepositoryBase.cs | 1 + .../Implement/ExternalLoginRepository.cs | 2 +- .../Implement/KeyValueRepository.cs | 2 +- .../Implement/LanguageRepository.cs | 2 +- .../Implement/LogViewerQueryRepository.cs | 4 +- .../Repositories/Implement/MacroRepository.cs | 2 +- .../Repositories/Implement/MediaRepository.cs | 2 +- .../Implement/MediaTypeContainerRepository.cs | 4 +- .../Implement/MediaTypeRepository.cs | 4 +- .../Implement/MemberGroupRepository.cs | 2 +- .../Implement/MemberRepository.cs | 1 + .../Implement/MemberTypeRepository.cs | 2 +- .../Implement/NotificationsRepository.cs | 5 +- .../Implement/PermissionRepository.cs | 2 +- .../Implement/PublicAccessRepository.cs | 2 +- .../Implement/RedirectUrlRepository.cs | 2 +- .../Implement/RelationRepository.cs | 2 +- .../Implement/RelationTypeRepository.cs | 2 +- .../Repositories/Implement/RepositoryBase.cs | 6 +- .../Implement/ServerRegistrationRepository.cs | 1 + .../Implement/SimpleGetRepository.cs | 2 +- .../Repositories/Implement/TagRepository.cs | 2 +- .../Implement/TemplateRepository.cs | 2 +- .../Implement/UserGroupRepository.cs | 2 +- .../Repositories/Implement/UserRepository.cs | 2 +- .../Scoping/IDatabaseScope.cs | 18 +++++ .../Scoping/IScopeAccessor.cs | 10 +-- src/Umbraco.Infrastructure/Scoping/Scope.cs | 3 +- .../Scoping/ScopeProvider.cs | 5 +- .../Services/IdKeyMap.cs | 15 ++-- .../UmbracoBuilderExtensions.cs | 3 +- .../Persistence/NuCacheContentRepository.cs | 3 +- .../Testing/UmbracoIntegrationTest.cs | 1 + .../Migrations/AdvancedMigrationTests.cs | 2 +- .../Packaging/PackageDataInstallationTests.cs | 4 +- .../Persistence/LocksTests.cs | 14 ++-- .../NPocoTests/NPocoBulkInsertTests.cs | 14 ++-- .../Persistence/NPocoTests/NPocoFetchTests.cs | 62 ++++++++-------- .../Repositories/AuditRepositoryTest.cs | 23 +++--- .../CacheInstructionRepositoryTest.cs | 3 +- .../Repositories/ContentTypeRepositoryTest.cs | 7 +- .../DataTypeDefinitionRepositoryTest.cs | 4 +- .../Repositories/DictionaryRepositoryTest.cs | 4 +- .../Repositories/DocumentRepositoryTest.cs | 69 ++++++++--------- .../DocumentVersionRepositoryTest.cs | 7 +- .../Repositories/DomainRepositoryTest.cs | 1 + .../Repositories/EntityRepositoryTest.cs | 3 +- .../Repositories/KeyValueRepositoryTests.cs | 1 + .../Repositories/LanguageRepositoryTest.cs | 7 +- .../Repositories/MacroRepositoryTest.cs | 5 +- .../Repositories/MediaRepositoryTest.cs | 29 ++++---- .../Repositories/MediaTypeRepositoryTest.cs | 5 +- .../Repositories/MemberRepositoryTest.cs | 36 +++++---- .../Repositories/MemberTypeRepositoryTest.cs | 1 + .../NotificationsRepositoryTest.cs | 23 +++--- .../PublicAccessRepositoryTest.cs | 5 +- .../RedirectUrlRepositoryTests.cs | 1 + .../Repositories/RelationRepositoryTest.cs | 5 +- .../RelationTypeRepositoryTest.cs | 5 +- .../ServerRegistrationRepositoryTest.cs | 1 + .../Repositories/TagRepositoryTest.cs | 3 +- .../Repositories/TemplateRepositoryTest.cs | 1 + .../Repositories/UserGroupRepositoryTest.cs | 5 +- .../Repositories/UserRepositoryTest.cs | 29 ++++---- .../Persistence/SchemaValidationTest.cs | 4 +- .../Persistence/SqlServerTableByTableTest.cs | 68 ++++++++--------- .../Scoping/ScopeTests.cs | 74 ++++++++++--------- .../Services/CacheInstructionServiceTests.cs | 2 +- .../Services/ContentServiceTagsTests.cs | 14 ++-- .../ContentTypeServiceVariantsTests.cs | 2 +- .../ContentVersionCleanupServiceTest.cs | 8 +- .../Services/ExternalLoginServiceTests.cs | 4 +- .../Services/LocalizationServiceTests.cs | 8 +- .../Services/MacroServiceTests.cs | 1 + .../Services/MediaServiceTests.cs | 4 +- .../Services/MemberServiceTests.cs | 8 +- .../Services/RedirectUrlServiceTests.cs | 3 +- .../Services/ThreadSafetyServiceTest.cs | 4 +- .../Cache/DefaultCachePolicyTests.cs | 3 +- .../Cache/FullDataSetCachePolicyTests.cs | 3 +- .../Cache/SingleItemsOnlyCachePolicyTests.cs | 3 +- .../Migrations/MigrationPlanTests.cs | 5 +- .../Migrations/MigrationTests.cs | 10 ++- .../Migrations/PostMigrationTests.cs | 13 ++-- 111 files changed, 453 insertions(+), 379 deletions(-) rename src/{Umbraco.Infrastructure => Umbraco.Core}/Scoping/IScope.cs (88%) rename src/{Umbraco.Infrastructure => Umbraco.Core}/Scoping/IScopeProvider.cs (97%) create mode 100644 src/Umbraco.Infrastructure/Scoping/IDatabaseScope.cs diff --git a/src/Umbraco.Infrastructure/Scoping/IScope.cs b/src/Umbraco.Core/Scoping/IScope.cs similarity index 88% rename from src/Umbraco.Infrastructure/Scoping/IScope.cs rename to src/Umbraco.Core/Scoping/IScope.cs index 8027e10606..312be1a35f 100644 --- a/src/Umbraco.Infrastructure/Scoping/IScope.cs +++ b/src/Umbraco.Core/Scoping/IScope.cs @@ -1,7 +1,6 @@ using System; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Events; -using Umbraco.Cms.Infrastructure.Persistence; namespace Umbraco.Cms.Core.Scoping { @@ -10,16 +9,6 @@ namespace Umbraco.Cms.Core.Scoping /// public interface IScope : IDisposable, IInstanceIdentifiable { - /// - /// Gets the scope database. - /// - IUmbracoDatabase Database { get; } - - /// - /// Gets the Sql context. - /// - ISqlContext SqlContext { get; } - /// /// Gets the scope notification publisher /// diff --git a/src/Umbraco.Infrastructure/Scoping/IScopeProvider.cs b/src/Umbraco.Core/Scoping/IScopeProvider.cs similarity index 97% rename from src/Umbraco.Infrastructure/Scoping/IScopeProvider.cs rename to src/Umbraco.Core/Scoping/IScopeProvider.cs index 51b7ba874a..c12680763f 100644 --- a/src/Umbraco.Infrastructure/Scoping/IScopeProvider.cs +++ b/src/Umbraco.Core/Scoping/IScopeProvider.cs @@ -1,7 +1,6 @@ using System.Data; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Persistence.Querying; -using Umbraco.Cms.Infrastructure.Persistence; #if DEBUG_SCOPES using System.Collections.Generic; @@ -84,10 +83,8 @@ namespace Umbraco.Cms.Core.Scoping IScopeContext Context { get; } /// - /// Gets the sql context. + /// Creates an instance of /// - ISqlContext SqlContext { get; } - IQuery CreateQuery(); #if DEBUG_SCOPES diff --git a/src/Umbraco.Infrastructure/Cache/DefaultRepositoryCachePolicy.cs b/src/Umbraco.Infrastructure/Cache/DefaultRepositoryCachePolicy.cs index ef7255a0d6..b8fc8d68a5 100644 --- a/src/Umbraco.Infrastructure/Cache/DefaultRepositoryCachePolicy.cs +++ b/src/Umbraco.Infrastructure/Cache/DefaultRepositoryCachePolicy.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Extensions; namespace Umbraco.Cms.Core.Cache diff --git a/src/Umbraco.Infrastructure/Cache/FullDataSetRepositoryCachePolicy.cs b/src/Umbraco.Infrastructure/Cache/FullDataSetRepositoryCachePolicy.cs index a04cd69900..6b2be94d45 100644 --- a/src/Umbraco.Infrastructure/Cache/FullDataSetRepositoryCachePolicy.cs +++ b/src/Umbraco.Infrastructure/Cache/FullDataSetRepositoryCachePolicy.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using System; @@ -7,6 +7,7 @@ using System.Linq; using Umbraco.Cms.Core.Collections; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Extensions; namespace Umbraco.Cms.Core.Cache diff --git a/src/Umbraco.Infrastructure/Cache/RepositoryCachePolicyBase.cs b/src/Umbraco.Infrastructure/Cache/RepositoryCachePolicyBase.cs index 8913de956b..6124d9fb7a 100644 --- a/src/Umbraco.Infrastructure/Cache/RepositoryCachePolicyBase.cs +++ b/src/Umbraco.Infrastructure/Cache/RepositoryCachePolicyBase.cs @@ -1,10 +1,11 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using System; using System.Collections.Generic; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Infrastructure.Scoping; namespace Umbraco.Cms.Core.Cache { diff --git a/src/Umbraco.Infrastructure/Cache/SingleItemsOnlyRepositoryCachePolicy.cs b/src/Umbraco.Infrastructure/Cache/SingleItemsOnlyRepositoryCachePolicy.cs index 5b1420be65..f74d602bb8 100644 --- a/src/Umbraco.Infrastructure/Cache/SingleItemsOnlyRepositoryCachePolicy.cs +++ b/src/Umbraco.Infrastructure/Cache/SingleItemsOnlyRepositoryCachePolicy.cs @@ -1,8 +1,9 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Infrastructure.Scoping; namespace Umbraco.Cms.Core.Cache { diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs index b979ce239d..5a4faa6c43 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs @@ -50,6 +50,7 @@ using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_8_0_0.DataTypes; using Umbraco.Cms.Infrastructure.Persistence; using Umbraco.Cms.Infrastructure.Persistence.Mappers; using Umbraco.Cms.Infrastructure.Runtime; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Cms.Infrastructure.Search; using Umbraco.Cms.Infrastructure.Serialization; using Umbraco.Extensions; @@ -92,9 +93,11 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection builder.Mappers().AddCoreMappers(); // register the scope provider - builder.Services.AddSingleton(); // implements both IScopeProvider and IScopeAccessor + builder.Services.AddSingleton(); // implements IScopeProvider, IScopeAccessor builder.Services.AddSingleton(f => f.GetRequiredService()); builder.Services.AddSingleton(f => f.GetRequiredService()); + + builder.Services.AddScoped(); builder.Services.AddSingleton(); diff --git a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseBuilder.cs b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseBuilder.cs index 78dbdddc84..0aa2cdda57 100644 --- a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseBuilder.cs +++ b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseBuilder.cs @@ -10,6 +10,7 @@ using Umbraco.Cms.Core.Services; using Umbraco.Cms.Infrastructure.Migrations.Upgrade; using Umbraco.Cms.Infrastructure.Persistence; using Umbraco.Cms.Infrastructure.Persistence.Dtos; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.Migrations.Install @@ -21,6 +22,7 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install { private readonly IUmbracoDatabaseFactory _databaseFactory; private readonly IScopeProvider _scopeProvider; + private readonly IScopeAccessor _scopeAccessor; private readonly IRuntimeState _runtimeState; private readonly IKeyValueService _keyValueService; private readonly ILogger _logger; @@ -38,6 +40,7 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install /// public DatabaseBuilder( IScopeProvider scopeProvider, + IScopeAccessor scopeAccessor, IUmbracoDatabaseFactory databaseFactory, IRuntimeState runtimeState, ILoggerFactory loggerFactory, @@ -50,6 +53,7 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install DatabaseSchemaCreatorFactory databaseSchemaCreatorFactory) { _scopeProvider = scopeProvider; + _scopeAccessor = scopeAccessor; _databaseFactory = databaseFactory; _runtimeState = runtimeState; _logger = loggerFactory.CreateLogger(); @@ -113,17 +117,17 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install using (var scope = _scopeProvider.CreateScope()) { // look for the super user with default password - var sql = scope.Database.SqlContext.Sql() + var sql = _scopeAccessor.AmbientScope.Database.SqlContext.Sql() .SelectCount() .From() .Where(x => x.Id == Constants.Security.SuperUserId && x.Password == "default"); - var result = scope.Database.ExecuteScalar(sql); + var result = _scopeAccessor.AmbientScope.Database.ExecuteScalar(sql); var has = result != 1; if (has == false) { // found only 1 user == the default user with default password // however this always exists on uCloud, also need to check if there are other users too - result = scope.Database.ExecuteScalar("SELECT COUNT(*) FROM umbracoUser"); + result = _scopeAccessor.AmbientScope.Database.ExecuteScalar("SELECT COUNT(*) FROM umbracoUser"); has = result != 1; } scope.Complete(); @@ -135,7 +139,7 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install { using (var scope = _scopeProvider.CreateScope(autoComplete: true)) { - return scope.Database.IsUmbracoInstalled(); + return _scopeAccessor.AmbientScope.Database.IsUmbracoInstalled(); } } @@ -336,7 +340,7 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install if (_databaseSchemaValidationResult != null) return _databaseSchemaValidationResult; - _databaseSchemaValidationResult = scope.Database.ValidateSchema(); + _databaseSchemaValidationResult = _scopeAccessor.AmbientScope.Database.ValidateSchema(); scope.Complete(); @@ -372,7 +376,7 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install _logger.LogInformation("Database configuration status: Started"); - var database = scope.Database; + var database = _scopeAccessor.AmbientScope.Database; var message = string.Empty; diff --git a/src/Umbraco.Infrastructure/Migrations/MigrationPlanExecutor.cs b/src/Umbraco.Infrastructure/Migrations/MigrationPlanExecutor.cs index 09cddbc20b..358d7d0281 100644 --- a/src/Umbraco.Infrastructure/Migrations/MigrationPlanExecutor.cs +++ b/src/Umbraco.Infrastructure/Migrations/MigrationPlanExecutor.cs @@ -4,6 +4,7 @@ using System.Linq; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Migrations; using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Extensions; using Type = System.Type; @@ -12,16 +13,19 @@ namespace Umbraco.Cms.Infrastructure.Migrations public class MigrationPlanExecutor : IMigrationPlanExecutor { private readonly IScopeProvider _scopeProvider; + private readonly IScopeAccessor _scopeAccessor; private readonly ILoggerFactory _loggerFactory; private readonly IMigrationBuilder _migrationBuilder; private readonly ILogger _logger; public MigrationPlanExecutor( IScopeProvider scopeProvider, + IScopeAccessor scopeAccessor, ILoggerFactory loggerFactory, IMigrationBuilder migrationBuilder) { _scopeProvider = scopeProvider; + _scopeAccessor = scopeAccessor; _loggerFactory = loggerFactory; _migrationBuilder = migrationBuilder; _logger = _loggerFactory.CreateLogger(); @@ -61,7 +65,7 @@ namespace Umbraco.Cms.Infrastructure.Migrations // that packages notification handlers may explode because that package isn't fully installed yet. using (scope.Notifications.Suppress()) { - var context = new MigrationContext(plan, scope.Database, _loggerFactory.CreateLogger()); + var context = new MigrationContext(plan, _scopeAccessor.AmbientScope.Database, _loggerFactory.CreateLogger()); while (transition != null) { diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditEntryRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditEntryRepository.cs index e3685dd32c..9cadc82e49 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditEntryRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditEntryRepository.cs @@ -12,6 +12,7 @@ using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Infrastructure.Persistence.Factories; using Umbraco.Cms.Infrastructure.Persistence.Querying; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditRepository.cs index d528f69dce..644dc4bc26 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditRepository.cs @@ -8,9 +8,9 @@ using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Core.Persistence.Repositories; -using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Infrastructure.Persistence.Querying; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CacheInstructionRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CacheInstructionRepository.cs index d0e025e06e..3ce9aad530 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CacheInstructionRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CacheInstructionRepository.cs @@ -4,9 +4,9 @@ using System.Linq; using NPoco; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Repositories; -using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Infrastructure.Persistence.Factories; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement @@ -21,7 +21,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement public CacheInstructionRepository(IScopeAccessor scopeAccessor) => _scopeAccessor = scopeAccessor; /// - private IScope AmbientScope => _scopeAccessor.AmbientScope; + private IDatabaseScope AmbientScope => _scopeAccessor.AmbientScope; /// public int CountAll() diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ConsentRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ConsentRepository.cs index 87a112fe08..194369285b 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ConsentRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ConsentRepository.cs @@ -6,10 +6,10 @@ using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Core.Persistence.Repositories; -using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Infrastructure.Persistence.Factories; using Umbraco.Cms.Infrastructure.Persistence.Querying; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs index fd4d1c33b9..0b4af6bdee 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs @@ -15,12 +15,12 @@ using Umbraco.Cms.Core.Persistence; using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.PropertyEditors; -using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Infrastructure.Persistence.Factories; using Umbraco.Cms.Infrastructure.Persistence.Querying; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Extensions; using static Umbraco.Cms.Core.Persistence.SqlExtensionsStatics; diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs index 04ca55b499..66ab87b743 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs @@ -12,6 +12,7 @@ using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Strings; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Infrastructure.Persistence.Factories; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Extensions; using Enumerable = System.Linq.Enumerable; @@ -42,7 +43,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement _shortStringHelper = shortStringHelper; } - private IScope AmbientScope => _scopeAccessor.AmbientScope; + private IDatabaseScope AmbientScope => _scopeAccessor.AmbientScope; private IUmbracoDatabase Database => AmbientScope.Database; private ISqlContext SqlContext => AmbientScope.SqlContext; diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepository.cs index 9b0fe45c79..56fd356011 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepository.cs @@ -8,11 +8,11 @@ using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Core.Persistence.Repositories; -using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Strings; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Infrastructure.Persistence.Querying; using Umbraco.Cms.Infrastructure.Persistence.SqlSyntax; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs index 38e8a99d08..5ffdf4bf10 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Data; using System.Globalization; @@ -12,12 +12,12 @@ using Umbraco.Cms.Core.Exceptions; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence; using Umbraco.Cms.Core.Persistence.Repositories; -using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Infrastructure.Persistence.Factories; using Umbraco.Cms.Infrastructure.Persistence.Querying; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DataTypeContainerRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DataTypeContainerRepository.cs index 6128b2e9b2..f8fc9e14be 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DataTypeContainerRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DataTypeContainerRepository.cs @@ -1,7 +1,7 @@ -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Persistence.Repositories; -using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Infrastructure.Scoping; namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement { diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DataTypeRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DataTypeRepository.cs index f70048c4f9..954d3e0d0f 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DataTypeRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DataTypeRepository.cs @@ -13,12 +13,12 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.PropertyEditors; -using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Infrastructure.Persistence.Factories; using Umbraco.Cms.Infrastructure.Persistence.Querying; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Extensions; using static Umbraco.Cms.Core.Persistence.SqlExtensionsStatics; diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DictionaryRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DictionaryRepository.cs index bc9892b1ee..98b1820204 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DictionaryRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DictionaryRepository.cs @@ -9,10 +9,10 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Core.Persistence.Repositories; -using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Infrastructure.Persistence.Factories; using Umbraco.Cms.Infrastructure.Persistence.Querying; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentBlueprintRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentBlueprintRepository.cs index 79663c292e..f97aec0917 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentBlueprintRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentBlueprintRepository.cs @@ -4,9 +4,9 @@ using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.PropertyEditors; -using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.Scoping; namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement { diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs index 41645dba27..0d77f3d53b 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs @@ -21,6 +21,7 @@ using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Infrastructure.Persistence.Factories; using Umbraco.Cms.Infrastructure.Persistence.Querying; using Umbraco.Cms.Infrastructure.Persistence.SqlSyntax; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentTypeContainerRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentTypeContainerRepository.cs index c6c0df848c..f37886fee2 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentTypeContainerRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentTypeContainerRepository.cs @@ -1,7 +1,7 @@ -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Persistence.Repositories; -using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Infrastructure.Scoping; namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement { diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentVersionRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentVersionRepository.cs index 21bd883c68..52b73bed26 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentVersionRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentVersionRepository.cs @@ -7,6 +7,7 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Infrastructure.Persistence.Dtos; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DomainRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DomainRepository.cs index d7fd2e9c7c..7633264ed6 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DomainRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DomainRepository.cs @@ -11,6 +11,7 @@ using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Infrastructure.Persistence.Dtos; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityContainerRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityContainerRepository.cs index b30c5ae1a4..37cbf979e2 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityContainerRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityContainerRepository.cs @@ -8,8 +8,8 @@ using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Core.Persistence.Repositories; -using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Infrastructure.Persistence.Dtos; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepository.cs index 83cb31774d..82cc897e45 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepository.cs @@ -8,11 +8,11 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Core.Persistence.Repositories; -using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Infrastructure.Persistence.Querying; using Umbraco.Cms.Infrastructure.Persistence.SqlSyntax; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Extensions; using static Umbraco.Cms.Core.Persistence.SqlExtensionsStatics; diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepositoryBase.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepositoryBase.cs index 79e6f732a2..3518803490 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepositoryBase.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepositoryBase.cs @@ -10,6 +10,7 @@ using Umbraco.Cms.Core.Persistence; using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Infrastructure.Persistence.Querying; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ExternalLoginRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ExternalLoginRepository.cs index 814bfefc88..60fa5e02e7 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ExternalLoginRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ExternalLoginRepository.cs @@ -9,11 +9,11 @@ using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Persistence; using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Core.Persistence.Repositories; -using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Infrastructure.Persistence.Factories; using Umbraco.Cms.Infrastructure.Persistence.Querying; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/KeyValueRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/KeyValueRepository.cs index 288f480ed1..653e916be6 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/KeyValueRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/KeyValueRepository.cs @@ -7,9 +7,9 @@ using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Core.Persistence.Repositories; -using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Infrastructure.Persistence.Querying; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/LanguageRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/LanguageRepository.cs index d0aee52406..fc40343692 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/LanguageRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/LanguageRepository.cs @@ -10,10 +10,10 @@ using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Core.Persistence.Repositories; -using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Infrastructure.Persistence.Factories; using Umbraco.Cms.Infrastructure.Persistence.Querying; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/LogViewerQueryRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/LogViewerQueryRepository.cs index 556aba8e22..3482da377d 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/LogViewerQueryRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/LogViewerQueryRepository.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Data; using System.Linq; @@ -8,8 +8,8 @@ using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Core.Persistence.Repositories; -using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Infrastructure.Persistence.Dtos; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MacroRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MacroRepository.cs index 535895e8ed..c0ba8f3ac4 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MacroRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MacroRepository.cs @@ -9,11 +9,11 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Core.Persistence.Repositories; -using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Strings; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Infrastructure.Persistence.Factories; using Umbraco.Cms.Infrastructure.Persistence.Querying; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaRepository.cs index 29960a4044..a431fb6896 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaRepository.cs @@ -12,12 +12,12 @@ using Umbraco.Cms.Core.Persistence; using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.PropertyEditors; -using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Infrastructure.Persistence.Factories; using Umbraco.Cms.Infrastructure.Persistence.Querying; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Extensions; using static Umbraco.Cms.Core.Persistence.SqlExtensionsStatics; diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaTypeContainerRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaTypeContainerRepository.cs index 7710f3efb1..069b49de2f 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaTypeContainerRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaTypeContainerRepository.cs @@ -1,7 +1,7 @@ -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Persistence.Repositories; -using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Infrastructure.Scoping; namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement { diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaTypeRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaTypeRepository.cs index 26d24cbac0..0a07fb2260 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaTypeRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaTypeRepository.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Logging; @@ -8,10 +8,10 @@ using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Core.Persistence.Repositories; -using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Strings; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Infrastructure.Persistence.Querying; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberGroupRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberGroupRepository.cs index 970e498b26..89858951d7 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberGroupRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberGroupRepository.cs @@ -9,10 +9,10 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Core.Persistence.Repositories; -using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Infrastructure.Persistence.Factories; using Umbraco.Cms.Infrastructure.Persistence.Querying; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs index a1b15f407d..db45c40007 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs @@ -21,6 +21,7 @@ using Umbraco.Cms.Core.Services; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Infrastructure.Persistence.Factories; using Umbraco.Cms.Infrastructure.Persistence.Querying; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Extensions; using static Umbraco.Cms.Core.Persistence.SqlExtensionsStatics; diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberTypeRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberTypeRepository.cs index ca3edea81c..27e51551e2 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberTypeRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberTypeRepository.cs @@ -8,11 +8,11 @@ using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Core.Persistence.Repositories; -using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Strings; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Infrastructure.Persistence.Factories; using Umbraco.Cms.Infrastructure.Persistence.Querying; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/NotificationsRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/NotificationsRepository.cs index 2d68c95fe2..c35052b9d1 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/NotificationsRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/NotificationsRepository.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Umbraco.Cms.Core.Models; @@ -7,6 +7,7 @@ using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Infrastructure.Persistence.Dtos; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement @@ -20,7 +21,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement _scopeAccessor = scopeAccessor; } - private IScope AmbientScope => _scopeAccessor.AmbientScope; + private IDatabaseScope AmbientScope => _scopeAccessor.AmbientScope; public IEnumerable GetUsersNotifications(IEnumerable userIds, string action, IEnumerable nodeIds, Guid objectType) { diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/PermissionRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/PermissionRepository.cs index 2b38d55212..afcefdee2c 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/PermissionRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/PermissionRepository.cs @@ -9,8 +9,8 @@ using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Persistence.Querying; -using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Infrastructure.Persistence.Dtos; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/PublicAccessRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/PublicAccessRepository.cs index 1c4dcdb675..dbddb56b68 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/PublicAccessRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/PublicAccessRepository.cs @@ -8,10 +8,10 @@ using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Core.Persistence.Repositories; -using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Infrastructure.Persistence.Factories; using Umbraco.Cms.Infrastructure.Persistence.Querying; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RedirectUrlRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RedirectUrlRepository.cs index 6ab29aa47e..5a15cc5b3d 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RedirectUrlRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RedirectUrlRepository.cs @@ -9,8 +9,8 @@ using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Core.Persistence.Repositories; -using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Infrastructure.Persistence.Dtos; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationRepository.cs index 749fc9d77b..63d49f06af 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationRepository.cs @@ -9,12 +9,12 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Core.Persistence.Repositories; -using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Infrastructure.Persistence.Factories; using Umbraco.Cms.Infrastructure.Persistence.Querying; using Umbraco.Cms.Infrastructure.Persistence.SqlSyntax; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Extensions; using static Umbraco.Cms.Core.Persistence.SqlExtensionsStatics; diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationTypeRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationTypeRepository.cs index 151d08a2ff..f50e307cfa 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationTypeRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationTypeRepository.cs @@ -9,10 +9,10 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Core.Persistence.Repositories; -using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Infrastructure.Persistence.Factories; using Umbraco.Cms.Infrastructure.Persistence.Querying; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RepositoryBase.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RepositoryBase.cs index 6e550f9362..4f82603710 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RepositoryBase.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RepositoryBase.cs @@ -3,8 +3,8 @@ using NPoco; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Persistence; using Umbraco.Cms.Core.Persistence.Querying; -using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Infrastructure.Persistence.SqlSyntax; +using Umbraco.Cms.Infrastructure.Scoping; namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement { @@ -35,11 +35,11 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement /// /// Gets the AmbientScope /// - protected IScope AmbientScope + protected IDatabaseScope AmbientScope { get { - IScope scope = ScopeAccessor.AmbientScope; + IDatabaseScope scope = ScopeAccessor.AmbientScope; if (scope == null) { throw new InvalidOperationException("Cannot run a repository without an ambient scope."); diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ServerRegistrationRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ServerRegistrationRepository.cs index 98aef6fa6d..4d1ef3fa88 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ServerRegistrationRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ServerRegistrationRepository.cs @@ -10,6 +10,7 @@ using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Infrastructure.Persistence.Factories; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/SimpleGetRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/SimpleGetRepository.cs index 6053d35085..2b7257ff8e 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/SimpleGetRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/SimpleGetRepository.cs @@ -6,8 +6,8 @@ using NPoco; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Persistence.Querying; -using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Infrastructure.Persistence.Querying; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TagRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TagRepository.cs index 919bbeea31..81e6a82f89 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TagRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TagRepository.cs @@ -9,10 +9,10 @@ using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Core.Persistence.Repositories; -using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Infrastructure.Persistence.Factories; using Umbraco.Cms.Infrastructure.Persistence.Querying; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Extensions; using static Umbraco.Cms.Core.Persistence.SqlExtensionsStatics; diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TemplateRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TemplateRepository.cs index 6d2c02484a..f318d4d31e 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TemplateRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TemplateRepository.cs @@ -12,11 +12,11 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Core.Persistence.Repositories; -using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Strings; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Infrastructure.Persistence.Factories; using Umbraco.Cms.Infrastructure.Persistence.Querying; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserGroupRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserGroupRepository.cs index d6be3cf730..b27f42858a 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserGroupRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserGroupRepository.cs @@ -10,11 +10,11 @@ using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Core.Persistence.Repositories; -using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Strings; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Infrastructure.Persistence.Factories; using Umbraco.Cms.Infrastructure.Persistence.Querying; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs index b94daf5d83..26d37ed80a 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs @@ -13,13 +13,13 @@ using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Core.Persistence.Repositories; -using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Infrastructure.Persistence.Factories; using Umbraco.Cms.Infrastructure.Persistence.Mappers; using Umbraco.Cms.Infrastructure.Persistence.Querying; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement diff --git a/src/Umbraco.Infrastructure/Scoping/IDatabaseScope.cs b/src/Umbraco.Infrastructure/Scoping/IDatabaseScope.cs new file mode 100644 index 0000000000..aac5f48b4b --- /dev/null +++ b/src/Umbraco.Infrastructure/Scoping/IDatabaseScope.cs @@ -0,0 +1,18 @@ +using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Infrastructure.Persistence; + +namespace Umbraco.Cms.Infrastructure.Scoping +{ + public interface IDatabaseScope : IScope + { + /// + /// Gets the scope database. + /// + IUmbracoDatabase Database { get; } + + /// + /// Gets the Sql context. + /// + ISqlContext SqlContext { get; } + } +} diff --git a/src/Umbraco.Infrastructure/Scoping/IScopeAccessor.cs b/src/Umbraco.Infrastructure/Scoping/IScopeAccessor.cs index a699a8bc3e..234ee0232b 100644 --- a/src/Umbraco.Infrastructure/Scoping/IScopeAccessor.cs +++ b/src/Umbraco.Infrastructure/Scoping/IScopeAccessor.cs @@ -1,17 +1,11 @@ -// Copyright (c) Umbraco. -// See LICENSE for more details. - -namespace Umbraco.Cms.Core.Scoping +namespace Umbraco.Cms.Infrastructure.Scoping { - /// - /// Provides access to the ambient scope. - /// public interface IScopeAccessor { /// /// Gets the ambient scope. /// /// Returns null if there is no ambient scope. - IScope AmbientScope { get; } + IDatabaseScope AmbientScope { get; } } } diff --git a/src/Umbraco.Infrastructure/Scoping/Scope.cs b/src/Umbraco.Infrastructure/Scoping/Scope.cs index e0f947b4aa..1c1bd636b9 100644 --- a/src/Umbraco.Infrastructure/Scoping/Scope.cs +++ b/src/Umbraco.Infrastructure/Scoping/Scope.cs @@ -10,6 +10,7 @@ using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Infrastructure.Persistence; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Core.Collections; using Umbraco.Extensions; @@ -19,7 +20,7 @@ namespace Umbraco.Cms.Core.Scoping /// Implements . /// /// Not thread-safe obviously. - internal class Scope : IScope + internal class Scope : IDatabaseScope { private readonly bool _autoComplete; private readonly CoreDebugSettings _coreDebugSettings; diff --git a/src/Umbraco.Infrastructure/Scoping/ScopeProvider.cs b/src/Umbraco.Infrastructure/Scoping/ScopeProvider.cs index 944c547236..0f5fba4d4a 100644 --- a/src/Umbraco.Infrastructure/Scoping/ScopeProvider.cs +++ b/src/Umbraco.Infrastructure/Scoping/ScopeProvider.cs @@ -12,6 +12,7 @@ using System.Collections.Generic; using System.Collections.Concurrent; using System.Threading; using Umbraco.Cms.Core.Persistence.Querying; +using Umbraco.Cms.Infrastructure.Scoping; #if DEBUG_SCOPES using System.Linq; @@ -286,10 +287,10 @@ namespace Umbraco.Cms.Core.Scoping #region Ambient Scope - IScope IScopeAccessor.AmbientScope => AmbientScope; + IDatabaseScope IScopeAccessor.AmbientScope => AmbientScope; /// - /// Get or set the Ambient (Current) for the current execution context. + /// Gets or set the Ambient (Current) for the current execution context. /// /// /// The current execution context may be request based (HttpContext) or on a background thread (AsyncLocal) diff --git a/src/Umbraco.Infrastructure/Services/IdKeyMap.cs b/src/Umbraco.Infrastructure/Services/IdKeyMap.cs index 49747534c6..cc461cead7 100644 --- a/src/Umbraco.Infrastructure/Services/IdKeyMap.cs +++ b/src/Umbraco.Infrastructure/Services/IdKeyMap.cs @@ -1,23 +1,26 @@ -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Infrastructure.Scoping; namespace Umbraco.Cms.Core.Services { public class IdKeyMap : IIdKeyMap,IDisposable { private readonly IScopeProvider _scopeProvider; + private readonly IScopeAccessor _scopeAccessor; private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim(); private readonly Dictionary> _id2Key = new Dictionary>(); private readonly Dictionary> _key2Id = new Dictionary>(); - public IdKeyMap(IScopeProvider scopeProvider) + public IdKeyMap(IScopeProvider scopeProvider, IScopeAccessor scopeAccessor) { _scopeProvider = scopeProvider; + _scopeAccessor = scopeAccessor; } // note - for pure read-only we might want to *not* enforce a transaction? @@ -170,11 +173,11 @@ namespace Umbraco.Cms.Core.Services //if it's unknown don't include the nodeObjectType in the query if (umbracoObjectType == UmbracoObjectTypes.Unknown) { - val = scope.Database.ExecuteScalar("SELECT id FROM umbracoNode WHERE uniqueId=@id", new { id = key}); + val = _scopeAccessor.AmbientScope.Database.ExecuteScalar("SELECT id FROM umbracoNode WHERE uniqueId=@id", new { id = key}); } else { - val = scope.Database.ExecuteScalar("SELECT id FROM umbracoNode WHERE uniqueId=@id AND (nodeObjectType=@type OR nodeObjectType=@reservation)", + val = _scopeAccessor.AmbientScope.Database.ExecuteScalar("SELECT id FROM umbracoNode WHERE uniqueId=@id AND (nodeObjectType=@type OR nodeObjectType=@reservation)", new { id = key, type = GetNodeObjectTypeGuid(umbracoObjectType), reservation = Cms.Core.Constants.ObjectTypes.IdReservation }); } scope.Complete(); @@ -258,11 +261,11 @@ namespace Umbraco.Cms.Core.Services //if it's unknown don't include the nodeObjectType in the query if (umbracoObjectType == UmbracoObjectTypes.Unknown) { - val = scope.Database.ExecuteScalar("SELECT uniqueId FROM umbracoNode WHERE id=@id", new { id }); + val = _scopeAccessor.AmbientScope.Database.ExecuteScalar("SELECT uniqueId FROM umbracoNode WHERE id=@id", new { id }); } else { - val = scope.Database.ExecuteScalar("SELECT uniqueId FROM umbracoNode WHERE id=@id AND (nodeObjectType=@type OR nodeObjectType=@reservation)", + val = _scopeAccessor.AmbientScope.Database.ExecuteScalar("SELECT uniqueId FROM umbracoNode WHERE id=@id AND (nodeObjectType=@type OR nodeObjectType=@reservation)", new { id, type = GetNodeObjectTypeGuid(umbracoObjectType), reservation = Cms.Core.Constants.ObjectTypes.IdReservation }); } scope.Complete(); diff --git a/src/Umbraco.PublishedCache.NuCache/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.PublishedCache.NuCache/DependencyInjection/UmbracoBuilderExtensions.cs index c52c1271ad..60face6428 100644 --- a/src/Umbraco.PublishedCache.NuCache/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.PublishedCache.NuCache/DependencyInjection/UmbracoBuilderExtensions.cs @@ -13,6 +13,7 @@ using Umbraco.Cms.Core.Services; using Umbraco.Cms.Infrastructure.PublishedCache; using Umbraco.Cms.Infrastructure.PublishedCache.DataSource; using Umbraco.Cms.Infrastructure.PublishedCache.Persistence; +using Umbraco.Cms.Infrastructure.Scoping; namespace Umbraco.Extensions { @@ -42,7 +43,7 @@ namespace Umbraco.Extensions // TODO: Gotta wonder how much this does actually improve perf? It's a lot of weird code to make this happen so hope it's worth it builder.Services.AddUnique(factory => { - var idkSvc = new IdKeyMap(factory.GetRequiredService()); + var idkSvc = new IdKeyMap(factory.GetRequiredService(), factory.GetRequiredService()); if (factory.GetRequiredService() is PublishedSnapshotService publishedSnapshotService) { idkSvc.SetMapper(UmbracoObjectTypes.Document, id => publishedSnapshotService.GetDocumentUid(id), uid => publishedSnapshotService.GetDocumentId(uid)); diff --git a/src/Umbraco.PublishedCache.NuCache/Persistence/NuCacheContentRepository.cs b/src/Umbraco.PublishedCache.NuCache/Persistence/NuCacheContentRepository.cs index 649bc0eebb..8aa35c1608 100644 --- a/src/Umbraco.PublishedCache.NuCache/Persistence/NuCacheContentRepository.cs +++ b/src/Umbraco.PublishedCache.NuCache/Persistence/NuCacheContentRepository.cs @@ -18,6 +18,7 @@ using Umbraco.Cms.Infrastructure.Persistence; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; using Umbraco.Cms.Infrastructure.PublishedCache.DataSource; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Extensions; using static Umbraco.Cms.Core.Persistence.SqlExtensionsStatics; using Constants = Umbraco.Cms.Core.Constants; @@ -694,7 +695,7 @@ AND cmsContentNu.nodeId IS NULL } } - public ContentNodeKit GetMediaSource(IScope scope, int id) + public ContentNodeKit GetMediaSource(IDatabaseScope scope, int id) { var sql = SqlMediaSourcesSelect() .Append(SqlObjectTypeNotTrashed(SqlContext, Constants.ObjectTypes.Media)) diff --git a/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs b/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs index 314a614b49..25f6194fde 100644 --- a/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs +++ b/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs @@ -31,6 +31,7 @@ using Umbraco.Cms.Core.Web; using Umbraco.Cms.Infrastructure.DependencyInjection; using Umbraco.Cms.Infrastructure.Persistence; using Umbraco.Cms.Infrastructure.Persistence.Mappers; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Cms.Tests.Common.Builders; using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Cms.Tests.Integration.DependencyInjection; diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Migrations/AdvancedMigrationTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Migrations/AdvancedMigrationTests.cs index 40dbad176e..19d4080524 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Migrations/AdvancedMigrationTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Migrations/AdvancedMigrationTests.cs @@ -56,7 +56,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Migrations upgrader.Execute(MigrationPlanExecutor, ScopeProvider, Mock.Of()); - var helper = new DatabaseSchemaCreator(scope.Database, LoggerFactory.CreateLogger(), LoggerFactory, UmbracoVersion, EventAggregator); + var helper = new DatabaseSchemaCreator(ScopeAccessor.AmbientScope.Database, LoggerFactory.CreateLogger(), LoggerFactory, UmbracoVersion, EventAggregator); bool exists = helper.TableExists("umbracoUser"); Assert.IsTrue(exists); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Packaging/PackageDataInstallationTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Packaging/PackageDataInstallationTests.cs index ef6585bef6..3feac1ce38 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Packaging/PackageDataInstallationTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Packaging/PackageDataInstallationTests.cs @@ -150,7 +150,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Packaging using IScope scope = ScopeProvider.CreateScope(); foreach (IPropertyType propertyType in mRBasePage.PropertyTypes) { - PropertyTypeDto propertyTypeDto = scope.Database.First("WHERE id = @id", new { id = propertyType.Id }); + PropertyTypeDto propertyTypeDto = ScopeAccessor.AmbientScope.Database.First("WHERE id = @id", new { id = propertyType.Id }); Assert.AreEqual(propertyTypeDto.UniqueId, propertyType.Key); } } @@ -424,7 +424,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Packaging string configuration; using (IScope scope = ScopeProvider.CreateScope()) { - List dtos = scope.Database.Fetch("WHERE nodeId = @Id", new { dataTypeDefinitions.First().Id }); + List dtos = ScopeAccessor.AmbientScope.Database.Fetch("WHERE nodeId = @Id", new { dataTypeDefinitions.First().Id }); configuration = dtos.Single().Configuration; } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/LocksTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/LocksTests.cs index 96ded92f41..fa539ef1e8 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/LocksTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/LocksTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Data.SqlClient; using System.Linq; @@ -27,7 +27,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence // create a few lock objects using (var scope = ScopeProvider.CreateScope()) { - var database = scope.Database; + var database = ScopeAccessor.AmbientScope.Database; database.Insert("umbracoLock", "id", false, new LockDto { Id = 1, Name = "Lock.1" }); database.Insert("umbracoLock", "id", false, new LockDto { Id = 2, Name = "Lock.2" }); database.Insert("umbracoLock", "id", false, new LockDto { Id = 3, Name = "Lock.3" }); @@ -121,7 +121,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence using (var scope = ScopeProvider.CreateScope()) { - var db = scope.Database; + var db = ScopeAccessor.AmbientScope.Database; try { db.EnableSqlCount = true; @@ -297,7 +297,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence Console.WriteLine($"[{id1}] WAIT {id1}"); scope.EagerWriteLock(id1); Console.WriteLine($"[{id1}] GRANT {id1}"); - WriteLocks(scope.Database); + WriteLocks(ScopeAccessor.AmbientScope.Database); myEv.Set(); if (id1 == 1) @@ -312,7 +312,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence Console.WriteLine($"[{id1}] WAIT {id2}"); scope.EagerWriteLock(id2); Console.WriteLine($"[{id1}] GRANT {id2}"); - WriteLocks(scope.Database); + WriteLocks(ScopeAccessor.AmbientScope.Database); } catch (Exception e) { @@ -483,7 +483,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var realDb = (Database)scope.Database; + var realDb = (Database)ScopeAccessor.AmbientScope.Database; realDb.CommandTimeout = 1000; Console.WriteLine("Write lock A"); @@ -505,7 +505,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence Console.WriteLine($"[{id}] WAIT {id}"); scope.EagerWriteLock(id); Console.WriteLine($"[{id}] GRANT {id}"); - WriteLocks(scope.Database); + WriteLocks(ScopeAccessor.AmbientScope.Database); myEv.Set(); otherEv.WaitOne(); } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/NPocoTests/NPocoBulkInsertTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/NPocoTests/NPocoBulkInsertTests.cs index 7fd90c97b3..10b0a06032 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/NPocoTests/NPocoBulkInsertTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/NPocoTests/NPocoBulkInsertTests.cs @@ -39,7 +39,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.NPoco using (IScope scope = ScopeProvider.CreateScope()) { // Still no what we want, but look above. - IUmbracoDatabase dbSqlServer = scope.Database; + IUmbracoDatabase dbSqlServer = ScopeAccessor.AmbientScope.Database; // drop the table dbSqlServer.Execute("DROP TABLE [umbracoServer]"); @@ -103,7 +103,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.NPoco { using (IScope scope = ScopeProvider.CreateScope()) { - scope.Database.BulkInsertRecords(servers); + ScopeAccessor.AmbientScope.Database.BulkInsertRecords(servers); scope.Complete(); } } @@ -111,7 +111,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.NPoco // Assert using (IScope scope = ScopeProvider.CreateScope()) { - Assert.That(scope.Database.ExecuteScalar("SELECT COUNT(*) FROM umbracoServer"), Is.EqualTo(1000)); + Assert.That(ScopeAccessor.AmbientScope.Database.ExecuteScalar("SELECT COUNT(*) FROM umbracoServer"), Is.EqualTo(1000)); } } @@ -136,7 +136,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.NPoco { using (IScope scope = ScopeProvider.CreateScope()) { - scope.Database.BulkInsertRecords(servers); + ScopeAccessor.AmbientScope.Database.BulkInsertRecords(servers); // Don't call complete here - the transaction will be rolled back. } @@ -145,7 +145,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.NPoco // Assert using (IScope scope = ScopeProvider.CreateScope()) { - Assert.That(scope.Database.ExecuteScalar("SELECT COUNT(*) FROM umbracoServer"), Is.EqualTo(0)); + Assert.That(ScopeAccessor.AmbientScope.Database.ExecuteScalar("SELECT COUNT(*) FROM umbracoServer"), Is.EqualTo(0)); } } @@ -168,7 +168,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.NPoco IDbCommand[] commands; using (IScope scope = ScopeProvider.CreateScope()) { - commands = scope.Database.GenerateBulkInsertCommands(servers.ToArray()); + commands = ScopeAccessor.AmbientScope.Database.GenerateBulkInsertCommands(servers.ToArray()); scope.Complete(); } @@ -198,7 +198,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.NPoco IDbCommand[] commands; using (IScope scope = ScopeProvider.CreateScope()) { - commands = scope.Database.GenerateBulkInsertCommands(servers.ToArray()); + commands = ScopeAccessor.AmbientScope.Database.GenerateBulkInsertCommands(servers.ToArray()); scope.Complete(); } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/NPocoTests/NPocoFetchTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/NPocoTests/NPocoFetchTests.cs index bea5a8fb8c..19976da976 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/NPocoTests/NPocoFetchTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/NPocoTests/NPocoFetchTests.cs @@ -23,7 +23,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.NPoco { using (IScope scope = ScopeProvider.CreateScope()) { - InsertData(scope.Database); + InsertData(ScopeAccessor.AmbientScope.Database); scope.Complete(); } } @@ -166,11 +166,11 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.NPoco // var sql = @" // SELECT zbThing1.id, zbThing1.name // FROM zbThing1"; - Sql sql = scope.SqlContext.Sql() + Sql sql = ScopeAccessor.AmbientScope.SqlContext.Sql() .Select() .From(); - List dtos = scope.Database.Fetch(sql); + List dtos = ScopeAccessor.AmbientScope.Database.Fetch(sql); Assert.AreEqual(2, dtos.Count); Assert.AreEqual("one", dtos.First(x => x.Id == 1).Name); } @@ -189,12 +189,12 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.NPoco // zbThing1.id Thing__id, zbThing1.name Thing__name // FROM zbThing2 // JOIN zbThing1 ON zbThing2.thingId=zbThing1.id"; - Sql sql = scope.SqlContext.Sql() + Sql sql = ScopeAccessor.AmbientScope.SqlContext.Sql() .Select(r => r.Select(x => x.Thing)) .From() .InnerJoin().On((t2, t1) => t2.ThingId == t1.Id); - List dtos = scope.Database.Fetch(sql); + List dtos = ScopeAccessor.AmbientScope.Database.Fetch(sql); Assert.AreEqual(3, dtos.Count); Assert.AreEqual("uno", dtos.First(x => x.Id == 1).Name); Assert.IsNotNull(dtos.First(x => x.Id == 1).Thing); @@ -218,14 +218,14 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.NPoco // FROM zbThing1 // JOIN zbThing2 ON zbThing1.id=zbThing2.thingId // WHERE zbThing1.id=1"); - Sql sql = scope.SqlContext.Sql() + Sql sql = ScopeAccessor.AmbientScope.SqlContext.Sql() .Select(r => r.Select(x => x.Things)) .From() .InnerJoin().On(left => left.Id, right => right.ThingId) .Where(x => x.Id == 1); // var dtos = scope.Database.FetchOneToMany(x => x.Things, x => x.Id, sql); - List dtos = scope.Database.FetchOneToMany(x => x.Things, sql); + List dtos = ScopeAccessor.AmbientScope.Database.FetchOneToMany(x => x.Things, sql); Assert.AreEqual(1, dtos.Count); Thing3Dto dto1 = dtos.FirstOrDefault(x => x.Id == 1); @@ -258,13 +258,13 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.NPoco // FROM zbThing1 // JOIN zbThing2 ON zbThing1.id=zbThing2.thingId // ORDER BY zbThing1.id"; - Sql sql = scope.SqlContext.Sql() + Sql sql = ScopeAccessor.AmbientScope.SqlContext.Sql() .Select(r => r.Select(x => x.Things)) // select Thing3Dto, and Thing2Dto for Things .From() .InnerJoin().On(left => left.Id, right => right.ThingId) .OrderBy(x => x.Id); - List dtos = scope.Database.FetchOneToMany(x => x.Things, /*x => x.Id,*/ sql); + List dtos = ScopeAccessor.AmbientScope.Database.FetchOneToMany(x => x.Things, /*x => x.Id,*/ sql); Assert.AreEqual(2, dtos.Count); Thing3Dto dto1 = dtos.FirstOrDefault(x => x.Id == 1); @@ -283,18 +283,18 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.NPoco { using (IScope scope = ScopeProvider.CreateScope()) { - scope.SqlContext.Templates.Clear(); + ScopeAccessor.AmbientScope.SqlContext.Templates.Clear(); - Sql sql = scope.SqlContext.Templates.Get("xxx", s => s + Sql sql = ScopeAccessor.AmbientScope.SqlContext.Templates.Get("xxx", s => s .Select(r => r.Select(x => x.Things)) // select Thing3Dto, and Thing2Dto for Things .From() .InnerJoin().On(left => left.Id, right => right.ThingId) .OrderBy(x => x.Id)).Sql(); // cached - sql = scope.SqlContext.Templates.Get("xxx", s => throw new InvalidOperationException()).Sql(); + sql = ScopeAccessor.AmbientScope.SqlContext.Templates.Get("xxx", s => throw new InvalidOperationException()).Sql(); - List dtos = scope.Database.FetchOneToMany(x => x.Things, /*x => x.Id,*/ sql); + List dtos = ScopeAccessor.AmbientScope.Database.FetchOneToMany(x => x.Things, /*x => x.Id,*/ sql); Assert.AreEqual(2, dtos.Count); Thing3Dto dto1 = dtos.FirstOrDefault(x => x.Id == 1); @@ -326,14 +326,14 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.NPoco // JOIN zbThing2Group ON zbThing1.id=zbThing2Group.thingId // JOIN zbThingGroup ON zbThing2Group.groupId=zbThingGroup.id // ORDER BY zbThing1.id"; - Sql sql = scope.SqlContext.Sql() + Sql sql = ScopeAccessor.AmbientScope.SqlContext.Sql() .Select(r => r.Select(x => x.Groups)) .From() .InnerJoin().On((t, t2g) => t.Id == t2g.ThingId) .InnerJoin().On((t2g, tg) => t2g.GroupId == tg.Id) .OrderBy(x => x.Id); - List dtos = scope.Database.FetchOneToMany(x => x.Groups, /*x => x.Id,*/ sql); + List dtos = ScopeAccessor.AmbientScope.Database.FetchOneToMany(x => x.Groups, /*x => x.Id,*/ sql); Assert.AreEqual(2, dtos.Count); Thing4Dto dto1 = dtos.FirstOrDefault(x => x.Id == 1); @@ -360,14 +360,14 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.NPoco // FROM zbThing1 // JOIN zbThing2Group ON zbThing1.id=zbThing2Group.thingId // GROUP BY zbThing1.id, zbThing1.name"; - Sql sql = scope.SqlContext.Sql() + Sql sql = ScopeAccessor.AmbientScope.SqlContext.Sql() .Select() .Append(", COUNT(zbThing2Group.groupId) AS groupCount") // FIXME: .From() .InnerJoin().On((t, t2g) => t.Id == t2g.ThingId) .GroupBy(x => x.Id, x => x.Name); - List dtos = scope.Database.Fetch(sql); + List dtos = ScopeAccessor.AmbientScope.Database.Fetch(sql); Assert.AreEqual(2, dtos.Count); Thing5Dto dto1 = dtos.FirstOrDefault(x => x.Id == 1); @@ -390,12 +390,12 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.NPoco { using (IScope scope = ScopeProvider.CreateScope()) { - Sql sql = scope.SqlContext.Sql() + Sql sql = ScopeAccessor.AmbientScope.SqlContext.Sql() .SelectAll() .From() .Where(x => x.Id == 1); - Thing1Dto dto = scope.Database.Fetch(sql).FirstOrDefault(); + Thing1Dto dto = ScopeAccessor.AmbientScope.Database.Fetch(sql).FirstOrDefault(); Assert.IsNotNull(dto); Assert.AreEqual("one", dto.Name); @@ -406,7 +406,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.NPoco //// Assert.AreEqual("one", dto.Name); var sql3 = new Sql(sql.SQL, 1); - dto = scope.Database.Fetch(sql3).FirstOrDefault(); + dto = ScopeAccessor.AmbientScope.Database.Fetch(sql3).FirstOrDefault(); Assert.IsNotNull(dto); Assert.AreEqual("one", dto.Name); } @@ -418,28 +418,28 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.NPoco using (IScope scope = ScopeProvider.CreateScope()) { var tA1A = new ThingA1Dto { Id = 1, Name = "a1_a" }; - scope.Database.Insert(tA1A); + ScopeAccessor.AmbientScope.Database.Insert(tA1A); var tA1B = new ThingA1Dto { Id = 2, Name = "a1_b" }; - scope.Database.Insert(tA1B); + ScopeAccessor.AmbientScope.Database.Insert(tA1B); var tA1C = new ThingA1Dto { Id = 3, Name = "a1_c" }; - scope.Database.Insert(tA1C); + ScopeAccessor.AmbientScope.Database.Insert(tA1C); var tA2A = new ThingA2Dto { Id = 1, Name = "a2_a" }; - scope.Database.Insert(tA2A); + ScopeAccessor.AmbientScope.Database.Insert(tA2A); var tA2B = new ThingA2Dto { Id = 2, Name = "a2_b" }; - scope.Database.Insert(tA2B); + ScopeAccessor.AmbientScope.Database.Insert(tA2B); var tA2C = new ThingA2Dto { Id = 3, Name = "a2_c" }; - scope.Database.Insert(tA2C); + ScopeAccessor.AmbientScope.Database.Insert(tA2C); var tA3A = new ThingA3Dto { Id = 1, Name = "a3_a" }; - scope.Database.Insert(tA3A); + ScopeAccessor.AmbientScope.Database.Insert(tA3A); var tA3B = new ThingA3Dto { Id = 2, Name = "a3_b" }; - scope.Database.Insert(tA3B); + ScopeAccessor.AmbientScope.Database.Insert(tA3B); var k1 = new ThingA12Dto { Name = "a", Thing1Id = tA1A.Id, Thing2Id = tA2A.Id }; - scope.Database.Insert(k1); + ScopeAccessor.AmbientScope.Database.Insert(k1); var k2 = new ThingA12Dto { Name = "B", Thing1Id = tA1A.Id, Thing2Id = tA2B.Id }; - scope.Database.Insert(k2); + ScopeAccessor.AmbientScope.Database.Insert(k2); string sql = @"SELECT a1.id, a1.name, a2.id AS T2A__Id, a2.name AS T2A__Name, a3.id AS T2A__T3__Id, a3.name AS T2A__T3__Name, @@ -453,7 +453,7 @@ JOIN zbThingA2 a2x ON a12x.thing2id=a2x.id JOIN zbThingA3 a3x ON a2x.id=a3x.id "; - List ts = scope.Database.Fetch(sql); + List ts = ScopeAccessor.AmbientScope.Database.Fetch(sql); Assert.AreEqual(1, ts.Count); ThingA1Dto t = ts.First(); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/AuditRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/AuditRepositoryTest.cs index c586d496b7..4951146147 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/AuditRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/AuditRepositoryTest.cs @@ -12,6 +12,7 @@ using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Infrastructure.Persistence; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Cms.Tests.Integration.Testing; @@ -35,7 +36,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos var repo = new AuditRepository((IScopeAccessor)sp, _logger); repo.Save(new AuditItem(-1, AuditType.System, -1, UmbracoObjectTypes.Document.GetName(), "This is a System audit trail")); - List dtos = scope.Database.Fetch("WHERE id > -1"); + List dtos = ScopeAccessor.AmbientScope.Database.Fetch("WHERE id > -1"); Assert.That(dtos.Any(), Is.True); Assert.That(dtos.First().Comment, Is.EqualTo("This is a System audit trail")); @@ -63,7 +64,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos { var repo = new AuditRepository((IScopeAccessor)sp, _logger); - IEnumerable page = repo.GetPagedResultsByQuery(sp.SqlContext.Query(), 0, 10, out long total, Direction.Descending, null, null); + IEnumerable page = repo.GetPagedResultsByQuery(sp.CreateQuery(), 0, 10, out long total, Direction.Descending, null, null); Assert.AreEqual(10, page.Count()); Assert.AreEqual(200, total); @@ -91,12 +92,12 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos { var repo = new AuditRepository((IScopeAccessor)sp, _logger); - IQuery query = sp.SqlContext.Query().Where(x => x.UserId == -1); + IQuery query = sp.CreateQuery().Where(x => x.UserId == -1); try { - scope.Database.AsUmbracoDatabase().EnableSqlTrace = true; - scope.Database.AsUmbracoDatabase().EnableSqlCount = true; + ScopeAccessor.AmbientScope.Database.AsUmbracoDatabase().EnableSqlTrace = true; + ScopeAccessor.AmbientScope.Database.AsUmbracoDatabase().EnableSqlCount = true; IEnumerable page = repo.GetPagedResultsByQuery( query, @@ -105,7 +106,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos out long total, Direction.Descending, new[] { AuditType.Publish }, - sp.SqlContext.Query() + sp.CreateQuery() .Where(x => x.UserId > -2)); Assert.AreEqual(10, page.Count()); @@ -113,8 +114,8 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos } finally { - scope.Database.AsUmbracoDatabase().EnableSqlTrace = false; - scope.Database.AsUmbracoDatabase().EnableSqlCount = false; + ScopeAccessor.AmbientScope.Database.AsUmbracoDatabase().EnableSqlTrace = false; + ScopeAccessor.AmbientScope.Database.AsUmbracoDatabase().EnableSqlCount = false; } } } @@ -141,7 +142,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos var repo = new AuditRepository((IScopeAccessor)sp, _logger); IAuditItem[] page = repo.GetPagedResultsByQuery( - sp.SqlContext.Query(), + sp.CreateQuery(), 0, 9, out long total, @@ -178,13 +179,13 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos var repo = new AuditRepository((IScopeAccessor)sp, _logger); IAuditItem[] page = repo.GetPagedResultsByQuery( - sp.SqlContext.Query(), + sp.CreateQuery(), 0, 8, out long total, Direction.Descending, null, - sp.SqlContext.Query() + sp.CreateQuery() .Where(item => item.Comment == "Content created")) .ToArray(); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/CacheInstructionRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/CacheInstructionRepositoryTest.cs index faa5fd9d6e..f39b4311b2 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/CacheInstructionRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/CacheInstructionRepositoryTest.cs @@ -13,6 +13,7 @@ using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Infrastructure.Persistence; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Cms.Tests.Integration.Testing; @@ -98,7 +99,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos var repo = new CacheInstructionRepository((IScopeAccessor)sp); repo.Add(new CacheInstruction(0, date, Instructions, OriginIdentiy, InstructionCount)); - List dtos = scope.Database.Fetch("WHERE id > -1"); + List dtos = ScopeAccessor.AmbientScope.Database.Fetch("WHERE id > -1"); Assert.That(dtos.Any(), Is.True); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ContentTypeRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ContentTypeRepositoryTest.cs index 9f79414b55..38882c1638 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ContentTypeRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ContentTypeRepositoryTest.cs @@ -17,6 +17,7 @@ using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Cms.Tests.Common.Builders; using Umbraco.Cms.Tests.Common.Extensions; using Umbraco.Cms.Tests.Common.Testing; @@ -552,7 +553,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos ContentTypeRepository repository = ContentTypeRepository; // Act - IEnumerable contentTypes = repository.Get(scope.SqlContext.Query().Where(x => x.ParentId == contentType.Id)); + IEnumerable contentTypes = repository.Get(provider.CreateQuery().Where(x => x.ParentId == contentType.Id)); // Assert Assert.That(contentTypes.Count(), Is.EqualTo(3)); @@ -630,7 +631,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos // Act IEnumerable contentTypes = repository.GetMany(); int count = - scope.Database.ExecuteScalar( + ScopeAccessor.AmbientScope.Database.ExecuteScalar( "SELECT COUNT(*) FROM umbracoNode WHERE nodeObjectType = @NodeObjectType", new { NodeObjectType = Constants.ObjectTypes.DocumentType }); @@ -653,7 +654,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos // Act IEnumerable contentTypes = ((IReadRepository)repository).GetMany(allGuidIds); int count = - scope.Database.ExecuteScalar( + ScopeAccessor.AmbientScope.Database.ExecuteScalar( "SELECT COUNT(*) FROM umbracoNode WHERE nodeObjectType = @NodeObjectType", new { NodeObjectType = Constants.ObjectTypes.DocumentType }); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs index baa94c6e9e..8cb7686982 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs @@ -279,7 +279,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos using (IScope scope = ScopeProvider.CreateScope()) { // Act - IQuery query = scope.SqlContext.Query().Where(x => x.EditorAlias == Constants.PropertyEditors.Aliases.RadioButtonList); + IQuery query = ScopeProvider.CreateQuery().Where(x => x.EditorAlias == Constants.PropertyEditors.Aliases.RadioButtonList); IDataType[] result = DataTypeRepository.Get(query).ToArray(); // Assert @@ -295,7 +295,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos using (IScope scope = ScopeProvider.CreateScope()) { // Act - IQuery query = scope.SqlContext.Query().Where(x => x.Name.StartsWith("D")); + IQuery query = ScopeProvider.CreateQuery().Where(x => x.Name.StartsWith("D")); int count = DataTypeRepository.Count(query); // Assert diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DictionaryRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DictionaryRepositoryTest.cs index 87f33b8589..fc3caa6acf 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DictionaryRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DictionaryRepositoryTest.cs @@ -191,7 +191,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos IDictionaryRepository repository = CreateRepository(); // Act - IQuery query = provider.SqlContext.Query().Where(x => x.ItemKey == "Article"); + IQuery query = provider.CreateQuery().Where(x => x.ItemKey == "Article"); IEnumerable result = repository.Get(query); // Assert @@ -211,7 +211,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos IDictionaryRepository repository = CreateRepository(); // Act - IQuery query = provider.SqlContext.Query().Where(x => x.ItemKey.StartsWith("Read")); + IQuery query = provider.CreateQuery().Where(x => x.ItemKey.StartsWith("Read")); int result = repository.Count(query); // Assert diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DocumentRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DocumentRepositoryTest.cs index 7207718071..2059564cd8 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DocumentRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DocumentRepositoryTest.cs @@ -24,6 +24,7 @@ using Umbraco.Cms.Core.Services; using Umbraco.Cms.Infrastructure.Persistence; using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; using Umbraco.Cms.Infrastructure.Persistence.SqlSyntax; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Cms.Tests.Common.Builders; using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Cms.Tests.Integration.Testing; @@ -152,11 +153,13 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos new IsolatedCaches(t => new ObjectCacheAppCache())); IScopeProvider provider = ScopeProvider; + IScopeAccessor scopeAccessor = ScopeAccessor; + using (IScope scope = provider.CreateScope()) { DocumentRepository repository = CreateRepository((IScopeAccessor)provider, out ContentTypeRepository contentTypeRepository, appCaches: realCache); - IUmbracoDatabase udb = scope.Database; + IUmbracoDatabase udb = scopeAccessor.AmbientScope.Database; udb.EnableSqlCount = false; @@ -230,7 +233,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos Assert.AreEqual(versions[^1], repository.Get(content1.Id).VersionId); // misc checks - Assert.AreEqual(true, scope.Database.ExecuteScalar($"SELECT published FROM {Constants.DatabaseSchema.Tables.Document} WHERE nodeId=@id", new { id = content1.Id })); + Assert.AreEqual(true, ScopeAccessor.AmbientScope.Database.ExecuteScalar($"SELECT published FROM {Constants.DatabaseSchema.Tables.Document} WHERE nodeId=@id", new { id = content1.Id })); // change something // save = update the current (draft) version @@ -246,7 +249,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos Assert.AreEqual(versions[^1], repository.Get(content1.Id).VersionId); // misc checks - Assert.AreEqual(true, scope.Database.ExecuteScalar($"SELECT published FROM {Constants.DatabaseSchema.Tables.Document} WHERE nodeId=@id", new { id = content1.Id })); + Assert.AreEqual(true, ScopeAccessor.AmbientScope.Database.ExecuteScalar($"SELECT published FROM {Constants.DatabaseSchema.Tables.Document} WHERE nodeId=@id", new { id = content1.Id })); // unpublish = no impact on versions ((Content)content1).PublishedState = PublishedState.Unpublishing; @@ -261,7 +264,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos Assert.AreEqual(versions[^1], repository.Get(content1.Id).VersionId); // misc checks - Assert.AreEqual(false, scope.Database.ExecuteScalar($"SELECT published FROM {Constants.DatabaseSchema.Tables.Document} WHERE nodeId=@id", new { id = content1.Id })); + Assert.AreEqual(false, ScopeAccessor.AmbientScope.Database.ExecuteScalar($"SELECT published FROM {Constants.DatabaseSchema.Tables.Document} WHERE nodeId=@id", new { id = content1.Id })); // change something // save = update the current (draft) version @@ -276,7 +279,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos Assert.AreEqual(versions[^1], repository.Get(content1.Id).VersionId); // misc checks - Assert.AreEqual(false, scope.Database.ExecuteScalar($"SELECT published FROM {Constants.DatabaseSchema.Tables.Document} WHERE nodeId=@id", new { id = content1.Id })); + Assert.AreEqual(false, ScopeAccessor.AmbientScope.Database.ExecuteScalar($"SELECT published FROM {Constants.DatabaseSchema.Tables.Document} WHERE nodeId=@id", new { id = content1.Id })); // publish = version content1.PublishCulture(CultureImpact.Invariant); @@ -292,7 +295,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos Assert.AreEqual(versions[^1], repository.Get(content1.Id).VersionId); // misc checks - Assert.AreEqual(true, scope.Database.ExecuteScalar($"SELECT published FROM {Constants.DatabaseSchema.Tables.Document} WHERE nodeId=@id", new { id = content1.Id })); + Assert.AreEqual(true, ScopeAccessor.AmbientScope.Database.ExecuteScalar($"SELECT published FROM {Constants.DatabaseSchema.Tables.Document} WHERE nodeId=@id", new { id = content1.Id })); // change something // save = update the current (draft) version @@ -310,7 +313,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos Assert.AreEqual(versions[^1], repository.Get(content1.Id).VersionId); // misc checks - Assert.AreEqual(true, scope.Database.ExecuteScalar($"SELECT published FROM {Constants.DatabaseSchema.Tables.Document} WHERE nodeId=@id", new { id = content1.Id })); + Assert.AreEqual(true, ScopeAccessor.AmbientScope.Database.ExecuteScalar($"SELECT published FROM {Constants.DatabaseSchema.Tables.Document} WHERE nodeId=@id", new { id = content1.Id })); // publish = new version content1.Name = "name-4"; @@ -328,7 +331,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos Assert.AreEqual(versions[^1], repository.Get(content1.Id).VersionId); // misc checks - Assert.AreEqual(true, scope.Database.ExecuteScalar($"SELECT published FROM {Constants.DatabaseSchema.Tables.Document} WHERE nodeId=@id", new { id = content1.Id })); + Assert.AreEqual(true, ScopeAccessor.AmbientScope.Database.ExecuteScalar($"SELECT published FROM {Constants.DatabaseSchema.Tables.Document} WHERE nodeId=@id", new { id = content1.Id })); // all versions IContent[] allVersions = repository.GetAllVersions(content1.Id).ToArray(); @@ -700,7 +703,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos { DocumentRepository repository = CreateRepository((IScopeAccessor)provider, out _); - IQuery query = scope.SqlContext.Query().Where(x => x.Level == 2); + IQuery query = ScopeProvider.CreateQuery().Where(x => x.Level == 2); IEnumerable result = repository.Get(query); Assert.GreaterOrEqual(2, result.Count()); @@ -838,10 +841,10 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos try { - scope.Database.AsUmbracoDatabase().EnableSqlTrace = true; - scope.Database.AsUmbracoDatabase().EnableSqlCount = true; + ScopeAccessor.AmbientScope.Database.AsUmbracoDatabase().EnableSqlTrace = true; + ScopeAccessor.AmbientScope.Database.AsUmbracoDatabase().EnableSqlCount = true; - IQuery query = scope.SqlContext.Query().Where(x => x.ParentId == root.Id); + IQuery query = ScopeProvider.CreateQuery().Where(x => x.ParentId == root.Id); IEnumerable result = repository.GetPage(query, 0, 20, out long totalRecords, null, Ordering.By("UpdateDate")); Assert.AreEqual(25, totalRecords); @@ -864,8 +867,8 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos } finally { - scope.Database.AsUmbracoDatabase().EnableSqlTrace = false; - scope.Database.AsUmbracoDatabase().EnableSqlCount = false; + ScopeAccessor.AmbientScope.Database.AsUmbracoDatabase().EnableSqlTrace = false; + ScopeAccessor.AmbientScope.Database.AsUmbracoDatabase().EnableSqlCount = false; } } } @@ -878,12 +881,12 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos { DocumentRepository repository = CreateRepository((IScopeAccessor)provider, out _); - IQuery query = scope.SqlContext.Query().Where(x => x.Name.Contains("Text")); + IQuery query = ScopeProvider.CreateQuery().Where(x => x.Name.Contains("Text")); try { - scope.Database.AsUmbracoDatabase().EnableSqlTrace = true; - scope.Database.AsUmbracoDatabase().EnableSqlCount = true; + ScopeAccessor.AmbientScope.Database.AsUmbracoDatabase().EnableSqlTrace = true; + ScopeAccessor.AmbientScope.Database.AsUmbracoDatabase().EnableSqlCount = true; IEnumerable result = repository.GetPage(query, 0, 2, out long totalRecords, null, Ordering.By("title", isCustomField: true)); @@ -896,8 +899,8 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos } finally { - scope.Database.AsUmbracoDatabase().EnableSqlTrace = false; - scope.Database.AsUmbracoDatabase().EnableSqlCount = false; + ScopeAccessor.AmbientScope.Database.AsUmbracoDatabase().EnableSqlTrace = false; + ScopeAccessor.AmbientScope.Database.AsUmbracoDatabase().EnableSqlCount = false; } } } @@ -910,12 +913,12 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos { DocumentRepository repository = CreateRepository((IScopeAccessor)provider, out _); - IQuery query = scope.SqlContext.Query().Where(x => x.Level == 2); + IQuery query = ScopeProvider.CreateQuery().Where(x => x.Level == 2); try { - scope.Database.AsUmbracoDatabase().EnableSqlTrace = true; - scope.Database.AsUmbracoDatabase().EnableSqlCount = true; + ScopeAccessor.AmbientScope.Database.AsUmbracoDatabase().EnableSqlTrace = true; + ScopeAccessor.AmbientScope.Database.AsUmbracoDatabase().EnableSqlCount = true; IEnumerable result = repository.GetPage(query, 0, 1, out long totalRecords, null, Ordering.By("Name")); @@ -925,8 +928,8 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos } finally { - scope.Database.AsUmbracoDatabase().EnableSqlTrace = false; - scope.Database.AsUmbracoDatabase().EnableSqlCount = false; + ScopeAccessor.AmbientScope.Database.AsUmbracoDatabase().EnableSqlTrace = false; + ScopeAccessor.AmbientScope.Database.AsUmbracoDatabase().EnableSqlCount = false; } } } @@ -939,7 +942,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos { DocumentRepository repository = CreateRepository((IScopeAccessor)provider, out _); - IQuery query = scope.SqlContext.Query().Where(x => x.Level == 2); + IQuery query = ScopeProvider.CreateQuery().Where(x => x.Level == 2); IEnumerable result = repository.GetPage(query, 1, 1, out long totalRecords, null, Ordering.By("Name")); Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); @@ -956,7 +959,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos { DocumentRepository repository = CreateRepository((IScopeAccessor)provider, out _); - IQuery query = scope.SqlContext.Query().Where(x => x.Level == 2); + IQuery query = ScopeProvider.CreateQuery().Where(x => x.Level == 2); IEnumerable result = repository.GetPage(query, 0, 2, out long totalRecords, null, Ordering.By("Name")); Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); @@ -973,7 +976,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos { DocumentRepository repository = CreateRepository((IScopeAccessor)provider, out _); - IQuery query = scope.SqlContext.Query().Where(x => x.Level == 2); + IQuery query = ScopeProvider.CreateQuery().Where(x => x.Level == 2); IEnumerable result = repository.GetPage(query, 0, 1, out long totalRecords, null, Ordering.By("Name", Direction.Descending)); Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); @@ -990,9 +993,9 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos { DocumentRepository repository = CreateRepository((IScopeAccessor)provider, out _); - IQuery query = scope.SqlContext.Query().Where(x => x.Level == 2); + IQuery query = ScopeProvider.CreateQuery().Where(x => x.Level == 2); - IQuery filterQuery = scope.SqlContext.Query().Where(x => x.Name.Contains("Page 2")); + IQuery filterQuery = ScopeProvider.CreateQuery().Where(x => x.Name.Contains("Page 2")); IEnumerable result = repository.GetPage(query, 0, 1, out long totalRecords, filterQuery, Ordering.By("Name")); Assert.That(totalRecords, Is.EqualTo(1)); @@ -1009,9 +1012,9 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos { DocumentRepository repository = CreateRepository((IScopeAccessor)provider, out _); - IQuery query = scope.SqlContext.Query().Where(x => x.Level == 2); + IQuery query = ScopeProvider.CreateQuery().Where(x => x.Level == 2); - IQuery filterQuery = scope.SqlContext.Query().Where(x => x.Name.Contains("text")); + IQuery filterQuery = ScopeProvider.CreateQuery().Where(x => x.Name.Contains("text")); IEnumerable result = repository.GetPage(query, 0, 1, out long totalRecords, filterQuery, Ordering.By("Name")); Assert.That(totalRecords, Is.EqualTo(2)); @@ -1084,7 +1087,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos { DocumentRepository repository = CreateRepository((IScopeAccessor)provider, out _); - IQuery query = scope.SqlContext.Query().Where(x => x.Level == 2); + IQuery query = ScopeProvider.CreateQuery().Where(x => x.Level == 2); int result = repository.Count(query); Assert.That(result, Is.GreaterThanOrEqualTo(2)); @@ -1099,7 +1102,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos { DocumentRepository repository = CreateRepository((IScopeAccessor)provider, out _); - IQuery query = scope.SqlContext.Query().Where(x => x.Key == new Guid("B58B3AD4-62C2-4E27-B1BE-837BD7C533E0")); + IQuery query = ScopeProvider.CreateQuery().Where(x => x.Key == new Guid("B58B3AD4-62C2-4E27-B1BE-837BD7C533E0")); IContent content = repository.Get(query).SingleOrDefault(); Assert.IsNotNull(content); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DocumentVersionRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DocumentVersionRepositoryTest.cs index ba5c176ee2..c25bd69653 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DocumentVersionRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DocumentVersionRepositoryTest.cs @@ -6,6 +6,7 @@ using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Cms.Tests.Common.Builders; using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Cms.Tests.Integration.Testing; @@ -78,7 +79,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos using (var scope = ScopeProvider.CreateScope()) { - scope.Database.Update("set preventCleanup = 1 where id in (1,3)"); + ScopeAccessor.AmbientScope.Database.Update("set preventCleanup = 1 where id in (1,3)"); var sut = new DocumentVersionRepository(ScopeAccessor); var results = sut.GetDocumentVersionsEligibleForCleanup(); @@ -113,7 +114,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos ContentService.SaveAndPublish(content); using (var scope = ScopeProvider.CreateScope()) { - var query = scope.SqlContext.Sql(); + var query = ScopeAccessor.AmbientScope.SqlContext.Sql(); query.Select() .From(); @@ -121,7 +122,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos var sut = new DocumentVersionRepository(ScopeAccessor); sut.DeleteVersions(new []{1,2,3}); - var after = scope.Database.Fetch(query); + var after = ScopeAccessor.AmbientScope.Database.Fetch(query); Assert.Multiple(() => { diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DomainRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DomainRepositoryTest.cs index c53099cee9..4928354d97 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DomainRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DomainRepositoryTest.cs @@ -12,6 +12,7 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Cms.Tests.Common.Builders; using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Cms.Tests.Integration.Testing; diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/EntityRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/EntityRepositoryTest.cs index 9da3f6ef59..a70355c887 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/EntityRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/EntityRepositoryTest.cs @@ -11,6 +11,7 @@ using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Cms.Tests.Common.Builders; using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Cms.Tests.Integration.Testing; @@ -68,7 +69,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos System.Guid[] objectTypes = new[] { Constants.ObjectTypes.Document, Constants.ObjectTypes.Media, Constants.ObjectTypes.Member }; - IQuery query = provider.SqlContext.Query() + IQuery query = provider.CreateQuery() .WhereIn(e => e.Id, ids); var entities = repo.GetPagedResultsByQuery(query, objectTypes, 0, 20, out long totalRecords, null, null).ToList(); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/KeyValueRepositoryTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/KeyValueRepositoryTests.cs index 3f3a709ce9..4f6934a481 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/KeyValueRepositoryTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/KeyValueRepositoryTests.cs @@ -8,6 +8,7 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Cms.Tests.Integration.Testing; diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/LanguageRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/LanguageRepositoryTest.cs index 242533c651..3c157d9c5a 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/LanguageRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/LanguageRepositoryTest.cs @@ -15,6 +15,7 @@ using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Infrastructure.Persistence; using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Cms.Tests.Integration.Testing; @@ -40,7 +41,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos IScopeProvider provider = ScopeProvider; using (IScope scope = provider.CreateScope()) { - scope.Database.AsUmbracoDatabase().EnableSqlTrace = true; + ScopeAccessor.AmbientScope.Database.AsUmbracoDatabase().EnableSqlTrace = true; LanguageRepository repository = CreateRepository(provider); // Act @@ -150,7 +151,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos LanguageRepository repository = CreateRepository(provider); // Act - IQuery query = scope.SqlContext.Query().Where(x => x.IsoCode == "da-DK"); + IQuery query = provider.CreateQuery().Where(x => x.IsoCode == "da-DK"); IEnumerable result = repository.Get(query); // Assert @@ -170,7 +171,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos LanguageRepository repository = CreateRepository(provider); // Act - IQuery query = scope.SqlContext.Query().Where(x => x.IsoCode.StartsWith("D")); + IQuery query = provider.CreateQuery().Where(x => x.IsoCode.StartsWith("D")); int count = repository.Count(query); // Assert diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/MacroRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/MacroRepositoryTest.cs index 2766e66426..bfba8b32e0 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/MacroRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/MacroRepositoryTest.cs @@ -11,6 +11,7 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Cms.Tests.Integration.Testing; @@ -127,7 +128,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos var repository = new MacroRepository((IScopeAccessor)provider, AppCaches.Disabled, _logger, ShortStringHelper); // Act - IQuery query = scope.SqlContext.Query().Where(x => x.Alias.ToUpper() == "TEST1"); + IQuery query = ScopeAccessor.AmbientScope.SqlContext.Query().Where(x => x.Alias.ToUpper() == "TEST1"); IEnumerable result = repository.Get((IQuery)query); // Assert @@ -145,7 +146,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos var repository = new MacroRepository((IScopeAccessor)provider, AppCaches.Disabled, _logger, ShortStringHelper); // Act - IQuery query = scope.SqlContext.Query().Where(x => x.Name.StartsWith("Test")); + IQuery query = ScopeAccessor.AmbientScope.SqlContext.Query().Where(x => x.Name.StartsWith("Test")); int count = repository.Count(query); // Assert diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/MediaRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/MediaRepositoryTest.cs index fc98b8ef10..1acba24035 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/MediaRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/MediaRepositoryTest.cs @@ -24,6 +24,7 @@ using Umbraco.Cms.Core.Services; using Umbraco.Cms.Infrastructure.Persistence; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Cms.Tests.Common.Builders; using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Cms.Tests.Integration.Testing; @@ -84,7 +85,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos { MediaRepository repository = CreateRepository(provider, out MediaTypeRepository mediaTypeRepository, appCaches: realCache); - IUmbracoDatabase udb = scope.Database; + IUmbracoDatabase udb = ScopeAccessor.AmbientScope.Database; udb.EnableSqlCount = false; @@ -271,7 +272,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos MediaRepository repository = CreateRepository(provider, out MediaTypeRepository mediaTypeRepository); // Act - IQuery query = scope.SqlContext.Query().Where(x => x.Level == 2); + IQuery query = provider.CreateQuery().Where(x => x.Level == 2); IEnumerable result = repository.Get(query); // Assert @@ -297,7 +298,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos } int[] types = new[] { 1031 }; - IQuery query = scope.SqlContext.Query().Where(x => types.Contains(x.ContentTypeId)); + IQuery query = provider.CreateQuery().Where(x => types.Contains(x.ContentTypeId)); IEnumerable result = repository.Get(query); // Assert @@ -327,7 +328,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos } string[] types = new[] { "Folder" }; - IQuery query = scope.SqlContext.Query().Where(x => types.Contains(x.ContentType.Alias)); + IQuery query = provider.CreateQuery().Where(x => types.Contains(x.ContentType.Alias)); IEnumerable result = repository.Get(query); // Assert @@ -345,7 +346,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos MediaRepository repository = CreateRepository(provider, out MediaTypeRepository mediaTypeRepository); // Act - IQuery query = scope.SqlContext.Query().Where(x => x.Level == 2); + IQuery query = provider.CreateQuery().Where(x => x.Level == 2); IEnumerable result = repository.GetPage(query, 0, 1, out long totalRecords, null, Ordering.By("SortOrder")); // Assert @@ -365,7 +366,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos MediaRepository repository = CreateRepository(provider, out MediaTypeRepository mediaTypeRepository); // Act - IQuery query = scope.SqlContext.Query().Where(x => x.Level == 2); + IQuery query = provider.CreateQuery().Where(x => x.Level == 2); IEnumerable result = repository.GetPage(query, 1, 1, out long totalRecords, null, Ordering.By("SortOrder")); // Assert @@ -385,7 +386,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos MediaRepository repository = CreateRepository(provider, out MediaTypeRepository mediaTypeRepository); // Act - IQuery query = scope.SqlContext.Query().Where(x => x.Level == 2); + IQuery query = provider.CreateQuery().Where(x => x.Level == 2); IEnumerable result = repository.GetPage(query, 0, 2, out long totalRecords, null, Ordering.By("SortOrder")); // Assert @@ -405,7 +406,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos MediaRepository repository = CreateRepository(provider, out MediaTypeRepository mediaTypeRepository); // Act - IQuery query = scope.SqlContext.Query().Where(x => x.Level == 2); + IQuery query = provider.CreateQuery().Where(x => x.Level == 2); IEnumerable result = repository.GetPage(query, 0, 1, out long totalRecords, null, Ordering.By("SortOrder", Direction.Descending)); // Assert @@ -425,7 +426,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos MediaRepository repository = CreateRepository(provider, out MediaTypeRepository mediaTypeRepository); // Act - IQuery query = scope.SqlContext.Query().Where(x => x.Level == 2); + IQuery query = provider.CreateQuery().Where(x => x.Level == 2); IEnumerable result = repository.GetPage(query, 0, 1, out long totalRecords, null, Ordering.By("Name")); // Assert @@ -445,9 +446,9 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos MediaRepository repository = CreateRepository(provider, out MediaTypeRepository mediaTypeRepository); // Act - IQuery query = scope.SqlContext.Query().Where(x => x.Level == 2); + IQuery query = provider.CreateQuery().Where(x => x.Level == 2); - IQuery filter = scope.SqlContext.Query().Where(x => x.Name.Contains("File")); + IQuery filter = provider.CreateQuery().Where(x => x.Name.Contains("File")); IEnumerable result = repository.GetPage(query, 0, 1, out long totalRecords, filter, Ordering.By("SortOrder")); // Assert @@ -467,9 +468,9 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos MediaRepository repository = CreateRepository(provider, out _); // Act - IQuery query = scope.SqlContext.Query().Where(x => x.Level == 2); + IQuery query = provider.CreateQuery().Where(x => x.Level == 2); - IQuery filter = scope.SqlContext.Query().Where(x => x.Name.Contains("Test")); + IQuery filter = provider.CreateQuery().Where(x => x.Name.Contains("Test")); IEnumerable result = repository.GetPage(query, 0, 1, out long totalRecords, filter, Ordering.By("SortOrder")); // Assert @@ -559,7 +560,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos // Act int level = 2; - IQuery query = scope.SqlContext.Query().Where(x => x.Level == level); + IQuery query = provider.CreateQuery().Where(x => x.Level == level); int result = repository.Count(query); // Assert diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/MediaTypeRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/MediaTypeRepositoryTest.cs index 46cdca4194..51a71c30e1 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/MediaTypeRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/MediaTypeRepositoryTest.cs @@ -13,6 +13,7 @@ using Umbraco.Cms.Core.Persistence; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Cms.Tests.Common.Builders; using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Cms.Tests.Integration.Testing; @@ -294,7 +295,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos // Act IEnumerable mediaTypes = repository.GetMany(); int count = - scope.Database.ExecuteScalar( + ScopeAccessor.AmbientScope.Database.ExecuteScalar( "SELECT COUNT(*) FROM umbracoNode WHERE nodeObjectType = @NodeObjectType", new { NodeObjectType = Constants.ObjectTypes.MediaType }); @@ -319,7 +320,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos IEnumerable mediaTypes = ((IReadRepository)repository).GetMany(allGuidIds); int count = - scope.Database.ExecuteScalar( + ScopeAccessor.AmbientScope.Database.ExecuteScalar( "SELECT COUNT(*) FROM umbracoNode WHERE nodeObjectType = @NodeObjectType", new { NodeObjectType = Constants.ObjectTypes.MediaType }); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/MemberRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/MemberRepositoryTest.cs index d8bf4be14a..d5fb7d3edc 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/MemberRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/MemberRepositoryTest.cs @@ -28,6 +28,7 @@ using Umbraco.Cms.Infrastructure.Persistence; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Infrastructure.Persistence.Querying; using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Cms.Tests.Common.Builders; using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Cms.Tests.Integration.Testing; @@ -151,7 +152,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos IMember member = CreateTestMember(key: key); // Act - IQuery query = scope.SqlContext.Query().Where(x => x.Key == key); + IQuery query = provider.CreateQuery().Where(x => x.Key == key); IEnumerable result = repository.Get(query); // Assert @@ -271,20 +272,23 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos { IScopeProvider provider = ScopeProvider; - IQuery query = provider.SqlContext.Query().Where(x => - ((Member)x).LongStringPropertyValue.Contains("1095") && - ((Member)x).PropertyTypeAlias == "headshot"); + using (provider.CreateScope()) + { + IQuery query = provider.CreateQuery().Where(x => + ((Member)x).LongStringPropertyValue.Contains("1095") && + ((Member)x).PropertyTypeAlias == "headshot"); - Sql sqlSubquery = GetSubquery(); - var translator = new SqlTranslator(sqlSubquery, query); - Sql subquery = translator.Translate(); - Sql sql = GetBaseQuery(false) - .Append("WHERE umbracoNode.id IN (" + subquery.SQL + ")", subquery.Arguments) - .OrderByDescending(x => x.VersionDate) - .OrderBy(x => x.SortOrder); + Sql sqlSubquery = GetSubquery(); + var translator = new SqlTranslator(sqlSubquery, query); + Sql subquery = translator.Translate(); + Sql sql = GetBaseQuery(false) + .Append("WHERE umbracoNode.id IN (" + subquery.SQL + ")", subquery.Arguments) + .OrderByDescending(x => x.VersionDate) + .OrderBy(x => x.SortOrder); - Debug.Print(sql.SQL); - Assert.That(sql.SQL, Is.Not.Empty); + Debug.Print(sql.SQL); + Assert.That(sql.SQL, Is.Not.Empty); + } } private IMember CreateTestMember(IMemberType memberType = null, string name = null, string email = null, string password = null, string username = null, Guid? key = null) @@ -327,7 +331,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos IScopeProvider provider = ScopeProvider; if (isCount) { - Sql sqlCount = provider.SqlContext.Sql() + Sql sqlCount = ScopeAccessor.AmbientScope.SqlContext.Sql() .SelectCount() .From() .InnerJoin().On(left => left.NodeId, right => right.NodeId) @@ -338,7 +342,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos return sqlCount; } - Sql sql = provider.SqlContext.Sql(); + Sql sql = ScopeAccessor.AmbientScope.SqlContext.Sql(); sql.Select( "umbracoNode.*", $"{Constants.DatabaseSchema.Tables.Content}.contentTypeId", @@ -381,7 +385,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos private Sql GetSubquery() { IScopeProvider provider = ScopeProvider; - Sql sql = provider.SqlContext.Sql(); + Sql sql = ScopeAccessor.AmbientScope.SqlContext.Sql(); sql.Select("umbracoNode.id") .From() .InnerJoin().On(left => left.NodeId, right => right.NodeId) diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/MemberTypeRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/MemberTypeRepositoryTest.cs index c963375e3f..33209abc6a 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/MemberTypeRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/MemberTypeRepositoryTest.cs @@ -14,6 +14,7 @@ using Umbraco.Cms.Core.Persistence; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Cms.Tests.Common.Builders; using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Cms.Tests.Integration.Testing; diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/NotificationsRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/NotificationsRepositoryTest.cs index be625e4afd..f7bfbb989e 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/NotificationsRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/NotificationsRepositoryTest.cs @@ -13,6 +13,7 @@ using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Cms.Tests.Integration.Testing; using Constants = Umbraco.Cms.Core.Constants; @@ -44,7 +45,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos UniqueId = Guid.NewGuid(), UserId = Constants.Security.SuperUserId }; - object result = scope.Database.Insert(node); + object result = ScopeAccessor.AmbientScope.Database.Insert(node); IEntity entity = Mock.Of(e => e.Id == node.NodeId); IUser user = Mock.Of(e => e.Id == node.UserId); @@ -66,7 +67,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos var repo = new NotificationsRepository((IScopeAccessor)provider); var userDto = new UserDto { Email = "test", Login = "test", Password = "test", UserName = "test", UserLanguage = "en", CreateDate = DateTime.Now, UpdateDate = DateTime.Now }; - scope.Database.Insert(userDto); + ScopeAccessor.AmbientScope.Database.Insert(userDto); IUser userNew = Mock.Of(e => e.Id == userDto.Id); IUser userAdmin = Mock.Of(e => e.Id == Constants.Security.SuperUserId); @@ -74,7 +75,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos for (int i = 0; i < 10; i++) { var node = new NodeDto { CreateDate = DateTime.Now, Level = 1, NodeObjectType = Constants.ObjectTypes.ContentItem, ParentId = -1, Path = "-1," + i, SortOrder = 1, Text = "hello" + i, Trashed = false, UniqueId = Guid.NewGuid(), UserId = -1 }; - object result = scope.Database.Insert(node); + object result = ScopeAccessor.AmbientScope.Database.Insert(node); IEntity entity = Mock.Of(e => e.Id == node.NodeId); Notification notification = repo.CreateNotification((i % 2 == 0) ? userAdmin : userNew, entity, i.ToString(CultureInfo.InvariantCulture)); } @@ -94,16 +95,16 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos var repo = new NotificationsRepository((IScopeAccessor)provider); var node1 = new NodeDto { CreateDate = DateTime.Now, Level = 1, NodeObjectType = Constants.ObjectTypes.ContentItem, ParentId = -1, Path = "-1,1", SortOrder = 1, Text = "hello1", Trashed = false, UniqueId = Guid.NewGuid(), UserId = -1 }; - scope.Database.Insert(node1); + ScopeAccessor.AmbientScope.Database.Insert(node1); IEntity entity1 = Mock.Of(e => e.Id == node1.NodeId); var node2 = new NodeDto { CreateDate = DateTime.Now, Level = 1, NodeObjectType = Constants.ObjectTypes.ContentItem, ParentId = -1, Path = "-1,2", SortOrder = 1, Text = "hello2", Trashed = false, UniqueId = Guid.NewGuid(), UserId = -1 }; - scope.Database.Insert(node2); + ScopeAccessor.AmbientScope.Database.Insert(node2); IEntity entity2 = Mock.Of(e => e.Id == node2.NodeId); for (int i = 0; i < 10; i++) { var userDto = new UserDto { Email = "test" + i, Login = "test" + i, Password = "test", UserName = "test" + i, UserLanguage = "en", CreateDate = DateTime.Now, UpdateDate = DateTime.Now }; - scope.Database.Insert(userDto); + ScopeAccessor.AmbientScope.Database.Insert(userDto); IUser userNew = Mock.Of(e => e.Id == userDto.Id); Notification notification = repo.CreateNotification(userNew, (i % 2 == 0) ? entity1 : entity2, i.ToString(CultureInfo.InvariantCulture)); } @@ -123,16 +124,16 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos var repo = new NotificationsRepository((IScopeAccessor)provider); var node1 = new NodeDto { CreateDate = DateTime.Now, Level = 1, NodeObjectType = Constants.ObjectTypes.ContentItem, ParentId = -1, Path = "-1,1", SortOrder = 1, Text = "hello1", Trashed = false, UniqueId = Guid.NewGuid(), UserId = -1 }; - scope.Database.Insert(node1); + ScopeAccessor.AmbientScope.Database.Insert(node1); IEntity entity1 = Mock.Of(e => e.Id == node1.NodeId); var node2 = new NodeDto { CreateDate = DateTime.Now, Level = 1, NodeObjectType = Constants.ObjectTypes.ContentItem, ParentId = -1, Path = "-1,2", SortOrder = 1, Text = "hello2", Trashed = false, UniqueId = Guid.NewGuid(), UserId = -1 }; - scope.Database.Insert(node2); + ScopeAccessor.AmbientScope.Database.Insert(node2); IEntity entity2 = Mock.Of(e => e.Id == node2.NodeId); for (int i = 0; i < 10; i++) { var userDto = new UserDto { Email = "test" + i, Login = "test" + i, Password = "test", UserName = "test" + i, UserLanguage = "en", CreateDate = DateTime.Now, UpdateDate = DateTime.Now }; - scope.Database.Insert(userDto); + ScopeAccessor.AmbientScope.Database.Insert(userDto); IUser userNew = Mock.Of(e => e.Id == userDto.Id); Notification notification = repo.CreateNotification(userNew, (i % 2 == 0) ? entity1 : entity2, i.ToString(CultureInfo.InvariantCulture)); } @@ -152,7 +153,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos var repo = new NotificationsRepository((IScopeAccessor)provider); var userDto = new UserDto { Email = "test", Login = "test", Password = "test", UserName = "test", UserLanguage = "en", CreateDate = DateTime.Now, UpdateDate = DateTime.Now }; - scope.Database.Insert(userDto); + ScopeAccessor.AmbientScope.Database.Insert(userDto); IUser userNew = Mock.Of(e => e.Id == userDto.Id); IUser userAdmin = Mock.Of(e => e.Id == Constants.Security.SuperUserId); @@ -160,7 +161,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos for (int i = 0; i < 10; i++) { var node = new NodeDto { CreateDate = DateTime.Now, Level = 1, NodeObjectType = Constants.ObjectTypes.ContentItem, ParentId = -1, Path = "-1," + i, SortOrder = 1, Text = "hello" + i, Trashed = false, UniqueId = Guid.NewGuid(), UserId = -1 }; - object result = scope.Database.Insert(node); + object result = ScopeAccessor.AmbientScope.Database.Insert(node); IEntity entity = Mock.Of(e => e.Id == node.NodeId); Notification notification = repo.CreateNotification((i % 2 == 0) ? userAdmin : userNew, entity, i.ToString(CultureInfo.InvariantCulture)); } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/PublicAccessRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/PublicAccessRepositoryTest.cs index 3926c4975c..283c8e57aa 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/PublicAccessRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/PublicAccessRepositoryTest.cs @@ -11,6 +11,7 @@ using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Infrastructure.Persistence; using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Cms.Tests.Common.Builders; using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Cms.Tests.Integration.Testing; @@ -62,7 +63,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos IScopeProvider provider = ScopeProvider; using (IScope scope = provider.CreateScope()) { - scope.Database.AsUmbracoDatabase().EnableSqlTrace = true; + ScopeAccessor.AmbientScope.Database.AsUmbracoDatabase().EnableSqlTrace = true; var repo = new PublicAccessRepository((IScopeAccessor)provider, AppCaches, LoggerFactory.CreateLogger()); PublicAccessRule[] rules = new[] @@ -102,7 +103,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos IScopeProvider provider = ScopeProvider; using (IScope scope = provider.CreateScope()) { - scope.Database.AsUmbracoDatabase().EnableSqlTrace = true; + ScopeAccessor.AmbientScope.Database.AsUmbracoDatabase().EnableSqlTrace = true; var repo = new PublicAccessRepository((IScopeAccessor)provider, AppCaches, LoggerFactory.CreateLogger()); PublicAccessRule[] rules = new[] diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/RedirectUrlRepositoryTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/RedirectUrlRepositoryTests.cs index 04a0fd3ebb..af75016d9a 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/RedirectUrlRepositoryTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/RedirectUrlRepositoryTests.cs @@ -11,6 +11,7 @@ using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Cms.Tests.Common.Builders; using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Cms.Tests.Integration.Testing; diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/RelationRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/RelationRepositoryTest.cs index ee01816f09..a20706c597 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/RelationRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/RelationRepositoryTest.cs @@ -15,6 +15,7 @@ using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Cms.Tests.Common.Builders; using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Cms.Tests.Integration.Testing; @@ -361,7 +362,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos RelationRepository repository = CreateRepository(ScopeProvider, out RelationTypeRepository repositoryType); // Act - IQuery query = scope.SqlContext.Query().Where(x => x.ParentId == _textpage.Id); + IQuery query = ScopeProvider.CreateQuery().Where(x => x.ParentId == _textpage.Id); int count = repository.Count(query); // Assert @@ -378,7 +379,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos RelationRepository repository = CreateRepository(ScopeProvider, out RelationTypeRepository repositoryType); // Act - IQuery query = scope.SqlContext.Query().Where(x => x.RelationTypeId == _relateContent.Id); + IQuery query = ScopeProvider.CreateQuery().Where(x => x.RelationTypeId == _relateContent.Id); IEnumerable relations = repository.Get(query); // Assert diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/RelationTypeRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/RelationTypeRepositoryTest.cs index 4b0a23464f..d99de65721 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/RelationTypeRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/RelationTypeRepositoryTest.cs @@ -10,6 +10,7 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Cms.Tests.Integration.Testing; using Constants = Umbraco.Cms.Core.Constants; @@ -183,7 +184,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos RelationTypeRepository repository = CreateRepository(provider); // Act - IQuery query = scope.SqlContext.Query().Where(x => x.Alias.StartsWith("relate")); + IQuery query = provider.CreateQuery().Where(x => x.Alias.StartsWith("relate")); int count = repository.Count(query); // Assert @@ -202,7 +203,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos // Act System.Guid childObjType = Constants.ObjectTypes.DocumentType; - IQuery query = scope.SqlContext.Query().Where(x => x.ChildObjectType == childObjType); + IQuery query = provider.CreateQuery().Where(x => x.ChildObjectType == childObjType); IEnumerable result = repository.Get(query); // Assert diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ServerRegistrationRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ServerRegistrationRepositoryTest.cs index f85796f622..10461dd577 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ServerRegistrationRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ServerRegistrationRepositoryTest.cs @@ -11,6 +11,7 @@ using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Cms.Tests.Integration.Testing; diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/TagRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/TagRepositoryTest.cs index e2972ad504..8dc0a2ed81 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/TagRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/TagRepositoryTest.cs @@ -11,6 +11,7 @@ using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Cms.Tests.Common.Builders; using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Cms.Tests.Integration.Testing; @@ -864,7 +865,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos DocumentRepository.Delete(content1); - Assert.AreEqual(0, scope.Database.ExecuteScalar( + Assert.AreEqual(0, ScopeAccessor.AmbientScope.Database.ExecuteScalar( "SELECT COUNT(*) FROM cmsTagRelationship WHERE nodeId=@nodeId AND propertyTypeId=@propTypeId", new { nodeId = content1.Id, propTypeId = contentType.PropertyTypes.First().Id })); } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/TemplateRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/TemplateRepositoryTest.cs index 80eb61d21f..7b0b8b9e21 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/TemplateRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/TemplateRepositoryTest.cs @@ -22,6 +22,7 @@ using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Cms.Infrastructure.Serialization; using Umbraco.Cms.Tests.Common.Builders; using Umbraco.Cms.Tests.Common.Testing; diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/UserGroupRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/UserGroupRepositoryTest.cs index b38814b928..367d4f770e 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/UserGroupRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/UserGroupRepositoryTest.cs @@ -10,6 +10,7 @@ using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Cms.Tests.Common.Builders; using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Cms.Tests.Integration.Testing; @@ -180,7 +181,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos CreateAndCommitMultipleUserGroups(repository); // Act - IQuery query = scope.SqlContext.Query().Where(x => x.Alias == "testGroup1"); + IQuery query = provider.CreateQuery().Where(x => x.Alias == "testGroup1"); IEnumerable result = repository.Get(query); // Assert @@ -261,7 +262,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos IUserGroup[] userGroups = CreateAndCommitMultipleUserGroups(repository); // Act - IQuery query = scope.SqlContext.Query().Where(x => x.Alias == "testGroup1" || x.Alias == "testGroup2"); + IQuery query = provider.CreateQuery().Where(x => x.Alias == "testGroup1" || x.Alias == "testGroup2"); int result = repository.Count(query); // Assert diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/UserRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/UserRepositoryTest.cs index a613a42c36..52af0ac615 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/UserRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/UserRepositoryTest.cs @@ -21,6 +21,7 @@ using Umbraco.Cms.Infrastructure.Persistence; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Infrastructure.Persistence.Mappers; using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Cms.Infrastructure.Serialization; using Umbraco.Cms.Tests.Common.Builders; using Umbraco.Cms.Tests.Common.Builders.Extensions; @@ -192,7 +193,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos CreateAndCommitMultipleUsers(repository); // Act - IQuery query = scope.SqlContext.Query().Where(x => x.Username == "TestUser1"); + IQuery query = ScopeProvider.CreateQuery().Where(x => x.Username == "TestUser1"); IEnumerable result = repository.Get(query); // Assert @@ -273,7 +274,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos IUser[] users = CreateAndCommitMultipleUsers(repository); // Act - IQuery query = scope.SqlContext.Query().Where(x => x.Username == "TestUser1" || x.Username == "TestUser2"); + IQuery query = ScopeProvider.CreateQuery().Where(x => x.Username == "TestUser1" || x.Username == "TestUser2"); int result = repository.Count(query); // Assert @@ -290,12 +291,12 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos UserRepository repository = CreateRepository(provider); IUser[] users = CreateAndCommitMultipleUsers(repository); - IQuery query = provider.SqlContext.Query().Where(x => x.Username == "TestUser1" || x.Username == "TestUser2"); + IQuery query = provider.CreateQuery().Where(x => x.Username == "TestUser1" || x.Username == "TestUser2"); try { - scope.Database.AsUmbracoDatabase().EnableSqlTrace = true; - scope.Database.AsUmbracoDatabase().EnableSqlCount = true; + ScopeAccessor.AmbientScope.Database.AsUmbracoDatabase().EnableSqlTrace = true; + ScopeAccessor.AmbientScope.Database.AsUmbracoDatabase().EnableSqlCount = true; // Act IEnumerable result = repository.GetPagedResultsByQuery( @@ -306,15 +307,15 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos user => user.Id, Direction.Ascending, excludeUserGroups: new[] { Constants.Security.TranslatorGroupAlias }, - filter: provider.SqlContext.Query().Where(x => x.Id > -1)); + filter: provider.CreateQuery().Where(x => x.Id > -1)); // Assert Assert.AreEqual(2, totalRecs); } finally { - scope.Database.AsUmbracoDatabase().EnableSqlTrace = false; - scope.Database.AsUmbracoDatabase().EnableSqlCount = false; + ScopeAccessor.AmbientScope.Database.AsUmbracoDatabase().EnableSqlTrace = false; + ScopeAccessor.AmbientScope.Database.AsUmbracoDatabase().EnableSqlCount = false; } } } @@ -331,8 +332,8 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos try { - scope.Database.AsUmbracoDatabase().EnableSqlTrace = true; - scope.Database.AsUmbracoDatabase().EnableSqlCount = true; + ScopeAccessor.AmbientScope.Database.AsUmbracoDatabase().EnableSqlTrace = true; + ScopeAccessor.AmbientScope.Database.AsUmbracoDatabase().EnableSqlCount = true; // Act IEnumerable result = repository.GetPagedResultsByQuery( @@ -344,15 +345,15 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos Direction.Ascending, includeUserGroups: new[] { Constants.Security.AdminGroupAlias, Constants.Security.SensitiveDataGroupAlias }, excludeUserGroups: new[] { Constants.Security.TranslatorGroupAlias }, - filter: provider.SqlContext.Query().Where(x => x.Id == -1)); + filter: provider.CreateQuery().Where(x => x.Id == -1)); // Assert Assert.AreEqual(1, totalRecs); } finally { - scope.Database.AsUmbracoDatabase().EnableSqlTrace = false; - scope.Database.AsUmbracoDatabase().EnableSqlCount = false; + ScopeAccessor.AmbientScope.Database.AsUmbracoDatabase().EnableSqlTrace = false; + ScopeAccessor.AmbientScope.Database.AsUmbracoDatabase().EnableSqlCount = false; } } } @@ -404,7 +405,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos Guid sessionId = repository.CreateLoginSession(user.Id, "1.2.3.4"); // manually update this record to be in the past - scope.Database.Execute(scope.SqlContext.Sql() + ScopeAccessor.AmbientScope.Database.Execute(ScopeAccessor.AmbientScope.SqlContext.Sql() .Update(u => u.Set(x => x.LoggedOutUtc, DateTime.UtcNow.AddDays(-100))) .Where(x => x.SessionId == sessionId)); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/SchemaValidationTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/SchemaValidationTest.cs index 13f543c130..90f6fab9e1 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/SchemaValidationTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/SchemaValidationTest.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using NUnit.Framework; using Umbraco.Cms.Core.Configuration; using Umbraco.Cms.Core.Events; @@ -22,7 +22,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence using (var scope = ScopeProvider.CreateScope()) { - var schema = new DatabaseSchemaCreator(scope.Database, LoggerFactory.CreateLogger(), LoggerFactory, UmbracoVersion, EventAggregator); + var schema = new DatabaseSchemaCreator(ScopeAccessor.AmbientScope.Database, LoggerFactory.CreateLogger(), LoggerFactory, UmbracoVersion, EventAggregator); result = schema.ValidateSchema(DatabaseSchemaCreator.OrderedTables); } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/SqlServerTableByTableTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/SqlServerTableByTableTest.cs index 4b3d159ff5..8c34202fe4 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/SqlServerTableByTableTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/SqlServerTableByTableTest.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using NUnit.Framework; using Umbraco.Cms.Core.Configuration; @@ -23,7 +23,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = new DatabaseSchemaCreator(ScopeAccessor.AmbientScope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); helper.CreateTable(); @@ -36,7 +36,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = new DatabaseSchemaCreator(ScopeAccessor.AmbientScope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); helper.CreateTable(); helper.CreateTable(); @@ -50,7 +50,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = new DatabaseSchemaCreator(ScopeAccessor.AmbientScope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); helper.CreateTable(); helper.CreateTable(); @@ -65,7 +65,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = new DatabaseSchemaCreator(ScopeAccessor.AmbientScope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); helper.CreateTable(); helper.CreateTable(); @@ -79,7 +79,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = new DatabaseSchemaCreator(ScopeAccessor.AmbientScope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); helper.CreateTable(); helper.CreateTable(); @@ -94,7 +94,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = new DatabaseSchemaCreator(ScopeAccessor.AmbientScope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); helper.CreateTable(); helper.CreateTable(); @@ -108,7 +108,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = new DatabaseSchemaCreator(ScopeAccessor.AmbientScope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); helper.CreateTable(); helper.CreateTable(); @@ -124,7 +124,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = new DatabaseSchemaCreator(ScopeAccessor.AmbientScope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); helper.CreateTable(); helper.CreateTable(); @@ -138,7 +138,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = new DatabaseSchemaCreator(ScopeAccessor.AmbientScope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); helper.CreateTable(); @@ -151,7 +151,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = new DatabaseSchemaCreator(ScopeAccessor.AmbientScope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); helper.CreateTable(); helper.CreateTable(); @@ -166,7 +166,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = new DatabaseSchemaCreator(ScopeAccessor.AmbientScope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); helper.CreateTable(); helper.CreateTable(); @@ -180,7 +180,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = new DatabaseSchemaCreator(ScopeAccessor.AmbientScope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); helper.CreateTable(); helper.CreateTable(); @@ -197,7 +197,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = new DatabaseSchemaCreator(ScopeAccessor.AmbientScope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); helper.CreateTable(); helper.CreateTable(); @@ -213,7 +213,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = new DatabaseSchemaCreator(ScopeAccessor.AmbientScope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); helper.CreateTable(); helper.CreateTable(); @@ -227,7 +227,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = new DatabaseSchemaCreator(ScopeAccessor.AmbientScope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); helper.CreateTable(); @@ -240,7 +240,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = new DatabaseSchemaCreator(ScopeAccessor.AmbientScope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); helper.CreateTable(); @@ -253,7 +253,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = new DatabaseSchemaCreator(ScopeAccessor.AmbientScope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); helper.CreateTable(); @@ -266,7 +266,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = new DatabaseSchemaCreator(ScopeAccessor.AmbientScope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); helper.CreateTable(); @@ -279,7 +279,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = new DatabaseSchemaCreator(ScopeAccessor.AmbientScope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); helper.CreateTable(); helper.CreateTable(); @@ -295,7 +295,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = new DatabaseSchemaCreator(ScopeAccessor.AmbientScope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); helper.CreateTable(); helper.CreateTable(); @@ -312,7 +312,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = new DatabaseSchemaCreator(ScopeAccessor.AmbientScope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); helper.CreateTable(); helper.CreateTable(); @@ -327,7 +327,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = new DatabaseSchemaCreator(ScopeAccessor.AmbientScope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); helper.CreateTable(); helper.CreateTable(); @@ -345,7 +345,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = new DatabaseSchemaCreator(ScopeAccessor.AmbientScope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); helper.CreateTable(); helper.CreateTable(); @@ -362,7 +362,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = new DatabaseSchemaCreator(ScopeAccessor.AmbientScope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); helper.CreateTable(); helper.CreateTable(); @@ -377,7 +377,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = new DatabaseSchemaCreator(ScopeAccessor.AmbientScope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); helper.CreateTable(); helper.CreateTable(); @@ -392,7 +392,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = new DatabaseSchemaCreator(ScopeAccessor.AmbientScope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); helper.CreateTable(); @@ -405,7 +405,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = new DatabaseSchemaCreator(ScopeAccessor.AmbientScope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); helper.CreateTable(); @@ -418,7 +418,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = new DatabaseSchemaCreator(ScopeAccessor.AmbientScope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); helper.CreateTable(); helper.CreateTable(); @@ -439,7 +439,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = new DatabaseSchemaCreator(ScopeAccessor.AmbientScope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); helper.CreateTable(); @@ -452,7 +452,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = new DatabaseSchemaCreator(ScopeAccessor.AmbientScope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); helper.CreateTable(); helper.CreateTable(); @@ -466,7 +466,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = new DatabaseSchemaCreator(ScopeAccessor.AmbientScope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); helper.CreateTable(); helper.CreateTable(); @@ -480,7 +480,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = new DatabaseSchemaCreator(ScopeAccessor.AmbientScope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); helper.CreateTable(); helper.CreateTable(); @@ -495,7 +495,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = new DatabaseSchemaCreator(ScopeAccessor.AmbientScope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); helper.CreateTable(); helper.CreateTable(); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopeTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopeTests.cs index 37a21cf9b4..bbb9ea8766 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopeTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopeTests.cs @@ -187,7 +187,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Scoping Assert.IsInstanceOf(scope); Assert.IsNotNull(scopeProvider.AmbientScope); Assert.AreSame(scope, scopeProvider.AmbientScope); - database = scope.Database; // populates scope's database + database = ScopeAccessor.AmbientScope.Database; // populates scope's database Assert.IsNotNull(database); Assert.IsNotNull(database.Connection); // in a transaction } @@ -360,7 +360,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Scoping Assert.IsInstanceOf(scope); Assert.IsNotNull(scopeProvider.AmbientScope); Assert.AreSame(scope, scopeProvider.AmbientScope); - database = scope.Database; // populates scope's database + database = ScopeAccessor.AmbientScope.Database; // populates scope's database Assert.IsNotNull(database); Assert.IsNotNull(database.Connection); // in a transaction using (IScope nested = scopeProvider.CreateScope()) @@ -369,7 +369,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Scoping Assert.IsNotNull(scopeProvider.AmbientScope); Assert.AreSame(nested, scopeProvider.AmbientScope); Assert.AreSame(scope, ((Scope)nested).ParentScope); - Assert.AreSame(database, nested.Database); + Assert.AreSame(database, ScopeAccessor.AmbientScope.Database); } Assert.IsNotNull(database.Connection); // still @@ -386,32 +386,32 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Scoping using (IScope scope = scopeProvider.CreateScope()) { - scope.Database.Execute("CREATE TABLE tmp3 (id INT, name NVARCHAR(64))"); + ScopeAccessor.AmbientScope.Database.Execute("CREATE TABLE tmp3 (id INT, name NVARCHAR(64))"); scope.Complete(); } using (IScope scope = scopeProvider.CreateScope()) { - scope.Database.Execute("INSERT INTO tmp3 (id, name) VALUES (1, 'a')"); - string n = scope.Database.ExecuteScalar("SELECT name FROM tmp3 WHERE id=1"); + ScopeAccessor.AmbientScope.Database.Execute("INSERT INTO tmp3 (id, name) VALUES (1, 'a')"); + string n = ScopeAccessor.AmbientScope.Database.ExecuteScalar("SELECT name FROM tmp3 WHERE id=1"); Assert.AreEqual("a", n); } using (IScope scope = scopeProvider.CreateScope()) { - string n = scope.Database.ExecuteScalar("SELECT name FROM tmp3 WHERE id=1"); + string n = ScopeAccessor.AmbientScope.Database.ExecuteScalar("SELECT name FROM tmp3 WHERE id=1"); Assert.IsNull(n); } using (IScope scope = scopeProvider.CreateScope()) { - scope.Database.Execute("INSERT INTO tmp3 (id, name) VALUES (1, 'a')"); + ScopeAccessor.AmbientScope.Database.Execute("INSERT INTO tmp3 (id, name) VALUES (1, 'a')"); scope.Complete(); } using (IScope scope = scopeProvider.CreateScope()) { - string n = scope.Database.ExecuteScalar("SELECT name FROM tmp3 WHERE id=1"); + string n = ScopeAccessor.AmbientScope.Database.ExecuteScalar("SELECT name FROM tmp3 WHERE id=1"); Assert.AreEqual("a", n); } } @@ -423,24 +423,24 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Scoping using (IScope scope = scopeProvider.CreateScope()) { - scope.Database.Execute($"CREATE TABLE tmp1 (id INT, name NVARCHAR(64))"); + ScopeAccessor.AmbientScope.Database.Execute($"CREATE TABLE tmp1 (id INT, name NVARCHAR(64))"); scope.Complete(); } using (IScope scope = scopeProvider.CreateScope()) { - scope.Database.Execute("INSERT INTO tmp1 (id, name) VALUES (1, 'a')"); - string n = scope.Database.ExecuteScalar("SELECT name FROM tmp1 WHERE id=1"); + ScopeAccessor.AmbientScope.Database.Execute("INSERT INTO tmp1 (id, name) VALUES (1, 'a')"); + string n = ScopeAccessor.AmbientScope.Database.ExecuteScalar("SELECT name FROM tmp1 WHERE id=1"); Assert.AreEqual("a", n); using (IScope nested = scopeProvider.CreateScope()) { - nested.Database.Execute("INSERT INTO tmp1 (id, name) VALUES (2, 'b')"); - string nn = nested.Database.ExecuteScalar("SELECT name FROM tmp1 WHERE id=2"); + ScopeAccessor.AmbientScope.Database.Execute("INSERT INTO tmp1 (id, name) VALUES (2, 'b')"); + string nn = ScopeAccessor.AmbientScope.Database.ExecuteScalar("SELECT name FROM tmp1 WHERE id=2"); Assert.AreEqual("b", nn); } - n = scope.Database.ExecuteScalar("SELECT name FROM tmp1 WHERE id=2"); + n = ScopeAccessor.AmbientScope.Database.ExecuteScalar("SELECT name FROM tmp1 WHERE id=2"); Assert.AreEqual("b", n); scope.Complete(); @@ -448,9 +448,9 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Scoping using (IScope scope = scopeProvider.CreateScope()) { - string n = scope.Database.ExecuteScalar("SELECT name FROM tmp1 WHERE id=1"); + string n = ScopeAccessor.AmbientScope.Database.ExecuteScalar("SELECT name FROM tmp1 WHERE id=1"); Assert.IsNull(n); - n = scope.Database.ExecuteScalar("SELECT name FROM tmp1 WHERE id=2"); + n = ScopeAccessor.AmbientScope.Database.ExecuteScalar("SELECT name FROM tmp1 WHERE id=2"); Assert.IsNull(n); } } @@ -462,33 +462,33 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Scoping using (IScope scope = scopeProvider.CreateScope()) { - scope.Database.Execute("CREATE TABLE tmp2 (id INT, name NVARCHAR(64))"); + ScopeAccessor.AmbientScope.Database.Execute("CREATE TABLE tmp2 (id INT, name NVARCHAR(64))"); scope.Complete(); } using (IScope scope = scopeProvider.CreateScope()) { - scope.Database.Execute("INSERT INTO tmp2 (id, name) VALUES (1, 'a')"); - string n = scope.Database.ExecuteScalar("SELECT name FROM tmp2 WHERE id=1"); + ScopeAccessor.AmbientScope.Database.Execute("INSERT INTO tmp2 (id, name) VALUES (1, 'a')"); + string n = ScopeAccessor.AmbientScope.Database.ExecuteScalar("SELECT name FROM tmp2 WHERE id=1"); Assert.AreEqual("a", n); using (IScope nested = scopeProvider.CreateScope()) { - nested.Database.Execute("INSERT INTO tmp2 (id, name) VALUES (2, 'b')"); - string nn = nested.Database.ExecuteScalar("SELECT name FROM tmp2 WHERE id=2"); + ScopeAccessor.AmbientScope.Database.Execute("INSERT INTO tmp2 (id, name) VALUES (2, 'b')"); + string nn = ScopeAccessor.AmbientScope.Database.ExecuteScalar("SELECT name FROM tmp2 WHERE id=2"); Assert.AreEqual("b", nn); nested.Complete(); } - n = scope.Database.ExecuteScalar("SELECT name FROM tmp2 WHERE id=2"); + n = ScopeAccessor.AmbientScope.Database.ExecuteScalar("SELECT name FROM tmp2 WHERE id=2"); Assert.AreEqual("b", n); } using (IScope scope = scopeProvider.CreateScope()) { - string n = scope.Database.ExecuteScalar("SELECT name FROM tmp2 WHERE id=1"); + string n = ScopeAccessor.AmbientScope.Database.ExecuteScalar("SELECT name FROM tmp2 WHERE id=1"); Assert.IsNull(n); - n = scope.Database.ExecuteScalar("SELECT name FROM tmp2 WHERE id=2"); + n = ScopeAccessor.AmbientScope.Database.ExecuteScalar("SELECT name FROM tmp2 WHERE id=2"); Assert.IsNull(n); } } @@ -500,34 +500,34 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Scoping using (IScope scope = scopeProvider.CreateScope()) { - scope.Database.Execute("CREATE TABLE tmp (id INT, name NVARCHAR(64))"); + ScopeAccessor.AmbientScope.Database.Execute("CREATE TABLE tmp (id INT, name NVARCHAR(64))"); scope.Complete(); } using (IScope scope = scopeProvider.CreateScope()) { - scope.Database.Execute("INSERT INTO tmp (id, name) VALUES (1, 'a')"); - string n = scope.Database.ExecuteScalar("SELECT name FROM tmp WHERE id=1"); + ScopeAccessor.AmbientScope.Database.Execute("INSERT INTO tmp (id, name) VALUES (1, 'a')"); + string n = ScopeAccessor.AmbientScope.Database.ExecuteScalar("SELECT name FROM tmp WHERE id=1"); Assert.AreEqual("a", n); using (IScope nested = scopeProvider.CreateScope()) { - nested.Database.Execute("INSERT INTO tmp (id, name) VALUES (2, 'b')"); - string nn = nested.Database.ExecuteScalar("SELECT name FROM tmp WHERE id=2"); + ScopeAccessor.AmbientScope.Database.Execute("INSERT INTO tmp (id, name) VALUES (2, 'b')"); + string nn = ScopeAccessor.AmbientScope.Database.ExecuteScalar("SELECT name FROM tmp WHERE id=2"); Assert.AreEqual("b", nn); nested.Complete(); } - n = scope.Database.ExecuteScalar("SELECT name FROM tmp WHERE id=2"); + n = ScopeAccessor.AmbientScope.Database.ExecuteScalar("SELECT name FROM tmp WHERE id=2"); Assert.AreEqual("b", n); scope.Complete(); } using (IScope scope = scopeProvider.CreateScope()) { - string n = scope.Database.ExecuteScalar("SELECT name FROM tmp WHERE id=1"); + string n = ScopeAccessor.AmbientScope.Database.ExecuteScalar("SELECT name FROM tmp WHERE id=1"); Assert.AreEqual("a", n); - n = scope.Database.ExecuteScalar("SELECT name FROM tmp WHERE id=2"); + n = ScopeAccessor.AmbientScope.Database.ExecuteScalar("SELECT name FROM tmp WHERE id=2"); Assert.AreEqual("b", n); } } @@ -615,13 +615,17 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Scoping public void ScopeReference() { ScopeProvider scopeProvider = ScopeProvider; - IScope scope = scopeProvider.CreateScope(); - IScope nested = scopeProvider.CreateScope(); + Scope scope = (Scope) scopeProvider.CreateScope(); + Scope nested = (Scope) scopeProvider.CreateScope(); + Assert.IsNotNull(scopeProvider.AmbientScope); + var scopeRef = new HttpScopeReference(scopeProvider); scopeRef.Register(); scopeRef.Dispose(); + Assert.IsNull(scopeProvider.AmbientScope); + Assert.Throws(() => { IUmbracoDatabase db = scope.Database; diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/CacheInstructionServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/CacheInstructionServiceTests.cs index bce214eada..80005f8058 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/CacheInstructionServiceTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/CacheInstructionServiceTests.cs @@ -142,7 +142,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services .From(); using (IScope scope = ScopeProvider.CreateScope()) { - cacheInstructions = scope.Database.Fetch(sql); + cacheInstructions = ScopeAccessor.AmbientScope.Database.Fetch(sql); scope.Complete(); } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceTagsTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceTagsTests.cs index 32883138c2..c082d93946 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceTagsTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceTagsTests.cs @@ -624,15 +624,15 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services using (IScope scope = ScopeProvider.CreateScope()) { - Assert.AreEqual(4, scope.Database.ExecuteScalar( + Assert.AreEqual(4, ScopeAccessor.AmbientScope.Database.ExecuteScalar( "SELECT COUNT(*) FROM cmsTagRelationship WHERE nodeId=@nodeId AND propertyTypeId=@propTypeId", new { nodeId = content.Id, propTypeId = propertyTypeId })); - Assert.AreEqual(3, scope.Database.ExecuteScalar( + Assert.AreEqual(3, ScopeAccessor.AmbientScope.Database.ExecuteScalar( "SELECT COUNT(*) FROM cmsTagRelationship WHERE nodeId=@nodeId AND propertyTypeId=@propTypeId", new { nodeId = child1.Id, propTypeId = propertyTypeId })); - Assert.AreEqual(2, scope.Database.ExecuteScalar( + Assert.AreEqual(2, ScopeAccessor.AmbientScope.Database.ExecuteScalar( "SELECT COUNT(*) FROM cmsTagRelationship WHERE nodeId=@nodeId AND propertyTypeId=@propTypeId", new { nodeId = child2.Id, propTypeId = propertyTypeId })); @@ -667,7 +667,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services int propertyTypeId = contentType.PropertyTypes.Single(x => x.Alias == "tags").Id; using (IScope scope = ScopeProvider.CreateScope()) { - Assert.AreEqual(4, scope.Database.ExecuteScalar( + Assert.AreEqual(4, ScopeAccessor.AmbientScope.Database.ExecuteScalar( "SELECT COUNT(*) FROM cmsTagRelationship WHERE nodeId=@nodeId AND propertyTypeId=@propTypeId", new { nodeId = content.Id, propTypeId = propertyTypeId })); scope.Complete(); @@ -696,7 +696,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services int propertyTypeId = contentType.PropertyTypes.Single(x => x.Alias == "tags").Id; using (IScope scope = ScopeProvider.CreateScope()) { - Assert.AreEqual(4, scope.Database.ExecuteScalar( + Assert.AreEqual(4, ScopeAccessor.AmbientScope.Database.ExecuteScalar( "SELECT COUNT(*) FROM cmsTagRelationship WHERE nodeId=@nodeId AND propertyTypeId=@propTypeId", new { nodeId = content.Id, propTypeId = propertyTypeId })); @@ -727,7 +727,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services int propertyTypeId = contentType.PropertyTypes.Single(x => x.Alias == "tags").Id; using (IScope scope = ScopeProvider.CreateScope()) { - Assert.AreEqual(5, scope.Database.ExecuteScalar( + Assert.AreEqual(5, ScopeAccessor.AmbientScope.Database.ExecuteScalar( "SELECT COUNT(*) FROM cmsTagRelationship WHERE nodeId=@nodeId AND propertyTypeId=@propTypeId", new { nodeId = content.Id, propTypeId = propertyTypeId })); @@ -758,7 +758,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services int propertyTypeId = contentType.PropertyTypes.Single(x => x.Alias == "tags").Id; using (IScope scope = ScopeProvider.CreateScope()) { - Assert.AreEqual(2, scope.Database.ExecuteScalar( + Assert.AreEqual(2, ScopeAccessor.AmbientScope.Database.ExecuteScalar( "SELECT COUNT(*) FROM cmsTagRelationship WHERE nodeId=@nodeId AND propertyTypeId=@propTypeId", new { nodeId = content.Id, propTypeId = propertyTypeId })); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentTypeServiceVariantsTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentTypeServiceVariantsTests.cs index 04617ae5f3..985aed0597 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentTypeServiceVariantsTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentTypeServiceVariantsTests.cs @@ -75,7 +75,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services using (IScope scope = ScopeProvider.CreateScope(autoComplete: true)) { Sql selectJson = SqlContext.Sql().Select().From().Where(x => x.NodeId == id && !x.Published); - ContentNuDto dto = scope.Database.Fetch(selectJson).FirstOrDefault(); + ContentNuDto dto = ScopeAccessor.AmbientScope.Database.Fetch(selectJson).FirstOrDefault(); Assert.IsNotNull(dto); string json = dto.Data; return json; diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentVersionCleanupServiceTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentVersionCleanupServiceTest.cs index 08200a6f7e..97a5698ef0 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentVersionCleanupServiceTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentVersionCleanupServiceTest.cs @@ -77,9 +77,9 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services using (IScope scope = ScopeProvider.CreateScope(autoComplete: true)) { // SQL CE is fun! - var contentVersions = scope.Database.Single(@"select count(1) from umbracoContentVersion"); - var documentVersions = scope.Database.Single(@"select count(1) from umbracoDocumentVersion"); - var propertyData = scope.Database.Single(@"select count(1) from umbracoPropertyData"); + var contentVersions = ScopeAccessor.AmbientScope.Database.Single(@"select count(1) from umbracoContentVersion"); + var documentVersions = ScopeAccessor.AmbientScope.Database.Single(@"select count(1) from umbracoDocumentVersion"); + var propertyData = ScopeAccessor.AmbientScope.Database.Single(@"select count(1) from umbracoPropertyData"); return new Report { @@ -103,7 +103,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services Updated = DateTime.Today }; - scope.Database.Insert(entity); + ScopeAccessor.AmbientScope.Database.Insert(entity); } } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ExternalLoginServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ExternalLoginServiceTests.cs index b4f6c1ae5f..09870a08e1 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ExternalLoginServiceTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ExternalLoginServiceTests.cs @@ -36,14 +36,14 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services using (global::Umbraco.Cms.Core.Scoping.IScope scope = ScopeProvider.CreateScope()) { // insert duplicates manuall - scope.Database.Insert(new ExternalLoginDto + ScopeAccessor.AmbientScope.Database.Insert(new ExternalLoginDto { UserId = user.Id, LoginProvider = "test1", ProviderKey = providerKey, CreateDate = latest }); - scope.Database.Insert(new ExternalLoginDto + ScopeAccessor.AmbientScope.Database.Insert(new ExternalLoginDto { UserId = user.Id, LoginProvider = "test1", diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/LocalizationServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/LocalizationServiceTests.cs index dee9ec819e..aca0ee4183 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/LocalizationServiceTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/LocalizationServiceTests.cs @@ -142,17 +142,17 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services currParentId = desc1.Key; } - scope.Database.AsUmbracoDatabase().EnableSqlTrace = true; - scope.Database.AsUmbracoDatabase().EnableSqlCount = true; + ScopeAccessor.AmbientScope.Database.AsUmbracoDatabase().EnableSqlTrace = true; + ScopeAccessor.AmbientScope.Database.AsUmbracoDatabase().EnableSqlCount = true; IDictionaryItem[] items = LocalizationService.GetDictionaryItemDescendants(_parentItemGuidId).ToArray(); - Debug.WriteLine("SQL CALLS: " + scope.Database.AsUmbracoDatabase().SqlCount); + Debug.WriteLine("SQL CALLS: " + ScopeAccessor.AmbientScope.Database.AsUmbracoDatabase().SqlCount); Assert.AreEqual(51, items.Length); // There's a call or two to get languages, so apart from that there should only be one call per level. - Assert.Less(scope.Database.AsUmbracoDatabase().SqlCount, 30); + Assert.Less(ScopeAccessor.AmbientScope.Database.AsUmbracoDatabase().SqlCount, 30); } } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/MacroServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/MacroServiceTests.cs index 75dae7515b..1f847356a1 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/MacroServiceTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/MacroServiceTests.cs @@ -13,6 +13,7 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Cms.Tests.Common.Builders; using Umbraco.Cms.Tests.Common.Builders.Extensions; using Umbraco.Cms.Tests.Common.Testing; diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/MediaServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/MediaServiceTests.cs index 786a665509..16b9430ff7 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/MediaServiceTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/MediaServiceTests.cs @@ -109,7 +109,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services 0, 11, out long total, - provider.SqlContext.Query() + provider.CreateQuery() .Where(x => new[] { mediaType1.Id, mediaType2.Id }.Contains(x.ContentTypeId)), Ordering.By("SortOrder", Direction.Ascending)); Assert.AreEqual(11, result.Count()); @@ -120,7 +120,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services 1, 11, out total, - provider.SqlContext.Query() + provider.CreateQuery() .Where(x => new[] { mediaType1.Id, mediaType2.Id }.Contains(x.ContentTypeId)), Ordering.By("SortOrder", Direction.Ascending)); Assert.AreEqual(9, result.Count()); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/MemberServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/MemberServiceTests.cs index 96a91af10f..2fe543c1e2 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/MemberServiceTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/MemberServiceTests.cs @@ -333,7 +333,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services int roleId; using (IScope scope = ScopeProvider.CreateScope()) { - roleId = scope.Database.ExecuteScalar("SELECT id from umbracoNode where [text] = 'MyTestRole1'"); + roleId = ScopeAccessor.AmbientScope.Database.ExecuteScalar("SELECT id from umbracoNode where [text] = 'MyTestRole1'"); scope.Complete(); } @@ -346,8 +346,8 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services using (IScope scope = ScopeProvider.CreateScope()) { - scope.Database.Insert(new Member2MemberGroupDto { MemberGroup = roleId, Member = member1.Id }); - scope.Database.Insert(new Member2MemberGroupDto { MemberGroup = roleId, Member = member2.Id }); + ScopeAccessor.AmbientScope.Database.Insert(new Member2MemberGroupDto { MemberGroup = roleId, Member = member1.Id }); + ScopeAccessor.AmbientScope.Database.Insert(new Member2MemberGroupDto { MemberGroup = roleId, Member = member2.Id }); scope.Complete(); } @@ -1281,7 +1281,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services List colResult; using (IScope scope = ScopeProvider.CreateScope()) { - colResult = scope.Database.Fetch(sql); + colResult = ScopeAccessor.AmbientScope.Database.Fetch(sql); scope.Complete(); } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/RedirectUrlServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/RedirectUrlServiceTests.cs index 1c10f31e18..a0abfd70ee 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/RedirectUrlServiceTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/RedirectUrlServiceTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using System.Linq; @@ -11,6 +11,7 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Cms.Tests.Integration.Testing; diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ThreadSafetyServiceTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ThreadSafetyServiceTest.cs index 9eded84598..7611bea687 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ThreadSafetyServiceTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ThreadSafetyServiceTest.cs @@ -54,7 +54,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services { using (IScope scope = ScopeProvider.CreateScope()) { - scope.Database.Execute("SET LOCK_TIMEOUT 60000"); + ScopeAccessor.AmbientScope.Database.Execute("SET LOCK_TIMEOUT 60000"); service.Save(content); scope.Complete(); } @@ -64,7 +64,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services { using (IScope scope = ScopeProvider.CreateScope()) { - scope.Database.Execute("SET LOCK_TIMEOUT 60000"); + ScopeAccessor.AmbientScope.Database.Execute("SET LOCK_TIMEOUT 60000"); service.Save(media); scope.Complete(); } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/DefaultCachePolicyTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/DefaultCachePolicyTests.cs index 2caa33aa3f..85e1c9a221 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/DefaultCachePolicyTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/DefaultCachePolicyTests.cs @@ -8,6 +8,7 @@ using NUnit.Framework; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Infrastructure.Scoping; namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Cache { @@ -19,7 +20,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Cache get { var accessor = new Mock(); - var scope = new Mock(); + var scope = new Mock(); scope.Setup(x => x.RepositoryCacheMode).Returns(RepositoryCacheMode.Default); accessor.Setup(x => x.AmbientScope).Returns(scope.Object); return accessor.Object; diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/FullDataSetCachePolicyTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/FullDataSetCachePolicyTests.cs index d4b91a196f..578e133a16 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/FullDataSetCachePolicyTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/FullDataSetCachePolicyTests.cs @@ -11,6 +11,7 @@ using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Collections; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Infrastructure.Scoping; namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Cache { @@ -22,7 +23,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Cache get { var accessor = new Mock(); - var scope = new Mock(); + var scope = new Mock(); scope.Setup(x => x.RepositoryCacheMode).Returns(RepositoryCacheMode.Default); accessor.Setup(x => x.AmbientScope).Returns(scope.Object); return accessor.Object; diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/SingleItemsOnlyCachePolicyTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/SingleItemsOnlyCachePolicyTests.cs index 4696dc708c..20c6d173c2 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/SingleItemsOnlyCachePolicyTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/SingleItemsOnlyCachePolicyTests.cs @@ -8,6 +8,7 @@ using NUnit.Framework; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Infrastructure.Scoping; namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Cache { @@ -19,7 +20,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Cache get { var accessor = new Mock(); - var scope = new Mock(); + var scope = new Mock(); scope.Setup(x => x.RepositoryCacheMode).Returns(RepositoryCacheMode.Default); accessor.Setup(x => x.AmbientScope).Returns(scope.Object); return accessor.Object; diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/MigrationPlanTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/MigrationPlanTests.cs index 08bcee255e..2e1ac83cd8 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/MigrationPlanTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/MigrationPlanTests.cs @@ -19,6 +19,7 @@ using Umbraco.Cms.Infrastructure.Migrations; using Umbraco.Cms.Infrastructure.Migrations.Upgrade; using Umbraco.Cms.Infrastructure.Persistence; using Umbraco.Cms.Infrastructure.Persistence.SqlSyntax; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Cms.Tests.Common.TestHelpers; using Umbraco.Cms.Tests.UnitTests.TestHelpers; using Umbraco.Extensions; @@ -34,7 +35,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Migrations NullLoggerFactory loggerFactory = NullLoggerFactory.Instance; var database = new TestDatabase(); - IScope scope = Mock.Of(x => x.Notifications == Mock.Of()); + IDatabaseScope scope = Mock.Of(x => x.Notifications == Mock.Of()); Mock.Get(scope) .Setup(x => x.Database) .Returns(database); @@ -58,7 +59,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Migrations } }); - var executor = new MigrationPlanExecutor(scopeProvider, loggerFactory, migrationBuilder); + var executor = new MigrationPlanExecutor(scopeProvider, scopeProvider, loggerFactory, migrationBuilder); MigrationPlan plan = new MigrationPlan("default") .From(string.Empty) diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/MigrationTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/MigrationTests.cs index 7dd2b8be2b..2f2e36221c 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/MigrationTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/MigrationTests.cs @@ -10,20 +10,22 @@ using Microsoft.Extensions.Logging; using Moq; using NUnit.Framework; using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Infrastructure.Migrations; using Umbraco.Cms.Infrastructure.Persistence; +using Umbraco.Cms.Infrastructure.Scoping; namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Migrations { [TestFixture] public class MigrationTests { - public class TestScopeProvider : IScopeProvider + public class TestScopeProvider : IScopeProvider, IScopeAccessor { - private readonly IScope _scope; + private readonly IDatabaseScope _scope; - public TestScopeProvider(IScope scope) => _scope = scope; + public TestScopeProvider(IDatabaseScope scope) => _scope = scope; public IScope CreateScope( IsolationLevel isolationLevel = IsolationLevel.Unspecified, @@ -44,6 +46,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Migrations public IScope DetachScope() => throw new NotImplementedException(); public IScopeContext Context { get; set; } + public IQuery CreateQuery() => SqlContext.Query(); public ISqlContext SqlContext { get; set; } @@ -54,6 +57,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Migrations } public IEnumerable ScopeInfos => throw new NotImplementedException(); #endif + public IDatabaseScope AmbientScope => _scope; } private class TestPlan : MigrationPlan diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/PostMigrationTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/PostMigrationTests.cs index a61de49ee5..09d0cb99d1 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/PostMigrationTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/PostMigrationTests.cs @@ -18,6 +18,7 @@ using Umbraco.Cms.Infrastructure.Migrations; using Umbraco.Cms.Infrastructure.Migrations.Upgrade; using Umbraco.Cms.Infrastructure.Persistence; using Umbraco.Cms.Infrastructure.Persistence.SqlSyntax; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Cms.Tests.Common.TestHelpers; namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Migrations @@ -26,8 +27,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Migrations public class PostMigrationTests { private static readonly ILoggerFactory s_loggerFactory = NullLoggerFactory.Instance; - private IMigrationPlanExecutor GetMigrationPlanExecutor(IScopeProvider scopeProvider, IMigrationBuilder builder) - => new MigrationPlanExecutor(scopeProvider, s_loggerFactory, builder); + private IMigrationPlanExecutor GetMigrationPlanExecutor(IScopeProvider scopeProvider, IScopeAccessor scopeAccessor,IMigrationBuilder builder) + => new MigrationPlanExecutor(scopeProvider, scopeAccessor, s_loggerFactory, builder); [Test] public void ExecutesPlanPostMigration() @@ -49,7 +50,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Migrations }); var database = new TestDatabase(); - IScope scope = Mock.Of(x => x.Notifications == Mock.Of()); + IDatabaseScope scope = Mock.Of(x => x.Notifications == Mock.Of()); Mock.Get(scope) .Setup(x => x.Database) .Returns(database); @@ -67,7 +68,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Migrations TestPostMigration.MigrateCount = 0; var upgrader = new Upgrader(plan); - IMigrationPlanExecutor executor = GetMigrationPlanExecutor(scopeProvider, builder); + IMigrationPlanExecutor executor = GetMigrationPlanExecutor(scopeProvider, scopeProvider, builder); upgrader.Execute( executor, scopeProvider, @@ -98,7 +99,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Migrations }); var database = new TestDatabase(); - IScope scope = Mock.Of(x => x.Notifications == Mock.Of()); + IDatabaseScope scope = Mock.Of(x => x.Notifications == Mock.Of()); Mock.Get(scope) .Setup(x => x.Database) .Returns(database); @@ -118,7 +119,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Migrations new MigrationContext(plan, database, s_loggerFactory.CreateLogger()); var upgrader = new Upgrader(plan); - IMigrationPlanExecutor executor = GetMigrationPlanExecutor(scopeProvider, builder); + IMigrationPlanExecutor executor = GetMigrationPlanExecutor(scopeProvider, scopeProvider, builder); upgrader.Execute( executor, scopeProvider, From 44261589ae8e5ad8243bbd4377d4be4d2a8bd895 Mon Sep 17 00:00:00 2001 From: Paul Johnson Date: Thu, 13 Jan 2022 23:22:19 +0000 Subject: [PATCH 066/141] Move repository interfaces to Core --- .../Repositories/IContentRepository.cs | 0 .../Repositories/IContentTypeRepository.cs | 0 .../IContentTypeRepositoryBase.cs | 0 .../Repositories/IDataTypeRepository.cs | 0 .../IDocumentBlueprintRepository.cs | 0 .../Repositories/IDocumentRepository.cs | 0 .../Repositories/IEntityRepository.cs | 25 -------------- .../Repositories/IMediaRepository.cs | 0 .../Repositories/IMediaTypeRepository.cs | 0 .../Repositories/IMemberRepository.cs | 0 .../Repositories/IMemberTypeRepository.cs | 0 .../Repositories/IPublicAccessRepository.cs | 0 .../UmbracoBuilder.Repositories.cs | 3 +- .../Repositories/IEntityRepositoryExtended.cs | 34 +++++++++++++++++++ .../Implement/EntityRepository.cs | 2 +- .../Implement/RelationRepository.cs | 4 +-- 16 files changed, 39 insertions(+), 29 deletions(-) rename src/{Umbraco.Infrastructure => Umbraco.Core}/Persistence/Repositories/IContentRepository.cs (100%) rename src/{Umbraco.Infrastructure => Umbraco.Core}/Persistence/Repositories/IContentTypeRepository.cs (100%) rename src/{Umbraco.Infrastructure => Umbraco.Core}/Persistence/Repositories/IContentTypeRepositoryBase.cs (100%) rename src/{Umbraco.Infrastructure => Umbraco.Core}/Persistence/Repositories/IDataTypeRepository.cs (100%) rename src/{Umbraco.Infrastructure => Umbraco.Core}/Persistence/Repositories/IDocumentBlueprintRepository.cs (100%) rename src/{Umbraco.Infrastructure => Umbraco.Core}/Persistence/Repositories/IDocumentRepository.cs (100%) rename src/{Umbraco.Infrastructure => Umbraco.Core}/Persistence/Repositories/IEntityRepository.cs (65%) rename src/{Umbraco.Infrastructure => Umbraco.Core}/Persistence/Repositories/IMediaRepository.cs (100%) rename src/{Umbraco.Infrastructure => Umbraco.Core}/Persistence/Repositories/IMediaTypeRepository.cs (100%) rename src/{Umbraco.Infrastructure => Umbraco.Core}/Persistence/Repositories/IMemberRepository.cs (100%) rename src/{Umbraco.Infrastructure => Umbraco.Core}/Persistence/Repositories/IMemberTypeRepository.cs (100%) rename src/{Umbraco.Infrastructure => Umbraco.Core}/Persistence/Repositories/IPublicAccessRepository.cs (100%) create mode 100644 src/Umbraco.Infrastructure/Persistence/Repositories/IEntityRepositoryExtended.cs diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/IContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IContentRepository.cs similarity index 100% rename from src/Umbraco.Infrastructure/Persistence/Repositories/IContentRepository.cs rename to src/Umbraco.Core/Persistence/Repositories/IContentRepository.cs diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/IContentTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IContentTypeRepository.cs similarity index 100% rename from src/Umbraco.Infrastructure/Persistence/Repositories/IContentTypeRepository.cs rename to src/Umbraco.Core/Persistence/Repositories/IContentTypeRepository.cs diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/IContentTypeRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/IContentTypeRepositoryBase.cs similarity index 100% rename from src/Umbraco.Infrastructure/Persistence/Repositories/IContentTypeRepositoryBase.cs rename to src/Umbraco.Core/Persistence/Repositories/IContentTypeRepositoryBase.cs diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/IDataTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IDataTypeRepository.cs similarity index 100% rename from src/Umbraco.Infrastructure/Persistence/Repositories/IDataTypeRepository.cs rename to src/Umbraco.Core/Persistence/Repositories/IDataTypeRepository.cs diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/IDocumentBlueprintRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IDocumentBlueprintRepository.cs similarity index 100% rename from src/Umbraco.Infrastructure/Persistence/Repositories/IDocumentBlueprintRepository.cs rename to src/Umbraco.Core/Persistence/Repositories/IDocumentBlueprintRepository.cs diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/IDocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IDocumentRepository.cs similarity index 100% rename from src/Umbraco.Infrastructure/Persistence/Repositories/IDocumentRepository.cs rename to src/Umbraco.Core/Persistence/Repositories/IDocumentRepository.cs diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/IEntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IEntityRepository.cs similarity index 65% rename from src/Umbraco.Infrastructure/Persistence/Repositories/IEntityRepository.cs rename to src/Umbraco.Core/Persistence/Repositories/IEntityRepository.cs index bc4f82b02b..800981540b 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/IEntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IEntityRepository.cs @@ -1,11 +1,9 @@ using System; using System.Collections.Generic; -using NPoco; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Infrastructure.Persistence; namespace Umbraco.Cms.Core.Persistence.Repositories { @@ -44,27 +42,6 @@ namespace Umbraco.Cms.Core.Persistence.Repositories bool Exists(int id); bool Exists(Guid key); - /// - /// Gets paged entities for a query and a subset of object types - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// A callback providing the ability to customize the generated SQL used to retrieve entities - /// - /// - /// A collection of mixed entity types which would be of type , , , - /// - /// - IEnumerable GetPagedResultsByQuery( - IQuery query, Guid[] objectTypes, long pageIndex, int pageSize, out long totalRecords, - IQuery filter, Ordering ordering, Action> sqlCustomization = null); - /// /// Gets paged entities for a query and a specific object type /// @@ -78,7 +55,5 @@ namespace Umbraco.Cms.Core.Persistence.Repositories /// IEnumerable GetPagedResultsByQuery(IQuery query, Guid objectType, long pageIndex, int pageSize, out long totalRecords, IQuery filter, Ordering ordering); - - } } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/IMediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IMediaRepository.cs similarity index 100% rename from src/Umbraco.Infrastructure/Persistence/Repositories/IMediaRepository.cs rename to src/Umbraco.Core/Persistence/Repositories/IMediaRepository.cs diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/IMediaTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IMediaTypeRepository.cs similarity index 100% rename from src/Umbraco.Infrastructure/Persistence/Repositories/IMediaTypeRepository.cs rename to src/Umbraco.Core/Persistence/Repositories/IMediaTypeRepository.cs diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/IMemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IMemberRepository.cs similarity index 100% rename from src/Umbraco.Infrastructure/Persistence/Repositories/IMemberRepository.cs rename to src/Umbraco.Core/Persistence/Repositories/IMemberRepository.cs diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/IMemberTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IMemberTypeRepository.cs similarity index 100% rename from src/Umbraco.Infrastructure/Persistence/Repositories/IMemberTypeRepository.cs rename to src/Umbraco.Core/Persistence/Repositories/IMemberTypeRepository.cs diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/IPublicAccessRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IPublicAccessRepository.cs similarity index 100% rename from src/Umbraco.Infrastructure/Persistence/Repositories/IPublicAccessRepository.cs rename to src/Umbraco.Core/Persistence/Repositories/IPublicAccessRepository.cs diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs index d3ebb28f9c..e157f929b3 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs @@ -1,5 +1,6 @@ using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Persistence.Repositories; +using Umbraco.Cms.Infrastructure.Persistence.Repositories; using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; using Umbraco.Extensions; @@ -28,7 +29,7 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection builder.Services.AddUnique(); builder.Services.AddUnique(); builder.Services.AddUnique(); - builder.Services.AddUnique(); + builder.Services.AddMultipleUnique(); builder.Services.AddUnique(); builder.Services.AddUnique(); builder.Services.AddUnique(); diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/IEntityRepositoryExtended.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/IEntityRepositoryExtended.cs new file mode 100644 index 0000000000..7e1b2ad0df --- /dev/null +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/IEntityRepositoryExtended.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using NPoco; +using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Persistence.Querying; +using Umbraco.Cms.Core.Persistence.Repositories; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Infrastructure.Persistence.Repositories +{ + public interface IEntityRepositoryExtended : IEntityRepository + { + /// + /// Gets paged entities for a query and a subset of object types + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// A callback providing the ability to customize the generated SQL used to retrieve entities + /// + /// + /// A collection of mixed entity types which would be of type , , , + /// + /// + IEnumerable GetPagedResultsByQuery( + IQuery query, Guid[] objectTypes, long pageIndex, int pageSize, out long totalRecords, + IQuery filter, Ordering ordering, Action> sqlCustomization = null); + } +} diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepository.cs index 82cc897e45..965c85b6e6 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepository.cs @@ -25,7 +25,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement /// Limited to objects that have a corresponding node (in umbracoNode table). /// Returns objects, i.e. lightweight representation of entities. /// - internal class EntityRepository : RepositoryBase, IEntityRepository + internal class EntityRepository : RepositoryBase, IEntityRepositoryExtended { public EntityRepository(IScopeAccessor scopeAccessor, AppCaches appCaches) : base(scopeAccessor, appCaches) diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationRepository.cs index 63d49f06af..718ea04f63 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationRepository.cs @@ -26,9 +26,9 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement internal class RelationRepository : EntityRepositoryBase, IRelationRepository { private readonly IRelationTypeRepository _relationTypeRepository; - private readonly IEntityRepository _entityRepository; + private readonly IEntityRepositoryExtended _entityRepository; - public RelationRepository(IScopeAccessor scopeAccessor, ILogger logger, IRelationTypeRepository relationTypeRepository, IEntityRepository entityRepository) + public RelationRepository(IScopeAccessor scopeAccessor, ILogger logger, IRelationTypeRepository relationTypeRepository, IEntityRepositoryExtended entityRepository) : base(scopeAccessor, AppCaches.NoCache, logger) { _relationTypeRepository = relationTypeRepository; From 39c3fecc450cd808f817fa3a18cb6091c773ab5e Mon Sep 17 00:00:00 2001 From: Paul Johnson Date: Thu, 13 Jan 2022 23:46:21 +0000 Subject: [PATCH 067/141] Prep for moving some services --- .../Persistence}/SqlExpressionExtensions.cs | 4 ++-- .../Querying => Umbraco.Core/Persistence}/TextColumnType.cs | 2 +- .../Persistence/Repositories/Implement/MemberRepository.cs | 1 + .../Persistence/SqlSyntax/ISqlSyntaxProvider.cs | 1 + .../Persistence/SqlSyntax/MicrosoftSqlSyntaxProviderBase.cs | 3 ++- .../Persistence/SqlSyntax/SqlSyntaxProviderBase.cs | 1 + .../Persistence/UmbracoDatabaseExtensions.cs | 3 ++- .../Services/Implement/ContentService.cs | 1 + .../Services/Implement/EntityService.cs | 1 + src/Umbraco.Infrastructure/Services/Implement/MediaService.cs | 1 + .../Services/Implement/MemberService.cs | 1 + src/Umbraco.Infrastructure/Services/Implement/UserService.cs | 1 + .../Persistence/NPocoTests/NPocoSqlExtensionsTests.cs | 1 + .../Persistence/NPocoTests/NPocoSqlTests.cs | 1 + 14 files changed, 17 insertions(+), 5 deletions(-) rename src/{Umbraco.Infrastructure/Persistence/Querying => Umbraco.Core/Persistence}/SqlExpressionExtensions.cs (95%) rename src/{Umbraco.Infrastructure/Persistence/Querying => Umbraco.Core/Persistence}/TextColumnType.cs (56%) diff --git a/src/Umbraco.Infrastructure/Persistence/Querying/SqlExpressionExtensions.cs b/src/Umbraco.Core/Persistence/SqlExpressionExtensions.cs similarity index 95% rename from src/Umbraco.Infrastructure/Persistence/Querying/SqlExpressionExtensions.cs rename to src/Umbraco.Core/Persistence/SqlExpressionExtensions.cs index 99c4c3fd1a..321e40cb88 100644 --- a/src/Umbraco.Infrastructure/Persistence/Querying/SqlExpressionExtensions.cs +++ b/src/Umbraco.Core/Persistence/SqlExpressionExtensions.cs @@ -3,12 +3,12 @@ using System.Linq; using System.Text.RegularExpressions; using Umbraco.Extensions; -namespace Umbraco.Cms.Infrastructure.Persistence.Querying +namespace Umbraco.Cms.Core.Persistence { /// /// String extension methods used specifically to translate into SQL /// - internal static class SqlExpressionExtensions + public static class SqlExpressionExtensions { /// /// Indicates whether two nullable values are equal, substituting a fallback value for nulls. diff --git a/src/Umbraco.Infrastructure/Persistence/Querying/TextColumnType.cs b/src/Umbraco.Core/Persistence/TextColumnType.cs similarity index 56% rename from src/Umbraco.Infrastructure/Persistence/Querying/TextColumnType.cs rename to src/Umbraco.Core/Persistence/TextColumnType.cs index befc1be79f..dc0b8d56bd 100644 --- a/src/Umbraco.Infrastructure/Persistence/Querying/TextColumnType.cs +++ b/src/Umbraco.Core/Persistence/TextColumnType.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Infrastructure.Persistence.Querying +namespace Umbraco.Cms.Core.Persistence { public enum TextColumnType { diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs index db45c40007..4a3ed0aba6 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs @@ -11,6 +11,7 @@ using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Persistence; using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.PropertyEditors; diff --git a/src/Umbraco.Infrastructure/Persistence/SqlSyntax/ISqlSyntaxProvider.cs b/src/Umbraco.Infrastructure/Persistence/SqlSyntax/ISqlSyntaxProvider.cs index 75d348df1a..b9890d85d6 100644 --- a/src/Umbraco.Infrastructure/Persistence/SqlSyntax/ISqlSyntaxProvider.cs +++ b/src/Umbraco.Infrastructure/Persistence/SqlSyntax/ISqlSyntaxProvider.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Data; using System.Text.RegularExpressions; using NPoco; +using Umbraco.Cms.Core.Persistence; using Umbraco.Cms.Infrastructure.Persistence.DatabaseAnnotations; using Umbraco.Cms.Infrastructure.Persistence.DatabaseModelDefinitions; using Umbraco.Cms.Infrastructure.Persistence.Querying; diff --git a/src/Umbraco.Infrastructure/Persistence/SqlSyntax/MicrosoftSqlSyntaxProviderBase.cs b/src/Umbraco.Infrastructure/Persistence/SqlSyntax/MicrosoftSqlSyntaxProviderBase.cs index 0093ee14ce..f045f379e4 100644 --- a/src/Umbraco.Infrastructure/Persistence/SqlSyntax/MicrosoftSqlSyntaxProviderBase.cs +++ b/src/Umbraco.Infrastructure/Persistence/SqlSyntax/MicrosoftSqlSyntaxProviderBase.cs @@ -1,7 +1,8 @@ -using System; +using System; using System.Data; using System.Linq; using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Persistence; using Umbraco.Cms.Infrastructure.Persistence.Querying; namespace Umbraco.Cms.Infrastructure.Persistence.SqlSyntax diff --git a/src/Umbraco.Infrastructure/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs b/src/Umbraco.Infrastructure/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs index db6adeca77..5dbf97021d 100644 --- a/src/Umbraco.Infrastructure/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs +++ b/src/Umbraco.Infrastructure/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs @@ -7,6 +7,7 @@ using System.Text; using System.Text.RegularExpressions; using NPoco; using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Persistence; using Umbraco.Cms.Infrastructure.Persistence.DatabaseAnnotations; using Umbraco.Cms.Infrastructure.Persistence.DatabaseModelDefinitions; using Umbraco.Cms.Infrastructure.Persistence.Querying; diff --git a/src/Umbraco.Infrastructure/Persistence/UmbracoDatabaseExtensions.cs b/src/Umbraco.Infrastructure/Persistence/UmbracoDatabaseExtensions.cs index 86ffc1b128..ae2a17dc7c 100644 --- a/src/Umbraco.Infrastructure/Persistence/UmbracoDatabaseExtensions.cs +++ b/src/Umbraco.Infrastructure/Persistence/UmbracoDatabaseExtensions.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Umbraco.Cms.Core.Persistence; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Infrastructure.Persistence.SqlSyntax; using Umbraco.Extensions; @@ -32,7 +33,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence var whereParam = sqlSyntax.GetStringColumnWildcardComparison( sqlSyntax.GetQuotedColumnName("key"), 0, - Querying.TextColumnType.NVarchar); + TextColumnType.NVarchar); var sql = database.SqlContext.Sql() .Select() diff --git a/src/Umbraco.Infrastructure/Services/Implement/ContentService.cs b/src/Umbraco.Infrastructure/Services/Implement/ContentService.cs index 020c18b221..c48c220d77 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/ContentService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/ContentService.cs @@ -8,6 +8,7 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Persistence; using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; diff --git a/src/Umbraco.Infrastructure/Services/Implement/EntityService.cs b/src/Umbraco.Infrastructure/Services/Implement/EntityService.cs index 34eb122c73..9efd991fb4 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/EntityService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/EntityService.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Persistence; using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; diff --git a/src/Umbraco.Infrastructure/Services/Implement/MediaService.cs b/src/Umbraco.Infrastructure/Services/Implement/MediaService.cs index 0aa49cde44..dc44ddca8c 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/MediaService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/MediaService.cs @@ -8,6 +8,7 @@ using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Persistence; using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; diff --git a/src/Umbraco.Infrastructure/Services/Implement/MemberService.cs b/src/Umbraco.Infrastructure/Services/Implement/MemberService.cs index 99f21b4fc3..4d776d8379 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/MemberService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/MemberService.cs @@ -6,6 +6,7 @@ using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Persistence; using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; diff --git a/src/Umbraco.Infrastructure/Services/Implement/UserService.cs b/src/Umbraco.Infrastructure/Services/Implement/UserService.cs index c64a91a598..04f5ff8103 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/UserService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/UserService.cs @@ -10,6 +10,7 @@ using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Persistence; using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/NPocoTests/NPocoSqlExtensionsTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/NPocoTests/NPocoSqlExtensionsTests.cs index 92258c46e7..222b2c2b36 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/NPocoTests/NPocoSqlExtensionsTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/NPocoTests/NPocoSqlExtensionsTests.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using NPoco; using NUnit.Framework; +using Umbraco.Cms.Core.Persistence; using Umbraco.Cms.Infrastructure.Persistence; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Infrastructure.Persistence.Querying; diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/NPocoTests/NPocoSqlTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/NPocoTests/NPocoSqlTests.cs index 103f9207d0..e826af74fd 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/NPocoTests/NPocoSqlTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/NPocoTests/NPocoSqlTests.cs @@ -5,6 +5,7 @@ using System; using System.Diagnostics; using NPoco; using NUnit.Framework; +using Umbraco.Cms.Core.Persistence; using Umbraco.Cms.Infrastructure.Persistence; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Infrastructure.Persistence.Querying; From 2a4c974568bbd08dda8639e253db10df84302f10 Mon Sep 17 00:00:00 2001 From: Paul Johnson Date: Fri, 14 Jan 2022 10:57:31 +0000 Subject: [PATCH 068/141] Move services infra -> core --- .../DependencyInjection/UmbracoBuilder.cs | 38 ++++++++++++++++++- .../IEntityContainerRepository.cs | 10 ++++- .../Repositories/IUserRepository.cs | 2 + .../Services}/ConsentService.cs | 2 +- .../Services}/ContentService.cs | 3 +- .../ContentTypeBaseServiceProvider.cs | 2 +- .../Services}/ContentTypeService.cs | 3 +- .../Services}/ContentTypeServiceBase.cs | 2 +- ...peServiceBaseOfTRepositoryTItemTService.cs | 7 ++-- .../Services}/ContentVersionService.cs | 2 +- .../DefaultContentVersionCleanupPolicy.cs | 3 +- .../Services}/DomainService.cs | 2 +- .../Services}/EntityService.cs | 5 +-- .../Services}/EntityXmlSerializer.cs | 2 +- .../Services}/ExternalLoginService.cs | 2 +- .../Services}/FileService.cs | 2 +- .../Services}/KeyValueService.cs | 2 +- .../Services}/LocalizationService.cs | 2 +- .../Services}/LocalizedTextService.cs | 2 +- .../LocalizedTextServiceFileSources.cs | 2 +- ...lizedTextServiceSupplementaryFileSource.cs | 2 +- .../Services}/MacroService.cs | 2 +- .../Services}/MediaService.cs | 3 +- .../Services}/MediaTypeService.cs | 2 +- .../Services}/MemberGroupService.cs | 2 +- .../Services}/MemberService.cs | 3 +- .../Services}/MemberTypeService.cs | 2 +- .../Services}/NotificationService.cs | 2 +- .../Services}/PropertyValidationService.cs | 2 +- .../Services}/PublicAccessService.cs | 2 +- .../Services}/RedirectUrlService.cs | 2 +- .../Services}/RelationService.cs | 2 +- .../Services}/RepositoryService.cs | 2 +- .../Services}/TagService.cs | 2 +- .../Services}/UserService.cs | 6 +-- .../UmbracoBuilder.Services.cs | 35 ----------------- .../Repositories/Implement/UserRepository.cs | 2 +- .../Services/Implement/DataTypeService.cs | 5 +-- .../DefaultContentVersionCleanupPolicyTest.cs | 2 +- .../Services/AmbiguousEventTests.cs | 1 + .../Services/LocalizedTextServiceTests.cs | 1 + 41 files changed, 88 insertions(+), 89 deletions(-) rename src/{Umbraco.Infrastructure/Services/Implement => Umbraco.Core/Services}/ConsentService.cs (98%) rename src/{Umbraco.Infrastructure/Services/Implement => Umbraco.Core/Services}/ContentService.cs (99%) rename src/{Umbraco.Infrastructure/Services/Implement => Umbraco.Core/Services}/ContentTypeBaseServiceProvider.cs (97%) rename src/{Umbraco.Infrastructure/Services/Implement => Umbraco.Core/Services}/ContentTypeService.cs (98%) rename src/{Umbraco.Infrastructure/Services/Implement => Umbraco.Core/Services}/ContentTypeServiceBase.cs (89%) rename src/{Umbraco.Infrastructure/Services/Implement => Umbraco.Core/Services}/ContentTypeServiceBaseOfTRepositoryTItemTService.cs (99%) rename src/{Umbraco.Infrastructure/Services/Implement => Umbraco.Core/Services}/ContentVersionService.cs (99%) rename src/{Umbraco.Infrastructure/Services/Implement => Umbraco.Core/Services}/DefaultContentVersionCleanupPolicy.cs (97%) rename src/{Umbraco.Infrastructure/Services/Implement => Umbraco.Core/Services}/DomainService.cs (98%) rename src/{Umbraco.Infrastructure/Services/Implement => Umbraco.Core/Services}/EntityService.cs (99%) rename src/{Umbraco.Infrastructure/Services/Implement => Umbraco.Core/Services}/EntityXmlSerializer.cs (99%) rename src/{Umbraco.Infrastructure/Services/Implement => Umbraco.Core/Services}/ExternalLoginService.cs (98%) rename src/{Umbraco.Infrastructure/Services/Implement => Umbraco.Core/Services}/FileService.cs (99%) rename src/{Umbraco.Infrastructure/Services/Implement => Umbraco.Core/Services}/KeyValueService.cs (98%) rename src/{Umbraco.Infrastructure/Services/Implement => Umbraco.Core/Services}/LocalizationService.cs (99%) rename src/{Umbraco.Infrastructure/Services/Implement => Umbraco.Core/Services}/LocalizedTextService.cs (99%) rename src/{Umbraco.Infrastructure/Services/Implement => Umbraco.Core/Services}/LocalizedTextServiceFileSources.cs (99%) rename src/{Umbraco.Infrastructure/Services/Implement => Umbraco.Core/Services}/LocalizedTextServiceSupplementaryFileSource.cs (91%) rename src/{Umbraco.Infrastructure/Services/Implement => Umbraco.Core/Services}/MacroService.cs (99%) rename src/{Umbraco.Infrastructure/Services/Implement => Umbraco.Core/Services}/MediaService.cs (99%) rename src/{Umbraco.Infrastructure/Services/Implement => Umbraco.Core/Services}/MediaTypeService.cs (98%) rename src/{Umbraco.Infrastructure/Services/Implement => Umbraco.Core/Services}/MemberGroupService.cs (98%) rename src/{Umbraco.Infrastructure/Services/Implement => Umbraco.Core/Services}/MemberService.cs (99%) rename src/{Umbraco.Infrastructure/Services/Implement => Umbraco.Core/Services}/MemberTypeService.cs (99%) rename src/{Umbraco.Infrastructure/Services/Implement => Umbraco.Core/Services}/NotificationService.cs (99%) rename src/{Umbraco.Infrastructure/Services/Implement => Umbraco.Core/Services}/PropertyValidationService.cs (99%) rename src/{Umbraco.Infrastructure/Services/Implement => Umbraco.Core/Services}/PublicAccessService.cs (99%) rename src/{Umbraco.Infrastructure/Services/Implement => Umbraco.Core/Services}/RedirectUrlService.cs (98%) rename src/{Umbraco.Infrastructure/Services/Implement => Umbraco.Core/Services}/RelationService.cs (99%) rename src/{Umbraco.Infrastructure/Services/Implement => Umbraco.Core/Services}/RepositoryService.cs (95%) rename src/{Umbraco.Infrastructure/Services/Implement => Umbraco.Core/Services}/TagService.cs (99%) rename src/{Umbraco.Infrastructure/Services/Implement => Umbraco.Core/Services}/UserService.cs (99%) diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs index eacd615830..30494b2761 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs @@ -28,6 +28,7 @@ using Umbraco.Cms.Core.Mail; using Umbraco.Cms.Core.Manifest; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Packaging; using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.PublishedCache; using Umbraco.Cms.Core.PublishedCache.Internal; @@ -240,7 +241,7 @@ namespace Umbraco.Cms.Core.DependencyInjection GlobalSettings globalSettings = f.GetRequiredService>().Value; var singleServer = globalSettings.DisableElectionForSingleServer; return singleServer - ? (IServerRoleAccessor)new SingleServerRoleAccessor() + ? new SingleServerRoleAccessor() : new ElectedServerRoleAccessor(f.GetRequiredService()); }); @@ -263,6 +264,41 @@ namespace Umbraco.Cms.Core.DependencyInjection // Register telemetry service used to gather data about installed packages Services.AddUnique(); + + Services.AddUnique(); + Services.AddUnique(); + Services.AddUnique(); + Services.AddUnique(); + Services.AddUnique(); + Services.AddUnique(); + Services.AddUnique(); + Services.AddUnique(); + Services.AddUnique(); + Services.AddUnique(); + Services.AddUnique(); + Services.AddUnique(); + Services.AddUnique(); + Services.AddUnique(); + Services.AddUnique(); + Services.AddUnique(); + Services.AddUnique(); + Services.AddUnique(); + Services.AddUnique(); + Services.AddUnique(); + Services.AddUnique(); + Services.AddUnique(); + Services.AddUnique(); + Services.AddUnique(); + Services.AddUnique(); + + Services.AddUnique(factory => new LocalizedTextService( + factory.GetRequiredService>(), + factory.GetRequiredService>())); + + Services.AddUnique(); + + Services.AddSingleton(); + Services.AddSingleton(); } } } diff --git a/src/Umbraco.Core/Persistence/Repositories/IEntityContainerRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IEntityContainerRepository.cs index 9b76a8f965..36a07c2fe9 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IEntityContainerRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IEntityContainerRepository.cs @@ -1,7 +1,13 @@ -using Umbraco.Cms.Core.Models; +using System; +using System.Collections.Generic; +using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Core.Persistence.Repositories { public interface IEntityContainerRepository : IReadRepository, IWriteRepository - { } + { + EntityContainer Get(Guid id); + + IEnumerable Get(string name, int level); + } } diff --git a/src/Umbraco.Core/Persistence/Repositories/IUserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IUserRepository.cs index 799cb01963..51886ab86a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IUserRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IUserRepository.cs @@ -106,5 +106,7 @@ namespace Umbraco.Cms.Core.Persistence.Repositories int ClearLoginSessions(int userId); int ClearLoginSessions(TimeSpan timespan); void ClearLoginSession(Guid sessionId); + + IEnumerable GetNextUsers(int id, int count); } } diff --git a/src/Umbraco.Infrastructure/Services/Implement/ConsentService.cs b/src/Umbraco.Core/Services/ConsentService.cs similarity index 98% rename from src/Umbraco.Infrastructure/Services/Implement/ConsentService.cs rename to src/Umbraco.Core/Services/ConsentService.cs index b00a2579fd..0d7dc77660 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/ConsentService.cs +++ b/src/Umbraco.Core/Services/ConsentService.cs @@ -6,7 +6,7 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; -namespace Umbraco.Cms.Core.Services.Implement +namespace Umbraco.Cms.Core.Services { /// /// Implements . diff --git a/src/Umbraco.Infrastructure/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs similarity index 99% rename from src/Umbraco.Infrastructure/Services/Implement/ContentService.cs rename to src/Umbraco.Core/Services/ContentService.cs index c48c220d77..5a73ce4c0c 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -14,10 +14,9 @@ using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services.Changes; using Umbraco.Cms.Core.Strings; -using Umbraco.Cms.Infrastructure.Persistence.Querying; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Services.Implement +namespace Umbraco.Cms.Core.Services { /// /// Implements the content service. diff --git a/src/Umbraco.Infrastructure/Services/Implement/ContentTypeBaseServiceProvider.cs b/src/Umbraco.Core/Services/ContentTypeBaseServiceProvider.cs similarity index 97% rename from src/Umbraco.Infrastructure/Services/Implement/ContentTypeBaseServiceProvider.cs rename to src/Umbraco.Core/Services/ContentTypeBaseServiceProvider.cs index 619070c297..769f52edf5 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/ContentTypeBaseServiceProvider.cs +++ b/src/Umbraco.Core/Services/ContentTypeBaseServiceProvider.cs @@ -1,7 +1,7 @@ using System; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Services.Implement +namespace Umbraco.Cms.Core.Services { public class ContentTypeBaseServiceProvider : IContentTypeBaseServiceProvider { diff --git a/src/Umbraco.Infrastructure/Services/Implement/ContentTypeService.cs b/src/Umbraco.Core/Services/ContentTypeService.cs similarity index 98% rename from src/Umbraco.Infrastructure/Services/Implement/ContentTypeService.cs rename to src/Umbraco.Core/Services/ContentTypeService.cs index 3d8dcf33bf..11dafd1464 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/ContentTypeService.cs +++ b/src/Umbraco.Core/Services/ContentTypeService.cs @@ -8,9 +8,8 @@ using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services.Changes; -using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Services.Implement +namespace Umbraco.Cms.Core.Services { /// /// Represents the ContentType Service, which is an easy access to operations involving diff --git a/src/Umbraco.Infrastructure/Services/Implement/ContentTypeServiceBase.cs b/src/Umbraco.Core/Services/ContentTypeServiceBase.cs similarity index 89% rename from src/Umbraco.Infrastructure/Services/Implement/ContentTypeServiceBase.cs rename to src/Umbraco.Core/Services/ContentTypeServiceBase.cs index d850e0c21a..2aa8f0268f 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/ContentTypeServiceBase.cs +++ b/src/Umbraco.Core/Services/ContentTypeServiceBase.cs @@ -2,7 +2,7 @@ using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Scoping; -namespace Umbraco.Cms.Core.Services.Implement +namespace Umbraco.Cms.Core.Services { public abstract class ContentTypeServiceBase : RepositoryService { diff --git a/src/Umbraco.Infrastructure/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs b/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs similarity index 99% rename from src/Umbraco.Infrastructure/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs rename to src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs index f72738b221..d12ed1bddc 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs +++ b/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs @@ -11,10 +11,9 @@ using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services.Changes; -using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Services.Implement +namespace Umbraco.Cms.Core.Services { public abstract class ContentTypeServiceBase : ContentTypeServiceBase, IContentTypeBaseService where TRepository : IContentTypeRepositoryBase @@ -934,7 +933,7 @@ namespace Umbraco.Cms.Core.Services.Implement { scope.ReadLock(ReadLockIds); // also for containers - return ((EntityContainerRepository) _containerRepository).Get(containerId); + return _containerRepository.Get(containerId); } } @@ -964,7 +963,7 @@ namespace Umbraco.Cms.Core.Services.Implement { scope.ReadLock(ReadLockIds); // also for containers - return ((EntityContainerRepository) _containerRepository).Get(name, level); + return _containerRepository.Get(name, level); } } diff --git a/src/Umbraco.Infrastructure/Services/Implement/ContentVersionService.cs b/src/Umbraco.Core/Services/ContentVersionService.cs similarity index 99% rename from src/Umbraco.Infrastructure/Services/Implement/ContentVersionService.cs rename to src/Umbraco.Core/Services/ContentVersionService.cs index e409f3af47..84f66469e1 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/ContentVersionService.cs +++ b/src/Umbraco.Core/Services/ContentVersionService.cs @@ -10,7 +10,7 @@ using Umbraco.Cms.Core.Scoping; using Umbraco.Extensions; // ReSharper disable once CheckNamespace -namespace Umbraco.Cms.Core.Services.Implement +namespace Umbraco.Cms.Core.Services { internal class ContentVersionService : IContentVersionService { diff --git a/src/Umbraco.Infrastructure/Services/Implement/DefaultContentVersionCleanupPolicy.cs b/src/Umbraco.Core/Services/DefaultContentVersionCleanupPolicy.cs similarity index 97% rename from src/Umbraco.Infrastructure/Services/Implement/DefaultContentVersionCleanupPolicy.cs rename to src/Umbraco.Core/Services/DefaultContentVersionCleanupPolicy.cs index 30193396ee..ee4bf6c952 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/DefaultContentVersionCleanupPolicy.cs +++ b/src/Umbraco.Core/Services/DefaultContentVersionCleanupPolicy.cs @@ -6,10 +6,9 @@ using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; -using Umbraco.Cms.Core.Services; using ContentVersionCleanupPolicySettings = Umbraco.Cms.Core.Models.ContentVersionCleanupPolicySettings; -namespace Umbraco.Cms.Infrastructure.Services.Implement +namespace Umbraco.Cms.Core.Services { public class DefaultContentVersionCleanupPolicy : IContentVersionCleanupPolicy { diff --git a/src/Umbraco.Infrastructure/Services/Implement/DomainService.cs b/src/Umbraco.Core/Services/DomainService.cs similarity index 98% rename from src/Umbraco.Infrastructure/Services/Implement/DomainService.cs rename to src/Umbraco.Core/Services/DomainService.cs index fc5d26e4df..f0ad6d63ee 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/DomainService.cs +++ b/src/Umbraco.Core/Services/DomainService.cs @@ -6,7 +6,7 @@ using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; -namespace Umbraco.Cms.Core.Services.Implement +namespace Umbraco.Cms.Core.Services { public class DomainService : RepositoryService, IDomainService { diff --git a/src/Umbraco.Infrastructure/Services/Implement/EntityService.cs b/src/Umbraco.Core/Services/EntityService.cs similarity index 99% rename from src/Umbraco.Infrastructure/Services/Implement/EntityService.cs rename to src/Umbraco.Core/Services/EntityService.cs index 9efd991fb4..9169c6c96b 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/EntityService.cs +++ b/src/Umbraco.Core/Services/EntityService.cs @@ -10,11 +10,8 @@ using Umbraco.Cms.Core.Persistence; using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; -using Umbraco.Cms.Infrastructure.Persistence.Dtos; -using Umbraco.Cms.Infrastructure.Persistence.Querying; -using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Services.Implement +namespace Umbraco.Cms.Core.Services { public class EntityService : RepositoryService, IEntityService { diff --git a/src/Umbraco.Infrastructure/Services/Implement/EntityXmlSerializer.cs b/src/Umbraco.Core/Services/EntityXmlSerializer.cs similarity index 99% rename from src/Umbraco.Infrastructure/Services/Implement/EntityXmlSerializer.cs rename to src/Umbraco.Core/Services/EntityXmlSerializer.cs index 26060bf988..be4efe2082 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/EntityXmlSerializer.cs +++ b/src/Umbraco.Core/Services/EntityXmlSerializer.cs @@ -11,7 +11,7 @@ using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Strings; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Services.Implement +namespace Umbraco.Cms.Core.Services { /// /// Serializes entities to XML diff --git a/src/Umbraco.Infrastructure/Services/Implement/ExternalLoginService.cs b/src/Umbraco.Core/Services/ExternalLoginService.cs similarity index 98% rename from src/Umbraco.Infrastructure/Services/Implement/ExternalLoginService.cs rename to src/Umbraco.Core/Services/ExternalLoginService.cs index 079971be24..0117a5cf8f 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/ExternalLoginService.cs +++ b/src/Umbraco.Core/Services/ExternalLoginService.cs @@ -7,7 +7,7 @@ using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Security; -namespace Umbraco.Cms.Core.Services.Implement +namespace Umbraco.Cms.Core.Services { public class ExternalLoginService : RepositoryService, IExternalLoginService { diff --git a/src/Umbraco.Infrastructure/Services/Implement/FileService.cs b/src/Umbraco.Core/Services/FileService.cs similarity index 99% rename from src/Umbraco.Infrastructure/Services/Implement/FileService.cs rename to src/Umbraco.Core/Services/FileService.cs index 017312e306..7fc97e7c67 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/FileService.cs +++ b/src/Umbraco.Core/Services/FileService.cs @@ -16,7 +16,7 @@ using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Strings; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Services.Implement +namespace Umbraco.Cms.Core.Services { /// /// Represents the File Service, which is an easy access to operations involving objects like Scripts, Stylesheets and Templates diff --git a/src/Umbraco.Infrastructure/Services/Implement/KeyValueService.cs b/src/Umbraco.Core/Services/KeyValueService.cs similarity index 98% rename from src/Umbraco.Infrastructure/Services/Implement/KeyValueService.cs rename to src/Umbraco.Core/Services/KeyValueService.cs index 7fda83a427..e54a803659 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/KeyValueService.cs +++ b/src/Umbraco.Core/Services/KeyValueService.cs @@ -4,7 +4,7 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; -namespace Umbraco.Cms.Core.Services.Implement +namespace Umbraco.Cms.Core.Services { internal class KeyValueService : IKeyValueService { diff --git a/src/Umbraco.Infrastructure/Services/Implement/LocalizationService.cs b/src/Umbraco.Core/Services/LocalizationService.cs similarity index 99% rename from src/Umbraco.Infrastructure/Services/Implement/LocalizationService.cs rename to src/Umbraco.Core/Services/LocalizationService.cs index b024b8935e..347872fe0b 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/LocalizationService.cs +++ b/src/Umbraco.Core/Services/LocalizationService.cs @@ -9,7 +9,7 @@ using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Services.Implement +namespace Umbraco.Cms.Core.Services { /// /// Represents the Localization Service, which is an easy access to operations involving and diff --git a/src/Umbraco.Infrastructure/Services/Implement/LocalizedTextService.cs b/src/Umbraco.Core/Services/LocalizedTextService.cs similarity index 99% rename from src/Umbraco.Infrastructure/Services/Implement/LocalizedTextService.cs rename to src/Umbraco.Core/Services/LocalizedTextService.cs index d09abb02b0..00f7812b49 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/LocalizedTextService.cs +++ b/src/Umbraco.Core/Services/LocalizedTextService.cs @@ -6,7 +6,7 @@ using System.Xml.Linq; using System.Xml.XPath; using Microsoft.Extensions.Logging; -namespace Umbraco.Cms.Core.Services.Implement +namespace Umbraco.Cms.Core.Services { /// public class LocalizedTextService : ILocalizedTextService diff --git a/src/Umbraco.Infrastructure/Services/Implement/LocalizedTextServiceFileSources.cs b/src/Umbraco.Core/Services/LocalizedTextServiceFileSources.cs similarity index 99% rename from src/Umbraco.Infrastructure/Services/Implement/LocalizedTextServiceFileSources.cs rename to src/Umbraco.Core/Services/LocalizedTextServiceFileSources.cs index 5dac893cf4..a24d47fe4b 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/LocalizedTextServiceFileSources.cs +++ b/src/Umbraco.Core/Services/LocalizedTextServiceFileSources.cs @@ -9,7 +9,7 @@ using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Cache; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Services.Implement +namespace Umbraco.Cms.Core.Services { /// /// Exposes the XDocument sources from files for the default localization text service and ensure caching is taken care of diff --git a/src/Umbraco.Infrastructure/Services/Implement/LocalizedTextServiceSupplementaryFileSource.cs b/src/Umbraco.Core/Services/LocalizedTextServiceSupplementaryFileSource.cs similarity index 91% rename from src/Umbraco.Infrastructure/Services/Implement/LocalizedTextServiceSupplementaryFileSource.cs rename to src/Umbraco.Core/Services/LocalizedTextServiceSupplementaryFileSource.cs index 3e847167ce..7fe5e0e48a 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/LocalizedTextServiceSupplementaryFileSource.cs +++ b/src/Umbraco.Core/Services/LocalizedTextServiceSupplementaryFileSource.cs @@ -1,7 +1,7 @@ using System; using System.IO; -namespace Umbraco.Cms.Core.Services.Implement +namespace Umbraco.Cms.Core.Services { public class LocalizedTextServiceSupplementaryFileSource { diff --git a/src/Umbraco.Infrastructure/Services/Implement/MacroService.cs b/src/Umbraco.Core/Services/MacroService.cs similarity index 99% rename from src/Umbraco.Infrastructure/Services/Implement/MacroService.cs rename to src/Umbraco.Core/Services/MacroService.cs index a79d9fddce..dd3c2d9af1 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/MacroService.cs +++ b/src/Umbraco.Core/Services/MacroService.cs @@ -8,7 +8,7 @@ using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; -namespace Umbraco.Cms.Core.Services.Implement +namespace Umbraco.Cms.Core.Services { /// /// Represents the Macro Service, which is an easy access to operations involving diff --git a/src/Umbraco.Infrastructure/Services/Implement/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs similarity index 99% rename from src/Umbraco.Infrastructure/Services/Implement/MediaService.cs rename to src/Umbraco.Core/Services/MediaService.cs index dc44ddca8c..def72653bd 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/MediaService.cs +++ b/src/Umbraco.Core/Services/MediaService.cs @@ -14,10 +14,9 @@ using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services.Changes; using Umbraco.Cms.Core.Strings; -using Umbraco.Cms.Infrastructure.Persistence.Querying; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Services.Implement +namespace Umbraco.Cms.Core.Services { /// /// Represents the Media Service, which is an easy access to operations involving diff --git a/src/Umbraco.Infrastructure/Services/Implement/MediaTypeService.cs b/src/Umbraco.Core/Services/MediaTypeService.cs similarity index 98% rename from src/Umbraco.Infrastructure/Services/Implement/MediaTypeService.cs rename to src/Umbraco.Core/Services/MediaTypeService.cs index 205e5b176e..49021333cc 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/MediaTypeService.cs +++ b/src/Umbraco.Core/Services/MediaTypeService.cs @@ -8,7 +8,7 @@ using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services.Changes; -namespace Umbraco.Cms.Core.Services.Implement +namespace Umbraco.Cms.Core.Services { public class MediaTypeService : ContentTypeServiceBase, IMediaTypeService { diff --git a/src/Umbraco.Infrastructure/Services/Implement/MemberGroupService.cs b/src/Umbraco.Core/Services/MemberGroupService.cs similarity index 98% rename from src/Umbraco.Infrastructure/Services/Implement/MemberGroupService.cs rename to src/Umbraco.Core/Services/MemberGroupService.cs index 9d68415ad5..253f2b433a 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/MemberGroupService.cs +++ b/src/Umbraco.Core/Services/MemberGroupService.cs @@ -8,7 +8,7 @@ using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; -namespace Umbraco.Cms.Core.Services.Implement +namespace Umbraco.Cms.Core.Services { internal class MemberGroupService : RepositoryService, IMemberGroupService { diff --git a/src/Umbraco.Infrastructure/Services/Implement/MemberService.cs b/src/Umbraco.Core/Services/MemberService.cs similarity index 99% rename from src/Umbraco.Infrastructure/Services/Implement/MemberService.cs rename to src/Umbraco.Core/Services/MemberService.cs index 4d776d8379..0b8d981143 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/MemberService.cs +++ b/src/Umbraco.Core/Services/MemberService.cs @@ -10,10 +10,9 @@ using Umbraco.Cms.Core.Persistence; using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; -using Umbraco.Cms.Infrastructure.Persistence.Querying; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Services.Implement +namespace Umbraco.Cms.Core.Services { /// /// Represents the MemberService. diff --git a/src/Umbraco.Infrastructure/Services/Implement/MemberTypeService.cs b/src/Umbraco.Core/Services/MemberTypeService.cs similarity index 99% rename from src/Umbraco.Infrastructure/Services/Implement/MemberTypeService.cs rename to src/Umbraco.Core/Services/MemberTypeService.cs index ad0498d0b2..be3831c675 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/MemberTypeService.cs +++ b/src/Umbraco.Core/Services/MemberTypeService.cs @@ -9,7 +9,7 @@ using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services.Changes; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Services.Implement +namespace Umbraco.Cms.Core.Services { public class MemberTypeService : ContentTypeServiceBase, IMemberTypeService { diff --git a/src/Umbraco.Infrastructure/Services/Implement/NotificationService.cs b/src/Umbraco.Core/Services/NotificationService.cs similarity index 99% rename from src/Umbraco.Infrastructure/Services/Implement/NotificationService.cs rename to src/Umbraco.Core/Services/NotificationService.cs index 447143092a..28e309bef5 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/NotificationService.cs +++ b/src/Umbraco.Core/Services/NotificationService.cs @@ -18,7 +18,7 @@ using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Services.Implement +namespace Umbraco.Cms.Core.Services { public class NotificationService : INotificationService { diff --git a/src/Umbraco.Infrastructure/Services/Implement/PropertyValidationService.cs b/src/Umbraco.Core/Services/PropertyValidationService.cs similarity index 99% rename from src/Umbraco.Infrastructure/Services/Implement/PropertyValidationService.cs rename to src/Umbraco.Core/Services/PropertyValidationService.cs index 70580153de..e3c041716f 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/PropertyValidationService.cs +++ b/src/Umbraco.Core/Services/PropertyValidationService.cs @@ -7,7 +7,7 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Services.Implement +namespace Umbraco.Cms.Core.Services { public class PropertyValidationService : IPropertyValidationService { diff --git a/src/Umbraco.Infrastructure/Services/Implement/PublicAccessService.cs b/src/Umbraco.Core/Services/PublicAccessService.cs similarity index 99% rename from src/Umbraco.Infrastructure/Services/Implement/PublicAccessService.cs rename to src/Umbraco.Core/Services/PublicAccessService.cs index 90184cd258..0d0d82c64c 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/PublicAccessService.cs +++ b/src/Umbraco.Core/Services/PublicAccessService.cs @@ -10,7 +10,7 @@ using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Services.Implement +namespace Umbraco.Cms.Core.Services { internal class PublicAccessService : RepositoryService, IPublicAccessService { diff --git a/src/Umbraco.Infrastructure/Services/Implement/RedirectUrlService.cs b/src/Umbraco.Core/Services/RedirectUrlService.cs similarity index 98% rename from src/Umbraco.Infrastructure/Services/Implement/RedirectUrlService.cs rename to src/Umbraco.Core/Services/RedirectUrlService.cs index a3faf64081..fe2c56c7e2 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/RedirectUrlService.cs +++ b/src/Umbraco.Core/Services/RedirectUrlService.cs @@ -6,7 +6,7 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; -namespace Umbraco.Cms.Core.Services.Implement +namespace Umbraco.Cms.Core.Services { internal class RedirectUrlService : RepositoryService, IRedirectUrlService { diff --git a/src/Umbraco.Infrastructure/Services/Implement/RelationService.cs b/src/Umbraco.Core/Services/RelationService.cs similarity index 99% rename from src/Umbraco.Infrastructure/Services/Implement/RelationService.cs rename to src/Umbraco.Core/Services/RelationService.cs index 7a5d10c222..d4cf07e9d0 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/RelationService.cs +++ b/src/Umbraco.Core/Services/RelationService.cs @@ -11,7 +11,7 @@ using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Services.Implement +namespace Umbraco.Cms.Core.Services { public class RelationService : RepositoryService, IRelationService { diff --git a/src/Umbraco.Infrastructure/Services/Implement/RepositoryService.cs b/src/Umbraco.Core/Services/RepositoryService.cs similarity index 95% rename from src/Umbraco.Infrastructure/Services/Implement/RepositoryService.cs rename to src/Umbraco.Core/Services/RepositoryService.cs index e529616961..825c30e43d 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/RepositoryService.cs +++ b/src/Umbraco.Core/Services/RepositoryService.cs @@ -4,7 +4,7 @@ using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Core.Scoping; -namespace Umbraco.Cms.Core.Services.Implement +namespace Umbraco.Cms.Core.Services { /// /// Represents a service that works on top of repositories. diff --git a/src/Umbraco.Infrastructure/Services/Implement/TagService.cs b/src/Umbraco.Core/Services/TagService.cs similarity index 99% rename from src/Umbraco.Infrastructure/Services/Implement/TagService.cs rename to src/Umbraco.Core/Services/TagService.cs index 907af05ab2..16584bd491 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/TagService.cs +++ b/src/Umbraco.Core/Services/TagService.cs @@ -6,7 +6,7 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; -namespace Umbraco.Cms.Core.Services.Implement +namespace Umbraco.Cms.Core.Services { /// /// Tag service to query for tags in the tags db table. The tags returned are only relevant for published content & saved media or members diff --git a/src/Umbraco.Infrastructure/Services/Implement/UserService.cs b/src/Umbraco.Core/Services/UserService.cs similarity index 99% rename from src/Umbraco.Infrastructure/Services/Implement/UserService.cs rename to src/Umbraco.Core/Services/UserService.cs index 04f5ff8103..47205a3b11 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/UserService.cs +++ b/src/Umbraco.Core/Services/UserService.cs @@ -14,11 +14,9 @@ using Umbraco.Cms.Core.Persistence; using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; -using Umbraco.Cms.Infrastructure.Persistence.Querying; -using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Services.Implement +namespace Umbraco.Cms.Core.Services { /// /// Represents the UserService, which is an easy access to operations involving , and eventually Backoffice Users. @@ -594,7 +592,7 @@ namespace Umbraco.Cms.Core.Services.Implement { using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { - return ((UserRepository) _userRepository).GetNextUsers(id, count); + return _userRepository.GetNextUsers(id, count); } } diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs index 1558f929ee..ff538bb9f6 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs @@ -15,7 +15,6 @@ using Umbraco.Cms.Core.Routing; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services.Implement; using Umbraco.Cms.Infrastructure.Packaging; -using Umbraco.Cms.Infrastructure.Services.Implement; using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.DependencyInjection @@ -33,47 +32,13 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection // register the special idk map builder.Services.AddUnique(); - // register the services - builder.Services.AddUnique(); - builder.Services.AddUnique(); - builder.Services.AddUnique(); - builder.Services.AddUnique(); builder.Services.AddUnique(); builder.Services.AddUnique(); builder.Services.AddUnique(); - builder.Services.AddUnique(); - builder.Services.AddUnique(); - builder.Services.AddUnique(); - builder.Services.AddUnique(); - builder.Services.AddUnique(); - builder.Services.AddUnique(); - builder.Services.AddUnique(); - builder.Services.AddUnique(); - builder.Services.AddUnique(); - builder.Services.AddUnique(); builder.Services.AddUnique(); - builder.Services.AddUnique(); - builder.Services.AddUnique(); builder.Services.AddUnique(); builder.Services.AddUnique(); - builder.Services.AddUnique(); - builder.Services.AddUnique(); - builder.Services.AddUnique(); - builder.Services.AddUnique(); - builder.Services.AddUnique(); - builder.Services.AddUnique(); - builder.Services.AddUnique(); - builder.Services.AddUnique(); - builder.Services.AddUnique(); builder.Services.AddTransient(SourcesFactory); - builder.Services.AddUnique(factory => new LocalizedTextService( - factory.GetRequiredService>(), - factory.GetRequiredService>())); - - builder.Services.AddUnique(); - - builder.Services.AddSingleton(); - builder.Services.AddSingleton(); builder.Services.AddUnique(factory => CreatePackageRepository(factory, "createdPackages.config")); builder.Services.AddSingleton(); builder.Services.AddUnique(); diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs index 26d37ed80a..a9cba6b2ae 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs @@ -911,7 +911,7 @@ SELECT 4 AS [Key], COUNT(id) AS [Value] FROM umbracoUser WHERE userDisabled = 0 return sql; } - internal IEnumerable GetNextUsers(int id, int count) + public IEnumerable GetNextUsers(int id, int count) { var idsQuery = SqlContext.Sql() .Select(x => x.Id) diff --git a/src/Umbraco.Infrastructure/Services/Implement/DataTypeService.cs b/src/Umbraco.Infrastructure/Services/Implement/DataTypeService.cs index d63335ba33..7fabd9327b 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/DataTypeService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/DataTypeService.cs @@ -12,7 +12,6 @@ using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Strings; -using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; using Umbraco.Extensions; namespace Umbraco.Cms.Core.Services.Implement @@ -109,7 +108,7 @@ namespace Umbraco.Cms.Core.Services.Implement { using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { - return ((EntityContainerRepository) _dataTypeContainerRepository).Get(containerId); + return _dataTypeContainerRepository.Get(containerId); } } @@ -117,7 +116,7 @@ namespace Umbraco.Cms.Core.Services.Implement { using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { - return ((EntityContainerRepository) _dataTypeContainerRepository).Get(name, level); + return _dataTypeContainerRepository.Get(name, level); } } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Services/DefaultContentVersionCleanupPolicyTest.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Services/DefaultContentVersionCleanupPolicyTest.cs index 801c27110d..6eb9af166f 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Services/DefaultContentVersionCleanupPolicyTest.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Services/DefaultContentVersionCleanupPolicyTest.cs @@ -8,7 +8,7 @@ using NUnit.Framework; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Repositories; -using Umbraco.Cms.Infrastructure.Services.Implement; +using Umbraco.Cms.Core.Services; using Umbraco.Cms.Tests.UnitTests.AutoFixture; using ContentVersionCleanupPolicySettings = Umbraco.Cms.Core.Models.ContentVersionCleanupPolicySettings; diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Services/AmbiguousEventTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Services/AmbiguousEventTests.cs index fa62ad8fc3..ad4cb80113 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Services/AmbiguousEventTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Services/AmbiguousEventTests.cs @@ -6,6 +6,7 @@ using System.Reflection; using System.Text; using NUnit.Framework; using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services.Implement; namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Services diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Services/LocalizedTextServiceTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Services/LocalizedTextServiceTests.cs index 497af66d63..848d634409 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Services/LocalizedTextServiceTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Services/LocalizedTextServiceTests.cs @@ -9,6 +9,7 @@ using System.Xml.Linq; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using NUnit.Framework; +using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services.Implement; namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Services From 73e99b75b28a06376ace326431a22c77d0e179c1 Mon Sep 17 00:00:00 2001 From: Erik-Jan Westendorp Date: Sun, 16 Jan 2022 05:36:45 +0100 Subject: [PATCH 069/141] Add missing read more translation (#11850) --- src/Umbraco.Web.UI/umbraco/config/lang/nl.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml b/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml index 23678534d2..98f395413e 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml @@ -770,6 +770,7 @@ Een ogenblik geduld aub... Vorige Eigenschappen + Lees meer Opnieuw opbouwen E-mail om formulier resultaten te ontvangen Prullenbak From 294ea7373ef24d073b6785445b674fefa5c12c3f Mon Sep 17 00:00:00 2001 From: Erik-Jan Westendorp Date: Sun, 16 Jan 2022 12:13:17 +0100 Subject: [PATCH 070/141] Localization: Rename RelatieTypen to RelatieTypes (Dutch) (#11849) * Change RelatieTypen to RelatieTypes * Small 't' --- src/Umbraco.Web.UI/umbraco/config/lang/nl.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml b/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml index 98f395413e..00ae98c40d 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml @@ -1795,7 +1795,7 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je Rollen Ledentypes Documenttypes - RelatieTypen + Relatietypes Packages Packages Partial Views From ee1153ee56d7105353dd32561d035533b65b3087 Mon Sep 17 00:00:00 2001 From: Erik-Jan Westendorp Date: Sun, 16 Jan 2022 12:33:49 +0100 Subject: [PATCH 071/141] Localization: Translate history cleanup labels/descriptions to Dutch (#11835) * Update nl.xml to show labels in dutch in info content app * Update nl.xml to show labels in dutch in permissions app --- src/Umbraco.Web.UI/umbraco/config/lang/nl.xml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml b/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml index 00ae98c40d..4c3557b5d1 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml @@ -1252,6 +1252,10 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je Terugzetten naar Selecteer versie Bekijk + + Versies + Conceptversie + Gepubliceerde versie Bewerk script-bestand @@ -1671,6 +1675,12 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je weggooien? Tabblad toevoegen + Geschiedenis opschonen + Overschrijf de standaard geschiedenis opschonen instellingen. + Bewaar alle versies nieuwer dan dagen + Bewaar de laatste versie per dag voor dagen + Voorkom opschonen + Geschiedenis opschonen is globaal uitgeschakeld. Deze instellingen worden pas van kracht nadat ze zijn ingeschakeld. Taal toevoegen From 8cd0e5a0031e2cf5d063c9f59aa7e820cff5bff3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Kottal?= Date: Sat, 8 Jan 2022 11:27:02 +0100 Subject: [PATCH 072/141] Change logmessages --- src/Umbraco.Core/Runtime/MainDom.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Core/Runtime/MainDom.cs b/src/Umbraco.Core/Runtime/MainDom.cs index ec4e56df1b..08d11db5cd 100644 --- a/src/Umbraco.Core/Runtime/MainDom.cs +++ b/src/Umbraco.Core/Runtime/MainDom.cs @@ -154,11 +154,11 @@ namespace Umbraco.Cms.Core.Runtime // the handler is not installed so that would be the hosting environment if (_signaled) { - _logger.LogInformation("Cannot acquire (signaled)."); + _logger.LogInformation("Cannot acquire MainDom (signaled)."); return false; } - _logger.LogInformation("Acquiring."); + _logger.LogInformation("Acquiring MainDom."); // Get the lock var acquired = false; @@ -168,12 +168,12 @@ namespace Umbraco.Cms.Core.Runtime } catch (Exception ex) { - _logger.LogError(ex, "Error while acquiring"); + _logger.LogError(ex, "Error while acquiring MainDom"); } if (!acquired) { - _logger.LogInformation("Cannot acquire (timeout)."); + _logger.LogInformation("Cannot acquire MainDom (timeout)."); // In previous versions we'd let a TimeoutException be thrown // and the appdomain would not start. We have the opportunity to allow it to @@ -209,7 +209,7 @@ namespace Umbraco.Cms.Core.Runtime _logger.LogWarning(ex, ex.Message); } - _logger.LogInformation("Acquired."); + _logger.LogInformation("Acquired MainDom."); return true; } From 0e9525d216ea66ea4eaf2528ca25717e5e7b9cc1 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Mon, 17 Jan 2022 08:32:13 +0100 Subject: [PATCH 073/141] Added null check --- src/Umbraco.Web/WebApi/EnableDetailedErrorsAttribute.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web/WebApi/EnableDetailedErrorsAttribute.cs b/src/Umbraco.Web/WebApi/EnableDetailedErrorsAttribute.cs index 3df6ffb47b..5d6d53ab52 100644 --- a/src/Umbraco.Web/WebApi/EnableDetailedErrorsAttribute.cs +++ b/src/Umbraco.Web/WebApi/EnableDetailedErrorsAttribute.cs @@ -12,7 +12,12 @@ namespace Umbraco.Web.WebApi { public override void OnActionExecuting(HttpActionContext actionContext) { - actionContext.ControllerContext.Configuration.IncludeErrorDetailPolicy = HttpContext.Current.IsDebuggingEnabled ? IncludeErrorDetailPolicy.Always : IncludeErrorDetailPolicy.Default; + if (HttpContext.Current?.IsDebuggingEnabled ?? false) + { + actionContext.ControllerContext.Configuration.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always; + } + + actionContext.ControllerContext.Configuration.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Default; } } } From 2c44d676860dad2aa552e755f5b444b46b2c3abd Mon Sep 17 00:00:00 2001 From: Paul Johnson Date: Mon, 17 Jan 2022 14:42:41 +0000 Subject: [PATCH 074/141] Modify codeql setup. (#11876) --- .github/config/codeql-config.yml | 14 -------- .github/workflows/codeql-analysis.yml | 46 ++------------------------- 2 files changed, 3 insertions(+), 57 deletions(-) delete mode 100644 .github/config/codeql-config.yml diff --git a/.github/config/codeql-config.yml b/.github/config/codeql-config.yml deleted file mode 100644 index 7bac345491..0000000000 --- a/.github/config/codeql-config.yml +++ /dev/null @@ -1,14 +0,0 @@ -name: "CodeQL config" -on: - push: - branches: [v8/contrib,v8/dev] -paths-ignore: - - node_modules - - Umbraco.TestData - - Umbraco.Tests - - Umbraco.Tests.AcceptanceTest - - Umbraco.Tests.Benchmarks - - bin - - build.tmp -paths: - - src diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index ee912262d7..c30bd4b44f 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -2,11 +2,10 @@ name: "Code scanning - action" on: push: - branches: [v8/contrib,v8/dev,v8/bug,v8/feature] + branches: ['*/dev','*/contrib', '*/feature/**'] pull_request: # The branches below must be a subset of the branches above - schedule: - - cron: '0 7 * * 2' + branches: ['*/dev','*/contrib'] jobs: CodeQL-Build: @@ -16,53 +15,14 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v2 - with: - # We must fetch at least the immediate parents so that if this is - # a pull request then we can checkout the head. - fetch-depth: 2 - - # If this run was triggered by a pull request event, then checkout - # the head of the pull request instead of the merge commit. - - run: git checkout HEAD^2 - if: ${{ github.event_name == 'pull_request' }} # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v1 - # Override language selection by uncommenting this and choosing your languages - # with: - # languages: go, javascript, csharp, python, cpp, java - - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - # ℹ️ Command-line programs to run using the OS shell. - # 📚 https://git.io/JvXDl - - # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines - # and modify them (or add more) to build your code if your project - # uses a compiled language - - - name: configure Pagefile - uses: al-cheb/configure-pagefile-action@v1.2 - with: - minimum-size: 8GB - maximum-size: 32GB - + - run: | echo "Run Umbraco-CMS build" pwsh -command .\build\build.ps1 - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v1 - with: - config-file: ./.github/config/codeql-config.yml - - # This job is to prevent the workflow status from showing as failed when all other jobs are skipped - See https://github.community/t/workflow-is-failing-if-no-job-can-be-ran-due-to-condition/16873 - always_job: - name: Always run job - runs-on: ubuntu-latest - steps: - - name: Always run - run: echo "This job is to prevent the workflow status from showing as failed when all other jobs are skipped" - From c410f789860db0fd89267a619e442b6730eff8bf Mon Sep 17 00:00:00 2001 From: Paul Johnson Date: Mon, 17 Jan 2022 21:41:24 +0000 Subject: [PATCH 075/141] Restore config file and prevent duplicate run for feature branches --- .github/config/codeql-config.yml | 4 ++++ .github/workflows/codeql-analysis.yml | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 .github/config/codeql-config.yml diff --git a/.github/config/codeql-config.yml b/.github/config/codeql-config.yml new file mode 100644 index 0000000000..dd94726dba --- /dev/null +++ b/.github/config/codeql-config.yml @@ -0,0 +1,4 @@ +name: "CodeQL config" + +paths: + - src diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index c30bd4b44f..9138fc4a20 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -2,7 +2,7 @@ name: "Code scanning - action" on: push: - branches: ['*/dev','*/contrib', '*/feature/**'] + branches: ['*/dev','*/contrib'] pull_request: # The branches below must be a subset of the branches above branches: ['*/dev','*/contrib'] @@ -26,3 +26,5 @@ jobs: - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v1 + with: + config-file: ./.github/config/codeql-config.yml \ No newline at end of file From 9a145537aae0220bbbfe7bb895456c12deff3b73 Mon Sep 17 00:00:00 2001 From: Paul Johnson Date: Mon, 17 Jan 2022 22:29:42 +0000 Subject: [PATCH 076/141] Fix config file, should be on init --- .github/workflows/codeql-analysis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 9138fc4a20..d3575421f8 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -19,12 +19,12 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v1 - + with: + config-file: ./.github/config/codeql-config.yml + - run: | echo "Run Umbraco-CMS build" pwsh -command .\build\build.ps1 - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v1 - with: - config-file: ./.github/config/codeql-config.yml \ No newline at end of file From 576f90ad37569bafd5db73ad8fab29432e57bd56 Mon Sep 17 00:00:00 2001 From: Paul Johnson Date: Tue, 18 Jan 2022 00:35:53 +0000 Subject: [PATCH 077/141] Speedup codeql run (#11877) * Speedup codeql run * npm build once, ignore node_modules * Misc cleanup --- .github/config/codeql-config.yml | 3 +++ .github/workflows/codeql-analysis.yml | 7 +++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/config/codeql-config.yml b/.github/config/codeql-config.yml index dd94726dba..432be995a5 100644 --- a/.github/config/codeql-config.yml +++ b/.github/config/codeql-config.yml @@ -2,3 +2,6 @@ name: "CodeQL config" paths: - src + +paths-ignore: + - '**/node_modules' diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index d3575421f8..60292dba45 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -10,7 +10,7 @@ on: jobs: CodeQL-Build: - runs-on: windows-latest + runs-on: ubuntu-latest steps: - name: Checkout repository @@ -22,9 +22,8 @@ jobs: with: config-file: ./.github/config/codeql-config.yml - - run: | - echo "Run Umbraco-CMS build" - pwsh -command .\build\build.ps1 + - name: Build + run: dotnet build umbraco-netcore-only.sln # also runs npm build - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v1 From 366a8ba56501bf49ff641852200ced7155bbf548 Mon Sep 17 00:00:00 2001 From: Paul Johnson Date: Mon, 17 Jan 2022 10:38:34 +0000 Subject: [PATCH 078/141] Prevent issues for those who wish use ServiceBasedControllerActivator. Adds registration for CurrentUserController which has ambiguous constructors. Register our controllers so that issues are visible during dev/test. --- .../UmbracoBuilderExtensions.cs | 20 +++++- .../ControllersAsServicesComposer.cs | 61 +++++++++++++++++++ 2 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 src/Umbraco.Web.UI/Composers/ControllersAsServicesComposer.cs diff --git a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs index 2c801e963b..46002a6c8d 100644 --- a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.DependencyInjection; @@ -54,7 +55,8 @@ namespace Umbraco.Extensions .AddCoreNotifications() .AddLogViewer() .AddExamine() - .AddExamineIndexes(); + .AddExamineIndexes() + .AddControllersWithAmbiguousConstructors(); public static IUmbracoBuilder AddUnattendedInstallInstallCreateUser(this IUmbracoBuilder builder) { @@ -119,5 +121,21 @@ namespace Umbraco.Extensions return builder; } + + /// + /// Adds explicit registrations for controllers with ambiguous constructors to prevent downstream issues for + /// users who wish to use + /// + public static IUmbracoBuilder AddControllersWithAmbiguousConstructors( + this IUmbracoBuilder builder) + { + builder.Services.TryAddTransient(sp => + { + IUserDataService userDataService = sp.GetRequiredService(); + return ActivatorUtilities.CreateInstance(sp, userDataService); + }); + + return builder; + } } } diff --git a/src/Umbraco.Web.UI/Composers/ControllersAsServicesComposer.cs b/src/Umbraco.Web.UI/Composers/ControllersAsServicesComposer.cs new file mode 100644 index 0000000000..042343df67 --- /dev/null +++ b/src/Umbraco.Web.UI/Composers/ControllersAsServicesComposer.cs @@ -0,0 +1,61 @@ +using System; +using System.Linq; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.DependencyInjection; + +namespace Umbraco.Cms.Web.UI.Composers +{ + /// + /// Adds controllers to the service collection. + /// + /// + /// + /// Umbraco 9 out of the box, makes use of which doesn't resolve controller + /// instances from the IOC container, instead it resolves the required dependencies of the controller and constructs an instance + /// of the controller. + /// + /// + /// Some users may wish to switch to (perhaps to make use of interception/decoration). + /// + /// + /// This composer exists to help us detect ambiguous constructors in the CMS such that we do not cause unnecessary effort downstream. + /// + /// + /// This Composer is not shipped by the Umbraco.Templates package. + /// + /// + public class ControllersAsServicesComposer : IComposer + { + /// + public void Compose(IUmbracoBuilder builder) => builder.Services + .AddMvc() + .AddControllersAsServicesWithoutChangingActivator(); + } + + internal static class MvcBuilderExtensions + { + /// + /// but without the replacement of + /// . + /// + /// + /// We don't need to opt in to to ensure container validation + /// passes. + /// + public static IMvcBuilder AddControllersAsServicesWithoutChangingActivator(this IMvcBuilder builder) + { + var feature = new ControllerFeature(); + builder.PartManager.PopulateFeature(feature); + + foreach (Type controller in feature.Controllers.Select(c => c.AsType())) + { + builder.Services.TryAddTransient(controller, controller); + } + + return builder; + } + } +} From bf166225def87b80a25e11244cb5868a3ca2d43e Mon Sep 17 00:00:00 2001 From: Paul Johnson Date: Tue, 18 Jan 2022 10:39:22 +0000 Subject: [PATCH 079/141] Move Umbraco.Tests to legacy dir. (#11878) (makes grep easier) --- {tests => legacy}/Umbraco.Tests/App.config | 0 {tests => legacy}/Umbraco.Tests/Properties/AssemblyInfo.cs | 0 {tests => legacy}/Umbraco.Tests/Published/ModelTypeTests.cs | 0 {tests => legacy}/Umbraco.Tests/Routing/BaseUrlProviderTest.cs | 0 {tests => legacy}/Umbraco.Tests/Routing/MediaUrlProviderTests.cs | 0 {tests => legacy}/Umbraco.Tests/Umbraco.Tests.csproj | 0 .../Web/Controllers/AuthenticationControllerTests.cs | 0 {tests => legacy}/Umbraco.Tests/unit-test-log4net.CI.config | 0 {tests => legacy}/Umbraco.Tests/unit-test-logger.config | 0 9 files changed, 0 insertions(+), 0 deletions(-) rename {tests => legacy}/Umbraco.Tests/App.config (100%) rename {tests => legacy}/Umbraco.Tests/Properties/AssemblyInfo.cs (100%) rename {tests => legacy}/Umbraco.Tests/Published/ModelTypeTests.cs (100%) rename {tests => legacy}/Umbraco.Tests/Routing/BaseUrlProviderTest.cs (100%) rename {tests => legacy}/Umbraco.Tests/Routing/MediaUrlProviderTests.cs (100%) rename {tests => legacy}/Umbraco.Tests/Umbraco.Tests.csproj (100%) rename {tests => legacy}/Umbraco.Tests/Web/Controllers/AuthenticationControllerTests.cs (100%) rename {tests => legacy}/Umbraco.Tests/unit-test-log4net.CI.config (100%) rename {tests => legacy}/Umbraco.Tests/unit-test-logger.config (100%) diff --git a/tests/Umbraco.Tests/App.config b/legacy/Umbraco.Tests/App.config similarity index 100% rename from tests/Umbraco.Tests/App.config rename to legacy/Umbraco.Tests/App.config diff --git a/tests/Umbraco.Tests/Properties/AssemblyInfo.cs b/legacy/Umbraco.Tests/Properties/AssemblyInfo.cs similarity index 100% rename from tests/Umbraco.Tests/Properties/AssemblyInfo.cs rename to legacy/Umbraco.Tests/Properties/AssemblyInfo.cs diff --git a/tests/Umbraco.Tests/Published/ModelTypeTests.cs b/legacy/Umbraco.Tests/Published/ModelTypeTests.cs similarity index 100% rename from tests/Umbraco.Tests/Published/ModelTypeTests.cs rename to legacy/Umbraco.Tests/Published/ModelTypeTests.cs diff --git a/tests/Umbraco.Tests/Routing/BaseUrlProviderTest.cs b/legacy/Umbraco.Tests/Routing/BaseUrlProviderTest.cs similarity index 100% rename from tests/Umbraco.Tests/Routing/BaseUrlProviderTest.cs rename to legacy/Umbraco.Tests/Routing/BaseUrlProviderTest.cs diff --git a/tests/Umbraco.Tests/Routing/MediaUrlProviderTests.cs b/legacy/Umbraco.Tests/Routing/MediaUrlProviderTests.cs similarity index 100% rename from tests/Umbraco.Tests/Routing/MediaUrlProviderTests.cs rename to legacy/Umbraco.Tests/Routing/MediaUrlProviderTests.cs diff --git a/tests/Umbraco.Tests/Umbraco.Tests.csproj b/legacy/Umbraco.Tests/Umbraco.Tests.csproj similarity index 100% rename from tests/Umbraco.Tests/Umbraco.Tests.csproj rename to legacy/Umbraco.Tests/Umbraco.Tests.csproj diff --git a/tests/Umbraco.Tests/Web/Controllers/AuthenticationControllerTests.cs b/legacy/Umbraco.Tests/Web/Controllers/AuthenticationControllerTests.cs similarity index 100% rename from tests/Umbraco.Tests/Web/Controllers/AuthenticationControllerTests.cs rename to legacy/Umbraco.Tests/Web/Controllers/AuthenticationControllerTests.cs diff --git a/tests/Umbraco.Tests/unit-test-log4net.CI.config b/legacy/Umbraco.Tests/unit-test-log4net.CI.config similarity index 100% rename from tests/Umbraco.Tests/unit-test-log4net.CI.config rename to legacy/Umbraco.Tests/unit-test-log4net.CI.config diff --git a/tests/Umbraco.Tests/unit-test-logger.config b/legacy/Umbraco.Tests/unit-test-logger.config similarity index 100% rename from tests/Umbraco.Tests/unit-test-logger.config rename to legacy/Umbraco.Tests/unit-test-logger.config From ea3ceab4e2579513fa7181f65ec70d308f8d07bf Mon Sep 17 00:00:00 2001 From: Paul Johnson Date: Tue, 18 Jan 2022 11:46:26 +0000 Subject: [PATCH 080/141] GH actions - install latest dotnet --- .github/workflows/codeql-analysis.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 60292dba45..9ec8c3b4e3 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -22,6 +22,11 @@ jobs: with: config-file: ./.github/config/codeql-config.yml + - name: Setup dotnet + uses: actions/setup-dotnet@v1 + with: + dotnet-version: '6.0.x' + - name: Build run: dotnet build umbraco-netcore-only.sln # also runs npm build From ca3bdcf39c0920f9d639fef47d7fc9b8fb4f3e85 Mon Sep 17 00:00:00 2001 From: Paul Johnson Date: Tue, 18 Jan 2022 12:20:33 +0000 Subject: [PATCH 081/141] Fix Umbraco.Core csproj --- src/Umbraco.Core/Umbraco.Core.csproj | 39 ++++++++-------------------- 1 file changed, 11 insertions(+), 28 deletions(-) diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index ff8f1bc1ab..8867a6c9fb 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -15,39 +15,22 @@ - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - all - - - - - - - - - - + + + + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + all From 364b8f1c72925b81224a535b1f1ab1e03fd460f2 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Tue, 18 Jan 2022 13:58:14 +0100 Subject: [PATCH 082/141] Add UmbracoApplicationStartedNotification and UmbracoApplicationStoppedNotification (#11857) * Add UmbracoApplicationStartedNotification and UmbracoApplicationStoppedNotification * Include cancellation token when publishing unattended install/upgrade notifications --- .../UmbracoApplicationStartedNotification.cs | 9 ++ .../UmbracoApplicationStartingNotification.cs | 14 +-- .../UmbracoApplicationStoppedNotification.cs | 9 ++ .../UmbracoApplicationStoppingNotification.cs | 7 +- .../Runtime/CoreRuntime.cs | 90 ++++++++++++++----- 5 files changed, 99 insertions(+), 30 deletions(-) create mode 100644 src/Umbraco.Core/Notifications/UmbracoApplicationStartedNotification.cs create mode 100644 src/Umbraco.Core/Notifications/UmbracoApplicationStoppedNotification.cs diff --git a/src/Umbraco.Core/Notifications/UmbracoApplicationStartedNotification.cs b/src/Umbraco.Core/Notifications/UmbracoApplicationStartedNotification.cs new file mode 100644 index 0000000000..a3d38720d7 --- /dev/null +++ b/src/Umbraco.Core/Notifications/UmbracoApplicationStartedNotification.cs @@ -0,0 +1,9 @@ +namespace Umbraco.Cms.Core.Notifications +{ + /// + /// Notification that occurs when Umbraco has completely booted up and the request processing pipeline is configured. + /// + /// + public class UmbracoApplicationStartedNotification : INotification + { } +} diff --git a/src/Umbraco.Core/Notifications/UmbracoApplicationStartingNotification.cs b/src/Umbraco.Core/Notifications/UmbracoApplicationStartingNotification.cs index 4cbf0a55c6..dd60f9431c 100644 --- a/src/Umbraco.Core/Notifications/UmbracoApplicationStartingNotification.cs +++ b/src/Umbraco.Core/Notifications/UmbracoApplicationStartingNotification.cs @@ -1,23 +1,23 @@ -// Copyright (c) Umbraco. -// See LICENSE for more details. - namespace Umbraco.Cms.Core.Notifications { /// - /// Notification that occurs at the very end of the Umbraco boot - /// process and after all initialize. + /// Notification that occurs at the very end of the Umbraco boot process (after all s are initialized). /// + /// public class UmbracoApplicationStartingNotification : INotification { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The runtime level public UmbracoApplicationStartingNotification(RuntimeLevel runtimeLevel) => RuntimeLevel = runtimeLevel; /// - /// Gets the runtime level of execution. + /// Gets the runtime level. /// + /// + /// The runtime level. + /// public RuntimeLevel RuntimeLevel { get; } } } diff --git a/src/Umbraco.Core/Notifications/UmbracoApplicationStoppedNotification.cs b/src/Umbraco.Core/Notifications/UmbracoApplicationStoppedNotification.cs new file mode 100644 index 0000000000..be4c6ccfd4 --- /dev/null +++ b/src/Umbraco.Core/Notifications/UmbracoApplicationStoppedNotification.cs @@ -0,0 +1,9 @@ +namespace Umbraco.Cms.Core.Notifications +{ + /// + /// Notification that occurs when Umbraco has completely shutdown. + /// + /// + public class UmbracoApplicationStoppedNotification : INotification + { } +} diff --git a/src/Umbraco.Core/Notifications/UmbracoApplicationStoppingNotification.cs b/src/Umbraco.Core/Notifications/UmbracoApplicationStoppingNotification.cs index db86a1e614..6d5234bbcc 100644 --- a/src/Umbraco.Core/Notifications/UmbracoApplicationStoppingNotification.cs +++ b/src/Umbraco.Core/Notifications/UmbracoApplicationStoppingNotification.cs @@ -1,4 +1,9 @@ namespace Umbraco.Cms.Core.Notifications { - public class UmbracoApplicationStoppingNotification : INotification { } + /// + /// Notification that occurs when Umbraco is shutting down (after all s are terminated). + /// + /// + public class UmbracoApplicationStoppingNotification : INotification + { } } diff --git a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs index 175bceb9e0..5dbe78c2f5 100644 --- a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs @@ -2,6 +2,8 @@ using System; using System.ComponentModel; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration; @@ -16,12 +18,13 @@ using Umbraco.Cms.Infrastructure.Persistence; using Umbraco.Cms.Web.Common.DependencyInjection; using Umbraco.Extensions; using ComponentCollection = Umbraco.Cms.Core.Composing.ComponentCollection; +using IHostingEnvironment = Umbraco.Cms.Core.Hosting.IHostingEnvironment; namespace Umbraco.Cms.Infrastructure.Runtime { + /// public class CoreRuntime : IRuntime { - private readonly ILogger _logger; private readonly ILoggerFactory _loggerFactory; private readonly ComponentCollection _components; private readonly IApplicationShutdownRegistry _applicationShutdownRegistry; @@ -32,14 +35,16 @@ namespace Umbraco.Cms.Infrastructure.Runtime private readonly IHostingEnvironment _hostingEnvironment; private readonly IUmbracoVersion _umbracoVersion; private readonly IServiceProvider _serviceProvider; + private readonly IHostApplicationLifetime _hostApplicationLifetime; + private readonly ILogger _logger; private CancellationToken _cancellationToken; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// public CoreRuntime( - ILoggerFactory loggerFactory, IRuntimeState state, + ILoggerFactory loggerFactory, ComponentCollection components, IApplicationShutdownRegistry applicationShutdownRegistry, IProfilingLogger profilingLogger, @@ -48,9 +53,11 @@ namespace Umbraco.Cms.Infrastructure.Runtime IEventAggregator eventAggregator, IHostingEnvironment hostingEnvironment, IUmbracoVersion umbracoVersion, - IServiceProvider serviceProvider) + IServiceProvider serviceProvider, + IHostApplicationLifetime hostApplicationLifetime) { State = state; + _loggerFactory = loggerFactory; _components = components; _applicationShutdownRegistry = applicationShutdownRegistry; @@ -61,6 +68,7 @@ namespace Umbraco.Cms.Infrastructure.Runtime _hostingEnvironment = hostingEnvironment; _umbracoVersion = umbracoVersion; _serviceProvider = serviceProvider; + _hostApplicationLifetime = hostApplicationLifetime; _logger = _loggerFactory.CreateLogger(); } @@ -76,23 +84,49 @@ namespace Umbraco.Cms.Infrastructure.Runtime IUmbracoDatabaseFactory databaseFactory, IEventAggregator eventAggregator, IHostingEnvironment hostingEnvironment, - IUmbracoVersion umbracoVersion - ):this( - loggerFactory, - state, - components, - applicationShutdownRegistry, - profilingLogger, - mainDom, - databaseFactory, - eventAggregator, - hostingEnvironment, - umbracoVersion, - null - ) - { + IUmbracoVersion umbracoVersion, + IServiceProvider serviceProvider) + : this( + state, + loggerFactory, + components, + applicationShutdownRegistry, + profilingLogger, + mainDom, + databaseFactory, + eventAggregator, + hostingEnvironment, + umbracoVersion, + serviceProvider, + serviceProvider?.GetRequiredService()) + { } - } + [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete] + public CoreRuntime( + ILoggerFactory loggerFactory, + IRuntimeState state, + ComponentCollection components, + IApplicationShutdownRegistry applicationShutdownRegistry, + IProfilingLogger profilingLogger, + IMainDom mainDom, + IUmbracoDatabaseFactory databaseFactory, + IEventAggregator eventAggregator, + IHostingEnvironment hostingEnvironment, + IUmbracoVersion umbracoVersion) + : this( + loggerFactory, + state, + components, + applicationShutdownRegistry, + profilingLogger, + mainDom, + databaseFactory, + eventAggregator, + hostingEnvironment, + umbracoVersion, + null) + { } /// /// Gets the state of the Umbraco runtime. @@ -103,13 +137,17 @@ namespace Umbraco.Cms.Infrastructure.Runtime public async Task RestartAsync() { await StopAsync(_cancellationToken); + await _eventAggregator.PublishAsync(new UmbracoApplicationStoppedNotification(), _cancellationToken); await StartAsync(_cancellationToken); + await _eventAggregator.PublishAsync(new UmbracoApplicationStartedNotification(), _cancellationToken); } /// public async Task StartAsync(CancellationToken cancellationToken) { + // Store token, so we can re-use this during restart _cancellationToken = cancellationToken; + StaticApplicationLogging.Initialize(_loggerFactory); StaticServiceProvider.Instance = _serviceProvider; @@ -130,6 +168,13 @@ namespace Umbraco.Cms.Infrastructure.Runtime _logger.LogError(exception, msg); }; + // Add application started and stopped notifications (only on initial startup, not restarts) + if (_hostApplicationLifetime.ApplicationStarted.IsCancellationRequested == false) + { + _hostApplicationLifetime.ApplicationStarted.Register(() => _eventAggregator.Publish(new UmbracoApplicationStartedNotification())); + _hostApplicationLifetime.ApplicationStopped.Register(() => _eventAggregator.Publish(new UmbracoApplicationStoppedNotification())); + } + // acquire the main domain - if this fails then anything that should be registered with MainDom will not operate AcquireMainDom(); @@ -137,7 +182,7 @@ namespace Umbraco.Cms.Infrastructure.Runtime await _eventAggregator.PublishAsync(new UmbracoApplicationMainDomAcquiredNotification(), cancellationToken); // notify for unattended install - await _eventAggregator.PublishAsync(new RuntimeUnattendedInstallNotification()); + await _eventAggregator.PublishAsync(new RuntimeUnattendedInstallNotification(), cancellationToken); DetermineRuntimeLevel(); if (!State.UmbracoCanBoot()) @@ -153,7 +198,7 @@ namespace Umbraco.Cms.Infrastructure.Runtime // if level is Run and reason is UpgradeMigrations, that means we need to perform an unattended upgrade var unattendedUpgradeNotification = new RuntimeUnattendedUpgradeNotification(); - await _eventAggregator.PublishAsync(unattendedUpgradeNotification); + await _eventAggregator.PublishAsync(unattendedUpgradeNotification, cancellationToken); switch (unattendedUpgradeNotification.UnattendedUpgradeResult) { case RuntimeUnattendedUpgradeNotification.UpgradeResult.HasErrors: @@ -161,6 +206,7 @@ namespace Umbraco.Cms.Infrastructure.Runtime { throw new InvalidOperationException($"Unattended upgrade result was {RuntimeUnattendedUpgradeNotification.UpgradeResult.HasErrors} but no {nameof(BootFailedException)} was registered"); } + // we cannot continue here, the exception will be rethrown by BootFailedMiddelware return; case RuntimeUnattendedUpgradeNotification.UpgradeResult.CoreUpgradeComplete: From 72f30eb937c63433c83eefb35e534eacb0f77df4 Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska Date: Tue, 18 Jan 2022 15:19:05 +0100 Subject: [PATCH 083/141] Adding else case --- src/Umbraco.Web/WebApi/EnableDetailedErrorsAttribute.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web/WebApi/EnableDetailedErrorsAttribute.cs b/src/Umbraco.Web/WebApi/EnableDetailedErrorsAttribute.cs index 5d6d53ab52..ef07dfcb79 100644 --- a/src/Umbraco.Web/WebApi/EnableDetailedErrorsAttribute.cs +++ b/src/Umbraco.Web/WebApi/EnableDetailedErrorsAttribute.cs @@ -16,8 +16,10 @@ namespace Umbraco.Web.WebApi { actionContext.ControllerContext.Configuration.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always; } - - actionContext.ControllerContext.Configuration.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Default; + else + { + actionContext.ControllerContext.Configuration.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Default; + } } } } From 1b5830a9d705c29cae65831821ae606b1114e8eb Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska <21998037+elit0451@users.noreply.github.com> Date: Tue, 18 Jan 2022 15:36:25 +0100 Subject: [PATCH 084/141] V9: Visiting a page without a template returns "Page Not Found" (#11852) * Returns "Page Not Found" when template doesn't exist for a docType * Fix comment * Use IsNullOrWhiteSpace instead of IsNullOrEmpty Co-authored-by: Mole --- .../Controllers/RenderController.cs | 22 ++++++++++++++++++- .../Controllers/UmbracoPageController.cs | 10 +++++++-- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.Common/Controllers/RenderController.cs b/src/Umbraco.Web.Common/Controllers/RenderController.cs index 84354e4988..4d74dd1767 100644 --- a/src/Umbraco.Web.Common/Controllers/RenderController.cs +++ b/src/Umbraco.Web.Common/Controllers/RenderController.cs @@ -53,6 +53,26 @@ namespace Umbraco.Cms.Web.Common.Controllers /// public virtual IActionResult Index() => CurrentTemplate(new ContentModel(CurrentPage)); + /// + /// Gets an action result based on the template name found in the route values and a model. + /// + /// The type of the model. + /// The model. + /// The action result. + /// + /// If the template found in the route values doesn't physically exist, Umbraco not found result is returned. + /// + protected override IActionResult CurrentTemplate(T model) + { + if (EnsurePhsyicalViewExists(UmbracoRouteValues.TemplateName) == false) + { + // no physical template file was found + return new PublishedContentNotFoundResult(UmbracoContext); + } + + return View(UmbracoRouteValues.TemplateName, model); + } + /// /// Before the controller executes we will handle redirects and not founds /// @@ -123,6 +143,6 @@ namespace Umbraco.Cms.Web.Common.Controllers { return new PublishedContentNotFoundResult(UmbracoContext); } - } + } } } diff --git a/src/Umbraco.Web.Common/Controllers/UmbracoPageController.cs b/src/Umbraco.Web.Common/Controllers/UmbracoPageController.cs index 0e6b6d0d0c..f9840df370 100644 --- a/src/Umbraco.Web.Common/Controllers/UmbracoPageController.cs +++ b/src/Umbraco.Web.Common/Controllers/UmbracoPageController.cs @@ -74,7 +74,7 @@ namespace Umbraco.Cms.Web.Common.Controllers /// The model. /// The action result. /// If the template found in the route values doesn't physically exist and exception is thrown - protected IActionResult CurrentTemplate(T model) + protected virtual IActionResult CurrentTemplate(T model) { if (EnsurePhsyicalViewExists(UmbracoRouteValues.TemplateName) == false) { @@ -90,6 +90,13 @@ namespace Umbraco.Cms.Web.Common.Controllers /// The view name. protected bool EnsurePhsyicalViewExists(string template) { + if (string.IsNullOrWhiteSpace(template)) + { + string docTypeAlias = UmbracoRouteValues.PublishedRequest.PublishedContent.ContentType.Alias; + _logger.LogWarning("No physical template file was found for document type with alias {Alias}", docTypeAlias); + return false; + } + ViewEngineResult result = _compositeViewEngine.FindView(ControllerContext, template, false); if (result.View != null) { @@ -99,6 +106,5 @@ namespace Umbraco.Cms.Web.Common.Controllers _logger.LogWarning("No physical template file was found for template {Template}", template); return false; } - } } From 229ca989eb2a97c72c7dd4e2a54ced28f493fbd1 Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska <21998037+elit0451@users.noreply.github.com> Date: Tue, 18 Jan 2022 15:37:31 +0100 Subject: [PATCH 085/141] V9: Allowlisting remote URLs for displaying content on the content dashboard (#11825) * Fixing ContentDashboardSettings to work when set in the config * Moving file in Models folder and adding ContentDashboardUrlAllowlist setting * Implementing allowlist for content dashboard base url * Cleanup * Error msg vs log msg --- src/JsonSchema/AppSettings.cs | 2 + .../{ => Models}/ContentDashboardSettings.cs | 18 +++++-- src/Umbraco.Core/Constants-Configuration.cs | 3 +- .../UmbracoBuilder.Configuration.cs | 3 +- .../Controllers/DashboardController.cs | 51 +++++++++++++++++-- 5 files changed, 67 insertions(+), 10 deletions(-) rename src/Umbraco.Core/Configuration/{ => Models}/ContentDashboardSettings.cs (55%) diff --git a/src/JsonSchema/AppSettings.cs b/src/JsonSchema/AppSettings.cs index a9b70fc4a1..048513a5da 100644 --- a/src/JsonSchema/AppSettings.cs +++ b/src/JsonSchema/AppSettings.cs @@ -86,6 +86,8 @@ namespace JsonSchema public PackageMigrationSettings PackageMigration { get; set; } public LegacyPasswordMigrationSettings LegacyPasswordMigration { get; set; } + + public ContentDashboardSettings ContentDashboard { get; set; } } /// diff --git a/src/Umbraco.Core/Configuration/ContentDashboardSettings.cs b/src/Umbraco.Core/Configuration/Models/ContentDashboardSettings.cs similarity index 55% rename from src/Umbraco.Core/Configuration/ContentDashboardSettings.cs rename to src/Umbraco.Core/Configuration/Models/ContentDashboardSettings.cs index 7bef36dba4..3f8546a1ad 100644 --- a/src/Umbraco.Core/Configuration/ContentDashboardSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/ContentDashboardSettings.cs @@ -1,6 +1,11 @@ - -namespace Umbraco.Cms.Core.Configuration +using System.ComponentModel; + +namespace Umbraco.Cms.Core.Configuration.Models { + /// + /// Typed configuration options for content dashboard settings. + /// + [UmbracoOptions(Constants.Configuration.ConfigContentDashboard)] public class ContentDashboardSettings { private const string DefaultContentDashboardPath = "cms"; @@ -18,6 +23,13 @@ namespace Umbraco.Cms.Core.Configuration /// Gets the path to use when constructing the URL for retrieving data for the content dashboard. /// /// The URL path. - public string ContentDashboardPath { get; set; } = DefaultContentDashboardPath; + [DefaultValue(DefaultContentDashboardPath)] + public string ContentDashboardPath { get; set; } = DefaultContentDashboardPath; + + /// + /// Gets the allowed addresses to retrieve data for the content dashboard. + /// + /// The URLs. + public string[] ContentDashboardUrlAllowlist { get; set; } } } diff --git a/src/Umbraco.Core/Constants-Configuration.cs b/src/Umbraco.Core/Constants-Configuration.cs index 063d733821..ab951618e3 100644 --- a/src/Umbraco.Core/Constants-Configuration.cs +++ b/src/Umbraco.Core/Constants-Configuration.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core +namespace Umbraco.Cms.Core { public static partial class Constants { @@ -54,6 +54,7 @@ public const string ConfigUserPassword = ConfigPrefix + "Security:UserPassword"; public const string ConfigRichTextEditor = ConfigPrefix + "RichTextEditor"; public const string ConfigPackageMigration = ConfigPrefix + "PackageMigration"; + public const string ConfigContentDashboard = ConfigPrefix + "ContentDashboard"; } } } diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs index ce2e4f2304..7bc5ae57c8 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs @@ -82,7 +82,8 @@ namespace Umbraco.Cms.Core.DependencyInjection .AddUmbracoOptions() .AddUmbracoOptions() .AddUmbracoOptions() - .AddUmbracoOptions(); + .AddUmbracoOptions() + .AddUmbracoOptions(); return builder; } diff --git a/src/Umbraco.Web.BackOffice/Controllers/DashboardController.cs b/src/Umbraco.Web.BackOffice/Controllers/DashboardController.cs index 7b64d05633..955081fa73 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/DashboardController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/DashboardController.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Net; using System.Net.Http; using System.Text; using System.Threading.Tasks; @@ -8,9 +9,11 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Configuration; +using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Dashboards; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Security; @@ -40,7 +43,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers private readonly IDashboardService _dashboardService; private readonly IUmbracoVersion _umbracoVersion; private readonly IShortStringHelper _shortStringHelper; - private readonly IOptions _dashboardSettings; + private readonly ContentDashboardSettings _dashboardSettings; /// /// Initializes a new instance of the with all its dependencies. /// @@ -60,12 +63,13 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers _dashboardService = dashboardService; _umbracoVersion = umbracoVersion; _shortStringHelper = shortStringHelper; - _dashboardSettings = dashboardSettings; + _dashboardSettings = dashboardSettings.Value; } //we have just one instance of HttpClient shared for the entire application private static readonly HttpClient HttpClient = new HttpClient(); + // TODO(V10) : change return type to Task> and consider removing baseUrl as parameter //we have baseurl as a param to make previewing easier, so we can test with a dev domain from client side [ValidateAngularAntiForgeryToken] public async Task GetRemoteDashboardContent(string section, string baseUrl = "https://dashboard.umbraco.com/") @@ -76,9 +80,19 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers var version = _umbracoVersion.SemanticVersion.ToSemanticStringWithoutBuild(); var isAdmin = user.IsAdmin(); + if (!IsAllowedUrl(baseUrl)) + { + _logger.LogError($"The following URL is not listed in the setting 'Umbraco:CMS:ContentDashboard:ContentDashboardUrlAllowlist' in configuration: {baseUrl}"); + HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest; + + // Hacking the response - can't set the HttpContext.Response.Body, so instead returning the error as JSON + var errorJson = JsonConvert.SerializeObject(new { Error = "Dashboard source not permitted" }); + return JObject.Parse(errorJson); + } + var url = string.Format("{0}{1}?section={2}&allowed={3}&lang={4}&version={5}&admin={6}", baseUrl, - _dashboardSettings.Value.ContentDashboardPath, + _dashboardSettings.ContentDashboardPath, section, allowedSections, language, @@ -116,8 +130,15 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers return result; } + // TODO(V10) : consider removing baseUrl as parameter public async Task GetRemoteDashboardCss(string section, string baseUrl = "https://dashboard.umbraco.org/") { + if (!IsAllowedUrl(baseUrl)) + { + _logger.LogError($"The following URL is not listed in the setting 'Umbraco:CMS:ContentDashboard:ContentDashboardUrlAllowlist' in configuration: {baseUrl}"); + return BadRequest("Dashboard source not permitted"); + } + var url = string.Format(baseUrl + "css/dashboard.css?section={0}", section); var key = "umbraco-dynamic-dashboard-css-" + section; @@ -152,12 +173,18 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers } - return Content(result,"text/css", Encoding.UTF8); + return Content(result, "text/css", Encoding.UTF8); } public async Task GetRemoteXml(string site, string url) { + if (!IsAllowedUrl(url)) + { + _logger.LogError($"The following URL is not listed in the setting 'Umbraco:CMS:ContentDashboard:ContentDashboardUrlAllowlist' in configuration: {url}"); + return BadRequest("Dashboard source not permitted"); + } + // This is used in place of the old feedproxy.config // Which was used to grab data from our.umbraco.com, umbraco.com or umbraco.tv // for certain dashboards or the help drawer @@ -214,7 +241,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers } } - return Content(result,"text/xml", Encoding.UTF8); + return Content(result, "text/xml", Encoding.UTF8); } @@ -240,5 +267,19 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers }) }).ToList(); } + + // Checks if the passed URL is part of the configured allowlist of addresses + private bool IsAllowedUrl(string url) + { + // No addresses specified indicates that any URL is allowed + if (_dashboardSettings.ContentDashboardUrlAllowlist is null || _dashboardSettings.ContentDashboardUrlAllowlist.Contains(url, StringComparer.OrdinalIgnoreCase)) + { + return true; + } + else + { + return false; + } + } } } From 24519f6dad121e846395d58179804647688291f4 Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska <21998037+elit0451@users.noreply.github.com> Date: Tue, 18 Jan 2022 15:38:03 +0100 Subject: [PATCH 086/141] Allowlisting remote URLs for displaying content on the content dashboard (#11822) * Implement allowlisting of urls when fetching data for the content dashboard * Adding a new setting in the config & removing inexistent one * Adding description * Adding description * Tidy up code --- src/Umbraco.Core/Constants-AppSettings.cs | 5 +++ .../Dashboards/ContentDashboardSettings.cs | 11 ++++++- .../Dashboards/IContentDashboardSettings.cs | 6 ++++ src/Umbraco.Web.UI/web.Template.config | 2 +- .../Editors/DashboardController.cs | 31 ++++++++++++++++++- 5 files changed, 52 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Constants-AppSettings.cs b/src/Umbraco.Core/Constants-AppSettings.cs index de7799c165..4e5619813e 100644 --- a/src/Umbraco.Core/Constants-AppSettings.cs +++ b/src/Umbraco.Core/Constants-AppSettings.cs @@ -120,6 +120,11 @@ namespace Umbraco.Core /// public const string ContentDashboardPath = "Umbraco.Core.ContentDashboardPath"; + /// + /// A list of allowed addresses to fetch content for the content dashboard. + /// + public const string ContentDashboardUrlAllowlist = "Umbraco.Core.ContentDashboardUrl-Allowlist"; + /// /// TODO: FILL ME IN /// diff --git a/src/Umbraco.Core/Dashboards/ContentDashboardSettings.cs b/src/Umbraco.Core/Dashboards/ContentDashboardSettings.cs index b370f93eca..24daecf0b8 100644 --- a/src/Umbraco.Core/Dashboards/ContentDashboardSettings.cs +++ b/src/Umbraco.Core/Dashboards/ContentDashboardSettings.cs @@ -2,7 +2,7 @@ namespace Umbraco.Core.Dashboards { - public class ContentDashboardSettings: IContentDashboardSettings + public class ContentDashboardSettings : IContentDashboardSettings { private const string DefaultContentDashboardPath = "cms"; @@ -30,5 +30,14 @@ namespace Umbraco.Core.Dashboards ConfigurationManager.AppSettings.ContainsKey(Constants.AppSettings.ContentDashboardPath) ? ConfigurationManager.AppSettings[Constants.AppSettings.ContentDashboardPath] : DefaultContentDashboardPath; + + /// + /// Gets the allowed addresses to retrieve data for the content dashboard. + /// + /// The URLs. + public string ContentDashboardUrlAllowlist => + ConfigurationManager.AppSettings.ContainsKey(Constants.AppSettings.ContentDashboardUrlAllowlist) + ? ConfigurationManager.AppSettings[Constants.AppSettings.ContentDashboardUrlAllowlist] + : null; } } diff --git a/src/Umbraco.Core/Dashboards/IContentDashboardSettings.cs b/src/Umbraco.Core/Dashboards/IContentDashboardSettings.cs index f5c4e3da78..518a217bf8 100644 --- a/src/Umbraco.Core/Dashboards/IContentDashboardSettings.cs +++ b/src/Umbraco.Core/Dashboards/IContentDashboardSettings.cs @@ -16,5 +16,11 @@ /// /// The URL path. string ContentDashboardPath { get; } + + /// + /// Gets the allowed addresses to retrieve data for the content dashboard. + /// + /// The URLs. + string ContentDashboardUrlAllowlist { get; } } } diff --git a/src/Umbraco.Web.UI/web.Template.config b/src/Umbraco.Web.UI/web.Template.config index f19ab5d3b6..e4e3e19bcb 100644 --- a/src/Umbraco.Web.UI/web.Template.config +++ b/src/Umbraco.Web.UI/web.Template.config @@ -38,7 +38,7 @@ - + diff --git a/src/Umbraco.Web/Editors/DashboardController.cs b/src/Umbraco.Web/Editors/DashboardController.cs index cf56cc4be8..1073bef413 100644 --- a/src/Umbraco.Web/Editors/DashboardController.cs +++ b/src/Umbraco.Web/Editors/DashboardController.cs @@ -17,9 +17,9 @@ using Umbraco.Core.Logging; using Umbraco.Core.Persistence; using Umbraco.Core.Services; using Umbraco.Core.Dashboards; -using Umbraco.Core.IO; using Umbraco.Core.Models; using Umbraco.Web.Services; +using System.Web.Http; namespace Umbraco.Web.Editors { @@ -61,6 +61,8 @@ namespace Umbraco.Web.Editors var version = UmbracoVersion.SemanticVersion.ToSemanticString(); var isAdmin = user.IsAdmin(); + VerifyDashboardSource(baseUrl); + var url = string.Format("{0}{1}?section={2}&allowed={3}&lang={4}&version={5}&admin={6}", baseUrl, _dashboardSettings.ContentDashboardPath, @@ -103,6 +105,8 @@ namespace Umbraco.Web.Editors public async Task GetRemoteDashboardCss(string section, string baseUrl = "https://dashboard.umbraco.org/") { + VerifyDashboardSource(baseUrl); + var url = string.Format(baseUrl + "css/dashboard.css?section={0}", section); var key = "umbraco-dynamic-dashboard-css-" + section; @@ -144,6 +148,8 @@ namespace Umbraco.Web.Editors public async Task GetRemoteXml(string site, string url) { + VerifyDashboardSource(url); + // This is used in place of the old feedproxy.config // Which was used to grab data from our.umbraco.com, umbraco.com or umbraco.tv // for certain dashboards or the help drawer @@ -228,5 +234,28 @@ namespace Umbraco.Web.Editors }) }).ToList(); } + + // Checks if the passed URL is part of the configured allowlist of addresses + private bool IsAllowedUrl(string url) + { + // No addresses specified indicates that any URL is allowed + if (string.IsNullOrEmpty(_dashboardSettings.ContentDashboardUrlAllowlist) || _dashboardSettings.ContentDashboardUrlAllowlist.Contains(url)) + { + return true; + } + else + { + return false; + } + } + + private void VerifyDashboardSource(string url) + { + if(!IsAllowedUrl(url)) + { + Logger.Error($"The following URL is not listed in the allowlist for ContentDashboardUrl in the Web.config: {url}"); + throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Dashboard source not permitted")); + } + } } } From 2ead758a995143b57db1ca71c6a6a0fca9271541 Mon Sep 17 00:00:00 2001 From: Paul Johnson Date: Tue, 18 Jan 2022 15:23:53 +0000 Subject: [PATCH 087/141] Update to npoco5 --- .../Constants-DatabaseProviders.cs | 2 +- .../Persistence/Constants-DbProviderNames.cs | 2 +- .../Persistence/BulkDataReader.cs | 6 +-- ...tworkConnectivityErrorDetectionStrategy.cs | 2 +- ...SqlAzureTransientErrorDetectionStrategy.cs | 26 +++++------ .../FaultHandling/ThrottlingCondition.cs | 2 +- .../Persistence/LocalDb.cs | 2 +- .../NPocoDatabaseExtensions-Bulk.cs | 3 +- .../Persistence/NPocoDatabaseExtensions.cs | 2 +- .../SqlServerBulkSqlInsertProvider.cs | 2 +- .../Persistence/SqlServerDatabaseCreator.cs | 2 +- .../SqlSyntax/SqlServerSyntaxProvider.cs | 2 +- .../Persistence/UmbracoDatabaseFactory.cs | 2 +- .../Runtime/SqlMainDomLock.cs | 3 +- .../Umbraco.Infrastructure.csproj | 3 +- .../UmbracoBuilderExtensions.cs | 2 +- .../SqlTemplatesBenchmark.cs | 4 +- .../TestHelpers/TestDatabase.cs | 43 +++++++++++++++++++ .../Umbraco.Tests.Integration.SqlCe.csproj | 1 - .../Testing/BaseTestDatabase.cs | 2 +- .../Testing/SqlDeveloperTestDatabase.cs | 2 +- .../Testing/UmbracoIntegrationTest.cs | 2 +- .../Persistence/LocksTests.cs | 3 +- .../Repositories/MacroRepositoryTest.cs | 2 +- .../ServerRegistrationRepositoryTest.cs | 2 +- .../Umbraco.Tests.Integration.csproj | 1 - .../TestHelpers/BaseUsingSqlSyntax.cs | 2 +- .../Umbraco.Core/Composing/TypeHelperTests.cs | 2 +- .../Persistence/BulkDataReaderTests.cs | 2 +- .../NPocoTests/NPocoSqlTemplateTests.cs | 2 +- 30 files changed, 84 insertions(+), 49 deletions(-) diff --git a/src/Umbraco.Core/Constants-DatabaseProviders.cs b/src/Umbraco.Core/Constants-DatabaseProviders.cs index da82746445..1fd16133e5 100644 --- a/src/Umbraco.Core/Constants-DatabaseProviders.cs +++ b/src/Umbraco.Core/Constants-DatabaseProviders.cs @@ -5,7 +5,7 @@ public static class DatabaseProviders { public const string SqlCe = "System.Data.SqlServerCe.4.0"; - public const string SqlServer = "System.Data.SqlClient"; + public const string SqlServer = "Microsoft.Data.SqlClient"; } } } diff --git a/src/Umbraco.Core/Persistence/Constants-DbProviderNames.cs b/src/Umbraco.Core/Persistence/Constants-DbProviderNames.cs index 7c08189d74..bd95776dea 100644 --- a/src/Umbraco.Core/Persistence/Constants-DbProviderNames.cs +++ b/src/Umbraco.Core/Persistence/Constants-DbProviderNames.cs @@ -5,7 +5,7 @@ namespace Umbraco.Cms.Core { public static class DbProviderNames { - public const string SqlServer = "System.Data.SqlClient"; + public const string SqlServer = "Microsoft.Data.SqlClient"; public const string SqlCe = "System.Data.SqlServerCe.4.0"; } } diff --git a/src/Umbraco.Infrastructure/Persistence/BulkDataReader.cs b/src/Umbraco.Infrastructure/Persistence/BulkDataReader.cs index 61db41a20a..42c3ff1865 100644 --- a/src/Umbraco.Infrastructure/Persistence/BulkDataReader.cs +++ b/src/Umbraco.Infrastructure/Persistence/BulkDataReader.cs @@ -1,15 +1,11 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Data; using System.Data.Common; -using System.Data.SqlClient; using System.Diagnostics; using System.Globalization; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using Microsoft.Data.SqlClient; namespace Umbraco.Cms.Infrastructure.Persistence { diff --git a/src/Umbraco.Infrastructure/Persistence/FaultHandling/Strategies/NetworkConnectivityErrorDetectionStrategy.cs b/src/Umbraco.Infrastructure/Persistence/FaultHandling/Strategies/NetworkConnectivityErrorDetectionStrategy.cs index 004ec1f9b2..8cd01e706c 100644 --- a/src/Umbraco.Infrastructure/Persistence/FaultHandling/Strategies/NetworkConnectivityErrorDetectionStrategy.cs +++ b/src/Umbraco.Infrastructure/Persistence/FaultHandling/Strategies/NetworkConnectivityErrorDetectionStrategy.cs @@ -1,5 +1,5 @@ using System; -using System.Data.SqlClient; +using Microsoft.Data.SqlClient; namespace Umbraco.Cms.Infrastructure.Persistence.FaultHandling.Strategies { diff --git a/src/Umbraco.Infrastructure/Persistence/FaultHandling/Strategies/SqlAzureTransientErrorDetectionStrategy.cs b/src/Umbraco.Infrastructure/Persistence/FaultHandling/Strategies/SqlAzureTransientErrorDetectionStrategy.cs index 37968c4376..faf6442333 100644 --- a/src/Umbraco.Infrastructure/Persistence/FaultHandling/Strategies/SqlAzureTransientErrorDetectionStrategy.cs +++ b/src/Umbraco.Infrastructure/Persistence/FaultHandling/Strategies/SqlAzureTransientErrorDetectionStrategy.cs @@ -1,5 +1,5 @@ using System; -using System.Data.SqlClient; +using Microsoft.Data.SqlClient; namespace Umbraco.Cms.Infrastructure.Persistence.FaultHandling.Strategies { @@ -104,7 +104,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.FaultHandling.Strategies // Resource ID: %d. The %s limit for the database is %d and has been reached. case 10928: // SQL Error Code: 10929 - // Resource ID: %d. The %s minimum guarantee is %d, maximum limit is %d and the current usage for the database is %d. + // Resource ID: %d. The %s minimum guarantee is %d, maximum limit is %d and the current usage for the database is %d. // However, the server is currently too busy to support requests greater than %d for this database. case 10929: // SQL Error Code: 10053 @@ -112,14 +112,14 @@ namespace Umbraco.Cms.Infrastructure.Persistence.FaultHandling.Strategies // An established connection was aborted by the software in your host machine. case 10053: // SQL Error Code: 10054 - // A transport-level error has occurred when sending the request to the server. + // A transport-level error has occurred when sending the request to the server. // (provider: TCP Provider, error: 0 - An existing connection was forcibly closed by the remote host.) case 10054: // SQL Error Code: 10060 - // A network-related or instance-specific error occurred while establishing a connection to SQL Server. - // The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server - // is configured to allow remote connections. (provider: TCP Provider, error: 0 - A connection attempt failed - // because the connected party did not properly respond after a period of time, or established connection failed + // A network-related or instance-specific error occurred while establishing a connection to SQL Server. + // The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server + // is configured to allow remote connections. (provider: TCP Provider, error: 0 - A connection attempt failed + // because the connected party did not properly respond after a period of time, or established connection failed // because connected host has failed to respond.)"} case 10060: // SQL Error Code: 40197 @@ -129,21 +129,21 @@ namespace Umbraco.Cms.Infrastructure.Persistence.FaultHandling.Strategies // The service has encountered an error processing your request. Please try again. case 40540: // SQL Error Code: 40613 - // Database XXXX on server YYYY is not currently available. Please retry the connection later. If the problem persists, contact customer + // Database XXXX on server YYYY is not currently available. Please retry the connection later. If the problem persists, contact customer // support, and provide them the session tracing ID of ZZZZZ. case 40613: // SQL Error Code: 40143 // The service has encountered an error processing your request. Please try again. case 40143: // SQL Error Code: 233 - // The client was unable to establish a connection because of an error during connection initialization process before login. - // Possible causes include the following: the client tried to connect to an unsupported version of SQL Server; the server was too busy - // to accept new connections; or there was a resource limitation (insufficient memory or maximum allowed connections) on the server. + // The client was unable to establish a connection because of an error during connection initialization process before login. + // Possible causes include the following: the client tried to connect to an unsupported version of SQL Server; the server was too busy + // to accept new connections; or there was a resource limitation (insufficient memory or maximum allowed connections) on the server. // (provider: TCP Provider, error: 0 - An existing connection was forcibly closed by the remote host.) case 233: // SQL Error Code: 64 - // A connection was successfully established with the server, but then an error occurred during the login process. - // (provider: TCP Provider, error: 0 - The specified network name is no longer available.) + // A connection was successfully established with the server, but then an error occurred during the login process. + // (provider: TCP Provider, error: 0 - The specified network name is no longer available.) case 64: // DBNETLIB Error Code: 20 // The instance of SQL Server you attempted to connect to does not support encryption. diff --git a/src/Umbraco.Infrastructure/Persistence/FaultHandling/ThrottlingCondition.cs b/src/Umbraco.Infrastructure/Persistence/FaultHandling/ThrottlingCondition.cs index 9155937fe0..96d42a9481 100644 --- a/src/Umbraco.Infrastructure/Persistence/FaultHandling/ThrottlingCondition.cs +++ b/src/Umbraco.Infrastructure/Persistence/FaultHandling/ThrottlingCondition.cs @@ -1,10 +1,10 @@ using System; using System.Collections.Generic; -using System.Data.SqlClient; using System.Globalization; using System.Linq; using System.Text; using System.Text.RegularExpressions; +using Microsoft.Data.SqlClient; namespace Umbraco.Cms.Infrastructure.Persistence.FaultHandling { diff --git a/src/Umbraco.Infrastructure/Persistence/LocalDb.cs b/src/Umbraco.Infrastructure/Persistence/LocalDb.cs index c51344a342..fe616b56f6 100644 --- a/src/Umbraco.Infrastructure/Persistence/LocalDb.cs +++ b/src/Umbraco.Infrastructure/Persistence/LocalDb.cs @@ -1,10 +1,10 @@ using System; using System.Collections.Generic; using System.Data; -using System.Data.SqlClient; using System.Diagnostics; using System.IO; using System.Linq; +using Microsoft.Data.SqlClient; namespace Umbraco.Cms.Infrastructure.Persistence { diff --git a/src/Umbraco.Infrastructure/Persistence/NPocoDatabaseExtensions-Bulk.cs b/src/Umbraco.Infrastructure/Persistence/NPocoDatabaseExtensions-Bulk.cs index f07867cccc..c53076ff18 100644 --- a/src/Umbraco.Infrastructure/Persistence/NPocoDatabaseExtensions-Bulk.cs +++ b/src/Umbraco.Infrastructure/Persistence/NPocoDatabaseExtensions-Bulk.cs @@ -2,9 +2,10 @@ using System.Collections.Generic; using System.Data; using System.Data.Common; -using System.Data.SqlClient; using System.Linq; +using Microsoft.Data.SqlClient; using NPoco; +using NPoco.SqlServer; using Umbraco.Cms.Core; using Umbraco.Cms.Infrastructure.Persistence; diff --git a/src/Umbraco.Infrastructure/Persistence/NPocoDatabaseExtensions.cs b/src/Umbraco.Infrastructure/Persistence/NPocoDatabaseExtensions.cs index 813eea58ef..0159245bfd 100644 --- a/src/Umbraco.Infrastructure/Persistence/NPocoDatabaseExtensions.cs +++ b/src/Umbraco.Infrastructure/Persistence/NPocoDatabaseExtensions.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; using System.Data; -using System.Data.SqlClient; using System.Text.RegularExpressions; +using Microsoft.Data.SqlClient; using NPoco; using StackExchange.Profiling.Data; using Umbraco.Cms.Infrastructure.Persistence; diff --git a/src/Umbraco.Infrastructure/Persistence/SqlServerBulkSqlInsertProvider.cs b/src/Umbraco.Infrastructure/Persistence/SqlServerBulkSqlInsertProvider.cs index d67c97f2c4..ee2689b9e3 100644 --- a/src/Umbraco.Infrastructure/Persistence/SqlServerBulkSqlInsertProvider.cs +++ b/src/Umbraco.Infrastructure/Persistence/SqlServerBulkSqlInsertProvider.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; using System.Data; -using System.Data.SqlClient; using System.Linq; +using Microsoft.Data.SqlClient; using NPoco; using Umbraco.Cms.Infrastructure.Persistence.SqlSyntax; using Umbraco.Extensions; diff --git a/src/Umbraco.Infrastructure/Persistence/SqlServerDatabaseCreator.cs b/src/Umbraco.Infrastructure/Persistence/SqlServerDatabaseCreator.cs index e7f5934e78..63aab47047 100644 --- a/src/Umbraco.Infrastructure/Persistence/SqlServerDatabaseCreator.cs +++ b/src/Umbraco.Infrastructure/Persistence/SqlServerDatabaseCreator.cs @@ -1,6 +1,6 @@ using System; -using System.Data.SqlClient; using System.IO; +using Microsoft.Data.SqlClient; using Umbraco.Cms.Core; namespace Umbraco.Cms.Infrastructure.Persistence diff --git a/src/Umbraco.Infrastructure/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs b/src/Umbraco.Infrastructure/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs index 210b3f2d6b..2db603ad1a 100644 --- a/src/Umbraco.Infrastructure/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs +++ b/src/Umbraco.Infrastructure/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; using System.Data; -using System.Data.SqlClient; using System.Linq; +using Microsoft.Data.SqlClient; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using NPoco; diff --git a/src/Umbraco.Infrastructure/Persistence/UmbracoDatabaseFactory.cs b/src/Umbraco.Infrastructure/Persistence/UmbracoDatabaseFactory.cs index fd8bdc0269..6093c06a97 100644 --- a/src/Umbraco.Infrastructure/Persistence/UmbracoDatabaseFactory.cs +++ b/src/Umbraco.Infrastructure/Persistence/UmbracoDatabaseFactory.cs @@ -292,7 +292,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence _pocoMappers.AddRange(_dbProviderFactoryCreator.ProviderSpecificMappers(_providerName)); - var factory = new FluentPocoDataFactory(GetPocoDataFactoryResolver); + var factory = new FluentPocoDataFactory(GetPocoDataFactoryResolver, _pocoMappers); _pocoDataFactory = factory; var config = new FluentConfig(xmappers => factory); diff --git a/src/Umbraco.Infrastructure/Runtime/SqlMainDomLock.cs b/src/Umbraco.Infrastructure/Runtime/SqlMainDomLock.cs index 8d1c74b619..133c0d857a 100644 --- a/src/Umbraco.Infrastructure/Runtime/SqlMainDomLock.cs +++ b/src/Umbraco.Infrastructure/Runtime/SqlMainDomLock.cs @@ -1,15 +1,14 @@ using System; using System.Data; -using System.Data.SqlClient; using System.Diagnostics; using System.Linq; using System.Security.Cryptography; using System.Threading; using System.Threading.Tasks; +using Microsoft.Data.SqlClient; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using NPoco; -using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.Runtime; diff --git a/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj b/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj index 806fe0853d..0a50a7862c 100644 --- a/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj +++ b/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj @@ -36,7 +36,7 @@ - + @@ -49,7 +49,6 @@ - diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs index 819076bbb8..172a093c3c 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs @@ -1,6 +1,5 @@ using System; using System.Data.Common; -using System.Data.SqlClient; using System.IO; using System.Linq; using System.Reflection; @@ -11,6 +10,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.ApplicationModels; using Microsoft.AspNetCore.Mvc.Razor.Compilation; using Microsoft.AspNetCore.Server.Kestrel.Core; +using Microsoft.Data.SqlClient; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; diff --git a/tests/Umbraco.Tests.Benchmarks/SqlTemplatesBenchmark.cs b/tests/Umbraco.Tests.Benchmarks/SqlTemplatesBenchmark.cs index a69b6b8b76..bd0c426fec 100644 --- a/tests/Umbraco.Tests.Benchmarks/SqlTemplatesBenchmark.cs +++ b/tests/Umbraco.Tests.Benchmarks/SqlTemplatesBenchmark.cs @@ -33,8 +33,8 @@ namespace Umbraco.Tests.Benchmarks public SqlTemplatesBenchmark() { - var mappers = new NPoco.MapperCollection( ); - var factory = new FluentPocoDataFactory((type, iPocoDataFactory) => new PocoDataBuilder(type, mappers).Init()); + var mappers = new NPoco.MapperCollection(); + var factory = new FluentPocoDataFactory((type, iPocoDataFactory) => new PocoDataBuilder(type, mappers).Init(), mappers); SqlContext = new SqlContext(new SqlServerSyntaxProvider(Options.Create(new GlobalSettings())), DatabaseType.SQLCe, factory); SqlTemplates = new SqlTemplates(SqlContext); diff --git a/tests/Umbraco.Tests.Common/TestHelpers/TestDatabase.cs b/tests/Umbraco.Tests.Common/TestHelpers/TestDatabase.cs index 188c515bf0..1eec4f5ae7 100644 --- a/tests/Umbraco.Tests.Common/TestHelpers/TestDatabase.cs +++ b/tests/Umbraco.Tests.Common/TestHelpers/TestDatabase.cs @@ -86,6 +86,8 @@ namespace Umbraco.Cms.Tests.Common.TestHelpers { } + (List, List, List, List) IDatabaseQuery.FetchMultiple(Sql sql) => throw new NotImplementedException(); + public int OneTimeCommandTimeout { get; set; } public MapperCollection Mappers { get; set; } @@ -182,14 +184,17 @@ namespace Umbraco.Cms.Tests.Common.TestHelpers public Task ExecuteAsync(string sql, params object[] args) => throw new NotImplementedException(); public Task ExecuteAsync(Sql sql) => throw new NotImplementedException(); + public Task InsertAsync(string tableName, string primaryKeyName, object poco) => throw new NotImplementedException(); public object Insert(string tableName, string primaryKeyName, bool autoIncrement, T poco) => throw new NotImplementedException(); public object Insert(string tableName, string primaryKeyName, T poco) => throw new NotImplementedException(); public object Insert(T poco) => throw new NotImplementedException(); + public void InsertBulk(IEnumerable pocos, InsertBulkOptions? options = null) => throw new NotImplementedException(); public Task InsertAsync(T poco) => throw new NotImplementedException(); + public Task InsertBulkAsync(IEnumerable pocos, InsertBulkOptions options = null) => throw new NotImplementedException(); public Task InsertBatchAsync(IEnumerable pocos, BatchOptions options = null) => throw new NotImplementedException(); @@ -206,6 +211,9 @@ namespace Umbraco.Cms.Tests.Common.TestHelpers public IAsyncUpdateQueryProvider UpdateManyAsync() => throw new NotImplementedException(); public IAsyncDeleteQueryProvider DeleteManyAsync() => throw new NotImplementedException(); + public Task IsNewAsync(T poco) => throw new NotImplementedException(); + + public Task SaveAsync(T poco) => throw new NotImplementedException(); public void InsertBulk(IEnumerable pocos) => throw new NotImplementedException(); @@ -350,6 +358,15 @@ namespace Umbraco.Cms.Tests.Common.TestHelpers public TRet FetchMultiple(Func, List, List, TRet> cb, Sql sql) => throw new NotImplementedException(); public TRet FetchMultiple(Func, List, List, List, TRet> cb, Sql sql) => throw new NotImplementedException(); + (List, List) IDatabaseQuery.FetchMultiple(string sql, params object[] args) => throw new NotImplementedException(); + + (List, List, List) IDatabaseQuery.FetchMultiple(string sql, params object[] args) => throw new NotImplementedException(); + + (List, List, List, List) IDatabaseQuery.FetchMultiple(string sql, params object[] args) => throw new NotImplementedException(); + + (List, List) IDatabaseQuery.FetchMultiple(Sql sql) => throw new NotImplementedException(); + + (List, List, List) IDatabaseQuery.FetchMultiple(Sql sql) => throw new NotImplementedException(); public Tuple, List> FetchMultiple(string sql, params object[] args) => throw new NotImplementedException(); @@ -382,6 +399,9 @@ namespace Umbraco.Cms.Tests.Common.TestHelpers public Task FirstOrDefaultAsync(string sql, params object[] args) => throw new NotImplementedException(); public Task FirstOrDefaultAsync(Sql sql) => throw new NotImplementedException(); + IAsyncEnumerable IAsyncQueryDatabase.QueryAsync(string sql, params object[] args) => throw new NotImplementedException(); + + IAsyncEnumerable IAsyncQueryDatabase.QueryAsync(Sql sql) => throw new NotImplementedException(); public Task> QueryAsync(string sql, params object[] args) => throw new NotImplementedException(); @@ -406,6 +426,29 @@ namespace Umbraco.Cms.Tests.Common.TestHelpers public Task> SkipTakeAsync(long skip, long take, string sql, params object[] args) => throw new NotImplementedException(); public Task> SkipTakeAsync(long skip, long take, Sql sql) => throw new NotImplementedException(); + public Task FetchMultipleAsync(Func, List, TRet> cb, string sql, params object[] args) => throw new NotImplementedException(); + + public Task FetchMultipleAsync(Func, List, List, TRet> cb, string sql, params object[] args) => throw new NotImplementedException(); + + public Task FetchMultipleAsync(Func, List, List, List, TRet> cb, string sql, params object[] args) => throw new NotImplementedException(); + + public Task FetchMultipleAsync(Func, List, TRet> cb, Sql sql) => throw new NotImplementedException(); + + public Task FetchMultipleAsync(Func, List, List, TRet> cb, Sql sql) => throw new NotImplementedException(); + + public Task FetchMultipleAsync(Func, List, List, List, TRet> cb, Sql sql) => throw new NotImplementedException(); + + public Task<(List, List)> FetchMultipleAsync(string sql, params object[] args) => throw new NotImplementedException(); + + public Task<(List, List, List)> FetchMultipleAsync(string sql, params object[] args) => throw new NotImplementedException(); + + public Task<(List, List, List, List)> FetchMultipleAsync(string sql, params object[] args) => throw new NotImplementedException(); + + public Task<(List, List)> FetchMultipleAsync(Sql sql) => throw new NotImplementedException(); + + public Task<(List, List, List)> FetchMultipleAsync(Sql sql) => throw new NotImplementedException(); + + public Task<(List, List, List, List)> FetchMultipleAsync(Sql sql) => throw new NotImplementedException(); public void BuildPageQueries(long skip, long take, string sql, ref object[] args, out string sqlCount, out string sqlPage) => throw new NotImplementedException(); } diff --git a/tests/Umbraco.Tests.Integration.SqlCe/Umbraco.Tests.Integration.SqlCe.csproj b/tests/Umbraco.Tests.Integration.SqlCe/Umbraco.Tests.Integration.SqlCe.csproj index e8472258bd..2c05ea6bf9 100644 --- a/tests/Umbraco.Tests.Integration.SqlCe/Umbraco.Tests.Integration.SqlCe.csproj +++ b/tests/Umbraco.Tests.Integration.SqlCe/Umbraco.Tests.Integration.SqlCe.csproj @@ -17,7 +17,6 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - diff --git a/tests/Umbraco.Tests.Integration/Testing/BaseTestDatabase.cs b/tests/Umbraco.Tests.Integration/Testing/BaseTestDatabase.cs index 564c45d5ad..47d81e3c11 100644 --- a/tests/Umbraco.Tests.Integration/Testing/BaseTestDatabase.cs +++ b/tests/Umbraco.Tests.Integration/Testing/BaseTestDatabase.cs @@ -5,10 +5,10 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Data; -using System.Data.SqlClient; using System.Diagnostics; using System.Linq; using System.Threading; +using Microsoft.Data.SqlClient; using Microsoft.Extensions.Logging; using Moq; using Umbraco.Cms.Core; diff --git a/tests/Umbraco.Tests.Integration/Testing/SqlDeveloperTestDatabase.cs b/tests/Umbraco.Tests.Integration/Testing/SqlDeveloperTestDatabase.cs index b35b6ac0d5..9daa55a1cb 100644 --- a/tests/Umbraco.Tests.Integration/Testing/SqlDeveloperTestDatabase.cs +++ b/tests/Umbraco.Tests.Integration/Testing/SqlDeveloperTestDatabase.cs @@ -3,10 +3,10 @@ using System; using System.Collections.Concurrent; -using System.Data.SqlClient; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.Data.SqlClient; using Microsoft.Extensions.Logging; using Umbraco.Cms.Infrastructure.Persistence; diff --git a/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs b/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs index fb505e6b83..ccccb28a9d 100644 --- a/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs +++ b/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs @@ -4,11 +4,11 @@ using System; using System.Collections.Generic; using System.Data.Common; -using System.Data.SqlClient; using System.IO; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; +using Microsoft.Data.SqlClient; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/LocksTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/LocksTests.cs index 96ded92f41..51af43a0e8 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/LocksTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/LocksTests.cs @@ -1,14 +1,13 @@ using System; using System.Collections.Generic; -using System.Data.SqlClient; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; +using Microsoft.Data.SqlClient; using NPoco; using NUnit.Framework; using Umbraco.Cms.Core; -using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Cms.Tests.Integration.Testing; diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/MacroRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/MacroRepositoryTest.cs index 2766e66426..1b1c7cdc9f 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/MacroRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/MacroRepositoryTest.cs @@ -2,8 +2,8 @@ // See LICENSE for more details. using System.Collections.Generic; -using System.Data.SqlClient; using System.Linq; +using Microsoft.Data.SqlClient; using Microsoft.Extensions.Logging; using NUnit.Framework; using Umbraco.Cms.Core.Cache; diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ServerRegistrationRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ServerRegistrationRepositoryTest.cs index f85796f622..7fd582008d 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ServerRegistrationRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ServerRegistrationRepositoryTest.cs @@ -3,8 +3,8 @@ using System; using System.Collections.Generic; -using System.Data.SqlClient; using System.Linq; +using Microsoft.Data.SqlClient; using Microsoft.Extensions.Logging; using NUnit.Framework; using Umbraco.Cms.Core.Cache; diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj b/tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj index 41d540302b..6070f468b1 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj +++ b/tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj @@ -95,7 +95,6 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - diff --git a/tests/Umbraco.Tests.UnitTests/TestHelpers/BaseUsingSqlSyntax.cs b/tests/Umbraco.Tests.UnitTests/TestHelpers/BaseUsingSqlSyntax.cs index 023c9ad952..ca7950a7cf 100644 --- a/tests/Umbraco.Tests.UnitTests/TestHelpers/BaseUsingSqlSyntax.cs +++ b/tests/Umbraco.Tests.UnitTests/TestHelpers/BaseUsingSqlSyntax.cs @@ -45,7 +45,7 @@ namespace Umbraco.Cms.Tests.UnitTests.TestHelpers { new NullableDateMapper() }; - var pocoDataFactory = new FluentPocoDataFactory((type, iPocoDataFactory) => new PocoDataBuilder(type, pocoMappers).Init()); + var pocoDataFactory = new FluentPocoDataFactory((type, iPocoDataFactory) => new PocoDataBuilder(type, pocoMappers).Init(), pocoMappers); var sqlSyntax = new SqlServerSyntaxProvider(Options.Create(new GlobalSettings())); SqlContext = new SqlContext(sqlSyntax, DatabaseType.SqlServer2012, pocoDataFactory, factory.GetRequiredService()); Mappers = factory.GetRequiredService(); diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/TypeHelperTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/TypeHelperTests.cs index b2fade7e36..29c583dc75 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/TypeHelperTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/TypeHelperTests.cs @@ -6,9 +6,9 @@ using System.Collections.Generic; using System.ComponentModel; using System.Data.Odbc; using System.Data.OleDb; -using System.Data.SqlClient; using System.Linq; using System.Reflection; +using Microsoft.Data.SqlClient; using NUnit.Framework; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Composing; diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/BulkDataReaderTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/BulkDataReaderTests.cs index 8508281444..8ecf6870a4 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/BulkDataReaderTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/BulkDataReaderTests.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Data; using System.Data.Common; -using System.Data.SqlClient; +using Microsoft.Data.SqlClient; using NUnit.Framework; using Umbraco.Cms.Infrastructure.Persistence; diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/NPocoTests/NPocoSqlTemplateTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/NPocoTests/NPocoSqlTemplateTests.cs index ac880c2b3b..3eddb0fe29 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/NPocoTests/NPocoSqlTemplateTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/NPocoTests/NPocoSqlTemplateTests.cs @@ -42,7 +42,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Persistence.NPocoTe public void SqlTemplateArgs() { var mappers = new NPoco.MapperCollection { new NullableDateMapper() }; - var factory = new FluentPocoDataFactory((type, iPocoDataFactory) => new PocoDataBuilder(type, mappers).Init()); + var factory = new FluentPocoDataFactory((type, iPocoDataFactory) => new PocoDataBuilder(type, mappers).Init(), mappers); var sqlContext = new SqlContext(new SqlServerSyntaxProvider(Options.Create(new GlobalSettings())), DatabaseType.SQLCe, factory); var sqlTemplates = new SqlTemplates(sqlContext); From c60d8c8ab8836f21369a11ce5496f643db7a1556 Mon Sep 17 00:00:00 2001 From: Paul Johnson Date: Wed, 19 Jan 2022 07:32:33 +0000 Subject: [PATCH 088/141] Added EntityController.GetUrlsByIds support for int & guid + update MNTP (#11680) Fixes issue with MNTP (for 8.18) in a partial view macro - GH #11631 Renamed GetUrlsByUdis to match, don't do this in v9 as it would be breaking there, instead mark it obsolete. TODO: v9 ensure integration test coverage, more painful here as no WebApplicationFactory. --- .../common/mocks/resources/entity.mocks.js | 6 +- .../src/common/resources/entity.resource.js | 10 +- .../contentpicker/contentpicker.controller.js | 2 +- src/Umbraco.Web/Editors/EntityController.cs | 141 +++++++++++++++--- 4 files changed, 130 insertions(+), 29 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/mocks/resources/entity.mocks.js b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/entity.mocks.js index 05594115e1..08c28fcbd1 100644 --- a/src/Umbraco.Web.UI.Client/src/common/mocks/resources/entity.mocks.js +++ b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/entity.mocks.js @@ -34,7 +34,7 @@ angular.module('umbraco.mocks'). return [200, nodes, null]; } - function returnUrlsbyUdis(status, data, headers) { + function returnUrlsByIds(status, data, headers) { if (!mocksUtils.checkAuth()) { return [401, null, null]; @@ -83,8 +83,8 @@ angular.module('umbraco.mocks'). .respond(returnEntitybyIdsPost); $httpBackend - .whenPOST(mocksUtils.urlRegex('/umbraco/UmbracoApi/Entity/GetUrlsByUdis')) - .respond(returnUrlsbyUdis); + .whenPOST(mocksUtils.urlRegex('/umbraco/UmbracoApi/Entity/GetUrlsByIds')) + .respond(returnUrlsByIds); $httpBackend .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Entity/GetAncestors')) diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js index 44be85b8fd..6e7ace9a8d 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js @@ -127,19 +127,19 @@ function entityResource($q, $http, umbRequestHelper) { 'Failed to retrieve url for id:' + id); }, - getUrlsByUdis: function(udis, culture) { - var query = "culture=" + (culture || ""); + getUrlsByIds: function(ids, type, culture) { + var query = `type=${type}&culture=${culture || ""}`; return umbRequestHelper.resourcePromise( $http.post( umbRequestHelper.getApiUrl( "entityApiBaseUrl", - "GetUrlsByUdis", + "GetUrlsByIds", query), { - udis: udis + ids: ids }), - 'Failed to retrieve url map for udis ' + udis); + 'Failed to retrieve url map for ids ' + ids); }, getUrlByUdi: function (udi, culture) { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js index 1ecd6bdf26..d2a1710e49 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js @@ -421,7 +421,7 @@ function contentPickerController($scope, $q, $routeParams, $location, entityReso var requests = [ entityResource.getByIds(missingIds, entityType), - entityResource.getUrlsByUdis(missingIds) + entityResource.getUrlsByIds(missingIds, entityType) ]; return $q.all(requests).then(function ([data, urlMap]) { diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index 573246e1f0..c0535bf787 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -76,7 +76,8 @@ namespace Umbraco.Web.Editors new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetPath", "id", typeof(int), typeof(Guid), typeof(Udi)), new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetUrlAndAnchors", "id", typeof(int), typeof(Guid), typeof(Udi)), new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetById", "id", typeof(int), typeof(Guid), typeof(Udi)), - new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetByIds", "ids", typeof(int[]), typeof(Guid[]), typeof(Udi[])))); + new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetByIds", "ids", typeof(int[]), typeof(Guid[]), typeof(Udi[])), + new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetUrlsByIds", "ids", typeof(int[]), typeof(Guid[]), typeof(Udi[])))); } } @@ -236,47 +237,147 @@ namespace Umbraco.Web.Editors } /// - /// Get entity URLs by UDIs + /// Get entity URLs by IDs /// - /// - /// A list of UDIs to lookup items by + /// + /// A list of IDs to lookup items by /// - /// The culture to fetch the URL for + /// The entity type to look for. + /// The culture to fetch the URL for. /// Dictionary mapping Udi -> Url /// /// We allow for POST because there could be quite a lot of Ids. /// [HttpGet] [HttpPost] - public IDictionary GetUrlsByUdis([FromJsonPath] Udi[] udis, string culture = null) + public IDictionary GetUrlsByIds([FromJsonPath] int[] ids, [FromUri] UmbracoEntityTypes type, [FromUri] string culture = null) { - if (udis == null || udis.Length == 0) + if (ids == null || !ids.Any()) + { + return new Dictionary(); + } + + string MediaOrDocumentUrl(int id) + { + switch (type) + { + case UmbracoEntityTypes.Document: + return UmbracoContext.UrlProvider.GetUrl(id, culture: culture ?? ClientCulture()); + case UmbracoEntityTypes.Media: { + var media = UmbracoContext.Media.GetById(id); + // NOTE: If culture is passed here we get an empty string rather than a media item URL. + return UmbracoContext.UrlProvider.GetMediaUrl(media, culture: null); + } + default: + return null; + } + } + + return ids + .Distinct() + .Select(id => new { + Id = id, + Url = MediaOrDocumentUrl(id) + }).ToDictionary(x => x.Id, x => x.Url); + } + + /// + /// Get entity URLs by IDs + /// + /// + /// A list of IDs to lookup items by + /// + /// The entity type to look for. + /// The culture to fetch the URL for. + /// Dictionary mapping Udi -> Url + /// + /// We allow for POST because there could be quite a lot of Ids. + /// + [HttpGet] + [HttpPost] + public IDictionary GetUrlsByIds([FromJsonPath] Guid[] ids, [FromUri] UmbracoEntityTypes type, [FromUri] string culture = null) + { + if (ids == null || !ids.Any()) + { + return new Dictionary(); + } + + string MediaOrDocumentUrl(Guid id) + { + switch (type) + { + case UmbracoEntityTypes.Document: + return UmbracoContext.UrlProvider.GetUrl(id, culture: culture ?? ClientCulture()); + case UmbracoEntityTypes.Media: + { + var media = UmbracoContext.Media.GetById(id); + // NOTE: If culture is passed here we get an empty string rather than a media item URL. + return UmbracoContext.UrlProvider.GetMediaUrl(media, culture: null); + } + default: + return null; + } + } + + return ids + .Distinct() + .Select(id => new { + Id = id, + Url = MediaOrDocumentUrl(id) + }).ToDictionary(x => x.Id, x => x.Url); + } + + /// + /// Get entity URLs by IDs + /// + /// + /// A list of IDs to lookup items by + /// + /// The entity type to look for. + /// The culture to fetch the URL for. + /// Dictionary mapping Udi -> Url + /// + /// We allow for POST because there could be quite a lot of Ids. + /// + [HttpGet] + [HttpPost] + // NOTE: V9 - can't rename GetUrlsByUdis in v9 as it's already released, it's OK to do here as 8.18 isn't out yet. + public IDictionary GetUrlsByIds([FromJsonPath] Udi[] ids, [FromUri] UmbracoEntityTypes type, [FromUri] string culture = null) + { + if (ids == null || !ids.Any()) { return new Dictionary(); } // TODO: PMJ 2021-09-27 - Should GetUrl(Udi) exist as an extension method on UrlProvider/IUrlProvider (in v9) - string MediaOrDocumentUrl(Udi udi) + string MediaOrDocumentUrl(Udi id) { - if (udi is not GuidUdi guidUdi) + if (id is not GuidUdi guidUdi) { return null; } - return guidUdi.EntityType switch + switch (type) { - Constants.UdiEntityType.Document => UmbracoContext.UrlProvider.GetUrl(guidUdi.Guid, culture: culture ?? ClientCulture()), - // NOTE: If culture is passed here we get an empty string rather than a media item URL WAT - Constants.UdiEntityType.Media => UmbracoContext.UrlProvider.GetMediaUrl(guidUdi.Guid, culture: null), - _ => null - }; + case UmbracoEntityTypes.Document: + return UmbracoContext.UrlProvider.GetUrl(guidUdi.Guid, culture: culture ?? ClientCulture()); + case UmbracoEntityTypes.Media: + { + var media = UmbracoContext.Media.GetById(id); + // NOTE: If culture is passed here we get an empty string rather than a media item URL. + return UmbracoContext.UrlProvider.GetMediaUrl(media, culture: null); + } + default: + return null; + } } - return udis - .Select(udi => new { - Udi = udi, - Url = MediaOrDocumentUrl(udi) - }).ToDictionary(x => x.Udi, x => x.Url); + return ids + .Distinct() + .Select(id => new { + Id = id, + Url = MediaOrDocumentUrl(id) + }).ToDictionary(x => x.Id, x => x.Url); } /// From 029a2614763504447bd379c3189a0626a1c1082c Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 19 Jan 2022 09:21:50 +0100 Subject: [PATCH 089/141] Improve functionality for external member logins (#11855) * Bugfix - Take ufprt from form data if the request has form content type, otherwise fallback to use the query * External linking for members * Changed migration to reuse old table * removed unnecessary web.config files * Cleanup * Extracted class to own file * Clean up * Rollback changes to Umbraco.Web.UI.csproj * Fixed migration for SqlCE * Change notification handler to be on deleted * Update src/Umbraco.Infrastructure/Security/MemberUserStore.cs Co-authored-by: Mole * Fixed issue with errors not shown on member linking * fixed issue with errors * clean up * Fix issue where external logins could not be used to upgrade Umbraco, because the externalLogin table was expected to look different. (Like after the migration) * Fixed issue in Ignore legacy column now using result column. Co-authored-by: Mole --- src/Umbraco.Core/Constants-Security.cs | 1 + .../Extensions/ClaimsIdentityExtensions.cs | 13 + .../Repositories/IExternalLoginRepository.cs | 7 +- .../IExternalLoginWithKeyRepository.cs | 28 ++ .../Security/IIdentityUserLogin.cs | 2 +- .../Services/IExternalLoginService.cs | 2 + .../Services/IExternalLoginWithKeyService.cs | 54 +++ .../UmbracoBuilder.Repositories.cs | 5 +- .../UmbracoBuilder.Services.cs | 12 +- .../Migrations/Upgrade/UmbracoPlan.cs | 6 + .../UpdateExternalLoginToUseKeyInsteadOfId.cs | 77 +++++ .../Persistence/Dtos/ExternalLoginDto.cs | 12 +- .../Factories/ExternalLoginFactory.cs | 16 +- .../Mappers/ExternalLoginMapper.cs | 3 +- .../Mappers/ExternalLoginTokenMapper.cs | 2 +- .../Implement/ExternalLoginRepository.cs | 38 ++- .../Security/BackOfficeUserStore.cs | 45 ++- ...eteExternalLoginsOnMemberDeletedHandler.cs | 30 ++ .../Security/MemberUserStore.cs | 105 ++++-- .../Security/UmbracoUserStore.cs | 6 + .../Implement/ExternalLoginService.cs | 80 +++-- .../UmbracoBuilder.BackOfficeIdentity.cs | 17 +- .../BackOfficeAuthenticationBuilder.cs | 5 + .../Security/ExternalSignInAutoLinkOptions.cs | 20 +- .../UmbracoBuilder.MembersIdentity.cs | 25 +- .../Extensions/IdentityBuilderExtensions.cs | 9 + .../Security/IMemberExternalLoginProviders.cs | 26 ++ .../Security/IMemberSignInManager.cs | 1 + .../IMemberSignInManagerExternalLogins.cs | 16 + .../Security/MemberExternalLoginProvider.cs | 36 ++ .../MemberExternalLoginProviderOptions.cs | 26 ++ .../MemberExternalLoginProviderScheme.cs | 20 ++ .../Security/MemberExternalLoginProviders.cs | 64 ++++ .../MemberExternalSignInAutoLinkOptions.cs | 76 +++++ .../Security/MemberSignInManager.cs | 307 +++++++++++++++++- .../src/assets/fonts/web.config | 8 - .../src/views/common/overlays/user/user.html | 2 +- src/Umbraco.Web.UI.Client/src/web.config | 8 - src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 2 - .../Templates/EditProfile.cshtml | 58 +++- .../PartialViewMacros/Templates/Login.cshtml | 77 +++-- .../Controllers/UmbExternalLoginController.cs | 279 ++++++++++++++++ .../UmbracoBuilder.MemberIdentity.cs | 23 ++ .../UmbracoBuilderExtensions.cs | 2 +- .../Models/ProfileModel.cs | 4 + .../Models/ProfileModelBuilder.cs | 5 +- .../Security/MemberAuthenticationBuilder.cs | 75 +++++ .../Security/MemberExternalLoginsBuilder.cs | 34 ++ .../Security/BackOfficeUserStoreTests.cs | 2 +- .../Services/ExternalLoginServiceTests.cs | 54 +-- .../Security/MemberManagerTests.cs | 2 +- .../Security/MemberUserStoreTests.cs | 4 +- .../Security/MemberSignInManagerTests.cs | 16 +- 53 files changed, 1657 insertions(+), 190 deletions(-) create mode 100644 src/Umbraco.Core/Persistence/Repositories/IExternalLoginWithKeyRepository.cs create mode 100644 src/Umbraco.Core/Services/IExternalLoginWithKeyService.cs create mode 100644 src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_3_0/UpdateExternalLoginToUseKeyInsteadOfId.cs create mode 100644 src/Umbraco.Infrastructure/Security/DeleteExternalLoginsOnMemberDeletedHandler.cs create mode 100644 src/Umbraco.Web.Common/Security/IMemberExternalLoginProviders.cs create mode 100644 src/Umbraco.Web.Common/Security/IMemberSignInManagerExternalLogins.cs create mode 100644 src/Umbraco.Web.Common/Security/MemberExternalLoginProvider.cs create mode 100644 src/Umbraco.Web.Common/Security/MemberExternalLoginProviderOptions.cs create mode 100644 src/Umbraco.Web.Common/Security/MemberExternalLoginProviderScheme.cs create mode 100644 src/Umbraco.Web.Common/Security/MemberExternalLoginProviders.cs create mode 100644 src/Umbraco.Web.Common/Security/MemberExternalSignInAutoLinkOptions.cs delete mode 100644 src/Umbraco.Web.UI.Client/src/assets/fonts/web.config delete mode 100644 src/Umbraco.Web.UI.Client/src/web.config create mode 100644 src/Umbraco.Web.Website/Controllers/UmbExternalLoginController.cs create mode 100644 src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilder.MemberIdentity.cs create mode 100644 src/Umbraco.Web.Website/Security/MemberAuthenticationBuilder.cs create mode 100644 src/Umbraco.Web.Website/Security/MemberExternalLoginsBuilder.cs diff --git a/src/Umbraco.Core/Constants-Security.cs b/src/Umbraco.Core/Constants-Security.cs index b509c12ff5..68601a78b0 100644 --- a/src/Umbraco.Core/Constants-Security.cs +++ b/src/Umbraco.Core/Constants-Security.cs @@ -50,6 +50,7 @@ namespace Umbraco.Cms.Core /// providers need to be setup differently and each auth type for the back office will be prefixed with this value /// public const string BackOfficeExternalAuthenticationTypePrefix = "Umbraco."; + public const string MemberExternalAuthenticationTypePrefix = "UmbracoMembers."; public const string StartContentNodeIdClaimType = "http://umbraco.org/2015/02/identity/claims/backoffice/startcontentnode"; public const string StartMediaNodeIdClaimType = "http://umbraco.org/2015/02/identity/claims/backoffice/startmedianode"; diff --git a/src/Umbraco.Core/Extensions/ClaimsIdentityExtensions.cs b/src/Umbraco.Core/Extensions/ClaimsIdentityExtensions.cs index 9b3674b07b..bceddf1fd6 100644 --- a/src/Umbraco.Core/Extensions/ClaimsIdentityExtensions.cs +++ b/src/Umbraco.Core/Extensions/ClaimsIdentityExtensions.cs @@ -62,6 +62,19 @@ namespace Umbraco.Extensions return username; } + public static string GetEmail(this IIdentity identity) + { + if (identity == null) throw new ArgumentNullException(nameof(identity)); + + string email = null; + if (identity is ClaimsIdentity claimsIdentity) + { + email = claimsIdentity.FindFirstValue(ClaimTypes.Email); + } + + return email; + } + /// /// Returns the first claim value found in the for the given claimType /// diff --git a/src/Umbraco.Core/Persistence/Repositories/IExternalLoginRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IExternalLoginRepository.cs index a685ab67f1..7d9594a3c6 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IExternalLoginRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IExternalLoginRepository.cs @@ -1,15 +1,19 @@ +using System; using System.Collections.Generic; using Umbraco.Cms.Core.Security; namespace Umbraco.Cms.Core.Persistence.Repositories { + public interface IExternalLoginRepository : IReadWriteQueryRepository, IQueryRepository { + /// /// Replaces all external login providers for the user /// /// /// + [Obsolete("Use method that takes guid as param from IExternalLoginWithKeyRepository")] void Save(int userId, IEnumerable logins); /// @@ -17,8 +21,9 @@ namespace Umbraco.Cms.Core.Persistence.Repositories /// /// /// + [Obsolete("Use method that takes guid as param from IExternalLoginWithKeyRepository")] void Save(int userId, IEnumerable tokens); - + [Obsolete("Use method that takes guid as param from IExternalLoginWithKeyRepository")] void DeleteUserLogins(int memberId); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/IExternalLoginWithKeyRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IExternalLoginWithKeyRepository.cs new file mode 100644 index 0000000000..0a4b9e76cf --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/IExternalLoginWithKeyRepository.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using Umbraco.Cms.Core.Security; + +namespace Umbraco.Cms.Core.Persistence.Repositories +{ + + /// + /// Repository for external logins with Guid as key, so it can be shared for members and users + /// + public interface IExternalLoginWithKeyRepository : IReadWriteQueryRepository, IQueryRepository + { + /// + /// Replaces all external login providers for the user/member key + /// + void Save(Guid userOrMemberKey, IEnumerable logins); + + /// + /// Replaces all external login provider tokens for the providers specified for the user/member key + /// + void Save(Guid userOrMemberKey, IEnumerable tokens); + + /// + /// Deletes all external logins for the specified the user/member key + /// + void DeleteUserLogins(Guid userOrMemberKey); + } +} diff --git a/src/Umbraco.Core/Security/IIdentityUserLogin.cs b/src/Umbraco.Core/Security/IIdentityUserLogin.cs index 67ca739509..4e18771a17 100644 --- a/src/Umbraco.Core/Security/IIdentityUserLogin.cs +++ b/src/Umbraco.Core/Security/IIdentityUserLogin.cs @@ -19,7 +19,7 @@ namespace Umbraco.Cms.Core.Security string ProviderKey { get; set; } /// - /// Gets or sets user Id for the user who owns this login + /// Gets or sets user or member key (Guid) for the user/member who owns this login /// string UserId { get; set; } // TODO: This should be able to be used by both users and members diff --git a/src/Umbraco.Core/Services/IExternalLoginService.cs b/src/Umbraco.Core/Services/IExternalLoginService.cs index 787631d500..75f8069f0c 100644 --- a/src/Umbraco.Core/Services/IExternalLoginService.cs +++ b/src/Umbraco.Core/Services/IExternalLoginService.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using Umbraco.Cms.Core.Security; @@ -6,6 +7,7 @@ namespace Umbraco.Cms.Core.Services /// /// Used to store the external login info /// + [Obsolete("Use IExternalLoginServiceWithKey. This will be removed in Umbraco 10")] public interface IExternalLoginService : IService { /// diff --git a/src/Umbraco.Core/Services/IExternalLoginWithKeyService.cs b/src/Umbraco.Core/Services/IExternalLoginWithKeyService.cs new file mode 100644 index 0000000000..bc31f54f8b --- /dev/null +++ b/src/Umbraco.Core/Services/IExternalLoginWithKeyService.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using Umbraco.Cms.Core.Security; + +namespace Umbraco.Cms.Core.Services +{ + public interface IExternalLoginWithKeyService : IService + { + /// + /// Returns all user logins assigned + /// + IEnumerable GetExternalLogins(Guid userOrMemberKey); + + /// + /// Returns all user login tokens assigned + /// + IEnumerable GetExternalLoginTokens(Guid userOrMemberKey); + + /// + /// Returns all logins matching the login info - generally there should only be one but in some cases + /// there might be more than one depending on if an administrator has been editing/removing members + /// + IEnumerable Find(string loginProvider, string providerKey); + + /// + /// Saves the external logins associated with the user + /// + /// + /// The user or member key associated with the logins + /// + /// + /// + /// This will replace all external login provider information for the user + /// + void Save(Guid userOrMemberKey, IEnumerable logins); + + /// + /// Saves the external login tokens associated with the user + /// + /// + /// The user or member key associated with the logins + /// + /// + /// + /// This will replace all external login tokens for the user + /// + void Save(Guid userOrMemberKey,IEnumerable tokens); + + /// + /// Deletes all user logins - normally used when a member is deleted + /// + void DeleteUserLogins(Guid userOrMemberKey); + } +} diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs index d3ebb28f9c..ed2bf67e4a 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs @@ -1,3 +1,4 @@ +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; @@ -29,7 +30,9 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection builder.Services.AddUnique(); builder.Services.AddUnique(); builder.Services.AddUnique(); - builder.Services.AddUnique(); + builder.Services.AddUnique(); + builder.Services.AddUnique(factory => factory.GetRequiredService()); + builder.Services.AddUnique(factory => factory.GetRequiredService()); builder.Services.AddUnique(); builder.Services.AddUnique(); builder.Services.AddUnique(); diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs index 7d74ff13c8..c79cbf9d94 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs @@ -8,10 +8,13 @@ using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Packaging; +using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Routing; +using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services.Implement; using Umbraco.Cms.Infrastructure.Packaging; @@ -64,7 +67,14 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection builder.Services.AddUnique(); builder.Services.AddUnique(); builder.Services.AddUnique(); - builder.Services.AddUnique(); + builder.Services.AddUnique(factory => new ExternalLoginService( + factory.GetRequiredService(), + factory.GetRequiredService(), + factory.GetRequiredService(), + factory.GetRequiredService() + )); + builder.Services.AddUnique(factory => factory.GetRequiredService()); + builder.Services.AddUnique(factory => factory.GetRequiredService()); builder.Services.AddUnique(); builder.Services.AddUnique(); builder.Services.AddTransient(SourcesFactory); diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs index 2b6f2fe6d6..ac11666960 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs @@ -15,6 +15,7 @@ using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_8_9_0; using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_9_0_0; using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_9_1_0; using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_9_2_0; +using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_9_3_0; using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade @@ -269,6 +270,11 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade To("{0571C395-8F0B-44E9-8E3F-47BDD08D817B}"); To("{AD3D3B7F-8E74-45A4-85DB-7FFAD57F9243}"); To("{A2F22F17-5870-4179-8A8D-2362AA4A0A5F}"); + + + // TO 9.3.0 + To("{CA7A1D9D-C9D4-4914-BC0A-459E7B9C3C8C}"); + } } } diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_3_0/UpdateExternalLoginToUseKeyInsteadOfId.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_3_0/UpdateExternalLoginToUseKeyInsteadOfId.cs new file mode 100644 index 0000000000..4c7104e762 --- /dev/null +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_3_0/UpdateExternalLoginToUseKeyInsteadOfId.cs @@ -0,0 +1,77 @@ +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.Persistence; +using Umbraco.Cms.Infrastructure.Persistence.Dtos; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_9_3_0 +{ + public class UpdateExternalLoginToUseKeyInsteadOfId : MigrationBase + { + public UpdateExternalLoginToUseKeyInsteadOfId(IMigrationContext context) : base(context) + { + } + + protected override void Migrate() + { + if (!ColumnExists(ExternalLoginDto.TableName, "userOrMemberKey")) + { + var indexNameToRecreate = "IX_" + ExternalLoginDto.TableName + "_LoginProvider"; + var indexNameToDelete = "IX_" + ExternalLoginDto.TableName + "_userId"; + + if (IndexExists(indexNameToRecreate)) + { + // drop it since the previous migration index was wrong, and we + // need to modify a column that belons to it + Delete.Index(indexNameToRecreate).OnTable(ExternalLoginDto.TableName).Do(); + } + + if (IndexExists(indexNameToDelete)) + { + // drop it since the previous migration index was wrong, and we + // need to modify a column that belons to it + Delete.Index(indexNameToDelete).OnTable(ExternalLoginDto.TableName).Do(); + } + + //special trick to add the column without constraints and return the sql to add them later + AddColumn("userOrMemberKey", out var sqls); + + + if (DatabaseType.IsSqlCe()) + { + var userIds = Database.Fetch(Sql().Select("userId").From()); + + foreach (int userId in userIds) + { + Execute.Sql($"UPDATE {ExternalLoginDto.TableName} SET userOrMemberKey = '{userId.ToGuid()}' WHERE userId = {userId}").Do(); + } + } + else + { + //populate the new columns with the userId as a Guid. Same method as IntExtensions.ToGuid. + Execute.Sql($"UPDATE {ExternalLoginDto.TableName} SET userOrMemberKey = CAST(CONVERT(char(8), CONVERT(BINARY(4), userId), 2) + '-0000-0000-0000-000000000000' AS UNIQUEIDENTIFIER)").Do(); + + } + + //now apply constraints (NOT NULL) to new table + foreach (var sql in sqls) Execute.Sql(sql).Do(); + + //now remove these old columns + Delete.Column("userId").FromTable(ExternalLoginDto.TableName).Do(); + + // create index with the correct definition + Create + .Index(indexNameToRecreate) + .OnTable(ExternalLoginDto.TableName) + .OnColumn("loginProvider").Ascending() + .OnColumn("userOrMemberKey").Ascending() + .WithOptions() + .Unique() + .WithOptions() + .NonClustered() + .Do(); + } + } + + + } +} diff --git a/src/Umbraco.Infrastructure/Persistence/Dtos/ExternalLoginDto.cs b/src/Umbraco.Infrastructure/Persistence/Dtos/ExternalLoginDto.cs index 69bf1b837e..0af1ff83c5 100644 --- a/src/Umbraco.Infrastructure/Persistence/Dtos/ExternalLoginDto.cs +++ b/src/Umbraco.Infrastructure/Persistence/Dtos/ExternalLoginDto.cs @@ -16,13 +16,13 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Dtos [PrimaryKeyColumn] public int Id { get; set; } - // TODO: This is completely missing a FK!!? ... IIRC that is because we want to change this to a GUID - // to support both members and users for external logins and that will not have any referential integrity - // This should be part of the members task for enabling external logins. + [Obsolete("This only exists to ensure you can upgrade using external logins from umbraco version where this was used to the new where it is not used")] + [ResultColumn("userId")] + public int? UserId { get; set; } - [Column("userId")] + [Column("userOrMemberKey")] [Index(IndexTypes.NonClustered)] - public int UserId { get; set; } + public Guid UserOrMemberKey { get; set; } /// /// Used to store the name of the provider (i.e. Facebook, Google) @@ -30,7 +30,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Dtos [Column("loginProvider")] [Length(400)] [NullSetting(NullSetting = NullSettings.NotNull)] - [Index(IndexTypes.UniqueNonClustered, ForColumns = "loginProvider,userId", Name = "IX_" + TableName + "_LoginProvider")] + [Index(IndexTypes.UniqueNonClustered, ForColumns = "loginProvider,userOrMemberKey", Name = "IX_" + TableName + "_LoginProvider")] public string LoginProvider { get; set; } /// diff --git a/src/Umbraco.Infrastructure/Persistence/Factories/ExternalLoginFactory.cs b/src/Umbraco.Infrastructure/Persistence/Factories/ExternalLoginFactory.cs index 82bbb4a40a..1c74dcb8bd 100644 --- a/src/Umbraco.Infrastructure/Persistence/Factories/ExternalLoginFactory.cs +++ b/src/Umbraco.Infrastructure/Persistence/Factories/ExternalLoginFactory.cs @@ -2,6 +2,7 @@ using System; using System.Globalization; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Infrastructure.Persistence.Dtos; +using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.Persistence.Factories { @@ -9,7 +10,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Factories { public static IIdentityUserToken BuildEntity(ExternalLoginTokenDto dto) { - var entity = new IdentityUserToken(dto.Id, dto.ExternalLoginDto.LoginProvider, dto.Name, dto.Value, dto.ExternalLoginDto.UserId.ToString(CultureInfo.InvariantCulture), dto.CreateDate); + var entity = new IdentityUserToken(dto.Id, dto.ExternalLoginDto.LoginProvider, dto.Name, dto.Value, dto.ExternalLoginDto.UserOrMemberKey.ToString(), dto.CreateDate); // reset dirty initial properties (U4-1946) entity.ResetDirtyProperties(false); @@ -18,7 +19,12 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Factories public static IIdentityUserLogin BuildEntity(ExternalLoginDto dto) { - var entity = new IdentityUserLogin(dto.Id, dto.LoginProvider, dto.ProviderKey, dto.UserId.ToString(CultureInfo.InvariantCulture), dto.CreateDate) + + //If there exists a UserId - this means the database is still not migrated. E.g on the upgrade state. + //At this point we have to manually set the key, to ensure external logins can be used to upgrade + var key = dto.UserId.HasValue ? dto.UserId.Value.ToGuid().ToString() : dto.UserOrMemberKey.ToString(); + + var entity = new IdentityUserLogin(dto.Id, dto.LoginProvider, dto.ProviderKey, key, dto.CreateDate) { UserData = dto.UserData }; @@ -36,19 +42,19 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Factories CreateDate = entity.CreateDate, LoginProvider = entity.LoginProvider, ProviderKey = entity.ProviderKey, - UserId = int.Parse(entity.UserId, CultureInfo.InvariantCulture), // TODO: This is temp until we change the ext logins to use GUIDs + UserOrMemberKey = entity.Key, UserData = entity.UserData }; return dto; } - public static ExternalLoginDto BuildDto(int userId, IExternalLogin entity, int? id = null) + public static ExternalLoginDto BuildDto(Guid userOrMemberKey, IExternalLogin entity, int? id = null) { var dto = new ExternalLoginDto { Id = id ?? default, - UserId = userId, + UserOrMemberKey = userOrMemberKey, LoginProvider = entity.LoginProvider, ProviderKey = entity.ProviderKey, UserData = entity.UserData, diff --git a/src/Umbraco.Infrastructure/Persistence/Mappers/ExternalLoginMapper.cs b/src/Umbraco.Infrastructure/Persistence/Mappers/ExternalLoginMapper.cs index 2d47746baa..85db7bf553 100644 --- a/src/Umbraco.Infrastructure/Persistence/Mappers/ExternalLoginMapper.cs +++ b/src/Umbraco.Infrastructure/Persistence/Mappers/ExternalLoginMapper.cs @@ -18,7 +18,8 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Mappers DefineMap(nameof(IdentityUserLogin.CreateDate), nameof(ExternalLoginDto.CreateDate)); DefineMap(nameof(IdentityUserLogin.LoginProvider), nameof(ExternalLoginDto.LoginProvider)); DefineMap(nameof(IdentityUserLogin.ProviderKey), nameof(ExternalLoginDto.ProviderKey)); - DefineMap(nameof(IdentityUserLogin.UserId), nameof(ExternalLoginDto.UserId)); + DefineMap(nameof(IdentityUserLogin.Key), nameof(ExternalLoginDto.UserOrMemberKey)); + DefineMap(nameof(IdentityUserLogin.UserData), nameof(ExternalLoginDto.UserData)); } } } diff --git a/src/Umbraco.Infrastructure/Persistence/Mappers/ExternalLoginTokenMapper.cs b/src/Umbraco.Infrastructure/Persistence/Mappers/ExternalLoginTokenMapper.cs index 4d03031ffd..ca8360c626 100644 --- a/src/Umbraco.Infrastructure/Persistence/Mappers/ExternalLoginTokenMapper.cs +++ b/src/Umbraco.Infrastructure/Persistence/Mappers/ExternalLoginTokenMapper.cs @@ -19,7 +19,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Mappers DefineMap(nameof(IdentityUserToken.Name), nameof(ExternalLoginTokenDto.Name)); DefineMap(nameof(IdentityUserToken.Value), nameof(ExternalLoginTokenDto.Value)); // separate table - DefineMap(nameof(IdentityUserToken.UserId), nameof(ExternalLoginDto.UserId)); + DefineMap(nameof(IdentityUserLogin.Key), nameof(ExternalLoginDto.UserOrMemberKey)); } } } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ExternalLoginRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ExternalLoginRepository.cs index 64200d3bbe..4bb6b8a4eb 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ExternalLoginRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ExternalLoginRepository.cs @@ -6,7 +6,6 @@ using NPoco; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models.Entities; -using Umbraco.Cms.Core.Persistence; using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; @@ -18,22 +17,34 @@ using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement { - // TODO: We should update this to support both users and members. It means we would remove referential integrity from users - // and the user/member key would be a GUID (we also need to add a GUID to users) - internal class ExternalLoginRepository : EntityRepositoryBase, IExternalLoginRepository + internal class ExternalLoginRepository : EntityRepositoryBase, IExternalLoginRepository, IExternalLoginWithKeyRepository { public ExternalLoginRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger) : base(scopeAccessor, cache, logger) { } - public void DeleteUserLogins(int memberId) => Database.Delete("WHERE userId=@userId", new { userId = memberId }); + /// + [Obsolete("Use method that takes guid as param")] + public void DeleteUserLogins(int memberId) => DeleteUserLogins(memberId.ToGuid()); - public void Save(int userId, IEnumerable logins) + /// + [Obsolete("Use method that takes guid as param")] + public void Save(int userId, IEnumerable logins) => Save(userId.ToGuid(), logins); + + /// + [Obsolete("Use method that takes guid as param")] + public void Save(int userId, IEnumerable tokens) => Save(userId.ToGuid(), tokens); + + /// + public void DeleteUserLogins(Guid userOrMemberKey) => Database.Delete("WHERE userOrMemberKey=@userOrMemberKey", new { userOrMemberKey }); + + /// + public void Save(Guid userOrMemberKey, IEnumerable logins) { var sql = Sql() .Select() .From() - .Where(x => x.UserId == userId) + .Where(x => x.UserOrMemberKey == userOrMemberKey) .ForUpdate(); // deduplicate the logins @@ -71,10 +82,10 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement foreach (var u in toUpdate) { - Database.Update(ExternalLoginFactory.BuildDto(userId, u.Value, u.Key)); + Database.Update(ExternalLoginFactory.BuildDto(userOrMemberKey, u.Value, u.Key)); } - Database.InsertBulk(toInsert.Select(i => ExternalLoginFactory.BuildDto(userId, i))); + Database.InsertBulk(toInsert.Select(i => ExternalLoginFactory.BuildDto(userOrMemberKey, i))); } protected override IIdentityUserLogin PerformGet(int id) @@ -217,11 +228,12 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement return Database.ExecuteScalar(sql); } - public void Save(int userId, IEnumerable tokens) + /// + public void Save(Guid userOrMemberKey, IEnumerable tokens) { // get the existing logins (provider + id) var existingUserLogins = Database - .Fetch(GetBaseQuery(false).Where(x => x.UserId == userId)) + .Fetch(GetBaseQuery(false).Where(x => x.UserOrMemberKey == userOrMemberKey)) .ToDictionary(x => x.LoginProvider, x => x.Id); // deduplicate the tokens @@ -231,7 +243,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement Sql sql = GetBaseTokenQuery(true) .WhereIn(x => x.LoginProvider, providers) - .Where(x => x.UserId == userId); + .Where(x => x.UserOrMemberKey == userOrMemberKey); var toUpdate = new Dictionary(); var toDelete = new List(); @@ -289,7 +301,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement .On(x => x.ExternalLoginId, x => x.Id) : Sql() .Select() - .AndSelect(x => x.LoginProvider, x => x.UserId) + .AndSelect(x => x.LoginProvider, x => x.UserOrMemberKey) .From() .InnerJoin() .On(x => x.ExternalLoginId, x => x.Id); diff --git a/src/Umbraco.Infrastructure/Security/BackOfficeUserStore.cs b/src/Umbraco.Infrastructure/Security/BackOfficeUserStore.cs index 67e69c0788..32c0500a79 100644 --- a/src/Umbraco.Infrastructure/Security/BackOfficeUserStore.cs +++ b/src/Umbraco.Infrastructure/Security/BackOfficeUserStore.cs @@ -8,6 +8,7 @@ using System.Security.Claims; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Configuration.Models; @@ -16,6 +17,7 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Web.Common.DependencyInjection; using Umbraco.Extensions; namespace Umbraco.Cms.Core.Security @@ -29,7 +31,7 @@ namespace Umbraco.Cms.Core.Security private readonly IScopeProvider _scopeProvider; private readonly IUserService _userService; private readonly IEntityService _entityService; - private readonly IExternalLoginService _externalLoginService; + private readonly IExternalLoginWithKeyService _externalLoginService; private readonly GlobalSettings _globalSettings; private readonly IUmbracoMapper _mapper; private readonly AppCaches _appCaches; @@ -37,11 +39,12 @@ namespace Umbraco.Cms.Core.Security /// /// Initializes a new instance of the class. /// + [ActivatorUtilitiesConstructor] public BackOfficeUserStore( IScopeProvider scopeProvider, IUserService userService, IEntityService entityService, - IExternalLoginService externalLoginService, + IExternalLoginWithKeyService externalLoginService, IOptions globalSettings, IUmbracoMapper mapper, BackOfficeErrorDescriber describer, @@ -59,6 +62,29 @@ namespace Umbraco.Cms.Core.Security _externalLoginService = externalLoginService; } + [Obsolete("Use ctor injecting IExternalLoginWithKeyService ")] + public BackOfficeUserStore( + IScopeProvider scopeProvider, + IUserService userService, + IEntityService entityService, + IExternalLoginService externalLoginService, + IOptions globalSettings, + IUmbracoMapper mapper, + BackOfficeErrorDescriber describer, + AppCaches appCaches) + : this( + scopeProvider, + userService, + entityService, + StaticServiceProvider.Instance.GetRequiredService(), + globalSettings, + mapper, + describer, + appCaches) + { + + } + /// public override Task CreateAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default) { @@ -104,7 +130,7 @@ namespace Umbraco.Cms.Core.Security if (isLoginsPropertyDirty) { _externalLoginService.Save( - userEntity.Id, + userEntity.Key, user.Logins.Select(x => new ExternalLogin( x.LoginProvider, x.ProviderKey, @@ -114,7 +140,7 @@ namespace Umbraco.Cms.Core.Security if (isTokensPropertyDirty) { _externalLoginService.Save( - userEntity.Id, + userEntity.Key, user.LoginTokens.Select(x => new ExternalLoginToken( x.LoginProvider, x.Name, @@ -156,7 +182,7 @@ namespace Umbraco.Cms.Core.Security if (isLoginsPropertyDirty) { _externalLoginService.Save( - found.Id, + found.Key, user.Logins.Select(x => new ExternalLogin( x.LoginProvider, x.ProviderKey, @@ -166,7 +192,7 @@ namespace Umbraco.Cms.Core.Security if (isTokensPropertyDirty) { _externalLoginService.Save( - found.Id, + found.Key, user.LoginTokens.Select(x => new ExternalLoginToken( x.LoginProvider, x.Name, @@ -190,13 +216,14 @@ namespace Umbraco.Cms.Core.Security throw new ArgumentNullException(nameof(user)); } - IUser found = _userService.GetUserById(UserIdToInt(user.Id)); + var userId = UserIdToInt(user.Id); + IUser found = _userService.GetUserById(userId); if (found != null) { _userService.Delete(found); } - _externalLoginService.DeleteUserLogins(UserIdToInt(user.Id)); + _externalLoginService.DeleteUserLogins(userId.ToGuid()); return Task.FromResult(IdentityResult.Success); } @@ -414,7 +441,7 @@ namespace Umbraco.Cms.Core.Security { if (user != null) { - var userId = UserIdToInt(user.Id); + var userId = UserIdToInt(user.Id).ToGuid(); user.SetLoginsCallback(new Lazy>(() => _externalLoginService.GetExternalLogins(userId))); user.SetTokensCallback(new Lazy>(() => _externalLoginService.GetExternalLoginTokens(userId))); } diff --git a/src/Umbraco.Infrastructure/Security/DeleteExternalLoginsOnMemberDeletedHandler.cs b/src/Umbraco.Infrastructure/Security/DeleteExternalLoginsOnMemberDeletedHandler.cs new file mode 100644 index 0000000000..b2b829d8e1 --- /dev/null +++ b/src/Umbraco.Infrastructure/Security/DeleteExternalLoginsOnMemberDeletedHandler.cs @@ -0,0 +1,30 @@ +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Infrastructure.Security +{ + /// + /// Deletes the external logins for the deleted members. This cannot be handled by the database as there is not foreign keys. + /// + public class DeleteExternalLoginsOnMemberDeletedHandler : INotificationHandler + { + private readonly IExternalLoginWithKeyService _externalLoginWithKeyService; + + /// + /// Initializes a new instance of the class. + /// + public DeleteExternalLoginsOnMemberDeletedHandler(IExternalLoginWithKeyService externalLoginWithKeyService) + => _externalLoginWithKeyService = externalLoginWithKeyService; + + /// + public void Handle(MemberDeletedNotification notification) + { + foreach (IMember member in notification.DeletedEntities) + { + _externalLoginWithKeyService.DeleteUserLogins(member.Key); + } + } + } +} diff --git a/src/Umbraco.Infrastructure/Security/MemberUserStore.cs b/src/Umbraco.Infrastructure/Security/MemberUserStore.cs index d757cfb088..da45e4d888 100644 --- a/src/Umbraco.Infrastructure/Security/MemberUserStore.cs +++ b/src/Umbraco.Infrastructure/Security/MemberUserStore.cs @@ -6,12 +6,14 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.PublishedCache; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Web.Common.DependencyInjection; using Umbraco.Extensions; namespace Umbraco.Cms.Core.Security @@ -26,6 +28,7 @@ namespace Umbraco.Cms.Core.Security private readonly IUmbracoMapper _mapper; private readonly IScopeProvider _scopeProvider; private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; + private readonly IExternalLoginWithKeyService _externalLoginService; /// /// Initializes a new instance of the class for the members identity store @@ -34,18 +37,48 @@ namespace Umbraco.Cms.Core.Security /// The mapper for properties /// The scope provider /// The error describer + /// The external login service + [ActivatorUtilitiesConstructor] public MemberUserStore( IMemberService memberService, IUmbracoMapper mapper, IScopeProvider scopeProvider, IdentityErrorDescriber describer, - IPublishedSnapshotAccessor publishedSnapshotAccessor) + IPublishedSnapshotAccessor publishedSnapshotAccessor, + IExternalLoginWithKeyService externalLoginService + ) : base(describer) { _memberService = memberService ?? throw new ArgumentNullException(nameof(memberService)); _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper)); _scopeProvider = scopeProvider ?? throw new ArgumentNullException(nameof(scopeProvider)); _publishedSnapshotAccessor = publishedSnapshotAccessor; + _externalLoginService = externalLoginService; + } + + [Obsolete("Use ctor with IExternalLoginWithKeyService param")] + public MemberUserStore( + IMemberService memberService, + IUmbracoMapper mapper, + IScopeProvider scopeProvider, + IdentityErrorDescriber describer, + IPublishedSnapshotAccessor publishedSnapshotAccessor, + IExternalLoginService externalLoginService) + : this(memberService, mapper, scopeProvider, describer, publishedSnapshotAccessor, StaticServiceProvider.Instance.GetRequiredService()) + { + + } + + [Obsolete("Use ctor with IExternalLoginWithKeyService param")] + public MemberUserStore( + IMemberService memberService, + IUmbracoMapper mapper, + IScopeProvider scopeProvider, + IdentityErrorDescriber describer, + IPublishedSnapshotAccessor publishedSnapshotAccessor) + : this(memberService, mapper, scopeProvider, describer, publishedSnapshotAccessor, StaticServiceProvider.Instance.GetRequiredService()) + { + } /// @@ -83,18 +116,29 @@ namespace Umbraco.Cms.Core.Security user.Id = UserIdToString(memberEntity.Id); user.Key = memberEntity.Key; - // [from backofficeuser] we have to remember whether Logins property is dirty, since the UpdateMemberProperties will reset it. - // var isLoginsPropertyDirty = user.IsPropertyDirty(nameof(MembersIdentityUser.Logins)); - // TODO: confirm re externallogins implementation - //if (isLoginsPropertyDirty) - //{ - // _externalLoginService.Save( - // user.Id, - // user.Logins.Select(x => new ExternalLogin( - // x.LoginProvider, - // x.ProviderKey, - // x.UserData))); - //} + // we have to remember whether Logins property is dirty, since the UpdateMemberProperties will reset it. + var isLoginsPropertyDirty = user.IsPropertyDirty(nameof(MemberIdentityUser.Logins)); + var isTokensPropertyDirty = user.IsPropertyDirty(nameof(MemberIdentityUser.LoginTokens)); + + if (isLoginsPropertyDirty) + { + _externalLoginService.Save( + memberEntity.Key, + user.Logins.Select(x => new ExternalLogin( + x.LoginProvider, + x.ProviderKey, + x.UserData))); + } + + if (isTokensPropertyDirty) + { + _externalLoginService.Save( + memberEntity.Key, + user.LoginTokens.Select(x => new ExternalLoginToken( + x.LoginProvider, + x.Name, + x.Value))); + } return Task.FromResult(IdentityResult.Success); @@ -142,17 +186,15 @@ namespace Umbraco.Cms.Core.Security _memberService.SetLastLogin(found.Username, DateTime.Now); } - // TODO: when to implement external login service? - - //if (isLoginsPropertyDirty) - //{ - // _externalLoginService.Save( - // found.Id, - // user.Logins.Select(x => new ExternalLogin( - // x.LoginProvider, - // x.ProviderKey, - // x.UserData))); - //} + if (isLoginsPropertyDirty) + { + _externalLoginService.Save( + found.Key, + user.Logins.Select(x => new ExternalLogin( + x.LoginProvider, + x.ProviderKey, + x.UserData))); + } } return Task.FromResult(IdentityResult.Success); @@ -181,8 +223,7 @@ namespace Umbraco.Cms.Core.Security _memberService.Delete(found); } - // TODO: when to implement external login service? - //_externalLoginService.DeleteUserLogins(UserIdToInt(user.Id)); + _externalLoginService.DeleteUserLogins(user.Key); return Task.FromResult(IdentityResult.Success); } @@ -203,7 +244,8 @@ namespace Umbraco.Cms.Core.Security throw new ArgumentNullException(nameof(userId)); } - IMember user = _memberService.GetById(UserIdToInt(userId)); + + IMember user = Guid.TryParse(userId, out var key) ? _memberService.GetByKey(key) : _memberService.GetById(UserIdToInt(userId)); if (user == null) { return Task.FromResult((MemberIdentityUser)null); @@ -375,10 +417,7 @@ namespace Umbraco.Cms.Core.Security throw new ArgumentNullException(nameof(providerKey)); } - var logins = new List(); - - // TODO: external login needed - //_externalLoginService.Find(loginProvider, providerKey).ToList(); + var logins = _externalLoginService.Find(loginProvider, providerKey).ToList(); if (logins.Count == 0) { return Task.FromResult((IdentityUserLogin)null); @@ -492,8 +531,8 @@ namespace Umbraco.Cms.Core.Security { if (user != null) { - //TODO: implement - //user.SetLoginsCallback(new Lazy>(() => _externalLoginService.GetAll(UserIdToInt(user.Id)))); + user.SetLoginsCallback(new Lazy>(() => _externalLoginService.GetExternalLogins(user.Key))); + user.SetTokensCallback(new Lazy>(() => _externalLoginService.GetExternalLoginTokens(user.Key))); } return user; diff --git a/src/Umbraco.Infrastructure/Security/UmbracoUserStore.cs b/src/Umbraco.Infrastructure/Security/UmbracoUserStore.cs index 6a2325c316..aaaaed55e7 100644 --- a/src/Umbraco.Infrastructure/Security/UmbracoUserStore.cs +++ b/src/Umbraco.Infrastructure/Security/UmbracoUserStore.cs @@ -34,6 +34,12 @@ namespace Umbraco.Cms.Core.Security return result; } + if(Guid.TryParse(userId, out var key)) + { + // Reverse the IntExtensions.ToGuid + return BitConverter.ToInt32(key.ToByteArray(), 0); + } + throw new InvalidOperationException($"Unable to convert user ID ({userId})to int using InvariantCulture"); } diff --git a/src/Umbraco.Infrastructure/Services/Implement/ExternalLoginService.cs b/src/Umbraco.Infrastructure/Services/Implement/ExternalLoginService.cs index 079971be24..589ac288c8 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/ExternalLoginService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/ExternalLoginService.cs @@ -1,44 +1,77 @@ +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Web.Common.DependencyInjection; +using Umbraco.Extensions; namespace Umbraco.Cms.Core.Services.Implement { - public class ExternalLoginService : RepositoryService, IExternalLoginService + public class ExternalLoginService : RepositoryService, IExternalLoginService, IExternalLoginWithKeyService { - private readonly IExternalLoginRepository _externalLoginRepository; + private readonly IExternalLoginWithKeyRepository _externalLoginRepository; public ExternalLoginService(IScopeProvider provider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory, - IExternalLoginRepository externalLoginRepository) + IExternalLoginWithKeyRepository externalLoginRepository) : base(provider, loggerFactory, eventMessagesFactory) { _externalLoginRepository = externalLoginRepository; } + [Obsolete("Use ctor injecting IExternalLoginWithKeyRepository")] + public ExternalLoginService(IScopeProvider provider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory, + IExternalLoginRepository externalLoginRepository) + : this(provider, loggerFactory, eventMessagesFactory, StaticServiceProvider.Instance.GetRequiredService()) + { + } + /// + [Obsolete("Use overload that takes a user/member key (Guid).")] public IEnumerable GetExternalLogins(int userId) + => GetExternalLogins(userId.ToGuid()); + + /// + [Obsolete("Use overload that takes a user/member key (Guid).")] + public IEnumerable GetExternalLoginTokens(int userId) => + GetExternalLoginTokens(userId.ToGuid()); + + /// + [Obsolete("Use overload that takes a user/member key (Guid).")] + public void Save(int userId, IEnumerable logins) + => Save(userId.ToGuid(), logins); + + /// + [Obsolete("Use overload that takes a user/member key (Guid).")] + public void Save(int userId, IEnumerable tokens) + => Save(userId.ToGuid(), tokens); + + /// + [Obsolete("Use overload that takes a user/member key (Guid).")] + public void DeleteUserLogins(int userId) + => DeleteUserLogins(userId.ToGuid()); + + /// + public IEnumerable GetExternalLogins(Guid userOrMemberKey) { using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { - // TODO: This is temp until we update the external service to support guids for both users and members - var asString = userId.ToString(CultureInfo.InvariantCulture); - return _externalLoginRepository.Get(Query().Where(x => x.UserId == asString)) + return _externalLoginRepository.Get(Query().Where(x => x.Key == userOrMemberKey)) .ToList(); } } - public IEnumerable GetExternalLoginTokens(int userId) + /// + public IEnumerable GetExternalLoginTokens(Guid userOrMemberKey) { using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { - // TODO: This is temp until we update the external service to support guids for both users and members - var asString = userId.ToString(CultureInfo.InvariantCulture); - return _externalLoginRepository.Get(Query().Where(x => x.UserId == asString)) + return _externalLoginRepository.Get(Query().Where(x => x.Key == userOrMemberKey)) .ToList(); } } @@ -55,30 +88,31 @@ namespace Umbraco.Cms.Core.Services.Implement } /// - public void Save(int userId, IEnumerable logins) + public void Save(Guid userOrMemberKey, IEnumerable logins) { using (var scope = ScopeProvider.CreateScope()) { - _externalLoginRepository.Save(userId, logins); - scope.Complete(); - } - } - - public void Save(int userId, IEnumerable tokens) - { - using (var scope = ScopeProvider.CreateScope()) - { - _externalLoginRepository.Save(userId, tokens); + _externalLoginRepository.Save(userOrMemberKey, logins); scope.Complete(); } } /// - public void DeleteUserLogins(int userId) + public void Save(Guid userOrMemberKey, IEnumerable tokens) { using (var scope = ScopeProvider.CreateScope()) { - _externalLoginRepository.DeleteUserLogins(userId); + _externalLoginRepository.Save(userOrMemberKey, tokens); + scope.Complete(); + } + } + + /// + public void DeleteUserLogins(Guid userOrMemberKey) + { + using (var scope = ScopeProvider.CreateScope()) + { + _externalLoginRepository.DeleteUserLogins(userOrMemberKey); scope.Complete(); } } diff --git a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilder.BackOfficeIdentity.cs b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilder.BackOfficeIdentity.cs index 4e95236b5f..e9cc213598 100644 --- a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilder.BackOfficeIdentity.cs +++ b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilder.BackOfficeIdentity.cs @@ -2,9 +2,15 @@ using System; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Net; +using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Services; using Umbraco.Cms.Web.BackOffice.Security; using Umbraco.Cms.Web.Common.AspNetCore; using Umbraco.Cms.Web.Common.Security; @@ -27,7 +33,16 @@ namespace Umbraco.Extensions builder.BuildUmbracoBackOfficeIdentity() .AddDefaultTokenProviders() - .AddUserStore() + .AddUserStore, BackOfficeUserStore>(factory => new BackOfficeUserStore( + factory.GetRequiredService(), + factory.GetRequiredService(), + factory.GetRequiredService(), + factory.GetRequiredService(), + factory.GetRequiredService>(), + factory.GetRequiredService(), + factory.GetRequiredService(), + factory.GetRequiredService() + )) .AddUserManager() .AddSignInManager() .AddClaimsPrincipalFactory() diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeAuthenticationBuilder.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeAuthenticationBuilder.cs index 80a9d920a1..d62edcc1f9 100644 --- a/src/Umbraco.Web.BackOffice/Security/BackOfficeAuthenticationBuilder.cs +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeAuthenticationBuilder.cs @@ -62,6 +62,11 @@ namespace Umbraco.Cms.Web.BackOffice.Security { public void PostConfigure(string name, TOptions options) { + if (!name.StartsWith(Constants.Security.BackOfficeExternalAuthenticationTypePrefix)) + { + return; + } + options.SignInScheme = Constants.Security.BackOfficeExternalAuthenticationType; } } diff --git a/src/Umbraco.Web.BackOffice/Security/ExternalSignInAutoLinkOptions.cs b/src/Umbraco.Web.BackOffice/Security/ExternalSignInAutoLinkOptions.cs index 504c74d90e..1f5a7fad33 100644 --- a/src/Umbraco.Web.BackOffice/Security/ExternalSignInAutoLinkOptions.cs +++ b/src/Umbraco.Web.BackOffice/Security/ExternalSignInAutoLinkOptions.cs @@ -3,6 +3,7 @@ using System.Runtime.Serialization; using Microsoft.AspNetCore.Identity; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Web.Common.Security; using SecurityConstants = Umbraco.Cms.Core.Constants.Security; namespace Umbraco.Cms.Web.BackOffice.Security @@ -13,11 +14,12 @@ namespace Umbraco.Cms.Web.BackOffice.Security public class ExternalSignInAutoLinkOptions { /// - /// Creates a new instance + /// Initializes a new instance of the class. /// /// /// If null, the default will be the 'editor' group /// + /// public ExternalSignInAutoLinkOptions( bool autoLinkExternalAccount = false, string[] defaultUserGroups = null, @@ -30,12 +32,6 @@ namespace Umbraco.Cms.Web.BackOffice.Security _defaultCulture = defaultCulture; } - /// - /// By default this is true which allows the user to manually link and unlink the external provider, if set to false the back office user - /// will not see and cannot perform manual linking or unlinking of the external provider. - /// - public bool AllowManualLinking { get; } - /// /// A callback executed during account auto-linking and before the user is persisted /// @@ -50,10 +46,16 @@ namespace Umbraco.Cms.Web.BackOffice.Security public Func OnExternalLogin { get; set; } /// - /// Flag indicating if logging in with the external provider should auto-link/create a local user + /// Gets a value indicating whether flag indicating if logging in with the external provider should auto-link/create a local user /// public bool AutoLinkExternalAccount { get; } + /// + /// By default this is true which allows the user to manually link and unlink the external provider, if set to false the back office user + /// will not see and cannot perform manual linking or unlinking of the external provider. + /// + public bool AllowManualLinking { get; protected set; } + /// /// The default user groups to assign to the created local user linked /// @@ -64,7 +66,7 @@ namespace Umbraco.Cms.Web.BackOffice.Security /// /// The default Culture to use for auto-linking users /// - // TODO: Should we use IDefaultCultureAccessor here intead? + // TODO: Should we use IDefaultCultureAccessor here instead? public string GetUserAutoLinkCulture(GlobalSettings globalSettings) => _defaultCulture ?? globalSettings.DefaultUILanguage; } } diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.MembersIdentity.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.MembersIdentity.cs index c6856f8f19..66badc479e 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.MembersIdentity.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.MembersIdentity.cs @@ -1,7 +1,18 @@ +using System; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Mapping; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.PublishedCache; +using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.Security; using Umbraco.Cms.Web.Common.Security; namespace Umbraco.Extensions @@ -34,14 +45,24 @@ namespace Umbraco.Extensions services.AddIdentity() .AddDefaultTokenProviders() - .AddUserStore() + .AddUserStore, MemberUserStore>(factory => new MemberUserStore( + factory.GetRequiredService(), + factory.GetRequiredService(), + factory.GetRequiredService(), + factory.GetRequiredService(), + factory.GetRequiredService(), + factory.GetRequiredService() + )) .AddRoleStore() .AddRoleManager() .AddMemberManager() .AddSignInManager() + .AddSignInManager() .AddErrorDescriber() .AddUserConfirmation>(); + + builder.AddNotificationHandler(); services.ConfigureOptions(); services.AddScoped(x => (IMemberUserStore)x.GetRequiredService>()); @@ -50,6 +71,8 @@ namespace Umbraco.Extensions services.ConfigureOptions(); services.ConfigureOptions(); + services.AddUnique(); + return builder; } } diff --git a/src/Umbraco.Web.Common/Extensions/IdentityBuilderExtensions.cs b/src/Umbraco.Web.Common/Extensions/IdentityBuilderExtensions.cs index 77b9f6c8dd..f1d2ac4a3d 100644 --- a/src/Umbraco.Web.Common/Extensions/IdentityBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/IdentityBuilderExtensions.cs @@ -1,3 +1,4 @@ +using System; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.DependencyInjection; @@ -50,5 +51,13 @@ namespace Umbraco.Extensions identityBuilder.Services.AddScoped(typeof(TInterface), typeof(TSignInManager)); return identityBuilder; } + + + public static IdentityBuilder AddUserStore(this IdentityBuilder identityBuilder, Func implementationFactory) + where TStore : class, TInterface + { + identityBuilder.Services.AddScoped(typeof(TInterface), implementationFactory); + return identityBuilder; + } } } diff --git a/src/Umbraco.Web.Common/Security/IMemberExternalLoginProviders.cs b/src/Umbraco.Web.Common/Security/IMemberExternalLoginProviders.cs new file mode 100644 index 0000000000..b3d6813c2f --- /dev/null +++ b/src/Umbraco.Web.Common/Security/IMemberExternalLoginProviders.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Umbraco.Cms.Web.Common.Security +{ + + /// + /// Service to return instances + /// + public interface IMemberExternalLoginProviders + { + /// + /// Get the for the specified scheme + /// + /// + /// + Task GetAsync(string authenticationType); + + /// + /// Get all registered + /// + /// + Task> GetMemberProvidersAsync(); + } + +} diff --git a/src/Umbraco.Web.Common/Security/IMemberSignInManager.cs b/src/Umbraco.Web.Common/Security/IMemberSignInManager.cs index cc6b0e88b9..4ba5caca9b 100644 --- a/src/Umbraco.Web.Common/Security/IMemberSignInManager.cs +++ b/src/Umbraco.Web.Common/Security/IMemberSignInManager.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; using Umbraco.Cms.Core.Security; + namespace Umbraco.Cms.Web.Common.Security { public interface IMemberSignInManager diff --git a/src/Umbraco.Web.Common/Security/IMemberSignInManagerExternalLogins.cs b/src/Umbraco.Web.Common/Security/IMemberSignInManagerExternalLogins.cs new file mode 100644 index 0000000000..3599a028f4 --- /dev/null +++ b/src/Umbraco.Web.Common/Security/IMemberSignInManagerExternalLogins.cs @@ -0,0 +1,16 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Identity; + +namespace Umbraco.Cms.Web.Common.Security +{ + [Obsolete("This interface will be merged with IMemberSignInManager in Umbraco 10")] + public interface IMemberSignInManagerExternalLogins : IMemberSignInManager + { + AuthenticationProperties ConfigureExternalAuthenticationProperties(string provider, string redirectUrl, string userId = null); + Task GetExternalLoginInfoAsync(string expectedXsrf = null); + Task UpdateExternalAuthenticationTokensAsync(ExternalLoginInfo externalLogin); + Task ExternalLoginSignInAsync(ExternalLoginInfo loginInfo, bool isPersistent, bool bypassTwoFactor = false); + } +} diff --git a/src/Umbraco.Web.Common/Security/MemberExternalLoginProvider.cs b/src/Umbraco.Web.Common/Security/MemberExternalLoginProvider.cs new file mode 100644 index 0000000000..9681d47413 --- /dev/null +++ b/src/Umbraco.Web.Common/Security/MemberExternalLoginProvider.cs @@ -0,0 +1,36 @@ +using System; +using Microsoft.Extensions.Options; + +namespace Umbraco.Cms.Web.Common.Security +{ + /// + /// An external login (OAuth) provider for the members + /// + public class MemberExternalLoginProvider : IEquatable + { + public MemberExternalLoginProvider( + string authenticationType, + IOptionsMonitor properties) + { + if (properties is null) + { + throw new ArgumentNullException(nameof(properties)); + } + + AuthenticationType = authenticationType ?? throw new ArgumentNullException(nameof(authenticationType)); + Options = properties.Get(authenticationType); + } + + /// + /// The authentication "Scheme" + /// + public string AuthenticationType { get; } + + public MemberExternalLoginProviderOptions Options { get; } + + public override bool Equals(object obj) => Equals(obj as MemberExternalLoginProvider); + public bool Equals(MemberExternalLoginProvider other) => other != null && AuthenticationType == other.AuthenticationType; + public override int GetHashCode() => HashCode.Combine(AuthenticationType); + } + +} diff --git a/src/Umbraco.Web.Common/Security/MemberExternalLoginProviderOptions.cs b/src/Umbraco.Web.Common/Security/MemberExternalLoginProviderOptions.cs new file mode 100644 index 0000000000..ea93a522da --- /dev/null +++ b/src/Umbraco.Web.Common/Security/MemberExternalLoginProviderOptions.cs @@ -0,0 +1,26 @@ +namespace Umbraco.Cms.Web.Common.Security +{ + /// + /// Options used to configure member external login providers + /// + public class MemberExternalLoginProviderOptions + { + public MemberExternalLoginProviderOptions( + MemberExternalSignInAutoLinkOptions autoLinkOptions = null, + bool autoRedirectLoginToExternalProvider = false, + string customBackOfficeView = null) + { + AutoLinkOptions = autoLinkOptions ?? new MemberExternalSignInAutoLinkOptions(); + } + + public MemberExternalLoginProviderOptions() + { + } + + /// + /// Options used to control how users can be auto-linked/created/updated based on the external login provider + /// + public MemberExternalSignInAutoLinkOptions AutoLinkOptions { get; set; } = new MemberExternalSignInAutoLinkOptions(); + + } +} diff --git a/src/Umbraco.Web.Common/Security/MemberExternalLoginProviderScheme.cs b/src/Umbraco.Web.Common/Security/MemberExternalLoginProviderScheme.cs new file mode 100644 index 0000000000..600405b638 --- /dev/null +++ b/src/Umbraco.Web.Common/Security/MemberExternalLoginProviderScheme.cs @@ -0,0 +1,20 @@ +using System; +using Microsoft.AspNetCore.Authentication; + +namespace Umbraco.Cms.Web.Common.Security +{ + public class MemberExternalLoginProviderScheme + { + public MemberExternalLoginProviderScheme( + MemberExternalLoginProvider externalLoginProvider, + AuthenticationScheme authenticationScheme) + { + ExternalLoginProvider = externalLoginProvider ?? throw new ArgumentNullException(nameof(externalLoginProvider)); + AuthenticationScheme = authenticationScheme ?? throw new ArgumentNullException(nameof(authenticationScheme)); + } + + public MemberExternalLoginProvider ExternalLoginProvider { get; } + public AuthenticationScheme AuthenticationScheme { get; } + } + +} diff --git a/src/Umbraco.Web.Common/Security/MemberExternalLoginProviders.cs b/src/Umbraco.Web.Common/Security/MemberExternalLoginProviders.cs new file mode 100644 index 0000000000..28102c434f --- /dev/null +++ b/src/Umbraco.Web.Common/Security/MemberExternalLoginProviders.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Web.Common.Security +{ + + /// + public class MemberExternalLoginProviders : IMemberExternalLoginProviders + { + private readonly Dictionary _externalLogins; + private readonly IAuthenticationSchemeProvider _authenticationSchemeProvider; + + public MemberExternalLoginProviders( + IEnumerable externalLogins, + IAuthenticationSchemeProvider authenticationSchemeProvider) + { + _externalLogins = externalLogins.ToDictionary(x => x.AuthenticationType); + _authenticationSchemeProvider = authenticationSchemeProvider; + } + + /// + public async Task GetAsync(string authenticationType) + { + var schemaName = + authenticationType.EnsureStartsWith(Core.Constants.Security.MemberExternalAuthenticationTypePrefix); + + if (!_externalLogins.TryGetValue(schemaName, out MemberExternalLoginProvider provider)) + { + return null; + } + + // get the associated scheme + AuthenticationScheme associatedScheme = await _authenticationSchemeProvider.GetSchemeAsync(provider.AuthenticationType); + + if (associatedScheme == null) + { + throw new InvalidOperationException("No authentication scheme registered for " + provider.AuthenticationType); + } + + return new MemberExternalLoginProviderScheme(provider, associatedScheme); + } + + /// + public async Task> GetMemberProvidersAsync() + { + var providersWithSchemes = new List(); + foreach (MemberExternalLoginProvider login in _externalLogins.Values) + { + // get the associated scheme + AuthenticationScheme associatedScheme = await _authenticationSchemeProvider.GetSchemeAsync(login.AuthenticationType); + + providersWithSchemes.Add(new MemberExternalLoginProviderScheme(login, associatedScheme)); + } + + return providersWithSchemes; + } + + } + +} diff --git a/src/Umbraco.Web.Common/Security/MemberExternalSignInAutoLinkOptions.cs b/src/Umbraco.Web.Common/Security/MemberExternalSignInAutoLinkOptions.cs new file mode 100644 index 0000000000..42dcf6d56f --- /dev/null +++ b/src/Umbraco.Web.Common/Security/MemberExternalSignInAutoLinkOptions.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; +using Microsoft.AspNetCore.Identity; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Security; +using SecurityConstants = Umbraco.Cms.Core.Constants.Security; + +namespace Umbraco.Cms.Web.Common.Security +{ + /// + /// Options used to configure auto-linking external OAuth providers + /// + public class MemberExternalSignInAutoLinkOptions + { + private readonly string _defaultCulture; + + /// + /// Initializes a new instance of the class. + /// + public MemberExternalSignInAutoLinkOptions( + bool autoLinkExternalAccount = false, + bool defaultIsApproved = true, + string defaultMemberTypeAlias = Core.Constants.Conventions.MemberTypes.DefaultAlias, + string defaultCulture = null, + IEnumerable defaultMemberGroups = null) + { + AutoLinkExternalAccount = autoLinkExternalAccount; + DefaultIsApproved = defaultIsApproved; + DefaultMemberTypeAlias = defaultMemberTypeAlias; + _defaultCulture = defaultCulture; + DefaultMemberGroups = defaultMemberGroups ?? Array.Empty(); + } + + /// + /// A callback executed during account auto-linking and before the user is persisted + /// + [IgnoreDataMember] + public Action OnAutoLinking { get; set; } + + /// + /// A callback executed during every time a user authenticates using an external login. + /// returns a boolean indicating if sign in should continue or not. + /// + [IgnoreDataMember] + public Func OnExternalLogin { get; set; } + + /// + /// Gets a value indicating whether flag indicating if logging in with the external provider should auto-link/create a + /// local user + /// + public bool AutoLinkExternalAccount { get; } + + /// + /// Gets the member type alias that auto linked members are created as + /// + public string DefaultMemberTypeAlias { get; } + + /// + /// Gets the IsApproved value for auto linked members. + /// + public bool DefaultIsApproved { get; } + + /// + /// Gets the default member groups to add the user in. + /// + public IEnumerable DefaultMemberGroups { get; } + + /// + /// The default Culture to use for auto-linking users + /// + // TODO: Should we use IDefaultCultureAccessor here instead? + public string GetUserAutoLinkCulture(GlobalSettings globalSettings) => + _defaultCulture ?? globalSettings.DefaultUILanguage; + } +} diff --git a/src/Umbraco.Web.Common/Security/MemberSignInManager.cs b/src/Umbraco.Web.Common/Security/MemberSignInManager.cs index 40cc17667d..6407c4fac8 100644 --- a/src/Umbraco.Web.Common/Security/MemberSignInManager.cs +++ b/src/Umbraco.Web.Common/Security/MemberSignInManager.cs @@ -1,12 +1,17 @@ using System; using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Web.Common.DependencyInjection; +using Umbraco.Extensions; namespace Umbraco.Cms.Web.Common.Security { @@ -14,8 +19,25 @@ namespace Umbraco.Cms.Web.Common.Security /// /// The sign in manager for members /// - public class MemberSignInManager : UmbracoSignInManager, IMemberSignInManager + public class MemberSignInManager : UmbracoSignInManager, IMemberSignInManagerExternalLogins { + private readonly IMemberExternalLoginProviders _memberExternalLoginProviders; + + public MemberSignInManager( + UserManager memberManager, + IHttpContextAccessor contextAccessor, + IUserClaimsPrincipalFactory claimsFactory, + IOptions optionsAccessor, + ILogger> logger, + IAuthenticationSchemeProvider schemes, + IUserConfirmation confirmation, + IMemberExternalLoginProviders memberExternalLoginProviders) : + base(memberManager, contextAccessor, claimsFactory, optionsAccessor, logger, schemes, confirmation) + { + _memberExternalLoginProviders = memberExternalLoginProviders; + } + + [Obsolete("Use ctor with all params")] public MemberSignInManager( UserManager memberManager, IHttpContextAccessor contextAccessor, @@ -24,7 +46,7 @@ namespace Umbraco.Cms.Web.Common.Security ILogger> logger, IAuthenticationSchemeProvider schemes, IUserConfirmation confirmation) : - base(memberManager, contextAccessor, claimsFactory, optionsAccessor, logger, schemes, confirmation) + this(memberManager, contextAccessor, claimsFactory, optionsAccessor, logger, schemes, confirmation, StaticServiceProvider.Instance.GetRequiredService()) { } // use default scheme for members @@ -64,16 +86,289 @@ namespace Umbraco.Cms.Web.Common.Security => throw new NotImplementedException("Two factor is not yet implemented for members"); /// - public override Task GetExternalLoginInfoAsync(string expectedXsrf = null) - => throw new NotImplementedException("External login is not yet implemented for members"); + public override async Task GetExternalLoginInfoAsync(string expectedXsrf = null) + { + // borrowed from https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs#L422 + // to replace the auth scheme + + var auth = await Context.AuthenticateAsync(ExternalAuthenticationType); + var items = auth?.Properties?.Items; + if (auth?.Principal == null || items == null) + { + Logger.LogDebug(auth?.Failure ?? new NullReferenceException("Context.AuthenticateAsync(ExternalAuthenticationType) is null"), + "The external login authentication failed. No user Principal or authentication items was resolved."); + return null; + } + + if (!items.ContainsKey(UmbracoSignInMgrLoginProviderKey)) + { + throw new InvalidOperationException($"The external login authenticated successfully but the key {UmbracoSignInMgrLoginProviderKey} was not found in the authentication properties. Ensure you call SignInManager.ConfigureExternalAuthenticationProperties before issuing a ChallengeResult."); + } + + if (expectedXsrf != null) + { + if (!items.ContainsKey(UmbracoSignInMgrXsrfKey)) + { + return null; + } + var userId = items[UmbracoSignInMgrXsrfKey]; + if (userId != expectedXsrf) + { + return null; + } + } + + var providerKey = auth.Principal.FindFirstValue(ClaimTypes.NameIdentifier); + if (providerKey == null || items[UmbracoSignInMgrLoginProviderKey] is not string provider) + { + return null; + } + + var providerDisplayName = (await GetExternalAuthenticationSchemesAsync()).FirstOrDefault(p => p.Name == provider)?.DisplayName ?? provider; + return new ExternalLoginInfo(auth.Principal, provider, providerKey, providerDisplayName) + { + AuthenticationTokens = auth.Properties.GetTokens(), + AuthenticationProperties = auth.Properties + }; + } + + /// + /// Custom ExternalLoginSignInAsync overload for handling external sign in with auto-linking + /// + public async Task ExternalLoginSignInAsync(ExternalLoginInfo loginInfo, bool isPersistent, bool bypassTwoFactor = false) + { + // borrowed from https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs + // to be able to deal with auto-linking and reduce duplicate lookups + + var autoLinkOptions = (await _memberExternalLoginProviders.GetAsync(loginInfo.LoginProvider))?.ExternalLoginProvider?.Options?.AutoLinkOptions; + var user = await UserManager.FindByLoginAsync(loginInfo.LoginProvider, loginInfo.ProviderKey); + if (user == null) + { + // user doesn't exist so see if we can auto link + return await AutoLinkAndSignInExternalAccount(loginInfo, autoLinkOptions); + } + + if (autoLinkOptions != null && autoLinkOptions.OnExternalLogin != null) + { + var shouldSignIn = autoLinkOptions.OnExternalLogin(user, loginInfo); + if (shouldSignIn == false) + { + LogFailedExternalLogin(loginInfo, user); + return ExternalLoginSignInResult.NotAllowed; + } + } + + var error = await PreSignInCheck(user); + if (error != null) + { + return error; + } + return await SignInOrTwoFactorAsync(user, isPersistent, loginInfo.LoginProvider, bypassTwoFactor); + } + + + /// + /// Used for auto linking/creating user accounts for external logins + /// + /// + /// + /// + private async Task AutoLinkAndSignInExternalAccount(ExternalLoginInfo loginInfo, MemberExternalSignInAutoLinkOptions autoLinkOptions) + { + // If there are no autolink options then the attempt is failed (user does not exist) + if (autoLinkOptions == null || !autoLinkOptions.AutoLinkExternalAccount) + { + return SignInResult.Failed; + } + + var email = loginInfo.Principal.FindFirstValue(ClaimTypes.Email); + + //we are allowing auto-linking/creating of local accounts + if (email.IsNullOrWhiteSpace()) + { + return AutoLinkSignInResult.FailedNoEmail; + } + else + { + //Now we need to perform the auto-link, so first we need to lookup/create a user with the email address + var autoLinkUser = await UserManager.FindByEmailAsync(email); + if (autoLinkUser != null) + { + try + { + //call the callback if one is assigned + autoLinkOptions.OnAutoLinking?.Invoke(autoLinkUser, loginInfo); + } + catch (Exception ex) + { + Logger.LogError(ex, "Could not link login provider {LoginProvider}.", loginInfo.LoginProvider); + return AutoLinkSignInResult.FailedException(ex.Message); + } + + var shouldLinkUser = autoLinkOptions.OnExternalLogin == null || autoLinkOptions.OnExternalLogin(autoLinkUser, loginInfo); + if (shouldLinkUser) + { + return await LinkUser(autoLinkUser, loginInfo); + } + else + { + LogFailedExternalLogin(loginInfo, autoLinkUser); + return ExternalLoginSignInResult.NotAllowed; + } + } + else + { + var name = loginInfo.Principal?.Identity?.Name; + if (name.IsNullOrWhiteSpace()) throw new InvalidOperationException("The Name value cannot be null"); + + autoLinkUser = MemberIdentityUser.CreateNew(email, email, autoLinkOptions.DefaultMemberTypeAlias, autoLinkOptions.DefaultIsApproved, name); + + foreach (var userGroup in autoLinkOptions.DefaultMemberGroups) + { + autoLinkUser.AddRole(userGroup); + } + + //call the callback if one is assigned + try + { + autoLinkOptions.OnAutoLinking?.Invoke(autoLinkUser, loginInfo); + } + catch (Exception ex) + { + Logger.LogError(ex, "Could not link login provider {LoginProvider}.", loginInfo.LoginProvider); + return AutoLinkSignInResult.FailedException(ex.Message); + } + + var userCreationResult = await UserManager.CreateAsync(autoLinkUser); + + if (!userCreationResult.Succeeded) + { + return AutoLinkSignInResult.FailedCreatingUser(userCreationResult.Errors.Select(x => x.Description).ToList()); + } + else + { + var shouldLinkUser = autoLinkOptions.OnExternalLogin == null || autoLinkOptions.OnExternalLogin(autoLinkUser, loginInfo); + if (shouldLinkUser) + { + return await LinkUser(autoLinkUser, loginInfo); + } + else + { + LogFailedExternalLogin(loginInfo, autoLinkUser); + return ExternalLoginSignInResult.NotAllowed; + } + } + } + } + } + + // TODO in v10 we can share this with backoffice by moving the backoffice into common. + public class ExternalLoginSignInResult : SignInResult + { + public static ExternalLoginSignInResult NotAllowed { get; } = new ExternalLoginSignInResult() + { + Succeeded = false + }; + } + // TODO in v10 we can share this with backoffice by moving the backoffice into common. + public class AutoLinkSignInResult : SignInResult + { + public static AutoLinkSignInResult FailedNotLinked { get; } = new AutoLinkSignInResult() + { + Succeeded = false + }; + + public static AutoLinkSignInResult FailedNoEmail { get; } = new AutoLinkSignInResult() + { + Succeeded = false + }; + + public static AutoLinkSignInResult FailedException(string error) => new AutoLinkSignInResult(new[] { error }) + { + Succeeded = false + }; + + public static AutoLinkSignInResult FailedCreatingUser(IReadOnlyCollection errors) => new AutoLinkSignInResult(errors) + { + Succeeded = false + }; + + public static AutoLinkSignInResult FailedLinkingUser(IReadOnlyCollection errors) => new AutoLinkSignInResult(errors) + { + Succeeded = false + }; + + public AutoLinkSignInResult(IReadOnlyCollection errors) + { + Errors = errors ?? throw new ArgumentNullException(nameof(errors)); + } + + public AutoLinkSignInResult() + { + } + + public IReadOnlyCollection Errors { get; } = Array.Empty(); + } /// public override AuthenticationProperties ConfigureExternalAuthenticationProperties(string provider, string redirectUrl, string userId = null) - => throw new NotImplementedException("External login is not yet implemented for members"); + { + // borrowed from https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs + // to be able to use our own XsrfKey/LoginProviderKey because the default is private :/ + + var properties = new AuthenticationProperties { RedirectUri = redirectUrl }; + properties.Items[UmbracoSignInMgrLoginProviderKey] = provider; + if (userId != null) + { + properties.Items[UmbracoSignInMgrXsrfKey] = userId; + } + return properties; + } /// public override Task> GetExternalAuthenticationSchemesAsync() - => throw new NotImplementedException("External login is not yet implemented for members"); + { + // That can be done by either checking the scheme (maybe) or comparing it to what we have registered in the collection of BackOfficeExternalLoginProvider + return base.GetExternalAuthenticationSchemesAsync(); + } + + private async Task LinkUser(MemberIdentityUser autoLinkUser, ExternalLoginInfo loginInfo) + { + var existingLogins = await UserManager.GetLoginsAsync(autoLinkUser); + var exists = existingLogins.FirstOrDefault(x => x.LoginProvider == loginInfo.LoginProvider && x.ProviderKey == loginInfo.ProviderKey); + + // if it already exists (perhaps it was added in the AutoLink callbak) then we just continue + if (exists != null) + { + //sign in + return await SignInOrTwoFactorAsync(autoLinkUser, isPersistent: false, loginInfo.LoginProvider); + } + + var linkResult = await UserManager.AddLoginAsync(autoLinkUser, loginInfo); + if (linkResult.Succeeded) + { + //we're good! sign in + return await SignInOrTwoFactorAsync(autoLinkUser, isPersistent: false, loginInfo.LoginProvider); + } + + //If this fails, we should really delete the user since it will be in an inconsistent state! + var deleteResult = await UserManager.DeleteAsync(autoLinkUser); + if (deleteResult.Succeeded) + { + var errors = linkResult.Errors.Select(x => x.Description).ToList(); + return AutoLinkSignInResult.FailedLinkingUser(errors); + } + else + { + //DOH! ... this isn't good, combine all errors to be shown + var errors = linkResult.Errors.Concat(deleteResult.Errors).Select(x => x.Description).ToList(); + return AutoLinkSignInResult.FailedLinkingUser(errors); + } + } + + private void LogFailedExternalLogin(ExternalLoginInfo loginInfo, MemberIdentityUser user) => + Logger.LogWarning("The AutoLinkOptions of the external authentication provider '{LoginProvider}' have refused the login based on the OnExternalLogin method. Affected user id: '{UserId}'", loginInfo.LoginProvider, user.Id); + } } diff --git a/src/Umbraco.Web.UI.Client/src/assets/fonts/web.config b/src/Umbraco.Web.UI.Client/src/assets/fonts/web.config deleted file mode 100644 index 42051b6de2..0000000000 --- a/src/Umbraco.Web.UI.Client/src/assets/fonts/web.config +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.html index fa85785868..24acef995e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.html @@ -69,7 +69,7 @@ ng-click="unlink($event, login.authType, login.linkedProviderKey)" ng-if="login.linkedProviderKey != undefined" value="{{login.authType}}"> - + Un-link your {{login.caption}} account diff --git a/src/Umbraco.Web.UI.Client/src/web.config b/src/Umbraco.Web.UI.Client/src/web.config deleted file mode 100644 index 6d14a9bab7..0000000000 --- a/src/Umbraco.Web.UI.Client/src/web.config +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index ac13badfaa..69f812b6e6 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -102,8 +102,6 @@ - - diff --git a/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/EditProfile.cshtml b/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/EditProfile.cshtml index 09954b3f8d..095c3c050d 100644 --- a/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/EditProfile.cshtml +++ b/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/EditProfile.cshtml @@ -1,11 +1,15 @@ @inherits Umbraco.Cms.Web.Common.Macros.PartialViewMacroPage + @using Umbraco.Cms.Core @using Umbraco.Cms.Core.Security +@using Umbraco.Cms.Core.Services +@using Umbraco.Cms.Web.Common.Security @using Umbraco.Cms.Web.Website.Controllers @using Umbraco.Cms.Web.Website.Models @using Umbraco.Extensions @inject MemberModelBuilderFactory memberModelBuilderFactory; - +@inject IMemberExternalLoginProviders memberExternalLoginProviders +@inject IExternalLoginWithKeyService externalLoginWithKeyService @{ // Build a profile model to edit var profileModel = await memberModelBuilderFactory @@ -17,6 +21,13 @@ .BuildForCurrentMemberAsync(); var success = TempData["FormSuccess"] != null; + + var loginProviders = await memberExternalLoginProviders.GetMemberProvidersAsync(); + var externalSignInError = ViewData.GetExternalSignInProviderErrors(); + + var currentExternalLogin = profileModel is null + ? new Dictionary() + : externalLoginWithKeyService.GetExternalLogins(profileModel.Key).ToDictionary(x=>x.LoginProvider, x=>x.ProviderKey); } @@ -70,5 +81,50 @@ } + + if (loginProviders.Any()) + { +
+

Link external accounts

+ + if (externalSignInError?.AuthenticationType is null && externalSignInError?.Errors.Any() == true) + { + @Html.DisplayFor(x => externalSignInError.Errors); + } + + @foreach (var login in loginProviders) + { + if (currentExternalLogin.TryGetValue(login.ExternalLoginProvider.AuthenticationType, out var providerKey)) + { + @using (Html.BeginUmbracoForm(nameof(UmbExternalLoginController.Disassociate))) + { + + + + if (externalSignInError?.AuthenticationType == login.ExternalLoginProvider.AuthenticationType) + { + @Html.DisplayFor(x => externalSignInError.Errors); + } + } + } + else + { + @using (Html.BeginUmbracoForm(nameof(UmbExternalLoginController.LinkLogin))) + { + + + if (externalSignInError?.AuthenticationType == login.ExternalLoginProvider.AuthenticationType) + { + @Html.DisplayFor(x => externalSignInError.Errors); + } + } + } + + } + } } } diff --git a/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/Login.cshtml b/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/Login.cshtml index d3c389c78d..85b7f53c24 100644 --- a/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/Login.cshtml +++ b/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/Login.cshtml @@ -1,9 +1,9 @@ @inherits Umbraco.Cms.Web.Common.Macros.PartialViewMacroPage -@using Microsoft.AspNetCore.Http.Extensions @using Umbraco.Cms.Web.Common.Models +@using Umbraco.Cms.Web.Common.Security @using Umbraco.Cms.Web.Website.Controllers @using Umbraco.Extensions - +@inject IMemberExternalLoginProviders memberExternalLoginProviders @{ var loginModel = new LoginModel(); // You can modify this to redirect to a different URL instead of the current one @@ -19,27 +19,58 @@ @using (Html.BeginUmbracoForm( "HandleLogin", new { RedirectUrl = loginModel.RedirectUrl })) { -

Log in with a local account.

-
-
-
- - - -
-
- - - -
-
- - -
+

Log in with a local account.

+
+
+
+ + + +
+
+ + + +
+
+ + +
- + + + + } +@{ + var loginProviders = await memberExternalLoginProviders.GetMemberProvidersAsync(); + var externalSignInError = ViewData.GetExternalSignInProviderErrors(); + + if (loginProviders.Any()) + { +
+

Or using external providers

+ if (externalSignInError?.AuthenticationType is null && externalSignInError?.Errors.Any() == true) + { + @Html.DisplayFor(x => externalSignInError.Errors); + } + + @foreach (var login in await memberExternalLoginProviders.GetMemberProvidersAsync()) + { + + @using (Html.BeginUmbracoForm(nameof(UmbExternalLoginController.ExternalLogin))) + { + + + if (externalSignInError?.AuthenticationType == login.ExternalLoginProvider.AuthenticationType) + { + @Html.DisplayFor(x => externalSignInError.Errors); + } + } + } } - +} diff --git a/src/Umbraco.Web.Website/Controllers/UmbExternalLoginController.cs b/src/Umbraco.Web.Website/Controllers/UmbExternalLoginController.cs new file mode 100644 index 0000000000..2d5ec250e9 --- /dev/null +++ b/src/Umbraco.Web.Website/Controllers/UmbExternalLoginController.cs @@ -0,0 +1,279 @@ +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Extensions; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Logging; +using Umbraco.Cms.Core.Routing; +using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Web; +using Umbraco.Cms.Infrastructure.Persistence; +using Umbraco.Cms.Web.Common.ActionsResults; +using Umbraco.Cms.Web.Common.Filters; +using Umbraco.Cms.Web.Common.Security; +using Umbraco.Extensions; +using SignInResult = Microsoft.AspNetCore.Identity.SignInResult; + +namespace Umbraco.Cms.Web.Website.Controllers +{ + [UmbracoMemberAuthorize] + public class UmbExternalLoginController : SurfaceController + { + private readonly IMemberManager _memberManager; + private readonly IMemberSignInManagerExternalLogins _memberSignInManager; + + public UmbExternalLoginController( + IUmbracoContextAccessor umbracoContextAccessor, + IUmbracoDatabaseFactory databaseFactory, + ServiceContext services, + AppCaches appCaches, + IProfilingLogger profilingLogger, + IPublishedUrlProvider publishedUrlProvider, + IMemberSignInManagerExternalLogins memberSignInManager, + IMemberManager memberManager) + : base( + umbracoContextAccessor, + databaseFactory, + services, + appCaches, + profilingLogger, + publishedUrlProvider) + { + _memberSignInManager = memberSignInManager; + _memberManager = memberManager; + } + + /// + /// Endpoint used to redirect to a specific login provider. This endpoint is used from the Login Macro snippet. + /// + [HttpPost] + [AllowAnonymous] + [ValidateAntiForgeryToken] + public ActionResult ExternalLogin(string provider, string returnUrl = null) + { + if (returnUrl.IsNullOrWhiteSpace()) + { + returnUrl = Request.GetEncodedPathAndQuery(); + } + + var wrappedReturnUrl = + Url.SurfaceAction(nameof(ExternalLoginCallback), this.GetControllerName(), new { returnUrl }); + + AuthenticationProperties properties = + _memberSignInManager.ConfigureExternalAuthenticationProperties(provider, wrappedReturnUrl); + + return Challenge(properties, provider); + } + + /// + /// Endpoint used my the login provider to call back to our solution. + /// + [HttpGet] + [AllowAnonymous] + public async Task ExternalLoginCallback(string returnUrl) + { + var errors = new List(); + + ExternalLoginInfo loginInfo = await _memberSignInManager.GetExternalLoginInfoAsync(); + if (loginInfo is null) + { + errors.Add("Invalid response from the login provider"); + } + else + { + SignInResult result = await _memberSignInManager.ExternalLoginSignInAsync(loginInfo, false); + + if (result == SignInResult.Success) + { + // Update any authentication tokens if succeeded + await _memberSignInManager.UpdateExternalAuthenticationTokensAsync(loginInfo); + + return RedirectToLocal(returnUrl); + } + + if (result == SignInResult.TwoFactorRequired) + { + MemberIdentityUser attemptedUser = + await _memberManager.FindByLoginAsync(loginInfo.LoginProvider, loginInfo.ProviderKey); + if (attemptedUser == null) + { + return new ValidationErrorResult( + $"No local user found for the login provider {loginInfo.LoginProvider} - {loginInfo.ProviderKey}"); + } + + // create a with information to display a custom two factor send code view + var verifyResponse = + new ObjectResult(new { userId = attemptedUser.Id }) + { + StatusCode = StatusCodes.Status402PaymentRequired + }; + + return verifyResponse; + } + + if (result == SignInResult.LockedOut) + { + errors.Add( + $"The local member {loginInfo.Principal.Identity.Name} for the external provider {loginInfo.ProviderDisplayName} is locked out."); + } + else if (result == SignInResult.NotAllowed) + { + // This occurs when SignInManager.CanSignInAsync fails which is when RequireConfirmedEmail , RequireConfirmedPhoneNumber or RequireConfirmedAccount fails + // however since we don't enforce those rules (yet) this shouldn't happen. + errors.Add( + $"The member {loginInfo.Principal.Identity.Name} for the external provider {loginInfo.ProviderDisplayName} has not confirmed their details and cannot sign in."); + } + else if (result == SignInResult.Failed) + { + // Failed only occurs when the user does not exist + errors.Add("The requested provider (" + loginInfo.LoginProvider + + ") has not been linked to an account, the provider must be linked before it can be used."); + } + else if (result == MemberSignInManager.ExternalLoginSignInResult.NotAllowed) + { + // This occurs when the external provider has approved the login but custom logic in OnExternalLogin has denined it. + errors.Add( + $"The user {loginInfo.Principal.Identity.Name} for the external provider {loginInfo.ProviderDisplayName} has not been accepted and cannot sign in."); + } + else if (result == MemberSignInManager.AutoLinkSignInResult.FailedNotLinked) + { + errors.Add("The requested provider (" + loginInfo.LoginProvider + + ") has not been linked to an account, the provider must be linked from the back office."); + } + else if (result == MemberSignInManager.AutoLinkSignInResult.FailedNoEmail) + { + errors.Add( + $"The requested provider ({loginInfo.LoginProvider}) has not provided the email claim {ClaimTypes.Email}, the account cannot be linked."); + } + else if (result is MemberSignInManager.AutoLinkSignInResult autoLinkSignInResult && + autoLinkSignInResult.Errors.Count > 0) + { + errors.AddRange(autoLinkSignInResult.Errors); + } + else if (!result.Succeeded) + { + // this shouldn't occur, the above should catch the correct error but we'll be safe just in case + errors.Add($"An unknown error with the requested provider ({loginInfo.LoginProvider}) occurred."); + } + } + + if (errors.Count > 0) + { + ViewData.SetExternalSignInProviderErrors( + new BackOfficeExternalLoginProviderErrors( + loginInfo?.LoginProvider, + errors)); + + return CurrentUmbracoPage(); + } + + return RedirectToLocal(returnUrl); + } + + private void AddModelErrors(IdentityResult result, string prefix = "") + { + foreach (IdentityError error in result.Errors) + { + ModelState.AddModelError(prefix, error.Description); + } + } + + [HttpPost] + [ValidateAntiForgeryToken] + public IActionResult LinkLogin(string provider, string returnUrl = null) + { + if (returnUrl.IsNullOrWhiteSpace()) + { + returnUrl = Request.GetEncodedPathAndQuery(); + } + + var wrappedReturnUrl = + Url.SurfaceAction(nameof(ExternalLinkLoginCallback), this.GetControllerName(), new { returnUrl }); + + // Configures the redirect URL and user identifier for the specified external login including xsrf data + AuthenticationProperties properties = + _memberSignInManager.ConfigureExternalAuthenticationProperties(provider, wrappedReturnUrl, + _memberManager.GetUserId(User)); + + return Challenge(properties, provider); + } + + [HttpGet] + public async Task ExternalLinkLoginCallback(string returnUrl) + { + MemberIdentityUser user = await _memberManager.GetUserAsync(User); + string loginProvider = null; + var errors = new List(); + if (user == null) + { + // ... this should really not happen + errors.Add("Local user does not exist"); + } + else + { + ExternalLoginInfo info = + await _memberSignInManager.GetExternalLoginInfoAsync(await _memberManager.GetUserIdAsync(user)); + + if (info == null) + { + //Add error and redirect for it to be displayed + errors.Add( "An error occurred, could not get external login info"); + } + else + { + loginProvider = info.LoginProvider; + IdentityResult addLoginResult = await _memberManager.AddLoginAsync(user, info); + if (addLoginResult.Succeeded) + { + // Update any authentication tokens if succeeded + await _memberSignInManager.UpdateExternalAuthenticationTokensAsync(info); + + return RedirectToLocal(returnUrl); + } + + //Add errors and redirect for it to be displayed + errors.AddRange(addLoginResult.Errors.Select(x => x.Description)); + } + } + + ViewData.SetExternalSignInProviderErrors( + new BackOfficeExternalLoginProviderErrors( + loginProvider, + errors)); + return CurrentUmbracoPage(); + } + + private IActionResult RedirectToLocal(string returnUrl) => + Url.IsLocalUrl(returnUrl) ? Redirect(returnUrl) : RedirectToCurrentUmbracoPage(); + + [HttpPost] + [ValidateAntiForgeryToken] + public async Task Disassociate(string provider, string providerKey, string returnUrl = null) + { + if (returnUrl.IsNullOrWhiteSpace()) + { + returnUrl = Request.GetEncodedPathAndQuery(); + } + + MemberIdentityUser user = await _memberManager.FindByIdAsync(User.Identity.GetUserId()); + + IdentityResult result = await _memberManager.RemoveLoginAsync(user, provider, providerKey); + + if (result.Succeeded) + { + await _memberSignInManager.SignInAsync(user, false); + return RedirectToLocal(returnUrl); + } + + AddModelErrors(result); + return CurrentUmbracoPage(); + } + } +} diff --git a/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilder.MemberIdentity.cs b/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilder.MemberIdentity.cs new file mode 100644 index 0000000000..c208d96972 --- /dev/null +++ b/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilder.MemberIdentity.cs @@ -0,0 +1,23 @@ +using System; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Web.Website.Security; + + +namespace Umbraco.Extensions +{ + /// + /// Extension methods for for the Umbraco back office + /// + public static partial class UmbracoBuilderExtensions + { + /// + /// Adds support for external login providers in Umbraco + /// + public static IUmbracoBuilder AddMemberExternalLogins(this IUmbracoBuilder umbracoBuilder, Action builder) + { + builder(new MemberExternalLoginsBuilder(umbracoBuilder.Services)); + return umbracoBuilder; + } + + } +} diff --git a/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs index 797a4b2202..5f8f1d9b69 100644 --- a/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs @@ -18,7 +18,7 @@ namespace Umbraco.Extensions /// /// extensions for umbraco front-end website /// - public static class UmbracoBuilderExtensions + public static partial class UmbracoBuilderExtensions { /// /// Add services for the umbraco front-end website diff --git a/src/Umbraco.Web.Website/Models/ProfileModel.cs b/src/Umbraco.Web.Website/Models/ProfileModel.cs index a435d22c06..5fc7ed2df8 100644 --- a/src/Umbraco.Web.Website/Models/ProfileModel.cs +++ b/src/Umbraco.Web.Website/Models/ProfileModel.cs @@ -12,6 +12,10 @@ namespace Umbraco.Cms.Web.Website.Models /// public class ProfileModel : PostRedirectModel { + + [ReadOnly(true)] + public Guid Key { get; set; } + [Required] [EmailAddress] [Display(Name = "Email")] diff --git a/src/Umbraco.Web.Website/Models/ProfileModelBuilder.cs b/src/Umbraco.Web.Website/Models/ProfileModelBuilder.cs index 00d4cebd1e..4a94fb094c 100644 --- a/src/Umbraco.Web.Website/Models/ProfileModelBuilder.cs +++ b/src/Umbraco.Web.Website/Models/ProfileModelBuilder.cs @@ -68,7 +68,8 @@ namespace Umbraco.Cms.Web.Website.Models CreatedDate = member.CreatedDateUtc.ToLocalTime(), LastLoginDate = member.LastLoginDateUtc?.ToLocalTime(), LastPasswordChangedDate = member.LastPasswordChangeDateUtc?.ToLocalTime(), - RedirectUrl = _redirectUrl + RedirectUrl = _redirectUrl, + Key = member.Key }; IMemberType memberType = MemberTypeService.Get(member.MemberTypeAlias); @@ -83,7 +84,7 @@ namespace Umbraco.Cms.Web.Website.Models { // should never happen throw new InvalidOperationException($"Could not find a member with key: {member.Key}."); - } + } if (_lookupProperties) { diff --git a/src/Umbraco.Web.Website/Security/MemberAuthenticationBuilder.cs b/src/Umbraco.Web.Website/Security/MemberAuthenticationBuilder.cs new file mode 100644 index 0000000000..d58abfc871 --- /dev/null +++ b/src/Umbraco.Web.Website/Security/MemberAuthenticationBuilder.cs @@ -0,0 +1,75 @@ +using System; +using System.Diagnostics; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; +using Umbraco.Cms.Web.Common.Security; +using Umbraco.Extensions; +using Constants = Umbraco.Cms.Core.Constants; + +namespace Umbraco.Cms.Web.Website.Security +{ + /// + /// Custom used to associate external logins with umbraco external login options + /// + public class MemberAuthenticationBuilder : AuthenticationBuilder + { + private readonly Action _loginProviderOptions; + + public MemberAuthenticationBuilder( + IServiceCollection services, + Action loginProviderOptions = null) + : base(services) + => _loginProviderOptions = loginProviderOptions ?? (x => { }); + + public string SchemeForMembers(string scheme) + => scheme?.EnsureStartsWith(Constants.Security.MemberExternalAuthenticationTypePrefix); + + /// + /// Overridden to track the final authenticationScheme being registered for the external login + /// + /// + /// + /// + /// + /// + /// + public override AuthenticationBuilder AddRemoteScheme(string authenticationScheme, string displayName, Action configureOptions) + { + // Validate that the prefix is set + if (!authenticationScheme.StartsWith(Constants.Security.MemberExternalAuthenticationTypePrefix)) + { + throw new InvalidOperationException($"The {nameof(authenticationScheme)} is not prefixed with {Constants.Security.BackOfficeExternalAuthenticationTypePrefix}. The scheme must be created with a call to the method {nameof(SchemeForMembers)}"); + } + + // add our login provider to the container along with a custom options configuration + Services.Configure(authenticationScheme, _loginProviderOptions); + base.Services.AddSingleton(services => + { + return new MemberExternalLoginProvider( + authenticationScheme, + services.GetRequiredService>()); + }); + Services.TryAddEnumerable(ServiceDescriptor.Singleton, EnsureMemberScheme>()); + + return base.AddRemoteScheme(authenticationScheme, displayName, configureOptions); + } + + // Ensures that the sign in scheme is always the Umbraco member external type + private class EnsureMemberScheme : IPostConfigureOptions where TOptions : RemoteAuthenticationOptions + { + public void PostConfigure(string name, TOptions options) + { + if (!name.StartsWith(Constants.Security.MemberExternalAuthenticationTypePrefix)) + { + return; + } + + options.SignInScheme = IdentityConstants.ExternalScheme; + } + } + } + +} diff --git a/src/Umbraco.Web.Website/Security/MemberExternalLoginsBuilder.cs b/src/Umbraco.Web.Website/Security/MemberExternalLoginsBuilder.cs new file mode 100644 index 0000000000..4f8eb407be --- /dev/null +++ b/src/Umbraco.Web.Website/Security/MemberExternalLoginsBuilder.cs @@ -0,0 +1,34 @@ +using System; +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Web.Common.Security; + +namespace Umbraco.Cms.Web.Website.Security +{ + /// + /// Used to add back office login providers + /// + public class MemberExternalLoginsBuilder + { + public MemberExternalLoginsBuilder(IServiceCollection services) + { + _services = services; + } + + private readonly IServiceCollection _services; + + /// + /// Add a back office login provider with options + /// + /// + /// + /// + public MemberExternalLoginsBuilder AddMemberLogin( + Action build, + Action loginProviderOptions = null) + { + build(new MemberAuthenticationBuilder(_services, loginProviderOptions)); + return this; + } + } + +} diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Security/BackOfficeUserStoreTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Security/BackOfficeUserStoreTests.cs index 665bb2f079..ac0d19040e 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Security/BackOfficeUserStoreTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Security/BackOfficeUserStoreTests.cs @@ -23,7 +23,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Security { private IUserService UserService => GetRequiredService(); private IEntityService EntityService => GetRequiredService(); - private IExternalLoginService ExternalLoginService => GetRequiredService(); + private IExternalLoginWithKeyService ExternalLoginService => GetRequiredService(); private IUmbracoMapper UmbracoMapper => GetRequiredService(); private ILocalizedTextService TextService => GetRequiredService(); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ExternalLoginServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ExternalLoginServiceTests.cs index b4f6c1ae5f..f0803b25d0 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ExternalLoginServiceTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ExternalLoginServiceTests.cs @@ -20,7 +20,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services { private IUserService UserService => GetRequiredService(); - private IExternalLoginService ExternalLoginService => (IExternalLoginService)GetRequiredService(); + private IExternalLoginWithKeyService ExternalLoginService => GetRequiredService(); [Test] [Ignore("We don't support duplicates anymore, this removing on save was a breaking change work around, this needs to be ported to a migration")] @@ -38,14 +38,14 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services // insert duplicates manuall scope.Database.Insert(new ExternalLoginDto { - UserId = user.Id, + UserOrMemberKey = user.Key, LoginProvider = "test1", ProviderKey = providerKey, CreateDate = latest }); scope.Database.Insert(new ExternalLoginDto { - UserId = user.Id, + UserOrMemberKey = user.Key, LoginProvider = "test1", ProviderKey = providerKey, CreateDate = oldest @@ -60,9 +60,9 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services new ExternalLogin("test1", providerKey) }; - ExternalLoginService.Save(user.Id, externalLogins); + ExternalLoginService.Save(user.Key, externalLogins); - var logins = ExternalLoginService.GetExternalLogins(user.Id).ToList(); + var logins = ExternalLoginService.GetExternalLogins(user.Key).ToList(); // duplicates will be removed, keeping the latest entries Assert.AreEqual(2, logins.Count); @@ -84,9 +84,9 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services new ExternalLogin("test1", providerKey) }; - ExternalLoginService.Save(user.Id, externalLogins); + ExternalLoginService.Save(user.Key, externalLogins); - var logins = ExternalLoginService.GetExternalLogins(user.Id).ToList(); + var logins = ExternalLoginService.GetExternalLogins(user.Key).ToList(); Assert.AreEqual(1, logins.Count); } @@ -103,16 +103,16 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services new ExternalLogin("test1", providerKey1, "hello"), new ExternalLogin("test2", providerKey2, "world") }; - ExternalLoginService.Save(user.Id, extLogins); + ExternalLoginService.Save(user.Key, extLogins); extLogins = new[] { new ExternalLogin("test1", providerKey1, "123456"), new ExternalLogin("test2", providerKey2, "987654") }; - ExternalLoginService.Save(user.Id, extLogins); + ExternalLoginService.Save(user.Key, extLogins); - var found = ExternalLoginService.GetExternalLogins(user.Id).OrderBy(x => x.LoginProvider).ToList(); + var found = ExternalLoginService.GetExternalLogins(user.Key).OrderBy(x => x.LoginProvider).ToList(); Assert.AreEqual(2, found.Count); Assert.AreEqual("123456", found[0].UserData); Assert.AreEqual("987654", found[1].UserData); @@ -131,7 +131,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services new ExternalLogin("test1", providerKey1, "hello"), new ExternalLogin("test2", providerKey2, "world") }; - ExternalLoginService.Save(user.Id, extLogins); + ExternalLoginService.Save(user.Key, extLogins); var found = ExternalLoginService.Find("test2", providerKey2).ToList(); Assert.AreEqual(1, found.Count); @@ -151,9 +151,9 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services new ExternalLogin("test2", Guid.NewGuid().ToString("N")) }; - ExternalLoginService.Save(user.Id, externalLogins); + ExternalLoginService.Save(user.Key, externalLogins); - var logins = ExternalLoginService.GetExternalLogins(user.Id).OrderBy(x => x.LoginProvider).ToList(); + var logins = ExternalLoginService.GetExternalLogins(user.Key).OrderBy(x => x.LoginProvider).ToList(); Assert.AreEqual(2, logins.Count); for (int i = 0; i < logins.Count; i++) { @@ -173,7 +173,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services new ExternalLogin("test1", Guid.NewGuid().ToString("N")) }; - ExternalLoginService.Save(user.Id, externalLogins); + ExternalLoginService.Save(user.Key, externalLogins); ExternalLoginToken[] externalTokens = new[] { @@ -181,9 +181,9 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services new ExternalLoginToken(externalLogins[0].LoginProvider, "hello2", "world2") }; - ExternalLoginService.Save(user.Id, externalTokens); + ExternalLoginService.Save(user.Key, externalTokens); - var tokens = ExternalLoginService.GetExternalLoginTokens(user.Id).ToList(); + var tokens = ExternalLoginService.GetExternalLoginTokens(user.Key).ToList(); Assert.AreEqual(2, tokens.Count); } @@ -201,18 +201,18 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services new ExternalLogin("test4", Guid.NewGuid().ToString("N")) }; - ExternalLoginService.Save(user.Id, externalLogins); + ExternalLoginService.Save(user.Key, externalLogins); - var logins = ExternalLoginService.GetExternalLogins(user.Id).OrderBy(x => x.LoginProvider).ToList(); + var logins = ExternalLoginService.GetExternalLogins(user.Key).OrderBy(x => x.LoginProvider).ToList(); logins.RemoveAt(0); // remove the first one logins.Add(new IdentityUserLogin("test5", Guid.NewGuid().ToString("N"), user.Id.ToString())); // add a new one logins[0].ProviderKey = "abcd123"; // update // save new list - ExternalLoginService.Save(user.Id, logins.Select(x => new ExternalLogin(x.LoginProvider, x.ProviderKey))); + ExternalLoginService.Save(user.Key, logins.Select(x => new ExternalLogin(x.LoginProvider, x.ProviderKey))); - var updatedLogins = ExternalLoginService.GetExternalLogins(user.Id).OrderBy(x => x.LoginProvider).ToList(); + var updatedLogins = ExternalLoginService.GetExternalLogins(user.Key).OrderBy(x => x.LoginProvider).ToList(); Assert.AreEqual(4, updatedLogins.Count); for (int i = 0; i < updatedLogins.Count; i++) { @@ -233,7 +233,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services new ExternalLogin("test2", Guid.NewGuid().ToString("N")) }; - ExternalLoginService.Save(user.Id, externalLogins); + ExternalLoginService.Save(user.Key, externalLogins); ExternalLoginToken[] externalTokens = new[] { @@ -243,18 +243,18 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services new ExternalLoginToken(externalLogins[1].LoginProvider, "hello2a", "world2a") }; - ExternalLoginService.Save(user.Id, externalTokens); + ExternalLoginService.Save(user.Key, externalTokens); - var tokens = ExternalLoginService.GetExternalLoginTokens(user.Id).OrderBy(x => x.LoginProvider).ToList(); + var tokens = ExternalLoginService.GetExternalLoginTokens(user.Key).OrderBy(x => x.LoginProvider).ToList(); tokens.RemoveAt(0); // remove the first one tokens.Add(new IdentityUserToken(externalLogins[1].LoginProvider, "hello2b", "world2b", user.Id.ToString())); // add a new one tokens[0].Value = "abcd123"; // update // save new list - ExternalLoginService.Save(user.Id, tokens.Select(x => new ExternalLoginToken(x.LoginProvider, x.Name, x.Value))); + ExternalLoginService.Save(user.Key, tokens.Select(x => new ExternalLoginToken(x.LoginProvider, x.Name, x.Value))); - var updatedTokens = ExternalLoginService.GetExternalLoginTokens(user.Id).OrderBy(x => x.LoginProvider).ToList(); + var updatedTokens = ExternalLoginService.GetExternalLoginTokens(user.Key).OrderBy(x => x.LoginProvider).ToList(); Assert.AreEqual(4, updatedTokens.Count); for (int i = 0; i < updatedTokens.Count; i++) { @@ -275,9 +275,9 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services new ExternalLogin("test1", Guid.NewGuid().ToString("N"), "hello world") }; - ExternalLoginService.Save(user.Id, externalLogins); + ExternalLoginService.Save(user.Key, externalLogins); - var logins = ExternalLoginService.GetExternalLogins(user.Id).ToList(); + var logins = ExternalLoginService.GetExternalLogins(user.Key).ToList(); Assert.AreEqual("hello world", logins[0].UserData); } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberManagerTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberManagerTests.cs index 05afd547e1..e09fb70d8e 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberManagerTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberManagerTests.cs @@ -52,7 +52,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Security new UmbracoMapper(new MapDefinitionCollection(() => mapDefinitions), scopeProvider), scopeProvider, new IdentityErrorDescriber(), - Mock.Of()); + Mock.Of(), Mock.Of()); _mockIdentityOptions = new Mock>(); var idOptions = new IdentityOptions { Lockout = { AllowedForNewUsers = false } }; diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberUserStoreTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberUserStoreTests.cs index db58239e5f..4ed2f0895d 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberUserStoreTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberUserStoreTests.cs @@ -37,7 +37,9 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Security new UmbracoMapper(new MapDefinitionCollection(() => new List()), mockScopeProvider.Object), mockScopeProvider.Object, new IdentityErrorDescriber(), - Mock.Of()); + Mock.Of(), + Mock.Of() + ); } [Test] diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Security/MemberSignInManagerTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Security/MemberSignInManagerTests.cs index a49ba1a8ac..e616cafd08 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Security/MemberSignInManagerTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Security/MemberSignInManagerTests.cs @@ -37,7 +37,17 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Common.Security serviceCollection .AddLogging() .AddAuthentication() - .AddCookie(IdentityConstants.ApplicationScheme); + .AddCookie(IdentityConstants.ApplicationScheme) + .AddCookie(IdentityConstants.ExternalScheme, o => + { + o.Cookie.Name = IdentityConstants.ExternalScheme; + o.ExpireTimeSpan = TimeSpan.FromMinutes(5); + }) + .AddCookie(IdentityConstants.TwoFactorUserIdScheme, o => + { + o.Cookie.Name = IdentityConstants.TwoFactorUserIdScheme; + o.ExpireTimeSpan = TimeSpan.FromMinutes(5); + }); IServiceProvider serviceProvider = serviceProviderFactory.CreateServiceProvider(serviceCollection); var httpContextFactory = new DefaultHttpContextFactory(serviceProvider); IFeatureCollection features = new DefaultHttpContext().Features; @@ -55,7 +65,9 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Common.Security Mock.Of>(), _mockLogger.Object, Mock.Of(), - Mock.Of>()); + Mock.Of>(), + Mock.Of() + ); } private static Mock MockMemberManager() => new Mock( From e204c5bab97c71edc06dc679aeb5fbe8e5b5f2ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Thu, 20 Jan 2022 16:47:00 +0100 Subject: [PATCH 090/141] Media Previews (#11888) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Niels Lyngsø Co-authored-by: Mads Rasmussen Co-authored-by: Paul Johnson --- .github/config/codeql-config.yml | 1 + .github/workflows/codeql-analysis.yml | 4 +- .../gulp/tasks/dependencies.js | 18 +- src/Umbraco.Web.UI.Client/gulpfile.js | 2 + src/Umbraco.Web.UI.Client/package.json | 1 + .../upload/umbpropertyfileupload.directive.js | 22 +- .../common/services/mediapreview.service.js | 94 +++++++ src/Umbraco.Web.UI.Client/src/less/belle.less | 5 + .../components/umb-property-file-upload.less | 4 + .../src/less/property-editors.less | 6 +- .../mediaentryeditor.controller.js | 2 + .../mediaentryeditor/mediaentryeditor.html | 251 ++++++++++-------- .../mediaentryeditor/mediaentryeditor.less | 18 +- .../umbaudiopreview/umb-audio-preview.html | 8 + .../umbaudiopreview/umb-audio-preview.less | 18 ++ .../umbaudiopreview.controller.js | 11 + .../umbfilepreview/umb-file-preview.html | 18 ++ .../umbfilepreview/umb-file-preview.less | 13 + .../umbimagepreview/umb-image-preview.html | 6 + .../umbimagepreview/umb-image-preview.less | 9 + .../umbimagepreview.controller.js | 18 ++ .../umbmediapreview/umb-media-preview.less | 14 + .../umbmediapreview.component.js | 38 +++ .../umbvideopreview/umb-video-preview.html | 8 + .../umbvideopreview/umb-video-preview.less | 10 + .../umbvideopreview.controller.js | 15 ++ .../components/mediacard/umb-media-card.html | 2 +- .../mediacard/umbMediaCard.component.js | 1 + .../upload/umb-property-file-upload.html | 114 ++++---- 29 files changed, 530 insertions(+), 201 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/common/services/mediapreview.service.js create mode 100644 src/Umbraco.Web.UI.Client/src/views/components/media/umbaudiopreview/umb-audio-preview.html create mode 100644 src/Umbraco.Web.UI.Client/src/views/components/media/umbaudiopreview/umb-audio-preview.less create mode 100644 src/Umbraco.Web.UI.Client/src/views/components/media/umbaudiopreview/umbaudiopreview.controller.js create mode 100644 src/Umbraco.Web.UI.Client/src/views/components/media/umbfilepreview/umb-file-preview.html create mode 100644 src/Umbraco.Web.UI.Client/src/views/components/media/umbfilepreview/umb-file-preview.less create mode 100644 src/Umbraco.Web.UI.Client/src/views/components/media/umbimagepreview/umb-image-preview.html create mode 100644 src/Umbraco.Web.UI.Client/src/views/components/media/umbimagepreview/umb-image-preview.less create mode 100644 src/Umbraco.Web.UI.Client/src/views/components/media/umbimagepreview/umbimagepreview.controller.js create mode 100644 src/Umbraco.Web.UI.Client/src/views/components/media/umbmediapreview/umb-media-preview.less create mode 100644 src/Umbraco.Web.UI.Client/src/views/components/media/umbmediapreview/umbmediapreview.component.js create mode 100644 src/Umbraco.Web.UI.Client/src/views/components/media/umbvideopreview/umb-video-preview.html create mode 100644 src/Umbraco.Web.UI.Client/src/views/components/media/umbvideopreview/umb-video-preview.less create mode 100644 src/Umbraco.Web.UI.Client/src/views/components/media/umbvideopreview/umbvideopreview.controller.js diff --git a/.github/config/codeql-config.yml b/.github/config/codeql-config.yml index 432be995a5..77b390d392 100644 --- a/.github/config/codeql-config.yml +++ b/.github/config/codeql-config.yml @@ -5,3 +5,4 @@ paths: paths-ignore: - '**/node_modules' + - 'src/Umbraco.Web.UI/wwwroot' \ No newline at end of file diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 60292dba45..894ad709e9 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -22,8 +22,8 @@ jobs: with: config-file: ./.github/config/codeql-config.yml - - name: Build - run: dotnet build umbraco-netcore-only.sln # also runs npm build + - name: dotnet build + run: dotnet build umbraco-netcore-only.sln - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v1 diff --git a/src/Umbraco.Web.UI.Client/gulp/tasks/dependencies.js b/src/Umbraco.Web.UI.Client/gulp/tasks/dependencies.js index e8a40e9d70..55390bb520 100644 --- a/src/Umbraco.Web.UI.Client/gulp/tasks/dependencies.js +++ b/src/Umbraco.Web.UI.Client/gulp/tasks/dependencies.js @@ -134,7 +134,7 @@ function dependencies() { "./node_modules/angular-messages/angular-messages.min.js.map" ], "base": "./node_modules/angular-messages" - }, + }, { "name": "angular-mocks", "src": ["./node_modules/angular-mocks/angular-mocks.js"], @@ -285,11 +285,11 @@ function dependencies() { // add streams for node modules nodeModules.forEach(module => { var task = gulp.src(module.src, { base: module.base, allowEmpty: true }); - + _.forEach(config.roots, function(root){ task = task.pipe(gulp.dest(root + config.targets.lib + "/" + module.name)) }); - + stream.add(task); }); @@ -299,12 +299,12 @@ function dependencies() { _.forEach(config.roots, function(root){ libTask = libTask.pipe(gulp.dest(root + config.targets.lib)) }); - + stream.add(libTask); //Copies all static assets into /root / assets folder //css, fonts and image files - + var assetsTask = gulp.src(config.sources.globs.assets, { allowEmpty: true }); assetsTask = assetsTask.pipe(imagemin([ imagemin.gifsicle({interlaced: true}), @@ -321,8 +321,8 @@ function dependencies() { _.forEach(config.roots, function(root){ assetsTask = assetsTask.pipe(gulp.dest(root + config.targets.assets)); }); - - + + stream.add(assetsTask); // Copies all the less files related to the preview into their folder @@ -342,13 +342,13 @@ function dependencies() { configTask = configTask.pipe(gulp.dest(root + config.targets.views + "/propertyeditors/grid/config")); }); stream.add(configTask); - + var dashboardTask = gulp.src("src/views/dashboard/default/*.jpg", { allowEmpty: true }); _.forEach(config.roots, function(root){ dashboardTask = dashboardTask .pipe(gulp.dest(root + config.targets.views + "/dashboard/default")); }); stream.add(dashboardTask); - + return stream; }; diff --git a/src/Umbraco.Web.UI.Client/gulpfile.js b/src/Umbraco.Web.UI.Client/gulpfile.js index ffc5f061cc..81fa1d79cd 100644 --- a/src/Umbraco.Web.UI.Client/gulpfile.js +++ b/src/Umbraco.Web.UI.Client/gulpfile.js @@ -31,6 +31,8 @@ const coreBuild = parallel(dependencies, js, less, views); // *********************************************************** exports.build = series(coreBuild, testUnit); +exports.buildDev = series(setDevelopmentMode, coreBuild); + exports.coreBuild = coreBuild; exports.dev = series(setDevelopmentMode, coreBuild, runUnitTestServer, watchTask); exports.watch = series(watchTask); diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index 43d7a3cecd..6f451b89a7 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -7,6 +7,7 @@ "e2e": "gulp testE2e", "build": "gulp build", "build:skip-tests": "gulp coreBuild", + "build:dev": "gulp buildDev", "dev": "gulp dev", "fastdev": "gulp fastdev", "watch": "gulp watch" diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbpropertyfileupload.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbpropertyfileupload.directive.js index 5492fee1a0..6656f370d0 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbpropertyfileupload.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbpropertyfileupload.directive.js @@ -117,12 +117,11 @@ vm.files = _.map(files, function (file) { var f = { fileName: file, + fileSrc: file, isImage: mediaHelper.detectIfImageByExtension(file), extension: getExtension(file) }; - f.fileSrc = getThumbnail(f); - return f; }); @@ -190,21 +189,6 @@ } } - function getThumbnail(file) { - - if (file.extension === 'svg') { - return file.fileName; - } - - if (!file.isImage) { - return null; - } - - var thumbnailUrl = mediaHelper.getThumbnailFromPath(file.fileName); - - return thumbnailUrl; - } - function getExtension(fileName) { var extension = fileName.substring(fileName.lastIndexOf(".") + 1, fileName.length); return extension.toLowerCase(); @@ -238,7 +222,8 @@ isImage: isImage, extension: extension, fileName: files[i].name, - isClientSide: true + isClientSide: true, + fileData: files[i] }; // Save the file object to the files collection @@ -247,6 +232,7 @@ //special check for a comma in the name newVal += files[i].name.split(',').join('-') + ","; + // TODO: I would love to remove this part. But I'm affright it would be breaking if removed. Its not used by File upload anymore as each preview handles the client-side data on their own. if (isImage || extension === "svg") { var deferred = $q.defer(); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/mediapreview.service.js b/src/Umbraco.Web.UI.Client/src/common/services/mediapreview.service.js new file mode 100644 index 0000000000..b922e07c9c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/services/mediapreview.service.js @@ -0,0 +1,94 @@ +/** +* @ngdoc service +* @name umbraco.services.mediaPreview +* @description A service providing views used for dealing with previewing files. +* +* ##usage +* The service allows for registering and retrieving the view for one or more file extensions. +* +* You can register your own custom view in this way: +* +*
+*    angular.module('umbraco').run(['mediaPreview', function (mediaPreview) {
+*        mediaPreview.registerPreview(['docx'], "app_plugins/My_PACKAGE/preview.html");
+*    }]);
+* 
+* +* Here is a example of a preview template. (base on the audio-preview). +* +*
+*   
+*    
+* 
+* +* Notice that there often is a need to differentiate based on the file-data origin. In the state of the file still begin located locally its often needed to create an Object-URL for the data to be useable in HTML. As well you might want to provide links for the uploaded file when it is uploaded to the server. See 'vm.clientSide' and 'vm.clientSideData'. +* +**/ +function mediaPreview() { + + const DEFAULT_FILE_PREVIEW = "views/components/media/umbfilepreview/umb-file-preview.html"; + + var _mediaPreviews = []; + + function init(service) { + service.registerPreview(Umbraco.Sys.ServerVariables.umbracoSettings.imageFileTypes.split(","), "views/components/media/umbimagepreview/umb-image-preview.html"); + service.registerPreview(["svg"], "views/components/media/umbimagepreview/umb-image-preview.html"); + service.registerPreview(["mp4", "mov", "webm", "ogv"], "views/components/media/umbvideopreview/umb-video-preview.html"); + service.registerPreview(["mp3", "weba", "oga", "opus"], "views/components/media/umbaudiopreview/umb-audio-preview.html"); + } + + var service = { + + /** + * @ngdoc method + * @name umbraco.services.mediaPreview#getMediaPreview + * @methodOf umbraco.services.mediaPreview + * + * @param {string} fileExtension A string with the file extension, example: "pdf" + * + * @description + * The registered view matching this file extensions will be returned. + * + */ + getMediaPreview: function (fileExtension) { + + fileExtension = fileExtension.toLowerCase(); + + var previewObject = _mediaPreviews.find((preview) => preview.fileExtensions.indexOf(fileExtension) !== -1); + + if(previewObject !== undefined) { + return previewObject.view; + } + + return DEFAULT_FILE_PREVIEW; + }, + + /** + * @ngdoc method + * @name umbraco.services.mediaPreview#registerPreview + * @methodOf umbraco.services.mediaPreview + * + * @param {array} fileExtensions An array of file extensions, example: ["pdf", "jpg"] + * @param {array} view A URL to the view to be used for these file extensions. + * + * @description + * The registered view will be used when file extensions match the given file. + * + */ + registerPreview: function (fileExtensions, view) { + _mediaPreviews.push({ + fileExtensions: fileExtensions.map(e => e.toLowerCase()), + view: view + }) + } + + }; + + init(service); + + return service; +} angular.module('umbraco.services').factory('mediaPreview', mediaPreview); diff --git a/src/Umbraco.Web.UI.Client/src/less/belle.less b/src/Umbraco.Web.UI.Client/src/less/belle.less index ba4df33aa0..fa0543aeaf 100644 --- a/src/Umbraco.Web.UI.Client/src/less/belle.less +++ b/src/Umbraco.Web.UI.Client/src/less/belle.less @@ -202,6 +202,11 @@ @import "components/contextdialogs/umb-dialog-datatype-delete.less"; @import "components/umbemailmarketing.less"; +@import "../views/components/media/umbmediapreview/umb-media-preview.less"; +@import "../views/components/media/umbaudiopreview/umb-audio-preview.less"; +@import "../views/components/media/umbfilepreview/umb-file-preview.less"; +@import "../views/components/media/umbimagepreview/umb-image-preview.less"; +@import "../views/components/media/umbvideopreview/umb-video-preview.less"; // Editors @import "../views/common/infiniteeditors/rollback/rollback.less"; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-property-file-upload.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-property-file-upload.less index 75d171dd87..5856b0bd04 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-property-file-upload.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-property-file-upload.less @@ -38,4 +38,8 @@ border-color: @gray-1; } } + + .umb-property-file-upload--actions { + margin-top: 10px; + } } diff --git a/src/Umbraco.Web.UI.Client/src/less/property-editors.less b/src/Umbraco.Web.UI.Client/src/less/property-editors.less index 2805e7f79b..c3ad08b8f8 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -828,11 +828,15 @@ .umb-fileupload { display: flex; flex-direction: column; + padding: 20px; + border: 1px solid @inputBorder; + box-sizing: border-box; + width: 100%; + .umb-property-editor--limit-width(); } .umb-fileupload .preview { border-radius: 5px; - border: 1px solid @gray-6; padding: 3px; background: @gray-9; float: left; diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediaentryeditor/mediaentryeditor.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediaentryeditor/mediaentryeditor.controller.js index 6c8a038536..eea8e87034 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediaentryeditor/mediaentryeditor.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediaentryeditor/mediaentryeditor.controller.js @@ -38,6 +38,8 @@ angular.module("umbraco") entityResource.getById(vm.mediaEntry.mediaKey, "Media").then(function (mediaEntity) { vm.media = mediaEntity; vm.imageSrc = mediaHelper.resolveFileFromEntity(mediaEntity, true); + vm.fileSrc = mediaHelper.resolveFileFromEntity(mediaEntity, false); + vm.fileExtension = mediaHelper.getFileExtension(vm.fileSrc); vm.loading = false; vm.hasDimensions = false; vm.isCroppable = false; diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediaentryeditor/mediaentryeditor.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediaentryeditor/mediaentryeditor.html index 938d719431..a56e3aeed6 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediaentryeditor/mediaentryeditor.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediaentryeditor/mediaentryeditor.html @@ -1,127 +1,148 @@ -
+
+ + + + - +
+
+ + This item is in the Recycle Bin +
- +
+
+ - - - -
- -
- This item is in the Recycle Bin -
- -
-
- - - - -
- -
- -
- - - - - -
- -
- - - - - - - -
- - - -
-
- -
-
+ +
+
+
+ + +
- +
+ + - + + - +
+ + + +
+
+
+
+
- - + + - - + + + - - - -
- + + + + + +
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediaentryeditor/mediaentryeditor.less b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediaentryeditor/mediaentryeditor.less index 139d7bef4a..982ef7bc63 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediaentryeditor/mediaentryeditor.less +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediaentryeditor/mediaentryeditor.less @@ -110,11 +110,23 @@ } .umb-media-entry-editor__imageholder { - display: flex; - align-items: center; - justify-content: center; + position: relative; height: calc(100% - 50px); + + display: block; +} + +.umb-media-entry-editor__previewholder { + + position: relative; + height: calc(100% - 50px); + + display: flex; + justify-content: center; + align-items: center; + + overflow-y: auto; } .umb-media-entry-editor__imageholder-actions { diff --git a/src/Umbraco.Web.UI.Client/src/views/components/media/umbaudiopreview/umb-audio-preview.html b/src/Umbraco.Web.UI.Client/src/views/components/media/umbaudiopreview/umb-audio-preview.html new file mode 100644 index 0000000000..4b7bd1c2a6 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/media/umbaudiopreview/umb-audio-preview.html @@ -0,0 +1,8 @@ +
+ + +
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/media/umbaudiopreview/umb-audio-preview.less b/src/Umbraco.Web.UI.Client/src/views/components/media/umbaudiopreview/umb-audio-preview.less new file mode 100644 index 0000000000..5d69fa07f9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/media/umbaudiopreview/umb-audio-preview.less @@ -0,0 +1,18 @@ +.umb-audio-preview { + display: flex; + justify-content: center; + align-items: center; + audio { + max-width: 100%; + } + audio::-webkit-media-controls-panel { + background-color: white; + } + audio::-webkit-media-controls { + padding: 6px; + } + audio::-webkit-media-controls-enclosure { + &:extend(.shadow-depth-1); + border-radius: 6px; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/views/components/media/umbaudiopreview/umbaudiopreview.controller.js b/src/Umbraco.Web.UI.Client/src/views/components/media/umbaudiopreview/umbaudiopreview.controller.js new file mode 100644 index 0000000000..985c8540a3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/media/umbaudiopreview/umbaudiopreview.controller.js @@ -0,0 +1,11 @@ +angular.module("umbraco") + .controller("umbAudioPreviewController", + function () { + + var vm = this; + + vm.getClientSideUrl = function(source) { + return URL.createObjectURL(source); + } + + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/components/media/umbfilepreview/umb-file-preview.html b/src/Umbraco.Web.UI.Client/src/views/components/media/umbfilepreview/umb-file-preview.html new file mode 100644 index 0000000000..2c72e27c82 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/media/umbfilepreview/umb-file-preview.html @@ -0,0 +1,18 @@ +
+ + +
{{vm.name}}
+
+
+ +
{{vm.name}}
+
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/media/umbfilepreview/umb-file-preview.less b/src/Umbraco.Web.UI.Client/src/views/components/media/umbfilepreview/umb-file-preview.less new file mode 100644 index 0000000000..427ac38244 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/media/umbfilepreview/umb-file-preview.less @@ -0,0 +1,13 @@ +.umb-file-preview { + display: flex; + justify-content: center; + align-items: center; + + .umb-file-preview--file { + display: block; + box-sizing: border-box; + text-align: center; + max-width: 320px; + padding: 10px; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/views/components/media/umbimagepreview/umb-image-preview.html b/src/Umbraco.Web.UI.Client/src/views/components/media/umbimagepreview/umb-image-preview.html new file mode 100644 index 0000000000..989f8ef093 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/media/umbimagepreview/umb-image-preview.html @@ -0,0 +1,6 @@ +
+ {{vm.name}} + + {{vm.name}} + +
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/media/umbimagepreview/umb-image-preview.less b/src/Umbraco.Web.UI.Client/src/views/components/media/umbimagepreview/umb-image-preview.less new file mode 100644 index 0000000000..13f934c251 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/media/umbimagepreview/umb-image-preview.less @@ -0,0 +1,9 @@ +.umb-image-preview { + display: flex; + justify-content: center; + align-items: center; + + img { + width: 100%; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/views/components/media/umbimagepreview/umbimagepreview.controller.js b/src/Umbraco.Web.UI.Client/src/views/components/media/umbimagepreview/umbimagepreview.controller.js new file mode 100644 index 0000000000..36eb3958e2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/media/umbimagepreview/umbimagepreview.controller.js @@ -0,0 +1,18 @@ + + + + +angular.module("umbraco") + .controller("umbImagePreviewController", + function (mediaHelper) { + + var vm = this; + + vm.getThumbnail = function(source) { + return mediaHelper.getThumbnailFromPath(source) || source; + } + vm.getClientSideUrl = function(sourceData) { + return URL.createObjectURL(sourceData); + } + + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/components/media/umbmediapreview/umb-media-preview.less b/src/Umbraco.Web.UI.Client/src/views/components/media/umbmediapreview/umb-media-preview.less new file mode 100644 index 0000000000..78c41eeab6 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/media/umbmediapreview/umb-media-preview.less @@ -0,0 +1,14 @@ +umb-media-preview { + position: relative; +} +.umb-media-preview { + position: relative; + width: 100%; + height: 100%; + + min-height: 240px; + + display: flex; + justify-content: center; + align-items: center; +} diff --git a/src/Umbraco.Web.UI.Client/src/views/components/media/umbmediapreview/umbmediapreview.component.js b/src/Umbraco.Web.UI.Client/src/views/components/media/umbmediapreview/umbmediapreview.component.js new file mode 100644 index 0000000000..b7b5536b0a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/media/umbmediapreview/umbmediapreview.component.js @@ -0,0 +1,38 @@ +(function () { + "use strict"; + + angular + .module("umbraco") + .component("umbMediaPreview", { + template: "
", + controller: UmbMediaPreviewController, + controllerAs: "vm", + bindings: { + extension: "<", + source: "<", + name: "<", + clientSide: " { + vm.loading = true; + }) + $scope.$on("mediaPreviewLoadingComplete", () => { + vm.loading = false; + }) + + } + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/components/media/umbvideopreview/umb-video-preview.html b/src/Umbraco.Web.UI.Client/src/views/components/media/umbvideopreview/umb-video-preview.html new file mode 100644 index 0000000000..26003ab4c1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/media/umbvideopreview/umb-video-preview.html @@ -0,0 +1,8 @@ +
+ + +
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/media/umbvideopreview/umb-video-preview.less b/src/Umbraco.Web.UI.Client/src/views/components/media/umbvideopreview/umb-video-preview.less new file mode 100644 index 0000000000..3dd4e2f589 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/media/umbvideopreview/umb-video-preview.less @@ -0,0 +1,10 @@ +.umb-video-preview { + display: flex; + justify-content: center; + align-items: center; + height: 100%; + video { + max-width: 100%; + max-height: 100%; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/views/components/media/umbvideopreview/umbvideopreview.controller.js b/src/Umbraco.Web.UI.Client/src/views/components/media/umbvideopreview/umbvideopreview.controller.js new file mode 100644 index 0000000000..9c8b32d8b7 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/media/umbvideopreview/umbvideopreview.controller.js @@ -0,0 +1,15 @@ + + + + +angular.module("umbraco") + .controller("umbVideoPreviewController", + function () { + + var vm = this; + + vm.getClientSideUrl = function(source) { + return URL.createObjectURL(source); + } + + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/components/mediacard/umb-media-card.html b/src/Umbraco.Web.UI.Client/src/views/components/mediacard/umb-media-card.html index 76e1e99314..e54cc3e898 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/mediacard/umb-media-card.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/mediacard/umb-media-card.html @@ -30,7 +30,7 @@ diff --git a/src/Umbraco.Web.UI.Client/src/views/components/mediacard/umbMediaCard.component.js b/src/Umbraco.Web.UI.Client/src/views/components/mediacard/umbMediaCard.component.js index 24b20367aa..1014b95227 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/mediacard/umbMediaCard.component.js +++ b/src/Umbraco.Web.UI.Client/src/views/components/mediacard/umbMediaCard.component.js @@ -74,6 +74,7 @@ vm.media = mediaEntity; checkErrorState(); vm.thumbnail = mediaHelper.resolveFileFromEntity(mediaEntity, true); + vm.fileExtension = mediaHelper.getFileExtension(vm.media.metaData.MediaPath); vm.loading = false; }, function () { diff --git a/src/Umbraco.Web.UI.Client/src/views/components/upload/umb-property-file-upload.html b/src/Umbraco.Web.UI.Client/src/views/components/upload/umb-property-file-upload.html index ce5d7292c9..a6df878997 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/upload/umb-property-file-upload.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/upload/umb-property-file-upload.html @@ -1,58 +1,68 @@ 
+ + - - +
+ +

Click to upload

+ +
-
- -

Click to upload

- +
+
+
+
- -
-
- -
- -
-
-
- {{file.fileName}} - - {{file.fileName}} - -
-
-
- -
- - - -
{{file.fileName}}
-
-
- - -
{{file.fileName}}
-
-
-
-
- - - -
- -
-
-
-
+
+ + +
- - +
+
+
+
+
+
From a8e069019971ef4d159d4fa1f3bb2ca2194233b3 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Fri, 21 Jan 2022 09:31:09 +0100 Subject: [PATCH 091/141] moved namespace to match umbraco version --- src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs | 3 ++- .../Upgrade/{V_9_2_0 => V_9_3_0}/MovePackageXMLToDb.cs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) rename src/Umbraco.Infrastructure/Migrations/Upgrade/{V_9_2_0 => V_9_3_0}/MovePackageXMLToDb.cs (97%) diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs index ac11666960..502a4a0e7c 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs @@ -269,10 +269,11 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade // TO 9.2.0 To("{0571C395-8F0B-44E9-8E3F-47BDD08D817B}"); To("{AD3D3B7F-8E74-45A4-85DB-7FFAD57F9243}"); - To("{A2F22F17-5870-4179-8A8D-2362AA4A0A5F}"); + // TO 9.3.0 + To("{A2F22F17-5870-4179-8A8D-2362AA4A0A5F}"); To("{CA7A1D9D-C9D4-4914-BC0A-459E7B9C3C8C}"); } diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_2_0/MovePackageXMLToDb.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_3_0/MovePackageXMLToDb.cs similarity index 97% rename from src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_2_0/MovePackageXMLToDb.cs rename to src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_3_0/MovePackageXMLToDb.cs index e4a4af0cbb..3d003eb31d 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_2_0/MovePackageXMLToDb.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_3_0/MovePackageXMLToDb.cs @@ -9,7 +9,7 @@ using Umbraco.Cms.Infrastructure.Persistence; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Extensions; -namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_9_2_0 +namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_9_3_0 { public class MovePackageXMLToDb : MigrationBase { From 4136adebc89ca873be86cdc5159622886e1884b2 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 5 Jan 2022 15:36:36 +1100 Subject: [PATCH 092/141] Bump ImageSharp.Web version --- src/Umbraco.Web.Common/Umbraco.Web.Common.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj b/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj index 031a7a782f..060151fdb2 100644 --- a/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj +++ b/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj @@ -34,7 +34,7 @@ - + From ecd32bf03305c0a23afccbf9b3f9fcfdd4e4d784 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Fri, 21 Jan 2022 10:48:22 +0100 Subject: [PATCH 093/141] The `marked` dev dependency was unused --- src/Umbraco.Web.UI.Client/package-lock.json | 6 ------ src/Umbraco.Web.UI.Client/package.json | 1 - 2 files changed, 7 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index a9b706a677..0145421fa9 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -8256,12 +8256,6 @@ "object-visit": "^1.0.0" } }, - "marked": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-0.7.0.tgz", - "integrity": "sha512-c+yYdCZJQrsRjTPhUx7VKkApw9bwDkNbHUKo1ovgcfDjb2kc8rLuRbIFyXL5WOEUwzSSKo3IXpph2K6DqB/KZg==", - "dev": true - }, "matchdep": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz", diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index 6f451b89a7..ef8135487a 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -88,7 +88,6 @@ "karma-spec-reporter": "0.0.32", "less": "3.10.3", "lodash": "4.17.21", - "marked": "^0.7.0", "merge-stream": "2.0.0", "run-sequence": "2.2.1" } From 12abd883a9fc10bcb38f2272cbd143ebe7d7eac2 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Fri, 21 Jan 2022 13:10:34 +0100 Subject: [PATCH 094/141] Member 2FA (#11889) * Bugfix - Take ufprt from form data if the request has form content type, otherwise fallback to use the query * External linking for members * Changed migration to reuse old table * removed unnecessary web.config files * Cleanup * Extracted class to own file * Clean up * Rollback changes to Umbraco.Web.UI.csproj * Fixed migration for SqlCE * Added 2fa for members * Change notification handler to be on deleted * Update src/Umbraco.Infrastructure/Security/MemberUserStore.cs Co-authored-by: Mole * updated snippets * Fixed issue with errors not shown on member linking * fixed issue with errors * clean up * Fix issue where external logins could not be used to upgrade Umbraco, because the externalLogin table was expected to look different. (Like after the migration) * Fixed issue in Ignore legacy column now using result column. * Updated 2fa for members + publish notification when 2fa is requested. * Changed so only Members out of box supports 2fa * Cleanup * rollback of csproj file, that should not have been changed * Removed confirmed flag from db. It was not used. Handle case where a user is signed up for 2fa, but the provider do not exist anymore. Then it is just ignored until it shows up again Reintroduced ProviderName on interface, to ensure the class can be renamed safely * Bugfix * Registering DeleteTwoFactorLoginsOnMemberDeletedHandler * Rollback nuget packages added by mistake * Update src/Umbraco.Infrastructure/Services/Implement/TwoFactorLoginService.cs Co-authored-by: Mole * Update src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TwoFactorLoginRepository.cs Co-authored-by: Mole * Added providername to snippet Co-authored-by: Mole --- src/Umbraco.Core/Models/ITwoFactorLogin.cs | 12 ++ src/Umbraco.Core/Models/TwoFactorLogin.cs | 13 ++ .../MemberTwoFactorRequestedNotification.cs | 14 ++ .../Persistence/Constants-DatabaseSchema.cs | 1 + .../Repositories/ITwoFactorLoginRepository.cs | 16 ++ .../Services/ITwoFactorLoginService.cs | 28 +++ .../UmbracoBuilder.Repositories.cs | 2 + .../UmbracoBuilder.Services.cs | 1 + .../Install/DatabaseSchemaCreator.cs | 1 + .../Migrations/Upgrade/UmbracoPlan.cs | 1 + .../Upgrade/V_9_3_0/AddTwoFactorLoginTable.cs | 24 +++ .../Persistence/Dtos/TwoFactorLoginDto.cs | 33 ++++ .../Implement/TwoFactorLoginRepository.cs | 137 +++++++++++++++ .../Security/BackOfficeIdentityUser.cs | 19 +++ ...teTwoFactorLoginsOnMemberDeletedHandler.cs | 33 ++++ .../Security/ITwoFactorProvider.cs | 22 +++ .../Security/MemberIdentityBuilder.cs | 63 +++++++ .../Security/MemberUserStore.cs | 44 ++++- .../Security/UmbracoUserManager.cs | 8 + .../Implement/TwoFactorLoginService.cs | 118 +++++++++++++ .../UmbracoBuilder.MembersIdentity.cs | 4 +- .../Extensions/IdentityBuilderExtensions.cs | 10 ++ .../UmbracoApplicationBuilder.Identity.cs | 28 +++ .../Extensions/ViewDataExtensions.cs | 14 ++ .../IMemberSignInManagerExternalLogins.cs | 3 + .../Security/MemberManager.cs | 3 + .../Security/MemberSignInManager.cs | 59 ++++--- .../Security/TwoFactorValidationProvider.cs | 91 ++++++++++ .../Templates/EditProfile.cshtml | 4 +- .../PartialViewMacros/Templates/Login.cshtml | 31 ++++ .../Controllers/UmbExternalLoginController.cs | 23 ++- .../Controllers/UmbLoginController.cs | 65 +++++-- .../UmbTwoFactorLoginController.cs | 160 ++++++++++++++++++ .../Security/MemberManagerTests.cs | 4 +- .../Security/MemberUserStoreTests.cs | 3 +- .../Security/MemberSignInManagerTests.cs | 9 +- 36 files changed, 1044 insertions(+), 57 deletions(-) create mode 100644 src/Umbraco.Core/Models/ITwoFactorLogin.cs create mode 100644 src/Umbraco.Core/Models/TwoFactorLogin.cs create mode 100644 src/Umbraco.Core/Notifications/MemberTwoFactorRequestedNotification.cs create mode 100644 src/Umbraco.Core/Persistence/Repositories/ITwoFactorLoginRepository.cs create mode 100644 src/Umbraco.Core/Services/ITwoFactorLoginService.cs create mode 100644 src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_3_0/AddTwoFactorLoginTable.cs create mode 100644 src/Umbraco.Infrastructure/Persistence/Dtos/TwoFactorLoginDto.cs create mode 100644 src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TwoFactorLoginRepository.cs create mode 100644 src/Umbraco.Infrastructure/Security/DeleteTwoFactorLoginsOnMemberDeletedHandler.cs create mode 100644 src/Umbraco.Infrastructure/Security/ITwoFactorProvider.cs create mode 100644 src/Umbraco.Infrastructure/Security/MemberIdentityBuilder.cs create mode 100644 src/Umbraco.Infrastructure/Services/Implement/TwoFactorLoginService.cs create mode 100644 src/Umbraco.Web.Common/Security/TwoFactorValidationProvider.cs create mode 100644 src/Umbraco.Web.Website/Controllers/UmbTwoFactorLoginController.cs diff --git a/src/Umbraco.Core/Models/ITwoFactorLogin.cs b/src/Umbraco.Core/Models/ITwoFactorLogin.cs new file mode 100644 index 0000000000..ca005309b2 --- /dev/null +++ b/src/Umbraco.Core/Models/ITwoFactorLogin.cs @@ -0,0 +1,12 @@ +using System; +using Umbraco.Cms.Core.Models.Entities; + +namespace Umbraco.Cms.Core.Models +{ + public interface ITwoFactorLogin: IEntity, IRememberBeingDirty + { + string ProviderName { get; } + string Secret { get; } + Guid UserOrMemberKey { get; } + } +} diff --git a/src/Umbraco.Core/Models/TwoFactorLogin.cs b/src/Umbraco.Core/Models/TwoFactorLogin.cs new file mode 100644 index 0000000000..6ede9606e8 --- /dev/null +++ b/src/Umbraco.Core/Models/TwoFactorLogin.cs @@ -0,0 +1,13 @@ +using System; +using Umbraco.Cms.Core.Models.Entities; + +namespace Umbraco.Cms.Core.Models +{ + public class TwoFactorLogin : EntityBase, ITwoFactorLogin + { + public string ProviderName { get; set; } + public string Secret { get; set; } + public Guid UserOrMemberKey { get; set; } + public bool Confirmed { get; set; } + } +} diff --git a/src/Umbraco.Core/Notifications/MemberTwoFactorRequestedNotification.cs b/src/Umbraco.Core/Notifications/MemberTwoFactorRequestedNotification.cs new file mode 100644 index 0000000000..980a531ffd --- /dev/null +++ b/src/Umbraco.Core/Notifications/MemberTwoFactorRequestedNotification.cs @@ -0,0 +1,14 @@ +using System; + +namespace Umbraco.Cms.Core.Notifications +{ + public class MemberTwoFactorRequestedNotification : INotification + { + public MemberTwoFactorRequestedNotification(Guid memberKey) + { + MemberKey = memberKey; + } + + public Guid MemberKey { get; } + } +} diff --git a/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs b/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs index 37560b4c0a..de5b8c04ae 100644 --- a/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs +++ b/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs @@ -55,6 +55,7 @@ namespace Umbraco.Cms.Core public const string UserGroup2Node = TableNamePrefix + "UserGroup2Node"; public const string UserGroup2NodePermission = TableNamePrefix + "UserGroup2NodePermission"; public const string ExternalLogin = TableNamePrefix + "ExternalLogin"; + public const string TwoFactorLogin = TableNamePrefix + "TwoFactorLogin"; public const string ExternalLoginToken = TableNamePrefix + "ExternalLoginToken"; public const string Macro = /*TableNamePrefix*/ "cms" + "Macro"; diff --git a/src/Umbraco.Core/Persistence/Repositories/ITwoFactorLoginRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ITwoFactorLoginRepository.cs new file mode 100644 index 0000000000..63622f8e82 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/ITwoFactorLoginRepository.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Core.Persistence.Repositories +{ + public interface ITwoFactorLoginRepository: IReadRepository, IWriteRepository + { + Task DeleteUserLoginsAsync(Guid userOrMemberKey); + Task DeleteUserLoginsAsync(Guid userOrMemberKey, string providerName); + + Task> GetByUserOrMemberKeyAsync(Guid userOrMemberKey); + } + +} diff --git a/src/Umbraco.Core/Services/ITwoFactorLoginService.cs b/src/Umbraco.Core/Services/ITwoFactorLoginService.cs new file mode 100644 index 0000000000..dd11f864fb --- /dev/null +++ b/src/Umbraco.Core/Services/ITwoFactorLoginService.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Core.Services +{ + public interface ITwoFactorLoginService : IService + { + /// + /// Deletes all user logins - normally used when a member is deleted + /// + Task DeleteUserLoginsAsync(Guid userOrMemberKey); + + Task IsTwoFactorEnabledAsync(Guid userKey); + Task GetSecretForUserAndProviderAsync(Guid userKey, string providerName); + + Task GetSetupInfoAsync(Guid userOrMemberKey, string providerName); + + IEnumerable GetAllProviderNames(); + Task DisableAsync(Guid userOrMemberKey, string providerName); + + bool ValidateTwoFactorSetup(string providerName, string secret, string code); + Task SaveAsync(TwoFactorLogin twoFactorLogin); + Task> GetEnabledTwoFactorProviderNamesAsync(Guid userOrMemberKey); + } + +} diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs index ed2bf67e4a..f9dc43cbd5 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs @@ -1,6 +1,7 @@ using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Persistence.Repositories; +using Umbraco.Cms.Core.Services; using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; using Umbraco.Extensions; @@ -30,6 +31,7 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection builder.Services.AddUnique(); builder.Services.AddUnique(); builder.Services.AddUnique(); + builder.Services.AddUnique(); builder.Services.AddUnique(); builder.Services.AddUnique(factory => factory.GetRequiredService()); builder.Services.AddUnique(factory => factory.GetRequiredService()); diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs index c79cbf9d94..aeec82a94e 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs @@ -75,6 +75,7 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection )); builder.Services.AddUnique(factory => factory.GetRequiredService()); builder.Services.AddUnique(factory => factory.GetRequiredService()); + builder.Services.AddUnique(); builder.Services.AddUnique(); builder.Services.AddUnique(); builder.Services.AddTransient(SourcesFactory); diff --git a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseSchemaCreator.cs b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseSchemaCreator.cs index 8fb9767eb7..9dab0bd14a 100644 --- a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseSchemaCreator.cs +++ b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseSchemaCreator.cs @@ -60,6 +60,7 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install typeof(CacheInstructionDto), typeof(ExternalLoginDto), typeof(ExternalLoginTokenDto), + typeof(TwoFactorLoginDto), typeof(RedirectUrlDto), typeof(LockDto), typeof(UserGroupDto), diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs index 502a4a0e7c..2080034554 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs @@ -275,6 +275,7 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade // TO 9.3.0 To("{A2F22F17-5870-4179-8A8D-2362AA4A0A5F}"); To("{CA7A1D9D-C9D4-4914-BC0A-459E7B9C3C8C}"); + To("{0828F206-DCF7-4F73-ABBB-6792275532EB}"); } } diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_3_0/AddTwoFactorLoginTable.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_3_0/AddTwoFactorLoginTable.cs new file mode 100644 index 0000000000..c5e569282a --- /dev/null +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_3_0/AddTwoFactorLoginTable.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; +using Umbraco.Cms.Infrastructure.Persistence.Dtos; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_9_3_0 +{ + public class AddTwoFactorLoginTable : MigrationBase + { + public AddTwoFactorLoginTable(IMigrationContext context) : base(context) + { + } + + protected override void Migrate() + { + IEnumerable tables = SqlSyntax.GetTablesInSchema(Context.Database); + if (tables.InvariantContains(TwoFactorLoginDto.TableName)) + { + return; + } + + Create.Table().Do(); + } + } +} diff --git a/src/Umbraco.Infrastructure/Persistence/Dtos/TwoFactorLoginDto.cs b/src/Umbraco.Infrastructure/Persistence/Dtos/TwoFactorLoginDto.cs new file mode 100644 index 0000000000..1202fe2a19 --- /dev/null +++ b/src/Umbraco.Infrastructure/Persistence/Dtos/TwoFactorLoginDto.cs @@ -0,0 +1,33 @@ +using System; +using NPoco; +using Umbraco.Cms.Infrastructure.Persistence.DatabaseAnnotations; + +namespace Umbraco.Cms.Infrastructure.Persistence.Dtos +{ + [TableName(TableName)] + [ExplicitColumns] + [PrimaryKey("Id")] + internal class TwoFactorLoginDto + { + public const string TableName = Cms.Core.Constants.DatabaseSchema.Tables.TwoFactorLogin; + + [Column("id")] + [PrimaryKeyColumn] + public int Id { get; set; } + + [Column("userOrMemberKey")] + [Index(IndexTypes.NonClustered)] + public Guid UserOrMemberKey { get; set; } + + [Column("providerName")] + [Length(400)] + [NullSetting(NullSetting = NullSettings.NotNull)] + [Index(IndexTypes.UniqueNonClustered, ForColumns = "providerName,userOrMemberKey", Name = "IX_" + TableName + "_ProviderName")] + public string ProviderName { get; set; } + + [Column("secret")] + [Length(400)] + [NullSetting(NullSetting = NullSettings.NotNull)] + public string Secret { get; set; } + } +} diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TwoFactorLoginRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TwoFactorLoginRepository.cs new file mode 100644 index 0000000000..18063edf16 --- /dev/null +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TwoFactorLoginRepository.cs @@ -0,0 +1,137 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using NPoco; +using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Persistence.Querying; +using Umbraco.Cms.Core.Persistence.Repositories; +using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Infrastructure.Persistence.Dtos; +using Umbraco.Cms.Infrastructure.Persistence.Querying; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement +{ + internal class TwoFactorLoginRepository : EntityRepositoryBase, ITwoFactorLoginRepository + { + public TwoFactorLoginRepository(IScopeAccessor scopeAccessor, AppCaches cache, + ILogger logger) + : base(scopeAccessor, cache, logger) + { + } + + + protected override Sql GetBaseQuery(bool isCount) + { + var sql = SqlContext.Sql(); + + sql = isCount + ? sql.SelectCount() + : sql.Select(); + + sql.From(); + + return sql; + } + + protected override string GetBaseWhereClause() => + Core.Constants.DatabaseSchema.Tables.TwoFactorLogin + ".id = @id"; + + protected override IEnumerable GetDeleteClauses() => Enumerable.Empty(); + + protected override ITwoFactorLogin PerformGet(int id) + { + var sql = GetBaseQuery(false).Where(x => x.Id == id); + var dto = Database.Fetch(sql).FirstOrDefault(); + return dto == null ? null : Map(dto); + } + + protected override IEnumerable PerformGetAll(params int[] ids) + { + var sql = GetBaseQuery(false).WhereIn(x => x.Id, ids); + var dtos = Database.Fetch(sql); + return dtos.WhereNotNull().Select(Map); + } + + protected override IEnumerable PerformGetByQuery(IQuery query) + { + var sqlClause = GetBaseQuery(false); + var translator = new SqlTranslator(sqlClause, query); + var sql = translator.Translate(); + return Database.Fetch(sql).Select(Map); + } + + protected override void PersistNewItem(ITwoFactorLogin entity) + { + var dto = Map(entity); + Database.Insert(dto); + } + + protected override void PersistUpdatedItem(ITwoFactorLogin entity) + { + var dto = Map(entity); + Database.Update(dto); + } + + private static TwoFactorLoginDto Map(ITwoFactorLogin entity) + { + if (entity == null) return null; + + return new TwoFactorLoginDto + { + Id = entity.Id, + UserOrMemberKey = entity.UserOrMemberKey, + ProviderName = entity.ProviderName, + Secret = entity.Secret, + }; + } + + private static ITwoFactorLogin Map(TwoFactorLoginDto dto) + { + if (dto == null) return null; + + return new TwoFactorLogin + { + Id = dto.Id, + UserOrMemberKey = dto.UserOrMemberKey, + ProviderName = dto.ProviderName, + Secret = dto.Secret, + }; + } + + public async Task DeleteUserLoginsAsync(Guid userOrMemberKey) + { + return await DeleteUserLoginsAsync(userOrMemberKey, null); + } + + public async Task DeleteUserLoginsAsync(Guid userOrMemberKey, string providerName) + { + var sql = Sql() + .Delete() + .From() + .Where(x => x.UserOrMemberKey == userOrMemberKey); + + if (providerName is not null) + { + sql = sql.Where(x => x.ProviderName == providerName); + } + + var deletedRows = await Database.ExecuteAsync(sql); + + return deletedRows > 0; + } + + public async Task> GetByUserOrMemberKeyAsync(Guid userOrMemberKey) + { + var sql = Sql() + .Select() + .From() + .Where(x => x.UserOrMemberKey == userOrMemberKey); + var dtos = await Database.FetchAsync(sql); + return dtos.WhereNotNull().Select(Map); + } + } +} diff --git a/src/Umbraco.Infrastructure/Security/BackOfficeIdentityUser.cs b/src/Umbraco.Infrastructure/Security/BackOfficeIdentityUser.cs index ebd12719e1..df4d704781 100644 --- a/src/Umbraco.Infrastructure/Security/BackOfficeIdentityUser.cs +++ b/src/Umbraco.Infrastructure/Security/BackOfficeIdentityUser.cs @@ -159,5 +159,24 @@ namespace Umbraco.Cms.Core.Security } private static string UserIdToString(int userId) => string.Intern(userId.ToString(CultureInfo.InvariantCulture)); + + public Guid Key => UserIdToInt(Id).ToGuid(); + + + private static int UserIdToInt(string userId) + { + if(int.TryParse(userId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) + { + return result; + } + + if(Guid.TryParse(userId, out var key)) + { + // Reverse the IntExtensions.ToGuid + return BitConverter.ToInt32(key.ToByteArray(), 0); + } + + throw new InvalidOperationException($"Unable to convert user ID ({userId})to int using InvariantCulture"); + } } } diff --git a/src/Umbraco.Infrastructure/Security/DeleteTwoFactorLoginsOnMemberDeletedHandler.cs b/src/Umbraco.Infrastructure/Security/DeleteTwoFactorLoginsOnMemberDeletedHandler.cs new file mode 100644 index 0000000000..7fe4a7c506 --- /dev/null +++ b/src/Umbraco.Infrastructure/Security/DeleteTwoFactorLoginsOnMemberDeletedHandler.cs @@ -0,0 +1,33 @@ +using System.Threading; +using System.Threading.Tasks; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Infrastructure.Security +{ + /// + /// Deletes the two factor for the deleted members. This cannot be handled by the database as there is not foreign keys. + /// + public class DeleteTwoFactorLoginsOnMemberDeletedHandler : INotificationAsyncHandler + { + private readonly ITwoFactorLoginService _twoFactorLoginService; + + /// + /// Initializes a new instance of the class. + /// + public DeleteTwoFactorLoginsOnMemberDeletedHandler(ITwoFactorLoginService twoFactorLoginService) + => _twoFactorLoginService = twoFactorLoginService; + + /// + public async Task HandleAsync(MemberDeletedNotification notification, CancellationToken cancellationToken) + { + foreach (IMember member in notification.DeletedEntities) + { + await _twoFactorLoginService.DeleteUserLoginsAsync(member.Key); + } + } + + } +} diff --git a/src/Umbraco.Infrastructure/Security/ITwoFactorProvider.cs b/src/Umbraco.Infrastructure/Security/ITwoFactorProvider.cs new file mode 100644 index 0000000000..f0da6c314a --- /dev/null +++ b/src/Umbraco.Infrastructure/Security/ITwoFactorProvider.cs @@ -0,0 +1,22 @@ +using System; +using System.Threading.Tasks; + +namespace Umbraco.Cms.Core.Security +{ + public interface ITwoFactorProvider + { + string ProviderName { get; } + + Task GetSetupDataAsync(Guid userOrMemberKey, string secret); + + bool ValidateTwoFactorPIN(string secret, string token); + + /// + /// + /// + /// Called to confirm the setup of two factor on the user. + bool ValidateTwoFactorSetup(string secret, string token); + } + + +} diff --git a/src/Umbraco.Infrastructure/Security/MemberIdentityBuilder.cs b/src/Umbraco.Infrastructure/Security/MemberIdentityBuilder.cs new file mode 100644 index 0000000000..c0df423638 --- /dev/null +++ b/src/Umbraco.Infrastructure/Security/MemberIdentityBuilder.cs @@ -0,0 +1,63 @@ +using System; +using System.Reflection; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Umbraco.Cms.Core.Net; +using Umbraco.Cms.Core.Serialization; + +namespace Umbraco.Cms.Core.Security +{ + + public class MemberIdentityBuilder : IdentityBuilder + { + /// + /// Initializes a new instance of the class. + /// + public MemberIdentityBuilder(IServiceCollection services) + : base(typeof(MemberIdentityUser), services) + => InitializeServices(services); + + /// + /// Initializes a new instance of the class. + /// + public MemberIdentityBuilder(Type role, IServiceCollection services) + : base(typeof(MemberIdentityUser), role, services) + => InitializeServices(services); + + private void InitializeServices(IServiceCollection services) + { + + } + + // override to add itself, by default identity only wants a single IdentityErrorDescriber + public override IdentityBuilder AddErrorDescriber() + { + if (!typeof(MembersErrorDescriber).IsAssignableFrom(typeof(TDescriber))) + { + throw new InvalidOperationException($"The type {typeof(TDescriber)} does not inherit from {typeof(MembersErrorDescriber)}"); + } + + Services.AddScoped(); + return this; + } + + /// + /// Adds a token provider for the . + /// + /// The name of the provider to add. + /// The type of the to add. + /// The current instance. + public override IdentityBuilder AddTokenProvider(string providerName, Type provider) + { + if (!typeof(IUserTwoFactorTokenProvider<>).MakeGenericType(UserType).GetTypeInfo().IsAssignableFrom(provider.GetTypeInfo())) + { + throw new InvalidOperationException($"Invalid Type for TokenProvider: {provider.FullName}"); + } + + Services.Configure(options => options.Tokens.ProviderMap[providerName] = new TokenProviderDescriptor(provider)); + Services.AddTransient(provider); + return this; + } + } +} diff --git a/src/Umbraco.Infrastructure/Security/MemberUserStore.cs b/src/Umbraco.Infrastructure/Security/MemberUserStore.cs index da45e4d888..4fba880e81 100644 --- a/src/Umbraco.Infrastructure/Security/MemberUserStore.cs +++ b/src/Umbraco.Infrastructure/Security/MemberUserStore.cs @@ -29,6 +29,7 @@ namespace Umbraco.Cms.Core.Security private readonly IScopeProvider _scopeProvider; private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; private readonly IExternalLoginWithKeyService _externalLoginService; + private readonly ITwoFactorLoginService _twoFactorLoginService; /// /// Initializes a new instance of the class for the members identity store @@ -37,7 +38,9 @@ namespace Umbraco.Cms.Core.Security /// The mapper for properties /// The scope provider /// The error describer + /// The published snapshot accessor /// The external login service + /// The two factor login service [ActivatorUtilitiesConstructor] public MemberUserStore( IMemberService memberService, @@ -45,7 +48,8 @@ namespace Umbraco.Cms.Core.Security IScopeProvider scopeProvider, IdentityErrorDescriber describer, IPublishedSnapshotAccessor publishedSnapshotAccessor, - IExternalLoginWithKeyService externalLoginService + IExternalLoginWithKeyService externalLoginService, + ITwoFactorLoginService twoFactorLoginService ) : base(describer) { @@ -54,9 +58,10 @@ namespace Umbraco.Cms.Core.Security _scopeProvider = scopeProvider ?? throw new ArgumentNullException(nameof(scopeProvider)); _publishedSnapshotAccessor = publishedSnapshotAccessor; _externalLoginService = externalLoginService; + _twoFactorLoginService = twoFactorLoginService; } - [Obsolete("Use ctor with IExternalLoginWithKeyService param")] + [Obsolete("Use ctor with IExternalLoginWithKeyService and ITwoFactorLoginService param")] public MemberUserStore( IMemberService memberService, IUmbracoMapper mapper, @@ -64,19 +69,19 @@ namespace Umbraco.Cms.Core.Security IdentityErrorDescriber describer, IPublishedSnapshotAccessor publishedSnapshotAccessor, IExternalLoginService externalLoginService) - : this(memberService, mapper, scopeProvider, describer, publishedSnapshotAccessor, StaticServiceProvider.Instance.GetRequiredService()) + : this(memberService, mapper, scopeProvider, describer, publishedSnapshotAccessor, StaticServiceProvider.Instance.GetRequiredService(), StaticServiceProvider.Instance.GetRequiredService()) { } - [Obsolete("Use ctor with IExternalLoginWithKeyService param")] + [Obsolete("Use ctor with IExternalLoginWithKeyService and ITwoFactorLoginService param")] public MemberUserStore( IMemberService memberService, IUmbracoMapper mapper, IScopeProvider scopeProvider, IdentityErrorDescriber describer, IPublishedSnapshotAccessor publishedSnapshotAccessor) - : this(memberService, mapper, scopeProvider, describer, publishedSnapshotAccessor, StaticServiceProvider.Instance.GetRequiredService()) + : this(memberService, mapper, scopeProvider, describer, publishedSnapshotAccessor, StaticServiceProvider.Instance.GetRequiredService(), StaticServiceProvider.Instance.GetRequiredService()) { } @@ -678,5 +683,34 @@ namespace Umbraco.Cms.Core.Security LoginOnly, FullSave } + + /// + /// Overridden to support Umbraco's own data storage requirements + /// + /// + /// The base class's implementation of this calls into FindTokenAsync, RemoveUserTokenAsync and AddUserTokenAsync, both methods will only work with ORMs that are change + /// tracking ORMs like EFCore. + /// + /// + public override Task GetTokenAsync(MemberIdentityUser user, string loginProvider, string name, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + IIdentityUserToken token = user.LoginTokens.FirstOrDefault(x => x.LoginProvider.InvariantEquals(loginProvider) && x.Name.InvariantEquals(name)); + + return Task.FromResult(token?.Value); + } + + /// + public override async Task GetTwoFactorEnabledAsync(MemberIdentityUser user, + CancellationToken cancellationToken = default(CancellationToken)) + { + return await _twoFactorLoginService.IsTwoFactorEnabledAsync(user.Key); + } } } diff --git a/src/Umbraco.Infrastructure/Security/UmbracoUserManager.cs b/src/Umbraco.Infrastructure/Security/UmbracoUserManager.cs index dfef27242b..1410473f6a 100644 --- a/src/Umbraco.Infrastructure/Security/UmbracoUserManager.cs +++ b/src/Umbraco.Infrastructure/Security/UmbracoUserManager.cs @@ -263,5 +263,13 @@ namespace Umbraco.Cms.Core.Security return await VerifyPasswordAsync(userPasswordStore, user, password) == PasswordVerificationResult.Success; } + + /// + public virtual async Task> GetValidTwoFactorProvidersAsync(TUser user) + { + var results = await base.GetValidTwoFactorProvidersAsync(user); + + return results; + } } } diff --git a/src/Umbraco.Infrastructure/Services/Implement/TwoFactorLoginService.cs b/src/Umbraco.Infrastructure/Services/Implement/TwoFactorLoginService.cs new file mode 100644 index 0000000000..713a73c1df --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Implement/TwoFactorLoginService.cs @@ -0,0 +1,118 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Persistence.Repositories; +using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Core.Security; + +namespace Umbraco.Cms.Core.Services +{ + public class TwoFactorLoginService : ITwoFactorLoginService + { + private readonly ITwoFactorLoginRepository _twoFactorLoginRepository; + private readonly IScopeProvider _scopeProvider; + private readonly IOptions _identityOptions; + private readonly IDictionary _twoFactorSetupGenerators; + + public TwoFactorLoginService( + ITwoFactorLoginRepository twoFactorLoginRepository, + IScopeProvider scopeProvider, + IEnumerable twoFactorSetupGenerators, + IOptions identityOptions) + { + _twoFactorLoginRepository = twoFactorLoginRepository; + _scopeProvider = scopeProvider; + _identityOptions = identityOptions; + _twoFactorSetupGenerators = twoFactorSetupGenerators.ToDictionary(x=>x.ProviderName); + } + + public async Task DeleteUserLoginsAsync(Guid userOrMemberKey) + { + using IScope scope = _scopeProvider.CreateScope(autoComplete: true); + await _twoFactorLoginRepository.DeleteUserLoginsAsync(userOrMemberKey); + } + + public async Task> GetEnabledTwoFactorProviderNamesAsync(Guid userOrMemberKey) + { + return await GetEnabledProviderNamesAsync(userOrMemberKey); + } + + private async Task> GetEnabledProviderNamesAsync(Guid userOrMemberKey) + { + using IScope scope = _scopeProvider.CreateScope(autoComplete: true); + var providersOnUser = (await _twoFactorLoginRepository.GetByUserOrMemberKeyAsync(userOrMemberKey)) + .Select(x => x.ProviderName).ToArray(); + + return providersOnUser.Where(x => _identityOptions.Value.Tokens.ProviderMap.ContainsKey(x)); + } + + + public async Task IsTwoFactorEnabledAsync(Guid userOrMemberKey) + { + return (await GetEnabledProviderNamesAsync(userOrMemberKey)).Any(); + } + + public async Task GetSecretForUserAndProviderAsync(Guid userOrMemberKey, string providerName) + { + using IScope scope = _scopeProvider.CreateScope(autoComplete: true); + return (await _twoFactorLoginRepository.GetByUserOrMemberKeyAsync(userOrMemberKey)).FirstOrDefault(x=>x.ProviderName == providerName)?.Secret; + } + + public async Task GetSetupInfoAsync(Guid userOrMemberKey, string providerName) + { + var secret = await GetSecretForUserAndProviderAsync(userOrMemberKey, providerName); + + //Dont allow to generate a new secrets if user already has one + if (!string.IsNullOrEmpty(secret)) + { + return default; + } + + secret = GenerateSecret(); + + if (!_twoFactorSetupGenerators.TryGetValue(providerName, out ITwoFactorProvider generator)) + { + throw new InvalidOperationException($"No ITwoFactorSetupGenerator found for provider: {providerName}"); + } + + return await generator.GetSetupDataAsync(userOrMemberKey, secret); + } + + public IEnumerable GetAllProviderNames() => _twoFactorSetupGenerators.Keys; + public async Task DisableAsync(Guid userOrMemberKey, string providerName) + { + using IScope scope = _scopeProvider.CreateScope(autoComplete: true); + return (await _twoFactorLoginRepository.DeleteUserLoginsAsync(userOrMemberKey, providerName)); + + } + + public bool ValidateTwoFactorSetup(string providerName, string secret, string code) + { + if (!_twoFactorSetupGenerators.TryGetValue(providerName, out ITwoFactorProvider generator)) + { + throw new InvalidOperationException($"No ITwoFactorSetupGenerator found for provider: {providerName}"); + } + + return generator.ValidateTwoFactorSetup(secret, code); + } + + public Task SaveAsync(TwoFactorLogin twoFactorLogin) + { + using IScope scope = _scopeProvider.CreateScope(autoComplete: true); + _twoFactorLoginRepository.Save(twoFactorLogin); + + return Task.CompletedTask; + } + + + /// + /// Generates a new random unique secret. + /// + /// The random secret + protected virtual string GenerateSecret() => Guid.NewGuid().ToString(); + } +} diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.MembersIdentity.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.MembersIdentity.cs index 66badc479e..98391d7590 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.MembersIdentity.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.MembersIdentity.cs @@ -51,7 +51,8 @@ namespace Umbraco.Extensions factory.GetRequiredService(), factory.GetRequiredService(), factory.GetRequiredService(), - factory.GetRequiredService() + factory.GetRequiredService(), + factory.GetRequiredService() )) .AddRoleStore() .AddRoleManager() @@ -63,6 +64,7 @@ namespace Umbraco.Extensions builder.AddNotificationHandler(); + builder.AddNotificationAsyncHandler(); services.ConfigureOptions(); services.AddScoped(x => (IMemberUserStore)x.GetRequiredService>()); diff --git a/src/Umbraco.Web.Common/Extensions/IdentityBuilderExtensions.cs b/src/Umbraco.Web.Common/Extensions/IdentityBuilderExtensions.cs index f1d2ac4a3d..9b80f3e82a 100644 --- a/src/Umbraco.Web.Common/Extensions/IdentityBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/IdentityBuilderExtensions.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Infrastructure.Security; namespace Umbraco.Extensions { @@ -59,5 +60,14 @@ namespace Umbraco.Extensions identityBuilder.Services.AddScoped(typeof(TInterface), implementationFactory); return identityBuilder; } + + public static MemberIdentityBuilder AddTwoFactorProvider(this MemberIdentityBuilder identityBuilder, string providerName) where T : class, ITwoFactorProvider + { + identityBuilder.Services.AddSingleton(); + identityBuilder.Services.AddSingleton(); + identityBuilder.AddTokenProvider>(providerName); + + return identityBuilder; + } } } diff --git a/src/Umbraco.Web.Common/Extensions/UmbracoApplicationBuilder.Identity.cs b/src/Umbraco.Web.Common/Extensions/UmbracoApplicationBuilder.Identity.cs index 64fde06ac8..e7c0246f40 100644 --- a/src/Umbraco.Web.Common/Extensions/UmbracoApplicationBuilder.Identity.cs +++ b/src/Umbraco.Web.Common/Extensions/UmbracoApplicationBuilder.Identity.cs @@ -20,5 +20,33 @@ namespace Umbraco.Extensions builder.Services.Replace(ServiceDescriptor.Scoped(userManagerType, customType)); return builder; } + + public static IUmbracoBuilder SetBackOfficeUserStore(this IUmbracoBuilder builder) + where TUserStore : BackOfficeUserStore + { + Type customType = typeof(TUserStore); + builder.Services.Replace(ServiceDescriptor.Scoped(typeof(IUserStore<>).MakeGenericType(typeof(BackOfficeIdentityUser)), customType)); + return builder; + } + + public static IUmbracoBuilder SetMemberManager(this IUmbracoBuilder builder) + where TUserManager : UserManager, IMemberManager + { + + Type customType = typeof(TUserManager); + Type userManagerType = typeof(UserManager); + builder.Services.Replace(ServiceDescriptor.Scoped(typeof(IMemberManager), customType)); + builder.Services.AddScoped(customType, services => services.GetRequiredService(userManagerType)); + builder.Services.Replace(ServiceDescriptor.Scoped(userManagerType, customType)); + return builder; + } + + public static IUmbracoBuilder SetMemberUserStore(this IUmbracoBuilder builder) + where TUserStore : MemberUserStore + { + Type customType = typeof(TUserStore); + builder.Services.Replace(ServiceDescriptor.Scoped(typeof(IUserStore<>).MakeGenericType(typeof(MemberIdentityUser)), customType)); + return builder; + } } } diff --git a/src/Umbraco.Web.Common/Extensions/ViewDataExtensions.cs b/src/Umbraco.Web.Common/Extensions/ViewDataExtensions.cs index 36adacc2d2..8e62ca09cf 100644 --- a/src/Umbraco.Web.Common/Extensions/ViewDataExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/ViewDataExtensions.cs @@ -1,7 +1,9 @@ using System; +using System.Collections.Generic; using System.Text; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Semver; using Umbraco.Cms.Core.Serialization; @@ -16,6 +18,7 @@ namespace Umbraco.Extensions public const string TokenUmbracoVersion = "UmbracoVersion"; public const string TokenExternalSignInError = "ExternalSignInError"; public const string TokenPasswordResetCode = "PasswordResetCode"; + public const string TokenTwoFactorRequired = "TwoFactorRequired"; public static bool FromTempData(this ViewDataDictionary viewData, ITempDataDictionary tempData, string token) { @@ -135,5 +138,16 @@ namespace Umbraco.Extensions { viewData[TokenPasswordResetCode] = value; } + + public static void SetTwoFactorProviderNames(this ViewDataDictionary viewData, IEnumerable providerNames) + { + viewData[TokenTwoFactorRequired] = providerNames; + } + + public static bool TryGetTwoFactorProviderNames(this ViewDataDictionary viewData, out IEnumerable providerNames) + { + providerNames = viewData[TokenTwoFactorRequired] as IEnumerable; + return providerNames is not null; + } } } diff --git a/src/Umbraco.Web.Common/Security/IMemberSignInManagerExternalLogins.cs b/src/Umbraco.Web.Common/Security/IMemberSignInManagerExternalLogins.cs index 3599a028f4..eb6a66a000 100644 --- a/src/Umbraco.Web.Common/Security/IMemberSignInManagerExternalLogins.cs +++ b/src/Umbraco.Web.Common/Security/IMemberSignInManagerExternalLogins.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Identity; +using Umbraco.Cms.Core.Security; namespace Umbraco.Cms.Web.Common.Security { @@ -12,5 +13,7 @@ namespace Umbraco.Cms.Web.Common.Security Task GetExternalLoginInfoAsync(string expectedXsrf = null); Task UpdateExternalAuthenticationTokensAsync(ExternalLoginInfo externalLogin); Task ExternalLoginSignInAsync(ExternalLoginInfo loginInfo, bool isPersistent, bool bypassTwoFactor = false); + Task GetTwoFactorAuthenticationUserAsync(); + Task TwoFactorSignInAsync(string provider, string code, bool isPersistent, bool rememberClient); } } diff --git a/src/Umbraco.Web.Common/Security/MemberManager.cs b/src/Umbraco.Web.Common/Security/MemberManager.cs index 9a0f26aff4..93aad3a060 100644 --- a/src/Umbraco.Web.Common/Security/MemberManager.cs +++ b/src/Umbraco.Web.Common/Security/MemberManager.cs @@ -45,6 +45,9 @@ namespace Umbraco.Cms.Web.Common.Security _httpContextAccessor = httpContextAccessor; } + /// + public override bool SupportsUserTwoFactor => true; + /// public async Task IsMemberAuthorizedAsync(IEnumerable allowTypes = null, IEnumerable allowGroups = null, IEnumerable allowMembers = null) { diff --git a/src/Umbraco.Web.Common/Security/MemberSignInManager.cs b/src/Umbraco.Web.Common/Security/MemberSignInManager.cs index 6407c4fac8..e8bf1c2eb3 100644 --- a/src/Umbraco.Web.Common/Security/MemberSignInManager.cs +++ b/src/Umbraco.Web.Common/Security/MemberSignInManager.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Security.Claims; +using System.Security.Principal; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Http; @@ -9,6 +10,8 @@ using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Web.Common.DependencyInjection; using Umbraco.Extensions; @@ -22,6 +25,7 @@ namespace Umbraco.Cms.Web.Common.Security public class MemberSignInManager : UmbracoSignInManager, IMemberSignInManagerExternalLogins { private readonly IMemberExternalLoginProviders _memberExternalLoginProviders; + private readonly IEventAggregator _eventAggregator; public MemberSignInManager( UserManager memberManager, @@ -31,10 +35,12 @@ namespace Umbraco.Cms.Web.Common.Security ILogger> logger, IAuthenticationSchemeProvider schemes, IUserConfirmation confirmation, - IMemberExternalLoginProviders memberExternalLoginProviders) : + IMemberExternalLoginProviders memberExternalLoginProviders, + IEventAggregator eventAggregator) : base(memberManager, contextAccessor, claimsFactory, optionsAccessor, logger, schemes, confirmation) { _memberExternalLoginProviders = memberExternalLoginProviders; + _eventAggregator = eventAggregator; } [Obsolete("Use ctor with all params")] @@ -46,7 +52,9 @@ namespace Umbraco.Cms.Web.Common.Security ILogger> logger, IAuthenticationSchemeProvider schemes, IUserConfirmation confirmation) : - this(memberManager, contextAccessor, claimsFactory, optionsAccessor, logger, schemes, confirmation, StaticServiceProvider.Instance.GetRequiredService()) + this(memberManager, contextAccessor, claimsFactory, optionsAccessor, logger, schemes, confirmation, + StaticServiceProvider.Instance.GetRequiredService(), + StaticServiceProvider.Instance.GetRequiredService()) { } // use default scheme for members @@ -61,30 +69,6 @@ namespace Umbraco.Cms.Web.Common.Security // use default scheme for members protected override string TwoFactorRememberMeAuthenticationType => IdentityConstants.TwoFactorRememberMeScheme; - /// - public override Task GetTwoFactorAuthenticationUserAsync() - => throw new NotImplementedException("Two factor is not yet implemented for members"); - - /// - public override Task TwoFactorSignInAsync(string provider, string code, bool isPersistent, bool rememberClient) - => throw new NotImplementedException("Two factor is not yet implemented for members"); - - /// - public override Task IsTwoFactorClientRememberedAsync(MemberIdentityUser user) - => throw new NotImplementedException("Two factor is not yet implemented for members"); - - /// - public override Task RememberTwoFactorClientAsync(MemberIdentityUser user) - => throw new NotImplementedException("Two factor is not yet implemented for members"); - - /// - public override Task ForgetTwoFactorClientAsync() - => throw new NotImplementedException("Two factor is not yet implemented for members"); - - /// - public override Task TwoFactorRecoveryCodeSignInAsync(string recoveryCode) - => throw new NotImplementedException("Two factor is not yet implemented for members"); - /// public override async Task GetExternalLoginInfoAsync(string expectedXsrf = null) { @@ -369,6 +353,29 @@ namespace Umbraco.Cms.Web.Common.Security private void LogFailedExternalLogin(ExternalLoginInfo loginInfo, MemberIdentityUser user) => Logger.LogWarning("The AutoLinkOptions of the external authentication provider '{LoginProvider}' have refused the login based on the OnExternalLogin method. Affected user id: '{UserId}'", loginInfo.LoginProvider, user.Id); + protected override async Task SignInOrTwoFactorAsync(MemberIdentityUser user, bool isPersistent, + string loginProvider = null, bool bypassTwoFactor = false) + { + var result = await base.SignInOrTwoFactorAsync(user, isPersistent, loginProvider, bypassTwoFactor); + if (result.RequiresTwoFactor) + { + NotifyRequiresTwoFactor(user); + } + + return result; + } + + protected void NotifyRequiresTwoFactor(MemberIdentityUser user) => Notify(user, + (currentUser) => new MemberTwoFactorRequestedNotification(currentUser.Key) + ); + + private T Notify(MemberIdentityUser currentUser, Func createNotification) where T : INotification + { + + var notification = createNotification(currentUser); + _eventAggregator.Publish(notification); + return notification; + } } } diff --git a/src/Umbraco.Web.Common/Security/TwoFactorValidationProvider.cs b/src/Umbraco.Web.Common/Security/TwoFactorValidationProvider.cs new file mode 100644 index 0000000000..32b3226440 --- /dev/null +++ b/src/Umbraco.Web.Common/Security/TwoFactorValidationProvider.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.DataProtection; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Services; + +using Microsoft.Extensions.Logging; + +namespace Umbraco.Cms.Infrastructure.Security +{ + public class TwoFactorBackOfficeValidationProvider : TwoFactorValidationProvider + where TTwoFactorSetupGenerator : ITwoFactorProvider + { + protected TwoFactorBackOfficeValidationProvider(IDataProtectionProvider dataProtectionProvider, IOptions options, ILogger> logger, ITwoFactorLoginService twoFactorLoginService, TTwoFactorSetupGenerator generator) : base(dataProtectionProvider, options, logger, twoFactorLoginService, generator) + { + } + + } + + public class TwoFactorMemberValidationProvider : TwoFactorValidationProvider + where TTwoFactorSetupGenerator : ITwoFactorProvider + { + public TwoFactorMemberValidationProvider(IDataProtectionProvider dataProtectionProvider, IOptions options, ILogger> logger, ITwoFactorLoginService twoFactorLoginService, TTwoFactorSetupGenerator generator) : base(dataProtectionProvider, options, logger, twoFactorLoginService, generator) + { + } + + } + + public class TwoFactorValidationProvider + : DataProtectorTokenProvider + where TUmbracoIdentityUser : UmbracoIdentityUser + where TTwoFactorSetupGenerator : ITwoFactorProvider + { + private readonly ITwoFactorLoginService _twoFactorLoginService; + private readonly TTwoFactorSetupGenerator _generator; + + protected TwoFactorValidationProvider( + + IDataProtectionProvider dataProtectionProvider, + IOptions options, + ILogger> logger, + ITwoFactorLoginService twoFactorLoginService, + TTwoFactorSetupGenerator generator) + : base(dataProtectionProvider, options, logger) + { + _twoFactorLoginService = twoFactorLoginService; + _generator = generator; + } + + public override Task CanGenerateTwoFactorTokenAsync(UserManager manager, + TUmbracoIdentityUser user) => Task.FromResult(_generator is not null); + + public override async Task ValidateAsync(string purpose, string token, + UserManager manager, TUmbracoIdentityUser user) + { + var secret = + await _twoFactorLoginService.GetSecretForUserAndProviderAsync(GetUserKey(user), _generator.ProviderName); + + if (secret is null) + { + return false; + } + + var validToken = _generator.ValidateTwoFactorPIN(secret, token); + + + return validToken; + } + + protected Guid GetUserKey(TUmbracoIdentityUser user) + { + + switch (user) + { + case MemberIdentityUser memberIdentityUser: + return memberIdentityUser.Key; + case BackOfficeIdentityUser backOfficeIdentityUser: + return backOfficeIdentityUser.Key; + default: + throw new NotSupportedException( + "Current we only support MemberIdentityUser and BackOfficeIdentityUser"); + } + + } + + } +} diff --git a/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/EditProfile.cshtml b/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/EditProfile.cshtml index 095c3c050d..1b1ebd7284 100644 --- a/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/EditProfile.cshtml +++ b/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/EditProfile.cshtml @@ -1,7 +1,4 @@ @inherits Umbraco.Cms.Web.Common.Macros.PartialViewMacroPage - -@using Umbraco.Cms.Core -@using Umbraco.Cms.Core.Security @using Umbraco.Cms.Core.Services @using Umbraco.Cms.Web.Common.Security @using Umbraco.Cms.Web.Website.Controllers @@ -11,6 +8,7 @@ @inject IMemberExternalLoginProviders memberExternalLoginProviders @inject IExternalLoginWithKeyService externalLoginWithKeyService @{ + // Build a profile model to edit var profileModel = await memberModelBuilderFactory .CreateProfileModel() diff --git a/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/Login.cshtml b/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/Login.cshtml index 85b7f53c24..7ba7f2acca 100644 --- a/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/Login.cshtml +++ b/src/Umbraco.Web.UI/umbraco/PartialViewMacros/Templates/Login.cshtml @@ -1,9 +1,12 @@ @inherits Umbraco.Cms.Web.Common.Macros.PartialViewMacroPage + @using Umbraco.Cms.Web.Common.Models @using Umbraco.Cms.Web.Common.Security @using Umbraco.Cms.Web.Website.Controllers +@using Umbraco.Cms.Core.Services @using Umbraco.Extensions @inject IMemberExternalLoginProviders memberExternalLoginProviders +@inject ITwoFactorLoginService twoFactorLoginService @{ var loginModel = new LoginModel(); // You can modify this to redirect to a different URL instead of the current one @@ -14,6 +17,33 @@ + +@if (ViewData.TryGetTwoFactorProviderNames(out var providerNames)) +{ + + foreach (var providerName in providerNames) + { +
+

Two factor with @providerName.

+
+ @using (Html.BeginUmbracoForm(nameof(UmbTwoFactorLoginController.Verify2FACode))) + { + + + + Input security code:
+ +
+
+ } +
+ } + +} +else +{ + + +} diff --git a/src/Umbraco.Web.Website/Controllers/UmbExternalLoginController.cs b/src/Umbraco.Web.Website/Controllers/UmbExternalLoginController.cs index 2d5ec250e9..c43754e170 100644 --- a/src/Umbraco.Web.Website/Controllers/UmbExternalLoginController.cs +++ b/src/Umbraco.Web.Website/Controllers/UmbExternalLoginController.cs @@ -4,12 +4,13 @@ using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Logging; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Routing; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; @@ -27,9 +28,12 @@ namespace Umbraco.Cms.Web.Website.Controllers public class UmbExternalLoginController : SurfaceController { private readonly IMemberManager _memberManager; + private readonly ITwoFactorLoginService _twoFactorLoginService; + private readonly ILogger _logger; private readonly IMemberSignInManagerExternalLogins _memberSignInManager; public UmbExternalLoginController( + ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, IUmbracoDatabaseFactory databaseFactory, ServiceContext services, @@ -37,7 +41,8 @@ namespace Umbraco.Cms.Web.Website.Controllers IProfilingLogger profilingLogger, IPublishedUrlProvider publishedUrlProvider, IMemberSignInManagerExternalLogins memberSignInManager, - IMemberManager memberManager) + IMemberManager memberManager, + ITwoFactorLoginService twoFactorLoginService) : base( umbracoContextAccessor, databaseFactory, @@ -46,8 +51,10 @@ namespace Umbraco.Cms.Web.Website.Controllers profilingLogger, publishedUrlProvider) { + _logger = logger; _memberSignInManager = memberSignInManager; _memberManager = memberManager; + _twoFactorLoginService = twoFactorLoginService; } /// @@ -108,14 +115,12 @@ namespace Umbraco.Cms.Web.Website.Controllers $"No local user found for the login provider {loginInfo.LoginProvider} - {loginInfo.ProviderKey}"); } - // create a with information to display a custom two factor send code view - var verifyResponse = - new ObjectResult(new { userId = attemptedUser.Id }) - { - StatusCode = StatusCodes.Status402PaymentRequired - }; - return verifyResponse; + var providerNames = await _twoFactorLoginService.GetEnabledTwoFactorProviderNamesAsync(attemptedUser.Key); + ViewData.SetTwoFactorProviderNames(providerNames); + + return CurrentUmbracoPage(); + } if (result == SignInResult.LockedOut) diff --git a/src/Umbraco.Web.Website/Controllers/UmbLoginController.cs b/src/Umbraco.Web.Website/Controllers/UmbLoginController.cs index afeb41a252..9dbcd292e4 100644 --- a/src/Umbraco.Web.Website/Controllers/UmbLoginController.cs +++ b/src/Umbraco.Web.Website/Controllers/UmbLoginController.cs @@ -1,14 +1,20 @@ using System; using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Logging; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Routing; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Web; using Umbraco.Cms.Infrastructure.Persistence; +using Umbraco.Cms.Web.Common.ActionsResults; +using Umbraco.Cms.Web.Common.DependencyInjection; using Umbraco.Cms.Web.Common.Filters; using Umbraco.Cms.Web.Common.Models; using Umbraco.Cms.Web.Common.Security; @@ -20,7 +26,29 @@ namespace Umbraco.Cms.Web.Website.Controllers public class UmbLoginController : SurfaceController { private readonly IMemberSignInManager _signInManager; + private readonly IMemberManager _memberManager; + private readonly ITwoFactorLoginService _twoFactorLoginService; + + [ActivatorUtilitiesConstructor] + public UmbLoginController( + IUmbracoContextAccessor umbracoContextAccessor, + IUmbracoDatabaseFactory databaseFactory, + ServiceContext services, + AppCaches appCaches, + IProfilingLogger profilingLogger, + IPublishedUrlProvider publishedUrlProvider, + IMemberSignInManager signInManager, + IMemberManager memberManager, + ITwoFactorLoginService twoFactorLoginService) + : base(umbracoContextAccessor, databaseFactory, services, appCaches, profilingLogger, publishedUrlProvider) + { + _signInManager = signInManager; + _memberManager = memberManager; + _twoFactorLoginService = twoFactorLoginService; + } + + [Obsolete("Use ctor with all params")] public UmbLoginController( IUmbracoContextAccessor umbracoContextAccessor, IUmbracoDatabaseFactory databaseFactory, @@ -29,9 +57,11 @@ namespace Umbraco.Cms.Web.Website.Controllers IProfilingLogger profilingLogger, IPublishedUrlProvider publishedUrlProvider, IMemberSignInManager signInManager) - : base(umbracoContextAccessor, databaseFactory, services, appCaches, profilingLogger, publishedUrlProvider) + : this(umbracoContextAccessor, databaseFactory, services, appCaches, profilingLogger, publishedUrlProvider, signInManager, + StaticServiceProvider.Instance.GetRequiredService(), + StaticServiceProvider.Instance.GetRequiredService()) { - _signInManager = signInManager; + } [HttpPost] @@ -74,15 +104,28 @@ namespace Umbraco.Cms.Web.Website.Controllers if (result.RequiresTwoFactor) { - throw new NotImplementedException("Two factor support is not supported for Umbraco members yet"); + MemberIdentityUser attemptedUser = await _memberManager.FindByNameAsync(model.Username); + if (attemptedUser == null) + { + return new ValidationErrorResult( + $"No local member found for username {model.Username}"); + } + + var providerNames = await _twoFactorLoginService.GetEnabledTwoFactorProviderNamesAsync(attemptedUser.Key); + ViewData.SetTwoFactorProviderNames(providerNames); + } + else if (result.IsLockedOut) + { + ModelState.AddModelError("loginModel", "Member is locked out"); + } + else if (result.IsNotAllowed) + { + ModelState.AddModelError("loginModel", "Member is not allowed"); + } + else + { + ModelState.AddModelError("loginModel", "Invalid username or password"); } - - // TODO: We can check for these and respond differently if we think it's important - // result.IsLockedOut - // result.IsNotAllowed - - // Don't add a field level error, just model level. - ModelState.AddModelError("loginModel", "Invalid username or password"); return CurrentUmbracoPage(); } @@ -97,5 +140,7 @@ namespace Umbraco.Cms.Web.Website.Controllers model.RedirectUrl = redirectUrl.ToString(); } } + + } } diff --git a/src/Umbraco.Web.Website/Controllers/UmbTwoFactorLoginController.cs b/src/Umbraco.Web.Website/Controllers/UmbTwoFactorLoginController.cs new file mode 100644 index 0000000000..ba86e63a36 --- /dev/null +++ b/src/Umbraco.Web.Website/Controllers/UmbTwoFactorLoginController.cs @@ -0,0 +1,160 @@ +using System.Collections.Generic; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http.Extensions; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Logging; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Routing; +using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Web; +using Umbraco.Cms.Infrastructure.Persistence; +using Umbraco.Cms.Web.Common.ActionsResults; +using Umbraco.Cms.Web.Common.Filters; +using Umbraco.Cms.Web.Common.Security; +using Umbraco.Extensions; +using SignInResult = Microsoft.AspNetCore.Identity.SignInResult; + +namespace Umbraco.Cms.Web.Website.Controllers +{ + [UmbracoMemberAuthorize] + public class UmbTwoFactorLoginController : SurfaceController + { + private readonly IMemberManager _memberManager; + private readonly ITwoFactorLoginService _twoFactorLoginService; + private readonly ILogger _logger; + private readonly IMemberSignInManagerExternalLogins _memberSignInManager; + + public UmbTwoFactorLoginController( + ILogger logger, + IUmbracoContextAccessor umbracoContextAccessor, + IUmbracoDatabaseFactory databaseFactory, + ServiceContext services, + AppCaches appCaches, + IProfilingLogger profilingLogger, + IPublishedUrlProvider publishedUrlProvider, + IMemberSignInManagerExternalLogins memberSignInManager, + IMemberManager memberManager, + ITwoFactorLoginService twoFactorLoginService) + : base( + umbracoContextAccessor, + databaseFactory, + services, + appCaches, + profilingLogger, + publishedUrlProvider) + { + _logger = logger; + _memberSignInManager = memberSignInManager; + _memberManager = memberManager; + _twoFactorLoginService = twoFactorLoginService; + } + + /// + /// Used to retrieve the 2FA providers for code submission + /// + /// + [AllowAnonymous] + public async Task>> Get2FAProviders() + { + var user = await _memberSignInManager.GetTwoFactorAuthenticationUserAsync(); + if (user == null) + { + _logger.LogWarning("Get2FAProviders :: No verified member found, returning 404"); + return NotFound(); + } + + var userFactors = await _memberManager.GetValidTwoFactorProvidersAsync(user); + return new ObjectResult(userFactors); + } + + [AllowAnonymous] + public async Task Verify2FACode(Verify2FACodeModel model, string returnUrl = null) + { + var user = await _memberSignInManager.GetTwoFactorAuthenticationUserAsync(); + if (user == null) + { + _logger.LogWarning("PostVerify2FACode :: No verified member found, returning 404"); + return NotFound(); + } + + if (ModelState.IsValid) + { + var result = await _memberSignInManager.TwoFactorSignInAsync(model.Provider, model.Code, model.IsPersistent, model.RememberClient); + if (result.Succeeded) + { + return RedirectToLocal(returnUrl); + } + + if (result.IsLockedOut) + { + ModelState.AddModelError(nameof(Verify2FACodeModel.Code), "Member is locked out"); + } + else if (result.IsNotAllowed) + { + ModelState.AddModelError(nameof(Verify2FACodeModel.Code), "Member is not allowed"); + } + else + { + ModelState.AddModelError(nameof(Verify2FACodeModel.Code), "Invalid code"); + } + } + + //We need to set this, to ensure we show the 2fa login page + var providerNames = await _twoFactorLoginService.GetEnabledTwoFactorProviderNamesAsync(user.Key); + ViewData.SetTwoFactorProviderNames(providerNames); + return CurrentUmbracoPage(); + } + + [HttpPost] + public async Task ValidateAndSaveSetup(string providerName, string secret, string code, string returnUrl = null) + { + var member = await _memberManager.GetCurrentMemberAsync(); + + var isValid = _twoFactorLoginService.ValidateTwoFactorSetup(providerName, secret, code); + + if (isValid == false) + { + ModelState.AddModelError(nameof(code), "Invalid Code"); + + return CurrentUmbracoPage(); + } + + var twoFactorLogin = new TwoFactorLogin() + { + Confirmed = true, + Secret = secret, + UserOrMemberKey = member.Key, + ProviderName = providerName + }; + + await _twoFactorLoginService.SaveAsync(twoFactorLogin); + + return RedirectToLocal(returnUrl); + } + + [HttpPost] + public async Task Disable(string providerName, string returnUrl = null) + { + var member = await _memberManager.GetCurrentMemberAsync(); + + var success = await _twoFactorLoginService.DisableAsync(member.Key, providerName); + + if (!success) + { + return CurrentUmbracoPage(); + } + + return RedirectToLocal(returnUrl); + } + + private IActionResult RedirectToLocal(string returnUrl) => + Url.IsLocalUrl(returnUrl) ? Redirect(returnUrl) : RedirectToCurrentUmbracoPage(); + } +} diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberManagerTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberManagerTests.cs index e09fb70d8e..dedccca16e 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberManagerTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberManagerTests.cs @@ -52,7 +52,9 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Security new UmbracoMapper(new MapDefinitionCollection(() => mapDefinitions), scopeProvider), scopeProvider, new IdentityErrorDescriber(), - Mock.Of(), Mock.Of()); + Mock.Of(), + Mock.Of(), + Mock.Of()); _mockIdentityOptions = new Mock>(); var idOptions = new IdentityOptions { Lockout = { AllowedForNewUsers = false } }; diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberUserStoreTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberUserStoreTests.cs index 4ed2f0895d..14261e34fb 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberUserStoreTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberUserStoreTests.cs @@ -38,7 +38,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Security mockScopeProvider.Object, new IdentityErrorDescriber(), Mock.Of(), - Mock.Of() + Mock.Of(), + Mock.Of() ); } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Security/MemberSignInManagerTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Security/MemberSignInManagerTests.cs index e616cafd08..ccba5a4494 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Security/MemberSignInManagerTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Security/MemberSignInManagerTests.cs @@ -12,6 +12,7 @@ using Microsoft.Extensions.Options; using Moq; using NUnit.Framework; using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Net; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; @@ -47,6 +48,11 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Common.Security { o.Cookie.Name = IdentityConstants.TwoFactorUserIdScheme; o.ExpireTimeSpan = TimeSpan.FromMinutes(5); + }) + .AddCookie(IdentityConstants.TwoFactorRememberMeScheme, o => + { + o.Cookie.Name = IdentityConstants.TwoFactorRememberMeScheme; + o.ExpireTimeSpan = TimeSpan.FromMinutes(5); }); IServiceProvider serviceProvider = serviceProviderFactory.CreateServiceProvider(serviceCollection); var httpContextFactory = new DefaultHttpContextFactory(serviceProvider); @@ -66,7 +72,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Common.Security _mockLogger.Object, Mock.Of(), Mock.Of>(), - Mock.Of() + Mock.Of(), + Mock.Of() ); } private static Mock MockMemberManager() From 4843b28184b0c9c9c40fe96b06633f000e685954 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Fri, 21 Jan 2022 19:30:38 +0100 Subject: [PATCH 095/141] added contains check for acceptance tests --- .../cypress/integration/Languages/languages.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/Umbraco.Tests.AcceptanceTest/cypress/integration/Languages/languages.ts b/tests/Umbraco.Tests.AcceptanceTest/cypress/integration/Languages/languages.ts index 5898335105..33d5de24cb 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/cypress/integration/Languages/languages.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/cypress/integration/Languages/languages.ts @@ -13,6 +13,7 @@ context('Languages', () => { cy.umbracoEnsureLanguageCultureNotExists(culture); cy.umbracoSection('settings'); + cy.get('.umb-tree-root-link').contains('Settings') // Enter language tree and create new language cy.umbracoTreeItem('settings', ['Languages']).click(); cy.umbracoButtonByLabelKey('languages_addLanguage').click(); From 775bdff03bfa15e5bbff56b0b88e1298c79adfc2 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Sun, 23 Jan 2022 15:26:17 +0100 Subject: [PATCH 096/141] Fix after merge. --- .../DependencyInjection/UmbracoBuilder.cs | 12 ++++++++-- .../UmbracoBuilder.Services.cs | 14 ----------- .../CreatedPackageSchemaRepository.cs | 4 ++-- .../Implement/TwoFactorLoginRepository.cs | 2 +- .../Security/BackOfficeUserStore.cs | 23 ------------------- .../UmbracoBuilder.BackOfficeIdentity.cs | 2 +- 6 files changed, 14 insertions(+), 43 deletions(-) diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs index 30494b2761..d89cb5715e 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs @@ -29,11 +29,13 @@ using Umbraco.Cms.Core.Manifest; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Packaging; +using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.PublishedCache; using Umbraco.Cms.Core.PublishedCache.Internal; using Umbraco.Cms.Core.Routing; using Umbraco.Cms.Core.Runtime; +using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; @@ -289,8 +291,14 @@ namespace Umbraco.Cms.Core.DependencyInjection Services.AddUnique(); Services.AddUnique(); Services.AddUnique(); - Services.AddUnique(); - + Services.AddUnique(factory => new ExternalLoginService( + factory.GetRequiredService(), + factory.GetRequiredService(), + factory.GetRequiredService(), + factory.GetRequiredService() + )); + Services.AddUnique(factory => factory.GetRequiredService()); + Services.AddUnique(factory => factory.GetRequiredService()); Services.AddUnique(factory => new LocalizedTextService( factory.GetRequiredService>(), factory.GetRequiredService>())); diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs index cf2dde2bea..80d4dd9b3f 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs @@ -18,9 +18,7 @@ using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services.Implement; using Umbraco.Cms.Infrastructure.Packaging; -using Umbraco.Cms.Infrastructure.Persistence; using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; -using Umbraco.Cms.Infrastructure.Services.Implement; using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.DependencyInjection @@ -46,21 +44,9 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection builder.Services.AddUnique(); builder.Services.AddUnique(); builder.Services.AddUnique(); - builder.Services.AddUnique(); builder.Services.AddUnique(); - builder.Services.AddUnique(); builder.Services.AddUnique(); - builder.Services.AddUnique(factory => new ExternalLoginService( - factory.GetRequiredService(), - factory.GetRequiredService(), - factory.GetRequiredService(), - factory.GetRequiredService() - )); - builder.Services.AddUnique(factory => factory.GetRequiredService()); - builder.Services.AddUnique(factory => factory.GetRequiredService()); builder.Services.AddUnique(); - builder.Services.AddUnique(); - builder.Services.AddUnique(); builder.Services.AddTransient(SourcesFactory); builder.Services.AddUnique(factory => CreatePackageRepository(factory, "createdPackages.config")); builder.Services.AddUnique(); diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs index be1a31c2c9..462ae0a64e 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs @@ -45,7 +45,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement /// Initializes a new instance of the class. /// public CreatedPackageSchemaRepository( - IUmbracoDatabase umbracoDatabase, + IUmbracoDatabaseFactory umbracoDatabaseFactory, IHostingEnvironment hostingEnvironment, IOptions globalSettings, FileSystems fileSystems, @@ -62,7 +62,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement string mediaFolderPath = null, string tempFolderPath = null) { - _umbracoDatabase = umbracoDatabase; + _umbracoDatabase = umbracoDatabaseFactory.CreateDatabase(); _hostingEnvironment = hostingEnvironment; _fileSystems = fileSystems; _serializer = serializer; diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TwoFactorLoginRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TwoFactorLoginRepository.cs index 18063edf16..4b3ccb4d6a 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TwoFactorLoginRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TwoFactorLoginRepository.cs @@ -8,9 +8,9 @@ using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Core.Persistence.Repositories; -using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Infrastructure.Persistence.Querying; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement diff --git a/src/Umbraco.Infrastructure/Security/BackOfficeUserStore.cs b/src/Umbraco.Infrastructure/Security/BackOfficeUserStore.cs index df05b1e6b5..4fff80ced6 100644 --- a/src/Umbraco.Infrastructure/Security/BackOfficeUserStore.cs +++ b/src/Umbraco.Infrastructure/Security/BackOfficeUserStore.cs @@ -62,29 +62,6 @@ namespace Umbraco.Cms.Core.Security _externalLoginService = externalLoginService; } - [Obsolete("Use ctor injecting IExternalLoginWithKeyService ")] - public BackOfficeUserStore( - IScopeProvider scopeProvider, - IUserService userService, - IEntityService entityService, - IExternalLoginService externalLoginService, - IOptions globalSettings, - IUmbracoMapper mapper, - BackOfficeErrorDescriber describer, - AppCaches appCaches) - : this( - scopeProvider, - userService, - entityService, - StaticServiceProvider.Instance.GetRequiredService(), - globalSettings, - mapper, - describer, - appCaches) - { - - } - /// public override Task CreateAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default) { diff --git a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilder.BackOfficeIdentity.cs b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilder.BackOfficeIdentity.cs index e9cc213598..90ee7aabcc 100644 --- a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilder.BackOfficeIdentity.cs +++ b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilder.BackOfficeIdentity.cs @@ -38,7 +38,7 @@ namespace Umbraco.Extensions factory.GetRequiredService(), factory.GetRequiredService(), factory.GetRequiredService(), - factory.GetRequiredService>(), + factory.GetRequiredService>(), factory.GetRequiredService(), factory.GetRequiredService(), factory.GetRequiredService() From 8b773c90c20045bc2a336f482c1c1a70e6030b0f Mon Sep 17 00:00:00 2001 From: Mole Date: Wed, 19 Jan 2022 15:43:14 +0100 Subject: [PATCH 097/141] Add IHtmlSanitizer --- src/Umbraco.Core/Security/IHtmlSanitizer.cs | 7 +++++++ src/Umbraco.Core/Security/NoOpHtmlSanitizer.cs | 10 ++++++++++ 2 files changed, 17 insertions(+) create mode 100644 src/Umbraco.Core/Security/IHtmlSanitizer.cs create mode 100644 src/Umbraco.Core/Security/NoOpHtmlSanitizer.cs diff --git a/src/Umbraco.Core/Security/IHtmlSanitizer.cs b/src/Umbraco.Core/Security/IHtmlSanitizer.cs new file mode 100644 index 0000000000..7f3f033ba7 --- /dev/null +++ b/src/Umbraco.Core/Security/IHtmlSanitizer.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Core.Security +{ + public interface IHtmlSanitizer + { + string Sanitize(string html); + } +} diff --git a/src/Umbraco.Core/Security/NoOpHtmlSanitizer.cs b/src/Umbraco.Core/Security/NoOpHtmlSanitizer.cs new file mode 100644 index 0000000000..f16ce81ce1 --- /dev/null +++ b/src/Umbraco.Core/Security/NoOpHtmlSanitizer.cs @@ -0,0 +1,10 @@ +namespace Umbraco.Core.Security +{ + public class NoOpHtmlSanitizer : IHtmlSanitizer + { + public string Sanitize(string html) + { + return html; + } + } +} From e2d0a0f699c755821df3a847aed5f0e1b170d64f Mon Sep 17 00:00:00 2001 From: Mole Date: Mon, 24 Jan 2022 08:38:31 +0100 Subject: [PATCH 098/141] Add docstrings to IHtmlSanitizer --- src/Umbraco.Core/Security/IHtmlSanitizer.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Umbraco.Core/Security/IHtmlSanitizer.cs b/src/Umbraco.Core/Security/IHtmlSanitizer.cs index 7f3f033ba7..fa1e0b3ee5 100644 --- a/src/Umbraco.Core/Security/IHtmlSanitizer.cs +++ b/src/Umbraco.Core/Security/IHtmlSanitizer.cs @@ -2,6 +2,11 @@ namespace Umbraco.Core.Security { public interface IHtmlSanitizer { + /// + /// Sanitizes HTML + /// + /// HTML to be sanitized + /// Sanitized HTML string Sanitize(string html); } } From 01c1e68cf023887ef25af6bda2c7b11efb816ad3 Mon Sep 17 00:00:00 2001 From: Mole Date: Mon, 24 Jan 2022 09:19:06 +0100 Subject: [PATCH 099/141] Fix up namespaces --- src/Umbraco.Core/Security/IHtmlSanitizer.cs | 2 +- src/Umbraco.Core/Security/NoOpHtmlSanitizer.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Security/IHtmlSanitizer.cs b/src/Umbraco.Core/Security/IHtmlSanitizer.cs index fa1e0b3ee5..9bcfe405dd 100644 --- a/src/Umbraco.Core/Security/IHtmlSanitizer.cs +++ b/src/Umbraco.Core/Security/IHtmlSanitizer.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Core.Security +namespace Umbraco.Cms.Core.Security { public interface IHtmlSanitizer { diff --git a/src/Umbraco.Core/Security/NoOpHtmlSanitizer.cs b/src/Umbraco.Core/Security/NoOpHtmlSanitizer.cs index f16ce81ce1..f2e8a48ad0 100644 --- a/src/Umbraco.Core/Security/NoOpHtmlSanitizer.cs +++ b/src/Umbraco.Core/Security/NoOpHtmlSanitizer.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Core.Security +namespace Umbraco.Cms.Core.Security { public class NoOpHtmlSanitizer : IHtmlSanitizer { From 249774c815345293ea98c56addb1dd6604ee51f8 Mon Sep 17 00:00:00 2001 From: Mole Date: Mon, 24 Jan 2022 09:23:07 +0100 Subject: [PATCH 100/141] Rename NoOp to Noop To match the rest of the classes --- src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs | 3 +++ .../Security/{NoOpHtmlSanitizer.cs => NoopHtmlSanitizer.cs} | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) rename src/Umbraco.Core/Security/{NoOpHtmlSanitizer.cs => NoopHtmlSanitizer.cs} (73%) diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs index eacd615830..c4a95d45e5 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs @@ -263,6 +263,9 @@ namespace Umbraco.Cms.Core.DependencyInjection // Register telemetry service used to gather data about installed packages Services.AddUnique(); + + // Register a noop IHtmlSanitizer to be replaced + Services.AddUnique(); } } } diff --git a/src/Umbraco.Core/Security/NoOpHtmlSanitizer.cs b/src/Umbraco.Core/Security/NoopHtmlSanitizer.cs similarity index 73% rename from src/Umbraco.Core/Security/NoOpHtmlSanitizer.cs rename to src/Umbraco.Core/Security/NoopHtmlSanitizer.cs index f2e8a48ad0..2ada23631a 100644 --- a/src/Umbraco.Core/Security/NoOpHtmlSanitizer.cs +++ b/src/Umbraco.Core/Security/NoopHtmlSanitizer.cs @@ -1,6 +1,6 @@ namespace Umbraco.Cms.Core.Security { - public class NoOpHtmlSanitizer : IHtmlSanitizer + public class NoopHtmlSanitizer : IHtmlSanitizer { public string Sanitize(string html) { From 39f7102312f55d08a18c86132a65479250966653 Mon Sep 17 00:00:00 2001 From: Mole Date: Mon, 24 Jan 2022 09:30:23 +0100 Subject: [PATCH 101/141] Use IHtmlSanitizer in RichTextValueEditor --- .../PropertyEditors/RichTextPropertyEditor.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs index 1f05da3bde..8eeb935c12 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs @@ -81,6 +81,7 @@ namespace Umbraco.Cms.Core.PropertyEditors private readonly HtmlLocalLinkParser _localLinkParser; private readonly RichTextEditorPastedImages _pastedImages; private readonly IImageUrlGenerator _imageUrlGenerator; + private readonly IHtmlSanitizer _htmlSanitizer; public RichTextPropertyValueEditor( DataEditorAttribute attribute, @@ -92,7 +93,8 @@ namespace Umbraco.Cms.Core.PropertyEditors RichTextEditorPastedImages pastedImages, IImageUrlGenerator imageUrlGenerator, IJsonSerializer jsonSerializer, - IIOHelper ioHelper) + IIOHelper ioHelper, + IHtmlSanitizer htmlSanitizer) : base(localizedTextService, shortStringHelper, jsonSerializer, ioHelper, attribute) { _backOfficeSecurityAccessor = backOfficeSecurityAccessor; @@ -100,6 +102,7 @@ namespace Umbraco.Cms.Core.PropertyEditors _localLinkParser = localLinkParser; _pastedImages = pastedImages; _imageUrlGenerator = imageUrlGenerator; + _htmlSanitizer = htmlSanitizer; } /// @@ -156,8 +159,9 @@ namespace Umbraco.Cms.Core.PropertyEditors var parseAndSavedTempImages = _pastedImages.FindAndPersistPastedTempImages(editorValue.Value.ToString(), mediaParentId, userId, _imageUrlGenerator); var editorValueWithMediaUrlsRemoved = _imageSourceParser.RemoveImageSources(parseAndSavedTempImages); var parsed = MacroTagParser.FormatRichTextContentForPersistence(editorValueWithMediaUrlsRemoved); + var sanitized = _htmlSanitizer.Sanitize(parsed); - return parsed.NullOrWhiteSpaceAsNull(); + return sanitized.NullOrWhiteSpaceAsNull(); } /// From ca56e0971f41314c416f1f55e89ae6dba553abfd Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Tue, 25 Jan 2022 06:38:20 +0100 Subject: [PATCH 102/141] Add IsRestarting property to Umbraco application notifications (#11883) * Add IsRestarting property to Umbraco application notifications * Add IUmbracoApplicationLifetimeNotification and update constructors * Only subscribe to events on initial startup * Cleanup CoreRuntime * Do not reset StaticApplicationLogging instance after stopping/during restart --- ...IUmbracoApplicationLifetimeNotification.cs | 17 +++ .../UmbracoApplicationStartedNotification.cs | 15 ++- .../UmbracoApplicationStartingNotification.cs | 27 ++++- .../UmbracoApplicationStoppedNotification.cs | 15 ++- .../UmbracoApplicationStoppingNotification.cs | 27 ++++- .../Runtime/CoreRuntime.cs | 106 +++++++++--------- 6 files changed, 139 insertions(+), 68 deletions(-) create mode 100644 src/Umbraco.Core/Notifications/IUmbracoApplicationLifetimeNotification.cs diff --git a/src/Umbraco.Core/Notifications/IUmbracoApplicationLifetimeNotification.cs b/src/Umbraco.Core/Notifications/IUmbracoApplicationLifetimeNotification.cs new file mode 100644 index 0000000000..4b0ea6826a --- /dev/null +++ b/src/Umbraco.Core/Notifications/IUmbracoApplicationLifetimeNotification.cs @@ -0,0 +1,17 @@ +namespace Umbraco.Cms.Core.Notifications +{ + /// + /// Represents an Umbraco application lifetime (starting, started, stopping, stopped) notification. + /// + /// + public interface IUmbracoApplicationLifetimeNotification : INotification + { + /// + /// Gets a value indicating whether Umbraco is restarting (e.g. after an install or upgrade). + /// + /// + /// true if Umbraco is restarting; otherwise, false. + /// + bool IsRestarting { get; } + } +} diff --git a/src/Umbraco.Core/Notifications/UmbracoApplicationStartedNotification.cs b/src/Umbraco.Core/Notifications/UmbracoApplicationStartedNotification.cs index a3d38720d7..196af7dfe1 100644 --- a/src/Umbraco.Core/Notifications/UmbracoApplicationStartedNotification.cs +++ b/src/Umbraco.Core/Notifications/UmbracoApplicationStartedNotification.cs @@ -3,7 +3,16 @@ namespace Umbraco.Cms.Core.Notifications /// /// Notification that occurs when Umbraco has completely booted up and the request processing pipeline is configured. /// - /// - public class UmbracoApplicationStartedNotification : INotification - { } + /// + public class UmbracoApplicationStartedNotification : IUmbracoApplicationLifetimeNotification + { + /// + /// Initializes a new instance of the class. + /// + /// Indicates whether Umbraco is restarting. + public UmbracoApplicationStartedNotification(bool isRestarting) => IsRestarting = isRestarting; + + /// + public bool IsRestarting { get; } + } } diff --git a/src/Umbraco.Core/Notifications/UmbracoApplicationStartingNotification.cs b/src/Umbraco.Core/Notifications/UmbracoApplicationStartingNotification.cs index dd60f9431c..82b87aa3bf 100644 --- a/src/Umbraco.Core/Notifications/UmbracoApplicationStartingNotification.cs +++ b/src/Umbraco.Core/Notifications/UmbracoApplicationStartingNotification.cs @@ -1,16 +1,34 @@ +using System; + namespace Umbraco.Cms.Core.Notifications { /// /// Notification that occurs at the very end of the Umbraco boot process (after all s are initialized). /// - /// - public class UmbracoApplicationStartingNotification : INotification + /// + public class UmbracoApplicationStartingNotification : IUmbracoApplicationLifetimeNotification { /// /// Initializes a new instance of the class. /// /// The runtime level - public UmbracoApplicationStartingNotification(RuntimeLevel runtimeLevel) => RuntimeLevel = runtimeLevel; + [Obsolete("Use ctor with all params")] + public UmbracoApplicationStartingNotification(RuntimeLevel runtimeLevel) + : this(runtimeLevel, false) + { + // TODO: Remove this constructor in V10 + } + + /// + /// Initializes a new instance of the class. + /// + /// The runtime level + /// Indicates whether Umbraco is restarting. + public UmbracoApplicationStartingNotification(RuntimeLevel runtimeLevel, bool isRestarting) + { + RuntimeLevel = runtimeLevel; + IsRestarting = isRestarting; + } /// /// Gets the runtime level. @@ -19,5 +37,8 @@ namespace Umbraco.Cms.Core.Notifications /// The runtime level. /// public RuntimeLevel RuntimeLevel { get; } + + /// + public bool IsRestarting { get; } } } diff --git a/src/Umbraco.Core/Notifications/UmbracoApplicationStoppedNotification.cs b/src/Umbraco.Core/Notifications/UmbracoApplicationStoppedNotification.cs index be4c6ccfd4..c6dac40a26 100644 --- a/src/Umbraco.Core/Notifications/UmbracoApplicationStoppedNotification.cs +++ b/src/Umbraco.Core/Notifications/UmbracoApplicationStoppedNotification.cs @@ -3,7 +3,16 @@ namespace Umbraco.Cms.Core.Notifications /// /// Notification that occurs when Umbraco has completely shutdown. /// - /// - public class UmbracoApplicationStoppedNotification : INotification - { } + /// + public class UmbracoApplicationStoppedNotification : IUmbracoApplicationLifetimeNotification + { + /// + /// Initializes a new instance of the class. + /// + /// Indicates whether Umbraco is restarting. + public UmbracoApplicationStoppedNotification(bool isRestarting) => IsRestarting = isRestarting; + + /// + public bool IsRestarting { get; } + } } diff --git a/src/Umbraco.Core/Notifications/UmbracoApplicationStoppingNotification.cs b/src/Umbraco.Core/Notifications/UmbracoApplicationStoppingNotification.cs index 6d5234bbcc..062ca954d9 100644 --- a/src/Umbraco.Core/Notifications/UmbracoApplicationStoppingNotification.cs +++ b/src/Umbraco.Core/Notifications/UmbracoApplicationStoppingNotification.cs @@ -1,9 +1,30 @@ +using System; + namespace Umbraco.Cms.Core.Notifications { /// /// Notification that occurs when Umbraco is shutting down (after all s are terminated). /// - /// - public class UmbracoApplicationStoppingNotification : INotification - { } + /// + public class UmbracoApplicationStoppingNotification : IUmbracoApplicationLifetimeNotification + { + /// + /// Initializes a new instance of the class. + /// + [Obsolete("Use ctor with all params")] + public UmbracoApplicationStoppingNotification() + : this(false) + { + // TODO: Remove this constructor in V10 + } + + /// + /// Initializes a new instance of the class. + /// + /// Indicates whether Umbraco is restarting. + public UmbracoApplicationStoppingNotification(bool isRestarting) => IsRestarting = isRestarting; + + /// + public bool IsRestarting { get; } + } } diff --git a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs index 5dbe78c2f5..851d67e713 100644 --- a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs @@ -134,60 +134,48 @@ namespace Umbraco.Cms.Infrastructure.Runtime public IRuntimeState State { get; } /// - public async Task RestartAsync() - { - await StopAsync(_cancellationToken); - await _eventAggregator.PublishAsync(new UmbracoApplicationStoppedNotification(), _cancellationToken); - await StartAsync(_cancellationToken); - await _eventAggregator.PublishAsync(new UmbracoApplicationStartedNotification(), _cancellationToken); - } + public async Task StartAsync(CancellationToken cancellationToken) => await StartAsync(cancellationToken, false); /// - public async Task StartAsync(CancellationToken cancellationToken) + public async Task StopAsync(CancellationToken cancellationToken) => await StopAsync(cancellationToken, false); + + /// + public async Task RestartAsync() + { + await StopAsync(_cancellationToken, true); + await _eventAggregator.PublishAsync(new UmbracoApplicationStoppedNotification(true), _cancellationToken); + await StartAsync(_cancellationToken, true); + await _eventAggregator.PublishAsync(new UmbracoApplicationStartedNotification(true), _cancellationToken); + } + + private async Task StartAsync(CancellationToken cancellationToken, bool isRestarting) { // Store token, so we can re-use this during restart _cancellationToken = cancellationToken; - StaticApplicationLogging.Initialize(_loggerFactory); - StaticServiceProvider.Instance = _serviceProvider; - - AppDomain.CurrentDomain.UnhandledException += (_, args) => + if (isRestarting == false) { - var exception = (Exception)args.ExceptionObject; - var isTerminating = args.IsTerminating; // always true? + StaticApplicationLogging.Initialize(_loggerFactory); + StaticServiceProvider.Instance = _serviceProvider; - var msg = "Unhandled exception in AppDomain"; - - if (isTerminating) - { - msg += " (terminating)"; - } - - msg += "."; - - _logger.LogError(exception, msg); - }; - - // Add application started and stopped notifications (only on initial startup, not restarts) - if (_hostApplicationLifetime.ApplicationStarted.IsCancellationRequested == false) - { - _hostApplicationLifetime.ApplicationStarted.Register(() => _eventAggregator.Publish(new UmbracoApplicationStartedNotification())); - _hostApplicationLifetime.ApplicationStopped.Register(() => _eventAggregator.Publish(new UmbracoApplicationStoppedNotification())); + AppDomain.CurrentDomain.UnhandledException += (_, args) + => _logger.LogError(args.ExceptionObject as Exception, $"Unhandled exception in AppDomain{(args.IsTerminating ? " (terminating)" : null)}."); } - // acquire the main domain - if this fails then anything that should be registered with MainDom will not operate + // Acquire the main domain - if this fails then anything that should be registered with MainDom will not operate AcquireMainDom(); // TODO (V10): Remove this obsoleted notification publish. await _eventAggregator.PublishAsync(new UmbracoApplicationMainDomAcquiredNotification(), cancellationToken); - // notify for unattended install + // Notify for unattended install await _eventAggregator.PublishAsync(new RuntimeUnattendedInstallNotification(), cancellationToken); DetermineRuntimeLevel(); if (!State.UmbracoCanBoot()) { - return; // The exception will be rethrown by BootFailedMiddelware + // We cannot continue here, the exception will be rethrown by BootFailedMiddelware + return; } IApplicationShutdownRegistry hostingEnvironmentLifetime = _applicationShutdownRegistry; @@ -196,7 +184,7 @@ namespace Umbraco.Cms.Infrastructure.Runtime throw new InvalidOperationException($"An instance of {typeof(IApplicationShutdownRegistry)} could not be resolved from the container, ensure that one if registered in your runtime before calling {nameof(IRuntime)}.{nameof(StartAsync)}"); } - // if level is Run and reason is UpgradeMigrations, that means we need to perform an unattended upgrade + // If level is Run and reason is UpgradeMigrations, that means we need to perform an unattended upgrade var unattendedUpgradeNotification = new RuntimeUnattendedUpgradeNotification(); await _eventAggregator.PublishAsync(unattendedUpgradeNotification, cancellationToken); switch (unattendedUpgradeNotification.UnattendedUpgradeResult) @@ -207,54 +195,59 @@ namespace Umbraco.Cms.Infrastructure.Runtime throw new InvalidOperationException($"Unattended upgrade result was {RuntimeUnattendedUpgradeNotification.UpgradeResult.HasErrors} but no {nameof(BootFailedException)} was registered"); } - // we cannot continue here, the exception will be rethrown by BootFailedMiddelware + // We cannot continue here, the exception will be rethrown by BootFailedMiddelware return; case RuntimeUnattendedUpgradeNotification.UpgradeResult.CoreUpgradeComplete: case RuntimeUnattendedUpgradeNotification.UpgradeResult.PackageMigrationComplete: - // upgrade is done, set reason to Run + // Upgrade is done, set reason to Run DetermineRuntimeLevel(); break; case RuntimeUnattendedUpgradeNotification.UpgradeResult.NotRequired: break; } - // TODO (V10): Remove this obsoleted notification publish. + // TODO (V10): Remove this obsoleted notification publish await _eventAggregator.PublishAsync(new UmbracoApplicationComponentsInstallingNotification(State.Level), cancellationToken); - // create & initialize the components + // Initialize the components _components.Initialize(); - await _eventAggregator.PublishAsync(new UmbracoApplicationStartingNotification(State.Level), cancellationToken); + await _eventAggregator.PublishAsync(new UmbracoApplicationStartingNotification(State.Level, isRestarting), cancellationToken); + + if (isRestarting == false) + { + // Add application started and stopped notifications last (to ensure they're always published after starting) + _hostApplicationLifetime.ApplicationStarted.Register(() => _eventAggregator.Publish(new UmbracoApplicationStartedNotification(false))); + _hostApplicationLifetime.ApplicationStopped.Register(() => _eventAggregator.Publish(new UmbracoApplicationStoppedNotification(false))); + } } - public async Task StopAsync(CancellationToken cancellationToken) + private async Task StopAsync(CancellationToken cancellationToken, bool isRestarting) { _components.Terminate(); - await _eventAggregator.PublishAsync(new UmbracoApplicationStoppingNotification(), cancellationToken); - StaticApplicationLogging.Initialize(null); + await _eventAggregator.PublishAsync(new UmbracoApplicationStoppingNotification(isRestarting), cancellationToken); } private void AcquireMainDom() { - using (DisposableTimer timer = _profilingLogger.DebugDuration("Acquiring MainDom.", "Acquired.")) + using DisposableTimer timer = _profilingLogger.DebugDuration("Acquiring MainDom.", "Acquired."); + + try { - try - { - _mainDom.Acquire(_applicationShutdownRegistry); - } - catch - { - timer?.Fail(); - throw; - } + _mainDom.Acquire(_applicationShutdownRegistry); + } + catch + { + timer?.Fail(); + throw; } } private void DetermineRuntimeLevel() { - if (State.BootFailedException != null) + if (State.BootFailedException is not null) { - // there's already been an exception so cannot boot and no need to check + // There's already been an exception, so cannot boot and no need to check return; } @@ -277,7 +270,8 @@ namespace Umbraco.Cms.Infrastructure.Runtime State.Configure(RuntimeLevel.BootFailed, RuntimeLevelReason.BootFailedOnException); timer?.Fail(); _logger.LogError(ex, "Boot Failed"); - // We do not throw the exception. It will be rethrown by BootFailedMiddleware + + // We do not throw the exception, it will be rethrown by BootFailedMiddleware } } } From 76593aa7ca2b49add058efe2825d7183cf7d5d36 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Tue, 25 Jan 2022 06:38:20 +0100 Subject: [PATCH 103/141] Add IsRestarting property to Umbraco application notifications (#11883) * Add IsRestarting property to Umbraco application notifications * Add IUmbracoApplicationLifetimeNotification and update constructors * Only subscribe to events on initial startup * Cleanup CoreRuntime * Do not reset StaticApplicationLogging instance after stopping/during restart --- ...IUmbracoApplicationLifetimeNotification.cs | 17 +++ .../UmbracoApplicationStartedNotification.cs | 15 ++- .../UmbracoApplicationStartingNotification.cs | 27 ++++- .../UmbracoApplicationStoppedNotification.cs | 15 ++- .../UmbracoApplicationStoppingNotification.cs | 27 ++++- .../Runtime/CoreRuntime.cs | 106 +++++++++--------- 6 files changed, 139 insertions(+), 68 deletions(-) create mode 100644 src/Umbraco.Core/Notifications/IUmbracoApplicationLifetimeNotification.cs diff --git a/src/Umbraco.Core/Notifications/IUmbracoApplicationLifetimeNotification.cs b/src/Umbraco.Core/Notifications/IUmbracoApplicationLifetimeNotification.cs new file mode 100644 index 0000000000..4b0ea6826a --- /dev/null +++ b/src/Umbraco.Core/Notifications/IUmbracoApplicationLifetimeNotification.cs @@ -0,0 +1,17 @@ +namespace Umbraco.Cms.Core.Notifications +{ + /// + /// Represents an Umbraco application lifetime (starting, started, stopping, stopped) notification. + /// + /// + public interface IUmbracoApplicationLifetimeNotification : INotification + { + /// + /// Gets a value indicating whether Umbraco is restarting (e.g. after an install or upgrade). + /// + /// + /// true if Umbraco is restarting; otherwise, false. + /// + bool IsRestarting { get; } + } +} diff --git a/src/Umbraco.Core/Notifications/UmbracoApplicationStartedNotification.cs b/src/Umbraco.Core/Notifications/UmbracoApplicationStartedNotification.cs index a3d38720d7..196af7dfe1 100644 --- a/src/Umbraco.Core/Notifications/UmbracoApplicationStartedNotification.cs +++ b/src/Umbraco.Core/Notifications/UmbracoApplicationStartedNotification.cs @@ -3,7 +3,16 @@ namespace Umbraco.Cms.Core.Notifications /// /// Notification that occurs when Umbraco has completely booted up and the request processing pipeline is configured. /// - /// - public class UmbracoApplicationStartedNotification : INotification - { } + /// + public class UmbracoApplicationStartedNotification : IUmbracoApplicationLifetimeNotification + { + /// + /// Initializes a new instance of the class. + /// + /// Indicates whether Umbraco is restarting. + public UmbracoApplicationStartedNotification(bool isRestarting) => IsRestarting = isRestarting; + + /// + public bool IsRestarting { get; } + } } diff --git a/src/Umbraco.Core/Notifications/UmbracoApplicationStartingNotification.cs b/src/Umbraco.Core/Notifications/UmbracoApplicationStartingNotification.cs index dd60f9431c..82b87aa3bf 100644 --- a/src/Umbraco.Core/Notifications/UmbracoApplicationStartingNotification.cs +++ b/src/Umbraco.Core/Notifications/UmbracoApplicationStartingNotification.cs @@ -1,16 +1,34 @@ +using System; + namespace Umbraco.Cms.Core.Notifications { /// /// Notification that occurs at the very end of the Umbraco boot process (after all s are initialized). /// - /// - public class UmbracoApplicationStartingNotification : INotification + /// + public class UmbracoApplicationStartingNotification : IUmbracoApplicationLifetimeNotification { /// /// Initializes a new instance of the class. /// /// The runtime level - public UmbracoApplicationStartingNotification(RuntimeLevel runtimeLevel) => RuntimeLevel = runtimeLevel; + [Obsolete("Use ctor with all params")] + public UmbracoApplicationStartingNotification(RuntimeLevel runtimeLevel) + : this(runtimeLevel, false) + { + // TODO: Remove this constructor in V10 + } + + /// + /// Initializes a new instance of the class. + /// + /// The runtime level + /// Indicates whether Umbraco is restarting. + public UmbracoApplicationStartingNotification(RuntimeLevel runtimeLevel, bool isRestarting) + { + RuntimeLevel = runtimeLevel; + IsRestarting = isRestarting; + } /// /// Gets the runtime level. @@ -19,5 +37,8 @@ namespace Umbraco.Cms.Core.Notifications /// The runtime level. /// public RuntimeLevel RuntimeLevel { get; } + + /// + public bool IsRestarting { get; } } } diff --git a/src/Umbraco.Core/Notifications/UmbracoApplicationStoppedNotification.cs b/src/Umbraco.Core/Notifications/UmbracoApplicationStoppedNotification.cs index be4c6ccfd4..c6dac40a26 100644 --- a/src/Umbraco.Core/Notifications/UmbracoApplicationStoppedNotification.cs +++ b/src/Umbraco.Core/Notifications/UmbracoApplicationStoppedNotification.cs @@ -3,7 +3,16 @@ namespace Umbraco.Cms.Core.Notifications /// /// Notification that occurs when Umbraco has completely shutdown. /// - /// - public class UmbracoApplicationStoppedNotification : INotification - { } + /// + public class UmbracoApplicationStoppedNotification : IUmbracoApplicationLifetimeNotification + { + /// + /// Initializes a new instance of the class. + /// + /// Indicates whether Umbraco is restarting. + public UmbracoApplicationStoppedNotification(bool isRestarting) => IsRestarting = isRestarting; + + /// + public bool IsRestarting { get; } + } } diff --git a/src/Umbraco.Core/Notifications/UmbracoApplicationStoppingNotification.cs b/src/Umbraco.Core/Notifications/UmbracoApplicationStoppingNotification.cs index 6d5234bbcc..062ca954d9 100644 --- a/src/Umbraco.Core/Notifications/UmbracoApplicationStoppingNotification.cs +++ b/src/Umbraco.Core/Notifications/UmbracoApplicationStoppingNotification.cs @@ -1,9 +1,30 @@ +using System; + namespace Umbraco.Cms.Core.Notifications { /// /// Notification that occurs when Umbraco is shutting down (after all s are terminated). /// - /// - public class UmbracoApplicationStoppingNotification : INotification - { } + /// + public class UmbracoApplicationStoppingNotification : IUmbracoApplicationLifetimeNotification + { + /// + /// Initializes a new instance of the class. + /// + [Obsolete("Use ctor with all params")] + public UmbracoApplicationStoppingNotification() + : this(false) + { + // TODO: Remove this constructor in V10 + } + + /// + /// Initializes a new instance of the class. + /// + /// Indicates whether Umbraco is restarting. + public UmbracoApplicationStoppingNotification(bool isRestarting) => IsRestarting = isRestarting; + + /// + public bool IsRestarting { get; } + } } diff --git a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs index 5dbe78c2f5..851d67e713 100644 --- a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs @@ -134,60 +134,48 @@ namespace Umbraco.Cms.Infrastructure.Runtime public IRuntimeState State { get; } /// - public async Task RestartAsync() - { - await StopAsync(_cancellationToken); - await _eventAggregator.PublishAsync(new UmbracoApplicationStoppedNotification(), _cancellationToken); - await StartAsync(_cancellationToken); - await _eventAggregator.PublishAsync(new UmbracoApplicationStartedNotification(), _cancellationToken); - } + public async Task StartAsync(CancellationToken cancellationToken) => await StartAsync(cancellationToken, false); /// - public async Task StartAsync(CancellationToken cancellationToken) + public async Task StopAsync(CancellationToken cancellationToken) => await StopAsync(cancellationToken, false); + + /// + public async Task RestartAsync() + { + await StopAsync(_cancellationToken, true); + await _eventAggregator.PublishAsync(new UmbracoApplicationStoppedNotification(true), _cancellationToken); + await StartAsync(_cancellationToken, true); + await _eventAggregator.PublishAsync(new UmbracoApplicationStartedNotification(true), _cancellationToken); + } + + private async Task StartAsync(CancellationToken cancellationToken, bool isRestarting) { // Store token, so we can re-use this during restart _cancellationToken = cancellationToken; - StaticApplicationLogging.Initialize(_loggerFactory); - StaticServiceProvider.Instance = _serviceProvider; - - AppDomain.CurrentDomain.UnhandledException += (_, args) => + if (isRestarting == false) { - var exception = (Exception)args.ExceptionObject; - var isTerminating = args.IsTerminating; // always true? + StaticApplicationLogging.Initialize(_loggerFactory); + StaticServiceProvider.Instance = _serviceProvider; - var msg = "Unhandled exception in AppDomain"; - - if (isTerminating) - { - msg += " (terminating)"; - } - - msg += "."; - - _logger.LogError(exception, msg); - }; - - // Add application started and stopped notifications (only on initial startup, not restarts) - if (_hostApplicationLifetime.ApplicationStarted.IsCancellationRequested == false) - { - _hostApplicationLifetime.ApplicationStarted.Register(() => _eventAggregator.Publish(new UmbracoApplicationStartedNotification())); - _hostApplicationLifetime.ApplicationStopped.Register(() => _eventAggregator.Publish(new UmbracoApplicationStoppedNotification())); + AppDomain.CurrentDomain.UnhandledException += (_, args) + => _logger.LogError(args.ExceptionObject as Exception, $"Unhandled exception in AppDomain{(args.IsTerminating ? " (terminating)" : null)}."); } - // acquire the main domain - if this fails then anything that should be registered with MainDom will not operate + // Acquire the main domain - if this fails then anything that should be registered with MainDom will not operate AcquireMainDom(); // TODO (V10): Remove this obsoleted notification publish. await _eventAggregator.PublishAsync(new UmbracoApplicationMainDomAcquiredNotification(), cancellationToken); - // notify for unattended install + // Notify for unattended install await _eventAggregator.PublishAsync(new RuntimeUnattendedInstallNotification(), cancellationToken); DetermineRuntimeLevel(); if (!State.UmbracoCanBoot()) { - return; // The exception will be rethrown by BootFailedMiddelware + // We cannot continue here, the exception will be rethrown by BootFailedMiddelware + return; } IApplicationShutdownRegistry hostingEnvironmentLifetime = _applicationShutdownRegistry; @@ -196,7 +184,7 @@ namespace Umbraco.Cms.Infrastructure.Runtime throw new InvalidOperationException($"An instance of {typeof(IApplicationShutdownRegistry)} could not be resolved from the container, ensure that one if registered in your runtime before calling {nameof(IRuntime)}.{nameof(StartAsync)}"); } - // if level is Run and reason is UpgradeMigrations, that means we need to perform an unattended upgrade + // If level is Run and reason is UpgradeMigrations, that means we need to perform an unattended upgrade var unattendedUpgradeNotification = new RuntimeUnattendedUpgradeNotification(); await _eventAggregator.PublishAsync(unattendedUpgradeNotification, cancellationToken); switch (unattendedUpgradeNotification.UnattendedUpgradeResult) @@ -207,54 +195,59 @@ namespace Umbraco.Cms.Infrastructure.Runtime throw new InvalidOperationException($"Unattended upgrade result was {RuntimeUnattendedUpgradeNotification.UpgradeResult.HasErrors} but no {nameof(BootFailedException)} was registered"); } - // we cannot continue here, the exception will be rethrown by BootFailedMiddelware + // We cannot continue here, the exception will be rethrown by BootFailedMiddelware return; case RuntimeUnattendedUpgradeNotification.UpgradeResult.CoreUpgradeComplete: case RuntimeUnattendedUpgradeNotification.UpgradeResult.PackageMigrationComplete: - // upgrade is done, set reason to Run + // Upgrade is done, set reason to Run DetermineRuntimeLevel(); break; case RuntimeUnattendedUpgradeNotification.UpgradeResult.NotRequired: break; } - // TODO (V10): Remove this obsoleted notification publish. + // TODO (V10): Remove this obsoleted notification publish await _eventAggregator.PublishAsync(new UmbracoApplicationComponentsInstallingNotification(State.Level), cancellationToken); - // create & initialize the components + // Initialize the components _components.Initialize(); - await _eventAggregator.PublishAsync(new UmbracoApplicationStartingNotification(State.Level), cancellationToken); + await _eventAggregator.PublishAsync(new UmbracoApplicationStartingNotification(State.Level, isRestarting), cancellationToken); + + if (isRestarting == false) + { + // Add application started and stopped notifications last (to ensure they're always published after starting) + _hostApplicationLifetime.ApplicationStarted.Register(() => _eventAggregator.Publish(new UmbracoApplicationStartedNotification(false))); + _hostApplicationLifetime.ApplicationStopped.Register(() => _eventAggregator.Publish(new UmbracoApplicationStoppedNotification(false))); + } } - public async Task StopAsync(CancellationToken cancellationToken) + private async Task StopAsync(CancellationToken cancellationToken, bool isRestarting) { _components.Terminate(); - await _eventAggregator.PublishAsync(new UmbracoApplicationStoppingNotification(), cancellationToken); - StaticApplicationLogging.Initialize(null); + await _eventAggregator.PublishAsync(new UmbracoApplicationStoppingNotification(isRestarting), cancellationToken); } private void AcquireMainDom() { - using (DisposableTimer timer = _profilingLogger.DebugDuration("Acquiring MainDom.", "Acquired.")) + using DisposableTimer timer = _profilingLogger.DebugDuration("Acquiring MainDom.", "Acquired."); + + try { - try - { - _mainDom.Acquire(_applicationShutdownRegistry); - } - catch - { - timer?.Fail(); - throw; - } + _mainDom.Acquire(_applicationShutdownRegistry); + } + catch + { + timer?.Fail(); + throw; } } private void DetermineRuntimeLevel() { - if (State.BootFailedException != null) + if (State.BootFailedException is not null) { - // there's already been an exception so cannot boot and no need to check + // There's already been an exception, so cannot boot and no need to check return; } @@ -277,7 +270,8 @@ namespace Umbraco.Cms.Infrastructure.Runtime State.Configure(RuntimeLevel.BootFailed, RuntimeLevelReason.BootFailedOnException); timer?.Fail(); _logger.LogError(ex, "Boot Failed"); - // We do not throw the exception. It will be rethrown by BootFailedMiddleware + + // We do not throw the exception, it will be rethrown by BootFailedMiddleware } } } From d70a207d60b6f4eff8cf1b446f0a0cf4f70d03b5 Mon Sep 17 00:00:00 2001 From: Mole Date: Wed, 26 Jan 2022 07:51:25 +0100 Subject: [PATCH 104/141] V8: Add ability to implement your own HtmlSanitizer (#11897) * Add IHtmlSanitizer * Use IHtmlSanitizer in RTE value editor * Add docstrings to IHtmlSanitizer * Rename NoOp to Noop To match the rest of the classes * Fix tests --- .../CompositionExtensions/Services.cs | 2 + src/Umbraco.Core/Security/IHtmlSanitizer.cs | 12 +++++ .../Security/NoopHtmlSanitizer.cs | 10 ++++ src/Umbraco.Core/Umbraco.Core.csproj | 2 + .../PublishedContent/PublishedContentTests.cs | 3 +- .../PropertyEditors/GridPropertyEditor.cs | 37 ++++++++++++-- .../PropertyEditors/RichTextPropertyEditor.cs | 49 ++++++++++++++++--- 7 files changed, 103 insertions(+), 12 deletions(-) create mode 100644 src/Umbraco.Core/Security/IHtmlSanitizer.cs create mode 100644 src/Umbraco.Core/Security/NoopHtmlSanitizer.cs diff --git a/src/Umbraco.Core/Composing/CompositionExtensions/Services.cs b/src/Umbraco.Core/Composing/CompositionExtensions/Services.cs index e912f7281c..3c9dd9d701 100644 --- a/src/Umbraco.Core/Composing/CompositionExtensions/Services.cs +++ b/src/Umbraco.Core/Composing/CompositionExtensions/Services.cs @@ -6,6 +6,7 @@ using Umbraco.Core.Events; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Packaging; +using Umbraco.Core.Security; using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; using Umbraco.Core.Telemetry; @@ -81,6 +82,7 @@ namespace Umbraco.Core.Composing.CompositionExtensions new DirectoryInfo(IOHelper.GetRootDirectorySafe()))); composition.RegisterUnique(); + composition.RegisterUnique(); return composition; } diff --git a/src/Umbraco.Core/Security/IHtmlSanitizer.cs b/src/Umbraco.Core/Security/IHtmlSanitizer.cs new file mode 100644 index 0000000000..fa1e0b3ee5 --- /dev/null +++ b/src/Umbraco.Core/Security/IHtmlSanitizer.cs @@ -0,0 +1,12 @@ +namespace Umbraco.Core.Security +{ + public interface IHtmlSanitizer + { + /// + /// Sanitizes HTML + /// + /// HTML to be sanitized + /// Sanitized HTML + string Sanitize(string html); + } +} diff --git a/src/Umbraco.Core/Security/NoopHtmlSanitizer.cs b/src/Umbraco.Core/Security/NoopHtmlSanitizer.cs new file mode 100644 index 0000000000..2ea34d52ea --- /dev/null +++ b/src/Umbraco.Core/Security/NoopHtmlSanitizer.cs @@ -0,0 +1,10 @@ +namespace Umbraco.Core.Security +{ + public class NoopHtmlSanitizer : IHtmlSanitizer + { + public string Sanitize(string html) + { + return html; + } + } +} diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 6729930174..e27c6eeceb 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -194,6 +194,8 @@ + + diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs index cafda161f4..9b8f5a05fd 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs @@ -16,6 +16,7 @@ using Umbraco.Core.Cache; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Models; +using Umbraco.Core.Security; using Umbraco.Core.Services; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing; @@ -54,7 +55,7 @@ namespace Umbraco.Tests.PublishedContent var dataTypeService = new TestObjects.TestDataTypeService( new DataType(new VoidEditor(logger)) { Id = 1 }, new DataType(new TrueFalsePropertyEditor(logger)) { Id = 1001 }, - new DataType(new RichTextPropertyEditor(logger, umbracoContextAccessor, imageSourceParser, linkParser, pastedImages, Mock.Of())) { Id = 1002 }, + new DataType(new RichTextPropertyEditor(logger, umbracoContextAccessor, imageSourceParser, linkParser, pastedImages, Mock.Of(), Mock.Of())) { Id = 1002 }, new DataType(new IntegerPropertyEditor(logger)) { Id = 1003 }, new DataType(new TextboxPropertyEditor(logger)) { Id = 1004 }, new DataType(new MediaPickerPropertyEditor(logger)) { Id = 1005 }); diff --git a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs index 6f919868f7..5ed3051e07 100644 --- a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs @@ -8,6 +8,7 @@ using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Editors; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Security; using Umbraco.Core.Services; using Umbraco.Web.Templates; @@ -32,8 +33,9 @@ namespace Umbraco.Web.PropertyEditors private readonly RichTextEditorPastedImages _pastedImages; private readonly HtmlLocalLinkParser _localLinkParser; private readonly IImageUrlGenerator _imageUrlGenerator; + private readonly IHtmlSanitizer _htmlSanitizer; - [Obsolete("Use the constructor which takes an IImageUrlGenerator")] + [Obsolete("Use the constructor which takes an IHtmlSanitizer")] public GridPropertyEditor(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, HtmlImageSourceParser imageSourceParser, @@ -43,12 +45,24 @@ namespace Umbraco.Web.PropertyEditors { } + [Obsolete("Use the constructor which takes an IHtmlSanitizer")] public GridPropertyEditor(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, HtmlImageSourceParser imageSourceParser, RichTextEditorPastedImages pastedImages, HtmlLocalLinkParser localLinkParser, IImageUrlGenerator imageUrlGenerator) + : this(logger, umbracoContextAccessor, imageSourceParser, pastedImages, localLinkParser, imageUrlGenerator, Current.Factory.GetInstance()) + { + } + + public GridPropertyEditor(ILogger logger, + IUmbracoContextAccessor umbracoContextAccessor, + HtmlImageSourceParser imageSourceParser, + RichTextEditorPastedImages pastedImages, + HtmlLocalLinkParser localLinkParser, + IImageUrlGenerator imageUrlGenerator, + IHtmlSanitizer htmlSanitizer) : base(logger) { _umbracoContextAccessor = umbracoContextAccessor; @@ -56,6 +70,7 @@ namespace Umbraco.Web.PropertyEditors _pastedImages = pastedImages; _localLinkParser = localLinkParser; _imageUrlGenerator = imageUrlGenerator; + _htmlSanitizer = htmlSanitizer; } public override IPropertyIndexValueFactory PropertyIndexValueFactory => new GridPropertyIndexValueFactory(); @@ -64,7 +79,7 @@ namespace Umbraco.Web.PropertyEditors /// Overridden to ensure that the value is validated /// /// - protected override IDataValueEditor CreateValueEditor() => new GridPropertyValueEditor(Attribute, _umbracoContextAccessor, _imageSourceParser, _pastedImages, _localLinkParser, _imageUrlGenerator); + protected override IDataValueEditor CreateValueEditor() => new GridPropertyValueEditor(Attribute, _umbracoContextAccessor, _imageSourceParser, _pastedImages, _localLinkParser, _imageUrlGenerator, _htmlSanitizer); protected override IConfigurationEditor CreateConfigurationEditor() => new GridConfigurationEditor(); @@ -77,7 +92,7 @@ namespace Umbraco.Web.PropertyEditors private readonly MediaPickerPropertyEditor.MediaPickerPropertyValueEditor _mediaPickerPropertyValueEditor; private readonly IImageUrlGenerator _imageUrlGenerator; - [Obsolete("Use the constructor which takes an IImageUrlGenerator")] + [Obsolete("Use the constructor which takes an IHtmlSanitizer")] public GridPropertyValueEditor(DataEditorAttribute attribute, IUmbracoContextAccessor umbracoContextAccessor, HtmlImageSourceParser imageSourceParser, @@ -87,20 +102,32 @@ namespace Umbraco.Web.PropertyEditors { } + [Obsolete("Use the constructor which takes an IHtmlSanitizer")] public GridPropertyValueEditor(DataEditorAttribute attribute, IUmbracoContextAccessor umbracoContextAccessor, HtmlImageSourceParser imageSourceParser, RichTextEditorPastedImages pastedImages, HtmlLocalLinkParser localLinkParser, IImageUrlGenerator imageUrlGenerator) + : this(attribute, umbracoContextAccessor, imageSourceParser, pastedImages, localLinkParser, imageUrlGenerator, Current.Factory.GetInstance()) + { + } + + public GridPropertyValueEditor(DataEditorAttribute attribute, + IUmbracoContextAccessor umbracoContextAccessor, + HtmlImageSourceParser imageSourceParser, + RichTextEditorPastedImages pastedImages, + HtmlLocalLinkParser localLinkParser, + IImageUrlGenerator imageUrlGenerator, + IHtmlSanitizer htmlSanitizer) : base(attribute) { _umbracoContextAccessor = umbracoContextAccessor; _imageSourceParser = imageSourceParser; _pastedImages = pastedImages; - _richTextPropertyValueEditor = new RichTextPropertyEditor.RichTextPropertyValueEditor(attribute, umbracoContextAccessor, imageSourceParser, localLinkParser, pastedImages, _imageUrlGenerator); - _mediaPickerPropertyValueEditor = new MediaPickerPropertyEditor.MediaPickerPropertyValueEditor(attribute); _imageUrlGenerator = imageUrlGenerator; + _richTextPropertyValueEditor = new RichTextPropertyEditor.RichTextPropertyValueEditor(attribute, umbracoContextAccessor, imageSourceParser, localLinkParser, pastedImages, _imageUrlGenerator, htmlSanitizer); + _mediaPickerPropertyValueEditor = new MediaPickerPropertyEditor.MediaPickerPropertyValueEditor(attribute); } /// diff --git a/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs index 2d698835b0..4a6c0c09b6 100644 --- a/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs @@ -7,6 +7,7 @@ using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Editors; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Security; using Umbraco.Core.Services; using Umbraco.Examine; using Umbraco.Web.Macros; @@ -32,21 +33,46 @@ namespace Umbraco.Web.PropertyEditors private readonly HtmlLocalLinkParser _localLinkParser; private readonly RichTextEditorPastedImages _pastedImages; private readonly IImageUrlGenerator _imageUrlGenerator; + private readonly IHtmlSanitizer _htmlSanitizer; /// /// The constructor will setup the property editor based on the attribute if one is found /// - [Obsolete("Use the constructor which takes an IImageUrlGenerator")] - public RichTextPropertyEditor(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, HtmlImageSourceParser imageSourceParser, HtmlLocalLinkParser localLinkParser, RichTextEditorPastedImages pastedImages) + [Obsolete("Use the constructor which takes an IHtmlSanitizer")] + public RichTextPropertyEditor( + ILogger logger, + IUmbracoContextAccessor umbracoContextAccessor, + HtmlImageSourceParser imageSourceParser, + HtmlLocalLinkParser localLinkParser, + RichTextEditorPastedImages pastedImages) : this(logger, umbracoContextAccessor, imageSourceParser, localLinkParser, pastedImages, Current.ImageUrlGenerator) { } + [Obsolete("Use the constructor which takes an IHtmlSanitizer")] + public RichTextPropertyEditor( + ILogger logger, + IUmbracoContextAccessor umbracoContextAccessor, + HtmlImageSourceParser imageSourceParser, + HtmlLocalLinkParser localLinkParser, + RichTextEditorPastedImages pastedImages, + IImageUrlGenerator imageUrlGenerator) + : this(logger, umbracoContextAccessor, imageSourceParser, localLinkParser, pastedImages, imageUrlGenerator, Current.Factory.GetInstance()) + { + } + /// /// The constructor will setup the property editor based on the attribute if one is found /// - public RichTextPropertyEditor(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, HtmlImageSourceParser imageSourceParser, HtmlLocalLinkParser localLinkParser, RichTextEditorPastedImages pastedImages, IImageUrlGenerator imageUrlGenerator) + public RichTextPropertyEditor( + ILogger logger, + IUmbracoContextAccessor umbracoContextAccessor, + HtmlImageSourceParser imageSourceParser, + HtmlLocalLinkParser localLinkParser, + RichTextEditorPastedImages pastedImages, + IImageUrlGenerator imageUrlGenerator, + IHtmlSanitizer htmlSanitizer) : base(logger) { _umbracoContextAccessor = umbracoContextAccessor; @@ -54,13 +80,14 @@ namespace Umbraco.Web.PropertyEditors _localLinkParser = localLinkParser; _pastedImages = pastedImages; _imageUrlGenerator = imageUrlGenerator; + _htmlSanitizer = htmlSanitizer; } /// /// Create a custom value editor /// /// - protected override IDataValueEditor CreateValueEditor() => new RichTextPropertyValueEditor(Attribute, _umbracoContextAccessor, _imageSourceParser, _localLinkParser, _pastedImages, _imageUrlGenerator); + protected override IDataValueEditor CreateValueEditor() => new RichTextPropertyValueEditor(Attribute, _umbracoContextAccessor, _imageSourceParser, _localLinkParser, _pastedImages, _imageUrlGenerator, _htmlSanitizer); protected override IConfigurationEditor CreateConfigurationEditor() => new RichTextConfigurationEditor(); @@ -76,8 +103,16 @@ namespace Umbraco.Web.PropertyEditors private readonly HtmlLocalLinkParser _localLinkParser; private readonly RichTextEditorPastedImages _pastedImages; private readonly IImageUrlGenerator _imageUrlGenerator; + private readonly IHtmlSanitizer _htmlSanitizer; - public RichTextPropertyValueEditor(DataEditorAttribute attribute, IUmbracoContextAccessor umbracoContextAccessor, HtmlImageSourceParser imageSourceParser, HtmlLocalLinkParser localLinkParser, RichTextEditorPastedImages pastedImages, IImageUrlGenerator imageUrlGenerator) + public RichTextPropertyValueEditor( + DataEditorAttribute attribute, + IUmbracoContextAccessor umbracoContextAccessor, + HtmlImageSourceParser imageSourceParser, + HtmlLocalLinkParser localLinkParser, + RichTextEditorPastedImages pastedImages, + IImageUrlGenerator imageUrlGenerator, + IHtmlSanitizer htmlSanitizer) : base(attribute) { _umbracoContextAccessor = umbracoContextAccessor; @@ -85,6 +120,7 @@ namespace Umbraco.Web.PropertyEditors _localLinkParser = localLinkParser; _pastedImages = pastedImages; _imageUrlGenerator = imageUrlGenerator; + _htmlSanitizer = htmlSanitizer; } /// @@ -141,8 +177,9 @@ namespace Umbraco.Web.PropertyEditors var parseAndSavedTempImages = _pastedImages.FindAndPersistPastedTempImages(editorValue.Value.ToString(), mediaParentId, userId, _imageUrlGenerator); var editorValueWithMediaUrlsRemoved = _imageSourceParser.RemoveImageSources(parseAndSavedTempImages); var parsed = MacroTagParser.FormatRichTextContentForPersistence(editorValueWithMediaUrlsRemoved); + var sanitized = _htmlSanitizer.Sanitize(parsed); - return parsed.NullOrWhiteSpaceAsNull(); + return sanitized.NullOrWhiteSpaceAsNull(); } /// From 3ade2b6de32faa062a14f1a20a487e9d23c2966d Mon Sep 17 00:00:00 2001 From: Mole Date: Wed, 26 Jan 2022 08:43:43 +0100 Subject: [PATCH 105/141] Add RC to version --- build/templates/UmbracoPackage/.template.config/template.json | 2 +- build/templates/UmbracoProject/.template.config/template.json | 2 +- src/Directory.Build.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build/templates/UmbracoPackage/.template.config/template.json b/build/templates/UmbracoPackage/.template.config/template.json index 32f0c924dd..1a4dd16fd7 100644 --- a/build/templates/UmbracoPackage/.template.config/template.json +++ b/build/templates/UmbracoPackage/.template.config/template.json @@ -24,7 +24,7 @@ "version": { "type": "parameter", "datatype": "string", - "defaultValue": "9.3.0", + "defaultValue": "9.3.0-rc", "description": "The version of Umbraco to load using NuGet", "replaces": "UMBRACO_VERSION_FROM_TEMPLATE" }, diff --git a/build/templates/UmbracoProject/.template.config/template.json b/build/templates/UmbracoProject/.template.config/template.json index b09050a2a4..fd41de8d1c 100644 --- a/build/templates/UmbracoProject/.template.config/template.json +++ b/build/templates/UmbracoProject/.template.config/template.json @@ -57,7 +57,7 @@ "version": { "type": "parameter", "datatype": "string", - "defaultValue": "9.3.0", + "defaultValue": "9.3.0-rc", "description": "The version of Umbraco to load using NuGet", "replaces": "UMBRACO_VERSION_FROM_TEMPLATE" }, diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 55448806ef..995c8afebd 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -5,7 +5,7 @@ 9.3.0 9.3.0 - 9.3.0 + 9.3.0-rc 9.3.0 9.0 en-US From 2ddb3e13e567a1b452ac0a4f11c37342a183514d Mon Sep 17 00:00:00 2001 From: Mole Date: Wed, 26 Jan 2022 10:05:01 +0100 Subject: [PATCH 106/141] Fix breaking changes --- src/JsonSchema/AppSettings.cs | 1 + .../Configuration/Models/ContentDashboardSettings.cs | 3 ++- .../Configuration/Models/RequestHandlerSettings.cs | 10 ++++++++++ .../UmbracoBuilder.Configuration.cs | 1 + 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/JsonSchema/AppSettings.cs b/src/JsonSchema/AppSettings.cs index 048513a5da..62817bdec7 100644 --- a/src/JsonSchema/AppSettings.cs +++ b/src/JsonSchema/AppSettings.cs @@ -1,6 +1,7 @@ // Copyright (c) Umbraco. // See LICENSE for more details. +using Umbraco.Cms.Core.Configuration; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Deploy.Core.Configuration.DeployConfiguration; using Umbraco.Deploy.Core.Configuration.DeployProjectConfiguration; diff --git a/src/Umbraco.Core/Configuration/Models/ContentDashboardSettings.cs b/src/Umbraco.Core/Configuration/Models/ContentDashboardSettings.cs index 3f8546a1ad..768f7c2088 100644 --- a/src/Umbraco.Core/Configuration/Models/ContentDashboardSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/ContentDashboardSettings.cs @@ -1,6 +1,7 @@ using System.ComponentModel; +using Umbraco.Cms.Core.Configuration.Models; -namespace Umbraco.Cms.Core.Configuration.Models +namespace Umbraco.Cms.Core.Configuration { /// /// Typed configuration options for content dashboard settings. diff --git a/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs b/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs index 051c31dc26..c820ea191b 100644 --- a/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs @@ -85,5 +85,15 @@ namespace Umbraco.Cms.Core.Configuration.Models /// Add additional character replacements, or override defaults /// public IEnumerable UserDefinedCharCollection { get; set; } + + [Obsolete("Use CharItem in the Umbraco.Cms.Core.Configuration.Models namespace instead.")] + public class CharItem : IChar + { + /// + public string Char { get; set; } + + /// + public string Replacement { get; set; } + } } } diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs index f0cbf7f95d..8baf34f9cb 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs @@ -4,6 +4,7 @@ using System.Reflection; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Configuration.Models.Validation; using Umbraco.Extensions; From 72533d29c89f1ece658e230fd12484fb68f8474c Mon Sep 17 00:00:00 2001 From: Mole Date: Wed, 26 Jan 2022 10:20:12 +0100 Subject: [PATCH 107/141] Specify namespace for CharITem --- .../Configuration/Models/RequestHandlerSettings.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs b/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs index c820ea191b..2bdcaef7f3 100644 --- a/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs @@ -19,7 +19,7 @@ namespace Umbraco.Cms.Core.Configuration.Models internal const string StaticConvertUrlsToAscii = "try"; internal const bool StaticEnableDefaultCharReplacements = true; - internal static readonly CharItem[] DefaultCharCollection = + internal static readonly Umbraco.Cms.Core.Configuration.Models.CharItem[] DefaultCharCollection = { new () { Char = " ", Replacement = "-" }, new () { Char = "\"", Replacement = string.Empty }, @@ -84,9 +84,9 @@ namespace Umbraco.Cms.Core.Configuration.Models /// /// Add additional character replacements, or override defaults /// - public IEnumerable UserDefinedCharCollection { get; set; } + public IEnumerable UserDefinedCharCollection { get; set; } - [Obsolete("Use CharItem in the Umbraco.Cms.Core.Configuration.Models namespace instead.")] + [Obsolete("Use CharItem in the Umbraco.Cms.Core.Configuration.Models namespace instead. Scheduled for removal in V10.")] public class CharItem : IChar { /// From 9dbe2d211c11fc69702dee3eb244af511cd9dc53 Mon Sep 17 00:00:00 2001 From: Mole Date: Wed, 26 Jan 2022 10:57:49 +0100 Subject: [PATCH 108/141] Add allowlist for HelpPage --- src/Umbraco.Core/ConfigsExtensions.cs | 3 +++ src/Umbraco.Core/Constants-AppSettings.cs | 5 ++++ src/Umbraco.Core/Help/HelpPageSettings.cs | 12 ++++++++++ src/Umbraco.Core/Help/IHelpPageSettings.cs | 10 ++++++++ src/Umbraco.Core/Umbraco.Core.csproj | 2 ++ src/Umbraco.Web.UI/web.Template.config | 1 + src/Umbraco.Web/Editors/HelpController.cs | 28 ++++++++++++++++++++++ 7 files changed, 61 insertions(+) create mode 100644 src/Umbraco.Core/Help/HelpPageSettings.cs create mode 100644 src/Umbraco.Core/Help/IHelpPageSettings.cs diff --git a/src/Umbraco.Core/ConfigsExtensions.cs b/src/Umbraco.Core/ConfigsExtensions.cs index 25c69899c0..01247cc69e 100644 --- a/src/Umbraco.Core/ConfigsExtensions.cs +++ b/src/Umbraco.Core/ConfigsExtensions.cs @@ -5,6 +5,7 @@ using Umbraco.Core.Configuration.Grid; using Umbraco.Core.Configuration.HealthChecks; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Dashboards; +using Umbraco.Core.Help; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Manifest; @@ -50,6 +51,8 @@ namespace Umbraco.Core factory.GetInstance().Debug)); configs.Add(() => new ContentDashboardSettings()); + + configs.Add(() => new HelpPageSettings()); } } } diff --git a/src/Umbraco.Core/Constants-AppSettings.cs b/src/Umbraco.Core/Constants-AppSettings.cs index 4e5619813e..6a58675e91 100644 --- a/src/Umbraco.Core/Constants-AppSettings.cs +++ b/src/Umbraco.Core/Constants-AppSettings.cs @@ -125,6 +125,11 @@ namespace Umbraco.Core /// public const string ContentDashboardUrlAllowlist = "Umbraco.Core.ContentDashboardUrl-Allowlist"; + /// + /// A list of allowed addresses to fetch content for the help page. + /// + public const string HelpPageUrlAllowList = "Umbraco.Core.HelpPage-Allowlist"; + /// /// TODO: FILL ME IN /// diff --git a/src/Umbraco.Core/Help/HelpPageSettings.cs b/src/Umbraco.Core/Help/HelpPageSettings.cs new file mode 100644 index 0000000000..d2a4a3a0f5 --- /dev/null +++ b/src/Umbraco.Core/Help/HelpPageSettings.cs @@ -0,0 +1,12 @@ +using System.Configuration; + +namespace Umbraco.Core.Help +{ + public class HelpPageSettings : IHelpPageSettings + { + public string HelpPageUrlAllowList => + ConfigurationManager.AppSettings.ContainsKey(Constants.AppSettings.HelpPageUrlAllowList) + ? ConfigurationManager.AppSettings[Constants.AppSettings.HelpPageUrlAllowList] + : null; + } +} diff --git a/src/Umbraco.Core/Help/IHelpPageSettings.cs b/src/Umbraco.Core/Help/IHelpPageSettings.cs new file mode 100644 index 0000000000..5643e47a30 --- /dev/null +++ b/src/Umbraco.Core/Help/IHelpPageSettings.cs @@ -0,0 +1,10 @@ +namespace Umbraco.Core.Help +{ + public interface IHelpPageSettings + { + /// + /// Gets the allowed addresses to retrieve data for the help page. + /// + string HelpPageUrlAllowList { get; } + } +} diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index e27c6eeceb..35948ede91 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -137,6 +137,8 @@ + + diff --git a/src/Umbraco.Web.UI/web.Template.config b/src/Umbraco.Web.UI/web.Template.config index e4e3e19bcb..32e624e400 100644 --- a/src/Umbraco.Web.UI/web.Template.config +++ b/src/Umbraco.Web.UI/web.Template.config @@ -39,6 +39,7 @@ + diff --git a/src/Umbraco.Web/Editors/HelpController.cs b/src/Umbraco.Web/Editors/HelpController.cs index 39dbbc435c..77e1675f87 100644 --- a/src/Umbraco.Web/Editors/HelpController.cs +++ b/src/Umbraco.Web/Editors/HelpController.cs @@ -1,16 +1,33 @@ using Newtonsoft.Json; using System.Collections.Generic; +using System.Net; using System.Net.Http; using System.Runtime.Serialization; using System.Threading.Tasks; +using System.Web.Http; +using Umbraco.Core.Help; +using Umbraco.Core.Logging; namespace Umbraco.Web.Editors { public class HelpController : UmbracoAuthorizedJsonController { + private readonly IHelpPageSettings _helpPageSettings; + + public HelpController(IHelpPageSettings helpPageSettings) + { + _helpPageSettings = helpPageSettings; + } + private static HttpClient _httpClient; public async Task> GetContextHelpForPage(string section, string tree, string baseUrl = "https://our.umbraco.com") { + if (IsAllowedUrl(baseUrl) is false) + { + Logger.Error($"The following URL is not listed in the allowlist for HelpPage in web.config: {baseUrl}"); + throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, "HelpPage source not permitted")); + } + var url = string.Format(baseUrl + "/Umbraco/Documentation/Lessons/GetContextHelpDocs?sectionAlias={0}&treeAlias={1}", section, tree); try @@ -33,6 +50,17 @@ namespace Umbraco.Web.Editors return new List(); } + + private bool IsAllowedUrl(string url) + { + if (string.IsNullOrEmpty(_helpPageSettings.HelpPageUrlAllowList) || + _helpPageSettings.HelpPageUrlAllowList.Contains(url)) + { + return true; + } + + return false; + } } [DataContract(Name = "HelpPage")] From 2f17d766be1894340637a8f2f1a7ebaf7cab2638 Mon Sep 17 00:00:00 2001 From: Mole Date: Wed, 26 Jan 2022 10:57:49 +0100 Subject: [PATCH 109/141] Cherry pick Add allowlist for HelpPage --- src/Umbraco.Core/Help/HelpPageSettings.cs | 12 ++++++++++ src/Umbraco.Core/Help/IHelpPageSettings.cs | 10 ++++++++ .../Controllers/HelpController.cs | 24 +++++++++++++++++-- 3 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 src/Umbraco.Core/Help/HelpPageSettings.cs create mode 100644 src/Umbraco.Core/Help/IHelpPageSettings.cs diff --git a/src/Umbraco.Core/Help/HelpPageSettings.cs b/src/Umbraco.Core/Help/HelpPageSettings.cs new file mode 100644 index 0000000000..d2a4a3a0f5 --- /dev/null +++ b/src/Umbraco.Core/Help/HelpPageSettings.cs @@ -0,0 +1,12 @@ +using System.Configuration; + +namespace Umbraco.Core.Help +{ + public class HelpPageSettings : IHelpPageSettings + { + public string HelpPageUrlAllowList => + ConfigurationManager.AppSettings.ContainsKey(Constants.AppSettings.HelpPageUrlAllowList) + ? ConfigurationManager.AppSettings[Constants.AppSettings.HelpPageUrlAllowList] + : null; + } +} diff --git a/src/Umbraco.Core/Help/IHelpPageSettings.cs b/src/Umbraco.Core/Help/IHelpPageSettings.cs new file mode 100644 index 0000000000..5643e47a30 --- /dev/null +++ b/src/Umbraco.Core/Help/IHelpPageSettings.cs @@ -0,0 +1,10 @@ +namespace Umbraco.Core.Help +{ + public interface IHelpPageSettings + { + /// + /// Gets the allowed addresses to retrieve data for the help page. + /// + string HelpPageUrlAllowList { get; } + } +} diff --git a/src/Umbraco.Web.BackOffice/Controllers/HelpController.cs b/src/Umbraco.Web.BackOffice/Controllers/HelpController.cs index 3bc45703fa..ecec8f864d 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/HelpController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/HelpController.cs @@ -1,10 +1,11 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Net.Http; using System.Runtime.Serialization; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Umbraco.Cms.Web.Common.Attributes; +using Umbraco.Core.Help; using Constants = Umbraco.Cms.Core.Constants; namespace Umbraco.Cms.Web.BackOffice.Controllers @@ -13,8 +14,10 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers public class HelpController : UmbracoAuthorizedJsonController { private readonly ILogger _logger; + private readonly IHelpPageSettings _helpPageSettings; - public HelpController(ILogger logger) + public HelpController(ILogger logger, + IHelpPageSettings helpPageSettings) { _logger = logger; } @@ -22,6 +25,12 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers private static HttpClient _httpClient; public async Task> GetContextHelpForPage(string section, string tree, string baseUrl = "https://our.umbraco.com") { + if (IsAllowedUrl(baseUrl) is false) + { + Logger.Error($"The following URL is not listed in the allowlist for HelpPage in web.config: {baseUrl}"); + throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, "HelpPage source not permitted")); + } + var url = string.Format(baseUrl + "/Umbraco/Documentation/Lessons/GetContextHelpDocs?sectionAlias={0}&treeAlias={1}", section, tree); try @@ -44,6 +53,17 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers return new List(); } + + private bool IsAllowedUrl(string url) + { + if (string.IsNullOrEmpty(_helpPageSettings.HelpPageUrlAllowList) || + _helpPageSettings.HelpPageUrlAllowList.Contains(url)) + { + return true; + } + + return false; + } } [DataContract(Name = "HelpPage")] From 3261a6f71dd519ab0c17b9df6a1a773e044f2a87 Mon Sep 17 00:00:00 2001 From: Mole Date: Wed, 26 Jan 2022 12:12:59 +0100 Subject: [PATCH 110/141] Fix up for V9 --- src/JsonSchema/AppSettings.cs | 2 ++ .../Configuration/Models/HelpPageSettings.cs | 11 ++++++ src/Umbraco.Core/Constants-Configuration.cs | 1 + .../UmbracoBuilder.Configuration.cs | 3 +- src/Umbraco.Core/Help/HelpPageSettings.cs | 12 ------- src/Umbraco.Core/Help/IHelpPageSettings.cs | 10 ------ .../Controllers/HelpController.cs | 34 +++++++++++++++---- 7 files changed, 43 insertions(+), 30 deletions(-) create mode 100644 src/Umbraco.Core/Configuration/Models/HelpPageSettings.cs delete mode 100644 src/Umbraco.Core/Help/HelpPageSettings.cs delete mode 100644 src/Umbraco.Core/Help/IHelpPageSettings.cs diff --git a/src/JsonSchema/AppSettings.cs b/src/JsonSchema/AppSettings.cs index 62817bdec7..73c5ea18f5 100644 --- a/src/JsonSchema/AppSettings.cs +++ b/src/JsonSchema/AppSettings.cs @@ -89,6 +89,8 @@ namespace JsonSchema public LegacyPasswordMigrationSettings LegacyPasswordMigration { get; set; } public ContentDashboardSettings ContentDashboard { get; set; } + + public HelpPageSettings HelpPage { get; set; } } /// diff --git a/src/Umbraco.Core/Configuration/Models/HelpPageSettings.cs b/src/Umbraco.Core/Configuration/Models/HelpPageSettings.cs new file mode 100644 index 0000000000..3bd518b37e --- /dev/null +++ b/src/Umbraco.Core/Configuration/Models/HelpPageSettings.cs @@ -0,0 +1,11 @@ +namespace Umbraco.Cms.Core.Configuration.Models +{ + [UmbracoOptions(Constants.Configuration.ConfigHelpPage)] + public class HelpPageSettings + { + /// + /// Gets or sets the allowed addresses to retrieve data for the content dashboard. + /// + public string[] HelpPageUrlAllowList { get; set; } + } +} diff --git a/src/Umbraco.Core/Constants-Configuration.cs b/src/Umbraco.Core/Constants-Configuration.cs index ab951618e3..bdbd13b2a4 100644 --- a/src/Umbraco.Core/Constants-Configuration.cs +++ b/src/Umbraco.Core/Constants-Configuration.cs @@ -55,6 +55,7 @@ namespace Umbraco.Cms.Core public const string ConfigRichTextEditor = ConfigPrefix + "RichTextEditor"; public const string ConfigPackageMigration = ConfigPrefix + "PackageMigration"; public const string ConfigContentDashboard = ConfigPrefix + "ContentDashboard"; + public const string ConfigHelpPage = ConfigPrefix + "HelpPage"; } } } diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs index 8baf34f9cb..91e6f71415 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs @@ -87,7 +87,8 @@ namespace Umbraco.Cms.Core.DependencyInjection .AddUmbracoOptions() .AddUmbracoOptions() .AddUmbracoOptions() - .AddUmbracoOptions(); + .AddUmbracoOptions() + .AddUmbracoOptions(); builder.Services.Configure(options => options.MergeReplacements(builder.Config)); diff --git a/src/Umbraco.Core/Help/HelpPageSettings.cs b/src/Umbraco.Core/Help/HelpPageSettings.cs deleted file mode 100644 index d2a4a3a0f5..0000000000 --- a/src/Umbraco.Core/Help/HelpPageSettings.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Configuration; - -namespace Umbraco.Core.Help -{ - public class HelpPageSettings : IHelpPageSettings - { - public string HelpPageUrlAllowList => - ConfigurationManager.AppSettings.ContainsKey(Constants.AppSettings.HelpPageUrlAllowList) - ? ConfigurationManager.AppSettings[Constants.AppSettings.HelpPageUrlAllowList] - : null; - } -} diff --git a/src/Umbraco.Core/Help/IHelpPageSettings.cs b/src/Umbraco.Core/Help/IHelpPageSettings.cs deleted file mode 100644 index 5643e47a30..0000000000 --- a/src/Umbraco.Core/Help/IHelpPageSettings.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Umbraco.Core.Help -{ - public interface IHelpPageSettings - { - /// - /// Gets the allowed addresses to retrieve data for the help page. - /// - string HelpPageUrlAllowList { get; } - } -} diff --git a/src/Umbraco.Web.BackOffice/Controllers/HelpController.cs b/src/Umbraco.Web.BackOffice/Controllers/HelpController.cs index ecec8f864d..dd01f9621f 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/HelpController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/HelpController.cs @@ -1,11 +1,17 @@ +using System; using System.Collections.Generic; +using System.Linq; +using System.Net; using System.Net.Http; using System.Runtime.Serialization; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Newtonsoft.Json; +using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Web.Common.Attributes; -using Umbraco.Core.Help; +using Umbraco.Cms.Web.Common.DependencyInjection; using Constants = Umbraco.Cms.Core.Constants; namespace Umbraco.Cms.Web.BackOffice.Controllers @@ -14,21 +20,35 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers public class HelpController : UmbracoAuthorizedJsonController { private readonly ILogger _logger; - private readonly IHelpPageSettings _helpPageSettings; + private readonly HelpPageSettings _helpPageSettings; - public HelpController(ILogger logger, - IHelpPageSettings helpPageSettings) + [Obsolete("Use constructor that takes IOptions")] + public HelpController(ILogger logger) + : this(logger, StaticServiceProvider.Instance.GetRequiredService>()) + { + } + + [ActivatorUtilitiesConstructor] + public HelpController( + ILogger logger, + IOptions helpPageSettings) { _logger = logger; + _helpPageSettings = helpPageSettings.Value; } private static HttpClient _httpClient; + public async Task> GetContextHelpForPage(string section, string tree, string baseUrl = "https://our.umbraco.com") { if (IsAllowedUrl(baseUrl) is false) { - Logger.Error($"The following URL is not listed in the allowlist for HelpPage in web.config: {baseUrl}"); - throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, "HelpPage source not permitted")); + _logger.LogError($"The following URL is not listed in the allowlist for HelpPage in web.config: {baseUrl}"); + HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest; + + // Ideally we'd want to return a BadRequestResult here, + // however, since we're not returning ActionResult this is not possible and changing it would be a breaking change. + return new List(); } var url = string.Format(baseUrl + "/Umbraco/Documentation/Lessons/GetContextHelpDocs?sectionAlias={0}&treeAlias={1}", section, tree); @@ -56,7 +76,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers private bool IsAllowedUrl(string url) { - if (string.IsNullOrEmpty(_helpPageSettings.HelpPageUrlAllowList) || + if (_helpPageSettings.HelpPageUrlAllowList is null || _helpPageSettings.HelpPageUrlAllowList.Contains(url)) { return true; From 4d4aff4c67893ba9ae360cc0a88d3655bc319d6c Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Wed, 26 Jan 2022 12:22:05 +0100 Subject: [PATCH 111/141] Apply changes from #11805 and #11806 to v9 (#11904) * Apply changes from #11805 and #11806 to v9 * Update documentation and cleanup code styling --- .../Models/PropertyTagsExtensions.cs | 8 +- .../PropertyEditors/DataValueEditor.cs | 157 ++++++++++-------- .../PropertyEditors/GridPropertyEditor.cs | 9 +- .../ImageCropperPropertyEditor.cs | 7 +- .../ImageCropperPropertyValueEditor.cs | 36 ++-- .../MediaPicker3PropertyEditor.cs | 7 +- .../MultiUrlPickerValueEditor.cs | 6 +- .../MultipleTextStringPropertyEditor.cs | 4 +- .../PropertyEditors/MultipleValueEditor.cs | 3 +- .../NestedContentPropertyEditor.cs | 6 +- .../PropertyEditors/RichTextPropertyEditor.cs | 4 +- .../PropertyEditors/TagsPropertyEditor.cs | 2 +- .../ValueConverters/ImageCropperValue.cs | 34 ++-- .../Serialization/JsonNetSerializer.cs | 21 +-- .../Serialization/JsonToStringConverter.cs | 3 +- .../BlockEditorComponentTests.cs | 2 +- 16 files changed, 159 insertions(+), 150 deletions(-) diff --git a/src/Umbraco.Core/Models/PropertyTagsExtensions.cs b/src/Umbraco.Core/Models/PropertyTagsExtensions.cs index 7168f99078..a9da454986 100644 --- a/src/Umbraco.Core/Models/PropertyTagsExtensions.cs +++ b/src/Umbraco.Core/Models/PropertyTagsExtensions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Logging; @@ -75,8 +75,7 @@ namespace Umbraco.Extensions var updatedTags = currentTags.Union(trimmedTags).ToArray(); var updatedValue = updatedTags.Length == 0 ? null : serializer.Serialize(updatedTags); property.SetValue(updatedValue, culture); // json array - break; - property.SetValue(serializer.Serialize(currentTags.Union(trimmedTags).ToArray()), culture); // json array + break; } } else @@ -88,7 +87,8 @@ namespace Umbraco.Extensions break; case TagsStorageType.Json: - property.SetValue(serializer.Serialize(trimmedTags), culture); // json array + var updatedValue = trimmedTags.Length == 0 ? null : serializer.Serialize(trimmedTags); + property.SetValue(updatedValue, culture); // json array break; } } diff --git a/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs b/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs index 6d3e40067e..6f9e1b6611 100644 --- a/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Globalization; @@ -149,84 +149,90 @@ namespace Umbraco.Cms.Core.PropertyEditors public virtual bool IsReadOnly => false; /// - /// Used to try to convert the string value to the correct CLR type based on the DatabaseDataType specified for this value editor + /// Used to try to convert the string value to the correct CLR type based on the specified for this value editor. /// - /// - /// + /// The value. + /// + /// The result of the conversion attempt. + /// + /// ValueType was out of range. internal Attempt TryConvertValueToCrlType(object value) { - // if (value is JValue) - // value = value.ToString(); - - //this is a custom check to avoid any errors, if it's a string and it's empty just make it null + // Ensure empty string values are converted to null if (value is string s && string.IsNullOrWhiteSpace(s)) + { value = null; + } + // Ensure JSON is serialized properly (without indentation or converted to null when empty) + if (value is not null && ValueType.InvariantEquals(ValueTypes.Json)) + { + var jsonValue = _jsonSerializer.Serialize(value); + + if (jsonValue.DetectIsEmptyJson()) + { + value = null; + } + else + { + value = jsonValue; + } + } + + // Convert the string to a known type Type valueType; - //convert the string to a known type switch (ValueTypes.ToStorageType(ValueType)) { case ValueStorageType.Ntext: case ValueStorageType.Nvarchar: valueType = typeof(string); break; - case ValueStorageType.Integer: - //ensure these are nullable so we can return a null if required - //NOTE: This is allowing type of 'long' because I think json.net will deserialize a numerical value as long - // instead of int. Even though our db will not support this (will get truncated), we'll at least parse to this. + case ValueStorageType.Integer: + // Ensure these are nullable so we can return a null if required + // NOTE: This is allowing type of 'long' because I think JSON.NEt will deserialize a numerical value as long instead of int + // Even though our DB will not support this (will get truncated), we'll at least parse to this valueType = typeof(long?); - //if parsing is successful, we need to return as an Int, we're only dealing with long's here because of json.net, we actually - //don't support long values and if we return a long value it will get set as a 'long' on the Property.Value (object) and then - //when we compare the values for dirty tracking we'll be comparing an int -> long and they will not match. + // If parsing is successful, we need to return as an int, we're only dealing with long's here because of JSON.NET, + // we actually don't support long values and if we return a long value, it will get set as a 'long' on the Property.Value (object) and then + // when we compare the values for dirty tracking we'll be comparing an int -> long and they will not match. var result = value.TryConvertTo(valueType); + return result.Success && result.Result != null ? Attempt.Succeed((int)(long)result.Result) : result; case ValueStorageType.Decimal: - //ensure these are nullable so we can return a null if required + // Ensure these are nullable so we can return a null if required valueType = typeof(decimal?); break; case ValueStorageType.Date: - //ensure these are nullable so we can return a null if required + // Ensure these are nullable so we can return a null if required valueType = typeof(DateTime?); break; + default: - throw new ArgumentOutOfRangeException(); + throw new ArgumentOutOfRangeException("ValueType was out of range."); } return value.TryConvertTo(valueType); } - /// - /// A method to deserialize the string value that has been saved in the content editor - /// to an object to be stored in the database. - /// - /// - /// - /// The current value that has been persisted to the database for this editor. This value may be useful for - /// how the value then get's deserialized again to be re-persisted. In most cases it will probably not be used. - /// - /// - /// - /// - /// - /// By default this will attempt to automatically convert the string value to the value type supplied by ValueType. - /// - /// If overridden then the object returned must match the type supplied in the ValueType, otherwise persisting the - /// value to the DB will fail when it tries to validate the value type. - /// + /// + /// A method to deserialize the string value that has been saved in the content editor to an object to be stored in the database. + /// + /// The value returned by the editor. + /// The current value that has been persisted to the database for this editor. This value may be useful for how the value then get's deserialized again to be re-persisted. In most cases it will probably not be used. + /// The value that gets persisted to the database. + /// + /// By default this will attempt to automatically convert the string value to the value type supplied by ValueType. + /// If overridden then the object returned must match the type supplied in the ValueType, otherwise persisting the + /// value to the DB will fail when it tries to validate the value type. + /// public virtual object FromEditor(ContentPropertyData editorValue, object currentValue) { - //if it's json but it's empty json, then return null - if (ValueType.InvariantEquals(ValueTypes.Json) && editorValue.Value != null && editorValue.Value.ToString().DetectIsEmptyJson()) - { - return null; - } - var result = TryConvertValueToCrlType(editorValue.Value); if (result.Success == false) { @@ -238,64 +244,71 @@ namespace Umbraco.Cms.Core.PropertyEditors } /// - /// A method used to format the database value to a value that can be used by the editor + /// A method used to format the database value to a value that can be used by the editor. /// - /// - /// - /// - /// + /// The property. + /// The culture. + /// The segment. /// + /// ValueType was out of range. /// - /// The object returned will automatically be serialized into json notation. For most property editors - /// the value returned is probably just a string but in some cases a json structure will be returned. + /// The object returned will automatically be serialized into JSON notation. For most property editors + /// the value returned is probably just a string, but in some cases a JSON structure will be returned. /// public virtual object ToEditor(IProperty property, string culture = null, string segment = null) { - var val = property.GetValue(culture, segment); - if (val == null) return string.Empty; + var value = property.GetValue(culture, segment); + if (value == null) + { + return string.Empty; + } switch (ValueTypes.ToStorageType(ValueType)) { case ValueStorageType.Ntext: case ValueStorageType.Nvarchar: - //if it is a string type, we will attempt to see if it is json stored data, if it is we'll try to convert - //to a real json object so we can pass the true json object directly to angular! - var asString = val.ToString(); - if (asString.DetectIsJson()) + // If it is a string type, we will attempt to see if it is JSON stored data, if it is we'll try to convert + // to a real JSON object so we can pass the true JSON object directly to Angular! + var stringValue = value as string ?? value.ToString(); + if (stringValue.DetectIsJson()) { try { - var json = _jsonSerializer.Deserialize(asString); - return json; + return _jsonSerializer.Deserialize(stringValue); } catch { - //swallow this exception, we thought it was json but it really isn't so continue returning a string + // Swallow this exception, we thought it was JSON but it really isn't so continue returning a string } } - return asString; + + return stringValue; + case ValueStorageType.Integer: case ValueStorageType.Decimal: - //Decimals need to be formatted with invariant culture (dots, not commas) - //Anything else falls back to ToString() - var decim = val.TryConvertTo(); - return decim.Success - ? decim.Result.ToString(NumberFormatInfo.InvariantInfo) - : val.ToString(); + // Decimals need to be formatted with invariant culture (dots, not commas) + // Anything else falls back to ToString() + var decimalValue = value.TryConvertTo(); + + return decimalValue.Success + ? decimalValue.Result.ToString(NumberFormatInfo.InvariantInfo) + : value.ToString(); + case ValueStorageType.Date: - var date = val.TryConvertTo(); - if (date.Success == false || date.Result == null) + var dateValue = value.TryConvertTo(); + if (dateValue.Success == false || dateValue.Result == null) { return string.Empty; } - //Dates will be formatted as yyyy-MM-dd HH:mm:ss - return date.Result.Value.ToIsoString(); + + // Dates will be formatted as yyyy-MM-dd HH:mm:ss + return dateValue.Result.Value.ToIsoString(); + default: - throw new ArgumentOutOfRangeException(); + throw new ArgumentOutOfRangeException("ValueType was out of range."); } } - // TODO: the methods below should be replaced by proper property value convert ToXPath usage! /// diff --git a/src/Umbraco.Infrastructure/PropertyEditors/GridPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/GridPropertyEditor.cs index 52a1e50fc4..f149757919 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/GridPropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/GridPropertyEditor.cs @@ -1,12 +1,10 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using System; using System.Collections.Generic; using System.Linq; -using Microsoft.Extensions.Logging; using Newtonsoft.Json; -using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Media; using Umbraco.Cms.Core.Models; @@ -153,7 +151,8 @@ namespace Umbraco.Cms.Core.PropertyEditors public override object ToEditor(IProperty property, string culture = null, string segment = null) { var val = property.GetValue(culture, segment)?.ToString(); - if (val.IsNullOrWhiteSpace()) return string.Empty; + if (val.IsNullOrWhiteSpace()) + return string.Empty; var grid = DeserializeGridValue(val, out var rtes, out _); @@ -199,7 +198,7 @@ namespace Umbraco.Cms.Core.PropertyEditors _richTextPropertyValueEditor.GetReferences(x.Value))) yield return umbracoEntityReference; - foreach (var umbracoEntityReference in mediaValues.Where(x=>x.Value.HasValues) + foreach (var umbracoEntityReference in mediaValues.Where(x => x.Value.HasValues) .SelectMany(x => _mediaPickerPropertyValueEditor.GetReferences(x.Value["udi"]))) yield return umbracoEntityReference; } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyEditor.cs index 8a38a85551..ed194038d9 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyEditor.cs @@ -208,6 +208,7 @@ namespace Umbraco.Cms.Core.PropertyEditors { continue; } + var sourcePath = _mediaFileManager.FileSystem.GetRelativePath(src); var copyPath = _mediaFileManager.CopyFile(notification.Copy, property.PropertyType, sourcePath); jo["src"] = _mediaFileManager.FileSystem.GetUrl(copyPath); @@ -273,10 +274,8 @@ namespace Umbraco.Cms.Core.PropertyEditors // the property value will be the file source eg '/media/23454/hello.jpg' and we // are fixing that anomaly here - does not make any sense at all but... bah... src = svalue; - property.SetValue(JsonConvert.SerializeObject(new - { - src = svalue - }, Formatting.None), pvalue.Culture, pvalue.Segment); + + property.SetValue(JsonConvert.SerializeObject(new { src = svalue }, Formatting.None), pvalue.Culture, pvalue.Segment); } else { diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyValueEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyValueEditor.cs index 5ae10bc178..d24a46f815 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyValueEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyValueEditor.cs @@ -86,31 +86,42 @@ namespace Umbraco.Cms.Core.PropertyEditors /// public override object FromEditor(ContentPropertyData editorValue, object currentValue) { - // get the current path + // Get the current path var currentPath = string.Empty; try { var svalue = currentValue as string; var currentJson = string.IsNullOrWhiteSpace(svalue) ? null : JObject.Parse(svalue); - if (currentJson != null && currentJson["src"] != null) - currentPath = currentJson["src"].Value(); + if (currentJson != null && currentJson.TryGetValue("src", out var src)) + { + currentPath = src.Value(); + } } catch (Exception ex) { - // for some reason the value is invalid so continue as if there was no value there + // For some reason the value is invalid so continue as if there was no value there _logger.LogWarning(ex, "Could not parse current db value to a JObject."); } + if (string.IsNullOrWhiteSpace(currentPath) == false) currentPath = _mediaFileManager.FileSystem.GetRelativePath(currentPath); - // get the new json and path - JObject editorJson = null; + // Get the new JSON and file path var editorFile = string.Empty; - if (editorValue.Value != null) + if (editorValue.Value is JObject editorJson) { - editorJson = editorValue.Value as JObject; - if (editorJson != null && editorJson["src"] != null) + // Populate current file + if (editorJson["src"] != null) + { editorFile = editorJson["src"].Value(); + } + + // Clean up redundant/default data + ImageCropperValue.Prune(editorJson); + } + else + { + editorJson = null; } // ensure we have the required guids @@ -138,7 +149,7 @@ namespace Umbraco.Cms.Core.PropertyEditors return null; // clear } - return editorJson?.ToString(); // unchanged + return editorJson?.ToString(Formatting.None); // unchanged } // process the file @@ -159,7 +170,7 @@ namespace Umbraco.Cms.Core.PropertyEditors // update json and return if (editorJson == null) return null; editorJson["src"] = filepath == null ? string.Empty : _mediaFileManager.FileSystem.GetUrl(filepath); - return editorJson.ToString(); + return editorJson.ToString(Formatting.None); } private string ProcessFile(ContentPropertyFile file, Guid cuid, Guid puid) @@ -186,7 +197,6 @@ namespace Umbraco.Cms.Core.PropertyEditors return filepath; } - public override string ConvertDbToString(IPropertyType propertyType, object value) { if (value == null || string.IsNullOrEmpty(value.ToString())) @@ -205,7 +215,7 @@ namespace Umbraco.Cms.Core.PropertyEditors { src = val, crops = crops - },new JsonSerializerSettings() + }, new JsonSerializerSettings() { Formatting = Formatting.None, NullValueHandling = NullValueHandling.Ignore diff --git a/src/Umbraco.Infrastructure/PropertyEditors/MediaPicker3PropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/MediaPicker3PropertyEditor.cs index 2cfe5dd56e..25174d5599 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/MediaPicker3PropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/MediaPicker3PropertyEditor.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.IO; @@ -157,7 +157,6 @@ namespace Umbraco.Cms.Core.PropertyEditors } } - /// /// Model/DTO that represents the JSON that the MediaPicker3 stores. /// @@ -176,7 +175,6 @@ namespace Umbraco.Cms.Core.PropertyEditors [DataMember(Name = "focalPoint")] public ImageCropperValue.ImageCropperFocalPoint FocalPoint { get; set; } - /// /// Applies the configuration to ensure only valid crops are kept and have the correct width/height. /// @@ -214,9 +212,6 @@ namespace Umbraco.Cms.Core.PropertyEditors /// Removes redundant crop data/default focal point. /// /// The media with crops DTO. - /// - /// The cleaned up value. - /// /// /// Because the DTO uses the same JSON keys as the image cropper value for crops and focal point, we can re-use the prune method. /// diff --git a/src/Umbraco.Infrastructure/PropertyEditors/MultiUrlPickerValueEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/MultiUrlPickerValueEditor.cs index f6d8a598b0..db55792a31 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/MultiUrlPickerValueEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/MultiUrlPickerValueEditor.cs @@ -57,7 +57,7 @@ namespace Umbraco.Cms.Core.PropertyEditors try { - var links = JsonConvert.DeserializeObject>(value); + var links = JsonConvert.DeserializeObject>(value); var documentLinks = links.FindAll(link => link.Udi != null && link.Udi.EntityType == Constants.UdiEntityType.Document); var mediaLinks = links.FindAll(link => link.Udi != null && link.Udi.EntityType == Constants.UdiEntityType.Media); @@ -158,11 +158,13 @@ namespace Umbraco.Cms.Core.PropertyEditors { var links = JsonConvert.DeserializeObject>(value); if (links.Count == 0) + { return null; + } return JsonConvert.SerializeObject( from link in links - select new MultiUrlPickerValueEditor.LinkDto + select new LinkDto { Name = link.Name, QueryString = link.QueryString, diff --git a/src/Umbraco.Infrastructure/PropertyEditors/MultipleTextStringPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/MultipleTextStringPropertyEditor.cs index 97cb677d4c..f0d5907e8e 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/MultipleTextStringPropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/MultipleTextStringPropertyEditor.cs @@ -1,14 +1,12 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; -using Microsoft.Extensions.Logging; using Newtonsoft.Json.Linq; using Umbraco.Cms.Core.Exceptions; -using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Editors; diff --git a/src/Umbraco.Infrastructure/PropertyEditors/MultipleValueEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/MultipleValueEditor.cs index 47f8c9a169..8177c9ffeb 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/MultipleValueEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/MultipleValueEditor.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using System; @@ -65,7 +65,6 @@ namespace Umbraco.Cms.Core.PropertyEditors } var values = json.Select(item => item.Value()).ToArray(); - if (values.Length == 0) { return null; diff --git a/src/Umbraco.Infrastructure/PropertyEditors/NestedContentPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/NestedContentPropertyEditor.cs index 835431820c..a3d30d0578 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/NestedContentPropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/NestedContentPropertyEditor.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using System; @@ -105,7 +105,9 @@ namespace Umbraco.Cms.Core.PropertyEditors var rows = _nestedContentValues.GetPropertyValues(propertyValue); if (rows.Count == 0) + { return null; + } foreach (var row in rows.ToList()) { @@ -141,8 +143,6 @@ namespace Umbraco.Cms.Core.PropertyEditors #endregion - - #region Convert database // editor // note: there is NO variant support here diff --git a/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs index 8eeb935c12..1cfbc3449e 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using System; @@ -148,7 +148,9 @@ namespace Umbraco.Cms.Core.PropertyEditors public override object FromEditor(ContentPropertyData editorValue, object currentValue) { if (editorValue.Value == null) + { return null; + } var userId = _backOfficeSecurityAccessor?.BackOfficeSecurity?.CurrentUser?.Id ?? Constants.Security.SuperUserId; diff --git a/src/Umbraco.Infrastructure/PropertyEditors/TagsPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/TagsPropertyEditor.cs index 42f6424bfa..30911b0866 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/TagsPropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/TagsPropertyEditor.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using System; diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/ImageCropperValue.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/ImageCropperValue.cs index 97f1b8398c..af9e820d66 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/ImageCropperValue.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/ImageCropperValue.cs @@ -21,14 +21,14 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters /// [JsonConverter(typeof(NoTypeConverterJsonConverter))] [TypeConverter(typeof(ImageCropperValueTypeConverter))] - [DataContract(Name="imageCropDataSet")] + [DataContract(Name = "imageCropDataSet")] public class ImageCropperValue : IHtmlEncodedString, IEquatable { /// /// Gets or sets the value source image. /// - [DataMember(Name="src")] - public string Src { get; set;} + [DataMember(Name = "src")] + public string Src { get; set; } /// /// Gets or sets the value focal point. @@ -44,9 +44,7 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters /// public override string ToString() - { - return Crops != null ? (Crops.Any() ? JsonConvert.SerializeObject(this) : Src) : string.Empty; - } + => HasCrops() || HasFocalPoint() ? JsonConvert.SerializeObject(this, Formatting.None) : Src; /// public string ToHtmlString() => Src; @@ -178,12 +176,10 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters /// Removes redundant crop data/default focal point. /// /// The image cropper value. - /// - /// The cleaned up value. - /// public static void Prune(JObject value) { - if (value is null) throw new ArgumentNullException(nameof(value)); + if (value is null) + throw new ArgumentNullException(nameof(value)); if (value.TryGetValue("crops", out var crops)) { @@ -252,8 +248,8 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters // properties are, practically, readonly // ReSharper disable NonReadonlyMemberInGetHashCode var hashCode = Src?.GetHashCode() ?? 0; - hashCode = (hashCode*397) ^ (FocalPoint?.GetHashCode() ?? 0); - hashCode = (hashCode*397) ^ (Crops?.GetHashCode() ?? 0); + hashCode = (hashCode * 397) ^ (FocalPoint?.GetHashCode() ?? 0); + hashCode = (hashCode * 397) ^ (Crops?.GetHashCode() ?? 0); return hashCode; // ReSharper restore NonReadonlyMemberInGetHashCode } @@ -298,7 +294,7 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters { // properties are, practically, readonly // ReSharper disable NonReadonlyMemberInGetHashCode - return (Left.GetHashCode()*397) ^ Top.GetHashCode(); + return (Left.GetHashCode() * 397) ^ Top.GetHashCode(); // ReSharper restore NonReadonlyMemberInGetHashCode } } @@ -352,9 +348,9 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters // properties are, practically, readonly // ReSharper disable NonReadonlyMemberInGetHashCode var hashCode = Alias?.GetHashCode() ?? 0; - hashCode = (hashCode*397) ^ Width; - hashCode = (hashCode*397) ^ Height; - hashCode = (hashCode*397) ^ (Coordinates?.GetHashCode() ?? 0); + hashCode = (hashCode * 397) ^ Width; + hashCode = (hashCode * 397) ^ Height; + hashCode = (hashCode * 397) ^ (Coordinates?.GetHashCode() ?? 0); return hashCode; // ReSharper restore NonReadonlyMemberInGetHashCode } @@ -409,9 +405,9 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters // properties are, practically, readonly // ReSharper disable NonReadonlyMemberInGetHashCode var hashCode = X1.GetHashCode(); - hashCode = (hashCode*397) ^ Y1.GetHashCode(); - hashCode = (hashCode*397) ^ X2.GetHashCode(); - hashCode = (hashCode*397) ^ Y2.GetHashCode(); + hashCode = (hashCode * 397) ^ Y1.GetHashCode(); + hashCode = (hashCode * 397) ^ X2.GetHashCode(); + hashCode = (hashCode * 397) ^ Y2.GetHashCode(); return hashCode; // ReSharper restore NonReadonlyMemberInGetHashCode } diff --git a/src/Umbraco.Infrastructure/Serialization/JsonNetSerializer.cs b/src/Umbraco.Infrastructure/Serialization/JsonNetSerializer.cs index 5c5377c0a1..dd228ac008 100644 --- a/src/Umbraco.Infrastructure/Serialization/JsonNetSerializer.cs +++ b/src/Umbraco.Infrastructure/Serialization/JsonNetSerializer.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using Newtonsoft.Json; using Newtonsoft.Json.Converters; @@ -15,25 +15,20 @@ namespace Umbraco.Cms.Infrastructure.Serialization { new StringEnumConverter() }, - Formatting = Formatting.None + Formatting = Formatting.None, + NullValueHandling = NullValueHandling.Ignore }; - public string Serialize(object input) - { - return JsonConvert.SerializeObject(input, JsonSerializerSettings); - } - public T Deserialize(string input) - { - return JsonConvert.DeserializeObject(input, JsonSerializerSettings); - } + public string Serialize(object input) => JsonConvert.SerializeObject(input, JsonSerializerSettings); + + public T Deserialize(string input) => JsonConvert.DeserializeObject(input, JsonSerializerSettings); public T DeserializeSubset(string input, string key) { if (key == null) throw new ArgumentNullException(nameof(key)); - var root = JsonConvert.DeserializeObject(input); - - var jToken = root.SelectToken(key); + var root = Deserialize(input); + var jToken = root?.SelectToken(key); return jToken switch { diff --git a/src/Umbraco.Infrastructure/Serialization/JsonToStringConverter.cs b/src/Umbraco.Infrastructure/Serialization/JsonToStringConverter.cs index 2e7416b2d2..3cf23154c8 100644 --- a/src/Umbraco.Infrastructure/Serialization/JsonToStringConverter.cs +++ b/src/Umbraco.Infrastructure/Serialization/JsonToStringConverter.cs @@ -1,4 +1,4 @@ -using System; +using System; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -20,6 +20,7 @@ namespace Umbraco.Cms.Infrastructure.Serialization { return reader.Value; } + // Load JObject from stream JObject jObject = JObject.Load(reader); return jObject.ToString(Formatting.None); diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockEditorComponentTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockEditorComponentTests.cs index b76719888f..efd753296a 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockEditorComponentTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockEditorComponentTests.cs @@ -21,7 +21,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors private readonly JsonSerializerSettings _serializerSettings = new JsonSerializerSettings { Formatting = Formatting.None, - NullValueHandling = NullValueHandling.Ignore, + NullValueHandling = NullValueHandling.Ignore }; private const string ContentGuid1 = "036ce82586a64dfba2d523a99ed80f58"; From d4c43b69f77f5a7fd2a3ab45f61c85c7cfe0b5c0 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 26 Jan 2022 12:40:14 +0100 Subject: [PATCH 112/141] Updated to use IOptionsMonitor --- .../Controllers/HelpController.cs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.BackOffice/Controllers/HelpController.cs b/src/Umbraco.Web.BackOffice/Controllers/HelpController.cs index dd01f9621f..d79001d0f8 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/HelpController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/HelpController.cs @@ -20,21 +20,28 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers public class HelpController : UmbracoAuthorizedJsonController { private readonly ILogger _logger; - private readonly HelpPageSettings _helpPageSettings; + private HelpPageSettings _helpPageSettings; [Obsolete("Use constructor that takes IOptions")] public HelpController(ILogger logger) - : this(logger, StaticServiceProvider.Instance.GetRequiredService>()) + : this(logger, StaticServiceProvider.Instance.GetRequiredService>()) { } [ActivatorUtilitiesConstructor] public HelpController( ILogger logger, - IOptions helpPageSettings) + IOptionsMonitor helpPageSettings) { _logger = logger; - _helpPageSettings = helpPageSettings.Value; + + ResetHelpPageSettings(helpPageSettings.CurrentValue); + helpPageSettings.OnChange(ResetHelpPageSettings); + } + + private void ResetHelpPageSettings(HelpPageSettings settings) + { + _helpPageSettings = settings; } private static HttpClient _httpClient; From c73d0bf6a6b97ecc95f3d1e85db635ee0353deb5 Mon Sep 17 00:00:00 2001 From: Mole Date: Thu, 27 Jan 2022 09:53:16 +0100 Subject: [PATCH 113/141] Update logging message in HelpController --- src/Umbraco.Web.BackOffice/Controllers/HelpController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.BackOffice/Controllers/HelpController.cs b/src/Umbraco.Web.BackOffice/Controllers/HelpController.cs index d79001d0f8..f431b1a827 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/HelpController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/HelpController.cs @@ -50,7 +50,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers { if (IsAllowedUrl(baseUrl) is false) { - _logger.LogError($"The following URL is not listed in the allowlist for HelpPage in web.config: {baseUrl}"); + _logger.LogError($"The following URL is not listed in the allowlist for HelpPage in HelpPageSettings: {baseUrl}"); HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest; // Ideally we'd want to return a BadRequestResult here, From 7971f36b78e333aa61cb94e9fc3003b70459c6ff Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Thu, 27 Jan 2022 10:32:56 +0100 Subject: [PATCH 114/141] Add check for PluginControllerAttribute and compare area name (#11911) * Add check for PluginControllerAttribute and compare area name * Added null check --- .../Runtime/RuntimeState.cs | 43 ++++++++++--------- .../Services/ConflictingRouteService.cs | 18 ++++++-- 2 files changed, 37 insertions(+), 24 deletions(-) diff --git a/src/Umbraco.Infrastructure/Runtime/RuntimeState.cs b/src/Umbraco.Infrastructure/Runtime/RuntimeState.cs index 73b6692e3a..c81041849a 100644 --- a/src/Umbraco.Infrastructure/Runtime/RuntimeState.cs +++ b/src/Umbraco.Infrastructure/Runtime/RuntimeState.cs @@ -44,27 +44,6 @@ namespace Umbraco.Cms.Infrastructure.Runtime { } - /// - /// Initializes a new instance of the class. - /// - public RuntimeState( - IOptions globalSettings, - IOptions unattendedSettings, - IUmbracoVersion umbracoVersion, - IUmbracoDatabaseFactory databaseFactory, - ILogger logger, - PendingPackageMigrations packageMigrationState) - : this( - globalSettings, - unattendedSettings, - umbracoVersion, - databaseFactory, - logger, - packageMigrationState, - StaticServiceProvider.Instance.GetRequiredService()) - { - } - public RuntimeState( IOptions globalSettings, IOptions unattendedSettings, @@ -83,6 +62,28 @@ namespace Umbraco.Cms.Infrastructure.Runtime _conflictingRouteService = conflictingRouteService; } + /// + /// Initializes a new instance of the class. + /// + [Obsolete("use ctor with all params")] + public RuntimeState( + IOptions globalSettings, + IOptions unattendedSettings, + IUmbracoVersion umbracoVersion, + IUmbracoDatabaseFactory databaseFactory, + ILogger logger, + PendingPackageMigrations packageMigrationState) + : this( + globalSettings, + unattendedSettings, + umbracoVersion, + databaseFactory, + logger, + packageMigrationState, + StaticServiceProvider.Instance.GetRequiredService()) + { + } + /// public Version Version => _umbracoVersion.Version; diff --git a/src/Umbraco.Web.BackOffice/Services/ConflictingRouteService.cs b/src/Umbraco.Web.BackOffice/Services/ConflictingRouteService.cs index 2951ace9e1..af8d0d877e 100644 --- a/src/Umbraco.Web.BackOffice/Services/ConflictingRouteService.cs +++ b/src/Umbraco.Web.BackOffice/Services/ConflictingRouteService.cs @@ -1,7 +1,9 @@ using System; using System.Linq; +using System.Reflection; using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Web.Common.Attributes; using Umbraco.Cms.Web.Common.Controllers; namespace Umbraco.Cms.Web.BackOffice.Services @@ -21,10 +23,20 @@ namespace Umbraco.Cms.Web.BackOffice.Services var controllers = _typeLoader.GetTypes().ToList(); foreach (Type controller in controllers) { - if (controllers.Count(x => x.Name == controller.Name) > 1) + var potentialConflicting = controllers.Where(x => x.Name == controller.Name).ToArray(); + if (potentialConflicting.Length > 1) { - controllerName = controller.Name; - return true; + //If we have any with same controller name and located in the same area, then it is a confict. + var conflicting = potentialConflicting + .Select(x => x.GetCustomAttribute()) + .GroupBy(x => x?.AreaName) + .Any(x => x?.Count() > 1); + + if (conflicting) + { + controllerName = controller.Name; + return true; + } } } From 1b98f8985ec45c20ed23ab7d4bd7c71d5cba0dfd Mon Sep 17 00:00:00 2001 From: Mole Date: Tue, 21 Dec 2021 07:23:08 +0100 Subject: [PATCH 115/141] Use current request for emails (#11775) * Use current request for emails * Fix tests --- src/Umbraco.Core/Sync/ApplicationUrlHelper.cs | 17 ++++++++++ .../AuthenticationControllerTests.cs | 4 ++- .../Web/Controllers/UsersControllerTests.cs | 13 ++++--- .../Editors/AuthenticationController.cs | 19 +++++++++-- src/Umbraco.Web/Editors/UsersController.cs | 34 +++++++++++++------ 5 files changed, 69 insertions(+), 18 deletions(-) diff --git a/src/Umbraco.Core/Sync/ApplicationUrlHelper.cs b/src/Umbraco.Core/Sync/ApplicationUrlHelper.cs index 52af734f1c..d934e24575 100644 --- a/src/Umbraco.Core/Sync/ApplicationUrlHelper.cs +++ b/src/Umbraco.Core/Sync/ApplicationUrlHelper.cs @@ -102,5 +102,22 @@ namespace Umbraco.Core.Sync return url.TrimEnd(Constants.CharArrays.ForwardSlash); } + + /// + /// Will get the application URL from configuration, if none is specified will fall back to URL from request. + /// + /// + /// + /// + /// + public static Uri GetApplicationUriUncached( + HttpRequestBase request, + IUmbracoSettingsSection umbracoSettingsSection) + { + var settingUrl = umbracoSettingsSection.WebRouting.UmbracoApplicationUrl; + return string.IsNullOrEmpty(settingUrl) + ? new Uri(request.Url, IOHelper.ResolveUrl(SystemDirectories.Umbraco)) + : new Uri(settingUrl); + } } } diff --git a/src/Umbraco.Tests/Web/Controllers/AuthenticationControllerTests.cs b/src/Umbraco.Tests/Web/Controllers/AuthenticationControllerTests.cs index 3d264663b5..9bd9ee73ed 100644 --- a/src/Umbraco.Tests/Web/Controllers/AuthenticationControllerTests.cs +++ b/src/Umbraco.Tests/Web/Controllers/AuthenticationControllerTests.cs @@ -16,6 +16,7 @@ using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; +using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Persistence; @@ -82,7 +83,8 @@ namespace Umbraco.Tests.Web.Controllers Factory.GetInstance(), Factory.GetInstance(), Factory.GetInstance(), - helper); + helper, + Factory.GetInstance()); return usersController; } diff --git a/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs b/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs index 85dd303432..b9289c1392 100644 --- a/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs +++ b/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs @@ -14,6 +14,7 @@ using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; +using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; @@ -84,7 +85,8 @@ namespace Umbraco.Tests.Web.Controllers Factory.GetInstance(), Factory.GetInstance(), Factory.GetInstance(), - helper); + helper, + Factory.GetInstance()); return usersController; } @@ -148,7 +150,8 @@ namespace Umbraco.Tests.Web.Controllers Factory.GetInstance(), Factory.GetInstance(), Factory.GetInstance(), - helper); + helper, + Factory.GetInstance()); return usersController; } @@ -183,7 +186,8 @@ namespace Umbraco.Tests.Web.Controllers Factory.GetInstance(), Factory.GetInstance(), Factory.GetInstance(), - helper); + helper, + Factory.GetInstance()); return usersController; } @@ -253,7 +257,8 @@ namespace Umbraco.Tests.Web.Controllers Factory.GetInstance(), Factory.GetInstance(), Factory.GetInstance(), - helper); + helper, + Factory.GetInstance()); return usersController; } diff --git a/src/Umbraco.Web/Editors/AuthenticationController.cs b/src/Umbraco.Web/Editors/AuthenticationController.cs index 3ecc6b64a4..54612377e0 100644 --- a/src/Umbraco.Web/Editors/AuthenticationController.cs +++ b/src/Umbraco.Web/Editors/AuthenticationController.cs @@ -27,6 +27,8 @@ using Umbraco.Web.Composing; using IUser = Umbraco.Core.Models.Membership.IUser; using Umbraco.Web.Editors.Filters; using Microsoft.Owin.Security; +using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.Sync; namespace Umbraco.Web.Editors { @@ -40,12 +42,23 @@ namespace Umbraco.Web.Editors [DisableBrowserCache] public class AuthenticationController : UmbracoApiController { + private readonly IUmbracoSettingsSection _umbracoSettingsSection; private BackOfficeUserManager _userManager; private BackOfficeSignInManager _signInManager; - public AuthenticationController(IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper) + public AuthenticationController( + IGlobalSettings globalSettings, + IUmbracoContextAccessor umbracoContextAccessor, + ISqlContext sqlContext, + ServiceContext services, + AppCaches appCaches, + IProfilingLogger logger, + IRuntimeState runtimeState, + UmbracoHelper umbracoHelper, + IUmbracoSettingsSection umbracoSettingsSection) : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper, Current.Mapper) { + _umbracoSettingsSection = umbracoSettingsSection; } protected BackOfficeUserManager UserManager => _userManager @@ -552,8 +565,8 @@ namespace Umbraco.Web.Editors r = code }); - // Construct full URL using configured application URL (which will fall back to request) - var applicationUri = Current.RuntimeState.ApplicationUrl; + // Construct full URL using configured application URL (which will fall back to current request) + var applicationUri = ApplicationUrlHelper.GetApplicationUriUncached(http.Request, _umbracoSettingsSection); var callbackUri = new Uri(applicationUri, action); return callbackUri.ToString(); } diff --git a/src/Umbraco.Web/Editors/UsersController.cs b/src/Umbraco.Web/Editors/UsersController.cs index dda0dfc933..4bfd72854f 100644 --- a/src/Umbraco.Web/Editors/UsersController.cs +++ b/src/Umbraco.Web/Editors/UsersController.cs @@ -17,6 +17,7 @@ using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; +using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models; @@ -27,6 +28,7 @@ using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Security; using Umbraco.Core.Services; +using Umbraco.Core.Sync; using Umbraco.Web.Editors.Filters; using Umbraco.Web.Models; using Umbraco.Web.Models.ContentEditing; @@ -46,9 +48,21 @@ namespace Umbraco.Web.Editors [IsCurrentUserModelFilter] public class UsersController : UmbracoAuthorizedJsonController { - public UsersController(IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper) + private readonly IUmbracoSettingsSection _umbracoSettingsSection; + + public UsersController( + IGlobalSettings globalSettings, + IUmbracoContextAccessor umbracoContextAccessor, + ISqlContext sqlContext, + ServiceContext services, + AppCaches appCaches, + IProfilingLogger logger, + IRuntimeState runtimeState, + UmbracoHelper umbracoHelper, + IUmbracoSettingsSection umbracoSettingsSection) : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper) { + _umbracoSettingsSection = umbracoSettingsSection; } /// @@ -390,7 +404,7 @@ namespace Umbraco.Web.Editors user = CheckUniqueEmail(userSave.Email, u => u.LastLoginDate != default || u.EmailConfirmedDate.HasValue); var userMgr = TryGetOwinContext().Result.GetBackOfficeUserManager(); - + if (!EmailSender.CanSendRequiredEmail && !userMgr.HasSendingUserInviteEventHandler) { throw new HttpResponseException( @@ -462,12 +476,12 @@ namespace Umbraco.Web.Editors Email = userSave.Email, Username = userSave.Username }; - } + } } else { //send the email - await SendUserInviteEmailAsync(display, Security.CurrentUser.Name, Security.CurrentUser.Email, user, userSave.Message); + await SendUserInviteEmailAsync(display, Security.CurrentUser.Name, Security.CurrentUser.Email, user, userSave.Message); } display.AddSuccessNotification(Services.TextService.Localize("speechBubbles", "resendInviteHeader"), Services.TextService.Localize("speechBubbles", "resendInviteSuccess", new[] { user.Name })); @@ -525,9 +539,9 @@ namespace Umbraco.Web.Editors invite = inviteToken }); - // Construct full URL using configured application URL (which will fall back to request) - var applicationUri = RuntimeState.ApplicationUrl; - var inviteUri = new Uri(applicationUri, action); + // Construct full URL will use the value in settings if specified, otherwise will use the current request URL + var requestUrl = ApplicationUrlHelper.GetApplicationUriUncached(http.Request, _umbracoSettingsSection); + var inviteUri = new Uri(requestUrl, action); var emailSubject = Services.TextService.Localize("user", "inviteEmailCopySubject", //Ensure the culture of the found user is used for the email! @@ -622,7 +636,7 @@ namespace Umbraco.Web.Editors if (Current.Configs.Settings().Security.UsernameIsEmail && found.Username == found.Email && userSave.Username != userSave.Email) { userSave.Username = userSave.Email; - } + } if (hasErrors) throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState)); @@ -647,13 +661,13 @@ namespace Umbraco.Web.Editors } /// - /// + /// /// /// /// public async Task> PostChangePassword(ChangingPasswordModel changingPasswordModel) { - changingPasswordModel = changingPasswordModel ?? throw new ArgumentNullException(nameof(changingPasswordModel)); + changingPasswordModel = changingPasswordModel ?? throw new ArgumentNullException(nameof(changingPasswordModel)); if (ModelState.IsValid == false) { From a779803763d45c079579f4216d4f516d6e8e9648 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Thu, 27 Jan 2022 13:18:21 +0100 Subject: [PATCH 116/141] Bump version to 8.17.2 --- src/SolutionInfo.cs | 4 ++-- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index a7cfdaa562..a8a04a0679 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -18,5 +18,5 @@ using System.Resources; [assembly: AssemblyVersion("8.0.0")] // these are FYI and changed automatically -[assembly: AssemblyFileVersion("8.17.1")] -[assembly: AssemblyInformationalVersion("8.17.1")] +[assembly: AssemblyFileVersion("8.17.2")] +[assembly: AssemblyInformationalVersion("8.17.2")] diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index afe9ade5f7..6114a84b2a 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -348,9 +348,9 @@ False True - 8171 + 8172 / - http://localhost:8171 + http://localhost:8172 False False @@ -433,4 +433,4 @@ - \ No newline at end of file + From c79380191bfa206d081fcd08ca9008be39a79d6c Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Thu, 27 Jan 2022 15:57:22 +0100 Subject: [PATCH 117/141] Use Umbraco Path instead of constant --- src/Umbraco.Core/Sync/ApplicationUrlHelper.cs | 21 +++++++++++++++---- .../Editors/AuthenticationController.cs | 2 +- src/Umbraco.Web/Editors/UsersController.cs | 2 +- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Core/Sync/ApplicationUrlHelper.cs b/src/Umbraco.Core/Sync/ApplicationUrlHelper.cs index d934e24575..f3cc5a6db6 100644 --- a/src/Umbraco.Core/Sync/ApplicationUrlHelper.cs +++ b/src/Umbraco.Core/Sync/ApplicationUrlHelper.cs @@ -112,12 +112,25 @@ namespace Umbraco.Core.Sync /// public static Uri GetApplicationUriUncached( HttpRequestBase request, - IUmbracoSettingsSection umbracoSettingsSection) + IUmbracoSettingsSection umbracoSettingsSection, + IGlobalSettings globalSettings) { var settingUrl = umbracoSettingsSection.WebRouting.UmbracoApplicationUrl; - return string.IsNullOrEmpty(settingUrl) - ? new Uri(request.Url, IOHelper.ResolveUrl(SystemDirectories.Umbraco)) - : new Uri(settingUrl); + + + if (string.IsNullOrEmpty(settingUrl)) + { + if (!Uri.TryCreate(request.Url, VirtualPathUtility.ToAbsolute(globalSettings.Path), out var result)) + { + throw new InvalidOperationException( + $"Could not create an url from {request.Url} and {globalSettings.Path}"); + } + return result; + } + else + { + return new Uri(settingUrl); + } } } } diff --git a/src/Umbraco.Web/Editors/AuthenticationController.cs b/src/Umbraco.Web/Editors/AuthenticationController.cs index 54612377e0..85889c5869 100644 --- a/src/Umbraco.Web/Editors/AuthenticationController.cs +++ b/src/Umbraco.Web/Editors/AuthenticationController.cs @@ -566,7 +566,7 @@ namespace Umbraco.Web.Editors }); // Construct full URL using configured application URL (which will fall back to current request) - var applicationUri = ApplicationUrlHelper.GetApplicationUriUncached(http.Request, _umbracoSettingsSection); + var applicationUri = ApplicationUrlHelper.GetApplicationUriUncached(http.Request, _umbracoSettingsSection, GlobalSettings); var callbackUri = new Uri(applicationUri, action); return callbackUri.ToString(); } diff --git a/src/Umbraco.Web/Editors/UsersController.cs b/src/Umbraco.Web/Editors/UsersController.cs index 4bfd72854f..58bb5dcb0a 100644 --- a/src/Umbraco.Web/Editors/UsersController.cs +++ b/src/Umbraco.Web/Editors/UsersController.cs @@ -540,7 +540,7 @@ namespace Umbraco.Web.Editors }); // Construct full URL will use the value in settings if specified, otherwise will use the current request URL - var requestUrl = ApplicationUrlHelper.GetApplicationUriUncached(http.Request, _umbracoSettingsSection); + var requestUrl = ApplicationUrlHelper.GetApplicationUriUncached(http.Request, _umbracoSettingsSection, GlobalSettings); var inviteUri = new Uri(requestUrl, action); var emailSubject = Services.TextService.Localize("user", "inviteEmailCopySubject", From a71529a0583fc357616494d4ce36a1961391e7e6 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Thu, 27 Jan 2022 17:37:32 +0100 Subject: [PATCH 118/141] V9/feature/merge v8 27012022 (#11915) * Add allowlist for HelpPage * Use current request for emails (#11775) * Use current request for emails * Fix tests * Bump version to 8.17.2 * Use Umbraco Path instead of constant Co-authored-by: Mole --- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 69f812b6e6..e1067f31e3 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -91,8 +91,8 @@ - - + + From c0dfb3391656589a8c85364bac97f1adfc35921a Mon Sep 17 00:00:00 2001 From: Mole Date: Fri, 28 Jan 2022 12:30:44 +0100 Subject: [PATCH 119/141] Fix importing DocType if parent folder already exists (#11885) * Don't try to create parent folder if it already exists * Fix typo Co-authored-by: Elitsa Marinovska --- .../Packaging/PackageDataInstallation.cs | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/src/Umbraco.Infrastructure/Packaging/PackageDataInstallation.cs b/src/Umbraco.Infrastructure/Packaging/PackageDataInstallation.cs index defea0ea51..790cefe7e9 100644 --- a/src/Umbraco.Infrastructure/Packaging/PackageDataInstallation.cs +++ b/src/Umbraco.Infrastructure/Packaging/PackageDataInstallation.cs @@ -575,21 +575,22 @@ namespace Umbraco.Cms.Infrastructure.Packaging { var importedFolders = new Dictionary(); var trackEntityContainersInstalled = new List(); - foreach (var documentType in unsortedDocumentTypes) + + foreach (XElement documentType in unsortedDocumentTypes) { - var foldersAttribute = documentType.Attribute("Folders"); - var infoElement = documentType.Element("Info"); + XAttribute foldersAttribute = documentType.Attribute("Folders"); + XElement infoElement = documentType.Element("Info"); if (foldersAttribute != null && infoElement != null - //don't import any folder if this is a child doc type - the parent doc type will need to - //exist which contains it's folders + // don't import any folder if this is a child doc type - the parent doc type will need to + // exist which contains it's folders && ((string)infoElement.Element("Master")).IsNullOrWhiteSpace()) { var alias = documentType.Element("Info").Element("Alias").Value; var folders = foldersAttribute.Value.Split(Constants.CharArrays.ForwardSlash); - var folderKeysAttribute = documentType.Attribute("FolderKeys"); + XAttribute folderKeysAttribute = documentType.Attribute("FolderKeys"); - var folderKeys = Array.Empty(); + Guid[] folderKeys = Array.Empty(); if (folderKeysAttribute != null) { folderKeys = folderKeysAttribute.Value.Split(Constants.CharArrays.ForwardSlash).Select(x=>Guid.Parse(x)).ToArray(); @@ -597,22 +598,22 @@ namespace Umbraco.Cms.Infrastructure.Packaging var rootFolder = WebUtility.UrlDecode(folders[0]); - EntityContainer current; + EntityContainer current = null; Guid? rootFolderKey = null; if (folderKeys.Length == folders.Length && folderKeys.Length > 0) { rootFolderKey = folderKeys[0]; current = _contentTypeService.GetContainer(rootFolderKey.Value); } - else - { - //level 1 = root level folders, there can only be one with the same name - current = _contentTypeService.GetContainers(rootFolder, 1).FirstOrDefault(); - } + + // The folder might already exist, but with a different key, so check if it exists, even if there is a key. + // Level 1 = root level folders, there can only be one with the same name + current ??= _contentTypeService.GetContainers(rootFolder, 1).FirstOrDefault(); if (current == null) { - var tryCreateFolder = _contentTypeService.CreateContainer(-1, rootFolderKey ?? Guid.NewGuid(), rootFolder); + Attempt> tryCreateFolder = _contentTypeService.CreateContainer(-1, rootFolderKey ?? Guid.NewGuid(), rootFolder); + if (tryCreateFolder == false) { _logger.LogError(tryCreateFolder.Exception, "Could not create folder: {FolderName}", rootFolder); @@ -644,7 +645,7 @@ namespace Umbraco.Cms.Infrastructure.Packaging private EntityContainer CreateContentTypeChildFolder(string folderName, Guid folderKey, IUmbracoEntity current) { var children = _entityService.GetChildren(current.Id).ToArray(); - var found = children.Any(x => x.Name.InvariantEquals(folderName) ||x.Key.Equals(folderKey)); + var found = children.Any(x => x.Name.InvariantEquals(folderName) || x.Key.Equals(folderKey)); if (found) { var containerId = children.Single(x => x.Name.InvariantEquals(folderName)).Id; From 1810cec80a2af07027c9c036c1a126089a840ab4 Mon Sep 17 00:00:00 2001 From: Paul Johnson Date: Mon, 31 Jan 2022 08:39:33 +0000 Subject: [PATCH 120/141] Simplify registration for ambiguous ctor ActivatorUtilitiesConstructor respected. --- .../DependencyInjection/UmbracoBuilderExtensions.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs index 46002a6c8d..63027a3c9e 100644 --- a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs @@ -130,10 +130,7 @@ namespace Umbraco.Extensions this IUmbracoBuilder builder) { builder.Services.TryAddTransient(sp => - { - IUserDataService userDataService = sp.GetRequiredService(); - return ActivatorUtilities.CreateInstance(sp, userDataService); - }); + ActivatorUtilities.CreateInstance(sp)); return builder; } From bdcb5d859e3a2524f8b43ade340dc9ac15e873c4 Mon Sep 17 00:00:00 2001 From: Mole Date: Mon, 31 Jan 2022 10:12:02 +0100 Subject: [PATCH 121/141] Get site name from appsettings if possible --- .../Configuration/Models/HostingSettings.cs | 7 ++++++- .../AspNetCoreHostingEnvironment.cs | 20 +++++++++++++++---- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Core/Configuration/Models/HostingSettings.cs b/src/Umbraco.Core/Configuration/Models/HostingSettings.cs index b9e11e99ca..cbe1fa6965 100644 --- a/src/Umbraco.Core/Configuration/Models/HostingSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/HostingSettings.cs @@ -22,7 +22,7 @@ namespace Umbraco.Cms.Core.Configuration.Models /// /// Gets or sets a value for the location of temporary files. /// - [DefaultValue(StaticLocalTempStorageLocation)] + [DefaultValue(StaticLocalTempStorageLocation)] public LocalTempStorage LocalTempStorageLocation { get; set; } = Enum.Parse(StaticLocalTempStorageLocation); /// @@ -31,5 +31,10 @@ namespace Umbraco.Cms.Core.Configuration.Models /// true if [debug mode]; otherwise, false. [DefaultValue(StaticDebug)] public bool Debug { get; set; } = StaticDebug; + + /// + /// Gets or sets a value specifying the name of the site. + /// + public string SiteName { get; set; } } } diff --git a/src/Umbraco.Web.Common/AspNetCore/AspNetCoreHostingEnvironment.cs b/src/Umbraco.Web.Common/AspNetCore/AspNetCoreHostingEnvironment.cs index 9e5919c1e2..13f73e1b41 100644 --- a/src/Umbraco.Web.Common/AspNetCore/AspNetCoreHostingEnvironment.cs +++ b/src/Umbraco.Web.Common/AspNetCore/AspNetCoreHostingEnvironment.cs @@ -37,7 +37,16 @@ namespace Umbraco.Cms.Web.Common.AspNetCore _webHostEnvironment = webHostEnvironment ?? throw new ArgumentNullException(nameof(webHostEnvironment)); _urlProviderMode = _webRoutingSettings.CurrentValue.UrlProviderMode; - SiteName = webHostEnvironment.ApplicationName; + SetSiteName(hostingSettings.CurrentValue.SiteName); + + // We have to ensure that the OptionsMonitor is an actual options monitor since we have a hack + // where we initially use an OptionsMonitorAdapter, which doesn't implement OnChange. + // See summery of OptionsMonitorAdapter for more information. + if (hostingSettings is OptionsMonitor) + { + hostingSettings.OnChange(settings => SetSiteName(settings.SiteName)); + } + ApplicationPhysicalPath = webHostEnvironment.ContentRootPath; if (_webRoutingSettings.CurrentValue.UmbracoApplicationUrl is not null) @@ -53,7 +62,7 @@ namespace Umbraco.Cms.Web.Common.AspNetCore public Uri ApplicationMainUrl { get; private set; } /// - public string SiteName { get; } + public string SiteName { get; private set; } /// public string ApplicationId @@ -198,7 +207,10 @@ namespace Umbraco.Cms.Web.Common.AspNetCore } } } + + private void SetSiteName(string siteName) => + SiteName = string.IsNullOrWhiteSpace(siteName) + ? _webHostEnvironment.ApplicationName + : siteName; } - - } From 22dd3a214cef510eb5764382b1e209decb1ab5aa Mon Sep 17 00:00:00 2001 From: Mole Date: Mon, 31 Jan 2022 14:39:29 +0100 Subject: [PATCH 122/141] Stop TouchServerTask is registered role accessor is not elected --- .../RecurringHostedServiceBase.cs | 3 +- .../ServerRegistration/TouchServerTask.cs | 34 ++++++++++++++++++- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs b/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs index 70dcb3a04e..b6f2b469f6 100644 --- a/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs +++ b/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs @@ -21,7 +21,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices /// protected static readonly TimeSpan DefaultDelay = TimeSpan.FromMinutes(3); - private readonly TimeSpan _period; + private TimeSpan _period; private readonly TimeSpan _delay; private Timer _timer; @@ -73,6 +73,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices /// public Task StopAsync(CancellationToken cancellationToken) { + _period = Timeout.InfiniteTimeSpan; _timer?.Change(Timeout.Infinite, 0); return Task.CompletedTask; } diff --git a/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTask.cs b/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTask.cs index 282847963f..f650ce9c94 100644 --- a/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTask.cs +++ b/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTask.cs @@ -2,13 +2,17 @@ // See LICENSE for more details. using System; +using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; +using Umbraco.Cms.Web.Common.DependencyInjection; using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.HostedServices.ServerRegistration @@ -22,6 +26,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices.ServerRegistration private readonly IServerRegistrationService _serverRegistrationService; private readonly IHostingEnvironment _hostingEnvironment; private readonly ILogger _logger; + private readonly IServerRoleAccessor _serverRoleAccessor; private readonly GlobalSettings _globalSettings; /// @@ -37,7 +42,8 @@ namespace Umbraco.Cms.Infrastructure.HostedServices.ServerRegistration IServerRegistrationService serverRegistrationService, IHostingEnvironment hostingEnvironment, ILogger logger, - IOptions globalSettings) + IOptions globalSettings, + IServerRoleAccessor serverRoleAccessor) : base(globalSettings.Value.DatabaseServerRegistrar.WaitTimeBetweenCalls, TimeSpan.FromSeconds(15)) { _runtimeState = runtimeState; @@ -45,6 +51,24 @@ namespace Umbraco.Cms.Infrastructure.HostedServices.ServerRegistration _hostingEnvironment = hostingEnvironment; _logger = logger; _globalSettings = globalSettings.Value; + _serverRoleAccessor = serverRoleAccessor; + } + + [Obsolete("Use constructor that takes an IServerRoleAccessor")] + public TouchServerTask( + IRuntimeState runtimeState, + IServerRegistrationService serverRegistrationService, + IHostingEnvironment hostingEnvironment, + ILogger logger, + IOptions globalSettings) + : this( + runtimeState, + serverRegistrationService, + hostingEnvironment, + logger, + globalSettings, + StaticServiceProvider.Instance.GetRequiredService()) + { } public override Task PerformExecuteAsync(object state) @@ -54,6 +78,14 @@ namespace Umbraco.Cms.Infrastructure.HostedServices.ServerRegistration return Task.CompletedTask; } + // If we're the IServerRoleAccessor has been changed away from ElectedServerRoleAccessor + // this task no longer makes sense, since all it's used for is to allow the ElectedServerRoleAccessor + // to figure out what role a given server has, so we just stop this task. + if (_serverRoleAccessor is not ElectedServerRoleAccessor) + { + return StopAsync(CancellationToken.None); + } + var serverAddress = _hostingEnvironment.ApplicationMainUrl?.ToString(); if (serverAddress.IsNullOrWhiteSpace()) { From febdac5713fe7c648bd7824e6614d7956163c60b Mon Sep 17 00:00:00 2001 From: Mole Date: Mon, 31 Jan 2022 14:46:25 +0100 Subject: [PATCH 123/141] Fix tests --- .../ServerRegistration/TouchServerTaskTests.cs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTaskTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTaskTests.cs index 29b011a5a6..c690f35b7a 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTaskTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTaskTests.cs @@ -11,6 +11,7 @@ using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; using Umbraco.Cms.Infrastructure.HostedServices.ServerRegistration; namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.HostedServices.ServerRegistration @@ -51,7 +52,15 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.HostedServices.Serv VerifyServerTouched(); } - private TouchServerTask CreateTouchServerTask(RuntimeLevel runtimeLevel = RuntimeLevel.Run, string applicationUrl = ApplicationUrl) + [Test] + public async Task Does_Not_Execute_When_Role_Accessor_Is_Not_Elected() + { + TouchServerTask sut = CreateTouchServerTask(useElection: false); + await sut.PerformExecuteAsync(null); + VerifyServerNotTouched(); + } + + private TouchServerTask CreateTouchServerTask(RuntimeLevel runtimeLevel = RuntimeLevel.Run, string applicationUrl = ApplicationUrl, bool useElection = true) { var mockRequestAccessor = new Mock(); mockRequestAccessor.SetupGet(x => x.ApplicationMainUrl).Returns(!string.IsNullOrEmpty(applicationUrl) ? new Uri(ApplicationUrl) : null); @@ -71,12 +80,17 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.HostedServices.Serv } }; + IServerRoleAccessor roleAccessor = useElection + ? new ElectedServerRoleAccessor(_mockServerRegistrationService.Object) + : new SingleServerRoleAccessor(); + return new TouchServerTask( mockRunTimeState.Object, _mockServerRegistrationService.Object, mockRequestAccessor.Object, mockLogger.Object, - Options.Create(settings)); + Options.Create(settings), + roleAccessor); } private void VerifyServerNotTouched() => VerifyServerTouchedTimes(Times.Never()); From 824bc2ac285651999492378a3c04890d6ca823f8 Mon Sep 17 00:00:00 2001 From: Mole Date: Mon, 31 Jan 2022 14:58:45 +0100 Subject: [PATCH 124/141] Fix word salad in comment --- .../HostedServices/ServerRegistration/TouchServerTask.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTask.cs b/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTask.cs index f650ce9c94..d54d67338e 100644 --- a/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTask.cs +++ b/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTask.cs @@ -78,8 +78,8 @@ namespace Umbraco.Cms.Infrastructure.HostedServices.ServerRegistration return Task.CompletedTask; } - // If we're the IServerRoleAccessor has been changed away from ElectedServerRoleAccessor - // this task no longer makes sense, since all it's used for is to allow the ElectedServerRoleAccessor + // If the IServerRoleAccessor has been changed away from ElectedServerRoleAccessor this task no longer makes sense, + // since all it's used for is to allow the ElectedServerRoleAccessor // to figure out what role a given server has, so we just stop this task. if (_serverRoleAccessor is not ElectedServerRoleAccessor) { From 3af6645ad89a8b541e81284deab9bae765c4b73e Mon Sep 17 00:00:00 2001 From: Mole Date: Mon, 31 Jan 2022 15:02:25 +0100 Subject: [PATCH 125/141] Fix URL with culture (#11886) * Ignore casing when comparing default culture * Ignore casing in GetAssignedWithCulture Co-authored-by: Bjarke Berg --- src/Umbraco.Core/Routing/DefaultUrlProvider.cs | 13 +++++++++---- .../DomainCacheExtensions.cs | 4 +++- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Core/Routing/DefaultUrlProvider.cs b/src/Umbraco.Core/Routing/DefaultUrlProvider.cs index ae2c3d7f3a..5c27760b2a 100644 --- a/src/Umbraco.Core/Routing/DefaultUrlProvider.cs +++ b/src/Umbraco.Core/Routing/DefaultUrlProvider.cs @@ -134,8 +134,13 @@ namespace Umbraco.Cms.Core.Routing return GetUrlFromRoute(route, umbracoContext, content.Id, current, mode, culture); } - internal UrlInfo GetUrlFromRoute(string route, IUmbracoContext umbracoContext, int id, Uri current, - UrlMode mode, string culture) + internal UrlInfo GetUrlFromRoute( + string route, + IUmbracoContext umbracoContext, + int id, + Uri current, + UrlMode mode, + string culture) { if (string.IsNullOrWhiteSpace(route)) { @@ -149,12 +154,12 @@ namespace Umbraco.Cms.Core.Routing // route is / or / var pos = route.IndexOf('/'); var path = pos == 0 ? route : route.Substring(pos); - var domainUri = pos == 0 + DomainAndUri domainUri = pos == 0 ? null : DomainUtilities.DomainForNode(umbracoContext.PublishedSnapshot.Domains, _siteDomainMapper, int.Parse(route.Substring(0, pos), CultureInfo.InvariantCulture), current, culture); var defaultCulture = _localizationService.GetDefaultLanguageIsoCode(); - if (domainUri is not null || culture == defaultCulture || string.IsNullOrEmpty(culture)) + if (domainUri is not null || culture is null || culture.Equals(defaultCulture, StringComparison.InvariantCultureIgnoreCase)) { var url = AssembleUrl(domainUri, path, current, mode).ToString(); return UrlInfo.Url(url, culture); diff --git a/src/Umbraco.PublishedCache.NuCache/DomainCacheExtensions.cs b/src/Umbraco.PublishedCache.NuCache/DomainCacheExtensions.cs index 61f10917fd..47cc427217 100644 --- a/src/Umbraco.PublishedCache.NuCache/DomainCacheExtensions.cs +++ b/src/Umbraco.PublishedCache.NuCache/DomainCacheExtensions.cs @@ -1,3 +1,4 @@ +using System; using System.Linq; using Umbraco.Cms.Core.PublishedCache; @@ -9,7 +10,8 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache { var assigned = domainCache.GetAssigned(documentId, includeWildcards); - return culture is null ? assigned.Any() : assigned.Any(x => x.Culture == culture); + // It's super important that we always compare cultures with ignore case, since we can't be sure of the casing! + return culture is null ? assigned.Any() : assigned.Any(x => x.Culture.Equals(culture, StringComparison.InvariantCultureIgnoreCase)); } } } From 158f4d29f6e7854e596d434078eae765d884a0b1 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Fri, 4 Feb 2022 13:02:19 +0100 Subject: [PATCH 126/141] Bump version to 9.4.0-rc --- .../UmbracoPackage/.template.config/template.json | 2 +- .../UmbracoProject/.template.config/template.json | 2 +- src/Directory.Build.props | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/build/templates/UmbracoPackage/.template.config/template.json b/build/templates/UmbracoPackage/.template.config/template.json index 1a4dd16fd7..082f9301bf 100644 --- a/build/templates/UmbracoPackage/.template.config/template.json +++ b/build/templates/UmbracoPackage/.template.config/template.json @@ -24,7 +24,7 @@ "version": { "type": "parameter", "datatype": "string", - "defaultValue": "9.3.0-rc", + "defaultValue": "9.4.0-rc", "description": "The version of Umbraco to load using NuGet", "replaces": "UMBRACO_VERSION_FROM_TEMPLATE" }, diff --git a/build/templates/UmbracoProject/.template.config/template.json b/build/templates/UmbracoProject/.template.config/template.json index fd41de8d1c..810940c4eb 100644 --- a/build/templates/UmbracoProject/.template.config/template.json +++ b/build/templates/UmbracoProject/.template.config/template.json @@ -57,7 +57,7 @@ "version": { "type": "parameter", "datatype": "string", - "defaultValue": "9.3.0-rc", + "defaultValue": "9.4.0-rc", "description": "The version of Umbraco to load using NuGet", "replaces": "UMBRACO_VERSION_FROM_TEMPLATE" }, diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 995c8afebd..68962caef4 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -3,10 +3,10 @@ - 9.3.0 - 9.3.0 - 9.3.0-rc - 9.3.0 + 9.4.0 + 9.4.0 + 9.4.0-rc + 9.4.0 9.0 en-US Umbraco CMS From 58b75c58aa2116a53760ef090cddbaab55cb180f Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Mon, 7 Feb 2022 09:21:26 +0100 Subject: [PATCH 127/141] Fixes issue with miniprofiler losing the information when doing a redirect, e.g. in a successful surfacecontroller call. (#11939) Now we store the profiler in a cookie on local redirects and pick it up on next request and add it as a child to that profiler --- .../Profiler/WebProfiler.cs | 45 ++++++++++++++++--- 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web.Common/Profiler/WebProfiler.cs b/src/Umbraco.Web.Common/Profiler/WebProfiler.cs index 7c5a89fa71..688414b3de 100644 --- a/src/Umbraco.Web.Common/Profiler/WebProfiler.cs +++ b/src/Umbraco.Web.Common/Profiler/WebProfiler.cs @@ -1,11 +1,16 @@ using System; using System.Linq; +using System.Net; using System.Threading; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.Extensions.DependencyInjection; using StackExchange.Profiling; +using StackExchange.Profiling.Internal; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Logging; using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Web; using Umbraco.Extensions; namespace Umbraco.Cms.Web.Common.Profiler @@ -13,6 +18,8 @@ namespace Umbraco.Cms.Web.Common.Profiler public class WebProfiler : IProfiler { + private const string WebProfileCookieKey = "umbracoWebProfiler"; + public static readonly AsyncLocal MiniProfilerContext = new AsyncLocal(x => { _ = x; @@ -39,7 +46,6 @@ namespace Umbraco.Cms.Web.Common.Profiler public void Stop(bool discardResults = false) => MiniProfilerContext.Value?.Stop(discardResults); - public void UmbracoApplicationBeginRequest(HttpContext context, RuntimeLevel runtimeLevel) { if (runtimeLevel != RuntimeLevel.Run) @@ -50,9 +56,13 @@ namespace Umbraco.Cms.Web.Common.Profiler if (ShouldProfile(context.Request)) { Start(); + ICookieManager cookieManager = GetCookieManager(context); + cookieManager.ExpireCookie(WebProfileCookieKey); //Ensure we expire the cookie, so we do not reuse the old potential value saved } } + private static ICookieManager GetCookieManager(HttpContext context) => context.RequestServices.GetRequiredService(); + public void UmbracoApplicationEndRequest(HttpContext context, RuntimeLevel runtimeLevel) { if (runtimeLevel != RuntimeLevel.Run) @@ -70,19 +80,42 @@ namespace Umbraco.Cms.Web.Common.Profiler var first = Interlocked.Exchange(ref _first, 1) == 0; if (first) { - - var startupDuration = _startupProfiler.Root.DurationMilliseconds.GetValueOrDefault(); - MiniProfilerContext.Value.DurationMilliseconds += startupDuration; - MiniProfilerContext.Value.GetTimingHierarchy().First().DurationMilliseconds += startupDuration; - MiniProfilerContext.Value.Root.AddChild(_startupProfiler.Root); + AddSubProfiler(_startupProfiler); _startupProfiler = null; } + + ICookieManager cookieManager = GetCookieManager(context); + var cookieValue = cookieManager.GetCookieValue(WebProfileCookieKey); + + if (cookieValue is not null) + { + AddSubProfiler(MiniProfiler.FromJson(cookieValue)); + } + + //If it is a redirect to a relative path (local redirect) + if (context.Response.StatusCode == (int)HttpStatusCode.Redirect + && context.Response.Headers.TryGetValue(Microsoft.Net.Http.Headers.HeaderNames.Location, out var location) + && !location.Contains("://")) + { + MiniProfilerContext.Value.Root.Name = "Before Redirect"; + cookieManager.SetCookieValue(WebProfileCookieKey, MiniProfilerContext.Value.ToJson()); + } + } } } + private void AddSubProfiler(MiniProfiler subProfiler) + { + var startupDuration = subProfiler.Root.DurationMilliseconds.GetValueOrDefault(); + MiniProfilerContext.Value.DurationMilliseconds += startupDuration; + MiniProfilerContext.Value.GetTimingHierarchy().First().DurationMilliseconds += startupDuration; + MiniProfilerContext.Value.Root.AddChild(subProfiler.Root); + + } + private static bool ShouldProfile(HttpRequest request) { if (request.IsClientSideRequest()) return false; From d7fef7cd0cbfe824220b27f3bed0f07bdead64fa Mon Sep 17 00:00:00 2001 From: Mole Date: Mon, 7 Feb 2022 12:09:13 +0100 Subject: [PATCH 128/141] Check blockObject.content for null --- .../blockeditormodelobject.service.js | 34 ++++++++++++++----- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/blockeditormodelobject.service.js b/src/Umbraco.Web.UI.Client/src/common/services/blockeditormodelobject.service.js index 22baed8472..09c1659775 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/blockeditormodelobject.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/blockeditormodelobject.service.js @@ -101,11 +101,30 @@ /** * Generate label for Block, uses either the labelInterpolator or falls back to the contentTypeName. - * @param {Object} blockObject BlockObject to recive data values from. + * @param {Object} blockObject BlockObject to receive data values from. */ function getBlockLabel(blockObject) { if (blockObject.labelInterpolator !== undefined) { - var labelVars = Object.assign({"$contentTypeName": blockObject.content.contentTypeName, "$settings": blockObject.settingsData || {}, "$layout": blockObject.layout || {}, "$index": (blockObject.index || 0)+1 }, blockObject.data); + // blockobject.content may be null if the block is no longer allowed, + // so try and fall back to the label in the config, + // if that too is null, there's not much we can do, so just default to empty string. + var contentTypeName; + if(blockObject.content != null){ + contentTypeName = blockObject.content.contentTypeName; + } + else if(blockObject.config != null && blockObject.config.label != null){ + contentTypeName = blockObject.config.label; + } + else { + contentTypeName = ""; + } + + var labelVars = Object.assign({ + "$contentTypeName": contentTypeName, + "$settings": blockObject.settingsData || {}, + "$layout": blockObject.layout || {}, + "$index": (blockObject.index || 0)+1 + }, blockObject.data); var label = blockObject.labelInterpolator(labelVars); if (label) { return label; @@ -511,10 +530,10 @@ * @methodOf umbraco.services.blockEditorModelObject * @description Retrieve a Block Object for the given layout entry. * The Block Object offers the necessary data to display and edit a block. - * The Block Object setups live syncronization of content and settings models back to the data of your Property Editor model. - * The returned object, named ´BlockObject´, contains several usefull models to make editing of this block happen. + * The Block Object setups live synchronization of content and settings models back to the data of your Property Editor model. + * The returned object, named ´BlockObject´, contains several useful models to make editing of this block happen. * The ´BlockObject´ contains the following properties: - * - key {string}: runtime generated key, usefull for tracking of this object + * - key {string}: runtime generated key, useful for tracking of this object * - content {Object}: Content model, the content data in a ElementType model. * - settings {Object}: Settings model, the settings data in a ElementType model. * - config {Object}: A local deep copy of the block configuration model. @@ -522,12 +541,11 @@ * - updateLabel {Method}: Method to trigger an update of the label for this block. * - data {Object}: A reference to the content data object from your property editor model. * - settingsData {Object}: A reference to the settings data object from your property editor model. - * - layout {Object}: A refernce to the layout entry from your property editor model. + * - layout {Object}: A reference to the layout entry from your property editor model. * @param {Object} layoutEntry the layout entry object to build the block model from. - * @return {Object | null} The BlockObject for the given layout entry. Or null if data or configuration wasnt found for this block. + * @return {Object | null} The BlockObject for the given layout entry. Or null if data or configuration wasn't found for this block. */ getBlockObject: function (layoutEntry) { - var contentUdi = layoutEntry.contentUdi; var dataModel = getDataByUdi(contentUdi, this.value.contentData); From 23803a44b7c32c446decceff3794c4bb7d60f327 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Mon, 7 Feb 2022 14:32:58 +0100 Subject: [PATCH 129/141] Fixed minor issues and added xml docs (#11943) --- .../Services/ITwoFactorLoginService.cs | 41 ++++++++++++++-- .../Implement/TwoFactorLoginService.cs | 49 ++++++++++++++++--- .../Security/TwoFactorValidationProvider.cs | 2 +- 3 files changed, 79 insertions(+), 13 deletions(-) diff --git a/src/Umbraco.Core/Services/ITwoFactorLoginService.cs b/src/Umbraco.Core/Services/ITwoFactorLoginService.cs index dd11f864fb..33a96ad751 100644 --- a/src/Umbraco.Core/Services/ITwoFactorLoginService.cs +++ b/src/Umbraco.Core/Services/ITwoFactorLoginService.cs @@ -5,24 +5,57 @@ using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Core.Services { + /// + /// Service handling 2FA logins. + /// public interface ITwoFactorLoginService : IService { /// - /// Deletes all user logins - normally used when a member is deleted + /// Deletes all user logins - normally used when a member is deleted. /// Task DeleteUserLoginsAsync(Guid userOrMemberKey); - Task IsTwoFactorEnabledAsync(Guid userKey); - Task GetSecretForUserAndProviderAsync(Guid userKey, string providerName); + /// + /// Checks whether 2FA is enabled for the user or member with the specified key. + /// + Task IsTwoFactorEnabledAsync(Guid userOrMemberKey); + /// + /// Gets the secret for user or member and a specific provider. + /// + Task GetSecretForUserAndProviderAsync(Guid userOrMemberKey, string providerName); + + /// + /// Gets the setup info for a specific user or member and a specific provider. + /// + /// + /// The returned type can be anything depending on the setup providers. You will need to cast it to the type handled by the provider. + /// Task GetSetupInfoAsync(Guid userOrMemberKey, string providerName); + /// + /// Gets all registered providers names. + /// IEnumerable GetAllProviderNames(); + + /// + /// Disables the 2FA provider with the specified provider name for the specified user or member. + /// Task DisableAsync(Guid userOrMemberKey, string providerName); + /// + /// Validates the setup of the provider using the secret and code. + /// bool ValidateTwoFactorSetup(string providerName, string secret, string code); + + /// + /// Saves the 2FA login information. + /// Task SaveAsync(TwoFactorLogin twoFactorLogin); + + /// + /// Gets all the enabled 2FA providers for the user or member with the specified key. + /// Task> GetEnabledTwoFactorProviderNamesAsync(Guid userOrMemberKey); } - } diff --git a/src/Umbraco.Infrastructure/Services/Implement/TwoFactorLoginService.cs b/src/Umbraco.Infrastructure/Services/Implement/TwoFactorLoginService.cs index 713a73c1df..cdcc6b19e9 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/TwoFactorLoginService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/TwoFactorLoginService.cs @@ -11,31 +11,41 @@ using Umbraco.Cms.Core.Security; namespace Umbraco.Cms.Core.Services { + /// public class TwoFactorLoginService : ITwoFactorLoginService { private readonly ITwoFactorLoginRepository _twoFactorLoginRepository; private readonly IScopeProvider _scopeProvider; private readonly IOptions _identityOptions; + private readonly IOptions _backOfficeIdentityOptions; private readonly IDictionary _twoFactorSetupGenerators; + /// + /// Initializes a new instance of the class. + /// public TwoFactorLoginService( ITwoFactorLoginRepository twoFactorLoginRepository, IScopeProvider scopeProvider, IEnumerable twoFactorSetupGenerators, - IOptions identityOptions) + IOptions identityOptions, + IOptions backOfficeIdentityOptions + ) { _twoFactorLoginRepository = twoFactorLoginRepository; _scopeProvider = scopeProvider; _identityOptions = identityOptions; - _twoFactorSetupGenerators = twoFactorSetupGenerators.ToDictionary(x=>x.ProviderName); + _backOfficeIdentityOptions = backOfficeIdentityOptions; + _twoFactorSetupGenerators = twoFactorSetupGenerators.ToDictionary(x =>x.ProviderName); } + /// public async Task DeleteUserLoginsAsync(Guid userOrMemberKey) { using IScope scope = _scopeProvider.CreateScope(autoComplete: true); await _twoFactorLoginRepository.DeleteUserLoginsAsync(userOrMemberKey); } + /// public async Task> GetEnabledTwoFactorProviderNamesAsync(Guid userOrMemberKey) { return await GetEnabledProviderNamesAsync(userOrMemberKey); @@ -47,26 +57,46 @@ namespace Umbraco.Cms.Core.Services var providersOnUser = (await _twoFactorLoginRepository.GetByUserOrMemberKeyAsync(userOrMemberKey)) .Select(x => x.ProviderName).ToArray(); - return providersOnUser.Where(x => _identityOptions.Value.Tokens.ProviderMap.ContainsKey(x)); + return providersOnUser.Where(IsKnownProviderName); } + /// + /// The provider needs to be registered as either a member provider or backoffice provider to show up. + /// + private bool IsKnownProviderName(string providerName) + { + if (_identityOptions.Value.Tokens.ProviderMap.ContainsKey(providerName)) + { + return true; + } + if (_backOfficeIdentityOptions.Value.Tokens.ProviderMap.ContainsKey(providerName)) + { + return true; + } + + return false; + } + + /// public async Task IsTwoFactorEnabledAsync(Guid userOrMemberKey) { return (await GetEnabledProviderNamesAsync(userOrMemberKey)).Any(); } + /// public async Task GetSecretForUserAndProviderAsync(Guid userOrMemberKey, string providerName) { using IScope scope = _scopeProvider.CreateScope(autoComplete: true); - return (await _twoFactorLoginRepository.GetByUserOrMemberKeyAsync(userOrMemberKey)).FirstOrDefault(x=>x.ProviderName == providerName)?.Secret; + return (await _twoFactorLoginRepository.GetByUserOrMemberKeyAsync(userOrMemberKey)).FirstOrDefault(x => x.ProviderName == providerName)?.Secret; } + /// public async Task GetSetupInfoAsync(Guid userOrMemberKey, string providerName) { var secret = await GetSecretForUserAndProviderAsync(userOrMemberKey, providerName); - //Dont allow to generate a new secrets if user already has one + // Dont allow to generate a new secrets if user already has one if (!string.IsNullOrEmpty(secret)) { return default; @@ -82,14 +112,17 @@ namespace Umbraco.Cms.Core.Services return await generator.GetSetupDataAsync(userOrMemberKey, secret); } + /// public IEnumerable GetAllProviderNames() => _twoFactorSetupGenerators.Keys; + + /// public async Task DisableAsync(Guid userOrMemberKey, string providerName) { using IScope scope = _scopeProvider.CreateScope(autoComplete: true); - return (await _twoFactorLoginRepository.DeleteUserLoginsAsync(userOrMemberKey, providerName)); - + return await _twoFactorLoginRepository.DeleteUserLoginsAsync(userOrMemberKey, providerName); } + /// public bool ValidateTwoFactorSetup(string providerName, string secret, string code) { if (!_twoFactorSetupGenerators.TryGetValue(providerName, out ITwoFactorProvider generator)) @@ -100,6 +133,7 @@ namespace Umbraco.Cms.Core.Services return generator.ValidateTwoFactorSetup(secret, code); } + /// public Task SaveAsync(TwoFactorLogin twoFactorLogin) { using IScope scope = _scopeProvider.CreateScope(autoComplete: true); @@ -108,7 +142,6 @@ namespace Umbraco.Cms.Core.Services return Task.CompletedTask; } - /// /// Generates a new random unique secret. /// diff --git a/src/Umbraco.Web.Common/Security/TwoFactorValidationProvider.cs b/src/Umbraco.Web.Common/Security/TwoFactorValidationProvider.cs index 32b3226440..d4272515e5 100644 --- a/src/Umbraco.Web.Common/Security/TwoFactorValidationProvider.cs +++ b/src/Umbraco.Web.Common/Security/TwoFactorValidationProvider.cs @@ -15,7 +15,7 @@ namespace Umbraco.Cms.Infrastructure.Security public class TwoFactorBackOfficeValidationProvider : TwoFactorValidationProvider where TTwoFactorSetupGenerator : ITwoFactorProvider { - protected TwoFactorBackOfficeValidationProvider(IDataProtectionProvider dataProtectionProvider, IOptions options, ILogger> logger, ITwoFactorLoginService twoFactorLoginService, TTwoFactorSetupGenerator generator) : base(dataProtectionProvider, options, logger, twoFactorLoginService, generator) + public TwoFactorBackOfficeValidationProvider(IDataProtectionProvider dataProtectionProvider, IOptions options, ILogger> logger, ITwoFactorLoginService twoFactorLoginService, TTwoFactorSetupGenerator generator) : base(dataProtectionProvider, options, logger, twoFactorLoginService, generator) { } From b89cd86d850c2cc3e817b0ee78d9b190fd19f11f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 8 Feb 2022 10:35:06 +0100 Subject: [PATCH 130/141] Apply the Umbraco logo to BackOffice (#11949) * adding logo as text in replacement of umbraco_logo_white, this will enable existing configuration to continue to work and minimise the breaking change of this PR. * adjust logo position to fit with logged-in logomark. Allowing for the logo and customised logo to be very wide. * adding logomark in topbar of backoffice * login box style * correction of shadow * Logo modal, to display more information about the product including linking to the website * rename to modal * stop hidden when mouse is out * Version line without Umbraco * focus link and use blur as the indication for closing. * correcting to rgba * focus and click outside needs a little help to work well * use @zindexUmbOverlay to ensure right depth going forward. * adding large logo svg * append ; * tidy logo svg file --- .../application/umbraco_logo_large_blue.svg | 41 +++++ .../img/application/umbraco_logo_white.svg | 44 ++++- .../application/umbraco_logomark_white.svg | 3 + .../application/umbappheader.directive.js | 33 +++- .../application/umb-app-header.less | 55 +++++- .../src/less/pages/login.less | 13 +- .../application/umb-app-header.html | 100 +++++++++-- .../src/views/errors/BootFailed.html | 160 +++++++++--------- 8 files changed, 343 insertions(+), 106 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/assets/img/application/umbraco_logo_large_blue.svg create mode 100644 src/Umbraco.Web.UI.Client/src/assets/img/application/umbraco_logomark_white.svg diff --git a/src/Umbraco.Web.UI.Client/src/assets/img/application/umbraco_logo_large_blue.svg b/src/Umbraco.Web.UI.Client/src/assets/img/application/umbraco_logo_large_blue.svg new file mode 100644 index 0000000000..ce15dd3092 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/img/application/umbraco_logo_large_blue.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + diff --git a/src/Umbraco.Web.UI.Client/src/assets/img/application/umbraco_logo_white.svg b/src/Umbraco.Web.UI.Client/src/assets/img/application/umbraco_logo_white.svg index b27ae89e91..c0bdbdd40c 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/img/application/umbraco_logo_white.svg +++ b/src/Umbraco.Web.UI.Client/src/assets/img/application/umbraco_logo_white.svg @@ -1,3 +1,41 @@ - - - + + + + + + + + + + + + diff --git a/src/Umbraco.Web.UI.Client/src/assets/img/application/umbraco_logomark_white.svg b/src/Umbraco.Web.UI.Client/src/assets/img/application/umbraco_logomark_white.svg new file mode 100644 index 0000000000..b27ae89e91 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/img/application/umbraco_logomark_white.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbappheader.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbappheader.directive.js index b52b0a5763..6cf6dd85f3 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbappheader.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbappheader.directive.js @@ -1,9 +1,9 @@ (function () { "use strict"; - function AppHeaderDirective(eventsService, appState, userService, focusService, backdropService, overlayService) { + function AppHeaderDirective(eventsService, appState, userService, focusService, overlayService, $timeout) { - function link(scope, el, attr, ctrl) { + function link(scope, element) { var evts = []; @@ -84,6 +84,35 @@ overlayService.open(dialog); }; + scope.logoModal = { + show: false, + text: "", + timer: null + }; + scope.showLogoModal = function() { + $timeout.cancel(scope.logoModal.timer); + scope.logoModal.show = true; + scope.logoModal.text = "version "+Umbraco.Sys.ServerVariables.application.version; + $timeout(function () { + const anchorLink = element[0].querySelector('.umb-app-header__logo-modal'); + if(anchorLink) { + anchorLink.focus(); + } + }); + }; + scope.keepLogoModal = function() { + $timeout.cancel(scope.logoModal.timer); + }; + scope.hideLogoModal = function() { + $timeout.cancel(scope.logoModal.timer); + scope.logoModal.timer = $timeout(function () { + scope.logoModal.show = false; + }, 100); + }; + scope.stopClickEvent = function($event) { + $event.stopPropagation(); + }; + } var directive = { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-app-header.less b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-app-header.less index 68a29df89e..bb346fc402 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-app-header.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-app-header.less @@ -2,12 +2,65 @@ background: @blueExtraDark; display: flex; align-items: center; - justify-content: space-between; max-width: 100%; height: @appHeaderHeight; padding: 0 20px; } +.umb-app-header__logo { + margin-right: 30px; + button { + img { + height: 30px; + } + } +} + +.umb-app-header__logo-modal { + position: absolute; + z-index: @zindexUmbOverlay; + top: 50px; + left: 17px; + font-size: 13px; + + border-radius: 6px; + + width: 160px; + padding: 20px 20px; + background-color:@white; + color: @blueExtraDark; + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, .14), 0 1px 6px 1px rgba(0, 0, 0, .14); + text-decoration: none; + + text-align: center; + + &::before { + content:''; + position: absolute; + transform: rotate(45deg); + background-color:@white; + top: -4px; + left: 14px; + width: 8px; + height: 8px; + } + + img { + display: block; + height: auto; + width: 120px; + margin-left: auto; + margin-right: auto; + margin-bottom: 3px; + } +} + +.umb-app-header__right { + display: flex; + align-items: center; + margin-left: auto; +} + .umb-app-header__actions { display: flex; list-style: none; diff --git a/src/Umbraco.Web.UI.Client/src/less/pages/login.less b/src/Umbraco.Web.UI.Client/src/less/pages/login.less index 2763a879ea..015c291564 100644 --- a/src/Umbraco.Web.UI.Client/src/less/pages/login.less +++ b/src/Umbraco.Web.UI.Client/src/less/pages/login.less @@ -28,14 +28,14 @@ .login-overlay__logo { position: absolute; - top: 22px; - left: 25px; + top: 12.5px; + left: 20px; + right: 25px; height: 30px; z-index: 1; } - -.login-overlay__logo > img { - max-height:100%; +.login-overlay__logo img { + height: 100%; } .login-overlay .umb-modalcolumn { @@ -69,7 +69,8 @@ margin-right: 25px; margin-top: auto; margin-bottom: auto; - border-radius: @baseBorderRadius; + border-radius: @doubleBorderRadius; + box-shadow: 0 1px 6px 1px rgba(0, 0, 0, 0.12); } .login-overlay .form input[type="text"], diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-app-header.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-app-header.html index e0fb4aeb77..98b8d88869 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-app-header.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-app-header.html @@ -1,34 +1,99 @@ -
-
- - + -
+ + +
  • -
  • -
  • -
-
-
diff --git a/src/Umbraco.Web.UI.Client/src/views/errors/BootFailed.html b/src/Umbraco.Web.UI.Client/src/views/errors/BootFailed.html index c08627739a..7b91125e09 100644 --- a/src/Umbraco.Web.UI.Client/src/views/errors/BootFailed.html +++ b/src/Umbraco.Web.UI.Client/src/views/errors/BootFailed.html @@ -1,79 +1,87 @@ - - - - Boot Failed - - - -
- -
-

Boot Failed

-

Umbraco failed to boot, if you are the owner of the website please see the log file for more details.

-
-
- + + + + Boot Failed + + + +
+ +
+

Boot Failed

+

+ Umbraco failed to boot, if you are the owner of the website + please see the log file for more details. +

+
+
+ From 3d28552a77f110fe9e69ee85fbfa89914cb154a8 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 9 Feb 2022 10:19:39 +0100 Subject: [PATCH 131/141] Add settings to bypass 2fa for external logins (#11959) * Added settings for bypassing 2fa for external logins * Fixed issue with saving roles using member ID before the member had an ID. * Added missing extension method * Removed test classes from git * rollback csproj --- .../Configuration/Models/SecuritySettings.cs | 14 +++++ .../Security/MemberUserStore.cs | 3 + .../Controllers/BackOfficeController.cs | 55 ++++++++++++++++++- .../UmbracoBuilder.BackOfficeIdentity.cs | 10 ++++ src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 1 + .../Controllers/UmbExternalLoginController.cs | 9 ++- 6 files changed, 88 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Core/Configuration/Models/SecuritySettings.cs b/src/Umbraco.Core/Configuration/Models/SecuritySettings.cs index 7d4dd45fb8..982ba8c63e 100644 --- a/src/Umbraco.Core/Configuration/Models/SecuritySettings.cs +++ b/src/Umbraco.Core/Configuration/Models/SecuritySettings.cs @@ -11,6 +11,8 @@ namespace Umbraco.Cms.Core.Configuration.Models [UmbracoOptions(Constants.Configuration.ConfigSecurity)] public class SecuritySettings { + internal const bool StaticMemberBypassTwoFactorForExternalLogins = true; + internal const bool StaticUserBypassTwoFactorForExternalLogins = true; internal const bool StaticKeepUserLoggedIn = false; internal const bool StaticHideDisabledUsersInBackOffice = false; internal const bool StaticAllowPasswordReset = true; @@ -66,5 +68,17 @@ namespace Umbraco.Cms.Core.Configuration.Models /// Gets or sets a value for the member password settings. /// public MemberPasswordConfigurationSettings MemberPassword { get; set; } + + /// + /// Gets or sets a value indicating whether to bypass the two factor requirement in Umbraco when using external login for members. Thereby rely on the External login and potential 2FA at that provider. + /// + [DefaultValue(StaticMemberBypassTwoFactorForExternalLogins)] + public bool MemberBypassTwoFactorForExternalLogins { get; set; } = StaticMemberBypassTwoFactorForExternalLogins; + + /// + /// Gets or sets a value indicating whether to bypass the two factor requirement in Umbraco when using external login for users. Thereby rely on the External login and potential 2FA at that provider. + /// + [DefaultValue(StaticUserBypassTwoFactorForExternalLogins)] + public bool UserBypassTwoFactorForExternalLogins { get; set; } = StaticUserBypassTwoFactorForExternalLogins; } } diff --git a/src/Umbraco.Infrastructure/Security/MemberUserStore.cs b/src/Umbraco.Infrastructure/Security/MemberUserStore.cs index 4fba880e81..420d66b0b4 100644 --- a/src/Umbraco.Infrastructure/Security/MemberUserStore.cs +++ b/src/Umbraco.Infrastructure/Security/MemberUserStore.cs @@ -112,6 +112,9 @@ namespace Umbraco.Cms.Core.Security // create the member _memberService.Save(memberEntity); + //We need to add roles now that the member has an Id. It do not work implicit in UpdateMemberProperties + _memberService.AssignRoles(new[] { memberEntity.Id }, user.Roles.Select(x => x.RoleId).ToArray()); + if (!memberEntity.HasIdentity) { throw new DataException("Could not create the member, check logs for details"); diff --git a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs index aa5fc83e1e..e866409c17 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs @@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Cms.Core; @@ -31,6 +32,7 @@ using Umbraco.Cms.Web.Common.ActionsResults; using Umbraco.Cms.Web.Common.Attributes; using Umbraco.Cms.Web.Common.Authorization; using Umbraco.Cms.Web.Common.Controllers; +using Umbraco.Cms.Web.Common.DependencyInjection; using Umbraco.Cms.Web.Common.Filters; using Umbraco.Extensions; using Constants = Umbraco.Cms.Core.Constants; @@ -68,7 +70,10 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers private readonly IBackOfficeTwoFactorOptions _backOfficeTwoFactorOptions; private readonly IManifestParser _manifestParser; private readonly ServerVariablesParser _serverVariables; + private readonly IOptions _securitySettings; + + [ActivatorUtilitiesConstructor] public BackOfficeController( IBackOfficeUserManager userManager, IRuntimeState runtimeState, @@ -87,7 +92,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers IHttpContextAccessor httpContextAccessor, IBackOfficeTwoFactorOptions backOfficeTwoFactorOptions, IManifestParser manifestParser, - ServerVariablesParser serverVariables) + ServerVariablesParser serverVariables, + IOptions securitySettings) { _userManager = userManager; _runtimeState = runtimeState; @@ -107,6 +113,51 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers _backOfficeTwoFactorOptions = backOfficeTwoFactorOptions; _manifestParser = manifestParser; _serverVariables = serverVariables; + _securitySettings = securitySettings; + } + + [Obsolete("Use ctor with all params. This overload will be removed in Umbraco 10.")] + public BackOfficeController( + IBackOfficeUserManager userManager, + IRuntimeState runtimeState, + IRuntimeMinifier runtimeMinifier, + IOptions globalSettings, + IHostingEnvironment hostingEnvironment, + ILocalizedTextService textService, + IGridConfig gridConfig, + BackOfficeServerVariables backOfficeServerVariables, + AppCaches appCaches, + IBackOfficeSignInManager signInManager, + IBackOfficeSecurityAccessor backofficeSecurityAccessor, + ILogger logger, + IJsonSerializer jsonSerializer, + IBackOfficeExternalLoginProviders externalLogins, + IHttpContextAccessor httpContextAccessor, + IBackOfficeTwoFactorOptions backOfficeTwoFactorOptions, + IManifestParser manifestParser, + ServerVariablesParser serverVariables) + : this(userManager, + runtimeState, + runtimeMinifier, + globalSettings, + hostingEnvironment, + textService, + gridConfig, + backOfficeServerVariables, + appCaches, + signInManager, + backofficeSecurityAccessor, + logger, + jsonSerializer, + externalLogins, + httpContextAccessor, + backOfficeTwoFactorOptions, + manifestParser, + serverVariables, + StaticServiceProvider.Instance.GetRequiredService>() + ) + { + } [HttpGet] @@ -458,7 +509,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers if (response == null) throw new ArgumentNullException(nameof(response)); // Sign in the user with this external login provider (which auto links, etc...) - SignInResult result = await _signInManager.ExternalLoginSignInAsync(loginInfo, isPersistent: false); + SignInResult result = await _signInManager.ExternalLoginSignInAsync(loginInfo, isPersistent: false, bypassTwoFactor: _securitySettings.Value.UserBypassTwoFactorForExternalLogins); var errors = new List(); diff --git a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilder.BackOfficeIdentity.cs b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilder.BackOfficeIdentity.cs index e9cc213598..1dc5bda7a9 100644 --- a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilder.BackOfficeIdentity.cs +++ b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilder.BackOfficeIdentity.cs @@ -11,6 +11,7 @@ using Umbraco.Cms.Core.Net; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.Security; using Umbraco.Cms.Web.BackOffice.Security; using Umbraco.Cms.Web.Common.AspNetCore; using Umbraco.Cms.Web.Common.Security; @@ -77,5 +78,14 @@ namespace Umbraco.Extensions return umbracoBuilder; } + public static BackOfficeIdentityBuilder AddTwoFactorProvider(this BackOfficeIdentityBuilder identityBuilder, string providerName) where T : class, ITwoFactorProvider + { + identityBuilder.Services.AddSingleton(); + identityBuilder.Services.AddSingleton(); + identityBuilder.AddTokenProvider>(providerName); + + return identityBuilder; + } + } } diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 69f812b6e6..46ff558aee 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -17,6 +17,7 @@ + diff --git a/src/Umbraco.Web.Website/Controllers/UmbExternalLoginController.cs b/src/Umbraco.Web.Website/Controllers/UmbExternalLoginController.cs index c43754e170..cb9188f5d0 100644 --- a/src/Umbraco.Web.Website/Controllers/UmbExternalLoginController.cs +++ b/src/Umbraco.Web.Website/Controllers/UmbExternalLoginController.cs @@ -8,7 +8,9 @@ using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Logging; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Routing; @@ -29,6 +31,7 @@ namespace Umbraco.Cms.Web.Website.Controllers { private readonly IMemberManager _memberManager; private readonly ITwoFactorLoginService _twoFactorLoginService; + private readonly IOptions _securitySettings; private readonly ILogger _logger; private readonly IMemberSignInManagerExternalLogins _memberSignInManager; @@ -42,7 +45,8 @@ namespace Umbraco.Cms.Web.Website.Controllers IPublishedUrlProvider publishedUrlProvider, IMemberSignInManagerExternalLogins memberSignInManager, IMemberManager memberManager, - ITwoFactorLoginService twoFactorLoginService) + ITwoFactorLoginService twoFactorLoginService, + IOptions securitySettings) : base( umbracoContextAccessor, databaseFactory, @@ -55,6 +59,7 @@ namespace Umbraco.Cms.Web.Website.Controllers _memberSignInManager = memberSignInManager; _memberManager = memberManager; _twoFactorLoginService = twoFactorLoginService; + _securitySettings = securitySettings; } /// @@ -95,7 +100,7 @@ namespace Umbraco.Cms.Web.Website.Controllers } else { - SignInResult result = await _memberSignInManager.ExternalLoginSignInAsync(loginInfo, false); + SignInResult result = await _memberSignInManager.ExternalLoginSignInAsync(loginInfo, false, _securitySettings.Value.MemberBypassTwoFactorForExternalLogins); if (result == SignInResult.Success) { From eea02137ae0b709861b45ded11882279a990c421 Mon Sep 17 00:00:00 2001 From: nikolajlauridsen Date: Wed, 9 Feb 2022 10:26:31 +0100 Subject: [PATCH 132/141] Bump version to non-rc --- build/templates/UmbracoPackage/.template.config/template.json | 2 +- build/templates/UmbracoProject/.template.config/template.json | 2 +- src/Directory.Build.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build/templates/UmbracoPackage/.template.config/template.json b/build/templates/UmbracoPackage/.template.config/template.json index 1a4dd16fd7..32f0c924dd 100644 --- a/build/templates/UmbracoPackage/.template.config/template.json +++ b/build/templates/UmbracoPackage/.template.config/template.json @@ -24,7 +24,7 @@ "version": { "type": "parameter", "datatype": "string", - "defaultValue": "9.3.0-rc", + "defaultValue": "9.3.0", "description": "The version of Umbraco to load using NuGet", "replaces": "UMBRACO_VERSION_FROM_TEMPLATE" }, diff --git a/build/templates/UmbracoProject/.template.config/template.json b/build/templates/UmbracoProject/.template.config/template.json index fd41de8d1c..b09050a2a4 100644 --- a/build/templates/UmbracoProject/.template.config/template.json +++ b/build/templates/UmbracoProject/.template.config/template.json @@ -57,7 +57,7 @@ "version": { "type": "parameter", "datatype": "string", - "defaultValue": "9.3.0-rc", + "defaultValue": "9.3.0", "description": "The version of Umbraco to load using NuGet", "replaces": "UMBRACO_VERSION_FROM_TEMPLATE" }, diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 995c8afebd..55448806ef 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -5,7 +5,7 @@ 9.3.0 9.3.0 - 9.3.0-rc + 9.3.0 9.3.0 9.0 en-US From cf410ab91e7d21620a971a219280886d852d10a8 Mon Sep 17 00:00:00 2001 From: Paul Johnson Date: Thu, 10 Feb 2022 12:03:35 +0000 Subject: [PATCH 133/141] Attempt to make app local icu setup less problematic. (#11961) * Attempt to make app local icu setup less problematic. Prevents issues for windows build agent -> linux app server. On Windows version is split at first '.' e.g. 68.2.0.9 -> icuuc68.dll https://github.com/dotnet/runtime/blob/205f70e/src/libraries/System.Private.CoreLib/src/System/Globalization/GlobalizationMode.Windows.cs On Linux we are looking for libicuuc.so.68.2.0.9 https://github.com/dotnet/runtime/blob/205f70e/src/libraries/System.Private.CoreLib/src/System/Globalization/GlobalizationMode.Unix.cs On macos we don't have a native library in a shiny nuget package so hope folks are building on their macs or setting the rid until we have a better solution. * Combine elements --- build/templates/UmbracoProject/UmbracoProject.csproj | 10 +++++++--- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 11 ++++++++++- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/build/templates/UmbracoProject/UmbracoProject.csproj b/build/templates/UmbracoProject/UmbracoProject.csproj index 3fa1eb2f36..6b47686415 100644 --- a/build/templates/UmbracoProject/UmbracoProject.csproj +++ b/build/templates/UmbracoProject/UmbracoProject.csproj @@ -12,9 +12,13 @@ - - - + + + + diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index ec4d3c1798..b584606f4f 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -19,6 +19,15 @@ + + + + + + @@ -92,7 +101,7 @@ - + From 91c4c776767a8851317b339ebecc1a76ffdc9827 Mon Sep 17 00:00:00 2001 From: Paul Johnson Date: Fri, 11 Feb 2022 16:24:53 +0000 Subject: [PATCH 134/141] Switch a lot of warnings to suggestions until we are able to resolve. (#11974) * Switch a lot of warnings to suggestions until we are able to resolve. * Make stylecop respect more csharp_style rules e.g. csharp_using_directive_placement * Added cheatsheet * Drop sorting requirements for using directives. --- .editorconfig | 43 ----------------------- .globalconfig | 81 +++++++++++++++++++++++++++++++++++++++++++ Directory.Build.props | 8 +---- codeanalysis.ruleset | 18 ---------- stylecop.json | 16 --------- 5 files changed, 82 insertions(+), 84 deletions(-) create mode 100644 .globalconfig delete mode 100644 codeanalysis.ruleset delete mode 100644 stylecop.json diff --git a/.editorconfig b/.editorconfig index d4094b2cf3..eba04ad326 100644 --- a/.editorconfig +++ b/.editorconfig @@ -306,48 +306,6 @@ dotnet_naming_rule.other_public_protected_fields_disallowed_rule.symbols dotnet_naming_rule.other_public_protected_fields_disallowed_rule.style = disallowed_style dotnet_naming_rule.other_public_protected_fields_disallowed_rule.severity = error -########################################## -# StyleCop Field Naming Rules -# Naming rules for fields follow the StyleCop analyzers -# This does not override any rules using disallowed_style above -# https://github.com/DotNetAnalyzers/StyleCopAnalyzers -########################################## - -# All constant fields must be PascalCase -# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1303.md -dotnet_naming_symbols.stylecop_constant_fields_group.applicable_accessibilities = public, internal, protected_internal, protected, private_protected, private -dotnet_naming_symbols.stylecop_constant_fields_group.required_modifiers = const -dotnet_naming_symbols.stylecop_constant_fields_group.applicable_kinds = field -dotnet_naming_rule.stylecop_constant_fields_must_be_pascal_case_rule.symbols = stylecop_constant_fields_group -dotnet_naming_rule.stylecop_constant_fields_must_be_pascal_case_rule.style = pascal_case_style -dotnet_naming_rule.stylecop_constant_fields_must_be_pascal_case_rule.severity = warning - -# All static readonly fields must be PascalCase -# Ajusted to ignore private fields. -# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1311.md -dotnet_naming_symbols.stylecop_static_readonly_fields_group.applicable_accessibilities = public, internal, protected_internal, protected, private_protected -dotnet_naming_symbols.stylecop_static_readonly_fields_group.required_modifiers = static, readonly -dotnet_naming_symbols.stylecop_static_readonly_fields_group.applicable_kinds = field -dotnet_naming_rule.stylecop_static_readonly_fields_must_be_pascal_case_rule.symbols = stylecop_static_readonly_fields_group -dotnet_naming_rule.stylecop_static_readonly_fields_must_be_pascal_case_rule.style = pascal_case_style -dotnet_naming_rule.stylecop_static_readonly_fields_must_be_pascal_case_rule.severity = warning - -# No non-private instance fields are allowed -# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1401.md -dotnet_naming_symbols.stylecop_fields_must_be_private_group.applicable_accessibilities = public, internal, protected_internal, protected, private_protected -dotnet_naming_symbols.stylecop_fields_must_be_private_group.applicable_kinds = field -dotnet_naming_rule.stylecop_instance_fields_must_be_private_rule.symbols = stylecop_fields_must_be_private_group -dotnet_naming_rule.stylecop_instance_fields_must_be_private_rule.style = disallowed_style -dotnet_naming_rule.stylecop_instance_fields_must_be_private_rule.severity = error - -# Local variables must be camelCase -# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1312.md -dotnet_naming_symbols.stylecop_local_fields_group.applicable_accessibilities = local -dotnet_naming_symbols.stylecop_local_fields_group.applicable_kinds = local -dotnet_naming_rule.stylecop_local_fields_must_be_camel_case_rule.symbols = stylecop_local_fields_group -dotnet_naming_rule.stylecop_local_fields_must_be_camel_case_rule.style = camel_case_style -dotnet_naming_rule.stylecop_local_fields_must_be_camel_case_rule.severity = silent - # This rule should never fire. However, it's included for at least two purposes: # First, it helps to understand, reason about, and root-case certain types of issues, such as bugs in .editorconfig parsers. # Second, it helps to raise immediate awareness if a new field type is added (as occurred recently in C#). @@ -357,7 +315,6 @@ dotnet_naming_rule.sanity_check_uncovered_field_case_rule.symbols = sanity_chec dotnet_naming_rule.sanity_check_uncovered_field_case_rule.style = internal_error_style dotnet_naming_rule.sanity_check_uncovered_field_case_rule.severity = error - ########################################## # Other Naming Rules ########################################## diff --git a/.globalconfig b/.globalconfig new file mode 100644 index 0000000000..8342ab4580 --- /dev/null +++ b/.globalconfig @@ -0,0 +1,81 @@ +is_global = true + +########################################## +# StyleCopAnalyzers Settings +########################################## + +# All constant fields must be PascalCase +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1303.md +dotnet_naming_symbols.stylecop_constant_fields_group.applicable_accessibilities = public, internal, protected_internal, protected, private_protected, private +dotnet_naming_symbols.stylecop_constant_fields_group.required_modifiers = const +dotnet_naming_symbols.stylecop_constant_fields_group.applicable_kinds = field +dotnet_naming_rule.stylecop_constant_fields_must_be_pascal_case_rule.symbols = stylecop_constant_fields_group +dotnet_naming_rule.stylecop_constant_fields_must_be_pascal_case_rule.style = pascal_case_style + +# All static readonly fields must be PascalCase +# Ajusted to ignore private fields. +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1311.md +dotnet_naming_symbols.stylecop_static_readonly_fields_group.applicable_accessibilities = public, internal, protected_internal, protected, private_protected +dotnet_naming_symbols.stylecop_static_readonly_fields_group.required_modifiers = static, readonly +dotnet_naming_symbols.stylecop_static_readonly_fields_group.applicable_kinds = field +dotnet_naming_rule.stylecop_static_readonly_fields_must_be_pascal_case_rule.symbols = stylecop_static_readonly_fields_group +dotnet_naming_rule.stylecop_static_readonly_fields_must_be_pascal_case_rule.style = pascal_case_style + +# No non-private instance fields are allowed +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1401.md +dotnet_naming_symbols.stylecop_fields_must_be_private_group.applicable_accessibilities = public, internal, protected_internal, protected, private_protected +dotnet_naming_symbols.stylecop_fields_must_be_private_group.applicable_kinds = field +dotnet_naming_rule.stylecop_instance_fields_must_be_private_rule.symbols = stylecop_fields_must_be_private_group +dotnet_naming_rule.stylecop_instance_fields_must_be_private_rule.style = disallowed_style + +# Local variables must be camelCase +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1312.md +dotnet_naming_symbols.stylecop_local_fields_group.applicable_accessibilities = local +dotnet_naming_symbols.stylecop_local_fields_group.applicable_kinds = local +dotnet_naming_rule.stylecop_local_fields_must_be_camel_case_rule.symbols = stylecop_local_fields_group +dotnet_naming_rule.stylecop_local_fields_must_be_camel_case_rule.style = camel_case_style + +########################################## +# StyleCopAnalyzers rule severity +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers +########################################## + +dotnet_analyzer_diagnostic.category-StyleCop.CSharp.DocumentationRules.severity = suggestion +dotnet_analyzer_diagnostic.category-StyleCop.CSharp.ReadabilityRules.severity = suggestion +dotnet_analyzer_diagnostic.category-StyleCop.CSharp.NamingRules.severity = suggestion +dotnet_analyzer_diagnostic.category-StyleCop.CSharp.SpacingRules.severity = suggestion +dotnet_analyzer_diagnostic.category-StyleCop.CSharp.OrderingRules.severity = suggestion +dotnet_analyzer_diagnostic.category-StyleCop.CSharp.MaintainabilityRules.severity = suggestion +dotnet_analyzer_diagnostic.category-StyleCop.CSharp.LayoutRules.severity = suggestion + +dotnet_diagnostic.SA1636.severity = none # SA1636: File header copyright text should match + +dotnet_diagnostic.SA1503.severity = warning # BracesMustNotBeOmitted +dotnet_diagnostic.SA1117.severity = warning # ParametersMustBeOnSameLineOrSeparateLines +dotnet_diagnostic.SA1116.severity = warning # SplitParametersMustStartOnLineAfterDeclaration +dotnet_diagnostic.SA1122.severity = warning # UseStringEmptyForEmptyStrings +dotnet_diagnostic.SA1028.severity = warning # CodeMustNotContainTrailingWhitespace +dotnet_diagnostic.SA1500.severity = warning # BracesForMultiLineStatementsMustNotShareLine +dotnet_diagnostic.SA1401.severity = warning # FieldsMustBePrivate +dotnet_diagnostic.SA1519.severity = warning # BracesMustNotBeOmittedFromMultiLineChildStatement +dotnet_diagnostic.SA1111.severity = warning # ClosingParenthesisMustBeOnLineOfLastParameter +dotnet_diagnostic.SA1520.severity = warning # UseBracesConsistently +dotnet_diagnostic.SA1407.severity = warning # ArithmeticExpressionsMustDeclarePrecedence +dotnet_diagnostic.SA1400.severity = warning # AccessModifierMustBeDeclared +dotnet_diagnostic.SA1119.severity = warning # StatementMustNotUseUnnecessaryParenthesis +dotnet_diagnostic.SA1649.severity = warning # FileNameMustMatchTypeName +dotnet_diagnostic.SA1121.severity = warning # UseBuiltInTypeAlias +dotnet_diagnostic.SA1132.severity = warning # DoNotCombineFields +dotnet_diagnostic.SA1134.severity = warning # AttributesMustNotShareLine +dotnet_diagnostic.SA1106.severity = warning # CodeMustNotContainEmptyStatements +dotnet_diagnostic.SA1312.severity = warning # VariableNamesMustBeginWithLowerCaseLetter +dotnet_diagnostic.SA1303.severity = warning # ConstFieldNamesMustBeginWithUpperCaseLetter +dotnet_diagnostic.SA1310.severity = warning # FieldNamesMustNotContainUnderscore +dotnet_diagnostic.SA1130.severity = warning # UseLambdaSyntax +dotnet_diagnostic.SA1405.severity = warning # DebugAssertMustProvideMessageText +dotnet_diagnostic.SA1205.severity = warning # PartialElementsMustDeclareAccess +dotnet_diagnostic.SA1306.severity = warning # FieldNamesMustBeginWithLowerCaseLetter +dotnet_diagnostic.SA1209.severity = warning # UsingAliasDirectivesMustBePlacedAfterOtherUsingDirectives +dotnet_diagnostic.SA1216.severity = warning # UsingStaticDirectivesMustBePlacedAtTheCorrectLocation +dotnet_diagnostic.SA1133.severity = warning # DoNotCombineAttributes +dotnet_diagnostic.SA1135.severity = warning # UsingDirectivesMustBeQualified diff --git a/Directory.Build.props b/Directory.Build.props index 74f1ebad3d..fcf605f555 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -2,12 +2,6 @@ - - + - - - - $(MSBuildThisFileDirectory)codeanalysis.ruleset - diff --git a/codeanalysis.ruleset b/codeanalysis.ruleset deleted file mode 100644 index ab5ad88f57..0000000000 --- a/codeanalysis.ruleset +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/stylecop.json b/stylecop.json deleted file mode 100644 index b2f7771470..0000000000 --- a/stylecop.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", - "settings": { - "orderingRules": { - "usingDirectivesPlacement": "outsideNamespace", - "elementOrder": [ - "kind" - ] - }, - "documentationRules": { - "xmlHeader": false, - "documentInternalElements": false, - "copyrightText": "Copyright (c) Umbraco.\nSee LICENSE for more details." - } - } -} From 76cda7fd0cc758c1ad15bfd5c94932b18da83e22 Mon Sep 17 00:00:00 2001 From: Paul Johnson Date: Fri, 11 Feb 2022 14:24:45 +0000 Subject: [PATCH 135/141] Fix issue running UmbracoTestServerTestBase tests with debugger attached Ensure ConfigureWebServer called before ConfigureServices for tests that make use of WebApplicationFactory. Still not apparent why tests would pass if debugger isn't attached? --- .../UmbracoTestServerTestBase.cs | 70 +++++++++++++------ .../Testing/UmbracoIntegrationTest.cs | 14 ++-- .../Events/EventAggregatorTests.cs | 3 +- .../UmbracoExamine/ExamineBaseTest.cs | 7 +- 4 files changed, 63 insertions(+), 31 deletions(-) diff --git a/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs b/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs index bd9418bdb6..45fb305de9 100644 --- a/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs +++ b/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs @@ -12,13 +12,16 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Moq; using NUnit.Framework; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Web; using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Cms.Tests.Integration.DependencyInjection; @@ -77,28 +80,55 @@ namespace Umbraco.Cms.Tests.Integration.TestServerTest LinkGenerator = Factory.Services.GetRequiredService(); } - public override IHostBuilder CreateHostBuilder() + protected override IHostBuilder CreateHostBuilder() { - IHostBuilder builder = base.CreateHostBuilder(); - builder.ConfigureWebHost(builder => - { - // need to configure the IWebHostEnvironment too - builder.ConfigureServices((c, s) => c.HostingEnvironment = TestHelper.GetWebHostEnvironment()); + /* It is important that ConfigureWebHost is called before ConfigureServices, this is consistent with the host setup + * found in Program.cs and avoids nasty surprises. + * + * e.g. the registration for RefreshingRazorViewEngine requires that IWebHostEnvironment is registered + * at the point in time that the service collection is snapshotted. + */ + IHostBuilder hostBuilder = Host.CreateDefaultBuilder() + .ConfigureAppConfiguration((context, configBuilder) => + { + context.HostingEnvironment = TestHelper.GetWebHostEnvironment(); + configBuilder.Sources.Clear(); + configBuilder.AddInMemoryCollection(InMemoryConfiguration); - // call startup - builder.Configure(app => Configure(app)); - }) - .UseDefaultServiceProvider(cfg => - { - // These default to true *if* WebHostEnvironment.EnvironmentName == Development - // When running tests, EnvironmentName used to be null on the mock that we register into services. - // Enable opt in for tests so that validation occurs regardless of environment name. - // Would be nice to have this on for UmbracoIntegrationTest also but requires a lot more effort to resolve issues. - cfg.ValidateOnBuild = true; - cfg.ValidateScopes = true; - }); + Configuration = configBuilder.Build(); + }) + .ConfigureWebHost(builder => + { + // need to configure the IWebHostEnvironment too + builder.ConfigureServices((c, s) => c.HostingEnvironment = TestHelper.GetWebHostEnvironment()); - return builder; + // call startup + builder.Configure(app => Configure(app)); + }) + .ConfigureServices((_, services) => + { + ConfigureServices(services); + ConfigureTestSpecificServices(services); + services.AddUnique(CreateLoggerFactory()); + + if (!TestOptions.Boot) + { + // If boot is false, we don't want the CoreRuntime hosted service to start + // So we replace it with a Mock + services.AddUnique(Mock.Of()); + } + }) + .UseDefaultServiceProvider(cfg => + { + // These default to true *if* WebHostEnvironment.EnvironmentName == Development + // When running tests, EnvironmentName used to be null on the mock that we register into services. + // Enable opt in for tests so that validation occurs regardless of environment name. + // Would be nice to have this on for UmbracoIntegrationTest also but requires a lot more effort to resolve issues. + cfg.ValidateOnBuild = true; + cfg.ValidateScopes = true; + }); + + return hostBuilder; } /// @@ -157,7 +187,7 @@ namespace Umbraco.Cms.Tests.Integration.TestServerTest protected WebApplicationFactory Factory { get; private set; } - public override void ConfigureServices(IServiceCollection services) + private void ConfigureServices(IServiceCollection services) { services.AddTransient(); diff --git a/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs b/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs index 7ec8a7fc14..915d2d7629 100644 --- a/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs +++ b/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs @@ -128,7 +128,7 @@ namespace Umbraco.Cms.Tests.Integration.Testing UseTestDatabase(Services); } - private ILoggerFactory CreateLoggerFactory() + protected ILoggerFactory CreateLoggerFactory() { try { @@ -163,7 +163,7 @@ namespace Umbraco.Cms.Tests.Integration.Testing /// /// Create the Generic Host and execute startup ConfigureServices/Configure calls /// - public virtual IHostBuilder CreateHostBuilder() + protected virtual IHostBuilder CreateHostBuilder() { IHostBuilder hostBuilder = Host.CreateDefaultBuilder() @@ -180,9 +180,10 @@ namespace Umbraco.Cms.Tests.Integration.Testing Configuration = configBuilder.Build(); }) - .ConfigureServices((hostContext, services) => + .ConfigureServices((_, services) => { ConfigureServices(services); + ConfigureTestSpecificServices(services); services.AddUnique(CreateLoggerFactory()); if (!TestOptions.Boot) @@ -192,10 +193,15 @@ namespace Umbraco.Cms.Tests.Integration.Testing services.AddUnique(Mock.Of()); } }); + return hostBuilder; } - public virtual void ConfigureServices(IServiceCollection services) + protected virtual void ConfigureTestSpecificServices(IServiceCollection services) + { + } + + private void ConfigureServices(IServiceCollection services) { services.AddSingleton(TestHelper.DbProviderFactoryCreator); services.AddTransient(); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Events/EventAggregatorTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Events/EventAggregatorTests.cs index 6446bf542a..55429af147 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Core/Events/EventAggregatorTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Events/EventAggregatorTests.cs @@ -14,9 +14,8 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Events [TestFixture] public class EventAggregatorTests : UmbracoTestServerTestBase { - public override void ConfigureServices(IServiceCollection services) + protected override void ConfigureTestSpecificServices(IServiceCollection services) { - base.ConfigureServices(services); services.AddScoped(); services.AddTransient, EventAggregatorTestNotificationHandler>(); } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineBaseTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineBaseTest.cs index bc1c6b8f02..7769f99d83 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineBaseTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineBaseTest.cs @@ -29,11 +29,8 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Examine.Lucene.UmbracoExamine protected IRuntimeState RunningRuntimeState { get; } = Mock.Of(x => x.Level == RuntimeLevel.Run); - public override void ConfigureServices(IServiceCollection services) - { - base.ConfigureServices(services); - services.AddSingleton(); - } + protected override void ConfigureTestSpecificServices(IServiceCollection services) + => services.AddSingleton(); /// /// Used to create and manage a testable index From c1fddd98be5e42ed49ce6f21eec18f9ac4070812 Mon Sep 17 00:00:00 2001 From: Paul Johnson Date: Fri, 11 Feb 2022 20:37:56 +0000 Subject: [PATCH 136/141] Additional refactoring I have been meaning to get around to for a while. --- .../UmbracoBuilderExtensions.cs | 4 +- .../UmbracoTestServerTestBase.cs | 140 ++++---- .../UmbracoWebApplicationFactory.cs | 6 +- .../Testing/UmbracoIntegrationTest.cs | 316 +----------------- .../Testing/UmbracoIntegrationTestBase.cs | 243 ++++++++++++++ .../Umbraco.Core/RuntimeStateTests.cs | 9 +- .../Scoping/ScopeTests.cs | 9 +- .../Scoping/ScopedRepositoryTests.cs | 6 +- .../Controllers/EntityControllerTests.cs | 3 + 9 files changed, 349 insertions(+), 387 deletions(-) create mode 100644 tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTestBase.cs diff --git a/tests/Umbraco.Tests.Integration/DependencyInjection/UmbracoBuilderExtensions.cs b/tests/Umbraco.Tests.Integration/DependencyInjection/UmbracoBuilderExtensions.cs index 7bf435485f..b5c917a337 100644 --- a/tests/Umbraco.Tests.Integration/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/tests/Umbraco.Tests.Integration/DependencyInjection/UmbracoBuilderExtensions.cs @@ -39,9 +39,9 @@ namespace Umbraco.Cms.Tests.Integration.DependencyInjection /// /// Uses/Replaces services with testing services /// - public static IUmbracoBuilder AddTestServices(this IUmbracoBuilder builder, TestHelper testHelper, AppCaches appCaches = null) + public static IUmbracoBuilder AddTestServices(this IUmbracoBuilder builder, TestHelper testHelper) { - builder.Services.AddUnique(appCaches ?? AppCaches.NoCache); + builder.Services.AddUnique(AppCaches.NoCache); builder.Services.AddUnique(Mock.Of()); builder.Services.AddUnique(testHelper.MainDom); diff --git a/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs b/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs index 45fb305de9..6d7835b889 100644 --- a/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs +++ b/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs @@ -1,6 +1,3 @@ -// Copyright (c) Umbraco. -// See LICENSE for more details. - using System; using System.Linq.Expressions; using System.Net.Http; @@ -17,7 +14,6 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Moq; using NUnit.Framework; -using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.DependencyInjection; @@ -35,14 +31,17 @@ namespace Umbraco.Cms.Tests.Integration.TestServerTest { [TestFixture] [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, Logger = UmbracoTestOptions.Logger.Console, Boot = true)] - public abstract class UmbracoTestServerTestBase : UmbracoIntegrationTest + public abstract class UmbracoTestServerTestBase : UmbracoIntegrationTestBase { - [SetUp] - public override void Setup() - { - InMemoryConfiguration["ConnectionStrings:" + Constants.System.UmbracoConnectionName] = null; - InMemoryConfiguration["Umbraco:CMS:Hosting:Debug"] = "true"; + protected HttpClient Client { get; private set; } + protected LinkGenerator LinkGenerator { get; private set; } + + protected WebApplicationFactory Factory { get; private set; } + + [SetUp] + public void Setup() + { /* * It's worth noting that our usage of WebApplicationFactory is non-standard, * the intent is that your Startup.ConfigureServices is called just like @@ -53,9 +52,12 @@ namespace Umbraco.Cms.Tests.Integration.TestServerTest * This is currently a pain to refactor towards due to UmbracoBuilder+TypeFinder+TypeLoader setup but * we should get there one day. * + * However we need to separate the testing framework we provide for downstream projects from our own tests. + * We cannot use the Umbraco.Web.UI startup yet as that is not available downstream. + * * See https://docs.microsoft.com/en-us/aspnet/core/test/integration-tests */ - var factory = new UmbracoWebApplicationFactory(CreateHostBuilder, BeforeHostStart); + var factory = new UmbracoWebApplicationFactory(CreateHostBuilder); // additional host configuration for web server integration tests Factory = factory.WithWebHostBuilder(builder => @@ -71,7 +73,6 @@ namespace Umbraco.Cms.Tests.Integration.TestServerTest .AddScheme(TestAuthHandler.TestAuthenticationScheme, options => { })); }); - Client = Factory.CreateClient(new WebApplicationFactoryClientOptions { AllowAutoRedirect = false @@ -80,57 +81,6 @@ namespace Umbraco.Cms.Tests.Integration.TestServerTest LinkGenerator = Factory.Services.GetRequiredService(); } - protected override IHostBuilder CreateHostBuilder() - { - /* It is important that ConfigureWebHost is called before ConfigureServices, this is consistent with the host setup - * found in Program.cs and avoids nasty surprises. - * - * e.g. the registration for RefreshingRazorViewEngine requires that IWebHostEnvironment is registered - * at the point in time that the service collection is snapshotted. - */ - IHostBuilder hostBuilder = Host.CreateDefaultBuilder() - .ConfigureAppConfiguration((context, configBuilder) => - { - context.HostingEnvironment = TestHelper.GetWebHostEnvironment(); - configBuilder.Sources.Clear(); - configBuilder.AddInMemoryCollection(InMemoryConfiguration); - - Configuration = configBuilder.Build(); - }) - .ConfigureWebHost(builder => - { - // need to configure the IWebHostEnvironment too - builder.ConfigureServices((c, s) => c.HostingEnvironment = TestHelper.GetWebHostEnvironment()); - - // call startup - builder.Configure(app => Configure(app)); - }) - .ConfigureServices((_, services) => - { - ConfigureServices(services); - ConfigureTestSpecificServices(services); - services.AddUnique(CreateLoggerFactory()); - - if (!TestOptions.Boot) - { - // If boot is false, we don't want the CoreRuntime hosted service to start - // So we replace it with a Mock - services.AddUnique(Mock.Of()); - } - }) - .UseDefaultServiceProvider(cfg => - { - // These default to true *if* WebHostEnvironment.EnvironmentName == Development - // When running tests, EnvironmentName used to be null on the mock that we register into services. - // Enable opt in for tests so that validation occurs regardless of environment name. - // Would be nice to have this on for UmbracoIntegrationTest also but requires a lot more effort to resolve issues. - cfg.ValidateOnBuild = true; - cfg.ValidateScopes = true; - }); - - return hostBuilder; - } - /// /// Prepare a url before using . /// This returns the url but also sets the HttpContext.request into to use this url. @@ -139,7 +89,7 @@ namespace Umbraco.Cms.Tests.Integration.TestServerTest protected string PrepareApiControllerUrl(Expression> methodSelector) where T : UmbracoApiController { - string url = LinkGenerator.GetUmbracoApiService(methodSelector); + var url = LinkGenerator.GetUmbracoApiService(methodSelector); return PrepareUrl(url); } @@ -151,7 +101,7 @@ namespace Umbraco.Cms.Tests.Integration.TestServerTest protected string PrepareSurfaceControllerUrl(Expression> methodSelector) where T : SurfaceController { - string url = LinkGenerator.GetUmbracoSurfaceUrl(methodSelector); + var url = LinkGenerator.GetUmbracoSurfaceUrl(methodSelector); return PrepareUrl(url); } @@ -181,17 +131,67 @@ namespace Umbraco.Cms.Tests.Integration.TestServerTest return url; } - protected HttpClient Client { get; private set; } + private IHostBuilder CreateHostBuilder() + { + IHostBuilder hostBuilder = Host.CreateDefaultBuilder() + .ConfigureAppConfiguration((context, configBuilder) => + { + context.HostingEnvironment = TestHelper.GetWebHostEnvironment(); + configBuilder.Sources.Clear(); + configBuilder.AddInMemoryCollection(InMemoryConfiguration); - protected LinkGenerator LinkGenerator { get; private set; } + Configuration = configBuilder.Build(); + }) + /* It is important that ConfigureWebHost is called before ConfigureServices, this is consistent with the host setup + * found in Program.cs and avoids nasty surprises. + * + * e.g. the registration for RefreshingRazorViewEngine requires that IWebHostEnvironment is registered + * at the point in time that the service collection is snapshotted. + */ + .ConfigureWebHost(builder => + { + // need to configure the IWebHostEnvironment too + builder.ConfigureServices((c, s) => c.HostingEnvironment = TestHelper.GetWebHostEnvironment()); - protected WebApplicationFactory Factory { get; private set; } + // call startup + builder.Configure(Configure); + }) + .ConfigureServices((_, services) => + { + ConfigureServices(services); + ConfigureTestSpecificServices(services); + + if (!TestOptions.Boot) + { + // If boot is false, we don't want the CoreRuntime hosted service to start + // So we replace it with a Mock + services.AddUnique(Mock.Of()); + } + }) + .UseDefaultServiceProvider(cfg => + { + // These default to true *if* WebHostEnvironment.EnvironmentName == Development + // When running tests, EnvironmentName used to be null on the mock that we register into services. + // Enable opt in for tests so that validation occurs regardless of environment name. + // Would be nice to have this on for UmbracoIntegrationTest also but requires a lot more effort to resolve issues. + cfg.ValidateOnBuild = true; + cfg.ValidateScopes = true; + }); + + return hostBuilder; + } + + protected virtual IServiceProvider Services => Factory.Services; + + protected virtual T GetRequiredService() => Factory.Services.GetRequiredService(); private void ConfigureServices(IServiceCollection services) { + services.AddUnique(CreateLoggerFactory()); services.AddTransient(); Core.Hosting.IHostingEnvironment hostingEnvironment = TestHelper.GetHostingEnvironment(); + TypeLoader typeLoader = services.AddTypeLoader( GetType().Assembly, hostingEnvironment, @@ -234,8 +234,10 @@ namespace Umbraco.Cms.Tests.Integration.TestServerTest .Build(); } - public override void Configure(IApplicationBuilder app) + private void Configure(IApplicationBuilder app) { + UseTestDatabase(app); + app.UseUmbraco() .WithMiddleware(u => { diff --git a/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoWebApplicationFactory.cs b/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoWebApplicationFactory.cs index 380603ae5c..62d84a19a1 100644 --- a/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoWebApplicationFactory.cs +++ b/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoWebApplicationFactory.cs @@ -18,11 +18,7 @@ namespace Umbraco.Cms.Tests.Integration.TestServerTest /// /// Method to create the IHostBuilder /// Method to perform an action before IHost starts - public UmbracoWebApplicationFactory(Func createHostBuilder, Action beforeStart = null) - { - _createHostBuilder = createHostBuilder; - _beforeStart = beforeStart; - } + public UmbracoWebApplicationFactory(Func createHostBuilder) => _createHostBuilder = createHostBuilder; protected override IHostBuilder CreateHostBuilder() => _createHostBuilder(); diff --git a/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs b/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs index 915d2d7629..1e9ae97474 100644 --- a/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs +++ b/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs @@ -1,23 +1,12 @@ -// Copyright (c) Umbraco. -// See LICENSE for more details. - using System; -using System.Collections.Generic; -using System.Data.Common; -using System.IO; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; -using Microsoft.Data.SqlClient; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Moq; using NUnit.Framework; -using Serilog; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Composing; @@ -29,14 +18,11 @@ using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; using Umbraco.Cms.Core.Web; using Umbraco.Cms.Infrastructure.DependencyInjection; -using Umbraco.Cms.Infrastructure.Persistence; using Umbraco.Cms.Infrastructure.Persistence.Mappers; using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Cms.Tests.Common.Builders; -using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Cms.Tests.Integration.DependencyInjection; using Umbraco.Cms.Tests.Integration.Extensions; -using Umbraco.Cms.Tests.Integration.Implementations; using Umbraco.Extensions; namespace Umbraco.Cms.Tests.Integration.Testing @@ -47,123 +33,35 @@ namespace Umbraco.Cms.Tests.Integration.Testing /// /// This will use a Host Builder to boot and install Umbraco ready for use /// - [SingleThreaded] - [NonParallelizable] - public abstract class UmbracoIntegrationTest + public abstract class UmbracoIntegrationTest : UmbracoIntegrationTestBase { - private List _testTeardown = null; - private readonly List _fixtureTeardown = new List(); + private IHost _host; - public void OnTestTearDown(Action tearDown) - { - if (_testTeardown == null) - { - _testTeardown = new List(); - } - - _testTeardown.Add(tearDown); - } - - public void OnFixtureTearDown(Action tearDown) => _fixtureTeardown.Add(tearDown); - - [OneTimeTearDown] - public void FixtureTearDown() - { - foreach (Action a in _fixtureTeardown) - { - a(); - } - } - - [TearDown] - public async Task TearDownAsync() - { - if (_testTeardown != null) - { - foreach (Action a in _testTeardown) - { - a(); - } - } - - _testTeardown = null; - _firstTestInFixture = false; - s_firstTestInSession = false; - - // Ensure CoreRuntime stopped (now it's a HostedService) - IHost host = Services?.GetService(); - if (host is not null) - { - await host.StopAsync(); - host.Dispose(); - } - - } - - [TearDown] - public virtual void TearDown_Logging() => - TestContext.Progress.Write($" {TestContext.CurrentContext.Result.Outcome.Status}"); + protected IServiceProvider Services => _host.Services; [SetUp] - public virtual void SetUp_Logging() => - TestContext.Progress.Write($"Start test {s_testCount++}: {TestContext.CurrentContext.Test.Name}"); - - [SetUp] - public virtual void Setup() + public void Setup() { InMemoryConfiguration[Constants.Configuration.ConfigUnattended + ":" + nameof(UnattendedSettings.InstallUnattended)] = "true"; IHostBuilder hostBuilder = CreateHostBuilder(); - IHost host = hostBuilder.Build(); - BeforeHostStart(host); - host.Start(); + _host = hostBuilder.Build(); + UseTestDatabase(_host.Services); + _host.Start(); - var app = new ApplicationBuilder(host.Services); - Configure(app); - } - - protected virtual void BeforeHostStart(IHost host) - { - Services = host.Services; - UseTestDatabase(Services); - } - - protected ILoggerFactory CreateLoggerFactory() - { - try + if (TestOptions.Boot) { - switch (TestOptions.Logger) - { - case UmbracoTestOptions.Logger.Mock: - return NullLoggerFactory.Instance; - case UmbracoTestOptions.Logger.Serilog: - return Microsoft.Extensions.Logging.LoggerFactory.Create(builder => - { - string path = Path.Combine(TestHelper.WorkingDirectory, "logs", "umbraco_integration_tests_.txt"); - - Log.Logger = new LoggerConfiguration() - .WriteTo.File(path, rollingInterval: RollingInterval.Day) - .MinimumLevel.Debug() - .CreateLogger(); - - builder.AddSerilog(Log.Logger); - }); - case UmbracoTestOptions.Logger.Console: - return Microsoft.Extensions.Logging.LoggerFactory.Create(builder => builder.AddConsole().SetMinimumLevel(LogLevel.Debug)); - } + Services.GetRequiredService().EnsureUmbracoContext(); } - catch - { - // ignored - } - - return NullLoggerFactory.Instance; } + [TearDown] + public void TearDownAsync() => _host.StopAsync(); + /// /// Create the Generic Host and execute startup ConfigureServices/Configure calls /// - protected virtual IHostBuilder CreateHostBuilder() + private IHostBuilder CreateHostBuilder() { IHostBuilder hostBuilder = Host.CreateDefaultBuilder() @@ -171,7 +69,6 @@ namespace Umbraco.Cms.Tests.Integration.Testing // if you want to setup your tests this way, it is a bit annoying to do that as the WebApplicationFactory will // create separate Host instances. So instead of UseStartup, we just call ConfigureServices/Configure ourselves, // and in the case of the UmbracoTestServerTestBase it will use the ConfigureWebHost to Configure the IApplicationBuilder directly. - // .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(GetType()); }) .ConfigureAppConfiguration((context, configBuilder) => { context.HostingEnvironment = TestHelper.GetWebHostEnvironment(); @@ -184,7 +81,6 @@ namespace Umbraco.Cms.Tests.Integration.Testing { ConfigureServices(services); ConfigureTestSpecificServices(services); - services.AddUnique(CreateLoggerFactory()); if (!TestOptions.Boot) { @@ -197,12 +93,9 @@ namespace Umbraco.Cms.Tests.Integration.Testing return hostBuilder; } - protected virtual void ConfigureTestSpecificServices(IServiceCollection services) - { - } - private void ConfigureServices(IServiceCollection services) { + services.AddUnique(CreateLoggerFactory()); services.AddSingleton(TestHelper.DbProviderFactoryCreator); services.AddTransient(); IWebHostEnvironment webHostEnvironment = TestHelper.GetWebHostEnvironment(); @@ -232,7 +125,7 @@ namespace Umbraco.Cms.Tests.Integration.Testing .AddBackOfficeIdentity() .AddMembersIdentity() .AddExamine() - .AddTestServices(TestHelper, GetAppCaches()); + .AddTestServices(TestHelper); if (TestOptions.Mapper) { @@ -250,182 +143,12 @@ namespace Umbraco.Cms.Tests.Integration.Testing builder.Build(); } - protected virtual AppCaches GetAppCaches() => - - // Disable caches for integration tests - AppCaches.NoCache; - - public virtual void Configure(IApplicationBuilder app) + protected virtual void CustomTestSetup(IUmbracoBuilder builder) { - if (TestOptions.Boot) - { - Services.GetRequiredService().EnsureUmbracoContext(); - } - - app.UseUmbracoCore(); // This no longer starts CoreRuntime, it's very fast } - private static readonly object s_dbLocker = new object(); - private static ITestDatabase s_dbInstance; - private static TestDbMeta s_fixtureDbMeta; - - protected void UseTestDatabase(IServiceProvider serviceProvider) - { - IRuntimeState state = serviceProvider.GetRequiredService(); - TestUmbracoDatabaseFactoryProvider testDatabaseFactoryProvider = serviceProvider.GetRequiredService(); - IUmbracoDatabaseFactory databaseFactory = serviceProvider.GetRequiredService(); - ILoggerFactory loggerFactory = serviceProvider.GetRequiredService(); - - // This will create a db, install the schema and ensure the app is configured to run - SetupTestDatabase(testDatabaseFactoryProvider, databaseFactory, loggerFactory, state, TestHelper.WorkingDirectory); - } - - /// - /// Get or create an instance of - /// - /// - /// There must only be ONE instance shared between all tests in a session - /// - private static ITestDatabase GetOrCreateDatabase(string filesPath, ILoggerFactory loggerFactory, TestUmbracoDatabaseFactoryProvider dbFactory) - { - lock (s_dbLocker) - { - if (s_dbInstance != null) - { - return s_dbInstance; - } - - // TODO: pull from IConfiguration - var settings = new TestDatabaseSettings - { - PrepareThreadCount = 4, - EmptyDatabasesCount = 2, - SchemaDatabaseCount = 4 - }; - - s_dbInstance = TestDatabaseFactory.Create(settings, filesPath, loggerFactory, dbFactory); - - return s_dbInstance; - } - } - - /// - /// Creates a LocalDb instance to use for the test - /// - private void SetupTestDatabase( - TestUmbracoDatabaseFactoryProvider testUmbracoDatabaseFactoryProvider, - IUmbracoDatabaseFactory databaseFactory, - ILoggerFactory loggerFactory, - IRuntimeState runtimeState, - string workingDirectory) - { - if (TestOptions.Database == UmbracoTestOptions.Database.None) - { - return; - } - - // need to manually register this factory - DbProviderFactories.RegisterFactory(Constants.DbProviderNames.SqlServer, SqlClientFactory.Instance); - - string dbFilePath = Path.Combine(workingDirectory, "LocalDb"); - - ITestDatabase db = GetOrCreateDatabase(dbFilePath, loggerFactory, testUmbracoDatabaseFactoryProvider); - - switch (TestOptions.Database) - { - case UmbracoTestOptions.Database.NewSchemaPerTest: - - // New DB + Schema - TestDbMeta newSchemaDbMeta = db.AttachSchema(); - - // Add teardown callback - OnTestTearDown(() => db.Detach(newSchemaDbMeta)); - - ConfigureTestDatabaseFactory(newSchemaDbMeta, databaseFactory, runtimeState); - - Assert.AreEqual(RuntimeLevel.Run, runtimeState.Level); - - break; - case UmbracoTestOptions.Database.NewEmptyPerTest: - TestDbMeta newEmptyDbMeta = db.AttachEmpty(); - - // Add teardown callback - OnTestTearDown(() => db.Detach(newEmptyDbMeta)); - - ConfigureTestDatabaseFactory(newEmptyDbMeta, databaseFactory, runtimeState); - - Assert.AreEqual(RuntimeLevel.Install, runtimeState.Level); - - break; - case UmbracoTestOptions.Database.NewSchemaPerFixture: - // Only attach schema once per fixture - // Doing it more than once will block the process since the old db hasn't been detached - // and it would be the same as NewSchemaPerTest even if it didn't block - if (_firstTestInFixture) - { - // New DB + Schema - TestDbMeta newSchemaFixtureDbMeta = db.AttachSchema(); - s_fixtureDbMeta = newSchemaFixtureDbMeta; - - // Add teardown callback - OnFixtureTearDown(() => db.Detach(newSchemaFixtureDbMeta)); - } - - ConfigureTestDatabaseFactory(s_fixtureDbMeta, databaseFactory, runtimeState); - - break; - case UmbracoTestOptions.Database.NewEmptyPerFixture: - // Only attach schema once per fixture - // Doing it more than once will block the process since the old db hasn't been detached - // and it would be the same as NewSchemaPerTest even if it didn't block - if (_firstTestInFixture) - { - // New DB + Schema - TestDbMeta newEmptyFixtureDbMeta = db.AttachEmpty(); - s_fixtureDbMeta = newEmptyFixtureDbMeta; - - // Add teardown callback - OnFixtureTearDown(() => db.Detach(newEmptyFixtureDbMeta)); - } - - ConfigureTestDatabaseFactory(s_fixtureDbMeta, databaseFactory, runtimeState); - - break; - default: - throw new ArgumentOutOfRangeException(nameof(TestOptions), TestOptions, null); - } - } - - private void ConfigureTestDatabaseFactory(TestDbMeta meta, IUmbracoDatabaseFactory factory, IRuntimeState state) - { - ILogger log = Services.GetRequiredService>(); - log.LogInformation($"ConfigureTestDatabaseFactory - Using test database: [{meta.Name}] - IsEmpty: [{meta.IsEmpty}]"); - - // It's just been pulled from container and wasn't used to create test database - Assert.IsFalse(factory.Configured); - - factory.Configure(meta.ConnectionString, Constants.DatabaseProviders.SqlServer); - state.DetermineRuntimeLevel(); - log.LogInformation($"ConfigureTestDatabaseFactory - Determined RuntimeLevel: [{state.Level}]"); - } - - protected UmbracoTestAttribute TestOptions => TestOptionAttributeBase.GetTestOptions(); - protected virtual T GetRequiredService() => Services.GetRequiredService(); - public Dictionary InMemoryConfiguration { get; } = new Dictionary(); - - public IConfiguration Configuration { get; protected set; } - - public TestHelper TestHelper { get; } = new TestHelper(); - - protected virtual void CustomTestSetup(IUmbracoBuilder builder) { } - - /// - /// Gets or sets the DI container. - /// - protected IServiceProvider Services { get; set; } - /// /// Gets the /// @@ -451,11 +174,8 @@ namespace Umbraco.Cms.Tests.Integration.Testing protected IMapperCollection Mappers => Services.GetRequiredService(); - protected UserBuilder UserBuilderInstance { get; } = new UserBuilder(); - protected UserGroupBuilder UserGroupBuilderInstance { get; } = new UserGroupBuilder(); + protected UserBuilder UserBuilderInstance { get; } = new (); - private static bool s_firstTestInSession = true; - private bool _firstTestInFixture = true; - private static int s_testCount = 1; + protected UserGroupBuilder UserGroupBuilderInstance { get; } = new (); } } diff --git a/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTestBase.cs b/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTestBase.cs new file mode 100644 index 0000000000..439d169c61 --- /dev/null +++ b/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTestBase.cs @@ -0,0 +1,243 @@ +using System; +using System.Collections.Generic; +using System.Data.Common; +using System.IO; +using Microsoft.AspNetCore.Builder; +using Microsoft.Data.SqlClient; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using NUnit.Framework; +using Serilog; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.Persistence; +using Umbraco.Cms.Tests.Common.Testing; +using Umbraco.Cms.Tests.Integration.Implementations; + +namespace Umbraco.Cms.Tests.Integration.Testing; + +/// +/// Base class for all UmbracoIntegrationTests +/// +[SingleThreaded] +[NonParallelizable] +public abstract class UmbracoIntegrationTestBase +{ + private static readonly object s_dbLocker = new (); + private static ITestDatabase s_dbInstance; + private static TestDbMeta s_fixtureDbMeta; + private static int s_testCount = 1; + + private bool _firstTestInFixture = true; + private readonly Queue _testTeardown = new (); + private readonly List _fixtureTeardown = new (); + + protected Dictionary InMemoryConfiguration { get; } = new (); + + protected IConfiguration Configuration { get; set; } + + protected UmbracoTestAttribute TestOptions => + TestOptionAttributeBase.GetTestOptions(); + + protected TestHelper TestHelper { get; } = new (); + + private void AddOnTestTearDown(Action tearDown) => _testTeardown.Enqueue(tearDown); + + private void AddOnFixtureTearDown(Action tearDown) => _fixtureTeardown.Add(tearDown); + + [SetUp] + public void SetUp_Logging() => + TestContext.Progress.Write($"Start test {s_testCount++}: {TestContext.CurrentContext.Test.Name}"); + + [TearDown] + public void TearDown_Logging() => + TestContext.Progress.Write($" {TestContext.CurrentContext.Result.Outcome.Status}"); + + [OneTimeTearDown] + public void FixtureTearDown() + { + foreach (Action a in _fixtureTeardown) + { + a(); + } + } + + [TearDown] + public void TearDown() + { + _firstTestInFixture = false; + + while (_testTeardown.TryDequeue(out Action a)) + { + a(); + } + } + + protected ILoggerFactory CreateLoggerFactory() + { + try + { + switch (TestOptions.Logger) + { + case UmbracoTestOptions.Logger.Mock: + return NullLoggerFactory.Instance; + case UmbracoTestOptions.Logger.Serilog: + return LoggerFactory.Create(builder => + { + var path = Path.Combine(TestHelper.WorkingDirectory, "logs", "umbraco_integration_tests_.txt"); + + Log.Logger = new LoggerConfiguration() + .WriteTo.File(path, rollingInterval: RollingInterval.Day) + .MinimumLevel.Debug() + .CreateLogger(); + + builder.AddSerilog(Log.Logger); + }); + case UmbracoTestOptions.Logger.Console: + return LoggerFactory.Create(builder => builder.AddConsole().SetMinimumLevel(LogLevel.Debug)); + } + } + catch + { + // ignored + } + + return NullLoggerFactory.Instance; + } + + protected virtual void ConfigureTestSpecificServices(IServiceCollection services) + { + } + + protected void UseTestDatabase(IApplicationBuilder app) + => UseTestDatabase(app.ApplicationServices); + + protected void UseTestDatabase(IServiceProvider serviceProvider) + { + IRuntimeState state = serviceProvider.GetRequiredService(); + TestUmbracoDatabaseFactoryProvider testDatabaseFactoryProvider = serviceProvider.GetRequiredService(); + IUmbracoDatabaseFactory databaseFactory = serviceProvider.GetRequiredService(); + ILoggerFactory loggerFactory = serviceProvider.GetRequiredService(); + + // This will create a db, install the schema and ensure the app is configured to run + SetupTestDatabase(testDatabaseFactoryProvider, databaseFactory, loggerFactory, state, TestHelper.WorkingDirectory); + } + + private void ConfigureTestDatabaseFactory(TestDbMeta meta, IUmbracoDatabaseFactory factory, IRuntimeState state) + { + // It's just been pulled from container and wasn't used to create test database + Assert.IsFalse(factory.Configured); + + factory.Configure(meta.ConnectionString, Constants.DatabaseProviders.SqlServer); + state.DetermineRuntimeLevel(); + } + + private void SetupTestDatabase( + TestUmbracoDatabaseFactoryProvider testUmbracoDatabaseFactoryProvider, + IUmbracoDatabaseFactory databaseFactory, + ILoggerFactory loggerFactory, + IRuntimeState runtimeState, + string workingDirectory) + { + if (TestOptions.Database == UmbracoTestOptions.Database.None) + { + return; + } + + // need to manually register this factory + DbProviderFactories.RegisterFactory(Constants.DbProviderNames.SqlServer, SqlClientFactory.Instance); + + var dbFilePath = Path.Combine(workingDirectory, "LocalDb"); + + ITestDatabase db = GetOrCreateDatabase(dbFilePath, loggerFactory, testUmbracoDatabaseFactoryProvider); + + switch (TestOptions.Database) + { + case UmbracoTestOptions.Database.NewSchemaPerTest: + + // New DB + Schema + TestDbMeta newSchemaDbMeta = db.AttachSchema(); + + // Add teardown callback + AddOnTestTearDown(() => db.Detach(newSchemaDbMeta)); + + ConfigureTestDatabaseFactory(newSchemaDbMeta, databaseFactory, runtimeState); + + Assert.AreEqual(RuntimeLevel.Run, runtimeState.Level); + + break; + case UmbracoTestOptions.Database.NewEmptyPerTest: + TestDbMeta newEmptyDbMeta = db.AttachEmpty(); + + // Add teardown callback + AddOnTestTearDown(() => db.Detach(newEmptyDbMeta)); + + ConfigureTestDatabaseFactory(newEmptyDbMeta, databaseFactory, runtimeState); + + Assert.AreEqual(RuntimeLevel.Install, runtimeState.Level); + + break; + case UmbracoTestOptions.Database.NewSchemaPerFixture: + // Only attach schema once per fixture + // Doing it more than once will block the process since the old db hasn't been detached + // and it would be the same as NewSchemaPerTest even if it didn't block + if (_firstTestInFixture) + { + // New DB + Schema + TestDbMeta newSchemaFixtureDbMeta = db.AttachSchema(); + s_fixtureDbMeta = newSchemaFixtureDbMeta; + + // Add teardown callback + AddOnFixtureTearDown(() => db.Detach(newSchemaFixtureDbMeta)); + } + + ConfigureTestDatabaseFactory(s_fixtureDbMeta, databaseFactory, runtimeState); + + break; + case UmbracoTestOptions.Database.NewEmptyPerFixture: + // Only attach schema once per fixture + // Doing it more than once will block the process since the old db hasn't been detached + // and it would be the same as NewSchemaPerTest even if it didn't block + if (_firstTestInFixture) + { + // New DB + Schema + TestDbMeta newEmptyFixtureDbMeta = db.AttachEmpty(); + s_fixtureDbMeta = newEmptyFixtureDbMeta; + + // Add teardown callback + AddOnFixtureTearDown(() => db.Detach(newEmptyFixtureDbMeta)); + } + + ConfigureTestDatabaseFactory(s_fixtureDbMeta, databaseFactory, runtimeState); + + break; + default: + throw new ArgumentOutOfRangeException(nameof(TestOptions), TestOptions, null); + } + } + + private static ITestDatabase GetOrCreateDatabase(string filesPath, ILoggerFactory loggerFactory, TestUmbracoDatabaseFactoryProvider dbFactory) + { + lock (s_dbLocker) + { + if (s_dbInstance != null) + { + return s_dbInstance; + } + + // TODO: pull from IConfiguration + var settings = new TestDatabaseSettings + { + PrepareThreadCount = 4, + EmptyDatabasesCount = 2, + SchemaDatabaseCount = 4 + }; + + s_dbInstance = TestDatabaseFactory.Create(settings, filesPath, loggerFactory, dbFactory); + + return s_dbInstance; + } + } +} diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/RuntimeStateTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/RuntimeStateTests.cs index 52fade8dc2..d0d90c5726 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Core/RuntimeStateTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/RuntimeStateTests.cs @@ -24,14 +24,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Core [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] public class RuntimeStateTests : UmbracoIntegrationTest { - private protected IRuntimeState RuntimeState { get; private set; } - - public override void Configure(IApplicationBuilder app) - { - base.Configure(app); - - RuntimeState = Services.GetRequiredService(); - } + private IRuntimeState RuntimeState => Services.GetRequiredService(); protected override void CustomTestSetup(IUmbracoBuilder builder) { diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopeTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopeTests.cs index bbb9ea8766..1fcb14f0f5 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopeTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopeTests.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Castle.Core.Logging; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Moq; using NUnit.Framework; @@ -16,6 +17,7 @@ using Umbraco.Cms.Infrastructure.Persistence; using Umbraco.Cms.Tests.Common; using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Cms.Tests.Integration.Testing; +using Umbraco.Extensions; namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Scoping { @@ -28,16 +30,19 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Scoping [SetUp] public void SetUp() => Assert.IsNull(ScopeProvider.AmbientScope); // gone - protected override AppCaches GetAppCaches() + + protected override void ConfigureTestSpecificServices(IServiceCollection services) { // Need to have a mockable request cache for tests var appCaches = new AppCaches( NoAppCache.Instance, Mock.Of(x => x.IsAvailable == false), new IsolatedCaches(_ => NoAppCache.Instance)); - return appCaches; + + services.AddUnique(appCaches); } + [Test] public void GivenUncompletedScopeOnChildThread_WhenTheParentCompletes_TheTransactionIsRolledBack() { diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopedRepositoryTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopedRepositoryTests.cs index 0c0508e523..f9a844817e 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopedRepositoryTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopedRepositoryTests.cs @@ -31,15 +31,15 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Scoping private ILocalizationService LocalizationService => GetRequiredService(); - protected override AppCaches GetAppCaches() + protected override void ConfigureTestSpecificServices(IServiceCollection services) { // this is what's created core web runtime - var result = new AppCaches( + var appCaches = new AppCaches( new DeepCloneAppCache(new ObjectCacheAppCache()), NoAppCache.Instance, new IsolatedCaches(type => new DeepCloneAppCache(new ObjectCacheAppCache()))); - return result; + services.AddUnique(appCaches); } protected override void CustomTestSetup(IUmbracoBuilder builder) diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/EntityControllerTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/EntityControllerTests.cs index 4e4ce29e9a..ecbd8d52aa 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/EntityControllerTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/EntityControllerTests.cs @@ -8,6 +8,7 @@ using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Tests.Common.Builders; using Umbraco.Cms.Tests.Common.Builders.Extensions; @@ -21,6 +22,8 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Web.BackOffice.Controllers [TestFixture] public class EntityControllerTests : UmbracoTestServerTestBase { + private IScopeProvider ScopeProvider => GetRequiredService(); + [Test] public async Task GetUrlsByIds_MediaWithIntegerIds_ReturnsValidMap() { From 1764440b35fe1800b73cae50176efd2849dcf7d3 Mon Sep 17 00:00:00 2001 From: Paul Johnson Date: Fri, 11 Feb 2022 16:24:53 +0000 Subject: [PATCH 137/141] Switch a lot of warnings to suggestions until we are able to resolve. (#11974) * Switch a lot of warnings to suggestions until we are able to resolve. * Make stylecop respect more csharp_style rules e.g. csharp_using_directive_placement * Added cheatsheet * Drop sorting requirements for using directives. (cherry picked from commit 91c4c776767a8851317b339ebecc1a76ffdc9827) --- .editorconfig | 43 ----------------------- .globalconfig | 81 +++++++++++++++++++++++++++++++++++++++++++ Directory.Build.props | 8 +---- codeanalysis.ruleset | 18 ---------- stylecop.json | 16 --------- 5 files changed, 82 insertions(+), 84 deletions(-) create mode 100644 .globalconfig delete mode 100644 codeanalysis.ruleset delete mode 100644 stylecop.json diff --git a/.editorconfig b/.editorconfig index d4094b2cf3..eba04ad326 100644 --- a/.editorconfig +++ b/.editorconfig @@ -306,48 +306,6 @@ dotnet_naming_rule.other_public_protected_fields_disallowed_rule.symbols dotnet_naming_rule.other_public_protected_fields_disallowed_rule.style = disallowed_style dotnet_naming_rule.other_public_protected_fields_disallowed_rule.severity = error -########################################## -# StyleCop Field Naming Rules -# Naming rules for fields follow the StyleCop analyzers -# This does not override any rules using disallowed_style above -# https://github.com/DotNetAnalyzers/StyleCopAnalyzers -########################################## - -# All constant fields must be PascalCase -# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1303.md -dotnet_naming_symbols.stylecop_constant_fields_group.applicable_accessibilities = public, internal, protected_internal, protected, private_protected, private -dotnet_naming_symbols.stylecop_constant_fields_group.required_modifiers = const -dotnet_naming_symbols.stylecop_constant_fields_group.applicable_kinds = field -dotnet_naming_rule.stylecop_constant_fields_must_be_pascal_case_rule.symbols = stylecop_constant_fields_group -dotnet_naming_rule.stylecop_constant_fields_must_be_pascal_case_rule.style = pascal_case_style -dotnet_naming_rule.stylecop_constant_fields_must_be_pascal_case_rule.severity = warning - -# All static readonly fields must be PascalCase -# Ajusted to ignore private fields. -# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1311.md -dotnet_naming_symbols.stylecop_static_readonly_fields_group.applicable_accessibilities = public, internal, protected_internal, protected, private_protected -dotnet_naming_symbols.stylecop_static_readonly_fields_group.required_modifiers = static, readonly -dotnet_naming_symbols.stylecop_static_readonly_fields_group.applicable_kinds = field -dotnet_naming_rule.stylecop_static_readonly_fields_must_be_pascal_case_rule.symbols = stylecop_static_readonly_fields_group -dotnet_naming_rule.stylecop_static_readonly_fields_must_be_pascal_case_rule.style = pascal_case_style -dotnet_naming_rule.stylecop_static_readonly_fields_must_be_pascal_case_rule.severity = warning - -# No non-private instance fields are allowed -# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1401.md -dotnet_naming_symbols.stylecop_fields_must_be_private_group.applicable_accessibilities = public, internal, protected_internal, protected, private_protected -dotnet_naming_symbols.stylecop_fields_must_be_private_group.applicable_kinds = field -dotnet_naming_rule.stylecop_instance_fields_must_be_private_rule.symbols = stylecop_fields_must_be_private_group -dotnet_naming_rule.stylecop_instance_fields_must_be_private_rule.style = disallowed_style -dotnet_naming_rule.stylecop_instance_fields_must_be_private_rule.severity = error - -# Local variables must be camelCase -# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1312.md -dotnet_naming_symbols.stylecop_local_fields_group.applicable_accessibilities = local -dotnet_naming_symbols.stylecop_local_fields_group.applicable_kinds = local -dotnet_naming_rule.stylecop_local_fields_must_be_camel_case_rule.symbols = stylecop_local_fields_group -dotnet_naming_rule.stylecop_local_fields_must_be_camel_case_rule.style = camel_case_style -dotnet_naming_rule.stylecop_local_fields_must_be_camel_case_rule.severity = silent - # This rule should never fire. However, it's included for at least two purposes: # First, it helps to understand, reason about, and root-case certain types of issues, such as bugs in .editorconfig parsers. # Second, it helps to raise immediate awareness if a new field type is added (as occurred recently in C#). @@ -357,7 +315,6 @@ dotnet_naming_rule.sanity_check_uncovered_field_case_rule.symbols = sanity_chec dotnet_naming_rule.sanity_check_uncovered_field_case_rule.style = internal_error_style dotnet_naming_rule.sanity_check_uncovered_field_case_rule.severity = error - ########################################## # Other Naming Rules ########################################## diff --git a/.globalconfig b/.globalconfig new file mode 100644 index 0000000000..8342ab4580 --- /dev/null +++ b/.globalconfig @@ -0,0 +1,81 @@ +is_global = true + +########################################## +# StyleCopAnalyzers Settings +########################################## + +# All constant fields must be PascalCase +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1303.md +dotnet_naming_symbols.stylecop_constant_fields_group.applicable_accessibilities = public, internal, protected_internal, protected, private_protected, private +dotnet_naming_symbols.stylecop_constant_fields_group.required_modifiers = const +dotnet_naming_symbols.stylecop_constant_fields_group.applicable_kinds = field +dotnet_naming_rule.stylecop_constant_fields_must_be_pascal_case_rule.symbols = stylecop_constant_fields_group +dotnet_naming_rule.stylecop_constant_fields_must_be_pascal_case_rule.style = pascal_case_style + +# All static readonly fields must be PascalCase +# Ajusted to ignore private fields. +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1311.md +dotnet_naming_symbols.stylecop_static_readonly_fields_group.applicable_accessibilities = public, internal, protected_internal, protected, private_protected +dotnet_naming_symbols.stylecop_static_readonly_fields_group.required_modifiers = static, readonly +dotnet_naming_symbols.stylecop_static_readonly_fields_group.applicable_kinds = field +dotnet_naming_rule.stylecop_static_readonly_fields_must_be_pascal_case_rule.symbols = stylecop_static_readonly_fields_group +dotnet_naming_rule.stylecop_static_readonly_fields_must_be_pascal_case_rule.style = pascal_case_style + +# No non-private instance fields are allowed +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1401.md +dotnet_naming_symbols.stylecop_fields_must_be_private_group.applicable_accessibilities = public, internal, protected_internal, protected, private_protected +dotnet_naming_symbols.stylecop_fields_must_be_private_group.applicable_kinds = field +dotnet_naming_rule.stylecop_instance_fields_must_be_private_rule.symbols = stylecop_fields_must_be_private_group +dotnet_naming_rule.stylecop_instance_fields_must_be_private_rule.style = disallowed_style + +# Local variables must be camelCase +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1312.md +dotnet_naming_symbols.stylecop_local_fields_group.applicable_accessibilities = local +dotnet_naming_symbols.stylecop_local_fields_group.applicable_kinds = local +dotnet_naming_rule.stylecop_local_fields_must_be_camel_case_rule.symbols = stylecop_local_fields_group +dotnet_naming_rule.stylecop_local_fields_must_be_camel_case_rule.style = camel_case_style + +########################################## +# StyleCopAnalyzers rule severity +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers +########################################## + +dotnet_analyzer_diagnostic.category-StyleCop.CSharp.DocumentationRules.severity = suggestion +dotnet_analyzer_diagnostic.category-StyleCop.CSharp.ReadabilityRules.severity = suggestion +dotnet_analyzer_diagnostic.category-StyleCop.CSharp.NamingRules.severity = suggestion +dotnet_analyzer_diagnostic.category-StyleCop.CSharp.SpacingRules.severity = suggestion +dotnet_analyzer_diagnostic.category-StyleCop.CSharp.OrderingRules.severity = suggestion +dotnet_analyzer_diagnostic.category-StyleCop.CSharp.MaintainabilityRules.severity = suggestion +dotnet_analyzer_diagnostic.category-StyleCop.CSharp.LayoutRules.severity = suggestion + +dotnet_diagnostic.SA1636.severity = none # SA1636: File header copyright text should match + +dotnet_diagnostic.SA1503.severity = warning # BracesMustNotBeOmitted +dotnet_diagnostic.SA1117.severity = warning # ParametersMustBeOnSameLineOrSeparateLines +dotnet_diagnostic.SA1116.severity = warning # SplitParametersMustStartOnLineAfterDeclaration +dotnet_diagnostic.SA1122.severity = warning # UseStringEmptyForEmptyStrings +dotnet_diagnostic.SA1028.severity = warning # CodeMustNotContainTrailingWhitespace +dotnet_diagnostic.SA1500.severity = warning # BracesForMultiLineStatementsMustNotShareLine +dotnet_diagnostic.SA1401.severity = warning # FieldsMustBePrivate +dotnet_diagnostic.SA1519.severity = warning # BracesMustNotBeOmittedFromMultiLineChildStatement +dotnet_diagnostic.SA1111.severity = warning # ClosingParenthesisMustBeOnLineOfLastParameter +dotnet_diagnostic.SA1520.severity = warning # UseBracesConsistently +dotnet_diagnostic.SA1407.severity = warning # ArithmeticExpressionsMustDeclarePrecedence +dotnet_diagnostic.SA1400.severity = warning # AccessModifierMustBeDeclared +dotnet_diagnostic.SA1119.severity = warning # StatementMustNotUseUnnecessaryParenthesis +dotnet_diagnostic.SA1649.severity = warning # FileNameMustMatchTypeName +dotnet_diagnostic.SA1121.severity = warning # UseBuiltInTypeAlias +dotnet_diagnostic.SA1132.severity = warning # DoNotCombineFields +dotnet_diagnostic.SA1134.severity = warning # AttributesMustNotShareLine +dotnet_diagnostic.SA1106.severity = warning # CodeMustNotContainEmptyStatements +dotnet_diagnostic.SA1312.severity = warning # VariableNamesMustBeginWithLowerCaseLetter +dotnet_diagnostic.SA1303.severity = warning # ConstFieldNamesMustBeginWithUpperCaseLetter +dotnet_diagnostic.SA1310.severity = warning # FieldNamesMustNotContainUnderscore +dotnet_diagnostic.SA1130.severity = warning # UseLambdaSyntax +dotnet_diagnostic.SA1405.severity = warning # DebugAssertMustProvideMessageText +dotnet_diagnostic.SA1205.severity = warning # PartialElementsMustDeclareAccess +dotnet_diagnostic.SA1306.severity = warning # FieldNamesMustBeginWithLowerCaseLetter +dotnet_diagnostic.SA1209.severity = warning # UsingAliasDirectivesMustBePlacedAfterOtherUsingDirectives +dotnet_diagnostic.SA1216.severity = warning # UsingStaticDirectivesMustBePlacedAtTheCorrectLocation +dotnet_diagnostic.SA1133.severity = warning # DoNotCombineAttributes +dotnet_diagnostic.SA1135.severity = warning # UsingDirectivesMustBeQualified diff --git a/Directory.Build.props b/Directory.Build.props index 74f1ebad3d..fcf605f555 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -2,12 +2,6 @@ - - + - - - - $(MSBuildThisFileDirectory)codeanalysis.ruleset - diff --git a/codeanalysis.ruleset b/codeanalysis.ruleset deleted file mode 100644 index ab5ad88f57..0000000000 --- a/codeanalysis.ruleset +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/stylecop.json b/stylecop.json deleted file mode 100644 index b2f7771470..0000000000 --- a/stylecop.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", - "settings": { - "orderingRules": { - "usingDirectivesPlacement": "outsideNamespace", - "elementOrder": [ - "kind" - ] - }, - "documentationRules": { - "xmlHeader": false, - "documentInternalElements": false, - "copyrightText": "Copyright (c) Umbraco.\nSee LICENSE for more details." - } - } -} From fcd4ad17bb5b794b81db26312f322b7546be0f83 Mon Sep 17 00:00:00 2001 From: Paul Johnson Date: Sat, 12 Feb 2022 11:40:39 +0000 Subject: [PATCH 138/141] Make webhost setup less confusing. --- .../UmbracoTestServerTestBase.cs | 34 ++++++++----------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs b/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs index 6d7835b889..632bbe87d3 100644 --- a/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs +++ b/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs @@ -142,32 +142,26 @@ namespace Umbraco.Cms.Tests.Integration.TestServerTest Configuration = configBuilder.Build(); }) - /* It is important that ConfigureWebHost is called before ConfigureServices, this is consistent with the host setup - * found in Program.cs and avoids nasty surprises. - * - * e.g. the registration for RefreshingRazorViewEngine requires that IWebHostEnvironment is registered - * at the point in time that the service collection is snapshotted. - */ .ConfigureWebHost(builder => { - // need to configure the IWebHostEnvironment too - builder.ConfigureServices((c, s) => c.HostingEnvironment = TestHelper.GetWebHostEnvironment()); + builder.ConfigureServices((context, services) => + { + context.HostingEnvironment = TestHelper.GetWebHostEnvironment(); + + ConfigureServices(services); + ConfigureTestSpecificServices(services); + + if (!TestOptions.Boot) + { + // If boot is false, we don't want the CoreRuntime hosted service to start + // So we replace it with a Mock + services.AddUnique(Mock.Of()); + } + }); // call startup builder.Configure(Configure); }) - .ConfigureServices((_, services) => - { - ConfigureServices(services); - ConfigureTestSpecificServices(services); - - if (!TestOptions.Boot) - { - // If boot is false, we don't want the CoreRuntime hosted service to start - // So we replace it with a Mock - services.AddUnique(Mock.Of()); - } - }) .UseDefaultServiceProvider(cfg => { // These default to true *if* WebHostEnvironment.EnvironmentName == Development From a1e562cab613ec56ed1cfa2e7c251ee2f5286018 Mon Sep 17 00:00:00 2001 From: Paul Johnson Date: Sat, 12 Feb 2022 11:57:28 +0000 Subject: [PATCH 139/141] Switch ConfigureServices back to protected. Downstream users may wish to subclass and add their application specific services. --- .../TestServerTest/UmbracoTestServerTestBase.cs | 13 ++++++++++--- .../Testing/UmbracoIntegrationTest.cs | 17 +++++++++++++++-- .../Testing/UmbracoIntegrationTestBase.cs | 4 ---- .../Umbraco.Core/Events/EventAggregatorTests.cs | 2 +- .../UmbracoExamine/ExamineBaseTest.cs | 2 +- .../Scoping/ScopeTests.cs | 2 +- .../Scoping/ScopedRepositoryTests.cs | 2 +- 7 files changed, 29 insertions(+), 13 deletions(-) diff --git a/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs b/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs index 632bbe87d3..8c7eabadde 100644 --- a/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs +++ b/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs @@ -149,7 +149,7 @@ namespace Umbraco.Cms.Tests.Integration.TestServerTest context.HostingEnvironment = TestHelper.GetWebHostEnvironment(); ConfigureServices(services); - ConfigureTestSpecificServices(services); + ConfigureTestServices(services); if (!TestOptions.Boot) { @@ -179,7 +179,7 @@ namespace Umbraco.Cms.Tests.Integration.TestServerTest protected virtual T GetRequiredService() => Factory.Services.GetRequiredService(); - private void ConfigureServices(IServiceCollection services) + protected void ConfigureServices(IServiceCollection services) { services.AddUnique(CreateLoggerFactory()); services.AddTransient(); @@ -228,7 +228,14 @@ namespace Umbraco.Cms.Tests.Integration.TestServerTest .Build(); } - private void Configure(IApplicationBuilder app) + /// + /// Hook for registering test doubles. + /// + protected virtual void ConfigureTestServices(IServiceCollection services) + { + } + + protected void Configure(IApplicationBuilder app) { UseTestDatabase(app); diff --git a/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs b/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs index 1e9ae97474..dd9fd3fe4e 100644 --- a/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs +++ b/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs @@ -80,7 +80,7 @@ namespace Umbraco.Cms.Tests.Integration.Testing .ConfigureServices((_, services) => { ConfigureServices(services); - ConfigureTestSpecificServices(services); + ConfigureTestServices(services); if (!TestOptions.Boot) { @@ -93,7 +93,7 @@ namespace Umbraco.Cms.Tests.Integration.Testing return hostBuilder; } - private void ConfigureServices(IServiceCollection services) + protected void ConfigureServices(IServiceCollection services) { services.AddUnique(CreateLoggerFactory()); services.AddSingleton(TestHelper.DbProviderFactoryCreator); @@ -143,10 +143,23 @@ namespace Umbraco.Cms.Tests.Integration.Testing builder.Build(); } + /// + /// Hook for altering UmbracoBuilder setup + /// + /// + /// Can also be used for registering test doubles. + /// protected virtual void CustomTestSetup(IUmbracoBuilder builder) { } + /// + /// Hook for registering test doubles. + /// + protected virtual void ConfigureTestServices(IServiceCollection services) + { + } + protected virtual T GetRequiredService() => Services.GetRequiredService(); /// diff --git a/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTestBase.cs b/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTestBase.cs index 439d169c61..b77ec1806f 100644 --- a/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTestBase.cs +++ b/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTestBase.cs @@ -107,10 +107,6 @@ public abstract class UmbracoIntegrationTestBase return NullLoggerFactory.Instance; } - protected virtual void ConfigureTestSpecificServices(IServiceCollection services) - { - } - protected void UseTestDatabase(IApplicationBuilder app) => UseTestDatabase(app.ApplicationServices); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Events/EventAggregatorTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Events/EventAggregatorTests.cs index 55429af147..4f7db59791 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Core/Events/EventAggregatorTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Events/EventAggregatorTests.cs @@ -14,7 +14,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Events [TestFixture] public class EventAggregatorTests : UmbracoTestServerTestBase { - protected override void ConfigureTestSpecificServices(IServiceCollection services) + protected override void ConfigureTestServices(IServiceCollection services) { services.AddScoped(); services.AddTransient, EventAggregatorTestNotificationHandler>(); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineBaseTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineBaseTest.cs index 7769f99d83..95b708f167 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineBaseTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineBaseTest.cs @@ -29,7 +29,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Examine.Lucene.UmbracoExamine protected IRuntimeState RunningRuntimeState { get; } = Mock.Of(x => x.Level == RuntimeLevel.Run); - protected override void ConfigureTestSpecificServices(IServiceCollection services) + protected override void ConfigureTestServices(IServiceCollection services) => services.AddSingleton(); /// diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopeTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopeTests.cs index 1fcb14f0f5..c4f2db58fe 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopeTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopeTests.cs @@ -31,7 +31,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Scoping public void SetUp() => Assert.IsNull(ScopeProvider.AmbientScope); // gone - protected override void ConfigureTestSpecificServices(IServiceCollection services) + protected override void ConfigureTestServices(IServiceCollection services) { // Need to have a mockable request cache for tests var appCaches = new AppCaches( diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopedRepositoryTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopedRepositoryTests.cs index f9a844817e..c228e07178 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopedRepositoryTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopedRepositoryTests.cs @@ -31,7 +31,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Scoping private ILocalizationService LocalizationService => GetRequiredService(); - protected override void ConfigureTestSpecificServices(IServiceCollection services) + protected override void ConfigureTestServices(IServiceCollection services) { // this is what's created core web runtime var appCaches = new AppCaches( From d4dca18cbe32a5579da505558139f25340660af8 Mon Sep 17 00:00:00 2001 From: Paul Johnson Date: Mon, 14 Feb 2022 14:43:33 +0000 Subject: [PATCH 140/141] Change web projects TargetFrameworkMoniker to 4.5.2 to stop VS 2022 err (cherry picked from commit 461043bd82568aa77476691981fdf3e0a8857ad9) --- umbraco.sln | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/umbraco.sln b/umbraco.sln index 0018c91041..49da6eb441 100644 --- a/umbraco.sln +++ b/umbraco.sln @@ -1,4 +1,4 @@ - + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.29209.152 @@ -38,7 +38,7 @@ EndProject Project("{E24C65DC-7377-472B-9ABA-BC803B73C61A}") = "Umbraco.Web.UI.Client", "http://localhost:3961", "{3819A550-DCEC-4153-91B4-8BA9F7F0B9B4}" ProjectSection(WebsiteProperties) = preProject UseIISExpress = "true" - TargetFrameworkMoniker = ".NETFramework,Version%3Dv4.5" + TargetFrameworkMoniker = ".NETFramework,Version%3Dv4.5.2" Debug.AspNetCompiler.VirtualPath = "/localhost_3961" Debug.AspNetCompiler.PhysicalPath = "src\Umbraco.Web.UI.Client\" Debug.AspNetCompiler.TargetPath = "PrecompiledWeb\localhost_3961\" @@ -61,7 +61,7 @@ EndProject Project("{E24C65DC-7377-472B-9ABA-BC803B73C61A}") = "Umbraco.Tests.AcceptanceTest", "http://localhost:58896", "{9E4C8A12-FBE0-4673-8CE2-DF99D5D57817}" ProjectSection(WebsiteProperties) = preProject UseIISExpress = "true" - TargetFrameworkMoniker = ".NETFramework,Version%3Dv4.5" + TargetFrameworkMoniker = ".NETFramework,Version%3Dv4.5.2" Debug.AspNetCompiler.VirtualPath = "/localhost_62926" Debug.AspNetCompiler.PhysicalPath = "tests\Umbraco.Tests.AcceptanceTest\" Debug.AspNetCompiler.TargetPath = "PrecompiledWeb\localhost_62926\" From 548870d59df5d9bbe242a0f9ae015fdfb572088d Mon Sep 17 00:00:00 2001 From: Paul Johnson Date: Mon, 14 Feb 2022 14:46:37 +0000 Subject: [PATCH 141/141] Fix whitespace in sln (cherry picked from commit a34e278a409eeca183624627ad284b2f762e8fbe) --- umbraco.sln | 1 - 1 file changed, 1 deletion(-) diff --git a/umbraco.sln b/umbraco.sln index 49da6eb441..497258c699 100644 --- a/umbraco.sln +++ b/umbraco.sln @@ -1,4 +1,3 @@ - Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.29209.152