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..8c0929382d
--- /dev/null
+++ b/.globalconfig
@@ -0,0 +1,82 @@
+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.SA1101.severity = none # PrefixLocalCallsWithThis - stylecop appears to be ignoring dotnet_style_qualification_for_*
+
+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/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/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/codeanalysis.ruleset b/codeanalysis.ruleset
deleted file mode 100644
index ab5ad88f57..0000000000
--- a/codeanalysis.ruleset
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
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
diff --git a/src/JsonSchema/AppSettings.cs b/src/JsonSchema/AppSettings.cs
index 048513a5da..73c5ea18f5 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;
@@ -88,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/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/ContentSettings.cs b/src/Umbraco.Core/Configuration/Models/ContentSettings.cs
index 1caa81d80a..93a97355d9 100644
--- a/src/Umbraco.Core/Configuration/Models/ContentSettings.cs
+++ b/src/Umbraco.Core/Configuration/Models/ContentSettings.cs
@@ -156,6 +156,7 @@ namespace Umbraco.Cms.Core.Configuration.Models
internal const bool StaticShowDeprecatedPropertyEditors = false;
internal const string StaticLoginBackgroundImage = "assets/img/login.jpg";
internal const string StaticLoginLogoImage = "assets/img/application/umbraco_logo_white.svg";
+ internal const bool StaticHideBackOfficeLogo = false;
///
/// Gets or sets a value for the content notification settings.
@@ -219,6 +220,12 @@ namespace Umbraco.Cms.Core.Configuration.Models
[DefaultValue(StaticLoginLogoImage)]
public string LoginLogoImage { get; set; } = StaticLoginLogoImage;
+ ///
+ /// Gets or sets a value indicating whether to hide the backoffice umbraco logo or not.
+ ///
+ [DefaultValue(StaticHideBackOfficeLogo)]
+ public bool HideBackOfficeLogo { get; set; } = StaticHideBackOfficeLogo;
+
///
/// Get or sets the model representing the global content version cleanup policy
///
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/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.Core/Configuration/Models/RequestHandlerSettings.cs b/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs
index 051c31dc26..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,6 +84,16 @@ 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. Scheduled for removal in V10.")]
+ public class CharItem : IChar
+ {
+ ///
+ public string Char { get; set; }
+
+ ///
+ public string Replacement { get; set; }
+ }
}
}
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.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 f0cbf7f95d..91e6f71415 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;
@@ -86,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/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/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/PropertyTagsExtensions.cs b/src/Umbraco.Core/Models/PropertyTagsExtensions.cs
index 390f644831..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;
@@ -68,11 +68,13 @@ namespace Umbraco.Extensions
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(serializer.Serialize(currentTags.Union(trimmedTags).ToArray()), culture); // json array
+ var updatedTags = currentTags.Union(trimmedTags).ToArray();
+ var updatedValue = updatedTags.Length == 0 ? null : serializer.Serialize(updatedTags);
+ property.SetValue(updatedValue, culture); // json array
break;
}
}
@@ -81,11 +83,12 @@ namespace Umbraco.Extensions
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(serializer.Serialize(trimmedTags), culture); // json array
+ var updatedValue = trimmedTags.Length == 0 ? null : serializer.Serialize(trimmedTags);
+ property.SetValue(updatedValue, culture); // json array
break;
}
}
@@ -124,11 +127,13 @@ namespace Umbraco.Extensions
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(serializer.Serialize(currentTags.Except(trimmedTags).ToArray()), culture); // json array
+ var updatedTags = currentTags.Except(trimmedTags).ToArray();
+ var updatedValue = updatedTags.Length == 0 ? null : serializer.Serialize(updatedTags);
+ property.SetValue(updatedValue, culture); // json array
break;
}
}
@@ -160,7 +165,7 @@ namespace Umbraco.Extensions
case TagsStorageType.Json:
try
{
- return serializer.Deserialize(value).Select(x => x.ToString().Trim());
+ return serializer.Deserialize(value).Select(x => x.Trim());
}
catch (Exception)
{
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/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/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/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.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/PropertyEditors/DataValueEditor.cs b/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs
index 07607be2b0..795d75c319 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;
@@ -6,7 +6,6 @@ using System.Linq;
using System.Runtime.Serialization;
using System.Xml.Linq;
using Microsoft.Extensions.Logging;
-using Umbraco.Cms.Core.Hosting;
using Umbraco.Cms.Core.IO;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Editors;
@@ -149,151 +148,164 @@ 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