diff --git a/.gitignore b/.gitignore index dc6f431550..b87d1df346 100644 --- a/.gitignore +++ b/.gitignore @@ -169,6 +169,7 @@ cypress.env.json /src/Umbraco.Tests.AcceptanceTest/package-lock.json /src/Umbraco.Tests.AcceptanceTest/cypress/videos/ /src/Umbraco.Tests.AcceptanceTest/cypress/screenshots/ +src/Umbraco.Web.UI/Umbraco/telemetrics-id.umb # eof diff --git a/build/NuSpecs/UmbracoCms.Web.nuspec b/build/NuSpecs/UmbracoCms.Web.nuspec index 2fea306ce0..fe5ef6344c 100644 --- a/build/NuSpecs/UmbracoCms.Web.nuspec +++ b/build/NuSpecs/UmbracoCms.Web.nuspec @@ -30,8 +30,8 @@ - - + + diff --git a/linting/codeanalysis.ruleset b/linting/codeanalysis.ruleset index 4fde2bef8d..57c9fb7d60 100644 --- a/linting/codeanalysis.ruleset +++ b/linting/codeanalysis.ruleset @@ -11,6 +11,8 @@ + + \ No newline at end of file diff --git a/src/Umbraco.Core/BackOffice/BackOfficeIdentityUser.cs b/src/Umbraco.Core/BackOffice/BackOfficeIdentityUser.cs deleted file mode 100644 index 027e7c0904..0000000000 --- a/src/Umbraco.Core/BackOffice/BackOfficeIdentityUser.cs +++ /dev/null @@ -1,449 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Linq; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.Models; -using Umbraco.Core.Models.Entities; -using Umbraco.Core.Models.Identity; -using Umbraco.Core.Models.Membership; - -namespace Umbraco.Core.BackOffice -{ - public class BackOfficeIdentityUser : IdentityUser, IdentityUserClaim>, IRememberBeingDirty - { - private string _email; - private string _userName; - private int _id; - private bool _hasIdentity; - private DateTime? _lastLoginDateUtc; - private bool _emailConfirmed; - private string _name; - private int _accessFailedCount; - private string _passwordHash; - private string _passwordConfig; - private string _culture; - private ObservableCollection _logins; - private Lazy> _getLogins; - private IReadOnlyUserGroup[] _groups; - private string[] _allowedSections; - private int[] _startMediaIds; - private int[] _startContentIds; - private DateTime? _lastPasswordChangeDateUtc; - - /// - /// Used to construct a new instance without an identity - /// - /// - /// This is allowed to be null (but would need to be filled in if trying to persist this instance) - /// - /// - public static BackOfficeIdentityUser CreateNew(GlobalSettings globalSettings, string username, string email, string culture, string name = null) - { - if (string.IsNullOrWhiteSpace(username)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(username)); - if (string.IsNullOrWhiteSpace(culture)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(culture)); - - var user = new BackOfficeIdentityUser(globalSettings, Array.Empty()); - user.DisableChangeTracking(); - user._userName = username; - user._email = email; - //we are setting minvalue here because the default is "0" which is the id of the admin user - //which we cannot allow because the admin user will always exist - user._id = int.MinValue; - user._hasIdentity = false; - user._culture = culture; - user._name = name; - user.EnableChangeTracking(); - return user; - } - - private BackOfficeIdentityUser(GlobalSettings globalSettings, IReadOnlyUserGroup[] groups) - { - _startMediaIds = Array.Empty(); - _startContentIds = Array.Empty(); - _allowedSections = Array.Empty(); - _culture = globalSettings.DefaultUILanguage; - - // must initialize before setting groups - _roles = new ObservableCollection>(); - _roles.CollectionChanged += _roles_CollectionChanged; - - // use the property setters - they do more than just setting a field - Groups = groups; - } - - /// - /// Creates an existing user with the specified groups - /// - /// - /// - /// - public BackOfficeIdentityUser(GlobalSettings globalSettings, int userId, IEnumerable groups) - : this(globalSettings, groups.ToArray()) - { - // use the property setters - they do more than just setting a field - Id = userId; - } - - /// - /// Returns true if an Id has been set on this object this will be false if the object is new and not persisted to the database - /// - public bool HasIdentity => _hasIdentity; - - public int[] CalculatedMediaStartNodeIds { get; set; } - public int[] CalculatedContentStartNodeIds { get; set; } - - public override int Id - { - get => _id; - set - { - _id = value; - _hasIdentity = true; - } - } - - /// - /// Override Email so we can track changes to it - /// - public override string Email - { - get => _email; - set => _beingDirty.SetPropertyValueAndDetectChanges(value, ref _email, nameof(Email)); - } - - /// - /// Override UserName so we can track changes to it - /// - public override string UserName - { - get => _userName; - set => _beingDirty.SetPropertyValueAndDetectChanges(value, ref _userName, nameof(UserName)); - } - - /// - /// LastPasswordChangeDateUtc so we can track changes to it - /// - public override DateTime? LastPasswordChangeDateUtc - { - get { return _lastPasswordChangeDateUtc; } - set { _beingDirty.SetPropertyValueAndDetectChanges(value, ref _lastPasswordChangeDateUtc, nameof(LastPasswordChangeDateUtc)); } - } - - /// - /// Override LastLoginDateUtc so we can track changes to it - /// - public override DateTime? LastLoginDateUtc - { - get => _lastLoginDateUtc; - set => _beingDirty.SetPropertyValueAndDetectChanges(value, ref _lastLoginDateUtc, nameof(LastLoginDateUtc)); - } - - /// - /// Override EmailConfirmed so we can track changes to it - /// - public override bool EmailConfirmed - { - get => _emailConfirmed; - set => _beingDirty.SetPropertyValueAndDetectChanges(value, ref _emailConfirmed, nameof(EmailConfirmed)); - } - - /// - /// Gets/sets the user's real name - /// - public string Name - { - get => _name; - set => _beingDirty.SetPropertyValueAndDetectChanges(value, ref _name, nameof(Name)); - } - - /// - /// Override AccessFailedCount so we can track changes to it - /// - public override int AccessFailedCount - { - get => _accessFailedCount; - set => _beingDirty.SetPropertyValueAndDetectChanges(value, ref _accessFailedCount, nameof(AccessFailedCount)); - } - - /// - /// Override PasswordHash so we can track changes to it - /// - public override string PasswordHash - { - get => _passwordHash; - set => _beingDirty.SetPropertyValueAndDetectChanges(value, ref _passwordHash, nameof(PasswordHash)); - } - - public string PasswordConfig - { - get => _passwordConfig; - set => _beingDirty.SetPropertyValueAndDetectChanges(value, ref _passwordConfig, nameof(PasswordConfig)); - } - - - /// - /// Content start nodes assigned to the User (not ones assigned to the user's groups) - /// - public int[] StartContentIds - { - get => _startContentIds; - set - { - if (value == null) value = new int[0]; - _beingDirty.SetPropertyValueAndDetectChanges(value, ref _startContentIds, nameof(StartContentIds), StartIdsComparer); - } - } - - /// - /// Media start nodes assigned to the User (not ones assigned to the user's groups) - /// - public int[] StartMediaIds - { - get => _startMediaIds; - set - { - if (value == null) value = new int[0]; - _beingDirty.SetPropertyValueAndDetectChanges(value, ref _startMediaIds, nameof(StartMediaIds), StartIdsComparer); - } - } - - /// - /// This is a readonly list of the user's allowed sections which are based on it's user groups - /// - public string[] AllowedSections - { - get { return _allowedSections ?? (_allowedSections = _groups.SelectMany(x => x.AllowedSections).Distinct().ToArray()); } - } - - public string Culture - { - get => _culture; - set => _beingDirty.SetPropertyValueAndDetectChanges(value, ref _culture, nameof(Culture)); - } - - public IReadOnlyUserGroup[] Groups - { - get => _groups; - set - { - //so they recalculate - _allowedSections = null; - - _groups = value; - - //now clear all roles and re-add them - _roles.CollectionChanged -= _roles_CollectionChanged; - _roles.Clear(); - foreach (var identityUserRole in _groups.Select(x => new IdentityUserRole - { - RoleId = x.Alias, - UserId = Id.ToString() - })) - { - _roles.Add(identityUserRole); - } - _roles.CollectionChanged += _roles_CollectionChanged; - - _beingDirty.SetPropertyValueAndDetectChanges(value, ref _groups, nameof(Groups), GroupsComparer); - } - } - - /// - /// Lockout is always enabled - /// - public override bool LockoutEnabled - { - get { return true; } - set - { - //do nothing - } - } - - /// - /// Based on the user's lockout end date, this will determine if they are locked out - /// - public bool IsLockedOut - { - get - { - var isLocked = LockoutEndDateUtc.HasValue && LockoutEndDateUtc.Value.ToLocalTime() >= DateTime.Now; - return isLocked; - } - } - - /// - /// This is a 1:1 mapping with IUser.IsApproved - /// - public bool IsApproved { get; set; } - - /// - /// Overridden to make the retrieval lazy - /// - public override ICollection Logins - { - get - { - // return if it exists - if (_logins != null) return _logins; - - _logins = new ObservableCollection(); - - // if the callback is there and hasn't been created yet then execute it and populate the logins - if (_getLogins != null && !_getLogins.IsValueCreated) - { - foreach (var l in _getLogins.Value) - { - _logins.Add(l); - } - } - - //now assign events - _logins.CollectionChanged += Logins_CollectionChanged; - - return _logins; - } - } - - void Logins_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) - { - _beingDirty.OnPropertyChanged(nameof(Logins)); - } - - private void _roles_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) - { - _beingDirty.OnPropertyChanged(nameof(Roles)); - } - - private readonly ObservableCollection> _roles; - - /// - /// helper method to easily add a role without having to deal with IdentityUserRole{T} - /// - /// - /// - /// Adding a role this way will not reflect on the user's group's collection or it's allowed sections until the user is persisted - /// - public void AddRole(string role) - { - Roles.Add(new IdentityUserRole - { - UserId = Id.ToString(), - RoleId = role - }); - } - - /// - /// Override Roles because the value of these are the user's group aliases - /// - public override ICollection> Roles => _roles; - - /// - /// Used to set a lazy call back to populate the user's Login list - /// - /// - public void SetLoginsCallback(Lazy> callback) - { - _getLogins = callback ?? throw new ArgumentNullException(nameof(callback)); - } - - #region BeingDirty - - private readonly BeingDirty _beingDirty = new BeingDirty(); - - /// - public bool IsDirty() - { - return _beingDirty.IsDirty(); - } - - /// - public bool IsPropertyDirty(string propName) - { - return _beingDirty.IsPropertyDirty(propName); - } - - /// - public IEnumerable GetDirtyProperties() - { - return _beingDirty.GetDirtyProperties(); - } - - /// - public void ResetDirtyProperties() - { - _beingDirty.ResetDirtyProperties(); - } - - /// - public bool WasDirty() - { - return _beingDirty.WasDirty(); - } - - /// - public bool WasPropertyDirty(string propertyName) - { - return _beingDirty.WasPropertyDirty(propertyName); - } - - /// - public void ResetWereDirtyProperties() - { - _beingDirty.ResetWereDirtyProperties(); - } - - /// - public void ResetDirtyProperties(bool rememberDirty) - { - _beingDirty.ResetDirtyProperties(rememberDirty); - } - - /// - public IEnumerable GetWereDirtyProperties() - => _beingDirty.GetWereDirtyProperties(); - - /// - /// Disables change tracking. - /// - public void DisableChangeTracking() - { - _beingDirty.DisableChangeTracking(); - } - - /// - /// Enables change tracking. - /// - public void EnableChangeTracking() - { - _beingDirty.EnableChangeTracking(); - } - - public event PropertyChangedEventHandler PropertyChanged - { - add - { - _beingDirty.PropertyChanged += value; - } - remove - { - _beingDirty.PropertyChanged -= value; - } - } - - #endregion - - //Custom comparer for enumerables - private static readonly DelegateEqualityComparer GroupsComparer = new DelegateEqualityComparer( - (groups, enumerable) => groups.Select(x => x.Alias).UnsortedSequenceEqual(enumerable.Select(x => x.Alias)), - groups => groups.GetHashCode()); - - private static readonly DelegateEqualityComparer StartIdsComparer = new DelegateEqualityComparer( - (groups, enumerable) => groups.UnsortedSequenceEqual(enumerable), - groups => groups.GetHashCode()); - - } -} diff --git a/src/Umbraco.Core/Configuration/IPasswordConfiguration.cs b/src/Umbraco.Core/Configuration/IPasswordConfiguration.cs index 6a5fd8e73f..0c143f22e6 100644 --- a/src/Umbraco.Core/Configuration/IPasswordConfiguration.cs +++ b/src/Umbraco.Core/Configuration/IPasswordConfiguration.cs @@ -1,19 +1,49 @@ -namespace Umbraco.Core.Configuration -{ +// Copyright (c) Umbraco. +// See LICENSE for more details. +namespace Umbraco.Core.Configuration +{ /// /// Password configuration /// public interface IPasswordConfiguration { + /// + /// Gets a value for the minimum required length for the password. + /// int RequiredLength { get; } + + /// + /// Gets a value indicating whether at least one non-letter or digit is required for the password. + /// bool RequireNonLetterOrDigit { get; } + + /// + /// Gets a value indicating whether at least one digit is required for the password. + /// bool RequireDigit { get; } + + /// + /// Gets a value indicating whether at least one lower-case character is required for the password. + /// bool RequireLowercase { get; } + + /// + /// Gets a value indicating whether at least one upper-case character is required for the password. + /// bool RequireUppercase { get; } + + /// + /// Gets a value for the password hash algorithm type. + /// string HashAlgorithmType { get; } - // TODO: This doesn't really belong here + /// + /// Gets a value for the maximum failed access attempts before lockout. + /// + /// + /// TODO: This doesn't really belong here + /// int MaxFailedAccessAttemptsBeforeLockout { get; } } } diff --git a/src/Umbraco.Core/Configuration/Models/ActiveDirectorySettings.cs b/src/Umbraco.Core/Configuration/Models/ActiveDirectorySettings.cs index 0fe541416a..135b8b763c 100644 --- a/src/Umbraco.Core/Configuration/Models/ActiveDirectorySettings.cs +++ b/src/Umbraco.Core/Configuration/Models/ActiveDirectorySettings.cs @@ -1,7 +1,16 @@ -namespace Umbraco.Core.Configuration.Models +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Core.Configuration.Models { + /// + /// Typed configuration options for active directory settings. + /// public class ActiveDirectorySettings { + /// + /// Gets or sets a value for the Active Directory domain. + /// public string Domain { get; set; } } } diff --git a/src/Umbraco.Core/Configuration/Models/ConnectionStrings.cs b/src/Umbraco.Core/Configuration/Models/ConnectionStrings.cs index d21cba4486..52b8a02478 100644 --- a/src/Umbraco.Core/Configuration/Models/ConnectionStrings.cs +++ b/src/Umbraco.Core/Configuration/Models/ConnectionStrings.cs @@ -1,19 +1,30 @@ -using System.Collections.Generic; +// Copyright (c) Umbraco. +// See LICENSE for more details. namespace Umbraco.Core.Configuration.Models { + /// + /// Typed configuration options for connection strings. + /// public class ConnectionStrings { // Backing field for UmbracoConnectionString to load from configuration value with key umbracoDbDSN. // Attributes cannot be applied to map from keys that don't match, and have chosen to retain the key name // used in configuration for older Umbraco versions. // See: https://stackoverflow.com/a/54607296/489433 +#pragma warning disable SA1300 // Element should begin with upper-case letter +#pragma warning disable IDE1006 // Naming Styles private string umbracoDbDSN +#pragma warning restore IDE1006 // Naming Styles +#pragma warning restore SA1300 // Element should begin with upper-case letter { get => UmbracoConnectionString?.ConnectionString; set => UmbracoConnectionString = new ConfigConnectionString(Constants.System.UmbracoConnectionName, value); } + /// + /// Gets or sets a value for the Umbraco database connection string.. + /// public ConfigConnectionString UmbracoConnectionString { get; set; } } } diff --git a/src/Umbraco.Core/Configuration/Models/ContentErrorPage.cs b/src/Umbraco.Core/Configuration/Models/ContentErrorPage.cs index 6a362c93a3..842b2e6fb7 100644 --- a/src/Umbraco.Core/Configuration/Models/ContentErrorPage.cs +++ b/src/Umbraco.Core/Configuration/Models/ContentErrorPage.cs @@ -1,30 +1,55 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.ComponentModel.DataAnnotations; using Umbraco.Core.Configuration.Models.Validation; namespace Umbraco.Core.Configuration.Models { + /// + /// Typed configuration for a content error page. + /// public class ContentErrorPage : ValidatableEntryBase { + /// + /// Gets or sets a value for the content Id. + /// public int ContentId { get; set; } + /// + /// Gets or sets a value for the content key. + /// public Guid ContentKey { get; set; } + /// + /// Gets or sets a value for the content XPath. + /// public string ContentXPath { get; set; } + /// + /// Gets a value indicating whether the field is populated. + /// public bool HasContentId => ContentId != 0; + /// + /// Gets a value indicating whether the field is populated. + /// public bool HasContentKey => ContentKey != Guid.Empty; + /// + /// Gets a value indicating whether the field is populated. + /// public bool HasContentXPath => !string.IsNullOrEmpty(ContentXPath); + /// + /// Gets or sets a value for the content culture. + /// [Required] public string Culture { get; set; } - internal override bool IsValid() - { - return base.IsValid() && + internal override bool IsValid() => + base.IsValid() && ((HasContentId ? 1 : 0) + (HasContentKey ? 1 : 0) + (HasContentXPath ? 1 : 0) == 1); - } } } diff --git a/src/Umbraco.Core/Configuration/Models/ContentImagingSettings.cs b/src/Umbraco.Core/Configuration/Models/ContentImagingSettings.cs index 7c1e570426..d31c5cb6d7 100644 --- a/src/Umbraco.Core/Configuration/Models/ContentImagingSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/ContentImagingSettings.cs @@ -1,9 +1,15 @@ -namespace Umbraco.Core.Configuration.Models +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Core.Configuration.Models { + /// + /// Typed configuration options for content imaging settings. + /// public class ContentImagingSettings { - private static readonly ImagingAutoFillUploadField[] DefaultImagingAutoFillUploadField = -{ + private static readonly ImagingAutoFillUploadField[] s_defaultImagingAutoFillUploadField = + { new ImagingAutoFillUploadField { Alias = Constants.Conventions.Media.File, @@ -14,8 +20,14 @@ } }; + /// + /// Gets or sets a value for the collection of accepted image file extensions. + /// public string[] ImageFileTypes { get; set; } = new[] { "jpeg", "jpg", "gif", "bmp", "png", "tiff", "tif" }; - public ImagingAutoFillUploadField[] AutoFillImageProperties { get; set; } = DefaultImagingAutoFillUploadField; + /// + /// Gets or sets a value for the imaging autofill following media file upload fields. + /// + public ImagingAutoFillUploadField[] AutoFillImageProperties { get; set; } = s_defaultImagingAutoFillUploadField; } } diff --git a/src/Umbraco.Core/Configuration/Models/ContentNotificationSettings.cs b/src/Umbraco.Core/Configuration/Models/ContentNotificationSettings.cs index ab1c10ff77..48a131adfa 100644 --- a/src/Umbraco.Core/Configuration/Models/ContentNotificationSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/ContentNotificationSettings.cs @@ -1,9 +1,21 @@ -namespace Umbraco.Core.Configuration.Models +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Core.Configuration.Models { + /// + /// Typed configuration options for content notification settings. + /// public class ContentNotificationSettings { + /// + /// Gets or sets a value for the email address for notifications. + /// public string Email { get; set; } + /// + /// Gets or sets a value indicating whether HTML email notifications should be disabled. + /// public bool DisableHtmlEmail { get; set; } = false; } } diff --git a/src/Umbraco.Core/Configuration/Models/ContentSettings.cs b/src/Umbraco.Core/Configuration/Models/ContentSettings.cs index c26e6d403a..55881cd8db 100644 --- a/src/Umbraco.Core/Configuration/Models/ContentSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/ContentSettings.cs @@ -1,10 +1,15 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Collections.Generic; -using Umbraco.Core.Configuration.Models.Validation; using Umbraco.Core.Macros; namespace Umbraco.Core.Configuration.Models { + /// + /// Typed configuration options for content settings. + /// public class ContentSettings { private const string DefaultPreviewBadge = @@ -142,26 +147,54 @@ namespace Umbraco.Core.Configuration.Models "; + /// + /// Gets or sets a value for the content notification settings. + /// public ContentNotificationSettings Notifications { get; set; } = new ContentNotificationSettings(); + /// + /// Gets or sets a value for the content imaging settings. + /// public ContentImagingSettings Imaging { get; set; } = new ContentImagingSettings(); + /// + /// Gets or sets a value indicating whether URLs should be resolved from text strings. + /// public bool ResolveUrlsFromTextString { get; set; } = false; + /// + /// Gets or sets a value for the collection of error pages. + /// public ContentErrorPage[] Error404Collection { get; set; } = Array.Empty(); + /// + /// Gets or sets a value for the preview badge mark-up. + /// public string PreviewBadge { get; set; } = DefaultPreviewBadge; + /// + /// Gets or sets a value for the macro error behaviour. + /// public MacroErrorBehaviour MacroErrors { get; set; } = MacroErrorBehaviour.Inline; + /// + /// Gets or sets a value for the collection of file extensions that are disallowed for upload. + /// public IEnumerable DisallowedUploadFiles { get; set; } = new[] { "ashx", "aspx", "ascx", "config", "cshtml", "vbhtml", "asmx", "air", "axd" }; + /// + /// Gets or sets a value for the collection of file extensions that are allowed for upload. + /// public IEnumerable AllowedUploadFiles { get; set; } = Array.Empty(); + /// + /// Gets or sets a value indicating whether deprecated property editors should be shown. + /// public bool ShowDeprecatedPropertyEditors { get; set; } = false; + /// + /// Gets or sets a value for the pate to the login screen background image. + /// public string LoginBackgroundImage { get; set; } = "assets/img/login.jpg"; - - } } diff --git a/src/Umbraco.Core/Configuration/Models/CoreDebugSettings.cs b/src/Umbraco.Core/Configuration/Models/CoreDebugSettings.cs index 2b13609509..a263fb648a 100644 --- a/src/Umbraco.Core/Configuration/Models/CoreDebugSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/CoreDebugSettings.cs @@ -1,9 +1,21 @@ -namespace Umbraco.Core.Configuration.Models +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Core.Configuration.Models { + /// + /// Typed configuration options for core debug settings. + /// public class CoreDebugSettings { - public bool LogUncompletedScopes { get; set; } = false; + /// + /// Gets or sets a value indicating whether incompleted scopes should be logged. + /// + public bool LogIncompletedScopes { get; set; } = false; + /// + /// Gets or sets a value indicating whether memory dumps on thread abort should be taken. + /// public bool DumpOnTimeoutThreadAbort { get; set; } = false; } } diff --git a/src/Umbraco.Core/Configuration/Models/DatabaseServerMessengerSettings.cs b/src/Umbraco.Core/Configuration/Models/DatabaseServerMessengerSettings.cs index 32b957d5d0..8ad87bbb4e 100644 --- a/src/Umbraco.Core/Configuration/Models/DatabaseServerMessengerSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/DatabaseServerMessengerSettings.cs @@ -1,26 +1,32 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; namespace Umbraco.Core.Configuration.Models { + /// + /// Typed configuration options for database server messaging settings. + /// public class DatabaseServerMessengerSettings { /// - /// The maximum number of instructions that can be processed at startup; otherwise the server cold-boots (rebuilds its caches). + /// Gets or sets a value for the maximum number of instructions that can be processed at startup; otherwise the server cold-boots (rebuilds its caches). /// public int MaxProcessingInstructionCount { get; set; } = 1000; /// - /// The time to keep instructions in the database; records older than this number will be pruned. + /// Gets or sets a value for the time to keep instructions in the database; records older than this number will be pruned. /// public TimeSpan TimeToRetainInstructions { get; set; } = TimeSpan.FromDays(2); /// - /// The time to wait between each sync operations. + /// Gets or sets a value for the time to wait between each sync operations. /// public TimeSpan TimeBetweenSyncOperations { get; set; } = TimeSpan.FromSeconds(5); /// - /// The time to wait between each prune operations. + /// Gets or sets a value for the time to wait between each prune operations. /// public TimeSpan TimeBetweenPruneOperations { get; set; } = TimeSpan.FromMinutes(1); } diff --git a/src/Umbraco.Core/Configuration/Models/DatabaseServerRegistrarSettings.cs b/src/Umbraco.Core/Configuration/Models/DatabaseServerRegistrarSettings.cs index 1aa670fae6..ae2502d8af 100644 --- a/src/Umbraco.Core/Configuration/Models/DatabaseServerRegistrarSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/DatabaseServerRegistrarSettings.cs @@ -1,16 +1,22 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; namespace Umbraco.Core.Configuration.Models { + /// + /// Typed configuration options for database server registrar settings. + /// public class DatabaseServerRegistrarSettings { /// - /// The amount of time to wait between calls to the database on the background thread. + /// Gets or sets a value for the amount of time to wait between calls to the database on the background thread. /// public TimeSpan WaitTimeBetweenCalls { get; set; } = TimeSpan.FromMinutes(1); /// - /// The time span to wait before considering a server stale, after it has last been accessed. + /// Gets or sets a value for the time span to wait before considering a server stale, after it has last been accessed. /// public TimeSpan StaleServerTimeout { get; set; } = TimeSpan.FromMinutes(2); } diff --git a/src/Umbraco.Core/Configuration/Models/DisabledHealthCheckSettings.cs b/src/Umbraco.Core/Configuration/Models/DisabledHealthCheckSettings.cs index 1d96a9027f..38c71fd83f 100644 --- a/src/Umbraco.Core/Configuration/Models/DisabledHealthCheckSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/DisabledHealthCheckSettings.cs @@ -1,13 +1,28 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; namespace Umbraco.Core.Configuration.Models { + /// + /// Typed configuration options for disabled healthcheck settings. + /// public class DisabledHealthCheckSettings { + /// + /// Gets or sets a value for the healthcheck Id to disable. + /// public Guid Id { get; set; } + /// + /// Gets or sets a value for the date the healthcheck was disabled. + /// public DateTime DisabledOn { get; set; } + /// + /// Gets or sets a value for Id of the user that disabled the healthcheck. + /// public int DisabledBy { get; set; } } } diff --git a/src/Umbraco.Core/Configuration/Models/ExceptionFilterSettings.cs b/src/Umbraco.Core/Configuration/Models/ExceptionFilterSettings.cs index 6b8f74bef0..1a1362ff21 100644 --- a/src/Umbraco.Core/Configuration/Models/ExceptionFilterSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/ExceptionFilterSettings.cs @@ -1,7 +1,16 @@ -namespace Umbraco.Core.Configuration.Models +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Core.Configuration.Models { + /// + /// Typed configuration options for exception filter settings. + /// public class ExceptionFilterSettings { + /// + /// Gets or sets a value indicating whether the exception filter is disabled. + /// public bool Disabled { get; set; } = false; } } diff --git a/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs b/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs index b6e5cf2b4d..eb13427d2b 100644 --- a/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs @@ -1,68 +1,141 @@ -namespace Umbraco.Core.Configuration.Models +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Core.Configuration.Models { /// - /// The GlobalSettings Class contains general settings information for the entire Umbraco instance based on information - /// from web.config appsettings + /// Typed configuration options for global settings. /// public class GlobalSettings { internal const string - StaticReservedPaths = "~/app_plugins/,~/install/,~/mini-profiler-resources/,~/umbraco/,"; //must end with a comma! + StaticReservedPaths = "~/app_plugins/,~/install/,~/mini-profiler-resources/,~/umbraco/,"; // must end with a comma! internal const string - StaticReservedUrls = "~/config/splashes/noNodes.aspx,~/.well-known,"; //must end with a comma! + StaticReservedUrls = "~/config/splashes/noNodes.aspx,~/.well-known,"; // must end with a comma! + /// + /// Gets or sets a value for the reserved URLs. + /// public string ReservedUrls { get; set; } = StaticReservedUrls; + /// + /// Gets or sets a value for the reserved paths. + /// public string ReservedPaths { get; set; } = StaticReservedPaths; - // TODO: https://github.com/umbraco/Umbraco-CMS/issues/4238 - stop having version in web.config appSettings - // TODO: previously this would throw on set, but presumably we can't do that if we do still want this in config. + /// + /// Gets or sets a value for the configuration status. + /// + /// + /// TODO: https://github.com/umbraco/Umbraco-CMS/issues/4238 - stop having version in web.config appSettings + /// TODO: previously this would throw on set, but presumably we can't do that if we do still want this in config. + /// public string ConfigurationStatus { get; set; } + /// + /// Gets or sets a value for the timeout in minutes. + /// public int TimeOutInMinutes { get; set; } = 20; + /// + /// Gets or sets a value for the default UI language. + /// public string DefaultUILanguage { get; set; } = "en-US"; + /// + /// Gets or sets a value indicating whether to hide the top level node from the path. + /// public bool HideTopLevelNodeFromPath { get; set; } = false; + /// + /// Gets or sets a value indicating whether HTTPS should be used. + /// public bool UseHttps { get; set; } = false; + /// + /// Gets or sets a value for the version check period in days. + /// public int VersionCheckPeriod { get; set; } = 7; + /// + /// Gets or sets a value for the Umbraco back-office path. + /// public string UmbracoPath { get; set; } = "~/umbraco"; - // TODO: Umbraco cannot be hard coded here that is what UmbracoPath is for - // so this should not be a normal get set it has to have dynamic ability to return the correct - // path given UmbracoPath if this hasn't been explicitly set. + /// + /// Gets or sets a value for the Umbraco icons path. + /// + /// + /// TODO: Umbraco cannot be hard coded here that is what UmbracoPath is for + /// so this should not be a normal get set it has to have dynamic ability to return the correct + /// path given UmbracoPath if this hasn't been explicitly set. + /// public string IconsPath { get; set; } = $"~/umbraco/assets/icons"; + /// + /// Gets or sets a value for the Umbraco CSS path. + /// public string UmbracoCssPath { get; set; } = "~/css"; + /// + /// Gets or sets a value for the Umbraco scripts path. + /// public string UmbracoScriptsPath { get; set; } = "~/scripts"; + /// + /// Gets or sets a value for the Umbraco media path. + /// public string UmbracoMediaPath { get; set; } = "~/media"; + /// + /// Gets or sets a value indicating whether to install the database when it is missing. + /// public bool InstallMissingDatabase { get; set; } = false; + /// + /// Gets or sets a value indicating whether to install the database when it is empty. + /// public bool InstallEmptyDatabase { get; set; } = false; + /// + /// Gets or sets a value indicating whether to disable the election for a single server. + /// public bool DisableElectionForSingleServer { get; set; } = false; - public DatabaseServerRegistrarSettings DatabaseServerRegistrar { get; set; } = new DatabaseServerRegistrarSettings(); - - public DatabaseServerMessengerSettings DatabaseServerMessenger { get; set; } = new DatabaseServerMessengerSettings(); - - public string RegisterType { get; set; } = string.Empty; - + /// + /// Gets or sets a value for the database factory server version. + /// public string DatabaseFactoryServerVersion { get; set; } = string.Empty; + /// + /// Gets or sets a value for the main dom lock. + /// public string MainDomLock { get; set; } = string.Empty; + /// + /// Gets or sets a value for the path to the no content view. + /// public string NoNodesViewPath { get; set; } = "~/umbraco/UmbracoWebsite/NoNodes.cshtml"; - public bool IsSmtpServerConfigured => !string.IsNullOrWhiteSpace(Smtp?.Host); + /// + /// Gets or sets a value for the database server registrar settings. + /// + public DatabaseServerRegistrarSettings DatabaseServerRegistrar { get; set; } = new DatabaseServerRegistrarSettings(); + /// + /// Gets or sets a value for the database server messenger settings. + /// + public DatabaseServerMessengerSettings DatabaseServerMessenger { get; set; } = new DatabaseServerMessengerSettings(); + + /// + /// Gets or sets a value for the SMTP settings. + /// public SmtpSettings Smtp { get; set; } + + /// + /// Gets a value indicating whether SMTP is configured. + /// + public bool IsSmtpServerConfigured => !string.IsNullOrWhiteSpace(Smtp?.Host); } } diff --git a/src/Umbraco.Core/Configuration/Models/HealthChecksNotificationMethodSettings.cs b/src/Umbraco.Core/Configuration/Models/HealthChecksNotificationMethodSettings.cs index 1cfc4f9168..b4bffab4d6 100644 --- a/src/Umbraco.Core/Configuration/Models/HealthChecksNotificationMethodSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/HealthChecksNotificationMethodSettings.cs @@ -1,16 +1,34 @@ -using System.Collections.Generic; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; using Umbraco.Core.HealthCheck; namespace Umbraco.Core.Configuration.Models { + /// + /// Typed configuration options for healthcheck notification method settings. + /// public class HealthChecksNotificationMethodSettings { + /// + /// Gets or sets a value indicating whether the health check notification method is enabled. + /// public bool Enabled { get; set; } = false; + /// + /// Gets or sets a value for the health check notifications reporting verbosity. + /// public HealthCheckNotificationVerbosity Verbosity { get; set; } = HealthCheckNotificationVerbosity.Summary; + /// + /// Gets or sets a value indicating whether the health check notifications should occur on failures only. + /// public bool FailureOnly { get; set; } = false; + /// + /// Gets or sets a value providing provider specific settings for the health check notification method. + /// public IDictionary Settings { get; set; } = new Dictionary(); } } diff --git a/src/Umbraco.Core/Configuration/Models/HealthChecksNotificationSettings.cs b/src/Umbraco.Core/Configuration/Models/HealthChecksNotificationSettings.cs index 052b5a4997..3e52a70b29 100644 --- a/src/Umbraco.Core/Configuration/Models/HealthChecksNotificationSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/HealthChecksNotificationSettings.cs @@ -1,19 +1,40 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Collections.Generic; using System.Linq; namespace Umbraco.Core.Configuration.Models { + /// + /// Typed configuration options for healthcheck notification settings. + /// public class HealthChecksNotificationSettings { + /// + /// Gets or sets a value indicating whether health check notifications are enabled. + /// public bool Enabled { get; set; } = false; + /// + /// Gets or sets a value for the first run time of a healthcheck notification in crontab format. + /// public string FirstRunTime { get; set; } = string.Empty; + /// + /// Gets or sets a value for the period of the healthcheck notification. + /// public TimeSpan Period { get; set; } = TimeSpan.FromHours(24); + /// + /// Gets or sets a value for the collection of health check notification methods. + /// public IDictionary NotificationMethods { get; set; } = new Dictionary(); + /// + /// Gets or sets a value for the collection of health checks that are disabled for notifications. + /// public IEnumerable DisabledChecks { get; set; } = Enumerable.Empty(); } } diff --git a/src/Umbraco.Core/Configuration/Models/HealthChecksSettings.cs b/src/Umbraco.Core/Configuration/Models/HealthChecksSettings.cs index 705611b0c1..e3ae9a3f96 100644 --- a/src/Umbraco.Core/Configuration/Models/HealthChecksSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/HealthChecksSettings.cs @@ -1,12 +1,24 @@ -using System.Collections.Generic; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; using System.Linq; namespace Umbraco.Core.Configuration.Models { + /// + /// Typed configuration options for healthchecks settings. + /// public class HealthChecksSettings { + /// + /// Gets or sets a value for the collection of healthchecks that are disabled. + /// public IEnumerable DisabledChecks { get; set; } = Enumerable.Empty(); + /// + /// Gets or sets a value for the healthcheck notification settings. + /// public HealthChecksNotificationSettings Notification { get; set; } = new HealthChecksNotificationSettings(); } } diff --git a/src/Umbraco.Core/Configuration/Models/HostingSettings.cs b/src/Umbraco.Core/Configuration/Models/HostingSettings.cs index b003315c56..0478e9b7e1 100644 --- a/src/Umbraco.Core/Configuration/Models/HostingSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/HostingSettings.cs @@ -1,18 +1,25 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. namespace Umbraco.Core.Configuration.Models { + /// + /// Typed configuration options for hosting settings. + /// public class HostingSettings { + /// + /// Gets or sets a value for the application virtual path. + /// public string ApplicationVirtualPath { get; set; } /// - /// Gets the configuration for the location of temporary files. + /// Gets or sets a value for the location of temporary files. /// public LocalTempStorage LocalTempStorageLocation { get; set; } = LocalTempStorage.Default; /// - /// Gets a value indicating whether umbraco is running in [debug mode]. + /// Gets or sets a value indicating whether umbraco is running in [debug mode]. /// /// true if [debug mode]; otherwise, false. public bool Debug { get; set; } = false; diff --git a/src/Umbraco.Core/Configuration/Models/ImagingAutoFillUploadField.cs b/src/Umbraco.Core/Configuration/Models/ImagingAutoFillUploadField.cs index f58e2bb4f8..999bcf2dff 100644 --- a/src/Umbraco.Core/Configuration/Models/ImagingAutoFillUploadField.cs +++ b/src/Umbraco.Core/Configuration/Models/ImagingAutoFillUploadField.cs @@ -1,22 +1,43 @@ -using System.ComponentModel.DataAnnotations; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.ComponentModel.DataAnnotations; using Umbraco.Core.Configuration.Models.Validation; namespace Umbraco.Core.Configuration.Models { + /// + /// Typed configuration options for image autofill upload settings. + /// public class ImagingAutoFillUploadField : ValidatableEntryBase { + /// + /// Gets or sets a value for the alias of the image upload property. + /// [Required] public string Alias { get; set; } + /// + /// Gets or sets a value for the width field alias of the image upload property. + /// [Required] public string WidthFieldAlias { get; set; } + /// + /// Gets or sets a value for the height field alias of the image upload property. + /// [Required] public string HeightFieldAlias { get; set; } + /// + /// Gets or sets a value for the length field alias of the image upload property. + /// [Required] public string LengthFieldAlias { get; set; } + /// + /// Gets or sets a value for the extension field alias of the image upload property. + /// [Required] public string ExtensionFieldAlias { get; set; } } diff --git a/src/Umbraco.Core/Configuration/Models/ImagingCacheSettings.cs b/src/Umbraco.Core/Configuration/Models/ImagingCacheSettings.cs index 44f5ae89b6..0a3e723722 100644 --- a/src/Umbraco.Core/Configuration/Models/ImagingCacheSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/ImagingCacheSettings.cs @@ -1,17 +1,34 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.IO; -using System.Threading; namespace Umbraco.Core.Configuration.Models { + /// + /// Typed configuration options for image cache settings. + /// public class ImagingCacheSettings { - public TimeSpan BrowserMaxAge { get; set; } = TimeSpan.FromDays(7); + /// + /// Gets or sets a value for the browser image cache maximum age. + /// + public TimeSpan BrowserMaxAge { get; set; } = TimeSpan.FromDays(7); + /// + /// Gets or sets a value for the image cache maximum age. + /// public TimeSpan CacheMaxAge { get; set; } = TimeSpan.FromDays(365); + /// + /// Gets or sets a value for length of the cached name. + /// public uint CachedNameLength { get; set; } = 8; + /// + /// Gets or sets a value for the cache folder. + /// public string CacheFolder { get; set; } = Path.Combine("..", "umbraco", "mediacache"); } } diff --git a/src/Umbraco.Core/Configuration/Models/ImagingResizeSettings.cs b/src/Umbraco.Core/Configuration/Models/ImagingResizeSettings.cs index f9db53d7dd..c95aad52ad 100644 --- a/src/Umbraco.Core/Configuration/Models/ImagingResizeSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/ImagingResizeSettings.cs @@ -1,9 +1,21 @@ -namespace Umbraco.Core.Configuration.Models +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Core.Configuration.Models { + /// + /// Typed configuration options for image resize settings. + /// public class ImagingResizeSettings { + /// + /// Gets or sets a value for the maximim resize width. + /// public int MaxWidth { get; set; } = 5000; + /// + /// Gets or sets a value for the maximim resize height. + /// public int MaxHeight { get; set; } = 5000; } } diff --git a/src/Umbraco.Core/Configuration/Models/ImagingSettings.cs b/src/Umbraco.Core/Configuration/Models/ImagingSettings.cs index 2f253b151b..343e2a040f 100644 --- a/src/Umbraco.Core/Configuration/Models/ImagingSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/ImagingSettings.cs @@ -1,9 +1,21 @@ -namespace Umbraco.Core.Configuration.Models +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Core.Configuration.Models { + /// + /// Typed configuration options for imaging settings. + /// public class ImagingSettings { + /// + /// Gets or sets a value for imaging cache settings. + /// public ImagingCacheSettings Cache { get; set; } = new ImagingCacheSettings(); + /// + /// Gets or sets a value for imaging resize settings. + /// public ImagingResizeSettings Resize { get; set; } = new ImagingResizeSettings(); } } diff --git a/src/Umbraco.Core/Configuration/Models/IndexCreatorSettings.cs b/src/Umbraco.Core/Configuration/Models/IndexCreatorSettings.cs index fcc22de9a3..9ea7348211 100644 --- a/src/Umbraco.Core/Configuration/Models/IndexCreatorSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/IndexCreatorSettings.cs @@ -1,7 +1,16 @@ -namespace Umbraco.Core.Configuration.Models +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Core.Configuration.Models { + /// + /// Typed configuration options for index creator settings. + /// public class IndexCreatorSettings { + /// + /// Gets or sets a value for lucene directory factory type. + /// public string LuceneDirectoryFactory { get; set; } } } diff --git a/src/Umbraco.Core/Configuration/Models/KeepAliveSettings.cs b/src/Umbraco.Core/Configuration/Models/KeepAliveSettings.cs index 2c53407398..57336d35ac 100644 --- a/src/Umbraco.Core/Configuration/Models/KeepAliveSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/KeepAliveSettings.cs @@ -1,9 +1,21 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + namespace Umbraco.Core.Configuration.Models { + /// + /// Typed configuration options for keep alive settings. + /// public class KeepAliveSettings { + /// + /// Gets or sets a value indicating whether the keep alive task is disabled. + /// public bool DisableKeepAliveTask { get; set; } = false; + /// + /// Gets a value for the keep alive ping URL. + /// public string KeepAlivePingUrl => "{umbracoApplicationUrl}/api/keepalive/ping"; } } diff --git a/src/Umbraco.Core/Configuration/Models/LoggingSettings.cs b/src/Umbraco.Core/Configuration/Models/LoggingSettings.cs index 7d79bb83ae..49f2517f8f 100644 --- a/src/Umbraco.Core/Configuration/Models/LoggingSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/LoggingSettings.cs @@ -1,9 +1,18 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; namespace Umbraco.Core.Configuration.Models { + /// + /// Typed configuration options for logging settings. + /// public class LoggingSettings { + /// + /// Gets or sets a value for the maximum age of a log file. + /// public TimeSpan MaxLogAge { get; set; } = TimeSpan.FromHours(24); } } diff --git a/src/Umbraco.Core/Configuration/Models/MemberPasswordConfigurationSettings.cs b/src/Umbraco.Core/Configuration/Models/MemberPasswordConfigurationSettings.cs index 52bba6f4b8..abd12ae023 100644 --- a/src/Umbraco.Core/Configuration/Models/MemberPasswordConfigurationSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/MemberPasswordConfigurationSettings.cs @@ -1,19 +1,32 @@ -namespace Umbraco.Core.Configuration.Models +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Core.Configuration.Models { + /// + /// Typed configuration options for member password settings. + /// public class MemberPasswordConfigurationSettings : IPasswordConfiguration { + /// public int RequiredLength { get; set; } = 10; + /// public bool RequireNonLetterOrDigit { get; set; } = false; + /// public bool RequireDigit { get; set; } = false; + /// public bool RequireLowercase { get; set; } = false; + /// public bool RequireUppercase { get; set; } = false; + /// public string HashAlgorithmType { get; set; } = Constants.Security.AspNetUmbraco8PasswordHashAlgorithmName; + /// public int MaxFailedAccessAttemptsBeforeLockout { get; set; } = 5; } } diff --git a/src/Umbraco.Core/Configuration/Models/ModelsBuilderSettings.cs b/src/Umbraco.Core/Configuration/Models/ModelsBuilderSettings.cs index f0b56561e2..bace0f96c4 100644 --- a/src/Umbraco.Core/Configuration/Models/ModelsBuilderSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/ModelsBuilderSettings.cs @@ -1,17 +1,21 @@ -using Umbraco.Configuration; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Configuration; namespace Umbraco.Core.Configuration.Models { /// - /// Represents the models builder configuration. + /// Typed configuration options for models builder settings. /// public class ModelsBuilderSettings { - // TODO: This should not go into App_Data - that folder isn't really a real thing anymore - public static string DefaultModelsDirectory => "~/umbraco/models"; + private bool _flagOutOfDateModels; + + private static string DefaultModelsDirectory => "~/umbraco/models"; /// - /// Gets a value indicating whether the whole models experience is enabled. + /// Gets or sets a value indicating whether the whole models experience is enabled. /// /// /// If this is false then absolutely nothing happens. @@ -20,31 +24,29 @@ namespace Umbraco.Core.Configuration.Models public bool Enable { get; set; } = false; /// - /// Gets the models mode. + /// Gets or sets a value for the models mode. /// public ModelsMode ModelsMode { get; set; } = ModelsMode.Nothing; /// - /// Gets the models namespace. + /// Gets or sets a value for models namespace. /// /// That value could be overriden by other (attribute in user's code...). Return default if no value was supplied. public string ModelsNamespace { get; set; } /// - /// Gets a value indicating whether we should enable the models factory. + /// Gets or sets a value indicating whether we should enable the models factory. /// /// Default value is true because no factory is enabled by default in Umbraco. public bool EnableFactory { get; set; } = true; - private bool _flagOutOfDateModels; - /// - /// Gets a value indicating whether we should flag out-of-date models. + /// Gets or sets a value indicating whether we should flag out-of-date models. /// /// - /// Models become out-of-date when data types or content types are updated. When this - /// setting is activated the ~/App_Data/Models/ood.txt file is then created. When models are - /// generated through the dashboard, the files is cleared. Default value is false. + /// Models become out-of-date when data types or content types are updated. When this + /// setting is activated the ~/App_Data/Models/ood.txt file is then created. When models are + /// generated through the dashboard, the files is cleared. Default value is false. /// public bool FlagOutOfDateModels { @@ -62,22 +64,22 @@ namespace Umbraco.Core.Configuration.Models } /// - /// Gets the models directory. + /// Gets or sets a value for the models directory. /// /// Default is ~/App_Data/Models but that can be changed. public string ModelsDirectory { get; set; } = DefaultModelsDirectory; /// - /// Gets a value indicating whether to accept an unsafe value for ModelsDirectory. + /// Gets or sets a value indicating whether to accept an unsafe value for ModelsDirectory. /// /// - /// An unsafe value is an absolute path, or a relative path pointing outside - /// of the website root. + /// An unsafe value is an absolute path, or a relative path pointing outside + /// of the website root. /// public bool AcceptUnsafeModelsDirectory { get; set; } = false; /// - /// Gets a value indicating the debug log level. + /// Gets or sets a value indicating the debug log level. /// /// 0 means minimal (safe on live site), anything else means more and more details (maybe not safe). public int DebugLevel { get; set; } = 0; diff --git a/src/Umbraco.Core/Configuration/Models/NuCacheSettings.cs b/src/Umbraco.Core/Configuration/Models/NuCacheSettings.cs index 89a726f30a..f98b1f422e 100644 --- a/src/Umbraco.Core/Configuration/Models/NuCacheSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/NuCacheSettings.cs @@ -1,7 +1,16 @@ -namespace Umbraco.Core.Configuration.Models +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Core.Configuration.Models { - public class NuCacheSettings + /// + /// Typed configuration options for NuCache settings. + /// + public class NuCacheSettings { + /// + /// Gets or sets a value defining the BTree block size. + /// public int? BTreeBlockSize { get; set; } } } diff --git a/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs b/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs index d7203b4901..ceea0f9038 100644 --- a/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs @@ -1,26 +1,32 @@ -using System.Collections.Generic; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; using Umbraco.Core.Configuration.UmbracoSettings; namespace Umbraco.Core.Configuration.Models { + /// + /// Typed configuration options for request handler settings. + /// public class RequestHandlerSettings { internal static readonly CharItem[] DefaultCharCollection = { new CharItem { Char = " ", Replacement = "-" }, - new CharItem { Char = "\"", Replacement = "" }, - new CharItem { Char = "'", Replacement = "" }, - new CharItem { Char = "%", Replacement = "" }, - new CharItem { Char = ".", Replacement = "" }, - new CharItem { Char = ";", Replacement = "" }, - new CharItem { Char = "/", Replacement = "" }, - new CharItem { Char = "\\", Replacement = "" }, - new CharItem { Char = ":", Replacement = "" }, - 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 = "" }, - new CharItem { Char = "?", Replacement = "" }, + 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" }, @@ -29,42 +35,63 @@ namespace Umbraco.Core.Configuration.Models new CharItem { Char = "ü", Replacement = "ue" }, new CharItem { Char = "ß", Replacement = "ss" }, new CharItem { Char = "|", Replacement = "-" }, - new CharItem { Char = "<", Replacement = "" }, - new CharItem { Char = ">", Replacement = "" } + new CharItem { Char = "<", Replacement = string.Empty }, + new CharItem { Char = ">", Replacement = string.Empty } }; + /// + /// Gets or sets a value indicating whether to add a trailing slash to URLs. + /// public bool AddTrailingSlash { get; set; } = true; + /// + /// Gets or sets a value indicating whether to convert URLs to ASCII (valid values: "true", "try" or "false"). + /// public string ConvertUrlsToAscii { get; set; } = "try"; + /// + /// Gets a value indicating whether URLs should be converted to ASCII. + /// public bool ShouldConvertUrlsToAscii => ConvertUrlsToAscii.InvariantEquals("true"); + /// + /// Gets a value indicating whether URLs should be tried to be converted to ASCII. + /// public bool ShouldTryConvertUrlsToAscii => ConvertUrlsToAscii.InvariantEquals("try"); - //We need to special handle ":", as this character is special in keys + // 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(); + //// 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; - //} + //// if (collection.Any() || _configuration.GetSection("Prefix").GetChildren().Any(x => + //// x.Key.Equals("CharCollection", StringComparison.OrdinalIgnoreCase))) + //// { + //// return collection; + //// } - // return DefaultCharCollection; + //// return DefaultCharCollection; + + /// + /// Gets or sets a value for the default character collection for replacements. + /// public IEnumerable CharCollection { get; set; } = DefaultCharCollection; + /// + /// Defines a character replacement. + /// public class CharItem : IChar { + /// public string Char { get; set; } + /// public string Replacement { get; set; } } } diff --git a/src/Umbraco.Core/Configuration/Models/RuntimeSettings.cs b/src/Umbraco.Core/Configuration/Models/RuntimeSettings.cs index f93530b490..97af22ea8a 100644 --- a/src/Umbraco.Core/Configuration/Models/RuntimeSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/RuntimeSettings.cs @@ -1,9 +1,21 @@ -namespace Umbraco.Core.Configuration.Models +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Core.Configuration.Models { + /// + /// Typed configuration options for runtime settings. + /// public class RuntimeSettings { + /// + /// Gets or sets a value for the maximum query string length. + /// public int? MaxQueryStringLength { get; set; } + /// + /// Gets or sets a value for the maximum request length. + /// public int? MaxRequestLength { get; set; } } } diff --git a/src/Umbraco.Core/Configuration/Models/SecuritySettings.cs b/src/Umbraco.Core/Configuration/Models/SecuritySettings.cs index 55bd3a5bf7..25bbbb645d 100644 --- a/src/Umbraco.Core/Configuration/Models/SecuritySettings.cs +++ b/src/Umbraco.Core/Configuration/Models/SecuritySettings.cs @@ -1,21 +1,51 @@ -namespace Umbraco.Core.Configuration.Models +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Core.Configuration.Models { + /// + /// Typed configuration options for security settings. + /// public class SecuritySettings { + /// + /// Gets or sets a value indicating whether to keep the user logged in. + /// public bool KeepUserLoggedIn { get; set; } = false; + /// + /// Gets or sets a value indicating whether to hide disabled users in the back-office. + /// public bool HideDisabledUsersInBackOffice { get; set; } = false; + /// + /// Gets or sets a value indicating whether to allow user password reset. + /// public bool AllowPasswordReset { get; set; } = true; + /// + /// Gets or sets a value for the authorization cookie name. + /// public string AuthCookieName { get; set; } = "UMB_UCONTEXT"; + /// + /// Gets or sets a value for the authorization cookie domain. + /// public string AuthCookieDomain { get; set; } + /// + /// Gets or sets a value indicating whether the user's email address is to be considered as their username. + /// public bool UsernameIsEmail { get; set; } = true; + /// + /// Gets or sets a value for the user password settings. + /// public UserPasswordConfigurationSettings UserPassword { get; set; } + /// + /// Gets or sets a value for the member password settings. + /// public MemberPasswordConfigurationSettings MemberPassword { get; set; } } } diff --git a/src/Umbraco.Core/Configuration/Models/SmtpSettings.cs b/src/Umbraco.Core/Configuration/Models/SmtpSettings.cs index c1ba5edea2..fb4462f76d 100644 --- a/src/Umbraco.Core/Configuration/Models/SmtpSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/SmtpSettings.cs @@ -1,4 +1,7 @@ -using System.ComponentModel.DataAnnotations; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.ComponentModel.DataAnnotations; using System.Net.Mail; using Umbraco.Core.Configuration.Models.Validation; @@ -6,35 +9,82 @@ namespace Umbraco.Core.Configuration.Models { /// /// Matches MailKit.Security.SecureSocketOptions and defined locally to avoid having to take - /// thi + /// a dependency on this external library into Umbraco.Core. + /// See: http://www.mimekit.net/docs/html/T_MailKit_Security_SecureSocketOptions.htm /// public enum SecureSocketOptions { + /// + /// No SSL or TLS encryption should be used. + /// None = 0, + + /// + /// Allow the IMailService to decide which SSL or TLS options to use (default). If the server does not support SSL or TLS, then the connection will continue without any encryption. + /// Auto = 1, + + /// + /// The connection should use SSL or TLS encryption immediately. + /// SslOnConnect = 2, + + /// + /// Elevates the connection to use TLS encryption immediately after reading the greeting and capabilities of the server. If the server does not support the STARTTLS extension, then the connection will fail and a NotSupportedException will be thrown. + /// StartTls = 3, + + /// + /// Elevates the connection to use TLS encryption immediately after reading the greeting and capabilities of the server, but only if the server supports the STARTTLS extension. + /// StartTlsWhenAvailable = 4 } + /// + /// Typed configuration options for SMTP settings. + /// public class SmtpSettings : ValidatableEntryBase { + /// + /// Gets or sets a value for the SMTP from address to use for messages. + /// [Required] [EmailAddress] public string From { get; set; } + /// + /// Gets or sets a value for the SMTP host. + /// public string Host { get; set; } + /// + /// Gets or sets a value for the SMTP port. + /// public int Port { get; set; } + /// + /// Gets or sets a value for the secure socket options. + /// public SecureSocketOptions SecureSocketOptions { get; set; } = SecureSocketOptions.Auto; + /// + /// Gets or sets a value for the SMTP pick-up directory. + /// public string PickupDirectoryLocation { get; set; } + /// + /// Gets or sets a value for the SMTP delivery method. + /// public SmtpDeliveryMethod DeliveryMethod { get; set; } = SmtpDeliveryMethod.Network; + /// + /// Gets or sets a value for the SMTP user name. + /// public string Username { get; set; } + /// + /// Gets or sets a value for the SMTP password. + /// public string Password { get; set; } } } diff --git a/src/Umbraco.Core/Configuration/Models/TourSettings.cs b/src/Umbraco.Core/Configuration/Models/TourSettings.cs index 895eff6dee..25c06b9975 100644 --- a/src/Umbraco.Core/Configuration/Models/TourSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/TourSettings.cs @@ -1,7 +1,16 @@ -namespace Umbraco.Core.Configuration.Models +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Core.Configuration.Models { + /// + /// Typed configuration options for tour settings. + /// public class TourSettings { + /// + /// Gets or sets a value indicating whether back-office tours are enabled. + /// public bool EnableTours { get; set; } = true; } } diff --git a/src/Umbraco.Core/Configuration/Models/TypeFinderSettings.cs b/src/Umbraco.Core/Configuration/Models/TypeFinderSettings.cs index c5210f6c8e..63295c7259 100644 --- a/src/Umbraco.Core/Configuration/Models/TypeFinderSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/TypeFinderSettings.cs @@ -1,7 +1,16 @@ -namespace Umbraco.Core.Configuration.Models +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Core.Configuration.Models { + /// + /// Typed configuration options for type finder settings. + /// public class TypeFinderSettings { + /// + /// Gets or sets a value for the assemblies that accept load exceptions during type finder operations. + /// public string AssembliesAcceptingLoadExceptions { get; set; } } } diff --git a/src/Umbraco.Core/Configuration/Models/UserPasswordConfigurationSettings.cs b/src/Umbraco.Core/Configuration/Models/UserPasswordConfigurationSettings.cs index 445a0f545c..09b9200760 100644 --- a/src/Umbraco.Core/Configuration/Models/UserPasswordConfigurationSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/UserPasswordConfigurationSettings.cs @@ -1,19 +1,32 @@ -namespace Umbraco.Core.Configuration.Models +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Core.Configuration.Models { + /// + /// Typed configuration options for user password settings. + /// public class UserPasswordConfigurationSettings : IPasswordConfiguration { + /// public int RequiredLength { get; set; } = 10; + /// public bool RequireNonLetterOrDigit { get; set; } = false; + /// public bool RequireDigit { get; set; } = false; + /// public bool RequireLowercase { get; set; } = false; + /// public bool RequireUppercase { get; set; } = false; + /// public string HashAlgorithmType { get; set; } = Constants.Security.AspNetCoreV3PasswordHashAlgorithmName; + /// public int MaxFailedAccessAttemptsBeforeLockout { get; set; } = 5; } } diff --git a/src/Umbraco.Core/Configuration/Models/Validation/ConfigurationValidatorBase.cs b/src/Umbraco.Core/Configuration/Models/Validation/ConfigurationValidatorBase.cs index 6c0af9802b..348c809a91 100644 --- a/src/Umbraco.Core/Configuration/Models/Validation/ConfigurationValidatorBase.cs +++ b/src/Umbraco.Core/Configuration/Models/Validation/ConfigurationValidatorBase.cs @@ -1,11 +1,24 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System.Collections.Generic; using System.Linq; namespace Umbraco.Core.Configuration.Models.Validation { + /// + /// Base class for configuration validators. + /// public abstract class ConfigurationValidatorBase { + /// + /// Validates that a string is one of a set of valid values. + /// + /// Configuration path from where the setting is found. + /// The value to check. + /// The set of valid values. + /// A message to output if the value does not match. + /// True if valid, false if not. public bool ValidateStringIsOneOfValidValues(string configPath, string value, IEnumerable validValues, out string message) { if (!validValues.InvariantContains(value)) @@ -18,6 +31,14 @@ namespace Umbraco.Core.Configuration.Models.Validation return true; } + /// + /// Validates that a collection of objects are all valid based on their data annotations. + /// + /// Configuration path from where the setting is found. + /// The values to check. + /// Description of validation appended to message if validation fails. + /// A message to output if the value does not match. + /// True if valid, false if not. public bool ValidateCollection(string configPath, IEnumerable values, string validationDescription, out string message) { if (values.Any(x => !x.IsValid())) @@ -30,6 +51,14 @@ namespace Umbraco.Core.Configuration.Models.Validation return true; } + /// + /// Validates a configuration entry is valid if provided. + /// + /// Configuration path from where the setting is found. + /// The value to check. + /// Description of validation appended to message if validation fails. + /// A message to output if the value does not match. + /// True if valid, false if not. public bool ValidateOptionalEntry(string configPath, ValidatableEntryBase value, string validationDescription, out string message) { if (value != null && !value.IsValid()) @@ -41,8 +70,5 @@ namespace Umbraco.Core.Configuration.Models.Validation message = string.Empty; return true; } - - - } } diff --git a/src/Umbraco.Core/Configuration/Models/Validation/ContentSettingsValidator.cs b/src/Umbraco.Core/Configuration/Models/Validation/ContentSettingsValidator.cs index f6e839aa39..fdfd6de59c 100644 --- a/src/Umbraco.Core/Configuration/Models/Validation/ContentSettingsValidator.cs +++ b/src/Umbraco.Core/Configuration/Models/Validation/ContentSettingsValidator.cs @@ -1,14 +1,20 @@ -using System.Collections.Generic; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; using Microsoft.Extensions.Options; namespace Umbraco.Core.Configuration.Models.Validation { + /// + /// Validator for configuration representated as . + /// public class ContentSettingsValidator : ConfigurationValidatorBase, IValidateOptions { + /// public ValidateOptionsResult Validate(string name, ContentSettings options) { - string message; - if (!ValidateError404Collection(options.Error404Collection, out message)) + if (!ValidateError404Collection(options.Error404Collection, out string message)) { return ValidateOptionsResult.Fail(message); } @@ -21,14 +27,10 @@ namespace Umbraco.Core.Configuration.Models.Validation return ValidateOptionsResult.Success; } - private bool ValidateError404Collection(IEnumerable values, out string message) - { - return ValidateCollection($"{Constants.Configuration.ConfigContent}:{nameof(ContentSettings.Error404Collection)}", values, "Culture and one and only one of ContentId, ContentKey and ContentXPath must be specified for each entry", out message); - } + private bool ValidateError404Collection(IEnumerable values, out string message) => + ValidateCollection($"{Constants.Configuration.ConfigContent}:{nameof(ContentSettings.Error404Collection)}", values, "Culture and one and only one of ContentId, ContentKey and ContentXPath must be specified for each entry", out message); - private bool ValidateAutoFillImageProperties(IEnumerable values, out string message) - { - return ValidateCollection($"{Constants.Configuration.ConfigContent}:{nameof(ContentSettings.Imaging)}:{nameof(ContentSettings.Imaging.AutoFillImageProperties)}", values, "Alias, WidthFieldAlias, HeightFieldAlias, LengthFieldAlias and ExtensionFieldAlias must be specified for each entry", out message); - } + private bool ValidateAutoFillImageProperties(IEnumerable values, out string message) => + ValidateCollection($"{Constants.Configuration.ConfigContent}:{nameof(ContentSettings.Imaging)}:{nameof(ContentSettings.Imaging.AutoFillImageProperties)}", values, "Alias, WidthFieldAlias, HeightFieldAlias, LengthFieldAlias and ExtensionFieldAlias must be specified for each entry", out message); } } diff --git a/src/Umbraco.Core/Configuration/Models/Validation/GlobalSettingsValidator.cs b/src/Umbraco.Core/Configuration/Models/Validation/GlobalSettingsValidator.cs index ca3fee7999..6bc9dc0a6f 100644 --- a/src/Umbraco.Core/Configuration/Models/Validation/GlobalSettingsValidator.cs +++ b/src/Umbraco.Core/Configuration/Models/Validation/GlobalSettingsValidator.cs @@ -1,10 +1,17 @@ -using Microsoft.Extensions.Options; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Microsoft.Extensions.Options; namespace Umbraco.Core.Configuration.Models.Validation { + /// + /// Validator for configuration representated as . + /// public class GlobalSettingsValidator : ConfigurationValidatorBase, IValidateOptions { + /// public ValidateOptionsResult Validate(string name, GlobalSettings options) { if (!ValidateSmtpSetting(options.Smtp, out var message)) @@ -15,9 +22,7 @@ namespace Umbraco.Core.Configuration.Models.Validation return ValidateOptionsResult.Success; } - private bool ValidateSmtpSetting(SmtpSettings value, out string message) - { - return ValidateOptionalEntry($"{Constants.Configuration.ConfigGlobal}:{nameof(GlobalSettings.Smtp)}", value, "A valid From email address is required", out message); - } + private bool ValidateSmtpSetting(SmtpSettings value, out string message) => + ValidateOptionalEntry($"{Constants.Configuration.ConfigGlobal}:{nameof(GlobalSettings.Smtp)}", value, "A valid From email address is required", out message); } } diff --git a/src/Umbraco.Core/Configuration/Models/Validation/HealthChecksSettingsValidator.cs b/src/Umbraco.Core/Configuration/Models/Validation/HealthChecksSettingsValidator.cs index f26fce9bd9..449415c37f 100644 --- a/src/Umbraco.Core/Configuration/Models/Validation/HealthChecksSettingsValidator.cs +++ b/src/Umbraco.Core/Configuration/Models/Validation/HealthChecksSettingsValidator.cs @@ -1,16 +1,24 @@ -using Microsoft.Extensions.Options; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Microsoft.Extensions.Options; namespace Umbraco.Core.Configuration.Models.Validation { + /// + /// Validator for configuration representated as . + /// public class HealthChecksSettingsValidator : ConfigurationValidatorBase, IValidateOptions { private readonly ICronTabParser _cronTabParser; - public HealthChecksSettingsValidator(ICronTabParser cronTabParser) - { - _cronTabParser = cronTabParser; - } + /// + /// Initializes a new instance of the class. + /// + /// Helper for parsing crontab expressions. + public HealthChecksSettingsValidator(ICronTabParser cronTabParser) => _cronTabParser = cronTabParser; + /// public ValidateOptionsResult Validate(string name, HealthChecksSettings options) { if (!ValidateNotificationFirstRunTime(options.Notification.FirstRunTime, out var message)) @@ -21,12 +29,10 @@ namespace Umbraco.Core.Configuration.Models.Validation return ValidateOptionsResult.Success; } - private bool ValidateNotificationFirstRunTime(string value, out string message) - { - return ValidateOptionalCronTab($"{Constants.Configuration.ConfigHealthChecks}:{nameof(HealthChecksSettings.Notification)}:{nameof(HealthChecksSettings.Notification.FirstRunTime)}", value, out message); - } + private bool ValidateNotificationFirstRunTime(string value, out string message) => + ValidateOptionalCronTab($"{Constants.Configuration.ConfigHealthChecks}:{nameof(HealthChecksSettings.Notification)}:{nameof(HealthChecksSettings.Notification.FirstRunTime)}", value, out message); - public bool ValidateOptionalCronTab(string configPath, string value, out string message) + private bool ValidateOptionalCronTab(string configPath, string value, out string message) { if (!string.IsNullOrEmpty(value) && !_cronTabParser.IsValidCronTab(value)) { diff --git a/src/Umbraco.Core/Configuration/Models/Validation/RequestHandlerSettingsValidator.cs b/src/Umbraco.Core/Configuration/Models/Validation/RequestHandlerSettingsValidator.cs index 305fe812f8..647c7438c6 100644 --- a/src/Umbraco.Core/Configuration/Models/Validation/RequestHandlerSettingsValidator.cs +++ b/src/Umbraco.Core/Configuration/Models/Validation/RequestHandlerSettingsValidator.cs @@ -1,9 +1,16 @@ -using Microsoft.Extensions.Options; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Microsoft.Extensions.Options; namespace Umbraco.Core.Configuration.Models.Validation { + /// + /// Validator for configuration representated as . + /// public class RequestHandlerSettingsValidator : ConfigurationValidatorBase, IValidateOptions { + /// public ValidateOptionsResult Validate(string name, RequestHandlerSettings options) { if (!ValidateConvertUrlsToAscii(options.ConvertUrlsToAscii, out var message)) diff --git a/src/Umbraco.Core/Configuration/Models/Validation/ValidatableEntryBase.cs b/src/Umbraco.Core/Configuration/Models/Validation/ValidatableEntryBase.cs index 32e3c3270b..37380eb9a6 100644 --- a/src/Umbraco.Core/Configuration/Models/Validation/ValidatableEntryBase.cs +++ b/src/Umbraco.Core/Configuration/Models/Validation/ValidatableEntryBase.cs @@ -1,8 +1,14 @@ -using System.Collections.Generic; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; namespace Umbraco.Core.Configuration.Models.Validation { + /// + /// Provides a base class for configuration models that can be validated based on data annotations. + /// public abstract class ValidatableEntryBase { internal virtual bool IsValid() diff --git a/src/Umbraco.Core/Configuration/Models/WebRoutingSettings.cs b/src/Umbraco.Core/Configuration/Models/WebRoutingSettings.cs index 476d58c913..9f06046452 100644 --- a/src/Umbraco.Core/Configuration/Models/WebRoutingSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/WebRoutingSettings.cs @@ -1,23 +1,53 @@ -using Umbraco.Core.Models.PublishedContent; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Core.Models.PublishedContent; namespace Umbraco.Core.Configuration.Models { + /// + /// Typed configuration options for web routing settings. + /// public class WebRoutingSettings { + /// + /// Gets or sets a value indicating whether IIS custom errors should be skipped. + /// public bool TrySkipIisCustomErrors { get; set; } = false; + /// + /// Gets or sets a value indicating whether an internal redirect should preserve the template. + /// public bool InternalRedirectPreservesTemplate { get; set; } = false; + /// + /// Gets or sets a value indicating whether the use of alternative templates are disabled. + /// public bool DisableAlternativeTemplates { get; set; } = false; + /// + /// Gets or sets a value indicating whether the use of alternative templates should be validated. + /// public bool ValidateAlternativeTemplates { get; set; } = false; + /// + /// Gets or sets a value indicating whether find content ID by path is disabled. + /// public bool DisableFindContentByIdPath { get; set; } = false; + /// + /// Gets or sets a value indicating whether redirect URL tracking is disabled. + /// public bool DisableRedirectUrlTracking { get; set; } = false; + /// + /// Gets or sets a value for the URL provider mode (). + /// public UrlMode UrlProviderMode { get; set; } = UrlMode.Auto; + /// + /// Gets or sets a value for the Umbraco application URL. + /// public string UmbracoApplicationUrl { get; set; } } } diff --git a/src/Umbraco.Core/Constants-Security.cs b/src/Umbraco.Core/Constants-Security.cs index 24b8b20731..d50e5390d2 100644 --- a/src/Umbraco.Core/Constants-Security.cs +++ b/src/Umbraco.Core/Constants-Security.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Core +namespace Umbraco.Core { public static partial class Constants { @@ -11,6 +11,8 @@ /// public const int SuperUserId = -1; + public const string SuperUserIdAsString = "-1"; + /// /// The id for the 'unknown' user. /// @@ -22,7 +24,7 @@ /// /// The name of the 'unknown' user. /// - public const string UnknownUserName = "SYTEM"; + public const string UnknownUserName = "SYSTEM"; public const string AdminGroupAlias = "admin"; public const string EditorGroupAlias = "editor"; @@ -38,7 +40,6 @@ public const string BackOfficeTwoFactorRememberMeAuthenticationType = "UmbracoTwoFactorRememberMeCookie"; public const string EmptyPasswordPrefix = "___UIDEMPTYPWORD__"; - public const string ForceReAuthFlag = "umbraco-force-auth"; /// /// The prefix used for external identity providers for their authentication type diff --git a/src/Umbraco.Core/IO/SystemFiles.cs b/src/Umbraco.Core/IO/SystemFiles.cs index 92e9156f2f..c8151d076c 100644 --- a/src/Umbraco.Core/IO/SystemFiles.cs +++ b/src/Umbraco.Core/IO/SystemFiles.cs @@ -9,6 +9,8 @@ namespace Umbraco.Core.IO { public static string TinyMceConfig => Constants.SystemDirectories.Config + "/tinyMceConfig.config"; + public static string TelemetricsIdentifier => Constants.SystemDirectories.Data + "/telemetrics-id.umb"; + // TODO: Kill this off we don't have umbraco.config XML cache we now have NuCache public static string GetContentCacheXml(IHostingEnvironment hostingEnvironment) { diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentPropertyDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/ContentPropertyDisplay.cs index 39a4718dd0..9a07d29a02 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ContentPropertyDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ContentPropertyDisplay.cs @@ -33,6 +33,9 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "hideLabel")] public bool HideLabel { get; set; } + [DataMember(Name = "labelOnTop")] + public bool LabelOnTop { get; set; } + [DataMember(Name = "validation")] public PropertyTypeValidation Validation { get; set; } diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentPropertyDto.cs b/src/Umbraco.Core/Models/ContentEditing/ContentPropertyDto.cs index 865a10d5d5..2bf1603bb2 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ContentPropertyDto.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ContentPropertyDto.cs @@ -15,6 +15,8 @@ namespace Umbraco.Web.Models.ContentEditing public bool IsRequired { get; set; } + public bool LabelOnTop { get; set; } + public string IsRequiredMessage { get; set; } public string ValidationRegExp { get; set; } diff --git a/src/Umbraco.Core/Models/ContentEditing/PropertyTypeBasic.cs b/src/Umbraco.Core/Models/ContentEditing/PropertyTypeBasic.cs index 4252a29567..793e4e391d 100644 --- a/src/Umbraco.Core/Models/ContentEditing/PropertyTypeBasic.cs +++ b/src/Umbraco.Core/Models/ContentEditing/PropertyTypeBasic.cs @@ -65,5 +65,8 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "allowSegmentVariant")] public bool AllowSegmentVariant { get; set; } + + [DataMember(Name = "labelOnTop")] + public bool LabelOnTop { get; set; } } } diff --git a/src/Umbraco.Core/Models/IPropertyType.cs b/src/Umbraco.Core/Models/IPropertyType.cs index 288a3f2243..be52339d65 100644 --- a/src/Umbraco.Core/Models/IPropertyType.cs +++ b/src/Umbraco.Core/Models/IPropertyType.cs @@ -50,6 +50,11 @@ namespace Umbraco.Core.Models /// bool Mandatory { get; set; } + /// + /// Gets or sets a value indicating whether the label of this property type should be displayed on top. + /// + bool LabelOnTop { get; set; } + /// /// Gets of sets the sort order of the property type. /// diff --git a/src/Umbraco.Core/Models/Identity/ExternalLogin.cs b/src/Umbraco.Core/Models/Identity/ExternalLogin.cs index 6e4abf2906..a5de9da0cb 100644 --- a/src/Umbraco.Core/Models/Identity/ExternalLogin.cs +++ b/src/Umbraco.Core/Models/Identity/ExternalLogin.cs @@ -1,10 +1,13 @@ -using System; +using System; namespace Umbraco.Core.Models.Identity { /// public class ExternalLogin : IExternalLogin { + /// + /// Initializes a new instance of the class. + /// public ExternalLogin(string loginProvider, string providerKey, string userData = null) { LoginProvider = loginProvider ?? throw new ArgumentNullException(nameof(loginProvider)); diff --git a/src/Umbraco.Core/Models/Identity/IExternalLogin.cs b/src/Umbraco.Core/Models/Identity/IExternalLogin.cs index 68f66a5cee..2718802324 100644 --- a/src/Umbraco.Core/Models/Identity/IExternalLogin.cs +++ b/src/Umbraco.Core/Models/Identity/IExternalLogin.cs @@ -1,12 +1,23 @@ -namespace Umbraco.Core.Models.Identity +namespace Umbraco.Core.Models.Identity { /// /// Used to persist external login data for a user /// public interface IExternalLogin { + /// + /// Gets the login provider + /// string LoginProvider { get; } + + /// + /// Gets the provider key + /// string ProviderKey { get; } + + /// + /// Gets the user data + /// string UserData { get; } } } diff --git a/src/Umbraco.Core/Models/Identity/IIdentityUserLogin.cs b/src/Umbraco.Core/Models/Identity/IIdentityUserLogin.cs index cbe5b47b38..05703a1b2c 100644 --- a/src/Umbraco.Core/Models/Identity/IIdentityUserLogin.cs +++ b/src/Umbraco.Core/Models/Identity/IIdentityUserLogin.cs @@ -1,27 +1,30 @@ -using Umbraco.Core.Models.Entities; +using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models.Identity { - + /// + /// An external login provider linked to a user + /// + /// The PK type for the user public interface IIdentityUserLogin : IEntity, IRememberBeingDirty { /// - /// The login provider for the login (i.e. Facebook, Google) + /// Gets or sets the login provider for the login (i.e. Facebook, Google) /// string LoginProvider { get; set; } /// - /// Key representing the login for the provider + /// Gets or sets key representing the login for the provider /// string ProviderKey { get; set; } /// - /// User Id for the user who owns this login + /// Gets or sets user Id for the user who owns this login /// - int UserId { get; set; } + string UserId { get; set; } // TODO: This should be able to be used by both users and members /// - /// Used to store any arbitrary data for the user and external provider - like user tokens returned from the provider + /// Gets or sets any arbitrary data for the user and external provider - like user tokens returned from the provider /// string UserData { get; set; } } diff --git a/src/Umbraco.Core/Models/Identity/IdentityUser.cs b/src/Umbraco.Core/Models/Identity/IdentityUser.cs deleted file mode 100644 index 093e42c1e7..0000000000 --- a/src/Umbraco.Core/Models/Identity/IdentityUser.cs +++ /dev/null @@ -1,115 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Umbraco.Core.Models.Identity -{ - /// - /// Default IUser implementation - /// - /// - /// - /// This class normally exists inside of the EntityFramework library, not sure why MS chose to explicitly put it there but we don't want - /// references to that so we will create our own here - /// - public class IdentityUser - where TLogin : IIdentityUserLogin - //NOTE: Making our role id a string - where TRole : IdentityUserRole - where TClaim : IdentityUserClaim - { - /// - /// Initializes a new instance of the class. - /// - public IdentityUser() - { - Claims = new List(); - Roles = new List(); - Logins = new List(); - } - - /// - /// Last login date - /// - public virtual DateTime? LastLoginDateUtc { get; set; } - - /// - /// Email - /// - public virtual string Email { get; set; } - - /// - /// True if the email is confirmed, default is false - /// - public virtual bool EmailConfirmed { get; set; } - - /// - /// The salted/hashed form of the user password - /// - public virtual string PasswordHash { get; set; } - - /// - /// A random value that should change whenever a users credentials have changed (password changed, login removed) - /// - public virtual string SecurityStamp { get; set; } - - /// - /// PhoneNumber for the user - /// - public virtual string PhoneNumber { get; set; } - - /// - /// True if the phone number is confirmed, default is false - /// - public virtual bool PhoneNumberConfirmed { get; set; } - - /// - /// Is two factor enabled for the user - /// - public virtual bool TwoFactorEnabled { get; set; } - - /// - /// DateTime in UTC when lockout ends, any time in the past is considered not locked out. - /// - public virtual DateTime? LockoutEndDateUtc { get; set; } - - /// - /// DateTime in UTC when the password was last changed. - /// - public virtual DateTime? LastPasswordChangeDateUtc { get; set; } - - /// - /// Is lockout enabled for this user - /// - public virtual bool LockoutEnabled { get; set; } - - /// - /// Used to record failures for the purposes of lockout - /// - public virtual int AccessFailedCount { get; set; } - - /// - /// Navigation property for user roles - /// - public virtual ICollection Roles { get; } - - /// - /// Navigation property for user claims - /// - public virtual ICollection Claims { get; } - - /// - /// Navigation property for user logins - /// - public virtual ICollection Logins { get; } - - /// - /// User ID (Primary Key) - /// - public virtual TKey Id { get; set; } - - /// - /// User name - /// - public virtual string UserName { get; set; } - } -} diff --git a/src/Umbraco.Core/Models/Identity/IdentityUserClaim.cs b/src/Umbraco.Core/Models/Identity/IdentityUserClaim.cs deleted file mode 100644 index e117d2fd13..0000000000 --- a/src/Umbraco.Core/Models/Identity/IdentityUserClaim.cs +++ /dev/null @@ -1,38 +0,0 @@ -namespace Umbraco.Core.Models.Identity -{ - /// - /// EntityType that represents one specific user claim - /// - /// - /// - /// - /// This class normally exists inside of the EntityFramework library, not sure why MS chose to explicitly put it there but we don't want - /// references to that so we will create our own here - /// - public class IdentityUserClaim - { - /// - /// Primary key - /// - /// - public virtual int Id { get; set; } - - /// - /// User Id for the user who owns this login - /// - /// - public virtual TKey UserId { get; set; } - - /// - /// Claim type - /// - /// - public virtual string ClaimType { get; set; } - - /// - /// Claim value - /// - /// - public virtual string ClaimValue { get; set; } - } -} diff --git a/src/Umbraco.Core/Models/Identity/IdentityUserLogin.cs b/src/Umbraco.Core/Models/Identity/IdentityUserLogin.cs index c13b28461d..5974822c20 100644 --- a/src/Umbraco.Core/Models/Identity/IdentityUserLogin.cs +++ b/src/Umbraco.Core/Models/Identity/IdentityUserLogin.cs @@ -1,4 +1,4 @@ -using System; +using System; using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models.Identity @@ -9,14 +9,20 @@ namespace Umbraco.Core.Models.Identity /// public class IdentityUserLogin : EntityBase, IIdentityUserLogin { - public IdentityUserLogin(string loginProvider, string providerKey, int userId) + /// + /// Initializes a new instance of the class. + /// + public IdentityUserLogin(string loginProvider, string providerKey, string userId) { LoginProvider = loginProvider; ProviderKey = providerKey; UserId = userId; } - public IdentityUserLogin(int id, string loginProvider, string providerKey, int userId, DateTime createDate) + /// + /// Initializes a new instance of the class. + /// + public IdentityUserLogin(int id, string loginProvider, string providerKey, string userId, DateTime createDate) { Id = id; LoginProvider = loginProvider; @@ -32,7 +38,7 @@ namespace Umbraco.Core.Models.Identity public string ProviderKey { get; set; } /// - public int UserId { get; set; } + public string UserId { get; set; } /// public string UserData { get; set; } diff --git a/src/Umbraco.Core/Models/Identity/IdentityUserRole.cs b/src/Umbraco.Core/Models/Identity/IdentityUserRole.cs deleted file mode 100644 index ba9e87e46c..0000000000 --- a/src/Umbraco.Core/Models/Identity/IdentityUserRole.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace Umbraco.Core.Models.Identity -{ - /// - /// EntityType that represents a user belonging to a role - /// - /// - /// - /// - /// This class normally exists inside of the EntityFramework library, not sure why MS chose to explicitly put it there but we don't want - /// references to that so we will create our own here - /// - public class IdentityUserRole - { - /// - /// UserId for the user that is in the role - /// - /// - public virtual TKey UserId { get; set; } - - /// - /// RoleId for the role - /// - /// - public virtual TKey RoleId { get; set; } - } -} diff --git a/src/Umbraco.Core/Models/Mapping/ContentPropertyDisplayMapper.cs b/src/Umbraco.Core/Models/Mapping/ContentPropertyDisplayMapper.cs index a7771015a0..c72f4fac7c 100644 --- a/src/Umbraco.Core/Models/Mapping/ContentPropertyDisplayMapper.cs +++ b/src/Umbraco.Core/Models/Mapping/ContentPropertyDisplayMapper.cs @@ -42,6 +42,7 @@ namespace Umbraco.Web.Models.Mapping dest.Description = originalProp.PropertyType.Description; dest.Label = originalProp.PropertyType.Name; dest.HideLabel = valEditor.HideLabel; + dest.LabelOnTop = originalProp.PropertyType.LabelOnTop; //add the validation information dest.Validation.Mandatory = originalProp.PropertyType.Mandatory; diff --git a/src/Umbraco.Core/Models/Mapping/ContentPropertyDtoMapper.cs b/src/Umbraco.Core/Models/Mapping/ContentPropertyDtoMapper.cs index e431614afc..456e23b68a 100644 --- a/src/Umbraco.Core/Models/Mapping/ContentPropertyDtoMapper.cs +++ b/src/Umbraco.Core/Models/Mapping/ContentPropertyDtoMapper.cs @@ -27,6 +27,7 @@ namespace Umbraco.Web.Models.Mapping dest.Description = property.PropertyType.Description; dest.Label = property.PropertyType.Name; dest.DataType = DataTypeService.GetDataType(property.PropertyType.DataTypeId); + dest.LabelOnTop = property.PropertyType.LabelOnTop; } } } diff --git a/src/Umbraco.Core/Models/Mapping/ContentPropertyMapDefinition.cs b/src/Umbraco.Core/Models/Mapping/ContentPropertyMapDefinition.cs index a74b0467e2..7740685615 100644 --- a/src/Umbraco.Core/Models/Mapping/ContentPropertyMapDefinition.cs +++ b/src/Umbraco.Core/Models/Mapping/ContentPropertyMapDefinition.cs @@ -39,7 +39,7 @@ namespace Umbraco.Web.Models.Mapping target.Id = source.Id; target.IsActive = true; target.Label = source.Name; -} + } private void Map(IProperty source, ContentPropertyBasic target, MapperContext context) { diff --git a/src/Umbraco.Core/Models/Mapping/ContentTypeMapDefinition.cs b/src/Umbraco.Core/Models/Mapping/ContentTypeMapDefinition.cs index 34408469d4..0ed781fe10 100644 --- a/src/Umbraco.Core/Models/Mapping/ContentTypeMapDefinition.cs +++ b/src/Umbraco.Core/Models/Mapping/ContentTypeMapDefinition.cs @@ -262,6 +262,7 @@ namespace Umbraco.Web.Models.Mapping target.Alias = source.Alias; target.Description = source.Description; target.SortOrder = source.SortOrder; + target.LabelOnTop = source.LabelOnTop; } // no MapAll - take care @@ -373,6 +374,7 @@ namespace Umbraco.Web.Models.Mapping target.Label = source.Label; target.SortOrder = source.SortOrder; target.Validation = source.Validation; + target.LabelOnTop = source.LabelOnTop; } // Umbraco.Code.MapAll -Editor -View -Config -ContentTypeId -ContentTypeName -Locked -DataTypeIcon -DataTypeName @@ -393,6 +395,7 @@ namespace Umbraco.Web.Models.Mapping target.MemberCanViewProperty = source.MemberCanViewProperty; target.SortOrder = source.SortOrder; target.Validation = source.Validation; + target.LabelOnTop = source.LabelOnTop; } // Umbraco.Code.MapAll -CreatorId -Level -SortOrder -Variations diff --git a/src/Umbraco.Core/Models/Mapping/PropertyTypeGroupMapper.cs b/src/Umbraco.Core/Models/Mapping/PropertyTypeGroupMapper.cs index e2d4784aae..b41f5d4b19 100644 --- a/src/Umbraco.Core/Models/Mapping/PropertyTypeGroupMapper.cs +++ b/src/Umbraco.Core/Models/Mapping/PropertyTypeGroupMapper.cs @@ -224,6 +224,7 @@ namespace Umbraco.Web.Models.Mapping Id = p.Id, Alias = p.Alias, Description = p.Description, + LabelOnTop = p.LabelOnTop, Editor = p.PropertyEditorAlias, Validation = new PropertyTypeValidation { diff --git a/src/Umbraco.Core/Models/PropertyType.cs b/src/Umbraco.Core/Models/PropertyType.cs index 37097a16f6..4ded638e44 100644 --- a/src/Umbraco.Core/Models/PropertyType.cs +++ b/src/Umbraco.Core/Models/PropertyType.cs @@ -32,6 +32,7 @@ namespace Umbraco.Core.Models private string _validationRegExp; private string _validationRegExpMessage; private ContentVariation _variations; + private bool _labelOnTop; /// /// Initializes a new instance of the class. @@ -192,6 +193,14 @@ namespace Umbraco.Core.Models set => SetPropertyValueAndDetectChanges(value, ref _mandatoryMessage, nameof(MandatoryMessage)); } + /// + [DataMember] + public bool LabelOnTop + { + get => _labelOnTop; + set => SetPropertyValueAndDetectChanges(value, ref _labelOnTop, nameof(LabelOnTop)); + } + /// [DataMember] public int SortOrder @@ -274,7 +283,6 @@ namespace Umbraco.Core.Models base.PerformDeepClone(clone); var clonedEntity = (PropertyType) clone; - //need to manually assign the Lazy value as it will not be automatically mapped if (PropertyGroupId != null) { diff --git a/src/Umbraco.Core/Security/AuthenticationExtensions.cs b/src/Umbraco.Core/Security/AuthenticationExtensions.cs index edc11bcac2..607c4748cc 100644 --- a/src/Umbraco.Core/Security/AuthenticationExtensions.cs +++ b/src/Umbraco.Core/Security/AuthenticationExtensions.cs @@ -5,7 +5,6 @@ using System.Globalization; using System.Security.Principal; using System.Text; using System.Threading; -using Umbraco.Core.BackOffice; namespace Umbraco.Core.Security { diff --git a/src/Umbraco.Core/BackOffice/BackOfficeUserPasswordCheckerResult.cs b/src/Umbraco.Core/Security/BackOfficeUserPasswordCheckerResult.cs similarity index 87% rename from src/Umbraco.Core/BackOffice/BackOfficeUserPasswordCheckerResult.cs rename to src/Umbraco.Core/Security/BackOfficeUserPasswordCheckerResult.cs index 7936fab682..c640c85d0c 100644 --- a/src/Umbraco.Core/BackOffice/BackOfficeUserPasswordCheckerResult.cs +++ b/src/Umbraco.Core/Security/BackOfficeUserPasswordCheckerResult.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Core.BackOffice +namespace Umbraco.Core.Security { /// /// The result returned from the IBackOfficeUserPasswordChecker diff --git a/src/Umbraco.Core/BackOffice/ClaimsPrincipalExtensions.cs b/src/Umbraco.Core/Security/ClaimsPrincipalExtensions.cs similarity index 98% rename from src/Umbraco.Core/BackOffice/ClaimsPrincipalExtensions.cs rename to src/Umbraco.Core/Security/ClaimsPrincipalExtensions.cs index 7cbca0428a..395465cfb7 100644 --- a/src/Umbraco.Core/BackOffice/ClaimsPrincipalExtensions.cs +++ b/src/Umbraco.Core/Security/ClaimsPrincipalExtensions.cs @@ -1,10 +1,10 @@ -using System; +using System; using System.Globalization; using System.Linq; using System.Security.Claims; using System.Security.Principal; using Umbraco.Core; -using Umbraco.Core.BackOffice; +using Umbraco.Core.Security; namespace Umbraco.Extensions { diff --git a/src/Umbraco.Core/BackOffice/IdentityAuditEventArgs.cs b/src/Umbraco.Core/Security/IdentityAuditEventArgs.cs similarity index 83% rename from src/Umbraco.Core/BackOffice/IdentityAuditEventArgs.cs rename to src/Umbraco.Core/Security/IdentityAuditEventArgs.cs index 1d51c45074..b9884c8e7d 100644 --- a/src/Umbraco.Core/BackOffice/IdentityAuditEventArgs.cs +++ b/src/Umbraco.Core/Security/IdentityAuditEventArgs.cs @@ -1,7 +1,7 @@ -using System; +using System; -namespace Umbraco.Core.BackOffice +namespace Umbraco.Core.Security { /// @@ -27,12 +27,12 @@ namespace Umbraco.Core.BackOffice /// /// The user affected by the event raised /// - public int AffectedUser { get; private set; } + public string AffectedUser { get; private set; } /// /// If a user is performing an action on a different user, then this will be set. Otherwise it will be -1 /// - public int PerformingUser { get; private set; } + public string PerformingUser { get; private set; } /// /// An optional comment about the action being logged @@ -53,19 +53,19 @@ namespace Umbraco.Core.BackOffice /// /// /// - public IdentityAuditEventArgs(AuditEvent action, string ipAddress, int performingUser, string comment, int affectedUser, string affectedUsername) + public IdentityAuditEventArgs(AuditEvent action, string ipAddress, string performingUser, string comment, string affectedUser, string affectedUsername) { DateTimeUtc = DateTime.UtcNow; Action = action; IpAddress = ipAddress; - Comment = comment; + Comment = comment; PerformingUser = performingUser; AffectedUsername = affectedUsername; AffectedUser = affectedUser; } - public IdentityAuditEventArgs(AuditEvent action, string ipAddress, int performingUser, string comment, string affectedUsername) - : this(action, ipAddress, performingUser, comment, -1, affectedUsername) + public IdentityAuditEventArgs(AuditEvent action, string ipAddress, string performingUser, string comment, string affectedUsername) + : this(action, ipAddress, performingUser, comment, Constants.Security.SuperUserIdAsString, affectedUsername) { } diff --git a/src/Umbraco.Core/BackOffice/UmbracoBackOfficeIdentity.cs b/src/Umbraco.Core/Security/UmbracoBackOfficeIdentity.cs similarity index 83% rename from src/Umbraco.Core/BackOffice/UmbracoBackOfficeIdentity.cs rename to src/Umbraco.Core/Security/UmbracoBackOfficeIdentity.cs index 9a60c5d64f..5fd9f23c92 100644 --- a/src/Umbraco.Core/BackOffice/UmbracoBackOfficeIdentity.cs +++ b/src/Umbraco.Core/Security/UmbracoBackOfficeIdentity.cs @@ -1,9 +1,9 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Security.Claims; -namespace Umbraco.Core.BackOffice +namespace Umbraco.Core.Security { /// @@ -15,13 +15,12 @@ namespace Umbraco.Core.BackOffice // TODO: Ideally we remove this class and only deal with ClaimsIdentity as a best practice. All things relevant to our own // identity are part of claims. This class would essentially become extension methods on a ClaimsIdentity for resolving // values from it. - public static bool FromClaimsIdentity(ClaimsIdentity identity, out UmbracoBackOfficeIdentity backOfficeIdentity) { - //validate that all claims exist + // validate that all claims exist foreach (var t in RequiredBackOfficeIdentityClaimTypes) { - //if the identity doesn't have the claim, or the claim value is null + // if the identity doesn't have the claim, or the claim value is null if (identity.HasClaim(x => x.Type == t) == false || identity.HasClaim(x => x.Type == t && x.Value.IsNullOrWhiteSpace())) { backOfficeIdentity = null; @@ -54,16 +53,21 @@ namespace Umbraco.Core.BackOffice /// /// /// - public UmbracoBackOfficeIdentity(int userId, string username, string realName, + public UmbracoBackOfficeIdentity(string userId, string username, string realName, IEnumerable startContentNodes, IEnumerable startMediaNodes, string culture, string securityStamp, IEnumerable allowedApps, IEnumerable roles) : base(Enumerable.Empty(), Constants.Security.BackOfficeAuthenticationType) //this ctor is used to ensure the IsAuthenticated property is true { - if (allowedApps == null) throw new ArgumentNullException(nameof(allowedApps)); - if (string.IsNullOrWhiteSpace(username)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(username)); - if (string.IsNullOrWhiteSpace(realName)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(realName)); - if (string.IsNullOrWhiteSpace(culture)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(culture)); - if (string.IsNullOrWhiteSpace(securityStamp)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(securityStamp)); + if (allowedApps == null) + throw new ArgumentNullException(nameof(allowedApps)); + if (string.IsNullOrWhiteSpace(username)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(username)); + if (string.IsNullOrWhiteSpace(realName)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(realName)); + if (string.IsNullOrWhiteSpace(culture)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(culture)); + if (string.IsNullOrWhiteSpace(securityStamp)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(securityStamp)); AddRequiredClaims(userId, username, realName, startContentNodes, startMediaNodes, culture, securityStamp, allowedApps, roles); } @@ -83,15 +87,19 @@ namespace Umbraco.Core.BackOffice /// /// public UmbracoBackOfficeIdentity(ClaimsIdentity childIdentity, - int userId, string username, string realName, + string userId, string username, string realName, IEnumerable startContentNodes, IEnumerable startMediaNodes, string culture, string securityStamp, IEnumerable allowedApps, IEnumerable roles) : base(childIdentity.Claims, Constants.Security.BackOfficeAuthenticationType) { - if (string.IsNullOrWhiteSpace(username)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(username)); - if (string.IsNullOrWhiteSpace(realName)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(realName)); - if (string.IsNullOrWhiteSpace(culture)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(culture)); - if (string.IsNullOrWhiteSpace(securityStamp)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(securityStamp)); + if (string.IsNullOrWhiteSpace(username)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(username)); + if (string.IsNullOrWhiteSpace(realName)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(realName)); + if (string.IsNullOrWhiteSpace(culture)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(culture)); + if (string.IsNullOrWhiteSpace(securityStamp)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(securityStamp)); AddRequiredClaims(userId, username, realName, startContentNodes, startMediaNodes, culture, securityStamp, allowedApps, roles); } @@ -118,13 +126,13 @@ namespace Umbraco.Core.BackOffice /// /// Adds claims based on the ctor data /// - private void AddRequiredClaims(int userId, string username, string realName, + private void AddRequiredClaims(string userId, string username, string realName, IEnumerable startContentNodes, IEnumerable startMediaNodes, string culture, string securityStamp, IEnumerable allowedApps, IEnumerable roles) { //This is the id that 'identity' uses to check for the user id if (HasClaim(x => x.Type == ClaimTypes.NameIdentifier) == false) - AddClaim(new Claim(ClaimTypes.NameIdentifier, userId.ToInvariantString(), ClaimValueTypes.Integer32, Issuer, Issuer, this)); + AddClaim(new Claim(ClaimTypes.NameIdentifier, userId, ClaimValueTypes.String, Issuer, Issuer, this)); if (HasClaim(x => x.Type == ClaimTypes.Name) == false) AddClaim(new Claim(ClaimTypes.Name, username, ClaimValueTypes.String, Issuer, Issuer, this)); @@ -205,7 +213,7 @@ namespace Umbraco.Core.BackOffice public string SecurityStamp => this.FindFirstValue(Constants.Security.SecurityStampClaimType); - public string[] Roles => this.FindAll(x => x.Type == DefaultRoleClaimType).Select(role => role.Value).ToArray(); + public string[] Roles => FindAll(x => x.Type == DefaultRoleClaimType).Select(role => role.Value).ToArray(); /// /// Overridden to remove any temporary claims that shouldn't be copied diff --git a/src/Umbraco.Core/Telemetry/TelemetryMarkerComponent.cs b/src/Umbraco.Core/Telemetry/TelemetryMarkerComponent.cs new file mode 100644 index 0000000000..157cacd618 --- /dev/null +++ b/src/Umbraco.Core/Telemetry/TelemetryMarkerComponent.cs @@ -0,0 +1,70 @@ +using System; +using System.IO; +using Microsoft.Extensions.Logging; +using Umbraco.Core; +using Umbraco.Core.Composing; +using Umbraco.Core.Hosting; +using Umbraco.Core.IO; + +namespace Umbraco.Web.Telemetry +{ + public class TelemetryMarkerComponent : IComponent + { + private readonly ILogger _logger; + private readonly IRuntimeState _runtime; + private readonly IHostingEnvironment _hostingEnvironment; + + public TelemetryMarkerComponent(ILogger logger, IRuntimeState runtime, IHostingEnvironment hostingEnvironment) + { + _logger = logger; + _runtime = runtime; + _hostingEnvironment = hostingEnvironment; + } + + public void Initialize() + { + if (_runtime.Level != RuntimeLevel.Install && _runtime.Level != RuntimeLevel.Upgrade) + { + return; + } + + var telemetricsFilePath = _hostingEnvironment.MapPathContentRoot(SystemFiles.TelemetricsIdentifier); + + // Verify file does not exist already (if we are upgrading) + // In a clean install we know it would not exist + // If the site is upgraded and the file was removed it would re-create one + // NOTE: If user removed the marker file to opt out it would re-create a new guid marker file & potentially skew + if (_runtime.Level == RuntimeLevel.Upgrade && File.Exists(telemetricsFilePath)) + { + _logger.LogWarning("When upgrading the anonymous telemetry file already existed on disk at {filePath}", telemetricsFilePath); + return; + } + if (_runtime.Level == RuntimeLevel.Install && File.Exists(telemetricsFilePath)) + { + // No need to log for when level is install if file exists (As this component hit several times during install process) + return; + } + + // We are a clean install or an upgrade without the marker file + // Generate GUID + var telemetrySiteIdentifier = Guid.NewGuid(); + + // Write file contents + try + { + File.WriteAllText(telemetricsFilePath, telemetrySiteIdentifier.ToString()); + } + catch (Exception ex) + { + _logger.LogError(ex, "Unable to create telemetry file at {filePath}", telemetricsFilePath); + } + + + + } + + public void Terminate() + { + } + } +} diff --git a/src/Umbraco.Core/Telemetry/TelemetryMarkerComposer.cs b/src/Umbraco.Core/Telemetry/TelemetryMarkerComposer.cs new file mode 100644 index 0000000000..ccb66c7b2a --- /dev/null +++ b/src/Umbraco.Core/Telemetry/TelemetryMarkerComposer.cs @@ -0,0 +1,8 @@ +using Umbraco.Core; +using Umbraco.Core.Composing; + +namespace Umbraco.Web.Telemetry +{ + public class TelemetryMarkerComposer : ComponentComposer, ICoreComposer + { } +} diff --git a/src/Umbraco.Infrastructure/BackOffice/BackOfficeUserStore.cs b/src/Umbraco.Infrastructure/BackOffice/BackOfficeUserStore.cs deleted file mode 100644 index b271f5aa41..0000000000 --- a/src/Umbraco.Infrastructure/BackOffice/BackOfficeUserStore.cs +++ /dev/null @@ -1,919 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Identity; -using Microsoft.Extensions.Options; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.Models; -using Umbraco.Core.Mapping; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Identity; -using Umbraco.Core.Models.Membership; -using Umbraco.Core.Scoping; -using Umbraco.Core.Services; - -namespace Umbraco.Core.BackOffice -{ - public class BackOfficeUserStore : DisposableObjectSlim, - IUserPasswordStore, - IUserEmailStore, - IUserLoginStore, - IUserRoleStore, - IUserSecurityStampStore, - IUserLockoutStore, - IUserSessionStore - - // TODO: This would require additional columns/tables and then a lot of extra coding support to make this happen natively within umbraco - //IUserTwoFactorStore, - // TODO: This would require additional columns/tables for now people will need to implement this on their own - //IUserPhoneNumberStore, - // TODO: To do this we need to implement IQueryable - we'll have an IQuerable implementation soon with the UmbracoLinqPadDriver implementation - //IQueryableUserStore - { - private readonly IScopeProvider _scopeProvider; - private readonly IUserService _userService; - private readonly IEntityService _entityService; - private readonly IExternalLoginService _externalLoginService; - private readonly GlobalSettings _globalSettings; - private readonly UmbracoMapper _mapper; - private bool _disposed = false; - - public BackOfficeUserStore(IScopeProvider scopeProvider, IUserService userService, IEntityService entityService, IExternalLoginService externalLoginService, IOptions globalSettings, UmbracoMapper mapper) - { - _scopeProvider = scopeProvider; - _userService = userService; - _entityService = entityService; - _externalLoginService = externalLoginService; - _globalSettings = globalSettings.Value; - if (userService == null) throw new ArgumentNullException("userService"); - if (externalLoginService == null) throw new ArgumentNullException("externalLoginService"); - _mapper = mapper; - _userService = userService; - _externalLoginService = externalLoginService; - } - - /// - /// Handles the disposal of resources. Derived from abstract class which handles common required locking logic. - /// - protected override void DisposeResources() - { - _disposed = true; - } - - public Task GetUserIdAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); - - return Task.FromResult(user.Id.ToString()); - } - - public Task GetUserNameAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); - - return Task.FromResult(user.UserName); - } - - public Task SetUserNameAsync(BackOfficeIdentityUser user, string userName, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); - - user.UserName = userName; - return Task.CompletedTask; - } - - public Task GetNormalizedUserNameAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken) - { - return GetUserNameAsync(user, cancellationToken); - } - - public Task SetNormalizedUserNameAsync(BackOfficeIdentityUser user, string normalizedName, CancellationToken cancellationToken) - { - return SetUserNameAsync(user, normalizedName, cancellationToken); - } - - /// - /// Insert a new user - /// - /// - /// - /// - public Task CreateAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); - - //the password must be 'something' it could be empty if authenticating - // with an external provider so we'll just generate one and prefix it, the - // prefix will help us determine if the password hasn't actually been specified yet. - //this will hash the guid with a salt so should be nicely random - var aspHasher = new PasswordHasher(); - var emptyPasswordValue = Constants.Security.EmptyPasswordPrefix + - aspHasher.HashPassword(user, Guid.NewGuid().ToString("N")); - - var userEntity = new User(_globalSettings, user.Name, user.Email, user.UserName, emptyPasswordValue) - { - Language = user.Culture ?? _globalSettings.DefaultUILanguage, - StartContentIds = user.StartContentIds ?? new int[] { }, - StartMediaIds = user.StartMediaIds ?? new int[] { }, - IsLockedOut = user.IsLockedOut, - }; - - // we have to remember whether Logins property is dirty, since the UpdateMemberProperties will reset it. - var isLoginsPropertyDirty = user.IsPropertyDirty(nameof(BackOfficeIdentityUser.Logins)); - - UpdateMemberProperties(userEntity, user); - - _userService.Save(userEntity); - - if (!userEntity.HasIdentity) throw new DataException("Could not create the user, check logs for details"); - - //re-assign id - user.Id = userEntity.Id; - - if (isLoginsPropertyDirty) - { - _externalLoginService.Save( - user.Id, - user.Logins.Select(x => new ExternalLogin( - x.LoginProvider, - x.ProviderKey, - x.UserData))); - } - - return Task.FromResult(IdentityResult.Success); - } - - /// - /// Update a user - /// - /// - /// - /// - public Task UpdateAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); - - var asInt = user.Id.TryConvertTo(); - if (asInt == false) - { - throw new InvalidOperationException("The user id must be an integer to work with the Umbraco"); - } - - using (var scope = _scopeProvider.CreateScope()) - { - var found = _userService.GetUserById(asInt.Result); - if (found != null) - { - // we have to remember whether Logins property is dirty, since the UpdateMemberProperties will reset it. - var isLoginsPropertyDirty = user.IsPropertyDirty(nameof(BackOfficeIdentityUser.Logins)); - - if (UpdateMemberProperties(found, user)) - { - _userService.Save(found); - } - - if (isLoginsPropertyDirty) - { - _externalLoginService.Save( - found.Id, - user.Logins.Select(x => new ExternalLogin( - x.LoginProvider, - x.ProviderKey, - x.UserData))); - } - } - - scope.Complete(); - } - - return Task.FromResult(IdentityResult.Success); - } - - /// - /// Delete a user - /// - /// - /// - public Task DeleteAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); - - var found = _userService.GetUserById(user.Id); - if (found != null) - { - _userService.Delete(found); - } - _externalLoginService.DeleteUserLogins(user.Id); - - return Task.FromResult(IdentityResult.Success); - } - - /// - /// Finds a user - /// - /// - /// - /// - public async Task FindByIdAsync(string userId, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - - var user = _userService.GetUserById(UserIdToInt(userId)); - if (user == null) return null; - - return await Task.FromResult(AssignLoginsCallback(_mapper.Map(user))); - } - - /// - /// Find a user by name - /// - /// - /// - /// - public async Task FindByNameAsync(string userName, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - var user = _userService.GetByUsername(userName); - if (user == null) - { - return null; - } - - var result = AssignLoginsCallback(_mapper.Map(user)); - - return await Task.FromResult(result); - } - - /// - /// Set the user password hash - /// - /// - /// - /// - public Task SetPasswordHashAsync(BackOfficeIdentityUser user, string passwordHash, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); - if (passwordHash == null) throw new ArgumentNullException(nameof(passwordHash)); - if (string.IsNullOrEmpty(passwordHash)) throw new ArgumentException("Value can't be empty.", nameof(passwordHash)); - - user.PasswordHash = passwordHash; - user.PasswordConfig = null; // Clear this so that it's reset at the repository level - - return Task.CompletedTask; - } - - /// - /// Get the user password hash - /// - /// - /// - /// - public Task GetPasswordHashAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); - - return Task.FromResult(user.PasswordHash); - } - - /// - /// Returns true if a user has a password set - /// - /// - /// - /// - public Task HasPasswordAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); - - return Task.FromResult(string.IsNullOrEmpty(user.PasswordHash) == false); - } - - /// - /// Set the user email - /// - /// - /// - /// - public Task SetEmailAsync(BackOfficeIdentityUser user, string email, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); - if (email.IsNullOrWhiteSpace()) throw new ArgumentNullException(nameof(email)); - - user.Email = email; - - return Task.CompletedTask; - } - - /// - /// Get the user email - /// - /// - /// - /// - public Task GetEmailAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); - - return Task.FromResult(user.Email); - } - - /// - /// Returns true if the user email is confirmed - /// - /// - /// - /// - public Task GetEmailConfirmedAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); - - return Task.FromResult(user.EmailConfirmed); - } - - /// - /// Sets whether the user email is confirmed - /// - /// - /// - /// - public Task SetEmailConfirmedAsync(BackOfficeIdentityUser user, bool confirmed, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - user.EmailConfirmed = confirmed; - return Task.CompletedTask; - } - - /// - /// Returns the user associated with this email - /// - /// - /// - /// - public Task FindByEmailAsync(string email, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - var user = _userService.GetByEmail(email); - var result = user == null - ? null - : _mapper.Map(user); - - return Task.FromResult(AssignLoginsCallback(result)); - } - - public Task GetNormalizedEmailAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken) - { - return GetEmailAsync(user, cancellationToken); - } - - public Task SetNormalizedEmailAsync(BackOfficeIdentityUser user, string normalizedEmail, CancellationToken cancellationToken) - { - return SetEmailAsync(user, normalizedEmail, cancellationToken); - } - - /// - /// Adds a user login with the specified provider and key - /// - /// - /// - /// - /// - public Task AddLoginAsync(BackOfficeIdentityUser user, UserLoginInfo login, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); - if (login == null) throw new ArgumentNullException(nameof(login)); - - var logins = user.Logins; - var instance = new IdentityUserLogin(login.LoginProvider, login.ProviderKey, user.Id); - var userLogin = instance; - logins.Add(userLogin); - - return Task.CompletedTask; - } - - /// - /// Removes the user login with the specified combination if it exists - /// - /// - /// - /// - /// - /// - public Task RemoveLoginAsync(BackOfficeIdentityUser user, string loginProvider, string providerKey, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); - - var userLogin = user.Logins.SingleOrDefault(l => l.LoginProvider == loginProvider && l.ProviderKey == providerKey); - if (userLogin != null) user.Logins.Remove(userLogin); - - return Task.CompletedTask; - } - - /// - /// Returns the linked accounts for this user - /// - /// - /// - /// - public Task> GetLoginsAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); - return Task.FromResult((IList) - user.Logins.Select(l => new UserLoginInfo(l.LoginProvider, l.ProviderKey, l.LoginProvider)).ToList()); - } - - /// - /// Returns the user associated with this login - /// - /// - public Task FindByLoginAsync(string loginProvider, string providerKey, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - - //get all logins associated with the login id - var result = _externalLoginService.Find(loginProvider, providerKey).ToArray(); - if (result.Any()) - { - //return the first user that matches the result - BackOfficeIdentityUser output = null; - foreach (var l in result) - { - var user = _userService.GetUserById(l.UserId); - if (user != null) - { - output = _mapper.Map(user); - break; - } - } - - return Task.FromResult(AssignLoginsCallback(output)); - } - - return Task.FromResult(null); - } - - - /// - /// Adds a user to a role (user group) - /// - /// - /// - /// - /// - public Task AddToRoleAsync(BackOfficeIdentityUser user, string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); - if (normalizedRoleName == null) throw new ArgumentNullException(nameof(normalizedRoleName)); - if (string.IsNullOrWhiteSpace(normalizedRoleName)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(normalizedRoleName)); - - var userRole = user.Roles.SingleOrDefault(r => r.RoleId == normalizedRoleName); - - if (userRole == null) - { - user.AddRole(normalizedRoleName); - } - - return Task.CompletedTask; - } - - /// - /// Removes the role (user group) for the user - /// - /// - /// - /// - /// - public Task RemoveFromRoleAsync(BackOfficeIdentityUser user, string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - - if (user == null) throw new ArgumentNullException(nameof(user)); - if (normalizedRoleName == null) throw new ArgumentNullException(nameof(normalizedRoleName)); - if (string.IsNullOrWhiteSpace(normalizedRoleName)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(normalizedRoleName)); - - var userRole = user.Roles.SingleOrDefault(r => r.RoleId == normalizedRoleName); - - if (userRole != null) - { - user.Roles.Remove(userRole); - } - - return Task.CompletedTask; - } - - /// - /// Returns the roles (user groups) for this user - /// - /// - /// - /// - public Task> GetRolesAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); - return Task.FromResult((IList)user.Roles.Select(x => x.RoleId).ToList()); - } - - /// - /// Returns true if a user is in the role - /// - /// - /// - /// - /// - public Task IsInRoleAsync(BackOfficeIdentityUser user, string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); - return Task.FromResult(user.Roles.Select(x => x.RoleId).InvariantContains(normalizedRoleName)); - } - - /// - /// Lists all users of a given role. - /// - /// - /// Identity Role names are equal to Umbraco UserGroup alias. - /// - public Task> GetUsersInRoleAsync(string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (normalizedRoleName == null) throw new ArgumentNullException(nameof(normalizedRoleName)); - - var userGroup = _userService.GetUserGroupByAlias(normalizedRoleName); - - var users = _userService.GetAllInGroup(userGroup.Id); - IList backOfficeIdentityUsers = users.Select(x => _mapper.Map(x)).ToList(); - - return Task.FromResult(backOfficeIdentityUsers); - } - - /// - /// Set the security stamp for the user - /// - /// - /// - /// - /// - public Task SetSecurityStampAsync(BackOfficeIdentityUser user, string stamp, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); - - user.SecurityStamp = stamp; - return Task.CompletedTask; - } - - /// - /// Get the user security stamp - /// - /// - /// - /// - public Task GetSecurityStampAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); - - //the stamp cannot be null, so if it is currently null then we'll just return a hash of the password - return Task.FromResult(user.SecurityStamp.IsNullOrWhiteSpace() - ? user.PasswordHash.GenerateHash() - : user.SecurityStamp); - } - - private BackOfficeIdentityUser AssignLoginsCallback(BackOfficeIdentityUser user) - { - if (user != null) - { - user.SetLoginsCallback(new Lazy>(() => - _externalLoginService.GetAll(user.Id))); - } - return user; - } - - #region IUserLockoutStore - - /// - /// Returns the DateTimeOffset that represents the end of a user's lockout, any time in the past should be considered not locked out. - /// - /// - /// - /// - /// - /// Currently we do not support a timed lock out, when they are locked out, an admin will have to reset the status - /// - public Task GetLockoutEndDateAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); - - return user.LockoutEndDateUtc.HasValue - ? Task.FromResult(DateTimeOffset.MaxValue) - : Task.FromResult(DateTimeOffset.MinValue); - } - - /// - /// Locks a user out until the specified end date (set to a past date, to unlock a user) - /// - /// - /// - /// - /// - /// Currently we do not support a timed lock out, when they are locked out, an admin will have to reset the status - /// - public Task SetLockoutEndDateAsync(BackOfficeIdentityUser user, DateTimeOffset? lockoutEnd, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); - - user.LockoutEndDateUtc = lockoutEnd.Value.UtcDateTime; - return Task.CompletedTask; - } - - /// - /// Used to record when an attempt to access the user has failed - /// - /// - /// - /// - public Task IncrementAccessFailedCountAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); - - user.AccessFailedCount++; - return Task.FromResult(user.AccessFailedCount); - } - - /// - /// Used to reset the access failed count, typically after the account is successfully accessed - /// - /// - /// - /// - public Task ResetAccessFailedCountAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); - - user.AccessFailedCount = 0; - return Task.CompletedTask; - } - - /// - /// Returns the current number of failed access attempts. This number usually will be reset whenever the password is - /// verified or the account is locked out. - /// - /// - /// - /// - public Task GetAccessFailedCountAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); - return Task.FromResult(user.AccessFailedCount); - } - - /// - /// Returns true - /// - /// - /// - /// - public Task GetLockoutEnabledAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); - return Task.FromResult(user.LockoutEnabled); - } - - /// - /// Doesn't actually perform any function, users can always be locked out - /// - /// - /// - /// - public Task SetLockoutEnabledAsync(BackOfficeIdentityUser user, bool enabled, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); - - user.LockoutEnabled = enabled; - return Task.CompletedTask; - } - #endregion - - private bool UpdateMemberProperties(IUser user, BackOfficeIdentityUser identityUser) - { - var anythingChanged = false; - - //don't assign anything if nothing has changed as this will trigger the track changes of the model - - if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.LastLoginDateUtc)) - || (user.LastLoginDate != default(DateTime) && identityUser.LastLoginDateUtc.HasValue == false) - || identityUser.LastLoginDateUtc.HasValue && user.LastLoginDate.ToUniversalTime() != identityUser.LastLoginDateUtc.Value) - { - anythingChanged = true; - //if the LastLoginDate is being set to MinValue, don't convert it ToLocalTime - var dt = identityUser.LastLoginDateUtc == DateTime.MinValue ? DateTime.MinValue : identityUser.LastLoginDateUtc.Value.ToLocalTime(); - user.LastLoginDate = dt; - } - if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.LastPasswordChangeDateUtc)) - || (user.LastPasswordChangeDate != default(DateTime) && identityUser.LastPasswordChangeDateUtc.HasValue == false) - || identityUser.LastPasswordChangeDateUtc.HasValue && user.LastPasswordChangeDate.ToUniversalTime() != identityUser.LastPasswordChangeDateUtc.Value) - { - anythingChanged = true; - user.LastPasswordChangeDate = identityUser.LastPasswordChangeDateUtc.Value.ToLocalTime(); - } - if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.EmailConfirmed)) - || (user.EmailConfirmedDate.HasValue && user.EmailConfirmedDate.Value != default(DateTime) && identityUser.EmailConfirmed == false) - || ((user.EmailConfirmedDate.HasValue == false || user.EmailConfirmedDate.Value == default(DateTime)) && identityUser.EmailConfirmed)) - { - anythingChanged = true; - user.EmailConfirmedDate = identityUser.EmailConfirmed ? (DateTime?)DateTime.Now : null; - } - if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.Name)) - && user.Name != identityUser.Name && identityUser.Name.IsNullOrWhiteSpace() == false) - { - anythingChanged = true; - user.Name = identityUser.Name; - } - if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.Email)) - && user.Email != identityUser.Email && identityUser.Email.IsNullOrWhiteSpace() == false) - { - anythingChanged = true; - user.Email = identityUser.Email; - } - if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.AccessFailedCount)) - && user.FailedPasswordAttempts != identityUser.AccessFailedCount) - { - anythingChanged = true; - user.FailedPasswordAttempts = identityUser.AccessFailedCount; - } - if (user.IsLockedOut != identityUser.IsLockedOut) - { - anythingChanged = true; - user.IsLockedOut = identityUser.IsLockedOut; - - if (user.IsLockedOut) - { - //need to set the last lockout date - user.LastLockoutDate = DateTime.Now; - } - - } - if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.UserName)) - && user.Username != identityUser.UserName && identityUser.UserName.IsNullOrWhiteSpace() == false) - { - anythingChanged = true; - user.Username = identityUser.UserName; - } - if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.PasswordHash)) - && user.RawPasswordValue != identityUser.PasswordHash && identityUser.PasswordHash.IsNullOrWhiteSpace() == false) - { - anythingChanged = true; - user.RawPasswordValue = identityUser.PasswordHash; - user.PasswordConfiguration = identityUser.PasswordConfig; - } - - if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.Culture)) - && user.Language != identityUser.Culture && identityUser.Culture.IsNullOrWhiteSpace() == false) - { - anythingChanged = true; - user.Language = identityUser.Culture; - } - if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.StartMediaIds)) - && user.StartMediaIds.UnsortedSequenceEqual(identityUser.StartMediaIds) == false) - { - anythingChanged = true; - user.StartMediaIds = identityUser.StartMediaIds; - } - if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.StartContentIds)) - && user.StartContentIds.UnsortedSequenceEqual(identityUser.StartContentIds) == false) - { - anythingChanged = true; - user.StartContentIds = identityUser.StartContentIds; - } - if (user.SecurityStamp != identityUser.SecurityStamp) - { - anythingChanged = true; - user.SecurityStamp = identityUser.SecurityStamp; - } - - // TODO: Fix this for Groups too - if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.Roles)) || identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.Groups))) - { - var userGroupAliases = user.Groups.Select(x => x.Alias).ToArray(); - - var identityUserRoles = identityUser.Roles.Select(x => x.RoleId).ToArray(); - var identityUserGroups = identityUser.Groups.Select(x => x.Alias).ToArray(); - - var combinedAliases = identityUserRoles.Union(identityUserGroups).ToArray(); - - if (userGroupAliases.ContainsAll(combinedAliases) == false - || combinedAliases.ContainsAll(userGroupAliases) == false) - { - anythingChanged = true; - - //clear out the current groups (need to ToArray since we are modifying the iterator) - user.ClearGroups(); - - //go lookup all these groups - var groups = _userService.GetUserGroupsByAlias(combinedAliases).Select(x => x.ToReadOnlyGroup()).ToArray(); - - //use all of the ones assigned and add them - foreach (var group in groups) - { - user.AddGroup(group); - } - - //re-assign - identityUser.Groups = groups; - } - } - - //we should re-set the calculated start nodes - identityUser.CalculatedMediaStartNodeIds = user.CalculateMediaStartNodeIds(_entityService); - identityUser.CalculatedContentStartNodeIds = user.CalculateContentStartNodeIds(_entityService); - - //reset all changes - identityUser.ResetDirtyProperties(false); - - return anythingChanged; - } - - private void ThrowIfDisposed() - { - if (_disposed) throw new ObjectDisposedException(GetType().Name); - } - - public Task ValidateSessionIdAsync(string userId, string sessionId) - { - Guid guidSessionId; - if (Guid.TryParse(sessionId, out guidSessionId)) - { - return Task.FromResult(_userService.ValidateLoginSession(UserIdToInt(userId), guidSessionId)); - } - - return Task.FromResult(false); - } - - private static int UserIdToInt(string userId) - { - var attempt = userId.TryConvertTo(); - if (attempt.Success) return attempt.Result; - - throw new InvalidOperationException("Unable to convert user ID to int", attempt.Exception); - } - } -} diff --git a/src/Umbraco.Infrastructure/Composing/CompositionExtensions/CoreMappingProfiles.cs b/src/Umbraco.Infrastructure/Composing/CompositionExtensions/CoreMappingProfiles.cs index 09a8523cb9..c700938534 100644 --- a/src/Umbraco.Infrastructure/Composing/CompositionExtensions/CoreMappingProfiles.cs +++ b/src/Umbraco.Infrastructure/Composing/CompositionExtensions/CoreMappingProfiles.cs @@ -1,7 +1,7 @@ -using Microsoft.Extensions.DependencyInjection; -using Umbraco.Core.BackOffice; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Core.DependencyInjection; using Umbraco.Core.Mapping; +using Umbraco.Core.Security; using Umbraco.Web.Models.Mapping; namespace Umbraco.Core.Composing.CompositionExtensions @@ -19,7 +19,6 @@ namespace Umbraco.Core.Composing.CompositionExtensions builder.Services.AddUnique(); builder.WithCollectionBuilder() - .Add() .Add() .Add() .Add() diff --git a/src/Umbraco.Infrastructure/HostedServices/HealthCheckNotifier.cs b/src/Umbraco.Infrastructure/HostedServices/HealthCheckNotifier.cs index 1e775abaa9..cd89ebc046 100644 --- a/src/Umbraco.Infrastructure/HostedServices/HealthCheckNotifier.cs +++ b/src/Umbraco.Infrastructure/HostedServices/HealthCheckNotifier.cs @@ -1,4 +1,8 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.Logging; @@ -13,6 +17,7 @@ using Umbraco.Core.Scoping; using Umbraco.Core.Sync; using Umbraco.Infrastructure.HealthCheck; using Umbraco.Web.HealthCheck; +using Umbraco.Web.HealthCheck.NotificationMethods; namespace Umbraco.Infrastructure.HostedServices { @@ -31,6 +36,19 @@ namespace Umbraco.Infrastructure.HostedServices private readonly ILogger _logger; private readonly IProfilingLogger _profilingLogger; + /// + /// Initializes a new instance of the class. + /// + /// The configuration for health check settings. + /// The collection of healthchecks. + /// The collection of healthcheck notification methods. + /// Representation of the state of the Umbraco runtime. + /// Provider of server registrations to the distributed cache. + /// Representation of the main application domain. + /// Provides scopes for database operations. + /// The typed logger. + /// The profiling logger. + /// Parser of crontab expressions. public HealthCheckNotifier( IOptions healthChecksSettings, HealthCheckCollection healthChecks, @@ -42,8 +60,9 @@ namespace Umbraco.Infrastructure.HostedServices ILogger logger, IProfilingLogger profilingLogger, ICronTabParser cronTabParser) - : base(healthChecksSettings.Value.Notification.Period, - healthChecksSettings.Value.GetNotificationDelay(cronTabParser, DateTime.Now, DefaultDelay)) + : base( + healthChecksSettings.Value.Notification.Period, + healthChecksSettings.Value.GetNotificationDelay(cronTabParser, DateTime.Now, DefaultDelay)) { _healthChecksSettings = healthChecksSettings.Value; _healthChecks = healthChecks; @@ -88,25 +107,25 @@ namespace Umbraco.Infrastructure.HostedServices // Ensure we use an explicit scope since we are running on a background thread and plugin health // checks can be making service/database calls so we want to ensure the CallContext/Ambient scope // isn't used since that can be problematic. - using (var scope = _scopeProvider.CreateScope()) + using (IScope scope = _scopeProvider.CreateScope()) using (_profilingLogger.DebugDuration("Health checks executing", "Health checks complete")) { // Don't notify for any checks that are disabled, nor for any disabled just for notifications. - var disabledCheckIds = _healthChecksSettings.Notification.DisabledChecks + Guid[] disabledCheckIds = _healthChecksSettings.Notification.DisabledChecks .Select(x => x.Id) .Union(_healthChecksSettings.DisabledChecks .Select(x => x.Id)) .Distinct() .ToArray(); - var checks = _healthChecks + IEnumerable checks = _healthChecks .Where(x => disabledCheckIds.Contains(x.Id) == false); var results = new HealthCheckResults(checks); results.LogResults(); // Send using registered notification methods that are enabled. - foreach (var notificationMethod in _notifications.Where(x => x.Enabled)) + foreach (IHealthCheckNotificationMethod notificationMethod in _notifications.Where(x => x.Enabled)) { await notificationMethod.SendAsync(results); } diff --git a/src/Umbraco.Infrastructure/HostedServices/KeepAlive.cs b/src/Umbraco.Infrastructure/HostedServices/KeepAlive.cs index 39ac0f3d87..6a56b6f98e 100644 --- a/src/Umbraco.Infrastructure/HostedServices/KeepAlive.cs +++ b/src/Umbraco.Infrastructure/HostedServices/KeepAlive.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Net.Http; using System.Threading.Tasks; using Microsoft.Extensions.Logging; @@ -24,7 +27,24 @@ namespace Umbraco.Infrastructure.HostedServices private readonly IServerRegistrar _serverRegistrar; private readonly IHttpClientFactory _httpClientFactory; - public KeepAlive(IRequestAccessor requestAccessor, IMainDom mainDom, IOptions keepAliveSettings, ILogger logger, IProfilingLogger profilingLogger, IServerRegistrar serverRegistrar, IHttpClientFactory httpClientFactory) + /// + /// Initializes a new instance of the class. + /// + /// Accessor for the current request. + /// Representation of the main application domain. + /// The configuration for keep alive settings. + /// The typed logger. + /// The profiling logger. + /// Provider of server registrations to the distributed cache. + /// Factory for instances. + public KeepAlive( + IRequestAccessor requestAccessor, + IMainDom mainDom, + IOptions keepAliveSettings, + ILogger logger, + IProfilingLogger profilingLogger, + IServerRegistrar serverRegistrar, + IHttpClientFactory httpClientFactory) : base(TimeSpan.FromMinutes(5), DefaultDelay) { _requestAccessor = requestAccessor; @@ -79,8 +99,8 @@ namespace Umbraco.Infrastructure.HostedServices } var request = new HttpRequestMessage(HttpMethod.Get, keepAlivePingUrl); - var httpClient = _httpClientFactory.CreateClient(); - await httpClient.SendAsync(request); + HttpClient httpClient = _httpClientFactory.CreateClient(); + _ = await httpClient.SendAsync(request); } catch (Exception ex) { diff --git a/src/Umbraco.Infrastructure/HostedServices/LogScrubber.cs b/src/Umbraco.Infrastructure/HostedServices/LogScrubber.cs index 1cf2da05f9..ca87d3e84e 100644 --- a/src/Umbraco.Infrastructure/HostedServices/LogScrubber.cs +++ b/src/Umbraco.Infrastructure/HostedServices/LogScrubber.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -27,7 +30,24 @@ namespace Umbraco.Infrastructure.HostedServices private readonly ILogger _logger; private readonly IScopeProvider _scopeProvider; - public LogScrubber(IMainDom mainDom, IServerRegistrar serverRegistrar, IAuditService auditService, IOptions settings, IScopeProvider scopeProvider, ILogger logger, IProfilingLogger profilingLogger) + /// + /// Initializes a new instance of the class. + /// + /// Representation of the main application domain. + /// Provider of server registrations to the distributed cache. + /// Service for handling audit operations. + /// The configuration for logging settings. + /// Provides scopes for database operations. + /// The typed logger. + /// The profiling logger. + public LogScrubber( + IMainDom mainDom, + IServerRegistrar serverRegistrar, + IAuditService auditService, + IOptions settings, + IScopeProvider scopeProvider, + ILogger logger, + IProfilingLogger profilingLogger) : base(TimeSpan.FromHours(4), DefaultDelay) { _mainDom = mainDom; @@ -39,32 +59,34 @@ namespace Umbraco.Infrastructure.HostedServices _profilingLogger = profilingLogger; } - internal override async Task PerformExecuteAsync(object state) + internal override Task PerformExecuteAsync(object state) { switch (_serverRegistrar.GetCurrentServerRole()) { case ServerRole.Replica: _logger.LogDebug("Does not run on replica servers."); - return; + return Task.CompletedTask; case ServerRole.Unknown: _logger.LogDebug("Does not run on servers with unknown role."); - return; + return Task.CompletedTask; } // Ensure we do not run if not main domain, but do NOT lock it if (_mainDom.IsMainDom == false) { _logger.LogDebug("Does not run if not MainDom."); - return; + return Task.CompletedTask; } // Ensure we use an explicit scope since we are running on a background thread. - using (var scope = _scopeProvider.CreateScope()) + using (IScope scope = _scopeProvider.CreateScope()) using (_profilingLogger.DebugDuration("Log scrubbing executing", "Log scrubbing complete")) { _auditService.CleanLogs((int)_settings.MaxLogAge.TotalMinutes); - scope.Complete(); + _ = scope.Complete(); } + + return Task.CompletedTask; } } } diff --git a/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs b/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs index ee77326115..7e9354523a 100644 --- a/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs +++ b/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Hosting; @@ -13,44 +16,54 @@ namespace Umbraco.Infrastructure.HostedServices /// public abstract class RecurringHostedServiceBase : IHostedService, IDisposable { + /// + /// The default delay to use for recurring tasks for the first run after application start-up if no alternative is configured. + /// protected static readonly TimeSpan DefaultDelay = TimeSpan.FromMinutes(3); private readonly TimeSpan _period; private readonly TimeSpan _delay; private Timer _timer; + /// + /// Initializes a new instance of the class. + /// + /// Timepsan representing how often the task should recur. + /// Timespan represeting the initial delay after application start-up before the first run of the task occurs. protected RecurringHostedServiceBase(TimeSpan period, TimeSpan delay) { _period = period; _delay = delay; } + /// public Task StartAsync(CancellationToken cancellationToken) { _timer = new Timer(ExecuteAsync, null, (int)_delay.TotalMilliseconds, (int)_period.TotalMilliseconds); return Task.CompletedTask; } - public async void ExecuteAsync(object state) - { + /// + /// Executes the task. + /// + /// The task state. + public async void ExecuteAsync(object state) => // Delegate work to method returning a task, that can be called and asserted in a unit test. // Without this there can be behaviour where tests pass, but an error within them causes the test // running process to crash. // Hat-tip: https://stackoverflow.com/a/14207615/489433 await PerformExecuteAsync(state); - } internal abstract Task PerformExecuteAsync(object state); + /// public Task StopAsync(CancellationToken cancellationToken) { _timer?.Change(Timeout.Infinite, 0); return Task.CompletedTask; } - public void Dispose() - { - _timer?.Dispose(); - } + /// + public void Dispose() => _timer?.Dispose(); } } diff --git a/src/Umbraco.Infrastructure/HostedServices/ReportSiteTask.cs b/src/Umbraco.Infrastructure/HostedServices/ReportSiteTask.cs new file mode 100644 index 0000000000..95b09d5498 --- /dev/null +++ b/src/Umbraco.Infrastructure/HostedServices/ReportSiteTask.cs @@ -0,0 +1,132 @@ +using Newtonsoft.Json; +using System; +using System.IO; +using System.Net.Http; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Umbraco.Core; +using Umbraco.Core.Configuration; +using Umbraco.Core.Hosting; +using Umbraco.Core.IO; +using Umbraco.Infrastructure.HostedServices; + +namespace Umbraco.Web.Telemetry +{ + public class ReportSiteTask : RecurringHostedServiceBase + { + private readonly ILogger _logger; + private readonly IHostingEnvironment _hostingEnvironment; + private readonly IUmbracoVersion _umbracoVersion; + private static HttpClient s_httpClient; + + public ReportSiteTask( + ILogger logger, + IHostingEnvironment hostingEnvironment, + IUmbracoVersion umbracoVersion) + : base(TimeSpan.FromDays(1), TimeSpan.FromMinutes(1)) + { + _logger = logger; + _hostingEnvironment = hostingEnvironment; + _umbracoVersion = umbracoVersion; + s_httpClient = new HttpClient(); + } + + /// + /// Runs the background task to send the anynomous ID + /// to telemetry service + /// + internal override async Task PerformExecuteAsync(object state) + { + // Try & find file at '/umbraco/telemetrics-id.umb' + var telemetricsFilePath = _hostingEnvironment.MapPathContentRoot(SystemFiles.TelemetricsIdentifier); + + if (File.Exists(telemetricsFilePath) == false) + { + // Some users may have decided to not be tracked by deleting/removing the marker file + _logger.LogWarning("No telemetry marker file found at '{filePath}' and will not report site to telemetry service", telemetricsFilePath); + + return; + } + + + string telemetricsFileContents; + try + { + // Open file & read its contents + // It may throw due to file permissions or file locking + telemetricsFileContents = File.ReadAllText(telemetricsFilePath); + } + catch (Exception ex) + { + // Silently swallow ex - but lets log it (ReadAllText throws a ton of different types of ex) + // Hence the use of general exception type + _logger.LogError(ex, "Error in reading file contents of telemetry marker file found at '{filePath}'", telemetricsFilePath); + + // Exit out early, but mark this task to be repeated in case its a file lock so it can be rechecked the next time round + return; + } + + + // 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(telemetricsFileContents, out var telemetrySiteIdentifier) == false) + { + // Some users may have decided to mess with file contents + _logger.LogWarning("The telemetry marker file found at '{filePath}' with '{telemetrySiteId}' is not a valid identifier for the telemetry service", telemetricsFilePath, telemetrySiteIdentifier); + + return; + } + + try + { + + // Send data to LIVE telemetry + s_httpClient.BaseAddress = new Uri("https://telemetry.umbraco.com/"); + +#if DEBUG + // Send data to DEBUG telemetry service + s_httpClient.BaseAddress = new Uri("https://telemetry.rainbowsrock.net/"); +#endif + + s_httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/json"); + + 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 + + // Set a low timeout - no need to use a larger default timeout for this POST request + s_httpClient.Timeout = new TimeSpan(0, 0, 1); + + // Make a HTTP Post to telemetry service + // https://telemetry.umbraco.com/installs/ + // Fire & Forget, do not need to know if its a 200, 500 etc + using (HttpResponseMessage response = await s_httpClient.SendAsync(request)) + { + } + } + } + catch + { + // Silently swallow + // The user does not need the logs being polluted if our service has fallen over or is down etc + // Hence only loggigng this at a more verbose level (Which users should not be using in prod) + _logger.LogDebug("There was a problem sending a request to the Umbraco telemetry service"); + } + } + + [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.Infrastructure/HostedServices/ScheduledPublishing.cs b/src/Umbraco.Infrastructure/HostedServices/ScheduledPublishing.cs index f48e64ad2d..4c235255c2 100644 --- a/src/Umbraco.Infrastructure/HostedServices/ScheduledPublishing.cs +++ b/src/Umbraco.Infrastructure/HostedServices/ScheduledPublishing.cs @@ -1,8 +1,13 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Umbraco.Core; +using Umbraco.Core.Security; using Umbraco.Core.Services; using Umbraco.Core.Sync; using Umbraco.Web; @@ -19,18 +24,35 @@ namespace Umbraco.Infrastructure.HostedServices private readonly IContentService _contentService; private readonly ILogger _logger; private readonly IMainDom _mainDom; - private readonly IRuntimeState _runtime; + private readonly IRuntimeState _runtimeState; private readonly IServerMessenger _serverMessenger; private readonly IBackOfficeSecurityFactory _backofficeSecurityFactory; private readonly IServerRegistrar _serverRegistrar; private readonly IUmbracoContextFactory _umbracoContextFactory; + /// + /// Initializes a new instance of the class. + /// + /// Representation of the state of the Umbraco runtime. + /// Representation of the main application domain. + /// Provider of server registrations to the distributed cache. + /// Service for handling content operations. + /// Service for creating and managing Umbraco context. + /// The typed logger. + /// Service broadcasting cache notifications to registered servers. + /// Creates and manages instances. public ScheduledPublishing( - IRuntimeState runtime, IMainDom mainDom, IServerRegistrar serverRegistrar, IContentService contentService, - IUmbracoContextFactory umbracoContextFactory, ILogger logger, IServerMessenger serverMessenger, IBackOfficeSecurityFactory backofficeSecurityFactory) + IRuntimeState runtimeState, + IMainDom mainDom, + IServerRegistrar serverRegistrar, + IContentService contentService, + IUmbracoContextFactory umbracoContextFactory, + ILogger logger, + IServerMessenger serverMessenger, + IBackOfficeSecurityFactory backofficeSecurityFactory) : base(TimeSpan.FromMinutes(1), DefaultDelay) { - _runtime = runtime; + _runtimeState = runtimeState; _mainDom = mainDom; _serverRegistrar = serverRegistrar; _contentService = contentService; @@ -40,35 +62,35 @@ namespace Umbraco.Infrastructure.HostedServices _backofficeSecurityFactory = backofficeSecurityFactory; } - internal override async Task PerformExecuteAsync(object state) + internal override Task PerformExecuteAsync(object state) { if (Suspendable.ScheduledPublishing.CanRun == false) { - return; + return Task.CompletedTask; } switch (_serverRegistrar.GetCurrentServerRole()) { case ServerRole.Replica: _logger.LogDebug("Does not run on replica servers."); - return; + return Task.CompletedTask; case ServerRole.Unknown: _logger.LogDebug("Does not run on servers with unknown role."); - return; + return Task.CompletedTask; } // Ensure we do not run if not main domain, but do NOT lock it if (_mainDom.IsMainDom == false) { _logger.LogDebug("Does not run if not MainDom."); - return; + return Task.CompletedTask; } // Do NOT run publishing if not properly running - if (_runtime.Level != RuntimeLevel.Run) + if (_runtimeState.Level != RuntimeLevel.Run) { _logger.LogDebug("Does not run if run level is not Run."); - return; + return Task.CompletedTask; } try @@ -85,16 +107,17 @@ namespace Umbraco.Infrastructure.HostedServices // - and we should definitively *not* have to flush it here (should be auto) // _backofficeSecurityFactory.EnsureBackOfficeSecurity(); - using var contextReference = _umbracoContextFactory.EnsureUmbracoContext(); + using UmbracoContextReference contextReference = _umbracoContextFactory.EnsureUmbracoContext(); try { // Run - var result = _contentService.PerformScheduledPublish(DateTime.Now); - foreach (var grouped in result.GroupBy(x => x.Result)) + IEnumerable result = _contentService.PerformScheduledPublish(DateTime.Now); + foreach (IGrouping grouped in result.GroupBy(x => x.Result)) { _logger.LogInformation( "Scheduled publishing result: '{StatusCount}' items with status {Status}", - grouped.Count(), grouped.Key); + grouped.Count(), + grouped.Key); } } finally @@ -112,7 +135,7 @@ namespace Umbraco.Infrastructure.HostedServices _logger.LogError(ex, "Failed."); } - return; + return Task.CompletedTask; } } } diff --git a/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/InstructionProcessTask.cs b/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/InstructionProcessTask.cs index 8d669178b0..3c291f187b 100644 --- a/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/InstructionProcessTask.cs +++ b/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/InstructionProcessTask.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -20,6 +23,10 @@ namespace Umbraco.Infrastructure.HostedServices.ServerRegistration /// /// Initializes a new instance of the class. /// + /// Representation of the state of the Umbraco runtime. + /// Service broadcasting cache notifications to registered servers. + /// The typed logger. + /// The configuration for global settings. public InstructionProcessTask(IRuntimeState runtimeState, IServerMessenger messenger, ILogger logger, IOptions globalSettings) : base(globalSettings.Value.DatabaseServerMessenger.TimeBetweenSyncOperations, TimeSpan.FromMinutes(1)) { @@ -28,11 +35,11 @@ namespace Umbraco.Infrastructure.HostedServices.ServerRegistration _logger = logger; } - internal override async Task PerformExecuteAsync(object state) + internal override Task PerformExecuteAsync(object state) { if (_runtimeState.Level != RuntimeLevel.Run) { - return; + return Task.CompletedTask; } try @@ -43,6 +50,8 @@ namespace Umbraco.Infrastructure.HostedServices.ServerRegistration { _logger.LogError(e, "Failed (will repeat)."); } + + return Task.CompletedTask; } } } diff --git a/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTask.cs b/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTask.cs index 0384a071c6..25e975582d 100644 --- a/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTask.cs +++ b/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTask.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -18,11 +21,16 @@ namespace Umbraco.Infrastructure.HostedServices.ServerRegistration private readonly IServerRegistrationService _serverRegistrationService; private readonly IRequestAccessor _requestAccessor; private readonly ILogger _logger; - private GlobalSettings _globalSettings; + private readonly GlobalSettings _globalSettings; /// /// Initializes a new instance of the class. /// + /// Representation of the state of the Umbraco runtime. + /// Services for server registrations. + /// Accessor for the current request. + /// The typed logger. + /// The configuration for global settings. public TouchServerTask(IRuntimeState runtimeState, IServerRegistrationService serverRegistrationService, IRequestAccessor requestAccessor, ILogger logger, IOptions globalSettings) : base(globalSettings.Value.DatabaseServerRegistrar.WaitTimeBetweenCalls, TimeSpan.FromSeconds(15)) { @@ -33,18 +41,18 @@ namespace Umbraco.Infrastructure.HostedServices.ServerRegistration _globalSettings = globalSettings.Value; } - internal override async Task PerformExecuteAsync(object state) + internal override Task PerformExecuteAsync(object state) { if (_runtimeState.Level != RuntimeLevel.Run) { - return; + return Task.CompletedTask; } var serverAddress = _requestAccessor.GetApplicationUrl()?.ToString(); if (serverAddress.IsNullOrWhiteSpace()) { _logger.LogWarning("No umbracoApplicationUrl for service (yet), skip."); - return; + return Task.CompletedTask; } try @@ -57,6 +65,8 @@ namespace Umbraco.Infrastructure.HostedServices.ServerRegistration { _logger.LogError(ex, "Failed to update server record in database."); } + + return Task.CompletedTask; } } } diff --git a/src/Umbraco.Infrastructure/HostedServices/TempFileCleanup.cs b/src/Umbraco.Infrastructure/HostedServices/TempFileCleanup.cs index e27b83c8f6..7e3f70d510 100644 --- a/src/Umbraco.Infrastructure/HostedServices/TempFileCleanup.cs +++ b/src/Umbraco.Infrastructure/HostedServices/TempFileCleanup.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.IO; using System.Threading.Tasks; using Microsoft.Extensions.Logging; @@ -23,6 +26,12 @@ namespace Umbraco.Infrastructure.HostedServices private readonly DirectoryInfo[] _tempFolders; private readonly TimeSpan _age = TimeSpan.FromDays(1); + /// + /// Initializes a new instance of the class. + /// + /// Helper service for IO operations. + /// Representation of the main application domain. + /// The typed logger. public TempFileCleanup(IIOHelper ioHelper, IMainDom mainDom, ILogger logger) : base(TimeSpan.FromMinutes(60), DefaultDelay) { @@ -33,33 +42,33 @@ namespace Umbraco.Infrastructure.HostedServices _tempFolders = _ioHelper.GetTempFolders(); } - internal override async Task PerformExecuteAsync(object state) + internal override Task PerformExecuteAsync(object state) { // Ensure we do not run if not main domain if (_mainDom.IsMainDom == false) { _logger.LogDebug("Does not run if not MainDom."); - return; + return Task.CompletedTask; } - foreach (var folder in _tempFolders) + foreach (DirectoryInfo folder in _tempFolders) { CleanupFolder(folder); } - return; + return Task.CompletedTask; } private void CleanupFolder(DirectoryInfo folder) { - var result = _ioHelper.CleanFolder(folder, _age); + CleanFolderResult result = _ioHelper.CleanFolder(folder, _age); switch (result.Status) { case CleanFolderResultStatus.FailedAsDoesNotExist: _logger.LogDebug("The cleanup folder doesn't exist {Folder}", folder.FullName); break; case CleanFolderResultStatus.FailedWithException: - foreach (var error in result.Errors) + foreach (CleanFolderResult.Error error in result.Errors) { _logger.LogError(error.Exception, "Could not delete temp file {FileName}", error.ErroringFile.FullName); } @@ -74,8 +83,8 @@ namespace Umbraco.Infrastructure.HostedServices return; } - var files = folder.GetFiles("*.*", SearchOption.AllDirectories); - foreach (var file in files) + FileInfo[] files = folder.GetFiles("*.*", SearchOption.AllDirectories); + foreach (FileInfo file in files) { if (DateTime.UtcNow - file.LastWriteTimeUtc > _age) { diff --git a/src/Umbraco.Infrastructure/Migrations/PostMigrations/ClearCsrfCookies.cs b/src/Umbraco.Infrastructure/Migrations/PostMigrations/ClearCsrfCookies.cs index 3a91fc3369..2616e3a926 100644 --- a/src/Umbraco.Infrastructure/Migrations/PostMigrations/ClearCsrfCookies.cs +++ b/src/Umbraco.Infrastructure/Migrations/PostMigrations/ClearCsrfCookies.cs @@ -10,7 +10,7 @@ namespace Umbraco.Web.Migrations.PostMigrations { private readonly ICookieManager _cookieManager; - public ClearCsrfCookies(ICookieManager cookieManager) + public ClearCsrfCookies(IMigrationContext context, ICookieManager cookieManager) { _cookieManager = cookieManager; } diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs index 3fee15c10a..c0a4f5bd35 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs @@ -9,6 +9,7 @@ using Umbraco.Core.Migrations.Upgrade.V_8_0_1; using Umbraco.Core.Migrations.Upgrade.V_8_1_0; using Umbraco.Core.Migrations.Upgrade.V_8_6_0; using Umbraco.Core.Migrations.Upgrade.V_8_9_0; +using Umbraco.Core.Migrations.Upgrade.V_8_10_0; namespace Umbraco.Core.Migrations.Upgrade { @@ -194,7 +195,8 @@ namespace Umbraco.Core.Migrations.Upgrade // to 8.9.0 To("{B5838FF5-1D22-4F6C-BCEB-F83ACB14B575}"); - +// to 8.10.0 + To("{D6A8D863-38EC-44FB-91EC-ACD6A668BD18}"); //FINAL } } diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_10_0/AddPropertyTypeLabelOnTopColumn.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_10_0/AddPropertyTypeLabelOnTopColumn.cs new file mode 100644 index 0000000000..206ea2be02 --- /dev/null +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_10_0/AddPropertyTypeLabelOnTopColumn.cs @@ -0,0 +1,20 @@ +using System.Linq; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_10_0 +{ + + public class AddPropertyTypeLabelOnTopColumn : MigrationBase + { + public AddPropertyTypeLabelOnTopColumn(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToList(); + + AddColumnIfNotExists(columns, "labelOnTop"); + } + } +} diff --git a/src/Umbraco.Infrastructure/Packaging/PackageDataInstallation.cs b/src/Umbraco.Infrastructure/Packaging/PackageDataInstallation.cs index f8bf6283f5..0b9572cb20 100644 --- a/src/Umbraco.Infrastructure/Packaging/PackageDataInstallation.cs +++ b/src/Umbraco.Infrastructure/Packaging/PackageDataInstallation.cs @@ -814,7 +814,10 @@ namespace Umbraco.Core.Packaging SortOrder = sortOrder, Variations = property.Element("Variations") != null ? (ContentVariation)Enum.Parse(typeof(ContentVariation), property.Element("Variations").Value) - : ContentVariation.Nothing + : ContentVariation.Nothing, + LabelOnTop = property.Element("LabelOnTop") != null + ? property.Element("LabelOnTop").Value.ToLowerInvariant().Equals("true") + : false }; if (property.Element("Key") != null) propertyType.Key = new Guid(property.Element("Key").Value); diff --git a/src/Umbraco.Infrastructure/Persistence/Dtos/PropertyTypeDto.cs b/src/Umbraco.Infrastructure/Persistence/Dtos/PropertyTypeDto.cs index 3e8d6e7496..572201c94a 100644 --- a/src/Umbraco.Infrastructure/Persistence/Dtos/PropertyTypeDto.cs +++ b/src/Umbraco.Infrastructure/Persistence/Dtos/PropertyTypeDto.cs @@ -62,6 +62,10 @@ namespace Umbraco.Core.Persistence.Dtos [Length(2000)] public string Description { get; set; } + [Column("labelOnTop")] + [Constraint(Default = "0")] + public bool LabelOnTop { get; set; } + [Column("variations")] [Constraint(Default = "1" /*ContentVariation.InvariantNeutral*/)] public byte Variations { get; set; } diff --git a/src/Umbraco.Infrastructure/Persistence/Dtos/PropertyTypeReadOnlyDto.cs b/src/Umbraco.Infrastructure/Persistence/Dtos/PropertyTypeReadOnlyDto.cs index 4c352a0134..d2001c00d5 100644 --- a/src/Umbraco.Infrastructure/Persistence/Dtos/PropertyTypeReadOnlyDto.cs +++ b/src/Umbraco.Infrastructure/Persistence/Dtos/PropertyTypeReadOnlyDto.cs @@ -44,6 +44,9 @@ namespace Umbraco.Core.Persistence.Dtos [Column("Description")] public string Description { get; set; } + [Column("labelOnTop")] + public bool LabelOnTop { get; set; } + /* cmsMemberType */ [Column("memberCanEdit")] public bool CanEdit { get; set; } diff --git a/src/Umbraco.Infrastructure/Persistence/Dtos/UserDto.cs b/src/Umbraco.Infrastructure/Persistence/Dtos/UserDto.cs index 028b760ba5..46bec34a49 100644 --- a/src/Umbraco.Infrastructure/Persistence/Dtos/UserDto.cs +++ b/src/Umbraco.Infrastructure/Persistence/Dtos/UserDto.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using NPoco; using Umbraco.Core.Persistence.DatabaseAnnotations; @@ -19,6 +19,8 @@ namespace Umbraco.Core.Persistence.Dtos UserStartNodeDtos = new HashSet(); } + // TODO: We need to add a GUID for users and track external logins with that instead of the INT + [Column("id")] [PrimaryKeyColumn(Name = "PK_user")] public int Id { get; set; } diff --git a/src/Umbraco.Infrastructure/Persistence/Factories/ExternalLoginFactory.cs b/src/Umbraco.Infrastructure/Persistence/Factories/ExternalLoginFactory.cs index 74d2fe7ff0..aa4b20aa40 100644 --- a/src/Umbraco.Infrastructure/Persistence/Factories/ExternalLoginFactory.cs +++ b/src/Umbraco.Infrastructure/Persistence/Factories/ExternalLoginFactory.cs @@ -1,4 +1,4 @@ -using System; +using System; using Umbraco.Core.Models.Identity; using Umbraco.Core.Persistence.Dtos; @@ -8,7 +8,7 @@ namespace Umbraco.Core.Persistence.Factories { public static IIdentityUserLogin BuildEntity(ExternalLoginDto dto) { - var entity = new IdentityUserLogin(dto.Id, dto.LoginProvider, dto.ProviderKey, dto.UserId, dto.CreateDate) + var entity = new IdentityUserLogin(dto.Id, dto.LoginProvider, dto.ProviderKey, dto.UserId.ToString(), dto.CreateDate) { UserData = dto.UserData }; @@ -26,7 +26,7 @@ namespace Umbraco.Core.Persistence.Factories CreateDate = entity.CreateDate, LoginProvider = entity.LoginProvider, ProviderKey = entity.ProviderKey, - UserId = entity.UserId, + UserId = int.Parse(entity.UserId), // TODO: This is temp until we change the ext logins to use GUIDs UserData = entity.UserData }; diff --git a/src/Umbraco.Infrastructure/Persistence/Factories/PropertyGroupFactory.cs b/src/Umbraco.Infrastructure/Persistence/Factories/PropertyGroupFactory.cs index ab1de9cb40..29243d3a23 100644 --- a/src/Umbraco.Infrastructure/Persistence/Factories/PropertyGroupFactory.cs +++ b/src/Umbraco.Infrastructure/Persistence/Factories/PropertyGroupFactory.cs @@ -132,7 +132,8 @@ namespace Umbraco.Core.Persistence.Factories ValidationRegExp = propertyType.ValidationRegExp, ValidationRegExpMessage = propertyType.ValidationRegExpMessage, UniqueId = propertyType.Key, - Variations = (byte)propertyType.Variations + Variations = (byte)propertyType.Variations, + LabelOnTop = propertyType.LabelOnTop }; if (tabId != default) diff --git a/src/Umbraco.Infrastructure/Persistence/Mappers/PropertyTypeMapper.cs b/src/Umbraco.Infrastructure/Persistence/Mappers/PropertyTypeMapper.cs index 8aa95fd13a..6892a26d91 100644 --- a/src/Umbraco.Infrastructure/Persistence/Mappers/PropertyTypeMapper.cs +++ b/src/Umbraco.Infrastructure/Persistence/Mappers/PropertyTypeMapper.cs @@ -30,6 +30,7 @@ namespace Umbraco.Core.Persistence.Mappers DefineMap(nameof(PropertyType.SortOrder), nameof(PropertyTypeDto.SortOrder)); DefineMap(nameof(PropertyType.ValidationRegExp), nameof(PropertyTypeDto.ValidationRegExp)); DefineMap(nameof(PropertyType.ValidationRegExpMessage), nameof(PropertyTypeDto.ValidationRegExpMessage)); + DefineMap(nameof(PropertyType.LabelOnTop), nameof(PropertyTypeDto.LabelOnTop)); DefineMap(nameof(PropertyType.PropertyEditorAlias), nameof(DataTypeDto.EditorAlias)); DefineMap(nameof(PropertyType.ValueStorageType), nameof(DataTypeDto.DbType)); } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs index 58e05fa272..657159acc5 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs @@ -307,7 +307,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement SortOrder = dto.SortOrder, ValidationRegExp = dto.ValidationRegExp, ValidationRegExpMessage = dto.ValidationRegExpMessage, - Variations = (ContentVariation)dto.Variations + Variations = (ContentVariation)dto.Variations, + LabelOnTop = dto.LabelOnTop }; } } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ExternalLoginRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ExternalLoginRepository.cs index 33fd3af7fc..c3ed111ffb 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ExternalLoginRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ExternalLoginRepository.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Logging; @@ -13,6 +13,8 @@ using Umbraco.Core.Scoping; namespace Umbraco.Core.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 : NPocoRepositoryBase, IExternalLoginRepository { public ExternalLoginRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger) diff --git a/src/Umbraco.Infrastructure/Runtime/CoreInitialComposer.cs b/src/Umbraco.Infrastructure/Runtime/CoreInitialComposer.cs index b199a7e8dc..c9a67a8543 100644 --- a/src/Umbraco.Infrastructure/Runtime/CoreInitialComposer.cs +++ b/src/Umbraco.Infrastructure/Runtime/CoreInitialComposer.cs @@ -377,6 +377,7 @@ namespace Umbraco.Core.Runtime builder.Services.AddUnique(factory => new LegacyPasswordSecurity()); builder.Services.AddUnique(); builder.Services.AddUnique(); + builder.Services.AddUnique(); builder.Services.AddUnique(); } diff --git a/src/Umbraco.Infrastructure/Scoping/Scope.cs b/src/Umbraco.Infrastructure/Scoping/Scope.cs index 87986a6318..65e8e343f7 100644 --- a/src/Umbraco.Infrastructure/Scoping/Scope.cs +++ b/src/Umbraco.Infrastructure/Scoping/Scope.cs @@ -496,7 +496,7 @@ namespace Umbraco.Core.Scoping // caching config // true if Umbraco.CoreDebugSettings.LogUncompletedScope appSetting is set to "true" private bool LogUncompletedScopes => (_logUncompletedScopes - ?? (_logUncompletedScopes = _coreDebugSettings.LogUncompletedScopes)).Value; + ?? (_logUncompletedScopes = _coreDebugSettings.LogIncompletedScopes)).Value; /// public void ReadLock(params int[] lockIds) => Database.SqlContext.SqlSyntax.ReadLock(Database, lockIds); diff --git a/src/Umbraco.Infrastructure/BackOffice/BackOfficeClaimsPrincipalFactory.cs b/src/Umbraco.Infrastructure/Security/BackOfficeClaimsPrincipalFactory.cs similarity index 53% rename from src/Umbraco.Infrastructure/BackOffice/BackOfficeClaimsPrincipalFactory.cs rename to src/Umbraco.Infrastructure/Security/BackOfficeClaimsPrincipalFactory.cs index 568c028e67..77f707d812 100644 --- a/src/Umbraco.Infrastructure/BackOffice/BackOfficeClaimsPrincipalFactory.cs +++ b/src/Umbraco.Infrastructure/Security/BackOfficeClaimsPrincipalFactory.cs @@ -1,33 +1,50 @@ -using System; +using System; using System.Linq; using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Options; +using Umbraco.Core.Security; -namespace Umbraco.Core.BackOffice +namespace Umbraco.Core.Security { - public class BackOfficeClaimsPrincipalFactory : UserClaimsPrincipalFactory - where TUser : BackOfficeIdentityUser + /// + /// A + /// + public class BackOfficeClaimsPrincipalFactory : UserClaimsPrincipalFactory { - public BackOfficeClaimsPrincipalFactory(UserManager userManager, IOptions optionsAccessor) + + /// + /// Initializes a new instance of the class. + /// + /// The user manager + /// The + public BackOfficeClaimsPrincipalFactory(UserManager userManager, IOptions optionsAccessor) : base(userManager, optionsAccessor) { } - public override async Task CreateAsync(TUser user) + /// + /// + /// Returns a custom and allows flowing claims from the external identity + /// + public override async Task CreateAsync(BackOfficeIdentityUser user) { - if (user == null) throw new ArgumentNullException(nameof(user)); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } - var baseIdentity = await base.GenerateClaimsAsync(user); + ClaimsIdentity baseIdentity = await base.GenerateClaimsAsync(user); // now we can flow any custom claims that the actual user has currently assigned which could be done in the OnExternalLogin callback - foreach (var claim in user.Claims) + foreach (IdentityUserClaim claim in user.Claims) { baseIdentity.AddClaim(new Claim(claim.ClaimType, claim.ClaimValue)); } - + // TODO: We want to remove UmbracoBackOfficeIdentity and only rely on ClaimsIdentity, once + // that is done then we'll create a ClaimsIdentity with all of the requirements here instead var umbracoIdentity = new UmbracoBackOfficeIdentity( baseIdentity, user.Id, @@ -43,7 +60,8 @@ namespace Umbraco.Core.BackOffice return new ClaimsPrincipal(umbracoIdentity); } - protected override async Task GenerateClaimsAsync(TUser user) + /// + protected override async Task GenerateClaimsAsync(BackOfficeIdentityUser user) { // TODO: Have a look at the base implementation https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Extensions.Core/src/UserClaimsPrincipalFactory.cs#L79 // since it's setting an authentication type that is probably not what we want. @@ -51,7 +69,7 @@ namespace Umbraco.Core.BackOffice // the method above just returns a principal that wraps the identity and we dont use a custom principal, // see https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Extensions.Core/src/UserClaimsPrincipalFactory.cs#L66 - var identity = await base.GenerateClaimsAsync(user); + ClaimsIdentity identity = await base.GenerateClaimsAsync(user); return identity; } diff --git a/src/Umbraco.Infrastructure/BackOffice/BackOfficeIdentityBuilder.cs b/src/Umbraco.Infrastructure/Security/BackOfficeIdentityBuilder.cs similarity index 69% rename from src/Umbraco.Infrastructure/BackOffice/BackOfficeIdentityBuilder.cs rename to src/Umbraco.Infrastructure/Security/BackOfficeIdentityBuilder.cs index 5bae03cad6..c9f8d35ada 100644 --- a/src/Umbraco.Infrastructure/BackOffice/BackOfficeIdentityBuilder.cs +++ b/src/Umbraco.Infrastructure/Security/BackOfficeIdentityBuilder.cs @@ -1,18 +1,25 @@ -using System; +using System; using System.Reflection; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.DependencyInjection; -using Umbraco.Core.BackOffice; -namespace Umbraco.Infrastructure.BackOffice +namespace Umbraco.Core.Security { public class BackOfficeIdentityBuilder : IdentityBuilder { - public BackOfficeIdentityBuilder(IServiceCollection services) : base(typeof(BackOfficeIdentityUser), services) + /// + /// Initializes a new instance of the class. + /// + public BackOfficeIdentityBuilder(IServiceCollection services) + : base(typeof(BackOfficeIdentityUser), services) { } - public BackOfficeIdentityBuilder(Type role, IServiceCollection services) : base(typeof(BackOfficeIdentityUser), role, services) + /// + /// Initializes a new instance of the class. + /// + public BackOfficeIdentityBuilder(Type role, IServiceCollection services) + : base(typeof(BackOfficeIdentityUser), role, services) { } @@ -28,10 +35,8 @@ namespace Umbraco.Infrastructure.BackOffice { throw new InvalidOperationException($"Invalid Type for TokenProvider: {provider.FullName}"); } - Services.Configure(options => - { - options.Tokens.ProviderMap[providerName] = new TokenProviderDescriptor(provider); - }); + + Services.Configure(options => options.Tokens.ProviderMap[providerName] = new TokenProviderDescriptor(provider)); Services.AddTransient(provider); return this; } diff --git a/src/Umbraco.Infrastructure/BackOffice/BackOfficeIdentityErrorDescriber.cs b/src/Umbraco.Infrastructure/Security/BackOfficeIdentityErrorDescriber.cs similarity index 55% rename from src/Umbraco.Infrastructure/BackOffice/BackOfficeIdentityErrorDescriber.cs rename to src/Umbraco.Infrastructure/Security/BackOfficeIdentityErrorDescriber.cs index 012ac5650f..6d36e489b8 100644 --- a/src/Umbraco.Infrastructure/BackOffice/BackOfficeIdentityErrorDescriber.cs +++ b/src/Umbraco.Infrastructure/Security/BackOfficeIdentityErrorDescriber.cs @@ -1,11 +1,12 @@ -using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Identity; -namespace Umbraco.Core.BackOffice +namespace Umbraco.Core.Security { /// /// Umbraco back office specific /// public class BackOfficeIdentityErrorDescriber : IdentityErrorDescriber { + // TODO: Override all the methods in order to provide our own translated error messages } } diff --git a/src/Umbraco.Infrastructure/BackOffice/BackOfficeIdentityOptions.cs b/src/Umbraco.Infrastructure/Security/BackOfficeIdentityOptions.cs similarity index 72% rename from src/Umbraco.Infrastructure/BackOffice/BackOfficeIdentityOptions.cs rename to src/Umbraco.Infrastructure/Security/BackOfficeIdentityOptions.cs index 2f729072a6..77849c4d0c 100644 --- a/src/Umbraco.Infrastructure/BackOffice/BackOfficeIdentityOptions.cs +++ b/src/Umbraco.Infrastructure/Security/BackOfficeIdentityOptions.cs @@ -1,6 +1,6 @@ -using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Identity; -namespace Umbraco.Core.BackOffice +namespace Umbraco.Core.Security { /// /// Identity options specifically for the back office identity implementation diff --git a/src/Umbraco.Infrastructure/Security/BackOfficeIdentityUser.cs b/src/Umbraco.Infrastructure/Security/BackOfficeIdentityUser.cs new file mode 100644 index 0000000000..e2e8031768 --- /dev/null +++ b/src/Umbraco.Infrastructure/Security/BackOfficeIdentityUser.cs @@ -0,0 +1,201 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Identity; +using Umbraco.Core.Configuration.Models; +using Umbraco.Core.Models.Entities; +using Umbraco.Core.Models.Identity; +using Umbraco.Core.Models.Membership; + +namespace Umbraco.Core.Security +{ + /// + /// The identity user used for the back office + /// + public class BackOfficeIdentityUser : UmbracoIdentityUser + { + private string _name; + private string _passwordConfig; + private string _culture; + private IReadOnlyCollection _groups; + private string[] _allowedSections; + private int[] _startMediaIds; + private int[] _startContentIds; + + // Custom comparer for enumerables + private static readonly DelegateEqualityComparer> s_groupsComparer = new DelegateEqualityComparer>( + (groups, enumerable) => groups.Select(x => x.Alias).UnsortedSequenceEqual(enumerable.Select(x => x.Alias)), + groups => groups.GetHashCode()); + + private static readonly DelegateEqualityComparer s_startIdsComparer = new DelegateEqualityComparer( + (groups, enumerable) => groups.UnsortedSequenceEqual(enumerable), + groups => groups.GetHashCode()); + + /// + /// Used to construct a new instance without an identity + /// + /// This is allowed to be null (but would need to be filled in if trying to persist this instance) + public static BackOfficeIdentityUser CreateNew(GlobalSettings globalSettings, string username, string email, string culture, string name = null) + { + if (string.IsNullOrWhiteSpace(username)) + { + throw new ArgumentException("Value cannot be null or whitespace.", nameof(username)); + } + + if (string.IsNullOrWhiteSpace(culture)) + { + throw new ArgumentException("Value cannot be null or whitespace.", nameof(culture)); + } + + var user = new BackOfficeIdentityUser(globalSettings, Array.Empty()); + user.DisableChangeTracking(); + user.UserName = username; + user.Email = email; + + user.Id = null; + user.HasIdentity = false; + user._culture = culture; + user._name = name; + user.EnableChangeTracking(); + return user; + } + + private BackOfficeIdentityUser(GlobalSettings globalSettings, IReadOnlyCollection groups) + { + _startMediaIds = Array.Empty(); + _startContentIds = Array.Empty(); + _allowedSections = Array.Empty(); + _culture = globalSettings.DefaultUILanguage; + + // use the property setters - they do more than just setting a field + Groups = groups; + } + + /// + /// Initializes a new instance of the class. + /// + public BackOfficeIdentityUser(GlobalSettings globalSettings, int userId, IEnumerable groups) + : this(globalSettings, groups.ToArray()) + { + // use the property setters - they do more than just setting a field + Id = UserIdToString(userId); + } + + public int[] CalculatedMediaStartNodeIds { get; set; } + public int[] CalculatedContentStartNodeIds { get; set; } + + /// + /// Gets or sets the user's real name + /// + public string Name + { + get => _name; + set => BeingDirty.SetPropertyValueAndDetectChanges(value, ref _name, nameof(Name)); + } + + + public string PasswordConfig + { + get => _passwordConfig; + set => BeingDirty.SetPropertyValueAndDetectChanges(value, ref _passwordConfig, nameof(PasswordConfig)); + } + + + /// + /// Gets or sets content start nodes assigned to the User (not ones assigned to the user's groups) + /// + public int[] StartContentIds + { + get => _startContentIds; + set + { + if (value == null) + { + value = new int[0]; + } + + BeingDirty.SetPropertyValueAndDetectChanges(value, ref _startContentIds, nameof(StartContentIds), s_startIdsComparer); + } + } + + /// + /// Gets or sets media start nodes assigned to the User (not ones assigned to the user's groups) + /// + public int[] StartMediaIds + { + get => _startMediaIds; + set + { + if (value == null) + { + value = new int[0]; + } + + BeingDirty.SetPropertyValueAndDetectChanges(value, ref _startMediaIds, nameof(StartMediaIds), s_startIdsComparer); + } + } + + /// + /// Gets a readonly list of the user's allowed sections which are based on it's user groups + /// + public string[] AllowedSections => _allowedSections ?? (_allowedSections = _groups.SelectMany(x => x.AllowedSections).Distinct().ToArray()); + + /// + /// Gets or sets the culture + /// + public string Culture + { + get => _culture; + set => BeingDirty.SetPropertyValueAndDetectChanges(value, ref _culture, nameof(Culture)); + } + + /// + /// Gets or sets the user groups + /// + public IReadOnlyCollection Groups + { + get => _groups; + set + { + // so they recalculate + _allowedSections = null; + + _groups = value.Where(x => x.Alias != null).ToArray(); + + var roles = new List>(); + foreach (IdentityUserRole identityUserRole in _groups.Select(x => new IdentityUserRole + { + RoleId = x.Alias, + UserId = Id?.ToString() + })) + { + roles.Add(identityUserRole); + } + + // now reset the collection + Roles = roles; + + BeingDirty.SetPropertyValueAndDetectChanges(value, ref _groups, nameof(Groups), s_groupsComparer); + } + } + + /// + /// Gets a value indicating whether the user is locked out based on the user's lockout end date + /// + public bool IsLockedOut + { + get + { + var isLocked = LockoutEnd.HasValue && LockoutEnd.Value.ToLocalTime() >= DateTime.Now; + return isLocked; + } + } + + /// + /// Gets or sets a value indicating the IUser IsApproved + /// + public bool IsApproved { get; set; } + + private static string UserIdToString(int userId) => string.Intern(userId.ToString()); + } +} diff --git a/src/Umbraco.Infrastructure/BackOffice/BackOfficeLookupNormalizer.cs b/src/Umbraco.Infrastructure/Security/BackOfficeLookupNormalizer.cs similarity index 75% rename from src/Umbraco.Infrastructure/BackOffice/BackOfficeLookupNormalizer.cs rename to src/Umbraco.Infrastructure/Security/BackOfficeLookupNormalizer.cs index cc9249d462..957e36d1d0 100644 --- a/src/Umbraco.Infrastructure/BackOffice/BackOfficeLookupNormalizer.cs +++ b/src/Umbraco.Infrastructure/Security/BackOfficeLookupNormalizer.cs @@ -1,6 +1,6 @@ -using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Identity; -namespace Umbraco.Core.BackOffice +namespace Umbraco.Core.Security { /// @@ -8,6 +8,8 @@ namespace Umbraco.Core.BackOffice /// public class BackOfficeLookupNormalizer : ILookupNormalizer { + // TODO: Do we need this? + public string NormalizeName(string name) => name; public string NormalizeEmail(string email) => email; diff --git a/src/Umbraco.Infrastructure/Security/BackOfficeUserStore.cs b/src/Umbraco.Infrastructure/Security/BackOfficeUserStore.cs new file mode 100644 index 0000000000..1756e84d76 --- /dev/null +++ b/src/Umbraco.Infrastructure/Security/BackOfficeUserStore.cs @@ -0,0 +1,777 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Linq; +using System.Security.Claims; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Options; +using Umbraco.Core.Configuration.Models; +using Umbraco.Core.Mapping; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Identity; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Scoping; +using Umbraco.Core.Services; + +namespace Umbraco.Core.Security +{ + // TODO: Make this into a base class that can be re-used + + /// + /// The user store for back office users + /// + public class BackOfficeUserStore : UserStoreBase, string, IdentityUserClaim, IdentityUserRole, IdentityUserLogin, IdentityUserToken, IdentityRoleClaim> + { + private readonly IScopeProvider _scopeProvider; + private readonly IUserService _userService; + private readonly IEntityService _entityService; + private readonly IExternalLoginService _externalLoginService; + private readonly GlobalSettings _globalSettings; + private readonly UmbracoMapper _mapper; + + /// + /// Initializes a new instance of the class. + /// + public BackOfficeUserStore( + IScopeProvider scopeProvider, + IUserService userService, + IEntityService entityService, + IExternalLoginService externalLoginService, + IOptions globalSettings, + UmbracoMapper mapper, + IdentityErrorDescriber describer) + : base(describer) + { + _scopeProvider = scopeProvider; + _userService = userService ?? throw new ArgumentNullException(nameof(userService)); + _entityService = entityService; + _externalLoginService = externalLoginService ?? throw new ArgumentNullException(nameof(externalLoginService)); + _globalSettings = globalSettings.Value; + _mapper = mapper; + _userService = userService; + _externalLoginService = externalLoginService; + } + + /// + /// Not supported in Umbraco + /// + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override IQueryable Users => throw new NotImplementedException(); + + /// + public override Task GetNormalizedUserNameAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken) => GetUserNameAsync(user, cancellationToken); + + /// + public override Task SetNormalizedUserNameAsync(BackOfficeIdentityUser user, string normalizedName, CancellationToken cancellationToken) => SetUserNameAsync(user, normalizedName, cancellationToken); + + /// + public override Task CreateAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + // the password must be 'something' it could be empty if authenticating + // with an external provider so we'll just generate one and prefix it, the + // prefix will help us determine if the password hasn't actually been specified yet. + // this will hash the guid with a salt so should be nicely random + var aspHasher = new PasswordHasher(); + var emptyPasswordValue = Constants.Security.EmptyPasswordPrefix + + aspHasher.HashPassword(user, Guid.NewGuid().ToString("N")); + + var userEntity = new User(_globalSettings, user.Name, user.Email, user.UserName, emptyPasswordValue) + { + Language = user.Culture ?? _globalSettings.DefaultUILanguage, + StartContentIds = user.StartContentIds ?? new int[] { }, + StartMediaIds = user.StartMediaIds ?? new int[] { }, + IsLockedOut = user.IsLockedOut, + }; + + // we have to remember whether Logins property is dirty, since the UpdateMemberProperties will reset it. + var isLoginsPropertyDirty = user.IsPropertyDirty(nameof(BackOfficeIdentityUser.Logins)); + + UpdateMemberProperties(userEntity, user); + + _userService.Save(userEntity); + + if (!userEntity.HasIdentity) + { + throw new DataException("Could not create the user, check logs for details"); + } + + // re-assign id + user.Id = UserIdToString(userEntity.Id); + + if (isLoginsPropertyDirty) + { + _externalLoginService.Save( + userEntity.Id, + user.Logins.Select(x => new ExternalLogin( + x.LoginProvider, + x.ProviderKey, + x.UserData))); + } + + return Task.FromResult(IdentityResult.Success); + } + + /// + public override Task UpdateAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + Attempt asInt = user.Id.TryConvertTo(); + if (asInt == false) + { + throw new InvalidOperationException("The user id must be an integer to work with the Umbraco"); + } + + using (IScope scope = _scopeProvider.CreateScope()) + { + IUser found = _userService.GetUserById(asInt.Result); + if (found != null) + { + // we have to remember whether Logins property is dirty, since the UpdateMemberProperties will reset it. + var isLoginsPropertyDirty = user.IsPropertyDirty(nameof(BackOfficeIdentityUser.Logins)); + + if (UpdateMemberProperties(found, user)) + { + _userService.Save(found); + } + + if (isLoginsPropertyDirty) + { + _externalLoginService.Save( + found.Id, + user.Logins.Select(x => new ExternalLogin( + x.LoginProvider, + x.ProviderKey, + x.UserData))); + } + } + + scope.Complete(); + } + + return Task.FromResult(IdentityResult.Success); + } + + /// + public override Task DeleteAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + IUser found = _userService.GetUserById(UserIdToInt(user.Id)); + if (found != null) + { + _userService.Delete(found); + } + + _externalLoginService.DeleteUserLogins(UserIdToInt(user.Id)); + + return Task.FromResult(IdentityResult.Success); + } + + /// + public override Task FindByIdAsync(string userId, CancellationToken cancellationToken = default) => FindUserAsync(userId, cancellationToken); + + /// + protected override Task FindUserAsync(string userId, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + + IUser user = _userService.GetUserById(UserIdToInt(userId)); + if (user == null) + { + return Task.FromResult((BackOfficeIdentityUser)null); + } + + return Task.FromResult(AssignLoginsCallback(_mapper.Map(user))); + } + + /// + public override Task FindByNameAsync(string userName, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + IUser user = _userService.GetByUsername(userName); + if (user == null) + { + return Task.FromResult((BackOfficeIdentityUser)null); + } + + BackOfficeIdentityUser result = AssignLoginsCallback(_mapper.Map(user)); + + return Task.FromResult(result); + } + + /// + public override async Task SetPasswordHashAsync(BackOfficeIdentityUser user, string passwordHash, CancellationToken cancellationToken = default) + { + await base.SetPasswordHashAsync(user, passwordHash, cancellationToken); + + user.PasswordConfig = null; // Clear this so that it's reset at the repository level + user.LastPasswordChangeDateUtc = DateTime.UtcNow; + } + + /// + public override async Task HasPasswordAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default) + { + // This checks if it's null + var result = await base.HasPasswordAsync(user, cancellationToken); + if (result) + { + // we also want to check empty + return string.IsNullOrEmpty(user.PasswordHash) == false; + } + + return result; + } + + /// + public override Task FindByEmailAsync(string email, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + IUser user = _userService.GetByEmail(email); + BackOfficeIdentityUser result = user == null + ? null + : _mapper.Map(user); + + return Task.FromResult(AssignLoginsCallback(result)); + } + + /// + public override Task GetNormalizedEmailAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken) + => GetEmailAsync(user, cancellationToken); + + /// + public override Task SetNormalizedEmailAsync(BackOfficeIdentityUser user, string normalizedEmail, CancellationToken cancellationToken) + => SetEmailAsync(user, normalizedEmail, cancellationToken); + + /// + public override Task AddLoginAsync(BackOfficeIdentityUser user, UserLoginInfo login, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + if (login == null) + { + throw new ArgumentNullException(nameof(login)); + } + + ICollection logins = user.Logins; + var instance = new IdentityUserLogin(login.LoginProvider, login.ProviderKey, user.Id.ToString()); + IdentityUserLogin userLogin = instance; + logins.Add(userLogin); + + return Task.CompletedTask; + } + + /// + public override Task RemoveLoginAsync(BackOfficeIdentityUser user, string loginProvider, string providerKey, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + IIdentityUserLogin userLogin = user.Logins.SingleOrDefault(l => l.LoginProvider == loginProvider && l.ProviderKey == providerKey); + if (userLogin != null) + { + user.Logins.Remove(userLogin); + } + + return Task.CompletedTask; + } + + /// + public override Task> GetLoginsAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + return Task.FromResult((IList)user.Logins.Select(l => new UserLoginInfo(l.LoginProvider, l.ProviderKey, l.LoginProvider)).ToList()); + } + + /// + protected override async Task> FindUserLoginAsync(string userId, string loginProvider, string providerKey, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + + BackOfficeIdentityUser user = await FindUserAsync(userId, cancellationToken); + if (user == null) + { + return null; + } + + IList logins = await GetLoginsAsync(user, cancellationToken); + UserLoginInfo found = logins.FirstOrDefault(x => x.ProviderKey == providerKey && x.LoginProvider == loginProvider); + if (found == null) + { + return null; + } + + return new IdentityUserLogin + { + LoginProvider = found.LoginProvider, + ProviderKey = found.ProviderKey, + ProviderDisplayName = found.ProviderDisplayName, // TODO: We don't store this value so it will be null + UserId = user.Id + }; + } + + /// + protected override Task> FindUserLoginAsync(string loginProvider, string providerKey, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + + var logins = _externalLoginService.Find(loginProvider, providerKey).ToList(); + if (logins.Count == 0) + { + return Task.FromResult((IdentityUserLogin)null); + } + + IIdentityUserLogin found = logins[0]; + return Task.FromResult(new IdentityUserLogin + { + LoginProvider = found.LoginProvider, + ProviderKey = found.ProviderKey, + ProviderDisplayName = null, // TODO: We don't store this value so it will be null + UserId = found.UserId + }); + } + + /// + /// Adds a user to a role (user group) + /// + public override Task AddToRoleAsync(BackOfficeIdentityUser user, string normalizedRoleName, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + if (normalizedRoleName == null) + { + throw new ArgumentNullException(nameof(normalizedRoleName)); + } + + if (string.IsNullOrWhiteSpace(normalizedRoleName)) + { + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(normalizedRoleName)); + } + + IdentityUserRole userRole = user.Roles.SingleOrDefault(r => r.RoleId == normalizedRoleName); + + if (userRole == null) + { + user.AddRole(normalizedRoleName); + } + + return Task.CompletedTask; + } + + /// + /// Removes the role (user group) for the user + /// + public override Task RemoveFromRoleAsync(BackOfficeIdentityUser user, string normalizedRoleName, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + if (normalizedRoleName == null) + { + throw new ArgumentNullException(nameof(normalizedRoleName)); + } + + if (string.IsNullOrWhiteSpace(normalizedRoleName)) + { + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(normalizedRoleName)); + } + + IdentityUserRole userRole = user.Roles.SingleOrDefault(r => r.RoleId == normalizedRoleName); + + if (userRole != null) + { + user.Roles.Remove(userRole); + } + + return Task.CompletedTask; + } + + /// + /// Returns the roles (user groups) for this user + /// + public override Task> GetRolesAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + return Task.FromResult((IList)user.Roles.Select(x => x.RoleId).ToList()); + } + + /// + /// Returns true if a user is in the role + /// + public override Task IsInRoleAsync(BackOfficeIdentityUser user, string normalizedRoleName, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + return Task.FromResult(user.Roles.Select(x => x.RoleId).InvariantContains(normalizedRoleName)); + } + + /// + /// Lists all users of a given role. + /// + /// + /// Identity Role names are equal to Umbraco UserGroup alias. + /// + public override Task> GetUsersInRoleAsync(string normalizedRoleName, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (normalizedRoleName == null) + { + throw new ArgumentNullException(nameof(normalizedRoleName)); + } + + IUserGroup userGroup = _userService.GetUserGroupByAlias(normalizedRoleName); + + IEnumerable users = _userService.GetAllInGroup(userGroup.Id); + IList backOfficeIdentityUsers = users.Select(x => _mapper.Map(x)).ToList(); + + return Task.FromResult(backOfficeIdentityUsers); + } + + /// + protected override Task> FindRoleAsync(string normalizedRoleName, CancellationToken cancellationToken) + { + IUserGroup group = _userService.GetUserGroupByAlias(normalizedRoleName); + if (group == null) + { + return Task.FromResult((IdentityRole)null); + } + + return Task.FromResult(new IdentityRole(group.Name) + { + Id = group.Alias + }); + } + + /// + protected override async Task> FindUserRoleAsync(string userId, string roleId, CancellationToken cancellationToken) + { + BackOfficeIdentityUser user = await FindUserAsync(userId, cancellationToken); + if (user == null) + { + return null; + } + + IdentityUserRole found = user.Roles.FirstOrDefault(x => x.RoleId.InvariantEquals(roleId)); + return found; + } + + /// + public override Task GetSecurityStampAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + // the stamp cannot be null, so if it is currently null then we'll just return a hash of the password + return Task.FromResult(user.SecurityStamp.IsNullOrWhiteSpace() + ? user.PasswordHash.GenerateHash() + : user.SecurityStamp); + } + + private BackOfficeIdentityUser AssignLoginsCallback(BackOfficeIdentityUser user) + { + if (user != null) + { + user.SetLoginsCallback(new Lazy>(() => _externalLoginService.GetAll(UserIdToInt(user.Id)))); + } + + return user; + } + + private bool UpdateMemberProperties(IUser user, BackOfficeIdentityUser identityUser) + { + var anythingChanged = false; + + // don't assign anything if nothing has changed as this will trigger the track changes of the model + if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.LastLoginDateUtc)) + || (user.LastLoginDate != default && identityUser.LastLoginDateUtc.HasValue == false) + || (identityUser.LastLoginDateUtc.HasValue && user.LastLoginDate.ToUniversalTime() != identityUser.LastLoginDateUtc.Value)) + { + anythingChanged = true; + + // if the LastLoginDate is being set to MinValue, don't convert it ToLocalTime + DateTime dt = identityUser.LastLoginDateUtc == DateTime.MinValue ? DateTime.MinValue : identityUser.LastLoginDateUtc.Value.ToLocalTime(); + user.LastLoginDate = dt; + } + + if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.LastPasswordChangeDateUtc)) + || (user.LastPasswordChangeDate != default && identityUser.LastPasswordChangeDateUtc.HasValue == false) + || (identityUser.LastPasswordChangeDateUtc.HasValue && user.LastPasswordChangeDate.ToUniversalTime() != identityUser.LastPasswordChangeDateUtc.Value)) + { + anythingChanged = true; + user.LastPasswordChangeDate = identityUser.LastPasswordChangeDateUtc.Value.ToLocalTime(); + } + + if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.EmailConfirmed)) + || (user.EmailConfirmedDate.HasValue && user.EmailConfirmedDate.Value != default && identityUser.EmailConfirmed == false) + || ((user.EmailConfirmedDate.HasValue == false || user.EmailConfirmedDate.Value == default) && identityUser.EmailConfirmed)) + { + anythingChanged = true; + user.EmailConfirmedDate = identityUser.EmailConfirmed ? (DateTime?)DateTime.Now : null; + } + + if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.Name)) + && user.Name != identityUser.Name && identityUser.Name.IsNullOrWhiteSpace() == false) + { + anythingChanged = true; + user.Name = identityUser.Name; + } + + if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.Email)) + && user.Email != identityUser.Email && identityUser.Email.IsNullOrWhiteSpace() == false) + { + anythingChanged = true; + user.Email = identityUser.Email; + } + + if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.AccessFailedCount)) + && user.FailedPasswordAttempts != identityUser.AccessFailedCount) + { + anythingChanged = true; + user.FailedPasswordAttempts = identityUser.AccessFailedCount; + } + + if (user.IsLockedOut != identityUser.IsLockedOut) + { + anythingChanged = true; + user.IsLockedOut = identityUser.IsLockedOut; + + if (user.IsLockedOut) + { + // need to set the last lockout date + user.LastLockoutDate = DateTime.Now; + } + } + + if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.UserName)) + && user.Username != identityUser.UserName && identityUser.UserName.IsNullOrWhiteSpace() == false) + { + anythingChanged = true; + user.Username = identityUser.UserName; + } + + if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.PasswordHash)) + && user.RawPasswordValue != identityUser.PasswordHash && identityUser.PasswordHash.IsNullOrWhiteSpace() == false) + { + anythingChanged = true; + user.RawPasswordValue = identityUser.PasswordHash; + user.PasswordConfiguration = identityUser.PasswordConfig; + } + + if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.Culture)) + && user.Language != identityUser.Culture && identityUser.Culture.IsNullOrWhiteSpace() == false) + { + anythingChanged = true; + user.Language = identityUser.Culture; + } + + if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.StartMediaIds)) + && user.StartMediaIds.UnsortedSequenceEqual(identityUser.StartMediaIds) == false) + { + anythingChanged = true; + user.StartMediaIds = identityUser.StartMediaIds; + } + + if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.StartContentIds)) + && user.StartContentIds.UnsortedSequenceEqual(identityUser.StartContentIds) == false) + { + anythingChanged = true; + user.StartContentIds = identityUser.StartContentIds; + } + + if (user.SecurityStamp != identityUser.SecurityStamp) + { + anythingChanged = true; + user.SecurityStamp = identityUser.SecurityStamp; + } + + // TODO: Fix this for Groups too + if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.Roles)) || identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.Groups))) + { + var userGroupAliases = user.Groups.Select(x => x.Alias).ToArray(); + + var identityUserRoles = identityUser.Roles.Select(x => x.RoleId).ToArray(); + var identityUserGroups = identityUser.Groups.Select(x => x.Alias).ToArray(); + + var combinedAliases = identityUserRoles.Union(identityUserGroups).ToArray(); + + if (userGroupAliases.ContainsAll(combinedAliases) == false + || combinedAliases.ContainsAll(userGroupAliases) == false) + { + anythingChanged = true; + + // clear out the current groups (need to ToArray since we are modifying the iterator) + user.ClearGroups(); + + // go lookup all these groups + var groups = _userService.GetUserGroupsByAlias(combinedAliases).Select(x => x.ToReadOnlyGroup()).ToArray(); + + // use all of the ones assigned and add them + foreach (var group in groups) + { + user.AddGroup(group); + } + + // re-assign + identityUser.Groups = groups; + } + } + + // we should re-set the calculated start nodes + identityUser.CalculatedMediaStartNodeIds = user.CalculateMediaStartNodeIds(_entityService); + identityUser.CalculatedContentStartNodeIds = user.CalculateContentStartNodeIds(_entityService); + + // reset all changes + identityUser.ResetDirtyProperties(false); + + return anythingChanged; + } + + /// + public Task ValidateSessionIdAsync(string userId, string sessionId) + { + if (Guid.TryParse(sessionId, out Guid guidSessionId)) + { + return Task.FromResult(_userService.ValidateLoginSession(UserIdToInt(userId), guidSessionId)); + } + + return Task.FromResult(false); + } + + private static int UserIdToInt(string userId) + { + Attempt attempt = userId.TryConvertTo(); + if (attempt.Success) + { + return attempt.Result; + } + + throw new InvalidOperationException("Unable to convert user ID to int", attempt.Exception); + } + + private static string UserIdToString(int userId) => string.Intern(userId.ToString()); + + /// + /// Not supported in Umbraco + /// + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override Task> GetClaimsAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default) => throw new NotImplementedException(); + + /// + /// Not supported in Umbraco + /// + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override Task AddClaimsAsync(BackOfficeIdentityUser user, IEnumerable claims, CancellationToken cancellationToken = default) => throw new NotImplementedException(); + + /// + /// Not supported in Umbraco + /// + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override Task ReplaceClaimAsync(BackOfficeIdentityUser user, Claim claim, Claim newClaim, CancellationToken cancellationToken = default) => throw new NotImplementedException(); + + /// + /// Not supported in Umbraco + /// + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override Task RemoveClaimsAsync(BackOfficeIdentityUser user, IEnumerable claims, CancellationToken cancellationToken = default) => throw new NotImplementedException(); + + /// + /// Not supported in Umbraco + /// + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override Task> GetUsersForClaimAsync(Claim claim, CancellationToken cancellationToken = default) => throw new NotImplementedException(); + + // TODO: We should support these + + /// + /// Not supported in Umbraco + /// + /// + [EditorBrowsable(EditorBrowsableState.Never)] + protected override Task> FindTokenAsync(BackOfficeIdentityUser user, string loginProvider, string name, CancellationToken cancellationToken) => throw new NotImplementedException(); + + /// + /// Not supported in Umbraco + /// + /// + [EditorBrowsable(EditorBrowsableState.Never)] + protected override Task AddUserTokenAsync(IdentityUserToken token) => throw new NotImplementedException(); + + /// + /// Not supported in Umbraco + /// + /// + [EditorBrowsable(EditorBrowsableState.Never)] + protected override Task RemoveUserTokenAsync(IdentityUserToken token) => throw new NotImplementedException(); + } +} diff --git a/src/Umbraco.Infrastructure/BackOffice/BackOfficeUserValidator.cs b/src/Umbraco.Infrastructure/Security/BackOfficeUserValidator.cs similarity index 87% rename from src/Umbraco.Infrastructure/BackOffice/BackOfficeUserValidator.cs rename to src/Umbraco.Infrastructure/Security/BackOfficeUserValidator.cs index 131bd08ac9..8b2c8932a7 100644 --- a/src/Umbraco.Infrastructure/BackOffice/BackOfficeUserValidator.cs +++ b/src/Umbraco.Infrastructure/Security/BackOfficeUserValidator.cs @@ -1,7 +1,8 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; +using Umbraco.Core.Security; -namespace Umbraco.Core.BackOffice +namespace Umbraco.Core.Security { public class BackOfficeUserValidator : UserValidator where T : BackOfficeIdentityUser diff --git a/src/Umbraco.Infrastructure/Security/IBackOfficeUserManager.cs b/src/Umbraco.Infrastructure/Security/IBackOfficeUserManager.cs new file mode 100644 index 0000000000..4235195bb1 --- /dev/null +++ b/src/Umbraco.Infrastructure/Security/IBackOfficeUserManager.cs @@ -0,0 +1,11 @@ +using Umbraco.Core.Security; + +namespace Umbraco.Core.Security +{ + /// + /// The user manager for the back office + /// + public interface IBackOfficeUserManager : IUmbracoUserManager + { + } +} diff --git a/src/Umbraco.Core/BackOffice/IBackOfficeUserPasswordChecker.cs b/src/Umbraco.Infrastructure/Security/IBackOfficeUserPasswordChecker.cs similarity index 83% rename from src/Umbraco.Core/BackOffice/IBackOfficeUserPasswordChecker.cs rename to src/Umbraco.Infrastructure/Security/IBackOfficeUserPasswordChecker.cs index 5874337f4a..fdf1f1fcf2 100644 --- a/src/Umbraco.Core/BackOffice/IBackOfficeUserPasswordChecker.cs +++ b/src/Umbraco.Infrastructure/Security/IBackOfficeUserPasswordChecker.cs @@ -1,6 +1,6 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; -namespace Umbraco.Core.BackOffice +namespace Umbraco.Core.Security { /// /// Used by the BackOfficeUserManager to check the username/password which allows for developers to more easily @@ -11,9 +11,6 @@ namespace Umbraco.Core.BackOffice /// /// Checks a password for a user /// - /// - /// - /// /// /// This will allow a developer to auto-link a local account which is required if the user queried doesn't exist locally. /// The user parameter will always contain the username, if the user doesn't exist locally, the other properties will not be filled in. diff --git a/src/Umbraco.Infrastructure/BackOffice/IBackOfficeUserManager.cs b/src/Umbraco.Infrastructure/Security/IUmbracoUserManager.cs similarity index 85% rename from src/Umbraco.Infrastructure/BackOffice/IBackOfficeUserManager.cs rename to src/Umbraco.Infrastructure/Security/IUmbracoUserManager.cs index c026c256f5..4bec4c9c7a 100644 --- a/src/Umbraco.Infrastructure/BackOffice/IBackOfficeUserManager.cs +++ b/src/Umbraco.Infrastructure/Security/IUmbracoUserManager.cs @@ -5,27 +5,56 @@ using System.Security.Principal; using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; using Umbraco.Core.Models.Membership; +using Umbraco.Core.Security; using Umbraco.Web.Models.ContentEditing; -namespace Umbraco.Core.BackOffice +namespace Umbraco.Core.Security { - public interface IBackOfficeUserManager : IBackOfficeUserManager - { - } - public interface IBackOfficeUserManager: IDisposable + /// + /// A user manager for Umbraco (either back office users or front-end members) + /// + /// The type of user + public interface IUmbracoUserManager : IDisposable where TUser : BackOfficeIdentityUser { + /// + /// Gets the user id of a user + /// + /// The user + /// A representing the result of the asynchronous operation. Task GetUserIdAsync(TUser user); + /// + /// Get the from a + /// + /// The + /// A representing the result of the asynchronous operation. Task GetUserAsync(ClaimsPrincipal principal); + /// + /// Get the user id from the + /// + /// the + /// Returns the user id from the string GetUserId(ClaimsPrincipal principal); + /// + /// Gets the external logins for the user + /// + /// A representing the result of the asynchronous operation. Task> GetLoginsAsync(TUser user); + /// + /// Deletes a user + /// + /// A representing the result of the asynchronous operation. Task DeleteAsync(TUser user); + /// + /// Finds a user by the external login provider + /// + /// A representing the result of the asynchronous operation. Task FindByLoginAsync(string loginProvider, string providerKey); /// @@ -49,15 +78,11 @@ namespace Umbraco.Core.BackOffice /// /// This is a special method that will reset the password but will raise the Password Changed event instead of the reset event /// - /// - /// - /// - /// /// /// We use this because in the back office the only way an admin can change another user's password without first knowing their password /// is to generate a token and reset it, however, when we do this we want to track a password change, not a password reset /// - Task ChangePasswordWithResetAsync(int userId, string token, string newPassword); + Task ChangePasswordWithResetAsync(string userId, string token, string newPassword); /// /// Validates that an email confirmation token matches the specified . @@ -97,8 +122,6 @@ namespace Umbraco.Core.BackOffice /// /// Override to check the user approval value as well as the user lock out date, by default this only checks the user's locked out date /// - /// - /// /// /// In the ASP.NET Identity world, there is only one value for being locked out, in Umbraco we have 2 so when checking this for Umbraco we need to check both values /// @@ -145,8 +168,7 @@ namespace Umbraco.Core.BackOffice /// The that represents the asynchronous operation, returning true if the /// is valid, otherwise false. /// - Task VerifyUserTokenAsync(TUser user, string tokenProvider, string purpose, - string token); + Task VerifyUserTokenAsync(TUser user, string tokenProvider, string purpose, string token); /// /// Adds the to the specified only if the user @@ -160,7 +182,6 @@ namespace Umbraco.Core.BackOffice /// Task AddPasswordAsync(TUser user, string password); - /// /// Returns a flag indicating whether the given is valid for the /// specified . @@ -183,15 +204,12 @@ namespace Umbraco.Core.BackOffice /// The that represents the asynchronous operation, containing the /// of the operation. /// - Task ChangePasswordAsync(TUser user, string currentPassword, - string newPassword); + Task ChangePasswordAsync(TUser user, string currentPassword, string newPassword); /// /// Used to validate a user's session /// - /// - /// - /// + /// Returns true if the session is valid, otherwise false Task ValidateSessionIdAsync(string userId, string sessionId); /// @@ -206,12 +224,11 @@ namespace Umbraco.Core.BackOffice Task CreateAsync(TUser user); /// - /// Helper method to generate a password for a user based on the current password validator + /// Generate a password for a user based on the current password validator /// - /// + /// A generated password string GeneratePassword(); - /// /// Generates an email confirmation token for the specified user. /// @@ -290,8 +307,16 @@ namespace Umbraco.Core.BackOffice /// The System.Threading.Tasks.Task that represents the asynchronous operation, containing the Microsoft.AspNetCore.Identity.IdentityResult of the operation. Task RemoveLoginAsync(TUser user, string loginProvider, string providerKey); + /// + /// Resets the access failed count for the user + /// + /// A representing the result of the asynchronous operation. Task ResetAccessFailedCountAsync(TUser user); + /// + /// Generates a two factor token for the user + /// + /// A representing the result of the asynchronous operation. Task GenerateTwoFactorTokenAsync(TUser user, string tokenProvider); /// @@ -314,9 +339,10 @@ namespace Umbraco.Core.BackOffice // TODO: These are raised from outside the signinmanager and usermanager in the auth and user controllers, // let's see if there's a way to avoid that and only have these called within signinmanager and usermanager // which means we can remove these from the interface (things like invite seems like they cannot be moved) - void RaiseForgotPasswordRequestedEvent(IPrincipal currentUser, int userId); - void RaiseForgotPasswordChangedSuccessEvent(IPrincipal currentUser, int userId); - SignOutAuditEventArgs RaiseLogoutSuccessEvent(IPrincipal currentUser, int userId); + // TODO: When we change to not having the crappy static events this will need to be revisited + void RaiseForgotPasswordRequestedEvent(IPrincipal currentUser, string userId); + void RaiseForgotPasswordChangedSuccessEvent(IPrincipal currentUser, string userId); + SignOutAuditEventArgs RaiseLogoutSuccessEvent(IPrincipal currentUser, string userId); UserInviteEventArgs RaiseSendingUserInvite(IPrincipal currentUser, UserInvite invite, IUser createdUser); bool HasSendingUserInviteEventHandler { get; } diff --git a/src/Umbraco.Infrastructure/BackOffice/IUserSessionStore.cs b/src/Umbraco.Infrastructure/Security/IUserSessionStore.cs similarity index 55% rename from src/Umbraco.Infrastructure/BackOffice/IUserSessionStore.cs rename to src/Umbraco.Infrastructure/Security/IUserSessionStore.cs index 69d5408cf7..c68d1f13f9 100644 --- a/src/Umbraco.Infrastructure/BackOffice/IUserSessionStore.cs +++ b/src/Umbraco.Infrastructure/Security/IUserSessionStore.cs @@ -1,15 +1,17 @@ using System.Threading.Tasks; -using Microsoft.AspNetCore.Identity; -namespace Umbraco.Core.BackOffice +namespace Umbraco.Core.Security { /// /// An IUserStore interface part to implement if the store supports validating user session Ids /// - /// - public interface IUserSessionStore : IUserStore + /// The user type + public interface IUserSessionStore where TUser : class { + /// + /// Validates a user's session is still valid + /// Task ValidateSessionIdAsync(string userId, string sessionId); } } diff --git a/src/Umbraco.Infrastructure/BackOffice/IdentityExtensions.cs b/src/Umbraco.Infrastructure/Security/IdentityExtensions.cs similarity index 100% rename from src/Umbraco.Infrastructure/BackOffice/IdentityExtensions.cs rename to src/Umbraco.Infrastructure/Security/IdentityExtensions.cs diff --git a/src/Umbraco.Core/BackOffice/IdentityMapDefinition.cs b/src/Umbraco.Infrastructure/Security/IdentityMapDefinition.cs similarity index 95% rename from src/Umbraco.Core/BackOffice/IdentityMapDefinition.cs rename to src/Umbraco.Infrastructure/Security/IdentityMapDefinition.cs index 61fdf82d19..aebb2de5bf 100644 --- a/src/Umbraco.Core/BackOffice/IdentityMapDefinition.cs +++ b/src/Umbraco.Infrastructure/Security/IdentityMapDefinition.cs @@ -1,4 +1,4 @@ -using System; +using System; using Microsoft.Extensions.Options; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; @@ -7,7 +7,7 @@ using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; using Umbraco.Core.Services; -namespace Umbraco.Core.BackOffice +namespace Umbraco.Core.Security { public class IdentityMapDefinition : IMapDefinition { @@ -65,7 +65,7 @@ namespace Umbraco.Core.BackOffice target.Culture = source.GetUserCulture(_textService, _globalSettings).ToString(); // project CultureInfo to string target.IsApproved = source.IsApproved; target.SecurityStamp = source.SecurityStamp; - target.LockoutEndDateUtc = source.IsLockedOut ? DateTime.MaxValue.ToUniversalTime() : (DateTime?) null; + target.LockoutEnd = source.IsLockedOut ? DateTime.MaxValue.ToUniversalTime() : (DateTime?)null; // this was in AutoMapper but does not have a setter anyways //target.AllowedSections = source.AllowedSections.ToArray(), diff --git a/src/Umbraco.Infrastructure/Security/SignOutAuditEventArgs.cs b/src/Umbraco.Infrastructure/Security/SignOutAuditEventArgs.cs index 961c2e6137..626932640c 100644 --- a/src/Umbraco.Infrastructure/Security/SignOutAuditEventArgs.cs +++ b/src/Umbraco.Infrastructure/Security/SignOutAuditEventArgs.cs @@ -1,4 +1,6 @@ -namespace Umbraco.Core.BackOffice +using Umbraco.Core.Security; + +namespace Umbraco.Core.Security { /// @@ -6,7 +8,7 @@ /// public class SignOutAuditEventArgs : IdentityAuditEventArgs { - public SignOutAuditEventArgs(AuditEvent action, string ipAddress, string comment = null, int performingUser = -1, int affectedUser = -1) + public SignOutAuditEventArgs(AuditEvent action, string ipAddress, string comment = null, string performingUser = Constants.Security.SuperUserIdAsString, string affectedUser = Constants.Security.SuperUserIdAsString) : base(action, ipAddress, performingUser, comment, affectedUser, null) { } diff --git a/src/Umbraco.Infrastructure/Security/UmbracoIdentityUser.cs b/src/Umbraco.Infrastructure/Security/UmbracoIdentityUser.cs new file mode 100644 index 0000000000..1b888123be --- /dev/null +++ b/src/Umbraco.Infrastructure/Security/UmbracoIdentityUser.cs @@ -0,0 +1,276 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.ComponentModel; +using Microsoft.AspNetCore.Identity; +using Umbraco.Core.Models.Entities; + +namespace Umbraco.Core.Models.Identity +{ + + /// + /// Abstract class for use in Umbraco Identity for users and members + /// + /// + /// + /// This uses strings for the ID of the user, claims, roles. This is because aspnetcore identity's base store will + /// not support having an INT user PK and a string role PK with the way they've made the generics. So we will just use + /// string for both which makes things more flexible anyways for users and members and also if/when we transition to + /// GUID support + /// + /// + /// This class was originally borrowed from the EF implementation in Identity prior to netcore. + /// The new IdentityUser in netcore does not have properties such as Claims, Roles and Logins and those are instead + /// by default managed with their default user store backed by EF which utilizes EF's change tracking to track these values + /// to a user. We will continue using this approach since it works fine for what we need which does the change tracking of + /// claims, roles and logins directly on the user model. + /// + /// + public abstract class UmbracoIdentityUser : IdentityUser, IRememberBeingDirty + { + private string _id; + private string _email; + private string _userName; + private DateTime? _lastLoginDateUtc; + private bool _emailConfirmed; + private int _accessFailedCount; + private string _passwordHash; + private DateTime? _lastPasswordChangeDateUtc; + private ObservableCollection _logins; + private Lazy> _getLogins; + private ObservableCollection> _roles; + + /// + /// Initializes a new instance of the class. + /// + public UmbracoIdentityUser() + { + // must initialize before setting groups + _roles = new ObservableCollection>(); + _roles.CollectionChanged += Roles_CollectionChanged; + Claims = new List>(); + } + + public event PropertyChangedEventHandler PropertyChanged + { + add + { + BeingDirty.PropertyChanged += value; + } + + remove + { + BeingDirty.PropertyChanged -= value; + } + } + + /// + /// Gets or sets last login date + /// + public DateTime? LastLoginDateUtc + { + get => _lastLoginDateUtc; + set => BeingDirty.SetPropertyValueAndDetectChanges(value, ref _lastLoginDateUtc, nameof(LastLoginDateUtc)); + } + + /// + /// Gets or sets email + /// + public override string Email + { + get => _email; + set => BeingDirty.SetPropertyValueAndDetectChanges(value, ref _email, nameof(Email)); + } + + /// + /// Gets or sets a value indicating whether the email is confirmed, default is false + /// + public override bool EmailConfirmed + { + get => _emailConfirmed; + set => BeingDirty.SetPropertyValueAndDetectChanges(value, ref _emailConfirmed, nameof(EmailConfirmed)); + } + + /// + /// Gets or sets the salted/hashed form of the user password + /// + public override string PasswordHash + { + get => _passwordHash; + set => BeingDirty.SetPropertyValueAndDetectChanges(value, ref _passwordHash, nameof(PasswordHash)); + } + + /// + /// Gets or sets dateTime in UTC when the password was last changed. + /// + public DateTime? LastPasswordChangeDateUtc + { + get => _lastPasswordChangeDateUtc; + set => BeingDirty.SetPropertyValueAndDetectChanges(value, ref _lastPasswordChangeDateUtc, nameof(LastPasswordChangeDateUtc)); + } + + /// + /// Gets or sets a value indicating whether is lockout enabled for this user + /// + /// + /// Currently this is always true for users and members + /// + public override bool LockoutEnabled + { + get => true; + set { } + } + + /// + /// Gets or sets the value to record failures for the purposes of lockout + /// + public override int AccessFailedCount + { + get => _accessFailedCount; + set => BeingDirty.SetPropertyValueAndDetectChanges(value, ref _accessFailedCount, nameof(AccessFailedCount)); + } + + /// + /// Gets or sets the user roles collection + /// + public ICollection> Roles + { + get => _roles; + set + { + _roles.CollectionChanged -= Roles_CollectionChanged; + _roles = new ObservableCollection>(value); + _roles.CollectionChanged += Roles_CollectionChanged; + } + } + + /// + /// Gets navigation the user claims collection + /// + public ICollection> Claims { get; } + + /// + /// Gets the user logins collection + /// + public ICollection Logins + { + get + { + // return if it exists + if (_logins != null) + { + return _logins; + } + + _logins = new ObservableCollection(); + + // if the callback is there and hasn't been created yet then execute it and populate the logins + if (_getLogins != null && !_getLogins.IsValueCreated) + { + foreach (IIdentityUserLogin l in _getLogins.Value) + { + _logins.Add(l); + } + } + + // now assign events + _logins.CollectionChanged += Logins_CollectionChanged; + + return _logins; + } + } + + /// + /// Gets or sets user ID (Primary Key) + /// + public override string Id + { + get => _id; + set + { + _id = value; + HasIdentity = true; + } + } + + /// + /// Gets or sets a value indicating whether returns an Id has been set on this object this will be false if the object is new and not persisted to the database + /// + public bool HasIdentity { get; protected set; } + + /// + /// Gets or sets user name + /// + public override string UserName + { + get => _userName; + set => BeingDirty.SetPropertyValueAndDetectChanges(value, ref _userName, nameof(UserName)); + } + + /// + /// Gets the for change tracking + /// + protected BeingDirty BeingDirty { get; } = new BeingDirty(); + + /// + public bool IsDirty() => BeingDirty.IsDirty(); + + /// + public bool IsPropertyDirty(string propName) => BeingDirty.IsPropertyDirty(propName); + + /// + public IEnumerable GetDirtyProperties() => BeingDirty.GetDirtyProperties(); + + /// + public void ResetDirtyProperties() => BeingDirty.ResetDirtyProperties(); + + /// + public bool WasDirty() => BeingDirty.WasDirty(); + + /// + public bool WasPropertyDirty(string propertyName) => BeingDirty.WasPropertyDirty(propertyName); + + /// + public void ResetWereDirtyProperties() => BeingDirty.ResetWereDirtyProperties(); + + /// + public void ResetDirtyProperties(bool rememberDirty) => BeingDirty.ResetDirtyProperties(rememberDirty); + + /// + public IEnumerable GetWereDirtyProperties() => BeingDirty.GetWereDirtyProperties(); + + /// + /// Disables change tracking. + /// + public void DisableChangeTracking() => BeingDirty.DisableChangeTracking(); + + /// + /// Enables change tracking. + /// + public void EnableChangeTracking() => BeingDirty.EnableChangeTracking(); + + /// + /// Adds a role + /// + /// The role to add + /// + /// Adding a role this way will not reflect on the user's group's collection or it's allowed sections until the user is persisted + /// + public void AddRole(string role) => Roles.Add(new IdentityUserRole + { + UserId = Id, + RoleId = role + }); + + /// + /// Used to set a lazy call back to populate the user's Login list + /// + /// The lazy value + public void SetLoginsCallback(Lazy> callback) => _getLogins = callback ?? throw new ArgumentNullException(nameof(callback)); + + private void Logins_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) => BeingDirty.OnPropertyChanged(nameof(Logins)); + + private void Roles_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) => BeingDirty.OnPropertyChanged(nameof(Roles)); + } +} diff --git a/src/Umbraco.Infrastructure/Security/UmbracoUserManager.cs b/src/Umbraco.Infrastructure/Security/UmbracoUserManager.cs new file mode 100644 index 0000000000..6318218669 --- /dev/null +++ b/src/Umbraco.Infrastructure/Security/UmbracoUserManager.cs @@ -0,0 +1,223 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Umbraco.Core.Configuration; +using Umbraco.Core.Models.Identity; +using Umbraco.Net; + +namespace Umbraco.Core.Security +{ + + /// + /// Abstract class for Umbraco User Managers for back office users or front-end members + /// + /// The type of user + /// /// The type password config + public abstract class UmbracoUserManager : UserManager + where TUser : UmbracoIdentityUser + where TPasswordConfig : class, IPasswordConfiguration, new() + { + private PasswordGenerator _passwordGenerator; + + /// + /// Initializes a new instance of the class. + /// + public UmbracoUserManager( + IIpResolver ipResolver, + IUserStore store, + IOptions optionsAccessor, + IPasswordHasher passwordHasher, + IEnumerable> userValidators, + IEnumerable> passwordValidators, + ILookupNormalizer keyNormalizer, + IdentityErrorDescriber errors, + IServiceProvider services, + ILogger> logger, + IOptions passwordConfiguration) + : base(store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, logger) + { + IpResolver = ipResolver ?? throw new ArgumentNullException(nameof(ipResolver)); + PasswordConfiguration = passwordConfiguration.Value ?? throw new ArgumentNullException(nameof(passwordConfiguration)); + } + + /// + public override bool SupportsUserClaim => false; // We don't support an IUserClaimStore and don't need to (at least currently) + + /// + public override bool SupportsQueryableUsers => false; // It would be nice to support this but we don't need to currently and that would require IQueryable support for our user service/repository + + /// + /// Developers will need to override this to support custom 2 factor auth + /// + /// + public override bool SupportsUserTwoFactor => false; + + /// + public override bool SupportsUserPhoneNumber => false; // We haven't needed to support this yet, though might be necessary for 2FA + + /// + /// Gets the password configuration + /// + public IPasswordConfiguration PasswordConfiguration { get; } + + /// + /// Gets the IP resolver + /// + public IIpResolver IpResolver { get; } + + /// + /// Used to validate a user's session + /// + /// The user id + /// The sesion id + /// True if the sesion is valid, else false + public virtual async Task ValidateSessionIdAsync(string userId, string sessionId) + { + var userSessionStore = Store as IUserSessionStore; + + // if this is not set, for backwards compat (which would be super rare), we'll just approve it + // TODO: This should be removed after members supports this + if (userSessionStore == null) + { + return true; + } + + return await userSessionStore.ValidateSessionIdAsync(userId, sessionId); + } + + /// + /// This will determine which password hasher to use based on what is defined in config + /// + /// The + /// An + protected virtual IPasswordHasher GetDefaultPasswordHasher(IPasswordConfiguration passwordConfiguration) => new PasswordHasher(); + + /// + /// Helper method to generate a password for a user based on the current password validator + /// + /// The generated password + public string GeneratePassword() + { + if (_passwordGenerator == null) + { + _passwordGenerator = new PasswordGenerator(PasswordConfiguration); + } + + var password = _passwordGenerator.GeneratePassword(); + return password; + } + + /// + public override async Task CheckPasswordAsync(TUser user, string password) + { + // we cannot proceed if the user passed in does not have an identity + if (user.HasIdentity == false) + { + return false; + } + + // use the default behavior + return await base.CheckPasswordAsync(user, password); + } + + /// + /// This is a special method that will reset the password but will raise the Password Changed event instead of the reset event + /// + /// The userId + /// The reset password token + /// The new password to set it to + /// The + /// + /// We use this because in the back office the only way an admin can change another user's password without first knowing their password + /// is to generate a token and reset it, however, when we do this we want to track a password change, not a password reset + /// + public virtual async Task ChangePasswordWithResetAsync(string userId, string token, string newPassword) + { + TUser user = await FindByIdAsync(userId); + if (user == null) + { + throw new InvalidOperationException("Could not find user"); + } + + IdentityResult result = await ResetPasswordAsync(user, token, newPassword); + return result; + } + + /// + public override async Task SetLockoutEndDateAsync(TUser user, DateTimeOffset? lockoutEnd) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + IdentityResult result = await base.SetLockoutEndDateAsync(user, lockoutEnd); + + // The way we unlock is by setting the lockoutEnd date to the current datetime + if (!result.Succeeded || lockoutEnd < DateTimeOffset.UtcNow) + { + // Resets the login attempt fails back to 0 when unlock is clicked + await ResetAccessFailedCountAsync(user); + } + + return result; + } + + /// + public override async Task ResetAccessFailedCountAsync(TUser user) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + var lockoutStore = (IUserLockoutStore)Store; + var accessFailedCount = await GetAccessFailedCountAsync(user); + + if (accessFailedCount == 0) + { + return IdentityResult.Success; + } + + await lockoutStore.ResetAccessFailedCountAsync(user, CancellationToken.None); + + return await UpdateAsync(user); + } + + /// + /// Overrides the Microsoft ASP.NET user management method + /// + /// + public override async Task AccessFailedAsync(TUser user) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + var lockoutStore = Store as IUserLockoutStore; + if (lockoutStore == null) + { + throw new NotSupportedException("The current user store does not implement " + typeof(IUserLockoutStore<>)); + } + + var count = await lockoutStore.IncrementAccessFailedCountAsync(user, CancellationToken.None); + + if (count >= Options.Lockout.MaxFailedAccessAttempts) + { + await lockoutStore.SetLockoutEndDateAsync(user, DateTimeOffset.UtcNow.Add(Options.Lockout.DefaultLockoutTimeSpan), CancellationToken.None); + + // NOTE: in normal aspnet identity this would do set the number of failed attempts back to 0 + // here we are persisting the value for the back office + } + + IdentityResult result = await UpdateAsync(user); + return result; + } + + } +} diff --git a/src/Umbraco.Infrastructure/Security/UserInviteEventArgs.cs b/src/Umbraco.Infrastructure/Security/UserInviteEventArgs.cs index 4e980b7bb1..80b05497a8 100644 --- a/src/Umbraco.Infrastructure/Security/UserInviteEventArgs.cs +++ b/src/Umbraco.Infrastructure/Security/UserInviteEventArgs.cs @@ -1,12 +1,13 @@ -using Umbraco.Core.Models.Membership; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Security; using Umbraco.Web.Models.ContentEditing; -namespace Umbraco.Core.BackOffice +namespace Umbraco.Core.Security { public class UserInviteEventArgs : IdentityAuditEventArgs { - public UserInviteEventArgs(string ipAddress, int performingUser, UserInvite invitedUser, IUser localUser, string comment = null) - : base(AuditEvent.SendingUserInvite, ipAddress, performingUser, comment, localUser.Id, localUser.Name) + public UserInviteEventArgs(string ipAddress, string performingUser, UserInvite invitedUser, IUser localUser, string comment = null) + : base(AuditEvent.SendingUserInvite, ipAddress, performingUser, comment, string.Intern(localUser.Id.ToString()), localUser.Name) { InvitedUser = invitedUser ?? throw new System.ArgumentNullException(nameof(invitedUser)); User = localUser; @@ -24,7 +25,7 @@ namespace Umbraco.Core.BackOffice /// /// The local user that has been created that is pending the invite - /// + /// public IUser User { get; } /// diff --git a/src/Umbraco.Infrastructure/Services/Implement/EntityXmlSerializer.cs b/src/Umbraco.Infrastructure/Services/Implement/EntityXmlSerializer.cs index 3702a7e761..479e5cc4d8 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/EntityXmlSerializer.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/EntityXmlSerializer.cs @@ -374,6 +374,7 @@ namespace Umbraco.Core.Services.Implement new XElement("MandatoryMessage", propertyType.MandatoryMessage), new XElement("Validation", propertyType.ValidationRegExp), new XElement("ValidationRegExpMessage", propertyType.ValidationRegExpMessage), + new XElement("LabelOnTop", propertyType.LabelOnTop), new XElement("Description", new XCData(propertyType.Description))); genericProperties.Add(genericProperty); } @@ -501,6 +502,7 @@ namespace Umbraco.Core.Services.Implement new XElement("Tab", propertyGroup == null ? "" : propertyGroup.Name), new XElement("SortOrder", propertyType.SortOrder), new XElement("Mandatory", propertyType.Mandatory.ToString()), + new XElement("LabelOnTop", propertyType.LabelOnTop.ToString()), propertyType.MandatoryMessage != null ? new XElement("MandatoryMessage", propertyType.MandatoryMessage) : null, propertyType.ValidationRegExp != null ? new XElement("Validation", propertyType.ValidationRegExp) : null, propertyType.ValidationRegExpMessage != null ? new XElement("ValidationRegExpMessage", propertyType.ValidationRegExpMessage) : null, diff --git a/src/Umbraco.Infrastructure/Services/Implement/ExternalLoginService.cs b/src/Umbraco.Infrastructure/Services/Implement/ExternalLoginService.cs index fabbfea1d4..5edbe77cdb 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/ExternalLoginService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/ExternalLoginService.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Logging; @@ -25,7 +25,8 @@ namespace Umbraco.Core.Services.Implement { using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { - return _externalLoginRepository.Get(Query().Where(x => x.UserId == userId)) + var asString = userId.ToString(); // TODO: This is temp until we update the external service to support guids for both users and members + return _externalLoginRepository.Get(Query().Where(x => x.UserId == asString)) .ToList(); } } diff --git a/src/Umbraco.Tests.Common/Builders/AuditEntryBuilder.cs b/src/Umbraco.Tests.Common/Builders/AuditEntryBuilder.cs index 6efdc8aca8..447aab3fe9 100644 --- a/src/Umbraco.Tests.Common/Builders/AuditEntryBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/AuditEntryBuilder.cs @@ -1,5 +1,7 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; -using System.Globalization; using Umbraco.Core.Models; using Umbraco.Tests.Common.Builders.Interfaces; @@ -7,7 +9,8 @@ namespace Umbraco.Tests.Common.Builders { public class AuditEntryBuilder : AuditEntryBuilder { - public AuditEntryBuilder() : base(null) + public AuditEntryBuilder() + : base(null) { } } @@ -34,10 +37,59 @@ namespace Umbraco.Tests.Common.Builders private DateTime? _eventDateUtc = null; private int? _performingUserId = null; - public AuditEntryBuilder(TParent parentBuilder) : base(parentBuilder) + public AuditEntryBuilder(TParent parentBuilder) + : base(parentBuilder) { } + public AuditEntryBuilder WithAffectedDetails(string affectedDetails) + { + _affectedDetails = affectedDetails; + return this; + } + + public AuditEntryBuilder WithAffectedUserId(int affectedUserId) + { + _affectedUserId = affectedUserId; + return this; + } + + public AuditEntryBuilder WithEventDetails(string eventDetails) + { + _eventDetails = eventDetails; + return this; + } + + public AuditEntryBuilder WithEventType(string eventType) + { + _eventType = eventType; + return this; + } + + public AuditEntryBuilder WithPerformingDetails(string performingDetails) + { + _performingDetails = performingDetails; + return this; + } + + public AuditEntryBuilder WithPerformingIp(string performingIp) + { + _performingIp = performingIp; + return this; + } + + public AuditEntryBuilder WithEventDate(DateTime eventDateUtc) + { + _eventDateUtc = eventDateUtc; + return this; + } + + public AuditEntryBuilder WithPerformingUserId(int performingUserId) + { + _performingUserId = performingUserId; + return this; + } + DateTime? IWithCreateDateBuilder.CreateDate { get => _createDate; @@ -68,22 +120,20 @@ namespace Umbraco.Tests.Common.Builders set => _updateDate = value; } - - public override IAuditEntry Build() { var id = _id ?? 0; - var key = _key ?? Guid.NewGuid(); - var createDate = _createDate ?? DateTime.Now; - var updateDate = _updateDate ?? DateTime.Now; - var deleteDate = _deleteDate ?? null; + Guid key = _key ?? Guid.NewGuid(); + DateTime createDate = _createDate ?? DateTime.Now; + DateTime updateDate = _updateDate ?? DateTime.Now; + DateTime? deleteDate = _deleteDate ?? null; var affectedDetails = _affectedDetails ?? Guid.NewGuid().ToString(); var affectedUserId = _affectedUserId ?? -1; var eventDetails = _eventDetails ?? Guid.NewGuid().ToString(); var eventType = _eventType ?? "umbraco/user"; var performingDetails = _performingDetails ?? Guid.NewGuid().ToString(); var performingIp = _performingIp ?? "127.0.0.1"; - var eventDateUtc = _eventDateUtc ?? DateTime.UtcNow; + DateTime eventDateUtc = _eventDateUtc ?? DateTime.UtcNow; var performingUserId = _performingUserId ?? -1; return new AuditEntry diff --git a/src/Umbraco.Tests.Common/Builders/BuilderBase.cs b/src/Umbraco.Tests.Common/Builders/BuilderBase.cs index bb8255bbc0..102e5eaf05 100644 --- a/src/Umbraco.Tests.Common/Builders/BuilderBase.cs +++ b/src/Umbraco.Tests.Common/Builders/BuilderBase.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; namespace Umbraco.Tests.Common.Builders diff --git a/src/Umbraco.Tests.Common/Builders/ChildBuilderBase.cs b/src/Umbraco.Tests.Common/Builders/ChildBuilderBase.cs index fcd9691e1f..feb87f9556 100644 --- a/src/Umbraco.Tests.Common/Builders/ChildBuilderBase.cs +++ b/src/Umbraco.Tests.Common/Builders/ChildBuilderBase.cs @@ -1,18 +1,14 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + namespace Umbraco.Tests.Common.Builders { public abstract class ChildBuilderBase : BuilderBase { private readonly TParent _parentBuilder; - protected ChildBuilderBase(TParent parentBuilder) - { - _parentBuilder = parentBuilder; - } - - public TParent Done() - { - return _parentBuilder; - } + protected ChildBuilderBase(TParent parentBuilder) => _parentBuilder = parentBuilder; + public TParent Done() => _parentBuilder; } } diff --git a/src/Umbraco.Tests.Common/Builders/ConfigurationEditorBuilder.cs b/src/Umbraco.Tests.Common/Builders/ConfigurationEditorBuilder.cs index f80529ccab..5c9459bf47 100644 --- a/src/Umbraco.Tests.Common/Builders/ConfigurationEditorBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/ConfigurationEditorBuilder.cs @@ -1,7 +1,8 @@ -using System.Collections.Generic; -using Umbraco.Core; -using Umbraco.Core.PropertyEditors; +// Copyright (c) Umbraco. +// See LICENSE for more details. +using System.Collections.Generic; +using Umbraco.Core.PropertyEditors; namespace Umbraco.Tests.Common.Builders { @@ -9,7 +10,8 @@ namespace Umbraco.Tests.Common.Builders { private IDictionary _defaultConfiguration; - public ConfigurationEditorBuilder(TParent parentBuilder) : base(parentBuilder) + public ConfigurationEditorBuilder(TParent parentBuilder) + : base(parentBuilder) { } @@ -21,7 +23,7 @@ namespace Umbraco.Tests.Common.Builders public override IConfigurationEditor Build() { - var defaultConfiguration = _defaultConfiguration ?? new Dictionary(); + IDictionary defaultConfiguration = _defaultConfiguration ?? new Dictionary(); return new ConfigurationEditor() { diff --git a/src/Umbraco.Tests.Common/Builders/ContentBuilder.cs b/src/Umbraco.Tests.Common/Builders/ContentBuilder.cs index 8cd95e8baf..bd7e3b5b3f 100644 --- a/src/Umbraco.Tests.Common/Builders/ContentBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/ContentBuilder.cs @@ -1,11 +1,14 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; using System.Collections.Generic; using System.Globalization; -using Umbraco.Core.Models; -using Umbraco.Tests.Common.Builders.Interfaces; -using Umbraco.Tests.Common.Builders.Extensions; -using Umbraco.Tests.Testing; using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Tests.Common.Builders.Extensions; +using Umbraco.Tests.Common.Builders.Interfaces; +using Umbraco.Tests.Testing; namespace Umbraco.Tests.Common.Builders { @@ -44,7 +47,7 @@ namespace Umbraco.Tests.Common.Builders private bool? _trashed; private CultureInfo _cultureInfo; private IContentType _contentType; - private IDictionary _cultureNames = new Dictionary(); + private readonly IDictionary _cultureNames = new Dictionary(); private object _propertyValues; private string _propertyValuesCulture; private string _propertyValuesSegment; @@ -105,11 +108,11 @@ namespace Umbraco.Tests.Common.Builders { var id = _id ?? 0; var versionId = _versionId ?? 0; - var key = _key ?? Guid.NewGuid(); + Guid key = _key ?? Guid.NewGuid(); var parentId = _parentId ?? -1; - var parent = _parent ?? null; - var createDate = _createDate ?? DateTime.Now; - var updateDate = _updateDate ?? DateTime.Now; + IContent parent = _parent ?? null; + DateTime createDate = _createDate ?? DateTime.Now; + DateTime updateDate = _updateDate ?? DateTime.Now; var name = _name ?? Guid.NewGuid().ToString(); var creatorId = _creatorId ?? 0; var level = _level ?? 1; @@ -126,7 +129,7 @@ namespace Umbraco.Tests.Common.Builders throw new InvalidOperationException("A content item cannot be constructed without providing a content type. Use AddContentType() or WithContentType()."); } - var contentType = _contentType ?? _contentTypeBuilder.Build(); + IContentType contentType = _contentType ?? _contentTypeBuilder.Build(); Content content; if (parent != null) @@ -149,7 +152,7 @@ namespace Umbraco.Tests.Common.Builders content.SortOrder = sortOrder; content.Trashed = trashed; - foreach (var cultureName in _cultureNames) + foreach (KeyValuePair cultureName in _cultureNames) { content.SetCultureName(cultureName.Value, cultureName.Key); } @@ -158,8 +161,8 @@ namespace Umbraco.Tests.Common.Builders { if (_propertyDataBuilder != null) { - var propertyData = _propertyDataBuilder.Build(); - foreach (var keyValuePair in propertyData) + IDictionary propertyData = _propertyDataBuilder.Build(); + foreach (KeyValuePair keyValuePair in propertyData) { content.SetValue(keyValuePair.Key, keyValuePair.Value); } @@ -175,47 +178,44 @@ namespace Umbraco.Tests.Common.Builders return content; } - public static Content CreateBasicContent(IContentType contentType, int id = 0) - { - return new ContentBuilder() + public static Content CreateBasicContent(IContentType contentType, int id = 0) => + new ContentBuilder() .WithId(id) .WithContentType(contentType) .WithName("Home") .Build(); - } - public static Content CreateSimpleContent(IContentType contentType) - { - return new ContentBuilder() + public static Content CreateSimpleContent(IContentType contentType) => + new ContentBuilder() .WithContentType(contentType) .WithName("Home") .WithPropertyValues(new - { - title = "Welcome to our Home page", - bodyText = "This is the welcome message on the first page", - author = "John Doe" - }) + { + title = "Welcome to our Home page", + bodyText = "This is the welcome message on the first page", + author = "John Doe" + }) .Build(); - } - public static Content CreateSimpleContent(IContentType contentType, string name, int parentId = -1, string culture = null, string segment = null) - { - return new ContentBuilder() + public static Content CreateSimpleContent(IContentType contentType, string name, int parentId = -1, string culture = null, string segment = null) => + new ContentBuilder() .WithContentType(contentType) .WithName(name) .WithParentId(parentId) - .WithPropertyValues(new + .WithPropertyValues( + new { title = "Welcome to our Home page", bodyText = "This is the welcome message on the first page", author = "John Doe" - }, culture, segment) + }, + culture, + segment) .Build(); - } public static Content CreateSimpleContent(IContentType contentType, string name, IContent parent, string culture = null, string segment = null, bool setPropertyValues = true) { - var builder = new ContentBuilder() + ContentBuilder builder = new ContentBuilder() .WithContentType(contentType) .WithName(name) .WithParent(parent); @@ -228,15 +228,18 @@ namespace Umbraco.Tests.Common.Builders if (setPropertyValues) { - builder = builder.WithPropertyValues(new - { - title = name + " Subpage", - bodyText = "This is a subpage", - author = "John Doe" - }, culture, segment); + builder = builder.WithPropertyValues( + new + { + title = name + " Subpage", + bodyText = "This is a subpage", + author = "John Doe" + }, + culture, + segment); } - var content = builder.Build(); + Content content = builder.Build(); content.ResetDirtyProperties(false); @@ -247,7 +250,7 @@ namespace Umbraco.Tests.Common.Builders { var list = new List(); - for (int i = 0; i < amount; i++) + for (var i = 0; i < amount; i++) { var name = "Textpage No-" + i; var content = new Content(name, parentId, contentType) { CreatorId = 0, WriterId = 0 }; @@ -269,14 +272,15 @@ namespace Umbraco.Tests.Common.Builders return list; } - public static Content CreateTextpageContent(IContentType contentType, string name, int parentId) - { - return new ContentBuilder() + + public static Content CreateTextpageContent(IContentType contentType, string name, int parentId) => + new ContentBuilder() .WithId(0) .WithContentType(contentType) .WithName(name) .WithParentId(parentId) - .WithPropertyValues(new + .WithPropertyValues( + new { title = name + " textpage", bodyText = string.Format("This is a textpage based on the {0} ContentType", contentType.Alias), @@ -284,7 +288,6 @@ namespace Umbraco.Tests.Common.Builders description = "This is the meta description for a textpage" }) .Build(); - } public static IEnumerable CreateMultipleTextpageContent(IContentType contentType, int parentId, int amount) { @@ -293,7 +296,7 @@ namespace Umbraco.Tests.Common.Builders for (var i = 0; i < amount; i++) { var name = "Textpage No-" + i; - var content = new ContentBuilder() + Content content = new ContentBuilder() .WithName(name) .WithParentId(parentId) .WithContentType(contentType) @@ -314,7 +317,7 @@ namespace Umbraco.Tests.Common.Builders public static Content CreateAllTypesContent(IContentType contentType, string name, int parentId) { - var content = new ContentBuilder() + Content content = new ContentBuilder() .WithName(name) .WithParentId(parentId) .WithContentType(contentType) diff --git a/src/Umbraco.Tests.Common/Builders/ContentItemSaveBuilder.cs b/src/Umbraco.Tests.Common/Builders/ContentItemSaveBuilder.cs index 13196a038b..5eadd01608 100644 --- a/src/Umbraco.Tests.Common/Builders/ContentItemSaveBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/ContentItemSaveBuilder.cs @@ -1,6 +1,8 @@ -using System.Collections.Generic; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; using System.Linq; -using Umbraco.Core.Models; using Umbraco.Tests.Common.Builders.Interfaces; using Umbraco.Web.Models.ContentEditing; @@ -29,8 +31,8 @@ namespace Umbraco.Tests.Common.Builders var id = _id ?? 0; var parentId = _parentId ?? -1; var contentTypeAlias = _contentTypeAlias ?? null; - var action = _action ?? ContentSaveAction.Save; - var variants = _variantBuilders.Select(x => x.Build()); + ContentSaveAction action = _action ?? ContentSaveAction.Save; + IEnumerable variants = _variantBuilders.Select(x => x.Build()); return new TestContentItemSave() { @@ -47,6 +49,7 @@ namespace Umbraco.Tests.Common.Builders get => _id; set => _id = value; } + int? IWithParentIdBuilder.ParentId { get => _parentId; diff --git a/src/Umbraco.Tests.Common/Builders/ContentPropertyBasicBuilder.cs b/src/Umbraco.Tests.Common/Builders/ContentPropertyBasicBuilder.cs index 35f76bfd6a..f9ad509be4 100644 --- a/src/Umbraco.Tests.Common/Builders/ContentPropertyBasicBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/ContentPropertyBasicBuilder.cs @@ -1,4 +1,7 @@ -using Umbraco.Tests.Common.Builders.Interfaces; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Tests.Common.Builders.Interfaces; using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Tests.Common.Builders @@ -31,9 +34,9 @@ namespace Umbraco.Tests.Common.Builders public ContentPropertyBasicBuilder WithValue(object value) { _value = value; - return this; } + int? IWithIdBuilder.Id { get => _id; diff --git a/src/Umbraco.Tests.Common/Builders/ContentTypeBaseBuilder.cs b/src/Umbraco.Tests.Common/Builders/ContentTypeBaseBuilder.cs index aab18d70af..0f00cc53d7 100644 --- a/src/Umbraco.Tests.Common/Builders/ContentTypeBaseBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/ContentTypeBaseBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; using System.Collections.Generic; using Umbraco.Core.Models; @@ -26,7 +29,8 @@ namespace Umbraco.Tests.Common.Builders IWithIconBuilder, IWithThumbnailBuilder, IWithTrashedBuilder, - IWithIsContainerBuilder where TParent : IBuildContentTypes + IWithIsContainerBuilder + where TParent : IBuildContentTypes { private int? _id; private Guid? _key; @@ -48,7 +52,8 @@ namespace Umbraco.Tests.Common.Builders protected IShortStringHelper ShortStringHelper => new DefaultShortStringHelper(new DefaultShortStringHelperConfig()); - public ContentTypeBaseBuilder(TParent parentBuilder) : base(parentBuilder) + public ContentTypeBaseBuilder(TParent parentBuilder) + : base(parentBuilder) { } @@ -88,7 +93,7 @@ namespace Umbraco.Tests.Common.Builders protected void BuildPropertyGroups(ContentTypeCompositionBase contentType, IEnumerable propertyGroups) { - foreach (var propertyGroup in propertyGroups) + foreach (PropertyGroup propertyGroup in propertyGroups) { contentType.PropertyGroups.Add(propertyGroup); } @@ -99,7 +104,7 @@ namespace Umbraco.Tests.Common.Builders if (propertyTypeIdsIncrementingFrom.HasValue) { var i = propertyTypeIdsIncrementingFrom.Value; - foreach (var propertyType in contentType.PropertyTypes) + foreach (IPropertyType propertyType in contentType.PropertyTypes) { propertyType.Id = ++i; } @@ -112,12 +117,12 @@ namespace Umbraco.Tests.Common.Builders // and ensure there are no clashes). contentType.Id = seedId; var itemid = seedId + 1; - foreach (var propertyGroup in contentType.PropertyGroups) + foreach (PropertyGroup propertyGroup in contentType.PropertyGroups) { propertyGroup.Id = itemid++; } - foreach (var propertyType in contentType.PropertyTypes) + foreach (IPropertyType propertyType in contentType.PropertyTypes) { propertyType.Id = itemid++; } diff --git a/src/Umbraco.Tests.Common/Builders/ContentTypeBuilder.cs b/src/Umbraco.Tests.Common/Builders/ContentTypeBuilder.cs index fca148f542..80aa414e46 100644 --- a/src/Umbraco.Tests.Common/Builders/ContentTypeBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/ContentTypeBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System.Collections.Generic; using System.Linq; using Umbraco.Core; @@ -12,21 +15,23 @@ namespace Umbraco.Tests.Common.Builders IWithPropertyTypeIdsIncrementingFrom, IBuildPropertyTypes { - private List> _propertyGroupBuilders = new List>(); - private List> _noGroupPropertyTypeBuilders = new List>(); - private List _templateBuilders = new List(); - private List _allowedContentTypeBuilders = new List(); + private readonly List> _propertyGroupBuilders = new List>(); + private readonly List> _noGroupPropertyTypeBuilders = new List>(); + private readonly List _templateBuilders = new List(); + private readonly List _allowedContentTypeBuilders = new List(); private int? _propertyTypeIdsIncrementingFrom; private int? _defaultTemplateId; private ContentVariation? _contentVariation; private PropertyTypeCollection _propertyTypeCollection; - public ContentTypeBuilder() : base(null) + public ContentTypeBuilder() + : base(null) { } - public ContentTypeBuilder(ContentBuilder parentBuilder) : base(parentBuilder) + public ContentTypeBuilder(ContentBuilder parentBuilder) + : base(parentBuilder) { } @@ -78,10 +83,10 @@ namespace Umbraco.Tests.Common.Builders public override IContentType Build() { - var contentVariation = _contentVariation ?? ContentVariation.Nothing; + ContentVariation contentVariation = _contentVariation ?? ContentVariation.Nothing; ContentType contentType; - var parent = GetParent(); + IContentTypeComposition parent = GetParent(); if (parent != null) { contentType = new ContentType(ShortStringHelper, (IContentType)parent, GetAlias()); @@ -113,7 +118,7 @@ namespace Umbraco.Tests.Common.Builders if (_propertyTypeCollection != null) { - var propertyGroup = new PropertyGroupBuilder() + PropertyGroup propertyGroup = new PropertyGroupBuilder() .WithName("Content") .WithSortOrder(1) .WithPropertyTypeCollection(_propertyTypeCollection) @@ -153,7 +158,7 @@ namespace Umbraco.Tests.Common.Builders public static ContentType CreateSimpleContentType2(string alias, string name, IContentType parent = null, bool randomizeAliases = false, string propertyGroupName = "Content") { - var builder = CreateSimpleContentTypeHelper(alias, name, parent, randomizeAliases: randomizeAliases, propertyGroupName: propertyGroupName); + ContentTypeBuilder builder = CreateSimpleContentTypeHelper(alias, name, parent, randomizeAliases: randomizeAliases, propertyGroupName: propertyGroupName); builder.AddPropertyType() .WithAlias(RandomAlias("gen", randomizeAliases)) @@ -162,19 +167,18 @@ namespace Umbraco.Tests.Common.Builders .WithDataTypeId(-88) .WithMandatory(false) .WithDescription(string.Empty) + .WithLabelOnTop(true) .Done(); return (ContentType)builder.Build(); } - public static ContentType CreateSimpleContentType(string alias = null, string name = null, IContentType parent = null, PropertyTypeCollection propertyTypeCollection = null, bool randomizeAliases = false, string propertyGroupName = "Content", bool mandatoryProperties = false, int defaultTemplateId = 0) - { - return (ContentType)CreateSimpleContentTypeHelper(alias, name, parent, propertyTypeCollection, randomizeAliases, propertyGroupName, mandatoryProperties, defaultTemplateId).Build(); - } + public static ContentType CreateSimpleContentType(string alias = null, string name = null, IContentType parent = null, PropertyTypeCollection propertyTypeCollection = null, bool randomizeAliases = false, string propertyGroupName = "Content", bool mandatoryProperties = false, int defaultTemplateId = 0) => + (ContentType)CreateSimpleContentTypeHelper(alias, name, parent, propertyTypeCollection, randomizeAliases, propertyGroupName, mandatoryProperties, defaultTemplateId).Build(); public static ContentTypeBuilder CreateSimpleContentTypeHelper(string alias = null, string name = null, IContentType parent = null, PropertyTypeCollection propertyTypeCollection = null, bool randomizeAliases = false, string propertyGroupName = "Content", bool mandatoryProperties = false, int defaultTemplateId = 0) { - var builder = new ContentTypeBuilder() + ContentTypeBuilder builder = new ContentTypeBuilder() .WithAlias(alias ?? "simple") .WithName(name ?? "Simple Page") .WithParentContentType(parent); @@ -196,6 +200,7 @@ namespace Umbraco.Tests.Common.Builders .WithName("Title") .WithSortOrder(1) .WithMandatory(mandatoryProperties) + .WithLabelOnTop(true) .Done() .AddPropertyType() .WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.TinyMce) @@ -205,12 +210,14 @@ namespace Umbraco.Tests.Common.Builders .WithSortOrder(2) .WithDataTypeId(Constants.DataTypes.RichtextEditor) .WithMandatory(mandatoryProperties) + .WithLabelOnTop(true) .Done() .AddPropertyType() .WithAlias(RandomAlias("author", randomizeAliases)) .WithName("Author") .WithSortOrder(3) .WithMandatory(mandatoryProperties) + .WithLabelOnTop(true) .Done() .Done(); } @@ -228,9 +235,9 @@ namespace Umbraco.Tests.Common.Builders public static ContentType CreateSimpleTagsContentType(string alias, string name, IContentType parent = null, bool randomizeAliases = false, string propertyGroupName = "Content", int defaultTemplateId = 1) { - var contentType = CreateSimpleContentType(alias, name, parent, randomizeAliases: randomizeAliases, propertyGroupName: propertyGroupName, defaultTemplateId: defaultTemplateId); + ContentType contentType = CreateSimpleContentType(alias, name, parent, randomizeAliases: randomizeAliases, propertyGroupName: propertyGroupName, defaultTemplateId: defaultTemplateId); - var propertyType = new PropertyTypeBuilder() + PropertyType propertyType = new PropertyTypeBuilder() .WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.Tags) .WithValueStorageType(ValueStorageType.Nvarchar) .WithAlias(RandomAlias("tags", randomizeAliases)) diff --git a/src/Umbraco.Tests.Common/Builders/ContentTypeSortBuilder.cs b/src/Umbraco.Tests.Common/Builders/ContentTypeSortBuilder.cs index 42c3ebcc1b..b1e3c494f1 100644 --- a/src/Umbraco.Tests.Common/Builders/ContentTypeSortBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/ContentTypeSortBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; using Umbraco.Core.Models; using Umbraco.Tests.Common.Builders.Extensions; @@ -15,7 +18,8 @@ namespace Umbraco.Tests.Common.Builders private string _alias; private int? _sortOrder; - public ContentTypeSortBuilder() : base(null) + public ContentTypeSortBuilder() + : base(null) { } @@ -44,7 +48,7 @@ namespace Umbraco.Tests.Common.Builders set => _alias = value; } - int? IWithSortOrderBuilder.SortOrder + int? IWithSortOrderBuilder.SortOrder { get => _sortOrder; set => _sortOrder = value; diff --git a/src/Umbraco.Tests.Common/Builders/ContentVariantSaveBuilder.cs b/src/Umbraco.Tests.Common/Builders/ContentVariantSaveBuilder.cs index 4b7b4b3d2b..3408e6a244 100644 --- a/src/Umbraco.Tests.Common/Builders/ContentVariantSaveBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/ContentVariantSaveBuilder.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; using System.Globalization; using System.Linq; using Umbraco.Tests.Common.Builders.Interfaces; @@ -10,18 +13,30 @@ namespace Umbraco.Tests.Common.Builders IWithNameBuilder, IWithCultureInfoBuilder { - private List>> _propertyBuilders = new List>>(); - + private readonly List>> _propertyBuilders = new List>>(); private string _name; private CultureInfo _cultureInfo; private bool? _save = null; private bool? _publish = null; - public ContentVariantSaveBuilder(TParent parentBuilder) : base(parentBuilder) + public ContentVariantSaveBuilder(TParent parentBuilder) + : base(parentBuilder) { } + public ContentVariantSaveBuilder WithSave(bool save) + { + _save = save; + return this; + } + + public ContentVariantSaveBuilder WithPublish(bool publish) + { + _publish = publish; + return this; + } + public ContentPropertyBasicBuilder> AddProperty() { var builder = new ContentPropertyBasicBuilder>(this); @@ -35,7 +50,7 @@ namespace Umbraco.Tests.Common.Builders var culture = _cultureInfo?.Name ?? null; var save = _save ?? true; var publish = _publish ?? true; - var properties = _propertyBuilders.Select(x => x.Build()); + IEnumerable properties = _propertyBuilders.Select(x => x.Build()); return new ContentVariantSave() { diff --git a/src/Umbraco.Tests.Common/Builders/DataEditorBuilder.cs b/src/Umbraco.Tests.Common/Builders/DataEditorBuilder.cs index 4cc7b3ad8e..a600d2d962 100644 --- a/src/Umbraco.Tests.Common/Builders/DataEditorBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/DataEditorBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System.Collections.Generic; using Microsoft.Extensions.Logging.Abstractions; using Moq; @@ -10,11 +13,12 @@ namespace Umbraco.Tests.Common.Builders { public class DataEditorBuilder : ChildBuilderBase { - private ConfigurationEditorBuilder> _explicitConfigurationEditorBuilder; - private DataValueEditorBuilder> _explicitValueEditorBuilder; + private readonly ConfigurationEditorBuilder> _explicitConfigurationEditorBuilder; + private readonly DataValueEditorBuilder> _explicitValueEditorBuilder; private IDictionary _defaultConfiguration; - public DataEditorBuilder(TParent parentBuilder) : base(parentBuilder) + public DataEditorBuilder(TParent parentBuilder) + : base(parentBuilder) { _explicitConfigurationEditorBuilder = new ConfigurationEditorBuilder>(this); _explicitValueEditorBuilder = new DataValueEditorBuilder>(this); @@ -34,9 +38,9 @@ namespace Umbraco.Tests.Common.Builders public override IDataEditor Build() { - var defaultConfiguration = _defaultConfiguration ?? new Dictionary(); - var explicitConfigurationEditor = _explicitConfigurationEditorBuilder.Build(); - var explicitValueEditor = _explicitValueEditorBuilder.Build(); + IDictionary defaultConfiguration = _defaultConfiguration ?? new Dictionary(); + IConfigurationEditor explicitConfigurationEditor = _explicitConfigurationEditorBuilder.Build(); + IDataValueEditor explicitValueEditor = _explicitValueEditorBuilder.Build(); return new DataEditor( NullLoggerFactory.Instance, diff --git a/src/Umbraco.Tests.Common/Builders/DataTypeBuilder.cs b/src/Umbraco.Tests.Common/Builders/DataTypeBuilder.cs index 3ed7334367..d853b2a5c2 100644 --- a/src/Umbraco.Tests.Common/Builders/DataTypeBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/DataTypeBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; using Umbraco.Core.Models; using Umbraco.Core.Serialization; @@ -20,7 +23,7 @@ namespace Umbraco.Tests.Common.Builders IWithPathBuilder, IWithSortOrderBuilder { - private DataEditorBuilder _dataEditorBuilder; + private readonly DataEditorBuilder _dataEditorBuilder; private int? _id; private int? _parentId; private Guid? _key; @@ -35,10 +38,7 @@ namespace Umbraco.Tests.Common.Builders private ValueStorageType? _databaseType; private int? _sortOrder; - public DataTypeBuilder() - { - _dataEditorBuilder = new DataEditorBuilder(this); - } + public DataTypeBuilder() => _dataEditorBuilder = new DataEditorBuilder(this); public DataTypeBuilder WithDatabaseType(ValueStorageType databaseType) { @@ -46,25 +46,22 @@ namespace Umbraco.Tests.Common.Builders return this; } - public DataEditorBuilder AddEditor() - { - return _dataEditorBuilder; - } + public DataEditorBuilder AddEditor() => _dataEditorBuilder; public override DataType Build() { - var editor = _dataEditorBuilder.Build(); + Core.PropertyEditors.IDataEditor editor = _dataEditorBuilder.Build(); var parentId = _parentId ?? -1; var id = _id ?? 1; - var key = _key ?? Guid.NewGuid(); - var createDate = _createDate ?? DateTime.Now; - var updateDate = _updateDate ?? DateTime.Now; - var deleteDate = _deleteDate ?? null; + Guid key = _key ?? Guid.NewGuid(); + DateTime createDate = _createDate ?? DateTime.Now; + DateTime updateDate = _updateDate ?? DateTime.Now; + DateTime? deleteDate = _deleteDate ?? null; var name = _name ?? Guid.NewGuid().ToString(); var level = _level ?? 0; var path = _path ?? $"-1,{id}"; var creatorId = _creatorId ?? 1; - var databaseType = _databaseType ?? ValueStorageType.Ntext; + ValueStorageType databaseType = _databaseType ?? ValueStorageType.Ntext; var sortOrder = _sortOrder ?? 0; var serializer = new ConfigurationEditorJsonSerializer(); diff --git a/src/Umbraco.Tests.Common/Builders/DataValueEditorBuilder.cs b/src/Umbraco.Tests.Common/Builders/DataValueEditorBuilder.cs index bd6966bbeb..61f6c3df78 100644 --- a/src/Umbraco.Tests.Common/Builders/DataValueEditorBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/DataValueEditorBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; using Moq; using Umbraco.Core.PropertyEditors; @@ -14,7 +17,8 @@ namespace Umbraco.Tests.Common.Builders private bool? _hideLabel; private string _valueType; - public DataValueEditorBuilder(TParent parentBuilder) : base(parentBuilder) + public DataValueEditorBuilder(TParent parentBuilder) + : base(parentBuilder) { } diff --git a/src/Umbraco.Tests.Common/Builders/DictionaryItemBuilder.cs b/src/Umbraco.Tests.Common/Builders/DictionaryItemBuilder.cs index 206bccba80..9ad4c4178e 100644 --- a/src/Umbraco.Tests.Common/Builders/DictionaryItemBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/DictionaryItemBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; using System.Collections.Generic; using System.Linq; @@ -57,12 +60,12 @@ namespace Umbraco.Tests.Common.Builders public override DictionaryItem Build() { - var createDate = _createDate ?? DateTime.Now; - var updateDate = _updateDate ?? DateTime.Now; - var deleteDate = _deleteDate ?? null; + DateTime createDate = _createDate ?? DateTime.Now; + DateTime updateDate = _updateDate ?? DateTime.Now; + DateTime? deleteDate = _deleteDate ?? null; var id = _id ?? 1; - var key = _key ?? Guid.NewGuid(); - var parentId = _parentId ?? null; + Guid key = _key ?? Guid.NewGuid(); + Guid? parentId = _parentId ?? null; var itemKey = _itemKey ?? Guid.NewGuid().ToString(); var result = new DictionaryItem(itemKey) diff --git a/src/Umbraco.Tests.Common/Builders/DictionaryTranslationBuilder.cs b/src/Umbraco.Tests.Common/Builders/DictionaryTranslationBuilder.cs index 444afe6ddb..6029097307 100644 --- a/src/Umbraco.Tests.Common/Builders/DictionaryTranslationBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/DictionaryTranslationBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; using Umbraco.Core.Models; using Umbraco.Tests.Common.Builders.Interfaces; @@ -12,7 +15,7 @@ namespace Umbraco.Tests.Common.Builders IWithDeleteDateBuilder, IWithKeyBuilder { - private LanguageBuilder _languageBuilder; + private readonly LanguageBuilder _languageBuilder; private DateTime? _createDate; private DateTime? _deleteDate; private int? _id; @@ -20,15 +23,11 @@ namespace Umbraco.Tests.Common.Builders private DateTime? _updateDate; private string _value; - public DictionaryTranslationBuilder() : base(null) - { - _languageBuilder = new LanguageBuilder(this); - } + public DictionaryTranslationBuilder() + : base(null) => _languageBuilder = new LanguageBuilder(this); - public DictionaryTranslationBuilder(DictionaryItemBuilder parentBuilder) : base(parentBuilder) - { - _languageBuilder = new LanguageBuilder(this); - } + public DictionaryTranslationBuilder(DictionaryItemBuilder parentBuilder) + : base(parentBuilder) => _languageBuilder = new LanguageBuilder(this); public LanguageBuilder AddLanguage() => _languageBuilder; @@ -40,11 +39,11 @@ namespace Umbraco.Tests.Common.Builders public override IDictionaryTranslation Build() { - var createDate = _createDate ?? DateTime.Now; - var updateDate = _updateDate ?? DateTime.Now; - var deleteDate = _deleteDate ?? null; + DateTime createDate = _createDate ?? DateTime.Now; + DateTime updateDate = _updateDate ?? DateTime.Now; + DateTime? deleteDate = _deleteDate ?? null; var id = _id ?? 1; - var key = _key ?? Guid.NewGuid(); + Guid key = _key ?? Guid.NewGuid(); var result = new DictionaryTranslation( _languageBuilder.Build(), diff --git a/src/Umbraco.Tests.Common/Builders/DocumentEntitySlimBuilder.cs b/src/Umbraco.Tests.Common/Builders/DocumentEntitySlimBuilder.cs index 84ed5fec17..490f94f789 100644 --- a/src/Umbraco.Tests.Common/Builders/DocumentEntitySlimBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/DocumentEntitySlimBuilder.cs @@ -1,4 +1,8 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; +using System.Collections.Generic; using Umbraco.Core.Models.Entities; using Umbraco.Tests.Common.Builders.Interfaces; @@ -75,9 +79,9 @@ namespace Umbraco.Tests.Common.Builders public override DocumentEntitySlim Build() { var id = _id ?? 1; - var key = _key ?? Guid.NewGuid(); - var createDate = _createDate ?? DateTime.Now; - var updateDate = _updateDate ?? DateTime.Now; + Guid key = _key ?? Guid.NewGuid(); + DateTime createDate = _createDate ?? DateTime.Now; + DateTime updateDate = _updateDate ?? DateTime.Now; var name = _name ?? Guid.NewGuid().ToString(); var creatorId = _creatorId ?? 1; var level = _level ?? 1; @@ -111,8 +115,8 @@ namespace Umbraco.Tests.Common.Builders if (_additionalDataBuilder != null) { - var additionalData = _additionalDataBuilder.Build(); - foreach (var kvp in additionalData) + IDictionary additionalData = _additionalDataBuilder.Build(); + foreach (KeyValuePair kvp in additionalData) { documentEntitySlim.AdditionalData.Add(kvp.Key, kvp.Value); } diff --git a/src/Umbraco.Tests.Common/Builders/EntitySlimBuilder.cs b/src/Umbraco.Tests.Common/Builders/EntitySlimBuilder.cs index 844c765a9d..ce35bb21b1 100644 --- a/src/Umbraco.Tests.Common/Builders/EntitySlimBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/EntitySlimBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using Umbraco.Core.Models.Entities; using Umbraco.Tests.Common.Builders.Interfaces; diff --git a/src/Umbraco.Tests.Common/Builders/Extensions/BuilderExtensions.cs b/src/Umbraco.Tests.Common/Builders/Extensions/BuilderExtensions.cs index 1af2dc5344..c93b150647 100644 --- a/src/Umbraco.Tests.Common/Builders/Extensions/BuilderExtensions.cs +++ b/src/Umbraco.Tests.Common/Builders/Extensions/BuilderExtensions.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; using System.Globalization; using Umbraco.Core.Models; diff --git a/src/Umbraco.Tests.Common/Builders/Extensions/ContentItemSaveBuilderExtensions.cs b/src/Umbraco.Tests.Common/Builders/Extensions/ContentItemSaveBuilderExtensions.cs index c402049745..04e95bd8a4 100644 --- a/src/Umbraco.Tests.Common/Builders/Extensions/ContentItemSaveBuilderExtensions.cs +++ b/src/Umbraco.Tests.Common/Builders/Extensions/ContentItemSaveBuilderExtensions.cs @@ -1,11 +1,12 @@ -using System.Linq; +// Copyright (c) Umbraco. +// See LICENSE for more details. + using Umbraco.Core.Models; namespace Umbraco.Tests.Common.Builders.Extensions { public static class ContentItemSaveBuilderExtensions { - public static ContentItemSaveBuilder WithContent(this ContentItemSaveBuilder builder, IContent content) { builder.WithId(content.Id); @@ -13,43 +14,39 @@ namespace Umbraco.Tests.Common.Builders.Extensions if (content.CultureInfos.Count == 0) { - var variantBuilder = builder.AddVariant(); + ContentVariantSaveBuilder variantBuilder = builder.AddVariant(); variantBuilder.WithName(content.Name); - foreach (var contentProperty in content.Properties) + foreach (IProperty contentProperty in content.Properties) { AddInvariantProperty(variantBuilder, contentProperty); } } else { - foreach (var contentCultureInfos in content.CultureInfos) + foreach (ContentCultureInfos contentCultureInfos in content.CultureInfos) { - var variantBuilder = builder.AddVariant(); + ContentVariantSaveBuilder variantBuilder = builder.AddVariant(); variantBuilder.WithName(contentCultureInfos.Name); variantBuilder.WithCultureInfo(contentCultureInfos.Culture); - foreach (var contentProperty in content.Properties) + foreach (IProperty contentProperty in content.Properties) { AddInvariantProperty(variantBuilder, contentProperty); } } } - - return builder; } - private static void AddInvariantProperty(ContentVariantSaveBuilder variantBuilder, IProperty contentProperty) - { + private static void AddInvariantProperty(ContentVariantSaveBuilder variantBuilder, IProperty contentProperty) => variantBuilder .AddProperty() .WithId(contentProperty.Id) .WithAlias(contentProperty.Alias) .WithValue(contentProperty.GetValue()) .Done(); - } } } diff --git a/src/Umbraco.Tests.Common/Builders/Extensions/ContentTypeBuilderExtensions.cs b/src/Umbraco.Tests.Common/Builders/Extensions/ContentTypeBuilderExtensions.cs index f39b09e899..4ff0bae60c 100644 --- a/src/Umbraco.Tests.Common/Builders/Extensions/ContentTypeBuilderExtensions.cs +++ b/src/Umbraco.Tests.Common/Builders/Extensions/ContentTypeBuilderExtensions.cs @@ -1,13 +1,15 @@ -using Umbraco.Core; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Core; using Umbraco.Core.Models; namespace Umbraco.Tests.Common.Builders.Extensions { public static class ContentTypeBuilderExtensions { - public static ContentType BuildSimpleContentType(this ContentTypeBuilder builder) - { - return (ContentType)builder + public static ContentType BuildSimpleContentType(this ContentTypeBuilder builder) => + (ContentType)builder .WithId(10) .WithAlias("textPage") .WithName("Text Page") @@ -64,11 +66,9 @@ namespace Umbraco.Tests.Common.Builders.Extensions .WithSortOrder(9) .Done() .Build(); - } - public static MediaType BuildImageMediaType(this MediaTypeBuilder builder) - { - return (MediaType)builder + public static MediaType BuildImageMediaType(this MediaTypeBuilder builder) => + (MediaType)builder .WithId(10) .WithAlias(Constants.Conventions.MediaTypes.Image) .WithName("Image") @@ -77,11 +77,9 @@ namespace Umbraco.Tests.Common.Builders.Extensions .WithPropertyTypeIdsIncrementingFrom(200) .WithMediaPropertyGroup() .Build(); - } - public static MemberType BuildSimpleMemberType(this MemberTypeBuilder builder) - { - return (MemberType)builder + public static MemberType BuildSimpleMemberType(this MemberTypeBuilder builder) => + (MemberType)builder .WithId(10) .WithAlias("memberType") .WithName("Member type") @@ -105,6 +103,5 @@ namespace Umbraco.Tests.Common.Builders.Extensions .WithMemberCanEditProperty("title", true) .WithMemberCanViewProperty("bodyText", true) .Build(); - } } } diff --git a/src/Umbraco.Tests.Common/Builders/Extensions/StringExtensions.cs b/src/Umbraco.Tests.Common/Builders/Extensions/StringExtensions.cs index b426cabaa6..e0fef2647f 100644 --- a/src/Umbraco.Tests.Common/Builders/Extensions/StringExtensions.cs +++ b/src/Umbraco.Tests.Common/Builders/Extensions/StringExtensions.cs @@ -1,4 +1,7 @@ -namespace Umbraco.Tests.Common.Builders.Extensions +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Tests.Common.Builders.Extensions { public static class StringExtensions { diff --git a/src/Umbraco.Tests.Common/Builders/GenericCollectionBuilder.cs b/src/Umbraco.Tests.Common/Builders/GenericCollectionBuilder.cs index 0c45f6a599..7fc58e4961 100644 --- a/src/Umbraco.Tests.Common/Builders/GenericCollectionBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/GenericCollectionBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System.Collections.Generic; using System.Linq; @@ -8,14 +11,12 @@ namespace Umbraco.Tests.Common.Builders { private readonly IList _collection; - public GenericCollectionBuilder(TBuilder parentBuilder) : base(parentBuilder) - { - _collection = new List(); - } + public GenericCollectionBuilder(TBuilder parentBuilder) + : base(parentBuilder) => _collection = new List(); public override IEnumerable Build() { - var collection = _collection?.ToList() ?? Enumerable.Empty(); + IEnumerable collection = _collection?.ToList() ?? Enumerable.Empty(); return collection; } diff --git a/src/Umbraco.Tests.Common/Builders/GenericDictionaryBuilder.cs b/src/Umbraco.Tests.Common/Builders/GenericDictionaryBuilder.cs index f4cb7c6a30..3d7823b612 100644 --- a/src/Umbraco.Tests.Common/Builders/GenericDictionaryBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/GenericDictionaryBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System.Collections.Generic; namespace Umbraco.Tests.Common.Builders @@ -7,17 +10,12 @@ namespace Umbraco.Tests.Common.Builders { private readonly IDictionary _dictionary; - public GenericDictionaryBuilder(TBuilder parentBuilder) : base(parentBuilder) - { - _dictionary = new Dictionary(); - } + public GenericDictionaryBuilder(TBuilder parentBuilder) + : base(parentBuilder) => _dictionary = new Dictionary(); - public override IDictionary Build() - { - return _dictionary == null + public override IDictionary Build() => _dictionary == null ? new Dictionary() : new Dictionary(_dictionary); - } public GenericDictionaryBuilder WithKeyValue(TKey key, TValue value) { diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IAccountBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IAccountBuilder.cs index 77fdd30547..1249209418 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IAccountBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IAccountBuilder.cs @@ -1,4 +1,7 @@ -namespace Umbraco.Tests.Common.Builders.Interfaces +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Tests.Common.Builders.Interfaces { public interface IAccountBuilder : IWithLoginBuilder, IWithEmailBuilder, diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IBuildContentTypes.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IBuildContentTypes.cs index 428613dc5d..740da59a10 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IBuildContentTypes.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IBuildContentTypes.cs @@ -1,4 +1,7 @@ -namespace Umbraco.Tests.Common.Builders.Interfaces +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Tests.Common.Builders.Interfaces { public interface IBuildContentTypes { diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IBuildPropertyGroups.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IBuildPropertyGroups.cs index 0391061a84..756aa19744 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IBuildPropertyGroups.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IBuildPropertyGroups.cs @@ -1,4 +1,7 @@ -namespace Umbraco.Tests.Common.Builders.Interfaces +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Tests.Common.Builders.Interfaces { public interface IBuildPropertyGroups { diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IBuildPropertyTypes.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IBuildPropertyTypes.cs index e0eb9e19d5..91a7c10041 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IBuildPropertyTypes.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IBuildPropertyTypes.cs @@ -1,4 +1,7 @@ -namespace Umbraco.Tests.Common.Builders.Interfaces +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Tests.Common.Builders.Interfaces { public interface IBuildPropertyTypes { diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithAliasBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithAliasBuilder.cs index 78bbbddec9..cf4db5382b 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithAliasBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithAliasBuilder.cs @@ -1,4 +1,7 @@ -namespace Umbraco.Tests.Common.Builders.Interfaces +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Tests.Common.Builders.Interfaces { public interface IWithAliasBuilder { diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithCreateDateBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithCreateDateBuilder.cs index 47acaa9a52..46745c4428 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithCreateDateBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithCreateDateBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; namespace Umbraco.Tests.Common.Builders.Interfaces diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithCreatorIdBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithCreatorIdBuilder.cs index ae7712cf9e..0f3e11a4de 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithCreatorIdBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithCreatorIdBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; namespace Umbraco.Tests.Common.Builders.Interfaces diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithCultureInfoBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithCultureInfoBuilder.cs index a60fe3c23c..bcb74c5c94 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithCultureInfoBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithCultureInfoBuilder.cs @@ -1,4 +1,7 @@ -using System.Globalization; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Globalization; namespace Umbraco.Tests.Common.Builders.Interfaces { diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithDeleteDateBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithDeleteDateBuilder.cs index 0fdeb6d69d..25042be231 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithDeleteDateBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithDeleteDateBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; namespace Umbraco.Tests.Common.Builders.Interfaces diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithDescriptionBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithDescriptionBuilder.cs index 1a155073b3..98d14d81bc 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithDescriptionBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithDescriptionBuilder.cs @@ -1,4 +1,7 @@ -namespace Umbraco.Tests.Common.Builders.Interfaces +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Tests.Common.Builders.Interfaces { public interface IWithDescriptionBuilder { diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithEmailBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithEmailBuilder.cs index cb3be57e62..4dd5708aaf 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithEmailBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithEmailBuilder.cs @@ -1,4 +1,7 @@ -namespace Umbraco.Tests.Common.Builders.Interfaces +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Tests.Common.Builders.Interfaces { public interface IWithEmailBuilder { diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithFailedPasswordAttemptsBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithFailedPasswordAttemptsBuilder.cs index d39647b28e..7669a7609e 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithFailedPasswordAttemptsBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithFailedPasswordAttemptsBuilder.cs @@ -1,4 +1,7 @@ -namespace Umbraco.Tests.Common.Builders.Interfaces +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Tests.Common.Builders.Interfaces { public interface IWithFailedPasswordAttemptsBuilder { diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithIconBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithIconBuilder.cs index 5de5224e18..a58c8c554b 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithIconBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithIconBuilder.cs @@ -1,4 +1,7 @@ -namespace Umbraco.Tests.Common.Builders.Interfaces +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Tests.Common.Builders.Interfaces { public interface IWithIconBuilder { diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithIdBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithIdBuilder.cs index c13343df15..8f99388086 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithIdBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithIdBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + namespace Umbraco.Tests.Common.Builders.Interfaces { public interface IWithIdBuilder diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithIsApprovedBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithIsApprovedBuilder.cs index c27c34d3a2..2645bc8071 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithIsApprovedBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithIsApprovedBuilder.cs @@ -1,4 +1,7 @@ -namespace Umbraco.Tests.Common.Builders.Interfaces +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Tests.Common.Builders.Interfaces { public interface IWithIsApprovedBuilder { diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithIsContainerBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithIsContainerBuilder.cs index 7daee8afb9..a74f2b658f 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithIsContainerBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithIsContainerBuilder.cs @@ -1,4 +1,7 @@ -namespace Umbraco.Tests.Common.Builders.Interfaces +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Tests.Common.Builders.Interfaces { public interface IWithIsContainerBuilder { diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithIsLockedOutBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithIsLockedOutBuilder.cs index 55577ed312..d10db7d881 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithIsLockedOutBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithIsLockedOutBuilder.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; namespace Umbraco.Tests.Common.Builders.Interfaces { diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithKeyBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithKeyBuilder.cs index a71bd2d114..a709dff734 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithKeyBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithKeyBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; namespace Umbraco.Tests.Common.Builders.Interfaces diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithLastLoginDateBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithLastLoginDateBuilder.cs index 0355786927..9b969a210e 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithLastLoginDateBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithLastLoginDateBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; namespace Umbraco.Tests.Common.Builders.Interfaces diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithLastPasswordChangeDateBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithLastPasswordChangeDateBuilder.cs index 833da022d4..ffd7019404 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithLastPasswordChangeDateBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithLastPasswordChangeDateBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; namespace Umbraco.Tests.Common.Builders.Interfaces diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithLevelBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithLevelBuilder.cs index dc6ee239ab..51d08e9143 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithLevelBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithLevelBuilder.cs @@ -1,4 +1,7 @@ -namespace Umbraco.Tests.Common.Builders.Interfaces +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Tests.Common.Builders.Interfaces { public interface IWithLevelBuilder { diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithLoginBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithLoginBuilder.cs index 5c11dd7d05..5b844869f0 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithLoginBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithLoginBuilder.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Tests.Common.Builders.Interfaces +namespace Umbraco.Tests.Common.Builders.Interfaces { public interface IWithLoginBuilder { diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithNameBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithNameBuilder.cs index d2ccb8dbbc..17962dc678 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithNameBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithNameBuilder.cs @@ -1,4 +1,7 @@ -namespace Umbraco.Tests.Common.Builders.Interfaces +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Tests.Common.Builders.Interfaces { public interface IWithNameBuilder { diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithParentContentTypeBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithParentContentTypeBuilder.cs index a9812654ef..c5357164a5 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithParentContentTypeBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithParentContentTypeBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using Umbraco.Core.Models; namespace Umbraco.Tests.Common.Builders.Interfaces diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithParentIdBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithParentIdBuilder.cs index 33d13b7ef1..edba880af8 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithParentIdBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithParentIdBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + namespace Umbraco.Tests.Common.Builders.Interfaces { public interface IWithParentIdBuilder diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithPathBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithPathBuilder.cs index ed632c4e7d..9fb99bc825 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithPathBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithPathBuilder.cs @@ -1,4 +1,7 @@ -namespace Umbraco.Tests.Common.Builders.Interfaces +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Tests.Common.Builders.Interfaces { public interface IWithPathBuilder { diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithPropertyTypeIdsIncrementingFrom.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithPropertyTypeIdsIncrementingFrom.cs index 6e12803e5c..215b0d3791 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithPropertyTypeIdsIncrementingFrom.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithPropertyTypeIdsIncrementingFrom.cs @@ -1,4 +1,7 @@ -namespace Umbraco.Tests.Common.Builders.Interfaces +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Tests.Common.Builders.Interfaces { public interface IWithPropertyTypeIdsIncrementingFrom { diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithPropertyValues.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithPropertyValues.cs index c5a6c35724..06ac06070c 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithPropertyValues.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithPropertyValues.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + namespace Umbraco.Tests.Common.Builders.Interfaces { public interface IWithPropertyValues diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithSortOrderBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithSortOrderBuilder.cs index 3202c243fb..8b23fd2b95 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithSortOrderBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithSortOrderBuilder.cs @@ -1,4 +1,7 @@ -namespace Umbraco.Tests.Common.Builders.Interfaces +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Tests.Common.Builders.Interfaces { public interface IWithSortOrderBuilder { diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithSupportsPublishing.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithSupportsPublishing.cs index 03bf74aa06..4b9f9e805b 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithSupportsPublishing.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithSupportsPublishing.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; namespace Umbraco.Tests.Common.Builders.Interfaces diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithThumbnailBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithThumbnailBuilder.cs index ce5b10e274..59b4fbff81 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithThumbnailBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithThumbnailBuilder.cs @@ -1,4 +1,7 @@ -namespace Umbraco.Tests.Common.Builders.Interfaces +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Tests.Common.Builders.Interfaces { public interface IWithThumbnailBuilder { diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithTrashedBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithTrashedBuilder.cs index 119e6a6e52..fe155aa07a 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithTrashedBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithTrashedBuilder.cs @@ -1,4 +1,7 @@ -namespace Umbraco.Tests.Common.Builders.Interfaces +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Tests.Common.Builders.Interfaces { public interface IWithTrashedBuilder { diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithUpdateDateBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithUpdateDateBuilder.cs index 80a5aa4f61..9c01286179 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithUpdateDateBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithUpdateDateBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; namespace Umbraco.Tests.Common.Builders.Interfaces diff --git a/src/Umbraco.Tests.Common/Builders/LanguageBuilder.cs b/src/Umbraco.Tests.Common/Builders/LanguageBuilder.cs index 4cd924fa3a..653d729dfd 100644 --- a/src/Umbraco.Tests.Common/Builders/LanguageBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/LanguageBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; using System.Globalization; using Umbraco.Core.Configuration.Models; @@ -8,7 +11,8 @@ namespace Umbraco.Tests.Common.Builders { public class LanguageBuilder : LanguageBuilder { - public LanguageBuilder() : base(null) + public LanguageBuilder() + : base(null) { } } @@ -33,7 +37,8 @@ namespace Umbraco.Tests.Common.Builders private Guid? _key; private DateTime? _updateDate; - public LanguageBuilder(TParent parentBuilder) : base(parentBuilder) + public LanguageBuilder(TParent parentBuilder) + : base(parentBuilder) { } @@ -63,13 +68,13 @@ namespace Umbraco.Tests.Common.Builders public override ILanguage Build() { - var cultureInfo = _cultureInfo ?? CultureInfo.GetCultureInfo("en-US"); + CultureInfo cultureInfo = _cultureInfo ?? CultureInfo.GetCultureInfo("en-US"); var cultureName = _cultureName ?? cultureInfo.EnglishName; var globalSettings = new GlobalSettings { DefaultUILanguage = cultureInfo.Name }; - var key = _key ?? Guid.NewGuid(); - var createDate = _createDate ?? DateTime.Now; - var updateDate = _updateDate ?? DateTime.Now; - var deleteDate = _deleteDate ?? null; + Guid key = _key ?? Guid.NewGuid(); + DateTime createDate = _createDate ?? DateTime.Now; + DateTime updateDate = _updateDate ?? DateTime.Now; + DateTime? deleteDate = _deleteDate ?? null; var fallbackLanguageId = _fallbackLanguageId ?? null; var isDefault = _isDefault ?? false; var isMandatory = _isMandatory ?? false; diff --git a/src/Umbraco.Tests.Common/Builders/MacroBuilder.cs b/src/Umbraco.Tests.Common/Builders/MacroBuilder.cs index 66e81ef8ce..4206dcc3de 100644 --- a/src/Umbraco.Tests.Common/Builders/MacroBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/MacroBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; using System.Collections.Generic; using System.Linq; @@ -15,7 +18,7 @@ namespace Umbraco.Tests.Common.Builders IWithAliasBuilder, IWithNameBuilder { - private List _propertyBuilders = new List(); + private readonly List _propertyBuilders = new List(); private int? _id; private Guid? _key; @@ -78,7 +81,7 @@ namespace Umbraco.Tests.Common.Builders var id = _id ?? 1; var name = _name ?? Guid.NewGuid().ToString(); var alias = _alias ?? name.ToCamelCase(); - var key = _key ?? Guid.NewGuid(); + Guid key = _key ?? Guid.NewGuid(); var useInEditor = _useInEditor ?? false; var cacheDuration = _cacheDuration ?? 0; var cacheByPage = _cacheByPage ?? false; @@ -90,7 +93,7 @@ namespace Umbraco.Tests.Common.Builders var macro = new Macro(shortStringHelper, id, key, useInEditor, cacheDuration, alias, name, cacheByPage, cacheByMember, dontRender, macroSource); - foreach (var property in _propertyBuilders.Select(x => x.Build())) + foreach (IMacroProperty property in _propertyBuilders.Select(x => x.Build())) { macro.Properties.Add(property); } diff --git a/src/Umbraco.Tests.Common/Builders/MacroPropertyBuilder.cs b/src/Umbraco.Tests.Common/Builders/MacroPropertyBuilder.cs index ce80a056e9..15532b9cc9 100644 --- a/src/Umbraco.Tests.Common/Builders/MacroPropertyBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/MacroPropertyBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; using Umbraco.Core.Models; using Umbraco.Tests.Common.Builders.Extensions; @@ -20,7 +23,8 @@ namespace Umbraco.Tests.Common.Builders private int? _sortOrder; private string _editorAlias; - public MacroPropertyBuilder(MacroBuilder parentBuilder) : base(parentBuilder) + public MacroPropertyBuilder(MacroBuilder parentBuilder) + : base(parentBuilder) { } @@ -28,20 +32,20 @@ namespace Umbraco.Tests.Common.Builders { _editorAlias = editorAlias; return this; - } + } public override IMacroProperty Build() { var id = _id ?? 1; var name = _name ?? Guid.NewGuid().ToString(); var alias = _alias ?? name.ToCamelCase(); - var key = _key ?? Guid.NewGuid(); + Guid key = _key ?? Guid.NewGuid(); var sortOrder = _sortOrder ?? 0; var editorAlias = _editorAlias ?? string.Empty; return new MacroProperty(id, key, alias, name, sortOrder, editorAlias); } - + int? IWithIdBuilder.Id { get => _id; diff --git a/src/Umbraco.Tests.Common/Builders/MediaBuilder.cs b/src/Umbraco.Tests.Common/Builders/MediaBuilder.cs index d9ca6480bc..a2afe1c964 100644 --- a/src/Umbraco.Tests.Common/Builders/MediaBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/MediaBuilder.cs @@ -1,8 +1,12 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; -using Umbraco.Core.Models; -using Umbraco.Tests.Common.Builders.Interfaces; -using Umbraco.Tests.Common.Builders.Extensions; +using System.Collections.Generic; using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Tests.Common.Builders.Extensions; +using Umbraco.Tests.Common.Builders.Interfaces; using Umbraco.Tests.Testing; namespace Umbraco.Tests.Common.Builders @@ -68,10 +72,10 @@ namespace Umbraco.Tests.Common.Builders public override Media Build() { var id = _id ?? 0; - var key = _key ?? Guid.NewGuid(); + Guid key = _key ?? Guid.NewGuid(); var parentId = _parentId ?? -1; - var createDate = _createDate ?? DateTime.Now; - var updateDate = _updateDate ?? DateTime.Now; + DateTime createDate = _createDate ?? DateTime.Now; + DateTime updateDate = _updateDate ?? DateTime.Now; var name = _name ?? Guid.NewGuid().ToString(); var creatorId = _creatorId ?? 0; var level = _level ?? 1; @@ -87,7 +91,7 @@ namespace Umbraco.Tests.Common.Builders throw new InvalidOperationException("A media item cannot be constructed without providing a media type. Use AddMediaType() or WithMediaType()."); } - var mediaType = _mediaType ?? _mediaTypeBuilder.Build(); + IMediaType mediaType = _mediaType ?? _mediaTypeBuilder.Build(); var media = new Media(name, parentId, mediaType) { @@ -106,8 +110,8 @@ namespace Umbraco.Tests.Common.Builders { if (_propertyDataBuilder != null) { - var propertyData = _propertyDataBuilder.Build(); - foreach (var keyValuePair in propertyData) + IDictionary propertyData = _propertyDataBuilder.Build(); + foreach (KeyValuePair keyValuePair in propertyData) { media.SetValue(keyValuePair.Key, keyValuePair.Value); } @@ -123,35 +127,27 @@ namespace Umbraco.Tests.Common.Builders return media; } - public static Media CreateSimpleMedia(IMediaType mediaType, string name, int parentId, int id = 0) - { - return new MediaBuilder() + public static Media CreateSimpleMedia(IMediaType mediaType, string name, int parentId, int id = 0) => + new MediaBuilder() .WithId(id) .WithName(name) .WithMediaType(mediaType) .WithParentId(parentId) .WithPropertyValues(new - { - title = name + " Subpage", - bodyText = "This is a subpage", - author = "John Doe" - }) + { + title = name + " Subpage", + bodyText = "This is a subpage", + author = "John Doe" + }) .Build(); - } - public static Media CreateMediaImage(IMediaType mediaType, int parentId) - { - return CreateMediaImage(mediaType, parentId, "/media/test-image.png"); - } + public static Media CreateMediaImage(IMediaType mediaType, int parentId) => + CreateMediaImage(mediaType, parentId, "/media/test-image.png"); - public static Media CreateMediaImageWithCrop(IMediaType mediaType, int parentId) - { - return CreateMediaImage(mediaType, parentId, "{src: '/media/test-image.png', crops: []}"); - } + public static Media CreateMediaImageWithCrop(IMediaType mediaType, int parentId) => + CreateMediaImage(mediaType, parentId, "{src: '/media/test-image.png', crops: []}"); - private static Media CreateMediaImage(IMediaType mediaType, int parentId, string fileValue) - { - return new MediaBuilder() + private static Media CreateMediaImage(IMediaType mediaType, int parentId, string fileValue) => new MediaBuilder() .WithMediaType(mediaType) .WithName("Test Image") .WithParentId(parentId) @@ -163,20 +159,14 @@ namespace Umbraco.Tests.Common.Builders .WithKeyValue(Constants.Conventions.Media.Extension, "png") .Done() .Build(); - } - public static Media CreateMediaFolder(IMediaType mediaType, int parentId) - { - return new MediaBuilder() + public static Media CreateMediaFolder(IMediaType mediaType, int parentId) => new MediaBuilder() .WithMediaType(mediaType) .WithName("Test Folder") .WithParentId(parentId) .Build(); - } - public static Media CreateMediaFile(IMediaType mediaType, int parentId) - { - return new MediaBuilder() + public static Media CreateMediaFile(IMediaType mediaType, int parentId) => new MediaBuilder() .WithMediaType(mediaType) .WithName("Test File") .WithParentId(parentId) @@ -186,7 +176,6 @@ namespace Umbraco.Tests.Common.Builders .WithKeyValue(Constants.Conventions.Media.Extension, "png") .Done() .Build(); - } int? IWithIdBuilder.Id { @@ -247,6 +236,7 @@ namespace Umbraco.Tests.Common.Builders get => _sortOrder; set => _sortOrder = value; } + int? IWithParentIdBuilder.ParentId { get => _parentId; diff --git a/src/Umbraco.Tests.Common/Builders/MediaTypeBuilder.cs b/src/Umbraco.Tests.Common/Builders/MediaTypeBuilder.cs index 3cbb80bde4..668dbbc961 100644 --- a/src/Umbraco.Tests.Common/Builders/MediaTypeBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/MediaTypeBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System.Collections.Generic; using System.Linq; using Umbraco.Core; @@ -11,20 +14,22 @@ namespace Umbraco.Tests.Common.Builders : ContentTypeBaseBuilder, IWithPropertyTypeIdsIncrementingFrom { - private List> _propertyGroupBuilders = new List>(); + private readonly List> _propertyGroupBuilders = new List>(); private int? _propertyTypeIdsIncrementingFrom; - public MediaTypeBuilder() : base(null) + public MediaTypeBuilder() + : base(null) { } - public MediaTypeBuilder(MediaBuilder parentBuilder) : base(parentBuilder) + public MediaTypeBuilder(MediaBuilder parentBuilder) + : base(parentBuilder) { } public MediaTypeBuilder WithMediaPropertyGroup() { - var builder = new PropertyGroupBuilder(this) + PropertyGroupBuilder builder = new PropertyGroupBuilder(this) .WithId(99) .WithName("Media") .WithSortOrder(1) @@ -82,7 +87,7 @@ namespace Umbraco.Tests.Common.Builders public override IMediaType Build() { MediaType mediaType; - var parent = GetParent(); + IContentTypeComposition parent = GetParent(); if (parent != null) { mediaType = new MediaType(ShortStringHelper, (IMediaType)parent, GetAlias()); @@ -122,7 +127,7 @@ namespace Umbraco.Tests.Common.Builders public static MediaType CreateSimpleMediaType(string alias, string name, IMediaType parent = null, bool randomizeAliases = false, string propertyGroupName = "Content") { var builder = new MediaTypeBuilder(); - var mediaType = builder + IMediaType mediaType = builder .WithAlias(alias) .WithName(name) .WithParentContentType(parent) @@ -156,20 +161,16 @@ namespace Umbraco.Tests.Common.Builders return (MediaType)mediaType; } - public static MediaType CreateImageMediaType(string alias = Constants.Conventions.MediaTypes.Image) - { - return CreateImageMediaType(alias ?? "Image", Constants.PropertyEditors.Aliases.UploadField, -90); - } + public static MediaType CreateImageMediaType(string alias = Constants.Conventions.MediaTypes.Image) => + CreateImageMediaType(alias ?? "Image", Constants.PropertyEditors.Aliases.UploadField, -90); - public static MediaType CreateImageMediaTypeWithCrop(string alias = Constants.Conventions.MediaTypes.Image) - { - return CreateImageMediaType(alias ?? "Image", Constants.PropertyEditors.Aliases.ImageCropper, 1043); - } + public static MediaType CreateImageMediaTypeWithCrop(string alias = Constants.Conventions.MediaTypes.Image) => + CreateImageMediaType(alias ?? "Image", Constants.PropertyEditors.Aliases.ImageCropper, 1043); private static MediaType CreateImageMediaType(string alias, string imageFieldPropertyEditorAlias, int imageFieldDataTypeId) { var builder = new MediaTypeBuilder(); - var mediaType = builder + IMediaType mediaType = builder .WithAlias(alias) .WithName("Image") .AddPropertyGroup() @@ -222,7 +223,7 @@ namespace Umbraco.Tests.Common.Builders public static MediaType CreateVideoMediaType() { var builder = new MediaTypeBuilder(); - var mediaType = builder + IMediaType mediaType = builder .WithAlias("video") .WithName("Video") .AddPropertyGroup() @@ -237,7 +238,7 @@ namespace Umbraco.Tests.Common.Builders .WithAlias("videoFile") .WithName("Video file") .WithSortOrder(1) - .Done() + .Done() .Done() .Build(); diff --git a/src/Umbraco.Tests.Common/Builders/MemberBuilder.cs b/src/Umbraco.Tests.Common/Builders/MemberBuilder.cs index 001956b66e..3eacfaaff0 100644 --- a/src/Umbraco.Tests.Common/Builders/MemberBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/MemberBuilder.cs @@ -1,8 +1,11 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; -using Umbraco.Core.Models; -using Umbraco.Tests.Common.Builders.Interfaces; -using Umbraco.Tests.Common.Builders.Extensions; using System.Collections.Generic; +using Umbraco.Core.Models; +using Umbraco.Tests.Common.Builders.Extensions; +using Umbraco.Tests.Common.Builders.Interfaces; namespace Umbraco.Tests.Common.Builders { @@ -95,9 +98,9 @@ namespace Umbraco.Tests.Common.Builders public override Member Build() { var id = _id ?? 0; - var key = _key ?? Guid.NewGuid(); - var createDate = _createDate ?? DateTime.Now; - var updateDate = _updateDate ?? DateTime.Now; + Guid key = _key ?? Guid.NewGuid(); + DateTime createDate = _createDate ?? DateTime.Now; + DateTime updateDate = _updateDate ?? DateTime.Now; var name = _name ?? Guid.NewGuid().ToString(); var creatorId = _creatorId ?? 0; var level = _level ?? 1; @@ -110,16 +113,16 @@ namespace Umbraco.Tests.Common.Builders var failedPasswordAttempts = _failedPasswordAttempts ?? 0; var isApproved = _isApproved ?? false; var isLockedOut = _isLockedOut ?? false; - var lastLockoutDate = _lastLockoutDate ?? DateTime.Now; - var lastLoginDate = _lastLoginDate ?? DateTime.Now; - var lastPasswordChangeDate = _lastPasswordChangeDate ?? DateTime.Now; + DateTime lastLockoutDate = _lastLockoutDate ?? DateTime.Now; + DateTime lastLoginDate = _lastLoginDate ?? DateTime.Now; + DateTime lastPasswordChangeDate = _lastPasswordChangeDate ?? DateTime.Now; if (_memberTypeBuilder is null && _memberType is null) { throw new InvalidOperationException("A member cannot be constructed without providing a member type. Use AddMemberType() or WithMemberType()."); } - var memberType = _memberType ?? _memberTypeBuilder.Build(); + IMemberType memberType = _memberType ?? _memberTypeBuilder.Build(); var member = new Member(name, email, username, rawPasswordValue, memberType) { @@ -137,7 +140,7 @@ namespace Umbraco.Tests.Common.Builders if (_propertyIdsIncrementingFrom.HasValue) { var i = _propertyIdsIncrementingFrom.Value; - foreach (var property in member.Properties) + foreach (IProperty property in member.Properties) { property.Id = ++i; } @@ -157,8 +160,8 @@ namespace Umbraco.Tests.Common.Builders if (_additionalDataBuilder != null) { - var additionalData = _additionalDataBuilder.Build(); - foreach (var kvp in additionalData) + IDictionary additionalData = _additionalDataBuilder.Build(); + foreach (KeyValuePair kvp in additionalData) { member.AdditionalData.Add(kvp.Key, kvp.Value); } @@ -166,8 +169,8 @@ namespace Umbraco.Tests.Common.Builders if (_propertyDataBuilder != null) { - var propertyData = _propertyDataBuilder.Build(); - foreach (var kvp in propertyData) + IDictionary propertyData = _propertyDataBuilder.Build(); + foreach (KeyValuePair kvp in propertyData) { member.SetValue(kvp.Key, kvp.Value); } @@ -178,7 +181,7 @@ namespace Umbraco.Tests.Common.Builders return member; } - public static IEnumerable CreateSimpleMembers(IMemberType memberType, int amount, Action onCreating = null) + public static IEnumerable CreateSimpleMembers(IMemberType memberType, int amount) { var list = new List(); @@ -186,13 +189,12 @@ namespace Umbraco.Tests.Common.Builders { var name = "Member No-" + i; - var builder = new MemberBuilder() + MemberBuilder builder = new MemberBuilder() .WithMemberType(memberType) .WithName(name) .WithEmail("test" + i + "@test.com") .WithLogin("test" + i, "test" + i); - builder = builder .AddPropertyData() .WithKeyValue("title", name + " member" + i) @@ -205,9 +207,10 @@ namespace Umbraco.Tests.Common.Builders return list; } + public static Member CreateSimpleMember(IMemberType memberType, string name, string email, string password, string username, Guid? key = null) { - var builder = new MemberBuilder() + MemberBuilder builder = new MemberBuilder() .WithMemberType(memberType) .WithName(name) .WithEmail(email) @@ -236,7 +239,7 @@ namespace Umbraco.Tests.Common.Builders for (var i = 0; i < amount; i++) { var name = "Member No-" + i; - var member = new MemberBuilder() + Member member = new MemberBuilder() .WithMemberType(memberType) .WithName(name) .WithEmail("test" + i + "@test.com") diff --git a/src/Umbraco.Tests.Common/Builders/MemberGroupBuilder.cs b/src/Umbraco.Tests.Common/Builders/MemberGroupBuilder.cs index bfd7f30a14..41bc4eb5c4 100644 --- a/src/Umbraco.Tests.Common/Builders/MemberGroupBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/MemberGroupBuilder.cs @@ -1,4 +1,8 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; +using System.Collections.Generic; using Umbraco.Core.Models; using Umbraco.Tests.Common.Builders.Interfaces; @@ -32,9 +36,9 @@ namespace Umbraco.Tests.Common.Builders public override MemberGroup Build() { var id = _id ?? 1; - var key = _key ?? Guid.NewGuid(); - var createDate = _createDate ?? DateTime.Now; - var updateDate = _updateDate ?? DateTime.Now; + Guid key = _key ?? Guid.NewGuid(); + DateTime createDate = _createDate ?? DateTime.Now; + DateTime updateDate = _updateDate ?? DateTime.Now; var name = _name ?? Guid.NewGuid().ToString(); var creatorId = _creatorId ?? 1; @@ -50,8 +54,8 @@ namespace Umbraco.Tests.Common.Builders if (_additionalDataBuilder != null) { - var additionalData = _additionalDataBuilder.Build(); - foreach (var kvp in additionalData) + IDictionary additionalData = _additionalDataBuilder.Build(); + foreach (KeyValuePair kvp in additionalData) { memberGroup.AdditionalData.Add(kvp.Key, kvp.Value); } diff --git a/src/Umbraco.Tests.Common/Builders/MemberTypeBuilder.cs b/src/Umbraco.Tests.Common/Builders/MemberTypeBuilder.cs index e111ae8cb4..480a07890a 100644 --- a/src/Umbraco.Tests.Common/Builders/MemberTypeBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/MemberTypeBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System.Collections.Generic; using System.Linq; using Umbraco.Core; @@ -11,22 +14,24 @@ namespace Umbraco.Tests.Common.Builders : ContentTypeBaseBuilder, IWithPropertyTypeIdsIncrementingFrom { - private List> _propertyGroupBuilders = new List>(); - private Dictionary _memberCanEditProperties = new Dictionary(); - private Dictionary _memberCanViewProperties = new Dictionary(); + private readonly List> _propertyGroupBuilders = new List>(); + private readonly Dictionary _memberCanEditProperties = new Dictionary(); + private readonly Dictionary _memberCanViewProperties = new Dictionary(); private int? _propertyTypeIdsIncrementingFrom; - public MemberTypeBuilder() : base(null) + public MemberTypeBuilder() + : base(null) { } - public MemberTypeBuilder(MemberBuilder parentBuilder) : base(parentBuilder) + public MemberTypeBuilder(MemberBuilder parentBuilder) + : base(parentBuilder) { } public MemberTypeBuilder WithMembershipPropertyGroup() { - var builder = new PropertyGroupBuilder(this) + PropertyGroupBuilder builder = new PropertyGroupBuilder(this) .WithId(99) .WithName(Constants.Conventions.Member.StandardPropertiesGroupName) .AddPropertyType() @@ -118,12 +123,12 @@ namespace Umbraco.Tests.Common.Builders BuildPropertyGroups(memberType, _propertyGroupBuilders.Select(x => x.Build())); BuildPropertyTypeIds(memberType, _propertyTypeIdsIncrementingFrom); - foreach (var kvp in _memberCanEditProperties) + foreach (KeyValuePair kvp in _memberCanEditProperties) { memberType.SetMemberCanEditProperty(kvp.Key, kvp.Value); } - foreach (var kvp in _memberCanViewProperties) + foreach (KeyValuePair kvp in _memberCanViewProperties) { memberType.SetMemberCanViewProperty(kvp.Key, kvp.Value); } @@ -136,7 +141,7 @@ namespace Umbraco.Tests.Common.Builders public static MemberType CreateSimpleMemberType(string alias = null, string name = null) { var builder = new MemberTypeBuilder(); - var memberType = builder + IMemberType memberType = builder .WithAlias(alias) .WithName(name) .AddPropertyGroup() @@ -160,8 +165,8 @@ namespace Umbraco.Tests.Common.Builders .Done() .Done() .Build(); - - // Ensure that nothing is marked as dirty + + // Ensure that nothing is marked as dirty. memberType.ResetDirtyProperties(false); return (MemberType)memberType; diff --git a/src/Umbraco.Tests.Common/Builders/PropertyBuilder.cs b/src/Umbraco.Tests.Common/Builders/PropertyBuilder.cs index a79141b11c..f6e3ab2557 100644 --- a/src/Umbraco.Tests.Common/Builders/PropertyBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/PropertyBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; using Umbraco.Core.Models; using Umbraco.Tests.Common.Builders.Interfaces; @@ -38,16 +41,16 @@ namespace Umbraco.Tests.Common.Builders public override IProperty Build() { var id = _id ?? 1; - var key = _key ?? Guid.NewGuid(); - var createDate = _createDate ?? DateTime.Now; - var updateDate = _updateDate ?? DateTime.Now; + Guid key = _key ?? Guid.NewGuid(); + DateTime createDate = _createDate ?? DateTime.Now; + DateTime updateDate = _updateDate ?? DateTime.Now; if (_propertyTypeBuilder is null && _propertyType is null) { throw new InvalidOperationException("A property cannot be constructed without providing a property type. Use AddPropertyType() or WithPropertyType()."); } - var propertyType = _propertyType ?? _propertyTypeBuilder.Build(); + IPropertyType propertyType = _propertyType ?? _propertyTypeBuilder.Build(); // Needs to be within collection to support publishing. var propertyTypeCollection = new PropertyTypeCollection(true, new[] { propertyType }); diff --git a/src/Umbraco.Tests.Common/Builders/PropertyGroupBuilder.cs b/src/Umbraco.Tests.Common/Builders/PropertyGroupBuilder.cs index 8fe6e1463d..be177a3138 100644 --- a/src/Umbraco.Tests.Common/Builders/PropertyGroupBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/PropertyGroupBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; using System.Collections.Generic; using System.Linq; @@ -8,7 +11,8 @@ namespace Umbraco.Tests.Common.Builders { public class PropertyGroupBuilder : PropertyGroupBuilder { - public PropertyGroupBuilder() : base(null) + public PropertyGroupBuilder() + : base(null) { } } @@ -26,7 +30,8 @@ namespace Umbraco.Tests.Common.Builders IWithUpdateDateBuilder, IWithNameBuilder, IWithSortOrderBuilder, - IWithSupportsPublishing where TParent: IBuildPropertyGroups + IWithSupportsPublishing + where TParent : IBuildPropertyGroups { private readonly List>> _propertyTypeBuilders = new List>>(); @@ -39,7 +44,8 @@ namespace Umbraco.Tests.Common.Builders private bool? _supportsPublishing; private PropertyTypeCollection _propertyTypeCollection; - public PropertyGroupBuilder(TParent parentBuilder) : base(parentBuilder) + public PropertyGroupBuilder(TParent parentBuilder) + : base(parentBuilder) { } @@ -59,9 +65,9 @@ namespace Umbraco.Tests.Common.Builders public override PropertyGroup Build() { var id = _id ?? 0; - var key = _key ?? Guid.NewGuid(); - var createDate = _createDate ?? DateTime.Now; - var updateDate = _updateDate ?? DateTime.Now; + Guid key = _key ?? Guid.NewGuid(); + DateTime createDate = _createDate ?? DateTime.Now; + DateTime updateDate = _updateDate ?? DateTime.Now; var name = _name ?? Guid.NewGuid().ToString(); var sortOrder = _sortOrder ?? 0; var supportsPublishing = _supportsPublishing ?? false; @@ -74,7 +80,7 @@ namespace Umbraco.Tests.Common.Builders else { propertyTypeCollection = new PropertyTypeCollection(supportsPublishing); - foreach (var propertyType in _propertyTypeBuilders.Select(x => x.Build())) + foreach (PropertyType propertyType in _propertyTypeBuilders.Select(x => x.Build())) { propertyTypeCollection.Add(propertyType); } diff --git a/src/Umbraco.Tests.Common/Builders/PropertyTypeBuilder.cs b/src/Umbraco.Tests.Common/Builders/PropertyTypeBuilder.cs index e4e9b4d621..f541616d17 100644 --- a/src/Umbraco.Tests.Common/Builders/PropertyTypeBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/PropertyTypeBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; using Umbraco.Core; using Umbraco.Core.Models; @@ -9,7 +12,8 @@ namespace Umbraco.Tests.Common.Builders { public class PropertyTypeBuilder : PropertyTypeBuilder { - public PropertyTypeBuilder() : base(null) + public PropertyTypeBuilder() + : base(null) { } } @@ -28,7 +32,8 @@ namespace Umbraco.Tests.Common.Builders IWithUpdateDateBuilder, IWithSortOrderBuilder, IWithDescriptionBuilder, - IWithSupportsPublishing where TParent : IBuildPropertyTypes + IWithSupportsPublishing + where TParent : IBuildPropertyTypes { private int? _id; private Guid? _key; @@ -43,13 +48,15 @@ namespace Umbraco.Tests.Common.Builders private int? _dataTypeId; private Lazy _propertyGroupId; private bool? _mandatory; + private bool? _labelOnTop; private string _mandatoryMessage; private string _validationRegExp; private string _validationRegExpMessage; private bool? _supportsPublishing; private ContentVariation? _variations; - public PropertyTypeBuilder(TParent parentBuilder) : base(parentBuilder) + public PropertyTypeBuilder(TParent parentBuilder) + : base(parentBuilder) { } @@ -84,6 +91,12 @@ namespace Umbraco.Tests.Common.Builders return this; } + public PropertyTypeBuilder WithLabelOnTop(bool labelOnTop) + { + _labelOnTop = labelOnTop; + return this; + } + public PropertyTypeBuilder WithValidationRegExp(string validationRegExp, string validationRegExpMessage = "") { _validationRegExp = validationRegExp; @@ -100,23 +113,24 @@ namespace Umbraco.Tests.Common.Builders public override PropertyType Build() { var id = _id ?? 0; - var key = _key ?? Guid.NewGuid(); + Guid key = _key ?? Guid.NewGuid(); var propertyEditorAlias = _propertyEditorAlias ?? Constants.PropertyEditors.Aliases.TextBox; - var valueStorageType = _valueStorageType ?? ValueStorageType.Nvarchar; + ValueStorageType valueStorageType = _valueStorageType ?? ValueStorageType.Nvarchar; var name = _name ?? Guid.NewGuid().ToString(); var alias = _alias ?? name.ToCamelCase(); - var createDate = _createDate ?? DateTime.Now; - var updateDate = _updateDate ?? DateTime.Now; + DateTime createDate = _createDate ?? DateTime.Now; + DateTime updateDate = _updateDate ?? DateTime.Now; var sortOrder = _sortOrder ?? 0; var dataTypeId = _dataTypeId ?? -88; var description = _description ?? string.Empty; - var propertyGroupId = _propertyGroupId ?? null; + Lazy propertyGroupId = _propertyGroupId ?? null; var mandatory = _mandatory ?? false; var mandatoryMessage = _mandatoryMessage ?? string.Empty; var validationRegExp = _validationRegExp ?? string.Empty; var validationRegExpMessage = _validationRegExpMessage ?? string.Empty; var supportsPublishing = _supportsPublishing ?? false; - var variations = _variations ?? ContentVariation.Nothing; + var labelOnTop = _labelOnTop ?? false; + ContentVariation variations = _variations ?? ContentVariation.Nothing; var shortStringHelper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig()); @@ -138,11 +152,12 @@ namespace Umbraco.Tests.Common.Builders ValidationRegExpMessage = validationRegExpMessage, SupportsPublishing = supportsPublishing, Variations = variations, + LabelOnTop = labelOnTop, }; return propertyType; } - + int? IWithIdBuilder.Id { get => _id; diff --git a/src/Umbraco.Tests.Common/Builders/RelationBuilder.cs b/src/Umbraco.Tests.Common/Builders/RelationBuilder.cs index 9c29897de4..8824e9b20e 100644 --- a/src/Umbraco.Tests.Common/Builders/RelationBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/RelationBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; using Umbraco.Core.Models; using Umbraco.Tests.Common.Builders.Interfaces; @@ -55,9 +58,9 @@ namespace Umbraco.Tests.Common.Builders var id = _id ?? 0; var parentId = _parentId ?? 0; var childId = _childId ?? 0; - var key = _key ?? Guid.NewGuid(); - var createDate = _createDate ?? DateTime.Now; - var updateDate = _updateDate ?? DateTime.Now; + Guid key = _key ?? Guid.NewGuid(); + DateTime createDate = _createDate ?? DateTime.Now; + DateTime updateDate = _updateDate ?? DateTime.Now; var comment = _comment ?? string.Empty; if (_relationTypeBuilder == null && _relationType == null) @@ -65,7 +68,7 @@ namespace Umbraco.Tests.Common.Builders throw new InvalidOperationException("Cannot construct a Relation without a RelationType. Use AddRelationType() or WithRelationType()."); } - var relationType = _relationType ?? _relationTypeBuilder.Build(); + IRelationType relationType = _relationType ?? _relationTypeBuilder.Build(); return new Relation(parentId, childId, relationType) { diff --git a/src/Umbraco.Tests.Common/Builders/RelationTypeBuilder.cs b/src/Umbraco.Tests.Common/Builders/RelationTypeBuilder.cs index 7c47c1dad1..4b1953322a 100644 --- a/src/Umbraco.Tests.Common/Builders/RelationTypeBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/RelationTypeBuilder.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using Umbraco.Core.Models; using Umbraco.Tests.Common.Builders.Interfaces; @@ -25,11 +28,13 @@ namespace Umbraco.Tests.Common.Builders private Guid? _parentObjectType; private DateTime? _updateDate; - public RelationTypeBuilder() : base(null) + public RelationTypeBuilder() + : base(null) { } - public RelationTypeBuilder(RelationBuilder parentBuilder) : base(parentBuilder) + public RelationTypeBuilder(RelationBuilder parentBuilder) + : base(parentBuilder) { } @@ -55,14 +60,14 @@ namespace Umbraco.Tests.Common.Builders { var alias = _alias ?? Guid.NewGuid().ToString(); var name = _name ?? Guid.NewGuid().ToString(); - var parentObjectType = _parentObjectType ?? null; - var childObjectType = _childObjectType ?? null; + Guid? parentObjectType = _parentObjectType ?? null; + Guid? childObjectType = _childObjectType ?? null; var id = _id ?? 0; - var key = _key ?? Guid.NewGuid(); + Guid key = _key ?? Guid.NewGuid(); var isBidirectional = _isBidirectional ?? false; - var createDate = _createDate ?? DateTime.Now; - var updateDate = _updateDate ?? DateTime.Now; - var deleteDate = _deleteDate ?? null; + DateTime createDate = _createDate ?? DateTime.Now; + DateTime updateDate = _updateDate ?? DateTime.Now; + DateTime? deleteDate = _deleteDate ?? null; return new RelationType(name, alias, isBidirectional, parentObjectType, childObjectType) { diff --git a/src/Umbraco.Tests.Common/Builders/StylesheetBuilder.cs b/src/Umbraco.Tests.Common/Builders/StylesheetBuilder.cs index ed871b9c31..b293cb7bb8 100644 --- a/src/Umbraco.Tests.Common/Builders/StylesheetBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/StylesheetBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using Umbraco.Core.Models; namespace Umbraco.Tests.Common.Builders diff --git a/src/Umbraco.Tests.Common/Builders/TemplateBuilder.cs b/src/Umbraco.Tests.Common/Builders/TemplateBuilder.cs index 2fd86b60b7..35a32a8d9f 100644 --- a/src/Umbraco.Tests.Common/Builders/TemplateBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/TemplateBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; using Umbraco.Core.Models; using Umbraco.Core.Strings; @@ -28,11 +31,13 @@ namespace Umbraco.Tests.Common.Builders private string _masterTemplateAlias; private Lazy _masterTemplateId; - public TemplateBuilder() : base(null) + public TemplateBuilder() + : base(null) { } - public TemplateBuilder(ContentTypeBuilder parentBuilder) : base(parentBuilder) + public TemplateBuilder(ContentTypeBuilder parentBuilder) + : base(parentBuilder) { } @@ -53,16 +58,16 @@ namespace Umbraco.Tests.Common.Builders public override ITemplate Build() { var id = _id ?? 0; - var key = _key ?? Guid.NewGuid(); + Guid key = _key ?? Guid.NewGuid(); var name = _name ?? Guid.NewGuid().ToString(); var alias = _alias ?? name.ToCamelCase(); - var createDate = _createDate ?? DateTime.Now; - var updateDate = _updateDate ?? DateTime.Now; + DateTime createDate = _createDate ?? DateTime.Now; + DateTime updateDate = _updateDate ?? DateTime.Now; var path = _path ?? $"-1,{id}"; var content = _content; var isMasterTemplate = _isMasterTemplate ?? false; var masterTemplateAlias = _masterTemplateAlias ?? string.Empty; - var masterTemplateId = _masterTemplateId ?? new Lazy(() => -1); + Lazy masterTemplateId = _masterTemplateId ?? new Lazy(() => -1); var shortStringHelper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig()); @@ -82,13 +87,11 @@ namespace Umbraco.Tests.Common.Builders return template; } - public static Template CreateTextPageTemplate(string alias = "textPage") - { - return (Template)new TemplateBuilder() + public static Template CreateTextPageTemplate(string alias = "textPage") => + (Template)new TemplateBuilder() .WithAlias(alias) .WithName("Text page") .Build(); - } int? IWithIdBuilder.Id { diff --git a/src/Umbraco.Tests.Common/Builders/TreeBuilder.cs b/src/Umbraco.Tests.Common/Builders/TreeBuilder.cs index 072e838d5a..29a78ef4d7 100644 --- a/src/Umbraco.Tests.Common/Builders/TreeBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/TreeBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using Umbraco.Core; using Umbraco.Tests.Common.Builders.Interfaces; using Umbraco.Web.Trees; @@ -54,7 +57,7 @@ namespace Umbraco.Tests.Common.Builders var sectionAlias = _sectionAlias ?? Constants.Applications.Content; var group = _group ?? string.Empty; var title = _title ?? string.Empty; - var treeUse = _treeUse ?? TreeUse.Main; + TreeUse treeUse = _treeUse ?? TreeUse.Main; var isSingleNode = _isSingleNode ?? false; return new Tree(sortOrder, sectionAlias, group, alias, title, treeUse, typeof(SampleTreeController), isSingleNode); diff --git a/src/Umbraco.Tests.Common/Builders/UserBuilder.cs b/src/Umbraco.Tests.Common/Builders/UserBuilder.cs index 0995622841..14ec8f6a99 100644 --- a/src/Umbraco.Tests.Common/Builders/UserBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/UserBuilder.cs @@ -1,16 +1,20 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; using System.Collections.Generic; using System.Linq; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Models.Membership; -using Umbraco.Tests.Common.Builders.Interfaces; using Umbraco.Tests.Common.Builders.Extensions; +using Umbraco.Tests.Common.Builders.Interfaces; namespace Umbraco.Tests.Common.Builders { public class UserBuilder : UserBuilder { - public UserBuilder() : base(null) + public UserBuilder() + : base(null) { } } @@ -48,7 +52,8 @@ namespace Umbraco.Tests.Common.Builders private int[] _startMediaIds; private readonly List>> _userGroupBuilders = new List>>(); - public UserBuilder(TParent parentBuilder) : base(parentBuilder) + public UserBuilder(TParent parentBuilder) + : base(parentBuilder) { } @@ -124,11 +129,6 @@ namespace Umbraco.Tests.Common.Builders return this; } - /// - /// Will suffix the name, email and username for testing - /// - /// - /// public UserBuilder WithSuffix(string suffix) { _suffix = suffix; @@ -147,9 +147,9 @@ namespace Umbraco.Tests.Common.Builders var id = _id ?? 0; var defaultLang = _defaultLang ?? "en"; var globalSettings = new GlobalSettings { DefaultUILanguage = defaultLang }; - var key = _key ?? Guid.NewGuid(); - var createDate = _createDate ?? DateTime.Now; - var updateDate = _updateDate ?? DateTime.Now; + Guid key = _key ?? Guid.NewGuid(); + DateTime createDate = _createDate ?? DateTime.Now; + DateTime updateDate = _updateDate ?? DateTime.Now; var name = _name ?? "TestUser" + _suffix; var language = _language ?? globalSettings.DefaultUILanguage; var username = _username ?? "TestUser" + _suffix; @@ -158,14 +158,14 @@ namespace Umbraco.Tests.Common.Builders var failedPasswordAttempts = _failedPasswordAttempts ?? 0; var isApproved = _isApproved ?? false; var isLockedOut = _isLockedOut ?? false; - var lastLockoutDate = _lastLockoutDate ?? DateTime.Now; - var lastLoginDate = _lastLoginDate ?? DateTime.Now; - var lastPasswordChangeDate = _lastPasswordChangeDate ?? DateTime.Now; + DateTime lastLockoutDate = _lastLockoutDate ?? DateTime.Now; + DateTime lastLoginDate = _lastLoginDate ?? DateTime.Now; + DateTime lastPasswordChangeDate = _lastPasswordChangeDate ?? DateTime.Now; var comments = _comments ?? string.Empty; var sessionTimeout = _sessionTimeout ?? 0; var startContentIds = _startContentIds ?? new[] { -1 }; var startMediaIds = _startMediaIds ?? new[] { -1 }; - var groups = _userGroupBuilders.Select(x => x.Build()); + IEnumerable groups = _userGroupBuilders.Select(x => x.Build()); var result = new User( globalSettings, @@ -190,7 +190,7 @@ namespace Umbraco.Tests.Common.Builders StartContentIds = startContentIds, StartMediaIds = startMediaIds, }; - foreach (var readOnlyUserGroup in groups) + foreach (IUserGroup readOnlyUserGroup in groups) { result.AddGroup(readOnlyUserGroup.ToReadOnlyGroup()); } @@ -205,7 +205,7 @@ namespace Umbraco.Tests.Common.Builders for (var i = 0; i < amount; i++) { var name = "User No-" + i; - var user = new UserBuilder() + User user = new UserBuilder() .WithName(name) .WithEmail("test" + i + "@test.com") .WithLogin("test" + i, "test" + i) @@ -221,15 +221,13 @@ namespace Umbraco.Tests.Common.Builders return list; } - public static User CreateUser(string suffix = "") - { - return new UserBuilder() + public static User CreateUser(string suffix = "") => + new UserBuilder() .WithIsApproved(true) .WithName("TestUser" + suffix) .WithLogin("TestUser" + suffix, "testing") .WithEmail("test" + suffix + "@test.com") .Build(); - } int? IWithIdBuilder.Id { diff --git a/src/Umbraco.Tests.Common/Builders/UserGroupBuilder.cs b/src/Umbraco.Tests.Common/Builders/UserGroupBuilder.cs index 9785c27218..ef1733dc7d 100644 --- a/src/Umbraco.Tests.Common/Builders/UserGroupBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/UserGroupBuilder.cs @@ -1,16 +1,20 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System.Collections.Generic; using System.Linq; using Moq; using Umbraco.Core.Models.Membership; using Umbraco.Core.Strings; -using Umbraco.Tests.Common.Builders.Interfaces; using Umbraco.Tests.Common.Builders.Extensions; +using Umbraco.Tests.Common.Builders.Interfaces; namespace Umbraco.Tests.Common.Builders { public class UserGroupBuilder : UserGroupBuilder { - public UserGroupBuilder() : base(null) + public UserGroupBuilder() + : base(null) { } } @@ -33,15 +37,16 @@ namespace Umbraco.Tests.Common.Builders private int? _startMediaId; private int? _userCount; - public UserGroupBuilder(TParent parentBuilder) : base(parentBuilder) + public UserGroupBuilder(TParent parentBuilder) + : base(parentBuilder) { } /// - /// Will suffix the name and alias for testing + /// Will suffix the name, email and username for testing. /// - /// - /// + /// Suffix to add to user group properties. + /// Current builder instance. public UserGroupBuilder WithSuffix(string suffix) { _suffix = suffix; @@ -84,9 +89,8 @@ namespace Umbraco.Tests.Common.Builders return this; } - public IReadOnlyUserGroup BuildReadOnly(IUserGroup userGroup) - { - return Mock.Of(x => + public IReadOnlyUserGroup BuildReadOnly(IUserGroup userGroup) => + Mock.Of(x => x.Permissions == userGroup.Permissions && x.Alias == userGroup.Alias && x.Icon == userGroup.Icon && @@ -95,7 +99,6 @@ namespace Umbraco.Tests.Common.Builders x.StartMediaId == userGroup.StartMediaId && x.AllowedSections == userGroup.AllowedSections && x.Id == userGroup.Id); - } public override IUserGroup Build() { @@ -109,10 +112,12 @@ namespace Umbraco.Tests.Common.Builders var shortStringHelper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig()); - var userGroup = new UserGroup(shortStringHelper, userCount, alias, name, _permissions, icon); - userGroup.Id = id; - userGroup.StartContentId = startContentId; - userGroup.StartMediaId = startMediaId; + var userGroup = new UserGroup(shortStringHelper, userCount, alias, name, _permissions, icon) + { + Id = id, + StartContentId = startContentId, + StartMediaId = startMediaId + }; foreach (var section in _allowedSections) { @@ -122,15 +127,13 @@ namespace Umbraco.Tests.Common.Builders return userGroup; } - public static UserGroup CreateUserGroup(string alias = "testGroup", string name = "Test Group", string suffix = "", string[] permissions = null, string[] allowedSections = null) - { - return (UserGroup)new UserGroupBuilder() + public static UserGroup CreateUserGroup(string alias = "testGroup", string name = "Test Group", string suffix = "", string[] permissions = null, string[] allowedSections = null) => + (UserGroup)new UserGroupBuilder() .WithAlias(alias + suffix) .WithName(name + suffix) .WithPermissions(permissions ?? new[] { "A", "B", "C" }) .WithAllowedSections(allowedSections ?? new[] { "content", "media" }) .Build(); - } int? IWithIdBuilder.Id { diff --git a/src/Umbraco.Tests.Common/Builders/XmlDocumentBuilder.cs b/src/Umbraco.Tests.Common/Builders/XmlDocumentBuilder.cs index d5250c3739..431b86c57c 100644 --- a/src/Umbraco.Tests.Common/Builders/XmlDocumentBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/XmlDocumentBuilder.cs @@ -1,4 +1,7 @@ -using System.Xml; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Xml; namespace Umbraco.Tests.Common.Builders { diff --git a/src/Umbraco.Tests.Integration/TestServerTest/TestAuthHandler.cs b/src/Umbraco.Tests.Integration/TestServerTest/TestAuthHandler.cs index b9acd9529c..ab5821c81c 100644 --- a/src/Umbraco.Tests.Integration/TestServerTest/TestAuthHandler.cs +++ b/src/Umbraco.Tests.Integration/TestServerTest/TestAuthHandler.cs @@ -4,9 +4,9 @@ using Microsoft.AspNetCore.Authentication; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Core; -using Umbraco.Core.BackOffice; using Umbraco.Core.Mapping; using Umbraco.Core.Models.Membership; +using Umbraco.Core.Security; using Umbraco.Core.Services; using Umbraco.Web.Common.Security; diff --git a/src/Umbraco.Tests.Integration/Umbraco.Core/Mapping/ContentTypeModelMappingTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Core/Mapping/ContentTypeModelMappingTests.cs index 7aa9c76791..fb34c7b467 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Core/Mapping/ContentTypeModelMappingTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Core/Mapping/ContentTypeModelMappingTests.cs @@ -203,6 +203,7 @@ namespace Umbraco.Tests.Integration.Umbraco.Core.Mapping { Assert.AreEqual(propTypes.ElementAt(j).Id, result.PropertyTypes.ElementAt(j).Id); Assert.AreEqual(propTypes.ElementAt(j).DataTypeId, result.PropertyTypes.ElementAt(j).DataTypeId); + Assert.AreEqual(propTypes.ElementAt(j).LabelOnTop, result.PropertyTypes.ElementAt(j).LabelOnTop); } } @@ -425,6 +426,7 @@ namespace Umbraco.Tests.Integration.Umbraco.Core.Mapping { Assert.AreEqual(propTypes[j].Id, result.Groups.ElementAt(i).Properties.ElementAt(j).Id); Assert.AreEqual(propTypes[j].DataTypeId, result.Groups.ElementAt(i).Properties.ElementAt(j).DataTypeId); + Assert.AreEqual(propTypes[j].LabelOnTop, result.Groups.ElementAt(i).Properties.ElementAt(j).LabelOnTop); } } @@ -619,6 +621,7 @@ namespace Umbraco.Tests.Integration.Umbraco.Core.Mapping Assert.AreEqual(basic.Validation.MandatoryMessage, result.MandatoryMessage); Assert.AreEqual(basic.Validation.Pattern, result.ValidationRegExp); Assert.AreEqual(basic.Validation.PatternMessage, result.ValidationRegExpMessage); + Assert.AreEqual(basic.LabelOnTop, result.LabelOnTop); } [Test] @@ -661,6 +664,7 @@ namespace Umbraco.Tests.Integration.Umbraco.Core.Mapping Assert.AreEqual(basic.Validation.MandatoryMessage, result.MandatoryMessage); Assert.AreEqual(basic.Validation.Pattern, result.ValidationRegExp); Assert.AreEqual(basic.Validation.PatternMessage, result.ValidationRegExpMessage); + Assert.AreEqual(basic.LabelOnTop, result.LabelOnTop); } [Test] @@ -1042,6 +1046,7 @@ namespace Umbraco.Tests.Integration.Umbraco.Core.Mapping Pattern = string.Empty }, SortOrder = 0, + LabelOnTop = true, DataTypeId = dataTypeId } } @@ -1088,6 +1093,7 @@ namespace Umbraco.Tests.Integration.Umbraco.Core.Mapping Pattern = string.Empty }, SortOrder = 0, + LabelOnTop = true, DataTypeId = dataTypeId } } @@ -1112,6 +1118,7 @@ namespace Umbraco.Tests.Integration.Umbraco.Core.Mapping Pattern = string.Empty }, SortOrder = 0, + LabelOnTop = false, DataTypeId = dataTypeId } } @@ -1166,6 +1173,7 @@ namespace Umbraco.Tests.Integration.Umbraco.Core.Mapping Pattern = string.Empty }, SortOrder = 0, + LabelOnTop = true, DataTypeId = dataTypeId } } @@ -1190,6 +1198,7 @@ namespace Umbraco.Tests.Integration.Umbraco.Core.Mapping Pattern = string.Empty }, SortOrder = 0, + LabelOnTop = false, DataTypeId = dataTypeId } } diff --git a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ContentTypeRepositoryTest.cs b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ContentTypeRepositoryTest.cs index 757e79eef3..5013823540 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ContentTypeRepositoryTest.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ContentTypeRepositoryTest.cs @@ -336,6 +336,7 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Persistence.Repositor Assert.That(contentType.Path.Contains(","), Is.True); Assert.That(contentType.SortOrder, Is.GreaterThan(0)); + Assert.That(contentType.PropertyGroups.ElementAt(0).Name == "testGroup", Is.True); var groupId = contentType.PropertyGroups.ElementAt(0).Id; @@ -343,6 +344,7 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Persistence.Repositor Assert.AreEqual("gen", propertyTypes[0].Alias); // just to be sure Assert.IsNull(propertyTypes[0].PropertyGroupId); Assert.IsTrue(propertyTypes.Skip(1).All((x => x.PropertyGroupId.Value == groupId))); + Assert.That(propertyTypes.Single(x=> x.Alias == "title").LabelOnTop, Is.True); } } @@ -365,7 +367,8 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Persistence.Repositor Description = "Optional Subtitle", Mandatory = false, SortOrder = 1, - DataTypeId = -88 + DataTypeId = -88, + LabelOnTop = true }); repository.Save(contentType); @@ -377,6 +380,8 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Persistence.Repositor Assert.That(dirty, Is.False); Assert.That(contentType.Thumbnail, Is.EqualTo("Doc2.png")); Assert.That(contentType.PropertyTypes.Any(x => x.Alias == "subtitle"), Is.True); + Assert.That(contentType.PropertyTypes.Single(x => x.Alias == "subtitle").LabelOnTop, Is.True); + } @@ -455,7 +460,8 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Persistence.Repositor Pattern = "" }, SortOrder = 1, - DataTypeId = -88 + DataTypeId = -88, + LabelOnTop = true } }); @@ -464,6 +470,7 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Persistence.Repositor // just making sure Assert.AreEqual(mapped.Thumbnail, "Doc2.png"); Assert.IsTrue(mapped.PropertyTypes.Any(x => x.Alias == "subtitle")); + Assert.IsTrue(mapped.PropertyTypes.Single(x => x.Alias == "subtitle").LabelOnTop); repository.Save(mapped); @@ -478,6 +485,9 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Persistence.Repositor Assert.That(dirty, Is.False); Assert.That(contentType.Thumbnail, Is.EqualTo("Doc2.png")); Assert.That(contentType.PropertyTypes.Any(x => x.Alias == "subtitle"), Is.True); + + Assert.That(contentType.PropertyTypes.Single(x => x.Alias == "subtitle").LabelOnTop, Is.True); + foreach (var propertyType in contentType.PropertyTypes) { Assert.IsTrue(propertyType.HasIdentity); diff --git a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ExternalLoginServiceTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ExternalLoginServiceTests.cs index 429e1953f7..192971f405 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ExternalLoginServiceTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ExternalLoginServiceTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using System.Threading; using NUnit.Framework; @@ -93,7 +93,7 @@ namespace Umbraco.Tests.Services var user = new User(GlobalSettings, "Test", "test@test.com", "test", "helloworldtest"); UserService.Save(user); - var extLogin = new IdentityUserLogin("test1", Guid.NewGuid().ToString("N"), user.Id) + var extLogin = new IdentityUserLogin("test1", Guid.NewGuid().ToString("N"), user.Id.ToString()) { UserData = "hello" }; @@ -112,7 +112,7 @@ namespace Umbraco.Tests.Services var user = new User(GlobalSettings, "Test", "test@test.com", "test", "helloworldtest"); UserService.Save(user); - var extLogin = new IdentityUserLogin("test1", Guid.NewGuid().ToString("N"), user.Id) + var extLogin = new IdentityUserLogin("test1", Guid.NewGuid().ToString("N"), user.Id.ToString()) { UserData = "hello" }; @@ -218,7 +218,7 @@ namespace Umbraco.Tests.Services var logins = ExternalLoginService.GetAll(user.Id).OrderBy(x => x.LoginProvider).ToList(); logins.RemoveAt(0); // remove the first one - logins.Add(new IdentityUserLogin("test5", Guid.NewGuid().ToString("N"), user.Id)); // add a new one + logins.Add(new IdentityUserLogin("test5", Guid.NewGuid().ToString("N"), user.Id.ToString())); // add a new one // save new list ExternalLoginService.Save(user.Id, logins.Select(x => new ExternalLogin(x.LoginProvider, x.ProviderKey))); diff --git a/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/UmbracoBackOfficeServiceCollectionExtensionsTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/UmbracoBackOfficeServiceCollectionExtensionsTests.cs index 450b3a341a..d9dee389ee 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/UmbracoBackOfficeServiceCollectionExtensionsTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/UmbracoBackOfficeServiceCollectionExtensionsTests.cs @@ -1,10 +1,11 @@ -using System; +using System; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; +using Umbraco.Core.Security; using Umbraco.Extensions; -using Umbraco.Core.BackOffice; using Umbraco.Tests.Integration.Testing; +using Umbraco.Web.Common.Security; namespace Umbraco.Tests.Integration.Umbraco.Web.BackOffice { @@ -26,7 +27,7 @@ namespace Umbraco.Tests.Integration.Umbraco.Web.BackOffice var principalFactory = Services.GetService>(); Assert.IsNotNull(principalFactory); - Assert.AreEqual(typeof(BackOfficeClaimsPrincipalFactory), principalFactory.GetType()); + Assert.AreEqual(typeof(BackOfficeClaimsPrincipalFactory), principalFactory.GetType()); } [Test] diff --git a/src/Umbraco.Tests.UnitTests/AutoFixture/AutoMoqDataAttribute.cs b/src/Umbraco.Tests.UnitTests/AutoFixture/AutoMoqDataAttribute.cs index 78d5d5554c..365dca780c 100644 --- a/src/Umbraco.Tests.UnitTests/AutoFixture/AutoMoqDataAttribute.cs +++ b/src/Umbraco.Tests.UnitTests/AutoFixture/AutoMoqDataAttribute.cs @@ -10,10 +10,10 @@ using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Options; using Moq; using Umbraco.Core; -using Umbraco.Core.BackOffice; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Hosting; +using Umbraco.Core.Security; using Umbraco.Tests.Common.Builders; using Umbraco.Web.BackOffice.Controllers; using Umbraco.Web.BackOffice.Routing; diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/BackOffice/BackOfficeClaimsPrincipalFactoryTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/BackOffice/BackOfficeClaimsPrincipalFactoryTests.cs index 13c73dfa96..64bdca6437 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/BackOffice/BackOfficeClaimsPrincipalFactoryTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/BackOffice/BackOfficeClaimsPrincipalFactoryTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Security.Claims; using System.Threading.Tasks; @@ -7,51 +7,42 @@ using Microsoft.Extensions.Options; using Moq; using NUnit.Framework; using Umbraco.Core; -using Umbraco.Core.BackOffice; -using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Models.Membership; +using Umbraco.Core.Security; using Umbraco.Extensions; -using Umbraco.Tests.Common.Builders; namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice { [TestFixture] public class BackOfficeClaimsPrincipalFactoryTests { - private const int _testUserId = 2; - private const string _testUserName = "bob"; - private const string _testUserGivenName = "Bob"; - private const string _testUserCulture = "en-US"; - private const string _testUserSecurityStamp = "B6937738-9C17-4C7D-A25A-628A875F5177"; + private const int TestUserId = 2; + private const string TestUserName = "bob"; + private const string TestUserGivenName = "Bob"; + private const string TestUserCulture = "en-US"; + private const string TestUserSecurityStamp = "B6937738-9C17-4C7D-A25A-628A875F5177"; private BackOfficeIdentityUser _testUser; private Mock> _mockUserManager; + private static Mock> GetMockedUserManager() + => new Mock>(new Mock>().Object, null, null, null, null, null, null, null, null); + [Test] public void Ctor_When_UserManager_Is_Null_Expect_ArgumentNullException() - { - Assert.Throws(() => new BackOfficeClaimsPrincipalFactory( - null, - new OptionsWrapper(new BackOfficeIdentityOptions()))); - } + => Assert.Throws(() => new BackOfficeClaimsPrincipalFactory( + null, + new OptionsWrapper(new BackOfficeIdentityOptions()))); [Test] public void Ctor_When_Options_Are_Null_Expect_ArgumentNullException() - { - Assert.Throws(() => new BackOfficeClaimsPrincipalFactory( - new Mock>(new Mock>().Object, - null, null, null, null, null, null, null, null).Object, - null)); - } + => Assert.Throws(() => new BackOfficeClaimsPrincipalFactory(GetMockedUserManager().Object, null)); [Test] public void Ctor_When_Options_Value_Is_Null_Expect_ArgumentNullException() - { - Assert.Throws(() => new BackOfficeClaimsPrincipalFactory( - new Mock>(new Mock>().Object, - null, null, null, null, null, null, null, null).Object, - new OptionsWrapper(null))); - } + => Assert.Throws(() => new BackOfficeClaimsPrincipalFactory( + GetMockedUserManager().Object, + new OptionsWrapper(null))); [Test] public void CreateAsync_When_User_Is_Null_Expect_ArgumentNullException() @@ -72,8 +63,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice Assert.IsNotNull(umbracoBackOfficeIdentity); } - [TestCase(ClaimTypes.NameIdentifier, _testUserId)] - [TestCase(ClaimTypes.Name, _testUserName)] + [TestCase(ClaimTypes.NameIdentifier, TestUserId)] + [TestCase(ClaimTypes.Name, TestUserName)] public async Task CreateAsync_Should_Include_Claim(string expectedClaimType, object expectedClaimValue) { var sut = CreateSut(); @@ -107,7 +98,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice const string expectedClaimType = ClaimTypes.Role; const string expectedClaimValue = "b87309fb-4caf-48dc-b45a-2b752d051508"; - _testUser.Roles.Add(new global::Umbraco.Core.Models.Identity.IdentityUserRole{RoleId = expectedClaimValue}); + _testUser.Roles.Add(new IdentityUserRole { RoleId = expectedClaimValue }); _mockUserManager.Setup(x => x.SupportsUserRole).Returns(true); _mockUserManager.Setup(x => x.GetRolesAsync(_testUser)).ReturnsAsync(new[] {expectedClaimValue}); @@ -124,7 +115,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice const string expectedClaimType = "custom"; const string expectedClaimValue = "val"; - _testUser.Claims.Add(new global::Umbraco.Core.Models.Identity.IdentityUserClaim {ClaimType = expectedClaimType, ClaimValue = expectedClaimValue}); + _testUser.Claims.Add(new IdentityUserClaim { ClaimType = expectedClaimType, ClaimValue = expectedClaimValue}); _mockUserManager.Setup(x => x.SupportsUserClaim).Returns(true); _mockUserManager.Setup(x => x.GetClaimsAsync(_testUser)).ReturnsAsync( new List {new Claim(expectedClaimType, expectedClaimValue)}); @@ -141,17 +132,16 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice { var globalSettings = new GlobalSettings { DefaultUILanguage = "test" }; - _testUser = new BackOfficeIdentityUser(globalSettings, _testUserId, new List()) + _testUser = new BackOfficeIdentityUser(globalSettings, TestUserId, new List()) { - UserName = _testUserName, - Name = _testUserGivenName, + UserName = TestUserName, + Name = TestUserGivenName, Email = "bob@umbraco.test", - SecurityStamp = _testUserSecurityStamp, - Culture = _testUserCulture + SecurityStamp = TestUserSecurityStamp, + Culture = TestUserCulture }; - _mockUserManager = new Mock>(new Mock>().Object, - null, null, null, null, null, null, null, null); + _mockUserManager = GetMockedUserManager(); _mockUserManager.Setup(x => x.GetUserIdAsync(_testUser)).ReturnsAsync(_testUser.Id.ToString); _mockUserManager.Setup(x => x.GetUserNameAsync(_testUser)).ReturnsAsync(_testUser.UserName); _mockUserManager.Setup(x => x.SupportsUserSecurityStamp).Returns(false); @@ -159,10 +149,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice _mockUserManager.Setup(x => x.SupportsUserRole).Returns(false); } - private BackOfficeClaimsPrincipalFactory CreateSut() - { - return new BackOfficeClaimsPrincipalFactory(_mockUserManager.Object, + private BackOfficeClaimsPrincipalFactory CreateSut() => new BackOfficeClaimsPrincipalFactory( + _mockUserManager.Object, new OptionsWrapper(new BackOfficeIdentityOptions())); - } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/BackOffice/NopLookupNormalizerTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/BackOffice/NopLookupNormalizerTests.cs index 1447b7f97e..02ff01ff3b 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/BackOffice/NopLookupNormalizerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/BackOffice/NopLookupNormalizerTests.cs @@ -1,6 +1,6 @@ -using System; +using System; using NUnit.Framework; -using Umbraco.Core.BackOffice; +using Umbraco.Core.Security; namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice { diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/BackOffice/UmbracoBackOfficeIdentityTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/BackOffice/UmbracoBackOfficeIdentityTests.cs index 9e9d29a123..79a9456643 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/BackOffice/UmbracoBackOfficeIdentityTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/BackOffice/UmbracoBackOfficeIdentityTests.cs @@ -1,9 +1,9 @@ -using System; +using System; using System.Linq; using System.Security.Claims; using NUnit.Framework; using Umbraco.Core; -using Umbraco.Core.BackOffice; +using Umbraco.Core.Security; namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice { @@ -103,7 +103,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice }); var identity = new UmbracoBackOfficeIdentity(claimsIdentity, - 1234, "testing", "hello world", new[] { 654 }, new[] { 654 }, "en-us", securityStamp, new[] { "content", "media" }, new[] { "admin" }); + "1234", "testing", "hello world", new[] { 654 }, new[] { 654 }, "en-us", securityStamp, new[] { "content", "media" }, new[] { "admin" }); Assert.AreEqual(12, identity.Claims.Count()); Assert.IsNull(identity.Actor); @@ -116,7 +116,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice var securityStamp = Guid.NewGuid().ToString(); var identity = new UmbracoBackOfficeIdentity( - 1234, "testing", "hello world", new[] { 654 }, new[] { 654 }, "en-us", securityStamp, new[] { "content", "media" }, new[] { "admin" }); + "1234", "testing", "hello world", new[] { 654 }, new[] { 654 }, "en-us", securityStamp, new[] { "content", "media" }, new[] { "admin" }); // this will be filtered out during cloning identity.AddClaim(new Claim(Constants.Security.TicketExpiresClaimType, "test")); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/Extensions/HealthCheckSettingsExtensionsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/Extensions/HealthCheckSettingsExtensionsTests.cs index 0eca0b7d02..d3e2ca3014 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/Extensions/HealthCheckSettingsExtensionsTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/Extensions/HealthCheckSettingsExtensionsTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using NUnit.Framework; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Extensions; @@ -12,10 +15,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Configuration.Models.Extensions private ICronTabParser CronTabParser => new NCronTabParser(); [TestCase("30 12 * * *", 30)] - [TestCase("15 18 * * *", 60 * 6 + 15)] + [TestCase("15 18 * * *", (60 * 6) + 15)] [TestCase("0 3 * * *", 60 * 15)] - [TestCase("0 3 2 * *", 24 * 60 * 1 + 60 * 15)] - [TestCase("0 6 * * 3", 24 * 60 * 3 + 60 * 18)] + [TestCase("0 3 2 * *", (24 * 60 * 1) + (60 * 15))] + [TestCase("0 6 * * 3", (24 * 60 * 3) + (60 * 18))] public void Returns_Notification_Delay_From_Provided_Time(string firstRunTimeCronExpression, int expectedDelayInMinutes) { var settings = new HealthChecksSettings @@ -26,7 +29,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Configuration.Models.Extensions } }; var now = new DateTime(2020, 10, 31, 12, 0, 0); - var result = settings.GetNotificationDelay(CronTabParser, now, TimeSpan.Zero); + TimeSpan result = settings.GetNotificationDelay(CronTabParser, now, TimeSpan.Zero); Assert.AreEqual(expectedDelayInMinutes, result.TotalMinutes); } @@ -41,7 +44,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Configuration.Models.Extensions } }; var now = new DateTime(2020, 10, 31, 12, 25, 0); - var result = settings.GetNotificationDelay(CronTabParser, now, TimeSpan.FromMinutes(10)); + TimeSpan result = settings.GetNotificationDelay(CronTabParser, now, TimeSpan.FromMinutes(10)); Assert.AreEqual(10, result.TotalMinutes); } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/Validation/ContentSettingsValidatorTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/Validation/ContentSettingsValidatorTests.cs index 31f6f5e31c..ded3f1c725 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/Validation/ContentSettingsValidatorTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/Validation/ContentSettingsValidatorTests.cs @@ -1,18 +1,22 @@ -using NUnit.Framework; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Microsoft.Extensions.Options; +using NUnit.Framework; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Configuration.Models.Validation; namespace Umbraco.Tests.UnitTests.Umbraco.Core.Configuration.Models.Validation { [TestFixture] - public class ContentSettingsValidationTests + public class ContentSettingsValidatorTests { [Test] public void Returns_Success_ForValid_Configuration() { var validator = new ContentSettingsValidator(); - var options = BuildContentSettings(); - var result = validator.Validate("settings", options); + ContentSettings options = BuildContentSettings(); + ValidateOptionsResult result = validator.Validate("settings", options); Assert.True(result.Succeeded); } @@ -20,8 +24,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Configuration.Models.Validation public void Returns_Fail_For_Configuration_With_Invalid_Error404Collection_Due_To_Duplicate_Id() { var validator = new ContentSettingsValidator(); - var options = BuildContentSettings(contentXPath: "/aaa/bbb"); - var result = validator.Validate("settings", options); + ContentSettings options = BuildContentSettings(contentXPath: "/aaa/bbb"); + ValidateOptionsResult result = validator.Validate("settings", options); Assert.False(result.Succeeded); } @@ -29,8 +33,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Configuration.Models.Validation public void Returns_Fail_For_Configuration_With_Invalid_Error404Collection_Due_To_Empty_Culture() { var validator = new ContentSettingsValidator(); - var options = BuildContentSettings(culture: string.Empty); - var result = validator.Validate("settings", options); + ContentSettings options = BuildContentSettings(culture: string.Empty); + ValidateOptionsResult result = validator.Validate("settings", options); Assert.False(result.Succeeded); } @@ -38,27 +42,25 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Configuration.Models.Validation public void Returns_Fail_For_Configuration_With_Invalid_AutoFillImageProperties_Collection() { var validator = new ContentSettingsValidator(); - var options = BuildContentSettings(culture: string.Empty); - var result = validator.Validate("settings", options); + ContentSettings options = BuildContentSettings(culture: string.Empty); + ValidateOptionsResult result = validator.Validate("settings", options); Assert.False(result.Succeeded); } - private static ContentSettings BuildContentSettings(string culture = "en-US", string contentXPath = "", string autoFillImagePropertyAlias = "testAlias") - { - return new ContentSettings + private static ContentSettings BuildContentSettings(string culture = "en-US", string contentXPath = "", string autoFillImagePropertyAlias = "testAlias") => + new ContentSettings { Error404Collection = new ContentErrorPage[] - { - new ContentErrorPage { Culture = culture, ContentId = 1, ContentXPath = contentXPath }, - }, + { + new ContentErrorPage { Culture = culture, ContentId = 1, ContentXPath = contentXPath }, + }, Imaging = new ContentImagingSettings { AutoFillImageProperties = new ImagingAutoFillUploadField[] - { - new ImagingAutoFillUploadField { Alias = autoFillImagePropertyAlias, WidthFieldAlias = "w", HeightFieldAlias = "h", LengthFieldAlias = "l", ExtensionFieldAlias = "e" } - } + { + new ImagingAutoFillUploadField { Alias = autoFillImagePropertyAlias, WidthFieldAlias = "w", HeightFieldAlias = "h", LengthFieldAlias = "l", ExtensionFieldAlias = "e" } + } } }; - } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/Validation/GlobalSettingsValidatorTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/Validation/GlobalSettingsValidatorTests.cs index 3cc0532db2..f286dd42b0 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/Validation/GlobalSettingsValidatorTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/Validation/GlobalSettingsValidatorTests.cs @@ -1,4 +1,8 @@ -using NUnit.Framework; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Microsoft.Extensions.Options; +using NUnit.Framework; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Configuration.Models.Validation; @@ -11,8 +15,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Configuration.Models.Validation public void Returns_Success_ForValid_Configuration() { var validator = new GlobalSettingsValidator(); - var options = BuildGlobalSettings(); - var result = validator.Validate("settings", options); + GlobalSettings options = BuildGlobalSettings(); + ValidateOptionsResult result = validator.Validate("settings", options); Assert.True(result.Succeeded); } @@ -20,20 +24,18 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Configuration.Models.Validation public void Returns_Fail_For_Configuration_With_Invalid_SmtpFrom_Field() { var validator = new GlobalSettingsValidator(); - var options = BuildGlobalSettings(smtpEmail: "invalid"); - var result = validator.Validate("settings", options); + GlobalSettings options = BuildGlobalSettings(smtpEmail: "invalid"); + ValidateOptionsResult result = validator.Validate("settings", options); Assert.False(result.Succeeded); } - private static GlobalSettings BuildGlobalSettings(string smtpEmail = "test@test.com") - { - return new GlobalSettings + private static GlobalSettings BuildGlobalSettings(string smtpEmail = "test@test.com") => + new GlobalSettings { Smtp = new SmtpSettings { From = smtpEmail, } }; - } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/Validation/HealthChecksSettingsValidatorTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/Validation/HealthChecksSettingsValidatorTests.cs index 64a8be79a8..9ae5444134 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/Validation/HealthChecksSettingsValidatorTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/Validation/HealthChecksSettingsValidatorTests.cs @@ -1,4 +1,8 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; +using Microsoft.Extensions.Options; using NUnit.Framework; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; @@ -13,8 +17,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Configuration.Models.Validation public void Returns_Success_ForValid_Configuration() { var validator = new HealthChecksSettingsValidator(new NCronTabParser()); - var options = BuildHealthChecksSettings(); - var result = validator.Validate("settings", options); + HealthChecksSettings options = BuildHealthChecksSettings(); + ValidateOptionsResult result = validator.Validate("settings", options); Assert.True(result.Succeeded); } @@ -22,14 +26,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Configuration.Models.Validation public void Returns_Fail_For_Configuration_With_Invalid_Notification_FirstRunTime() { var validator = new HealthChecksSettingsValidator(new NCronTabParser()); - var options = BuildHealthChecksSettings(firstRunTime: "0 3 *"); - var result = validator.Validate("settings", options); + HealthChecksSettings options = BuildHealthChecksSettings(firstRunTime: "0 3 *"); + ValidateOptionsResult result = validator.Validate("settings", options); Assert.False(result.Succeeded); } - private static HealthChecksSettings BuildHealthChecksSettings(string firstRunTime = "0 3 * * *") - { - return new HealthChecksSettings + private static HealthChecksSettings BuildHealthChecksSettings(string firstRunTime = "0 3 * * *") => + new HealthChecksSettings { Notification = new HealthChecksNotificationSettings { @@ -38,6 +41,5 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Configuration.Models.Validation Period = TimeSpan.FromHours(1), } }; - } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/Validation/RequestHandlerSettingsValidatorTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/Validation/RequestHandlerSettingsValidatorTests.cs index f579fec695..4a53fbf375 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/Validation/RequestHandlerSettingsValidatorTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/Validation/RequestHandlerSettingsValidatorTests.cs @@ -1,4 +1,8 @@ -using NUnit.Framework; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Microsoft.Extensions.Options; +using NUnit.Framework; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Configuration.Models.Validation; @@ -12,7 +16,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Configuration.Models.Validation { var validator = new RequestHandlerSettingsValidator(); var options = new RequestHandlerSettings(); - var result = validator.Validate("settings", options); + ValidateOptionsResult result = validator.Validate("settings", options); Assert.True(result.Succeeded); } @@ -21,7 +25,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Configuration.Models.Validation { var validator = new RequestHandlerSettingsValidator(); var options = new RequestHandlerSettings { ConvertUrlsToAscii = "invalid" }; - var result = validator.Validate("settings", options); + ValidateOptionsResult result = validator.Validate("settings", options); Assert.False(result.Succeeded); } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/ClaimsPrincipalExtensionsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/ClaimsPrincipalExtensionsTests.cs index a078456f8f..ad0f292fae 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/ClaimsPrincipalExtensionsTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/ClaimsPrincipalExtensionsTests.cs @@ -1,11 +1,11 @@ -using NUnit.Framework; +using NUnit.Framework; using System; using System.Collections.Generic; using System.Linq; using System.Security.Claims; using Umbraco.Extensions; using Umbraco.Core; -using Umbraco.Core.BackOffice; +using Umbraco.Core.Security; namespace Umbraco.Tests.UnitTests.Umbraco.Core.Extensions { @@ -15,7 +15,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Extensions [Test] public void Get_Remaining_Ticket_Seconds() { - var backOfficeIdentity = new UmbracoBackOfficeIdentity(-1, "test", "test", + var backOfficeIdentity = new UmbracoBackOfficeIdentity(Constants.Security.SuperUserIdAsString, "test", "test", Enumerable.Empty(), Enumerable.Empty(), "en-US", Guid.NewGuid().ToString(), Enumerable.Empty(), Enumerable.Empty()); var principal = new ClaimsPrincipal(backOfficeIdentity); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackOffice/BackOfficeLookupNormalizerTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackOffice/BackOfficeLookupNormalizerTests.cs index 3feb458fe8..8172a712d8 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackOffice/BackOfficeLookupNormalizerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackOffice/BackOfficeLookupNormalizerTests.cs @@ -1,6 +1,6 @@ -using System; +using System; using NUnit.Framework; -using Umbraco.Core.BackOffice; +using Umbraco.Core.Security; namespace Umbraco.Tests.UnitTests.Umbraco.Web.Backoffice { diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/HealthCheckNotifierTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/HealthCheckNotifierTests.cs index cdb98f7fa5..ffad002928 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/HealthCheckNotifierTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/HealthCheckNotifierTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -25,14 +28,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices { private Mock _mockNotificationMethod; - private const string _check1Id = "00000000-0000-0000-0000-000000000001"; - private const string _check2Id = "00000000-0000-0000-0000-000000000002"; - private const string _check3Id = "00000000-0000-0000-0000-000000000003"; + private const string Check1Id = "00000000-0000-0000-0000-000000000001"; + private const string Check2Id = "00000000-0000-0000-0000-000000000002"; + private const string Check3Id = "00000000-0000-0000-0000-000000000003"; [Test] public async Task Does_Not_Execute_When_Not_Enabled() { - var sut = CreateHealthCheckNotifier(enabled: false); + HealthCheckNotifier sut = CreateHealthCheckNotifier(enabled: false); await sut.PerformExecuteAsync(null); VerifyNotificationsNotSent(); } @@ -44,7 +47,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices [TestCase(RuntimeLevel.BootFailed)] public async Task Does_Not_Execute_When_Runtime_State_Is_Not_Run(RuntimeLevel runtimeLevel) { - var sut = CreateHealthCheckNotifier(runtimeLevel: runtimeLevel); + HealthCheckNotifier sut = CreateHealthCheckNotifier(runtimeLevel: runtimeLevel); await sut.PerformExecuteAsync(null); VerifyNotificationsNotSent(); } @@ -52,7 +55,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices [Test] public async Task Does_Not_Execute_When_Server_Role_Is_Replica() { - var sut = CreateHealthCheckNotifier(serverRole: ServerRole.Replica); + HealthCheckNotifier sut = CreateHealthCheckNotifier(serverRole: ServerRole.Replica); await sut.PerformExecuteAsync(null); VerifyNotificationsNotSent(); } @@ -60,7 +63,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices [Test] public async Task Does_Not_Execute_When_Server_Role_Is_Unknown() { - var sut = CreateHealthCheckNotifier(serverRole: ServerRole.Unknown); + HealthCheckNotifier sut = CreateHealthCheckNotifier(serverRole: ServerRole.Unknown); await sut.PerformExecuteAsync(null); VerifyNotificationsNotSent(); } @@ -68,7 +71,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices [Test] public async Task Does_Not_Execute_When_Not_Main_Dom() { - var sut = CreateHealthCheckNotifier(isMainDom: false); + HealthCheckNotifier sut = CreateHealthCheckNotifier(isMainDom: false); await sut.PerformExecuteAsync(null); VerifyNotificationsNotSent(); } @@ -76,7 +79,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices [Test] public async Task Does_Not_Execute_With_No_Enabled_Notification_Methods() { - var sut = CreateHealthCheckNotifier(notificationEnabled: false); + HealthCheckNotifier sut = CreateHealthCheckNotifier(notificationEnabled: false); await sut.PerformExecuteAsync(null); VerifyNotificationsNotSent(); } @@ -84,7 +87,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices [Test] public async Task Executes_With_Enabled_Notification_Methods() { - var sut = CreateHealthCheckNotifier(); + HealthCheckNotifier sut = CreateHealthCheckNotifier(); await sut.PerformExecuteAsync(null); VerifyNotificationsSent(); } @@ -92,10 +95,11 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices [Test] public async Task Executes_Only_Enabled_Checks() { - var sut = CreateHealthCheckNotifier(); + HealthCheckNotifier sut = CreateHealthCheckNotifier(); await sut.PerformExecuteAsync(null); - _mockNotificationMethod.Verify(x => x.SendAsync(It.Is( - y => y.ResultsAsDictionary.Count == 1 && y.ResultsAsDictionary.ContainsKey("Check1"))), Times.Once); + _mockNotificationMethod.Verify( + x => x.SendAsync( + It.Is(y => y.ResultsAsDictionary.Count == 1 && y.ResultsAsDictionary.ContainsKey("Check1"))), Times.Once); } private HealthCheckNotifier CreateHealthCheckNotifier( @@ -112,12 +116,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices Enabled = enabled, DisabledChecks = new List { - new DisabledHealthCheckSettings { Id = Guid.Parse(_check3Id) } + new DisabledHealthCheckSettings { Id = Guid.Parse(Check3Id) } } }, DisabledChecks = new List { - new DisabledHealthCheckSettings { Id = Guid.Parse(_check2Id) } + new DisabledHealthCheckSettings { Id = Guid.Parse(Check2Id) } } }; var checks = new HealthCheckCollection(new List @@ -144,52 +148,45 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices var mockLogger = new Mock>(); var mockProfilingLogger = new Mock(); - return new HealthCheckNotifier(Options.Create(settings), checks, notifications, - mockRunTimeState.Object, mockServerRegistrar.Object, mockMainDom.Object, mockScopeProvider.Object, - mockLogger.Object, mockProfilingLogger.Object, Mock.Of()); + return new HealthCheckNotifier( + Options.Create(settings), + checks, + notifications, + mockRunTimeState.Object, + mockServerRegistrar.Object, + mockMainDom.Object, + mockScopeProvider.Object, + mockLogger.Object, + mockProfilingLogger.Object, + Mock.Of()); } - private void VerifyNotificationsNotSent() - { - VerifyNotificationsSentTimes(Times.Never()); - } + private void VerifyNotificationsNotSent() => VerifyNotificationsSentTimes(Times.Never()); - private void VerifyNotificationsSent() - { - VerifyNotificationsSentTimes(Times.Once()); - } + private void VerifyNotificationsSent() => VerifyNotificationsSentTimes(Times.Once()); - private void VerifyNotificationsSentTimes(Times times) - { - _mockNotificationMethod.Verify(x => x.SendAsync(It.IsAny()), times); - } + private void VerifyNotificationsSentTimes(Times times) => _mockNotificationMethod.Verify(x => x.SendAsync(It.IsAny()), times); - [HealthCheck(_check1Id, "Check1")] + [HealthCheck(Check1Id, "Check1")] private class TestHealthCheck1 : TestHealthCheck { } - [HealthCheck(_check2Id, "Check2")] + [HealthCheck(Check2Id, "Check2")] private class TestHealthCheck2 : TestHealthCheck { } - [HealthCheck(_check3Id, "Check3")] + [HealthCheck(Check3Id, "Check3")] private class TestHealthCheck3 : TestHealthCheck { } private class TestHealthCheck : HealthCheck { - public override HealthCheckStatus ExecuteAction(HealthCheckAction action) - { - return new HealthCheckStatus("Check message"); - } + public override HealthCheckStatus ExecuteAction(HealthCheckAction action) => new HealthCheckStatus("Check message"); - public override IEnumerable GetStatus() - { - return Enumerable.Empty(); - } + public override IEnumerable GetStatus() => Enumerable.Empty(); } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/KeepAliveTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/KeepAliveTests.cs index 9fc1454b6d..98164a7aac 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/KeepAliveTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/KeepAliveTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Net; using System.Net.Http; using System.Threading; @@ -23,12 +26,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices { private Mock _mockHttpMessageHandler; - private const string _applicationUrl = "https://mysite.com"; + private const string ApplicationUrl = "https://mysite.com"; [Test] public async Task Does_Not_Execute_When_Not_Enabled() { - var sut = CreateKeepAlive(enabled: false); + KeepAlive sut = CreateKeepAlive(enabled: false); await sut.PerformExecuteAsync(null); VerifyKeepAliveRequestNotSent(); } @@ -36,7 +39,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices [Test] public async Task Does_Not_Execute_When_Server_Role_Is_Replica() { - var sut = CreateKeepAlive(serverRole: ServerRole.Replica); + KeepAlive sut = CreateKeepAlive(serverRole: ServerRole.Replica); await sut.PerformExecuteAsync(null); VerifyKeepAliveRequestNotSent(); } @@ -44,7 +47,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices [Test] public async Task Does_Not_Execute_When_Server_Role_Is_Unknown() { - var sut = CreateKeepAlive(serverRole: ServerRole.Unknown); + KeepAlive sut = CreateKeepAlive(serverRole: ServerRole.Unknown); await sut.PerformExecuteAsync(null); VerifyKeepAliveRequestNotSent(); } @@ -52,7 +55,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices [Test] public async Task Does_Not_Execute_When_Not_Main_Dom() { - var sut = CreateKeepAlive(isMainDom: false); + KeepAlive sut = CreateKeepAlive(isMainDom: false); await sut.PerformExecuteAsync(null); VerifyKeepAliveRequestNotSent(); } @@ -60,7 +63,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices [Test] public async Task Executes_And_Calls_Ping_Url() { - var sut = CreateKeepAlive(); + KeepAlive sut = CreateKeepAlive(); await sut.PerformExecuteAsync(null); VerifyKeepAliveRequestSent(); } @@ -76,7 +79,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices }; var mockRequestAccessor = new Mock(); - mockRequestAccessor.Setup(x => x.GetApplicationUrl()).Returns(new Uri(_applicationUrl)); + mockRequestAccessor.Setup(x => x.GetApplicationUrl()).Returns(new Uri(ApplicationUrl)); var mockServerRegistrar = new Mock(); mockServerRegistrar.Setup(x => x.GetCurrentServerRole()).Returns(serverRole); @@ -99,26 +102,25 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices var mockHttpClientFactory = new Mock(MockBehavior.Strict); mockHttpClientFactory.Setup(x => x.CreateClient(It.IsAny())).Returns(httpClient); - return new KeepAlive(mockRequestAccessor.Object, mockMainDom.Object, Options.Create(settings), - mockLogger.Object, mockProfilingLogger.Object, mockServerRegistrar.Object, mockHttpClientFactory.Object); + return new KeepAlive( + mockRequestAccessor.Object, + mockMainDom.Object, + Options.Create(settings), + mockLogger.Object, + mockProfilingLogger.Object, + mockServerRegistrar.Object, + mockHttpClientFactory.Object); } - private void VerifyKeepAliveRequestNotSent() - { - VerifyKeepAliveRequestSentTimes(Times.Never()); - } + private void VerifyKeepAliveRequestNotSent() => VerifyKeepAliveRequestSentTimes(Times.Never()); - private void VerifyKeepAliveRequestSent() - { - VerifyKeepAliveRequestSentTimes(Times.Once()); - } + private void VerifyKeepAliveRequestSent() => VerifyKeepAliveRequestSentTimes(Times.Once()); - private void VerifyKeepAliveRequestSentTimes(Times times) - { - _mockHttpMessageHandler.Protected().Verify("SendAsync", + private void VerifyKeepAliveRequestSentTimes(Times times) => _mockHttpMessageHandler.Protected() + .Verify( + "SendAsync", times, - ItExpr.Is(x => x.RequestUri.ToString() == $"{_applicationUrl}/api/keepalive/ping"), + ItExpr.Is(x => x.RequestUri.ToString() == $"{ApplicationUrl}/api/keepalive/ping"), ItExpr.IsAny()); - } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/LogScrubberTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/LogScrubberTests.cs index d30c83c545..564b716f75 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/LogScrubberTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/LogScrubberTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Data; using System.Threading.Tasks; using Microsoft.Extensions.Logging; @@ -21,12 +24,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices { private Mock _mockAuditService; - const int _maxLogAgeInMinutes = 60; + private const int MaxLogAgeInMinutes = 60; [Test] public async Task Does_Not_Execute_When_Server_Role_Is_Replica() { - var sut = CreateLogScrubber(serverRole: ServerRole.Replica); + LogScrubber sut = CreateLogScrubber(serverRole: ServerRole.Replica); await sut.PerformExecuteAsync(null); VerifyLogsNotScrubbed(); } @@ -34,7 +37,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices [Test] public async Task Does_Not_Execute_When_Server_Role_Is_Unknown() { - var sut = CreateLogScrubber(serverRole: ServerRole.Unknown); + LogScrubber sut = CreateLogScrubber(serverRole: ServerRole.Unknown); await sut.PerformExecuteAsync(null); VerifyLogsNotScrubbed(); } @@ -42,7 +45,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices [Test] public async Task Does_Not_Execute_When_Not_Main_Dom() { - var sut = CreateLogScrubber(isMainDom: false); + LogScrubber sut = CreateLogScrubber(isMainDom: false); await sut.PerformExecuteAsync(null); VerifyLogsNotScrubbed(); } @@ -50,7 +53,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices [Test] public async Task Executes_And_Scrubs_Logs() { - var sut = CreateLogScrubber(); + LogScrubber sut = CreateLogScrubber(); await sut.PerformExecuteAsync(null); VerifyLogsScrubbed(); } @@ -61,7 +64,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices { var settings = new LoggingSettings { - MaxLogAge = TimeSpan.FromMinutes(_maxLogAgeInMinutes), + MaxLogAge = TimeSpan.FromMinutes(MaxLogAgeInMinutes), }; var mockServerRegistrar = new Mock(); @@ -80,23 +83,20 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices _mockAuditService = new Mock(); - return new LogScrubber(mockMainDom.Object, mockServerRegistrar.Object, _mockAuditService.Object, - Options.Create(settings), mockScopeProvider.Object, mockLogger.Object, mockProfilingLogger.Object); + return new LogScrubber( + mockMainDom.Object, + mockServerRegistrar.Object, + _mockAuditService.Object, + Options.Create(settings), + mockScopeProvider.Object, + mockLogger.Object, + mockProfilingLogger.Object); } - private void VerifyLogsNotScrubbed() - { - VerifyLogsScrubbed(Times.Never()); - } + private void VerifyLogsNotScrubbed() => VerifyLogsScrubbed(Times.Never()); - private void VerifyLogsScrubbed() - { - VerifyLogsScrubbed(Times.Once()); - } + private void VerifyLogsScrubbed() => VerifyLogsScrubbed(Times.Once()); - private void VerifyLogsScrubbed(Times times) - { - _mockAuditService.Verify(x => x.CleanLogs(It.Is(y => y == _maxLogAgeInMinutes)), times); - } + private void VerifyLogsScrubbed(Times times) => _mockAuditService.Verify(x => x.CleanLogs(It.Is(y => y == MaxLogAgeInMinutes)), times); } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/ScheduledPublishingTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/ScheduledPublishingTests.cs index a7d6925d18..fa3a609ce6 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/ScheduledPublishingTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/ScheduledPublishingTests.cs @@ -1,15 +1,16 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Moq; using NUnit.Framework; using Umbraco.Core; -using Umbraco.Core.Logging; using Umbraco.Core.Services; using Umbraco.Core.Sync; using Umbraco.Infrastructure.HostedServices; using Umbraco.Web; -using LogLevel = Microsoft.Extensions.Logging.LogLevel; namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices { @@ -22,7 +23,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices [Test] public async Task Does_Not_Execute_When_Not_Enabled() { - var sut = CreateScheduledPublishing(enabled: false); + ScheduledPublishing sut = CreateScheduledPublishing(enabled: false); await sut.PerformExecuteAsync(null); VerifyScheduledPublishingNotPerformed(); } @@ -34,7 +35,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices [TestCase(RuntimeLevel.BootFailed)] public async Task Does_Not_Execute_When_Runtime_State_Is_Not_Run(RuntimeLevel runtimeLevel) { - var sut = CreateScheduledPublishing(runtimeLevel: runtimeLevel); + ScheduledPublishing sut = CreateScheduledPublishing(runtimeLevel: runtimeLevel); await sut.PerformExecuteAsync(null); VerifyScheduledPublishingNotPerformed(); } @@ -42,7 +43,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices [Test] public async Task Does_Not_Execute_When_Server_Role_Is_Replica() { - var sut = CreateScheduledPublishing(serverRole: ServerRole.Replica); + ScheduledPublishing sut = CreateScheduledPublishing(serverRole: ServerRole.Replica); await sut.PerformExecuteAsync(null); VerifyScheduledPublishingNotPerformed(); } @@ -50,7 +51,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices [Test] public async Task Does_Not_Execute_When_Server_Role_Is_Unknown() { - var sut = CreateScheduledPublishing(serverRole: ServerRole.Unknown); + ScheduledPublishing sut = CreateScheduledPublishing(serverRole: ServerRole.Unknown); await sut.PerformExecuteAsync(null); VerifyScheduledPublishingNotPerformed(); } @@ -58,7 +59,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices [Test] public async Task Does_Not_Execute_When_Not_Main_Dom() { - var sut = CreateScheduledPublishing(isMainDom: false); + ScheduledPublishing sut = CreateScheduledPublishing(isMainDom: false); await sut.PerformExecuteAsync(null); VerifyScheduledPublishingNotPerformed(); } @@ -66,7 +67,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices [Test] public async Task Executes_And_Performs_Scheduled_Publishing() { - var sut = CreateScheduledPublishing(); + ScheduledPublishing sut = CreateScheduledPublishing(); await sut.PerformExecuteAsync(null); VerifyScheduledPublishingPerformed(); } @@ -106,23 +107,21 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices var mockBackOfficeSecurityFactory = new Mock(); - return new ScheduledPublishing(mockRunTimeState.Object, mockMainDom.Object, mockServerRegistrar.Object, _mockContentService.Object, - mockUmbracoContextFactory.Object, _mockLogger.Object, mockServerMessenger.Object, mockBackOfficeSecurityFactory.Object); + return new ScheduledPublishing( + mockRunTimeState.Object, + mockMainDom.Object, + mockServerRegistrar.Object, + _mockContentService.Object, + mockUmbracoContextFactory.Object, + _mockLogger.Object, + mockServerMessenger.Object, + mockBackOfficeSecurityFactory.Object); } - private void VerifyScheduledPublishingNotPerformed() - { - VerifyScheduledPublishingPerformed(Times.Never()); - } + private void VerifyScheduledPublishingNotPerformed() => VerifyScheduledPublishingPerformed(Times.Never()); - private void VerifyScheduledPublishingPerformed() - { - VerifyScheduledPublishingPerformed(Times.Once()); - } + private void VerifyScheduledPublishingPerformed() => VerifyScheduledPublishingPerformed(Times.Once()); - private void VerifyScheduledPublishingPerformed(Times times) - { - _mockContentService.Verify(x => x.PerformScheduledPublish(It.IsAny()), times); - } + private void VerifyScheduledPublishingPerformed(Times times) => _mockContentService.Verify(x => x.PerformScheduledPublish(It.IsAny()), times); } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/ServerRegistration/InstructionProcessTaskTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/ServerRegistration/InstructionProcessTaskTests.cs index 86644afc77..6ea56792e2 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/ServerRegistration/InstructionProcessTaskTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/ServerRegistration/InstructionProcessTaskTests.cs @@ -1,4 +1,7 @@ -using System.Threading.Tasks; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Moq; @@ -22,7 +25,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices.ServerRe [TestCase(RuntimeLevel.BootFailed)] public async Task Does_Not_Execute_When_Runtime_State_Is_Not_Run(RuntimeLevel runtimeLevel) { - var sut = CreateInstructionProcessTask(runtimeLevel: runtimeLevel); + InstructionProcessTask sut = CreateInstructionProcessTask(runtimeLevel: runtimeLevel); await sut.PerformExecuteAsync(null); VerifyMessengerNotSynced(); } @@ -30,7 +33,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices.ServerRe [Test] public async Task Executes_And_Touches_Server() { - var sut = CreateInstructionProcessTask(); + InstructionProcessTask sut = CreateInstructionProcessTask(); await sut.PerformExecuteAsync(null); VerifyMessengerSynced(); } @@ -49,19 +52,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices.ServerRe return new InstructionProcessTask(mockRunTimeState.Object, _mockDatabaseServerMessenger.Object, mockLogger.Object, Options.Create(settings)); } - private void VerifyMessengerNotSynced() - { - VerifyMessengerSyncedTimes(Times.Never()); - } + private void VerifyMessengerNotSynced() => VerifyMessengerSyncedTimes(Times.Never()); - private void VerifyMessengerSynced() - { - VerifyMessengerSyncedTimes(Times.Once()); - } + private void VerifyMessengerSynced() => VerifyMessengerSyncedTimes(Times.Once()); - private void VerifyMessengerSyncedTimes(Times times) - { - _mockDatabaseServerMessenger.Verify(x => x.Sync(), times); - } + private void VerifyMessengerSyncedTimes(Times times) => _mockDatabaseServerMessenger.Verify(x => x.Sync(), times); } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTaskTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTaskTests.cs index 499ff05f04..7f58f39346 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTaskTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTaskTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -17,8 +20,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices.ServerRe { private Mock _mockServerRegistrationService; - private const string _applicationUrl = "https://mysite.com/"; - private const string _serverIdentity = "Test/1"; + private const string ApplicationUrl = "https://mysite.com/"; + private const string ServerIdentity = "Test/1"; private readonly TimeSpan _staleServerTimeout = TimeSpan.FromMinutes(2); [TestCase(RuntimeLevel.Boot)] @@ -28,7 +31,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices.ServerRe [TestCase(RuntimeLevel.BootFailed)] public async Task Does_Not_Execute_When_Runtime_State_Is_Not_Run(RuntimeLevel runtimeLevel) { - var sut = CreateTouchServerTask(runtimeLevel: runtimeLevel); + TouchServerTask sut = CreateTouchServerTask(runtimeLevel: runtimeLevel); await sut.PerformExecuteAsync(null); VerifyServerNotTouched(); } @@ -36,7 +39,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices.ServerRe [Test] public async Task Does_Not_Execute_When_Application_Url_Is_Not_Available() { - var sut = CreateTouchServerTask(applicationUrl: string.Empty); + TouchServerTask sut = CreateTouchServerTask(applicationUrl: string.Empty); await sut.PerformExecuteAsync(null); VerifyServerNotTouched(); } @@ -44,15 +47,15 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices.ServerRe [Test] public async Task Executes_And_Touches_Server() { - var sut = CreateTouchServerTask(); + TouchServerTask sut = CreateTouchServerTask(); await sut.PerformExecuteAsync(null); VerifyServerTouched(); } - private TouchServerTask CreateTouchServerTask(RuntimeLevel runtimeLevel = RuntimeLevel.Run, string applicationUrl = _applicationUrl) + private TouchServerTask CreateTouchServerTask(RuntimeLevel runtimeLevel = RuntimeLevel.Run, string applicationUrl = ApplicationUrl) { var mockRequestAccessor = new Mock(); - mockRequestAccessor.Setup(x => x.GetApplicationUrl()).Returns(!string.IsNullOrEmpty(applicationUrl) ? new Uri(_applicationUrl) : null); + mockRequestAccessor.Setup(x => x.GetApplicationUrl()).Returns(!string.IsNullOrEmpty(applicationUrl) ? new Uri(ApplicationUrl) : null); var mockRunTimeState = new Mock(); mockRunTimeState.SetupGet(x => x.Level).Returns(runtimeLevel); @@ -60,7 +63,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices.ServerRe var mockLogger = new Mock>(); _mockServerRegistrationService = new Mock(); - _mockServerRegistrationService.SetupGet(x => x.CurrentServerIdentity).Returns(_serverIdentity); + _mockServerRegistrationService.SetupGet(x => x.CurrentServerIdentity).Returns(ServerIdentity); var settings = new GlobalSettings { @@ -70,28 +73,24 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices.ServerRe } }; - return new TouchServerTask(mockRunTimeState.Object, _mockServerRegistrationService.Object, mockRequestAccessor.Object, - mockLogger.Object, Options.Create(settings)); + return new TouchServerTask( + mockRunTimeState.Object, + _mockServerRegistrationService.Object, + mockRequestAccessor.Object, + mockLogger.Object, + Options.Create(settings)); } - private void VerifyServerNotTouched() - { - VerifyServerTouchedTimes(Times.Never()); - } + private void VerifyServerNotTouched() => VerifyServerTouchedTimes(Times.Never()); - private void VerifyServerTouched() - { - VerifyServerTouchedTimes(Times.Once()); - } + private void VerifyServerTouched() => VerifyServerTouchedTimes(Times.Once()); - private void VerifyServerTouchedTimes(Times times) - { - _mockServerRegistrationService - .Verify(x => x.TouchServer( - It.Is(y => y == _applicationUrl), - It.Is(y => y == _serverIdentity), - It.Is(y => y == _staleServerTimeout)), - times); - } + private void VerifyServerTouchedTimes(Times times) => _mockServerRegistrationService + .Verify( + x => x.TouchServer( + It.Is(y => y == ApplicationUrl), + It.Is(y => y == ServerIdentity), + It.Is(y => y == _staleServerTimeout)), + times); } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/TempFileCleanupTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/TempFileCleanupTests.cs index 98666ece2f..6024d50ce3 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/TempFileCleanupTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/TempFileCleanupTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.IO; using System.Threading.Tasks; using Microsoft.Extensions.Logging; @@ -15,12 +18,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices public class TempFileCleanupTests { private Mock _mockIOHelper; - private string _testPath = Path.Combine(TestContext.CurrentContext.TestDirectory.Split("bin")[0], "App_Data", "TEMP"); + private readonly string _testPath = Path.Combine(TestContext.CurrentContext.TestDirectory.Split("bin")[0], "App_Data", "TEMP"); [Test] public async Task Does_Not_Execute_When_Not_Main_Dom() { - var sut = CreateTempFileCleanup(isMainDom: false); + TempFileCleanup sut = CreateTempFileCleanup(isMainDom: false); await sut.PerformExecuteAsync(null); VerifyFilesNotCleaned(); } @@ -28,7 +31,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices [Test] public async Task Executes_And_Cleans_Files() { - var sut = CreateTempFileCleanup(); + TempFileCleanup sut = CreateTempFileCleanup(); await sut.PerformExecuteAsync(null); VerifyFilesCleaned(); } @@ -50,19 +53,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices return new TempFileCleanup(_mockIOHelper.Object, mockMainDom.Object, mockLogger.Object); } - private void VerifyFilesNotCleaned() - { - VerifyFilesCleaned(Times.Never()); - } + private void VerifyFilesNotCleaned() => VerifyFilesCleaned(Times.Never()); - private void VerifyFilesCleaned() - { - VerifyFilesCleaned(Times.Once()); - } + private void VerifyFilesCleaned() => VerifyFilesCleaned(Times.Once()); - private void VerifyFilesCleaned(Times times) - { - _mockIOHelper.Verify(x => x.CleanFolder(It.Is(y => y.FullName == _testPath), It.IsAny()), times); - } + private void VerifyFilesCleaned(Times times) => _mockIOHelper.Verify(x => x.CleanFolder(It.Is(y => y.FullName == _testPath), It.IsAny()), times); } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/AdminUsersHandlerTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/AdminUsersHandlerTests.cs index 059508d561..c05373f5fb 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/AdminUsersHandlerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/AdminUsersHandlerTests.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; @@ -32,8 +35,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Missing_QueryString_Value_Is_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var sut = CreateHandler(queryStringName: "xxx"); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + AdminUsersHandler sut = CreateHandler(queryStringName: "xxx"); await sut.HandleAsync(authHandlerContext); @@ -43,8 +46,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Non_Integer_QueryString_Value_Is_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var sut = CreateHandler(queryStringValue: "xxx"); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + AdminUsersHandler sut = CreateHandler(queryStringValue: "xxx"); await sut.HandleAsync(authHandlerContext); @@ -54,8 +57,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Editing_Single_Admin_User_By_Admin_User_Is_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var sut = CreateHandler(queryStringValue: Admin2UserId.ToString(), editingWithAdmin: true); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + AdminUsersHandler sut = CreateHandler(queryStringValue: Admin2UserId.ToString(), editingWithAdmin: true); await sut.HandleAsync(authHandlerContext); @@ -65,8 +68,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Editing_Single_Admin_User_By_Non_Admin_User_Is_Not_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var sut = CreateHandler(queryStringValue: Admin2UserId.ToString()); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + AdminUsersHandler sut = CreateHandler(queryStringValue: Admin2UserId.ToString()); await sut.HandleAsync(authHandlerContext); @@ -76,8 +79,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Editing_Single_Non_Admin_User_By_Non_Admin_User_Is_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var sut = CreateHandler(queryStringValue: NonAdmin2UserId.ToString()); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + AdminUsersHandler sut = CreateHandler(queryStringValue: NonAdmin2UserId.ToString()); await sut.HandleAsync(authHandlerContext); @@ -87,8 +90,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Editing_Multiple_Users_Including_Admins_By_Admin_User_Is_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(queryStringName: MultipleUserEditQueryStringName); - var sut = CreateHandler(queryStringName: MultipleUserEditQueryStringName, queryStringValue: $"{Admin2UserId},{NonAdmin2UserId}", editingWithAdmin: true); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(queryStringName: MultipleUserEditQueryStringName); + AdminUsersHandler sut = CreateHandler(queryStringName: MultipleUserEditQueryStringName, queryStringValue: $"{Admin2UserId},{NonAdmin2UserId}", editingWithAdmin: true); await sut.HandleAsync(authHandlerContext); @@ -98,8 +101,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Editing_Multiple_Users_Including_Admins_By_Non_Admin_User_Is_Not_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(queryStringName: MultipleUserEditQueryStringName); - var sut = CreateHandler(queryStringName: MultipleUserEditQueryStringName, queryStringValue: $"{Admin2UserId},{NonAdmin2UserId}"); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(queryStringName: MultipleUserEditQueryStringName); + AdminUsersHandler sut = CreateHandler(queryStringName: MultipleUserEditQueryStringName, queryStringValue: $"{Admin2UserId},{NonAdmin2UserId}"); await sut.HandleAsync(authHandlerContext); @@ -109,8 +112,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Editing_Multiple_Users_Not_Including_Admins_By_Non_Admin_User_Is_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(queryStringName: MultipleUserEditQueryStringName); - var sut = CreateHandler(queryStringName: MultipleUserEditQueryStringName, queryStringValue: $"{NonAdmin2UserId},{NonAdmin3UserId}"); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(queryStringName: MultipleUserEditQueryStringName); + AdminUsersHandler sut = CreateHandler(queryStringName: MultipleUserEditQueryStringName, queryStringValue: $"{NonAdmin2UserId},{NonAdmin3UserId}"); await sut.HandleAsync(authHandlerContext); @@ -121,15 +124,15 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization { var requirement = new AdminUsersRequirement(queryStringName); var user = new ClaimsPrincipal(new ClaimsIdentity(new List())); - var resource = new object(); + object resource = new object(); return new AuthorizationHandlerContext(new List { requirement }, user, resource); } private AdminUsersHandler CreateHandler(string queryStringName = SingleUserEditQueryStringName, string queryStringValue = "", bool editingWithAdmin = false) { - var mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringName, queryStringValue); - CreateMockUserServiceAndSecurityAccessor(editingWithAdmin, out var mockUserService, out var mockBackOfficeSecurityAccessor); - var userEditorAuthorizationHelper = CreateUserEditorAuthorizationHelper(); + Mock mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringName, queryStringValue); + CreateMockUserServiceAndSecurityAccessor(editingWithAdmin, out Mock mockUserService, out Mock mockBackOfficeSecurityAccessor); + UserEditorAuthorizationHelper userEditorAuthorizationHelper = CreateUserEditorAuthorizationHelper(); return new AdminUsersHandler(mockHttpContextAccessor.Object, mockUserService.Object, mockBackOfficeSecurityAccessor.Object, userEditorAuthorizationHelper); } @@ -152,11 +155,11 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization { mockUserService = new Mock(); var globalSettings = new GlobalSettings(); - var adminUser1 = CreateUser(Admin1UserId, mockUserService, true); - var adminUser2 = CreateUser(Admin2UserId, mockUserService, true); - var nonAdminUser1 = CreateUser(NonAdmin1UserId, mockUserService); - var nonAdminUser2 = CreateUser(NonAdmin2UserId, mockUserService); - var nonAdminUser3 = CreateUser(NonAdmin3UserId, mockUserService); + User adminUser1 = CreateUser(Admin1UserId, mockUserService, true); + User adminUser2 = CreateUser(Admin2UserId, mockUserService, true); + User nonAdminUser1 = CreateUser(NonAdmin1UserId, mockUserService); + User nonAdminUser2 = CreateUser(NonAdmin2UserId, mockUserService); + User nonAdminUser3 = CreateUser(NonAdmin3UserId, mockUserService); // Single user requests have been setup in the create user operations, but // we also need to mock the responses when multiple users are being editing. @@ -175,7 +178,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization private static User CreateUser(int id, Mock mockUserService, bool isAdmin = false) { - var user = new UserBuilder() + User user = new UserBuilder() .WithId(id) .AddUserGroup() .WithAlias(isAdmin ? Constants.Security.AdminGroupAlias : Constants.Security.EditorGroupAlias) diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/BackOfficeHandlerTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/BackOfficeHandlerTests.cs index c959de86de..5cb59a7002 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/BackOfficeHandlerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/BackOfficeHandlerTests.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System.Collections.Generic; using System.Security.Claims; using System.Threading.Tasks; @@ -17,8 +20,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Runtime_State_Install_Is_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var sut = CreateHandler(runtimeLevel: RuntimeLevel.Install); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + BackOfficeHandler sut = CreateHandler(runtimeLevel: RuntimeLevel.Install); await sut.HandleAsync(authHandlerContext); @@ -28,8 +31,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Runtime_State_Upgrade_Is_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var sut = CreateHandler(runtimeLevel: RuntimeLevel.Upgrade); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + BackOfficeHandler sut = CreateHandler(runtimeLevel: RuntimeLevel.Upgrade); await sut.HandleAsync(authHandlerContext); @@ -39,8 +42,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Unauthenticated_User_Is_Not_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var sut = CreateHandler(); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + BackOfficeHandler sut = CreateHandler(); await sut.HandleAsync(authHandlerContext); @@ -50,8 +53,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Authenticated_User_Is_Not_Authorized_When_Not_Approved_And_Approval_Required() { - var authHandlerContext = CreateAuthorizationHandlerContext(requireApproval: true); - var sut = CreateHandler(currentUserIsAuthenticated: true); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(requireApproval: true); + BackOfficeHandler sut = CreateHandler(currentUserIsAuthenticated: true); await sut.HandleAsync(authHandlerContext); @@ -61,8 +64,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Authenticated_User_Is_Authorized_When_Not_Approved_And_Approval_Not_Required() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var sut = CreateHandler(currentUserIsAuthenticated: true); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + BackOfficeHandler sut = CreateHandler(currentUserIsAuthenticated: true); await sut.HandleAsync(authHandlerContext); @@ -72,8 +75,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Authenticated_User_Is_Authorized_When_Approved_And_Approval_Required() { - var authHandlerContext = CreateAuthorizationHandlerContext(requireApproval: true); - var sut = CreateHandler(currentUserIsAuthenticated: true, currentUserIsApproved: true); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(requireApproval: true); + BackOfficeHandler sut = CreateHandler(currentUserIsAuthenticated: true, currentUserIsApproved: true); await sut.HandleAsync(authHandlerContext); @@ -84,20 +87,20 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization { var requirement = new BackOfficeRequirement(requireApproval); var user = new ClaimsPrincipal(new ClaimsIdentity(new List())); - var resource = new object(); + object resource = new object(); return new AuthorizationHandlerContext(new List { requirement }, user, resource); } private BackOfficeHandler CreateHandler(RuntimeLevel runtimeLevel = RuntimeLevel.Run, bool currentUserIsAuthenticated = false, bool currentUserIsApproved = false) { - var mockBackOfficeSecurityAccessor = CreateMockBackOfficeSecurityAccessor(currentUserIsAuthenticated, currentUserIsApproved); - var mockRuntimeState = CreateMockRuntimeState(runtimeLevel); + Mock mockBackOfficeSecurityAccessor = CreateMockBackOfficeSecurityAccessor(currentUserIsAuthenticated, currentUserIsApproved); + Mock mockRuntimeState = CreateMockRuntimeState(runtimeLevel); return new BackOfficeHandler(mockBackOfficeSecurityAccessor.Object, mockRuntimeState.Object); } private static Mock CreateMockBackOfficeSecurityAccessor(bool currentUserIsAuthenticated, bool currentUserIsApproved) { - var user = new UserBuilder() + global::Umbraco.Core.Models.Membership.User user = new UserBuilder() .WithIsApproved(currentUserIsApproved) .Build(); var mockBackOfficeSecurityAccessor = new Mock(); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/ContentPermissionsPublishBranchHandlerTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/ContentPermissionsPublishBranchHandlerTests.cs index ca73f234b9..ce3a960b6f 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/ContentPermissionsPublishBranchHandlerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/ContentPermissionsPublishBranchHandlerTests.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; @@ -25,13 +28,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task User_With_Access_To_All_Descendent_Nodes_Is_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var mockUserService = CreateMockUserService(NodeId, new Dictionary + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + Mock mockUserService = CreateMockUserService(NodeId, new Dictionary { { DescendentNodeId1, new string[] { "A" } }, { DescendentNodeId2, new string[] { "A" } } }); - var sut = CreateHandler(mockUserService.Object, NodeId); + ContentPermissionsPublishBranchHandler sut = CreateHandler(mockUserService.Object, NodeId); await sut.HandleAsync(authHandlerContext); @@ -42,13 +45,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task User_Without_Access_To_One_Descendent_Node_Is_Not_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var mockUserService = CreateMockUserService(NodeId, new Dictionary + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + Mock mockUserService = CreateMockUserService(NodeId, new Dictionary { { DescendentNodeId1, new string[] { "A" } }, { DescendentNodeId2, new string[] { "B" } } }); - var sut = CreateHandler(mockUserService.Object, NodeId); + ContentPermissionsPublishBranchHandler sut = CreateHandler(mockUserService.Object, NodeId); await sut.HandleAsync(authHandlerContext); @@ -59,13 +62,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task User_Without_Access_To_First_Descendent_Node_Is_Not_Authorized_And_Checks_Exit_Early() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var mockUserService = CreateMockUserService(NodeId, new Dictionary + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + Mock mockUserService = CreateMockUserService(NodeId, new Dictionary { { DescendentNodeId1, new string[] { "B" } }, { DescendentNodeId2, new string[] { "A" } } }); - var sut = CreateHandler(mockUserService.Object, NodeId); + ContentPermissionsPublishBranchHandler sut = CreateHandler(mockUserService.Object, NodeId); await sut.HandleAsync(authHandlerContext); @@ -77,7 +80,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization { var requirement = new ContentPermissionsPublishBranchRequirement('A'); var user = new ClaimsPrincipal(new ClaimsIdentity(new List())); - var resource = CreateContent(NodeId); + IContent resource = CreateContent(NodeId); return new AuthorizationHandlerContext(new List { requirement }, user, resource); } @@ -97,9 +100,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization private ContentPermissionsPublishBranchHandler CreateHandler(IUserService userService, int nodeId) { - var mockEntityService = CreateMockEntityService(); - var contentPermissions = CreateContentPermissions(mockEntityService.Object, userService, nodeId); - var mockBackOfficeSecurityAccessor = CreateMockBackOfficeSecurityAccessor(); + Mock mockEntityService = CreateMockEntityService(); + ContentPermissions contentPermissions = CreateContentPermissions(mockEntityService.Object, userService, nodeId); + Mock mockBackOfficeSecurityAccessor = CreateMockBackOfficeSecurityAccessor(); return new ContentPermissionsPublishBranchHandler(mockEntityService.Object, contentPermissions, mockBackOfficeSecurityAccessor.Object); } @@ -129,13 +132,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization private static IContent CreateContent(int nodeId) { - var contentType = ContentTypeBuilder.CreateBasicContentType(); + ContentType contentType = ContentTypeBuilder.CreateBasicContentType(); return ContentBuilder.CreateBasicContent(contentType, nodeId); } private static Mock CreateMockBackOfficeSecurityAccessor() { - var user = CreateUser(); + User user = CreateUser(); var mockBackOfficeSecurity = new Mock(); mockBackOfficeSecurity.SetupGet(x => x.CurrentUser).Returns(user); var mockBackOfficeSecurityAccessor = new Mock(); @@ -143,10 +146,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization return mockBackOfficeSecurityAccessor; } - private static User CreateUser() - { - return new UserBuilder() + private static User CreateUser() => + new UserBuilder() .Build(); - } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/ContentPermissionsQueryStringHandlerTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/ContentPermissionsQueryStringHandlerTests.cs index 10549a6d7d..6f1a4b9e73 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/ContentPermissionsQueryStringHandlerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/ContentPermissionsQueryStringHandlerTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Collections.Generic; using System.Security.Claims; using System.Threading.Tasks; @@ -21,15 +24,15 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization { private const string QueryStringName = "id"; private const int NodeId = 1000; - private static readonly Guid NodeGuid = Guid.NewGuid(); - private static readonly Udi NodeUdi = UdiParser.Parse($"umb://document/{NodeGuid.ToString().ToLowerInvariant().Replace("-", string.Empty)}"); + private static readonly Guid s_nodeGuid = Guid.NewGuid(); + private static readonly Udi s_nodeUdi = UdiParser.Parse($"umb://document/{s_nodeGuid.ToString().ToLowerInvariant().Replace("-", string.Empty)}"); [Test] public async Task Node_Id_From_Requirement_With_Permission_Is_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(NodeId); - var mockHttpContextAccessor = CreateMockHttpContextAccessor(); - var sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, new string[] { "A" }); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(NodeId); + Mock mockHttpContextAccessor = CreateMockHttpContextAccessor(); + ContentPermissionsQueryStringHandler sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, new string[] { "A" }); await sut.HandleAsync(authHandlerContext); @@ -39,9 +42,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Node_Id_From_Requirement_Without_Permission_Is_Not_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(NodeId); - var mockHttpContextAccessor = CreateMockHttpContextAccessor(); - var sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, new string[] { "B" }); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(NodeId); + Mock mockHttpContextAccessor = CreateMockHttpContextAccessor(); + ContentPermissionsQueryStringHandler sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, new string[] { "B" }); await sut.HandleAsync(authHandlerContext); @@ -52,9 +55,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Node_Id_Missing_From_Requirement_And_QueryString_Is_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringName: "xxx"); - var sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, new string[] { "A" }); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + Mock mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringName: "xxx"); + ContentPermissionsQueryStringHandler sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, new string[] { "A" }); await sut.HandleAsync(authHandlerContext); @@ -64,9 +67,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Node_Integer_Id_From_QueryString_With_Permission_Is_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: NodeId.ToString()); - var sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, new string[] { "A" }); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + Mock mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: NodeId.ToString()); + ContentPermissionsQueryStringHandler sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, new string[] { "A" }); await sut.HandleAsync(authHandlerContext); @@ -77,9 +80,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Node_Integer_Id_From_QueryString_Without_Permission_Is_Not_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: NodeId.ToString()); - var sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, new string[] { "B" }); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + Mock mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: NodeId.ToString()); + ContentPermissionsQueryStringHandler sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, new string[] { "B" }); await sut.HandleAsync(authHandlerContext); @@ -90,9 +93,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Node_Udi_Id_From_QueryString_With_Permission_Is_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: NodeUdi.ToString()); - var sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, new string[] { "A" }); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + Mock mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: s_nodeUdi.ToString()); + ContentPermissionsQueryStringHandler sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, new string[] { "A" }); await sut.HandleAsync(authHandlerContext); @@ -103,9 +106,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Node_Udi_Id_From_QueryString_Without_Permission_Is_Not_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: NodeUdi.ToString()); - var sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, new string[] { "B" }); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + Mock mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: s_nodeUdi.ToString()); + ContentPermissionsQueryStringHandler sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, new string[] { "B" }); await sut.HandleAsync(authHandlerContext); @@ -116,9 +119,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Node_Guid_Id_From_QueryString_With_Permission_Is_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: NodeGuid.ToString()); - var sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, new string[] { "A" }); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + Mock mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: s_nodeGuid.ToString()); + ContentPermissionsQueryStringHandler sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, new string[] { "A" }); await sut.HandleAsync(authHandlerContext); @@ -129,9 +132,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Node_Guid_Id_From_QueryString_Without_Permission_Is_Not_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: NodeGuid.ToString()); - var sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, new string[] { "B" }); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + Mock mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: s_nodeGuid.ToString()); + ContentPermissionsQueryStringHandler sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, new string[] { "B" }); await sut.HandleAsync(authHandlerContext); @@ -142,9 +145,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Node_Invalid_Id_From_QueryString_Is_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: "invalid"); - var sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, new string[] { "A" }); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + Mock mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: "invalid"); + ContentPermissionsQueryStringHandler sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, new string[] { "A" }); await sut.HandleAsync(authHandlerContext); @@ -154,11 +157,11 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization private static AuthorizationHandlerContext CreateAuthorizationHandlerContext(int? nodeId = null) { const char Permission = 'A'; - var requirement = nodeId.HasValue + ContentPermissionsQueryStringRequirement requirement = nodeId.HasValue ? new ContentPermissionsQueryStringRequirement(nodeId.Value, Permission) : new ContentPermissionsQueryStringRequirement(Permission, QueryStringName); var user = new ClaimsPrincipal(new ClaimsIdentity(new List())); - var resource = new object(); + object resource = new object(); return new AuthorizationHandlerContext(new List { requirement }, user, resource); } @@ -180,9 +183,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization private ContentPermissionsQueryStringHandler CreateHandler(IHttpContextAccessor httpContextAccessor, int nodeId, string[] permissionsForPath) { - var mockBackOfficeSecurityAccessor = CreateMockBackOfficeSecurityAccessor(); - var mockEntityService = CreateMockEntityService(); - var contentPermissions = CreateContentPermissions(mockEntityService.Object, nodeId, permissionsForPath); + Mock mockBackOfficeSecurityAccessor = CreateMockBackOfficeSecurityAccessor(); + Mock mockEntityService = CreateMockEntityService(); + ContentPermissions contentPermissions = CreateContentPermissions(mockEntityService.Object, nodeId, permissionsForPath); return new ContentPermissionsQueryStringHandler(mockBackOfficeSecurityAccessor.Object, httpContextAccessor, mockEntityService.Object, contentPermissions); } @@ -190,17 +193,17 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization { var mockEntityService = new Mock(); mockEntityService - .Setup(x => x.GetId(It.Is(y => y == NodeUdi))) + .Setup(x => x.GetId(It.Is(y => y == s_nodeUdi))) .Returns(Attempt.Succeed(NodeId)); mockEntityService - .Setup(x => x.GetId(It.Is(y => y == NodeGuid), It.Is(y => y == UmbracoObjectTypes.Document))) + .Setup(x => x.GetId(It.Is(y => y == s_nodeGuid), It.Is(y => y == UmbracoObjectTypes.Document))) .Returns(Attempt.Succeed(NodeId)); return mockEntityService; } private static Mock CreateMockBackOfficeSecurityAccessor() { - var user = CreateUser(); + User user = CreateUser(); var mockBackOfficeSecurity = new Mock(); mockBackOfficeSecurity.SetupGet(x => x.CurrentUser).Returns(user); var mockBackOfficeSecurityAccessor = new Mock(); @@ -208,11 +211,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization return mockBackOfficeSecurityAccessor; } - private static User CreateUser() - { - return new UserBuilder() + private static User CreateUser() => + new UserBuilder() .Build(); - } private static ContentPermissions CreateContentPermissions(IEntityService entityService, int nodeId, string[] permissionsForPath) { @@ -232,13 +233,11 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization private static IContent CreateContent(int nodeId) { - var contentType = ContentTypeBuilder.CreateBasicContentType(); + ContentType contentType = ContentTypeBuilder.CreateBasicContentType(); return ContentBuilder.CreateBasicContent(contentType, nodeId); } - private static void AssertContentCached(Mock mockHttpContextAccessor) - { + private static void AssertContentCached(Mock mockHttpContextAccessor) => Assert.AreEqual(NodeId, ((IContent)mockHttpContextAccessor.Object.HttpContext.Items[typeof(IContent).ToString()]).Id); - } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/ContentPermissionsResourceHandlerTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/ContentPermissionsResourceHandlerTests.cs index ea6dd7f0bd..1ac74d99ba 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/ContentPermissionsResourceHandlerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/ContentPermissionsResourceHandlerTests.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; @@ -21,8 +24,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Resource_With_Node_Id_With_Permission_Is_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(NodeId, createWithNodeId: true); - var sut = CreateHandler(NodeId, new string[] { "A" }); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(NodeId, createWithNodeId: true); + ContentPermissionsResourceHandler sut = CreateHandler(NodeId, new string[] { "A" }); await sut.HandleAsync(authHandlerContext); @@ -32,8 +35,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Resource_With_Content_With_Permission_Is_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(NodeId); - var sut = CreateHandler(NodeId, new string[] { "A" }); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(NodeId); + ContentPermissionsResourceHandler sut = CreateHandler(NodeId, new string[] { "A" }); await sut.HandleAsync(authHandlerContext); @@ -43,8 +46,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Resource_With_Node_Id_Withou_Permission_Is_Not_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(NodeId, createWithNodeId: true); - var sut = CreateHandler(NodeId, new string[] { "B" }); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(NodeId, createWithNodeId: true); + ContentPermissionsResourceHandler sut = CreateHandler(NodeId, new string[] { "B" }); await sut.HandleAsync(authHandlerContext); @@ -54,8 +57,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Resource_With_Content_Without_Permission_Is_Not_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(NodeId); - var sut = CreateHandler(NodeId, new string[] { "B" }); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(NodeId); + ContentPermissionsResourceHandler sut = CreateHandler(NodeId, new string[] { "B" }); await sut.HandleAsync(authHandlerContext); @@ -66,9 +69,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization { var requirement = new ContentPermissionsResourceRequirement(); var user = new ClaimsPrincipal(new ClaimsIdentity(new List())); - var content = CreateContent(nodeId); - var permissions = new List { 'A' }.AsReadOnly(); - var resource = createWithNodeId + IContent content = CreateContent(nodeId); + System.Collections.ObjectModel.ReadOnlyCollection permissions = new List { 'A' }.AsReadOnly(); + ContentPermissionsResource resource = createWithNodeId ? new ContentPermissionsResource(content, nodeId, permissions) : new ContentPermissionsResource(content, permissions); return new AuthorizationHandlerContext(new List { requirement }, user, resource); @@ -76,20 +79,20 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization private static IContent CreateContent(int nodeId) { - var contentType = ContentTypeBuilder.CreateBasicContentType(); + ContentType contentType = ContentTypeBuilder.CreateBasicContentType(); return ContentBuilder.CreateBasicContent(contentType, nodeId); } private ContentPermissionsResourceHandler CreateHandler(int nodeId, string[] permissionsForPath) { - var mockBackOfficeSecurityAccessor = CreateMockBackOfficeSecurityAccessor(); - var contentPermissions = CreateContentPermissions(nodeId, permissionsForPath); + Mock mockBackOfficeSecurityAccessor = CreateMockBackOfficeSecurityAccessor(); + ContentPermissions contentPermissions = CreateContentPermissions(nodeId, permissionsForPath); return new ContentPermissionsResourceHandler(mockBackOfficeSecurityAccessor.Object, contentPermissions); } private static Mock CreateMockBackOfficeSecurityAccessor() { - var user = CreateUser(); + User user = CreateUser(); var mockBackOfficeSecurity = new Mock(); mockBackOfficeSecurity.SetupGet(x => x.CurrentUser).Returns(user); var mockBackOfficeSecurityAccessor = new Mock(); @@ -97,11 +100,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization return mockBackOfficeSecurityAccessor; } - private static User CreateUser() - { - return new UserBuilder() + private static User CreateUser() => + new UserBuilder() .Build(); - } private static ContentPermissions CreateContentPermissions(int nodeId, string[] permissionsForPath) { diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/DenyLocalLoginHandlerTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/DenyLocalLoginHandlerTests.cs index 602843b128..830954aaf6 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/DenyLocalLoginHandlerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/DenyLocalLoginHandlerTests.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; @@ -6,7 +9,6 @@ using Moq; using NUnit.Framework; using Umbraco.Web.BackOffice.Authorization; using Umbraco.Web.BackOffice.Security; -using Umbraco.Web.Common.Security; namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization { @@ -15,8 +17,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task With_Deny_Local_Login_Is_Not_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var sut = CreateHandler(denyLocalLogin: true); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + DenyLocalLoginHandler sut = CreateHandler(denyLocalLogin: true); await sut.HandleAsync(authHandlerContext); @@ -26,8 +28,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Without_Deny_Local_Login_Is_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var sut = CreateHandler(); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + DenyLocalLoginHandler sut = CreateHandler(); await sut.HandleAsync(authHandlerContext); @@ -38,13 +40,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization { var requirement = new DenyLocalLoginRequirement(); var user = new ClaimsPrincipal(new ClaimsIdentity(new List())); - var resource = new object(); + object resource = new object(); return new AuthorizationHandlerContext(new List { requirement }, user, resource); } private DenyLocalLoginHandler CreateHandler(bool denyLocalLogin = false) { - var mockBackOfficeExternalLoginProviders = CreateMockBackOfficeExternalLoginProviders(denyLocalLogin); + Mock mockBackOfficeExternalLoginProviders = CreateMockBackOfficeExternalLoginProviders(denyLocalLogin); return new DenyLocalLoginHandler(mockBackOfficeExternalLoginProviders.Object); } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/MediaPermissionsQueryStringHandlerTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/MediaPermissionsQueryStringHandlerTests.cs index 80e2cddb75..fb2eec93c8 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/MediaPermissionsQueryStringHandlerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/MediaPermissionsQueryStringHandlerTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Collections.Generic; using System.Security.Claims; using System.Threading.Tasks; @@ -21,15 +24,15 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization { private const string QueryStringName = "id"; private const int NodeId = 1000; - private static readonly Guid NodeGuid = Guid.NewGuid(); - private static readonly Udi NodeUdi = UdiParser.Parse($"umb://document/{NodeGuid.ToString().ToLowerInvariant().Replace("-", string.Empty)}"); + private static readonly Guid s_nodeGuid = Guid.NewGuid(); + private static readonly Udi s_nodeUdi = UdiParser.Parse($"umb://document/{s_nodeGuid.ToString().ToLowerInvariant().Replace("-", string.Empty)}"); [Test] public async Task Node_Id_Missing_From_QueryString_Is_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringName: "xxx"); - var sut = CreateHandler(mockHttpContextAccessor.Object, NodeId); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + Mock mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringName: "xxx"); + MediaPermissionsQueryStringHandler sut = CreateHandler(mockHttpContextAccessor.Object, NodeId); await sut.HandleAsync(authHandlerContext); @@ -39,9 +42,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Node_Integer_Id_From_QueryString_With_Permission_Is_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: NodeId.ToString()); - var sut = CreateHandler(mockHttpContextAccessor.Object, NodeId); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + Mock mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: NodeId.ToString()); + MediaPermissionsQueryStringHandler sut = CreateHandler(mockHttpContextAccessor.Object, NodeId); await sut.HandleAsync(authHandlerContext); @@ -52,9 +55,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Node_Integer_Id_From_QueryString_Without_Permission_Is_Not_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: NodeId.ToString()); - var sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, startMediaId: 1001); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + Mock mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: NodeId.ToString()); + MediaPermissionsQueryStringHandler sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, startMediaId: 1001); await sut.HandleAsync(authHandlerContext); @@ -65,9 +68,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Node_Udi_Id_From_QueryString_With_Permission_Is_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: NodeUdi.ToString()); - var sut = CreateHandler(mockHttpContextAccessor.Object, NodeId); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + Mock mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: s_nodeUdi.ToString()); + MediaPermissionsQueryStringHandler sut = CreateHandler(mockHttpContextAccessor.Object, NodeId); await sut.HandleAsync(authHandlerContext); @@ -78,9 +81,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Node_Udi_Id_From_QueryString_Without_Permission_Is_Not_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: NodeUdi.ToString()); - var sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, startMediaId: 1001); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + Mock mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: s_nodeUdi.ToString()); + MediaPermissionsQueryStringHandler sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, startMediaId: 1001); await sut.HandleAsync(authHandlerContext); @@ -91,9 +94,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Node_Guid_Id_From_QueryString_With_Permission_Is_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: NodeGuid.ToString()); - var sut = CreateHandler(mockHttpContextAccessor.Object, NodeId); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + Mock mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: s_nodeGuid.ToString()); + MediaPermissionsQueryStringHandler sut = CreateHandler(mockHttpContextAccessor.Object, NodeId); await sut.HandleAsync(authHandlerContext); @@ -104,9 +107,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Node_Guid_Id_From_QueryString_Without_Permission_Is_Not_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: NodeGuid.ToString()); - var sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, startMediaId: 1001); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + Mock mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: s_nodeGuid.ToString()); + MediaPermissionsQueryStringHandler sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, startMediaId: 1001); await sut.HandleAsync(authHandlerContext); @@ -117,9 +120,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Node_Invalid_Id_From_QueryString_Is_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: "invalid"); - var sut = CreateHandler(mockHttpContextAccessor.Object, NodeId); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + Mock mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: "invalid"); + MediaPermissionsQueryStringHandler sut = CreateHandler(mockHttpContextAccessor.Object, NodeId); await sut.HandleAsync(authHandlerContext); @@ -130,7 +133,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization { var requirement = new MediaPermissionsQueryStringRequirement(QueryStringName); var user = new ClaimsPrincipal(new ClaimsIdentity(new List())); - var resource = new object(); + object resource = new object(); return new AuthorizationHandlerContext(new List { requirement }, user, resource); } @@ -152,9 +155,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization private MediaPermissionsQueryStringHandler CreateHandler(IHttpContextAccessor httpContextAccessor, int nodeId, int startMediaId = -1) { - var mockBackOfficeSecurityAccessor = CreateMockBackOfficeSecurityAccessor(startMediaId); - var mockEntityService = CreateMockEntityService(); - var mediaPermissions = CreateMediaPermissions(mockEntityService.Object, nodeId); + Mock mockBackOfficeSecurityAccessor = CreateMockBackOfficeSecurityAccessor(startMediaId); + Mock mockEntityService = CreateMockEntityService(); + MediaPermissions mediaPermissions = CreateMediaPermissions(mockEntityService.Object, nodeId); return new MediaPermissionsQueryStringHandler(mockBackOfficeSecurityAccessor.Object, httpContextAccessor, mockEntityService.Object, mediaPermissions); } @@ -162,17 +165,17 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization { var mockEntityService = new Mock(); mockEntityService - .Setup(x => x.GetId(It.Is(y => y == NodeUdi))) + .Setup(x => x.GetId(It.Is(y => y == s_nodeUdi))) .Returns(Attempt.Succeed(NodeId)); mockEntityService - .Setup(x => x.GetId(It.Is(y => y == NodeGuid), It.Is(y => y == UmbracoObjectTypes.Document))) + .Setup(x => x.GetId(It.Is(y => y == s_nodeGuid), It.Is(y => y == UmbracoObjectTypes.Document))) .Returns(Attempt.Succeed(NodeId)); return mockEntityService; } private static Mock CreateMockBackOfficeSecurityAccessor(int startMediaId) { - var user = CreateUser(startMediaId); + User user = CreateUser(startMediaId); var mockBackOfficeSecurity = new Mock(); mockBackOfficeSecurity.SetupGet(x => x.CurrentUser).Returns(user); var mockBackOfficeSecurityAccessor = new Mock(); @@ -180,12 +183,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization return mockBackOfficeSecurityAccessor; } - private static User CreateUser(int startMediaId) - { - return new UserBuilder() + private static User CreateUser(int startMediaId) => + new UserBuilder() .WithStartMediaId(startMediaId) .Build(); - } private static MediaPermissions CreateMediaPermissions(IEntityService entityService, int nodeId) { @@ -199,13 +200,11 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization private static IMedia CreateMedia(int nodeId) { - var mediaType = MediaTypeBuilder.CreateSimpleMediaType("image", "Image"); + MediaType mediaType = MediaTypeBuilder.CreateSimpleMediaType("image", "Image"); return MediaBuilder.CreateSimpleMedia(mediaType, "Test image", -1, nodeId); } - private static void AssertMediaCached(Mock mockHttpContextAccessor) - { + private static void AssertMediaCached(Mock mockHttpContextAccessor) => Assert.AreEqual(NodeId, ((IMedia)mockHttpContextAccessor.Object.HttpContext.Items[typeof(IMedia).ToString()]).Id); - } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/MediaPermissionsResourceHandlerTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/MediaPermissionsResourceHandlerTests.cs index 19e2a6f06c..de4a7c0b52 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/MediaPermissionsResourceHandlerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/MediaPermissionsResourceHandlerTests.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; @@ -20,8 +23,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Resource_With_Node_Id_With_Permission_Is_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(NodeId, createWithNodeId: true); - var sut = CreateHandler(NodeId); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(NodeId, createWithNodeId: true); + MediaPermissionsResourceHandler sut = CreateHandler(NodeId); await sut.HandleAsync(authHandlerContext); @@ -31,8 +34,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Resource_With_Media_With_Permission_Is_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(NodeId); - var sut = CreateHandler(NodeId); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(NodeId); + MediaPermissionsResourceHandler sut = CreateHandler(NodeId); await sut.HandleAsync(authHandlerContext); @@ -42,8 +45,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Resource_With_Node_Id_Withou_Permission_Is_Not_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(NodeId, createWithNodeId: true); - var sut = CreateHandler(NodeId, startMediaId: 1001); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(NodeId, createWithNodeId: true); + MediaPermissionsResourceHandler sut = CreateHandler(NodeId, startMediaId: 1001); await sut.HandleAsync(authHandlerContext); @@ -53,8 +56,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Resource_With_Media_Without_Permission_Is_Not_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(NodeId); - var sut = CreateHandler(NodeId, startMediaId: 1001); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(NodeId); + MediaPermissionsResourceHandler sut = CreateHandler(NodeId, startMediaId: 1001); await sut.HandleAsync(authHandlerContext); @@ -65,8 +68,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization { var requirement = new MediaPermissionsResourceRequirement(); var user = new ClaimsPrincipal(new ClaimsIdentity(new List())); - var media = CreateMedia(nodeId); - var resource = createWithNodeId + IMedia media = CreateMedia(nodeId); + MediaPermissionsResource resource = createWithNodeId ? new MediaPermissionsResource(nodeId) : new MediaPermissionsResource(media); return new AuthorizationHandlerContext(new List { requirement }, user, resource); @@ -74,20 +77,20 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization private static IMedia CreateMedia(int nodeId) { - var mediaType = MediaTypeBuilder.CreateSimpleMediaType("image", "Image"); + MediaType mediaType = MediaTypeBuilder.CreateSimpleMediaType("image", "Image"); return MediaBuilder.CreateSimpleMedia(mediaType, "Test image", -1, nodeId); } private MediaPermissionsResourceHandler CreateHandler(int nodeId, int startMediaId = -1) { - var mockBackOfficeSecurityAccessor = CreateMockBackOfficeSecurityAccessor(startMediaId); - var contentPermissions = CreateMediaPermissions(nodeId, new string[0]); + Mock mockBackOfficeSecurityAccessor = CreateMockBackOfficeSecurityAccessor(startMediaId); + MediaPermissions contentPermissions = CreateMediaPermissions(nodeId); return new MediaPermissionsResourceHandler(mockBackOfficeSecurityAccessor.Object, contentPermissions); } private static Mock CreateMockBackOfficeSecurityAccessor(int startMediaId) { - var user = CreateUser(startMediaId); + User user = CreateUser(startMediaId); var mockBackOfficeSecurity = new Mock(); mockBackOfficeSecurity.SetupGet(x => x.CurrentUser).Returns(user); var mockBackOfficeSecurityAccessor = new Mock(); @@ -95,14 +98,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization return mockBackOfficeSecurityAccessor; } - private static User CreateUser(int startMediaId) - { - return new UserBuilder() + private static User CreateUser(int startMediaId) => + new UserBuilder() .WithStartMediaId(startMediaId) .Build(); - } - private static MediaPermissions CreateMediaPermissions(int nodeId, string[] permissionsForPath) + private static MediaPermissions CreateMediaPermissions(int nodeId) { var mockMediaService = new Mock(); mockMediaService diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/SectionHandlerTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/SectionHandlerTests.cs index fb4bb0192e..0435299081 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/SectionHandlerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/SectionHandlerTests.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; @@ -17,8 +20,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Unauthorized_User_Is_Not_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var sut = CreateHandler(); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + SectionHandler sut = CreateHandler(); await sut.HandleAsync(authHandlerContext); @@ -28,8 +31,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task User_With_Section_Access_Is_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var sut = CreateHandler(userIsAuthorized: true, userCanAccessContentSection: true); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + SectionHandler sut = CreateHandler(userIsAuthorized: true, userCanAccessContentSection: true); await sut.HandleAsync(authHandlerContext); @@ -39,8 +42,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task User_Without_Section_Access_Is_Not_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var sut = CreateHandler(userIsAuthorized: true); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + SectionHandler sut = CreateHandler(userIsAuthorized: true); await sut.HandleAsync(authHandlerContext); @@ -51,20 +54,20 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization { var requirement = new SectionRequirement(Constants.Applications.Content, Constants.Applications.Media); var user = new ClaimsPrincipal(new ClaimsIdentity(new List())); - var resource = new object(); + object resource = new object(); return new AuthorizationHandlerContext(new List { requirement }, user, resource); } private SectionHandler CreateHandler(bool userIsAuthorized = false, bool userCanAccessContentSection = false) { - var mockBackOfficeSecurityAccessor = CreateMockBackOfficeSecurityAccessor(userIsAuthorized, userCanAccessContentSection); + Mock mockBackOfficeSecurityAccessor = CreateMockBackOfficeSecurityAccessor(userIsAuthorized, userCanAccessContentSection); return new SectionHandler(mockBackOfficeSecurityAccessor.Object); } - + private static Mock CreateMockBackOfficeSecurityAccessor(bool userIsAuthorized, bool userCanAccessContentSection) { - var user = CreateUser(); + User user = CreateUser(); var mockBackOfficeSecurity = new Mock(); mockBackOfficeSecurity.SetupGet(x => x.CurrentUser).Returns(userIsAuthorized ? user : null); mockBackOfficeSecurity @@ -78,10 +81,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization return mockBackOfficeSecurityAccessor; } - private static User CreateUser() - { - return new UserBuilder() + private static User CreateUser() => + new UserBuilder() .Build(); - } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/TreeHandlerTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/TreeHandlerTests.cs index 8c688d10d3..7d91469379 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/TreeHandlerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/TreeHandlerTests.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; @@ -23,8 +26,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Unauthorized_User_Is_Not_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var sut = CreateHandler(); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + TreeHandler sut = CreateHandler(); await sut.HandleAsync(authHandlerContext); @@ -34,8 +37,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task User_With_Access_To_Tree_Section_Is_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var sut = CreateHandler(userIsAuthorized: true, userCanAccessContentSection: true); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + TreeHandler sut = CreateHandler(userIsAuthorized: true, userCanAccessContentSection: true); await sut.HandleAsync(authHandlerContext); @@ -45,8 +48,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task User_Without_Access_To_Tree_Section_Is_Not_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var sut = CreateHandler(userIsAuthorized: true); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + TreeHandler sut = CreateHandler(userIsAuthorized: true); await sut.HandleAsync(authHandlerContext); @@ -57,14 +60,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization { var requirement = new TreeRequirement(Tree1Alias, Tree2Alias); var user = new ClaimsPrincipal(new ClaimsIdentity(new List())); - var resource = new object(); + object resource = new object(); return new AuthorizationHandlerContext(new List { requirement }, user, resource); } private TreeHandler CreateHandler(bool userIsAuthorized = false, bool userCanAccessContentSection = false) { - var mockTreeService = CreateMockTreeService(); - var mockBackOfficeSecurityAccessor = CreateMockBackOfficeSecurityAccessor(userIsAuthorized, userCanAccessContentSection); + Mock mockTreeService = CreateMockTreeService(); + Mock mockBackOfficeSecurityAccessor = CreateMockBackOfficeSecurityAccessor(userIsAuthorized, userCanAccessContentSection); return new TreeHandler(mockTreeService.Object, mockBackOfficeSecurityAccessor.Object); } @@ -81,17 +84,15 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization return mockTreeService; } - private static Tree CreateTree(string alias, string sectionAlias) - { - return new TreeBuilder() + private static Tree CreateTree(string alias, string sectionAlias) => + new TreeBuilder() .WithAlias(alias) .WithSectionAlias(sectionAlias) .Build(); - } private static Mock CreateMockBackOfficeSecurityAccessor(bool userIsAuthorized, bool userCanAccessContentSection) { - var user = CreateUser(); + User user = CreateUser(); var mockBackOfficeSecurity = new Mock(); mockBackOfficeSecurity.SetupGet(x => x.CurrentUser).Returns(userIsAuthorized ? user : null); mockBackOfficeSecurity @@ -105,10 +106,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization return mockBackOfficeSecurityAccessor; } - private static User CreateUser() - { - return new UserBuilder() + private static User CreateUser() => new UserBuilder() .Build(); - } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/UserGroupHandlerTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/UserGroupHandlerTests.cs index e9416a3982..33f6dca02f 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/UserGroupHandlerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/UserGroupHandlerTests.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; @@ -30,8 +33,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Missing_QueryString_Value_Is_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var sut = CreateHandler(); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + UserGroupHandler sut = CreateHandler(); await sut.HandleAsync(authHandlerContext); @@ -41,8 +44,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Admin_User_Is_Authorised() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var sut = CreateHandler(queryStringValue: Group1Id.ToString(), userIsAdmin: true); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + UserGroupHandler sut = CreateHandler(queryStringValue: Group1Id.ToString(), userIsAdmin: true); await sut.HandleAsync(authHandlerContext); @@ -52,8 +55,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task User_Matching_Single_Requested_Group_Id_Is_Authorised() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var sut = CreateHandler(queryStringValue: Group1Id.ToString()); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + UserGroupHandler sut = CreateHandler(queryStringValue: Group1Id.ToString()); await sut.HandleAsync(authHandlerContext); @@ -63,8 +66,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task User_Matching_One_Of_Requested_Group_Ids_Is_Authorised() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var sut = CreateHandler(queryStringValue: $"{Group1Id},{Group2Id}"); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + UserGroupHandler sut = CreateHandler(queryStringValue: $"{Group1Id},{Group2Id}"); await sut.HandleAsync(authHandlerContext); @@ -74,8 +77,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task User_Not_Matching_Single_Requested_Group_Id_Is_Not_Authorised() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var sut = CreateHandler(queryStringValue: Group2Id.ToString()); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + UserGroupHandler sut = CreateHandler(queryStringValue: Group2Id.ToString()); await sut.HandleAsync(authHandlerContext); @@ -85,8 +88,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task User_Not_Matching_Any_Of_Requested_Group_Ids_Is_Not_Authorised() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var sut = CreateHandler(queryStringValue: $"{Group2Id},{Group3Id}"); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + UserGroupHandler sut = CreateHandler(queryStringValue: $"{Group2Id},{Group3Id}"); await sut.HandleAsync(authHandlerContext); @@ -97,21 +100,21 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization { var requirement = new UserGroupRequirement(QueryStringName); var user = new ClaimsPrincipal(new ClaimsIdentity(new List())); - var resource = new object(); + object resource = new object(); return new AuthorizationHandlerContext(new List { requirement }, user, resource); } private UserGroupHandler CreateHandler(string queryStringValue = "", bool userIsAdmin = false) { - var mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue); + Mock mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue); - var mockUserService = CreateMockUserService(); + Mock mockUserService = CreateMockUserService(); var mockContentService = new Mock(); var mockMediaService = new Mock(); var mockEntityService = new Mock(); - var mockBackOfficeSecurityAccessor = CreateMockBackOfficeSecurityAccessor(userIsAdmin); + Mock mockBackOfficeSecurityAccessor = CreateMockBackOfficeSecurityAccessor(userIsAdmin); return new UserGroupHandler(mockHttpContextAccessor.Object, mockUserService.Object, mockContentService.Object, mockMediaService.Object, mockEntityService.Object, mockBackOfficeSecurityAccessor.Object); } @@ -151,7 +154,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization private static Mock CreateMockBackOfficeSecurityAccessor(bool userIsAdmin) { - var user = CreateUser(userIsAdmin); + User user = CreateUser(userIsAdmin); var mockBackOfficeSecurity = new Mock(); mockBackOfficeSecurity.SetupGet(x => x.CurrentUser).Returns(user); var mockBackOfficeSecurityAccessor = new Mock(); @@ -159,21 +162,17 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization return mockBackOfficeSecurityAccessor; } - private static User CreateUser(bool isAdmin = false) - { - return new UserBuilder() + private static User CreateUser(bool isAdmin = false) => + new UserBuilder() .AddUserGroup() .WithAlias(isAdmin ? Constants.Security.AdminGroupAlias : Group1Alias) .Done() .Build(); - } - private IUserGroup CreateUserGroup(int id, string alias) - { - return new UserGroupBuilder() + private IUserGroup CreateUserGroup(int id, string alias) => + new UserGroupBuilder() .WithId(id) .WithAlias(alias) .Build(); - } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/UsersControllerUnitTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/UsersControllerUnitTests.cs index b04a5ff158..4f4db85e5e 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/UsersControllerUnitTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/UsersControllerUnitTests.cs @@ -1,9 +1,7 @@ -using System.Threading; using AutoFixture.NUnit3; -using Microsoft.AspNetCore.Identity; using Moq; using NUnit.Framework; -using Umbraco.Core.BackOffice; +using Umbraco.Core.Security; using Umbraco.Tests.UnitTests.AutoFixture; using Umbraco.Web.BackOffice.Controllers; using Umbraco.Web.Common.Exceptions; diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Security/BackOfficeAntiforgeryTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Security/BackOfficeAntiforgeryTests.cs index d93bc01b4e..7899ef39c2 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Security/BackOfficeAntiforgeryTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Security/BackOfficeAntiforgeryTests.cs @@ -1,19 +1,15 @@ -using Microsoft.AspNetCore.Antiforgery; +using System; +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Antiforgery; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Features; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Microsoft.Net.Http.Headers; -using Moq; using NUnit.Framework; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Security.Claims; -using System.Text; -using System.Threading.Tasks; using Umbraco.Core; -using Umbraco.Core.BackOffice; +using Umbraco.Core.Security; using Umbraco.Web.BackOffice.Security; namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Security @@ -25,8 +21,16 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Security { var httpContext = new DefaultHttpContext() { - User = new ClaimsPrincipal(new UmbracoBackOfficeIdentity(-1, "test", "test", Enumerable.Empty(), Enumerable.Empty(), "en-US", - Guid.NewGuid().ToString(), Enumerable.Empty(), Enumerable.Empty())) + User = new ClaimsPrincipal(new UmbracoBackOfficeIdentity( + Constants.Security.SuperUserIdAsString, + "test", + "test", + Enumerable.Empty(), + Enumerable.Empty(), + "en-US", + Guid.NewGuid().ToString(), + Enumerable.Empty(), + Enumerable.Empty())) }; httpContext.Request.IsHttps = true; return httpContext; diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Security/BackOfficeCookieManagerTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Security/BackOfficeCookieManagerTests.cs index d45887b3c3..80432cdce2 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Security/BackOfficeCookieManagerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Security/BackOfficeCookieManagerTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; using Moq; @@ -27,8 +27,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Backoffice.Security Mock.Of(), runtime, Mock.Of(), - globalSettings, - Mock.Of()); + globalSettings); var result = mgr.ShouldAuthenticateRequest(new Uri("http://localhost/umbraco")); @@ -45,8 +44,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Backoffice.Security Mock.Of(), runtime, Mock.Of(x => x.ApplicationVirtualPath == "/" && x.ToAbsolute(globalSettings.UmbracoPath) == "/umbraco"), - globalSettings, - Mock.Of()); + globalSettings); var result = mgr.ShouldAuthenticateRequest(new Uri("http://localhost/umbraco")); @@ -60,13 +58,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Backoffice.Security var runtime = Mock.Of(x => x.Level == RuntimeLevel.Run); - GetMockLinkGenerator(out var remainingTimeoutSecondsPath, out var isAuthPath); + GenerateAuthPaths(out var remainingTimeoutSecondsPath, out var isAuthPath); + var mgr = new BackOfficeCookieManager( Mock.Of(), runtime, Mock.Of(x => x.ApplicationVirtualPath == "/" && x.ToAbsolute(globalSettings.UmbracoPath) == "/umbraco" && x.ToAbsolute(Constants.SystemDirectories.Install) == "/install"), - globalSettings, - Mock.Of()); + globalSettings); var result = mgr.ShouldAuthenticateRequest(new Uri($"http://localhost{remainingTimeoutSecondsPath}")); Assert.IsTrue(result); @@ -75,23 +73,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Backoffice.Security Assert.IsTrue(result); } - [Test] - public void ShouldAuthenticateRequest_Force_Auth() - { - var globalSettings = new GlobalSettings(); - - var runtime = Mock.Of(x => x.Level == RuntimeLevel.Run); - - var mgr = new BackOfficeCookieManager( - Mock.Of(), - runtime, - Mock.Of(x => x.ApplicationVirtualPath == "/" && x.ToAbsolute(globalSettings.UmbracoPath) == "/umbraco" && x.ToAbsolute(Constants.SystemDirectories.Install) == "/install"), - globalSettings, - Mock.Of(x => x.IsAvailable == true && x.Get(Constants.Security.ForceReAuthFlag) == "not null")); - - var result = mgr.ShouldAuthenticateRequest(new Uri($"http://localhost/notbackoffice")); - Assert.IsTrue(result); - } [Test] public void ShouldAuthenticateRequest_Not_Back_Office() @@ -104,8 +85,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Backoffice.Security Mock.Of(), runtime, Mock.Of(x => x.ApplicationVirtualPath == "/" && x.ToAbsolute(globalSettings.UmbracoPath) == "/umbraco" && x.ToAbsolute(Constants.SystemDirectories.Install) == "/install"), - globalSettings, - Mock.Of()); + globalSettings); var result = mgr.ShouldAuthenticateRequest(new Uri($"http://localhost/notbackoffice")); Assert.IsFalse(result); @@ -115,7 +95,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Backoffice.Security Assert.IsFalse(result); } - private LinkGenerator GetMockLinkGenerator(out string remainingTimeoutSecondsPath, out string isAuthPath) + private void GenerateAuthPaths(out string remainingTimeoutSecondsPath, out string isAuthPath) { var controllerName = ControllerExtensions.GetControllerName(); @@ -125,24 +105,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Backoffice.Security // this is on the same controller but is considered a back office request var aPath = isAuthPath = $"/umbraco/{Constants.Web.Mvc.BackOfficePathSegment}/{Constants.Web.Mvc.BackOfficeApiArea}/{controllerName}/{nameof(AuthenticationController.IsAuthenticated)}".ToLower(); - var linkGenerator = new Mock(); - linkGenerator.Setup(x => x.GetPathByAddress( - //It.IsAny(), - It.IsAny(), - //It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny())).Returns((RouteValuesAddress address, RouteValueDictionary routeVals1, PathString path, FragmentString fragment, LinkOptions options) => - { - if (routeVals1["action"].ToString() == nameof(AuthenticationController.GetRemainingTimeoutSeconds)) - return rPath; - if (routeVals1["action"].ToString() == nameof(AuthenticationController.IsAuthenticated).ToLower()) - return aPath; - return null; - }); - - return linkGenerator.Object; } } } diff --git a/src/Umbraco.Tests/Security/OwinDataProtectorTokenProviderTests.cs b/src/Umbraco.Tests/Security/OwinDataProtectorTokenProviderTests.cs deleted file mode 100644 index 66965ca632..0000000000 --- a/src/Umbraco.Tests/Security/OwinDataProtectorTokenProviderTests.cs +++ /dev/null @@ -1,247 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Identity; -using Microsoft.Owin.Security.DataProtection; -using Moq; -using NUnit.Framework; -using Umbraco.Core.BackOffice; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.Models; -using Umbraco.Core.Models.Membership; -using Umbraco.Tests.Common.Builders; -using Umbraco.Web.Security; - -namespace Umbraco.Tests.Security -{ - public class OwinDataProtectorTokenProviderTests - { - private Mock _mockDataProtector; - private Mock> _mockUserManager; - private BackOfficeIdentityUser _testUser; - private const string _testPurpose = "test"; - - [Test] - public void Ctor_When_Protector_Is_Null_Expect_ArgumentNullException() - { - Assert.Throws(() => new OwinDataProtectorTokenProvider(null)); - } - - [Test] - public async Task CanGenerateTwoFactorTokenAsync_Expect_False() - { - var sut = CreateSut(); - - var canGenerate = await sut.CanGenerateTwoFactorTokenAsync(_mockUserManager.Object, _testUser); - - Assert.False(canGenerate); - } - - [Test] - public void GenerateAsync_When_UserManager_Is_Null_Expect_ArgumentNullException() - { - var sut = CreateSut(); - Assert.ThrowsAsync(async () => await sut.GenerateAsync(null, null, _testUser)); - } - - [Test] - public void GenerateAsync_When_User_Is_Null_Expect_ArgumentNullException() - { - var sut = CreateSut(); - Assert.ThrowsAsync(async () => await sut.GenerateAsync(null, _mockUserManager.Object, null)); - } - - [Test] - public async Task GenerateAsync_When_Token_Generated_Expect_Ticks_And_User_ID() - { - var sut = CreateSut(); - - var token = await sut.GenerateAsync(null, _mockUserManager.Object, _testUser); - - using (var reader = new BinaryReader(new MemoryStream(Convert.FromBase64String(token)))) - { - var creationTime = new DateTimeOffset(reader.ReadInt64(), TimeSpan.Zero); - var foundUserId = reader.ReadString(); - - Assert.That(creationTime.DateTime, Is.EqualTo(DateTime.UtcNow).Within(1).Minutes); - Assert.AreEqual(_testUser.Id.ToString(), foundUserId); - } - } - - [Test] - public async Task GenerateAsync_When_Token_Generated_With_Purpose_Expect_Purpose_In_Token() - { - var expectedPurpose = Guid.NewGuid().ToString(); - - var sut = CreateSut(); - - var token = await sut.GenerateAsync(expectedPurpose, _mockUserManager.Object, _testUser); - - using (var reader = new BinaryReader(new MemoryStream(Convert.FromBase64String(token)))) - { - reader.ReadInt64(); // creation time - reader.ReadString(); // user ID - var purpose = reader.ReadString(); - - Assert.AreEqual(expectedPurpose, purpose); - } - } - - [Test] - public async Task GenerateAsync_When_Token_Generated_And_SecurityStamp_Supported_Expect_SecurityStamp_In_Token() - { - var expectedSecurityStamp = Guid.NewGuid().ToString(); - - var sut = CreateSut(); - _mockUserManager.Setup(x => x.SupportsUserSecurityStamp).Returns(true); - _mockUserManager.Setup(x => x.GetSecurityStampAsync(_testUser)).ReturnsAsync(expectedSecurityStamp); - - var token = await sut.GenerateAsync(null, _mockUserManager.Object, _testUser); - - using (var reader = new BinaryReader(new MemoryStream(Convert.FromBase64String(token)))) - { - reader.ReadInt64(); // creation time - reader.ReadString(); // user ID - reader.ReadString(); // purpose - var securityStamp = reader.ReadString(); - - Assert.AreEqual(expectedSecurityStamp, securityStamp); - } - } - - [Test] - [TestCase(null)] - [TestCase("")] - [TestCase(" ")] - public void ValidateAsync_When_Token_Is_Null_Or_Whitespace_Expect_ArgumentNullException(string token) - { - var sut = CreateSut(); - - Assert.ThrowsAsync(() => sut.ValidateAsync(null, token, _mockUserManager.Object, _testUser)); - } - - [Test] - public void ValidateAsync_When_UserManager_Is_Null_Expect_ArgumentNullException() - { - var sut = CreateSut(); - - Assert.ThrowsAsync(() => sut.ValidateAsync(null, Guid.NewGuid().ToString(), null, _testUser)); - } - - [Test] - public void ValidateAsync_When_User_Is_Null_Expect_ArgumentNullException() - { - var sut = CreateSut(); - - Assert.ThrowsAsync(() => sut.ValidateAsync(null, Guid.NewGuid().ToString(), _mockUserManager.Object, null)); - } - - [Test] - public async Task ValidateAsync_When_Token_Has_Expired_Expect_False() - { - var sut = CreateSut(); - var testToken = CreateTestToken(creationDate: DateTime.UtcNow.AddYears(-10)); - - var isValid = await sut.ValidateAsync(null, testToken, _mockUserManager.Object, _testUser); - - Assert.False(isValid); - } - - [Test] - public async Task ValidateAsync_When_Token_Was_Issued_To_Wrong_User_Expect_False() - { - var sut = CreateSut(); - var testToken = CreateTestToken(userId: Guid.NewGuid().ToString()); - - var isValid = await sut.ValidateAsync(_testPurpose, testToken, _mockUserManager.Object, _testUser); - - Assert.False(isValid); - } - - [Test] - public async Task ValidateAsync_When_Token_Was_Has_Wrong_Purpose_Expect_False() - { - var sut = CreateSut(); - var testToken = CreateTestToken(purpose: "invalid"); - - var isValid = await sut.ValidateAsync("valid", testToken, _mockUserManager.Object, _testUser); - - Assert.False(isValid); - } - - [Test] - public async Task ValidateAsync_When_Token_Was_Has_Wrong_SecurityStamp_Expect_False() - { - var sut = CreateSut(); - _mockUserManager.Setup(x => x.SupportsUserSecurityStamp).Returns(true); - _mockUserManager.Setup(x => x.GetSecurityStampAsync(_testUser)).ReturnsAsync(Guid.NewGuid().ToString); - - var testToken = CreateTestToken(securityStamp: "invalid"); - - var isValid = await sut.ValidateAsync(_testPurpose, testToken, _mockUserManager.Object, _testUser); - - Assert.False(isValid); - } - - [Test] - public async Task ValidateAsync_When_Valid_Token_Expect_True() - { - const string validPurpose = "test"; - var validSecurityStamp = Guid.NewGuid().ToString(); - - var sut = CreateSut(); - _mockUserManager.Setup(x => x.SupportsUserSecurityStamp).Returns(true); - _mockUserManager.Setup(x => x.GetSecurityStampAsync(_testUser)).ReturnsAsync(validSecurityStamp); - - var testToken = CreateTestToken( - creationDate: DateTime.UtcNow, - userId: _testUser.Id.ToString(), - purpose: validPurpose, - securityStamp: validSecurityStamp); - - var isValid = await sut.ValidateAsync(validPurpose, testToken, _mockUserManager.Object, _testUser); - - Assert.True(isValid); - } - - private OwinDataProtectorTokenProvider CreateSut() - => new OwinDataProtectorTokenProvider(_mockDataProtector.Object); - - private string CreateTestToken(DateTime? creationDate = null, string userId = null, string purpose = null, string securityStamp = null) - { - var ms = new MemoryStream(); - using (var writer = new BinaryWriter(ms)) - { - writer.Write(creationDate?.Ticks ?? DateTimeOffset.UtcNow.UtcTicks); - writer.Write(userId ?? _testUser.Id.ToString()); - writer.Write(purpose ?? _testPurpose); - writer.Write(securityStamp ?? ""); - } - - return Convert.ToBase64String(ms.ToArray()); - } - - [SetUp] - public void Setup() - { - _mockDataProtector = new Mock(); - _mockDataProtector.Setup(x => x.Protect(It.IsAny())).Returns((byte[] originalBytes) => originalBytes); - _mockDataProtector.Setup(x => x.Unprotect(It.IsAny())).Returns((byte[] originalBytes) => originalBytes); - - var globalSettings = new GlobalSettings(); - - _mockUserManager = new Mock>(new Mock>().Object, - null, null, null, null, null, null, null, null); - _mockUserManager.Setup(x => x.SupportsUserSecurityStamp).Returns(false); - - _testUser = new BackOfficeIdentityUser(globalSettings, 2, new List()) - { - UserName = "alice", - Name = "Alice", - Email = "alice@umbraco.test", - SecurityStamp = Guid.NewGuid().ToString() - }; - } - } -} diff --git a/src/Umbraco.Tests/TestHelpers/ControllerTesting/AuthenticateEverythingMiddleware.cs b/src/Umbraco.Tests/TestHelpers/ControllerTesting/AuthenticateEverythingMiddleware.cs index 48ffdbcdec..e702753236 100644 --- a/src/Umbraco.Tests/TestHelpers/ControllerTesting/AuthenticateEverythingMiddleware.cs +++ b/src/Umbraco.Tests/TestHelpers/ControllerTesting/AuthenticateEverythingMiddleware.cs @@ -1,10 +1,10 @@ -using System; +using System; using System.Threading.Tasks; using Microsoft.Owin; using Microsoft.Owin.Security; using Microsoft.Owin.Security.Infrastructure; using Owin; -using Umbraco.Core.BackOffice; +using Umbraco.Core.Security; namespace Umbraco.Tests.TestHelpers.ControllerTesting { @@ -29,9 +29,10 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting { var securityStamp = Guid.NewGuid().ToString(); var identity = new UmbracoBackOfficeIdentity( - -1, "admin", "Admin", new []{-1}, new[] { -1 }, "en-US", securityStamp, new[] { "content", "media", "members" }, new[] { "admin" }); + Umbraco.Core.Constants.Security.SuperUserIdAsString, "admin", "Admin", new[] { -1 }, new[] { -1 }, "en-US", securityStamp, new[] { "content", "media", "members" }, new[] { "admin" }); - return Task.FromResult(new AuthenticationTicket(identity, + return Task.FromResult(new AuthenticationTicket( + identity, new AuthenticationProperties() { ExpiresUtc = DateTime.Now.AddDays(1) diff --git a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs index 23f7e09f5d..f993ee5b6a 100644 --- a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs +++ b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs @@ -6,7 +6,6 @@ using System.Web.Http; using System.Web.Http.Controllers; using System.Web.Http.Dispatcher; using Moq; -using Umbraco.Core.BackOffice; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Models.Membership; using Umbraco.Core.Models.PublishedContent; diff --git a/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs b/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs index 035788abfc..ae43cd4ea1 100644 --- a/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs +++ b/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs @@ -44,12 +44,12 @@ namespace Umbraco.Tests.TestHelpers.Entities }; var contentCollection = new PropertyTypeCollection(true); - contentCollection.Add(new PropertyType(ShortStringHelper, "test", ValueStorageType.Ntext) { Alias = "title", Name = "Title", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = -88 }); - contentCollection.Add(new PropertyType(ShortStringHelper, "test", ValueStorageType.Ntext) { Alias = "bodyText", Name = "Body Text", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -87 }); + contentCollection.Add(new PropertyType(ShortStringHelper, "test", ValueStorageType.Ntext) { Alias = "title", Name = "Title", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = Constants.DataTypes.Textbox, LabelOnTop = true }); + contentCollection.Add(new PropertyType(ShortStringHelper, "test", ValueStorageType.Ntext) { Alias = "bodyText", Name = "Body Text", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = Constants.DataTypes.RichtextEditor, LabelOnTop = false }); var metaCollection = new PropertyTypeCollection(true); - metaCollection.Add(new PropertyType(ShortStringHelper, "test", ValueStorageType.Ntext) { Alias = "keywords", Name = "Meta Keywords", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = -88 }); - metaCollection.Add(new PropertyType(ShortStringHelper, "test", ValueStorageType.Ntext) { Alias = "description", Name = "Meta Description", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -89 }); + metaCollection.Add(new PropertyType(ShortStringHelper, "test", ValueStorageType.Ntext) { Alias = "keywords", Name = "Meta Keywords", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = Constants.DataTypes.Textbox }); + metaCollection.Add(new PropertyType(ShortStringHelper, "test", ValueStorageType.Ntext) { Alias = "description", Name = "Meta Description", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = Constants.DataTypes.Textarea }); contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); contentType.PropertyGroups.Add(new PropertyGroup(metaCollection) { Name = "Meta", SortOrder = 2 }); @@ -200,7 +200,7 @@ namespace Umbraco.Tests.TestHelpers.Entities contentType.Trashed = false; var contentCollection = new PropertyTypeCollection(true); - contentCollection.Add(new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.TextBox, ValueStorageType.Ntext) { Alias = RandomAlias("title", randomizeAliases), Name = "Title", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = -88 }); + contentCollection.Add(new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.TextBox, ValueStorageType.Ntext) { Alias = RandomAlias("title", randomizeAliases), Name = "Title", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = -88, LabelOnTop = true }); contentCollection.Add(new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.TinyMce, ValueStorageType.Ntext) { Alias = RandomAlias("bodyText", randomizeAliases), Name = "Body Text", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -87 }); contentCollection.Add(new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.TextBox, ValueStorageType.Ntext) { Alias = RandomAlias("author", randomizeAliases) , Name = "Author", Description = "Name of the author", Mandatory = false, SortOrder = 3, DataTypeId = -88 }); diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 008fbc47d7..74eed99143 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -143,7 +143,6 @@ - @@ -195,7 +194,6 @@ - diff --git a/src/Umbraco.Tests/Web/AngularIntegration/AngularAntiForgeryTests.cs b/src/Umbraco.Tests/Web/AngularIntegration/AngularAntiForgeryTests.cs deleted file mode 100644 index fd30f1b5ec..0000000000 --- a/src/Umbraco.Tests/Web/AngularIntegration/AngularAntiForgeryTests.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System.IO; -using System.Net; -using System.Security.Principal; -using System.Web; -using NUnit.Framework; -using Umbraco.Web.WebApi.Filters; - -namespace Umbraco.Tests.Web.AngularIntegration -{ - [TestFixture] - public class AngularAntiForgeryTests - { - - [TearDown] - public void TearDown() - { - HttpContext.Current = null; - } - - [Test] - public void Can_Validate_Generated_Tokens() - { - using (var writer = new StringWriter()) - { - HttpContext.Current = new HttpContext(new HttpRequest("test.html", "http://test/", ""), new HttpResponse(writer)); - - string cookieToken, headerToken; - AngularAntiForgeryHelper.GetTokens(out cookieToken, out headerToken); - - Assert.IsTrue(AngularAntiForgeryHelper.ValidateTokens(cookieToken, headerToken)); - } - - } - - [Test] - public void Can_Validate_Generated_Tokens_With_User() - { - using (var writer = new StringWriter()) - { - HttpContext.Current = new HttpContext(new HttpRequest("test.html", "http://test/", ""), new HttpResponse(writer)) - { - User = new GenericPrincipal(new HttpListenerBasicIdentity("test", "test"), new string[] {}) - }; - - string cookieToken, headerToken; - AngularAntiForgeryHelper.GetTokens(out cookieToken, out headerToken); - - Assert.IsTrue(AngularAntiForgeryHelper.ValidateTokens(cookieToken, headerToken)); - } - - } - - } -} diff --git a/src/Umbraco.Web.BackOffice/Authorization/AdminUsersHandler.cs b/src/Umbraco.Web.BackOffice/Authorization/AdminUsersHandler.cs index 4d9f47c7dd..a7733b25bd 100644 --- a/src/Umbraco.Web.BackOffice/Authorization/AdminUsersHandler.cs +++ b/src/Umbraco.Web.BackOffice/Authorization/AdminUsersHandler.cs @@ -1,7 +1,12 @@ -using System.Linq; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Primitives; using Umbraco.Core; using Umbraco.Core.Security; using Umbraco.Core.Services; @@ -14,25 +19,34 @@ namespace Umbraco.Web.BackOffice.Authorization /// public class AdminUsersHandler : MustSatisfyRequirementAuthorizationHandler { - private readonly IHttpContextAccessor _httpContextAcessor; + private readonly IHttpContextAccessor _httpContextAccessor; private readonly IUserService _userService; - private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor; + private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; private readonly UserEditorAuthorizationHelper _userEditorAuthorizationHelper; - public AdminUsersHandler(IHttpContextAccessor httpContextAcessor, - IUserService userService, - IBackOfficeSecurityAccessor backofficeSecurityAccessor, - UserEditorAuthorizationHelper userEditorAuthorizationHelper) + /// + /// Initializes a new instance of the class. + /// + /// Accessor for the HTTP context of the current request. + /// Service for user related operations. + /// Accessor for back-office security. + /// Helper for user authorization checks. + public AdminUsersHandler( + IHttpContextAccessor httpContextAccessor, + IUserService userService, + IBackOfficeSecurityAccessor backOfficeSecurityAccessor, + UserEditorAuthorizationHelper userEditorAuthorizationHelper) { - _httpContextAcessor = httpContextAcessor; + _httpContextAccessor = httpContextAccessor; _userService = userService; - _backofficeSecurityAccessor = backofficeSecurityAccessor; + _backOfficeSecurityAccessor = backOfficeSecurityAccessor; _userEditorAuthorizationHelper = userEditorAuthorizationHelper; } + /// protected override Task IsAuthorized(AuthorizationHandlerContext context, AdminUsersRequirement requirement) { - var queryString = _httpContextAcessor.HttpContext?.Request.Query[requirement.QueryStringName]; + StringValues? queryString = _httpContextAccessor.HttpContext?.Request.Query[requirement.QueryStringName]; if (!queryString.HasValue || !queryString.Value.Any()) { // Must succeed this requirement since we cannot process it. @@ -46,12 +60,13 @@ namespace Umbraco.Web.BackOffice.Authorization } else { - var ids = _httpContextAcessor.HttpContext.Request.Query.Where(x => x.Key == requirement.QueryStringName).ToList(); + var ids = _httpContextAccessor.HttpContext.Request.Query.Where(x => x.Key == requirement.QueryStringName).ToList(); if (ids.Count == 0) { // Must succeed this requirement since we cannot process it. return Task.FromResult(true); } + userIds = ids .Select(x => x.Value.ToString()) .Select(x => x.TryConvertTo()).Where(x => x.Success).Select(x => x.Result).ToArray(); @@ -63,8 +78,8 @@ namespace Umbraco.Web.BackOffice.Authorization return Task.FromResult(true); } - var users = _userService.GetUsersById(userIds); - var isAuth = users.All(user => _userEditorAuthorizationHelper.IsAuthorized(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser, user, null, null, null) != false); + IEnumerable users = _userService.GetUsersById(userIds); + var isAuth = users.All(user => _userEditorAuthorizationHelper.IsAuthorized(_backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser, user, null, null, null) != false); return Task.FromResult(isAuth); } diff --git a/src/Umbraco.Web.BackOffice/Authorization/AdminUsersRequirement.cs b/src/Umbraco.Web.BackOffice/Authorization/AdminUsersRequirement.cs index d5cd5d80dc..e8a56ee8cb 100644 --- a/src/Umbraco.Web.BackOffice/Authorization/AdminUsersRequirement.cs +++ b/src/Umbraco.Web.BackOffice/Authorization/AdminUsersRequirement.cs @@ -1,4 +1,7 @@ -using Microsoft.AspNetCore.Authorization; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Microsoft.AspNetCore.Authorization; namespace Umbraco.Web.BackOffice.Authorization { @@ -7,11 +10,15 @@ namespace Umbraco.Web.BackOffice.Authorization /// public class AdminUsersRequirement : IAuthorizationRequirement { - public AdminUsersRequirement(string queryStringName = "id") - { - QueryStringName = queryStringName; - } + /// + /// Initializes a new instance of the class. + /// + /// Query string name from which to authorize values. + public AdminUsersRequirement(string queryStringName = "id") => QueryStringName = queryStringName; + /// + /// Gets the query string name from which to authorize values. + /// public string QueryStringName { get; } } } diff --git a/src/Umbraco.Web.BackOffice/Authorization/BackOfficeHandler.cs b/src/Umbraco.Web.BackOffice/Authorization/BackOfficeHandler.cs index f536483921..0d7c28f314 100644 --- a/src/Umbraco.Web.BackOffice/Authorization/BackOfficeHandler.cs +++ b/src/Umbraco.Web.BackOffice/Authorization/BackOfficeHandler.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Umbraco.Core; diff --git a/src/Umbraco.Web.BackOffice/Authorization/BackOfficeRequirement.cs b/src/Umbraco.Web.BackOffice/Authorization/BackOfficeRequirement.cs index d1b13efe2c..17c5f6e02a 100644 --- a/src/Umbraco.Web.BackOffice/Authorization/BackOfficeRequirement.cs +++ b/src/Umbraco.Web.BackOffice/Authorization/BackOfficeRequirement.cs @@ -1,4 +1,7 @@ -using Microsoft.AspNetCore.Authorization; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Microsoft.AspNetCore.Authorization; namespace Umbraco.Web.BackOffice.Authorization { @@ -7,11 +10,15 @@ namespace Umbraco.Web.BackOffice.Authorization /// public class BackOfficeRequirement : IAuthorizationRequirement { - public BackOfficeRequirement(bool requireApproval = true) - { - RequireApproval = requireApproval; - } + /// + /// Initializes a new instance of the class. + /// + /// Flag for whether back-office user approval is required. + public BackOfficeRequirement(bool requireApproval = true) => RequireApproval = requireApproval; + /// + /// Gets a value indicating whether back-office user approval is required. + /// public bool RequireApproval { get; } } } diff --git a/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionsPublishBranchHandler.cs b/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionsPublishBranchHandler.cs index 32eebaa105..bb6dc919be 100644 --- a/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionsPublishBranchHandler.cs +++ b/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionsPublishBranchHandler.cs @@ -1,7 +1,10 @@ -using Microsoft.AspNetCore.Authorization; +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; @@ -11,7 +14,7 @@ using Umbraco.Core.Services; namespace Umbraco.Web.BackOffice.Authorization { /// - /// The user must have access to all descendant nodes of the content item in order to continue + /// The user must have access to all descendant nodes of the content item in order to continue. /// public class ContentPermissionsPublishBranchHandler : MustSatisfyRequirementAuthorizationHandler { @@ -19,6 +22,12 @@ namespace Umbraco.Web.BackOffice.Authorization private readonly ContentPermissions _contentPermissions; private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; + /// + /// Initializes a new instance of the class. + /// + /// Service for entity operations. + /// per for user content authorization checks. + /// Accessor for back-office security. public ContentPermissionsPublishBranchHandler( IEntityService entityService, ContentPermissions contentPermissions, @@ -29,9 +38,10 @@ namespace Umbraco.Web.BackOffice.Authorization _backOfficeSecurityAccessor = backOfficeSecurityAccessor; } + /// protected override Task IsAuthorized(AuthorizationHandlerContext context, ContentPermissionsPublishBranchRequirement requirement, IContent resource) { - var currentUser = _backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser; + Core.Models.Membership.IUser currentUser = _backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser; var denied = new List(); var page = 0; @@ -40,16 +50,22 @@ namespace Umbraco.Web.BackOffice.Authorization while (page * pageSize < total) { - var descendants = _entityService.GetPagedDescendants(resource.Id, UmbracoObjectTypes.Document, page++, pageSize, out total, - // Order by shallowest to deepest, this allows us to check permissions from top to bottom so we can exit - // early if a permission higher up fails. - ordering: Ordering.By("path", Direction.Ascending)); + // Order descendents by shallowest to deepest, this allows us to check permissions from top to bottom so we can exit + // early if a permission higher up fails. + IEnumerable descendants = _entityService.GetPagedDescendants( + resource.Id, + UmbracoObjectTypes.Document, + page++, + pageSize, + out total, + ordering: Ordering.By("path", Direction.Ascending)); - foreach (var c in descendants) + foreach (IEntitySlim c in descendants) { // If this item's path has already been denied or if the user doesn't have access to it, add to the deny list. - if (denied.Any(x => c.Path.StartsWith($"{x.Path},")) - || (_contentPermissions.CheckPermissions(c, + if (denied.Any(x => c.Path.StartsWith($"{x.Path},")) || + (_contentPermissions.CheckPermissions( + c, currentUser, requirement.Permission) == ContentPermissions.ContentAccess.Denied)) { diff --git a/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionsPublishBranchRequirement.cs b/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionsPublishBranchRequirement.cs index 541f861f0d..cfa6f512a2 100644 --- a/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionsPublishBranchRequirement.cs +++ b/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionsPublishBranchRequirement.cs @@ -1,4 +1,7 @@ -using Microsoft.AspNetCore.Authorization; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Microsoft.AspNetCore.Authorization; namespace Umbraco.Web.BackOffice.Authorization { @@ -7,11 +10,15 @@ namespace Umbraco.Web.BackOffice.Authorization /// public class ContentPermissionsPublishBranchRequirement : IAuthorizationRequirement { - public ContentPermissionsPublishBranchRequirement(char permission) - { - Permission = permission; - } + /// + /// Initializes a new instance of the class. + /// + /// Permission to check. + public ContentPermissionsPublishBranchRequirement(char permission) => Permission = permission; + /// + /// Gets a value for the permission to check. + /// public char Permission { get; } } } diff --git a/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionsQueryStringHandler.cs b/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionsQueryStringHandler.cs index 863cbdfc3c..caf94c1cec 100644 --- a/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionsQueryStringHandler.cs +++ b/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionsQueryStringHandler.cs @@ -1,8 +1,10 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; -using Umbraco.Core; +using Microsoft.Extensions.Primitives; using Umbraco.Core.Models; using Umbraco.Core.Security; using Umbraco.Core.Services; @@ -10,28 +12,33 @@ using Umbraco.Core.Services; namespace Umbraco.Web.BackOffice.Authorization { /// - /// Used to authorize if the user has the correct permission access to the content for the content id specified in a query string + /// Used to authorize if the user has the correct permission access to the content for the content id specified in a query string. /// public class ContentPermissionsQueryStringHandler : PermissionsQueryStringHandler { private readonly ContentPermissions _contentPermissions; + /// + /// Initializes a new instance of the class. + /// + /// Accessor for back-office security. + /// Accessor for the HTTP context of the current request. + /// Service for entity operations. + /// Helper for content authorization checks. public ContentPermissionsQueryStringHandler( - IBackOfficeSecurityAccessor backofficeSecurityAccessor, - IHttpContextAccessor httpContextAccessor, + IBackOfficeSecurityAccessor backOfficeSecurityAccessor, + IHttpContextAccessor httpContextAccessor, IEntityService entityService, ContentPermissions contentPermissions) - : base(backofficeSecurityAccessor, httpContextAccessor, entityService) - { - _contentPermissions = contentPermissions; - } + : base(backOfficeSecurityAccessor, httpContextAccessor, entityService) => _contentPermissions = contentPermissions; + /// protected override Task IsAuthorized(AuthorizationHandlerContext context, ContentPermissionsQueryStringRequirement requirement) { int nodeId; if (requirement.NodeId.HasValue == false) { - if (!HttpContextAccessor.HttpContext.Request.Query.TryGetValue(requirement.QueryStringName, out var routeVal)) + if (!HttpContextAccessor.HttpContext.Request.Query.TryGetValue(requirement.QueryStringName, out StringValues routeVal)) { // Must succeed this requirement since we cannot process it return Task.FromResult(true); @@ -52,8 +59,9 @@ namespace Umbraco.Web.BackOffice.Authorization nodeId = requirement.NodeId.Value; } - var permissionResult = _contentPermissions.CheckPermissions(nodeId, - BackofficeSecurityAccessor.BackOfficeSecurity.CurrentUser, + ContentPermissions.ContentAccess permissionResult = _contentPermissions.CheckPermissions( + nodeId, + BackOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser, out IContent contentItem, new[] { requirement.PermissionToCheck }); diff --git a/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionsQueryStringRequirement.cs b/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionsQueryStringRequirement.cs index 2d558c569c..ee386295c2 100644 --- a/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionsQueryStringRequirement.cs +++ b/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionsQueryStringRequirement.cs @@ -1,19 +1,20 @@ -using Microsoft.AspNetCore.Authorization; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Microsoft.AspNetCore.Authorization; namespace Umbraco.Web.BackOffice.Authorization { - /// /// An authorization requirement for /// public class ContentPermissionsQueryStringRequirement : IAuthorizationRequirement { - /// - /// Create an authorization requirement for a specific node id + /// Initializes a new instance of the class for a specific node id. /// - /// - /// + /// The node Id. + /// The permission to authorize the current user against. public ContentPermissionsQueryStringRequirement(int nodeId, char permissionToCheck) { NodeId = nodeId; @@ -21,18 +22,30 @@ namespace Umbraco.Web.BackOffice.Authorization } /// - /// Create an authorization requirement for a node id based on a query string parameter + /// Initializes a new instance of the class for a + /// node id based on a query string parameter. /// - /// - /// + /// The querystring parameter name. + /// The permission to authorize the current user against. public ContentPermissionsQueryStringRequirement(char permissionToCheck, string paramName = "id") { QueryStringName = paramName; PermissionToCheck = permissionToCheck; } + /// + /// Gets the specific node Id. + /// public int? NodeId { get; } + + /// + /// Gets the querystring parameter name. + /// public string QueryStringName { get; } + + /// + /// Gets the permission to authorize the current user against. + /// public char PermissionToCheck { get; } } } diff --git a/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionsResource.cs b/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionsResource.cs index 0ec92c7af2..b544d28eb5 100644 --- a/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionsResource.cs +++ b/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionsResource.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; using Umbraco.Core.Models; namespace Umbraco.Web.BackOffice.Authorization @@ -8,27 +11,54 @@ namespace Umbraco.Web.BackOffice.Authorization /// public class ContentPermissionsResource { + /// + /// Initializes a new instance of the class. + /// + /// The content. + /// The permission to authorize. public ContentPermissionsResource(IContent content, char permissionToCheck) { PermissionsToCheck = new List { permissionToCheck }; Content = content; } - public ContentPermissionsResource(IContent content, IReadOnlyList permissionToCheck) + /// + /// Initializes a new instance of the class. + /// + /// The content. + /// The collection of permissions to authorize. + public ContentPermissionsResource(IContent content, IReadOnlyList permissionsToCheck) { Content = content; - PermissionsToCheck = permissionToCheck; + PermissionsToCheck = permissionsToCheck; } - public ContentPermissionsResource(IContent content, int nodeId, IReadOnlyList permissionToCheck) + /// + /// Initializes a new instance of the class. + /// + /// The content. + /// The node Id. + /// The collection of permissions to authorize. + public ContentPermissionsResource(IContent content, int nodeId, IReadOnlyList permissionsToCheck) { Content = content; NodeId = nodeId; - PermissionsToCheck = permissionToCheck; + PermissionsToCheck = permissionsToCheck; } - public int? NodeId { get; } + /// + /// Gets the node Id. + /// + public int? NodeId { get; } + + /// + /// Gets the collection of permissions to authorize. + /// public IReadOnlyList PermissionsToCheck { get; } + + /// + /// Gets the content. + /// public IContent Content { get; } } } diff --git a/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionsResourceHandler.cs b/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionsResourceHandler.cs index 3bade3c5fe..24e5e5b6ac 100644 --- a/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionsResourceHandler.cs +++ b/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionsResourceHandler.cs @@ -1,4 +1,7 @@ -using System.Threading.Tasks; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Umbraco.Core.Models; using Umbraco.Core.Security; @@ -10,28 +13,34 @@ namespace Umbraco.Web.BackOffice.Authorization /// public class ContentPermissionsResourceHandler : MustSatisfyRequirementAuthorizationHandler { - private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor; + private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; private readonly ContentPermissions _contentPermissions; + /// + /// Initializes a new instance of the class. + /// + /// Accessor for back-office security. + /// Helper for content authorization checks. public ContentPermissionsResourceHandler( - IBackOfficeSecurityAccessor backofficeSecurityAccessor, + IBackOfficeSecurityAccessor backOfficeSecurityAccessor, ContentPermissions contentPermissions) { - _backofficeSecurityAccessor = backofficeSecurityAccessor; + _backOfficeSecurityAccessor = backOfficeSecurityAccessor; _contentPermissions = contentPermissions; } + /// protected override Task IsAuthorized(AuthorizationHandlerContext context, ContentPermissionsResourceRequirement requirement, ContentPermissionsResource resource) { - var permissionResult = resource.NodeId.HasValue + ContentPermissions.ContentAccess permissionResult = resource.NodeId.HasValue ? _contentPermissions.CheckPermissions( resource.NodeId.Value, - _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser, + _backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser, out IContent _, resource.PermissionsToCheck) : _contentPermissions.CheckPermissions( resource.Content, - _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser, + _backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser, resource.PermissionsToCheck); return Task.FromResult(permissionResult != ContentPermissions.ContentAccess.Denied); diff --git a/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionsResourceRequirement.cs b/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionsResourceRequirement.cs index 22b69c93da..fcf2838f33 100644 --- a/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionsResourceRequirement.cs +++ b/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionsResourceRequirement.cs @@ -1,9 +1,10 @@ -using Microsoft.AspNetCore.Authorization; -using Umbraco.Web.Actions; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Microsoft.AspNetCore.Authorization; namespace Umbraco.Web.BackOffice.Authorization { - /// /// An authorization requirement for /// diff --git a/src/Umbraco.Web.BackOffice/Authorization/DenyLocalLoginHandler.cs b/src/Umbraco.Web.BackOffice/Authorization/DenyLocalLoginHandler.cs index 96341c5b1f..6c09d00ef7 100644 --- a/src/Umbraco.Web.BackOffice/Authorization/DenyLocalLoginHandler.cs +++ b/src/Umbraco.Web.BackOffice/Authorization/DenyLocalLoginHandler.cs @@ -1,26 +1,27 @@ -using Microsoft.AspNetCore.Authorization; +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; using Umbraco.Web.BackOffice.Security; -using Umbraco.Web.Common.Security; namespace Umbraco.Web.BackOffice.Authorization { - /// - /// Ensures the resource cannot be accessed if returns true + /// Ensures the resource cannot be accessed if returns true. /// public class DenyLocalLoginHandler : MustSatisfyRequirementAuthorizationHandler { private readonly IBackOfficeExternalLoginProviders _externalLogins; - public DenyLocalLoginHandler(IBackOfficeExternalLoginProviders externalLogins) - { - _externalLogins = externalLogins; - } + /// + /// Initializes a new instance of the class. + /// + /// Provides access to instances. + public DenyLocalLoginHandler(IBackOfficeExternalLoginProviders externalLogins) => _externalLogins = externalLogins; - protected override Task IsAuthorized(AuthorizationHandlerContext context, DenyLocalLoginRequirement requirement) - { - return Task.FromResult(!_externalLogins.HasDenyLocalLogin()); - } + /// + protected override Task IsAuthorized(AuthorizationHandlerContext context, DenyLocalLoginRequirement requirement) => + Task.FromResult(!_externalLogins.HasDenyLocalLogin()); } } diff --git a/src/Umbraco.Web.BackOffice/Authorization/DenyLocalLoginRequirement.cs b/src/Umbraco.Web.BackOffice/Authorization/DenyLocalLoginRequirement.cs index a4f3a7e306..b295c74621 100644 --- a/src/Umbraco.Web.BackOffice/Authorization/DenyLocalLoginRequirement.cs +++ b/src/Umbraco.Web.BackOffice/Authorization/DenyLocalLoginRequirement.cs @@ -1,9 +1,12 @@ -using Microsoft.AspNetCore.Authorization; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Microsoft.AspNetCore.Authorization; namespace Umbraco.Web.BackOffice.Authorization { /// - /// Marker requirement for the + /// Marker requirement for the . /// public class DenyLocalLoginRequirement : IAuthorizationRequirement { diff --git a/src/Umbraco.Web.BackOffice/Authorization/MediaPermissionsQueryStringHandler.cs b/src/Umbraco.Web.BackOffice/Authorization/MediaPermissionsQueryStringHandler.cs index d6ef44cef1..e55384ab15 100644 --- a/src/Umbraco.Web.BackOffice/Authorization/MediaPermissionsQueryStringHandler.cs +++ b/src/Umbraco.Web.BackOffice/Authorization/MediaPermissionsQueryStringHandler.cs @@ -1,29 +1,41 @@ -using System.Threading.Tasks; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Primitives; using Umbraco.Core.Models; using Umbraco.Core.Security; using Umbraco.Core.Services; namespace Umbraco.Web.BackOffice.Authorization { + /// + /// Used to authorize if the user has the correct permission access to the media for the media id specified in a query string. + /// public class MediaPermissionsQueryStringHandler : PermissionsQueryStringHandler { private readonly MediaPermissions _mediaPermissions; + /// + /// Initializes a new instance of the class. + /// + /// Accessor for back-office security. + /// Accessor for the HTTP context of the current request. + /// Service for entity operations. + /// Helper for media authorization checks. public MediaPermissionsQueryStringHandler( - IBackOfficeSecurityAccessor backofficeSecurityAccessor, + IBackOfficeSecurityAccessor backOfficeSecurityAccessor, IHttpContextAccessor httpContextAccessor, IEntityService entityService, MediaPermissions mediaPermissions) - : base(backofficeSecurityAccessor, httpContextAccessor, entityService) - { - _mediaPermissions = mediaPermissions; - } + : base(backOfficeSecurityAccessor, httpContextAccessor, entityService) => _mediaPermissions = mediaPermissions; + /// protected override Task IsAuthorized(AuthorizationHandlerContext context, MediaPermissionsQueryStringRequirement requirement) { - if (!HttpContextAccessor.HttpContext.Request.Query.TryGetValue(requirement.QueryStringName, out var routeVal)) + if (!HttpContextAccessor.HttpContext.Request.Query.TryGetValue(requirement.QueryStringName, out StringValues routeVal)) { // Must succeed this requirement since we cannot process it. return Task.FromResult(true); @@ -37,10 +49,10 @@ namespace Umbraco.Web.BackOffice.Authorization return Task.FromResult(true); } - var permissionResult = _mediaPermissions.CheckPermissions( - BackofficeSecurityAccessor.BackOfficeSecurity.CurrentUser, + MediaPermissions.MediaAccess permissionResult = _mediaPermissions.CheckPermissions( + BackOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser, nodeId, - out var mediaItem); + out IMedia mediaItem); if (mediaItem != null) { @@ -53,6 +65,6 @@ namespace Umbraco.Web.BackOffice.Authorization MediaPermissions.MediaAccess.Denied => Task.FromResult(false), _ => Task.FromResult(true), }; - } + } } } diff --git a/src/Umbraco.Web.BackOffice/Authorization/MediaPermissionsQueryStringRequirement.cs b/src/Umbraco.Web.BackOffice/Authorization/MediaPermissionsQueryStringRequirement.cs index c7b62a570f..14d9d77834 100644 --- a/src/Umbraco.Web.BackOffice/Authorization/MediaPermissionsQueryStringRequirement.cs +++ b/src/Umbraco.Web.BackOffice/Authorization/MediaPermissionsQueryStringRequirement.cs @@ -1,14 +1,24 @@ -using Microsoft.AspNetCore.Authorization; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Microsoft.AspNetCore.Authorization; namespace Umbraco.Web.BackOffice.Authorization { + /// + /// An authorization requirement for + /// public class MediaPermissionsQueryStringRequirement : IAuthorizationRequirement { - public MediaPermissionsQueryStringRequirement(string paramName) - { - QueryStringName = paramName; - } + /// + /// Initializes a new instance of the class. + /// + /// Querystring paramter name. + public MediaPermissionsQueryStringRequirement(string paramName) => QueryStringName = paramName; + /// + /// Gets the querystring paramter name. + /// public string QueryStringName { get; } } } diff --git a/src/Umbraco.Web.BackOffice/Authorization/MediaPermissionsResource.cs b/src/Umbraco.Web.BackOffice/Authorization/MediaPermissionsResource.cs index 5b1ed92f5f..267fbc2b90 100644 --- a/src/Umbraco.Web.BackOffice/Authorization/MediaPermissionsResource.cs +++ b/src/Umbraco.Web.BackOffice/Authorization/MediaPermissionsResource.cs @@ -1,4 +1,7 @@ -using Umbraco.Core.Models; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Core.Models; namespace Umbraco.Web.BackOffice.Authorization { diff --git a/src/Umbraco.Web.BackOffice/Authorization/MediaPermissionsResourceHandler.cs b/src/Umbraco.Web.BackOffice/Authorization/MediaPermissionsResourceHandler.cs index 6c5280a19c..613aaaa8d5 100644 --- a/src/Umbraco.Web.BackOffice/Authorization/MediaPermissionsResourceHandler.cs +++ b/src/Umbraco.Web.BackOffice/Authorization/MediaPermissionsResourceHandler.cs @@ -1,36 +1,45 @@ -using Microsoft.AspNetCore.Authorization; +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; using Umbraco.Core.Models; using Umbraco.Core.Security; namespace Umbraco.Web.BackOffice.Authorization { /// - /// Used to authorize if the user has the correct permission access to the content for the specified + /// Used to authorize if the user has the correct permission access to the content for the specified. /// public class MediaPermissionsResourceHandler : MustSatisfyRequirementAuthorizationHandler { - private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor; + private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; private readonly MediaPermissions _mediaPermissions; + /// + /// Initializes a new instance of the class. + /// + /// Accessor for back-office security. + /// Helper for media authorization checks. public MediaPermissionsResourceHandler( - IBackOfficeSecurityAccessor backofficeSecurityAccessor, + IBackOfficeSecurityAccessor backOfficeSecurityAccessor, MediaPermissions mediaPermissions) { - _backofficeSecurityAccessor = backofficeSecurityAccessor; + _backOfficeSecurityAccessor = backOfficeSecurityAccessor; _mediaPermissions = mediaPermissions; } + /// protected override Task IsAuthorized(AuthorizationHandlerContext context, MediaPermissionsResourceRequirement requirement, MediaPermissionsResource resource) { - var permissionResult = resource.NodeId.HasValue + MediaPermissions.MediaAccess permissionResult = resource.NodeId.HasValue ? _mediaPermissions.CheckPermissions( - _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser, + _backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser, resource.NodeId.Value, out _) : _mediaPermissions.CheckPermissions( resource.Media, - _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser); + _backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser); return Task.FromResult(permissionResult != MediaPermissions.MediaAccess.Denied); } diff --git a/src/Umbraco.Web.BackOffice/Authorization/MediaPermissionsResourceRequirement.cs b/src/Umbraco.Web.BackOffice/Authorization/MediaPermissionsResourceRequirement.cs index 3087e4b258..b615a81709 100644 --- a/src/Umbraco.Web.BackOffice/Authorization/MediaPermissionsResourceRequirement.cs +++ b/src/Umbraco.Web.BackOffice/Authorization/MediaPermissionsResourceRequirement.cs @@ -1,13 +1,14 @@ -using Microsoft.AspNetCore.Authorization; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Microsoft.AspNetCore.Authorization; namespace Umbraco.Web.BackOffice.Authorization { - /// /// An authorization requirement for /// public class MediaPermissionsResourceRequirement : IAuthorizationRequirement { - } } diff --git a/src/Umbraco.Web.BackOffice/Authorization/MustSatisfyRequirementAuthorizationHandler.cs b/src/Umbraco.Web.BackOffice/Authorization/MustSatisfyRequirementAuthorizationHandler.cs index 98baacf5ee..b990906942 100644 --- a/src/Umbraco.Web.BackOffice/Authorization/MustSatisfyRequirementAuthorizationHandler.cs +++ b/src/Umbraco.Web.BackOffice/Authorization/MustSatisfyRequirementAuthorizationHandler.cs @@ -1,18 +1,23 @@ -using Microsoft.AspNetCore.Authorization; +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; namespace Umbraco.Web.BackOffice.Authorization { /// - /// Abstract handler that must satisfy the requirement so Succeed or Fail will be called no matter what + /// Abstract handler that must satisfy the requirement so Succeed or Fail will be called no matter what. /// - /// + /// Authorization requirement. /// /// aspnetcore Authz handlers are not required to satisfy the requirement and generally don't explicitly call Fail when the requirement /// isn't satisfied, however in many simple cases explicitly calling Succeed or Fail is what we want which is what this class is used for. /// - public abstract class MustSatisfyRequirementAuthorizationHandler : AuthorizationHandler where T : IAuthorizationRequirement + public abstract class MustSatisfyRequirementAuthorizationHandler : AuthorizationHandler + where T : IAuthorizationRequirement { + /// protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, T requirement) { var isAuth = await IsAuthorized(context, requirement); @@ -29,23 +34,25 @@ namespace Umbraco.Web.BackOffice.Authorization /// /// Return true if the requirement is succeeded or ignored, return false if the requirement is explicitly not met /// - /// - /// - /// + /// The authorization context. + /// The authorization requirement. + /// True if request is authorized, false if not. protected abstract Task IsAuthorized(AuthorizationHandlerContext context, T requirement); } /// - /// Abstract handler that must satisfy the requirement so Succeed or Fail will be called no matter what + /// Abstract handler that must satisfy the requirement so Succeed or Fail will be called no matter what. /// - /// - /// + /// Authorization requirement. + /// Resource to authorize access to. /// /// aspnetcore Authz handlers are not required to satisfy the requirement and generally don't explicitly call Fail when the requirement /// isn't satisfied, however in many simple cases explicitly calling Succeed or Fail is what we want which is what this class is used for. /// - public abstract class MustSatisfyRequirementAuthorizationHandler : AuthorizationHandler where T : IAuthorizationRequirement + public abstract class MustSatisfyRequirementAuthorizationHandler : AuthorizationHandler + where T : IAuthorizationRequirement { + /// protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, T requirement, TResource resource) { var isAuth = await IsAuthorized(context, requirement, resource); @@ -62,9 +69,10 @@ namespace Umbraco.Web.BackOffice.Authorization /// /// Return true if the requirement is succeeded or ignored, return false if the requirement is explicitly not met /// - /// - /// - /// + /// The authorization context. + /// The authorization requirement. + /// The resource to authorize access to. + /// True if request is authorized, false if not. protected abstract Task IsAuthorized(AuthorizationHandlerContext context, T requirement, TResource resource); } } diff --git a/src/Umbraco.Web.BackOffice/Authorization/PermissionsQueryStringHandler.cs b/src/Umbraco.Web.BackOffice/Authorization/PermissionsQueryStringHandler.cs index 49f62dc2f9..47e7c6bb91 100644 --- a/src/Umbraco.Web.BackOffice/Authorization/PermissionsQueryStringHandler.cs +++ b/src/Umbraco.Web.BackOffice/Authorization/PermissionsQueryStringHandler.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Umbraco.Core; @@ -8,25 +11,50 @@ using Umbraco.Core.Services; namespace Umbraco.Web.BackOffice.Authorization { + /// + /// Abstract base class providing common functionality for authorization checks based on querystrings. + /// + /// Authorization requirement public abstract class PermissionsQueryStringHandler : MustSatisfyRequirementAuthorizationHandler where T : IAuthorizationRequirement { + /// + /// Initializes a new instance of the class. + /// + /// Accessor for back-office security. + /// Accessor for the HTTP context of the current request. + /// Service for entity operations. public PermissionsQueryStringHandler( - IBackOfficeSecurityAccessor backofficeSecurityAccessor, + IBackOfficeSecurityAccessor backOfficeSecurityAccessor, IHttpContextAccessor httpContextAccessor, IEntityService entityService) { - BackofficeSecurityAccessor = backofficeSecurityAccessor; + BackOfficeSecurityAccessor = backOfficeSecurityAccessor; HttpContextAccessor = httpContextAccessor; EntityService = entityService; } - protected IBackOfficeSecurityAccessor BackofficeSecurityAccessor { get; set; } + /// + /// Gets or sets the instance. + /// + protected IBackOfficeSecurityAccessor BackOfficeSecurityAccessor { get; set; } + /// + /// Gets or sets the instance. + /// protected IHttpContextAccessor HttpContextAccessor { get; set; } + /// + /// Gets or sets the instance. + /// protected IEntityService EntityService { get; set; } + /// + /// Attempts to parse a node ID from a string representation found in a querystring value. + /// + /// Querystring value. + /// Output parsed Id. + /// True of node ID could be parased, false it not. protected bool TryParseNodeId(string argument, out int nodeId) { // If the argument is an int, it will parse and can be assigned to nodeId. @@ -38,12 +66,12 @@ namespace Umbraco.Web.BackOffice.Authorization nodeId = parsedId; return true; } - else if (UdiParser.TryParse(argument, true, out var udi)) + else if (UdiParser.TryParse(argument, true, out Udi udi)) { nodeId = EntityService.GetId(udi).Result; return true; } - else if (Guid.TryParse(argument, out var key)) + else if (Guid.TryParse(argument, out Guid key)) { nodeId = EntityService.GetId(key, UmbracoObjectTypes.Document).Result; return true; diff --git a/src/Umbraco.Web.BackOffice/Authorization/SectionHandler.cs b/src/Umbraco.Web.BackOffice/Authorization/SectionHandler.cs index ffa6db220f..4620422742 100644 --- a/src/Umbraco.Web.BackOffice/Authorization/SectionHandler.cs +++ b/src/Umbraco.Web.BackOffice/Authorization/SectionHandler.cs @@ -1,11 +1,13 @@ -using Microsoft.AspNetCore.Authorization; +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System.Linq; using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; using Umbraco.Core.Security; namespace Umbraco.Web.BackOffice.Authorization { - /// /// Ensures that the current user has access to the section /// @@ -14,18 +16,21 @@ namespace Umbraco.Web.BackOffice.Authorization /// public class SectionHandler : MustSatisfyRequirementAuthorizationHandler { - private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor; + private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; - public SectionHandler(IBackOfficeSecurityAccessor backofficeSecurityAccessor) - { - _backofficeSecurityAccessor = backofficeSecurityAccessor; - } + /// + /// Initializes a new instance of the class. + /// + /// Accessor for back-office security. + public SectionHandler(IBackOfficeSecurityAccessor backOfficeSecurityAccessor) => _backOfficeSecurityAccessor = backOfficeSecurityAccessor; + /// protected override Task IsAuthorized(AuthorizationHandlerContext context, SectionRequirement requirement) { - var authorized = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser != null - && requirement.SectionAliases.Any(app => _backofficeSecurityAccessor.BackOfficeSecurity.UserHasSectionAccess( - app, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser)); + var authorized = _backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser != null && + requirement.SectionAliases + .Any(app => _backOfficeSecurityAccessor.BackOfficeSecurity.UserHasSectionAccess( + app, _backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser)); return Task.FromResult(authorized); } diff --git a/src/Umbraco.Web.BackOffice/Authorization/SectionRequirement.cs b/src/Umbraco.Web.BackOffice/Authorization/SectionRequirement.cs index 033eccdd18..a7e3aa5e2c 100644 --- a/src/Umbraco.Web.BackOffice/Authorization/SectionRequirement.cs +++ b/src/Umbraco.Web.BackOffice/Authorization/SectionRequirement.cs @@ -1,5 +1,8 @@ -using Microsoft.AspNetCore.Authorization; +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System.Collections.Generic; +using Microsoft.AspNetCore.Authorization; namespace Umbraco.Web.BackOffice.Authorization { @@ -9,10 +12,14 @@ namespace Umbraco.Web.BackOffice.Authorization public class SectionRequirement : IAuthorizationRequirement { /// - /// The aliases for sections that the user will need access to + /// Initializes a new instance of the class. + /// + /// Aliases for sections that the user will need access to. + public SectionRequirement(params string[] aliases) => SectionAliases = aliases; + + /// + /// Gets the aliases for sections that the user will need access to. /// public IReadOnlyCollection SectionAliases { get; } - - public SectionRequirement(params string[] aliases) => SectionAliases = aliases; } } diff --git a/src/Umbraco.Web.BackOffice/Authorization/TreeHandler.cs b/src/Umbraco.Web.BackOffice/Authorization/TreeHandler.cs index f151247850..f847f0981d 100644 --- a/src/Umbraco.Web.BackOffice/Authorization/TreeHandler.cs +++ b/src/Umbraco.Web.BackOffice/Authorization/TreeHandler.cs @@ -1,14 +1,16 @@ -using Microsoft.AspNetCore.Authorization; +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; using System.Linq; -using Umbraco.Core; using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Umbraco.Core; using Umbraco.Core.Security; using Umbraco.Web.Services; namespace Umbraco.Web.BackOffice.Authorization { - /// /// Ensures that the current user has access to the section for which the specified tree(s) belongs /// @@ -18,40 +20,35 @@ namespace Umbraco.Web.BackOffice.Authorization /// public class TreeHandler : MustSatisfyRequirementAuthorizationHandler { - private readonly ITreeService _treeService; - private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor; + private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; /// - /// Constructor to set authorization to be based on a tree alias for which application security will be applied + /// Initializes a new instance of the class. /// - /// - /// - /// - /// If the user has access to the application that the treeAlias is specified in, they will be authorized. - /// Multiple trees may be specified. - /// - public TreeHandler(ITreeService treeService, IBackOfficeSecurityAccessor backofficeSecurityAccessor) + /// Service for section tree operations. + /// Accessor for back-office security. + public TreeHandler(ITreeService treeService, IBackOfficeSecurityAccessor backOfficeSecurityAccessor) { _treeService = treeService ?? throw new ArgumentNullException(nameof(treeService)); - _backofficeSecurityAccessor = backofficeSecurityAccessor ?? throw new ArgumentNullException(nameof(backofficeSecurityAccessor)); + _backOfficeSecurityAccessor = backOfficeSecurityAccessor ?? throw new ArgumentNullException(nameof(backOfficeSecurityAccessor)); } + /// protected override Task IsAuthorized(AuthorizationHandlerContext context, TreeRequirement requirement) { - var apps = requirement.TreeAliases.Select(x => _treeService - .GetByAlias(x)) + var apps = requirement.TreeAliases + .Select(x => _treeService.GetByAlias(x)) .WhereNotNull() .Select(x => x.SectionAlias) .Distinct() .ToArray(); - var isAuth = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser != null - && apps.Any(app => _backofficeSecurityAccessor.BackOfficeSecurity.UserHasSectionAccess( - app, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser)); + var isAuth = _backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser != null && + apps.Any(app => _backOfficeSecurityAccessor.BackOfficeSecurity.UserHasSectionAccess( + app, _backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser)); return Task.FromResult(isAuth); } - } } diff --git a/src/Umbraco.Web.BackOffice/Authorization/TreeRequirement.cs b/src/Umbraco.Web.BackOffice/Authorization/TreeRequirement.cs index 7261cee51b..1d8671d3c9 100644 --- a/src/Umbraco.Web.BackOffice/Authorization/TreeRequirement.cs +++ b/src/Umbraco.Web.BackOffice/Authorization/TreeRequirement.cs @@ -1,19 +1,25 @@ -using Microsoft.AspNetCore.Authorization; +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System.Collections.Generic; +using Microsoft.AspNetCore.Authorization; namespace Umbraco.Web.BackOffice.Authorization { - /// /// Authorization requirements for /// public class TreeRequirement : IAuthorizationRequirement { /// - /// The aliases for trees that the user will need access to + /// Initializes a new instance of the class. + /// + /// The aliases for trees that the user will need access to. + public TreeRequirement(params string[] aliases) => TreeAliases = aliases; + + /// + /// Gets the aliases for trees that the user will need access to. /// public IReadOnlyCollection TreeAliases { get; } - - public TreeRequirement(params string[] aliases) => TreeAliases = aliases; } } diff --git a/src/Umbraco.Web.BackOffice/Authorization/UserGroupHandler.cs b/src/Umbraco.Web.BackOffice/Authorization/UserGroupHandler.cs index a693bc592b..92045fcdfb 100644 --- a/src/Umbraco.Web.BackOffice/Authorization/UserGroupHandler.cs +++ b/src/Umbraco.Web.BackOffice/Authorization/UserGroupHandler.cs @@ -1,8 +1,14 @@ -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Primitives; using Umbraco.Core; +using Umbraco.Core.Models.Membership; using Umbraco.Core.Security; using Umbraco.Core.Services; using Umbraco.Web.BackOffice.Controllers; @@ -14,40 +20,51 @@ namespace Umbraco.Web.BackOffice.Authorization /// public class UserGroupHandler : MustSatisfyRequirementAuthorizationHandler { - private readonly IHttpContextAccessor _httpContextAcessor; + private readonly IHttpContextAccessor _httpContextAccessor; private readonly IUserService _userService; private readonly IContentService _contentService; private readonly IMediaService _mediaService; private readonly IEntityService _entityService; - private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor; + private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; - public UserGroupHandler(IHttpContextAccessor httpContextAcessor, - IUserService userService, - IContentService contentService, - IMediaService mediaService, - IEntityService entityService, - IBackOfficeSecurityAccessor backofficeSecurityAccessor) + /// + /// Initializes a new instance of the class. + /// + /// Accessor for the HTTP context of the current request. + /// Service for user related operations. + /// Service for content related operations. + /// Service for media related operations. + /// Service for entity related operations. + /// Accessor for back-office security. + public UserGroupHandler( + IHttpContextAccessor httpContextAccessor, + IUserService userService, + IContentService contentService, + IMediaService mediaService, + IEntityService entityService, + IBackOfficeSecurityAccessor backOfficeSecurityAccessor) { - _httpContextAcessor = httpContextAcessor; + _httpContextAccessor = httpContextAccessor; _userService = userService; _contentService = contentService; _mediaService = mediaService; _entityService = entityService; - _backofficeSecurityAccessor = backofficeSecurityAccessor; + _backOfficeSecurityAccessor = backOfficeSecurityAccessor; } + /// protected override Task IsAuthorized(AuthorizationHandlerContext context, UserGroupRequirement requirement) { - var currentUser = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser; + IUser currentUser = _backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser; - var queryString = _httpContextAcessor.HttpContext?.Request.Query; + IQueryCollection queryString = _httpContextAccessor.HttpContext?.Request.Query; if (queryString == null) { // Must succeed this requirement since we cannot process it. return Task.FromResult(true); - } + } - var ids = queryString.Where(x => x.Key == requirement.QueryStringName).ToArray(); + KeyValuePair[] ids = queryString.Where(x => x.Key == requirement.QueryStringName).ToArray(); if (ids.Length == 0) { // Must succeed this requirement since we cannot process it. @@ -64,10 +81,9 @@ namespace Umbraco.Web.BackOffice.Authorization _mediaService, _entityService); - var isAuth = authHelper.AuthorizeGroupAccess(currentUser, intIds); + Attempt isAuth = authHelper.AuthorizeGroupAccess(currentUser, intIds); return Task.FromResult(isAuth.Success); } - } } diff --git a/src/Umbraco.Web.BackOffice/Authorization/UserGroupRequirement.cs b/src/Umbraco.Web.BackOffice/Authorization/UserGroupRequirement.cs index aae5733d96..2a14bb1a78 100644 --- a/src/Umbraco.Web.BackOffice/Authorization/UserGroupRequirement.cs +++ b/src/Umbraco.Web.BackOffice/Authorization/UserGroupRequirement.cs @@ -1,4 +1,7 @@ -using Microsoft.AspNetCore.Authorization; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Microsoft.AspNetCore.Authorization; namespace Umbraco.Web.BackOffice.Authorization { @@ -7,11 +10,15 @@ namespace Umbraco.Web.BackOffice.Authorization /// public class UserGroupRequirement : IAuthorizationRequirement { - public UserGroupRequirement(string queryStringName = "id") - { - QueryStringName = queryStringName; - } + /// + /// Initializes a new instance of the class. + /// + /// Query string name from which to authorize values. + public UserGroupRequirement(string queryStringName = "id") => QueryStringName = queryStringName; + /// + /// Gets the query string name from which to authorize values. + /// public string QueryStringName { get; } } } diff --git a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs index efe28763f1..36e5c2b6fe 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs @@ -1,10 +1,10 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Security.Claims; using System.Threading.Tasks; -using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; @@ -12,7 +12,6 @@ using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Core; -using Umbraco.Core.BackOffice; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Mapping; using Umbraco.Core.Models; @@ -26,6 +25,7 @@ using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.BackOffice.Security; using Umbraco.Web.Common.ActionsResults; using Umbraco.Web.Common.Attributes; +using Umbraco.Web.Common.Authorization; using Umbraco.Web.Common.Controllers; using Umbraco.Web.Common.Exceptions; using Umbraco.Web.Common.Filters; @@ -33,8 +33,6 @@ using Umbraco.Web.Common.Security; using Umbraco.Web.Models; using Umbraco.Web.Models.ContentEditing; using Constants = Umbraco.Core.Constants; -using Microsoft.AspNetCore.Authorization; -using Umbraco.Web.Common.Authorization; namespace Umbraco.Web.BackOffice.Controllers { @@ -392,7 +390,7 @@ namespace Umbraco.Web.BackOffice.Controllers await _emailSender.SendAsync(mailMessage); - _userManager.RaiseForgotPasswordRequestedEvent(User, user.Id); + _userManager.RaiseForgotPasswordRequestedEvent(User, user.Id.ToString()); } } @@ -556,7 +554,7 @@ namespace Umbraco.Web.BackOffice.Controllers } } - _userManager.RaiseForgotPasswordChangedSuccessEvent(User, model.UserId); + _userManager.RaiseForgotPasswordChangedSuccessEvent(User, model.UserId.ToString()); return Ok(); } @@ -579,7 +577,7 @@ namespace Umbraco.Web.BackOffice.Controllers _logger.LogInformation("User {UserName} from IP address {RemoteIpAddress} has logged out", User.Identity == null ? "UNKNOWN" : User.Identity.Name, HttpContext.Connection.RemoteIpAddress); - var userId = int.Parse(result.Principal.Identity.GetUserId()); + var userId = result.Principal.Identity.GetUserId(); var args = _userManager.RaiseLogoutSuccessEvent(User, userId); if (!args.SignOutRedirectUrl.IsNullOrWhiteSpace()) { @@ -610,10 +608,12 @@ namespace Umbraco.Web.BackOffice.Controllers return userDetail; } - private string ConstructCallbackUrl(int userId, string code) + private string ConstructCallbackUrl(string userId, string code) { // Get an mvc helper to get the url - var action = _linkGenerator.GetPathByAction(nameof(BackOfficeController.ValidatePasswordResetCode), ControllerExtensions.GetControllerName(), + var action = _linkGenerator.GetPathByAction( + nameof(BackOfficeController.ValidatePasswordResetCode), + ControllerExtensions.GetControllerName(), new { area = Constants.Web.Mvc.BackOfficeArea, diff --git a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs index 1ce0831502..19fb6aa2df 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs @@ -1,15 +1,19 @@ -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; +using System.Security.Claims; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Options; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Umbraco.Core; -using Umbraco.Core.BackOffice; using Umbraco.Core.Cache; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Grid; @@ -22,21 +26,16 @@ using Umbraco.Core.WebAssets; using Umbraco.Extensions; using Umbraco.Web.BackOffice.ActionResults; using Umbraco.Web.BackOffice.Filters; +using Umbraco.Web.BackOffice.Security; +using Umbraco.Web.Common.ActionsResults; using Umbraco.Web.Common.Attributes; +using Umbraco.Web.Common.Authorization; using Umbraco.Web.Common.Filters; using Umbraco.Web.Common.Security; using Umbraco.Web.Models; using Umbraco.Web.Mvc; using Umbraco.Web.WebAssets; using Constants = Umbraco.Core.Constants; -using Microsoft.AspNetCore.Identity; -using System.Security.Claims; -using Microsoft.AspNetCore.Http; -using Umbraco.Web.BackOffice.Security; -using Umbraco.Web.Common.ActionsResults; -using Microsoft.AspNetCore.Authorization; -using Umbraco.Web.Common.Authorization; -using Microsoft.AspNetCore.Authentication; namespace Umbraco.Web.BackOffice.Controllers { @@ -434,7 +433,7 @@ namespace Umbraco.Web.BackOffice.Controllers if (result == Microsoft.AspNetCore.Identity.SignInResult.Success) { - } + } else if (result == Microsoft.AspNetCore.Identity.SignInResult.TwoFactorRequired) { diff --git a/src/Umbraco.Web.BackOffice/Controllers/CurrentUserController.cs b/src/Umbraco.Web.BackOffice/Controllers/CurrentUserController.cs index 7c984e901e..d156551c26 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/CurrentUserController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/CurrentUserController.cs @@ -1,15 +1,15 @@ -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Options; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Newtonsoft.Json; using Umbraco.Core; -using Umbraco.Core.BackOffice; using Umbraco.Core.Cache; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Hosting; @@ -23,12 +23,10 @@ using Umbraco.Extensions; using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.BackOffice.Security; using Umbraco.Web.Common.Attributes; +using Umbraco.Web.Common.Authorization; using Umbraco.Web.Common.Exceptions; -using Umbraco.Web.Common.Filters; using Umbraco.Web.Models; using Umbraco.Web.Models.ContentEditing; -using Microsoft.AspNetCore.Authorization; -using Umbraco.Web.Common.Authorization; namespace Umbraco.Web.BackOffice.Controllers { diff --git a/src/Umbraco.Web.BackOffice/Controllers/UmbracoAuthorizedApiController.cs b/src/Umbraco.Web.BackOffice/Controllers/UmbracoAuthorizedApiController.cs index a807c663d0..9c415fe180 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/UmbracoAuthorizedApiController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/UmbracoAuthorizedApiController.cs @@ -1,4 +1,4 @@ -using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.Common.Attributes; @@ -16,7 +16,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// is logged in using forms authentication which indicates the seconds remaining /// before their timeout expires. /// - [IsBackOffice] + [IsBackOffice] [UmbracoUserTimeoutFilter] [Authorize(Policy = AuthorizationPolicies.BackOfficeAccess)] [DisableBrowserCache] diff --git a/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs b/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs index c31cd68707..9d7999b9f7 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -6,13 +6,13 @@ using System.Net; using System.Runtime.Serialization; using System.Security.Cryptography; using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; -using Microsoft.Extensions.Options; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Umbraco.Core; -using Umbraco.Core.BackOffice; using Umbraco.Core.Cache; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; @@ -26,23 +26,21 @@ using Umbraco.Core.Persistence; using Umbraco.Core.Security; using Umbraco.Core.Services; using Umbraco.Core.Strings; -using Umbraco.Web.Models; -using Umbraco.Web.Models.ContentEditing; using Umbraco.Extensions; +using Umbraco.Web.BackOffice.ActionResults; using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.BackOffice.ModelBinders; using Umbraco.Web.BackOffice.Security; -using Umbraco.Web.BackOffice.ActionResults; +using Umbraco.Web.Common.ActionsResults; using Umbraco.Web.Common.Attributes; +using Umbraco.Web.Common.Authorization; using Umbraco.Web.Common.Exceptions; using Umbraco.Web.Editors; +using Umbraco.Web.Models; +using Umbraco.Web.Models.ContentEditing; using Constants = Umbraco.Core.Constants; using IUser = Umbraco.Core.Models.Membership.IUser; using Task = System.Threading.Tasks.Task; -using Umbraco.Net; -using Umbraco.Web.Common.ActionsResults; -using Microsoft.AspNetCore.Authorization; -using Umbraco.Web.Common.Authorization; namespace Umbraco.Web.BackOffice.Controllers { diff --git a/src/Umbraco.Web.BackOffice/Security/AuthenticationBuilderExtensions.cs b/src/Umbraco.Web.BackOffice/Extensions/AuthenticationBuilderExtensions.cs similarity index 86% rename from src/Umbraco.Web.BackOffice/Security/AuthenticationBuilderExtensions.cs rename to src/Umbraco.Web.BackOffice/Extensions/AuthenticationBuilderExtensions.cs index 9949018d43..8145cb4278 100644 --- a/src/Umbraco.Web.BackOffice/Security/AuthenticationBuilderExtensions.cs +++ b/src/Umbraco.Web.BackOffice/Extensions/AuthenticationBuilderExtensions.cs @@ -1,7 +1,8 @@ using System; using Umbraco.Core.DependencyInjection; +using Umbraco.Web.BackOffice.Security; -namespace Umbraco.Web.BackOffice.Security +namespace Umbraco.Extensions { public static class AuthenticationBuilderExtensions { diff --git a/src/Umbraco.Web.BackOffice/Extensions/BackOfficeApplicationBuilderExtensions.cs b/src/Umbraco.Web.BackOffice/Extensions/BackOfficeApplicationBuilderExtensions.cs index a097ead4a1..6ff42a5737 100644 --- a/src/Umbraco.Web.BackOffice/Extensions/BackOfficeApplicationBuilderExtensions.cs +++ b/src/Umbraco.Web.BackOffice/Extensions/BackOfficeApplicationBuilderExtensions.cs @@ -2,7 +2,6 @@ using System; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using SixLabors.ImageSharp.Web.DependencyInjection; -using Umbraco.Core.BackOffice; using Umbraco.Web.BackOffice.Middleware; using Umbraco.Web.BackOffice.Routing; using Umbraco.Web.Common.Security; diff --git a/src/Umbraco.Web.BackOffice/Extensions/BackOfficeServiceCollectionExtensions.cs b/src/Umbraco.Web.BackOffice/Extensions/BackOfficeServiceCollectionExtensions.cs index 413a54a28b..9ad448a603 100644 --- a/src/Umbraco.Web.BackOffice/Extensions/BackOfficeServiceCollectionExtensions.cs +++ b/src/Umbraco.Web.BackOffice/Extensions/BackOfficeServiceCollectionExtensions.cs @@ -1,18 +1,13 @@ -using System; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; -using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Umbraco.Core; -using Umbraco.Core.BackOffice; using Umbraco.Core.Security; using Umbraco.Core.Serialization; -using Umbraco.Infrastructure.BackOffice; using Umbraco.Net; using Umbraco.Web.Actions; using Umbraco.Web.BackOffice.Authorization; -using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.BackOffice.Security; using Umbraco.Web.Common.AspNetCore; using Umbraco.Web.Common.Authorization; @@ -36,7 +31,7 @@ namespace Umbraco.Extensions .AddUserStore() .AddUserManager() .AddSignInManager() - .AddClaimsPrincipalFactory>(); + .AddClaimsPrincipalFactory(); // Configure the options specifically for the UmbracoBackOfficeIdentityOptions instance services.ConfigureOptions(); diff --git a/src/Umbraco.Web.BackOffice/Extensions/IdentityBuilderExtensions.cs b/src/Umbraco.Web.BackOffice/Extensions/IdentityBuilderExtensions.cs index ddf46a24a7..e6385e6bf9 100644 --- a/src/Umbraco.Web.BackOffice/Extensions/IdentityBuilderExtensions.cs +++ b/src/Umbraco.Web.BackOffice/Extensions/IdentityBuilderExtensions.cs @@ -1,6 +1,6 @@ using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.DependencyInjection; -using Umbraco.Core.BackOffice; +using Umbraco.Core.Security; namespace Umbraco.Extensions { diff --git a/src/Umbraco.Web.BackOffice/Extensions/WebMappingProfiles.cs b/src/Umbraco.Web.BackOffice/Extensions/WebMappingProfiles.cs index 4cd9cf5823..eeb0903e88 100644 --- a/src/Umbraco.Web.BackOffice/Extensions/WebMappingProfiles.cs +++ b/src/Umbraco.Web.BackOffice/Extensions/WebMappingProfiles.cs @@ -1,8 +1,5 @@ -using Microsoft.Extensions.DependencyInjection; -using Umbraco.Core; -using Umbraco.Core.BackOffice; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Core.DependencyInjection; -using Umbraco.Core.Composing; using Umbraco.Core.Mapping; using Umbraco.Web.BackOffice.Mapping; diff --git a/src/Umbraco.Web.BackOffice/Filters/CheckIfUserTicketDataIsStaleAttribute.cs b/src/Umbraco.Web.BackOffice/Filters/CheckIfUserTicketDataIsStaleAttribute.cs index 9cfaae6980..6e14468a2a 100644 --- a/src/Umbraco.Web.BackOffice/Filters/CheckIfUserTicketDataIsStaleAttribute.cs +++ b/src/Umbraco.Web.BackOffice/Filters/CheckIfUserTicketDataIsStaleAttribute.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Security.Claims; using System.Threading.Tasks; @@ -7,12 +8,12 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.Options; using Umbraco.Core; -using Umbraco.Core.BackOffice; using Umbraco.Core.Cache; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Mapping; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; +using Umbraco.Core.Security; using Umbraco.Core.Services; using Umbraco.Extensions; using Umbraco.Web.BackOffice.Security; @@ -20,9 +21,13 @@ using Umbraco.Web.Common.Security; namespace Umbraco.Web.BackOffice.Filters { + /// + /// + /// internal sealed class CheckIfUserTicketDataIsStaleAttribute : TypeFilterAttribute { - public CheckIfUserTicketDataIsStaleAttribute() : base(typeof(CheckIfUserTicketDataIsStaleFilter)) + public CheckIfUserTicketDataIsStaleAttribute() + : base(typeof(CheckIfUserTicketDataIsStaleFilter)) { } @@ -66,9 +71,11 @@ namespace Umbraco.Web.BackOffice.Filters await CheckStaleData(actionContext); - //return if nothing is updated + // return if nothing is updated if (_requestCache.Get(nameof(CheckIfUserTicketDataIsStaleFilter)) is null) + { return; + } await UpdateTokensAndAppendCustomHeaders(actionContext); } @@ -81,7 +88,7 @@ namespace Umbraco.Web.BackOffice.Filters await tokenFilter.OnActionExecutionAsync(actionContext, () => Task.FromResult(new ActionExecutedContext(actionContext, new List(), null))); - //add the header + // add the header AppendUserModifiedHeaderAttribute.AppendHeader(actionContext); } @@ -93,26 +100,37 @@ namespace Umbraco.Web.BackOffice.Filters return; } - //don't execute if it's already been done + // don't execute if it's already been done if (!(_requestCache.Get(nameof(CheckIfUserTicketDataIsStaleFilter)) is null)) + { return; + } var identity = actionContext.HttpContext.User.Identity as UmbracoBackOfficeIdentity; - if (identity == null) return; + if (identity == null) + { + return; + } - var userId = identity.Id.TryConvertTo(); - if (userId == false) return; + Attempt userId = identity.Id.TryConvertTo(); + if (userId == false) + { + return; + } - var user = _userService.GetUserById(userId.Result); - if (user == null) return; + IUser user = _userService.GetUserById(userId.Result); + if (user == null) + { + return; + } - //a list of checks to execute, if any of them pass then we resync + // a list of checks to execute, if any of them pass then we resync var checks = new Func[] { () => user.Username != identity.Username, () => { - var culture = user.GetUserCulture(_localizedTextService, _globalSettings.Value); + CultureInfo culture = user.GetUserCulture(_localizedTextService, _globalSettings.Value); return culture != null && culture.ToString() != identity.Culture; }, () => user.AllowedSections.UnsortedSequenceEqual(identity.AllowedApplications) == false, @@ -138,18 +156,15 @@ namespace Umbraco.Web.BackOffice.Filters /// /// This will update the current request IPrincipal to be correct and re-create the auth ticket /// - /// - /// - /// private async Task ReSync(IUser user, ActionExecutingContext actionContext) { - var backOfficeIdentityUser = _umbracoMapper.Map(user); + BackOfficeIdentityUser backOfficeIdentityUser = _umbracoMapper.Map(user); await _backOfficeSignInManager.SignInAsync(backOfficeIdentityUser, isPersistent: true); - //ensure the remainder of the request has the correct principal set + // ensure the remainder of the request has the correct principal set actionContext.HttpContext.SetPrincipalForRequest(ClaimsPrincipal.Current); - //flag that we've made changes + // flag that we've made changes _requestCache.Set(nameof(CheckIfUserTicketDataIsStaleFilter), true); } } diff --git a/src/Umbraco.Web.BackOffice/Filters/ValidateAngularAntiForgeryTokenAttribute.cs b/src/Umbraco.Web.BackOffice/Filters/ValidateAngularAntiForgeryTokenAttribute.cs index f7fd174e03..ef8e22c9d8 100644 --- a/src/Umbraco.Web.BackOffice/Filters/ValidateAngularAntiForgeryTokenAttribute.cs +++ b/src/Umbraco.Web.BackOffice/Filters/ValidateAngularAntiForgeryTokenAttribute.cs @@ -1,4 +1,4 @@ -using System.Linq; +using System.Linq; using System.Net; using System.Security.Claims; using System.Threading.Tasks; @@ -23,7 +23,8 @@ namespace Umbraco.Web.BackOffice.Filters /// public sealed class ValidateAngularAntiForgeryTokenAttribute : TypeFilterAttribute { - public ValidateAngularAntiForgeryTokenAttribute() : base(typeof(ValidateAngularAntiForgeryTokenFilter)) + public ValidateAngularAntiForgeryTokenAttribute() + : base(typeof(ValidateAngularAntiForgeryTokenFilter)) { } @@ -44,12 +45,13 @@ namespace Umbraco.Web.BackOffice.Filters { if (context.Controller is ControllerBase controller && controller.User.Identity is ClaimsIdentity userIdentity) { - //if there is not CookiePath claim, then exit + // if there is not CookiePath claim, then exit if (userIdentity.HasClaim(x => x.Type == ClaimTypes.CookiePath) == false) { await next(); } } + var cookieToken = _cookieManager.GetCookieValue(Constants.Web.CsrfValidationCookieName); var httpContext = context.HttpContext; diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeAuthenticationBuilder.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeAuthenticationBuilder.cs index b3418697e2..7012d5f1dd 100644 --- a/src/Umbraco.Web.BackOffice/Security/BackOfficeAuthenticationBuilder.cs +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeAuthenticationBuilder.cs @@ -1,4 +1,4 @@ -using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeCookieManager.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeCookieManager.cs index 8664713c72..513bbd255c 100644 --- a/src/Umbraco.Web.BackOffice/Security/BackOfficeCookieManager.cs +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeCookieManager.cs @@ -25,31 +25,33 @@ namespace Umbraco.Web.BackOffice.Security private readonly IRuntimeState _runtime; private readonly IHostingEnvironment _hostingEnvironment; private readonly GlobalSettings _globalSettings; - private readonly IRequestCache _requestCache; private readonly string[] _explicitPaths; + /// + /// Initializes a new instance of the class. + /// public BackOfficeCookieManager( IUmbracoContextAccessor umbracoContextAccessor, IRuntimeState runtime, IHostingEnvironment hostingEnvironment, - GlobalSettings globalSettings, - IRequestCache requestCache) - : this(umbracoContextAccessor, runtime, hostingEnvironment, globalSettings, requestCache, null) + GlobalSettings globalSettings) + : this(umbracoContextAccessor, runtime, hostingEnvironment, globalSettings, null) { } + /// + /// Initializes a new instance of the class. + /// public BackOfficeCookieManager( IUmbracoContextAccessor umbracoContextAccessor, IRuntimeState runtime, IHostingEnvironment hostingEnvironment, GlobalSettings globalSettings, - IRequestCache requestCache, IEnumerable explicitPaths) { _umbracoContextAccessor = umbracoContextAccessor; _runtime = runtime; _hostingEnvironment = hostingEnvironment; _globalSettings = globalSettings; - _requestCache = requestCache; _explicitPaths = explicitPaths?.ToArray(); } @@ -57,7 +59,6 @@ namespace Umbraco.Web.BackOffice.Security /// Determines if we should authenticate the request /// /// The to check - /// true to check if the has been assigned in the request. /// true if the request should be authenticated /// /// We auth the request when: @@ -65,7 +66,7 @@ namespace Umbraco.Web.BackOffice.Security /// * it is an installer request /// * it is a preview request /// - public bool ShouldAuthenticateRequest(Uri requestUri, bool checkForceAuthTokens = true) + public bool ShouldAuthenticateRequest(Uri requestUri) { // Do not authenticate the request if we are not running (don't have a db, are not configured) - since we will never need // to know a current user in this scenario - we treat it as a new install. Without this we can have some issues @@ -84,11 +85,8 @@ namespace Umbraco.Web.BackOffice.Security return _explicitPaths.Any(x => x.InvariantEquals(requestUri.AbsolutePath)); } - if (// check the explicit flag - (checkForceAuthTokens && _requestCache.IsAvailable && _requestCache.Get(Constants.Security.ForceReAuthFlag) != null) - - // check back office - || requestUri.IsBackOfficeRequest(_globalSettings, _hostingEnvironment) + if (// check back office + requestUri.IsBackOfficeRequest(_globalSettings, _hostingEnvironment) // check installer || requestUri.IsInstallerRequest(_hostingEnvironment)) diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficePasswordHasher.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficePasswordHasher.cs index 6c61e7bb35..65f1a7f5bc 100644 --- a/src/Umbraco.Web.BackOffice/Security/BackOfficePasswordHasher.cs +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficePasswordHasher.cs @@ -1,5 +1,4 @@ using Microsoft.AspNetCore.Identity; -using Umbraco.Core.BackOffice; using Umbraco.Core.Security; using Umbraco.Core; using Umbraco.Core.Models.Membership; diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeSecureDataFormat.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeSecureDataFormat.cs index 91b982b5f6..377801a0b7 100644 --- a/src/Umbraco.Web.BackOffice/Security/BackOfficeSecureDataFormat.cs +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeSecureDataFormat.cs @@ -1,7 +1,7 @@ using Microsoft.AspNetCore.Authentication; using System; using System.Security.Claims; -using Umbraco.Core.BackOffice; +using Umbraco.Core.Security; namespace Umbraco.Web.BackOffice.Security { diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeSecurityStampValidator.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeSecurityStampValidator.cs index f12b6279bb..abd0af1353 100644 --- a/src/Umbraco.Web.BackOffice/Security/BackOfficeSecurityStampValidator.cs +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeSecurityStampValidator.cs @@ -5,7 +5,7 @@ using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using Umbraco.Core.BackOffice; +using Umbraco.Core.Security; using Umbraco.Web.Common.Security; namespace Umbraco.Web.BackOffice.Security diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeSessionIdValidator.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeSessionIdValidator.cs index b5974c870a..1ccb94e988 100644 --- a/src/Umbraco.Web.BackOffice/Security/BackOfficeSessionIdValidator.cs +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeSessionIdValidator.cs @@ -1,4 +1,4 @@ - + using System; using System.Security.Claims; using System.Threading.Tasks; @@ -7,9 +7,9 @@ using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Options; using Umbraco.Core; -using Umbraco.Core.BackOffice; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Hosting; +using Umbraco.Core.Security; using Umbraco.Extensions; namespace Umbraco.Web.BackOffice.Security diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeSignInManager.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeSignInManager.cs index e17067daa0..6d1c348d7f 100644 --- a/src/Umbraco.Web.BackOffice/Security/BackOfficeSignInManager.cs +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeSignInManager.cs @@ -9,7 +9,6 @@ using System.Linq; using System.Security.Claims; using System.Threading.Tasks; using Umbraco.Core; -using Umbraco.Core.BackOffice; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Security; diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManager.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManager.cs deleted file mode 100644 index 464f2a38aa..0000000000 --- a/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManager.cs +++ /dev/null @@ -1,487 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Security.Principal; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Identity; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Umbraco.Core; -using Umbraco.Core.BackOffice; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.Models; -using Umbraco.Core.Models.Membership; -using Umbraco.Core.Security; -using Umbraco.Extensions; -using Umbraco.Net; -using Umbraco.Web.Models.ContentEditing; - - -namespace Umbraco.Web.Common.Security -{ - - public class BackOfficeUserManager : BackOfficeUserManager, IBackOfficeUserManager - { - public BackOfficeUserManager( - IIpResolver ipResolver, - IUserStore store, - IOptions optionsAccessor, - IPasswordHasher passwordHasher, - IEnumerable> userValidators, - IEnumerable> passwordValidators, - BackOfficeLookupNormalizer keyNormalizer, - BackOfficeIdentityErrorDescriber errors, - IServiceProvider services, - IHttpContextAccessor httpContextAccessor, - ILogger> logger, - IOptions passwordConfiguration) - : base(ipResolver, store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, httpContextAccessor, logger, passwordConfiguration) - { - } - } - - public class BackOfficeUserManager : UserManager - where T : BackOfficeIdentityUser - { - private PasswordGenerator _passwordGenerator; - private readonly IHttpContextAccessor _httpContextAccessor; - - public BackOfficeUserManager( - IIpResolver ipResolver, - IUserStore store, - IOptions optionsAccessor, - IPasswordHasher passwordHasher, - IEnumerable> userValidators, - IEnumerable> passwordValidators, - BackOfficeLookupNormalizer keyNormalizer, - BackOfficeIdentityErrorDescriber errors, - IServiceProvider services, - IHttpContextAccessor httpContextAccessor, - ILogger> logger, - IOptions passwordConfiguration) - : base(store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, logger) - { - IpResolver = ipResolver ?? throw new ArgumentNullException(nameof(ipResolver)); - _httpContextAccessor = httpContextAccessor; - PasswordConfiguration = passwordConfiguration.Value ?? throw new ArgumentNullException(nameof(passwordConfiguration)); - } - - #region What we do not currently support - - // We don't support an IUserClaimStore and don't need to (at least currently) - public override bool SupportsUserClaim => false; - - // It would be nice to support this but we don't need to currently and that would require IQueryable support for our user service/repository - public override bool SupportsQueryableUsers => false; - - /// - /// Developers will need to override this to support custom 2 factor auth - /// - public override bool SupportsUserTwoFactor => false; - - // We haven't needed to support this yet, though might be necessary for 2FA - public override bool SupportsUserPhoneNumber => false; - - #endregion - - /// - /// Replace the underlying options property with our own strongly typed version - /// - public new BackOfficeIdentityOptions Options - { - get => (BackOfficeIdentityOptions)base.Options; - set => base.Options = value; - } - - /// - /// Used to validate a user's session - /// - /// - /// - /// - public virtual async Task ValidateSessionIdAsync(string userId, string sessionId) - { - var userSessionStore = Store as IUserSessionStore; - //if this is not set, for backwards compat (which would be super rare), we'll just approve it - if (userSessionStore == null) return true; - - return await userSessionStore.ValidateSessionIdAsync(userId, sessionId); - } - - /// - /// This will determine which password hasher to use based on what is defined in config - /// - /// - protected virtual IPasswordHasher GetDefaultPasswordHasher(IPasswordConfiguration passwordConfiguration) - { - // we can use the user aware password hasher (which will be the default and preferred way) - return new PasswordHasher(); - } - - /// - /// Gets/sets the default back office user password checker - /// - public IBackOfficeUserPasswordChecker BackOfficeUserPasswordChecker { get; set; } - public IPasswordConfiguration PasswordConfiguration { get; protected set; } - public IIpResolver IpResolver { get; } - - /// - /// Helper method to generate a password for a user based on the current password validator - /// - /// - public string GeneratePassword() - { - if (_passwordGenerator == null) _passwordGenerator = new PasswordGenerator(PasswordConfiguration); - var password = _passwordGenerator.GeneratePassword(); - return password; - } - - /// - /// Override to check the user approval value as well as the user lock out date, by default this only checks the user's locked out date - /// - /// The user - /// True if the user is locked out, else false - /// - /// In the ASP.NET Identity world, there is only one value for being locked out, in Umbraco we have 2 so when checking this for Umbraco we need to check both values - /// - public override async Task IsLockedOutAsync(T user) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - if (user.IsApproved == false) - { - return true; - } - - return await base.IsLockedOutAsync(user); - } - - #region Overrides for password logic - - /// - /// Logic used to validate a username and password - /// - /// - /// - /// - /// - /// By default this uses the standard ASP.Net Identity approach which is: - /// * Get password store - /// * Call VerifyPasswordAsync with the password store + user + password - /// * Uses the PasswordHasher.VerifyHashedPassword to compare the stored password - /// - /// In some cases people want simple custom control over the username/password check, for simplicity - /// sake, developers would like the users to simply validate against an LDAP directory but the user - /// data remains stored inside of Umbraco. - /// See: http://issues.umbraco.org/issue/U4-7032 for the use cases. - /// - /// We've allowed this check to be overridden with a simple callback so that developers don't actually - /// have to implement/override this class. - /// - public override async Task CheckPasswordAsync(T user, string password) - { - if (BackOfficeUserPasswordChecker != null) - { - var result = await BackOfficeUserPasswordChecker.CheckPasswordAsync(user, password); - - if (user.HasIdentity == false) - { - return false; - } - - //if the result indicates to not fallback to the default, then return true if the credentials are valid - if (result != BackOfficeUserPasswordCheckerResult.FallbackToDefaultChecker) - { - return result == BackOfficeUserPasswordCheckerResult.ValidCredentials; - } - } - - //we cannot proceed if the user passed in does not have an identity - if (user.HasIdentity == false) - return false; - - //use the default behavior - return await base.CheckPasswordAsync(user, password); - } - - /// - /// This is a special method that will reset the password but will raise the Password Changed event instead of the reset event - /// - /// - /// - /// - /// - /// - /// We use this because in the back office the only way an admin can change another user's password without first knowing their password - /// is to generate a token and reset it, however, when we do this we want to track a password change, not a password reset - /// - public async Task ChangePasswordWithResetAsync(int userId, string token, string newPassword) - { - var user = await base.FindByIdAsync(userId.ToString()); - if (user == null) throw new InvalidOperationException("Could not find user"); - - var result = await base.ResetPasswordAsync(user, token, newPassword); - if (result.Succeeded) - { - RaisePasswordChangedEvent(_httpContextAccessor.HttpContext?.User, userId); - } - return result; - } - - public override async Task ChangePasswordAsync(T user, string currentPassword, string newPassword) - { - var result = await base.ChangePasswordAsync(user, currentPassword, newPassword); - if (result.Succeeded) - { - RaisePasswordChangedEvent(_httpContextAccessor.HttpContext?.User, user.Id); - } - return result; - } - - /// - /// Override to determine how to hash the password - /// - /// - /// - /// - /// - /// - /// This method is called anytime the password needs to be hashed for storage (i.e. including when reset password is used) - /// - protected override async Task UpdatePasswordHash(T user, string newPassword, bool validatePassword) - { - user.LastPasswordChangeDateUtc = DateTime.UtcNow; - - if (validatePassword) - { - var validate = await ValidatePasswordAsync(user, newPassword); - if (!validate.Succeeded) - { - return validate; - } - } - - var passwordStore = Store as IUserPasswordStore; - if (passwordStore == null) throw new NotSupportedException("The current user store does not implement " + typeof(IUserPasswordStore<>)); - - var hash = newPassword != null ? PasswordHasher.HashPassword(user, newPassword) : null; - await passwordStore.SetPasswordHashAsync(user, hash, CancellationToken); - await UpdateSecurityStampInternal(user); - return IdentityResult.Success; - } - - /// - /// This is copied from the underlying .NET base class since they decided to not expose it - /// - /// - /// - private async Task UpdateSecurityStampInternal(T user) - { - if (SupportsUserSecurityStamp == false) return; - await GetSecurityStore().SetSecurityStampAsync(user, NewSecurityStamp(), CancellationToken.None); - } - - /// - /// This is copied from the underlying .NET base class since they decided to not expose it - /// - /// - private IUserSecurityStampStore GetSecurityStore() - { - var store = Store as IUserSecurityStampStore; - if (store == null) throw new NotSupportedException("The current user store does not implement " + typeof(IUserSecurityStampStore<>)); - return store; - } - - /// - /// This is copied from the underlying .NET base class since they decided to not expose it - /// - /// - private static string NewSecurityStamp() - { - return Guid.NewGuid().ToString(); - } - - #endregion - - public override async Task SetLockoutEndDateAsync(T user, DateTimeOffset? lockoutEnd) - { - if (user == null) throw new ArgumentNullException(nameof(user)); - - var result = await base.SetLockoutEndDateAsync(user, lockoutEnd); - - // The way we unlock is by setting the lockoutEnd date to the current datetime - if (result.Succeeded && lockoutEnd >= DateTimeOffset.UtcNow) - { - RaiseAccountLockedEvent(_httpContextAccessor.HttpContext?.User, user.Id); - } - else - { - RaiseAccountUnlockedEvent(_httpContextAccessor.HttpContext?.User, user.Id); - //Resets the login attempt fails back to 0 when unlock is clicked - await ResetAccessFailedCountAsync(user); - } - - return result; - } - - public override async Task ResetAccessFailedCountAsync(T user) - { - if (user == null) throw new ArgumentNullException(nameof(user)); - - var lockoutStore = (IUserLockoutStore)Store; - var accessFailedCount = await GetAccessFailedCountAsync(user); - - if (accessFailedCount == 0) - return IdentityResult.Success; - - await lockoutStore.ResetAccessFailedCountAsync(user, CancellationToken.None); - //raise the event now that it's reset - RaiseResetAccessFailedCountEvent(_httpContextAccessor.HttpContext?.User, user.Id); - return await UpdateAsync(user); - } - - /// - /// Overrides the Microsoft ASP.NET user management method - /// - /// - /// - /// returns a Async Task - /// - /// - /// Doesn't set fail attempts back to 0 - /// - public override async Task AccessFailedAsync(T user) - { - if (user == null) throw new ArgumentNullException(nameof(user)); - - var lockoutStore = Store as IUserLockoutStore; - if (lockoutStore == null) throw new NotSupportedException("The current user store does not implement " + typeof(IUserLockoutStore<>)); - - var count = await lockoutStore.IncrementAccessFailedCountAsync(user, CancellationToken.None); - - if (count >= Options.Lockout.MaxFailedAccessAttempts) - { - await lockoutStore.SetLockoutEndDateAsync(user, DateTimeOffset.UtcNow.Add(Options.Lockout.DefaultLockoutTimeSpan), - CancellationToken.None); - //NOTE: in normal aspnet identity this would do set the number of failed attempts back to 0 - //here we are persisting the value for the back office - } - - var result = await UpdateAsync(user); - - //Slightly confusing: this will return a Success if we successfully update the AccessFailed count - if (result.Succeeded) - { - RaiseLoginFailedEvent(_httpContextAccessor.HttpContext?.User, user.Id); - } - - return result; - } - - private int GetCurrentUserId(IPrincipal currentUser) - { - var umbIdentity = currentUser?.GetUmbracoIdentity(); - var currentUserId = umbIdentity?.GetUserId() ?? Core.Constants.Security.SuperUserId; - return currentUserId; - } - private IdentityAuditEventArgs CreateArgs(AuditEvent auditEvent, IPrincipal currentUser, int affectedUserId, string affectedUsername) - { - var currentUserId = GetCurrentUserId(currentUser); - var ip = IpResolver.GetCurrentRequestIpAddress(); - return new IdentityAuditEventArgs(auditEvent, ip, currentUserId, string.Empty, affectedUserId, affectedUsername); - } - private IdentityAuditEventArgs CreateArgs(AuditEvent auditEvent, BackOfficeIdentityUser currentUser, int affectedUserId, string affectedUsername) - { - var currentUserId = currentUser.Id; - var ip = IpResolver.GetCurrentRequestIpAddress(); - return new IdentityAuditEventArgs(auditEvent, ip, currentUserId, string.Empty, affectedUserId, affectedUsername); - } - - // TODO: Review where these are raised and see if they can be simplified and either done in the this usermanager or the signin manager, - // lastly we'll resort to the authentication controller but we should try to remove all instances of that occuring - public void RaiseAccountLockedEvent(IPrincipal currentUser, int userId) => OnAccountLocked(CreateArgs(AuditEvent.AccountLocked, currentUser, userId, string.Empty)); - - public void RaiseAccountUnlockedEvent(IPrincipal currentUser, int userId) => OnAccountUnlocked(CreateArgs(AuditEvent.AccountUnlocked, currentUser, userId, string.Empty)); - - public void RaiseForgotPasswordRequestedEvent(IPrincipal currentUser, int userId) => OnForgotPasswordRequested(CreateArgs(AuditEvent.ForgotPasswordRequested, currentUser, userId, string.Empty)); - - public void RaiseForgotPasswordChangedSuccessEvent(IPrincipal currentUser, int userId) => OnForgotPasswordChangedSuccess(CreateArgs(AuditEvent.ForgotPasswordChangedSuccess, currentUser, userId, string.Empty)); - - public void RaiseLoginFailedEvent(IPrincipal currentUser, int userId) => OnLoginFailed(CreateArgs(AuditEvent.LoginFailed, currentUser, userId, string.Empty)); - - public void RaiseLoginRequiresVerificationEvent(IPrincipal currentUser, int userId) => OnLoginRequiresVerification(CreateArgs(AuditEvent.LoginRequiresVerification, currentUser, userId, string.Empty)); - - public void RaiseLoginSuccessEvent(IPrincipal currentUser, int userId) => OnLoginSuccess(CreateArgs(AuditEvent.LoginSucces, currentUser, userId, string.Empty)); - - public SignOutAuditEventArgs RaiseLogoutSuccessEvent(IPrincipal currentUser, int userId) - { - var currentUserId = GetCurrentUserId(currentUser); - var args = new SignOutAuditEventArgs(AuditEvent.LogoutSuccess, IpResolver.GetCurrentRequestIpAddress(), performingUser: currentUserId, affectedUser: userId); - OnLogoutSuccess(args); - return args; - } - - public void RaisePasswordChangedEvent(IPrincipal currentUser, int userId) => OnPasswordChanged(CreateArgs(AuditEvent.LogoutSuccess, currentUser, userId, string.Empty)); - - public void RaiseResetAccessFailedCountEvent(IPrincipal currentUser, int userId) => OnResetAccessFailedCount(CreateArgs(AuditEvent.ResetAccessFailedCount, currentUser, userId, string.Empty)); - - public UserInviteEventArgs RaiseSendingUserInvite(IPrincipal currentUser, UserInvite invite, IUser createdUser) - { - var currentUserId = GetCurrentUserId(currentUser); - var ip = IpResolver.GetCurrentRequestIpAddress(); - var args = new UserInviteEventArgs(ip, currentUserId, invite, createdUser); - OnSendingUserInvite(args); - return args; - } - - public bool HasSendingUserInviteEventHandler => SendingUserInvite != null; - - // TODO: These static events are problematic. Moving forward we don't want static events at all but we cannot - // have non-static events here because the user manager is a Scoped instance not a singleton - // so we'll have to deal with this a diff way i.e. refactoring how events are done entirely - public static event EventHandler AccountLocked; - public static event EventHandler AccountUnlocked; - public static event EventHandler ForgotPasswordRequested; - public static event EventHandler ForgotPasswordChangedSuccess; - public static event EventHandler LoginFailed; - public static event EventHandler LoginRequiresVerification; - public static event EventHandler LoginSuccess; - public static event EventHandler LogoutSuccess; - public static event EventHandler PasswordChanged; - public static event EventHandler PasswordReset; - public static event EventHandler ResetAccessFailedCount; - - /// - /// Raised when a user is invited - /// - public static event EventHandler SendingUserInvite; // this event really has nothing to do with the user manager but was the most convenient place to put it - - protected virtual void OnAccountLocked(IdentityAuditEventArgs e) => AccountLocked?.Invoke(this, e); - - protected virtual void OnSendingUserInvite(UserInviteEventArgs e) => SendingUserInvite?.Invoke(this, e); - - protected virtual void OnAccountUnlocked(IdentityAuditEventArgs e) => AccountUnlocked?.Invoke(this, e); - - protected virtual void OnForgotPasswordRequested(IdentityAuditEventArgs e) => ForgotPasswordRequested?.Invoke(this, e); - - protected virtual void OnForgotPasswordChangedSuccess(IdentityAuditEventArgs e) => ForgotPasswordChangedSuccess?.Invoke(this, e); - - protected virtual void OnLoginFailed(IdentityAuditEventArgs e) => LoginFailed?.Invoke(this, e); - - protected virtual void OnLoginRequiresVerification(IdentityAuditEventArgs e) => LoginRequiresVerification?.Invoke(this, e); - - protected virtual void OnLoginSuccess(IdentityAuditEventArgs e) => LoginSuccess?.Invoke(this, e); - - protected virtual void OnLogoutSuccess(SignOutAuditEventArgs e) => LogoutSuccess?.Invoke(this, e); - - protected virtual void OnPasswordChanged(IdentityAuditEventArgs e) => PasswordChanged?.Invoke(this, e); - - protected virtual void OnPasswordReset(IdentityAuditEventArgs e) => PasswordReset?.Invoke(this, e); - - protected virtual void OnResetAccessFailedCount(IdentityAuditEventArgs e) => ResetAccessFailedCount?.Invoke(this, e); - } -} diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManagerAuditer.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManagerAuditer.cs index 019eed7e39..81be953d22 100644 --- a/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManagerAuditer.cs +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManagerAuditer.cs @@ -1,11 +1,10 @@ -using Microsoft.Extensions.Options; using System; -using System.Threading.Tasks; +using Microsoft.Extensions.Options; using Umbraco.Core; -using Umbraco.Core.BackOffice; using Umbraco.Core.Compose; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Models.Membership; +using Umbraco.Core.Security; using Umbraco.Core.Services; namespace Umbraco.Web.Common.Security @@ -34,29 +33,27 @@ namespace Umbraco.Web.Common.Security { // NOTE: This was migrated as-is from v8 including these missing entries // TODO: See note about static events in BackOfficeUserManager - //BackOfficeUserManager.AccountLocked += ; - //BackOfficeUserManager.AccountUnlocked += ; BackOfficeUserManager.ForgotPasswordRequested += OnForgotPasswordRequest; BackOfficeUserManager.ForgotPasswordChangedSuccess += OnForgotPasswordChange; BackOfficeUserManager.LoginFailed += OnLoginFailed; - //BackOfficeUserManager.LoginRequiresVerification += ; BackOfficeUserManager.LoginSuccess += OnLoginSuccess; BackOfficeUserManager.LogoutSuccess += OnLogoutSuccess; BackOfficeUserManager.PasswordChanged += OnPasswordChanged; BackOfficeUserManager.PasswordReset += OnPasswordReset; - //BackOfficeUserManager.ResetAccessFailedCount += ; } - private IUser GetPerformingUser(int userId) + private IUser GetPerformingUser(string userId) { - var found = userId >= 0 ? _userService.GetUserById(userId) : null; + if (!int.TryParse(userId, out int asInt)) + { + return AuditEventsComponent.UnknownUser(_globalSettings); + } + + IUser found = asInt >= 0 ? _userService.GetUserById(asInt) : null; return found ?? AuditEventsComponent.UnknownUser(_globalSettings); } - private static string FormatEmail(IMembershipUser user) - { - return user == null ? string.Empty : user.Email.IsNullOrWhiteSpace() ? "" : $"<{user.Email}>"; - } + private static string FormatEmail(IMembershipUser user) => user == null ? string.Empty : user.Email.IsNullOrWhiteSpace() ? "" : $"<{user.Email}>"; private void OnLoginSuccess(object sender, IdentityAuditEventArgs args) { @@ -66,70 +63,78 @@ namespace Umbraco.Web.Common.Security private void OnLogoutSuccess(object sender, IdentityAuditEventArgs args) { - var performingUser = GetPerformingUser(args.PerformingUser); + IUser performingUser = GetPerformingUser(args.PerformingUser); WriteAudit(performingUser, args.AffectedUser, args.IpAddress, "umbraco/user/sign-in/logout", "logout success"); } - private void OnPasswordReset(object sender, IdentityAuditEventArgs args) - { - WriteAudit(args.PerformingUser, args.AffectedUser, args.IpAddress, "umbraco/user/password/reset", "password reset"); - } + private void OnPasswordReset(object sender, IdentityAuditEventArgs args) => WriteAudit(args.PerformingUser, args.AffectedUser, args.IpAddress, "umbraco/user/password/reset", "password reset"); - private void OnPasswordChanged(object sender, IdentityAuditEventArgs args) - { - WriteAudit(args.PerformingUser, args.AffectedUser, args.IpAddress, "umbraco/user/password/change", "password change"); - } + private void OnPasswordChanged(object sender, IdentityAuditEventArgs args) => WriteAudit(args.PerformingUser, args.AffectedUser, args.IpAddress, "umbraco/user/password/change", "password change"); - private void OnLoginFailed(object sender, IdentityAuditEventArgs args) - { - WriteAudit(args.PerformingUser, 0, args.IpAddress, "umbraco/user/sign-in/failed", "login failed", affectedDetails: ""); - } + private void OnLoginFailed(object sender, IdentityAuditEventArgs args) => WriteAudit(args.PerformingUser, "0", args.IpAddress, "umbraco/user/sign-in/failed", "login failed", affectedDetails: ""); - private void OnForgotPasswordChange(object sender, IdentityAuditEventArgs args) - { - WriteAudit(args.PerformingUser, args.AffectedUser, args.IpAddress, "umbraco/user/password/forgot/change", "password forgot/change"); - } + private void OnForgotPasswordChange(object sender, IdentityAuditEventArgs args) => WriteAudit(args.PerformingUser, args.AffectedUser, args.IpAddress, "umbraco/user/password/forgot/change", "password forgot/change"); - private void OnForgotPasswordRequest(object sender, IdentityAuditEventArgs args) - { - WriteAudit(args.PerformingUser, args.AffectedUser, args.IpAddress, "umbraco/user/password/forgot/request", "password forgot/request"); - } + private void OnForgotPasswordRequest(object sender, IdentityAuditEventArgs args) => WriteAudit(args.PerformingUser, args.AffectedUser, args.IpAddress, "umbraco/user/password/forgot/request", "password forgot/request"); - private void WriteAudit(int performingId, int affectedId, string ipAddress, string eventType, string eventDetails, string affectedDetails = null) + private void WriteAudit(string performingId, string affectedId, string ipAddress, string eventType, string eventDetails, string affectedDetails = null) { - var performingUser = _userService.GetUserById(performingId); + IUser performingUser = null; + if (int.TryParse(performingId, out int asInt)) + { + performingUser = _userService.GetUserById(asInt); + } var performingDetails = performingUser == null ? $"User UNKNOWN:{performingId}" : $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}"; - WriteAudit(performingId, performingDetails, affectedId, ipAddress, eventType, eventDetails, affectedDetails); + if (!int.TryParse(performingId, out int performingIdAsInt)) + { + performingIdAsInt = 0; + } + + if (!int.TryParse(affectedId, out int affectedIdAsInt)) + { + affectedIdAsInt = 0; + } + + WriteAudit(performingIdAsInt, performingDetails, affectedIdAsInt, ipAddress, eventType, eventDetails, affectedDetails); } - private void WriteAudit(IUser performingUser, int affectedId, string ipAddress, string eventType, string eventDetails) + private void WriteAudit(IUser performingUser, string affectedId, string ipAddress, string eventType, string eventDetails) { var performingDetails = performingUser == null ? $"User UNKNOWN" : $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}"; - WriteAudit(performingUser?.Id ?? 0, performingDetails, affectedId, ipAddress, eventType, eventDetails); + if (!int.TryParse(affectedId, out int affectedIdInt)) + { + affectedIdInt = 0; + } + + WriteAudit(performingUser?.Id ?? 0, performingDetails, affectedIdInt, ipAddress, eventType, eventDetails); } private void WriteAudit(int performingId, string performingDetails, int affectedId, string ipAddress, string eventType, string eventDetails, string affectedDetails = null) { if (affectedDetails == null) { - var affectedUser = _userService.GetUserById(affectedId); + IUser affectedUser = _userService.GetUserById(affectedId); affectedDetails = affectedUser == null ? $"User UNKNOWN:{affectedId}" : $"User \"{affectedUser.Name}\" {FormatEmail(affectedUser)}"; } - _auditService.Write(performingId, performingDetails, + _auditService.Write( + performingId, + performingDetails, ipAddress, DateTime.UtcNow, - affectedId, affectedDetails, - eventType, eventDetails); + affectedId, + affectedDetails, + eventType, + eventDetails); } protected virtual void Dispose(bool disposing) @@ -138,12 +143,9 @@ namespace Umbraco.Web.Common.Security { if (disposing) { - //BackOfficeUserManager.AccountLocked -= ; - //BackOfficeUserManager.AccountUnlocked -= ; BackOfficeUserManager.ForgotPasswordRequested -= OnForgotPasswordRequest; BackOfficeUserManager.ForgotPasswordChangedSuccess -= OnForgotPasswordChange; BackOfficeUserManager.LoginFailed -= OnLoginFailed; - //BackOfficeUserManager.LoginRequiresVerification -= ; BackOfficeUserManager.LoginSuccess -= OnLoginSuccess; BackOfficeUserManager.LogoutSuccess -= OnLogoutSuccess; BackOfficeUserManager.PasswordChanged -= OnPasswordChanged; diff --git a/src/Umbraco.Web.BackOffice/Security/ConfigureBackOfficeCookieOptions.cs b/src/Umbraco.Web.BackOffice/Security/ConfigureBackOfficeCookieOptions.cs index bd816e9382..b8568a2f03 100644 --- a/src/Umbraco.Web.BackOffice/Security/ConfigureBackOfficeCookieOptions.cs +++ b/src/Umbraco.Web.BackOffice/Security/ConfigureBackOfficeCookieOptions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; @@ -9,7 +9,6 @@ using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Umbraco.Core; -using Umbraco.Core.BackOffice; using Umbraco.Core.Cache; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; @@ -34,12 +33,23 @@ namespace Umbraco.Web.BackOffice.Security private readonly IHostingEnvironment _hostingEnvironment; private readonly IRuntimeState _runtimeState; private readonly IDataProtectionProvider _dataProtection; - private readonly IRequestCache _requestCache; private readonly IUserService _userService; private readonly IIpResolver _ipResolver; private readonly ISystemClock _systemClock; - private readonly LinkGenerator _linkGenerator; + /// + /// Initializes a new instance of the class. + /// + /// The + /// The + /// The options + /// The options + /// The + /// The + /// The + /// The + /// The + /// The public ConfigureBackOfficeCookieOptions( IServiceProvider serviceProvider, IUmbracoContextAccessor umbracoContextAccessor, @@ -48,11 +58,9 @@ namespace Umbraco.Web.BackOffice.Security IHostingEnvironment hostingEnvironment, IRuntimeState runtimeState, IDataProtectionProvider dataProtection, - IRequestCache requestCache, IUserService userService, IIpResolver ipResolver, - ISystemClock systemClock, - LinkGenerator linkGenerator) + ISystemClock systemClock) { _serviceProvider = serviceProvider; _umbracoContextAccessor = umbracoContextAccessor; @@ -61,19 +69,23 @@ namespace Umbraco.Web.BackOffice.Security _hostingEnvironment = hostingEnvironment; _runtimeState = runtimeState; _dataProtection = dataProtection; - _requestCache = requestCache; _userService = userService; _ipResolver = ipResolver; _systemClock = systemClock; - _linkGenerator = linkGenerator; } + /// public void Configure(string name, CookieAuthenticationOptions options) { - if (name != Constants.Security.BackOfficeAuthenticationType) return; + if (name != Constants.Security.BackOfficeAuthenticationType) + { + return; + } + Configure(options); } + /// public void Configure(CookieAuthenticationOptions options) { options.SlidingExpiration = true; @@ -94,20 +106,17 @@ namespace Umbraco.Web.BackOffice.Security // NOTE: This is borrowed directly from aspnetcore source // Note: the purpose for the data protector must remain fixed for interop to work. - var dataProtector = options.DataProtectionProvider.CreateProtector("Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware", Constants.Security.BackOfficeAuthenticationType, "v2"); + IDataProtector dataProtector = options.DataProtectionProvider.CreateProtector("Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware", Constants.Security.BackOfficeAuthenticationType, "v2"); var ticketDataFormat = new TicketDataFormat(dataProtector); options.TicketDataFormat = new BackOfficeSecureDataFormat(_globalSettings.TimeOutInMinutes, ticketDataFormat); - //Custom cookie manager so we can filter requests + // Custom cookie manager so we can filter requests options.CookieManager = new BackOfficeCookieManager( _umbracoContextAccessor, _runtimeState, _hostingEnvironment, - _globalSettings, - _requestCache); - // _explicitPaths); TODO: Implement this once we do OAuth somehow - + _globalSettings); // _explicitPaths); TODO: Implement this once we do OAuth somehow options.Events = new CookieAuthenticationEvents { @@ -118,22 +127,22 @@ namespace Umbraco.Web.BackOffice.Security // It would be possible to re-use the default behavior if any of these need to be set but that must be taken into account else // our back office requests will not function correctly. For now we don't need to set/configure any of these callbacks because // the defaults work fine with our setup. - OnValidatePrincipal = async ctx => { // We need to resolve the BackOfficeSecurityStampValidator per request as a requirement (even in aspnetcore they do this) - var securityStampValidator = ctx.HttpContext.RequestServices.GetRequiredService(); - // Same goes for the signinmanager - var signInManager = ctx.HttpContext.RequestServices.GetRequiredService(); + BackOfficeSecurityStampValidator securityStampValidator = ctx.HttpContext.RequestServices.GetRequiredService(); - var backOfficeIdentity = ctx.Principal.GetUmbracoIdentity(); + // Same goes for the signinmanager + IBackOfficeSignInManager signInManager = ctx.HttpContext.RequestServices.GetRequiredService(); + + UmbracoBackOfficeIdentity backOfficeIdentity = ctx.Principal.GetUmbracoIdentity(); if (backOfficeIdentity == null) { ctx.RejectPrincipal(); await signInManager.SignOutAsync(); } - //ensure the thread culture is set + // ensure the thread culture is set backOfficeIdentity.EnsureCulture(); await EnsureValidSessionId(ctx); @@ -153,19 +162,19 @@ namespace Umbraco.Web.BackOffice.Security OnSigningIn = ctx => { // occurs when sign in is successful but before the ticket is written to the outbound cookie - - var backOfficeIdentity = ctx.Principal.GetUmbracoIdentity(); + UmbracoBackOfficeIdentity backOfficeIdentity = ctx.Principal.GetUmbracoIdentity(); if (backOfficeIdentity != null) { - //generate a session id and assign it - //create a session token - if we are configured and not in an upgrade state then use the db, otherwise just generate one - var session = _runtimeState.Level == RuntimeLevel.Run + // generate a session id and assign it + // create a session token - if we are configured and not in an upgrade state then use the db, otherwise just generate one + Guid session = _runtimeState.Level == RuntimeLevel.Run ? _userService.CreateLoginSession(backOfficeIdentity.Id, _ipResolver.GetCurrentRequestIpAddress()) : Guid.NewGuid(); - //add our session claim + // add our session claim backOfficeIdentity.AddClaim(new Claim(Constants.Security.SessionIdClaimType, session.ToString(), ClaimValueTypes.String, UmbracoBackOfficeIdentity.Issuer, UmbracoBackOfficeIdentity.Issuer, backOfficeIdentity)); - //since it is a cookie-based authentication add that claim + + // since it is a cookie-based authentication add that claim backOfficeIdentity.AddClaim(new Claim(ClaimTypes.CookiePath, "/", ClaimValueTypes.String, UmbracoBackOfficeIdentity.Issuer, UmbracoBackOfficeIdentity.Issuer, backOfficeIdentity)); } @@ -182,12 +191,12 @@ namespace Umbraco.Web.BackOffice.Security }, OnSigningOut = ctx => { - //Clear the user's session on sign out + // Clear the user's session on sign out if (ctx.HttpContext?.User?.Identity != null) { var claimsIdentity = ctx.HttpContext.User.Identity as ClaimsIdentity; var sessionId = claimsIdentity.FindFirstValue(Constants.Security.SessionIdClaimType); - if (sessionId.IsNullOrWhiteSpace() == false && Guid.TryParse(sessionId, out var guidSession)) + if (sessionId.IsNullOrWhiteSpace() == false && Guid.TryParse(sessionId, out Guid guidSession)) { _userService.ClearLoginSession(guidSession); } @@ -224,10 +233,13 @@ namespace Umbraco.Web.BackOffice.Security /// private async Task EnsureValidSessionId(CookieValidatePrincipalContext context) { - if (_runtimeState.Level != RuntimeLevel.Run) return; + if (_runtimeState.Level != RuntimeLevel.Run) + { + return; + } - using var scope = _serviceProvider.CreateScope(); - var validator = scope.ServiceProvider.GetRequiredService(); + using IServiceScope scope = _serviceProvider.CreateScope(); + BackOfficeSessionIdValidator validator = scope.ServiceProvider.GetRequiredService(); await validator.ValidateSessionAsync(TimeSpan.FromMinutes(1), context); } @@ -235,21 +247,24 @@ namespace Umbraco.Web.BackOffice.Security /// Ensures the ticket is renewed if the is set to true /// and the current request is for the get user seconds endpoint /// - /// + /// The private void EnsureTicketRenewalIfKeepUserLoggedIn(CookieValidatePrincipalContext context) { - if (!_securitySettings.KeepUserLoggedIn) return; + if (!_securitySettings.KeepUserLoggedIn) + { + return; + } - var currentUtc = _systemClock.UtcNow; - var issuedUtc = context.Properties.IssuedUtc; - var expiresUtc = context.Properties.ExpiresUtc; + DateTimeOffset currentUtc = _systemClock.UtcNow; + DateTimeOffset? issuedUtc = context.Properties.IssuedUtc; + DateTimeOffset? expiresUtc = context.Properties.ExpiresUtc; if (expiresUtc.HasValue && issuedUtc.HasValue) { - var timeElapsed = currentUtc.Subtract(issuedUtc.Value); - var timeRemaining = expiresUtc.Value.Subtract(currentUtc); + TimeSpan timeElapsed = currentUtc.Subtract(issuedUtc.Value); + TimeSpan timeRemaining = expiresUtc.Value.Subtract(currentUtc); - //if it's time to renew, then do it + // if it's time to renew, then do it if (timeRemaining < timeElapsed) { context.ShouldRenew = true; diff --git a/src/Umbraco.Web.BackOffice/Security/ConfigureBackOfficeIdentityOptions.cs b/src/Umbraco.Web.BackOffice/Security/ConfigureBackOfficeIdentityOptions.cs index 31b5de2e43..989c852350 100644 --- a/src/Umbraco.Web.BackOffice/Security/ConfigureBackOfficeIdentityOptions.cs +++ b/src/Umbraco.Web.BackOffice/Security/ConfigureBackOfficeIdentityOptions.cs @@ -1,11 +1,11 @@ -using System; +using System; using System.Security.Claims; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Options; using Umbraco.Core; -using Umbraco.Core.BackOffice; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; +using Umbraco.Core.Security; namespace Umbraco.Web.BackOffice.Security { diff --git a/src/Umbraco.Web.BackOffice/Security/ExternalSignInAutoLinkOptions.cs b/src/Umbraco.Web.BackOffice/Security/ExternalSignInAutoLinkOptions.cs index 8d9f57945b..8636d9e62d 100644 --- a/src/Umbraco.Web.BackOffice/Security/ExternalSignInAutoLinkOptions.cs +++ b/src/Umbraco.Web.BackOffice/Security/ExternalSignInAutoLinkOptions.cs @@ -1,8 +1,8 @@ using Microsoft.AspNetCore.Identity; using System; using System.Runtime.Serialization; -using Umbraco.Core.BackOffice; using Umbraco.Core.Configuration.Models; +using Umbraco.Core.Security; using SecurityConstants = Umbraco.Core.Constants.Security; namespace Umbraco.Web.BackOffice.Security diff --git a/src/Umbraco.Web.BackOffice/Security/IBackOfficeSignInManager.cs b/src/Umbraco.Web.BackOffice/Security/IBackOfficeSignInManager.cs index ce87484b2c..669ca21239 100644 --- a/src/Umbraco.Web.BackOffice/Security/IBackOfficeSignInManager.cs +++ b/src/Umbraco.Web.BackOffice/Security/IBackOfficeSignInManager.cs @@ -3,7 +3,7 @@ using Microsoft.AspNetCore.Identity; using System.Collections.Generic; using System.Security.Claims; using System.Threading.Tasks; -using Umbraco.Core.BackOffice; +using Umbraco.Core.Security; namespace Umbraco.Web.Common.Security { diff --git a/src/Umbraco.Web.BackOffice/Security/PasswordChanger.cs b/src/Umbraco.Web.BackOffice/Security/PasswordChanger.cs index 1a4298cd6b..180f433fab 100644 --- a/src/Umbraco.Web.BackOffice/Security/PasswordChanger.cs +++ b/src/Umbraco.Web.BackOffice/Security/PasswordChanger.cs @@ -1,10 +1,10 @@ -using System; +using System; using System.ComponentModel.DataAnnotations; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Umbraco.Core; -using Umbraco.Core.BackOffice; using Umbraco.Core.Models; +using Umbraco.Core.Security; using Umbraco.Extensions; using Umbraco.Web.Models; using IUser = Umbraco.Core.Models.Membership.IUser; @@ -67,7 +67,7 @@ namespace Umbraco.Web.BackOffice.Security //ok, we should be able to reset it var resetToken = await userMgr.GeneratePasswordResetTokenAsync(backOfficeIdentityUser); - var resetResult = await userMgr.ChangePasswordWithResetAsync(savingUser.Id, resetToken, passwordModel.NewPassword); + var resetResult = await userMgr.ChangePasswordWithResetAsync(savingUser.Id.ToString(), resetToken, passwordModel.NewPassword); if (resetResult.Succeeded == false) { diff --git a/src/Umbraco.Web.Common/Builder/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Common/Builder/UmbracoBuilderExtensions.cs index 3bf4e9059b..f346e0dd79 100644 --- a/src/Umbraco.Web.Common/Builder/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/Builder/UmbracoBuilderExtensions.cs @@ -39,6 +39,7 @@ using Umbraco.Web.Common.AspNetCore; using Umbraco.Web.Common.Filters; using Umbraco.Web.Common.ModelBinders; using Umbraco.Web.Common.Profiler; +using Umbraco.Web.Telemetry; using IHostingEnvironment = Umbraco.Core.Hosting.IHostingEnvironment; namespace Umbraco.Core.DependencyInjection @@ -201,6 +202,7 @@ namespace Umbraco.Core.DependencyInjection builder.Services.AddHostedService(); builder.Services.AddHostedService(); builder.Services.AddHostedService(); + builder.Services.AddHostedService(); return builder; } diff --git a/src/Umbraco.Web.Common/Extensions/HttpContextExtensions.cs b/src/Umbraco.Web.Common/Extensions/HttpContextExtensions.cs index 15d3d04c0b..f484ddac18 100644 --- a/src/Umbraco.Web.Common/Extensions/HttpContextExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/HttpContextExtensions.cs @@ -5,7 +5,7 @@ using System.Security.Claims; using System.Security.Principal; using System.Text; using Microsoft.AspNetCore.Http.Features; -using Umbraco.Core.BackOffice; +using Umbraco.Core.Security; namespace Umbraco.Extensions { diff --git a/src/Umbraco.Web.Common/Security/BackOfficeUserManager.cs b/src/Umbraco.Web.Common/Security/BackOfficeUserManager.cs new file mode 100644 index 0000000000..081ca6b581 --- /dev/null +++ b/src/Umbraco.Web.Common/Security/BackOfficeUserManager.cs @@ -0,0 +1,283 @@ +using System; +using System.Collections.Generic; +using System.Security.Principal; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Umbraco.Core; +using Umbraco.Core.Configuration.Models; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Security; +using Umbraco.Extensions; +using Umbraco.Net; +using Umbraco.Web.Models.ContentEditing; + + +namespace Umbraco.Web.Common.Security +{ + public class BackOfficeUserManager : UmbracoUserManager, IBackOfficeUserManager + { + private readonly IHttpContextAccessor _httpContextAccessor; + + public BackOfficeUserManager( + IIpResolver ipResolver, + IUserStore store, + IOptions optionsAccessor, + IPasswordHasher passwordHasher, + IEnumerable> userValidators, + IEnumerable> passwordValidators, + BackOfficeLookupNormalizer keyNormalizer, + BackOfficeIdentityErrorDescriber errors, + IServiceProvider services, + IHttpContextAccessor httpContextAccessor, + ILogger> logger, + IOptions passwordConfiguration) + : base(ipResolver, store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, logger, passwordConfiguration) + { + _httpContextAccessor = httpContextAccessor; + } + + /// + /// Gets or sets the default back office user password checker + /// + public IBackOfficeUserPasswordChecker BackOfficeUserPasswordChecker { get; set; } // TODO: This isn't a good way to set this, it needs to be injected + + /// + /// + /// By default this uses the standard ASP.Net Identity approach which is: + /// * Get password store + /// * Call VerifyPasswordAsync with the password store + user + password + /// * Uses the PasswordHasher.VerifyHashedPassword to compare the stored password + /// + /// In some cases people want simple custom control over the username/password check, for simplicity + /// sake, developers would like the users to simply validate against an LDAP directory but the user + /// data remains stored inside of Umbraco. + /// See: http://issues.umbraco.org/issue/U4-7032 for the use cases. + /// + /// We've allowed this check to be overridden with a simple callback so that developers don't actually + /// have to implement/override this class. + /// + public override async Task CheckPasswordAsync(BackOfficeIdentityUser user, string password) + { + if (BackOfficeUserPasswordChecker != null) + { + BackOfficeUserPasswordCheckerResult result = await BackOfficeUserPasswordChecker.CheckPasswordAsync(user, password); + + if (user.HasIdentity == false) + { + return false; + } + + // if the result indicates to not fallback to the default, then return true if the credentials are valid + if (result != BackOfficeUserPasswordCheckerResult.FallbackToDefaultChecker) + { + return result == BackOfficeUserPasswordCheckerResult.ValidCredentials; + } + } + + // use the default behavior + return await base.CheckPasswordAsync(user, password); + } + + /// + /// Override to check the user approval value as well as the user lock out date, by default this only checks the user's locked out date + /// + /// The user + /// True if the user is locked out, else false + /// + /// In the ASP.NET Identity world, there is only one value for being locked out, in Umbraco we have 2 so when checking this for Umbraco we need to check both values + /// + public override async Task IsLockedOutAsync(BackOfficeIdentityUser user) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + if (user.IsApproved == false) + { + return true; + } + + return await base.IsLockedOutAsync(user); + } + + public override async Task AccessFailedAsync(BackOfficeIdentityUser user) + { + IdentityResult result = await base.AccessFailedAsync(user); + + // Slightly confusing: this will return a Success if we successfully update the AccessFailed count + if (result.Succeeded) + { + RaiseLoginFailedEvent(_httpContextAccessor.HttpContext?.User, user.Id); + } + + return result; + } + + public override async Task ChangePasswordWithResetAsync(string userId, string token, string newPassword) + { + IdentityResult result = await base.ChangePasswordWithResetAsync(userId, token, newPassword); + if (result.Succeeded) + { + RaisePasswordChangedEvent(_httpContextAccessor.HttpContext?.User, userId); + } + + return result; + } + + public override async Task ChangePasswordAsync(BackOfficeIdentityUser user, string currentPassword, string newPassword) + { + IdentityResult result = await base.ChangePasswordAsync(user, currentPassword, newPassword); + if (result.Succeeded) + { + RaisePasswordChangedEvent(_httpContextAccessor.HttpContext?.User, user.Id); + } + + return result; + } + + /// + public override async Task SetLockoutEndDateAsync(BackOfficeIdentityUser user, DateTimeOffset? lockoutEnd) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + IdentityResult result = await base.SetLockoutEndDateAsync(user, lockoutEnd); + + // The way we unlock is by setting the lockoutEnd date to the current datetime + if (result.Succeeded && lockoutEnd >= DateTimeOffset.UtcNow) + { + RaiseAccountLockedEvent(_httpContextAccessor.HttpContext?.User, user.Id); + } + else + { + RaiseAccountUnlockedEvent(_httpContextAccessor.HttpContext?.User, user.Id); + + // Resets the login attempt fails back to 0 when unlock is clicked + await ResetAccessFailedCountAsync(user); + } + + return result; + } + + /// + public override async Task ResetAccessFailedCountAsync(BackOfficeIdentityUser user) + { + IdentityResult result = await base.ResetAccessFailedCountAsync(user); + + // raise the event now that it's reset + RaiseResetAccessFailedCountEvent(_httpContextAccessor.HttpContext?.User, user.Id); + + return result; + } + + private string GetCurrentUserId(IPrincipal currentUser) + { + UmbracoBackOfficeIdentity umbIdentity = currentUser?.GetUmbracoIdentity(); + var currentUserId = umbIdentity?.GetUserId() ?? Core.Constants.Security.SuperUserIdAsString; + return currentUserId; + } + + private IdentityAuditEventArgs CreateArgs(AuditEvent auditEvent, IPrincipal currentUser, string affectedUserId, string affectedUsername) + { + var currentUserId = GetCurrentUserId(currentUser); + var ip = IpResolver.GetCurrentRequestIpAddress(); + return new IdentityAuditEventArgs(auditEvent, ip, currentUserId, string.Empty, affectedUserId, affectedUsername); + } + + private IdentityAuditEventArgs CreateArgs(AuditEvent auditEvent, BackOfficeIdentityUser currentUser, string affectedUserId, string affectedUsername) + { + var currentUserId = currentUser.Id; + var ip = IpResolver.GetCurrentRequestIpAddress(); + return new IdentityAuditEventArgs(auditEvent, ip, currentUserId, string.Empty, affectedUserId, affectedUsername); + } + + // TODO: Review where these are raised and see if they can be simplified and either done in the this usermanager or the signin manager, + // lastly we'll resort to the authentication controller but we should try to remove all instances of that occuring + public void RaiseAccountLockedEvent(IPrincipal currentUser, string userId) => OnAccountLocked(CreateArgs(AuditEvent.AccountLocked, currentUser, userId, string.Empty)); + + public void RaiseAccountUnlockedEvent(IPrincipal currentUser, string userId) => OnAccountUnlocked(CreateArgs(AuditEvent.AccountUnlocked, currentUser, userId, string.Empty)); + + public void RaiseForgotPasswordRequestedEvent(IPrincipal currentUser, string userId) => OnForgotPasswordRequested(CreateArgs(AuditEvent.ForgotPasswordRequested, currentUser, userId, string.Empty)); + + public void RaiseForgotPasswordChangedSuccessEvent(IPrincipal currentUser, string userId) => OnForgotPasswordChangedSuccess(CreateArgs(AuditEvent.ForgotPasswordChangedSuccess, currentUser, userId, string.Empty)); + + public void RaiseLoginFailedEvent(IPrincipal currentUser, string userId) => OnLoginFailed(CreateArgs(AuditEvent.LoginFailed, currentUser, userId, string.Empty)); + + public void RaiseLoginRequiresVerificationEvent(IPrincipal currentUser, string userId) => OnLoginRequiresVerification(CreateArgs(AuditEvent.LoginRequiresVerification, currentUser, userId, string.Empty)); + + public void RaiseLoginSuccessEvent(IPrincipal currentUser, string userId) => OnLoginSuccess(CreateArgs(AuditEvent.LoginSucces, currentUser, userId, string.Empty)); + + public SignOutAuditEventArgs RaiseLogoutSuccessEvent(IPrincipal currentUser, string userId) + { + var currentUserId = GetCurrentUserId(currentUser); + var args = new SignOutAuditEventArgs(AuditEvent.LogoutSuccess, IpResolver.GetCurrentRequestIpAddress(), performingUser: currentUserId, affectedUser: userId); + OnLogoutSuccess(args); + return args; + } + + public void RaisePasswordChangedEvent(IPrincipal currentUser, string userId) => OnPasswordChanged(CreateArgs(AuditEvent.LogoutSuccess, currentUser, userId, string.Empty)); + + public void RaiseResetAccessFailedCountEvent(IPrincipal currentUser, string userId) => OnResetAccessFailedCount(CreateArgs(AuditEvent.ResetAccessFailedCount, currentUser, userId, string.Empty)); + + public UserInviteEventArgs RaiseSendingUserInvite(IPrincipal currentUser, UserInvite invite, IUser createdUser) + { + var currentUserId = GetCurrentUserId(currentUser); + var ip = IpResolver.GetCurrentRequestIpAddress(); + var args = new UserInviteEventArgs(ip, currentUserId, invite, createdUser); + OnSendingUserInvite(args); + return args; + } + + public bool HasSendingUserInviteEventHandler => SendingUserInvite != null; + + // TODO: These static events are problematic. Moving forward we don't want static events at all but we cannot + // have non-static events here because the user manager is a Scoped instance not a singleton + // so we'll have to deal with this a diff way i.e. refactoring how events are done entirely + public static event EventHandler AccountLocked; + public static event EventHandler AccountUnlocked; + public static event EventHandler ForgotPasswordRequested; + public static event EventHandler ForgotPasswordChangedSuccess; + public static event EventHandler LoginFailed; + public static event EventHandler LoginRequiresVerification; + public static event EventHandler LoginSuccess; + public static event EventHandler LogoutSuccess; + public static event EventHandler PasswordChanged; + public static event EventHandler PasswordReset; + public static event EventHandler ResetAccessFailedCount; + + /// + /// Raised when a user is invited + /// + public static event EventHandler SendingUserInvite; // this event really has nothing to do with the user manager but was the most convenient place to put it + + protected virtual void OnAccountLocked(IdentityAuditEventArgs e) => AccountLocked?.Invoke(this, e); + + protected virtual void OnSendingUserInvite(UserInviteEventArgs e) => SendingUserInvite?.Invoke(this, e); + + protected virtual void OnAccountUnlocked(IdentityAuditEventArgs e) => AccountUnlocked?.Invoke(this, e); + + protected virtual void OnForgotPasswordRequested(IdentityAuditEventArgs e) => ForgotPasswordRequested?.Invoke(this, e); + + protected virtual void OnForgotPasswordChangedSuccess(IdentityAuditEventArgs e) => ForgotPasswordChangedSuccess?.Invoke(this, e); + + protected virtual void OnLoginFailed(IdentityAuditEventArgs e) => LoginFailed?.Invoke(this, e); + + protected virtual void OnLoginRequiresVerification(IdentityAuditEventArgs e) => LoginRequiresVerification?.Invoke(this, e); + + protected virtual void OnLoginSuccess(IdentityAuditEventArgs e) => LoginSuccess?.Invoke(this, e); + + protected virtual void OnLogoutSuccess(SignOutAuditEventArgs e) => LogoutSuccess?.Invoke(this, e); + + protected virtual void OnPasswordChanged(IdentityAuditEventArgs e) => PasswordChanged?.Invoke(this, e); + + protected virtual void OnPasswordReset(IdentityAuditEventArgs e) => PasswordReset?.Invoke(this, e); + + protected virtual void OnResetAccessFailedCount(IdentityAuditEventArgs e) => ResetAccessFailedCount?.Invoke(this, e); + } +} diff --git a/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj b/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj index a712a43a45..b09fde0a6a 100644 --- a/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj +++ b/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj @@ -26,8 +26,8 @@ - - + + diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index 2288b7a421..4df4043861 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -14,7 +14,7 @@ "@babel/core": { "version": "7.6.4", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.6.4.tgz", - "integrity": "sha512-Rm0HGw101GY8FTzpWSyRbki/jzq+/PkNQJ+nSulrdY6gFGOsNseCqD6KHRYe2E+EdzuBdr2pxCp6s4Uk6eJ+XQ==", + "integrity": "sha1-br2f4Akl9sPhd7tyahiLX1eAiP8=", "dev": true, "requires": { "@babel/code-frame": "^7.5.5", @@ -739,7 +739,7 @@ "@babel/preset-env": { "version": "7.6.3", "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.6.3.tgz", - "integrity": "sha512-CWQkn7EVnwzlOdR5NOm2+pfgSNEZmvGjOhlCHBDq0J8/EStr+G+FvPEiz9B56dR6MoiUFjXhfE4hjLoAKKJtIQ==", + "integrity": "sha1-nhvwWi4taHA20kxA5GOdxGzvInE=", "dev": true, "requires": { "@babel/helper-module-imports": "^7.0.0", @@ -865,7 +865,7 @@ "@gulp-sourcemaps/identity-map": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/identity-map/-/identity-map-1.0.2.tgz", - "integrity": "sha512-ciiioYMLdo16ShmfHBXJBOFm3xPC4AuwO4xeRpFeHz7WK9PYsWCmigagG2XyzZpubK4a3qNKoUBDhbzHfa50LQ==", + "integrity": "sha1-Hm/l2AJ7HyhdwNMXYvVmvM1z1ak=", "dev": true, "requires": { "acorn": "^5.0.3", @@ -884,7 +884,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", "dev": true } } @@ -922,7 +922,7 @@ "@nodelib/fs.scandir": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", - "integrity": "sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw==", + "integrity": "sha1-Olgr21OATGum0UZXnEblITDPSjs=", "dev": true, "requires": { "@nodelib/fs.stat": "2.0.3", @@ -932,13 +932,13 @@ "@nodelib/fs.stat": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz", - "integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==", + "integrity": "sha1-NNxfTKu8cg9OYPdadH5+zWwXW9M=", "dev": true }, "@nodelib/fs.walk": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz", - "integrity": "sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ==", + "integrity": "sha1-ARuSAqcKY2bkNspcBlhEUoqwSXY=", "dev": true, "requires": { "@nodelib/fs.scandir": "2.1.3", @@ -948,7 +948,7 @@ "@sindresorhus/is": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.7.0.tgz", - "integrity": "sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==", + "integrity": "sha1-mgb08TfuhNffBGDB/bETX/psUP0=", "dev": true, "optional": true }, @@ -970,7 +970,7 @@ "@types/minimatch": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", - "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", + "integrity": "sha1-PcoOPzOyAPx9ETnAzZbBJoyt/Z0=", "dev": true }, "@types/node": { @@ -994,7 +994,7 @@ "accepts": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "integrity": "sha1-UxvHJlF6OytB+FACHGzBXqq1B80=", "dev": true, "requires": { "mime-types": "~2.1.24", @@ -1004,7 +1004,7 @@ "accord": { "version": "0.29.0", "resolved": "https://registry.npmjs.org/accord/-/accord-0.29.0.tgz", - "integrity": "sha512-3OOR92FTc2p5/EcOzPcXp+Cbo+3C15nV9RXHlOUBCBpHhcB+0frbSNR9ehED/o7sTcyGVtqGJpguToEdlXhD0w==", + "integrity": "sha1-t0HBdtAENcWSnUZt/oz2vukzseQ=", "dev": true, "requires": { "convert-source-map": "^1.5.0", @@ -1051,7 +1051,7 @@ "ace-builds": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/ace-builds/-/ace-builds-1.4.2.tgz", - "integrity": "sha512-M1JtZctO2Zg+1qeGUFZXtYKsyaRptqQtqpVzlj80I0NzGW9MF3um0DBuizIvQlrPYUlTdm+wcOPZpZoerkxQdA==" + "integrity": "sha1-avwuQ6e17/3ETYQHQ2EShSVo6A0=" }, "acorn": { "version": "7.1.0", @@ -1148,12 +1148,12 @@ "angular-animate": { "version": "1.7.5", "resolved": "https://registry.npmjs.org/angular-animate/-/angular-animate-1.7.5.tgz", - "integrity": "sha512-kU/fHIGf2a4a3bH7E1tzALTHk+QfoUSCK9fEcMFisd6ZWvNDwPzXWAilItqOC3EDiAXPmGHaNc9/aXiD9xrAxQ==" + "integrity": "sha1-H/xsKpze4ieiunnMbNj3HsRNtdw=" }, "angular-aria": { "version": "1.7.9", "resolved": "https://registry.npmjs.org/angular-aria/-/angular-aria-1.7.9.tgz", - "integrity": "sha512-luI3Jemd1AbOQW0krdzfEG3fM0IFtLY0bSSqIDEx3POE0XjKIC1MkrO8Csyq9PPgueLphyAPofzUwZ8YeZ88SA==" + "integrity": "sha1-kMYYlf+9h26VkVIisyp70AcK9+M=" }, "angular-chart.js": { "version": "1.1.1", @@ -1178,12 +1178,12 @@ "angular-cookies": { "version": "1.7.5", "resolved": "https://registry.npmjs.org/angular-cookies/-/angular-cookies-1.7.5.tgz", - "integrity": "sha512-/8xvvSl/Z9Vwu8ChRm+OQE3vmli8Icwl8uTYkHqD7j7cknJP9kNaf7SgsENlsLVtOqLE/I7TCFYrSx3bmSeNQA==" + "integrity": "sha1-HFqzwFzcQ/F3e+lQbmRYfLNUNjQ=" }, "angular-dynamic-locale": { "version": "0.1.37", "resolved": "https://registry.npmjs.org/angular-dynamic-locale/-/angular-dynamic-locale-0.1.37.tgz", - "integrity": "sha512-m5Kyk8W8/mOZSqRxuByOwHBjv8labLBAgvl0Z3iQx2xT/tWCqb94imKUPwumudszdPDjxeopwyucQvm8Sw7ogw==", + "integrity": "sha1-fon70uxFvdaryJ82zaiJODjkk1Q=", "requires": { "@types/angular": "^1.6.25" } @@ -1191,7 +1191,7 @@ "angular-i18n": { "version": "1.7.5", "resolved": "https://registry.npmjs.org/angular-i18n/-/angular-i18n-1.7.5.tgz", - "integrity": "sha512-52+Jpt8HRJV2bqSbSU6fWkwOvGzj/DxbNpKXxnTuCS9heuJrlm77BS/lhrF4BA8+Uudnh7npr5/yRELobP+8Yw==" + "integrity": "sha1-Lie2Thl3qMa2sFHFHQF1xtTcglI=" }, "angular-local-storage": { "version": "0.7.1", @@ -1201,32 +1201,32 @@ "angular-messages": { "version": "1.7.5", "resolved": "https://registry.npmjs.org/angular-messages/-/angular-messages-1.7.5.tgz", - "integrity": "sha512-YDpJpFLyrIgZjE/sIAjgww1y6r3QqXBJbNDI0QjftD37vHXLkwvAOo3A4bxPw8BikyGLcJrFrgf6hRAzntJIWA==" + "integrity": "sha1-fC/XgTFaQ6GYOLEX2gFCqYhFThQ=" }, "angular-mocks": { "version": "1.7.5", "resolved": "https://registry.npmjs.org/angular-mocks/-/angular-mocks-1.7.5.tgz", - "integrity": "sha512-I+Ue2Bkx6R9W5178DYrNvzjIdGh4wKKoCWsgz8dc7ysH4mA70Q3M9v5xRF0RUu7r+2CZj+nDeUecvh2paxcYvg==" + "integrity": "sha1-yLq6WgbtYLk0aXAmtJIWliavOEs=" }, "angular-route": { "version": "1.7.5", "resolved": "https://registry.npmjs.org/angular-route/-/angular-route-1.7.5.tgz", - "integrity": "sha512-7KfyEVVOWTI+jTY/j5rUNCIHGRyeCOx7YqZI/Ci3IbDK7GIsy6xH+hS5ai0Xi0sLjzDZ0PUDO4gBn+K0dVtlOg==" + "integrity": "sha1-NKNkjEB6FKAw0HXPSFMY4zuiPw4=" }, "angular-sanitize": { "version": "1.7.5", "resolved": "https://registry.npmjs.org/angular-sanitize/-/angular-sanitize-1.7.5.tgz", - "integrity": "sha512-wjKCJOIwrkEvfD0keTnKGi6We13gtoCAQIHcdoqyoo3gwvcgNfYymVQIS3+iCGVcjfWz0jHuS3KgB4ysRWsTTA==" + "integrity": "sha1-ddSeFQccqccFgedtIJQPJjcuJNI=" }, "angular-touch": { "version": "1.7.5", "resolved": "https://registry.npmjs.org/angular-touch/-/angular-touch-1.7.5.tgz", - "integrity": "sha512-XNAZNG0RA1mtdwBJheViCF1H/7wOygp4MLIfs5y1K+rne6AeaYKZcV6EJs9fvgfLKLO6ecm1+3J8hoCkdhhxQw==" + "integrity": "sha1-7SYyKmhfApmyPLauqYNMEZQk2kY=" }, "angular-ui-sortable": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/angular-ui-sortable/-/angular-ui-sortable-0.19.0.tgz", - "integrity": "sha512-u/uc981Nzg4XN1bMU9qKleMTSt7F1XjMWnyGw6gxPLIeQeLZm8jWNy7tj8y2r2HmvzXFbQVq2z6rObznFKAekQ==", + "integrity": "sha1-SsQ5H8TU3lcRDbS10xp8GY0xT9A=", "requires": { "angular": ">=1.2.x", "jquery": ">=3.1.x", @@ -1241,7 +1241,7 @@ "ansi-colors": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", - "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", + "integrity": "sha1-Y3S03V1HGP884npnGjscrQdxMqk=", "dev": true, "requires": { "ansi-wrap": "^0.1.0" @@ -1289,7 +1289,7 @@ "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "integrity": "sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0=", "dev": true, "requires": { "color-convert": "^1.9.0" @@ -1469,7 +1469,7 @@ "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "integrity": "sha1-vNZ5HqWuCXJeF+WtmIE0zUCz2RE=", "dev": true, "requires": { "sprintf-js": "~1.0.2" @@ -1493,7 +1493,7 @@ "arr-flatten": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "integrity": "sha1-NgSLv/TntH4TZkQxbJlmnqWukfE=", "dev": true }, "arr-map": { @@ -1543,7 +1543,7 @@ "is-number": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "integrity": "sha1-ACbjf1RU1z41bf5lZGmYZ8an8P8=", "dev": true } } @@ -1551,7 +1551,7 @@ "array-last": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/array-last/-/array-last-1.3.0.tgz", - "integrity": "sha512-eOCut5rXlI6aCOS7Z7kCplKRKyiFQ6dHFBem4PwlwKeNFk2/XxTrhRh5T9PyaEWGy/NHTZWbY+nsZlNFJu9rYg==", + "integrity": "sha1-eqdwc/7FZd2rJJP1+IGF9ASp0zY=", "dev": true, "requires": { "is-number": "^4.0.0" @@ -1560,7 +1560,7 @@ "is-number": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "integrity": "sha1-ACbjf1RU1z41bf5lZGmYZ8an8P8=", "dev": true } } @@ -1568,13 +1568,13 @@ "array-slice": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", - "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", + "integrity": "sha1-42jqFfibxwaff/uJrsOmx9SsItQ=", "dev": true }, "array-sort": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-sort/-/array-sort-1.0.0.tgz", - "integrity": "sha512-ihLeJkonmdiAsD7vpgN3CRcx2J2S0TiYW+IS/5zHBI7mKUq3ySvBdzzBfD236ubDBQFiiyG3SWCPc+msQ9KoYg==", + "integrity": "sha1-5MBTVkU/VvU1EqfR1hI/LFTAqIo=", "dev": true, "requires": { "default-compare": "^1.0.0", @@ -1585,7 +1585,7 @@ "kind-of": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "integrity": "sha1-cpyR4thXt6QZofmqZWhcTDP1hF0=", "dev": true } } @@ -1593,7 +1593,7 @@ "array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "integrity": "sha1-t5hCCtvrHego2ErNii4j0+/oXo0=", "dev": true }, "array-uniq": { @@ -1611,7 +1611,7 @@ "arraybuffer.slice": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", - "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==", + "integrity": "sha1-O7xCdd1YTMGxCAm4nU6LY6aednU=", "dev": true }, "asap": { @@ -1624,7 +1624,7 @@ "asn1": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "integrity": "sha1-jSR136tVO7M+d7VOWeiAu4ziMTY=", "requires": { "safer-buffer": "~2.1.0" } @@ -1643,13 +1643,13 @@ "astral-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "integrity": "sha1-bIw/uCfdQ+45GPJ7gngqt2WKb9k=", "dev": true }, "async": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "integrity": "sha1-1yYl4jRKNlbjo61Pp0n6gymdgv8=", "dev": true, "requires": { "lodash": "^4.17.14" @@ -1666,7 +1666,7 @@ "async-done": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/async-done/-/async-done-1.3.2.tgz", - "integrity": "sha512-uYkTP8dw2og1tu1nmza1n1CMW0qb8gWWlwqMmLb7MhBVs4BXrFziT6HXUd+/RlRA/i4H9AkofYloUbs1fwMqlw==", + "integrity": "sha1-XhWqcplipLB0FPUoqIzfGOCykKI=", "dev": true, "requires": { "end-of-stream": "^1.1.0", @@ -1678,13 +1678,13 @@ "async-each": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", - "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", + "integrity": "sha1-tyfb+H12UWAvBvTUrDh/R9kbDL8=", "dev": true }, "async-limiter": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" + "integrity": "sha1-3TeelPDbgxCwgpH51kwyCXZmF/0=" }, "async-settle": { "version": "1.0.0", @@ -1703,13 +1703,13 @@ "atob": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "integrity": "sha1-bZUX654DDSQ2ZmZR6GvZ9vE1M8k=", "dev": true }, "autoprefixer": { "version": "9.6.5", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.6.5.tgz", - "integrity": "sha512-rGd50YV8LgwFQ2WQp4XzOTG69u1qQsXn0amww7tjqV5jJuNazgFKYEVItEBngyyvVITKOg20zr2V+9VsrXJQ2g==", + "integrity": "sha1-mPSv5+k8zPMjKHUV1CYBlhl3Xl4=", "dev": true, "requires": { "browserslist": "^4.7.0", @@ -1742,7 +1742,7 @@ "babel-plugin-dynamic-import-node": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz", - "integrity": "sha512-o6qFkpeQEBxcqt0XYlWzAVxNCSCZdUgcR8IRlhD/8DylxjjO4foPcvTW0GGKa/cVt3rvxZ7o5ippJ+/0nvLhlQ==", + "integrity": "sha1-8A9Qe9qjw+P/bn5emNkKesq5b38=", "dev": true, "requires": { "object.assign": "^4.1.0" @@ -1780,7 +1780,7 @@ "base": { "version": "0.11.2", "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "integrity": "sha1-e95c7RRbbVUakNuH+DxVi060io8=", "dev": true, "requires": { "cache-base": "^1.0.1", @@ -1804,7 +1804,7 @@ "is-accessor-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -1813,7 +1813,7 @@ "is-data-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -1822,7 +1822,7 @@ "is-descriptor": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=", "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", @@ -1877,7 +1877,7 @@ "bin-build": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/bin-build/-/bin-build-3.0.0.tgz", - "integrity": "sha512-jcUOof71/TNAI2uM5uoUaDq2ePcVBQ3R/qhxAz1rX7UfvduAL/RXD3jXzvn8cVcDJdGVkiR1shal3OH0ImpuhA==", + "integrity": "sha1-xXgKJaip+WbYJEIX5sH1CCoUOGE=", "dev": true, "optional": true, "requires": { @@ -1928,7 +1928,7 @@ "bin-check": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bin-check/-/bin-check-4.1.0.tgz", - "integrity": "sha512-b6weQyEUKsDGFlACWSIOfveEnImkJyK/FGW6FAG42loyoquvjdtOIqO6yBFzHyqyVVhNgNkQxxx09SFLK28YnA==", + "integrity": "sha1-/ElZcL3Ii7HVo1/BfmXEoUn8Skk=", "dev": true, "optional": true, "requires": { @@ -1976,7 +1976,7 @@ "bin-version": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/bin-version/-/bin-version-3.1.0.tgz", - "integrity": "sha512-Mkfm4iE1VFt4xd4vH+gx+0/71esbfus2LsnCGe8Pi4mndSPyT+NGES/Eg99jx8/lUGWfu3z2yuB/bt5UB+iVbQ==", + "integrity": "sha1-WwnrKAdSsb0o8MnbP5by9DtsCDk=", "dev": true, "optional": true, "requires": { @@ -1987,7 +1987,7 @@ "bin-version-check": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/bin-version-check/-/bin-version-check-4.0.0.tgz", - "integrity": "sha512-sR631OrhC+1f8Cvs8WyVWOA33Y8tgwjETNPyyD/myRBXLkfS/vl74FmH/lFcRl9KY3zwGh7jFhvyk9vV3/3ilQ==", + "integrity": "sha1-fYGcYklpkfgNiT5uAqMDI2Fgj3E=", "dev": true, "optional": true, "requires": { @@ -1999,7 +1999,7 @@ "bin-wrapper": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bin-wrapper/-/bin-wrapper-4.1.0.tgz", - "integrity": "sha512-hfRmo7hWIXPkbpi0ZltboCMVrU+0ClXR/JgbCKKjlDjQf6igXa7OwdqNcFWQZPZTgiY7ZpzE3+LjjkLiTN2T7Q==", + "integrity": "sha1-mTSPLPhQMePvfvzn5TAK6q6WBgU=", "dev": true, "optional": true, "requires": { @@ -2014,7 +2014,7 @@ "download": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/download/-/download-7.1.0.tgz", - "integrity": "sha512-xqnBTVd/E+GxJVrX5/eUJiLYjCGPwMpdL+jGhGU57BvtcA7wwhtHVbXBeUk51kOpW3S7Jn3BQbN9Q1R1Km2qDQ==", + "integrity": "sha1-kFmqnXC1A+52oTKJe+beyOVYcjM=", "dev": true, "optional": true, "requires": { @@ -2044,7 +2044,7 @@ "file-type": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/file-type/-/file-type-8.1.0.tgz", - "integrity": "sha512-qyQ0pzAy78gVoJsmYeNgl8uH8yKhr1lVhW7JbzJmnlRi0I4R2eEDEJZVKG8agpDnLpacwNbDhLNG/LMdxHD2YQ==", + "integrity": "sha1-JE87fvZBu+DMoZbHJ25LMyOZ9ow=", "dev": true, "optional": true }, @@ -2058,7 +2058,7 @@ "got": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/got/-/got-8.3.2.tgz", - "integrity": "sha512-qjUJ5U/hawxosMryILofZCkm3C84PLJS/0grRIpjAwu+Lkxxj5cxeCU25BG0/3mDSpXKTyZr8oh8wIgLaH0QCw==", + "integrity": "sha1-HSP2Q5Dpf3dsrFLluTbl9RTS6Tc=", "dev": true, "optional": true, "requires": { @@ -2093,7 +2093,7 @@ "make-dir": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", - "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "integrity": "sha1-ecEDO4BRW9bSTsmTPoYMp17ifww=", "dev": true, "optional": true, "requires": { @@ -2112,14 +2112,14 @@ "p-cancelable": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.4.1.tgz", - "integrity": "sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ==", + "integrity": "sha1-NfNj1n1SCByNlYXje8zrfgu8sqA=", "dev": true, "optional": true }, "p-event": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/p-event/-/p-event-2.3.1.tgz", - "integrity": "sha512-NQCqOFhbpVTMX4qMe8PF8lbGtzZ+LCiN7pcNrb/413Na7+TRoe1xkKUzuWa/YEJdGQ0FvKtj35EEbDoVPO2kbA==", + "integrity": "sha1-WWJ57xaassPgyuiMHPuwgHmZPvY=", "dev": true, "optional": true, "requires": { @@ -2129,7 +2129,7 @@ "p-timeout": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-2.0.1.tgz", - "integrity": "sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA==", + "integrity": "sha1-2N0ZeVldLcATnh/ka4tkbLPN8Dg=", "dev": true, "optional": true, "requires": { @@ -2139,7 +2139,7 @@ "pify": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "integrity": "sha1-SyzSXFDVmHNcUCkiJP2MbfQeMjE=", "dev": true, "optional": true }, @@ -2165,7 +2165,7 @@ "binary-extensions": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", - "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "integrity": "sha1-WYr+VHVbKGilMw0q/51Ou1Mgm2U=", "dev": true }, "bl": { @@ -2204,7 +2204,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "optional": true, @@ -2217,7 +2217,7 @@ "blob": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", - "integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==", + "integrity": "sha1-1oDu7yX4zZGtUz9bAe7UjmTK9oM=", "dev": true }, "bluebird": { @@ -2229,7 +2229,7 @@ "body-parser": { "version": "1.19.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", - "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "integrity": "sha1-lrJwnlfJxOCab9Zqj9l5hE9p8Io=", "dev": true, "requires": { "bytes": "3.1.0", @@ -2247,7 +2247,7 @@ "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=", "dev": true, "requires": { "ms": "2.0.0" @@ -2262,7 +2262,7 @@ "qs": { "version": "6.7.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", + "integrity": "sha1-QdwaAV49WB8WIXdr4xr7KHapsbw=", "dev": true } } @@ -2276,7 +2276,7 @@ "bootstrap": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-3.4.1.tgz", - "integrity": "sha512-yN5oZVmRCwe5aKwzRj6736nSmKDX7pLYwsXiCj/EYmo16hODaBiT4En5btW/jhBF/seV+XMx3aYwukYC3A49DA==" + "integrity": "sha1-w6NH1Bniia0R9AM+PEEyuHwIHXI=" }, "bootstrap-social": { "version": "5.1.1", @@ -2290,7 +2290,7 @@ "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "integrity": "sha1-PH/L9SnYcibz0vUrlm/1Jx60Qd0=", "dev": true, "requires": { "balanced-match": "^1.0.0", @@ -2300,7 +2300,7 @@ "braces": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "integrity": "sha1-WXn9PxTNUxVl5fot8av/8d+u5yk=", "dev": true, "requires": { "arr-flatten": "^1.1.0", @@ -2357,7 +2357,7 @@ "buffer-alloc": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", - "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "integrity": "sha1-iQ3ZDZI6hz4I4Q5f1RpX5bfM4Ow=", "dev": true, "requires": { "buffer-alloc-unsafe": "^1.1.0", @@ -2367,7 +2367,7 @@ "buffer-alloc-unsafe": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", - "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", + "integrity": "sha1-vX3CauKXLQ7aJTvgYdupkjScGfA=", "dev": true }, "buffer-crc32": { @@ -2392,7 +2392,7 @@ "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "integrity": "sha1-MnE7wCj3XAL9txDXx7zsHyxgcO8=", "dev": true }, "bufferstreams": { @@ -2407,13 +2407,13 @@ "bytes": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "integrity": "sha1-9s95M6Ng4FiPqf3oVlHNx/gF0fY=", "dev": true }, "cache-base": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "integrity": "sha1-Cn9GQWgxyLZi7jb+TnxZ129marI=", "dev": true, "requires": { "collection-visit": "^1.0.0", @@ -2460,7 +2460,7 @@ "normalize-url": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-2.0.1.tgz", - "integrity": "sha512-D6MUW4K/VzoJ4rJ01JFKxDrtY1v9wrgzCX5f2qj/lzH1m/lW6MhUZFKerVsnyjOhOsYzI9Kqqak+10l4LvLpMw==", + "integrity": "sha1-g1qdoVUfom9w6SMpBpojqmV01+Y=", "dev": true, "optional": true, "requires": { @@ -2547,7 +2547,7 @@ "caniuse-api": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", - "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", + "integrity": "sha1-Xk2Q4idJYdRikZl99Znj7QCO5MA=", "dev": true, "requires": { "browserslist": "^4.0.0", @@ -2570,7 +2570,7 @@ "caw": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/caw/-/caw-2.0.1.tgz", - "integrity": "sha512-Cg8/ZSBEa8ZVY9HspcGUYaK63d/bN7rqS3CYCzEGUxuYv6UlmcjzDUz2fCFFHyTvUW5Pk0I+3hkA3iXlIj6guA==", + "integrity": "sha1-bDygcfwZRyCIPC3F2psHS/x+npU=", "dev": true, "optional": true, "requires": { @@ -2593,7 +2593,7 @@ "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "integrity": "sha1-zUJUFnelQzPPVBpJEIwUMrRMlCQ=", "dev": true, "requires": { "ansi-styles": "^3.2.1", @@ -2604,7 +2604,7 @@ "chardet": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "integrity": "sha1-kAlISfCTfy7twkJdDSip5fDLrZ4=", "dev": true }, "chart.js": { @@ -2628,7 +2628,7 @@ "chartjs-color-string": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/chartjs-color-string/-/chartjs-color-string-0.6.0.tgz", - "integrity": "sha512-TIB5OKn1hPJvO7JcteW4WY/63v6KwEdt6udfnDE9iCAZgy+V4SrbSxoIbTw/xkUIapjEI4ExGtD0+6D3KyFd7A==", + "integrity": "sha1-HfCWYhwOcHIKZPQTXqFx0FFAL3E=", "requires": { "color-name": "^1.0.0" } @@ -2636,7 +2636,7 @@ "chokidar": { "version": "2.1.8", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", - "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", + "integrity": "sha1-gEs6e2qZNYw8XGHnHYco8EHP+Rc=", "dev": true, "requires": { "anymatch": "^2.0.0", @@ -2686,7 +2686,7 @@ "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "integrity": "sha1-Dc1p/yOhybEf0JeDFmRKA4ghamU=", "dev": true } } @@ -2694,7 +2694,7 @@ "class-utils": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "integrity": "sha1-+TNprouafOAv1B+q0MqDAzGQxGM=", "dev": true, "requires": { "arr-union": "^3.1.0", @@ -2717,7 +2717,7 @@ "clean-css": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.1.tgz", - "integrity": "sha512-4ZxI6dy4lrY6FHzfiy1aEOXgu4LIsW2MhwG0VBKdcoGoH/XLFgaHSdLTGr4O8Be6A8r3MOphEiI8Gc1n0ecf3g==", + "integrity": "sha1-LUEe92uFabbQyEBo2r6FsKpeXBc=", "dev": true, "requires": { "source-map": "~0.6.0" @@ -2726,7 +2726,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", "dev": true } } @@ -2734,7 +2734,7 @@ "cli-color": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-1.4.0.tgz", - "integrity": "sha512-xu6RvQqqrWEo6MPR1eixqGPywhYBHRs653F9jfXB2Hx4jdM/3WxiNE1vppRmxtMIfl16SFYTpYlrnqH/HsK/2w==", + "integrity": "sha1-fRBzj0hSaCT4/n2lGFfLD1cv4B8=", "dev": true, "requires": { "ansi-regex": "^2.1.1", @@ -2763,7 +2763,7 @@ "clipboard": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.4.tgz", - "integrity": "sha512-Vw26VSLRpJfBofiVaFb/I8PVfdI1OxKcYShe6fm0sP/DtmiWQNCjhM/okTvdCo0G+lMMm1rMYbk4IK4x1X+kgQ==", + "integrity": "sha1-g22v1mzw/qXXHOXVsL9ulYAJES0=", "requires": { "good-listener": "^1.2.2", "select": "^1.1.2", @@ -2812,7 +2812,7 @@ "cloneable-readable": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.3.tgz", - "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", + "integrity": "sha1-EgoAywU7+2OiIucJ+Wg+ouEdjOw=", "dev": true, "requires": { "inherits": "^2.0.1", @@ -2855,7 +2855,7 @@ "coa": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/coa/-/coa-2.0.2.tgz", - "integrity": "sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==", + "integrity": "sha1-Q/bCEVG07yv1cYfbDXPeIp4+fsM=", "dev": true, "requires": { "@types/q": "^1.5.1", @@ -2893,7 +2893,7 @@ "color": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/color/-/color-3.1.2.tgz", - "integrity": "sha512-vXTJhHebByxZn3lDvDJYw4lR5+uB3vuoHsuYA5AKuxRVn5wzzIfQKGLBmgdVRHKTJYeK5rvJcHnrd0Li49CFpg==", + "integrity": "sha1-aBSOf4XUGtdknF+oyBBvCY0inhA=", "dev": true, "requires": { "color-convert": "^1.9.1", @@ -2925,12 +2925,12 @@ "color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "integrity": "sha1-wqCah6y95pVD3m9j+jmVyCbFNqI=" }, "color-string": { "version": "1.5.3", "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.3.tgz", - "integrity": "sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==", + "integrity": "sha1-ybvF8BtYtUkvPWhXRZy2WQziBMw=", "dev": true, "requires": { "color-name": "^1.0.0", @@ -2940,7 +2940,7 @@ "color-support": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "integrity": "sha1-k4NDeaHMmgxh+C9S8NBDIiUb1aI=", "dev": true }, "colornames": { @@ -2952,13 +2952,13 @@ "colors": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "integrity": "sha1-xQSRR51MG9rtLJztMs98fcI2D3g=", "dev": true }, "colorspace": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.2.tgz", - "integrity": "sha512-vt+OoIP2d76xLhjwbBaucYlNSpPsrJWPlBTtwCpQKIu6/CSMutyzX93O/Do0qzpH3YoHEes8YEFXyZ797rEhzQ==", + "integrity": "sha1-4BKJUNCCuGohaFgHlqCqXWxo2MU=", "dev": true, "requires": { "color": "3.0.x", @@ -2968,7 +2968,7 @@ "color": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/color/-/color-3.0.0.tgz", - "integrity": "sha512-jCpd5+s0s0t7p3pHQKpnJ0TpQKKdleP71LWcA0aqiljpiuAkOSUFN/dyH8ZwF0hRmFlrIuRhufds1QyEP9EB+w==", + "integrity": "sha1-2SC0Mo1TSjrIKV1o971LpsQnvpo=", "dev": true, "requires": { "color-convert": "^1.9.1", @@ -2995,7 +2995,7 @@ "combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "integrity": "sha1-w9RaizT9cwYxoRCoolIGgrMdWn8=", "requires": { "delayed-stream": "~1.0.0" } @@ -3016,7 +3016,7 @@ "component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "integrity": "sha1-FuQHD7qK4ptnnyIVhT7hgasuq8A=", "dev": true }, "component-inherit": { @@ -3034,7 +3034,7 @@ "concat-stream": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "integrity": "sha1-kEvfGUzTEi/Gdcd/xKw9T/D9GjQ=", "dev": true, "requires": { "buffer-from": "^1.0.0", @@ -3066,7 +3066,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { @@ -3078,7 +3078,7 @@ "concat-with-sourcemaps": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz", - "integrity": "sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg==", + "integrity": "sha1-1OqT8FriV5CVG5nns7CeOQikCC4=", "dev": true, "requires": { "source-map": "^0.6.1" @@ -3087,7 +3087,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", "dev": true } } @@ -3095,7 +3095,7 @@ "config-chain": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.12.tgz", - "integrity": "sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA==", + "integrity": "sha1-D96NCRIA616AjK8l/mGMAvSOTvo=", "dev": true, "optional": true, "requires": { @@ -3106,7 +3106,7 @@ "connect": { "version": "3.7.0", "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", - "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "integrity": "sha1-XUk0iRDKpeB6AYALAw0MNfIEhPg=", "dev": true, "requires": { "debug": "2.6.9", @@ -3118,7 +3118,7 @@ "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=", "dev": true, "requires": { "ms": "2.0.0" @@ -3142,7 +3142,7 @@ "consolidate": { "version": "0.15.1", "resolved": "https://registry.npmjs.org/consolidate/-/consolidate-0.15.1.tgz", - "integrity": "sha512-DW46nrsMJgy9kqAbPt5rKaCr7uFtpo4mSUvLHIUbJEjm0vo+aY5QLwBUq3FK4tRnJr/X0Psc0C4jf/h+HtXSMw==", + "integrity": "sha1-IasEMjXHGgfUXZqtmFk7DbpWurc=", "dev": true, "requires": { "bluebird": "^3.1.1" @@ -3151,7 +3151,7 @@ "content-disposition": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "integrity": "sha1-4TDK9+cnkIfFYWwgB9BIVpiYT70=", "dev": true, "optional": true, "requires": { @@ -3161,7 +3161,7 @@ "content-type": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "integrity": "sha1-4TjMdeBAxyexlm/l5fjJruJW/js=", "dev": true }, "convert-source-map": { @@ -3188,7 +3188,7 @@ "copy-props": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/copy-props/-/copy-props-2.0.4.tgz", - "integrity": "sha512-7cjuUME+p+S3HZlbllgsn2CDwS+5eCCX16qBgNC4jgSTf49qR1VKy/Zhl400m0IQXl/bPGEVqncgUUMjrr4s8A==", + "integrity": "sha1-k7scrfr9MdpbuKnUtB9HHsOnLf4=", "dev": true, "requires": { "each-props": "^1.3.0", @@ -3247,7 +3247,7 @@ "cosmiconfig": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", - "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "integrity": "sha1-BA9yaAnFked6F8CjYmykW08Wixo=", "dev": true, "requires": { "import-fresh": "^2.0.0", @@ -3259,7 +3259,7 @@ "cross-spawn": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "integrity": "sha1-Sl7Hxk364iw6FBJNus3uhG2Ay8Q=", "dev": true, "requires": { "nice-try": "^1.0.4", @@ -3272,7 +3272,7 @@ "css": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/css/-/css-2.2.4.tgz", - "integrity": "sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw==", + "integrity": "sha1-xkZ1XHOXHyu6amAeLPL9cbEpiSk=", "dev": true, "requires": { "inherits": "^2.0.3", @@ -3284,7 +3284,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", "dev": true } } @@ -3298,7 +3298,7 @@ "css-declaration-sorter": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-4.0.1.tgz", - "integrity": "sha512-BcxQSKTSEEQUftYpBVnsH4SF05NTuBokb19/sBt6asXGKZ/6VP7PLG1CBCkFDYOnhXhPh0jMhO6xZ71oYHXHBA==", + "integrity": "sha1-wZiUD2OnbX42wecQGLABchBUyyI=", "dev": true, "requires": { "postcss": "^7.0.1", @@ -3320,7 +3320,7 @@ "css-select-base-adapter": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", - "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==", + "integrity": "sha1-Oy/0lyzDYquIVhUHqVQIoUMhNdc=", "dev": true }, "css-tree": { @@ -3354,7 +3354,7 @@ "cssnano": { "version": "4.1.10", "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-4.1.10.tgz", - "integrity": "sha512-5wny+F6H4/8RgNlaqab4ktc3e0/blKutmq8yNlBFXA//nSFFAqAngjNVRzUvCgYROULmZZUoosL/KSoZo5aUaQ==", + "integrity": "sha1-CsQfCxPRPUZUh+ERt3jULaYxuLI=", "dev": true, "requires": { "cosmiconfig": "^5.0.0", @@ -3366,7 +3366,7 @@ "cssnano-preset-default": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-4.0.7.tgz", - "integrity": "sha512-x0YHHx2h6p0fCl1zY9L9roD7rnlltugGu7zXSKQx6k2rYw0Hi3IqxcoAGF7u9Q5w1nt7vK0ulxV8Lo+EvllGsA==", + "integrity": "sha1-UexmLM/KD4izltzZZ5zbkxvhf3Y=", "dev": true, "requires": { "css-declaration-sorter": "^4.0.1", @@ -3416,7 +3416,7 @@ "cssnano-util-raw-cache": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/cssnano-util-raw-cache/-/cssnano-util-raw-cache-4.0.1.tgz", - "integrity": "sha512-qLuYtWK2b2Dy55I8ZX3ky1Z16WYsx544Q0UWViebptpwn/xDBmog2TLg4f+DBMg1rJ6JDWtn96WHbOKDWt1WQA==", + "integrity": "sha1-sm1f1fcqEd/np4RvtMZyYPlr8oI=", "dev": true, "requires": { "postcss": "^7.0.0" @@ -3425,7 +3425,7 @@ "cssnano-util-same-parent": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz", - "integrity": "sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q==", + "integrity": "sha1-V0CC+yhZ0ttDOFWDXZqEVuoYu/M=", "dev": true }, "csso": { @@ -3497,7 +3497,7 @@ "d": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", - "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "integrity": "sha1-hpgJU3LVjb7jRv/Qxwk/mfj561o=", "dev": true, "requires": { "es5-ext": "^0.10.50", @@ -3538,7 +3538,7 @@ "debug": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "integrity": "sha1-O3ImAlUQnGtYnO4FDx1RYTlmR5E=", "dev": true, "requires": { "ms": "^2.1.1" @@ -3547,7 +3547,7 @@ "debug-fabulous": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/debug-fabulous/-/debug-fabulous-1.1.0.tgz", - "integrity": "sha512-GZqvGIgKNlUnHUPQhepnUZFIMoi3dgZKQBzKDeL2g7oJF9SNAji/AAu36dusFUas0O+pae74lNeoIPHqXWDkLg==", + "integrity": "sha1-r4oIYyRlIk70F0qfBjCMPCoevI4=", "dev": true, "requires": { "debug": "3.X", @@ -3610,7 +3610,7 @@ "make-dir": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", - "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "integrity": "sha1-ecEDO4BRW9bSTsmTPoYMp17ifww=", "dev": true, "optional": true, "requires": { @@ -3641,7 +3641,7 @@ "decompress-tar": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/decompress-tar/-/decompress-tar-4.1.1.tgz", - "integrity": "sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ==", + "integrity": "sha1-cYy9P8sWIJcW5womuE57pFkuWvE=", "dev": true, "optional": true, "requires": { @@ -3662,7 +3662,7 @@ "decompress-tarbz2": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz", - "integrity": "sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A==", + "integrity": "sha1-MIKluIDqQEOBY0nzeLVsUWvho5s=", "dev": true, "optional": true, "requires": { @@ -3676,7 +3676,7 @@ "file-type": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/file-type/-/file-type-6.2.0.tgz", - "integrity": "sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg==", + "integrity": "sha1-5QzXXTVv/tTjBtxPW89Sp5kDqRk=", "dev": true, "optional": true } @@ -3685,7 +3685,7 @@ "decompress-targz": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/decompress-targz/-/decompress-targz-4.1.1.tgz", - "integrity": "sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w==", + "integrity": "sha1-wJvDXE0R894J8tLaU+neI+fOHu4=", "dev": true, "optional": true, "requires": { @@ -3752,7 +3752,7 @@ "default-compare": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/default-compare/-/default-compare-1.0.0.tgz", - "integrity": "sha512-QWfXlM0EkAbqOCbD/6HjdwT19j7WCkMyiRhWilc4H9/5h/RzTF9gv5LYh1+CmDV5d1rki6KAWLtQale0xt20eQ==", + "integrity": "sha1-y2ETGESthNhHiPto/QFoHKd4Gi8=", "dev": true, "requires": { "kind-of": "^5.0.2" @@ -3761,7 +3761,7 @@ "kind-of": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "integrity": "sha1-cpyR4thXt6QZofmqZWhcTDP1hF0=", "dev": true } } @@ -3775,7 +3775,7 @@ "define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "integrity": "sha1-z4jabL7ib+bbcJT2HYcMvYTO6fE=", "dev": true, "requires": { "object-keys": "^1.0.12" @@ -3784,7 +3784,7 @@ "define-property": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "integrity": "sha1-1Flono1lS6d+AqgX+HENcCyxbp0=", "dev": true, "requires": { "is-descriptor": "^1.0.2", @@ -3794,7 +3794,7 @@ "is-accessor-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -3803,7 +3803,7 @@ "is-data-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -3812,7 +3812,7 @@ "is-descriptor": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=", "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", @@ -3830,7 +3830,7 @@ "delegate": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz", - "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==" + "integrity": "sha1-tmtxwxWFIuirV0T3INjKDCr1kWY=" }, "depd": { "version": "1.1.2", @@ -3859,7 +3859,7 @@ "diagnostics": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/diagnostics/-/diagnostics-1.1.1.tgz", - "integrity": "sha512-8wn1PmdunLJ9Tqbx+Fx/ZEuHfJf4NKSN2ZBj7SJC/OWRWha843+WsTjqMe1B5E3p28jqBlp+mJ2fPVxPyNgYKQ==", + "integrity": "sha1-yrasM99wydmnJ0kK5DrJladpsio=", "dev": true, "requires": { "colorspace": "1.1.x", @@ -3870,12 +3870,12 @@ "diff": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==" + "integrity": "sha1-gAwN0eCov7yVg1wgKtIg/jF+WhI=" }, "dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "integrity": "sha1-Vtv3PZkqSpO6FYT0U0Bj/S5BcX8=", "dev": true, "requires": { "path-type": "^4.0.0" @@ -3884,7 +3884,7 @@ "path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "integrity": "sha1-hO0BwKe6OAr+CdkKjBgNzZ0DBDs=", "dev": true } } @@ -3892,7 +3892,7 @@ "doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "integrity": "sha1-rd6+rXKmV023g2OdyHoSF3OXOWE=", "dev": true, "requires": { "esutils": "^2.0.2" @@ -3923,7 +3923,7 @@ "domelementtype": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz", - "integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ==", + "integrity": "sha1-H4vf6R9aeAYydOgDtL3O326U+U0=", "dev": true } } @@ -3931,7 +3931,7 @@ "domelementtype": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", - "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", + "integrity": "sha1-0EjESzew0Qp/Kj1f7j9DM9eQSB8=", "dev": true }, "domexception": { @@ -3954,7 +3954,7 @@ "domhandler": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", - "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "integrity": "sha1-iAUJfpM9ZehVRvcm1g9euItE+AM=", "dev": true, "requires": { "domelementtype": "1" @@ -3963,7 +3963,7 @@ "domutils": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", - "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", + "integrity": "sha1-Vuo0HoNOBuZ0ivehyyXaZ+qfjCo=", "dev": true, "requires": { "dom-serializer": "0", @@ -3982,7 +3982,7 @@ "download": { "version": "6.2.5", "resolved": "https://registry.npmjs.org/download/-/download-6.2.5.tgz", - "integrity": "sha512-DpO9K1sXAST8Cpzb7kmEhogJxymyVUd5qz/vCOSyvwtp2Klj2XcDt5YUuasgxka44SxF0q5RriKIwJmQHG2AuA==", + "integrity": "sha1-rNalQuTNC7Qspwz8mMnkOwcDlxQ=", "dev": true, "optional": true, "requires": { @@ -4016,7 +4016,7 @@ "make-dir": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", - "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "integrity": "sha1-ecEDO4BRW9bSTsmTPoYMp17ifww=", "dev": true, "optional": true, "requires": { @@ -4051,7 +4051,7 @@ "duplexify": { "version": "3.7.1", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", - "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "integrity": "sha1-Kk31MX9sz9kfhtb9JdjYoQO4gwk=", "dev": true, "requires": { "end-of-stream": "^1.0.0", @@ -4095,7 +4095,7 @@ "each-props": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/each-props/-/each-props-1.3.2.tgz", - "integrity": "sha512-vV0Hem3zAGkJAyU7JSjixeU66rwdynTAa1vofCrSA5fEln+m67Az9CcnkVD776/fsN/UjIWmBDoNRS6t6G9RfA==", + "integrity": "sha1-6kWkFNFt1c+kGbGoFyDVygaJIzM=", "dev": true, "requires": { "is-plain-object": "^2.0.1", @@ -4153,7 +4153,7 @@ "end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "integrity": "sha1-WuZKX0UFe682JuwU2gyl5LJDHrA=", "dev": true, "requires": { "once": "^1.4.0" @@ -4265,7 +4265,7 @@ "entities": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz", - "integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==", + "integrity": "sha1-aNYITKsbB5dnVA2A5Wo5tCPkq/Q=", "dev": true }, "env-variable": { @@ -4277,7 +4277,7 @@ "errno": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", - "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", + "integrity": "sha1-RoTXF3mtOa8Xfj8AeZb3xnyFJhg=", "dev": true, "optional": true, "requires": { @@ -4287,7 +4287,7 @@ "error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "integrity": "sha1-tKxAZIEH/c3PriQvQovqihTU8b8=", "dev": true, "requires": { "is-arrayish": "^0.2.1" @@ -4347,7 +4347,7 @@ "es6-promise": { "version": "4.2.8", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", + "integrity": "sha1-TrIVlMlyvEBVPSduUQU5FD21Pgo=", "dev": true }, "es6-symbol": { @@ -4363,7 +4363,7 @@ "es6-weak-map": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", - "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", + "integrity": "sha1-ttofFswswNm+Q+a9v8Xn383zHVM=", "dev": true, "requires": { "d": "1", @@ -4406,7 +4406,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", "dev": true, "optional": true } @@ -4460,7 +4460,7 @@ "ansi-regex": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "integrity": "sha1-i5+PCM8ay4Q3Vqg5yox+MWjFGZc=", "dev": true }, "glob-parent": { @@ -4494,19 +4494,19 @@ "resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "integrity": "sha1-SrzYUq0y3Xuqv+m0DgCjbbXzkuY=", "dev": true }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "integrity": "sha1-7gpkyK9ejO6mdoexM3YeG+y9HT0=", "dev": true }, "strip-ansi": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "integrity": "sha1-jJpTb+tq/JYr36WxBKUJHBrZwK4=", "dev": true, "requires": { "ansi-regex": "^4.1.0" @@ -4527,7 +4527,7 @@ "eslint-utils": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", - "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", + "integrity": "sha1-dP7HxU0Hdrb2fgJRBAtYBlZOmB8=", "dev": true, "requires": { "eslint-visitor-keys": "^1.1.0" @@ -4553,7 +4553,7 @@ "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "integrity": "sha1-E7BM2z5sXRnfkatph6hpVhmwqnE=", "dev": true }, "esquery": { @@ -4595,13 +4595,13 @@ "estraverse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "integrity": "sha1-OYrT88WiSUi+dyXoPRGn3ijNvR0=", "dev": true }, "esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "integrity": "sha1-dNLrTeC42hKTcRkQ1Qd1ubcQ72Q=", "dev": true }, "event-emitter": { @@ -4631,7 +4631,7 @@ "exec-buffer": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/exec-buffer/-/exec-buffer-3.2.0.tgz", - "integrity": "sha512-wsiD+2Tp6BWHoVv3B+5Dcx6E7u5zky+hUwOHjuH2hKSLR3dvRmX8fk8UD8uqQixHs4Wk6eDmiegVrMPjKj7wpA==", + "integrity": "sha1-sWhtvZBMfPmC5lLB9aebHlVzCCs=", "dev": true, "optional": true, "requires": { @@ -4705,7 +4705,7 @@ "executable": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/executable/-/executable-4.1.1.tgz", - "integrity": "sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==", + "integrity": "sha1-QVMr/zYdPlevTXY7cFgtsY9dEzw=", "dev": true, "optional": true, "requires": { @@ -4730,7 +4730,7 @@ "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=", "dev": true, "requires": { "ms": "2.0.0" @@ -4774,7 +4774,7 @@ "fill-range": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", - "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", + "integrity": "sha1-6x53OrsFbc2N8r/favWbizqTZWU=", "dev": true, "requires": { "is-number": "^2.1.0", @@ -4831,7 +4831,7 @@ "ext-list": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/ext-list/-/ext-list-2.2.2.tgz", - "integrity": "sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA==", + "integrity": "sha1-C5jmTtgvWs8PKTG6v2khLvUt3Tc=", "dev": true, "optional": true, "requires": { @@ -4841,7 +4841,7 @@ "ext-name": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/ext-name/-/ext-name-5.0.0.tgz", - "integrity": "sha512-yblEwXAbGv1VQDmow7s38W77hzAgJAO50ztBLMcUyUBfxv1HC+LGwtiEN+Co6LtlqT/5uwVOxsD4TNIilWhwdQ==", + "integrity": "sha1-cHgZgdGD7hXROZPIgiBFxQbI8KY=", "dev": true, "optional": true, "requires": { @@ -4852,7 +4852,7 @@ "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + "integrity": "sha1-+LETa0Bx+9jrFAr/hYsQGewpFfo=" }, "extend-shallow": { "version": "3.0.2", @@ -4867,7 +4867,7 @@ "is-extendable": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "integrity": "sha1-p0cPnkJnM9gb2B4RVSZOOjUHyrQ=", "dev": true, "requires": { "is-plain-object": "^2.0.4" @@ -4878,7 +4878,7 @@ "external-editor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "integrity": "sha1-ywP3QL764D6k0oPK7SdBqD8zVJU=", "dev": true, "requires": { "chardet": "^0.7.0", @@ -4889,7 +4889,7 @@ "extglob": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "integrity": "sha1-rQD+TcYSqSMuhxhxHcXLWrAoVUM=", "dev": true, "requires": { "array-unique": "^0.3.2", @@ -4923,7 +4923,7 @@ "is-accessor-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -4932,7 +4932,7 @@ "is-data-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -4941,7 +4941,7 @@ "is-descriptor": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=", "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", @@ -4959,7 +4959,7 @@ "fancy-log": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz", - "integrity": "sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw==", + "integrity": "sha1-28GRVPVYaQFQojlToK29A1vkX8c=", "dev": true, "requires": { "ansi-gray": "^0.1.1", @@ -4990,7 +4990,7 @@ "braces": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "integrity": "sha1-NFThpGLujVmeI23zNs2epPiv4Qc=", "dev": true, "requires": { "fill-range": "^7.0.1" @@ -4999,7 +4999,7 @@ "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "integrity": "sha1-GRmmp8df44ssfHflGYU12prN2kA=", "dev": true, "requires": { "to-regex-range": "^5.0.1" @@ -5026,13 +5026,13 @@ "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "integrity": "sha1-dTU0W4lnNNX4DE0GxQlVUnoU8Ss=", "dev": true }, "micromatch": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", - "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "integrity": "sha1-T8sJmb+fvC/L3SEvbWKbmlbDklk=", "dev": true, "requires": { "braces": "^3.0.1", @@ -5048,7 +5048,7 @@ "to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "integrity": "sha1-FkjESq58jZiKMmAY7XL1tN0DkuQ=", "dev": true, "requires": { "is-number": "^7.0.0" @@ -5098,7 +5098,7 @@ "file-entry-cache": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", - "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "integrity": "sha1-yg9u+m3T1WEzP7FFFQZcL6/fQ5w=", "dev": true, "requires": { "flat-cache": "^2.0.1" @@ -5126,7 +5126,7 @@ "filenamify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-2.1.0.tgz", - "integrity": "sha512-ICw7NTT6RsDp2rnYKVd8Fu4cr6ITzGy3+u4vUujPkabyaz+03F24NWEX7fs5fp+kBonlaqPH8fAO2NM+SXt/JA==", + "integrity": "sha1-iPr0lfsbR6v9YSMAACoWIoxnfuk=", "dev": true, "optional": true, "requires": { @@ -5161,7 +5161,7 @@ "finalhandler": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "integrity": "sha1-t+fQAP/RGTjQ/bBTUG9uur6fWH0=", "dev": true, "requires": { "debug": "2.6.9", @@ -5176,7 +5176,7 @@ "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=", "dev": true, "requires": { "ms": "2.0.0" @@ -5236,7 +5236,7 @@ "fined": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/fined/-/fined-1.2.0.tgz", - "integrity": "sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng==", + "integrity": "sha1-0AvszxqitHXRbUI7Aji3E6LEo3s=", "dev": true, "requires": { "expand-tilde": "^2.0.2", @@ -5290,13 +5290,13 @@ "flagged-respawn": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz", - "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==", + "integrity": "sha1-595vEnnd2cqarIpZcdYYYGs6q0E=", "dev": true }, "flat-cache": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", - "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "integrity": "sha1-XSltbwS9pEpGMKMBQTvbwuwIXsA=", "dev": true, "requires": { "flatted": "^2.0.0", @@ -5307,18 +5307,18 @@ "flatpickr": { "version": "4.5.2", "resolved": "https://registry.npmjs.org/flatpickr/-/flatpickr-4.5.2.tgz", - "integrity": "sha512-jDy4QYGpmiy7+Qk8QvKJ4spjDdxcx9cxMydmq1x427HkKWBw0qizLYeYM2F6tMcvvqGjU5VpJS55j4LnsaBblA==" + "integrity": "sha1-R8itRyoJbl+350uAmwcDU1OD8g0=" }, "flatted": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.1.tgz", - "integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==", + "integrity": "sha1-aeV8qo8OrLwoHS4stFjUb9tEngg=", "dev": true }, "flush-write-stream": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", - "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", + "integrity": "sha1-jdfYc6G6vCB9lOrQwuDkQnbr8ug=", "dev": true, "requires": { "inherits": "^2.0.3", @@ -5405,7 +5405,7 @@ "form-data": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "integrity": "sha1-3M5SwF9kTymManq5Nr1yTO/786Y=", "requires": { "asynckit": "^0.4.0", "combined-stream": "^1.0.6", @@ -5476,7 +5476,7 @@ "fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "integrity": "sha1-a+Dem+mYzhavivwkSXue6bfM2a0=", "dev": true, "optional": true }, @@ -5493,7 +5493,7 @@ "fs-readfile-promise": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/fs-readfile-promise/-/fs-readfile-promise-3.0.1.tgz", - "integrity": "sha512-LsSxMeaJdYH27XrW7Dmq0Gx63mioULCRel63B5VeELYLavi1wF5s0XfsIdKDFdCL9hsfQ2qBvXJszQtQJ9h17A==", + "integrity": "sha1-0NMHt/au38kgwx+m5XEu+qKXyVg=", "dev": true, "requires": { "graceful-fs": "^4.1.11" @@ -6056,7 +6056,7 @@ "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "integrity": "sha1-pWiZ0+o8m6uHS7l3O3xe3pL0iV0=", "dev": true }, "functional-red-black-tree": { @@ -6068,13 +6068,13 @@ "get-caller-file": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "integrity": "sha1-+Xj6TJDR3+f/LWvtoqUV5xO9z0o=", "dev": true }, "get-proxy": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/get-proxy/-/get-proxy-2.1.0.tgz", - "integrity": "sha512-zmZIaQTWnNQb4R4fJUEp/FC51eZsc6EkErspy3xtIYStaq8EB/hDIWipxsal+E8rz0qD7f2sL/NA9Xee4RInJw==", + "integrity": "sha1-NJ8rTZHUTE1NTpy6KtkBQ/rF75M=", "dev": true, "optional": true, "requires": { @@ -6222,7 +6222,7 @@ "pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "integrity": "sha1-tKIRaBW94vTh6mAjVOjHVWUQemQ=", "dev": true, "optional": true, "requires": { @@ -6372,7 +6372,7 @@ "glob-watcher": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-5.0.3.tgz", - "integrity": "sha512-8tWsULNEPHKQ2MR4zXuzSmqbdyV5PtwwCaWSGQ1WwHsJ07ilNeN1JB8ntxhckbnpSHaf9dXFUHzIWvm1I13dsg==", + "integrity": "sha1-iKir8cTRMeuTkomUvEpZPC5d1iY=", "dev": true, "requires": { "anymatch": "^2.0.0", @@ -6398,7 +6398,7 @@ "global-modules": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", - "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "integrity": "sha1-bXcPDrUjrHgWTXK15xqIdyZcw+o=", "dev": true, "requires": { "global-prefix": "^1.0.1", @@ -6422,7 +6422,7 @@ "globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "integrity": "sha1-q4eVM4hooLq9hSV1gBjCp+uVxC4=", "dev": true }, "globby": { @@ -6458,7 +6458,7 @@ "glogg": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.2.tgz", - "integrity": "sha512-5mwUoSuBk44Y4EshyiqcH95ZntbDdTQqA3QYSrxmzj28Ai0vXBGMH1ApSANH14j2sIRtqCEyg6PfsuP7ElOEDA==", + "integrity": "sha1-LX3XAr7aIus7/634gGltpthGMT8=", "dev": true, "requires": { "sparkles": "^1.0.0" @@ -6475,7 +6475,7 @@ "got": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/got/-/got-7.1.0.tgz", - "integrity": "sha512-Y5WMo7xKKq1muPsxD+KmrR8DH5auG7fBdDVueZwETwV6VytKyU9OX/ddpq2/1hp1vIPvVb4T81dKQz3BivkNLw==", + "integrity": "sha1-BUUP2ECU5rvqVvRRpDqcKJFmOFo=", "dev": true, "optional": true, "requires": { @@ -6519,7 +6519,7 @@ "gulp": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.2.tgz", - "integrity": "sha512-dvEs27SCZt2ibF29xYgmnwwCYZxdxhQ/+LFWlbAW8y7jt68L/65402Lz3+CKy0Ov4rOs+NERmDq7YlZaDqUIfA==", + "integrity": "sha1-VDZRBw/Q9qsKBlDGo+b/WnywnKo=", "dev": true, "requires": { "glob-watcher": "^5.0.3", @@ -6552,7 +6552,7 @@ "gulp-babel": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/gulp-babel/-/gulp-babel-8.0.0.tgz", - "integrity": "sha512-oomaIqDXxFkg7lbpBou/gnUkX51/Y/M2ZfSjL2hdqXTAlSWZcgZtd2o0cOH0r/eE8LWD0+Q/PsLsr2DKOoqToQ==", + "integrity": "sha1-4NqW9PLsSojdOjAw9HbjirISbYc=", "dev": true, "requires": { "plugin-error": "^1.0.1", @@ -6572,7 +6572,7 @@ "gulp-clean-css": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/gulp-clean-css/-/gulp-clean-css-4.2.0.tgz", - "integrity": "sha512-r4zQsSOAK2UYUL/ipkAVCTRg/2CLZ2A+oPVORopBximRksJ6qy3EX1KGrIWT4ZrHxz3Hlobb1yyJtqiut7DNjA==", + "integrity": "sha1-kV7CWNxtPmpQBD9hAGbVwurE9U4=", "dev": true, "requires": { "clean-css": "4.2.1", @@ -6610,7 +6610,7 @@ "through2": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.1.tgz", - "integrity": "sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww==", + "integrity": "sha1-OSducTwzAu3544jdnIEt07glvVo=", "dev": true, "requires": { "readable-stream": "2 || 3" @@ -6692,7 +6692,7 @@ "gulp-eslint": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/gulp-eslint/-/gulp-eslint-6.0.0.tgz", - "integrity": "sha512-dCVPSh1sA+UVhn7JSQt7KEb4An2sQNbOdB3PA8UCfxsoPlAKjJHxYHGXdXC7eb+V1FAnilSFFqslPrq037l1ig==", + "integrity": "sha1-fUArtF+KZ2UrhoJ3ARgSBXNwqDI=", "dev": true, "requires": { "eslint": "^6.0.0", @@ -6767,7 +6767,7 @@ "gulp-less": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/gulp-less/-/gulp-less-4.0.1.tgz", - "integrity": "sha512-hmM2k0FfQp7Ptm3ZaqO2CkMX3hqpiIOn4OHtuSsCeFym63F7oWlEua5v6u1cIjVUKYsVIs9zPg9vbqTEb/udpA==", + "integrity": "sha1-NIwzpd3nogfFdxsdgmHRrBAhzu0=", "dev": true, "requires": { "accord": "^0.29.0", @@ -6846,7 +6846,7 @@ "gulp-notify": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/gulp-notify/-/gulp-notify-3.2.0.tgz", - "integrity": "sha512-qEocs1UVoDKKUjfsxJNMNwkRla0PbsyJwsqNNXpzYWsLQ29LhxRMY3wnTGZcc4hMHtalnvah/Dwlwb4NijH/0A==", + "integrity": "sha1-KugiUAnfiB7vWb5d1aLxM3OHdk4=", "dev": true, "requires": { "ansi-colors": "^1.0.1", @@ -6898,7 +6898,7 @@ "lodash.template": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz", - "integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==", + "integrity": "sha1-+XYZXPPzR9DV9SSDVp/oAxzM6Ks=", "dev": true, "requires": { "lodash._reinterpolate": "^3.0.0", @@ -6908,7 +6908,7 @@ "lodash.templatesettings": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz", - "integrity": "sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==", + "integrity": "sha1-5IExDwSdPPbUfpEq0JMTsVTw+zM=", "dev": true, "requires": { "lodash._reinterpolate": "^3.0.0" @@ -6932,7 +6932,7 @@ "gulp-postcss": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/gulp-postcss/-/gulp-postcss-8.0.0.tgz", - "integrity": "sha512-Wtl6vH7a+8IS/fU5W9IbOpcaLqKxd5L1DUOzaPmlnCbX1CrG0aWdwVnC3Spn8th0m8D59YbysV5zPUe1n/GJYg==", + "integrity": "sha1-jTdyzU0nvKVeyMtMjlduO95NxVA=", "dev": true, "requires": { "fancy-log": "^1.3.2", @@ -6945,7 +6945,7 @@ "gulp-rename": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/gulp-rename/-/gulp-rename-1.4.0.tgz", - "integrity": "sha512-swzbIGb/arEoFK89tPY58vg3Ok1bw+d35PfUNwWqdo7KM4jkmuGA78JiDNqR+JeZFaeeHnRg9N7aihX3YPmsyg==", + "integrity": "sha1-3hxxjnxAla6GH3KW708ySGSCQL0=", "dev": true }, "gulp-sort": { @@ -6960,7 +6960,7 @@ "gulp-sourcemaps": { "version": "2.6.5", "resolved": "https://registry.npmjs.org/gulp-sourcemaps/-/gulp-sourcemaps-2.6.5.tgz", - "integrity": "sha512-SYLBRzPTew8T5Suh2U8jCSDKY+4NARua4aqjj8HOysBh2tSgT9u4jc1FYirAdPx1akUxxDeK++fqw6Jg0LkQRg==", + "integrity": "sha1-o/AC2HNG0sDzrsNq9+uHPyPeiuY=", "dev": true, "requires": { "@gulp-sourcemaps/identity-map": "1.X", @@ -6985,7 +6985,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", "dev": true } } @@ -7046,7 +7046,7 @@ "gulp-watch": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/gulp-watch/-/gulp-watch-5.0.1.tgz", - "integrity": "sha512-HnTSBdzAOFIT4wmXYPDUn783TaYAq9bpaN05vuZNP5eni3z3aRx0NAKbjhhMYtcq76x4R1wf4oORDGdlrEjuog==", + "integrity": "sha1-g9N4dS9b+0baAj5zwX7R2nBmIV0=", "dev": true, "requires": { "ansi-colors": "1.1.0", @@ -7147,7 +7147,7 @@ "gulp-wrap": { "version": "0.15.0", "resolved": "https://registry.npmjs.org/gulp-wrap/-/gulp-wrap-0.15.0.tgz", - "integrity": "sha512-f17zkGObA+hE/FThlg55gfA0nsXbdmHK1WqzjjB2Ytq1TuhLR7JiCBJ3K4AlMzCyoFaCjfowos+VkToUNE0WTQ==", + "integrity": "sha1-6QFMm7hkOrMQ6TjURpuFaFUaVS8=", "dev": true, "requires": { "consolidate": "^0.15.1", @@ -7191,7 +7191,7 @@ "through2": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.1.tgz", - "integrity": "sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww==", + "integrity": "sha1-OSducTwzAu3544jdnIEt07glvVo=", "dev": true, "requires": { "readable-stream": "2 || 3" @@ -7256,7 +7256,7 @@ "har-validator": { "version": "5.1.3", "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", - "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "integrity": "sha1-HvievT5JllV2de7ZiTEQ3DUPoIA=", "requires": { "ajv": "^6.5.5", "har-schema": "^2.0.0" @@ -7265,7 +7265,7 @@ "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "integrity": "sha1-ci18v8H2qoJB8W3YFOAR4fQeh5Y=", "dev": true, "requires": { "function-bind": "^1.1.1" @@ -7283,7 +7283,7 @@ "has-binary2": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", - "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==", + "integrity": "sha1-d3asYn8+p3JQz8My2rfd9eT10R0=", "dev": true, "requires": { "isarray": "2.0.1" @@ -7321,7 +7321,7 @@ "has-symbol-support-x": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz", - "integrity": "sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw==", + "integrity": "sha1-FAn5i8ACR9pF2mfO4KNvKC/yZFU=", "dev": true, "optional": true }, @@ -7334,7 +7334,7 @@ "has-to-string-tag-x": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz", - "integrity": "sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw==", + "integrity": "sha1-oEWrOD17SyASoAFIqwql8pAETU0=", "dev": true, "optional": true, "requires": { @@ -7376,13 +7376,13 @@ "hex-color-regex": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz", - "integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==", + "integrity": "sha1-TAb8y0YC/iYCs8k9+C1+fb8aio4=", "dev": true }, "homedir-polyfill": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", - "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "integrity": "sha1-dDKYzvTlrz4ZQWH7rcwhUdOgWOg=", "dev": true, "requires": { "parse-passwd": "^1.0.0" @@ -7409,7 +7409,7 @@ "html-comment-regex": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/html-comment-regex/-/html-comment-regex-1.1.2.tgz", - "integrity": "sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ==", + "integrity": "sha1-l9RoiutcgYhqNk+qDK0d2hTUM6c=", "dev": true }, "html-encoding-sniffer": { @@ -7438,7 +7438,7 @@ "entities": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", - "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", + "integrity": "sha1-vfpzUplmTfr9NFKe1PhSKidf6lY=", "dev": true }, "isarray": { @@ -7476,14 +7476,14 @@ "http-cache-semantics": { "version": "3.8.1", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz", - "integrity": "sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==", + "integrity": "sha1-ObDhat2bYFvwqe89nar0hDtMrNI=", "dev": true, "optional": true }, "http-errors": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", - "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "integrity": "sha1-T1ApzxMjnzEDblsuVSkrz7zIXI8=", "dev": true, "requires": { "depd": "~1.1.2", @@ -7532,7 +7532,7 @@ "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "integrity": "sha1-ICK0sl+93CHS9SSXSkdKr+czkIs=", "dev": true, "requires": { "safer-buffer": ">= 2.1.2 < 3" @@ -7548,7 +7548,7 @@ "ignore": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "integrity": "sha1-dQ49tYYgh7RzfrrIIH/9HvJ7Jfw=", "dev": true }, "image-size": { @@ -7608,7 +7608,7 @@ "imagemin-optipng": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/imagemin-optipng/-/imagemin-optipng-7.1.0.tgz", - "integrity": "sha512-JNORTZ6j6untH7e5gF4aWdhDCxe3ODsSLKs/f7Grewy3ebZpl1ZsU+VUTPY4rzeHgaFA8GSWOoA8V2M3OixWZQ==", + "integrity": "sha1-IiXILDXlwpt/qY1Pns7hFhpo6Ig=", "dev": true, "optional": true, "requires": { @@ -7741,7 +7741,7 @@ "import-lazy": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-3.1.0.tgz", - "integrity": "sha512-8/gvXvX2JMn0F+CDlSC4l6kOmVaLOO3XLkksI7CI3Ud95KDYJuYur2b9P/PUt/i/pDAMd/DulQsNbbbmRRsDIQ==", + "integrity": "sha1-iRJ5ICyKIoD9vWZ029jaGh38Z8w=", "dev": true, "optional": true }, @@ -7792,13 +7792,13 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "integrity": "sha1-D6LGT5MpF8NDOg3tVTY6rjdBa3w=", "dev": true }, "ini": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "integrity": "sha1-7uJfVtscnsYIXgwid4CD9Zar+Sc=", "dev": true }, "inquirer": { @@ -7894,7 +7894,7 @@ "invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "integrity": "sha1-YQ88ksk1nOHbYW5TgAjSP/NRWOY=", "dev": true, "requires": { "loose-envify": "^1.0.0" @@ -7915,19 +7915,19 @@ "irregular-plurals": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/irregular-plurals/-/irregular-plurals-2.0.0.tgz", - "integrity": "sha512-Y75zBYLkh0lJ9qxeHlMjQ7bSbyiSqNW/UOPWDmzC7cXskL1hekSITh1Oc6JV0XCWWZ9DE8VYSB71xocLk3gmGw==", + "integrity": "sha1-OdQPBbAPZW0Lf6RxIw3TtxSvKHI=", "dev": true }, "is": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/is/-/is-3.3.0.tgz", - "integrity": "sha512-nW24QBoPcFGGHJGUwnfpI7Yc5CdqWNdsyHQszVE/z2pKHXzh7FZ5GWhJqSyaQ9wMkQnsTx+kAI8bHlCX4tKdbg==", + "integrity": "sha1-Yc/23TxBk9uUo9YlggcrROVkXXk=", "dev": true }, "is-absolute": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", - "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", + "integrity": "sha1-OV4a6EsR8mrReV5zwXN45IowFXY=", "dev": true, "requires": { "is-relative": "^1.0.0", @@ -7978,13 +7978,13 @@ "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "integrity": "sha1-76ouqdqg16suoTqXsritUf776L4=", "dev": true }, "is-callable": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", - "integrity": "sha1-HhrfIZ4e62hNaR+dagX/DTCiTXU=", + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", "dev": true }, "is-color-stop": { @@ -8030,7 +8030,7 @@ "is-descriptor": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "integrity": "sha1-Nm2CQN3kh8pRgjsaufB6EKeCUco=", "dev": true, "requires": { "is-accessor-descriptor": "^0.1.6", @@ -8041,7 +8041,7 @@ "kind-of": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "integrity": "sha1-cpyR4thXt6QZofmqZWhcTDP1hF0=", "dev": true } } @@ -8098,7 +8098,7 @@ "is-gif": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-gif/-/is-gif-3.0.0.tgz", - "integrity": "sha512-IqJ/jlbw5WJSNfwQ/lHEDXF8rxhRgF6ythk2oiEvhpG29F704eX9NO6TvPfMiq9DrbwgcEDnETYNcZDPewQoVw==", + "integrity": "sha1-xL5gsmowHWlbuDOyDZtdZsbPg7E=", "dev": true, "optional": true, "requires": { @@ -8108,7 +8108,7 @@ "file-type": { "version": "10.11.0", "resolved": "https://registry.npmjs.org/file-type/-/file-type-10.11.0.tgz", - "integrity": "sha512-uzk64HRpUZyTGZtVuvrjP0FYxzQrBf4rojot6J65YMEbwBLB0CWm0CLojVpwpmFmxcE/lkvYICgfcGozbBq6rw==", + "integrity": "sha1-KWHQnkZ1ufuaPua2npzSP0P9GJA=", "dev": true, "optional": true } @@ -8186,7 +8186,7 @@ "is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "integrity": "sha1-LBY7P6+xtgbZ0Xko8FwqHDjgdnc=", "dev": true, "requires": { "isobject": "^3.0.1" @@ -8195,7 +8195,7 @@ "is-png": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-png/-/is-png-2.0.0.tgz", - "integrity": "sha512-4KPGizaVGj2LK7xwJIz8o5B2ubu1D/vcQsgOGFEDlpcvgZHto4gBnyd0ig7Ws+67ixmwKoNmu0hYnpo6AaKb5g==", + "integrity": "sha1-7oy8npsFBCXO3utKb7dKZJsKSo0=", "dev": true, "optional": true }, @@ -8235,7 +8235,7 @@ "is-relative": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", - "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", + "integrity": "sha1-obtpNc6MXboei5dUubLcwCDiJg0=", "dev": true, "requires": { "is-unc-path": "^1.0.0" @@ -8244,13 +8244,13 @@ "is-resolvable": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", - "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", + "integrity": "sha1-+xj4fOH+uSUWnJpAfBkxijIG7Yg=", "dev": true }, "is-retry-allowed": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", - "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==", + "integrity": "sha1-13hIi9CkZmo76KFIK58rqv7eqLQ=", "dev": true, "optional": true }, @@ -8264,7 +8264,7 @@ "is-svg": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-3.0.0.tgz", - "integrity": "sha512-gi4iHK53LR2ujhLVVj+37Ykh9GLqYHX6JOVXbLAucaG/Cqw9xwdFOjDM2qeifLs1sF1npXXFvDu0r5HNgCMrzQ==", + "integrity": "sha1-kyHb0pwhLlypnE+peUxxS8r6L3U=", "dev": true, "requires": { "html-comment-regex": "^1.1.0" @@ -8287,7 +8287,7 @@ "is-unc-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", - "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", + "integrity": "sha1-1zHoiY7QkKEsNSrS6u1Qla0yLJ0=", "dev": true, "requires": { "unc-path-regex": "^0.1.2" @@ -8308,7 +8308,7 @@ "is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "integrity": "sha1-0YUOuXkezRjmGCzhKjDzlmNLsZ0=", "dev": true }, "is-wsl": { @@ -8352,7 +8352,7 @@ "isurl": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isurl/-/isurl-1.0.0.tgz", - "integrity": "sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w==", + "integrity": "sha1-sn9PSfPNqj6kSgpbfzRi5u3DnWc=", "dev": true, "optional": true, "requires": { @@ -8363,7 +8363,7 @@ "jasmine-core": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.5.0.tgz", - "integrity": "sha512-nCeAiw37MIMA9w9IXso7bRaLl+c/ef3wnxsoSAlYrzS+Ot0zTG6nU8G/cIfGkqpkjX2wNaIW9RFG0TwIFnG6bA==", + "integrity": "sha1-Eywj5kWvlthci8oTyHWLGEKfweQ=", "dev": true }, "jquery": { @@ -8384,19 +8384,19 @@ "js-levenshtein": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", - "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==", + "integrity": "sha1-xs7ljrNVA3LfjeuF+tXOZs4B1Z0=", "dev": true }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "integrity": "sha1-GSA/tZmR35jjoocFDUZHzerzJJk=", "dev": true }, "js-yaml": { "version": "3.13.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "integrity": "sha1-r/FRswv9+o5J4F2iLnQV6d+jeEc=", "dev": true, "requires": { "argparse": "^1.0.7", @@ -8530,7 +8530,7 @@ "jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "integrity": "sha1-gFZNLkg9rPbo7yCWUKZ98/DCg6Q=", "dev": true }, "json-buffer": { @@ -8543,7 +8543,7 @@ "json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "integrity": "sha1-u4Z8+zRQ5pEHwTHRxRS6s9yLyqk=", "dev": true }, "json-schema": { @@ -8554,7 +8554,7 @@ "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + "integrity": "sha1-afaofZUTq4u4/mO9sJecRI5oRmA=" }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -8570,7 +8570,7 @@ "json5": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.1.tgz", - "integrity": "sha512-l+3HXD0GEI3huGq1njuqtzYK8OYJyXMkOLtQ53pjWh89tvWS2h6l+1zMkYWqlb57+SiQodKZyvMEFb2X+KrFhQ==", + "integrity": "sha1-gbbLBOm6SW8ccAXQe0NoomOPkLY=", "dev": true, "requires": { "minimist": "^1.2.0" @@ -8590,7 +8590,7 @@ "junk": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/junk/-/junk-3.1.0.tgz", - "integrity": "sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ==", + "integrity": "sha1-MUmQmNkCt+mMXZucgPQ0V6iKv6E=", "dev": true }, "just-debounce": { @@ -8636,7 +8636,7 @@ "anymatch": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", - "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "integrity": "sha1-xV7PAhheJGklk5kxDBc84xIzsUI=", "dev": true, "requires": { "normalize-path": "^3.0.0", @@ -8652,7 +8652,7 @@ "braces": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "integrity": "sha1-NFThpGLujVmeI23zNs2epPiv4Qc=", "dev": true, "requires": { "fill-range": "^7.0.1" @@ -8677,7 +8677,7 @@ "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "integrity": "sha1-GRmmp8df44ssfHflGYU12prN2kA=", "dev": true, "requires": { "to-regex-range": "^5.0.1" @@ -8702,7 +8702,7 @@ "is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "integrity": "sha1-6h9/O4DwZCNug0cPhsCcJU+0Wwk=", "dev": true, "requires": { "binary-extensions": "^2.0.0" @@ -8720,7 +8720,7 @@ "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "integrity": "sha1-dTU0W4lnNNX4DE0GxQlVUnoU8Ss=", "dev": true }, "mime": { @@ -8732,7 +8732,7 @@ "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "integrity": "sha1-Dc1p/yOhybEf0JeDFmRKA4ghamU=", "dev": true }, "readdirp": { @@ -8753,7 +8753,7 @@ "to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "integrity": "sha1-FkjESq58jZiKMmAY7XL1tN0DkuQ=", "dev": true, "requires": { "is-number": "^7.0.0" @@ -8764,7 +8764,7 @@ "karma-jasmine": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-2.0.1.tgz", - "integrity": "sha512-iuC0hmr9b+SNn1DaUD2QEYtUxkS1J+bSJSn7ejdEexs7P8EYvA1CWkEdrDQ+8jVH3AgWlCNwjYsT1chjcNW9lA==", + "integrity": "sha1-JuPjHy+vJy3YDrsOGJiRTMOhl2M=", "dev": true, "requires": { "jasmine-core": "^3.3" @@ -8779,7 +8779,7 @@ "karma-junit-reporter": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/karma-junit-reporter/-/karma-junit-reporter-2.0.1.tgz", - "integrity": "sha512-VtcGfE0JE4OE1wn0LK8xxDKaTP7slN8DO3I+4xg6gAi1IoAHAXOJ1V9G/y45Xg6sxdxPOR3THCFtDlAfBo9Afw==", + "integrity": "sha1-007vfwsv0GTgiWlU6IUakM8UyPM=", "dev": true, "requires": { "path-is-absolute": "^1.0.0", @@ -8798,7 +8798,7 @@ "keyv": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.0.0.tgz", - "integrity": "sha512-eguHnq22OE3uVoSYG0LVWNP+4ppamWr9+zWBe1bsNcovIMy6huUJFPgy4mGwCd/rnl3vOLGW1MTlu4c57CT1xA==", + "integrity": "sha1-RJI7o55osSp87H32wyaMAx8u83M=", "dev": true, "optional": true, "requires": { @@ -8808,13 +8808,13 @@ "kind-of": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha1-ARRrNqYhjmTljzqNZt5df8b20FE=", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", "dev": true }, "kuler": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/kuler/-/kuler-1.0.1.tgz", - "integrity": "sha512-J9nVUucG1p/skKul6DU3PUZrhs0LPulNaeUOox0IyXDi8S4CztTHs1gQphhuZmzXG7VOQSf6NJfKuzteQLv9gQ==", + "integrity": "sha1-73x4TzbJ+24W3TFQ0VJneysCKKY=", "dev": true, "requires": { "colornames": "^1.1.1" @@ -8903,7 +8903,7 @@ "less": { "version": "3.10.3", "resolved": "https://registry.npmjs.org/less/-/less-3.10.3.tgz", - "integrity": "sha512-vz32vqfgmoxF1h3K4J+yKCtajH0PWmjkIFgbs5d78E/c/e+UQTnI+lWK+1eQRE95PXM2mC3rJlLSSP9VQHnaow==", + "integrity": "sha1-QXoJddXu7MUs/0vPo8CdNXgeZ5I=", "dev": true, "requires": { "clone": "^2.1.2", @@ -8926,7 +8926,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", "dev": true, "optional": true } @@ -9116,7 +9116,7 @@ "lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "integrity": "sha1-VYqlO0O2YeGSWgr9+japoQhf5Xo=", "dev": true }, "lodash.partialright": { @@ -9229,7 +9229,7 @@ "loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "integrity": "sha1-ce5R+nvkyuwaY4OffmgtgTLTDK8=", "dev": true, "requires": { "js-tokens": "^3.0.0 || ^4.0.0" @@ -9249,7 +9249,7 @@ "lowercase-keys": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "integrity": "sha1-b54wtHCE2XGnyCD/FabFFnt0wm8=", "dev": true, "optional": true }, @@ -9269,7 +9269,7 @@ "lru-cache": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "integrity": "sha1-i75Q6oW+1ZvJ4z3KuCNe6bz0Q80=", "dev": true, "requires": { "pseudomap": "^1.0.2", @@ -9297,7 +9297,7 @@ "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "integrity": "sha1-7gpkyK9ejO6mdoexM3YeG+y9HT0=", "dev": true } } @@ -9305,7 +9305,7 @@ "make-iterator": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", - "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==", + "integrity": "sha1-KbM/MSqo9UfEpeSQ9Wr87JkTOtY=", "dev": true, "requires": { "kind-of": "^6.0.2" @@ -9336,7 +9336,7 @@ "marked": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/marked/-/marked-0.7.0.tgz", - "integrity": "sha512-c+yYdCZJQrsRjTPhUx7VKkApw9bwDkNbHUKo1ovgcfDjb2kc8rLuRbIFyXL5WOEUwzSSKo3IXpph2K6DqB/KZg==", + "integrity": "sha1-tkIB8FHScbHtwQoE0a6bdLuOXA4=", "dev": true }, "matchdep": { @@ -9368,13 +9368,13 @@ "math-random": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.4.tgz", - "integrity": "sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==", + "integrity": "sha1-XdaUPJOFSCZwFtTjTwV1gwgMUUw=", "dev": true }, "mdn-data": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", - "integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==", + "integrity": "sha1-aZs8OKxvHXKAkaZGULZdOIUC/Vs=", "dev": true }, "media-typer": { @@ -9386,7 +9386,7 @@ "memoizee": { "version": "0.4.14", "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.14.tgz", - "integrity": "sha512-/SWFvWegAIYAO4NQMpcX+gcra0yEZu4OntmUdrBaWrJncxOqAziGFlHxc7yjKVK2uu3lpPW27P27wkR82wA8mg==", + "integrity": "sha1-B6APIEaZ+alcLZ53IYJxx81hDVc=", "dev": true, "requires": { "d": "1", @@ -9430,7 +9430,7 @@ "merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "integrity": "sha1-UoI2KaFN0AyXcPtq1H3GMQ8sH2A=", "dev": true }, "merge2": { @@ -9442,7 +9442,7 @@ "micromatch": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "integrity": "sha1-cIWbyVyYQJUvNZoGij/En57PrCM=", "dev": true, "requires": { "arr-diff": "^4.0.0", @@ -9463,7 +9463,7 @@ "mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "integrity": "sha1-Ms2eXGRVO9WNGaVor0Uqz/BJgbE=", "dev": true, "optional": true }, @@ -9489,14 +9489,14 @@ "mimic-response": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "integrity": "sha1-SSNTiHju9CBjy4o+OweYeBSHqxs=", "dev": true, "optional": true }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", "dev": true, "requires": { "brace-expansion": "^1.1.7" @@ -9504,14 +9504,14 @@ }, "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true }, "minimize": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/minimize/-/minimize-2.2.0.tgz", - "integrity": "sha512-IxR2XMbw9pXCxApkdD9BTcH2U4XlXhbeySUrv71rmMS9XDA8BVXEsIuFu24LtwCfBgfbL7Fuh8/ZzkO5DaTLlQ==", + "integrity": "sha1-ixZ28wBR2FmNdDZGvRJpCwdNpMM=", "dev": true, "requires": { "argh": "^0.1.4", @@ -9526,7 +9526,7 @@ "mixin-deep": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "integrity": "sha1-ESC0PcNZp4Xc5ltVuC4lfM9HlWY=", "dev": true, "requires": { "for-in": "^1.0.2", @@ -9536,7 +9536,7 @@ "is-extendable": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "integrity": "sha1-p0cPnkJnM9gb2B4RVSZOOjUHyrQ=", "dev": true, "requires": { "is-plain-object": "^2.0.4" @@ -9555,7 +9555,7 @@ "dependencies": { "minimist": { "version": "0.0.8", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "dev": true } @@ -9581,7 +9581,7 @@ "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "integrity": "sha1-0J0fNXtEP0kzgqjrPM0YOHKuYAk=", "dev": true }, "multipipe": { @@ -9596,7 +9596,7 @@ "mute-stdout": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mute-stdout/-/mute-stdout-1.0.1.tgz", - "integrity": "sha512-kDcwXR4PS7caBpuRYYBUz9iVixUk3anO3f5OYFiIPwK/20vCzKCHyKoulbiDY1S53zD2bxUpxN/IJ+TnXjfvxg==", + "integrity": "sha1-rLAwDrTeI6fd7sAU4+lgRLNHIzE=", "dev": true }, "mute-stream": { @@ -9615,7 +9615,7 @@ "nanomatch": { "version": "1.2.13", "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "integrity": "sha1-uHqKpPwN6P5r6IiVs4mD/yZb0Rk=", "dev": true, "requires": { "arr-diff": "^4.0.0", @@ -9640,7 +9640,7 @@ "negotiator": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", + "integrity": "sha1-/qz3zPUlp3rpY0Q2pkiD/+yjRvs=", "dev": true }, "next-tick": { @@ -9657,13 +9657,13 @@ "nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "integrity": "sha1-ozeKdpbOfSI+iPybdkvX7xCJ42Y=", "dev": true }, "node-notifier": { "version": "5.4.3", "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.4.3.tgz", - "integrity": "sha512-M4UBGcs4jeOK9CjTsYwkvH6/MzuUmGCyTW+kCY7uO+1ZVr0+FHGdPdIf5CCLqAaxnRrWidyoQlNkMIIVwbKB8Q==", + "integrity": "sha1-y3La+UyTkECY4oucWQ/YZuRkvVA=", "dev": true, "requires": { "growly": "^1.3.0", @@ -9685,7 +9685,7 @@ "node.extend": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/node.extend/-/node.extend-2.0.2.tgz", - "integrity": "sha512-pDT4Dchl94/+kkgdwyS2PauDFjZG0Hk0IcHIB+LkW27HLDtdoeMxHTxZh39DYbPP8UflWXWj9JcdDozF+YDOpQ==", + "integrity": "sha1-tEBFJUlKzJl0DzcDxJa31Rgsxsw=", "dev": true, "requires": { "has": "^1.0.3", @@ -9695,7 +9695,7 @@ "normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "integrity": "sha1-5m2xg4sgDB38IzIl0SyzZSDiNKg=", "dev": true, "requires": { "hosted-git-info": "^2.1.4", @@ -9722,7 +9722,7 @@ "normalize-url": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-3.3.0.tgz", - "integrity": "sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==", + "integrity": "sha1-suHE3E98bVd0PfczpPWXjRhlBVk=", "dev": true }, "nouislider": { @@ -9733,7 +9733,7 @@ "now-and-later": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-2.0.1.tgz", - "integrity": "sha512-KGvQ0cB70AQfg107Xvs/Fbu+dGmZoTRJp2TaPwcwQm3/7PteUyN2BCgk8KBMPGBUXZdVwyWS8fDCGFygBm19UQ==", + "integrity": "sha1-jlechoV2SnzALLaAOA6U9DzLH3w=", "dev": true, "requires": { "once": "^1.3.2" @@ -12836,7 +12836,7 @@ "npm-conf": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/npm-conf/-/npm-conf-1.1.3.tgz", - "integrity": "sha512-Yic4bZHJOt9RCFbRP3GgpqhScOY4HH3V2P8yBj6CeYq118Qr+BLXqT2JvpJ00mryLESpgOxf5XlFv4ZjXxLScw==", + "integrity": "sha1-JWzEe9DiGMJZxOlVC/QTvCGSr/k=", "dev": true, "optional": true, "requires": { @@ -12866,7 +12866,7 @@ "nth-check": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", - "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "integrity": "sha1-sr0pXDfj3VijvwcAN2Zjuk2c8Fw=", "dev": true, "requires": { "boolbase": "~1.0.0" @@ -12893,7 +12893,7 @@ "oauth-sign": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + "integrity": "sha1-R6ewFrqmi1+g7PPe4IqFxnmsZFU=" }, "object-assign": { "version": "3.0.0", @@ -12947,7 +12947,7 @@ "object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "integrity": "sha1-HEfyct8nfzsdrwYWd9nILiMixg4=", "dev": true }, "object-visit": { @@ -12962,7 +12962,7 @@ "object.assign": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", - "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "integrity": "sha1-lovxEA15Vrs8oIbwBvhGs7xACNo=", "dev": true, "requires": { "define-properties": "^1.1.2", @@ -13123,7 +13123,7 @@ "optipng-bin": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/optipng-bin/-/optipng-bin-6.0.0.tgz", - "integrity": "sha512-95bB4y8IaTsa/8x6QH4bLUuyvyOoGBCLDA7wOgDL8UFqJpSUh1Hob8JRJhit+wC1ZLN3tQ7mFt7KuBj0x8F2Wg==", + "integrity": "sha1-N2Eg+nnV5x7uL1JBdu/dOl6r0xY=", "dev": true, "optional": true, "requires": { @@ -13184,7 +13184,7 @@ "os-filter-obj": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/os-filter-obj/-/os-filter-obj-2.0.0.tgz", - "integrity": "sha512-uksVLsqG3pVdzzPvmAHpBK0wKxYItuzZr7SziusRPoz67tGV8rL1szZ6IdeUrbqLjGDwApBtN29eEE3IqGHOjg==", + "integrity": "sha1-HAti1fOiRCdJotE55t3e5ugdjRY=", "dev": true, "optional": true, "requires": { @@ -13209,7 +13209,7 @@ "p-cancelable": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.3.0.tgz", - "integrity": "sha512-RVbZPLso8+jFeq1MfNvgXtCRED2raz/dKpacfTNxsx6pLEpEomM7gah6VeHSYV3+vo0OAi4MkArtQcWWXuQoyw==", + "integrity": "sha1-ueEjgAvOu3rBOkeb4ZW1B7mNMPo=", "dev": true, "optional": true }, @@ -13273,7 +13273,7 @@ "parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "integrity": "sha1-aR0nCeeMefrjoVZiJFLQB2LKqqI=", "dev": true, "requires": { "callsites": "^3.0.0" @@ -13282,7 +13282,7 @@ "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "integrity": "sha1-s2MKvYlDQy9Us/BRkjjjPNffL3M=", "dev": true } } @@ -13340,7 +13340,7 @@ "parse-node-version": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", - "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", + "integrity": "sha1-4rXb7eAOf6m8NjYH9TMn6LBzGJs=", "dev": true }, "parse-passwd": { @@ -13376,7 +13376,7 @@ "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "integrity": "sha1-naGee+6NEt/wUT7Vt2lXeTvC6NQ=", "dev": true }, "pascalcase": { @@ -13415,7 +13415,7 @@ "path-parse": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "integrity": "sha1-1i27VnlAXXLEc37FhgDp3c8G0kw=", "dev": true }, "path-root": { @@ -13494,7 +13494,7 @@ "plugin-error": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz", - "integrity": "sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA==", + "integrity": "sha1-dwFr2JGdCsN3/c3QMiMolTyleBw=", "dev": true, "requires": { "ansi-colors": "^1.0.1", @@ -13506,7 +13506,7 @@ "plur": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/plur/-/plur-3.1.1.tgz", - "integrity": "sha512-t1Ax8KUvV3FFII8ltczPn2tJdjqbd1sIzu6t4JL7nQ3EyeL/lTrj5PWKb06ic5/6XYDr65rQ4uzQEGN70/6X5w==", + "integrity": "sha1-YCZ5Z4ZqjYEVBP5Y8vqrojdUals=", "dev": true, "requires": { "irregular-plurals": "^2.0.0" @@ -13532,13 +13532,13 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", "dev": true }, "supports-color": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "integrity": "sha1-B2Srxpxj1ayELdSGfo0CXogN+PM=", "dev": true, "requires": { "has-flag": "^3.0.0" @@ -13561,7 +13561,7 @@ "postcss-colormin": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-4.0.3.tgz", - "integrity": "sha512-WyQFAdDZpExQh32j0U0feWisZ0dmOtPl44qYmJKkq9xFWY3p+4qnRzCHeNrkeRhwPHz9bQ3mo0/yVkaply0MNw==", + "integrity": "sha1-rgYLzpPteUrHEmTwgTLVUJVr04E=", "dev": true, "requires": { "browserslist": "^4.0.0", @@ -13574,7 +13574,7 @@ "postcss-convert-values": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-4.0.1.tgz", - "integrity": "sha512-Kisdo1y77KUC0Jmn0OXU/COOJbzM8cImvw1ZFsBgBgMgb1iL23Zs/LXRe3r+EZqM3vGYKdQ2YJVQ5VkJI+zEJQ==", + "integrity": "sha1-yjgT7U2g+BL51DcDWE5Enr4Ymn8=", "dev": true, "requires": { "postcss": "^7.0.0", @@ -13584,7 +13584,7 @@ "postcss-discard-comments": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz", - "integrity": "sha512-RJutN259iuRf3IW7GZyLM5Sw4GLTOH8FmsXBnv8Ab/Tc2k4SR4qbV4DNbyyY4+Sjo362SyDmW2DQ7lBSChrpkg==", + "integrity": "sha1-H7q9LCRr/2qq15l7KwkY9NevQDM=", "dev": true, "requires": { "postcss": "^7.0.0" @@ -13593,7 +13593,7 @@ "postcss-discard-duplicates": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-4.0.2.tgz", - "integrity": "sha512-ZNQfR1gPNAiXZhgENFfEglF93pciw0WxMkJeVmw8eF+JZBbMD7jp6C67GqJAXVZP2BWbOztKfbsdmMp/k8c6oQ==", + "integrity": "sha1-P+EzzTyCKC5VD8myORdqkge3hOs=", "dev": true, "requires": { "postcss": "^7.0.0" @@ -13602,7 +13602,7 @@ "postcss-discard-empty": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-4.0.1.tgz", - "integrity": "sha512-B9miTzbznhDjTfjvipfHoqbWKwd0Mj+/fL5s1QOz06wufguil+Xheo4XpOnc4NqKYBCNqqEzgPv2aPBIJLox0w==", + "integrity": "sha1-yMlR6fc+2UKAGUWERKAq2Qu592U=", "dev": true, "requires": { "postcss": "^7.0.0" @@ -13611,7 +13611,7 @@ "postcss-discard-overridden": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-4.0.1.tgz", - "integrity": "sha512-IYY2bEDD7g1XM1IDEsUT4//iEYCxAmP5oDSFMVU/JVvT7gh+l4fmjciLqGgwjdWpQIdb0Che2VX00QObS5+cTg==", + "integrity": "sha1-ZSrvipZybwKfXj4AFG7npOdV/1c=", "dev": true, "requires": { "postcss": "^7.0.0" @@ -13620,7 +13620,7 @@ "postcss-load-config": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-2.1.0.tgz", - "integrity": "sha512-4pV3JJVPLd5+RueiVVB+gFOAa7GWc25XQcMp86Zexzke69mKf6Nx9LRcQywdz7yZI9n1udOxmLuAwTBypypF8Q==", + "integrity": "sha1-yE1pK3u3tB3c7ZTuYuirMbQXsAM=", "dev": true, "requires": { "cosmiconfig": "^5.0.0", @@ -13630,7 +13630,7 @@ "postcss-merge-longhand": { "version": "4.0.11", "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz", - "integrity": "sha512-alx/zmoeXvJjp7L4mxEMjh8lxVlDFX1gqWHzaaQewwMZiVhLo42TEClKaeHbRf6J7j82ZOdTJ808RtN0ZOZwvw==", + "integrity": "sha1-YvSaE+Sg7gTnuY9CuxYGLKJUniQ=", "dev": true, "requires": { "css-color-names": "0.0.4", @@ -13642,7 +13642,7 @@ "postcss-merge-rules": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-4.0.3.tgz", - "integrity": "sha512-U7e3r1SbvYzO0Jr3UT/zKBVgYYyhAz0aitvGIYOYK5CPmkNih+WDSsS5tvPrJ8YMQYlEMvsZIiqmn7HdFUaeEQ==", + "integrity": "sha1-NivqT/Wh+Y5AdacTxsslrv75plA=", "dev": true, "requires": { "browserslist": "^4.0.0", @@ -13669,7 +13669,7 @@ "postcss-minify-font-values": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-4.0.2.tgz", - "integrity": "sha512-j85oO6OnRU9zPf04+PZv1LYIYOprWm6IA6zkXkrJXyRveDEuQggG6tvoy8ir8ZwjLxLuGfNkCZEQG7zan+Hbtg==", + "integrity": "sha1-zUw0TM5HQ0P6xdgiBqssvLiv1aY=", "dev": true, "requires": { "postcss": "^7.0.0", @@ -13679,7 +13679,7 @@ "postcss-minify-gradients": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-4.0.2.tgz", - "integrity": "sha512-qKPfwlONdcf/AndP1U8SJ/uzIJtowHlMaSioKzebAXSG4iJthlWC9iSWznQcX4f66gIWX44RSA841HTHj3wK+Q==", + "integrity": "sha1-k7KcL/UJnFNe7NpWxKpuZlpmNHE=", "dev": true, "requires": { "cssnano-util-get-arguments": "^4.0.0", @@ -13691,7 +13691,7 @@ "postcss-minify-params": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-4.0.2.tgz", - "integrity": "sha512-G7eWyzEx0xL4/wiBBJxJOz48zAKV2WG3iZOqVhPet/9geefm/Px5uo1fzlHu+DOjT+m0Mmiz3jkQzVHe6wxAWg==", + "integrity": "sha1-a5zvAwwR41Jh+V9hjJADbWgNuHQ=", "dev": true, "requires": { "alphanum-sort": "^1.0.0", @@ -13705,7 +13705,7 @@ "postcss-minify-selectors": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-4.0.2.tgz", - "integrity": "sha512-D5S1iViljXBj9kflQo4YutWnJmwm8VvIsU1GeXJGiG9j8CIg9zs4voPMdQDUmIxetUOh60VilsNzCiAFTOqu3g==", + "integrity": "sha1-4uXrQL/uUA0M2SQ1APX46kJi+9g=", "dev": true, "requires": { "alphanum-sort": "^1.0.0", @@ -13730,7 +13730,7 @@ "postcss-normalize-charset": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-4.0.1.tgz", - "integrity": "sha512-gMXCrrlWh6G27U0hF3vNvR3w8I1s2wOBILvA87iNXaPvSNo5uZAMYsZG7XjCUf1eVxuPfyL4TJ7++SGZLc9A3g==", + "integrity": "sha1-izWt067oOhNrBHHg1ZvlilAoXdQ=", "dev": true, "requires": { "postcss": "^7.0.0" @@ -13739,7 +13739,7 @@ "postcss-normalize-display-values": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.2.tgz", - "integrity": "sha512-3F2jcsaMW7+VtRMAqf/3m4cPFhPD3EFRgNs18u+k3lTJJlVe7d0YPO+bnwqo2xg8YiRpDXJI2u8A0wqJxMsQuQ==", + "integrity": "sha1-Db4EpM6QY9RmftK+R2u4MMglk1o=", "dev": true, "requires": { "cssnano-util-get-match": "^4.0.0", @@ -13750,7 +13750,7 @@ "postcss-normalize-positions": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-4.0.2.tgz", - "integrity": "sha512-Dlf3/9AxpxE+NF1fJxYDeggi5WwV35MXGFnnoccP/9qDtFrTArZ0D0R+iKcg5WsUd8nUYMIl8yXDCtcrT8JrdA==", + "integrity": "sha1-BfdX+E8mBDc3g2ipH4ky1LECkX8=", "dev": true, "requires": { "cssnano-util-get-arguments": "^4.0.0", @@ -13762,7 +13762,7 @@ "postcss-normalize-repeat-style": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-4.0.2.tgz", - "integrity": "sha512-qvigdYYMpSuoFs3Is/f5nHdRLJN/ITA7huIoCyqqENJe9PvPmLhNLMu7QTjPdtnVf6OcYYO5SHonx4+fbJE1+Q==", + "integrity": "sha1-xOu8KJ85kaAo1EdRy90RkYsXkQw=", "dev": true, "requires": { "cssnano-util-get-arguments": "^4.0.0", @@ -13774,7 +13774,7 @@ "postcss-normalize-string": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-4.0.2.tgz", - "integrity": "sha512-RrERod97Dnwqq49WNz8qo66ps0swYZDSb6rM57kN2J+aoyEAJfZ6bMx0sx/F9TIEX0xthPGCmeyiam/jXif0eA==", + "integrity": "sha1-zUTECrB6DHo23F6Zqs4eyk7CaQw=", "dev": true, "requires": { "has": "^1.0.0", @@ -13785,7 +13785,7 @@ "postcss-normalize-timing-functions": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-4.0.2.tgz", - "integrity": "sha512-acwJY95edP762e++00Ehq9L4sZCEcOPyaHwoaFOhIwWCDfik6YvqsYNxckee65JHLKzuNSSmAdxwD2Cud1Z54A==", + "integrity": "sha1-jgCcoqOUnNr4rSPmtquZy159KNk=", "dev": true, "requires": { "cssnano-util-get-match": "^4.0.0", @@ -13796,7 +13796,7 @@ "postcss-normalize-unicode": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-4.0.1.tgz", - "integrity": "sha512-od18Uq2wCYn+vZ/qCOeutvHjB5jm57ToxRaMeNuf0nWVHaP9Hua56QyMF6fs/4FSUnVIw0CBPsU0K4LnBPwYwg==", + "integrity": "sha1-hBvUj9zzAZrUuqdJOj02O1KuHPs=", "dev": true, "requires": { "browserslist": "^4.0.0", @@ -13807,7 +13807,7 @@ "postcss-normalize-url": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-4.0.1.tgz", - "integrity": "sha512-p5oVaF4+IHwu7VpMan/SSpmpYxcJMtkGppYf0VbdH5B6hN8YNmVyJLuY9FmLQTzY3fag5ESUUHDqM+heid0UVA==", + "integrity": "sha1-EOQ3+GvHx+WPe5ZS7YeNqqlfquE=", "dev": true, "requires": { "is-absolute-url": "^2.0.0", @@ -13819,7 +13819,7 @@ "postcss-normalize-whitespace": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-4.0.2.tgz", - "integrity": "sha512-tO8QIgrsI3p95r8fyqKV+ufKlSHh9hMJqACqbv2XknufqEDhDvbguXGBBqxw9nsQoXWf0qOqppziKJKHMD4GtA==", + "integrity": "sha1-vx1AcP5Pzqh9E0joJdjMDF+qfYI=", "dev": true, "requires": { "postcss": "^7.0.0", @@ -13829,7 +13829,7 @@ "postcss-ordered-values": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-4.1.2.tgz", - "integrity": "sha512-2fCObh5UanxvSxeXrtLtlwVThBvHn6MQcu4ksNT2tsaV2Fg76R2CV98W7wNSlX+5/pFwEyaDwKLLoEV7uRybAw==", + "integrity": "sha1-DPdcgg7H1cTSgBiVWeC1ceusDu4=", "dev": true, "requires": { "cssnano-util-get-arguments": "^4.0.0", @@ -13840,7 +13840,7 @@ "postcss-reduce-initial": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz", - "integrity": "sha512-gKWmR5aUulSjbzOfD9AlJiHCGH6AEVLaM0AV+aSioxUDd16qXP1PCh8d1/BGVvpdWn8k/HiK7n6TjeoXN1F7DA==", + "integrity": "sha1-f9QuvqXpyBRgljniwuhK4nC6SN8=", "dev": true, "requires": { "browserslist": "^4.0.0", @@ -13852,7 +13852,7 @@ "postcss-reduce-transforms": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-4.0.2.tgz", - "integrity": "sha512-EEVig1Q2QJ4ELpJXMZR8Vt5DQx8/mo+dGWSR7vWXqcob2gQLyQGsionYcGKATXvQzMPn6DSN1vTN7yFximdIAg==", + "integrity": "sha1-F++kBerMbge+NBSlyi0QdGgdTik=", "dev": true, "requires": { "cssnano-util-get-match": "^4.0.0", @@ -13875,7 +13875,7 @@ "postcss-svgo": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-4.0.2.tgz", - "integrity": "sha512-C6wyjo3VwFm0QgBy+Fu7gCYOkCmgmClghO+pjcxvrcBKtiKt0uCF+hvbMO1fyv5BMImRK90SMb+dwUnfbGd+jw==", + "integrity": "sha1-F7mXvHEbMzurFDqu07jT1uPTglg=", "dev": true, "requires": { "is-svg": "^3.0.0", @@ -13887,7 +13887,7 @@ "postcss-unique-selectors": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-4.0.1.tgz", - "integrity": "sha512-+JanVaryLo9QwZjKrmJgkI4Fn8SBgRO6WXQBJi7KiAVPlmxikB5Jzc4EvXMT2H0/m0RjrVVm9rGNhZddm/8Spg==", + "integrity": "sha1-lEaRHzKJv9ZMbWgPBzwDsfnuS6w=", "dev": true, "requires": { "alphanum-sort": "^1.0.0", @@ -13935,25 +13935,25 @@ "private": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", - "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", + "integrity": "sha1-I4Hts2ifelPWUxkAYPz4ItLzaP8=", "dev": true }, "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "integrity": "sha1-eCDZsWEgzFXKmud5JoCufbptf+I=", "dev": true }, "progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "integrity": "sha1-foz42PW48jnBvGi+tOt4Vn1XLvg=", "dev": true }, "promise": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", - "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "integrity": "sha1-BktyYCsY+Q8pGSuLG8QY/9Hr078=", "dev": true, "optional": true, "requires": { @@ -13988,7 +13988,7 @@ "pump": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", - "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "integrity": "sha1-Ejma3W5M91Jtlzy8i1zi4pCLOQk=", "dev": true, "requires": { "end-of-stream": "^1.1.0", @@ -13998,7 +13998,7 @@ "pumpify": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", - "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "integrity": "sha1-NlE74karJ1cLGjdKXOJ4v9dDcM4=", "dev": true, "requires": { "duplexify": "^3.6.0", @@ -14009,7 +14009,7 @@ "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + "integrity": "sha1-tYsBCsQMIsVldhbI0sLALHv0eew=" }, "q": { "version": "1.5.1", @@ -14020,18 +14020,18 @@ "qjobs": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", - "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", + "integrity": "sha1-xF6cYYAL0IfviNfiVkI73Unl0HE=", "dev": true }, "qs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + "integrity": "sha1-yzroBuh0BERYTvFUzo7pjUA/PjY=" }, "query-string": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz", - "integrity": "sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==", + "integrity": "sha1-p4wBK3HBfgXy4/ojGd0zBoLvs8s=", "dev": true, "optional": true, "requires": { @@ -14057,7 +14057,7 @@ "randomatic": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz", - "integrity": "sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==", + "integrity": "sha1-t3bvxZN1mE42xTey9RofCv8Noe0=", "dev": true, "requires": { "is-number": "^4.0.0", @@ -14068,7 +14068,7 @@ "is-number": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "integrity": "sha1-ACbjf1RU1z41bf5lZGmYZ8an8P8=", "dev": true } } @@ -14076,13 +14076,13 @@ "range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "integrity": "sha1-PPNwI9GZ4cJNGlW4SADC8+ZGgDE=", "dev": true }, "raw-body": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", - "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "integrity": "sha1-oc5vucm8NWylLoklarWQWeE9AzI=", "dev": true, "requires": { "bytes": "3.1.0", @@ -14127,7 +14127,7 @@ "readdirp": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "integrity": "sha1-DodiKjMlqjPokihcr4tOhGUppSU=", "dev": true, "requires": { "graceful-fs": "^4.1.11", @@ -14164,7 +14164,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { @@ -14196,7 +14196,7 @@ "regenerate": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", - "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==", + "integrity": "sha1-SoVuxLVuQHfFV1icroXnpMiGmhE=", "dev": true }, "regenerate-unicode-properties": { @@ -14220,7 +14220,7 @@ "regex-cache": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", - "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", + "integrity": "sha1-db3FiioUls7EihKDW8VMjVYjNt0=", "dev": true, "requires": { "is-equal-shallow": "^0.1.3" @@ -14229,7 +14229,7 @@ "regex-not": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "integrity": "sha1-H07OJ+ALC2XgJHpoEOaoXYOldSw=", "dev": true, "requires": { "extend-shallow": "^3.0.2", @@ -14239,7 +14239,7 @@ "regexpp": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", - "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", + "integrity": "sha1-jRnTHPYySCtYkEn4KB+T28uk0H8=", "dev": true }, "regexpu-core": { @@ -14259,7 +14259,7 @@ "regjsgen": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.1.tgz", - "integrity": "sha512-5qxzGZjDs9w4tzT3TPhCJqWdCc3RLYwy9J2NB0nm5Lz+S273lvWcpjaTGHsT1dc6Hhfq41uSEOw8wBmxrKOuyg==", + "integrity": "sha1-SPC/Gl6iBRlpKcDZeYtC0e2YRDw=", "dev": true }, "regjsparser": { @@ -14282,7 +14282,7 @@ "remove-bom-buffer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz", - "integrity": "sha512-8v2rWhaakv18qcvNeli2mZ/TMTL2nEyAKRvzo1WtnZBl15SHyEhrCu2/xKlJyUFKHiHgfXIyuY6g2dObJJycXQ==", + "integrity": "sha1-wr8eN3Ug0yT2I4kuM8EMrCwlK1M=", "dev": true, "requires": { "is-buffer": "^1.1.5", @@ -14309,7 +14309,7 @@ "repeat-element": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", - "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", + "integrity": "sha1-eC4NglwMWjuzlzH4Tv7mt0Lmsc4=", "dev": true }, "repeat-string": { @@ -14482,19 +14482,19 @@ "ret": { "version": "0.1.15", "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "integrity": "sha1-uKSCXVvbH8P29Twrwz+BOIaBx7w=", "dev": true }, "reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "integrity": "sha1-kNo4Kx4SbvwCFG6QhFqI2xKSXXY=", "dev": true }, "rfdc": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.1.4.tgz", - "integrity": "sha512-5C9HXdzK8EAqN7JDif30jqsBzavB7wLpaubisuQIGHWf2gUXSpzy6ArX/+Da8RjFpagWsCn+pIgxTMAmKw9Zug==", + "integrity": "sha1-unLME2egzNnPgahws7WL060H+MI=", "dev": true }, "rgb-regex": { @@ -14521,7 +14521,7 @@ "rimraf": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "integrity": "sha1-stEE/g2Psnz54KHNqCYt04M8bKs=", "dev": true, "requires": { "glob": "^7.1.3" @@ -14545,7 +14545,7 @@ "run-sequence": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/run-sequence/-/run-sequence-2.2.1.tgz", - "integrity": "sha512-qkzZnQWMZjcKbh3CNly2srtrkaO/2H/SI5f2eliMCapdRD3UhMrwjfOAZJAnZ2H8Ju4aBzFZkBGXUqFs9V0yxw==", + "integrity": "sha1-HOZD2jb9jH6n4akynaM/wriJhJU=", "dev": true, "requires": { "chalk": "^1.1.3", @@ -14642,7 +14642,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "integrity": "sha1-mR7GnSluAxN0fVm9/St0XDX4go0=" }, "safe-regex": { "version": "1.1.0", @@ -14656,12 +14656,12 @@ "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + "integrity": "sha1-RPoWGwGHuVSd2Eu5GAL5vYOFzWo=" }, "sax": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "integrity": "sha1-KBYjTiN4vdxOU1T6tcqold9xANk=", "dev": true }, "saxes": { @@ -14691,7 +14691,7 @@ "semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "integrity": "sha1-qVT5Ma66UI0we78Gnv8MAclhFvc=", "dev": true }, "semver-greatest-satisfied-range": { @@ -14706,7 +14706,7 @@ "semver-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-2.0.0.tgz", - "integrity": "sha512-mUdIBBvdn0PLOeP3TEkMH7HHeUP3GjsXCwKarjv/kGmUFOYg1VqEemKhoQpWMu6X2I8kHeuVdGibLGkVK+/5Qw==", + "integrity": "sha1-qTwsWERTmncCMzeRB7OMe0rJ0zg=", "dev": true, "optional": true }, @@ -14729,7 +14729,7 @@ "set-value": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "integrity": "sha1-oY1AUw5vB95CKMfe/kInr4ytAFs=", "dev": true, "requires": { "extend-shallow": "^2.0.1", @@ -14752,7 +14752,7 @@ "setprototypeof": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", + "integrity": "sha1-fpWsskqpL1iF4KvvW6ExMw1K5oM=", "dev": true }, "shebang-command": { @@ -14773,7 +14773,7 @@ "shellwords": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", - "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", + "integrity": "sha1-1rkYHBpI05cyTISHHvvPxz/AZUs=", "dev": true }, "signal-exit": { @@ -14794,7 +14794,7 @@ "is-arrayish": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "integrity": "sha1-RXSirlb3qyBolvtDHq7tBm/fjwM=", "dev": true } } @@ -14808,7 +14808,7 @@ "slice-ansi": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", - "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "integrity": "sha1-ys12k0YaY3pXiNkqfdT7oGjoFjY=", "dev": true, "requires": { "ansi-styles": "^3.2.0", @@ -14827,7 +14827,7 @@ "snapdragon": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "integrity": "sha1-ZJIufFZbDhQgS6GqfWlkJ40lGC0=", "dev": true, "requires": { "base": "^0.11.1", @@ -14843,7 +14843,7 @@ "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=", "dev": true, "requires": { "ms": "2.0.0" @@ -14878,7 +14878,7 @@ "snapdragon-node": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "integrity": "sha1-bBdfhv8UvbByRWPo88GwIaKGhTs=", "dev": true, "requires": { "define-property": "^1.0.0", @@ -14898,7 +14898,7 @@ "is-accessor-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -14907,7 +14907,7 @@ "is-data-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -14916,7 +14916,7 @@ "is-descriptor": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=", "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", @@ -14929,7 +14929,7 @@ "snapdragon-util": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "integrity": "sha1-+VZHlIbyrNeXAGk/b3uAXkWrVuI=", "dev": true, "requires": { "kind-of": "^3.2.0" @@ -15116,13 +15116,13 @@ "sparkles": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.1.tgz", - "integrity": "sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw==", + "integrity": "sha1-AI22XtzmxQ7sDF4ijhlFBh3QQ3w=", "dev": true }, "spdx-correct": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", - "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", + "integrity": "sha1-+4PlBERSaPFUsHTiGMh8ADzTHfQ=", "dev": true, "requires": { "spdx-expression-parse": "^3.0.0", @@ -15132,7 +15132,7 @@ "spdx-exceptions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", - "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", + "integrity": "sha1-LqRQrudPKom/uUUZwH/Nb0EyKXc=", "dev": true }, "spdx-expression-parse": { @@ -15148,7 +15148,7 @@ "spdx-license-ids": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", - "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", + "integrity": "sha1-NpS1gEVnpFjTyARYQqY1hjL2JlQ=", "dev": true }, "spectrum-colorpicker2": { @@ -15159,7 +15159,7 @@ "split-string": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "integrity": "sha1-fLCd2jqGWFcFxks5pkZgOGguj+I=", "dev": true, "requires": { "extend-shallow": "^3.0.0" @@ -15216,7 +15216,7 @@ "sshpk": { "version": "1.16.1", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", - "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "integrity": "sha1-+2YcC+8ps520B2nuOfpwCT1vaHc=", "requires": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", @@ -15232,7 +15232,7 @@ "stable": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", - "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", + "integrity": "sha1-g26zyDgv4pNv6vVEYxAXzn1Ho88=", "dev": true }, "stack-trace": { @@ -15277,7 +15277,7 @@ "stream-exhaust": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz", - "integrity": "sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw==", + "integrity": "sha1-rNrI2lnvK8HheiwMz2wyDRIOVV0=", "dev": true }, "stream-shift": { @@ -15411,7 +15411,7 @@ "strip-dirs": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-2.1.0.tgz", - "integrity": "sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g==", + "integrity": "sha1-SYdzYmT8NEzyD2w0rKnRPR1O1sU=", "dev": true, "optional": true, "requires": { @@ -15451,7 +15451,7 @@ "strip-outer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", - "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", + "integrity": "sha1-sv0qv2YEudHmATBXGV34Nrip1jE=", "dev": true, "optional": true, "requires": { @@ -15461,7 +15461,7 @@ "stylehacks": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-4.0.3.tgz", - "integrity": "sha512-7GlLk9JwlElY4Y6a/rmbH2MhVlTyVmiJd1PfTCqFaIBEGMYNsrO/v3SeGTdhBThLg4Z+NbOk/qFMwCa+J+3p/g==", + "integrity": "sha1-Zxj8r00eB9ihMYaQiB6NlnJqcdU=", "dev": true, "requires": { "browserslist": "^4.0.0", @@ -15485,7 +15485,7 @@ "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "integrity": "sha1-4uaaRKyHcveKHsCzW2id9lMO/I8=", "dev": true, "requires": { "has-flag": "^3.0.0" @@ -15531,7 +15531,7 @@ "table": { "version": "5.4.6", "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", - "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", + "integrity": "sha1-EpLRlQDOP4YFOwXw6Ofko7shB54=", "dev": true, "requires": { "ajv": "^6.10.2", @@ -15543,7 +15543,7 @@ "ansi-regex": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "integrity": "sha1-i5+PCM8ay4Q3Vqg5yox+MWjFGZc=", "dev": true }, "is-fullwidth-code-point": { @@ -15555,7 +15555,7 @@ "string-width": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "integrity": "sha1-InZ74htirxCBV0MG9prFG2IgOWE=", "dev": true, "requires": { "emoji-regex": "^7.0.1", @@ -15566,7 +15566,7 @@ "strip-ansi": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "integrity": "sha1-jJpTb+tq/JYr36WxBKUJHBrZwK4=", "dev": true, "requires": { "ansi-regex": "^4.1.0" @@ -15577,7 +15577,7 @@ "tar-stream": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", - "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", + "integrity": "sha1-jqVdqzeXIlPZqa+Q/c1VmuQ1xVU=", "dev": true, "optional": true, "requires": { @@ -15646,7 +15646,7 @@ "text-hex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", - "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", + "integrity": "sha1-adycGxdEbueakr9biEu0uRJ1BvU=", "dev": true }, "text-table": { @@ -15664,7 +15664,7 @@ "through2": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "integrity": "sha1-AcHjnrMdB8t9A6lqcIIyYLIxMs0=", "dev": true, "requires": { "readable-stream": "~2.3.6", @@ -15706,7 +15706,7 @@ "through2-concurrent": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/through2-concurrent/-/through2-concurrent-2.0.0.tgz", - "integrity": "sha512-R5/jLkfMvdmDD+seLwN7vB+mhbqzWop5fAjx5IX8/yQq7VhBhzDmhXgaHAOnhnWkCpRMM7gToYHycB0CS/pd+A==", + "integrity": "sha1-yd0sFGUE7Jli28hqUWi2PWYmafo=", "dev": true, "requires": { "through2": "^2.0.0" @@ -15715,7 +15715,7 @@ "through2-filter": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-3.0.0.tgz", - "integrity": "sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==", + "integrity": "sha1-cA54bfI2fCyIzYqlvkz5weeDElQ=", "dev": true, "requires": { "through2": "~2.0.0", @@ -15738,7 +15738,7 @@ "timers-ext": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz", - "integrity": "sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==", + "integrity": "sha1-b1ethXjgej+5+R2Th9ZWR1VeJcY=", "dev": true, "requires": { "es5-ext": "~0.10.46", @@ -15754,7 +15754,7 @@ "tiny-emitter": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", - "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" + "integrity": "sha1-HRpW7fxRxD6GPLtTgqcjMONVVCM=" }, "tinymce": { "version": "4.9.11", @@ -15764,7 +15764,7 @@ "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "integrity": "sha1-bTQzWIl2jSGyvNoKonfO07G/rfk=", "dev": true, "requires": { "os-tmpdir": "~1.0.2" @@ -15789,7 +15789,7 @@ "to-buffer": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", - "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==", + "integrity": "sha1-STvUj2LXxD/N7TE6A9ytsuEhOoA=", "dev": true, "optional": true }, @@ -15822,7 +15822,7 @@ "to-regex": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "integrity": "sha1-E8/dmzNlUvMLUfM6iuG0Knp1mc4=", "dev": true, "requires": { "define-property": "^2.0.2", @@ -15853,7 +15853,7 @@ "toidentifier": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "integrity": "sha1-fhvjRw8ed5SLxD2Uo8j013UrpVM=", "dev": true }, "tough-cookie": { @@ -15927,7 +15927,7 @@ "type": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", - "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", + "integrity": "sha1-hI3XaY2vo+VKbEeedZxLw/GIR6A=", "dev": true }, "type-check": { @@ -15942,7 +15942,7 @@ "type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "integrity": "sha1-TlUs0F3wlGfcvE73Od6J8s83wTE=", "dev": true, "requires": { "media-typer": "0.3.0", @@ -16044,12 +16044,12 @@ "underscore": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz", - "integrity": "sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==" + "integrity": "sha1-BtzjSg5op7q8KbNluOdLiSUgOWE=" }, "undertaker": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-1.2.1.tgz", - "integrity": "sha512-71WxIzDkgYk9ZS+spIB8iZXchFhAdEo2YU8xYqBYJ39DIUIqziK78ftm26eecoIY49X0J2MLhG4hr18Yp6/CMA==", + "integrity": "sha1-cBZi/4zjWHFTJN/UkqTwNgVd/ks=", "dev": true, "requires": { "arr-flatten": "^1.0.1", @@ -16072,13 +16072,13 @@ "unicode-canonical-property-names-ecmascript": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", - "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==", + "integrity": "sha1-JhmADEyCWADv3YNDr33Zkzy+KBg=", "dev": true }, "unicode-match-property-ecmascript": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz", - "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==", + "integrity": "sha1-jtKjJWmWG86SJ9Cc0/+7j+1fAgw=", "dev": true, "requires": { "unicode-canonical-property-names-ecmascript": "^1.0.4", @@ -16100,7 +16100,7 @@ "union-value": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "integrity": "sha1-C2/nuDWuzaYcbqTU8CwUIh4QmEc=", "dev": true, "requires": { "arr-union": "^3.1.0", @@ -16124,7 +16124,7 @@ "unique-stream": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.3.1.tgz", - "integrity": "sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==", + "integrity": "sha1-xl0RDppK35psWUiygFPZqNBMvqw=", "dev": true, "requires": { "json-stable-stringify-without-jsonify": "^1.0.1", @@ -16134,7 +16134,7 @@ "universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "integrity": "sha1-tkb2m+OULavOzJ1mOcgNwQXvqmY=", "dev": true }, "unpipe": { @@ -16198,13 +16198,13 @@ "upath": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", - "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", + "integrity": "sha1-j2bbzVWog6za5ECK+LA1pQRMGJQ=", "dev": true }, "uri-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "integrity": "sha1-lMVA4f93KVbiKZUHwBCupsiDjrA=", "requires": { "punycode": "^2.1.0" } @@ -16244,7 +16244,7 @@ "use": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "integrity": "sha1-1QyMrHmhn7wg8pEfVuuXP04QBw8=", "dev": true }, "useragent": { @@ -16266,7 +16266,7 @@ "util.promisify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz", - "integrity": "sha1-RA9xZaRZyaFtwUXrjnLzVocJcDA=", + "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==", "dev": true, "requires": { "define-properties": "^1.1.2", @@ -16302,7 +16302,7 @@ "validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "integrity": "sha1-/JH2uce6FchX9MssXe/uw51PQQo=", "dev": true, "requires": { "spdx-correct": "^3.0.0", @@ -16381,7 +16381,7 @@ "vinyl-fs": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-3.0.3.tgz", - "integrity": "sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng==", + "integrity": "sha1-yFhJQF9nQo/qu71cXb3WT0fTG8c=", "dev": true, "requires": { "fs-mkdirp-stream": "^1.0.0", @@ -16590,7 +16590,7 @@ "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "integrity": "sha1-pFBD1U9YBTFtqNYvn1CRjT2nCwo=", "dev": true, "requires": { "isexe": "^2.0.0" @@ -16638,7 +16638,7 @@ "write": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", - "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", + "integrity": "sha1-CADhRSO5I6OH5BUSPIZWFqrg9cM=", "dev": true, "requires": { "mkdirp": "^0.5.1" @@ -16664,7 +16664,7 @@ "xmlbuilder": { "version": "12.0.0", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-12.0.0.tgz", - "integrity": "sha512-lMo8DJ8u6JRWp0/Y4XLa/atVDr75H9litKlb2E5j3V3MesoL50EBgZDWoLT3F/LztVnG67GjPXLZpqcky/UMnQ==", + "integrity": "sha1-4u1nXgaDSgid37hNuW4sKwP3jBo=", "dev": true }, "xmlchars": { @@ -16682,7 +16682,7 @@ "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "integrity": "sha1-u3J3n1+kZRhrH0OPZ0+jR/2121Q=", "dev": true }, "y18n": { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js index 92d2df43e5..06e1c61f1e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js @@ -323,12 +323,12 @@ filterAvailableCompositions(selectedContentType, newSelection).then(function () { deferred.resolve({ selectedContentType, newSelection }); // TODO: Here we could probably re-enable selection if we previously showed a throbber or something - }, function () { + }, function () { deferred.reject(); }); } - return deferred.promise; + return deferred.promise; } }; @@ -353,7 +353,7 @@ }), //get where used document types whereUsedContentTypeResource(scope.model.id).then(function (whereUsed) { - //pass to the dialog model the content type eg documentType or mediaType + //pass to the dialog model the content type eg documentType or mediaType scope.compositionsDialogModel.section = scope.contentType; //pass the list of 'where used' document types scope.compositionsDialogModel.whereCompositionUsed = whereUsed; @@ -566,6 +566,7 @@ property.isSensitiveValue = propertyModel.isSensitiveValue; property.allowCultureVariant = propertyModel.allowCultureVariant; property.allowSegmentVariant = propertyModel.allowSegmentVariant; + property.labelOnTop = propertyModel.labelOnTop; // update existing data types if (model.updateSameDataTypes) { @@ -647,7 +648,8 @@ mandatoryMessage: null, pattern: null, patternMessage: null - } + }, + labelOnTop: false }; // check if there already is an init property diff --git a/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js b/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js index 11c3db9aee..0788af66f7 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js @@ -83,7 +83,7 @@ }); var saveProperties = _.map(realProperties, function (p) { - var saveProperty = _.pick(p, 'id', 'alias', 'description', 'validation', 'label', 'sortOrder', 'dataTypeId', 'groupId', 'memberCanEdit', 'showOnMemberProfile', 'isSensitiveData', 'allowCultureVariant', 'allowSegmentVariant'); + var saveProperty = _.pick(p, 'id', 'alias', 'description', 'validation', 'label', 'sortOrder', 'dataTypeId', 'groupId', 'memberCanEdit', 'showOnMemberProfile', 'isSensitiveData', 'allowCultureVariant', 'allowSegmentVariant', 'labelOnTop'); return saveProperty; }); @@ -377,7 +377,7 @@ if (displayModel.variants && displayModel.variants.length > 1) { // Collect all invariant properties from the variants that are either the // default language variant or the default segment variant. - var defaultVariants = _.filter(displayModel.variants, function (variant) { + var defaultVariants = _.filter(displayModel.variants, function (variant) { var isDefaultLanguage = variant.language && variant.language.isDefault; var isDefaultSegment = variant.segment == null; @@ -406,7 +406,7 @@ return variant !== defaultVariant; }); - // now assign this same invariant property instance to the same index of the other variants property array + // now assign this same invariant property instance to the same index of the other variants property array _.each(otherVariants, function (variant) { _.each(invariantProps, function (invProp) { var tab = variant.tabs[invProp.tabIndex]; diff --git a/src/Umbraco.Web.UI.Client/src/less/main.less b/src/Umbraco.Web.UI.Client/src/less/main.less index e1e368f2e2..7c5ed4c9bb 100644 --- a/src/Umbraco.Web.UI.Client/src/less/main.less +++ b/src/Umbraco.Web.UI.Client/src/less/main.less @@ -228,6 +228,7 @@ umb-property:last-of-type .umb-control-group { padding-top: 5px; padding-bottom: 0; text-align: left; + margin-bottom: 5px; .control-label { width: auto; @@ -237,7 +238,7 @@ umb-property:last-of-type .umb-control-group { .control-description { max-width:480px;// avoiding description becoming too wide when its placed on top of property. - margin-bottom: 10px; + margin-bottom: 5px; } } @media (max-width: 767px) { @@ -245,9 +246,27 @@ umb-property:last-of-type .umb-control-group { .form-horizontal .umb-control-group .control-header { float: none; width: 100%; + &::after { + content: ""; + display: table; + clear: both; + } } - +} +.form-horizontal .umb-control-group.--label-on-top > .umb-el-wrap { + & > .control-header { + float: none; + width: 100%; + &::after { + content: ""; + display: table; + clear: both; + } + } + & > .controls { + margin-left: 0; + } } /* LABELS*/ diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.controller.js index 6310545b20..3c09745f15 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.controller.js @@ -35,6 +35,7 @@ vm.toggleShowOnMemberProfile = toggleShowOnMemberProfile; vm.toggleMemberCanEdit = toggleMemberCanEdit; vm.toggleIsSensitiveData = toggleIsSensitiveData; + vm.toggleLabelOnTop = toggleLabelOnTop; function onInit() { @@ -42,23 +43,24 @@ vm.showSensitiveData = user.userGroups.indexOf("sensitiveData") != -1; }); - //make the default the same as the content type + //make the default the same as the content type if (!$scope.model.property.dataTypeId) { $scope.model.property.allowCultureVariant = $scope.model.contentTypeAllowCultureVariant; } - + loadValidationTypes(); - + } function loadValidationTypes() { var labels = [ - "validation_validateAsEmail", - "validation_validateAsNumber", - "validation_validateAsUrl", + "validation_validateAsEmail", + "validation_validateAsNumber", + "validation_validateAsUrl", "validation_enterCustomValidation", - "validation_fieldIsMandatory" + "validation_fieldIsMandatory", + "contentTypeEditor_displaySettingsLabelOnTop" ]; localizationService.localizeMany(labels) @@ -69,6 +71,7 @@ vm.labels.validateAsUrl = data[2]; vm.labels.customValidation = data[3]; vm.labels.fieldIsMandatory = data[4]; + vm.labels.displaySettingsLabelOnTop = data[5]; vm.validationTypes = [ { @@ -121,7 +124,7 @@ $scope.model.updateSameDataTypes = model.updateSameDataTypes; vm.focusOnMandatoryField = true; - + // update property property.config = model.property.config; property.editor = model.property.editor; @@ -179,7 +182,7 @@ if(event && event.keyCode === 13) { submit(); } - } + } function submit() { if($scope.model.submit) { @@ -245,28 +248,31 @@ return !settingValue; } - function toggleAllowCultureVariants() { + function toggleAllowCultureVariants() { $scope.model.property.allowCultureVariant = toggleValue($scope.model.property.allowCultureVariant); } - function toggleAllowSegmentVariants() { + function toggleAllowSegmentVariants() { $scope.model.property.allowSegmentVariant = toggleValue($scope.model.property.allowSegmentVariant); } function toggleValidation() { - $scope.model.property.validation.mandatory = toggleValue($scope.model.property.validation.mandatory); + $scope.model.property.validation.mandatory = toggleValue($scope.model.property.validation.mandatory); } function toggleShowOnMemberProfile() { - $scope.model.property.showOnMemberProfile = toggleValue($scope.model.property.showOnMemberProfile); + $scope.model.property.showOnMemberProfile = toggleValue($scope.model.property.showOnMemberProfile); } function toggleMemberCanEdit() { - $scope.model.property.memberCanEdit = toggleValue($scope.model.property.memberCanEdit); + $scope.model.property.memberCanEdit = toggleValue($scope.model.property.memberCanEdit); } function toggleIsSensitiveData() { - $scope.model.property.isSensitiveData = toggleValue($scope.model.property.isSensitiveData); + $scope.model.property.isSensitiveData = toggleValue($scope.model.property.isSensitiveData); + } + function toggleLabelOnTop() { + $scope.model.property.labelOnTop = toggleValue($scope.model.property.labelOnTop); } onInit(); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html index af9295f1ed..482345c3b3 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html @@ -135,6 +135,21 @@ ng-if="vm.showValidationPattern" ng-keypress="vm.submitOnEnter($event)" /> + +
+ +
+ + + +
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html b/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html index dff62ee1eb..5b8e6d8f04 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html @@ -1,7 +1,7 @@
+ ng-class="{'hidelabel':vm.property.hideLabel, '--label-on-top':vm.property.labelOnTop, 'umb-control-group__listview': vm.property.alias === '_umb_containerView'}"> diff --git a/src/Umbraco.Web.UI.Client/src/views/memberGroups/edit.html b/src/Umbraco.Web.UI.Client/src/views/memberGroups/edit.html index aafaaa3bd4..63089de20c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/memberGroups/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/memberGroups/edit.html @@ -19,7 +19,7 @@ setpagetitle="header.setPageTitle"> - + diff --git a/src/Umbraco.Web.UI.NetCore/Startup.cs b/src/Umbraco.Web.UI.NetCore/Startup.cs index d86e6a8776..d496aadfd3 100644 --- a/src/Umbraco.Web.UI.NetCore/Startup.cs +++ b/src/Umbraco.Web.UI.NetCore/Startup.cs @@ -15,7 +15,7 @@ namespace Umbraco.Web.UI.NetCore private readonly IConfiguration _config; /// - /// Constructor + /// Initializes a new instance of the class. /// /// The Web Host Environment /// The Configuration @@ -30,15 +30,22 @@ namespace Umbraco.Web.UI.NetCore // This method gets called by the runtime. Use this method to add services to the container. // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 + /// + /// Configures the services + /// public void ConfigureServices(IServiceCollection services) { +#pragma warning disable IDE0022 // Use expression body for methods services.AddUmbraco(_env, _config) .AddAllBackOfficeComponents() .AddUmbracoWebsite() .Build(); +#pragma warning restore IDE0022 // Use expression body for methods } - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + /// + /// Configures the application + /// public void Configure(IApplicationBuilder app) { if (_env.IsDevelopment()) diff --git a/src/Umbraco.Web.UI.NetCore/Umbraco.Web.UI.NetCore.csproj b/src/Umbraco.Web.UI.NetCore/Umbraco.Web.UI.NetCore.csproj index da9523ae23..211238d951 100644 --- a/src/Umbraco.Web.UI.NetCore/Umbraco.Web.UI.NetCore.csproj +++ b/src/Umbraco.Web.UI.NetCore/Umbraco.Web.UI.NetCore.csproj @@ -22,7 +22,7 @@ - + diff --git a/src/Umbraco.Web.UI.NetCore/appsettings.json b/src/Umbraco.Web.UI.NetCore/appsettings.json index 00bd04c0b4..43ae07d5d6 100644 --- a/src/Umbraco.Web.UI.NetCore/appsettings.json +++ b/src/Umbraco.Web.UI.NetCore/appsettings.json @@ -1,6 +1,6 @@ { "ConnectionStrings": { - "umbracoDbDSN": "" + "umbracoDbDSN": "Server=(LocalDB)\\Umbraco;Database=NetCore;Integrated Security=true" }, "Serilog": { "MinimumLevel": { @@ -39,7 +39,7 @@ }, "RuntimeMinification": { "dataFolder": "App_Data/TEMP/Smidge", - "version": "637395756047165417" + "version": "637429346786793415" }, "Security": { "KeepUserLoggedIn": false, @@ -71,4 +71,4 @@ } } } -} +} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.NetCore/umbraco/config/lang/da.xml b/src/Umbraco.Web.UI.NetCore/umbraco/config/lang/da.xml index df20c1ce5a..857129b1c1 100644 --- a/src/Umbraco.Web.UI.NetCore/umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI.NetCore/umbraco/config/lang/da.xml @@ -1438,6 +1438,8 @@ Mange hilsner fra Umbraco robotten En Element-type er tiltænkt brug i f.eks. Nested Content, ikke i indholdstræet. Dette benyttes ikke for en Element-type Du har lavet ændringer til denne egenskab. Er du sikker på at du vil kassere dem? + Visning + Flyt label over editoren Tilføj sprog diff --git a/src/Umbraco.Web.UI.NetCore/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI.NetCore/umbraco/config/lang/en.xml index 4721e774ea..6916b00e1c 100644 --- a/src/Umbraco.Web.UI.NetCore/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI.NetCore/umbraco/config/lang/en.xml @@ -1698,6 +1698,8 @@ To manage your website, simply open the Umbraco back office and start adding con A document type cannot be changed to an Element type once it has been used to create one or more content items. This is not applicable for an Element type You have made changes to this property. Are you sure you want to discard them? + Appearance + Display label on top of editor. Add language diff --git a/src/Umbraco.Web.UI.NetCore/umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI.NetCore/umbraco/config/lang/en_us.xml index 14321e2ff5..cacb8288b3 100644 --- a/src/Umbraco.Web.UI.NetCore/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI.NetCore/umbraco/config/lang/en_us.xml @@ -1716,6 +1716,8 @@ To manage your website, simply open the Umbraco back office and start adding con A document type cannot be changed to an element type once it has been used to create one or more content items. This is not applicable for an element type You have made changes to this property. Are you sure you want to discard them? + Appearance + Display label on top of editor. Add language diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 78633bdf37..b5bfd75aad 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -231,9 +231,9 @@ False True - 8910 + 8100 / - http://localhost:8910 + http://localhost:8100 False False diff --git a/src/Umbraco.Web/AppBuilderExtensions.cs b/src/Umbraco.Web/AppBuilderExtensions.cs deleted file mode 100644 index f4766bc414..0000000000 --- a/src/Umbraco.Web/AppBuilderExtensions.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System.Web; -using Microsoft.AspNet.SignalR; -using Microsoft.Owin.Logging; -using Owin; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.Models; -using Umbraco.Core.Hosting; -using Umbraco.Web.Logging; - -namespace Umbraco.Web -{ - /// - /// Provides general extension methods to IAppBuilder. - /// - public static class AppBuilderExtensions - { - /// - /// Called at the end of configuring middleware - /// - /// The app builder. - /// - /// This could be used for something else in the future - maybe to inform Umbraco that middleware is done/ready, but for - /// now this is used to raise the custom event - /// - /// This is an extension method in case developer entirely replace the UmbracoDefaultOwinStartup class, in which case they will - /// need to ensure they call this extension method in their startup class. - /// - public static void FinalizeMiddlewareConfiguration(this IAppBuilder app) - { - UmbracoDefaultOwinStartup.OnMiddlewareConfigured(new OwinMiddlewareConfiguredEventArgs(app)); - } - - /// - /// Sets the OWIN logger to use Umbraco's logging system. - /// - /// The app builder. - public static void SetUmbracoLoggerFactory(this IAppBuilder app) - { - app.SetLoggerFactory(new OwinLoggerFactory()); - } - - /// - /// Configures SignalR. - /// - /// The app builder. - /// - /// - public static IAppBuilder UseSignalR(this IAppBuilder app, GlobalSettings globalSettings, IHostingEnvironment hostingEnvironment) - { - var umbracoPath = globalSettings.GetUmbracoMvcArea(hostingEnvironment); - var signalrPath = HttpRuntime.AppDomainAppVirtualPath + umbracoPath + "/BackOffice/signalr"; - return app.MapSignalR(signalrPath, new HubConfiguration { EnableDetailedErrors = true }); - } - } -} diff --git a/src/Umbraco.Web/HttpUrlHelperExtensions.cs b/src/Umbraco.Web/HttpUrlHelperExtensions.cs deleted file mode 100644 index 4e5533d327..0000000000 --- a/src/Umbraco.Web/HttpUrlHelperExtensions.cs +++ /dev/null @@ -1,154 +0,0 @@ -using System; -using System.Linq; -using System.Linq.Expressions; -using System.Web.Http.Routing; -using Umbraco.Core; -using Umbraco.Web.Composing; -using Umbraco.Web.Mvc; -using Umbraco.Web.WebApi; - -namespace Umbraco.Web -{ - public static class HttpUrlHelperExtensions - { - /// - /// Return the Url for a Web Api service - /// - /// - /// - /// - /// - /// - public static string GetUmbracoApiService(this UrlHelper url, string actionName, object id = null) - where T : UmbracoApiController - { - return url.GetUmbracoApiService(actionName, typeof(T), id); - } - - public static string GetUmbracoApiService(this UrlHelper url, Expression> methodSelector) - where T : UmbracoApiController - { - var method = ExpressionHelper.GetMethodInfo(methodSelector); - var methodParams = ExpressionHelper.GetMethodParams(methodSelector); - if (method == null) - { - throw new MissingMethodException("Could not find the method " + methodSelector + " on type " + typeof(T) + " or the result "); - } - - if (methodParams.Any() == false) - { - return url.GetUmbracoApiService(method.Name); - } - return url.GetUmbracoApiService(method.Name, methodParams.Values.First()); - } - - /// - /// Return the Url for a Web Api service - /// - /// - /// - /// - /// - /// - public static string GetUmbracoApiService(this UrlHelper url, string actionName, Type apiControllerType, object id = null) - { - if (actionName == null) throw new ArgumentNullException(nameof(actionName)); - if (string.IsNullOrWhiteSpace(actionName)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(actionName)); - if (apiControllerType == null) throw new ArgumentNullException(nameof(apiControllerType)); - - var area = ""; - - var apiController = Current.UmbracoApiControllerTypes - .SingleOrDefault(x => x == apiControllerType); - if (apiController == null) - throw new InvalidOperationException("Could not find the umbraco api controller of type " + apiControllerType.FullName); - var metaData = PluginController.GetMetadata(apiController); - if (metaData.AreaName.IsNullOrWhiteSpace() == false) - { - //set the area to the plugin area - area = metaData.AreaName; - } - return url.GetUmbracoApiService(actionName, ControllerExtensions.GetControllerName(apiControllerType), area, id); - } - - /// - /// Return the Url for a Web Api service - /// - /// - /// - /// - /// - /// - public static string GetUmbracoApiService(this UrlHelper url, string actionName, string controllerName, object id = null) - { - return url.GetUmbracoApiService(actionName, controllerName, "", id); - } - - /// - /// Return the Url for a Web Api service - /// - /// - /// - /// - /// - /// - /// - public static string GetUmbracoApiService(this UrlHelper url, string actionName, string controllerName, string area, object id = null) - { - if (actionName == null) throw new ArgumentNullException(nameof(actionName)); - if (string.IsNullOrWhiteSpace(actionName)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(actionName)); - if (controllerName == null) throw new ArgumentNullException(nameof(controllerName)); - if (string.IsNullOrWhiteSpace(controllerName)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(controllerName)); - - string routeName; - if (area.IsNullOrWhiteSpace()) - { - routeName = string.Format("umbraco-{0}-{1}", "api", controllerName); - if (id == null) - { - return url.Route(routeName, new { controller = controllerName, action = actionName, httproute = "" }); - } - else - { - return url.Route(routeName, new { controller = controllerName, action = actionName, id = id, httproute = "" }); - } - } - else - { - routeName = string.Format("umbraco-{0}-{1}-{2}", "api", area, controllerName); - if (id == null) - { - return url.Route(routeName, new { controller = controllerName, action = actionName, httproute = "" }); - } - else - { - return url.Route(routeName, new { controller = controllerName, action = actionName, id = id, httproute = "" }); - } - } - } - - /// - /// Return the Base Url (not including the action) for a Web Api service - /// - /// - /// - /// - /// - public static string GetUmbracoApiServiceBaseUrl(this UrlHelper url, string actionName) - where T : UmbracoApiController - { - return url.GetUmbracoApiService(actionName).TrimEnd(actionName); - } - - public static string GetUmbracoApiServiceBaseUrl(this UrlHelper url, Expression> methodSelector) - where T : UmbracoApiController - { - var method = ExpressionHelper.GetMethodInfo(methodSelector); - if (method == null) - { - throw new MissingMethodException("Could not find the method " + methodSelector + " on type " + typeof(T) + " or the result "); - } - return url.GetUmbracoApiService(method.Name).TrimEnd(method.Name); - } - } -} diff --git a/src/Umbraco.Web/ImageProcessorLogger.cs b/src/Umbraco.Web/ImageProcessorLogger.cs deleted file mode 100644 index ad7a6ceaaf..0000000000 --- a/src/Umbraco.Web/ImageProcessorLogger.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; -using System.Runtime.CompilerServices; -using Microsoft.Extensions.Logging; -using ImageProcessor.Common.Exceptions; -using Umbraco.Web.Composing; - -namespace Umbraco.Web -{ - - /// - /// A logger for explicitly logging ImageProcessor exceptions. - /// - /// Creating this logger is enough for ImageProcessor to find and replace its in-built debug logger - /// without any additional configuration required. This class currently has to be public in order - /// to do so. - /// - /// - public sealed class ImageProcessorLogger : ImageProcessor.Common.Exceptions.ILogger - { - /// - /// Logs the specified message as an error. - /// - /// The type calling the logger. - /// The message to log. - /// The property or method name calling the log. - /// The line number where the method is called. - public void Log(string text, [CallerMemberName] string callerName = null, [CallerLineNumber] int lineNumber = 0) - { - // Using LogHelper since the ImageProcessor logger expects a parameterless constructor. - var message = $"{callerName} {lineNumber} : {text}"; - Current.Logger.LogError(new ImageProcessingException(message).Message); - } - - /// - /// Logs the specified message as an error. - /// - /// The type calling the logger. - /// The message to log. - /// The property or method name calling the log. - /// The line number where the method is called. - public void Log(Type type, string text, [CallerMemberName] string callerName = null, [CallerLineNumber] int lineNumber = 0) - { - // Using LogHelper since the ImageProcessor logger expects a parameterless constructor. - var message = $"{callerName} {lineNumber} : {text}"; - Current.Logger.LogError(new ImageProcessingException(message).Message); - } - } -} diff --git a/src/Umbraco.Web/Logging/OwinLogger.cs b/src/Umbraco.Web/Logging/OwinLogger.cs deleted file mode 100644 index 7983ee36c7..0000000000 --- a/src/Umbraco.Web/Logging/OwinLogger.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System; -using System.Diagnostics; -using Microsoft.Extensions.Logging; - -namespace Umbraco.Web.Logging -{ - internal class OwinLogger : Microsoft.Owin.Logging.ILogger - { - private readonly ILogger _logger; - private readonly Lazy _type; - - public OwinLogger(ILogger logger, Lazy type) - { - _logger = logger; - _type = type; - } - - /// - /// Aggregates most logging patterns to a single method. This must be compatible with the Func representation in the OWIN environment. - /// To check IsEnabled call WriteCore with only TraceEventType and check the return value, no event will be written. - /// - /// - /// - public bool WriteCore(TraceEventType eventType, int eventId, object state, Exception exception, Func formatter) - { - if (state == null) state = ""; - switch (eventType) - { - case TraceEventType.Critical: - _logger.LogCritical(exception, "[{EventType}] Event Id: {EventId}, State: {State}", eventType, eventId, state); - return true; - case TraceEventType.Error: - _logger.LogError(exception, "[{EventType}] Event Id: {EventId}, State: {State}", eventType, eventId, state); - return true; - case TraceEventType.Warning: - _logger.LogWarning("[{EventType}] Event Id: {EventId}, State: {State}", eventType, eventId, state); - return true; - case TraceEventType.Information: - _logger.LogInformation("[{EventType}] Event Id: {EventId}, State: {State}", eventType, eventId, state); - return true; - case TraceEventType.Verbose: - _logger.LogDebug("[{EventType}] Event Id: {EventId}, State: {State}", eventType, eventId, state); - return true; - case TraceEventType.Start: - _logger.LogDebug("[{EventType}] Event Id: {EventId}, State: {State}", eventType, eventId, state); - return true; - case TraceEventType.Stop: - _logger.LogDebug("[{EventType}] Event Id: {EventId}, State: {State}", eventType, eventId, state); - return true; - case TraceEventType.Suspend: - _logger.LogDebug("[{EventType}] Event Id: {EventId}, State: {State}", eventType, eventId, state); - return true; - case TraceEventType.Resume: - _logger.LogDebug("[{EventType}] Event Id: {EventId}, State: {State}", eventType, eventId, state); - return true; - case TraceEventType.Transfer: - _logger.LogDebug("[{EventType}] Event Id: {EventId}, State: {State}", eventType, eventId, state); - return true; - default: - throw new ArgumentOutOfRangeException("eventType"); - } - } - } -} diff --git a/src/Umbraco.Web/Logging/OwinLoggerFactory.cs b/src/Umbraco.Web/Logging/OwinLoggerFactory.cs deleted file mode 100644 index d8b76145c6..0000000000 --- a/src/Umbraco.Web/Logging/OwinLoggerFactory.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using Microsoft.Owin.Logging; -using Umbraco.Core; - -namespace Umbraco.Web.Logging -{ - public class OwinLoggerFactory : ILoggerFactory - { - /// - /// Creates a new ILogger instance of the given name. - /// - /// - /// - public Microsoft.Owin.Logging.ILogger Create(string name) - { - return new OwinLogger(StaticApplicationLogging.Logger, new Lazy(() => Type.GetType(name) ?? typeof (OwinLogger))); - } - } -} diff --git a/src/Umbraco.Web/Mvc/ValidateMvcAngularAntiForgeryTokenAttribute.cs b/src/Umbraco.Web/Mvc/ValidateMvcAngularAntiForgeryTokenAttribute.cs deleted file mode 100644 index 484ee70ff7..0000000000 --- a/src/Umbraco.Web/Mvc/ValidateMvcAngularAntiForgeryTokenAttribute.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Security.Claims; -using System.Web.Mvc; -using Umbraco.Web.WebApi.Filters; - -namespace Umbraco.Web.Mvc -{ - /// - /// A filter to check for the csrf token based on Angular's standard approach - /// - /// - /// Code derived from http://ericpanorel.net/2013/07/28/spa-authentication-and-csrf-mvc4-antiforgery-implementation/ - /// - /// If the authentication type is cookie based, then this filter will execute, otherwise it will be disabled - /// - public sealed class ValidateMvcAngularAntiForgeryTokenAttribute : ActionFilterAttribute - { - public override void OnActionExecuting(ActionExecutingContext filterContext) - { - var userIdentity = filterContext.HttpContext.User.Identity as ClaimsIdentity; - if (userIdentity != null) - { - //if there is not CookiePath claim, then exit - if (userIdentity.HasClaim(x => x.Type == ClaimTypes.CookiePath) == false) - { - base.OnActionExecuting(filterContext); - return; - } - } - - string failedReason; - var headers = new List>>(); - foreach (var key in filterContext.HttpContext.Request.Headers.AllKeys) - { - if (headers.Any(x => x.Key == key)) - { - var found = headers.First(x => x.Key == key); - found.Value.Add(filterContext.HttpContext.Request.Headers[key]); - } - else - { - headers.Add(new KeyValuePair>(key, new List { filterContext.HttpContext.Request.Headers[key] })); - } - } - var cookie = filterContext.HttpContext.Request.Cookies[Core.Constants.Web.CsrfValidationCookieName]; - if (AngularAntiForgeryHelper.ValidateHeaders( - headers.Select(x => new KeyValuePair>(x.Key, x.Value)).ToArray(), - cookie == null ? "" : cookie.Value, - out failedReason) == false) - { - var result = new HttpStatusCodeResult(HttpStatusCode.ExpectationFailed); - filterContext.Result = result; - return; - } - - base.OnActionExecuting(filterContext); - } - } -} diff --git a/src/Umbraco.Web/OwinExtensions.cs b/src/Umbraco.Web/OwinExtensions.cs deleted file mode 100644 index 801ceae191..0000000000 --- a/src/Umbraco.Web/OwinExtensions.cs +++ /dev/null @@ -1,75 +0,0 @@ -using System; -using System.Web; -using Microsoft.Owin; -using Microsoft.Owin.Security; -using Umbraco.Core; -using Umbraco.Web.Security; - -namespace Umbraco.Web -{ - public static class OwinExtensions - { - - /// - /// Gets the for the Umbraco back office cookie - /// - /// - /// - internal static ISecureDataFormat GetUmbracoAuthTicketDataProtector(this IOwinContext owinContext) - { - var found = owinContext.Get(); - return found?.Protector; - } - - public static string GetCurrentRequestIpAddress(this IOwinContext owinContext) - { - if (owinContext == null) - { - return "Unknown, owinContext is null"; - } - if (owinContext.Request == null) - { - return "Unknown, owinContext.Request is null"; - } - - var httpContext = owinContext.TryGetHttpContext(); - if (httpContext == false) - { - return "Unknown, cannot resolve HttpContext from owinContext"; - } - - return httpContext.Result.GetCurrentRequestIpAddress(); - } - - /// - /// Nasty little hack to get HttpContextBase from an owin context - /// - /// - /// - internal static Attempt TryGetHttpContext(this IOwinContext owinContext) - { - var ctx = owinContext.Get(typeof(HttpContextBase).FullName); - return ctx == null ? Attempt.Fail() : Attempt.Succeed(ctx); - } - - /// - /// Adapted from Microsoft.AspNet.Identity.Owin.OwinContextExtensions - /// - public static T Get(this IOwinContext context) - { - if (context == null) throw new ArgumentNullException(nameof(context)); - return context.Get(GetKey(typeof(T))); - } - - public static IOwinContext Set(this IOwinContext context, T value) - { - if (context == null) throw new ArgumentNullException(nameof(context)); - return context.Set(GetKey(typeof(T)), value); - } - - private static string GetKey(Type t) - { - return "AspNet.Identity.Owin:" + t.AssemblyQualifiedName; - } - } -} diff --git a/src/Umbraco.Web/OwinMiddlewareConfiguredEventArgs.cs b/src/Umbraco.Web/OwinMiddlewareConfiguredEventArgs.cs deleted file mode 100644 index 8b4d46a373..0000000000 --- a/src/Umbraco.Web/OwinMiddlewareConfiguredEventArgs.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using Owin; - -namespace Umbraco.Web -{ - public class OwinMiddlewareConfiguredEventArgs : EventArgs - { - public OwinMiddlewareConfiguredEventArgs(IAppBuilder appBuilder) - { - AppBuilder = appBuilder; - } - - public IAppBuilder AppBuilder { get; private set; } - } -} diff --git a/src/Umbraco.Web/Security/ActiveDirectoryBackOfficeUserPasswordChecker.cs b/src/Umbraco.Web/Security/ActiveDirectoryBackOfficeUserPasswordChecker.cs index 82c9cb8496..46b6540d73 100644 --- a/src/Umbraco.Web/Security/ActiveDirectoryBackOfficeUserPasswordChecker.cs +++ b/src/Umbraco.Web/Security/ActiveDirectoryBackOfficeUserPasswordChecker.cs @@ -1,14 +1,14 @@ -using System; +using System; using System.DirectoryServices.AccountManagement; using System.Threading.Tasks; using Microsoft.Extensions.Options; -using Umbraco.Core.BackOffice; -using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; +using Umbraco.Core.Security; namespace Umbraco.Web.Security { // TODO: This relies on an assembly that is not .NET Standard (at least not at the time of implementation) :( + // TODO: This could be ported now, see https://stackoverflow.com/questions/37330705/working-with-directoryservices-in-asp-net-core public class ActiveDirectoryBackOfficeUserPasswordChecker : IBackOfficeUserPasswordChecker { private readonly IOptions _activeDirectorySettings; diff --git a/src/Umbraco.Web/Security/AppBuilderExtensions.cs b/src/Umbraco.Web/Security/AppBuilderExtensions.cs deleted file mode 100644 index aa5ff7a4e1..0000000000 --- a/src/Umbraco.Web/Security/AppBuilderExtensions.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System; -using System.Threading; -using Microsoft.Owin; -using Microsoft.Owin.Extensions; -using Microsoft.Owin.Security; -using Microsoft.Owin.Security.Cookies; -using Microsoft.Owin.Security.DataProtection; -using Owin; -using Umbraco.Core; -using Umbraco.Core.Cache; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.Models; -using Umbraco.Core.Hosting; -using Umbraco.Web.Composing; -using Constants = Umbraco.Core.Constants; - -namespace Umbraco.Web.Security -{ - /// - /// Provides security/identity extension methods to IAppBuilder. - /// - public static class AppBuilderExtensions - { - - // TODO: Migrate this! - - /// - /// Ensures that the cookie middleware for validating external logins is assigned to the pipeline with the correct - /// Umbraco back office configuration - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// By default this will be configured to execute on PipelineStage.Authenticate - /// - public static IAppBuilder UseUmbracoBackOfficeExternalCookieAuthentication(this IAppBuilder app, IUmbracoContextAccessor umbracoContextAccessor, IRuntimeState runtimeState,GlobalSettings globalSettings, IHostingEnvironment hostingEnvironment, IRequestCache requestCache) - { - return app.UseUmbracoBackOfficeExternalCookieAuthentication(umbracoContextAccessor, runtimeState, globalSettings, hostingEnvironment, requestCache, PipelineStage.Authenticate); - } - - /// - /// Ensures that the cookie middleware for validating external logins is assigned to the pipeline with the correct - /// Umbraco back office configuration - /// - /// - /// - /// - /// - /// - /// - /// - /// - public static IAppBuilder UseUmbracoBackOfficeExternalCookieAuthentication(this IAppBuilder app, - IUmbracoContextAccessor umbracoContextAccessor, IRuntimeState runtimeState, - GlobalSettings globalSettings, IHostingEnvironment hostingEnvironment, IRequestCache requestCache, PipelineStage stage) - { - if (app == null) throw new ArgumentNullException(nameof(app)); - if (runtimeState == null) throw new ArgumentNullException(nameof(runtimeState)); - if (hostingEnvironment == null) throw new ArgumentNullException(nameof(hostingEnvironment)); - - app.UseCookieAuthentication(new CookieAuthenticationOptions - { - AuthenticationType = Constants.Security.BackOfficeExternalAuthenticationType, - AuthenticationMode = AuthenticationMode.Passive, - CookieName = Constants.Security.BackOfficeExternalCookieName, - ExpireTimeSpan = TimeSpan.FromMinutes(5), - CookiePath = "/", - CookieSecure = globalSettings.UseHttps ? CookieSecureOption.Always : CookieSecureOption.SameAsRequest, - CookieHttpOnly = true, - CookieDomain = new SecuritySettings().AuthCookieDomain // TODO inject settings - }, stage); - - app.UseStageMarker(stage); - return app; - } - - public static void SanitizeThreadCulture(this IAppBuilder app) - { - Thread.CurrentThread.SanitizeThreadCulture(); - } - - } -} diff --git a/src/Umbraco.Web/Security/AuthenticationExtensions.cs b/src/Umbraco.Web/Security/AuthenticationExtensions.cs deleted file mode 100644 index de5abf8a6b..0000000000 --- a/src/Umbraco.Web/Security/AuthenticationExtensions.cs +++ /dev/null @@ -1,242 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Security.Claims; -using System.Security.Principal; -using System.Threading; -using System.Web; -using Microsoft.AspNetCore.Identity; -using Microsoft.Owin; -using Microsoft.Owin.Security; -using Newtonsoft.Json; -using Umbraco.Core; -using Umbraco.Core.BackOffice; -using Umbraco.Core.Configuration.Models; -using Umbraco.Extensions; -using Umbraco.Web.Composing; -using Constants = Umbraco.Core.Constants; - -namespace Umbraco.Web.Security -{ - /// - /// Extensions to create and renew and remove authentication tickets for the Umbraco back office - /// - public static class AuthenticationExtensions - { - /// - /// This will check the ticket to see if it is valid, if it is it will set the current thread's user and culture - /// - /// - /// - /// If true will attempt to renew the ticket - public static bool AuthenticateCurrentRequest(this HttpContextBase http, AuthenticationTicket ticket, bool renewTicket) - { - if (http == null) throw new ArgumentNullException(nameof(http)); - - // if there was a ticket, it's not expired, - it should not be renewed or its renewable - if (ticket?.Properties.ExpiresUtc != null && ticket.Properties.ExpiresUtc.Value > DateTimeOffset.UtcNow && (renewTicket == false || http.RenewUmbracoAuthTicket())) - { - try - { - // get the Umbraco user identity - if (!(ticket.Identity is UmbracoBackOfficeIdentity identity)) - throw new InvalidOperationException("The AuthenticationTicket specified does not contain the correct Identity type"); - - // set the principal object - var principal = new ClaimsPrincipal(identity); - - // It is actually not good enough to set this on the current app Context and the thread, it also needs - // to be set explicitly on the HttpContext.Current !! This is a strange web api thing that is actually - // an underlying fault of asp.net not propagating the User correctly. - if (HttpContext.Current != null) - { - HttpContext.Current.User = principal; - } - http.User = principal; - Thread.CurrentPrincipal = principal; - - // This is a back office request, we will also set the culture/ui culture - Thread.CurrentThread.CurrentCulture = - Thread.CurrentThread.CurrentUICulture = - new System.Globalization.CultureInfo(identity.Culture); - - return true; - } - catch (Exception ex) - { - if (ex is FormatException || ex is JsonReaderException) - { - // this will occur if the cookie data is invalid - - } - else - { - throw; - } - - } - } - - return false; - } - - - /// - /// This will return the current back office identity. - /// - /// - /// - /// Returns the current back office identity if an admin is authenticated otherwise null - /// - public static UmbracoBackOfficeIdentity GetCurrentIdentity(this HttpContextBase http) - { - if (http == null) throw new ArgumentNullException(nameof(http)); - if (http.User == null) return null; //there's no user at all so no identity - - // If it's already a UmbracoBackOfficeIdentity - var backOfficeIdentity = http.User.GetUmbracoIdentity(); - if (backOfficeIdentity != null) return backOfficeIdentity; - - return null; - } - - /// - /// This will return the current back office identity. - /// - /// - /// - /// Returns the current back office identity if an admin is authenticated otherwise null - /// - internal static UmbracoBackOfficeIdentity GetCurrentIdentity(this HttpContext http) - { - if (http == null) throw new ArgumentNullException("http"); - return new HttpContextWrapper(http).GetCurrentIdentity(); - } - - /// - /// This will force ticket renewal in the OWIN pipeline - /// - /// - /// - public static bool RenewUmbracoAuthTicket(this HttpContextBase http) - { - if (http == null) throw new ArgumentNullException("http"); - http.Items[Constants.Security.ForceReAuthFlag] = true; - return true; - } - - // NOTE: Migrated to netcore (though in a different way) - public static double GetRemainingAuthSeconds(this HttpContextBase http) - { - if (http == null) throw new ArgumentNullException(nameof(http)); - var ticket = http.GetUmbracoAuthTicket(); - return ticket.GetRemainingAuthSeconds(); - } - - // NOTE: Migrated to netcore (though in a different way) - public static double GetRemainingAuthSeconds(this AuthenticationTicket ticket) - { - var utcExpired = ticket?.Properties.ExpiresUtc; - if (utcExpired == null) return 0; - var secondsRemaining = utcExpired.Value.Subtract(DateTimeOffset.UtcNow).TotalSeconds; - return secondsRemaining; - } - - /// - /// Gets the umbraco auth ticket - /// - /// - /// - public static AuthenticationTicket GetUmbracoAuthTicket(this HttpContextBase http) - { - if (http == null) throw new ArgumentNullException(nameof(http)); - return GetAuthTicket(http, /*Current.Configs.Security() TODO*/new SecuritySettings().AuthCookieName); - } - - internal static AuthenticationTicket GetUmbracoAuthTicket(this HttpContext http) - { - if (http == null) throw new ArgumentNullException(nameof(http)); - return new HttpContextWrapper(http).GetUmbracoAuthTicket(); - } - - public static AuthenticationTicket GetUmbracoAuthTicket(this IOwinContext ctx) - { - if (ctx == null) throw new ArgumentNullException(nameof(ctx)); - return GetAuthTicket(ctx, /*Current.Configs.Security() TODO introduce injection instead of default value*/new SecuritySettings().AuthCookieName); - } - - private static AuthenticationTicket GetAuthTicket(this IOwinContext owinCtx, string cookieName) - { - var asDictionary = new Dictionary(); - foreach (var requestCookie in owinCtx.Request.Cookies) - { - var key = requestCookie.Key; - asDictionary[key] = requestCookie.Value; - } - - var secureFormat = owinCtx.GetUmbracoAuthTicketDataProtector(); - - // get the ticket - try - { - - return GetAuthTicket(secureFormat, asDictionary, cookieName); - } - catch (Exception) - { - owinCtx.Authentication.SignOut( - Constants.Security.BackOfficeAuthenticationType, - Constants.Security.BackOfficeExternalAuthenticationType); - return null; - } - } - - private static AuthenticationTicket GetAuthTicket(this HttpContextBase http, string cookieName) - { - var asDictionary = new Dictionary(); - for (var i = 0; i < http.Request.Cookies.Keys.Count; i++) - { - var key = http.Request.Cookies.Keys.Get(i); - asDictionary[key] = http.Request.Cookies[key].Value; - } - - var owinCtx = http.GetOwinContext(); - var secureFormat = owinCtx.GetUmbracoAuthTicketDataProtector(); - - // will only happen in tests - if (secureFormat == null) return null; - - // get the ticket - try - { - return GetAuthTicket(secureFormat, asDictionary, cookieName); - } - catch (Exception) - { - // occurs when decryption fails - - return null; - } - } - - private static AuthenticationTicket GetAuthTicket(ISecureDataFormat secureDataFormat, IDictionary cookies, string cookieName) - { - if (cookies == null) throw new ArgumentNullException(nameof(cookies)); - - if (cookies.ContainsKey(cookieName) == false) return null; - - var formsCookie = cookies[cookieName]; - if (formsCookie == null) - { - return null; - } - // get the ticket - - return secureDataFormat.Unprotect(formsCookie); - } - - - } -} diff --git a/src/Umbraco.Web/Security/BackOfficeCookieAuthenticationProvider.cs b/src/Umbraco.Web/Security/BackOfficeCookieAuthenticationProvider.cs deleted file mode 100644 index 6ce61c90d6..0000000000 --- a/src/Umbraco.Web/Security/BackOfficeCookieAuthenticationProvider.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using System.Security.Claims; -using System.Threading.Tasks; -using Microsoft.Extensions.Options; -using Microsoft.Owin; -using Microsoft.Owin.Security.Cookies; -using Umbraco.Core; -using Umbraco.Core.BackOffice; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.Models; -using Umbraco.Core.Services; -using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.Hosting; -using Umbraco.Core.Security; - -namespace Umbraco.Web.Security -{ - // TODO: Migrate this logic to cookie events in ConfigureUmbracoBackOfficeCookieOptions - - public class BackOfficeCookieAuthenticationProvider : CookieAuthenticationProvider - { - private readonly IUserService _userService; - private readonly IRuntimeState _runtimeState; - private readonly GlobalSettings _globalSettings; - private readonly IHostingEnvironment _hostingEnvironment; - private readonly SecuritySettings _securitySettings; - - public BackOfficeCookieAuthenticationProvider(IUserService userService, IRuntimeState runtimeState, GlobalSettings globalSettings, IHostingEnvironment hostingEnvironment, IOptions securitySettings) - { - _userService = userService; - _runtimeState = runtimeState; - _globalSettings = globalSettings; - _hostingEnvironment = hostingEnvironment; - _securitySettings = securitySettings.Value; - } - - - public override void ResponseSignOut(CookieResponseSignOutContext context) - { - - } - - - - } -} diff --git a/src/Umbraco.Web/Security/BackOfficeSignInManager.cs b/src/Umbraco.Web/Security/BackOfficeSignInManager.cs deleted file mode 100644 index e5ba931b0b..0000000000 --- a/src/Umbraco.Web/Security/BackOfficeSignInManager.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using System.Diagnostics; -using System.Security.Claims; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Identity; -using Microsoft.Extensions.Options; -using Microsoft.Owin; -using Microsoft.Owin.Logging; -using Microsoft.Owin.Security; -using Umbraco.Core; -using Umbraco.Core.BackOffice; -using Umbraco.Core.Configuration.Models; - -namespace Umbraco.Web.Security -{ - // TODO: This has been migrated to netcore - public class BackOfficeSignInManager : IDisposable - { - private readonly IBackOfficeUserManager _userManager; - private readonly IUserClaimsPrincipalFactory _claimsPrincipalFactory; - private readonly IAuthenticationManager _authenticationManager; - private readonly ILogger _logger; - private readonly GlobalSettings _globalSettings; - private readonly IOwinRequest _request; - - public BackOfficeSignInManager( - IBackOfficeUserManager userManager, - IUserClaimsPrincipalFactory claimsPrincipalFactory, - IAuthenticationManager authenticationManager, - ILogger logger, - GlobalSettings globalSettings, - IOwinRequest request) - { - _userManager = userManager ?? throw new ArgumentNullException(nameof(userManager)); - _claimsPrincipalFactory = claimsPrincipalFactory ?? throw new ArgumentNullException(nameof(claimsPrincipalFactory)); - _authenticationManager = authenticationManager ?? throw new ArgumentNullException(nameof(authenticationManager)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - _globalSettings = globalSettings ?? throw new ArgumentNullException(nameof(globalSettings)); - _request = request ?? throw new ArgumentNullException(nameof(request)); - } - - public void Dispose() - { - } - } -} diff --git a/src/Umbraco.Web/Security/FixWindowsAuthMiddlware.cs b/src/Umbraco.Web/Security/FixWindowsAuthMiddlware.cs deleted file mode 100644 index 9e26964091..0000000000 --- a/src/Umbraco.Web/Security/FixWindowsAuthMiddlware.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System.Linq; -using System.Security.Claims; -using System.Security.Principal; -using System.Threading.Tasks; -using Microsoft.Owin; -using Umbraco.Core; -using Umbraco.Core.BackOffice; - -namespace Umbraco.Web.Security -{ - - /// - /// This is used to inspect the request to see if 2 x identities are assigned: A windows one and a back office one. - /// When this is the case, it means that auth has executed for Windows & auth has executed for our back office cookie - /// handler and now two identities have been assigned. Unfortunately, at some stage in the pipeline - I'm pretty sure - /// it's the Role Provider Module - it again changes the user's Principal to a RolePrincipal and discards the second - /// Identity which is the Back office identity thus preventing a user from accessing the back office... it's very annoying. - /// - /// To fix this, we re-set the user Principal to only have a single identity: the back office one, since we know this is - /// for a back office request. - /// - internal class FixWindowsAuthMiddlware : OwinMiddleware - { - public FixWindowsAuthMiddlware(OwinMiddleware next) : base(next) - { - } - - public override async Task Invoke(IOwinContext context) - { - if (context.Request.Uri.IsClientSideRequest() == false) - { - var claimsPrincipal = context.Request.User as ClaimsPrincipal; - if (claimsPrincipal != null - && claimsPrincipal.Identities.Count() > 1 - && claimsPrincipal.Identities.Any(x => x is WindowsIdentity) - && claimsPrincipal.Identities.Any(x => x is UmbracoBackOfficeIdentity)) - { - var backOfficeIdentity = claimsPrincipal.Identities.First(x => x is UmbracoBackOfficeIdentity); - if (backOfficeIdentity.IsAuthenticated) - { - context.Request.User = new ClaimsPrincipal(backOfficeIdentity); - } - } - } - - if (Next != null) - { - await Next.Invoke(context); - } - } - } -} diff --git a/src/Umbraco.Web/Security/ForceRenewalCookieAuthenticationHandler.cs b/src/Umbraco.Web/Security/ForceRenewalCookieAuthenticationHandler.cs deleted file mode 100644 index deb8e9fd63..0000000000 --- a/src/Umbraco.Web/Security/ForceRenewalCookieAuthenticationHandler.cs +++ /dev/null @@ -1,129 +0,0 @@ -using System.Threading.Tasks; -using Microsoft.Owin.Security; -using Microsoft.Owin.Security.Cookies; -using Microsoft.Owin.Security.Infrastructure; -using Umbraco.Core; -using Umbraco.Core.Cache; -using Umbraco.Core.Security; - -namespace Umbraco.Web.Security -{ - /// - /// If a flag is set on the context to force renew the ticket, this will do it - /// - internal class ForceRenewalCookieAuthenticationHandler : AuthenticationHandler - { - private readonly IUmbracoContextAccessor _umbracoContextAccessor; - private readonly IRequestCache _requestCache; - - public ForceRenewalCookieAuthenticationHandler(IUmbracoContextAccessor umbracoContextAccessor, IRequestCache requestCache) - { - _umbracoContextAccessor = umbracoContextAccessor; - _requestCache = requestCache; - } - - /// - /// This handler doesn't actually do any auth so we return null; - /// - /// - protected override Task AuthenticateCoreAsync() - { - return Task.FromResult((AuthenticationTicket)null); - } - - /// - /// Gets the ticket from the request - /// - /// - private AuthenticationTicket GetTicket() - { - var cookie = Options.CookieManager.GetRequestCookie(Context, Options.CookieName); - if (string.IsNullOrWhiteSpace(cookie)) - { - return null; - } - var ticket = Options.TicketDataFormat.Unprotect(cookie); - if (ticket == null) - { - return null; - } - return ticket; - } - - /// - /// This will check if the token exists in the request to force renewal - /// - /// - protected override Task ApplyResponseGrantAsync() - { - if (_umbracoContextAccessor.UmbracoContext == null || Context.Request.Uri.IsClientSideRequest()) - { - return Task.FromResult(0); - } - - //Now we need to check if we should force renew this based on a flag in the context and whether this is a request that is not normally renewed by OWIN... - // which means that it is not a normal URL that is authenticated. - - //var normalAuthUrl = ((BackOfficeCookieManager) Options.CookieManager) - // .ShouldAuthenticateRequest(Context, _umbracoContextAccessor.UmbracoContext.OriginalRequestUrl, - // //Pass in false, we want to know if this is a normal auth'd page - // checkForceAuthTokens: false); - ////This is auth'd normally, so OWIN will naturally take care of the cookie renewal - //if (normalAuthUrl) return Task.FromResult(0); - - //check for the special flag in either the owin or http context - var shouldRenew = Context.Get(Constants.Security.ForceReAuthFlag) != null || (_requestCache.IsAvailable && _requestCache.Get(Constants.Security.ForceReAuthFlag) != null); - - if (shouldRenew) - { - var signin = Helper.LookupSignIn(Options.AuthenticationType); - var shouldSignin = signin != null; - var signout = Helper.LookupSignOut(Options.AuthenticationType, Options.AuthenticationMode); - var shouldSignout = signout != null; - - //we don't care about the sign in/sign out scenario, only renewal - if (shouldSignin == false && shouldSignout == false) - { - //get the ticket - var ticket = GetTicket(); - if (ticket != null) - { - var currentUtc = Options.SystemClock.UtcNow; - var issuedUtc = ticket.Properties.IssuedUtc; - var expiresUtc = ticket.Properties.ExpiresUtc; - - if (expiresUtc.HasValue && issuedUtc.HasValue) - { - var timeElapsed = currentUtc.Subtract(issuedUtc.Value); - var timeRemaining = expiresUtc.Value.Subtract(currentUtc); - - //if it's time to renew, then do it - if (timeRemaining < timeElapsed) - { - //renew the date/times - ticket.Properties.IssuedUtc = currentUtc; - var timeSpan = expiresUtc.Value.Subtract(issuedUtc.Value); - ticket.Properties.ExpiresUtc = currentUtc.Add(timeSpan); - - //now save back all the required cookie details - var cookieValue = Options.TicketDataFormat.Protect(ticket); - - var cookieOptions = ((UmbracoBackOfficeCookieAuthOptions)Options).CreateRequestCookieOptions(Context, ticket); - - Options.CookieManager.AppendResponseCookie( - Context, - Options.CookieName, - cookieValue, - cookieOptions); - } - - - } - } - } - } - - return Task.FromResult(0); - } - } -} diff --git a/src/Umbraco.Web/Security/ForceRenewalCookieAuthenticationMiddleware.cs b/src/Umbraco.Web/Security/ForceRenewalCookieAuthenticationMiddleware.cs deleted file mode 100644 index b8f8127544..0000000000 --- a/src/Umbraco.Web/Security/ForceRenewalCookieAuthenticationMiddleware.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Microsoft.Owin; -using Microsoft.Owin.Security.Cookies; -using Microsoft.Owin.Security.Infrastructure; -using Owin; -using Umbraco.Core.Cache; - -namespace Umbraco.Web.Security -{ - /// - /// This middleware is used simply to force renew the auth ticket if a flag to do so is found in the request - /// - internal class ForceRenewalCookieAuthenticationMiddleware : CookieAuthenticationMiddleware - { - private readonly IUmbracoContextAccessor _umbracoContextAccessor; - private readonly IRequestCache _requestCache; - - public ForceRenewalCookieAuthenticationMiddleware( - OwinMiddleware next, - IAppBuilder app, - UmbracoBackOfficeCookieAuthOptions options, - IUmbracoContextAccessor umbracoContextAccessor, - IRequestCache requestCache) : base(next, app, options) - { - _umbracoContextAccessor = umbracoContextAccessor; - _requestCache = requestCache; - } - - protected override AuthenticationHandler CreateHandler() - { - return new ForceRenewalCookieAuthenticationHandler(_umbracoContextAccessor, _requestCache); - } - } -} diff --git a/src/Umbraco.Web/Security/GetUserSecondsMiddleWare.cs b/src/Umbraco.Web/Security/GetUserSecondsMiddleWare.cs deleted file mode 100644 index 62724a4846..0000000000 --- a/src/Umbraco.Web/Security/GetUserSecondsMiddleWare.cs +++ /dev/null @@ -1,145 +0,0 @@ -using System; -using System.Diagnostics; -using System.Globalization; -using System.Threading.Tasks; -using System.Web; -using Microsoft.Extensions.Options; -using Microsoft.Owin; -using Microsoft.Owin.Logging; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.Models; -using Umbraco.Core.Hosting; - -namespace Umbraco.Web.Security -{ - /// - /// Custom middleware to return the remaining seconds the user has before they are logged out - /// - /// - /// This is quite a custom request because in most situations we just want to return the seconds and don't want - /// to renew the auth ticket, however if KeepUserLoggedIn is true, then we do want to renew the auth ticket for - /// this request! - /// - internal class GetUserSecondsMiddleWare : OwinMiddleware - { - private readonly UmbracoBackOfficeCookieAuthOptions _authOptions; - private readonly GlobalSettings _globalSettings; - private readonly SecuritySettings _security; - private readonly ILogger _logger; - private readonly IHostingEnvironment _hostingEnvironment; - - public GetUserSecondsMiddleWare( - OwinMiddleware next, - UmbracoBackOfficeCookieAuthOptions authOptions, - GlobalSettings globalSettings, - IOptions security, - ILogger logger, - IHostingEnvironment hostingEnvironment) - : base(next) - { - _authOptions = authOptions ?? throw new ArgumentNullException(nameof(authOptions)); - _globalSettings = globalSettings; - _security = security.Value; - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - _hostingEnvironment = hostingEnvironment; - } - - public override async Task Invoke(IOwinContext context) - { - var request = context.Request; - var response = context.Response; - - if (request.Uri.Scheme.InvariantStartsWith("http") - && request.Uri.AbsolutePath.InvariantEquals( - $"{_globalSettings.GetBackOfficePath(_hostingEnvironment)}/backoffice/UmbracoApi/Authentication/GetRemainingTimeoutSeconds")) - { - var cookie = _authOptions.CookieManager.GetRequestCookie(context, _security.AuthCookieName); - if (cookie.IsNullOrWhiteSpace() == false) - { - var ticket = _authOptions.TicketDataFormat.Unprotect(cookie); - if (ticket != null) - { - var remainingSeconds = ticket.Properties.ExpiresUtc.HasValue - ? (ticket.Properties.ExpiresUtc.Value - _authOptions.SystemClock.UtcNow).TotalSeconds - : 0; - - response.ContentType = "application/json; charset=utf-8"; - response.StatusCode = 200; - response.Headers.Add("Cache-Control", new[] { "no-cache" }); - response.Headers.Add("Pragma", new[] { "no-cache" }); - response.Headers.Add("Expires", new[] { "-1" }); - response.Headers.Add("Date", new[] { _authOptions.SystemClock.UtcNow.ToString("R") }); - - //Ok, so here we need to check if we want to process/renew the auth ticket for each - // of these requests. If that is the case, the user will really never be logged out until they - // close their browser (there will be edge cases of that, especially when debugging) - if (_security.KeepUserLoggedIn) - { - var currentUtc = _authOptions.SystemClock.UtcNow; - var issuedUtc = ticket.Properties.IssuedUtc; - var expiresUtc = ticket.Properties.ExpiresUtc; - - if (expiresUtc.HasValue && issuedUtc.HasValue) - { - var timeElapsed = currentUtc.Subtract(issuedUtc.Value); - var timeRemaining = expiresUtc.Value.Subtract(currentUtc); - - //if it's time to renew, then do it - if (timeRemaining < timeElapsed) - { - // TODO: This would probably be simpler just to do: context.OwinContext.Authentication.SignIn(context.Properties, identity); - // this will invoke the default Cookie middleware to basically perform this logic for us. - - ticket.Properties.IssuedUtc = currentUtc; - var timeSpan = expiresUtc.Value.Subtract(issuedUtc.Value); - ticket.Properties.ExpiresUtc = currentUtc.Add(timeSpan); - - var cookieValue = _authOptions.TicketDataFormat.Protect(ticket); - - var cookieOptions = _authOptions.CreateRequestCookieOptions(context, ticket); - - _authOptions.CookieManager.AppendResponseCookie( - context, - _authOptions.CookieName, - cookieValue, - cookieOptions); - - remainingSeconds = (ticket.Properties.ExpiresUtc.Value - currentUtc).TotalSeconds; - } - } - - // NOTE: SessionIdValidator has been moved to netcore - ////We also need to re-validate the user's session if we are relying on this ping to keep their session alive - //await SessionIdValidator.ValidateSessionAsync(TimeSpan.FromMinutes(1), context, _authOptions.CookieManager, _authOptions.SystemClock, issuedUtc, ticket.Identity, _globalSettings); - } - else if (remainingSeconds <= 30) - { - //NOTE: We are using 30 seconds because that is what is coded into angular to force logout to give some headway in - // the timeout process. - - _logger.WriteCore(TraceEventType.Information, 0, - string.Format("User logged will be logged out due to timeout: {0}, IP Address: {1}", ticket.Identity.Name, request.RemoteIpAddress), - null, null); - } - - await response.WriteAsync(remainingSeconds.ToString(CultureInfo.InvariantCulture)); - return; - } - } - - // HACK: we need to suppress the stupid forms authentication module but we can only do that by using non owin stuff - if (HttpContext.Current != null && HttpContext.Current.Response != null) - { - HttpContext.Current.Response.SuppressFormsAuthenticationRedirect = true; - } - - response.StatusCode = 401; - } - else if (Next != null) - { - await Next.Invoke(context); - } - } - } -} diff --git a/src/Umbraco.Web/Security/IBackOfficeUserPasswordChecker.cs b/src/Umbraco.Web/Security/IBackOfficeUserPasswordChecker.cs index 2fae308eb0..7bd67e608a 100644 --- a/src/Umbraco.Web/Security/IBackOfficeUserPasswordChecker.cs +++ b/src/Umbraco.Web/Security/IBackOfficeUserPasswordChecker.cs @@ -1,5 +1,5 @@ using System.Threading.Tasks; -using Umbraco.Core.BackOffice; +using Umbraco.Core.Security; namespace Umbraco.Web.Security { diff --git a/src/Umbraco.Web/Security/IdentityAuditEventArgs.cs b/src/Umbraco.Web/Security/IdentityAuditEventArgs.cs deleted file mode 100644 index b3b193a78f..0000000000 --- a/src/Umbraco.Web/Security/IdentityAuditEventArgs.cs +++ /dev/null @@ -1,134 +0,0 @@ -using System; -using System.Threading; -using Umbraco.Extensions; - - -namespace Umbraco.Web.Security -{ - - /// - /// This class is used by events raised from the BackofficeUserManager - /// - public class IdentityAuditEventArgs : EventArgs - { - /// - /// The action that got triggered from the audit event - /// - public AuditEvent Action { get; private set; } - - /// - /// Current date/time in UTC format - /// - public DateTime DateTimeUtc { get; private set; } - - /// - /// The source IP address of the user performing the action - /// - public string IpAddress { get; private set; } - - /// - /// The user affected by the event raised - /// - public int AffectedUser { get; private set; } - - /// - /// If a user is performing an action on a different user, then this will be set. Otherwise it will be -1 - /// - public int PerformingUser { get; private set; } - - /// - /// An optional comment about the action being logged - /// - public string Comment { get; private set; } - - /// - /// This property is always empty except in the LoginFailed event for an unknown user trying to login - /// - public string Username { get; private set; } - - - /// - /// Default constructor - /// - /// - /// - /// - /// - /// - public IdentityAuditEventArgs(AuditEvent action, string ipAddress, string comment = null, int performingUser = -1, int affectedUser = -1) - { - DateTimeUtc = DateTime.UtcNow; - Action = action; - - IpAddress = ipAddress; - Comment = comment; - AffectedUser = affectedUser; - - PerformingUser = performingUser == -1 - ? GetCurrentRequestBackOfficeUserId() - : performingUser; - } - - /// - /// Creates an instance without a performing or affected user (the id will be set to -1) - /// - /// - /// - /// - /// - public IdentityAuditEventArgs(AuditEvent action, string ipAddress, string username, string comment) - { - DateTimeUtc = DateTime.UtcNow; - Action = action; - - IpAddress = ipAddress; - Username = username; - Comment = comment; - - PerformingUser = -1; - } - - public IdentityAuditEventArgs(AuditEvent action, string ipAddress, string username, string comment, int performingUser) - { - DateTimeUtc = DateTime.UtcNow; - Action = action; - - IpAddress = ipAddress; - Username = username; - Comment = comment; - - PerformingUser = performingUser == -1 - ? GetCurrentRequestBackOfficeUserId() - : performingUser; - } - - /// - /// Returns the current logged in backoffice user's Id logging if there is one - /// - /// - protected int GetCurrentRequestBackOfficeUserId() - { - var userId = -1; - var backOfficeIdentity = Thread.CurrentPrincipal.GetUmbracoIdentity(); - if (backOfficeIdentity != null) - int.TryParse(backOfficeIdentity.Id.ToString(), out userId); - return userId; - } - } - - public enum AuditEvent - { - AccountLocked, - AccountUnlocked, - ForgotPasswordRequested, - ForgotPasswordChangedSuccess, - LoginFailed, - LoginRequiresVerification, - LoginSucces, - LogoutSuccess, - PasswordChanged, - PasswordReset, - ResetAccessFailedCount, - SendingUserInvite - } -} diff --git a/src/Umbraco.Web/Security/IdentityFactoryMiddleware.cs b/src/Umbraco.Web/Security/IdentityFactoryMiddleware.cs deleted file mode 100644 index 2975149107..0000000000 --- a/src/Umbraco.Web/Security/IdentityFactoryMiddleware.cs +++ /dev/null @@ -1,106 +0,0 @@ -using System; -using System.Threading.Tasks; -using Microsoft.Owin; -using Microsoft.Owin.Security.DataProtection; - -namespace Umbraco.Web.Security -{ - /// - /// Adapted from Microsoft.AspNet.Identity.Owin.IdentityFactoryMiddleware - /// - public class IdentityFactoryMiddleware : OwinMiddleware - where TResult : class, IDisposable - where TOptions : IdentityFactoryOptions - { - /// The next middleware in the OWIN pipeline to invoke - /// Configuration options for the middleware - public IdentityFactoryMiddleware(OwinMiddleware next, TOptions options) - : base(next) - { - if (options == null) throw new ArgumentNullException(nameof(options)); - if (options.Provider == null) throw new ArgumentException("options.Provider"); - - Options = options; - } - - /// - /// Configuration options - /// - public TOptions Options { get; private set; } - - /// - /// Create an object using the Options.Provider, storing it in the OwinContext and then disposes the object when finished - /// - /// - /// - public override async Task Invoke(IOwinContext context) - { - var instance = Options.Provider.Create(Options, context); - try - { - context.Set(instance); - if (Next != null) - { - await Next.Invoke(context); - } - } - finally - { - Options.Provider.Dispose(Options, instance); - } - } - } - - public class IdentityFactoryOptions where T : class, IDisposable - { - /// - /// Used to configure the data protection provider - /// - public IDataProtectionProvider DataProtectionProvider { get; set; } - - /// - /// Provider used to Create and Dispose objects - /// - public IdentityFactoryProvider Provider { get; set; } - } - - public class IdentityFactoryProvider where T : class, IDisposable - { - public IdentityFactoryProvider() - { - OnDispose = (options, instance) => { }; - OnCreate = (options, context) => null; - } - - /// - /// A delegate assigned to this property will be invoked when the related method is called - /// - public Func, IOwinContext, T> OnCreate { get; set; } - - /// - /// A delegate assigned to this property will be invoked when the related method is called - /// - public Action, T> OnDispose { get; set; } - - /// - /// Calls the OnCreate Delegate - /// - /// - /// - /// - public virtual T Create(IdentityFactoryOptions options, IOwinContext context) - { - return OnCreate(options, context); - } - - /// - /// Calls the OnDispose delegate - /// - /// - /// - public virtual void Dispose(IdentityFactoryOptions options, T instance) - { - OnDispose(options, instance); - } - } -} diff --git a/src/Umbraco.Web/Security/OwinDataProtectorTokenProvider.cs b/src/Umbraco.Web/Security/OwinDataProtectorTokenProvider.cs deleted file mode 100644 index 72e12b8621..0000000000 --- a/src/Umbraco.Web/Security/OwinDataProtectorTokenProvider.cs +++ /dev/null @@ -1,112 +0,0 @@ -using System; -using System.Globalization; -using System.IO; -using System.Text; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Identity; -using Microsoft.Owin.Security.DataProtection; -using Umbraco.Core.BackOffice; - -namespace Umbraco.Web.Security -{ - /// - /// Adapted from Microsoft.AspNet.Identity.Owin.DataProtectorTokenProvider - /// - public class OwinDataProtectorTokenProvider : IUserTwoFactorTokenProvider where TUser : BackOfficeIdentityUser - { - public TimeSpan TokenLifespan { get; set; } - private static readonly Encoding _defaultEncoding = new UTF8Encoding(false, true); - private readonly IDataProtector _protector; - - public OwinDataProtectorTokenProvider(IDataProtector protector) - { - _protector = protector ?? throw new ArgumentNullException(nameof(protector)); - TokenLifespan = TimeSpan.FromDays(1); - } - - public async Task GenerateAsync(string purpose, UserManager manager, TUser user) - { - if (manager == null) throw new ArgumentNullException(nameof(manager)); - if (user == null) throw new ArgumentNullException(nameof(user)); - - var ms = new MemoryStream(); - using (var writer = new BinaryWriter(ms, _defaultEncoding, true)) - { - writer.Write(DateTimeOffset.UtcNow.UtcTicks); - writer.Write(Convert.ToString(user.Id, CultureInfo.InvariantCulture)); - writer.Write(purpose ?? ""); - - string stamp = null; - if (manager.SupportsUserSecurityStamp) - { - stamp = await manager.GetSecurityStampAsync(user); - } - writer.Write(stamp ?? ""); - } - - var protectedBytes = _protector.Protect(ms.ToArray()); - return Convert.ToBase64String(protectedBytes); - } - - public async Task ValidateAsync(string purpose, string token, UserManager manager, TUser user) - { - if (string.IsNullOrWhiteSpace(token)) throw new ArgumentNullException(nameof(token)); - if (manager == null) throw new ArgumentNullException(nameof(manager)); - if (user == null) throw new ArgumentNullException(nameof(user)); - - try - { - var unprotectedData = _protector.Unprotect(Convert.FromBase64String(token)); - var ms = new MemoryStream(unprotectedData); - using (var reader = new BinaryReader(ms, _defaultEncoding, true)) - { - var creationTime = new DateTimeOffset(reader.ReadInt64(), TimeSpan.Zero); - var expirationTime = creationTime + TokenLifespan; - if (expirationTime < DateTimeOffset.UtcNow) - { - return false; - } - - var userId = reader.ReadString(); - if (!string.Equals(userId, Convert.ToString(user.Id, CultureInfo.InvariantCulture))) - { - return false; - } - - var purp = reader.ReadString(); - if (!string.Equals(purp, purpose)) - { - return false; - } - - var stamp = reader.ReadString(); - if (reader.PeekChar() != -1) - { - return false; - } - - if (manager.SupportsUserSecurityStamp) - { - var expectedStamp = await manager.GetSecurityStampAsync(user); - return stamp == expectedStamp; - } - - return stamp == ""; - } - } - // ReSharper disable once EmptyGeneralCatchClause - catch - { - // Do not leak exception - } - - return false; - } - - public Task CanGenerateTwoFactorTokenAsync(UserManager manager, TUser user) - { - // This token provider is designed for flows such as password reset and account confirmation - return Task.FromResult(false); - } - } -} diff --git a/src/Umbraco.Web/Security/UmbracoAuthTicketDataProtector.cs b/src/Umbraco.Web/Security/UmbracoAuthTicketDataProtector.cs deleted file mode 100644 index 3824935559..0000000000 --- a/src/Umbraco.Web/Security/UmbracoAuthTicketDataProtector.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using Microsoft.Owin.Security; -using Umbraco.Core; - -namespace Umbraco.Web.Security -{ - /// - /// This is used so that we can retrieve the auth ticket protector from an IOwinContext - /// - internal class UmbracoAuthTicketDataProtector : DisposableObjectSlim - { - public UmbracoAuthTicketDataProtector(ISecureDataFormat protector) - { - Protector = protector ?? throw new ArgumentNullException(nameof(protector)); - } - - public ISecureDataFormat Protector { get; } - } -} diff --git a/src/Umbraco.Web/Security/UmbracoBackOfficeCookieAuthOptions.cs b/src/Umbraco.Web/Security/UmbracoBackOfficeCookieAuthOptions.cs deleted file mode 100644 index 34669bc5ae..0000000000 --- a/src/Umbraco.Web/Security/UmbracoBackOfficeCookieAuthOptions.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using Microsoft.Owin; -using Microsoft.Owin.Security; -using Microsoft.Owin.Security.Cookies; -using Umbraco.Core; -using Umbraco.Core.Cache; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.Hosting; -using Umbraco.Core.IO; - -namespace Umbraco.Web.Security -{ - /// - /// Umbraco auth cookie options - /// - public sealed class UmbracoBackOfficeCookieAuthOptions : CookieAuthenticationOptions - { - public int LoginTimeoutMinutes { get; } - - /// - /// Creates the cookie options for saving the auth cookie - /// - /// - /// - /// - public CookieOptions CreateRequestCookieOptions(IOwinContext ctx, AuthenticationTicket ticket) - { - if (ctx == null) throw new ArgumentNullException(nameof(ctx)); - if (ticket == null) throw new ArgumentNullException(nameof(ticket)); - - var issuedUtc = ticket.Properties.IssuedUtc ?? SystemClock.UtcNow; - var expiresUtc = ticket.Properties.ExpiresUtc ?? issuedUtc.Add(ExpireTimeSpan); - - var cookieOptions = new CookieOptions - { - Path = "/", - Domain = this.CookieDomain ?? null, - HttpOnly = true, - Secure = this.CookieSecure == CookieSecureOption.Always - || (this.CookieSecure == CookieSecureOption.SameAsRequest && ctx.Request.IsSecure), - }; - - if (ticket.Properties.IsPersistent) - { - cookieOptions.Expires = expiresUtc.UtcDateTime; - } - - return cookieOptions; - } - - } -} diff --git a/src/Umbraco.Web/Security/UmbracoSecureDataFormat.cs b/src/Umbraco.Web/Security/UmbracoSecureDataFormat.cs deleted file mode 100644 index 73c1c3fd55..0000000000 --- a/src/Umbraco.Web/Security/UmbracoSecureDataFormat.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System; -using Microsoft.Owin.Security; -using Umbraco.Core.BackOffice; - -namespace Umbraco.Web.Security -{ - - /// - /// Custom secure format that ensures the Identity in the ticket is and not just a ClaimsIdentity - /// - internal class UmbracoSecureDataFormat : ISecureDataFormat - { - private readonly int _loginTimeoutMinutes; - private readonly ISecureDataFormat _ticketDataFormat; - - public UmbracoSecureDataFormat(int loginTimeoutMinutes, ISecureDataFormat ticketDataFormat) - { - _loginTimeoutMinutes = loginTimeoutMinutes; - _ticketDataFormat = ticketDataFormat ?? throw new ArgumentNullException(nameof(ticketDataFormat)); - } - - public string Protect(AuthenticationTicket data) - { - var backofficeIdentity = (UmbracoBackOfficeIdentity)data.Identity; - - //create a new ticket based on the passed in tickets details, however, we'll adjust the expires utc based on the specified timeout mins - var ticket = new AuthenticationTicket(backofficeIdentity, - new AuthenticationProperties(data.Properties.Dictionary) - { - IssuedUtc = data.Properties.IssuedUtc, - ExpiresUtc = data.Properties.ExpiresUtc ?? DateTimeOffset.UtcNow.AddMinutes(_loginTimeoutMinutes), - AllowRefresh = data.Properties.AllowRefresh, - IsPersistent = data.Properties.IsPersistent, - RedirectUri = data.Properties.RedirectUri - }); - - return _ticketDataFormat.Protect(ticket); - } - - /// - /// Un-protects the cookie - /// - /// - /// - public AuthenticationTicket Unprotect(string protectedText) - { - AuthenticationTicket decrypt; - try - { - decrypt = _ticketDataFormat.Unprotect(protectedText); - if (decrypt == null) return null; - } - catch (Exception) - { - return null; - } - - if (!UmbracoBackOfficeIdentity.FromClaimsIdentity(decrypt.Identity, out var identity)) - return null; - - //return the ticket with a UmbracoBackOfficeIdentity - var ticket = new AuthenticationTicket(identity, decrypt.Properties); - - return ticket; - } - } -} diff --git a/src/Umbraco.Web/Security/WebAuthExtensions.cs b/src/Umbraco.Web/Security/WebAuthExtensions.cs deleted file mode 100644 index f008dc8ba7..0000000000 --- a/src/Umbraco.Web/Security/WebAuthExtensions.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System.Net.Http; -using System.Security.Principal; -using System.ServiceModel.Channels; -using System.Threading; -using System.Web; -using Umbraco.Web.WebApi; - -namespace Umbraco.Web.Security -{ - internal static class WebAuthExtensions - { - /// - /// This will set a an authenticated IPrincipal to the current request for webforms & webapi - /// - /// - /// - /// - internal static IPrincipal SetPrincipalForRequest(this HttpRequestMessage request, IPrincipal principal) - { - //It is actually not good enough to set this on the current app Context and the thread, it also needs - // to be set explicitly on the HttpContext.Current !! This is a strange web api thing that is actually - // an underlying fault of asp.net not propagating the User correctly. - if (HttpContext.Current != null) - { - HttpContext.Current.User = principal; - } - var http = request.TryGetHttpContext(); - if (http) - { - http.Result.User = principal; - } - Thread.CurrentPrincipal = principal; - - //For WebAPI - request.SetUserPrincipal(principal); - - return principal; - } - - /// - /// This will set a an authenticated IPrincipal to the current request given the IUser object - /// - /// - /// - /// - internal static IPrincipal SetPrincipalForRequest(this HttpContextBase httpContext, IPrincipal principal) - { - //It is actually not good enough to set this on the current app Context and the thread, it also needs - // to be set explicitly on the HttpContext.Current !! This is a strange web api thing that is actually - // an underlying fault of asp.net not propagating the User correctly. - if (HttpContext.Current != null) - { - HttpContext.Current.User = principal; - } - httpContext.User = principal; - Thread.CurrentPrincipal = principal; - return principal; - } - } -} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index b1ddf26b05..ad7dd86730 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -126,7 +126,6 @@ Properties\SolutionInfo.cs - @@ -141,14 +140,10 @@ - - - - @@ -159,12 +154,8 @@ - - - - @@ -176,13 +167,8 @@ - - - - - @@ -192,9 +178,6 @@ - - - @@ -203,29 +186,15 @@ - - - - - - - - - - - - - - @@ -235,7 +204,6 @@ - @@ -306,4 +274,4 @@ - + \ No newline at end of file diff --git a/src/Umbraco.Web/UmbracoApplicationBase.cs b/src/Umbraco.Web/UmbracoApplicationBase.cs index c7066c664f..f10f4491d9 100644 --- a/src/Umbraco.Web/UmbracoApplicationBase.cs +++ b/src/Umbraco.Web/UmbracoApplicationBase.cs @@ -1,16 +1,15 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Reflection; using System.Threading; using System.Web; using System.Web.Hosting; -using Microsoft.Extensions.Logging; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; -using Serilog; using Serilog.Context; using Umbraco.Core; using Umbraco.Core.Cache; @@ -22,10 +21,8 @@ using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Logging.Serilog; using Umbraco.Core.Logging.Serilog.Enrichers; -using Umbraco.Core.Runtime; using Umbraco.Net; using Umbraco.Web.Hosting; -using Umbraco.Web.Logging; using ConnectionStrings = Umbraco.Core.Configuration.Models.ConnectionStrings; using Current = Umbraco.Web.Composing.Current; using GlobalSettings = Umbraco.Core.Configuration.Models.GlobalSettings; diff --git a/src/Umbraco.Web/UmbracoDefaultOwinStartup.cs b/src/Umbraco.Web/UmbracoDefaultOwinStartup.cs deleted file mode 100644 index 58fb36e13b..0000000000 --- a/src/Umbraco.Web/UmbracoDefaultOwinStartup.cs +++ /dev/null @@ -1,104 +0,0 @@ -using System; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; -using Microsoft.Owin; -using Owin; -using Umbraco.Core; -using Umbraco.Core.Cache; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.Models; -using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.Hosting; -using Umbraco.Core.Mapping; -using Umbraco.Net; -using Umbraco.Core.Services; -using Umbraco.Web; -using Umbraco.Web.Composing; -using Umbraco.Web.Security; - - -[assembly: OwinStartup("UmbracoDefaultOwinStartup", typeof(UmbracoDefaultOwinStartup))] - -namespace Umbraco.Web -{ - /// - /// The default way to configure OWIN for Umbraco - /// - /// - /// The startup type is specified in appSettings under owin:appStartup - /// - public class UmbracoDefaultOwinStartup - { - protected IUmbracoContextAccessor UmbracoContextAccessor => Current.UmbracoContextAccessor; - protected GlobalSettings GlobalSettings => Current.Factory.GetRequiredService(); - protected SecuritySettings SecuritySettings => Current.Factory.GetRequiredService>().Value; - protected IUserPasswordConfiguration UserPasswordConfig => Current.Factory.GetRequiredService(); - protected IRuntimeState RuntimeState => Current.RuntimeState; - protected ServiceContext Services => Current.Services; - protected UmbracoMapper Mapper => Current.Mapper; - protected IIpResolver IpResolver => Current.IpResolver; - protected IHostingEnvironment HostingEnvironment => Current.HostingEnvironment; - protected IRequestCache RequestCache => Current.AppCaches.RequestCache; - - /// - /// Main startup method - /// - /// - public virtual void Configuration(IAppBuilder app) - { - app.SanitizeThreadCulture(); - - // there's nothing we can do really - if (RuntimeState.Level == RuntimeLevel.BootFailed) - return; - - ConfigureServices(app, Services); - ConfigureMiddleware(app); - } - - /// - /// Configures services to be created in the OWIN context (CreatePerOwinContext) - /// - /// - /// - protected virtual void ConfigureServices(IAppBuilder app, ServiceContext services) - { - app.SetUmbracoLoggerFactory(); - } - - /// - /// Configures middleware to be used (i.e. app.Use...) - /// - /// - protected virtual void ConfigureMiddleware(IAppBuilder app) - { - - // Configure OWIN for authentication. - ConfigureUmbracoAuthentication(app); - - app - .UseSignalR(GlobalSettings, HostingEnvironment) - .FinalizeMiddlewareConfiguration(); - } - - /// - /// Configure external/OAuth login providers - /// - /// - protected virtual void ConfigureUmbracoAuthentication(IAppBuilder app) - { - // Ensure owin is configured for Umbraco back office authentication. - // Front-end OWIN cookie configuration must be declared after this code. - app - .UseUmbracoBackOfficeExternalCookieAuthentication(UmbracoContextAccessor, RuntimeState, GlobalSettings, HostingEnvironment, RequestCache, PipelineStage.Authenticate); - // TODO: this would be considered a breaking change but this must come after all authentication so should be moved within ConfigureMiddleware - } - - public static event EventHandler MiddlewareConfigured; - - internal static void OnMiddlewareConfigured(OwinMiddlewareConfiguredEventArgs args) - { - MiddlewareConfigured?.Invoke(null, args); - } - } -} diff --git a/src/Umbraco.Web/WebApi/Filters/AngularAntiForgeryHelper.cs b/src/Umbraco.Web/WebApi/Filters/AngularAntiForgeryHelper.cs deleted file mode 100644 index 4b16e650da..0000000000 --- a/src/Umbraco.Web/WebApi/Filters/AngularAntiForgeryHelper.cs +++ /dev/null @@ -1,105 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http.Headers; -using System.Web.Helpers; -using Microsoft.Extensions.Logging; -using Umbraco.Core; -using Umbraco.Web.Composing; - -namespace Umbraco.Web.WebApi.Filters -{ - /// - /// A helper class to deal with csrf prevention with angularjs and webapi - /// - public static class AngularAntiForgeryHelper - { - /// - /// Returns 2 tokens - one for the cookie value and one that angular should set as the header value - /// - /// - /// - /// - /// .Net provides us a way to validate one token with another for added security. With the way angular works, this - /// means that we need to set 2 cookies since angular uses one cookie value to create the header value, then we want to validate - /// this header value against our original cookie value. - /// - public static void GetTokens(out string cookieToken, out string headerToken) - { - AntiForgery.GetTokens(null, out cookieToken, out headerToken); - } - - /// - /// Validates the header token against the validation cookie value - /// - /// - /// - /// - public static bool ValidateTokens(string cookieToken, string headerToken) - { - // ensure that the cookie matches the header and then ensure it matches the correct value! - try - { - AntiForgery.Validate(cookieToken, headerToken); - } - catch (Exception ex) - { - Current.Logger.LogError(ex, "Could not validate XSRF token"); - return false; - } - return true; - } - - internal static bool ValidateHeaders( - KeyValuePair>[] requestHeaders, - string cookieToken, - out string failedReason) - { - failedReason = ""; - - if (requestHeaders.Any(z => z.Key.InvariantEquals(Constants.Web.AngularHeadername)) == false) - { - failedReason = "Missing token"; - return false; - } - - var headerToken = requestHeaders - .Where(z => z.Key.InvariantEquals(Constants.Web.AngularHeadername)) - .Select(z => z.Value) - .SelectMany(z => z) - .FirstOrDefault(); - - // both header and cookie must be there - if (cookieToken == null || headerToken == null) - { - failedReason = "Missing token null"; - return false; - } - - if (ValidateTokens(cookieToken, headerToken) == false) - { - failedReason = "Invalid token"; - return false; - } - - return true; - } - - /// - /// Validates the headers/cookies passed in for the request - /// - /// - /// - /// - public static bool ValidateHeaders(HttpRequestHeaders requestHeaders, out string failedReason) - { - var cookieToken = requestHeaders.GetCookieValue(Constants.Web.CsrfValidationCookieName); - - return ValidateHeaders( - requestHeaders.ToDictionary(x => x.Key, x => x.Value).ToArray(), - cookieToken == null ? null : cookieToken, - out failedReason); - } - - } -} diff --git a/src/Umbraco.Web/WebApi/Filters/ValidateAngularAntiForgeryTokenAttribute.cs b/src/Umbraco.Web/WebApi/Filters/ValidateAngularAntiForgeryTokenAttribute.cs deleted file mode 100644 index f147a2a4cb..0000000000 --- a/src/Umbraco.Web/WebApi/Filters/ValidateAngularAntiForgeryTokenAttribute.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System.Net; -using System.Net.Http; -using System.Security.Claims; -using System.Web.Http; -using System.Web.Http.Filters; - -namespace Umbraco.Web.WebApi.Filters -{ - /// - /// A filter to check for the csrf token based on Angular's standard approach - /// - /// - /// Code derived from http://ericpanorel.net/2013/07/28/spa-authentication-and-csrf-mvc4-antiforgery-implementation/ - /// - /// If the authentication type is cookie based, then this filter will execute, otherwise it will be disabled - /// - public sealed class ValidateAngularAntiForgeryTokenAttribute : ActionFilterAttribute - { - public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext) - { - var userIdentity = ((ApiController) actionContext.ControllerContext.Controller).User.Identity as ClaimsIdentity; - if (userIdentity != null) - { - //if there is not CookiePath claim, then exit - if (userIdentity.HasClaim(x => x.Type == ClaimTypes.CookiePath) == false) - { - base.OnActionExecuting(actionContext); - return; - } - } - - string failedReason; - if (AngularAntiForgeryHelper.ValidateHeaders(actionContext.Request.Headers, out failedReason) == false) - { - actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.ExpectationFailed); - actionContext.Response.ReasonPhrase = failedReason; - return; - } - - base.OnActionExecuting(actionContext); - } - } -} diff --git a/src/Umbraco.Web/WebApi/HttpRequestMessageExtensions.cs b/src/Umbraco.Web/WebApi/HttpRequestMessageExtensions.cs index 7bda897a5e..8fa387ec27 100644 --- a/src/Umbraco.Web/WebApi/HttpRequestMessageExtensions.cs +++ b/src/Umbraco.Web/WebApi/HttpRequestMessageExtensions.cs @@ -1,13 +1,9 @@ -using System; -using System.Globalization; -using System.Linq; +using System; using System.Net; using System.Net.Http; using System.Web; -using System.Web.Http.ModelBinding; using Microsoft.Owin; using Umbraco.Core; -using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Web.WebApi { @@ -63,108 +59,7 @@ namespace Umbraco.Web.WebApi return Attempt.Fail(); } - /// - /// Create a 403 (Forbidden) response indicating that the current user doesn't have access to the resource - /// requested or the action it needs to take. - /// - /// - /// - /// - /// This is different from a 401 which indicates that the user is not logged in. - /// - public static HttpResponseMessage CreateUserNoAccessResponse(this HttpRequestMessage request) - { - return request.CreateResponse(HttpStatusCode.Forbidden); - } - /// - /// Create a 400 response message indicating that a validation error occurred - /// - /// - /// - /// - /// - public static HttpResponseMessage CreateValidationErrorResponse(this HttpRequestMessage request, T value) - { - var msg = request.CreateResponse(HttpStatusCode.BadRequest, value); - msg.Headers.Add("X-Status-Reason", "Validation failed"); - return msg; - } - - /// - /// Create a 400 response message indicating that a validation error occurred - /// - /// - /// - public static HttpResponseMessage CreateValidationErrorResponse(this HttpRequestMessage request) - { - var msg = request.CreateResponse(HttpStatusCode.BadRequest); - msg.Headers.Add("X-Status-Reason", "Validation failed"); - return msg; - } - - /// - /// Create a 400 response message indicating that a validation error occurred - /// - /// - /// - /// - public static HttpResponseMessage CreateValidationErrorResponse(this HttpRequestMessage request, string errorMessage) - { - var msg = request.CreateErrorResponse(HttpStatusCode.BadRequest, errorMessage); - msg.Headers.Add("X-Status-Reason", "Validation failed"); - return msg; - } - - /// - /// Creates an error response with notifications in the result to be displayed in the UI - /// - /// - /// - /// - public static HttpResponseMessage CreateNotificationValidationErrorResponse(this HttpRequestMessage request, string errorMessage) - { - var notificationModel = new SimpleNotificationModel - { - Message = errorMessage - }; - notificationModel.AddErrorNotification(errorMessage, string.Empty); - return request.CreateValidationErrorResponse(notificationModel); - } - - /// - /// Creates a successful response with notifications in the result to be displayed in the UI - /// - /// - /// - /// - public static HttpResponseMessage CreateNotificationSuccessResponse(this HttpRequestMessage request, string successMessage) - { - var notificationModel = new SimpleNotificationModel - { - Message = successMessage - }; - notificationModel.AddSuccessNotification(successMessage, string.Empty); - return request.CreateResponse(HttpStatusCode.OK, notificationModel); - } - - /// - /// Create a 400 response message indicating that a validation error occurred - /// - /// - /// - /// - public static HttpResponseMessage CreateValidationErrorResponse(this HttpRequestMessage request, ModelStateDictionary modelState) - { - var msg = request.CreateErrorResponse(HttpStatusCode.BadRequest, modelState); - msg.Headers.Add("X-Status-Reason", "Validation failed"); - return msg; - } - - internal static string ClientCulture(this HttpRequestMessage request) - { - return request.Headers.TryGetValues("X-UMB-CULTURE", out var values) ? values.FirstOrDefault() : null; - } } } diff --git a/src/Umbraco.Web/WebApi/UmbracoApiControllerBase.cs b/src/Umbraco.Web/WebApi/UmbracoApiControllerBase.cs index e5e123db88..035cee052e 100644 --- a/src/Umbraco.Web/WebApi/UmbracoApiControllerBase.cs +++ b/src/Umbraco.Web/WebApi/UmbracoApiControllerBase.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Web; using System.Web.Http; using Microsoft.Extensions.DependencyInjection; @@ -124,16 +124,6 @@ namespace Umbraco.Web.WebApi /// public IBackOfficeSecurity Security => BackOfficeSecurityAccessor.BackOfficeSecurity; - /// - /// Tries to get the current HttpContext. - /// - protected Attempt TryGetHttpContext() - => Request.TryGetHttpContext(); - /// - /// Tries to get the current OWIN context. - /// - protected Attempt TryGetOwinContext() - => Request.TryGetOwinContext(); } } diff --git a/src/Umbraco.Web/WebAssets/CDF/ClientDependencyComponent.cs b/src/Umbraco.Web/WebAssets/CDF/ClientDependencyComponent.cs deleted file mode 100644 index 47dd0908dd..0000000000 --- a/src/Umbraco.Web/WebAssets/CDF/ClientDependencyComponent.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System; -using System.Collections.Specialized; -using System.IO; -using ClientDependency.Core.CompositeFiles.Providers; -using ClientDependency.Core.Config; -using Microsoft.Extensions.Options; -using Umbraco.Core; -using Umbraco.Core.Composing; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.Models; -using Umbraco.Core.Hosting; -using Umbraco.Web.Runtime; - -namespace Umbraco.Web.WebAssets.CDF -{ - [ComposeAfter(typeof(WebInitialComponent))] - public sealed class ClientDependencyComponent : IComponent - { - private readonly HostingSettings _hostingSettings; - private readonly IHostingEnvironment _hostingEnvironment; - private readonly RuntimeSettings _settings; - - public ClientDependencyComponent( - IOptions hostingSettings, - IHostingEnvironment hostingEnvironment, - IOptions settings) - { - _hostingSettings = hostingSettings.Value; - _hostingEnvironment = hostingEnvironment; - _settings = settings.Value; - } - - public void Initialize() - { - if (_hostingEnvironment.IsHosted) - { - ConfigureClientDependency(); - } - } - - private void ConfigureClientDependency() - { - // Backwards compatibility - set the path and URL type for ClientDependency 1.5.1 [LK] - XmlFileMapper.FileMapDefaultFolder = Core.Constants.SystemDirectories.TempData.EnsureEndsWith('/') + "ClientDependency"; - BaseCompositeFileProcessingProvider.UrlTypeDefault = CompositeUrlType.Base64QueryStrings; - - // Now we need to detect if we are running 'Umbraco.Core.LocalTempStorage' as EnvironmentTemp and in that case we want to change the CDF file - // location to be there - if (_hostingSettings.LocalTempStorageLocation == LocalTempStorage.EnvironmentTemp) - { - var cachePath = _hostingEnvironment.LocalTempPath; - - //set the file map and composite file default location to the %temp% location - BaseCompositeFileProcessingProvider.CompositeFilePathDefaultFolder - = XmlFileMapper.FileMapDefaultFolder - = Path.Combine(cachePath, "ClientDependency"); - } - - if (_settings.MaxQueryStringLength.HasValue || _settings.MaxRequestLength.HasValue) - { - //set the max url length for CDF to be the smallest of the max query length, max request length - ClientDependency.Core.CompositeFiles.CompositeDependencyHandler.MaxHandlerUrlLength = Math.Min(_settings.MaxQueryStringLength.GetValueOrDefault(), _settings.MaxRequestLength.GetValueOrDefault()); - } - - //Register a custom renderer - used to process property editor dependencies - var renderer = new DependencyPathRenderer(); - renderer.Initialize("Umbraco.DependencyPathRenderer", new NameValueCollection - { - { "compositeFileHandlerPath", ClientDependencySettings.Instance.CompositeFileHandlerPath } - }); - - ClientDependencySettings.Instance.MvcRendererCollection.Add(renderer); - } - public void Terminate() - { } - } -} diff --git a/src/Umbraco.Web/WebAssets/CDF/ClientDependencyComposer.cs b/src/Umbraco.Web/WebAssets/CDF/ClientDependencyComposer.cs deleted file mode 100644 index 75e0d6123f..0000000000 --- a/src/Umbraco.Web/WebAssets/CDF/ClientDependencyComposer.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Umbraco.Core; -using Umbraco.Core.DependencyInjection; -using Umbraco.Core.Composing; -using Umbraco.Core.WebAssets; - -namespace Umbraco.Web.WebAssets.CDF -{ - public sealed class ClientDependencyComposer : ComponentComposer - { - public override void Compose(IUmbracoBuilder builder) - { - base.Compose(builder); - builder.Services.AddUnique(); - } - } -} diff --git a/src/Umbraco.Web/WebAssets/CDF/ClientDependencyConfiguration.cs b/src/Umbraco.Web/WebAssets/CDF/ClientDependencyConfiguration.cs deleted file mode 100644 index 81dd05c25a..0000000000 --- a/src/Umbraco.Web/WebAssets/CDF/ClientDependencyConfiguration.cs +++ /dev/null @@ -1,151 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Security.Cryptography; -using System.Text; -using System.Web; -using System.Xml.Linq; -using Microsoft.Extensions.Logging; -using ClientDependency.Core.CompositeFiles.Providers; -using ClientDependency.Core.Config; -using Semver; -using Umbraco.Core.Hosting; - -namespace Umbraco.Web.WebAssets.CDF -{ - /// - /// A utility class for working with CDF config and cache files - use sparingly! - /// - public class ClientDependencyConfiguration - { - private readonly ILogger _logger; - private readonly string _fileName; - - private string FileMapDefaultFolder - { - get => XmlFileMapper.FileMapDefaultFolder; - set => XmlFileMapper.FileMapDefaultFolder = value; - } - - public ClientDependencyConfiguration(ILogger logger, IHostingEnvironment hostingEnvironment) - { - if (logger == null) throw new ArgumentNullException("logger"); - _logger = logger; - _fileName = hostingEnvironment.MapPathContentRoot(string.Format("{0}/ClientDependency.config", Core.Constants.SystemDirectories.Config)); - } - - /// - /// Changes the version number in ClientDependency.config to a hashed value for the version and the DateTime.Day - /// - /// The version of Umbraco we're upgrading to - /// A date value to use in the hash to prevent this method from updating the version on each startup - /// Allows the developer to specify the date precision for the hash (i.e. "yyyyMMdd" would be a precision for the day) - /// Boolean to indicate successful update of the ClientDependency.config file - public bool UpdateVersionNumber(SemVersion version, DateTime date, string dateFormat) - { - var byteContents = Encoding.Unicode.GetBytes(version + date.ToString(dateFormat)); - - //This is a way to convert a string to a long - //see https://www.codeproject.com/Articles/34309/Convert-String-to-bit-Integer - //We could much more easily use MD5 which would create us an INT but since that is not compliant with - //hashing standards we have to use SHA - int intHash; - using (var hash = SHA256.Create()) - { - var bytes = hash.ComputeHash(byteContents); - - var longResult = new[] { 0, 8, 16, 24 } - .Select(i => BitConverter.ToInt64(bytes, i)) - .Aggregate((x, y) => x ^ y); - - //CDF requires an INT, and although this isn't fail safe, it will work for our purposes. We are not hashing for crypto purposes - //so there could be some collisions with this conversion but it's not a problem for our purposes - //It's also important to note that the long.GetHashCode() implementation in .NET is this: return (int) this ^ (int) (this >> 32); - //which means that this value will not change per AppDomain like some GetHashCode implementations. - intHash = longResult.GetHashCode(); - } - - try - { - var clientDependencyConfigXml = XDocument.Load(_fileName, LoadOptions.PreserveWhitespace); - if (clientDependencyConfigXml.Root != null) - { - - var versionAttribute = clientDependencyConfigXml.Root.Attribute("version"); - - //Set the new version to the hashcode of now - var oldVersion = versionAttribute.Value; - var newVersion = Math.Abs(intHash).ToString(); - - //don't update if it's the same version - if (oldVersion == newVersion) - return false; - - versionAttribute.SetValue(newVersion); - clientDependencyConfigXml.Save(_fileName, SaveOptions.DisableFormatting); - - _logger.LogInformation("Updated version number from {OldVersion} to {NewVersion}", oldVersion, newVersion); - return true; - } - } - catch (Exception ex) - { - _logger.LogError(ex, "Couldn't update ClientDependency version number"); - } - - return false; - } - - /// - /// Clears the temporary files stored for the ClientDependency folder - /// - /// - public bool ClearTempFiles(HttpContextBase currentHttpContext) - { - var cdfTempDirectories = new HashSet(); - foreach (BaseCompositeFileProcessingProvider provider in ClientDependencySettings.Instance - .CompositeFileProcessingProviderCollection) - { - var path = provider.CompositeFilePath.FullName; - cdfTempDirectories.Add(path); - } - - try - { - var fullPath = FileMapDefaultFolder.StartsWith("~/") - ? currentHttpContext.Server.MapPath(FileMapDefaultFolder) - : FileMapDefaultFolder; - if (fullPath != null) - { - cdfTempDirectories.Add(fullPath); - } - } - catch (Exception ex) - { - //invalid path format or something... try/catch to be safe - _logger.LogError(ex, "Could not get path from ClientDependency.config"); - } - - var success = true; - foreach (var directory in cdfTempDirectories) - { - try - { - if (!Directory.Exists(directory)) - continue; - - Directory.Delete(directory, true); - } - catch (Exception ex) - { - // Something could be locking the directory or the was another error, making sure we don't break the upgrade installer - _logger.LogError(ex, "Could not clear temp files"); - success = false; - } - } - - return success; - } - } -} diff --git a/src/Umbraco.Web/WebAssets/CDF/ClientDependencyRuntimeMinifier.cs b/src/Umbraco.Web/WebAssets/CDF/ClientDependencyRuntimeMinifier.cs deleted file mode 100644 index c72887b4d2..0000000000 --- a/src/Umbraco.Web/WebAssets/CDF/ClientDependencyRuntimeMinifier.cs +++ /dev/null @@ -1,169 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; -using ClientDependency.Core; -using ClientDependency.Core.CompositeFiles; -using ClientDependency.Core.Config; -using Umbraco.Core.Configuration; -using Umbraco.Core.WebAssets; -using CssFile = ClientDependency.Core.CssFile; -using JavascriptFile = ClientDependency.Core.JavascriptFile; -using Umbraco.Core.Hosting; - -namespace Umbraco.Web.WebAssets.CDF -{ - public class ClientDependencyRuntimeMinifier : IRuntimeMinifier - { - private readonly IHttpContextAccessor _httpContextAccessor; - private readonly IHostingEnvironment _hostingEnvironment; - private readonly ILoggerFactory _loggerFactory; - private readonly ILogger _logger; - private readonly IUmbracoVersion _umbracoVersion; - - public string CacheBuster => ClientDependencySettings.Instance.Version.ToString(); - - public ClientDependencyRuntimeMinifier( - IHttpContextAccessor httpContextAccessor, - IHostingEnvironment hostingEnvironment, - ILoggerFactory loggerFactory, - IUmbracoVersion umbracoVersion) - { - _httpContextAccessor = httpContextAccessor; - _hostingEnvironment = hostingEnvironment; - _loggerFactory = loggerFactory; - _logger = _loggerFactory.CreateLogger(); - _umbracoVersion = umbracoVersion; - } - - public void CreateCssBundle(string bundleName, params string[] filePaths) - { - if (filePaths.Any(f => !f.StartsWith("/") && !f.StartsWith("~/"))) - throw new InvalidOperationException("All file paths must be absolute"); - - BundleManager.CreateCssBundle( - bundleName, - filePaths.Select(x => new CssFile(x)).ToArray()); - } - - public Task RenderCssHereAsync(string bundleName) - { - var bundleFiles = GetCssBundleFiles(bundleName); - if (bundleFiles == null) return Task.FromResult(string.Empty); - return Task.FromResult(RenderOutput(bundleFiles, AssetType.Css)); - } - - public void CreateJsBundle(string bundleName, params string[] filePaths) - { - if (filePaths.Any(f => !f.StartsWith("/") && !f.StartsWith("~/"))) - throw new InvalidOperationException("All file paths must be absolute"); - - BundleManager.CreateJsBundle( - bundleName, - filePaths.Select(x => new JavascriptFile(x)).ToArray()); - } - - public Task RenderJsHereAsync(string bundleName) - { - var bundleFiles = GetJsBundleFiles(bundleName); - if (bundleFiles == null) return Task.FromResult(string.Empty); - return Task.FromResult(RenderOutput(bundleFiles, AssetType.Javascript)); - } - - public Task> GetAssetPathsAsync(string bundleName) - { - var bundleFiles = GetJsBundleFiles(bundleName)?.ToList() ?? GetCssBundleFiles(bundleName)?.ToList(); - if (bundleFiles == null || bundleFiles.Count == 0) return Task.FromResult(Enumerable.Empty()); - - var assetType = bundleFiles[0].DependencyType == ClientDependencyType.Css ? AssetType.Css : AssetType.Javascript; - - // This is a hack on CDF so that we can resolve CDF urls directly since that isn't directly supported by the lib - var renderer = ClientDependencySettings.Instance.MvcRendererCollection["Umbraco.DependencyPathRenderer"]; - renderer.RegisterDependencies(bundleFiles, new HashSet(), out var scripts, out var stylesheets, _httpContextAccessor.HttpContext); - - var toParse = assetType == AssetType.Javascript ? scripts : stylesheets; - return Task.FromResult>(toParse.Split(new[] { DependencyPathRenderer.Delimiter }, StringSplitOptions.RemoveEmptyEntries)); - } - - public Task MinifyAsync(string fileContent, AssetType assetType) - { - TextReader reader = new StringReader(fileContent); - - if (assetType == AssetType.Javascript) - { - var jsMinifier = new JSMin(); - return Task.FromResult(jsMinifier.Minify(reader)); - } - - // asset type is Css - var cssMinifier = new CssMinifier(); - return Task.FromResult(cssMinifier.Minify(reader)); - } - - public void Reset() - { - // Update ClientDependency version - var clientDependencyConfig = new ClientDependencyConfiguration(_loggerFactory.CreateLogger(), _hostingEnvironment); - var clientDependencyUpdated = clientDependencyConfig.UpdateVersionNumber( - _umbracoVersion.SemanticVersion, DateTime.UtcNow, "yyyyMMdd"); - // Delete ClientDependency temp directories to make sure we get fresh caches - var clientDependencyTempFilesDeleted = clientDependencyConfig.ClearTempFiles(_httpContextAccessor.HttpContext); - } - - private string RenderOutput(IEnumerable bundleFiles, AssetType assetType) - { - var renderer = ClientDependencySettings.Instance.DefaultMvcRenderer; - - renderer.RegisterDependencies( - bundleFiles.ToList(), - new HashSet(), - out var jsOutput, out var cssOutput, _httpContextAccessor.GetRequiredHttpContext()); - - return assetType == AssetType.Css ? cssOutput : jsOutput; - } - - private IEnumerable GetCssBundleFiles(string bundleName) - { - // internal methods needs reflection - var bundle = typeof(BundleManager) - .GetMethod("GetCssBundle", BindingFlags.NonPublic | BindingFlags.Static) - .Invoke(null, new object[] { bundleName }); - var bundleFiles = (IEnumerable) bundle?.GetType().GetProperty("Files").GetValue(bundle, null); - return bundleFiles; - } - - private IEnumerable GetJsBundleFiles(string bundleName) - { - // internal methods needs reflection - var bundle = typeof(BundleManager) - .GetMethod("GetJsBundle", BindingFlags.NonPublic | BindingFlags.Static) - .Invoke(null, new object[] { bundleName }); - var bundleFiles = (IEnumerable)bundle?.GetType().GetProperty("Files").GetValue(bundle, null); - return bundleFiles; - } - - private ClientDependencyType MapDependencyTypeValue(AssetType type) - { - return type switch - { - AssetType.Javascript => ClientDependencyType.Javascript, - AssetType.Css => ClientDependencyType.Css, - _ => (ClientDependencyType) Enum.Parse(typeof(ClientDependencyType), type.ToString(), true) - }; - } - - private IClientDependencyFile MapAssetFile(IAssetFile assetFile) - { - var assetFileType = (AssetFile)assetFile; - var basicFile = new BasicFile(MapDependencyTypeValue(assetFileType.DependencyType)) - { - FilePath = assetFile.FilePath - }; - - return basicFile; - } - } -} diff --git a/src/Umbraco.Web/WebAssets/CDF/DependencyPathRenderer.cs b/src/Umbraco.Web/WebAssets/CDF/DependencyPathRenderer.cs deleted file mode 100644 index ef73dbecb1..0000000000 --- a/src/Umbraco.Web/WebAssets/CDF/DependencyPathRenderer.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System.Collections.Generic; -using System.Web; -using ClientDependency.Core; -using ClientDependency.Core.FileRegistration.Providers; - -namespace Umbraco.Web.WebAssets.CDF -{ - /// - /// A custom renderer that only outputs a dependency path instead of script tags - for use with the js loader with yepnope - /// - public class DependencyPathRenderer : StandardRenderer - { - public override string Name - { - get { return "Umbraco.DependencyPathRenderer"; } - } - - /// - /// Used to delimit each dependency so we can split later - /// - public const string Delimiter = "||||"; - - /// - /// Override because the StandardRenderer replaces & with & but we don't want that so we'll reverse it - /// - /// - /// - /// - /// - /// - public override void RegisterDependencies(List allDependencies, HashSet paths, out string jsOutput, out string cssOutput, HttpContextBase http) - { - base.RegisterDependencies(allDependencies, paths, out jsOutput, out cssOutput, http); - - jsOutput = jsOutput.Replace("&", "&"); - cssOutput = cssOutput.Replace("&", "&"); - } - - protected override string RenderSingleJsFile(string js, IDictionary htmlAttributes) - { - return js + Delimiter; - } - - protected override string RenderSingleCssFile(string css, IDictionary htmlAttributes) - { - return css + Delimiter; - } - } -} diff --git a/src/Umbraco.Web/WebAssets/CDF/UmbracoClientDependencyLoader.cs b/src/Umbraco.Web/WebAssets/CDF/UmbracoClientDependencyLoader.cs deleted file mode 100644 index a0704140f1..0000000000 --- a/src/Umbraco.Web/WebAssets/CDF/UmbracoClientDependencyLoader.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System.Web.UI; -using ClientDependency.Core.Controls; -using ClientDependency.Core.FileRegistration.Providers; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.Models; -using Umbraco.Core.IO; - -namespace Umbraco.Web.WebAssets.CDF -{ - /// - /// Used to load in all client dependencies for Umbraco. - /// Ensures that both UmbracoClient and UmbracoRoot paths are added to the loader. - /// - public class UmbracoClientDependencyLoader : ClientDependencyLoader - { - /// - /// Set the defaults - /// - public UmbracoClientDependencyLoader(GlobalSettings globalSettings, IIOHelper ioHelper) - : base() - { - this.AddPath("UmbracoRoot", ioHelper.ResolveUrl(globalSettings.UmbracoPath)); - this.ProviderName = LoaderControlProvider.DefaultName; - - } - - public static ClientDependencyLoader TryCreate(Control parent, out bool isNew, GlobalSettings globalSettings, IIOHelper ioHelper) - { - if (ClientDependencyLoader.Instance == null) - { - var loader = new UmbracoClientDependencyLoader(globalSettings, ioHelper); - parent.Controls.Add(loader); - isNew = true; - return loader; - } - else - { - isNew = false; - return ClientDependencyLoader.Instance; - } - - } - - } -}