From e51a3efe1548f74ba0384e16fe1abe4b27d5c536 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 22 Nov 2019 12:17:26 +1100 Subject: [PATCH 01/18] Moves more services and friends, removes more usages of Current --- .../Models/CultureImpact.cs | 2 +- .../Querying/ValuePropertyMatchType.cs | 0 .../Services/IMemberService.cs | 4 --- .../Services/IMembershipMemberService.cs | 23 +++++++++++++--- .../Services/IPropertyValidationService.cs | 10 +++++++ .../Services/PropertyValidationService.cs | 17 ++---------- .../Services/ServiceContext.cs | 0 .../Services/IMembershipMemberService.cs | 27 ------------------- .../Services/Implement/ContentService.cs | 18 ++++++------- src/Umbraco.Core/Umbraco.Core.csproj | 6 ----- 10 files changed, 42 insertions(+), 65 deletions(-) rename src/{Umbraco.Core => Umbraco.Abstractions}/Models/CultureImpact.cs (99%) rename src/{Umbraco.Core => Umbraco.Abstractions}/Persistence/Querying/ValuePropertyMatchType.cs (100%) rename src/{Umbraco.Core => Umbraco.Abstractions}/Services/IMemberService.cs (98%) create mode 100644 src/Umbraco.Abstractions/Services/IPropertyValidationService.cs rename src/{Umbraco.Core => Umbraco.Abstractions}/Services/PropertyValidationService.cs (92%) rename src/{Umbraco.Core => Umbraco.Abstractions}/Services/ServiceContext.cs (100%) delete mode 100644 src/Umbraco.Core/Services/IMembershipMemberService.cs diff --git a/src/Umbraco.Core/Models/CultureImpact.cs b/src/Umbraco.Abstractions/Models/CultureImpact.cs similarity index 99% rename from src/Umbraco.Core/Models/CultureImpact.cs rename to src/Umbraco.Abstractions/Models/CultureImpact.cs index ca18985941..eeb7fa82a3 100644 --- a/src/Umbraco.Core/Models/CultureImpact.cs +++ b/src/Umbraco.Abstractions/Models/CultureImpact.cs @@ -10,7 +10,7 @@ namespace Umbraco.Core.Models /// A set of cultures can be either all cultures (including the invariant culture), or /// the invariant culture, or a specific culture. /// - internal class CultureImpact + public sealed class CultureImpact { /// /// Utility method to return the culture used for invariant property errors based on what cultures are being actively saved, diff --git a/src/Umbraco.Core/Persistence/Querying/ValuePropertyMatchType.cs b/src/Umbraco.Abstractions/Persistence/Querying/ValuePropertyMatchType.cs similarity index 100% rename from src/Umbraco.Core/Persistence/Querying/ValuePropertyMatchType.cs rename to src/Umbraco.Abstractions/Persistence/Querying/ValuePropertyMatchType.cs diff --git a/src/Umbraco.Core/Services/IMemberService.cs b/src/Umbraco.Abstractions/Services/IMemberService.cs similarity index 98% rename from src/Umbraco.Core/Services/IMemberService.cs rename to src/Umbraco.Abstractions/Services/IMemberService.cs index ee0e2ef5ed..6f3979f101 100644 --- a/src/Umbraco.Core/Services/IMemberService.cs +++ b/src/Umbraco.Abstractions/Services/IMemberService.cs @@ -1,10 +1,6 @@ using System; using System.Collections.Generic; -using System.ComponentModel; -using System.Net.Http; using Umbraco.Core.Models; -using Umbraco.Core.Models.Membership; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Querying; namespace Umbraco.Core.Services diff --git a/src/Umbraco.Abstractions/Services/IMembershipMemberService.cs b/src/Umbraco.Abstractions/Services/IMembershipMemberService.cs index a224dfc967..448b0c761a 100644 --- a/src/Umbraco.Abstractions/Services/IMembershipMemberService.cs +++ b/src/Umbraco.Abstractions/Services/IMembershipMemberService.cs @@ -1,11 +1,28 @@ -using System; -using System.Collections.Generic; -using System.Text; +using System.Collections.Generic; +using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; using Umbraco.Core.Persistence.Querying; namespace Umbraco.Core.Services { + /// + /// Defines part of the MemberService, which is specific to methods used by the membership provider. + /// + /// + /// Idea is to have this as an isolated interface so that it can be easily 'replaced' in the membership provider implementation. + /// + public interface IMembershipMemberService : IMembershipMemberService, IMembershipRoleService + { + /// + /// Creates and persists a new Member + /// + /// Username of the Member to create + /// Email of the Member to create + /// which the Member should be based on + /// + IMember CreateMemberWithIdentity(string username, string email, IMemberType memberType); + } + /// /// Defines part of the UserService/MemberService, which is specific to methods used by the membership provider. /// The generic type is restricted to . The implementation of this interface uses diff --git a/src/Umbraco.Abstractions/Services/IPropertyValidationService.cs b/src/Umbraco.Abstractions/Services/IPropertyValidationService.cs new file mode 100644 index 0000000000..399bdd64d2 --- /dev/null +++ b/src/Umbraco.Abstractions/Services/IPropertyValidationService.cs @@ -0,0 +1,10 @@ +using Umbraco.Core.Models; + +namespace Umbraco.Core.Services +{ + public interface IPropertyValidationService + { + bool IsPropertyDataValid(IContent content, out IProperty[] invalidProperties, CultureImpact impact); + bool IsPropertyValid(IProperty property, string culture = "*", string segment = "*"); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Services/PropertyValidationService.cs b/src/Umbraco.Abstractions/Services/PropertyValidationService.cs similarity index 92% rename from src/Umbraco.Core/Services/PropertyValidationService.cs rename to src/Umbraco.Abstractions/Services/PropertyValidationService.cs index 1704d52206..bd9e3190b7 100644 --- a/src/Umbraco.Core/Services/PropertyValidationService.cs +++ b/src/Umbraco.Abstractions/Services/PropertyValidationService.cs @@ -1,17 +1,10 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Umbraco.Core.Collections; -using Umbraco.Core.Composing; +using System.Linq; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; namespace Umbraco.Core.Services { - //TODO: We should make this an interface and inject it into the ContentService - internal class PropertyValidationService + internal class PropertyValidationService : IPropertyValidationService { private readonly PropertyEditorCollection _propertyEditors; private readonly IDataTypeService _dataTypeService; @@ -22,12 +15,6 @@ namespace Umbraco.Core.Services _dataTypeService = dataTypeService; } - //TODO: Remove this method in favor of the overload specifying all dependencies - public PropertyValidationService() - : this(Current.PropertyEditors, Current.Services.DataTypeService) - { - } - /// /// Validates the content item's properties pass validation rules /// diff --git a/src/Umbraco.Core/Services/ServiceContext.cs b/src/Umbraco.Abstractions/Services/ServiceContext.cs similarity index 100% rename from src/Umbraco.Core/Services/ServiceContext.cs rename to src/Umbraco.Abstractions/Services/ServiceContext.cs diff --git a/src/Umbraco.Core/Services/IMembershipMemberService.cs b/src/Umbraco.Core/Services/IMembershipMemberService.cs deleted file mode 100644 index 4ecbaa5338..0000000000 --- a/src/Umbraco.Core/Services/IMembershipMemberService.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Collections.Generic; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Membership; -using Umbraco.Core.Persistence.Querying; - -namespace Umbraco.Core.Services -{ - /// - /// Defines part of the MemberService, which is specific to methods used by the membership provider. - /// - /// - /// Idea is to have this as an isolated interface so that it can be easily 'replaced' in the membership provider implementation. - /// - public interface IMembershipMemberService : IMembershipMemberService, IMembershipRoleService - { - /// - /// Creates and persists a new Member - /// - /// Username of the Member to create - /// Email of the Member to create - /// which the Member should be based on - /// - IMember CreateMemberWithIdentity(string username, string email, IMemberType memberType); - } - - -} diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs index 2fa75e2f3f..f255956971 100644 --- a/src/Umbraco.Core/Services/Implement/ContentService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentService.cs @@ -27,17 +27,16 @@ namespace Umbraco.Core.Services.Implement private readonly IContentTypeRepository _contentTypeRepository; private readonly IDocumentBlueprintRepository _documentBlueprintRepository; private readonly ILanguageRepository _languageRepository; + private readonly IPropertyValidationService _propertyValidationService; private IQuery _queryNotTrashed; - //TODO: The non-lazy object should be injected - private readonly Lazy _propertyValidationService = new Lazy(() => new PropertyValidationService()); - - + #region Constructors public ContentService(IScopeProvider provider, ILogger logger, IEventMessagesFactory eventMessagesFactory, IDocumentRepository documentRepository, IEntityRepository entityRepository, IAuditRepository auditRepository, - IContentTypeRepository contentTypeRepository, IDocumentBlueprintRepository documentBlueprintRepository, ILanguageRepository languageRepository) + IContentTypeRepository contentTypeRepository, IDocumentBlueprintRepository documentBlueprintRepository, ILanguageRepository languageRepository, + IPropertyValidationService propertyValidationService) : base(provider, logger, eventMessagesFactory) { _documentRepository = documentRepository; @@ -46,6 +45,7 @@ namespace Umbraco.Core.Services.Implement _contentTypeRepository = contentTypeRepository; _documentBlueprintRepository = documentBlueprintRepository; _languageRepository = languageRepository; + _propertyValidationService = propertyValidationService; } #endregion @@ -1404,7 +1404,7 @@ namespace Umbraco.Core.Services.Implement //publish the culture values and validate the property values, if validation fails, log the invalid properties so the develeper has an idea of what has failed IProperty[] invalidProperties = null; var impact = CultureImpact.Explicit(culture, IsDefaultCulture(allLangs, culture)); - var tryPublish = d.PublishCulture(impact) && _propertyValidationService.Value.IsPropertyDataValid(d, out invalidProperties, impact); + var tryPublish = d.PublishCulture(impact) && _propertyValidationService.IsPropertyDataValid(d, out invalidProperties, impact); if (invalidProperties != null && invalidProperties.Length > 0) Logger.Warn("Scheduled publishing will fail for document {DocumentId} and culture {Culture} because of invalid properties {InvalidProperties}", d.Id, culture, string.Join(",", invalidProperties.Select(x => x.Alias))); @@ -1505,12 +1505,12 @@ namespace Umbraco.Core.Services.Implement return culturesToPublish.All(culture => { var impact = CultureImpact.Create(culture, IsDefaultCulture(allLangs, culture), content); - return content.PublishCulture(impact) && _propertyValidationService.Value.IsPropertyDataValid(content, out _, impact); + return content.PublishCulture(impact) && _propertyValidationService.IsPropertyDataValid(content, out _, impact); }); } return content.PublishCulture(CultureImpact.Invariant) - && _propertyValidationService.Value.IsPropertyDataValid(content, out _, CultureImpact.Invariant); + && _propertyValidationService.IsPropertyDataValid(content, out _, CultureImpact.Invariant); } // utility 'ShouldPublish' func used by SaveAndPublishBranch @@ -2603,7 +2603,7 @@ namespace Umbraco.Core.Services.Implement //validate the property values IProperty[] invalidProperties = null; - if (!impactsToPublish.All(x => _propertyValidationService.Value.IsPropertyDataValid(content, out invalidProperties, x))) + if (!impactsToPublish.All(x => _propertyValidationService.IsPropertyDataValid(content, out invalidProperties, x))) return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content) { InvalidProperties = invalidProperties diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 99ce0ec07c..056e1a086b 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -203,7 +203,6 @@ - @@ -236,10 +235,7 @@ - - - @@ -651,7 +647,6 @@ - @@ -804,7 +799,6 @@ - From 3dbebfb153a20e7e18aeaaafbb6325bfca0cf526 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 22 Nov 2019 13:13:19 +1100 Subject: [PATCH 02/18] Refactors the usage of ICultureDictionary so its injectable, removes more uses of Current, removes unused methods relying on System.Web --- .../Services/IPropertyValidationService.cs | 9 ++++- .../LocalizedTextServiceExtensions.cs | 21 +---------- .../Services/PublicAccessServiceExtensions.cs | 6 ---- .../Services/UserServiceExtensions.cs | 36 ------------------- .../CompositionExtensions/Services.cs | 1 + src/Umbraco.Core/CompositionExtensions.cs | 2 +- .../Services/Implement/ContentService.cs | 13 ++++--- .../Implement}/PropertyValidationService.cs | 8 ++--- src/Umbraco.Core/Umbraco.Core.csproj | 4 +-- src/Umbraco.Tests/TestHelpers/TestObjects.cs | 8 +++-- src/Umbraco.Tests/Testing/UmbracoTestBase.cs | 7 ++-- .../Web/Controllers/ContentControllerTests.cs | 8 +++-- .../Dictionary/UmbracoCultureDictionary.cs | 21 +++++------ .../UmbracoCultureDictionaryFactory.cs | 16 +++++++-- src/Umbraco.Web/Editors/ContentController.cs | 10 +++--- .../Editors/ContentControllerBase.cs | 6 +++- .../Editors/ContentTypeController.cs | 8 ++--- .../Editors/ContentTypeControllerBase.cs | 14 +++----- src/Umbraco.Web/Editors/MediaController.cs | 5 +-- .../Editors/MediaTypeController.cs | 4 +-- src/Umbraco.Web/Editors/MemberController.cs | 7 ++-- .../Editors/MemberTypeController.cs | 4 +-- .../Models/Mapping/ContentMapDefinition.cs | 11 +++--- .../Mapping/ContentPropertyDisplayMapper.cs | 11 +++--- .../Mapping/ContentPropertyMapDefinition.cs | 7 ++-- .../Models/Mapping/MediaMapDefinition.cs | 5 +-- .../Mapping/MemberTabsAndPropertiesMapper.cs | 9 +++-- .../Models/Mapping/TabsAndPropertiesMapper.cs | 15 ++++---- src/Umbraco.Web/Runtime/WebFinalComposer.cs | 17 +++++++-- src/Umbraco.Web/Runtime/WebInitialComposer.cs | 2 +- 30 files changed, 140 insertions(+), 155 deletions(-) rename src/{Umbraco.Core => Umbraco.Abstractions}/Services/LocalizedTextServiceExtensions.cs (71%) rename src/{Umbraco.Core => Umbraco.Abstractions}/Services/PublicAccessServiceExtensions.cs (92%) rename src/{Umbraco.Core => Umbraco.Abstractions}/Services/UserServiceExtensions.cs (68%) rename src/{Umbraco.Abstractions/Services => Umbraco.Core/Services/Implement}/PropertyValidationService.cs (96%) diff --git a/src/Umbraco.Abstractions/Services/IPropertyValidationService.cs b/src/Umbraco.Abstractions/Services/IPropertyValidationService.cs index 399bdd64d2..8eb7106974 100644 --- a/src/Umbraco.Abstractions/Services/IPropertyValidationService.cs +++ b/src/Umbraco.Abstractions/Services/IPropertyValidationService.cs @@ -4,7 +4,14 @@ namespace Umbraco.Core.Services { public interface IPropertyValidationService { + /// + /// Validates the content item's properties pass validation rules + /// bool IsPropertyDataValid(IContent content, out IProperty[] invalidProperties, CultureImpact impact); + + /// + /// Gets a value indicating whether the property has valid values. + /// bool IsPropertyValid(IProperty property, string culture = "*", string segment = "*"); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Services/LocalizedTextServiceExtensions.cs b/src/Umbraco.Abstractions/Services/LocalizedTextServiceExtensions.cs similarity index 71% rename from src/Umbraco.Core/Services/LocalizedTextServiceExtensions.cs rename to src/Umbraco.Abstractions/Services/LocalizedTextServiceExtensions.cs index ce5b3ef8c4..92854c5a2b 100644 --- a/src/Umbraco.Core/Services/LocalizedTextServiceExtensions.cs +++ b/src/Umbraco.Abstractions/Services/LocalizedTextServiceExtensions.cs @@ -69,24 +69,7 @@ namespace Umbraco.Core.Services .ToDictionary(keyvals => keyvals.index, keyvals => keyvals.value); } - private static ICultureDictionary _cultureDictionary; - - /// - /// TODO: We need to refactor how we work with ICultureDictionary - this is supposed to be the 'fast' way to - /// do readonly access to the Dictionary without using the ILocalizationService. See TODO Notes in `DefaultCultureDictionary` - /// Also NOTE that the ICultureDictionary is based on the ILocalizationService not the ILocalizedTextService (which is used - /// only for the localization files - not the dictionary) - /// - /// - /// - /// - internal static string UmbracoDictionaryTranslate(this ILocalizedTextService manager, string text) - { - var cultureDictionary = CultureDictionary; - return manager.UmbracoDictionaryTranslate(text, cultureDictionary); - } - - private static string UmbracoDictionaryTranslate(this ILocalizedTextService manager, string text, ICultureDictionary cultureDictionary) + public static string UmbracoDictionaryTranslate(this ILocalizedTextService manager, ICultureDictionary cultureDictionary, string text) { if (text == null) return null; @@ -105,7 +88,5 @@ namespace Umbraco.Core.Services return value.StartsWith("[") ? text : value; } - private static ICultureDictionary CultureDictionary - => _cultureDictionary ?? (_cultureDictionary = Current.CultureDictionaryFactory.CreateDictionary()); } } diff --git a/src/Umbraco.Core/Services/PublicAccessServiceExtensions.cs b/src/Umbraco.Abstractions/Services/PublicAccessServiceExtensions.cs similarity index 92% rename from src/Umbraco.Core/Services/PublicAccessServiceExtensions.cs rename to src/Umbraco.Abstractions/Services/PublicAccessServiceExtensions.cs index b0dc979ebf..cb2a5f4956 100644 --- a/src/Umbraco.Core/Services/PublicAccessServiceExtensions.cs +++ b/src/Umbraco.Abstractions/Services/PublicAccessServiceExtensions.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Web.Security; using Umbraco.Core.Models; namespace Umbraco.Core.Services @@ -53,11 +52,6 @@ namespace Umbraco.Core.Services return HasAccess(entry, username, currentMemberRoles); } - public static bool HasAccess(this IPublicAccessService publicAccessService, string path, MembershipUser member, RoleProvider roleProvider) - { - return publicAccessService.HasAccess(path, member.UserName, roleProvider.GetRolesForUser); - } - /// /// Checks if the member with the specified username has access to the path which is also based on the passed in roles for the member /// diff --git a/src/Umbraco.Core/Services/UserServiceExtensions.cs b/src/Umbraco.Abstractions/Services/UserServiceExtensions.cs similarity index 68% rename from src/Umbraco.Core/Services/UserServiceExtensions.cs rename to src/Umbraco.Abstractions/Services/UserServiceExtensions.cs index 31c446352e..480222b351 100644 --- a/src/Umbraco.Core/Services/UserServiceExtensions.cs +++ b/src/Umbraco.Abstractions/Services/UserServiceExtensions.cs @@ -1,6 +1,5 @@ using System; using System.Linq; -using System.Web.Security; using Umbraco.Core.Models.Membership; namespace Umbraco.Core.Services @@ -69,40 +68,5 @@ namespace Umbraco.Core.Services userService.ReplaceUserGroupPermissions(groupId, new char[] { }); } - /// - /// Maps a custom provider's information to an umbraco user account - /// - /// - /// - /// - /// To maintain compatibility we have to check the login name if the provider key lookup fails but otherwise - /// we'll store the provider user key in the login column. - /// - internal static IUser CreateUserMappingForCustomProvider(this IUserService userService, MembershipUser member) - { - if (member == null) throw new ArgumentNullException("member"); - - - var valToLookup = member.ProviderUserKey == null ? member.UserName : member.ProviderUserKey.ToString(); - var found = userService.GetByUsername(valToLookup); - if (found == null && member.ProviderUserKey != null) - { - //try by username - found = userService.GetByUsername(member.UserName); - } - - if (found == null) - { - var user = new User( - member.UserName, - member.Email ?? Guid.NewGuid().ToString("N") + "@example.com", //email cannot be empty - member.ProviderUserKey == null ? member.UserName : member.ProviderUserKey.ToString(), - Guid.NewGuid().ToString("N")); //pass cannot be empty - userService.Save(user); - return user; - } - - return found; - } } } diff --git a/src/Umbraco.Core/Composing/CompositionExtensions/Services.cs b/src/Umbraco.Core/Composing/CompositionExtensions/Services.cs index a207e2197a..ba8442f8d7 100644 --- a/src/Umbraco.Core/Composing/CompositionExtensions/Services.cs +++ b/src/Umbraco.Core/Composing/CompositionExtensions/Services.cs @@ -27,6 +27,7 @@ namespace Umbraco.Core.Composing.CompositionExtensions composition.RegisterUnique(); // register the services + composition.RegisterUnique(); composition.RegisterUnique(); composition.RegisterUnique(); composition.RegisterUnique(); diff --git a/src/Umbraco.Core/CompositionExtensions.cs b/src/Umbraco.Core/CompositionExtensions.cs index 2d8254a3c0..1fd1de0c08 100644 --- a/src/Umbraco.Core/CompositionExtensions.cs +++ b/src/Umbraco.Core/CompositionExtensions.cs @@ -91,7 +91,7 @@ namespace Umbraco.Core public static void SetCultureDictionaryFactory(this Composition composition) where T : ICultureDictionaryFactory { - composition.RegisterUnique(); + composition.RegisterUnique(); } /// diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs index f255956971..82678f2889 100644 --- a/src/Umbraco.Core/Services/Implement/ContentService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentService.cs @@ -8,7 +8,6 @@ using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Scoping; @@ -27,7 +26,7 @@ namespace Umbraco.Core.Services.Implement private readonly IContentTypeRepository _contentTypeRepository; private readonly IDocumentBlueprintRepository _documentBlueprintRepository; private readonly ILanguageRepository _languageRepository; - private readonly IPropertyValidationService _propertyValidationService; + private readonly Lazy _propertyValidationService; private IQuery _queryNotTrashed; #region Constructors @@ -36,7 +35,7 @@ namespace Umbraco.Core.Services.Implement IEventMessagesFactory eventMessagesFactory, IDocumentRepository documentRepository, IEntityRepository entityRepository, IAuditRepository auditRepository, IContentTypeRepository contentTypeRepository, IDocumentBlueprintRepository documentBlueprintRepository, ILanguageRepository languageRepository, - IPropertyValidationService propertyValidationService) + Lazy propertyValidationService) : base(provider, logger, eventMessagesFactory) { _documentRepository = documentRepository; @@ -1404,7 +1403,7 @@ namespace Umbraco.Core.Services.Implement //publish the culture values and validate the property values, if validation fails, log the invalid properties so the develeper has an idea of what has failed IProperty[] invalidProperties = null; var impact = CultureImpact.Explicit(culture, IsDefaultCulture(allLangs, culture)); - var tryPublish = d.PublishCulture(impact) && _propertyValidationService.IsPropertyDataValid(d, out invalidProperties, impact); + var tryPublish = d.PublishCulture(impact) && _propertyValidationService.Value.IsPropertyDataValid(d, out invalidProperties, impact); if (invalidProperties != null && invalidProperties.Length > 0) Logger.Warn("Scheduled publishing will fail for document {DocumentId} and culture {Culture} because of invalid properties {InvalidProperties}", d.Id, culture, string.Join(",", invalidProperties.Select(x => x.Alias))); @@ -1505,12 +1504,12 @@ namespace Umbraco.Core.Services.Implement return culturesToPublish.All(culture => { var impact = CultureImpact.Create(culture, IsDefaultCulture(allLangs, culture), content); - return content.PublishCulture(impact) && _propertyValidationService.IsPropertyDataValid(content, out _, impact); + return content.PublishCulture(impact) && _propertyValidationService.Value.IsPropertyDataValid(content, out _, impact); }); } return content.PublishCulture(CultureImpact.Invariant) - && _propertyValidationService.IsPropertyDataValid(content, out _, CultureImpact.Invariant); + && _propertyValidationService.Value.IsPropertyDataValid(content, out _, CultureImpact.Invariant); } // utility 'ShouldPublish' func used by SaveAndPublishBranch @@ -2603,7 +2602,7 @@ namespace Umbraco.Core.Services.Implement //validate the property values IProperty[] invalidProperties = null; - if (!impactsToPublish.All(x => _propertyValidationService.IsPropertyDataValid(content, out invalidProperties, x))) + if (!impactsToPublish.All(x => _propertyValidationService.Value.IsPropertyDataValid(content, out invalidProperties, x))) return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content) { InvalidProperties = invalidProperties diff --git a/src/Umbraco.Abstractions/Services/PropertyValidationService.cs b/src/Umbraco.Core/Services/Implement/PropertyValidationService.cs similarity index 96% rename from src/Umbraco.Abstractions/Services/PropertyValidationService.cs rename to src/Umbraco.Core/Services/Implement/PropertyValidationService.cs index bd9e3190b7..10cc4bce23 100644 --- a/src/Umbraco.Abstractions/Services/PropertyValidationService.cs +++ b/src/Umbraco.Core/Services/Implement/PropertyValidationService.cs @@ -15,9 +15,7 @@ namespace Umbraco.Core.Services _dataTypeService = dataTypeService; } - /// - /// Validates the content item's properties pass validation rules - /// + /// public bool IsPropertyDataValid(IContent content, out IProperty[] invalidProperties, CultureImpact impact) { // select invalid properties @@ -50,9 +48,7 @@ namespace Umbraco.Core.Services return invalidProperties.Length == 0; } - /// - /// Gets a value indicating whether the property has valid values. - /// + /// public bool IsPropertyValid(IProperty property, string culture = "*", string segment = "*") { //NOTE - the pvalue and vvalues logic in here is borrowed directly from the Property.Values setter so if you are wondering what that's all about, look there. diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 056e1a086b..a52d18b7e3 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -235,7 +235,7 @@ - + @@ -793,7 +793,6 @@ - @@ -801,7 +800,6 @@ - diff --git a/src/Umbraco.Tests/TestHelpers/TestObjects.cs b/src/Umbraco.Tests/TestHelpers/TestObjects.cs index 9cc80dc891..ac14ba3fbb 100644 --- a/src/Umbraco.Tests/TestHelpers/TestObjects.cs +++ b/src/Umbraco.Tests/TestHelpers/TestObjects.cs @@ -152,10 +152,13 @@ namespace Umbraco.Tests.TestHelpers var runtimeState = Mock.Of(); var idkMap = new IdkMap(scopeProvider); + var propertyEditorCollection = new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty())); + var localizationService = GetLazyService(factory, c => new LocalizationService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), GetRepo(c), GetRepo(c))); var userService = GetLazyService(factory, c => new UserService(scopeProvider, logger, eventMessagesFactory, runtimeState, GetRepo(c), GetRepo(c),globalSettings)); var dataTypeService = GetLazyService(factory, c => new DataTypeService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c))); - var contentService = GetLazyService(factory, c => new ContentService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c))); + var propertyValidationService = new Lazy(() => new PropertyValidationService(propertyEditorCollection, dataTypeService.Value)); + var contentService = GetLazyService(factory, c => new ContentService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), propertyValidationService)); var notificationService = GetLazyService(factory, c => new NotificationService(scopeProvider, userService.Value, contentService.Value, localizationService.Value, logger, ioHelper, GetRepo(c), globalSettings, umbracoSettings.Content)); var serverRegistrationService = GetLazyService(factory, c => new ServerRegistrationService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); var memberGroupService = GetLazyService(factory, c => new MemberGroupService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); @@ -170,8 +173,7 @@ namespace Umbraco.Tests.TestHelpers var macroService = GetLazyService(factory, c => new MacroService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), GetRepo(c))); var packagingService = GetLazyService(factory, c => - { - var propertyEditorCollection = new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty())); + { var compiledPackageXmlParser = new CompiledPackageXmlParser(new ConflictingPackageData(macroService.Value, fileService.Value), globalSettings); return new PackagingService( auditService.Value, diff --git a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs index 44f19f4540..a321ce90cc 100644 --- a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs +++ b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs @@ -42,6 +42,8 @@ using Umbraco.Web.Composing.CompositionExtensions; using Umbraco.Web.Sections; using Current = Umbraco.Core.Composing.Current; using FileSystems = Umbraco.Core.IO.FileSystems; +using Umbraco.Web.Dictionary; +using Umbraco.Core.Dictionary; namespace Umbraco.Tests.Testing { @@ -131,7 +133,7 @@ namespace Umbraco.Tests.Testing // get/merge the attributes marking the method and/or the classes Options = TestOptionAttributeBase.GetTestOptions(); - // FIXME: align to runtimes & components - don't redo everything here + // FIXME: align to runtimes & components - don't redo everything here !!!! Yes this is getting painful var (logger, profiler) = GetLoggers(Options.Logger); var proflogger = new ProfilingLogger(logger, profiler); @@ -233,7 +235,8 @@ namespace Umbraco.Tests.Testing Composition.RegisterUnique(); Composition.RegisterUnique(); Composition.RegisterUnique(); - + Composition.SetCultureDictionaryFactory(); + Composition.Register(f => f.GetInstance().CreateDictionary(), Lifetime.Singleton); // register back office sections in the order we want them rendered Composition.WithCollectionBuilder().Append() .Append() diff --git a/src/Umbraco.Tests/Web/Controllers/ContentControllerTests.cs b/src/Umbraco.Tests/Web/Controllers/ContentControllerTests.cs index d77867152a..83d9623f14 100644 --- a/src/Umbraco.Tests/Web/Controllers/ContentControllerTests.cs +++ b/src/Umbraco.Tests/Web/Controllers/ContentControllerTests.cs @@ -21,8 +21,6 @@ using Umbraco.Tests.Testing; using Umbraco.Web; using Umbraco.Web.Editors; using Umbraco.Web.Models.ContentEditing; -using Umbraco.Web.PublishedCache; - using Task = System.Threading.Tasks.Task; using Umbraco.Core.Dictionary; using Umbraco.Web.PropertyEditors; @@ -260,6 +258,7 @@ namespace Umbraco.Tests.Web.Controllers var propertyEditorCollection = new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty())); var controller = new ContentController( + Factory.GetInstance(), propertyEditorCollection, Factory.GetInstance(), umbracoContextAccessor, @@ -293,6 +292,7 @@ namespace Umbraco.Tests.Web.Controllers { var propertyEditorCollection = new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty())); var controller = new ContentController( + Factory.GetInstance(), propertyEditorCollection, Factory.GetInstance(), umbracoContextAccessor, @@ -334,6 +334,7 @@ namespace Umbraco.Tests.Web.Controllers var propertyEditorCollection = new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty())); var controller = new ContentController( + Factory.GetInstance(), propertyEditorCollection, Factory.GetInstance(), umbracoContextAccessor, @@ -380,6 +381,7 @@ namespace Umbraco.Tests.Web.Controllers var propertyEditorCollection = new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty())); var controller = new ContentController( + Factory.GetInstance(), propertyEditorCollection, Factory.GetInstance(), umbracoContextAccessor, @@ -418,6 +420,7 @@ namespace Umbraco.Tests.Web.Controllers var propertyEditorCollection = new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty())); var controller = new ContentController( + Factory.GetInstance(), propertyEditorCollection, Factory.GetInstance(), umbracoContextAccessor, @@ -462,6 +465,7 @@ namespace Umbraco.Tests.Web.Controllers var propertyEditorCollection = new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty())); var controller = new ContentController( + Factory.GetInstance(), propertyEditorCollection, Factory.GetInstance(), umbracoContextAccessor, diff --git a/src/Umbraco.Web/Dictionary/UmbracoCultureDictionary.cs b/src/Umbraco.Web/Dictionary/UmbracoCultureDictionary.cs index 92c1412638..f11b2dcfcb 100644 --- a/src/Umbraco.Web/Dictionary/UmbracoCultureDictionary.cs +++ b/src/Umbraco.Web/Dictionary/UmbracoCultureDictionary.cs @@ -24,22 +24,23 @@ namespace Umbraco.Web.Dictionary private readonly IAppCache _requestCache; private readonly CultureInfo _specificCulture; - public DefaultCultureDictionary() - : this(Current.Services.LocalizationService, Current.AppCaches.RequestCache) - { } - + /// + /// Default constructor which will use the current thread's culture + /// + /// + /// public DefaultCultureDictionary(ILocalizationService localizationService, IAppCache requestCache) { _localizationService = localizationService ?? throw new ArgumentNullException(nameof(localizationService)); _requestCache = requestCache ?? throw new ArgumentNullException(nameof(requestCache)); } - public DefaultCultureDictionary(CultureInfo specificCulture) - : this(Current.Services.LocalizationService, Current.AppCaches.RequestCache) - { - _specificCulture = specificCulture ?? throw new ArgumentNullException(nameof(specificCulture)); - } - + /// + /// Constructor for testing to specify a static culture + /// + /// + /// + /// public DefaultCultureDictionary(CultureInfo specificCulture, ILocalizationService localizationService, IAppCache requestCache) { _localizationService = localizationService ?? throw new ArgumentNullException(nameof(localizationService)); diff --git a/src/Umbraco.Web/Dictionary/UmbracoCultureDictionaryFactory.cs b/src/Umbraco.Web/Dictionary/UmbracoCultureDictionaryFactory.cs index c46132f3c0..d66bd99f83 100644 --- a/src/Umbraco.Web/Dictionary/UmbracoCultureDictionaryFactory.cs +++ b/src/Umbraco.Web/Dictionary/UmbracoCultureDictionaryFactory.cs @@ -1,4 +1,7 @@ -namespace Umbraco.Web.Dictionary +using Umbraco.Core.Cache; +using Umbraco.Core.Services; + +namespace Umbraco.Web.Dictionary { /// /// A culture dictionary factory used to create an Umbraco.Core.Dictionary.ICultureDictionary. @@ -8,9 +11,18 @@ /// internal class DefaultCultureDictionaryFactory : Umbraco.Core.Dictionary.ICultureDictionaryFactory { + private readonly ILocalizationService _localizationService; + private readonly AppCaches _appCaches; + + public DefaultCultureDictionaryFactory(ILocalizationService localizationService, AppCaches appCaches) + { + _localizationService = localizationService; + _appCaches = appCaches; + } + public Umbraco.Core.Dictionary.ICultureDictionary CreateDictionary() { - return new DefaultCultureDictionary(); + return new DefaultCultureDictionary(_localizationService, _appCaches.RequestCache); } } } diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 9c2cc399fa..ed81a96196 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -15,7 +15,6 @@ using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Models.Mapping; @@ -37,6 +36,7 @@ using Umbraco.Core.Persistence; using Umbraco.Core.Security; using Umbraco.Web.Routing; using Constants = Umbraco.Core.Constants; +using Umbraco.Core.Dictionary; namespace Umbraco.Web.Editors { @@ -57,8 +57,8 @@ namespace Umbraco.Web.Editors public object Domains { get; private set; } - public ContentController(PropertyEditorCollection propertyEditors, IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper) - : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper) + public ContentController(ICultureDictionary cultureDictionary, PropertyEditorCollection propertyEditors, IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper) + : base(cultureDictionary, globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper) { _propertyEditors = propertyEditors ?? throw new ArgumentNullException(nameof(propertyEditors)); _allLangs = new Lazy>(() => Services.LocalizationService.GetAllLanguages().ToDictionary(x => x.IsoCode, x => x, StringComparer.InvariantCultureIgnoreCase)); @@ -357,10 +357,10 @@ namespace Umbraco.Web.Editors var emptyContent = Services.ContentService.Create("", parentId, contentType.Alias, Security.GetUserId().ResultOr(0)); var mapped = MapToDisplay(emptyContent); // translate the content type name if applicable - mapped.ContentTypeName = Services.TextService.UmbracoDictionaryTranslate(mapped.ContentTypeName); + mapped.ContentTypeName = Services.TextService.UmbracoDictionaryTranslate(CultureDictionary, mapped.ContentTypeName); // if your user type doesn't have access to the Settings section it would not get this property mapped if (mapped.DocumentType != null) - mapped.DocumentType.Name = Services.TextService.UmbracoDictionaryTranslate(mapped.DocumentType.Name); + mapped.DocumentType.Name = Services.TextService.UmbracoDictionaryTranslate(CultureDictionary, mapped.DocumentType.Name); //remove the listview app if it exists mapped.ContentApps = mapped.ContentApps.Where(x => x.Alias != "umbListView").ToList(); diff --git a/src/Umbraco.Web/Editors/ContentControllerBase.cs b/src/Umbraco.Web/Editors/ContentControllerBase.cs index 83167eb9ae..610f668997 100644 --- a/src/Umbraco.Web/Editors/ContentControllerBase.cs +++ b/src/Umbraco.Web/Editors/ContentControllerBase.cs @@ -6,6 +6,7 @@ using System.Web.Http; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Configuration; +using Umbraco.Core.Dictionary; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Editors; @@ -26,9 +27,12 @@ namespace Umbraco.Web.Editors [JsonDateTimeFormatAttribute] public abstract class ContentControllerBase : BackOfficeNotificationsController { - protected ContentControllerBase(IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper) + protected ICultureDictionary CultureDictionary { get; } + + protected ContentControllerBase(ICultureDictionary cultureDictionary, IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper) : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper) { + CultureDictionary = cultureDictionary; } protected HttpResponseMessage HandleContentNotFound(object id, bool throwException = true) diff --git a/src/Umbraco.Web/Editors/ContentTypeController.cs b/src/Umbraco.Web/Editors/ContentTypeController.cs index dc95e1e74e..d4e153ea9e 100644 --- a/src/Umbraco.Web/Editors/ContentTypeController.cs +++ b/src/Umbraco.Web/Editors/ContentTypeController.cs @@ -52,14 +52,14 @@ namespace Umbraco.Web.Editors private readonly IScopeProvider _scopeProvider; public ContentTypeController(IEntityXmlSerializer serializer, - ICultureDictionaryFactory cultureDictionaryFactory, + ICultureDictionary cultureDictionary, IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, PropertyEditorCollection propertyEditors, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper, IScopeProvider scopeProvider) - : base(cultureDictionaryFactory, globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper) + : base(cultureDictionary, globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper) { _serializer = serializer; _propertyEditors = propertyEditors; @@ -438,8 +438,8 @@ namespace Umbraco.Web.Editors var localizedTextService = Services.TextService; foreach (var basic in basics) { - basic.Name = localizedTextService.UmbracoDictionaryTranslate(basic.Name); - basic.Description = localizedTextService.UmbracoDictionaryTranslate(basic.Description); + basic.Name = localizedTextService.UmbracoDictionaryTranslate(CultureDictionary, basic.Name); + basic.Description = localizedTextService.UmbracoDictionaryTranslate(CultureDictionary, basic.Description); } //map the blueprints diff --git a/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs b/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs index 2b3990c7a1..138ef7e43d 100644 --- a/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs +++ b/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs @@ -28,16 +28,13 @@ namespace Umbraco.Web.Editors public abstract class ContentTypeControllerBase : UmbracoAuthorizedJsonController where TContentType : class, IContentTypeComposition { - private readonly ICultureDictionaryFactory _cultureDictionaryFactory; - private ICultureDictionary _cultureDictionary; - - protected ContentTypeControllerBase(ICultureDictionaryFactory cultureDictionaryFactory, IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper) + protected ContentTypeControllerBase(ICultureDictionary cultureDictionary, IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper) : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper) { - _cultureDictionaryFactory = cultureDictionaryFactory; + CultureDictionary = cultureDictionary; } - + protected ICultureDictionary CultureDictionary { get; } /// /// Returns the available composite content types for a given content type @@ -551,9 +548,6 @@ namespace Umbraco.Web.Editors forDisplay.Errors = ModelState.ToErrorDictionary(); return new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay)); - } - - private ICultureDictionary CultureDictionary - => _cultureDictionary ?? (_cultureDictionary = _cultureDictionaryFactory.CreateDictionary()); + } } } diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index ba5d64cde7..2f973382b5 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -36,6 +36,7 @@ using Umbraco.Web.ContentApps; using Umbraco.Web.Editors.Binders; using Umbraco.Web.Editors.Filters; using Constants = Umbraco.Core.Constants; +using Umbraco.Core.Dictionary; namespace Umbraco.Web.Editors { @@ -48,8 +49,8 @@ namespace Umbraco.Web.Editors [MediaControllerControllerConfiguration] public class MediaController : ContentControllerBase { - public MediaController(PropertyEditorCollection propertyEditors, IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper) - : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper) + public MediaController(ICultureDictionary cultureDictionary, PropertyEditorCollection propertyEditors, IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper) + : base(cultureDictionary, globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper) { _propertyEditors = propertyEditors ?? throw new ArgumentNullException(nameof(propertyEditors)); } diff --git a/src/Umbraco.Web/Editors/MediaTypeController.cs b/src/Umbraco.Web/Editors/MediaTypeController.cs index c8e3e202ae..f05a840ad6 100644 --- a/src/Umbraco.Web/Editors/MediaTypeController.cs +++ b/src/Umbraco.Web/Editors/MediaTypeController.cs @@ -36,8 +36,8 @@ namespace Umbraco.Web.Editors [MediaTypeControllerControllerConfiguration] public class MediaTypeController : ContentTypeControllerBase { - public MediaTypeController(ICultureDictionaryFactory cultureDictionaryFactory, IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper) - : base(cultureDictionaryFactory, globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper) + public MediaTypeController(ICultureDictionary cultureDictionary, IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper) + : base(cultureDictionary, globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper) { } diff --git a/src/Umbraco.Web/Editors/MemberController.cs b/src/Umbraco.Web/Editors/MemberController.cs index 7fd293d9b5..8bf7f20b38 100644 --- a/src/Umbraco.Web/Editors/MemberController.cs +++ b/src/Umbraco.Web/Editors/MemberController.cs @@ -30,6 +30,7 @@ using Umbraco.Web.ContentApps; using Umbraco.Web.Editors.Binders; using Umbraco.Web.Editors.Filters; using Constants = Umbraco.Core.Constants; +using Umbraco.Core.Dictionary; namespace Umbraco.Web.Editors { @@ -38,12 +39,12 @@ namespace Umbraco.Web.Editors /// access to ALL of the methods on this controller will need access to the member application. /// [PluginController("UmbracoApi")] - [UmbracoApplicationAuthorizeAttribute(Constants.Applications.Members)] + [UmbracoApplicationAuthorize(Constants.Applications.Members)] [OutgoingNoHyphenGuidFormat] public class MemberController : ContentControllerBase { - public MemberController(PropertyEditorCollection propertyEditors, IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper) - : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper) + public MemberController(ICultureDictionary cultureDictionary, PropertyEditorCollection propertyEditors, IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper) + : base(cultureDictionary, globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper) { _propertyEditors = propertyEditors ?? throw new ArgumentNullException(nameof(propertyEditors)); } diff --git a/src/Umbraco.Web/Editors/MemberTypeController.cs b/src/Umbraco.Web/Editors/MemberTypeController.cs index fffead9155..4907fa74b3 100644 --- a/src/Umbraco.Web/Editors/MemberTypeController.cs +++ b/src/Umbraco.Web/Editors/MemberTypeController.cs @@ -29,8 +29,8 @@ namespace Umbraco.Web.Editors [UmbracoTreeAuthorize(new string[] { Constants.Trees.MemberTypes, Constants.Trees.Members})] public class MemberTypeController : ContentTypeControllerBase { - public MemberTypeController(ICultureDictionaryFactory cultureDictionaryFactory, IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper) - : base(cultureDictionaryFactory, globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper) + public MemberTypeController(ICultureDictionary cultureDictionary, IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper) + : base(cultureDictionary, globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper) { } diff --git a/src/Umbraco.Web/Models/Mapping/ContentMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/ContentMapDefinition.cs index d3b4353f2f..fd76ce4943 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentMapDefinition.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using Umbraco.Core; +using Umbraco.Core.Dictionary; using Umbraco.Core.Logging; using Umbraco.Core.Mapping; using Umbraco.Core.Models; @@ -18,6 +19,7 @@ namespace Umbraco.Web.Models.Mapping internal class ContentMapDefinition : IMapDefinition { private readonly CommonMapper _commonMapper; + private readonly ICultureDictionary _cultureDictionary; private readonly ILocalizedTextService _localizedTextService; private readonly IContentService _contentService; private readonly IContentTypeService _contentTypeService; @@ -32,11 +34,12 @@ namespace Umbraco.Web.Models.Mapping private readonly ContentBasicSavedStateMapper _basicStateMapper; private readonly ContentVariantMapper _contentVariantMapper; - public ContentMapDefinition(CommonMapper commonMapper, ILocalizedTextService localizedTextService, IContentService contentService, IContentTypeService contentTypeService, + public ContentMapDefinition(CommonMapper commonMapper, ICultureDictionary cultureDictionary, ILocalizedTextService localizedTextService, IContentService contentService, IContentTypeService contentTypeService, IFileService fileService, IUmbracoContextAccessor umbracoContextAccessor, IPublishedRouter publishedRouter, ILocalizationService localizationService, ILogger logger, IUserService userService) { _commonMapper = commonMapper; + _cultureDictionary = cultureDictionary; _localizedTextService = localizedTextService; _contentService = contentService; _contentTypeService = contentTypeService; @@ -47,7 +50,7 @@ namespace Umbraco.Web.Models.Mapping _logger = logger; _userService = userService; - _tabsAndPropertiesMapper = new TabsAndPropertiesMapper(localizedTextService); + _tabsAndPropertiesMapper = new TabsAndPropertiesMapper(cultureDictionary, localizedTextService); _stateMapper = new ContentSavedStateMapper(); _basicStateMapper = new ContentBasicSavedStateMapper(); _contentVariantMapper = new ContentVariantMapper(_localizationService); @@ -75,7 +78,7 @@ namespace Umbraco.Web.Models.Mapping target.ContentApps = _commonMapper.GetContentApps(source); target.ContentTypeId = source.ContentType.Id; target.ContentTypeAlias = source.ContentType.Alias; - target.ContentTypeName = _localizedTextService.UmbracoDictionaryTranslate(source.ContentType.Name); + target.ContentTypeName = _localizedTextService.UmbracoDictionaryTranslate(_cultureDictionary, source.ContentType.Name); target.DocumentType = _commonMapper.GetContentType(source, context); target.Icon = source.ContentType.Icon; target.Id = source.Id; @@ -231,7 +234,7 @@ namespace Umbraco.Web.Models.Mapping return contentType.AllowedTemplates .Where(t => t.Alias.IsNullOrWhiteSpace() == false && t.Name.IsNullOrWhiteSpace() == false) - .ToDictionary(t => t.Alias, t => _localizedTextService.UmbracoDictionaryTranslate(t.Name)); + .ToDictionary(t => t.Alias, t => _localizedTextService.UmbracoDictionaryTranslate(_cultureDictionary, t.Name)); } private string GetDefaultTemplate(IContent source) diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayMapper.cs index 12278e97ea..e4d7007f73 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayMapper.cs @@ -1,4 +1,5 @@ -using Umbraco.Core.Logging; +using Umbraco.Core.Dictionary; +using Umbraco.Core.Logging; using Umbraco.Core.Mapping; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; @@ -12,11 +13,13 @@ namespace Umbraco.Web.Models.Mapping /// internal class ContentPropertyDisplayMapper : ContentPropertyBasicMapper { + private readonly ICultureDictionary _cultureDictionary; private readonly ILocalizedTextService _textService; - public ContentPropertyDisplayMapper(IDataTypeService dataTypeService, IEntityService entityService, ILocalizedTextService textService, ILogger logger, PropertyEditorCollection propertyEditors) + public ContentPropertyDisplayMapper(ICultureDictionary cultureDictionary, IDataTypeService dataTypeService, IEntityService entityService, ILocalizedTextService textService, ILogger logger, PropertyEditorCollection propertyEditors) : base(dataTypeService, entityService, logger, propertyEditors) { + _cultureDictionary = cultureDictionary; _textService = textService; } public override void Map(IProperty originalProp, ContentPropertyDisplay dest, MapperContext context) @@ -59,8 +62,8 @@ namespace Umbraco.Web.Models.Mapping } //Translate - dest.Label = _textService.UmbracoDictionaryTranslate(dest.Label); - dest.Description = _textService.UmbracoDictionaryTranslate(dest.Description); + dest.Label = _textService.UmbracoDictionaryTranslate(_cultureDictionary, dest.Label); + dest.Description = _textService.UmbracoDictionaryTranslate(_cultureDictionary, dest.Description); } } } diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyMapDefinition.cs index 5d659fbf9e..d55791f068 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyMapDefinition.cs @@ -1,4 +1,5 @@ -using Umbraco.Core.Logging; +using Umbraco.Core.Dictionary; +using Umbraco.Core.Logging; using Umbraco.Core.Mapping; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; @@ -17,11 +18,11 @@ namespace Umbraco.Web.Models.Mapping private readonly ContentPropertyDtoMapper _contentPropertyDtoConverter; private readonly ContentPropertyDisplayMapper _contentPropertyDisplayMapper; - public ContentPropertyMapDefinition(IDataTypeService dataTypeService, IEntityService entityService, ILocalizedTextService textService, ILogger logger, PropertyEditorCollection propertyEditors) + public ContentPropertyMapDefinition(ICultureDictionary cultureDictionary, IDataTypeService dataTypeService, IEntityService entityService, ILocalizedTextService textService, ILogger logger, PropertyEditorCollection propertyEditors) { _contentPropertyBasicConverter = new ContentPropertyBasicMapper(dataTypeService, entityService, logger, propertyEditors); _contentPropertyDtoConverter = new ContentPropertyDtoMapper(dataTypeService, entityService, logger, propertyEditors); - _contentPropertyDisplayMapper = new ContentPropertyDisplayMapper(dataTypeService, entityService, textService, logger, propertyEditors); + _contentPropertyDisplayMapper = new ContentPropertyDisplayMapper(cultureDictionary, dataTypeService, entityService, textService, logger, propertyEditors); } public void DefineMaps(UmbracoMapper mapper) diff --git a/src/Umbraco.Web/Models/Mapping/MediaMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/MediaMapDefinition.cs index 80bdc7ade4..67617807fc 100644 --- a/src/Umbraco.Web/Models/Mapping/MediaMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/MediaMapDefinition.cs @@ -1,6 +1,7 @@ using System.Linq; using Umbraco.Core; using Umbraco.Core.Composing; +using Umbraco.Core.Dictionary; using Umbraco.Core.Logging; using Umbraco.Core.Mapping; using Umbraco.Core.Models; @@ -21,7 +22,7 @@ namespace Umbraco.Web.Models.Mapping private readonly IMediaTypeService _mediaTypeService; private readonly TabsAndPropertiesMapper _tabsAndPropertiesMapper; - public MediaMapDefinition(ILogger logger, CommonMapper commonMapper, IMediaService mediaService, IMediaTypeService mediaTypeService, + public MediaMapDefinition(ICultureDictionary cultureDictionary, ILogger logger, CommonMapper commonMapper, IMediaService mediaService, IMediaTypeService mediaTypeService, ILocalizedTextService localizedTextService) { _logger = logger; @@ -29,7 +30,7 @@ namespace Umbraco.Web.Models.Mapping _mediaService = mediaService; _mediaTypeService = mediaTypeService; - _tabsAndPropertiesMapper = new TabsAndPropertiesMapper(localizedTextService); + _tabsAndPropertiesMapper = new TabsAndPropertiesMapper(cultureDictionary, localizedTextService); } public void DefineMaps(UmbracoMapper mapper) diff --git a/src/Umbraco.Web/Models/Mapping/MemberTabsAndPropertiesMapper.cs b/src/Umbraco.Web/Models/Mapping/MemberTabsAndPropertiesMapper.cs index 4173e9a943..46689de71b 100644 --- a/src/Umbraco.Web/Models/Mapping/MemberTabsAndPropertiesMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/MemberTabsAndPropertiesMapper.cs @@ -10,6 +10,7 @@ using Umbraco.Core.Models.Membership; using Umbraco.Core.Security; using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; +using Umbraco.Core.Dictionary; namespace Umbraco.Web.Models.Mapping { @@ -26,15 +27,13 @@ namespace Umbraco.Web.Models.Mapping private readonly IUmbracoContextAccessor _umbracoContextAccessor; private readonly ILocalizedTextService _localizedTextService; private readonly IMemberTypeService _memberTypeService; - private readonly IMemberService _memberService; private readonly IUserService _userService; - public MemberTabsAndPropertiesMapper(IUmbracoContextAccessor umbracoContextAccessor, ILocalizedTextService localizedTextService, IMemberService memberService, IUserService userService, IMemberTypeService memberTypeService) - : base(localizedTextService) + public MemberTabsAndPropertiesMapper(ICultureDictionary cultureDictionary, IUmbracoContextAccessor umbracoContextAccessor, ILocalizedTextService localizedTextService, IUserService userService, IMemberTypeService memberTypeService) + : base(cultureDictionary, localizedTextService) { _umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor)); _localizedTextService = localizedTextService ?? throw new ArgumentNullException(nameof(localizedTextService)); - _memberService = memberService ?? throw new ArgumentNullException(nameof(memberService)); _userService = userService ?? throw new ArgumentNullException(nameof(userService)); _memberTypeService = memberTypeService ?? throw new ArgumentNullException(nameof(memberTypeService)); } @@ -123,7 +122,7 @@ namespace Umbraco.Web.Models.Mapping { Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}doctype", Label = _localizedTextService.Localize("content/membertype"), - Value = _localizedTextService.UmbracoDictionaryTranslate(member.ContentType.Name), + Value = _localizedTextService.UmbracoDictionaryTranslate(CultureDictionary, member.ContentType.Name), View = Current.PropertyEditors[Constants.PropertyEditors.Aliases.Label].GetValueEditor().View }, GetLoginProperty(_memberTypeService, member, _localizedTextService), diff --git a/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesMapper.cs b/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesMapper.cs index 70a001735a..32432d74b1 100644 --- a/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesMapper.cs @@ -7,22 +7,25 @@ using Umbraco.Core.Models; using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Composing; +using Umbraco.Core.Dictionary; namespace Umbraco.Web.Models.Mapping { internal abstract class TabsAndPropertiesMapper { + protected ICultureDictionary CultureDictionary { get; } protected ILocalizedTextService LocalizedTextService { get; } protected IEnumerable IgnoreProperties { get; set; } - protected TabsAndPropertiesMapper(ILocalizedTextService localizedTextService) + protected TabsAndPropertiesMapper(ICultureDictionary cultureDictionary, ILocalizedTextService localizedTextService) { + CultureDictionary = cultureDictionary ?? throw new ArgumentNullException(nameof(cultureDictionary)); LocalizedTextService = localizedTextService ?? throw new ArgumentNullException(nameof(localizedTextService)); IgnoreProperties = new List(); } - protected TabsAndPropertiesMapper(ILocalizedTextService localizedTextService, IEnumerable ignoreProperties) - : this(localizedTextService) + protected TabsAndPropertiesMapper(ICultureDictionary cultureDictionary, ILocalizedTextService localizedTextService, IEnumerable ignoreProperties) + : this(cultureDictionary, localizedTextService) { IgnoreProperties = ignoreProperties ?? throw new ArgumentNullException(nameof(ignoreProperties)); } @@ -115,8 +118,8 @@ namespace Umbraco.Web.Models.Mapping internal class TabsAndPropertiesMapper : TabsAndPropertiesMapper where TSource : IContentBase { - public TabsAndPropertiesMapper(ILocalizedTextService localizedTextService) - : base(localizedTextService) + public TabsAndPropertiesMapper(ICultureDictionary cultureDictionary, ILocalizedTextService localizedTextService) + : base(cultureDictionary, localizedTextService) { } public virtual IEnumerable> Map(TSource source, MapperContext context) @@ -159,7 +162,7 @@ namespace Umbraco.Web.Models.Mapping { Id = groupId, Alias = groupName, - Label = LocalizedTextService.UmbracoDictionaryTranslate(groupName), + Label = LocalizedTextService.UmbracoDictionaryTranslate(CultureDictionary, groupName), Properties = mappedProperties, IsActive = false }); diff --git a/src/Umbraco.Web/Runtime/WebFinalComposer.cs b/src/Umbraco.Web/Runtime/WebFinalComposer.cs index c69ae1af1a..f0178413a0 100644 --- a/src/Umbraco.Web/Runtime/WebFinalComposer.cs +++ b/src/Umbraco.Web/Runtime/WebFinalComposer.cs @@ -1,4 +1,6 @@ -using Umbraco.Core.Composing; +using Umbraco.Core; +using Umbraco.Core.Composing; +using Umbraco.Core.Dictionary; namespace Umbraco.Web.Runtime { @@ -7,5 +9,16 @@ namespace Umbraco.Web.Runtime [ComposeAfter(typeof(IUserComposer))] [ComposeAfter(typeof(ICoreComposer))] public class WebFinalComposer : ComponentComposer - { } + { + + public override void Compose(Composition composition) + { + base.Compose(composition); + + // now that user composers have had a chance to register their own factory, we can add the result of the factory + // to the container. + composition.Register(f => f.GetInstance().CreateDictionary(), Lifetime.Singleton); + } + + } } diff --git a/src/Umbraco.Web/Runtime/WebInitialComposer.cs b/src/Umbraco.Web/Runtime/WebInitialComposer.cs index 87c0f46fba..046b8381d2 100644 --- a/src/Umbraco.Web/Runtime/WebInitialComposer.cs +++ b/src/Umbraco.Web/Runtime/WebInitialComposer.cs @@ -202,7 +202,7 @@ namespace Umbraco.Web.Runtime composition.RegisterUnique(); - composition.RegisterUnique(); + composition.SetCultureDictionaryFactory(); // register *all* checks, except those marked [HideFromTypeFinder] of course composition.WithCollectionBuilder() From 251f1c1f84b9226b65a3c6c7fdb0d69962343825 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 22 Nov 2019 13:15:41 +1100 Subject: [PATCH 03/18] Moves ContentTypeChange --- .../Services/Changes/ContentTypeChange.cs | 0 .../Services/Changes/ContentTypeChangeExtensions.cs | 0 src/Umbraco.Core/Umbraco.Core.csproj | 3 +-- 3 files changed, 1 insertion(+), 2 deletions(-) rename src/{Umbraco.Core => Umbraco.Abstractions}/Services/Changes/ContentTypeChange.cs (100%) rename src/{Umbraco.Core => Umbraco.Abstractions}/Services/Changes/ContentTypeChangeExtensions.cs (100%) diff --git a/src/Umbraco.Core/Services/Changes/ContentTypeChange.cs b/src/Umbraco.Abstractions/Services/Changes/ContentTypeChange.cs similarity index 100% rename from src/Umbraco.Core/Services/Changes/ContentTypeChange.cs rename to src/Umbraco.Abstractions/Services/Changes/ContentTypeChange.cs diff --git a/src/Umbraco.Core/Services/Changes/ContentTypeChangeExtensions.cs b/src/Umbraco.Abstractions/Services/Changes/ContentTypeChangeExtensions.cs similarity index 100% rename from src/Umbraco.Core/Services/Changes/ContentTypeChangeExtensions.cs rename to src/Umbraco.Abstractions/Services/Changes/ContentTypeChangeExtensions.cs diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index a52d18b7e3..00f9032bd7 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -233,8 +233,6 @@ - - @@ -828,5 +826,6 @@ Umbraco.Abstractions + \ No newline at end of file From 310597ce27923401289daf3c0f50d181cd455e78 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 22 Nov 2019 13:25:37 +1100 Subject: [PATCH 04/18] fix build --- src/Umbraco.Abstractions/Services/Changes/ContentTypeChange.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Abstractions/Services/Changes/ContentTypeChange.cs b/src/Umbraco.Abstractions/Services/Changes/ContentTypeChange.cs index 57e1d288e4..366a609418 100644 --- a/src/Umbraco.Abstractions/Services/Changes/ContentTypeChange.cs +++ b/src/Umbraco.Abstractions/Services/Changes/ContentTypeChange.cs @@ -15,7 +15,7 @@ namespace Umbraco.Core.Services.Changes public TItem Item { get; } - public ContentTypeChangeTypes ChangeTypes { get; internal set; } + public ContentTypeChangeTypes ChangeTypes { get; set; } public EventArgs ToEventArgs(ContentTypeChange change) { From 15db9ecbbfede6e191c2e8d7bae93b77d9792799 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 22 Nov 2019 13:32:37 +1100 Subject: [PATCH 05/18] start the process of removing membership providers --- .../Models/Membership/MemberCountType.cs | 1 - .../Services/Implement/MemberService.cs | 9 +------- .../Services/Implement/UserService.cs | 23 +------------------ .../Services/MemberServiceTests.cs | 17 -------------- .../Providers/UmbracoMembershipProvider.cs | 2 +- 5 files changed, 3 insertions(+), 49 deletions(-) diff --git a/src/Umbraco.Abstractions/Models/Membership/MemberCountType.cs b/src/Umbraco.Abstractions/Models/Membership/MemberCountType.cs index dffa4766dc..233b5c2424 100644 --- a/src/Umbraco.Abstractions/Models/Membership/MemberCountType.cs +++ b/src/Umbraco.Abstractions/Models/Membership/MemberCountType.cs @@ -6,7 +6,6 @@ public enum MemberCountType { All, - Online, LockedOut, Approved } diff --git a/src/Umbraco.Core/Services/Implement/MemberService.cs b/src/Umbraco.Core/Services/Implement/MemberService.cs index fa45771e6f..8bba46ed63 100644 --- a/src/Umbraco.Core/Services/Implement/MemberService.cs +++ b/src/Umbraco.Core/Services/Implement/MemberService.cs @@ -1,15 +1,12 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Web.Security; using Umbraco.Core.Composing; using Umbraco.Core.Events; using Umbraco.Core.Exceptions; -using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Scoping; @@ -71,11 +68,7 @@ namespace Umbraco.Core.Services.Implement { case MemberCountType.All: query = Query(); - break; - case MemberCountType.Online: - var fromDate = DateTime.Now.AddMinutes(-Membership.UserIsOnlineTimeWindow); - query = Query().Where(x => ((Member) x).PropertyTypeAlias == Constants.Conventions.Member.LastLoginDate && ((Member) x).DateTimePropertyValue > fromDate); - break; + break; case MemberCountType.LockedOut: query = Query().Where(x => ((Member) x).PropertyTypeAlias == Constants.Conventions.Member.IsLockedOut && ((Member) x).BoolPropertyValue); break; diff --git a/src/Umbraco.Core/Services/Implement/UserService.cs b/src/Umbraco.Core/Services/Implement/UserService.cs index e78cf9bc3a..6569ef03e7 100644 --- a/src/Umbraco.Core/Services/Implement/UserService.cs +++ b/src/Umbraco.Core/Services/Implement/UserService.cs @@ -1,9 +1,6 @@ using System; using System.Collections.Generic; -using System.ComponentModel; using System.Data.Common; -using System.Data.SqlClient; -using System.Data.SqlServerCe; using System.Globalization; using System.Linq; using System.Linq.Expressions; @@ -12,12 +9,10 @@ using Umbraco.Core.Events; using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; using Umbraco.Core.Models.Membership; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.Repositories.Implement; using Umbraco.Core.Scoping; -using Umbraco.Core.Security; namespace Umbraco.Core.Services.Implement { @@ -479,23 +474,7 @@ namespace Umbraco.Core.Services.Implement { case MemberCountType.All: query = Query(); - break; - case MemberCountType.Online: - throw new NotImplementedException(); - //var fromDate = DateTime.Now.AddMinutes(-Membership.UserIsOnlineTimeWindow); - //query = - // Query.Builder.Where( - // x => - // ((Member)x).PropertyTypeAlias == Constants.Conventions.Member.LastLoginDate && - // ((Member)x).DateTimePropertyValue > fromDate); - //return repository.GetCountByQuery(query); - //var fromDate = DateTime.Now.AddMinutes(-Membership.UserIsOnlineTimeWindow); - //query = - // Query.Builder.Where( - // x => - // ((Member)x).PropertyTypeAlias == Constants.Conventions.Member.LastLoginDate && - // ((Member)x).DateTimePropertyValue > fromDate); - //return repository.GetCountByQuery(query); + break; case MemberCountType.LockedOut: query = Query().Where(x => x.IsLockedOut); break; diff --git a/src/Umbraco.Tests/Services/MemberServiceTests.cs b/src/Umbraco.Tests/Services/MemberServiceTests.cs index 7bcd3790d4..90a0a78def 100644 --- a/src/Umbraco.Tests/Services/MemberServiceTests.cs +++ b/src/Umbraco.Tests/Services/MemberServiceTests.cs @@ -1118,23 +1118,6 @@ namespace Umbraco.Tests.Services Assert.AreEqual(11, found); } - [Test] - public void Count_All_Online_Members() - { - IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); - ServiceContext.MemberTypeService.Save(memberType); - var members = MockedMember.CreateSimpleMember(memberType, 10, (i, member) => member.LastLoginDate = DateTime.Now.AddMinutes(i * -2)); - ServiceContext.MemberService.Save(members); - - var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hello"); - customMember.SetValue(Constants.Conventions.Member.LastLoginDate, DateTime.Now); - ServiceContext.MemberService.Save(customMember); - - var found = ServiceContext.MemberService.GetCount(MemberCountType.Online); - - Assert.AreEqual(9, found); - } - [Test] public void Count_All_Locked_Members() { diff --git a/src/Umbraco.Web/Security/Providers/UmbracoMembershipProvider.cs b/src/Umbraco.Web/Security/Providers/UmbracoMembershipProvider.cs index 06b221288c..cb63c5a8cf 100644 --- a/src/Umbraco.Web/Security/Providers/UmbracoMembershipProvider.cs +++ b/src/Umbraco.Web/Security/Providers/UmbracoMembershipProvider.cs @@ -288,7 +288,7 @@ namespace Umbraco.Web.Security.Providers /// public override int GetNumberOfUsersOnline() { - return MemberService.GetCount(MemberCountType.Online); + throw new NotImplementedException(); } /// From 6c5c256e6287b31226cf3acb73926ad73a132e4d Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 22 Nov 2019 13:38:07 +1100 Subject: [PATCH 06/18] ... and down the rabbit hole we go --- .../Security/IUsersMembershipProvider.cs | 10 ---------- .../Security/MembershipProviderExtensions.cs | 10 ---------- src/Umbraco.Core/Umbraco.Core.csproj | 1 - src/Umbraco.Web/Editors/PasswordChanger.cs | 19 +++++++++---------- .../Security/BackOfficeUserManager.cs | 8 -------- .../Providers/UsersMembershipProvider.cs | 2 +- 6 files changed, 10 insertions(+), 40 deletions(-) delete mode 100644 src/Umbraco.Core/Security/IUsersMembershipProvider.cs diff --git a/src/Umbraco.Core/Security/IUsersMembershipProvider.cs b/src/Umbraco.Core/Security/IUsersMembershipProvider.cs deleted file mode 100644 index 700849aabb..0000000000 --- a/src/Umbraco.Core/Security/IUsersMembershipProvider.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Umbraco.Core.Security -{ - /// - /// A marker interface used internally to identify Umbraco built-in Users membership providers - /// - internal interface IUsersMembershipProvider - { - - } -} diff --git a/src/Umbraco.Core/Security/MembershipProviderExtensions.cs b/src/Umbraco.Core/Security/MembershipProviderExtensions.cs index 367e6f5c5a..a7f2664aa5 100644 --- a/src/Umbraco.Core/Security/MembershipProviderExtensions.cs +++ b/src/Umbraco.Core/Security/MembershipProviderExtensions.cs @@ -141,16 +141,6 @@ namespace Umbraco.Core.Security return currentPrincipal.Identity.Name; } - /// - /// Returns true if the provider specified is a built-in Umbraco users provider - /// - /// - /// - public static bool IsUmbracoUsersProvider(this MembershipProvider membershipProvider) - { - return (membershipProvider is IUsersMembershipProvider); - } - /// /// Returns true if the provider specified is a built-in Umbraco membership provider /// diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 00f9032bd7..dff9d5f987 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -742,7 +742,6 @@ - diff --git a/src/Umbraco.Web/Editors/PasswordChanger.cs b/src/Umbraco.Web/Editors/PasswordChanger.cs index d700d05c8a..171d23f1d9 100644 --- a/src/Umbraco.Web/Editors/PasswordChanger.cs +++ b/src/Umbraco.Web/Editors/PasswordChanger.cs @@ -158,20 +158,19 @@ namespace Umbraco.Web.Editors if (passwordModel == null) throw new ArgumentNullException(nameof(passwordModel)); if (membershipProvider == null) throw new ArgumentNullException(nameof(membershipProvider)); - BackOfficeUserManager backofficeUserManager = null; + var backofficeUserManager = _httpContext.GetOwinContext().GetBackOfficeUserManager(); var userId = -1; - if (membershipProvider.IsUmbracoUsersProvider()) + if (backofficeUserManager == null) { - backofficeUserManager = _httpContext.GetOwinContext().GetBackOfficeUserManager(); - if (backofficeUserManager != null) - { - var profile = _userService.GetProfileByUserName(username); - if (profile != null) - int.TryParse(profile.Id.ToString(), out userId); - } + // should never happen + throw new InvalidOperationException("Could not resolve the back office user manager"); } + var profile = _userService.GetProfileByUserName(username); + if (profile != null) + int.TryParse(profile.Id.ToString(), out userId); + //Are we resetting the password? //This flag indicates that either an admin user is changing another user's password without knowing the original password // or that the password needs to be reset to an auto-generated one. @@ -226,7 +225,7 @@ namespace Umbraco.Web.Editors username, membershipProvider.RequiresQuestionAndAnswer ? passwordModel.Answer : null); - if (membershipProvider.IsUmbracoUsersProvider() && backofficeUserManager != null && userId >= 0) + if (userId >= 0) backofficeUserManager.RaisePasswordResetEvent(userId); //return the generated pword diff --git a/src/Umbraco.Web/Security/BackOfficeUserManager.cs b/src/Umbraco.Web/Security/BackOfficeUserManager.cs index 6205c1705c..c3988451b2 100644 --- a/src/Umbraco.Web/Security/BackOfficeUserManager.cs +++ b/src/Umbraco.Web/Security/BackOfficeUserManager.cs @@ -252,14 +252,6 @@ namespace Umbraco.Web.Security /// protected virtual IPasswordHasher GetDefaultPasswordHasher(MembershipProviderBase provider) { - //if the current user membership provider is unknown (this would be rare), then return the default password hasher - if (provider.IsUmbracoUsersProvider() == false) - return new PasswordHasher(); - - //if the configured provider has legacy features enabled, then return the membership provider password hasher - if (provider.AllowManuallyChangingPassword || provider.DefaultUseLegacyEncoding) - return new MembershipProviderPasswordHasher(provider); - //we can use the user aware password hasher (which will be the default and preferred way) return new UserAwareMembershipProviderPasswordHasher(provider); } diff --git a/src/Umbraco.Web/Security/Providers/UsersMembershipProvider.cs b/src/Umbraco.Web/Security/Providers/UsersMembershipProvider.cs index 0a30b0e10e..5d9fa1d567 100644 --- a/src/Umbraco.Web/Security/Providers/UsersMembershipProvider.cs +++ b/src/Umbraco.Web/Security/Providers/UsersMembershipProvider.cs @@ -15,7 +15,7 @@ namespace Umbraco.Web.Security.Providers /// /// Custom Membership Provider for Umbraco Users (User authentication for Umbraco Backend CMS) /// - public class UsersMembershipProvider : UmbracoMembershipProvider, IUsersMembershipProvider + public class UsersMembershipProvider : UmbracoMembershipProvider { public UsersMembershipProvider() From 1136e15a27684c69429c679968e035a2577caad6 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 25 Nov 2019 21:20:00 +1100 Subject: [PATCH 07/18] kill user membership provider, adds some passwords api abstractions, moves membership provider logic out of Core/services, removes membership scenario and other membership code we don't want anymore. --- .../Configuration/ConfigsExtensions.cs | 3 + .../Configuration/IPasswordConfiguration.cs | 21 ++ .../IUserPasswordConfiguration.cs | 10 + .../Models/Membership/MembershipScenario.cs | 35 -- .../Security/IPasswordGenerator.cs | 17 + .../Security/PasswordGenerator.cs | 154 +++++++++ .../Services/IMemberService.cs | 14 - src/Umbraco.Configuration/ConfigsFactory.cs | 16 + src/Umbraco.Core/Composing/Current.cs | 4 + .../Membership/MembershipUserExtensions.cs | 21 -- .../Repositories/Implement/UserRepository.cs | 31 +- .../Runtime/CoreInitialComposer.cs | 3 + .../Security/BackOfficeUserStore.cs | 10 +- .../Security/ConfiguredPasswordValidator.cs | 20 ++ .../IMembershipProviderPasswordHasher.cs | 12 - .../Security/IUserAwarePasswordHasher.cs | 2 +- .../Security/MembershipProviderBase.cs | 257 +-------------- .../Security/MembershipProviderExtensions.cs | 161 ---------- .../MembershipProviderPasswordHasher.cs | 34 -- .../MembershipProviderPasswordValidator.cs | 38 --- src/Umbraco.Core/Security/PasswordSecurity.cs | 197 ++++++++++++ .../Security/UmbracoMembershipProviderBase.cs | 3 +- ...rdHasher.cs => UserAwarePasswordHasher.cs} | 25 +- .../Services/Implement/MemberService.cs | 37 --- src/Umbraco.Core/Umbraco.Core.csproj | 8 +- .../PublishedMemberCache.cs | 24 +- .../Membership/MembershipProviderBaseTests.cs | 218 +------------ .../UmbracoServiceMembershipProviderTests.cs | 4 +- .../Repositories/UserRepositoryTest.cs | 5 +- .../Routing/RenderRouteHandlerTests.cs | 3 +- .../Security/PasswordSecurityTests.cs | 111 +++++++ .../Services/MemberServiceTests.cs | 41 --- .../TestControllerActivatorBase.cs | 3 +- .../Testing/TestingTests/MockTests.cs | 5 +- src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + .../AuthenticationControllerTests.cs | 5 +- .../Web/Mvc/SurfaceControllerTests.cs | 3 +- .../application/umblogin.directive.js | 2 +- .../src/common/resources/auth.resource.js | 6 +- .../common/overlays/user/user.controller.js | 2 +- .../views/member/member.edit.controller.js | 5 +- .../src/views/users/user.controller.js | 2 +- src/Umbraco.Web.UI/web.Template.Debug.config | 6 + src/Umbraco.Web.UI/web.Template.config | 1 - .../Controllers/UmbProfileController.cs | 7 +- .../Editors/AuthenticationController.cs | 14 +- .../Editors/Binders/MemberBinder.cs | 88 +---- src/Umbraco.Web/Editors/ContentController.cs | 27 +- .../Filters/MemberSaveModelValidator.cs | 3 +- src/Umbraco.Web/Editors/MemberController.cs | 301 ++++-------------- .../Editors/MemberGroupController.cs | 17 +- .../Editors/MemberTypeController.cs | 9 +- src/Umbraco.Web/Editors/PasswordChanger.cs | 24 +- .../Install/InstallSteps/NewInstallStep.cs | 44 +-- src/Umbraco.Web/Macros/MacroRenderer.cs | 9 +- .../MembershipProviderExtensions.cs | 38 --- .../Models/ContentEditing/MemberDisplay.cs | 3 - .../Models/Mapping/MemberMapDefinition.cs | 30 +- .../Mapping/MemberTabsAndPropertiesMapper.cs | 52 +-- .../PasswordConfigurationExtensions.cs | 40 +++ .../PublishedCache/NuCache/MemberCache.cs | 18 +- src/Umbraco.Web/Routing/PublishedRouter.cs | 45 ++- src/Umbraco.Web/Runtime/WebInitialComposer.cs | 2 +- ...eDirectoryBackOfficeUserPasswordChecker.cs | 2 +- .../Security/AppBuilderExtensions.cs | 23 +- .../Security/BackOfficeUserManager.cs | 142 ++++----- src/Umbraco.Web/Security/MembershipHelper.cs | 247 +++++--------- .../Security/MembershipProviderExtensions.cs | 75 +++++ .../Providers/MembersMembershipProvider.cs | 47 +++ .../Providers/UmbracoMembershipProvider.cs | 16 +- .../Providers/UsersMembershipProvider.cs | 177 ---------- src/Umbraco.Web/Security/WebSecurity.cs | 5 +- src/Umbraco.Web/Trees/MemberTreeController.cs | 106 ++---- src/Umbraco.Web/Umbraco.Web.csproj | 4 +- src/Umbraco.Web/UmbracoDefaultOwinStartup.cs | 5 +- 75 files changed, 1138 insertions(+), 2062 deletions(-) create mode 100644 src/Umbraco.Abstractions/Configuration/IPasswordConfiguration.cs create mode 100644 src/Umbraco.Abstractions/Configuration/IUserPasswordConfiguration.cs delete mode 100644 src/Umbraco.Abstractions/Models/Membership/MembershipScenario.cs create mode 100644 src/Umbraco.Abstractions/Security/IPasswordGenerator.cs create mode 100644 src/Umbraco.Abstractions/Security/PasswordGenerator.cs create mode 100644 src/Umbraco.Core/Security/ConfiguredPasswordValidator.cs delete mode 100644 src/Umbraco.Core/Security/IMembershipProviderPasswordHasher.cs delete mode 100644 src/Umbraco.Core/Security/MembershipProviderExtensions.cs delete mode 100644 src/Umbraco.Core/Security/MembershipProviderPasswordHasher.cs delete mode 100644 src/Umbraco.Core/Security/MembershipProviderPasswordValidator.cs create mode 100644 src/Umbraco.Core/Security/PasswordSecurity.cs rename src/Umbraco.Core/Security/{UserAwareMembershipProviderPasswordHasher.cs => UserAwarePasswordHasher.cs} (55%) create mode 100644 src/Umbraco.Tests/Security/PasswordSecurityTests.cs delete mode 100644 src/Umbraco.Web/MembershipProviderExtensions.cs create mode 100644 src/Umbraco.Web/PasswordConfigurationExtensions.cs create mode 100644 src/Umbraco.Web/Security/MembershipProviderExtensions.cs delete mode 100644 src/Umbraco.Web/Security/Providers/UsersMembershipProvider.cs diff --git a/src/Umbraco.Abstractions/Configuration/ConfigsExtensions.cs b/src/Umbraco.Abstractions/Configuration/ConfigsExtensions.cs index 3beb7ee3cd..d83cabdaac 100644 --- a/src/Umbraco.Abstractions/Configuration/ConfigsExtensions.cs +++ b/src/Umbraco.Abstractions/Configuration/ConfigsExtensions.cs @@ -25,6 +25,9 @@ namespace Umbraco.Core public static IUmbracoSettingsSection Settings(this Configs configs) => configs.GetConfig(); + public static IUserPasswordConfiguration UserPasswordConfig(this Configs configs) + => configs.GetConfig(); + public static IHealthChecks HealthChecks(this Configs configs) => configs.GetConfig(); diff --git a/src/Umbraco.Abstractions/Configuration/IPasswordConfiguration.cs b/src/Umbraco.Abstractions/Configuration/IPasswordConfiguration.cs new file mode 100644 index 0000000000..98cd1010c0 --- /dev/null +++ b/src/Umbraco.Abstractions/Configuration/IPasswordConfiguration.cs @@ -0,0 +1,21 @@ +namespace Umbraco.Core.Configuration +{ + + /// + /// Password configuration + /// + public interface IPasswordConfiguration + { + int RequiredLength { get; } + bool RequireNonLetterOrDigit { get; } + bool RequireDigit { get; } + bool RequireLowercase { get; } + bool RequireUppercase { get; } + + bool UseLegacyEncoding { get; } + string HashAlgorithmType { get; } + + // TODO: This doesn't really belong here + int MaxFailedAccessAttemptsBeforeLockout { get; } + } +} diff --git a/src/Umbraco.Abstractions/Configuration/IUserPasswordConfiguration.cs b/src/Umbraco.Abstractions/Configuration/IUserPasswordConfiguration.cs new file mode 100644 index 0000000000..04af69c68c --- /dev/null +++ b/src/Umbraco.Abstractions/Configuration/IUserPasswordConfiguration.cs @@ -0,0 +1,10 @@ +namespace Umbraco.Core.Configuration +{ + /// + /// The password configuration for back office users + /// + public interface IUserPasswordConfiguration : IPasswordConfiguration + { + + } +} diff --git a/src/Umbraco.Abstractions/Models/Membership/MembershipScenario.cs b/src/Umbraco.Abstractions/Models/Membership/MembershipScenario.cs deleted file mode 100644 index 6fe587e1ce..0000000000 --- a/src/Umbraco.Abstractions/Models/Membership/MembershipScenario.cs +++ /dev/null @@ -1,35 +0,0 @@ -namespace Umbraco.Core.Models.Membership -{ - - /// - /// How membership is implemented in the current install. - /// - public enum MembershipScenario - { - //TODO: This will become obsolete when we get asp.net identity members in place - - /// - /// The member is based on the native Umbraco members (IMember + Umbraco membership provider) - /// - /// - /// This supports custom member properties - /// - NativeUmbraco, - - /// - /// The member is based on a custom member provider but it is linked to an IMember - /// - /// - /// This supports custom member properties (but that is not enabled yet) - /// - CustomProviderWithUmbracoLink, - - /// - /// The member is based purely on a custom member provider and is not linked to umbraco data - /// - /// - /// This does not support custom member properties. - /// - StandaloneCustomProvider - } -} diff --git a/src/Umbraco.Abstractions/Security/IPasswordGenerator.cs b/src/Umbraco.Abstractions/Security/IPasswordGenerator.cs new file mode 100644 index 0000000000..3977ec3cf6 --- /dev/null +++ b/src/Umbraco.Abstractions/Security/IPasswordGenerator.cs @@ -0,0 +1,17 @@ +using Umbraco.Core.Configuration; + +namespace Umbraco.Core.Security +{ + /// + /// Generates a password + /// + public interface IPasswordGenerator + { + /// + /// Generates a password + /// + /// + /// + string GeneratePassword(IPasswordConfiguration passwordConfiguration); + } +} diff --git a/src/Umbraco.Abstractions/Security/PasswordGenerator.cs b/src/Umbraco.Abstractions/Security/PasswordGenerator.cs new file mode 100644 index 0000000000..2dca396384 --- /dev/null +++ b/src/Umbraco.Abstractions/Security/PasswordGenerator.cs @@ -0,0 +1,154 @@ +using System; +using System.Linq; +using System.Security.Cryptography; +using Umbraco.Core.Configuration; + +namespace Umbraco.Core.Security +{ + /// + /// Generates a password + /// + /// + /// This uses logic copied from the old MembershipProvider.GeneratePassword logic + /// + public class PasswordGenerator : IPasswordGenerator + { + public string GeneratePassword(IPasswordConfiguration passwordConfiguration) + { + var password = PasswordStore.GeneratePassword( + passwordConfiguration.RequiredLength, + passwordConfiguration.RequireNonLetterOrDigit ? 2 : 0); + + var random = new Random(); + + var passwordChars = password.ToCharArray(); + + if (passwordConfiguration.RequireDigit && passwordChars.ContainsAny(Enumerable.Range(48, 58).Select(x => (char)x))) + password += Convert.ToChar(random.Next(48, 58)); // 0-9 + + if (passwordConfiguration.RequireLowercase && passwordChars.ContainsAny(Enumerable.Range(97, 123).Select(x => (char)x))) + password += Convert.ToChar(random.Next(97, 123)); // a-z + + if (passwordConfiguration.RequireUppercase && passwordChars.ContainsAny(Enumerable.Range(65, 91).Select(x => (char)x))) + password += Convert.ToChar(random.Next(65, 91)); // A-Z + + if (passwordConfiguration.RequireNonLetterOrDigit && passwordChars.ContainsAny(Enumerable.Range(33, 48).Select(x => (char)x))) + password += Convert.ToChar(random.Next(33, 48)); // symbols !"#$%&'()*+,-./ + + return password; + } + + /// + /// Internal class copied from ASP.NET Framework MembershipProvider + /// + /// + /// See https://stackoverflow.com/a/39855417/694494 + https://github.com/Microsoft/referencesource/blob/master/System.Web/Security/Membership.cs + /// + private static class PasswordStore + { + private static readonly char[] Punctuations = "!@#$%^&*()_-+=[{]};:>|./?".ToCharArray(); + private static readonly char[] StartingChars = new char[] { '<', '&' }; + /// Generates a random password of the specified length. + /// A random password of the specified length. + /// The number of characters in the generated password. The length must be between 1 and 128 characters. + /// The minimum number of non-alphanumeric characters (such as @, #, !, %, &, and so on) in the generated password. + /// + /// is less than 1 or greater than 128 -or- is less than 0 or greater than . + public static string GeneratePassword(int length, int numberOfNonAlphanumericCharacters) + { + if (length < 1 || length > 128) + throw new ArgumentException("password length incorrect", nameof(length)); + if (numberOfNonAlphanumericCharacters > length || numberOfNonAlphanumericCharacters < 0) + throw new ArgumentException("min required non alphanumeric characters incorrect", nameof(numberOfNonAlphanumericCharacters)); + string s; + int matchIndex; + do + { + var data = new byte[length]; + var chArray = new char[length]; + var num1 = 0; + new RNGCryptoServiceProvider().GetBytes(data); + for (var index = 0; index < length; ++index) + { + var num2 = (int)data[index] % 87; + if (num2 < 10) + chArray[index] = (char)(48 + num2); + else if (num2 < 36) + chArray[index] = (char)(65 + num2 - 10); + else if (num2 < 62) + { + chArray[index] = (char)(97 + num2 - 36); + } + else + { + chArray[index] = Punctuations[num2 - 62]; + ++num1; + } + } + if (num1 < numberOfNonAlphanumericCharacters) + { + var random = new Random(); + for (var index1 = 0; index1 < numberOfNonAlphanumericCharacters - num1; ++index1) + { + int index2; + do + { + index2 = random.Next(0, length); + } + while (!char.IsLetterOrDigit(chArray[index2])); + chArray[index2] = Punctuations[random.Next(0, Punctuations.Length)]; + } + } + s = new string(chArray); + } + while (IsDangerousString(s, out matchIndex)); + return s; + } + + private static bool IsDangerousString(string s, out int matchIndex) + { + //bool inComment = false; + matchIndex = 0; + + for (var i = 0; ;) + { + + // Look for the start of one of our patterns + var n = s.IndexOfAny(StartingChars, i); + + // If not found, the string is safe + if (n < 0) return false; + + // If it's the last char, it's safe + if (n == s.Length - 1) return false; + + matchIndex = n; + + switch (s[n]) + { + case '<': + // If the < is followed by a letter or '!', it's unsafe (looks like a tag or HTML comment) + if (IsAtoZ(s[n + 1]) || s[n + 1] == '!' || s[n + 1] == '/' || s[n + 1] == '?') return true; + break; + case '&': + // If the & is followed by a #, it's unsafe (e.g. S) + if (s[n + 1] == '#') return true; + break; + } + + // Continue searching + i = n + 1; + } + } + + private static bool IsAtoZ(char c) + { + if ((int)c >= 97 && (int)c <= 122) + return true; + if ((int)c >= 65) + return (int)c <= 90; + return false; + } + } + } +} diff --git a/src/Umbraco.Abstractions/Services/IMemberService.cs b/src/Umbraco.Abstractions/Services/IMemberService.cs index 6f3979f101..1ea3590964 100644 --- a/src/Umbraco.Abstractions/Services/IMemberService.cs +++ b/src/Umbraco.Abstractions/Services/IMemberService.cs @@ -91,20 +91,6 @@ namespace Umbraco.Core.Services /// IMember CreateMemberWithIdentity(string username, string email, string name, IMemberType memberType); - /// - /// This is simply a helper method which essentially just wraps the MembershipProvider's ChangePassword method which can be - /// used during Member creation. - /// - /// This method exists so that Umbraco developers can use one entry point to create/update - /// this will not work for updating members in most cases (depends on your membership provider settings) - /// - /// It is preferred to use the membership APIs for working with passwords, in the near future this method will be obsoleted - /// and the ASP.NET Identity APIs should be used instead. - /// - /// The Member to save the password for - /// The password to encrypt and save - void SavePassword(IMember member, string password); - /// /// Gets the count of Members by an optional MemberType alias /// diff --git a/src/Umbraco.Configuration/ConfigsFactory.cs b/src/Umbraco.Configuration/ConfigsFactory.cs index c016c3171d..cc68549412 100644 --- a/src/Umbraco.Configuration/ConfigsFactory.cs +++ b/src/Umbraco.Configuration/ConfigsFactory.cs @@ -25,10 +25,26 @@ namespace Umbraco.Core.Configuration configs.Add("umbracoConfiguration/settings"); configs.Add("umbracoConfiguration/HealthChecks"); + configs.Add(() => new DefaultUserPasswordConfig()); configs.Add(() => new CoreDebug()); configs.Add(() => new ConnectionStrings()); configs.AddCoreConfigs(_ioHelper); return configs; } } + + // Default/static user password configs + // TODO: Make this configurable somewhere - we've removed membership providers, so could be a section in the umbracosettings.config file? + // keeping in mind that we will also be removing the members membership provider so there will be 2x the same/similar configuration + internal class DefaultUserPasswordConfig : IUserPasswordConfiguration + { + public int RequiredLength => 12; + public bool RequireNonLetterOrDigit => false; + public bool RequireDigit => false; + public bool RequireLowercase => false; + public bool RequireUppercase => false; + public bool UseLegacyEncoding => false; + public string HashAlgorithmType => "HMACSHA256"; + public int MaxFailedAccessAttemptsBeforeLockout => 5; + } } diff --git a/src/Umbraco.Core/Composing/Current.cs b/src/Umbraco.Core/Composing/Current.cs index 3fba0518ff..8b5eab0139 100644 --- a/src/Umbraco.Core/Composing/Current.cs +++ b/src/Umbraco.Core/Composing/Current.cs @@ -11,6 +11,7 @@ using Umbraco.Core.Packaging; using Umbraco.Core.Persistence; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Scoping; +using Umbraco.Core.Security; using Umbraco.Core.Services; using Umbraco.Core.Strings; using Umbraco.Core.Sync; @@ -126,6 +127,9 @@ namespace Umbraco.Core.Composing public static IRuntimeState RuntimeState => Factory.GetInstance(); + public static IPasswordGenerator PasswordGenerator + => Factory.GetInstance(); + public static TypeLoader TypeLoader => Factory.GetInstance(); diff --git a/src/Umbraco.Core/Models/Membership/MembershipUserExtensions.cs b/src/Umbraco.Core/Models/Membership/MembershipUserExtensions.cs index 5100380ead..d8e9558fae 100644 --- a/src/Umbraco.Core/Models/Membership/MembershipUserExtensions.cs +++ b/src/Umbraco.Core/Models/Membership/MembershipUserExtensions.cs @@ -25,26 +25,5 @@ namespace Umbraco.Core.Models.Membership throw new NotImplementedException(); } - private static MembershipScenario? _scenario = null; - /// - /// Returns the currently configured membership scenario for members in umbraco - /// - /// - internal static MembershipScenario GetMembershipScenario(this IMemberTypeService memberTypeService) - { - if (_scenario.HasValue == false) - { - var provider = MembershipProviderExtensions.GetMembersMembershipProvider(); - if (provider.IsUmbracoMembershipProvider()) - { - return MembershipScenario.NativeUmbraco; - } - var memberType = memberTypeService.Get(Constants.Conventions.MemberTypes.DefaultAlias); - return memberType != null - ? MembershipScenario.CustomProviderWithUmbracoLink - : MembershipScenario.StandaloneCustomProvider; - } - return _scenario.Value; - } } } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs index 96abc37662..757ea6eb30 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Text; -using System.Web.Security; using Newtonsoft.Json; using NPoco; using Umbraco.Core.Cache; @@ -11,13 +10,11 @@ using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Models.Entities; using Umbraco.Core.Models.Membership; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Scoping; -using Umbraco.Core.Security; namespace Umbraco.Core.Persistence.Repositories.Implement { @@ -28,6 +25,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { private readonly IMapperCollection _mapperCollection; private readonly IGlobalSettings _globalSettings; + private readonly IUserPasswordConfiguration _passwordConfiguration; private string _passwordConfigJson; private bool _passwordConfigInitialized; @@ -41,23 +39,17 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// A dictionary specifying the configuration for user passwords. If this is null then no password configuration will be persisted or read. /// /// - public UserRepository(IScopeAccessor scopeAccessor, AppCaches appCaches, ILogger logger, IMapperCollection mapperCollection, IGlobalSettings globalSettings) + public UserRepository(IScopeAccessor scopeAccessor, AppCaches appCaches, ILogger logger, IMapperCollection mapperCollection, IGlobalSettings globalSettings, IUserPasswordConfiguration passwordConfiguration) : base(scopeAccessor, appCaches, logger) { _mapperCollection = mapperCollection; _globalSettings = globalSettings; - } - - // for tests - internal UserRepository(IScopeAccessor scopeAccessor, AppCaches appCaches, ILogger logger, IMapperCollection mapperCollection, IDictionary passwordConfig, IGlobalSettings globalSettings) - : base(scopeAccessor, appCaches, logger) - { - _mapperCollection = mapperCollection; - _globalSettings = globalSettings; - _passwordConfigJson = JsonConvert.SerializeObject(passwordConfig); - _passwordConfigInitialized = true; - } + _passwordConfiguration = passwordConfiguration; + } + /// + /// Returns a serialized dictionary of the password configuration that is stored against the user in the database + /// private string PasswordConfigJson { get @@ -65,14 +57,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (_passwordConfigInitialized) return _passwordConfigJson; - // TODO: this is bad - // because the membership provider we're trying to get has a dependency on the user service - // and we should not depend on services in repositories - need a way better way to do this - - var userMembershipProvider = MembershipProviderExtensions.GetUsersMembershipProvider(); - var passwordConfig = userMembershipProvider == null || userMembershipProvider.PasswordFormat != MembershipPasswordFormat.Hashed - ? null - : new Dictionary { { "hashAlgorithm", Membership.HashAlgorithmType } }; + var passwordConfig = new Dictionary { { "hashAlgorithm", _passwordConfiguration.HashAlgorithmType } }; _passwordConfigJson = passwordConfig == null ? null : JsonConvert.SerializeObject(passwordConfig); _passwordConfigInitialized = true; return _passwordConfigJson; diff --git a/src/Umbraco.Core/Runtime/CoreInitialComposer.cs b/src/Umbraco.Core/Runtime/CoreInitialComposer.cs index 2ab0de14ce..bf28898820 100644 --- a/src/Umbraco.Core/Runtime/CoreInitialComposer.cs +++ b/src/Umbraco.Core/Runtime/CoreInitialComposer.cs @@ -17,6 +17,7 @@ using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.PropertyEditors; using Umbraco.Core.PropertyEditors.Validators; using Umbraco.Core.Scoping; +using Umbraco.Core.Security; using Umbraco.Core.Serialization; using Umbraco.Core.Services; using Umbraco.Core.Strings; @@ -130,6 +131,8 @@ namespace Umbraco.Core.Runtime // by default, register a noop rebuilder composition.RegisterUnique(); + + composition.RegisterUnique(); } } } diff --git a/src/Umbraco.Core/Security/BackOfficeUserStore.cs b/src/Umbraco.Core/Security/BackOfficeUserStore.cs index d48bcc841f..ec2694f80e 100644 --- a/src/Umbraco.Core/Security/BackOfficeUserStore.cs +++ b/src/Umbraco.Core/Security/BackOfficeUserStore.cs @@ -34,32 +34,24 @@ namespace Umbraco.Core.Security //IQueryableUserStore { private readonly IUserService _userService; - private readonly IMemberTypeService _memberTypeService; private readonly IEntityService _entityService; private readonly IExternalLoginService _externalLoginService; private readonly IGlobalSettings _globalSettings; private readonly UmbracoMapper _mapper; private bool _disposed = false; - public BackOfficeUserStore(IUserService userService, IMemberTypeService memberTypeService, IEntityService entityService, IExternalLoginService externalLoginService, IGlobalSettings globalSettings, MembershipProviderBase usersMembershipProvider, UmbracoMapper mapper) + public BackOfficeUserStore(IUserService userService, IEntityService entityService, IExternalLoginService externalLoginService, IGlobalSettings globalSettings, UmbracoMapper mapper) { _userService = userService; - _memberTypeService = memberTypeService; _entityService = entityService; _externalLoginService = externalLoginService; _globalSettings = globalSettings; if (userService == null) throw new ArgumentNullException("userService"); - if (usersMembershipProvider == null) throw new ArgumentNullException("usersMembershipProvider"); if (externalLoginService == null) throw new ArgumentNullException("externalLoginService"); _mapper = mapper; _userService = userService; _externalLoginService = externalLoginService; - - if (usersMembershipProvider.PasswordFormat != MembershipPasswordFormat.Hashed) - { - throw new InvalidOperationException("Cannot use ASP.Net Identity with UmbracoMembersUserStore when the password format is not Hashed"); - } } /// diff --git a/src/Umbraco.Core/Security/ConfiguredPasswordValidator.cs b/src/Umbraco.Core/Security/ConfiguredPasswordValidator.cs new file mode 100644 index 0000000000..15baef1eaf --- /dev/null +++ b/src/Umbraco.Core/Security/ConfiguredPasswordValidator.cs @@ -0,0 +1,20 @@ +using Microsoft.AspNet.Identity; +using Umbraco.Core.Configuration; + +namespace Umbraco.Core.Security +{ + /// + /// Ensure that both the normal password validator rules are processed along with the underlying membership provider rules + /// + public class ConfiguredPasswordValidator : PasswordValidator + { + public ConfiguredPasswordValidator(IPasswordConfiguration config) + { + RequiredLength = config.RequiredLength; + RequireNonLetterOrDigit = config.RequireNonLetterOrDigit; + RequireDigit = config.RequireDigit; + RequireLowercase = config.RequireLowercase; + RequireUppercase = config.RequireUppercase; + } + } +} diff --git a/src/Umbraco.Core/Security/IMembershipProviderPasswordHasher.cs b/src/Umbraco.Core/Security/IMembershipProviderPasswordHasher.cs deleted file mode 100644 index 2333f310bc..0000000000 --- a/src/Umbraco.Core/Security/IMembershipProviderPasswordHasher.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Microsoft.AspNet.Identity; - -namespace Umbraco.Core.Security -{ - /// - /// A password hasher that is based on the rules configured for a membership provider - /// - public interface IMembershipProviderPasswordHasher : IPasswordHasher - { - MembershipProviderBase MembershipProvider { get; } - } -} diff --git a/src/Umbraco.Core/Security/IUserAwarePasswordHasher.cs b/src/Umbraco.Core/Security/IUserAwarePasswordHasher.cs index 7e1321eec5..48a25c0e2b 100644 --- a/src/Umbraco.Core/Security/IUserAwarePasswordHasher.cs +++ b/src/Umbraco.Core/Security/IUserAwarePasswordHasher.cs @@ -8,7 +8,7 @@ namespace Umbraco.Core.Security /// /// /// - public interface IUserAwarePasswordHasher + public interface IUserAwarePasswordHasher : IPasswordHasher where TUser : class, IUser where TKey : IEquatable { diff --git a/src/Umbraco.Core/Security/MembershipProviderBase.cs b/src/Umbraco.Core/Security/MembershipProviderBase.cs index aa0ef43b5c..a07ae8caa5 100644 --- a/src/Umbraco.Core/Security/MembershipProviderBase.cs +++ b/src/Umbraco.Core/Security/MembershipProviderBase.cs @@ -2,7 +2,6 @@ using System.Collections.Specialized; using System.ComponentModel.DataAnnotations; using System.Configuration.Provider; -using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using System.Web; @@ -19,20 +18,6 @@ namespace Umbraco.Core.Security /// public abstract class MembershipProviderBase : MembershipProvider { - - public string HashPasswordForStorage(string password) - { - string salt; - var hashed = EncryptOrHashNewPassword(password, out salt); - return FormatPasswordForStorage(hashed, salt); - } - - public bool VerifyPassword(string password, string hashedPassword) - { - if (string.IsNullOrWhiteSpace(hashedPassword)) throw new ArgumentException("Value cannot be null or whitespace.", "hashedPassword"); - return CheckPassword(password, hashedPassword); - } - /// /// Providers can override this setting, default is 10 /// @@ -91,12 +76,12 @@ namespace Umbraco.Core.Security private string _passwordStrengthRegularExpression; private bool _requiresQuestionAndAnswer; private bool _requiresUniqueEmail; - private string _customHashAlgorithmType ; public bool UseLegacyEncoding { get; private set; } #region Properties + public string CustomHashAlgorithmType { get; private set; } /// /// Indicates whether the membership provider is configured to allow users to reset their passwords. @@ -283,7 +268,7 @@ namespace Umbraco.Core.Security throw ex; } - _customHashAlgorithmType = config.GetValue("hashAlgorithmType", string.Empty); + CustomHashAlgorithmType = config.GetValue("hashAlgorithmType", string.Empty); } /// @@ -546,15 +531,6 @@ namespace Umbraco.Core.Security public override string ResetPassword(string username, string answer) { - var userService = Current.Services.UserService; - - var canReset = this.CanResetPassword(userService); - - if (canReset == false) - { - throw new NotSupportedException("Password reset is not supported"); - } - var newPassword = Membership.GeneratePassword(MinRequiredPasswordLength, MinRequiredNonAlphanumericCharacters); var args = new ValidatePasswordEventArgs(username, newPassword, true); @@ -653,142 +629,11 @@ namespace Umbraco.Core.Security return num; } - /// - /// If the password format is a hashed keyed algorithm then we will pre-pend the salt used to hash the password - /// to the hashed password itself. - /// - /// - /// - /// - protected internal string FormatPasswordForStorage(string pass, string salt) - { - if (UseLegacyEncoding) - { - return pass; - } - - if (PasswordFormat == MembershipPasswordFormat.Hashed) - { - //the better way, we use salt per member - return salt + pass; - } - return pass; - } - internal static bool IsEmailValid(string email) { return new EmailAddressAttribute().IsValid(email); } - protected internal string EncryptOrHashPassword(string pass, string salt) - { - //if we are doing it the old way - - if (UseLegacyEncoding) - { - return LegacyEncodePassword(pass); - } - - //This is the correct way to implement this (as per the sql membership provider) - - if (PasswordFormat == MembershipPasswordFormat.Clear) - return pass; - var bytes = Encoding.Unicode.GetBytes(pass); - var saltBytes = Convert.FromBase64String(salt); - byte[] inArray; - - if (PasswordFormat == MembershipPasswordFormat.Hashed) - { - var hashAlgorithm = GetHashAlgorithm(pass); - var algorithm = hashAlgorithm as KeyedHashAlgorithm; - if (algorithm != null) - { - var keyedHashAlgorithm = algorithm; - if (keyedHashAlgorithm.Key.Length == saltBytes.Length) - { - //if the salt bytes is the required key length for the algorithm, use it as-is - keyedHashAlgorithm.Key = saltBytes; - } - else if (keyedHashAlgorithm.Key.Length < saltBytes.Length) - { - //if the salt bytes is too long for the required key length for the algorithm, reduce it - var numArray2 = new byte[keyedHashAlgorithm.Key.Length]; - Buffer.BlockCopy(saltBytes, 0, numArray2, 0, numArray2.Length); - keyedHashAlgorithm.Key = numArray2; - } - else - { - //if the salt bytes is too short for the required key length for the algorithm, extend it - var numArray2 = new byte[keyedHashAlgorithm.Key.Length]; - var dstOffset = 0; - while (dstOffset < numArray2.Length) - { - var count = Math.Min(saltBytes.Length, numArray2.Length - dstOffset); - Buffer.BlockCopy(saltBytes, 0, numArray2, dstOffset, count); - dstOffset += count; - } - keyedHashAlgorithm.Key = numArray2; - } - inArray = keyedHashAlgorithm.ComputeHash(bytes); - } - else - { - var buffer = new byte[saltBytes.Length + bytes.Length]; - Buffer.BlockCopy(saltBytes, 0, buffer, 0, saltBytes.Length); - Buffer.BlockCopy(bytes, 0, buffer, saltBytes.Length, bytes.Length); - inArray = hashAlgorithm.ComputeHash(buffer); - } - } - else - { - //this code is copied from the sql membership provider - pretty sure this could be nicely re-written to completely - // ignore the salt stuff since we are not salting the password when encrypting. - var password = new byte[saltBytes.Length + bytes.Length]; - Buffer.BlockCopy(saltBytes, 0, password, 0, saltBytes.Length); - Buffer.BlockCopy(bytes, 0, password, saltBytes.Length, bytes.Length); - inArray = EncryptPassword(password, MembershipPasswordCompatibilityMode.Framework40); - } - return Convert.ToBase64String(inArray); - } - - /// - /// Checks the password. - /// - /// The password. - /// The dbPassword. - /// - protected internal bool CheckPassword(string password, string dbPassword) - { - if (string.IsNullOrWhiteSpace(dbPassword)) throw new ArgumentException("Value cannot be null or whitespace.", "dbPassword"); - switch (PasswordFormat) - { - case MembershipPasswordFormat.Encrypted: - var decrypted = DecryptPassword(dbPassword); - return decrypted == password; - case MembershipPasswordFormat.Hashed: - string salt; - var storedHashedPass = StoredPassword(dbPassword, out salt); - var hashed = EncryptOrHashPassword(password, salt); - return storedHashedPass == hashed; - case MembershipPasswordFormat.Clear: - return password == dbPassword; - default: - throw new ArgumentOutOfRangeException(); - } - } - - /// - /// Encrypt/hash a new password with a new salt - /// - /// - /// - /// - protected internal string EncryptOrHashNewPassword(string newPassword, out string salt) - { - salt = GenerateSalt(); - return EncryptOrHashPassword(newPassword, salt); - } - protected internal string DecryptPassword(string pass) { //if we are doing it the old way @@ -813,101 +658,6 @@ namespace Umbraco.Core.Security } } - /// - /// Returns the hashed password without the salt if it is hashed - /// - /// - /// returns the salt - /// - internal string StoredPassword(string storedString, out string salt) - { - if (string.IsNullOrWhiteSpace(storedString)) throw new ArgumentException("Value cannot be null or whitespace.", "storedString"); - if (UseLegacyEncoding) - { - salt = string.Empty; - return storedString; - } - - switch (PasswordFormat) - { - case MembershipPasswordFormat.Hashed: - var saltLen = GenerateSalt(); - salt = storedString.Substring(0, saltLen.Length); - return storedString.Substring(saltLen.Length); - case MembershipPasswordFormat.Clear: - case MembershipPasswordFormat.Encrypted: - default: - salt = string.Empty; - return storedString; - - } - } - - protected internal static string GenerateSalt() - { - var numArray = new byte[16]; - new RNGCryptoServiceProvider().GetBytes(numArray); - return Convert.ToBase64String(numArray); - } - - protected internal HashAlgorithm GetHashAlgorithm(string password) - { - if (UseLegacyEncoding) - { - //before we were never checking for an algorithm type so we were always using HMACSHA1 - // for any SHA specified algorithm :( so we'll need to keep doing that for backwards compat support. - if (Membership.HashAlgorithmType.InvariantContains("SHA")) - { - return new HMACSHA1 - { - //the legacy salt was actually the password :( - Key = Encoding.Unicode.GetBytes(password) - }; - } - } - - //get the algorithm by name - - if (_customHashAlgorithmType.IsNullOrWhiteSpace()) - { - _customHashAlgorithmType = Membership.HashAlgorithmType; - } - - var alg = HashAlgorithm.Create(_customHashAlgorithmType); - if (alg == null) - { - throw new InvalidOperationException("The hash algorithm specified " + Membership.HashAlgorithmType + " cannot be resolved"); - } - - return alg; - } - - /// - /// Encodes the password. - /// - /// The password. - /// The encoded password. - protected string LegacyEncodePassword(string password) - { - string encodedPassword = password; - switch (PasswordFormat) - { - case MembershipPasswordFormat.Clear: - break; - case MembershipPasswordFormat.Encrypted: - encodedPassword = - Convert.ToBase64String(EncryptPassword(Encoding.Unicode.GetBytes(password))); - break; - case MembershipPasswordFormat.Hashed: - var hashAlgorith = GetHashAlgorithm(password); - encodedPassword = Convert.ToBase64String(hashAlgorith.ComputeHash(Encoding.Unicode.GetBytes(password))); - break; - default: - throw new ProviderException("Unsupported password format."); - } - return encodedPassword; - } - /// /// Unencode password. /// @@ -956,9 +706,8 @@ namespace Umbraco.Core.Security /// protected string GetCurrentRequestIpAddress() { - var httpContext = HttpContext.Current == null ? (HttpContextBase) null : new HttpContextWrapper(HttpContext.Current); + var httpContext = HttpContext.Current == null ? (HttpContextBase)null : new HttpContextWrapper(HttpContext.Current); return httpContext.GetCurrentRequestIpAddress(); } - } } diff --git a/src/Umbraco.Core/Security/MembershipProviderExtensions.cs b/src/Umbraco.Core/Security/MembershipProviderExtensions.cs deleted file mode 100644 index a7f2664aa5..0000000000 --- a/src/Umbraco.Core/Security/MembershipProviderExtensions.cs +++ /dev/null @@ -1,161 +0,0 @@ -using System; -using System.Security.Principal; -using System.Threading; -using System.Web; -using System.Web.Hosting; -using System.Web.Security; -using Umbraco.Core.Composing; -using Umbraco.Core.Configuration; -using Umbraco.Core.Models; -using Umbraco.Core.Services; - -namespace Umbraco.Core.Security -{ - public static class MembershipProviderExtensions - { - /// - /// Extension method to check if a password can be reset based on a given provider and the current request (logged in user) - /// - /// - /// - /// - /// - /// An Admin can always reset the password - /// - internal static bool CanResetPassword(this MembershipProvider provider, IUserService userService) - { - if (provider == null) throw new ArgumentNullException("provider"); - - var canReset = provider.EnablePasswordReset; - - if (userService == null) return canReset; - - //we need to check for the special case in which a user is an admin - in which case they can reset the password even if EnablePasswordReset == false - if (provider.EnablePasswordReset == false) - { - var identity = Thread.CurrentPrincipal.GetUmbracoIdentity(); - if (identity != null) - { - var user = userService.GetUserById(identity.Id.TryConvertTo().Result); - if (user == null) throw new InvalidOperationException("No user with username " + identity.Username + " found"); - var userIsAdmin = user.IsAdmin(); - if (userIsAdmin) - { - canReset = true; - } - } - } - return canReset; - } - - internal static MembershipUserCollection FindUsersByName(this MembershipProvider provider, string usernameToMatch) - { - int totalRecords = 0; - return provider.FindUsersByName(usernameToMatch, 0, int.MaxValue, out totalRecords); - } - - internal static MembershipUserCollection FindUsersByEmail(this MembershipProvider provider, string emailToMatch) - { - int totalRecords = 0; - return provider.FindUsersByEmail(emailToMatch, 0, int.MaxValue, out totalRecords); - } - - internal static MembershipUser CreateUser(this MembershipProvider provider, string username, string password, string email) - { - MembershipCreateStatus status; - var user = provider.CreateUser(username, password, email, null, null, true, null, out status); - if (user == null) - throw new MembershipCreateUserException(status); - return user; - } - - /// - /// Method to get the Umbraco Members membership provider based on its alias - /// - /// - public static MembershipProvider GetMembersMembershipProvider() - { - if (Membership.Providers[Constants.Conventions.Member.UmbracoMemberProviderName] == null) - { - throw new InvalidOperationException("No membership provider found with name " + Constants.Conventions.Member.UmbracoMemberProviderName); - } - return Membership.Providers[Constants.Conventions.Member.UmbracoMemberProviderName]; - } - - /// - /// Method to get the Umbraco Users membership provider based on its alias - /// - /// - public static MembershipProvider GetUsersMembershipProvider() - { - if (Membership.Providers[Constants.Security.UserMembershipProviderName] == null) - { - throw new InvalidOperationException("No membership provider found with name " + Constants.Security.UserMembershipProviderName); - } - return Membership.Providers[Constants.Security.UserMembershipProviderName]; - } - - /// - /// Returns the currently logged in MembershipUser and flags them as being online - use sparingly (i.e. login) - /// - /// - /// - public static MembershipUser GetCurrentUserOnline(this MembershipProvider membershipProvider) - { - var username = membershipProvider.GetCurrentUserName(); - return username.IsNullOrWhiteSpace() - ? null - : membershipProvider.GetUser(username, true); - } - - /// - /// Returns the currently logged in MembershipUser - /// - /// - /// - internal static MembershipUser GetCurrentUser(this MembershipProvider membershipProvider) - { - var username = membershipProvider.GetCurrentUserName(); - return username.IsNullOrWhiteSpace() - ? null - : membershipProvider.GetUser(username, false); - } - - /// - /// Just returns the current user's login name (just a wrapper). - /// - /// - /// - internal static string GetCurrentUserName(this MembershipProvider membershipProvider) - { - if (HostingEnvironment.IsHosted) - { - HttpContext current = HttpContext.Current; - if (current != null && current.User != null && current.User.Identity != null) - return current.User.Identity.Name; - } - IPrincipal currentPrincipal = Thread.CurrentPrincipal; - if (currentPrincipal == null || currentPrincipal.Identity == null) - return string.Empty; - else - return currentPrincipal.Identity.Name; - } - - /// - /// Returns true if the provider specified is a built-in Umbraco membership provider - /// - /// - /// - public static bool IsUmbracoMembershipProvider(this MembershipProvider membershipProvider) - { - return (membershipProvider is UmbracoMembershipProviderBase); - } - - // TODO: Add role provider checks too - - public static UmbracoMembershipProviderBase AsUmbracoMembershipProvider(this MembershipProvider membershipProvider) - { - return (UmbracoMembershipProviderBase)membershipProvider; - } - } -} diff --git a/src/Umbraco.Core/Security/MembershipProviderPasswordHasher.cs b/src/Umbraco.Core/Security/MembershipProviderPasswordHasher.cs deleted file mode 100644 index ba2f226750..0000000000 --- a/src/Umbraco.Core/Security/MembershipProviderPasswordHasher.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Microsoft.AspNet.Identity; - -namespace Umbraco.Core.Security -{ - /// - /// A password hasher that conforms to the password hashing done with membership providers - /// - public class MembershipProviderPasswordHasher : IMembershipProviderPasswordHasher - { - /// - /// Exposes the underlying MembershipProvider - /// - public MembershipProviderBase MembershipProvider { get; private set; } - - public MembershipProviderPasswordHasher(MembershipProviderBase provider) - { - MembershipProvider = provider; - } - - public string HashPassword(string password) - { - return MembershipProvider.HashPasswordForStorage(password); - } - - public PasswordVerificationResult VerifyHashedPassword(string hashedPassword, string providedPassword) - { - return MembershipProvider.VerifyPassword(providedPassword, hashedPassword) - ? PasswordVerificationResult.Success - : PasswordVerificationResult.Failed; - } - - - } -} diff --git a/src/Umbraco.Core/Security/MembershipProviderPasswordValidator.cs b/src/Umbraco.Core/Security/MembershipProviderPasswordValidator.cs deleted file mode 100644 index e3448db181..0000000000 --- a/src/Umbraco.Core/Security/MembershipProviderPasswordValidator.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System.Threading.Tasks; -using System.Web.Security; -using Microsoft.AspNet.Identity; - -namespace Umbraco.Core.Security -{ - /// - /// Ensure that both the normal password validator rules are processed along with the underlying membership provider rules - /// - public class MembershipProviderPasswordValidator : PasswordValidator - { - public MembershipProvider Provider { get; private set; } - - public MembershipProviderPasswordValidator(MembershipProvider provider) - { - Provider = provider; - - RequiredLength = Provider.MinRequiredPasswordLength; - RequireNonLetterOrDigit = Provider.MinRequiredNonAlphanumericCharacters > 0; - RequireDigit = false; - RequireLowercase = false; - RequireUppercase = false; - } - - public override async Task ValidateAsync(string item) - { - var result = await base.ValidateAsync(item); - if (result.Succeeded == false) - return result; - var providerValidate = MembershipProviderBase.IsPasswordValid(item, Provider.MinRequiredNonAlphanumericCharacters, Provider.PasswordStrengthRegularExpression, Provider.MinRequiredPasswordLength); - if (providerValidate.Success == false) - { - return IdentityResult.Failed("Could not set password, password rules violated: " + providerValidate.Result); - } - return IdentityResult.Success; - } - } -} diff --git a/src/Umbraco.Core/Security/PasswordSecurity.cs b/src/Umbraco.Core/Security/PasswordSecurity.cs new file mode 100644 index 0000000000..aa332e6e96 --- /dev/null +++ b/src/Umbraco.Core/Security/PasswordSecurity.cs @@ -0,0 +1,197 @@ +using System; +using System.Security.Cryptography; +using System.Text; +using Umbraco.Core.Configuration; + +namespace Umbraco.Core.Security +{ + public class PasswordSecurity + { + private readonly IPasswordConfiguration _passwordConfiguration; + + public PasswordSecurity(IPasswordConfiguration passwordConfiguration) + { + _passwordConfiguration = passwordConfiguration; + } + + public string HashPasswordForStorage(string password) + { + string salt; + var hashed = EncryptOrHashNewPassword(password, out salt); + return FormatPasswordForStorage(hashed, salt); + } + + public bool VerifyPassword(string password, string hashedPassword) + { + if (string.IsNullOrWhiteSpace(hashedPassword)) throw new ArgumentException("Value cannot be null or whitespace.", "hashedPassword"); + return CheckPassword(password, hashedPassword); + } + + /// + /// If the password format is a hashed keyed algorithm then we will pre-pend the salt used to hash the password + /// to the hashed password itself. + /// + /// + /// + /// + public string FormatPasswordForStorage(string pass, string salt) + { + if (_passwordConfiguration.UseLegacyEncoding) + { + return pass; + } + + return salt + pass; + } + + public string EncryptOrHashPassword(string pass, string salt) + { + //if we are doing it the old way + + if (_passwordConfiguration.UseLegacyEncoding) + { + return LegacyEncodePassword(pass); + } + + //This is the correct way to implement this (as per the sql membership provider) + + var bytes = Encoding.Unicode.GetBytes(pass); + var saltBytes = Convert.FromBase64String(salt); + byte[] inArray; + + var hashAlgorithm = GetHashAlgorithm(pass); + var algorithm = hashAlgorithm as KeyedHashAlgorithm; + if (algorithm != null) + { + var keyedHashAlgorithm = algorithm; + if (keyedHashAlgorithm.Key.Length == saltBytes.Length) + { + //if the salt bytes is the required key length for the algorithm, use it as-is + keyedHashAlgorithm.Key = saltBytes; + } + else if (keyedHashAlgorithm.Key.Length < saltBytes.Length) + { + //if the salt bytes is too long for the required key length for the algorithm, reduce it + var numArray2 = new byte[keyedHashAlgorithm.Key.Length]; + Buffer.BlockCopy(saltBytes, 0, numArray2, 0, numArray2.Length); + keyedHashAlgorithm.Key = numArray2; + } + else + { + //if the salt bytes is too short for the required key length for the algorithm, extend it + var numArray2 = new byte[keyedHashAlgorithm.Key.Length]; + var dstOffset = 0; + while (dstOffset < numArray2.Length) + { + var count = Math.Min(saltBytes.Length, numArray2.Length - dstOffset); + Buffer.BlockCopy(saltBytes, 0, numArray2, dstOffset, count); + dstOffset += count; + } + keyedHashAlgorithm.Key = numArray2; + } + inArray = keyedHashAlgorithm.ComputeHash(bytes); + } + else + { + var buffer = new byte[saltBytes.Length + bytes.Length]; + Buffer.BlockCopy(saltBytes, 0, buffer, 0, saltBytes.Length); + Buffer.BlockCopy(bytes, 0, buffer, saltBytes.Length, bytes.Length); + inArray = hashAlgorithm.ComputeHash(buffer); + } + + return Convert.ToBase64String(inArray); + } + + /// + /// Checks the password. + /// + /// The password. + /// The dbPassword. + /// + public bool CheckPassword(string password, string dbPassword) + { + if (string.IsNullOrWhiteSpace(dbPassword)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(dbPassword)); + var storedHashedPass = StoredPassword(dbPassword, out var salt); + var hashed = EncryptOrHashPassword(password, salt); + return storedHashedPass == hashed; + } + + /// + /// Encrypt/hash a new password with a new salt + /// + /// + /// + /// + public string EncryptOrHashNewPassword(string newPassword, out string salt) + { + salt = GenerateSalt(); + return EncryptOrHashPassword(newPassword, salt); + } + + /// + /// Returns the hashed password without the salt if it is hashed + /// + /// + /// returns the salt + /// + public string StoredPassword(string storedString, out string salt) + { + if (string.IsNullOrWhiteSpace(storedString)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(storedString)); + if (_passwordConfiguration.UseLegacyEncoding) + { + salt = string.Empty; + return storedString; + } + + var saltLen = GenerateSalt(); + salt = storedString.Substring(0, saltLen.Length); + return storedString.Substring(saltLen.Length); + } + + public static string GenerateSalt() + { + var numArray = new byte[16]; + new RNGCryptoServiceProvider().GetBytes(numArray); + return Convert.ToBase64String(numArray); + } + + public HashAlgorithm GetHashAlgorithm(string password) + { + if (_passwordConfiguration.UseLegacyEncoding) + { + return new HMACSHA1 + { + //the legacy salt was actually the password :( + Key = Encoding.Unicode.GetBytes(password) + }; + } + + if (_passwordConfiguration.HashAlgorithmType.IsNullOrWhiteSpace()) + throw new InvalidOperationException("No hash algorithm type specified"); + + var alg = HashAlgorithm.Create(_passwordConfiguration.HashAlgorithmType); + if (alg == null) + throw new InvalidOperationException($"The hash algorithm specified {_passwordConfiguration.HashAlgorithmType} cannot be resolved"); + + return alg; + } + + /// + /// Encodes the password. + /// + /// The password. + /// The encoded password. + private string LegacyEncodePassword(string password) + { + string encodedPassword = password; + var hashAlgorith = GetHashAlgorithm(password); + encodedPassword = Convert.ToBase64String(hashAlgorith.ComputeHash(Encoding.Unicode.GetBytes(password))); + return encodedPassword; + } + + + + + + } +} diff --git a/src/Umbraco.Core/Security/UmbracoMembershipProviderBase.cs b/src/Umbraco.Core/Security/UmbracoMembershipProviderBase.cs index 6f88c9bcd5..fd5c68aa51 100644 --- a/src/Umbraco.Core/Security/UmbracoMembershipProviderBase.cs +++ b/src/Umbraco.Core/Security/UmbracoMembershipProviderBase.cs @@ -8,8 +8,7 @@ namespace Umbraco.Core.Security /// public abstract class UmbracoMembershipProviderBase : MembershipProviderBase { - - + public abstract PasswordSecurity PasswordSecurity { get; } public abstract string DefaultMemberTypeAlias { get; } /// diff --git a/src/Umbraco.Core/Security/UserAwareMembershipProviderPasswordHasher.cs b/src/Umbraco.Core/Security/UserAwarePasswordHasher.cs similarity index 55% rename from src/Umbraco.Core/Security/UserAwareMembershipProviderPasswordHasher.cs rename to src/Umbraco.Core/Security/UserAwarePasswordHasher.cs index e09c861caa..66545ab70e 100644 --- a/src/Umbraco.Core/Security/UserAwareMembershipProviderPasswordHasher.cs +++ b/src/Umbraco.Core/Security/UserAwarePasswordHasher.cs @@ -7,24 +7,41 @@ namespace Umbraco.Core.Security /// /// The default password hasher that is User aware so that it can process the hashing based on the user's settings /// - public class UserAwareMembershipProviderPasswordHasher : MembershipProviderPasswordHasher, IUserAwarePasswordHasher + public class UserAwarePasswordHasher : IUserAwarePasswordHasher { - public UserAwareMembershipProviderPasswordHasher(MembershipProviderBase provider) : base(provider) + private readonly PasswordSecurity _passwordSecurity; + + public UserAwarePasswordHasher(PasswordSecurity passwordSecurity) { + _passwordSecurity = passwordSecurity; + } + + public string HashPassword(string password) + { + return _passwordSecurity.HashPasswordForStorage(password); } public string HashPassword(BackOfficeIdentityUser user, string password) { // TODO: Implement the logic for this, we need to lookup the password format for the user and hash accordingly: http://issues.umbraco.org/issue/U4-10089 //NOTE: For now this just falls back to the hashing we are currently using - return base.HashPassword(password); + + return HashPassword(password); + } + + public PasswordVerificationResult VerifyHashedPassword(string hashedPassword, string providedPassword) + { + return _passwordSecurity.VerifyPassword(providedPassword, hashedPassword) + ? PasswordVerificationResult.Success + : PasswordVerificationResult.Failed; } public PasswordVerificationResult VerifyHashedPassword(BackOfficeIdentityUser user, string hashedPassword, string providedPassword) { // TODO: Implement the logic for this, we need to lookup the password format for the user and hash accordingly: http://issues.umbraco.org/issue/U4-10089 //NOTE: For now this just falls back to the hashing we are currently using - return base.VerifyHashedPassword(hashedPassword, providedPassword); + + return VerifyHashedPassword(hashedPassword, providedPassword); } } } diff --git a/src/Umbraco.Core/Services/Implement/MemberService.cs b/src/Umbraco.Core/Services/Implement/MemberService.cs index 8bba46ed63..9f68d86970 100644 --- a/src/Umbraco.Core/Services/Implement/MemberService.cs +++ b/src/Umbraco.Core/Services/Implement/MemberService.cs @@ -10,7 +10,6 @@ using Umbraco.Core.Models.Membership; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Scoping; -using Umbraco.Core.Security; namespace Umbraco.Core.Services.Implement { @@ -26,9 +25,6 @@ namespace Umbraco.Core.Services.Implement private readonly IMemberGroupService _memberGroupService; - //only for unit tests! - internal MembershipProviderBase MembershipProvider { get; set; } - #region Constructor public MemberService(IScopeProvider provider, ILogger logger, IEventMessagesFactory eventMessagesFactory, IMemberGroupService memberGroupService, @@ -1123,39 +1119,6 @@ namespace Umbraco.Core.Services.Implement #region Membership - /// - /// This is simply a helper method which essentially just wraps the MembershipProvider's ChangePassword method - /// - /// This method exists so that Umbraco developers can use one entry point to create/update - /// Members if they choose to. - /// The Member to save the password for - /// The password to encrypt and save - public void SavePassword(IMember member, string password) - { - if (member == null) throw new ArgumentNullException(nameof(member)); - - var provider = MembershipProvider ?? MembershipProviderExtensions.GetMembersMembershipProvider(); - if (provider.IsUmbracoMembershipProvider()) - provider.ChangePassword(member.Username, "", password); // this is actually updating the password - else - throw new NotSupportedException("When using a non-Umbraco membership provider you must change the member password by using the MembershipProvider.ChangePassword method"); - - // go re-fetch the member to update the properties that may have changed - // check that it still exists (optimistic concurrency somehow) - - // re-fetch and ensure it exists - var m = GetByUsername(member.Username); - if (m == null) return; // gone - - // update properties that have changed - member.RawPasswordValue = m.RawPasswordValue; - member.LastPasswordChangeDate = m.LastPasswordChangeDate; - member.UpdateDate = m.UpdateDate; - - // no need to save anything - provider.ChangePassword has done the updates, - // and then all we do is re-fetch to get the updated values, and update the - // in-memory member accordingly - } /// /// A helper method that will create a basic/generic member for use with a generic membership provider diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index dff9d5f987..6c28c9d68b 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -232,6 +232,7 @@ + @@ -738,19 +739,16 @@ - - - - + - + diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedMemberCache.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedMemberCache.cs index 19328c241e..651e65d571 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedMemberCache.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedMemberCache.cs @@ -31,11 +31,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache return _requestCache.GetCacheItem( GetCacheKey("GetByProviderKey", key), () => { - var provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); - if (provider.IsUmbracoMembershipProvider() == false) - { - throw new NotSupportedException("Cannot access this method unless the Umbraco membership provider is active"); - } + var provider = MembershipProviderExtensions.GetMembersMembershipProvider(); var result = _memberService.GetByProviderKey(key); if (result == null) return null; @@ -49,11 +45,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache return _requestCache.GetCacheItem( GetCacheKey("GetById", memberId), () => { - var provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); - if (provider.IsUmbracoMembershipProvider() == false) - { - throw new NotSupportedException("Cannot access this method unless the Umbraco membership provider is active"); - } + var provider = MembershipProviderExtensions.GetMembersMembershipProvider(); var result = _memberService.GetById(memberId); if (result == null) return null; @@ -67,11 +59,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache return _requestCache.GetCacheItem( GetCacheKey("GetByUsername", username), () => { - var provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); - if (provider.IsUmbracoMembershipProvider() == false) - { - throw new NotSupportedException("Cannot access this method unless the Umbraco membership provider is active"); - } + var provider = MembershipProviderExtensions.GetMembersMembershipProvider(); var result = _memberService.GetByUsername(username); if (result == null) return null; @@ -85,11 +73,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache return _requestCache.GetCacheItem( GetCacheKey("GetByEmail", email), () => { - var provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); - if (provider.IsUmbracoMembershipProvider() == false) - { - throw new NotSupportedException("Cannot access this method unless the Umbraco membership provider is active"); - } + var provider = MembershipProviderExtensions.GetMembersMembershipProvider(); var result = _memberService.GetByEmail(email); if (result == null) return null; diff --git a/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs b/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs index 091ea6f9a0..85092d3fdb 100644 --- a/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs +++ b/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs @@ -188,223 +188,9 @@ namespace Umbraco.Tests.Membership Assert.AreEqual(pass, result.Success); } - /// - /// The salt generated is always the same length - /// - [Test] - public void Check_Salt_Length() - { - var lastLength = 0; - for (var i = 0; i < 10000; i++) - { - var result = MembershipProviderBase.GenerateSalt(); - - if (i > 0) - { - Assert.AreEqual(lastLength, result.Length); - } - - lastLength = result.Length; - } - } - - [Test] - public void Get_Stored_Password_Hashed() - { - var providerMock = new Mock() { CallBase = true }; - var provider = providerMock.Object; - provider.Initialize("test", new NameValueCollection { { "passwordFormat", "Hashed" }, { "hashAlgorithmType", "HMACSHA256" } }); - - var salt = MembershipProviderBase.GenerateSalt(); - var stored = salt + "ThisIsAHashedPassword"; - - string initSalt; - var result = provider.StoredPassword(stored, out initSalt); - - Assert.AreEqual("ThisIsAHashedPassword", result); - } - - [Test] - public void Get_Stored_Password_Encrypted() - { - var providerMock = new Mock() { CallBase = true }; - var provider = providerMock.Object; - provider.Initialize("test", new NameValueCollection { { "passwordFormat", "Encrypted" } }); - - var stored = "ThisIsAnEncryptedPassword"; - - string initSalt; - var result = provider.StoredPassword(stored, out initSalt); - - Assert.AreEqual("ThisIsAnEncryptedPassword", result); - } - - [Test] - public void Get_Stored_Password_Clear() - { - var providerMock = new Mock() { CallBase = true }; - var provider = providerMock.Object; - provider.Initialize("test", new NameValueCollection { { "passwordFormat", "Clear" } }); - - var salt = MembershipProviderBase.GenerateSalt(); - var stored = "ThisIsAClearPassword"; - - string initSalt; - var result = provider.StoredPassword(stored, out initSalt); - - Assert.AreEqual("ThisIsAClearPassword", result); - } - - [Test] - public void Format_Pass_For_Storage_Hashed() - { - var providerMock = new Mock() { CallBase = true }; - var provider = providerMock.Object; - provider.Initialize("test", new NameValueCollection { { "passwordFormat", "Hashed" }, { "hashAlgorithmType", "HMACSHA256" } }); - - var salt = MembershipProviderBase.GenerateSalt(); - var stored = "ThisIsAHashedPassword"; - - var result = provider.FormatPasswordForStorage(stored, salt); - - Assert.AreEqual(salt + "ThisIsAHashedPassword", result); - } - - [Test] - public void Format_Pass_For_Storage_Encrypted() - { - var providerMock = new Mock() { CallBase = true }; - var provider = providerMock.Object; - provider.Initialize("test", new NameValueCollection { { "passwordFormat", "Encrypted" } }); - - var salt = MembershipProviderBase.GenerateSalt(); - var stored = "ThisIsAnEncryptedPassword"; - - var result = provider.FormatPasswordForStorage(stored, salt); - - Assert.AreEqual("ThisIsAnEncryptedPassword", result); - } - - [Test] - public void Format_Pass_For_Storage_Clear() - { - var providerMock = new Mock() { CallBase = true }; - var provider = providerMock.Object; - provider.Initialize("test", new NameValueCollection { { "passwordFormat", "Clear" } }); - - var salt = MembershipProviderBase.GenerateSalt(); - var stored = "ThisIsAClearPassword"; - - var result = provider.FormatPasswordForStorage(stored, salt); - - Assert.AreEqual("ThisIsAClearPassword", result); - } - - [Test] - public void Check_Password_Hashed_KeyedHashAlgorithm() - { - var providerMock = new Mock() { CallBase = true }; - var provider = providerMock.Object; - provider.Initialize("test", new NameValueCollection { { "passwordFormat", "Hashed" }, { "hashAlgorithmType", "HMACSHA256" } }); - - string salt; - var pass = "ThisIsAHashedPassword"; - var hashed = provider.EncryptOrHashNewPassword(pass, out salt); - var storedPassword = provider.FormatPasswordForStorage(hashed, salt); - - var result = provider.CheckPassword("ThisIsAHashedPassword", storedPassword); - - Assert.IsTrue(result); - } - - [Test] - public void Check_Password_Hashed_Non_KeyedHashAlgorithm() - { - var providerMock = new Mock() { CallBase = true }; - var provider = providerMock.Object; - provider.Initialize("test", new NameValueCollection { { "passwordFormat", "Hashed" } }); - - string salt; - var pass = "ThisIsAHashedPassword"; - var hashed = provider.EncryptOrHashNewPassword(pass, out salt); - var storedPassword = provider.FormatPasswordForStorage(hashed, salt); - - var result = provider.CheckPassword("ThisIsAHashedPassword", storedPassword); - - Assert.IsTrue(result); - } - - [Test] - public void Check_Password_Encrypted() - { - var providerMock = new Mock() { CallBase = true }; - var provider = providerMock.Object; - provider.Initialize("test", new NameValueCollection { { "passwordFormat", "Encrypted" } }); - - string salt; - var pass = "ThisIsAnEncryptedPassword"; - var encrypted = provider.EncryptOrHashNewPassword(pass, out salt); - - var result = provider.CheckPassword("ThisIsAnEncryptedPassword", encrypted); - - Assert.IsTrue(result); - } - - [Test] - public void Check_Password_Clear() - { - var providerMock = new Mock() { CallBase = true }; - var provider = providerMock.Object; - provider.Initialize("test", new NameValueCollection { { "passwordFormat", "Clear" } }); - - var pass = "ThisIsAClearPassword"; - - var result = provider.CheckPassword("ThisIsAClearPassword", pass); - - Assert.IsTrue(result); - } - - [Test] - public void Can_Decrypt_Password() - { - var providerMock = new Mock() { CallBase = true }; - var provider = providerMock.Object; - provider.Initialize("test", new NameValueCollection { { "passwordFormat", "Encrypted" } }); - - string salt; - var pass = "ThisIsAnEncryptedPassword"; - var encrypted = provider.EncryptOrHashNewPassword(pass, out salt); - - var result = provider.DecryptPassword(encrypted); - - Assert.AreEqual(pass, result); - - } - - [Test] - public void Get_Hash_Algorithm_Legacy() - { - var providerMock = new Mock() { CallBase = true }; - var provider = providerMock.Object; - provider.Initialize("test", new NameValueCollection { {"useLegacyEncoding", "true"}, { "passwordFormat", "Hashed" }, { "hashAlgorithmType", "HMACSHA256" } }); - - var alg = provider.GetHashAlgorithm("blah"); - - Assert.IsTrue(alg is HMACSHA1); - } - - [Test] - public void Get_Hash_Algorithm() - { - var providerMock = new Mock() { CallBase = true }; - var provider = providerMock.Object; - provider.Initialize("test", new NameValueCollection { { "passwordFormat", "Hashed" }, { "hashAlgorithmType", "HMACSHA256" } }); - - var alg = provider.GetHashAlgorithm("blah"); - - Assert.IsTrue(alg is HMACSHA256); - } + + } } diff --git a/src/Umbraco.Tests/Membership/UmbracoServiceMembershipProviderTests.cs b/src/Umbraco.Tests/Membership/UmbracoServiceMembershipProviderTests.cs index c70f304d66..0099f3f9d9 100644 --- a/src/Umbraco.Tests/Membership/UmbracoServiceMembershipProviderTests.cs +++ b/src/Umbraco.Tests/Membership/UmbracoServiceMembershipProviderTests.cs @@ -172,8 +172,8 @@ namespace Umbraco.Tests.Membership Assert.AreNotEqual("test", createdMember.RawPasswordValue); string salt; - var storedPassword = provider.StoredPassword(createdMember.RawPasswordValue, out salt); - var hashedPassword = provider.EncryptOrHashPassword("testtest$1", salt); + var storedPassword = provider.PasswordSecurity.StoredPassword(createdMember.RawPasswordValue, out salt); + var hashedPassword = provider.PasswordSecurity.EncryptOrHashPassword("testtest$1", salt); Assert.AreEqual(hashedPassword, storedPassword); } diff --git a/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs index e856cedbd6..553f77d05b 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs @@ -14,6 +14,7 @@ using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Entities; using Umbraco.Tests.Testing; using Umbraco.Core.Persistence; +using Umbraco.Core.Configuration; namespace Umbraco.Tests.Persistence.Repositories { @@ -54,7 +55,7 @@ namespace Umbraco.Tests.Persistence.Repositories private UserRepository CreateRepository(IScopeProvider provider) { var accessor = (IScopeAccessor) provider; - var repository = new UserRepository(accessor, AppCaches.Disabled, Logger, Mappers, TestObjects.GetGlobalSettings()); + var repository = new UserRepository(accessor, AppCaches.Disabled, Logger, Mappers, TestObjects.GetGlobalSettings(), Mock.Of()); return repository; } @@ -208,7 +209,7 @@ namespace Umbraco.Tests.Persistence.Repositories var id = user.Id; - var repository2 = new UserRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, Mock.Of(),TestObjects.GetGlobalSettings()); + var repository2 = new UserRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, Mock.Of(),TestObjects.GetGlobalSettings(), Mock.Of()); repository2.Delete(user); diff --git a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs index cfc42d3670..e690364dd6 100644 --- a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs +++ b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs @@ -30,6 +30,7 @@ using Umbraco.Web.PublishedCache; using Umbraco.Web.Runtime; using Umbraco.Web.Security; using Current = Umbraco.Web.Composing.Current; +using Umbraco.Web.Security.Providers; namespace Umbraco.Tests.Routing { @@ -147,7 +148,7 @@ namespace Umbraco.Tests.Routing var handler = new RenderRouteHandler(umbracoContext, new TestControllerFactory(umbracoContextAccessor, Mock.Of(), context => { var membershipHelper = new MembershipHelper( - umbracoContext.HttpContext, Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), AppCaches.Disabled, Mock.Of()); + umbracoContext.HttpContext, Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), AppCaches.Disabled, Mock.Of()); return new CustomDocumentController(Factory.GetInstance(), umbracoContextAccessor, Factory.GetInstance(), diff --git a/src/Umbraco.Tests/Security/PasswordSecurityTests.cs b/src/Umbraco.Tests/Security/PasswordSecurityTests.cs new file mode 100644 index 0000000000..ae461fcd86 --- /dev/null +++ b/src/Umbraco.Tests/Security/PasswordSecurityTests.cs @@ -0,0 +1,111 @@ +using Moq; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; +using Umbraco.Core.Configuration; +using Umbraco.Core.Security; + +namespace Umbraco.Tests.Security +{ + [TestFixture] + public class PasswordSecurityTests + { + [Test] + public void Get_Hash_Algorithm_Legacy() + { + var passwordSecurity = new PasswordSecurity(Mock.Of(x => x.UseLegacyEncoding == true && x.HashAlgorithmType == "HMACSHA256")); + var alg = passwordSecurity.GetHashAlgorithm("blah"); + Assert.IsTrue(alg is HMACSHA1); + } + + [Test] + public void Get_Hash_Algorithm_Default() + { + var passwordSecurity = new PasswordSecurity(Mock.Of(x => x.HashAlgorithmType == "HMACSHA256")); + var alg = passwordSecurity.GetHashAlgorithm("blah"); // not resolved + Assert.IsTrue(alg is HMACSHA256); + } + + [Test] + public void Check_Password_Hashed_Non_KeyedHashAlgorithm() + { + var passwordSecurity = new PasswordSecurity(Mock.Of(x => x.HashAlgorithmType == "SHA256")); + + string salt; + var pass = "ThisIsAHashedPassword"; + var hashed = passwordSecurity.EncryptOrHashNewPassword(pass, out salt); + var storedPassword = passwordSecurity.FormatPasswordForStorage(hashed, salt); + + var result = passwordSecurity.CheckPassword("ThisIsAHashedPassword", storedPassword); + + Assert.IsTrue(result); + } + + [Test] + public void Check_Password_Hashed_KeyedHashAlgorithm() + { + var passwordSecurity = new PasswordSecurity(Mock.Of(x => x.HashAlgorithmType == "HMACSHA256")); + + string salt; + var pass = "ThisIsAHashedPassword"; + var hashed = passwordSecurity.EncryptOrHashNewPassword(pass, out salt); + var storedPassword = passwordSecurity.FormatPasswordForStorage(hashed, salt); + + var result = passwordSecurity.CheckPassword("ThisIsAHashedPassword", storedPassword); + + Assert.IsTrue(result); + } + + [Test] + public void Format_Pass_For_Storage_Hashed() + { + var passwordSecurity = new PasswordSecurity(Mock.Of(x => x.HashAlgorithmType == "HMACSHA256")); + + var salt = PasswordSecurity.GenerateSalt(); + var stored = "ThisIsAHashedPassword"; + + var result = passwordSecurity.FormatPasswordForStorage(stored, salt); + + Assert.AreEqual(salt + "ThisIsAHashedPassword", result); + } + + [Test] + public void Get_Stored_Password_Hashed() + { + var passwordSecurity = new PasswordSecurity(Mock.Of(x => x.HashAlgorithmType == "HMACSHA256")); + + var salt = PasswordSecurity.GenerateSalt(); + var stored = salt + "ThisIsAHashedPassword"; + + string initSalt; + var result = passwordSecurity.StoredPassword(stored, out initSalt); + + Assert.AreEqual("ThisIsAHashedPassword", result); + } + + /// + /// The salt generated is always the same length + /// + [Test] + public void Check_Salt_Length() + { + var lastLength = 0; + for (var i = 0; i < 10000; i++) + { + var result = PasswordSecurity.GenerateSalt(); + + if (i > 0) + { + Assert.AreEqual(lastLength, result.Length); + } + + lastLength = result.Length; + } + } + + } +} diff --git a/src/Umbraco.Tests/Services/MemberServiceTests.cs b/src/Umbraco.Tests/Services/MemberServiceTests.cs index 90a0a78def..a0799c6856 100644 --- a/src/Umbraco.Tests/Services/MemberServiceTests.cs +++ b/src/Umbraco.Tests/Services/MemberServiceTests.cs @@ -39,14 +39,6 @@ namespace Umbraco.Tests.Services public override void SetUp() { base.SetUp(); - - // HACK: but we have no choice until we remove the SavePassword method from IMemberService - var providerMock = new Mock(ServiceContext.MemberService, ServiceContext.MemberTypeService, TestHelper.GetUmbracoVersion()) { CallBase = true }; - providerMock.Setup(@base => @base.AllowManuallyChangingPassword).Returns(false); - providerMock.Setup(@base => @base.PasswordFormat).Returns(MembershipPasswordFormat.Hashed); - var provider = providerMock.Object; - - ((MemberService)ServiceContext.MemberService).MembershipProvider = provider; } [Test] @@ -103,39 +95,6 @@ namespace Umbraco.Tests.Services Assert.AreEqual("xemail", email.GetSourceValue()); } - [Test] - public void Can_Set_Password_On_New_Member() - { - IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); - ServiceContext.MemberTypeService.Save(memberType); - //this will construct a member without a password - var member = MockedMember.CreateSimpleMember(memberType, "test", "test@test.com", "test"); - ServiceContext.MemberService.Save(member); - - Assert.IsTrue(member.RawPasswordValue.StartsWith(Constants.Security.EmptyPasswordPrefix)); - - ServiceContext.MemberService.SavePassword(member, "hello123456$!"); - - var foundMember = ServiceContext.MemberService.GetById(member.Id); - Assert.IsNotNull(foundMember); - Assert.AreNotEqual("hello123456$!", foundMember.RawPasswordValue); - Assert.IsFalse(member.RawPasswordValue.StartsWith(Constants.Security.EmptyPasswordPrefix)); - } - - [Test] - public void Can_Not_Set_Password_On_Existing_Member() - { - IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); - ServiceContext.MemberTypeService.Save(memberType); - //this will construct a member with a password - var member = MockedMember.CreateSimpleMember(memberType, "test", "test@test.com", "hello123456$!", "test"); - ServiceContext.MemberService.Save(member); - - Assert.IsFalse(member.RawPasswordValue.StartsWith(Constants.Security.EmptyPasswordPrefix)); - - Assert.Throws(() => ServiceContext.MemberService.SavePassword(member, "HELLO123456$!")); - } - [Test] public void Can_Create_Member() { diff --git a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs index 4b41d4fbc4..3dc3cefeef 100644 --- a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs +++ b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs @@ -22,6 +22,7 @@ using Umbraco.Web.Security; using Umbraco.Web.WebApi; using Umbraco.Core.Logging; using Umbraco.Tests.Testing.Objects.Accessors; +using Umbraco.Web.Security.Providers; namespace Umbraco.Tests.TestHelpers.ControllerTesting { @@ -150,7 +151,7 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting urlHelper.Setup(provider => provider.GetUrl(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(UrlInfo.Url("/hello/world/1234")); - var membershipHelper = new MembershipHelper(umbCtx.HttpContext, Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), AppCaches.Disabled, Mock.Of()); + var membershipHelper = new MembershipHelper(umbCtx.HttpContext, Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), AppCaches.Disabled, Mock.Of()); var umbHelper = new UmbracoHelper(Mock.Of(), Mock.Of(), diff --git a/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs b/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs index 7096158aa8..1c007d1e49 100644 --- a/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs +++ b/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs @@ -22,6 +22,7 @@ using Umbraco.Web; using Umbraco.Web.PublishedCache; using Umbraco.Web.Routing; using Umbraco.Web.Security; +using Umbraco.Web.Security.Providers; using Umbraco.Web.WebApi; using Current = Umbraco.Web.Composing.Current; @@ -70,7 +71,7 @@ namespace Umbraco.Tests.Testing.TestingTests Mock.Of(), Mock.Of(), Mock.Of(), - new MembershipHelper(umbracoContext.HttpContext, Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), AppCaches.Disabled, Mock.Of())); + new MembershipHelper(umbracoContext.HttpContext, Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), AppCaches.Disabled, Mock.Of())); Assert.Pass(); } @@ -98,7 +99,7 @@ namespace Umbraco.Tests.Testing.TestingTests { var umbracoContext = TestObjects.GetUmbracoContextMock(); - var membershipHelper = new MembershipHelper(umbracoContext.HttpContext, Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), AppCaches.Disabled, Mock.Of()); + var membershipHelper = new MembershipHelper(umbracoContext.HttpContext, Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), AppCaches.Disabled, Mock.Of()); var umbracoHelper = new UmbracoHelper(Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), membershipHelper); var umbracoMapper = new UmbracoMapper(new MapDefinitionCollection(new[] { Mock.Of() })); diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 80df5f24c5..e9b6e1f0cd 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -153,6 +153,7 @@ + diff --git a/src/Umbraco.Tests/Web/Controllers/AuthenticationControllerTests.cs b/src/Umbraco.Tests/Web/Controllers/AuthenticationControllerTests.cs index ef28a7fc9d..25885d0f04 100644 --- a/src/Umbraco.Tests/Web/Controllers/AuthenticationControllerTests.cs +++ b/src/Umbraco.Tests/Web/Controllers/AuthenticationControllerTests.cs @@ -18,6 +18,7 @@ using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.IO; using Umbraco.Core.Logging; +using Umbraco.Core.Mapping; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.Querying; @@ -75,6 +76,7 @@ namespace Umbraco.Tests.Web.Controllers } Current.IOHelper.ForceNotHosted = true; var usersController = new AuthenticationController( + new DefaultUserPasswordConfig(), Factory.GetInstance(), umbracoContextAccessor, Factory.GetInstance(), @@ -82,7 +84,8 @@ namespace Umbraco.Tests.Web.Controllers Factory.GetInstance(), Factory.GetInstance(), Factory.GetInstance(), - helper); + helper, + Factory.GetInstance()); return usersController; } diff --git a/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs b/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs index f051d2eef4..c5d904fa24 100644 --- a/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs +++ b/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs @@ -20,6 +20,7 @@ using Umbraco.Web.Mvc; using Umbraco.Web.PublishedCache; using Umbraco.Web.Routing; using Umbraco.Web.Security; +using Umbraco.Web.Security.Providers; using Current = Umbraco.Web.Composing.Current; namespace Umbraco.Tests.Web.Mvc @@ -120,7 +121,7 @@ namespace Umbraco.Tests.Web.Mvc Mock.Of(), Mock.Of(), Mock.Of(query => query.Content(2) == content.Object), - new MembershipHelper(umbracoContext.HttpContext, Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), AppCaches.Disabled, Mock.Of())); + new MembershipHelper(umbracoContext.HttpContext, Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), AppCaches.Disabled, Mock.Of())); var ctrl = new TestSurfaceController(umbracoContextAccessor, helper); var result = ctrl.GetContent(2) as PublishedContentResult; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umblogin.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umblogin.directive.js index 19ebe448e0..c86b32a101 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umblogin.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umblogin.directive.js @@ -90,7 +90,7 @@ $location.search('invite', null); }), //get the membership provider config for password policies - authResource.getMembershipProviderConfig().then(function (data) { + authResource.getPasswordConfig().then(function (data) { vm.invitedUserPasswordModel.passwordPolicies = data; //localize the text diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/auth.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/auth.resource.js index 678cffe42e..a088b8fc73 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/auth.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/auth.resource.js @@ -198,18 +198,18 @@ function authResource($q, $http, umbRequestHelper, angularHelper) { /** * @ngdoc method - * @name umbraco.resources.currentUserResource#getMembershipProviderConfig + * @name umbraco.resources.currentUserResource#getPasswordConfig * @methodOf umbraco.resources.currentUserResource * * @description * Gets the configuration of the user membership provider which is used to configure the change password form */ - getMembershipProviderConfig: function () { + getPasswordConfig: function () { return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl( "authenticationApiBaseUrl", - "GetMembershipProviderConfig")), + "GetPasswordConfig")), 'Failed to retrieve membership provider config'); }, diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.controller.js index 5ed87f073b..d098d3dd7c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.controller.js @@ -122,7 +122,7 @@ angular.module("umbraco") }; //go get the config for the membership provider and add it to the model - authResource.getMembershipProviderConfig().then(function(data) { + authResource.getPasswordConfig().then(function(data) { $scope.changePasswordModel.config = data; //ensure the hasPassword config option is set to true (the user of course has a password already assigned) //this will ensure the oldPassword is shown so they can change it diff --git a/src/Umbraco.Web.UI.Client/src/views/member/member.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/member/member.edit.controller.js index 23d35b6925..d36fd6d853 100644 --- a/src/Umbraco.Web.UI.Client/src/views/member/member.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/member/member.edit.controller.js @@ -122,10 +122,7 @@ function MemberEditController($scope, $routeParams, $location, appState, memberR } function setHeaderNameState(content) { - - if(content.membershipScenario === 0) { - $scope.page.nameLocked = true; - } + $scope.page.nameLocked = true; } /** Just shows a simple notification that there are client side validation issues to be fixed */ diff --git a/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js b/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js index c935852bf8..599775bc5c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js @@ -80,7 +80,7 @@ vm.usernameIsEmail = Umbraco.Sys.ServerVariables.umbracoSettings.usernameIsEmail && user.email === user.username; //go get the config for the membership provider and add it to the model - authResource.getMembershipProviderConfig().then(function (data) { + authResource.getPasswordConfig().then(function (data) { vm.changePasswordModel.config = data; //the user has a password if they are not states: Invited, NoCredentials diff --git a/src/Umbraco.Web.UI/web.Template.Debug.config b/src/Umbraco.Web.UI/web.Template.Debug.config index c7c662a184..78fddac67e 100644 --- a/src/Umbraco.Web.UI/web.Template.Debug.config +++ b/src/Umbraco.Web.UI/web.Template.Debug.config @@ -15,6 +15,12 @@ + + + + + + diff --git a/src/Umbraco.Web.UI/web.Template.config b/src/Umbraco.Web.UI/web.Template.config index 8335b80286..07e0326117 100644 --- a/src/Umbraco.Web.UI/web.Template.config +++ b/src/Umbraco.Web.UI/web.Template.config @@ -121,7 +121,6 @@ - diff --git a/src/Umbraco.Web/Controllers/UmbProfileController.cs b/src/Umbraco.Web/Controllers/UmbProfileController.cs index d9333e8e65..c922b4142f 100644 --- a/src/Umbraco.Web/Controllers/UmbProfileController.cs +++ b/src/Umbraco.Web/Controllers/UmbProfileController.cs @@ -8,6 +8,7 @@ using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Persistence; using Umbraco.Core.Services; +using Umbraco.Web.Security; namespace Umbraco.Web.Controllers { @@ -26,12 +27,6 @@ namespace Umbraco.Web.Controllers [ValidateUmbracoFormRouteString] public ActionResult HandleUpdateProfile([Bind(Prefix = "profileModel")] ProfileModel model) { - var provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); - if (provider.IsUmbracoMembershipProvider() == false) - { - throw new NotSupportedException("Profile editing with the " + typeof(UmbProfileController) + " is not supported when not using the default Umbraco membership provider"); - } - if (ModelState.IsValid == false) { return CurrentUmbracoPage(); diff --git a/src/Umbraco.Web/Editors/AuthenticationController.cs b/src/Umbraco.Web/Editors/AuthenticationController.cs index 42386faa20..2551d33c6e 100644 --- a/src/Umbraco.Web/Editors/AuthenticationController.cs +++ b/src/Umbraco.Web/Editors/AuthenticationController.cs @@ -26,6 +26,7 @@ using Umbraco.Core.Logging; using Umbraco.Core.Persistence; using Umbraco.Web.Composing; using IUser = Umbraco.Core.Models.Membership.IUser; +using Umbraco.Core.Mapping; namespace Umbraco.Web.Editors { @@ -40,10 +41,12 @@ namespace Umbraco.Web.Editors { private BackOfficeUserManager _userManager; private BackOfficeSignInManager _signInManager; + private readonly IUserPasswordConfiguration _passwordConfiguration; - public AuthenticationController(IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper) - : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper) + public AuthenticationController(IUserPasswordConfiguration passwordConfiguration, IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper, UmbracoMapper umbracoMapper) + : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper, umbracoMapper) { + _passwordConfiguration = passwordConfiguration; } protected BackOfficeUserManager UserManager => _userManager @@ -57,12 +60,9 @@ namespace Umbraco.Web.Editors /// /// [WebApi.UmbracoAuthorize(requireApproval: false)] - public IDictionary GetMembershipProviderConfig() + public IDictionary GetPasswordConfig() { - // TODO: Check if the current PasswordValidator is an IMembershipProviderPasswordValidator, if - // it's not than we should return some generic defaults - var provider = Core.Security.MembershipProviderExtensions.GetUsersMembershipProvider(); - return provider.GetConfiguration(Services.UserService); + return _passwordConfiguration.GetConfiguration(); } /// diff --git a/src/Umbraco.Web/Editors/Binders/MemberBinder.cs b/src/Umbraco.Web/Editors/Binders/MemberBinder.cs index 21810272ea..8cc34896ba 100644 --- a/src/Umbraco.Web/Editors/Binders/MemberBinder.cs +++ b/src/Umbraco.Web/Editors/Binders/MemberBinder.cs @@ -12,6 +12,7 @@ using System.Linq; using Umbraco.Core.Models.Membership; using Umbraco.Core.Services.Implement; using Umbraco.Web.Composing; +using Umbraco.Web.Security; namespace Umbraco.Web.Editors.Binders { @@ -66,49 +67,7 @@ namespace Umbraco.Web.Editors.Binders /// private IMember GetExisting(MemberSave model) { - var scenario = _services.MemberTypeService.GetMembershipScenario(); - var provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); - switch (scenario) - { - case MembershipScenario.NativeUmbraco: - return GetExisting(model.Key); - case MembershipScenario.CustomProviderWithUmbracoLink: - case MembershipScenario.StandaloneCustomProvider: - default: - var membershipUser = provider.GetUser(model.Key, false); - if (membershipUser == null) - { - throw new InvalidOperationException("Could not find member with key " + model.Key); - } - - // TODO: Support this scenario! - //if (scenario == MembershipScenario.CustomProviderWithUmbracoLink) - //{ - // //if there's a 'Member' type then we should be able to just go get it from the db since it was created with a link - // // to our data. - // var memberType = ApplicationContext.Services.MemberTypeService.GetMemberType(Constants.Conventions.MemberTypes.Member); - // if (memberType != null) - // { - // var existing = GetExisting(model.Key); - // FilterContentTypeProperties(existing.ContentType, existing.ContentType.PropertyTypes.Select(x => x.Alias).ToArray()); - // } - //} - - //generate a member for a generic membership provider - //NOTE: We don't care about the password here, so just generate something - //var member = MemberService.CreateGenericMembershipProviderMember(model.Name, model.Email, model.Username, Guid.NewGuid().ToString("N")); - - //var convertResult = membershipUser.ProviderUserKey.TryConvertTo(); - //if (convertResult.Success == false) - //{ - // throw new InvalidOperationException("Only membership providers that store a GUID as their ProviderUserKey are supported" + model.Key); - //} - //member.Key = convertResult.Result; - - var member = Current.Mapper.Map(membershipUser); - - return member; - } + return GetExisting(model.Key); } private IMember GetExisting(Guid key) @@ -132,46 +91,17 @@ namespace Umbraco.Web.Editors.Binders /// private IMember CreateNew(MemberSave model) { - var provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); - - if (provider.IsUmbracoMembershipProvider()) + var contentType = _services.MemberTypeService.Get(model.ContentTypeAlias); + if (contentType == null) { - var contentType = _services.MemberTypeService.Get(model.ContentTypeAlias); - if (contentType == null) - { - throw new InvalidOperationException("No member type found with alias " + model.ContentTypeAlias); - } - - //remove all membership properties, these values are set with the membership provider. - FilterMembershipProviderProperties(contentType); - - //return the new member with the details filled in - return new Member(model.Name, model.Email, model.Username, model.Password.NewPassword, contentType); + throw new InvalidOperationException("No member type found with alias " + model.ContentTypeAlias); } - else - { - //A custom membership provider is configured - //NOTE: Below we are assigning the password to just a new GUID because we are not actually storing the password, however that - // field is mandatory in the database so we need to put something there. + //remove all membership properties, these values are set with the membership provider. + FilterMembershipProviderProperties(contentType); - //If the default Member type exists, we'll use that to create the IMember - that way we can associate the custom membership - // provider to our data - eventually we can support editing custom properties with a custom provider. - var memberType = _services.MemberTypeService.Get(Constants.Conventions.MemberTypes.DefaultAlias); - if (memberType != null) - { - FilterContentTypeProperties(memberType, memberType.PropertyTypes.Select(x => x.Alias).ToArray()); - return new Member(model.Name, model.Email, model.Username, Guid.NewGuid().ToString("N"), memberType); - } - - //generate a member for a generic membership provider - var member = MemberService.CreateGenericMembershipProviderMember(model.Name, model.Email, model.Username, Guid.NewGuid().ToString("N")); - //we'll just remove all properties here otherwise we'll end up with validation errors, we don't want to persist any property data anyways - // in this case. - memberType = _services.MemberTypeService.Get(member.ContentTypeId); - FilterContentTypeProperties(memberType, memberType.PropertyTypes.Select(x => x.Alias).ToArray()); - return member; - } + //return the new member with the details filled in + return new Member(model.Name, model.Email, model.Username, model.Password.NewPassword, contentType); } /// diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index ed81a96196..18450f41ae 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -2309,28 +2309,11 @@ namespace Umbraco.Web.Editors .Where(rule => rule.RuleType == Constants.Conventions.PublicAccess.MemberUsernameRuleType) .Select(rule => rule.RuleValue).ToArray(); - MemberDisplay[] members; - switch (Services.MemberTypeService.GetMembershipScenario()) - { - case MembershipScenario.NativeUmbraco: - members = usernames - .Select(username => Services.MemberService.GetByUsername(username)) - .Where(member => member != null) - .Select(Mapper.Map) - .ToArray(); - break; - // TODO: test support custom membership providers - case MembershipScenario.CustomProviderWithUmbracoLink: - case MembershipScenario.StandaloneCustomProvider: - default: - var provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); - members = usernames - .Select(username => provider.GetUser(username, false)) - .Where(membershipUser => membershipUser != null) - .Select(Mapper.Map) - .ToArray(); - break; - } + var members = usernames + .Select(username => Services.MemberService.GetByUsername(username)) + .Where(member => member != null) + .Select(Mapper.Map) + .ToArray(); var allGroups = Services.MemberGroupService.GetAll().ToArray(); var groups = entry.Rules diff --git a/src/Umbraco.Web/Editors/Filters/MemberSaveModelValidator.cs b/src/Umbraco.Web/Editors/Filters/MemberSaveModelValidator.cs index 1d421c756b..f837253b4b 100644 --- a/src/Umbraco.Web/Editors/Filters/MemberSaveModelValidator.cs +++ b/src/Umbraco.Web/Editors/Filters/MemberSaveModelValidator.cs @@ -11,6 +11,7 @@ using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; +using Umbraco.Web.Security; namespace Umbraco.Web.Editors.Filters { @@ -52,7 +53,7 @@ namespace Umbraco.Web.Editors.Filters } //default provider! - var membershipProvider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); + var membershipProvider = MembershipProviderExtensions.GetMembersMembershipProvider(); var validEmail = ValidateUniqueEmail(model, membershipProvider); if (validEmail == false) diff --git a/src/Umbraco.Web/Editors/MemberController.cs b/src/Umbraco.Web/Editors/MemberController.cs index 8bf7f20b38..ad8408e2eb 100644 --- a/src/Umbraco.Web/Editors/MemberController.cs +++ b/src/Umbraco.Web/Editors/MemberController.cs @@ -31,6 +31,7 @@ using Umbraco.Web.Editors.Binders; using Umbraco.Web.Editors.Filters; using Constants = Umbraco.Core.Constants; using Umbraco.Core.Dictionary; +using Umbraco.Web.Security; namespace Umbraco.Web.Editors { @@ -49,17 +50,9 @@ namespace Umbraco.Web.Editors _propertyEditors = propertyEditors ?? throw new ArgumentNullException(nameof(propertyEditors)); } - private readonly MembershipProvider _provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); + private readonly MembershipProvider _provider = MembershipProviderExtensions.GetMembersMembershipProvider(); private readonly PropertyEditorCollection _propertyEditors; - /// - /// Returns the currently configured membership scenario for members in umbraco - /// - /// - protected MembershipScenario MembershipScenario - { - get { return Services.MemberTypeService.GetMembershipScenario(); } - } public PagedResult GetPagedResults( int pageNumber = 1, @@ -76,57 +69,19 @@ namespace Umbraco.Web.Editors throw new NotSupportedException("Both pageNumber and pageSize must be greater than zero"); } - if (MembershipScenario == MembershipScenario.NativeUmbraco) - { - var members = Services.MemberService + var members = Services.MemberService .GetAll((pageNumber - 1), pageSize, out var totalRecords, orderBy, orderDirection, orderBySystemField, memberTypeAlias, filter).ToArray(); - if (totalRecords == 0) - { - return new PagedResult(0, 0, 0); - } - - var pagedResult = new PagedResult(totalRecords, pageNumber, pageSize) - { - Items = members - .Select(x => Mapper.Map(x)) - }; - return pagedResult; - } - else + if (totalRecords == 0) { - int totalRecords; - - MembershipUserCollection members; - if (filter.IsNullOrWhiteSpace()) - { - members = _provider.GetAllUsers((pageNumber - 1), pageSize, out totalRecords); - } - else - { - //we need to search! - - //try by name first - members = _provider.FindUsersByName(filter, (pageNumber - 1), pageSize, out totalRecords); - if (totalRecords == 0) - { - //try by email then - members = _provider.FindUsersByEmail(filter, (pageNumber - 1), pageSize, out totalRecords); - } - } - if (totalRecords == 0) - { - return new PagedResult(0, 0, 0); - } - - var pagedResult = new PagedResult(totalRecords, pageNumber, pageSize) - { - Items = members - .Cast() - .Select(Mapper.Map) - }; - return pagedResult; + return new PagedResult(0, 0, 0); } + var pagedResult = new PagedResult(totalRecords, pageNumber, pageSize) + { + Items = members + .Select(x => Mapper.Map(x)) + }; + return pagedResult; } /// @@ -166,48 +121,12 @@ namespace Umbraco.Web.Editors [OutgoingEditorModelEvent] public MemberDisplay GetByKey(Guid key) { - MembershipUser foundMembershipMember; - MemberDisplay display; - IMember foundMember; - switch (MembershipScenario) + var foundMember = Services.MemberService.GetByKey(key); + if (foundMember == null) { - case MembershipScenario.NativeUmbraco: - foundMember = Services.MemberService.GetByKey(key); - if (foundMember == null) - { - HandleContentNotFound(key); - } - return Mapper.Map(foundMember); - case MembershipScenario.CustomProviderWithUmbracoLink: - - // TODO: Support editing custom properties for members with a custom membership provider here. - - //foundMember = Services.MemberService.GetByKey(key); - //if (foundMember == null) - //{ - // HandleContentNotFound(key); - //} - //foundMembershipMember = Membership.GetUser(key, false); - //if (foundMembershipMember == null) - //{ - // HandleContentNotFound(key); - //} - - //display = Mapper.Map(foundMembershipMember); - ////map the name over - //display.Name = foundMember.Name; - //return display; - - case MembershipScenario.StandaloneCustomProvider: - default: - foundMembershipMember = _provider.GetUser(key, false); - if (foundMembershipMember == null) - { - HandleContentNotFound(key); - } - display = Mapper.Map(foundMembershipMember); - return display; + HandleContentNotFound(key); } + return Mapper.Map(foundMember); } /// @@ -219,35 +138,22 @@ namespace Umbraco.Web.Editors public MemberDisplay GetEmpty(string contentTypeAlias = null) { IMember emptyContent; - switch (MembershipScenario) + if (contentTypeAlias == null) { - case MembershipScenario.NativeUmbraco: - if (contentTypeAlias == null) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - var contentType = Services.MemberTypeService.Get(contentTypeAlias); - if (contentType == null) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - var provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); - - emptyContent = new Member(contentType); - emptyContent.AdditionalData["NewPassword"] = Membership.GeneratePassword(provider.MinRequiredPasswordLength, provider.MinRequiredNonAlphanumericCharacters); - return Mapper.Map(emptyContent); - case MembershipScenario.CustomProviderWithUmbracoLink: - // TODO: Support editing custom properties for members with a custom membership provider here. - - case MembershipScenario.StandaloneCustomProvider: - default: - //we need to return a scaffold of a 'simple' member - basically just what a membership provider can edit - emptyContent = MemberService.CreateGenericMembershipProviderMember("", "", "", ""); - emptyContent.AdditionalData["NewPassword"] = Membership.GeneratePassword(Membership.MinRequiredPasswordLength, Membership.MinRequiredNonAlphanumericCharacters); - return Mapper.Map(emptyContent); + throw new HttpResponseException(HttpStatusCode.NotFound); } + + var contentType = Services.MemberTypeService.Get(contentTypeAlias); + if (contentType == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + var provider = MembershipProviderExtensions.GetMembersMembershipProvider(); + + emptyContent = new Member(contentType); + emptyContent.AdditionalData["NewPassword"] = Membership.GeneratePassword(provider.MinRequiredPasswordLength, provider.MinRequiredNonAlphanumericCharacters); + return Mapper.Map(emptyContent); } /// @@ -269,18 +175,6 @@ namespace Umbraco.Web.Editors // * we have a reference to the DTO object and the persisted object // * Permissions are valid - //This is a special case for when we're not using the umbraco membership provider - when this is the case - // we will not have a ContentTypeAlias set which means the model state will be invalid but we don't care about that - // so we'll remove that model state value - if (MembershipScenario != MembershipScenario.NativeUmbraco) - { - ModelState.Remove("ContentTypeAlias"); - - // TODO: We're removing this because we are not displaying it but when we support the CustomProviderWithUmbracoLink scenario - // we will be able to have a real name associated so do not remove this state once that is implemented! - ModelState.Remove("Name"); - } - //map the properties to the persisted entity MapPropertyValues(contentItem); @@ -335,17 +229,13 @@ namespace Umbraco.Web.Editors } //save the IMember - - // TODO: When we support the CustomProviderWithUmbracoLink scenario, we'll need to save the custom properties for that here too - if (MembershipScenario == MembershipScenario.NativeUmbraco) - { - //save the item - //NOTE: We are setting the password to NULL - this indicates to the system to not actually save the password - // so it will not get overwritten! - contentItem.PersistedContent.RawPasswordValue = null; + //save the item + //NOTE: We are setting the password to NULL - this indicates to the system to not actually save the password + // so it will not get overwritten! + contentItem.PersistedContent.RawPasswordValue = null; - //create/save the IMember - Services.MemberService.Save(contentItem.PersistedContent); - } + //create/save the IMember + Services.MemberService.Save(contentItem.PersistedContent); //Now let's do the role provider stuff - now that we've saved the content item (that is important since // if we are changing the username, it must be persisted before looking up the member roles). @@ -561,26 +451,14 @@ namespace Umbraco.Web.Editors { var currProps = contentItem.PersistedContent.Properties.ToArray(); - switch (MembershipScenario) + switch (lookup) { - case MembershipScenario.NativeUmbraco: - switch (lookup) - { - case LookupType.ByKey: - //Go and re-fetch the persisted item - contentItem.PersistedContent = Services.MemberService.GetByKey(contentItem.Key); - break; - case LookupType.ByUserName: - contentItem.PersistedContent = Services.MemberService.GetByUsername(contentItem.Username.Trim()); - break; - } - break; - case MembershipScenario.CustomProviderWithUmbracoLink: - case MembershipScenario.StandaloneCustomProvider: - default: - var membershipUser = _provider.GetUser(contentItem.Key, false); + case LookupType.ByKey: //Go and re-fetch the persisted item - contentItem.PersistedContent = Mapper.Map(membershipUser); + contentItem.PersistedContent = Services.MemberService.GetByKey(contentItem.Key); + break; + case LookupType.ByUserName: + contentItem.PersistedContent = Services.MemberService.GetByUsername(contentItem.Username.Trim()); break; } @@ -644,59 +522,16 @@ namespace Umbraco.Web.Editors { MembershipUser membershipUser; - switch (MembershipScenario) - { - case MembershipScenario.NativeUmbraco: - //We are using the umbraco membership provider, create the member using the membership provider first. - var umbracoMembershipProvider = (UmbracoMembershipProviderBase)_provider; - // TODO: We are not supporting q/a - passing in empty here - membershipUser = umbracoMembershipProvider.CreateUser( - contentItem.ContentTypeAlias, contentItem.Username, - contentItem.Password.NewPassword, - contentItem.Email, "", "", - contentItem.IsApproved, - Guid.NewGuid(), //since it's the umbraco provider, the user key here doesn't make any difference - out status); - - break; - case MembershipScenario.CustomProviderWithUmbracoLink: - //We are using a custom membership provider, we'll create an empty IMember first to get the unique id to use - // as the provider user key. - //create it - this persisted item has already been set in the MemberBinder based on the 'Member' member type: - Services.MemberService.Save(contentItem.PersistedContent); - - // TODO: We are not supporting q/a - passing in empty here - membershipUser = _provider.CreateUser( - contentItem.Username, - contentItem.Password.NewPassword, - contentItem.Email, - "TEMP", //some membership provider's require something here even if q/a is disabled! - "TEMP", //some membership provider's require something here even if q/a is disabled! - contentItem.IsApproved, - contentItem.PersistedContent.Key, //custom membership provider, we'll link that based on the IMember unique id (GUID) - out status); - - break; - case MembershipScenario.StandaloneCustomProvider: - // we don't have a member type to use so we will just create the basic membership user with the provider with no - // link back to the umbraco data - - var newKey = Guid.NewGuid(); - // TODO: We are not supporting q/a - passing in empty here - membershipUser = _provider.CreateUser( - contentItem.Username, - contentItem.Password.NewPassword, - contentItem.Email, - "TEMP", //some membership provider's require something here even if q/a is disabled! - "TEMP", //some membership provider's require something here even if q/a is disabled! - contentItem.IsApproved, - newKey, - out status); - - break; - default: - throw new ArgumentOutOfRangeException(); - } + //We are using the umbraco membership provider, create the member using the membership provider first. + var umbracoMembershipProvider = (UmbracoMembershipProviderBase)_provider; + // TODO: We are not supporting q/a - passing in empty here + membershipUser = umbracoMembershipProvider.CreateUser( + contentItem.ContentTypeAlias, contentItem.Username, + contentItem.Password.NewPassword, + contentItem.Email, "", "", + contentItem.IsApproved, + Guid.NewGuid(), //since it's the umbraco provider, the user key here doesn't make any difference + out status); // TODO: Localize these! switch (status) @@ -779,38 +614,12 @@ namespace Umbraco.Web.Editors { IMember foundMember; MembershipUser foundMembershipUser; - switch (MembershipScenario) + foundMember = Services.MemberService.GetByKey(key); + if (foundMember == null) { - case MembershipScenario.NativeUmbraco: - foundMember = Services.MemberService.GetByKey(key); - if (foundMember == null) - { - return HandleContentNotFound(key, false); - } - Services.MemberService.Delete(foundMember); - break; - case MembershipScenario.CustomProviderWithUmbracoLink: - foundMember = Services.MemberService.GetByKey(key); - if (foundMember != null) - { - Services.MemberService.Delete(foundMember); - } - foundMembershipUser = _provider.GetUser(key, false); - if (foundMembershipUser != null) - { - _provider.DeleteUser(foundMembershipUser.UserName, true); - } - break; - case MembershipScenario.StandaloneCustomProvider: - foundMembershipUser = _provider.GetUser(key, false); - if (foundMembershipUser != null) - { - _provider.DeleteUser(foundMembershipUser.UserName, true); - } - break; - default: - throw new ArgumentOutOfRangeException(); + return HandleContentNotFound(key, false); } + Services.MemberService.Delete(foundMember); return Request.CreateResponse(HttpStatusCode.OK); } diff --git a/src/Umbraco.Web/Editors/MemberGroupController.cs b/src/Umbraco.Web/Editors/MemberGroupController.cs index 81191311f9..89041fb512 100644 --- a/src/Umbraco.Web/Editors/MemberGroupController.cs +++ b/src/Umbraco.Web/Editors/MemberGroupController.cs @@ -10,6 +10,7 @@ using Umbraco.Core.Security; using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Mvc; +using Umbraco.Web.Security; using Umbraco.Web.WebApi.Filters; using Constants = Umbraco.Core.Constants; @@ -22,8 +23,6 @@ namespace Umbraco.Web.Editors [UmbracoTreeAuthorize(Constants.Trees.MemberGroups)] public class MemberGroupController : UmbracoAuthorizedJsonController { - private readonly MembershipProvider _provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); - public MemberGroupDisplay GetById(int id) { var memberGroup = Services.MemberGroupService.GetById(id); @@ -38,13 +37,8 @@ namespace Umbraco.Web.Editors public IEnumerable GetByIds([FromUri]int[] ids) { - if (_provider.IsUmbracoMembershipProvider()) - { - return Services.MemberGroupService.GetByIds(ids) + return Services.MemberGroupService.GetByIds(ids) .Select(Mapper.Map); - } - - return Enumerable.Empty(); } [HttpDelete] @@ -63,13 +57,8 @@ namespace Umbraco.Web.Editors public IEnumerable GetAllGroups() { - if (_provider.IsUmbracoMembershipProvider()) - { - return Services.MemberGroupService.GetAll() + return Services.MemberGroupService.GetAll() .Select(Mapper.Map); - } - - return Enumerable.Empty(); } public MemberGroupDisplay GetEmpty() diff --git a/src/Umbraco.Web/Editors/MemberTypeController.cs b/src/Umbraco.Web/Editors/MemberTypeController.cs index 4907fa74b3..9600b96f92 100644 --- a/src/Umbraco.Web/Editors/MemberTypeController.cs +++ b/src/Umbraco.Web/Editors/MemberTypeController.cs @@ -16,6 +16,7 @@ using Umbraco.Core.Security; using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Mvc; +using Umbraco.Web.Security; using Umbraco.Web.WebApi.Filters; using Constants = Umbraco.Core.Constants; @@ -34,8 +35,6 @@ namespace Umbraco.Web.Editors { } - private readonly MembershipProvider _provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); - [UmbracoTreeAuthorize(Constants.Trees.MemberTypes)] public MemberTypeDisplay GetById(int id) { @@ -114,12 +113,8 @@ namespace Umbraco.Web.Editors /// public IEnumerable GetAllTypes() { - if (_provider.IsUmbracoMembershipProvider()) - { - return Services.MemberTypeService.GetAll() + return Services.MemberTypeService.GetAll() .Select(Mapper.Map); - } - return Enumerable.Empty(); } [UmbracoTreeAuthorize(Constants.Trees.MemberTypes)] diff --git a/src/Umbraco.Web/Editors/PasswordChanger.cs b/src/Umbraco.Web/Editors/PasswordChanger.cs index 171d23f1d9..9bc77a4a28 100644 --- a/src/Umbraco.Web/Editors/PasswordChanger.cs +++ b/src/Umbraco.Web/Editors/PasswordChanger.cs @@ -2,7 +2,6 @@ using System.ComponentModel.DataAnnotations; using System.Threading.Tasks; using System.Web; -using System.Web.Http.ModelBinding; using System.Web.Security; using Umbraco.Core; using Umbraco.Core.Logging; @@ -46,27 +45,6 @@ namespace Umbraco.Web.Editors if (passwordModel == null) throw new ArgumentNullException(nameof(passwordModel)); if (userMgr == null) throw new ArgumentNullException(nameof(userMgr)); - //check if this identity implementation is powered by an underlying membership provider (it will be in most cases) - var membershipPasswordHasher = userMgr.PasswordHasher as IMembershipProviderPasswordHasher; - - //check if this identity implementation is powered by an IUserAwarePasswordHasher (it will be by default in 7.7+ but not for upgrades) - - if (membershipPasswordHasher != null && !(userMgr.PasswordHasher is IUserAwarePasswordHasher)) - { - //if this isn't using an IUserAwarePasswordHasher, then fallback to the old way - if (membershipPasswordHasher.MembershipProvider.RequiresQuestionAndAnswer) - throw new NotSupportedException("Currently the user editor does not support providers that have RequiresQuestionAndAnswer specified"); - return ChangePasswordWithMembershipProvider(savingUser.Username, passwordModel, membershipPasswordHasher.MembershipProvider); - } - - //if we are here, then a IUserAwarePasswordHasher is available, however we cannot proceed in that case if for some odd reason - //the user has configured the membership provider to not be hashed. This will actually never occur because the BackOfficeUserManager - //will throw if it's not hashed, but we should make sure to check anyways (i.e. in case we want to unit test!) - if (membershipPasswordHasher != null && membershipPasswordHasher.MembershipProvider.PasswordFormat != MembershipPasswordFormat.Hashed) - { - throw new InvalidOperationException("The membership provider cannot have a password format of " + membershipPasswordHasher.MembershipProvider.PasswordFormat + " and be configured with secured hashed passwords"); - } - //Are we resetting the password? //This flag indicates that either an admin user is changing another user's password without knowing the original password // or that the password needs to be reset to an auto-generated one. @@ -208,7 +186,7 @@ namespace Umbraco.Web.Editors //we've made it here which means we need to generate a new password - var canReset = membershipProvider.CanResetPassword(_userService); + var canReset = true; // TODO: Remove this! This is legacy, all of this will be gone when we get membership providers entirely gone if (canReset == false) { return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Password reset is not enabled", new[] { "resetPassword" }) }); diff --git a/src/Umbraco.Web/Install/InstallSteps/NewInstallStep.cs b/src/Umbraco.Web/Install/InstallSteps/NewInstallStep.cs index 15d14574ed..aeddb167e0 100644 --- a/src/Umbraco.Web/Install/InstallSteps/NewInstallStep.cs +++ b/src/Umbraco.Web/Install/InstallSteps/NewInstallStep.cs @@ -10,8 +10,11 @@ using Umbraco.Core; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.Migrations.Install; +using Umbraco.Core.Models.Identity; using Umbraco.Core.Services; using Umbraco.Web.Install.Models; +using Umbraco.Web.Security; +using System.Web; namespace Umbraco.Web.Install.InstallSteps { @@ -31,26 +34,20 @@ namespace Umbraco.Web.Install.InstallSteps private readonly DatabaseBuilder _databaseBuilder; private static HttpClient _httpClient; private readonly IGlobalSettings _globalSettings; + private readonly IUserPasswordConfiguration _passwordConfiguration; + private readonly BackOfficeUserManager _userManager; - public NewInstallStep(HttpContextBase http, IUserService userService, DatabaseBuilder databaseBuilder, IGlobalSettings globalSettings) + public NewInstallStep(HttpContextBase http, IUserService userService, DatabaseBuilder databaseBuilder, IGlobalSettings globalSettings, IUserPasswordConfiguration passwordConfiguration) { _http = http; _userService = userService; _databaseBuilder = databaseBuilder; _globalSettings = globalSettings; + _passwordConfiguration = passwordConfiguration; + _userManager = _http.GetOwinContext().GetBackOfficeUserManager(); } - // TODO: Change all logic in this step to use ASP.NET Identity NOT MembershipProviders - private MembershipProvider CurrentProvider - { - get - { - var provider = Core.Security.MembershipProviderExtensions.GetUsersMembershipProvider(); - return provider; - } - } - - public override Task ExecuteAsync(UserModel user) + public override async Task ExecuteAsync(UserModel user) { var admin = _userService.GetUserById(Constants.Security.SuperUserId); if (admin == null) @@ -58,23 +55,16 @@ namespace Umbraco.Web.Install.InstallSteps throw new InvalidOperationException("Could not find the super user!"); } - var membershipUser = CurrentProvider.GetUser(Constants.Security.SuperUserId, true); + var membershipUser = await _userManager.FindByIdAsync(Constants.Security.SuperUserId); if (membershipUser == null) { throw new InvalidOperationException($"No user found in membership provider with id of {Constants.Security.SuperUserId}."); } - try + var success = await _userManager.ChangePasswordAsync(membershipUser.Id, "default", user.Password.Trim()); + if (success.Succeeded == false) { - var success = membershipUser.ChangePassword("default", user.Password.Trim()); - if (success == false) - { - throw new FormatException("Password must be at least " + CurrentProvider.MinRequiredPasswordLength + " characters long and contain at least " + CurrentProvider.MinRequiredNonAlphanumericCharacters + " symbols"); - } - } - catch (Exception) - { - throw new FormatException("Password must be at least " + CurrentProvider.MinRequiredPasswordLength + " characters long and contain at least " + CurrentProvider.MinRequiredNonAlphanumericCharacters + " symbols"); + throw new InvalidOperationException("Invalid password: " + string.Join(", ", success.Errors)); } admin.Email = user.Email.Trim(); @@ -98,7 +88,7 @@ namespace Umbraco.Web.Install.InstallSteps catch { /* fail in silence */ } } - return Task.FromResult(null); + return null; } /// @@ -108,12 +98,10 @@ namespace Umbraco.Web.Install.InstallSteps { get { - var provider = Core.Security.MembershipProviderExtensions.GetUsersMembershipProvider(); - return new { - minCharLength = provider.MinRequiredPasswordLength, - minNonAlphaNumericLength = provider.MinRequiredNonAlphanumericCharacters + minCharLength = _passwordConfiguration.RequiredLength, + minNonAlphaNumericLength = _passwordConfiguration.RequireNonLetterOrDigit ? 1 : 0 }; } } diff --git a/src/Umbraco.Web/Macros/MacroRenderer.cs b/src/Umbraco.Web/Macros/MacroRenderer.cs index 472e0e19fd..f3e78bf3dc 100755 --- a/src/Umbraco.Web/Macros/MacroRenderer.cs +++ b/src/Umbraco.Web/Macros/MacroRenderer.cs @@ -15,6 +15,7 @@ using Umbraco.Core.Macros; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Services; +using Umbraco.Web.Security; namespace Umbraco.Web.Macros { @@ -57,8 +58,8 @@ namespace Umbraco.Web.Macros if (_umbracoContextAccessor.UmbracoContext.HttpContext?.User?.Identity?.IsAuthenticated ?? false) { //ugh, membershipproviders :( - var provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); - var member = Core.Security.MembershipProviderExtensions.GetCurrentUser(provider); + var provider = MembershipProviderExtensions.GetMembersMembershipProvider(); + var member = MembershipProviderExtensions.GetCurrentUser(provider); key = member?.ProviderUserKey ?? 0; } @@ -122,8 +123,8 @@ namespace Umbraco.Web.Macros // do not cache if it should cache by member and there's not member if (model.CacheByMember) { - var provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); - var member = Core.Security.MembershipProviderExtensions.GetCurrentUser(provider); + var provider = MembershipProviderExtensions.GetMembersMembershipProvider(); + var member = MembershipProviderExtensions.GetCurrentUser(provider); var key = member?.ProviderUserKey; if (key == null) return; } diff --git a/src/Umbraco.Web/MembershipProviderExtensions.cs b/src/Umbraco.Web/MembershipProviderExtensions.cs deleted file mode 100644 index 133e886e1f..0000000000 --- a/src/Umbraco.Web/MembershipProviderExtensions.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System.Collections.Generic; -using System.Web; -using System.Web.Security; -using Umbraco.Core.Models; -using Umbraco.Core.Security; -using Umbraco.Core.Services; - -namespace Umbraco.Web -{ - internal static class MembershipProviderExtensions - { - /// - /// Returns the configuration of the membership provider used to configure change password editors - /// - /// - /// - /// - public static IDictionary GetConfiguration( - this MembershipProvider membershipProvider, IUserService userService) - { - var baseProvider = membershipProvider as MembershipProviderBase; - - var canReset = membershipProvider.CanResetPassword(userService); - - return new Dictionary - { - {"minPasswordLength", membershipProvider.MinRequiredPasswordLength}, - {"enableReset", canReset}, - {"enablePasswordRetrieval", membershipProvider.EnablePasswordRetrieval}, - {"requiresQuestionAnswer", membershipProvider.RequiresQuestionAndAnswer}, - {"allowManuallyChangingPassword", baseProvider != null && baseProvider.AllowManuallyChangingPassword}, - {"minNonAlphaNumericChars", membershipProvider.MinRequiredNonAlphanumericCharacters} - // TODO: Inject the other parameters in here to change the behavior of this control - based on the membership provider settings. - }; - } - - } -} diff --git a/src/Umbraco.Web/Models/ContentEditing/MemberDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/MemberDisplay.cs index fd1c1ed5b8..3391447535 100644 --- a/src/Umbraco.Web/Models/ContentEditing/MemberDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/MemberDisplay.cs @@ -23,9 +23,6 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "email")] public string Email { get; set; } - [DataMember(Name = "membershipScenario")] - public MembershipScenario MembershipScenario { get; set; } - /// /// This is used to indicate how to map the membership provider properties to the save model, this mapping /// will change if a developer has opted to have custom member property aliases specified in their membership provider config, diff --git a/src/Umbraco.Web/Models/Mapping/MemberMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/MemberMapDefinition.cs index fd295803c1..f93c1208ed 100644 --- a/src/Umbraco.Web/Models/Mapping/MemberMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/MemberMapDefinition.cs @@ -11,6 +11,7 @@ using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; using Umbraco.Core.Services.Implement; using UserProfile = Umbraco.Web.Models.ContentEditing.UserProfile; +using Umbraco.Web.Security; namespace Umbraco.Web.Models.Mapping { @@ -85,7 +86,6 @@ namespace Umbraco.Web.Models.Mapping target.Id = source.Id; target.Key = source.Key; target.MemberProviderFieldMapping = GetMemberProviderFieldMapping(); - target.MembershipScenario = GetMembershipScenario(); target.Name = source.Name; target.Owner = _commonMapper.GetOwner(source, context); target.ParentId = source.ParentId; @@ -154,34 +154,10 @@ namespace Umbraco.Web.Models.Mapping target.Properties = context.MapEnumerable(source.Properties); } - private MembershipScenario GetMembershipScenario() - { - var provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); - - if (provider.IsUmbracoMembershipProvider()) - { - return MembershipScenario.NativeUmbraco; - } - var memberType = _memberTypeService.Get(Constants.Conventions.MemberTypes.DefaultAlias); - return memberType != null - ? MembershipScenario.CustomProviderWithUmbracoLink - : MembershipScenario.StandaloneCustomProvider; - } - private static IDictionary GetMemberProviderFieldMapping() { - var provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); - - if (provider.IsUmbracoMembershipProvider() == false) - { - return new Dictionary - { - {Constants.Conventions.Member.IsLockedOut, Constants.Conventions.Member.IsLockedOut}, - {Constants.Conventions.Member.IsApproved, Constants.Conventions.Member.IsApproved}, - {Constants.Conventions.Member.Comments, Constants.Conventions.Member.Comments} - }; - } - + var provider = MembershipProviderExtensions.GetMembersMembershipProvider(); + var umbracoProvider = (IUmbracoMemberTypeMembershipProvider)provider; return new Dictionary diff --git a/src/Umbraco.Web/Models/Mapping/MemberTabsAndPropertiesMapper.cs b/src/Umbraco.Web/Models/Mapping/MemberTabsAndPropertiesMapper.cs index 46689de71b..3bf6f5c066 100644 --- a/src/Umbraco.Web/Models/Mapping/MemberTabsAndPropertiesMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/MemberTabsAndPropertiesMapper.cs @@ -11,6 +11,7 @@ using Umbraco.Core.Security; using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; using Umbraco.Core.Dictionary; +using Umbraco.Web.Security; namespace Umbraco.Web.Models.Mapping { @@ -42,7 +43,7 @@ namespace Umbraco.Web.Models.Mapping /// Overridden to deal with custom member properties and permissions. public override IEnumerable> Map(IMember source, MapperContext context) { - var provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); + var provider = MembershipProviderExtensions.GetMembersMembershipProvider(); var memberType = _memberTypeService.Get(source.ContentTypeId); @@ -53,29 +54,16 @@ namespace Umbraco.Web.Models.Mapping var resolved = base.Map(source, context); - if (provider.IsUmbracoMembershipProvider() == false) - { - // it's a generic provider so update the locked out property based on our known constant alias - var isLockedOutProperty = resolved.SelectMany(x => x.Properties).FirstOrDefault(x => x.Alias == Constants.Conventions.Member.IsLockedOut); - if (isLockedOutProperty?.Value != null && isLockedOutProperty.Value.ToString() != "1") - { - isLockedOutProperty.View = "readonlyvalue"; - isLockedOutProperty.Value = _localizedTextService.Localize("general/no"); - } - } - else - { - var umbracoProvider = (IUmbracoMemberTypeMembershipProvider)provider; + var umbracoProvider = (IUmbracoMemberTypeMembershipProvider)provider; - // This is kind of a hack because a developer is supposed to be allowed to set their property editor - would have been much easier - // if we just had all of the membership provider fields on the member table :( - // TODO: But is there a way to map the IMember.IsLockedOut to the property ? i dunno. - var isLockedOutProperty = resolved.SelectMany(x => x.Properties).FirstOrDefault(x => x.Alias == umbracoProvider.LockPropertyTypeAlias); - if (isLockedOutProperty?.Value != null && isLockedOutProperty.Value.ToString() != "1") - { - isLockedOutProperty.View = "readonlyvalue"; - isLockedOutProperty.Value = _localizedTextService.Localize("general/no"); - } + // This is kind of a hack because a developer is supposed to be allowed to set their property editor - would have been much easier + // if we just had all of the membership provider fields on the member table :( + // TODO: But is there a way to map the IMember.IsLockedOut to the property ? i dunno. + var isLockedOutProperty = resolved.SelectMany(x => x.Properties).FirstOrDefault(x => x.Alias == umbracoProvider.LockPropertyTypeAlias); + if (isLockedOutProperty?.Value != null && isLockedOutProperty.Value.ToString() != "1") + { + isLockedOutProperty.View = "readonlyvalue"; + isLockedOutProperty.Value = _localizedTextService.Localize("general/no"); } var umbracoContext = _umbracoContextAccessor.UmbracoContext; @@ -107,7 +95,7 @@ namespace Umbraco.Web.Models.Mapping protected override IEnumerable GetCustomGenericProperties(IContentBase content) { var member = (IMember)content; - var membersProvider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); + var membersProvider = MembershipProviderExtensions.GetMembersMembershipProvider(); var genericProperties = new List { @@ -149,7 +137,7 @@ namespace Umbraco.Web.Models.Mapping // TODO: Hard coding this because the changepassword doesn't necessarily need to be a resolvable (real) property editor View = "changepassword", // initialize the dictionary with the configuration from the default membership provider - Config = new Dictionary(membersProvider.GetConfiguration(_userService)) + Config = new Dictionary(membersProvider.PasswordConfiguration.GetConfiguration()) { // the password change toggle will only be displayed if there is already a password assigned. {"hasPassword", member.RawPasswordValue.IsNullOrWhiteSpace() == false} @@ -226,18 +214,8 @@ namespace Umbraco.Web.Models.Mapping Value = member.Username }; - var scenario = memberTypeService.GetMembershipScenario(); - - // only allow editing if this is a new member, or if the membership provider is the Umbraco one - if (member.HasIdentity == false || scenario == MembershipScenario.NativeUmbraco) - { - prop.View = "textbox"; - prop.Validation.Mandatory = true; - } - else - { - prop.View = "readonlyvalue"; - } + prop.View = "textbox"; + prop.Validation.Mandatory = true; return prop; } diff --git a/src/Umbraco.Web/PasswordConfigurationExtensions.cs b/src/Umbraco.Web/PasswordConfigurationExtensions.cs new file mode 100644 index 0000000000..95b198bbb7 --- /dev/null +++ b/src/Umbraco.Web/PasswordConfigurationExtensions.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; +using Umbraco.Core.Configuration; +using Umbraco.Core.Security; +using Umbraco.Core.Services; + +namespace Umbraco.Web +{ + internal static class PasswordConfigurationExtensions + { + /// + /// Returns the configuration of the membership provider used to configure change password editors + /// + /// + /// + /// + public static IDictionary GetConfiguration( + this IPasswordConfiguration passwordConfiguration) + { + return new Dictionary + { + {"minPasswordLength", passwordConfiguration.RequiredLength}, + + // TODO: This doesn't make a ton of sense with asp.net identity and also there's a bunch of other settings + // that we can consider with IPasswordConfiguration, but these are currently still based on how membership providers worked. + {"minNonAlphaNumericChars", passwordConfiguration.RequireNonLetterOrDigit ? 2 : 0}, + + // TODO: These are legacy settings - we will always allow administrators to change another users password if the user + // has permission to the user section to edit them. Similarly, when we have ASP.Net identity enabled for members, these legacy settings + // will no longer exist and admins will just be able to change a members' password if they have access to the member section to edit them. + {"allowManuallyChangingPassword", true}, + {"enableReset", false}, + {"enablePasswordRetrieval", false}, + {"requiresQuestionAnswer", false} + + + }; + } + + } +} diff --git a/src/Umbraco.Web/PublishedCache/NuCache/MemberCache.cs b/src/Umbraco.Web/PublishedCache/NuCache/MemberCache.cs index 2e196f629e..83856389a1 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/MemberCache.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/MemberCache.cs @@ -10,6 +10,7 @@ using Umbraco.Core.Security; using Umbraco.Core.Services; using Umbraco.Core.Xml.XPath; using Umbraco.Web.PublishedCache.NuCache.Navigable; +using Umbraco.Web.Security; namespace Umbraco.Web.PublishedCache.NuCache { @@ -45,13 +46,6 @@ namespace Umbraco.Web.PublishedCache.NuCache : cache.GetCacheItem(cacheKey, getCacheItem); } - private static void EnsureProvider() - { - var provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); - if (provider.IsUmbracoMembershipProvider() == false) - throw new NotSupportedException("Cannot access this method unless the Umbraco membership provider is active"); - } - public IPublishedContent GetById(bool preview, int memberId) { return GetById(memberId); @@ -61,7 +55,6 @@ namespace Umbraco.Web.PublishedCache.NuCache { return GetCacheItem(CacheKeys.MemberCacheMember("ById", _previewDefault, memberId), () => { - EnsureProvider(); var member = _memberService.GetById(memberId); return member == null ? null @@ -79,7 +72,6 @@ namespace Umbraco.Web.PublishedCache.NuCache { return GetCacheItem(CacheKeys.MemberCacheMember("ByProviderKey", _previewDefault, key), () => { - EnsureProvider(); var member = _memberService.GetByProviderKey(key); return member == null ? null : GetById(member, _previewDefault); }); @@ -89,7 +81,6 @@ namespace Umbraco.Web.PublishedCache.NuCache { return GetCacheItem(CacheKeys.MemberCacheMember("ByUsername", _previewDefault, username), () => { - EnsureProvider(); var member = _memberService.GetByUsername(username); return member == null ? null : GetById(member, _previewDefault); }); @@ -99,7 +90,6 @@ namespace Umbraco.Web.PublishedCache.NuCache { return GetCacheItem(CacheKeys.MemberCacheMember("ByEmail", _previewDefault, email), () => { - EnsureProvider(); var member = _memberService.GetByEmail(email); return member == null ? null : GetById(member, _previewDefault); }); @@ -132,12 +122,6 @@ namespace Umbraco.Web.PublishedCache.NuCache public XPathNavigator CreateNodeNavigator(int id, bool preview) { - var provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); - if (provider.IsUmbracoMembershipProvider() == false) - { - throw new NotSupportedException("Cannot access this method unless the Umbraco membership provider is active"); - } - var result = _memberService.GetById(id); if (result == null) return null; diff --git a/src/Umbraco.Web/Routing/PublishedRouter.cs b/src/Umbraco.Web/Routing/PublishedRouter.cs index e811caf77b..91fc903ba8 100644 --- a/src/Umbraco.Web/Routing/PublishedRouter.cs +++ b/src/Umbraco.Web/Routing/PublishedRouter.cs @@ -594,38 +594,31 @@ namespace Umbraco.Web.Routing } else { - if (membershipHelper.IsUmbracoMembershipProviderActive()) + // grab the current member + var member = membershipHelper.GetCurrentMember(); + // if the member has the "approved" and/or "locked out" properties, make sure they're correctly set before allowing access + var memberIsActive = true; + if (member != null) { - // grab the current member - var member = membershipHelper.GetCurrentMember(); - // if the member has the "approved" and/or "locked out" properties, make sure they're correctly set before allowing access - var memberIsActive = true; - if (member != null) - { - if (member.HasProperty(Constants.Conventions.Member.IsApproved) == false) - memberIsActive = member.Value(Constants.Conventions.Member.IsApproved); + if (member.HasProperty(Constants.Conventions.Member.IsApproved) == false) + memberIsActive = member.Value(Constants.Conventions.Member.IsApproved); - if (member.HasProperty(Constants.Conventions.Member.IsLockedOut) == false) - memberIsActive = member.Value(Constants.Conventions.Member.IsLockedOut) == false; - } + if (member.HasProperty(Constants.Conventions.Member.IsLockedOut) == false) + memberIsActive = member.Value(Constants.Conventions.Member.IsLockedOut) == false; + } - if (memberIsActive == false) - { - _logger.Debug( - "Current member is either unapproved or locked out, redirect to error page"); - var errorPageId = publicAccessAttempt.Result.NoAccessNodeId; - if (errorPageId != request.PublishedContent.Id) - request.PublishedContent = - request.UmbracoContext.PublishedSnapshot.Content.GetById(errorPageId); - } - else - { - _logger.Debug("Current member has access"); - } + if (memberIsActive == false) + { + _logger.Debug( + "Current member is either unapproved or locked out, redirect to error page"); + var errorPageId = publicAccessAttempt.Result.NoAccessNodeId; + if (errorPageId != request.PublishedContent.Id) + request.PublishedContent = + request.UmbracoContext.PublishedSnapshot.Content.GetById(errorPageId); } else { - _logger.Debug("Current custom MembershipProvider member has access"); + _logger.Debug("Current member has access"); } } } diff --git a/src/Umbraco.Web/Runtime/WebInitialComposer.cs b/src/Umbraco.Web/Runtime/WebInitialComposer.cs index 046b8381d2..92236ae96a 100644 --- a/src/Umbraco.Web/Runtime/WebInitialComposer.cs +++ b/src/Umbraco.Web/Runtime/WebInitialComposer.cs @@ -65,7 +65,7 @@ namespace Umbraco.Web.Runtime composition.ComposeInstaller(); // register membership stuff - composition.Register(factory => Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider()); + composition.Register(factory => MembershipProviderExtensions.GetMembersMembershipProvider()); composition.Register(factory => Roles.Enabled ? Roles.Provider : new MembersRoleProvider(factory.GetInstance())); composition.Register(Lifetime.Request); composition.Register(factory => factory.GetInstance().PublishedSnapshot.Members); diff --git a/src/Umbraco.Web/Security/ActiveDirectoryBackOfficeUserPasswordChecker.cs b/src/Umbraco.Web/Security/ActiveDirectoryBackOfficeUserPasswordChecker.cs index 036a1718b0..7f43740fcd 100644 --- a/src/Umbraco.Web/Security/ActiveDirectoryBackOfficeUserPasswordChecker.cs +++ b/src/Umbraco.Web/Security/ActiveDirectoryBackOfficeUserPasswordChecker.cs @@ -6,7 +6,7 @@ using Umbraco.Core.Models.Identity; namespace Umbraco.Web.Security { - // TODO: This relies on an assembly that is not .NET Standard :( + // TODO: This relies on an assembly that is not .NET Standard (at least not at the time of implementation) :( public class ActiveDirectoryBackOfficeUserPasswordChecker : IBackOfficeUserPasswordChecker { public virtual string ActiveDirectoryDomain diff --git a/src/Umbraco.Web/Security/AppBuilderExtensions.cs b/src/Umbraco.Web/Security/AppBuilderExtensions.cs index 0a3e57c4fd..e8693d9cc6 100644 --- a/src/Umbraco.Web/Security/AppBuilderExtensions.cs +++ b/src/Umbraco.Web/Security/AppBuilderExtensions.cs @@ -40,23 +40,24 @@ namespace Umbraco.Web.Security UmbracoMapper mapper, IContentSection contentSettings, IGlobalSettings globalSettings, - MembershipProviderBase userMembershipProvider) + // TODO: This could probably be optional? + IPasswordConfiguration passwordConfiguration, + IPasswordGenerator passwordGenerator) { if (services == null) throw new ArgumentNullException(nameof(services)); - if (userMembershipProvider == null) throw new ArgumentNullException(nameof(userMembershipProvider)); //Configure Umbraco user manager to be created per request app.CreatePerOwinContext( (options, owinContext) => BackOfficeUserManager.Create( options, services.UserService, - services.MemberTypeService, services.EntityService, services.ExternalLoginService, - userMembershipProvider, mapper, contentSettings, - globalSettings)); + globalSettings, + passwordConfiguration, + passwordGenerator)); app.SetBackOfficeUserManagerType(); @@ -77,11 +78,12 @@ namespace Umbraco.Web.Security IRuntimeState runtimeState, IContentSection contentSettings, IGlobalSettings globalSettings, - MembershipProviderBase userMembershipProvider, - BackOfficeUserStore customUserStore) + BackOfficeUserStore customUserStore, + // TODO: This could probably be optional? + IPasswordConfiguration passwordConfiguration, + IPasswordGenerator passwordGenerator) { if (runtimeState == null) throw new ArgumentNullException(nameof(runtimeState)); - if (userMembershipProvider == null) throw new ArgumentNullException(nameof(userMembershipProvider)); if (customUserStore == null) throw new ArgumentNullException(nameof(customUserStore)); //Configure Umbraco user manager to be created per request @@ -89,8 +91,9 @@ namespace Umbraco.Web.Security (options, owinContext) => BackOfficeUserManager.Create( options, customUserStore, - userMembershipProvider, - contentSettings)); + contentSettings, + passwordConfiguration, + passwordGenerator)); app.SetBackOfficeUserManagerType(); diff --git a/src/Umbraco.Web/Security/BackOfficeUserManager.cs b/src/Umbraco.Web/Security/BackOfficeUserManager.cs index c3988451b2..f0044c40d5 100644 --- a/src/Umbraco.Web/Security/BackOfficeUserManager.cs +++ b/src/Umbraco.Web/Security/BackOfficeUserManager.cs @@ -24,20 +24,16 @@ namespace Umbraco.Web.Security { public const string OwinMarkerKey = "Umbraco.Web.Security.Identity.BackOfficeUserManagerMarker"; - public BackOfficeUserManager(IUserStore store) - : base(store) - { - } - public BackOfficeUserManager( IUserStore store, - IdentityFactoryOptions options, - MembershipProviderBase membershipProvider, - IContentSection contentSectionConfig) - : base(store) + IdentityFactoryOptions options, + IContentSection contentSectionConfig, + IPasswordConfiguration passwordConfiguration, + IPasswordGenerator passwordGenerator) + : base(store, passwordConfiguration, passwordGenerator) { if (options == null) throw new ArgumentNullException("options"); - InitUserManager(this, membershipProvider, contentSectionConfig, options); + InitUserManager(this, passwordConfiguration, options.DataProtectionProvider, contentSectionConfig); } #region Static Create methods @@ -47,32 +43,29 @@ namespace Umbraco.Web.Security /// /// /// - /// /// /// - /// + /// /// /// /// public static BackOfficeUserManager Create( IdentityFactoryOptions options, IUserService userService, - IMemberTypeService memberTypeService, IEntityService entityService, - IExternalLoginService externalLoginService, - MembershipProviderBase membershipProvider, + IExternalLoginService externalLoginService, UmbracoMapper mapper, IContentSection contentSectionConfig, - IGlobalSettings globalSettings) + IGlobalSettings globalSettings, + IPasswordConfiguration passwordConfiguration, + IPasswordGenerator passwordGenerator) { if (options == null) throw new ArgumentNullException("options"); if (userService == null) throw new ArgumentNullException("userService"); - if (memberTypeService == null) throw new ArgumentNullException("memberTypeService"); if (externalLoginService == null) throw new ArgumentNullException("externalLoginService"); - var manager = new BackOfficeUserManager( - new BackOfficeUserStore(userService, memberTypeService, entityService, externalLoginService, globalSettings, membershipProvider, mapper)); - manager.InitUserManager(manager, membershipProvider, contentSectionConfig, options); + var store = new BackOfficeUserStore(userService, entityService, externalLoginService, globalSettings, mapper); + var manager = new BackOfficeUserManager(store, options, contentSectionConfig, passwordConfiguration, passwordGenerator); return manager; } @@ -81,37 +74,22 @@ namespace Umbraco.Web.Security /// /// /// - /// + /// /// /// public static BackOfficeUserManager Create( IdentityFactoryOptions options, - BackOfficeUserStore customUserStore, - MembershipProviderBase membershipProvider, - IContentSection contentSectionConfig) + BackOfficeUserStore customUserStore, + IContentSection contentSectionConfig, + IPasswordConfiguration passwordConfiguration, + IPasswordGenerator passwordGenerator) { - var manager = new BackOfficeUserManager(customUserStore, options, membershipProvider, contentSectionConfig); + var manager = new BackOfficeUserManager(customUserStore, options, contentSectionConfig, passwordConfiguration, passwordGenerator); return manager; } #endregion - /// - /// Initializes the user manager with the correct options - /// - /// - /// - /// - /// - /// - protected void InitUserManager( - BackOfficeUserManager manager, - MembershipProviderBase membershipProvider, - IContentSection contentSectionConfig, - IdentityFactoryOptions options) - { - //NOTE: This method is mostly here for backwards compat - base.InitUserManager(manager, membershipProvider, options.DataProtectionProvider, contentSectionConfig); - } + } /// @@ -120,9 +98,13 @@ namespace Umbraco.Web.Security public class BackOfficeUserManager : UserManager where T : BackOfficeIdentityUser { - public BackOfficeUserManager(IUserStore store) + public BackOfficeUserManager(IUserStore store, + IPasswordConfiguration passwordConfiguration, + IPasswordGenerator passwordGenerator) : base(store) { + PasswordConfiguration = passwordConfiguration; + PasswordGenerator = passwordGenerator; } #region What we support do not currently @@ -162,19 +144,35 @@ namespace Umbraco.Web.Security return userIdentity; } + ///// + ///// Initializes the user manager with the correct options + ///// + ///// + ///// + ///// + ///// + ///// + //protected void InitUserManager( + // BackOfficeUserManager manager, + // IPasswordConfiguration passwordConfig, + // IContentSection contentSectionConfig, + // IdentityFactoryOptions options) + //{ + // //NOTE: This method is mostly here for backwards compat + // base.InitUserManager(manager, passwordConfig, options.DataProtectionProvider, contentSectionConfig); + //} + /// /// Initializes the user manager with the correct options /// /// - /// - /// The for the users called UsersMembershipProvider - /// + /// /// /// /// protected void InitUserManager( BackOfficeUserManager manager, - MembershipProviderBase membershipProvider, + IPasswordConfiguration passwordConfig, IDataProtectionProvider dataProtectionProvider, IContentSection contentSectionConfig) { @@ -186,10 +184,10 @@ namespace Umbraco.Web.Security }; // Configure validation logic for passwords - manager.PasswordValidator = new MembershipProviderPasswordValidator(membershipProvider); + manager.PasswordValidator = new ConfiguredPasswordValidator(passwordConfig); //use a custom hasher based on our membership provider - manager.PasswordHasher = GetDefaultPasswordHasher(membershipProvider); + manager.PasswordHasher = GetDefaultPasswordHasher(passwordConfig); if (dataProtectionProvider != null) { @@ -200,7 +198,7 @@ namespace Umbraco.Web.Security } manager.UserLockoutEnabledByDefault = true; - manager.MaxFailedAccessAttemptsBeforeLockout = membershipProvider.MaxInvalidPasswordAttempts; + manager.MaxFailedAccessAttemptsBeforeLockout = passwordConfig.MaxFailedAccessAttemptsBeforeLockout; //NOTE: This just needs to be in the future, we currently don't support a lockout timespan, it's either they are locked // or they are not locked, but this determines what is set on the account lockout date which corresponds to whether they are // locked out or not. @@ -250,58 +248,26 @@ namespace Umbraco.Web.Security /// This will determine which password hasher to use based on what is defined in config /// /// - protected virtual IPasswordHasher GetDefaultPasswordHasher(MembershipProviderBase provider) + protected virtual IPasswordHasher GetDefaultPasswordHasher(IPasswordConfiguration passwordConfiguration) { //we can use the user aware password hasher (which will be the default and preferred way) - return new UserAwareMembershipProviderPasswordHasher(provider); + return new UserAwarePasswordHasher(new PasswordSecurity(passwordConfiguration)); } /// /// Gets/sets the default back office user password checker /// public IBackOfficeUserPasswordChecker BackOfficeUserPasswordChecker { get; set; } + public IPasswordConfiguration PasswordConfiguration { get; } + public IPasswordGenerator PasswordGenerator { get; } /// /// Helper method to generate a password for a user based on the current password validator /// /// public string GeneratePassword() - { - var passwordValidator = PasswordValidator as PasswordValidator; - - if (passwordValidator == null) - { - var membershipPasswordHasher = PasswordHasher as IMembershipProviderPasswordHasher; - - //get the real password validator, this should not be null but in some very rare cases it could be, in which case - //we need to create a default password validator to use since we have no idea what it actually is or what it's rules are - //this is an Edge Case! - passwordValidator = PasswordValidator as PasswordValidator - ?? (membershipPasswordHasher != null - ? new MembershipProviderPasswordValidator(membershipPasswordHasher.MembershipProvider) - : new PasswordValidator()); - } - - var password = Membership.GeneratePassword( - passwordValidator.RequiredLength, - passwordValidator.RequireNonLetterOrDigit ? 2 : 0); - - var random = new Random(); - - var passwordChars = password.ToCharArray(); - - if (passwordValidator.RequireDigit && passwordChars.ContainsAny(Enumerable.Range(48, 58).Select(x => (char)x))) - password += Convert.ToChar(random.Next(48, 58)); // 0-9 - - if (passwordValidator.RequireLowercase && passwordChars.ContainsAny(Enumerable.Range(97, 123).Select(x => (char)x))) - password += Convert.ToChar(random.Next(97, 123)); // a-z - - if (passwordValidator.RequireUppercase && passwordChars.ContainsAny(Enumerable.Range(65, 91).Select(x => (char)x))) - password += Convert.ToChar(random.Next(65, 91)); // A-Z - - if (passwordValidator.RequireNonLetterOrDigit && passwordChars.ContainsAny(Enumerable.Range(33, 48).Select(x => (char)x))) - password += Convert.ToChar(random.Next(33, 48)); // symbols !"#$%&'()*+,-./ - + { + var password = PasswordGenerator.GeneratePassword(PasswordConfiguration); return password; } diff --git a/src/Umbraco.Web/Security/MembershipHelper.cs b/src/Umbraco.Web/Security/MembershipHelper.cs index 25a1cdcf42..aa9a4c6798 100644 --- a/src/Umbraco.Web/Security/MembershipHelper.cs +++ b/src/Umbraco.Web/Security/MembershipHelper.cs @@ -15,6 +15,7 @@ using Umbraco.Core.Composing; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Services; using Umbraco.Web.Editors; +using Umbraco.Web.Security.Providers; namespace Umbraco.Web.Security { @@ -23,7 +24,7 @@ namespace Umbraco.Web.Security /// public class MembershipHelper { - private readonly MembershipProvider _membershipProvider; + private readonly MembersMembershipProvider _membershipProvider; private readonly RoleProvider _roleProvider; private readonly IMemberService _memberService; private readonly IMemberTypeService _memberTypeService; @@ -38,7 +39,7 @@ namespace Umbraco.Web.Security ( HttpContextBase httpContext, IPublishedMemberCache memberCache, - MembershipProvider membershipProvider, + MembersMembershipProvider membershipProvider, RoleProvider roleProvider, IMemberService memberService, IMemberTypeService memberTypeService, @@ -106,15 +107,6 @@ namespace Umbraco.Web.Security return _publicAccessService.HasAccess(path, CurrentUserName, roleProvider.GetRolesForUser); } - /// - /// Returns true if the current membership provider is the Umbraco built-in one. - /// - /// - public bool IsUmbracoMembershipProviderActive() - { - var provider = _membershipProvider; - return provider.IsUmbracoMembershipProvider(); - } /// /// Updates the currently logged in members profile @@ -199,41 +191,28 @@ namespace Umbraco.Web.Security MembershipUser membershipUser; var provider = _membershipProvider; - //update their real name - if (provider.IsUmbracoMembershipProvider()) + membershipUser = ((UmbracoMembershipProviderBase)provider).CreateUser( + model.MemberTypeAlias, + model.Username, model.Password, model.Email, + // TODO: Support q/a http://issues.umbraco.org/issue/U4-3213 + null, null, + true, null, out status); + + if (status != MembershipCreateStatus.Success) return null; + + var member = _memberService.GetByUsername(membershipUser.UserName); + member.Name = model.Name; + + if (model.MemberProperties != null) { - membershipUser = ((UmbracoMembershipProviderBase)provider).CreateUser( - model.MemberTypeAlias, - model.Username, model.Password, model.Email, - // TODO: Support q/a http://issues.umbraco.org/issue/U4-3213 - null, null, - true, null, out status); - - if (status != MembershipCreateStatus.Success) return null; - - var member = _memberService.GetByUsername(membershipUser.UserName); - member.Name = model.Name; - - if (model.MemberProperties != null) + foreach (var property in model.MemberProperties.Where(p => p.Value != null) + .Where(property => member.Properties.Contains(property.Alias))) { - foreach (var property in model.MemberProperties.Where(p => p.Value != null) - .Where(property => member.Properties.Contains(property.Alias))) - { - member.Properties[property.Alias].SetValue(property.Value); - } + member.Properties[property.Alias].SetValue(property.Value); } - - _memberService.Save(member); } - else - { - membershipUser = provider.CreateUser(model.Username, model.Password, model.Email, - // TODO: Support q/a http://issues.umbraco.org/issue/U4-3213 - null, null, - true, null, out status); - if (status != MembershipCreateStatus.Success) return null; - } + _memberService.Save(member); if (logMemberIn) { @@ -390,49 +369,42 @@ namespace Umbraco.Web.Security var provider = _membershipProvider; - if (provider.IsUmbracoMembershipProvider()) + var membershipUser = provider.GetCurrentUserOnline(); + var member = GetCurrentPersistedMember(); + //this shouldn't happen but will if the member is deleted in the back office while the member is trying + // to use the front-end! + if (member == null) { - var membershipUser = provider.GetCurrentUserOnline(); - var member = GetCurrentPersistedMember(); - //this shouldn't happen but will if the member is deleted in the back office while the member is trying - // to use the front-end! - if (member == null) - { - //log them out since they've been removed - FormsAuthentication.SignOut(); + //log them out since they've been removed + FormsAuthentication.SignOut(); - return null; - } - - var model = ProfileModel.CreateModel(); - model.Name = member.Name; - model.MemberTypeAlias = member.ContentTypeAlias; - - model.Email = membershipUser.Email; - model.UserName = membershipUser.UserName; - model.PasswordQuestion = membershipUser.PasswordQuestion; - model.Comment = membershipUser.Comment; - model.IsApproved = membershipUser.IsApproved; - model.IsLockedOut = membershipUser.IsLockedOut; - model.LastLockoutDate = membershipUser.LastLockoutDate; - model.CreationDate = membershipUser.CreationDate; - model.LastLoginDate = membershipUser.LastLoginDate; - model.LastActivityDate = membershipUser.LastActivityDate; - model.LastPasswordChangedDate = membershipUser.LastPasswordChangedDate; - - - var memberType = _memberTypeService.Get(member.ContentTypeId); - - var builtIns = ConventionsHelper.GetStandardPropertyTypeStubs().Select(x => x.Key).ToArray(); - - model.MemberProperties = GetMemberPropertiesViewModel(memberType, builtIns, member).ToList(); - - return model; + return null; } - //we can try to look up an associated member by the provider user key - // TODO: Support this at some point! - throw new NotSupportedException("Currently a member profile cannot be edited unless using the built-in Umbraco membership providers"); + var model = ProfileModel.CreateModel(); + model.Name = member.Name; + model.MemberTypeAlias = member.ContentTypeAlias; + + model.Email = membershipUser.Email; + model.UserName = membershipUser.UserName; + model.PasswordQuestion = membershipUser.PasswordQuestion; + model.Comment = membershipUser.Comment; + model.IsApproved = membershipUser.IsApproved; + model.IsLockedOut = membershipUser.IsLockedOut; + model.LastLockoutDate = membershipUser.LastLockoutDate; + model.CreationDate = membershipUser.CreationDate; + model.LastLoginDate = membershipUser.LastLoginDate; + model.LastActivityDate = membershipUser.LastActivityDate; + model.LastPasswordChangedDate = membershipUser.LastPasswordChangedDate; + + + var memberType = _memberTypeService.Get(member.ContentTypeId); + + var builtIns = ConventionsHelper.GetStandardPropertyTypeStubs().Select(x => x.Key).ToArray(); + + model.MemberProperties = GetMemberPropertiesViewModel(memberType, builtIns, member).ToList(); + + return model; } /// @@ -443,25 +415,16 @@ namespace Umbraco.Web.Security public virtual RegisterModel CreateRegistrationModel(string memberTypeAlias = null) { var provider = _membershipProvider; - if (provider.IsUmbracoMembershipProvider()) - { - memberTypeAlias = memberTypeAlias ?? Constants.Conventions.MemberTypes.DefaultAlias; - var memberType = _memberTypeService.Get(memberTypeAlias); - if (memberType == null) - throw new InvalidOperationException("Could not find a member type with alias " + memberTypeAlias); + memberTypeAlias = memberTypeAlias ?? Constants.Conventions.MemberTypes.DefaultAlias; + var memberType = _memberTypeService.Get(memberTypeAlias); + if (memberType == null) + throw new InvalidOperationException("Could not find a member type with alias " + memberTypeAlias); - var builtIns = ConventionsHelper.GetStandardPropertyTypeStubs().Select(x => x.Key).ToArray(); - var model = RegisterModel.CreateModel(); - model.MemberTypeAlias = memberTypeAlias; - model.MemberProperties = GetMemberPropertiesViewModel(memberType, builtIns).ToList(); - return model; - } - else - { - var model = RegisterModel.CreateModel(); - model.MemberTypeAlias = string.Empty; - return model; - } + var builtIns = ConventionsHelper.GetStandardPropertyTypeStubs().Select(x => x.Key).ToArray(); + var model = RegisterModel.CreateModel(); + model.MemberTypeAlias = memberTypeAlias; + model.MemberProperties = GetMemberPropertiesViewModel(memberType, builtIns).ToList(); + return model; } private IEnumerable GetMemberPropertiesViewModel(IMemberType memberType, IEnumerable builtIns, IMember member = null) @@ -557,38 +520,19 @@ namespace Umbraco.Web.Security var provider = _membershipProvider; - if (provider.IsUmbracoMembershipProvider()) + var member = GetCurrentPersistedMember(); + //this shouldn't happen but will if the member is deleted in the back office while the member is trying + // to use the front-end! + if (member == null) { - var member = GetCurrentPersistedMember(); - //this shouldn't happen but will if the member is deleted in the back office while the member is trying - // to use the front-end! - if (member == null) - { - //log them out since they've been removed - FormsAuthentication.SignOut(); - model.IsLoggedIn = false; - return model; - } - model.Name = member.Name; - model.Username = member.Username; - model.Email = member.Email; - } - else - { - var member = provider.GetCurrentUserOnline(); - //this shouldn't happen but will if the member is deleted in the back office while the member is trying - // to use the front-end! - if (member == null) - { - //log them out since they've been removed - FormsAuthentication.SignOut(); - model.IsLoggedIn = false; - return model; - } - model.Name = member.UserName; - model.Username = member.UserName; - model.Email = member.Email; + //log them out since they've been removed + FormsAuthentication.SignOut(); + model.IsLoggedIn = false; + return model; } + model.Name = member.Name; + model.Username = member.Username; + model.Email = member.Email; model.IsLoggedIn = true; return model; @@ -641,36 +585,25 @@ namespace Umbraco.Web.Security string username; - if (provider.IsUmbracoMembershipProvider()) + var member = GetCurrentPersistedMember(); + // If a member could not be resolved from the provider, we are clearly not authorized and can break right here + if (member == null) + return false; + username = member.Username; + + // If types defined, check member is of one of those types + var allowTypesList = allowTypes as IList ?? allowTypes.ToList(); + if (allowTypesList.Any(allowType => allowType != string.Empty)) { - var member = GetCurrentPersistedMember(); - // If a member could not be resolved from the provider, we are clearly not authorized and can break right here - if (member == null) - return false; - username = member.Username; - - // If types defined, check member is of one of those types - var allowTypesList = allowTypes as IList ?? allowTypes.ToList(); - if (allowTypesList.Any(allowType => allowType != string.Empty)) - { - // Allow only if member's type is in list - allowAction = allowTypesList.Select(x => x.ToLowerInvariant()).Contains(member.ContentType.Alias.ToLowerInvariant()); - } - - // If specific members defined, check member is of one of those - if (allowAction && allowMembers.Any()) - { - // Allow only if member's Id is in the list - allowAction = allowMembers.Contains(member.Id); - } + // Allow only if member's type is in list + allowAction = allowTypesList.Select(x => x.ToLowerInvariant()).Contains(member.ContentType.Alias.ToLowerInvariant()); } - else + + // If specific members defined, check member is of one of those + if (allowAction && allowMembers.Any()) { - var member = provider.GetCurrentUser(); - // If a member could not be resolved from the provider, we are clearly not authorized and can break right here - if (member == null) - return false; - username = member.UserName; + // Allow only if member's Id is in the list + allowAction = allowMembers.Contains(member.Id); } // If groups defined, check member is of one of those groups @@ -783,10 +716,6 @@ namespace Umbraco.Web.Security { var provider = _membershipProvider; - if (provider.IsUmbracoMembershipProvider() == false) - { - throw new NotSupportedException("An IMember model can only be retrieved when using the built-in Umbraco membership providers"); - } var username = provider.GetCurrentUserName(); var member = _memberService.GetByUsername(username); return member; diff --git a/src/Umbraco.Web/Security/MembershipProviderExtensions.cs b/src/Umbraco.Web/Security/MembershipProviderExtensions.cs new file mode 100644 index 0000000000..9760a6c3f8 --- /dev/null +++ b/src/Umbraco.Web/Security/MembershipProviderExtensions.cs @@ -0,0 +1,75 @@ +using System; +using System.Security.Principal; +using System.Threading; +using System.Web; +using System.Web.Hosting; +using System.Web.Security; +using Umbraco.Core; +using Umbraco.Web.Security.Providers; + +namespace Umbraco.Web.Security +{ + public static class MembershipProviderExtensions + { + + + /// + /// Method to get the Umbraco Members membership provider based on its alias + /// + /// + public static MembersMembershipProvider GetMembersMembershipProvider() + { + if (Membership.Providers[Constants.Conventions.Member.UmbracoMemberProviderName] == null) + { + throw new InvalidOperationException("No membership provider found with name " + Constants.Conventions.Member.UmbracoMemberProviderName); + } + return (MembersMembershipProvider)Membership.Providers[Constants.Conventions.Member.UmbracoMemberProviderName]; + } + + /// + /// Returns the currently logged in MembershipUser and flags them as being online - use sparingly (i.e. login) + /// + /// + /// + public static MembershipUser GetCurrentUserOnline(this MembershipProvider membershipProvider) + { + var username = membershipProvider.GetCurrentUserName(); + return username.IsNullOrWhiteSpace() + ? null + : membershipProvider.GetUser(username, true); + } + + /// + /// Returns the currently logged in MembershipUser + /// + /// + /// + internal static MembershipUser GetCurrentUser(this MembershipProvider membershipProvider) + { + var username = membershipProvider.GetCurrentUserName(); + return username.IsNullOrWhiteSpace() + ? null + : membershipProvider.GetUser(username, false); + } + + /// + /// Just returns the current user's login name (just a wrapper). + /// + /// + /// + internal static string GetCurrentUserName(this MembershipProvider membershipProvider) + { + if (HostingEnvironment.IsHosted) + { + HttpContext current = HttpContext.Current; + if (current != null && current.User != null && current.User.Identity != null) + return current.User.Identity.Name; + } + IPrincipal currentPrincipal = Thread.CurrentPrincipal; + if (currentPrincipal == null || currentPrincipal.Identity == null) + return string.Empty; + else + return currentPrincipal.Identity.Name; + } + } +} diff --git a/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs b/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs index 656b4a4c66..84bdd5dd37 100644 --- a/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs +++ b/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs @@ -8,6 +8,7 @@ using Umbraco.Core.Security; using Umbraco.Core.Services; using Umbraco.Core.Models.Membership; using Umbraco.Web.Composing; +using System; namespace Umbraco.Web.Security.Providers { @@ -81,6 +82,16 @@ namespace Umbraco.Web.Security.Providers _providerKeyAsGuid = true; } } + + PasswordConfiguration = new MembershipProviderPasswordConfiguration( + MinRequiredPasswordLength, + MinRequiredNonAlphanumericCharacters > 0, + false, false, false, UseLegacyEncoding, + CustomHashAlgorithmType ?? Membership.HashAlgorithmType, + MaxInvalidPasswordAttempts); + + _passwordSecurity = new PasswordSecurity(PasswordConfiguration); + } protected override Attempt GetRawPassword(string username) @@ -112,5 +123,41 @@ namespace Umbraco.Web.Security.Providers return _defaultMemberTypeAlias; } } + + private PasswordSecurity _passwordSecurity; + + public override PasswordSecurity PasswordSecurity => _passwordSecurity; + public IPasswordConfiguration PasswordConfiguration { get; private set; } + + private class MembershipProviderPasswordConfiguration : IPasswordConfiguration + { + public MembershipProviderPasswordConfiguration(int requiredLength, bool requireNonLetterOrDigit, bool requireDigit, bool requireLowercase, bool requireUppercase, bool useLegacyEncoding, string hashAlgorithmType, int maxFailedAccessAttemptsBeforeLockout) + { + RequiredLength = requiredLength; + RequireNonLetterOrDigit = requireNonLetterOrDigit; + RequireDigit = requireDigit; + RequireLowercase = requireLowercase; + RequireUppercase = requireUppercase; + UseLegacyEncoding = useLegacyEncoding; + HashAlgorithmType = hashAlgorithmType ?? throw new ArgumentNullException(nameof(hashAlgorithmType)); + MaxFailedAccessAttemptsBeforeLockout = maxFailedAccessAttemptsBeforeLockout; + } + + public int RequiredLength { get; } + + public bool RequireNonLetterOrDigit { get; } + + public bool RequireDigit { get; } + + public bool RequireLowercase { get; } + + public bool RequireUppercase { get; } + + public bool UseLegacyEncoding { get; } + + public string HashAlgorithmType { get; } + + public int MaxFailedAccessAttemptsBeforeLockout { get; } + } } } diff --git a/src/Umbraco.Web/Security/Providers/UmbracoMembershipProvider.cs b/src/Umbraco.Web/Security/Providers/UmbracoMembershipProvider.cs index cb63c5a8cf..9789f86978 100644 --- a/src/Umbraco.Web/Security/Providers/UmbracoMembershipProvider.cs +++ b/src/Umbraco.Web/Security/Providers/UmbracoMembershipProvider.cs @@ -3,7 +3,6 @@ using System.Collections.Specialized; using System.Configuration.Provider; using System.Linq; using System.Text; -using System.Web; using System.Web.Configuration; using System.Web.Security; using Umbraco.Core; @@ -14,7 +13,6 @@ using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Security; using Umbraco.Core.Services; using Umbraco.Web.Composing; -using Umbraco.Core.Models.Identity; namespace Umbraco.Web.Security.Providers { @@ -93,9 +91,9 @@ namespace Umbraco.Web.Security.Providers if (m == null) return false; string salt; - var encodedPassword = EncryptOrHashNewPassword(newPassword, out salt); + var encodedPassword = PasswordSecurity.EncryptOrHashNewPassword(newPassword, out salt); - m.RawPasswordValue = FormatPasswordForStorage(encodedPassword, salt); + m.RawPasswordValue = PasswordSecurity.FormatPasswordForStorage(encodedPassword, salt); m.LastPasswordChangeDate = DateTime.Now; MemberService.Save(m); @@ -164,12 +162,12 @@ namespace Umbraco.Web.Security.Providers } string salt; - var encodedPassword = EncryptOrHashNewPassword(password, out salt); + var encodedPassword = PasswordSecurity.EncryptOrHashNewPassword(password, out salt); var member = MemberService.CreateWithIdentity( username, email, - FormatPasswordForStorage(encodedPassword, salt), + PasswordSecurity.FormatPasswordForStorage(encodedPassword, salt), memberTypeAlias, isApproved); @@ -442,8 +440,8 @@ namespace Umbraco.Web.Security.Providers } string salt; - var encodedPassword = EncryptOrHashNewPassword(generatedPassword, out salt); - m.RawPasswordValue = FormatPasswordForStorage(encodedPassword, salt); + var encodedPassword = PasswordSecurity.EncryptOrHashNewPassword(generatedPassword, out salt); + m.RawPasswordValue = PasswordSecurity.FormatPasswordForStorage(encodedPassword, salt); m.LastPasswordChangeDate = DateTime.Now; MemberService.Save(m); @@ -555,7 +553,7 @@ namespace Umbraco.Web.Security.Providers }; } - var authenticated = CheckPassword(password, member.RawPasswordValue); + var authenticated = PasswordSecurity.CheckPassword(password, member.RawPasswordValue); if (authenticated == false) { diff --git a/src/Umbraco.Web/Security/Providers/UsersMembershipProvider.cs b/src/Umbraco.Web/Security/Providers/UsersMembershipProvider.cs deleted file mode 100644 index 5d9fa1d567..0000000000 --- a/src/Umbraco.Web/Security/Providers/UsersMembershipProvider.cs +++ /dev/null @@ -1,177 +0,0 @@ -using System; -using System.Configuration.Provider; -using System.Web; -using System.Web.Security; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Models.Identity; -using Umbraco.Core.Models.Membership; -using Umbraco.Core.Security; -using Umbraco.Core.Services; -using Umbraco.Web.Composing; - -namespace Umbraco.Web.Security.Providers -{ - /// - /// Custom Membership Provider for Umbraco Users (User authentication for Umbraco Backend CMS) - /// - public class UsersMembershipProvider : UmbracoMembershipProvider - { - - public UsersMembershipProvider() - : this(Current.Services.UserService, Current.Services.MemberTypeService, Current.UmbracoVersion) - { - } - - public UsersMembershipProvider(IMembershipMemberService memberService, IMemberTypeService memberTypeService, IUmbracoVersion umbracoVersion) - : base(memberService, umbracoVersion) - { - _memberTypeService = memberTypeService; - } - - private readonly IMemberTypeService _memberTypeService; - private string _defaultMemberTypeAlias = "writer"; - private volatile bool _hasDefaultMember = false; - private static readonly object Locker = new object(); - - public override string ProviderName => Constants.Security.UserMembershipProviderName; - - protected override MembershipUser ConvertToMembershipUser(IUser entity) - { - //the provider user key is always the int id - return entity.AsConcreteMembershipUser(Name, true); - } - - private bool _allowManuallyChangingPassword = false; - private bool _enablePasswordReset = false; - - /// - /// Indicates whether the membership provider is configured to allow users to reset their passwords. - /// - /// - /// true if the membership provider supports password reset; otherwise, false. The default is FALSE for users. - public override bool EnablePasswordReset => _enablePasswordReset; - - /// - /// For backwards compatibility, this provider supports this option by default it is FALSE for users - /// - public override bool AllowManuallyChangingPassword => _allowManuallyChangingPassword; - - public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config) - { - base.Initialize(name, config); - - if (config == null) { throw new ArgumentNullException("config"); } - - _allowManuallyChangingPassword = config.GetValue("allowManuallyChangingPassword", false); - _enablePasswordReset = config.GetValue("enablePasswordReset", false); - - // test for membertype (if not specified, choose the first member type available) - // We'll support both names for legacy reasons: defaultUserTypeAlias & defaultUserGroupAlias - - if (config["defaultUserTypeAlias"] != null) - { - if (config["defaultUserTypeAlias"].IsNullOrWhiteSpace() == false) - { - _defaultMemberTypeAlias = config["defaultUserTypeAlias"]; - _hasDefaultMember = true; - } - } - if (_hasDefaultMember == false && config["defaultUserGroupAlias"] != null) - { - if (config["defaultUserGroupAlias"].IsNullOrWhiteSpace() == false) - { - _defaultMemberTypeAlias = config["defaultUserGroupAlias"]; - _hasDefaultMember = true; - } - } - } - - public override string DefaultMemberTypeAlias - { - get - { - if (_hasDefaultMember == false) - { - lock (Locker) - { - if (_hasDefaultMember == false) - { - _defaultMemberTypeAlias = _memberTypeService.GetDefault(); - if (_defaultMemberTypeAlias.IsNullOrWhiteSpace()) - { - throw new ProviderException("No default user group alias is specified in the web.config string. Please add a 'defaultUserTypeAlias' to the add element in the provider declaration in web.config"); - } - _hasDefaultMember = true; - } - } - } - return _defaultMemberTypeAlias; - } - } - - /// - /// Overridden in order to call the BackOfficeUserManager.UnlockUser method in order to raise the user audit events - /// - /// - /// - /// - internal override bool PerformUnlockUser(string username, out IUser member) - { - var result = base.PerformUnlockUser(username, out member); - if (result) - { - var userManager = GetBackofficeUserManager(); - if (userManager != null) - { - userManager.RaiseAccountUnlockedEvent(member.Id); - } - } - return result; - } - - /// - /// Override in order to raise appropriate events via the - /// - /// - /// - /// - internal override ValidateUserResult PerformValidateUser(string username, string password) - { - var result = base.PerformValidateUser(username, password); - - var userManager = GetBackofficeUserManager(); - - if (userManager == null) return result; - - if (result.Authenticated == false) - { - var count = result.Member.FailedPasswordAttempts; - if (count >= MaxInvalidPasswordAttempts) - { - userManager.RaiseAccountLockedEvent(result.Member.Id); - } - } - else - { - if (result.Member.FailedPasswordAttempts > 0) - { - //we have successfully logged in, if the failed password attempts was modified it means it was reset - if (result.Member.WasPropertyDirty("FailedPasswordAttempts")) - { - userManager.RaiseResetAccessFailedCountEvent(result.Member.Id); - } - } - } - - return result; - } - - internal BackOfficeUserManager GetBackofficeUserManager() - { - return HttpContext.Current == null - ? null - : HttpContext.Current.GetOwinContext().GetBackOfficeUserManager(); - } - } -} diff --git a/src/Umbraco.Web/Security/WebSecurity.cs b/src/Umbraco.Web/Security/WebSecurity.cs index 256238ae9b..c4836168dc 100644 --- a/src/Umbraco.Web/Security/WebSecurity.cs +++ b/src/Umbraco.Web/Security/WebSecurity.cs @@ -1,23 +1,20 @@ using System; -using System.Collections.Generic; using System.Linq; using System.Security; using System.Web; using Umbraco.Core; using Umbraco.Core.Services; using Umbraco.Core.Models.Membership; -using Umbraco.Core.Security; using Microsoft.AspNet.Identity.Owin; using Microsoft.Owin; using Umbraco.Core.Configuration; -using Umbraco.Core.Composing; -using Umbraco.Core.IO; using Umbraco.Core.Models; using Umbraco.Core.Models.Identity; using Current = Umbraco.Web.Composing.Current; namespace Umbraco.Web.Security { + /// /// A utility class used for dealing with USER security in Umbraco /// diff --git a/src/Umbraco.Web/Trees/MemberTreeController.cs b/src/Umbraco.Web/Trees/MemberTreeController.cs index c0a9d15cfa..7c97873a2c 100644 --- a/src/Umbraco.Web/Trees/MemberTreeController.cs +++ b/src/Umbraco.Web/Trees/MemberTreeController.cs @@ -18,6 +18,7 @@ using Umbraco.Web.WebApi.Filters; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Search; using Constants = Umbraco.Core.Constants; +using Umbraco.Web.Security; namespace Umbraco.Web.Trees { @@ -36,13 +37,11 @@ namespace Umbraco.Web.Trees public MemberTreeController(UmbracoTreeSearcher treeSearcher) { _treeSearcher = treeSearcher; - _provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); - _isUmbracoProvider = _provider.IsUmbracoMembershipProvider(); + _provider = MembershipProviderExtensions.GetMembersMembershipProvider(); } private readonly UmbracoTreeSearcher _treeSearcher; private readonly MembershipProvider _provider; - private readonly bool _isUmbracoProvider; /// /// Gets an individual tree node @@ -61,60 +60,32 @@ namespace Umbraco.Web.Trees protected TreeNode GetSingleTreeNode(string id, FormDataCollection queryStrings) { - if (_isUmbracoProvider) + Guid asGuid; + if (Guid.TryParse(id, out asGuid) == false) { - Guid asGuid; - if (Guid.TryParse(id, out asGuid) == false) - { - throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); - } - - var member = Services.MemberService.GetByKey(asGuid); - if (member == null) - { - throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); - } - - var node = CreateTreeNode( - member.Key.ToString("N"), - "-1", - queryStrings, - member.Name, - Constants.Icons.Member, - false, - "", - Udi.Create(ObjectTypes.GetUdiType(Constants.ObjectTypes.Member), member.Key)); - - node.AdditionalData.Add("contentType", member.ContentTypeAlias); - node.AdditionalData.Add("isContainer", true); - - return node; + throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); } - else + + var member = Services.MemberService.GetByKey(asGuid); + if (member == null) { - object providerId = id; - Guid asGuid; - if (Guid.TryParse(id, out asGuid)) - { - providerId = asGuid; - } - - var member = _provider.GetUser(providerId, false); - if (member == null) - { - throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); - } - - var node = CreateTreeNode( - member.ProviderUserKey.TryConvertTo().Result.ToString("N"), - "-1", - queryStrings, - member.UserName, - Constants.Icons.Member, - false); - - return node; + throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); } + + var node = CreateTreeNode( + member.Key.ToString("N"), + "-1", + queryStrings, + member.Name, + Constants.Icons.Member, + false, + "", + Udi.Create(ObjectTypes.GetUdiType(Constants.ObjectTypes.Member), member.Key)); + + node.AdditionalData.Add("contentType", member.ContentTypeAlias); + node.AdditionalData.Add("isContainer", true); + + return node; } protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) @@ -127,13 +98,10 @@ namespace Umbraco.Web.Trees CreateTreeNode(Constants.Conventions.MemberTypes.AllMembersListId, id, queryStrings, Services.TextService.Localize("member/allMembers"), Constants.Icons.MemberType, true, queryStrings.GetRequiredValue("application") + TreeAlias.EnsureStartsWith('/') + "/list/" + Constants.Conventions.MemberTypes.AllMembersListId)); - if (_isUmbracoProvider) - { - nodes.AddRange(Services.MemberTypeService.GetAll() + nodes.AddRange(Services.MemberTypeService.GetAll() .Select(memberType => CreateTreeNode(memberType.Alias, id, queryStrings, memberType.Name, memberType.Icon.IfNullOrWhiteSpace(Constants.Icons.Member), true, queryStrings.GetRequiredValue("application") + TreeAlias.EnsureStartsWith('/') + "/list/" + memberType.Alias))); - } } //There is no menu for any of these nodes @@ -151,27 +119,11 @@ namespace Umbraco.Web.Trees if (id == Constants.System.RootString) { // root actions - if (_provider.IsUmbracoMembershipProvider()) - { - //set default - menu.DefaultMenuAlias = ActionNew.ActionAlias; + //set default + menu.DefaultMenuAlias = ActionNew.ActionAlias; - //Create the normal create action - menu.Items.Add(Services.TextService, opensDialog: true); - } - else - { - //Create a custom create action - this does not launch a dialog, it just navigates to the create screen - // we'll create it based on the ActionNew so it maintains the same icon properties, name, etc... - var createMenuItem = new MenuItem(ActionNew.ActionAlias, Services.TextService) - { - Icon = "add", - OpensDialog = true - }; - //we want to go to this route: /member/member/edit/-1?create=true - createMenuItem.NavigateToRoute("/member/member/edit/-1?create=true"); - menu.Items.Add(createMenuItem); - } + //Create the normal create action + menu.Items.Add(Services.TextService, opensDialog: true); menu.Items.Add(new RefreshNode(Services.TextService, true)); return menu; diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index a732dfeb10..533eae8eee 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -257,6 +257,7 @@ + @@ -835,7 +836,7 @@ - + @@ -1020,7 +1021,6 @@ - diff --git a/src/Umbraco.Web/UmbracoDefaultOwinStartup.cs b/src/Umbraco.Web/UmbracoDefaultOwinStartup.cs index 0633cca3a0..801a09aaae 100644 --- a/src/Umbraco.Web/UmbracoDefaultOwinStartup.cs +++ b/src/Umbraco.Web/UmbracoDefaultOwinStartup.cs @@ -27,7 +27,9 @@ namespace Umbraco.Web protected IUmbracoContextAccessor UmbracoContextAccessor => Current.UmbracoContextAccessor; protected IGlobalSettings GlobalSettings => Current.Configs.Global(); protected IUmbracoSettingsSection UmbracoSettings => Current.Configs.Settings(); + protected IUserPasswordConfiguration UserPasswordConfig => Current.Configs.UserPasswordConfig(); protected IRuntimeState RuntimeState => Core.Composing.Current.RuntimeState; + protected IPasswordGenerator PasswordGenerator => Core.Composing.Current.PasswordGenerator; protected ServiceContext Services => Current.Services; protected UmbracoMapper Mapper => Current.Mapper; @@ -85,7 +87,8 @@ namespace Umbraco.Web Mapper, UmbracoSettings.Content, GlobalSettings, - Core.Security.MembershipProviderExtensions.GetUsersMembershipProvider().AsUmbracoMembershipProvider()); + UserPasswordConfig, + PasswordGenerator); } /// From 763b090b4d525ac28f20cb281e688a5bb7a955b7 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 25 Nov 2019 22:08:08 +1100 Subject: [PATCH 08/18] Fixes up issues with members, now it still works in the UI --- .../views/member/member.edit.controller.js | 2 +- src/Umbraco.Web/Editors/PasswordChanger.cs | 9 +++-- .../Mapping/MemberTabsAndPropertiesMapper.cs | 21 +++++++--- .../PasswordConfigurationExtensions.cs | 2 +- .../Security/MembershipProviderExtensions.cs | 39 ++++++++++++++++++- .../Providers/MembersMembershipProvider.cs | 17 ++++---- 6 files changed, 72 insertions(+), 18 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/member/member.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/member/member.edit.controller.js index d36fd6d853..964c783bc0 100644 --- a/src/Umbraco.Web.UI.Client/src/views/member/member.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/member/member.edit.controller.js @@ -148,7 +148,7 @@ function MemberEditController($scope, $routeParams, $location, appState, memberR //anytime a user is changing a member's password without the oldPassword, we are in effect resetting it so we need to set that flag here var passwordProp = _.find(contentEditingHelper.getAllProps($scope.content), function (e) { return e.alias === '_umb_password' }); if (passwordProp && passwordProp.value && (typeof passwordProp.value.reset !== 'undefined') && !passwordProp.value.reset) { - //so if the admin is not explicitly resetting the password, flag it for resetting if a new password is being entered + // if the admin is not explicitly resetting the password, flag it for resetting if a new password is being entered passwordProp.value.reset = !passwordProp.value.oldPassword && passwordProp.config.allowManuallyChangingPassword; } diff --git a/src/Umbraco.Web/Editors/PasswordChanger.cs b/src/Umbraco.Web/Editors/PasswordChanger.cs index 9bc77a4a28..e1546cae51 100644 --- a/src/Umbraco.Web/Editors/PasswordChanger.cs +++ b/src/Umbraco.Web/Editors/PasswordChanger.cs @@ -103,7 +103,7 @@ namespace Umbraco.Web.Editors } //is the old password correct? var validateResult = await userMgr.CheckPasswordAsync(backOfficeIdentityUser, passwordModel.OldPassword); - if(validateResult == false) + if (validateResult == false) { //no, fail with an error message for "oldPassword" return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Incorrect password", new[] { "oldPassword" }) }); @@ -127,7 +127,10 @@ namespace Umbraco.Web.Editors /// /// /// - public Attempt ChangePasswordWithMembershipProvider(string username, ChangingPasswordModel passwordModel, MembershipProvider membershipProvider) + public Attempt ChangePasswordWithMembershipProvider( + string username, + ChangingPasswordModel passwordModel, + MembershipProvider membershipProvider) { var umbracoBaseProvider = membershipProvider as MembershipProviderBase; @@ -158,7 +161,7 @@ namespace Umbraco.Web.Editors //this is only possible when using a membership provider if the membership provider supports AllowManuallyChangingPassword if (passwordModel.NewPassword.IsNullOrWhiteSpace() == false) { - if (umbracoBaseProvider !=null && umbracoBaseProvider.AllowManuallyChangingPassword) + if (umbracoBaseProvider != null && umbracoBaseProvider.AllowManuallyChangingPassword) { //this provider allows manually changing the password without the old password, so we can just do it try diff --git a/src/Umbraco.Web/Models/Mapping/MemberTabsAndPropertiesMapper.cs b/src/Umbraco.Web/Models/Mapping/MemberTabsAndPropertiesMapper.cs index 3bf6f5c066..3e000e53d6 100644 --- a/src/Umbraco.Web/Models/Mapping/MemberTabsAndPropertiesMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/MemberTabsAndPropertiesMapper.cs @@ -12,6 +12,7 @@ using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; using Umbraco.Core.Dictionary; using Umbraco.Web.Security; +using Umbraco.Web.Security.Providers; namespace Umbraco.Web.Models.Mapping { @@ -137,11 +138,7 @@ namespace Umbraco.Web.Models.Mapping // TODO: Hard coding this because the changepassword doesn't necessarily need to be a resolvable (real) property editor View = "changepassword", // initialize the dictionary with the configuration from the default membership provider - Config = new Dictionary(membersProvider.PasswordConfiguration.GetConfiguration()) - { - // the password change toggle will only be displayed if there is already a password assigned. - {"hasPassword", member.RawPasswordValue.IsNullOrWhiteSpace() == false} - } + Config = GetPasswordConfig(membersProvider, member) }, new ContentPropertyDisplay { @@ -156,6 +153,20 @@ namespace Umbraco.Web.Models.Mapping return genericProperties; } + private Dictionary GetPasswordConfig(MembersMembershipProvider membersProvider, IMember member) + { + var result = new Dictionary(membersProvider.PasswordConfiguration.GetConfiguration()) + { + // the password change toggle will only be displayed if there is already a password assigned. + {"hasPassword", member.RawPasswordValue.IsNullOrWhiteSpace() == false} + }; + + result["enableReset"] = membersProvider.CanResetPassword(_userService); + result["allowManuallyChangingPassword"] = membersProvider.AllowManuallyChangingPassword; + + return result; + } + /// /// Overridden to assign the IsSensitive property values /// diff --git a/src/Umbraco.Web/PasswordConfigurationExtensions.cs b/src/Umbraco.Web/PasswordConfigurationExtensions.cs index 95b198bbb7..8418531cb7 100644 --- a/src/Umbraco.Web/PasswordConfigurationExtensions.cs +++ b/src/Umbraco.Web/PasswordConfigurationExtensions.cs @@ -28,7 +28,7 @@ namespace Umbraco.Web // has permission to the user section to edit them. Similarly, when we have ASP.Net identity enabled for members, these legacy settings // will no longer exist and admins will just be able to change a members' password if they have access to the member section to edit them. {"allowManuallyChangingPassword", true}, - {"enableReset", false}, + {"enableReset", false}, // TODO: Actually, this is still used for the member editor, see MemberTabsAndPropertiesMapper.GetPasswordConfig, need to remove that eventually {"enablePasswordRetrieval", false}, {"requiresQuestionAnswer", false} diff --git a/src/Umbraco.Web/Security/MembershipProviderExtensions.cs b/src/Umbraco.Web/Security/MembershipProviderExtensions.cs index 9760a6c3f8..ba0abee7e5 100644 --- a/src/Umbraco.Web/Security/MembershipProviderExtensions.cs +++ b/src/Umbraco.Web/Security/MembershipProviderExtensions.cs @@ -5,13 +5,50 @@ using System.Web; using System.Web.Hosting; using System.Web.Security; using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.Security; +using Umbraco.Core.Services; using Umbraco.Web.Security.Providers; namespace Umbraco.Web.Security { public static class MembershipProviderExtensions { - + + /// + /// Extension method to check if a password can be reset based on a given provider and the current request (logged in user) + /// + /// + /// + /// + /// + /// An Admin can always reset the password + /// + internal static bool CanResetPassword(this MembershipProvider provider, IUserService userService) + { + if (provider == null) throw new ArgumentNullException("provider"); + + var canReset = provider.EnablePasswordReset; + + if (userService == null) return canReset; + + //we need to check for the special case in which a user is an admin - in which case they can reset the password even if EnablePasswordReset == false + if (provider.EnablePasswordReset == false) + { + var identity = Thread.CurrentPrincipal.GetUmbracoIdentity(); + if (identity != null) + { + var user = userService.GetUserById(identity.Id.TryConvertTo().Result); + if (user == null) throw new InvalidOperationException("No user with username " + identity.Username + " found"); + var userIsAdmin = user.IsAdmin(); + if (userIsAdmin) + { + canReset = true; + } + } + } + return canReset; + } /// /// Method to get the Umbraco Members membership provider based on its alias diff --git a/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs b/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs index 84bdd5dd37..8fef87f436 100644 --- a/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs +++ b/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs @@ -83,14 +83,16 @@ namespace Umbraco.Web.Security.Providers } } - PasswordConfiguration = new MembershipProviderPasswordConfiguration( + // these need to be lazy else we get a stack overflow since we cannot access Membership.HashAlgorithmType without initializing the providers + + _passwordConfig = new Lazy(() => new MembershipProviderPasswordConfiguration( MinRequiredPasswordLength, MinRequiredNonAlphanumericCharacters > 0, false, false, false, UseLegacyEncoding, - CustomHashAlgorithmType ?? Membership.HashAlgorithmType, - MaxInvalidPasswordAttempts); + CustomHashAlgorithmType.IsNullOrWhiteSpace() ? Membership.HashAlgorithmType : CustomHashAlgorithmType, + MaxInvalidPasswordAttempts)); - _passwordSecurity = new PasswordSecurity(PasswordConfiguration); + _passwordSecurity = new Lazy(() => new PasswordSecurity(PasswordConfiguration)); } @@ -124,10 +126,11 @@ namespace Umbraco.Web.Security.Providers } } - private PasswordSecurity _passwordSecurity; + private Lazy _passwordSecurity; + private Lazy _passwordConfig; - public override PasswordSecurity PasswordSecurity => _passwordSecurity; - public IPasswordConfiguration PasswordConfiguration { get; private set; } + public override PasswordSecurity PasswordSecurity => _passwordSecurity.Value; + public IPasswordConfiguration PasswordConfiguration => _passwordConfig.Value; private class MembershipProviderPasswordConfiguration : IPasswordConfiguration { From 73bb9e3279572724a8032e95f711994cf65c99a8 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 25 Nov 2019 23:10:54 +1100 Subject: [PATCH 09/18] Moves culture dictionary implementation to Core instead of web since it has nothing to do with Web really, fixes tests --- .../Composing/CompositionExtensions/Services.cs | 1 + .../Dictionary/UmbracoCultureDictionary.cs | 5 ++--- .../Dictionary/UmbracoCultureDictionaryFactory.cs | 6 +++--- .../Repositories/Implement/UserRepository.cs | 6 +++--- src/Umbraco.Core/Runtime/CoreInitialComposer.cs | 3 +++ src/Umbraco.Core/Umbraco.Core.csproj | 2 ++ .../Models/Mapping/ContentWebModelMappingTests.cs | 3 --- src/Umbraco.Tests/Testing/UmbracoTestBase.cs | 2 +- src/Umbraco.Web/Editors/AuthenticationController.cs | 2 +- src/Umbraco.Web/Install/InstallSteps/NewInstallStep.cs | 2 +- src/Umbraco.Web/Models/Mapping/MemberMapDefinition.cs | 4 +--- src/Umbraco.Web/Runtime/WebFinalComposer.cs | 10 ---------- src/Umbraco.Web/Runtime/WebInitialComposer.cs | 3 --- src/Umbraco.Web/Umbraco.Web.csproj | 2 -- 14 files changed, 18 insertions(+), 33 deletions(-) rename src/{Umbraco.Web => Umbraco.Core}/Dictionary/UmbracoCultureDictionary.cs (97%) rename src/{Umbraco.Web => Umbraco.Core}/Dictionary/UmbracoCultureDictionaryFactory.cs (79%) diff --git a/src/Umbraco.Core/Composing/CompositionExtensions/Services.cs b/src/Umbraco.Core/Composing/CompositionExtensions/Services.cs index ba8442f8d7..b0cd80d93f 100644 --- a/src/Umbraco.Core/Composing/CompositionExtensions/Services.cs +++ b/src/Umbraco.Core/Composing/CompositionExtensions/Services.cs @@ -3,6 +3,7 @@ using System.IO; using System.Linq; using Umbraco.Core.Cache; using Umbraco.Core.Configuration; +using Umbraco.Core.Dictionary; using Umbraco.Core.Events; using Umbraco.Core.IO; using Umbraco.Core.Logging; diff --git a/src/Umbraco.Web/Dictionary/UmbracoCultureDictionary.cs b/src/Umbraco.Core/Dictionary/UmbracoCultureDictionary.cs similarity index 97% rename from src/Umbraco.Web/Dictionary/UmbracoCultureDictionary.cs rename to src/Umbraco.Core/Dictionary/UmbracoCultureDictionary.cs index f11b2dcfcb..b0690c944b 100644 --- a/src/Umbraco.Web/Dictionary/UmbracoCultureDictionary.cs +++ b/src/Umbraco.Core/Dictionary/UmbracoCultureDictionary.cs @@ -5,9 +5,8 @@ using System.Linq; using Umbraco.Core.Cache; using Umbraco.Core.Models; using Umbraco.Core.Services; -using Umbraco.Web.Composing; -namespace Umbraco.Web.Dictionary +namespace Umbraco.Core.Dictionary { /// @@ -18,7 +17,7 @@ namespace Umbraco.Web.Dictionary /// The ILocalizationService is the service used for interacting with this data from the database which isn't all that fast /// (even though there is caching involved, if there's lots of dictionary items the caching is not great) /// - public class DefaultCultureDictionary : Core.Dictionary.ICultureDictionary + public class DefaultCultureDictionary : ICultureDictionary { private readonly ILocalizationService _localizationService; private readonly IAppCache _requestCache; diff --git a/src/Umbraco.Web/Dictionary/UmbracoCultureDictionaryFactory.cs b/src/Umbraco.Core/Dictionary/UmbracoCultureDictionaryFactory.cs similarity index 79% rename from src/Umbraco.Web/Dictionary/UmbracoCultureDictionaryFactory.cs rename to src/Umbraco.Core/Dictionary/UmbracoCultureDictionaryFactory.cs index d66bd99f83..a2d1fa12d3 100644 --- a/src/Umbraco.Web/Dictionary/UmbracoCultureDictionaryFactory.cs +++ b/src/Umbraco.Core/Dictionary/UmbracoCultureDictionaryFactory.cs @@ -1,7 +1,7 @@ using Umbraco.Core.Cache; using Umbraco.Core.Services; -namespace Umbraco.Web.Dictionary +namespace Umbraco.Core.Dictionary { /// /// A culture dictionary factory used to create an Umbraco.Core.Dictionary.ICultureDictionary. @@ -9,7 +9,7 @@ namespace Umbraco.Web.Dictionary /// /// In the future this will allow use to potentially store dictionary items elsewhere and allows for maximum flexibility. /// - internal class DefaultCultureDictionaryFactory : Umbraco.Core.Dictionary.ICultureDictionaryFactory + internal class DefaultCultureDictionaryFactory : ICultureDictionaryFactory { private readonly ILocalizationService _localizationService; private readonly AppCaches _appCaches; @@ -20,7 +20,7 @@ namespace Umbraco.Web.Dictionary _appCaches = appCaches; } - public Umbraco.Core.Dictionary.ICultureDictionary CreateDictionary() + public ICultureDictionary CreateDictionary() { return new DefaultCultureDictionary(_localizationService, _appCaches.RequestCache); } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs index 757ea6eb30..ff8beecff5 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs @@ -42,9 +42,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement public UserRepository(IScopeAccessor scopeAccessor, AppCaches appCaches, ILogger logger, IMapperCollection mapperCollection, IGlobalSettings globalSettings, IUserPasswordConfiguration passwordConfiguration) : base(scopeAccessor, appCaches, logger) { - _mapperCollection = mapperCollection; - _globalSettings = globalSettings; - _passwordConfiguration = passwordConfiguration; + _mapperCollection = mapperCollection ?? throw new ArgumentNullException(nameof(mapperCollection)); + _globalSettings = globalSettings ?? throw new ArgumentNullException(nameof(globalSettings)); + _passwordConfiguration = passwordConfiguration ?? throw new ArgumentNullException(nameof(passwordConfiguration)); } /// diff --git a/src/Umbraco.Core/Runtime/CoreInitialComposer.cs b/src/Umbraco.Core/Runtime/CoreInitialComposer.cs index bf28898820..0a2e39592f 100644 --- a/src/Umbraco.Core/Runtime/CoreInitialComposer.cs +++ b/src/Umbraco.Core/Runtime/CoreInitialComposer.cs @@ -4,6 +4,7 @@ using Umbraco.Core.Composing; using Umbraco.Core.Composing.CompositionExtensions; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.Dictionary; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Manifest; @@ -132,6 +133,8 @@ namespace Umbraco.Core.Runtime // by default, register a noop rebuilder composition.RegisterUnique(); + composition.SetCultureDictionaryFactory(); + composition.Register(f => f.GetInstance().CreateDictionary(), Lifetime.Singleton); composition.RegisterUnique(); } } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 6c28c9d68b..931d5ec023 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -147,6 +147,8 @@ + + diff --git a/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs b/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs index 9a47ad1b4a..66d3b570b0 100644 --- a/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs +++ b/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs @@ -31,9 +31,6 @@ namespace Umbraco.Tests.Models.Mapping { base.Compose(); - Composition.RegisterUnique(f => Mock.Of()); - - Composition.Register(_ => Mock.Of()); Composition.ComposeFileSystems(); Composition.Register(_ => Mock.Of()); diff --git a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs index a321ce90cc..4fb488c3e4 100644 --- a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs +++ b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs @@ -42,7 +42,6 @@ using Umbraco.Web.Composing.CompositionExtensions; using Umbraco.Web.Sections; using Current = Umbraco.Core.Composing.Current; using FileSystems = Umbraco.Core.IO.FileSystems; -using Umbraco.Web.Dictionary; using Umbraco.Core.Dictionary; namespace Umbraco.Tests.Testing @@ -325,6 +324,7 @@ namespace Umbraco.Tests.Testing { Composition.Configs.Add(SettingsForTests.GetDefaultUmbracoSettings); Composition.Configs.Add(SettingsForTests.GetDefaultGlobalSettings); + //Composition.Configs.Add(() => new DefaultUserPasswordConfig()); } protected virtual void ComposeApplication(bool withApplication) diff --git a/src/Umbraco.Web/Editors/AuthenticationController.cs b/src/Umbraco.Web/Editors/AuthenticationController.cs index 2551d33c6e..c77b24e607 100644 --- a/src/Umbraco.Web/Editors/AuthenticationController.cs +++ b/src/Umbraco.Web/Editors/AuthenticationController.cs @@ -46,7 +46,7 @@ namespace Umbraco.Web.Editors public AuthenticationController(IUserPasswordConfiguration passwordConfiguration, IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper, UmbracoMapper umbracoMapper) : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper, umbracoMapper) { - _passwordConfiguration = passwordConfiguration; + _passwordConfiguration = passwordConfiguration ?? throw new ArgumentNullException(nameof(passwordConfiguration)); } protected BackOfficeUserManager UserManager => _userManager diff --git a/src/Umbraco.Web/Install/InstallSteps/NewInstallStep.cs b/src/Umbraco.Web/Install/InstallSteps/NewInstallStep.cs index aeddb167e0..7f4ff496f1 100644 --- a/src/Umbraco.Web/Install/InstallSteps/NewInstallStep.cs +++ b/src/Umbraco.Web/Install/InstallSteps/NewInstallStep.cs @@ -43,7 +43,7 @@ namespace Umbraco.Web.Install.InstallSteps _userService = userService; _databaseBuilder = databaseBuilder; _globalSettings = globalSettings; - _passwordConfiguration = passwordConfiguration; + _passwordConfiguration = passwordConfiguration ?? throw new ArgumentNullException(nameof(passwordConfiguration)); _userManager = _http.GetOwinContext().GetBackOfficeUserManager(); } diff --git a/src/Umbraco.Web/Models/Mapping/MemberMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/MemberMapDefinition.cs index f93c1208ed..e6185ab80e 100644 --- a/src/Umbraco.Web/Models/Mapping/MemberMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/MemberMapDefinition.cs @@ -21,13 +21,11 @@ namespace Umbraco.Web.Models.Mapping internal class MemberMapDefinition : IMapDefinition { private readonly CommonMapper _commonMapper; - private readonly IMemberTypeService _memberTypeService; private readonly MemberTabsAndPropertiesMapper _tabsAndPropertiesMapper; - public MemberMapDefinition(CommonMapper commonMapper, IMemberTypeService memberTypeService, MemberTabsAndPropertiesMapper tabsAndPropertiesMapper) + public MemberMapDefinition(CommonMapper commonMapper, MemberTabsAndPropertiesMapper tabsAndPropertiesMapper) { _commonMapper = commonMapper; - _memberTypeService = memberTypeService; _tabsAndPropertiesMapper = tabsAndPropertiesMapper; } diff --git a/src/Umbraco.Web/Runtime/WebFinalComposer.cs b/src/Umbraco.Web/Runtime/WebFinalComposer.cs index f0178413a0..3f9fd2bbc4 100644 --- a/src/Umbraco.Web/Runtime/WebFinalComposer.cs +++ b/src/Umbraco.Web/Runtime/WebFinalComposer.cs @@ -10,15 +10,5 @@ namespace Umbraco.Web.Runtime [ComposeAfter(typeof(ICoreComposer))] public class WebFinalComposer : ComponentComposer { - - public override void Compose(Composition composition) - { - base.Compose(composition); - - // now that user composers have had a chance to register their own factory, we can add the result of the factory - // to the container. - composition.Register(f => f.GetInstance().CreateDictionary(), Lifetime.Singleton); - } - } } diff --git a/src/Umbraco.Web/Runtime/WebInitialComposer.cs b/src/Umbraco.Web/Runtime/WebInitialComposer.cs index 92236ae96a..f475ac9e78 100644 --- a/src/Umbraco.Web/Runtime/WebInitialComposer.cs +++ b/src/Umbraco.Web/Runtime/WebInitialComposer.cs @@ -20,7 +20,6 @@ using Umbraco.Web.Cache; using Umbraco.Web.Composing.CompositionExtensions; using Umbraco.Web.ContentApps; using Umbraco.Web.Dashboards; -using Umbraco.Web.Dictionary; using Umbraco.Web.Editors; using Umbraco.Web.Features; using Umbraco.Web.HealthCheck; @@ -202,8 +201,6 @@ namespace Umbraco.Web.Runtime composition.RegisterUnique(); - composition.SetCultureDictionaryFactory(); - // register *all* checks, except those marked [HideFromTypeFinder] of course composition.WithCollectionBuilder() .Add(() => composition.TypeLoader.GetTypes()); diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 533eae8eee..25337d09d8 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -1044,8 +1044,6 @@ - - From ed9a6d3f22deb9aef33ca2be5d3430567de66b85 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 25 Nov 2019 23:10:54 +1100 Subject: [PATCH 10/18] Moves culture dictionary implementation to Core instead of web since it has nothing to do with Web really, fixes tests # Conflicts: # src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs # src/Umbraco.Core/Runtime/CoreInitialComposer.cs # src/Umbraco.Web/Editors/AuthenticationController.cs # src/Umbraco.Web/Install/InstallSteps/NewInstallStep.cs --- .../Composing/CompositionExtensions/Services.cs | 1 + .../Dictionary/UmbracoCultureDictionary.cs | 5 ++--- .../Dictionary/UmbracoCultureDictionaryFactory.cs | 6 +++--- .../Repositories/Implement/UserRepository.cs | 4 ++-- src/Umbraco.Core/Runtime/CoreInitialComposer.cs | 3 +++ src/Umbraco.Core/Umbraco.Core.csproj | 2 ++ .../Models/Mapping/ContentWebModelMappingTests.cs | 3 --- src/Umbraco.Web/Runtime/WebFinalComposer.cs | 10 ---------- src/Umbraco.Web/Runtime/WebInitialComposer.cs | 3 --- src/Umbraco.Web/Umbraco.Web.csproj | 2 -- 10 files changed, 13 insertions(+), 26 deletions(-) rename src/{Umbraco.Web => Umbraco.Core}/Dictionary/UmbracoCultureDictionary.cs (97%) rename src/{Umbraco.Web => Umbraco.Core}/Dictionary/UmbracoCultureDictionaryFactory.cs (79%) diff --git a/src/Umbraco.Core/Composing/CompositionExtensions/Services.cs b/src/Umbraco.Core/Composing/CompositionExtensions/Services.cs index ba8442f8d7..b0cd80d93f 100644 --- a/src/Umbraco.Core/Composing/CompositionExtensions/Services.cs +++ b/src/Umbraco.Core/Composing/CompositionExtensions/Services.cs @@ -3,6 +3,7 @@ using System.IO; using System.Linq; using Umbraco.Core.Cache; using Umbraco.Core.Configuration; +using Umbraco.Core.Dictionary; using Umbraco.Core.Events; using Umbraco.Core.IO; using Umbraco.Core.Logging; diff --git a/src/Umbraco.Web/Dictionary/UmbracoCultureDictionary.cs b/src/Umbraco.Core/Dictionary/UmbracoCultureDictionary.cs similarity index 97% rename from src/Umbraco.Web/Dictionary/UmbracoCultureDictionary.cs rename to src/Umbraco.Core/Dictionary/UmbracoCultureDictionary.cs index f11b2dcfcb..b0690c944b 100644 --- a/src/Umbraco.Web/Dictionary/UmbracoCultureDictionary.cs +++ b/src/Umbraco.Core/Dictionary/UmbracoCultureDictionary.cs @@ -5,9 +5,8 @@ using System.Linq; using Umbraco.Core.Cache; using Umbraco.Core.Models; using Umbraco.Core.Services; -using Umbraco.Web.Composing; -namespace Umbraco.Web.Dictionary +namespace Umbraco.Core.Dictionary { /// @@ -18,7 +17,7 @@ namespace Umbraco.Web.Dictionary /// The ILocalizationService is the service used for interacting with this data from the database which isn't all that fast /// (even though there is caching involved, if there's lots of dictionary items the caching is not great) /// - public class DefaultCultureDictionary : Core.Dictionary.ICultureDictionary + public class DefaultCultureDictionary : ICultureDictionary { private readonly ILocalizationService _localizationService; private readonly IAppCache _requestCache; diff --git a/src/Umbraco.Web/Dictionary/UmbracoCultureDictionaryFactory.cs b/src/Umbraco.Core/Dictionary/UmbracoCultureDictionaryFactory.cs similarity index 79% rename from src/Umbraco.Web/Dictionary/UmbracoCultureDictionaryFactory.cs rename to src/Umbraco.Core/Dictionary/UmbracoCultureDictionaryFactory.cs index d66bd99f83..a2d1fa12d3 100644 --- a/src/Umbraco.Web/Dictionary/UmbracoCultureDictionaryFactory.cs +++ b/src/Umbraco.Core/Dictionary/UmbracoCultureDictionaryFactory.cs @@ -1,7 +1,7 @@ using Umbraco.Core.Cache; using Umbraco.Core.Services; -namespace Umbraco.Web.Dictionary +namespace Umbraco.Core.Dictionary { /// /// A culture dictionary factory used to create an Umbraco.Core.Dictionary.ICultureDictionary. @@ -9,7 +9,7 @@ namespace Umbraco.Web.Dictionary /// /// In the future this will allow use to potentially store dictionary items elsewhere and allows for maximum flexibility. /// - internal class DefaultCultureDictionaryFactory : Umbraco.Core.Dictionary.ICultureDictionaryFactory + internal class DefaultCultureDictionaryFactory : ICultureDictionaryFactory { private readonly ILocalizationService _localizationService; private readonly AppCaches _appCaches; @@ -20,7 +20,7 @@ namespace Umbraco.Web.Dictionary _appCaches = appCaches; } - public Umbraco.Core.Dictionary.ICultureDictionary CreateDictionary() + public ICultureDictionary CreateDictionary() { return new DefaultCultureDictionary(_localizationService, _appCaches.RequestCache); } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs index 96abc37662..cea1475d3a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs @@ -44,8 +44,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement public UserRepository(IScopeAccessor scopeAccessor, AppCaches appCaches, ILogger logger, IMapperCollection mapperCollection, IGlobalSettings globalSettings) : base(scopeAccessor, appCaches, logger) { - _mapperCollection = mapperCollection; - _globalSettings = globalSettings; + _mapperCollection = mapperCollection ?? throw new ArgumentNullException(nameof(mapperCollection)); + _globalSettings = globalSettings ?? throw new ArgumentNullException(nameof(globalSettings)); } // for tests diff --git a/src/Umbraco.Core/Runtime/CoreInitialComposer.cs b/src/Umbraco.Core/Runtime/CoreInitialComposer.cs index 2ab0de14ce..72d16de608 100644 --- a/src/Umbraco.Core/Runtime/CoreInitialComposer.cs +++ b/src/Umbraco.Core/Runtime/CoreInitialComposer.cs @@ -4,6 +4,7 @@ using Umbraco.Core.Composing; using Umbraco.Core.Composing.CompositionExtensions; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.Dictionary; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Manifest; @@ -130,6 +131,8 @@ namespace Umbraco.Core.Runtime // by default, register a noop rebuilder composition.RegisterUnique(); + composition.SetCultureDictionaryFactory(); + composition.Register(f => f.GetInstance().CreateDictionary(), Lifetime.Singleton); } } } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 00f9032bd7..e43b3c9861 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -147,6 +147,8 @@ + + diff --git a/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs b/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs index 9a47ad1b4a..66d3b570b0 100644 --- a/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs +++ b/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs @@ -31,9 +31,6 @@ namespace Umbraco.Tests.Models.Mapping { base.Compose(); - Composition.RegisterUnique(f => Mock.Of()); - - Composition.Register(_ => Mock.Of()); Composition.ComposeFileSystems(); Composition.Register(_ => Mock.Of()); diff --git a/src/Umbraco.Web/Runtime/WebFinalComposer.cs b/src/Umbraco.Web/Runtime/WebFinalComposer.cs index f0178413a0..3f9fd2bbc4 100644 --- a/src/Umbraco.Web/Runtime/WebFinalComposer.cs +++ b/src/Umbraco.Web/Runtime/WebFinalComposer.cs @@ -10,15 +10,5 @@ namespace Umbraco.Web.Runtime [ComposeAfter(typeof(ICoreComposer))] public class WebFinalComposer : ComponentComposer { - - public override void Compose(Composition composition) - { - base.Compose(composition); - - // now that user composers have had a chance to register their own factory, we can add the result of the factory - // to the container. - composition.Register(f => f.GetInstance().CreateDictionary(), Lifetime.Singleton); - } - } } diff --git a/src/Umbraco.Web/Runtime/WebInitialComposer.cs b/src/Umbraco.Web/Runtime/WebInitialComposer.cs index 046b8381d2..a5e8aeb4ae 100644 --- a/src/Umbraco.Web/Runtime/WebInitialComposer.cs +++ b/src/Umbraco.Web/Runtime/WebInitialComposer.cs @@ -20,7 +20,6 @@ using Umbraco.Web.Cache; using Umbraco.Web.Composing.CompositionExtensions; using Umbraco.Web.ContentApps; using Umbraco.Web.Dashboards; -using Umbraco.Web.Dictionary; using Umbraco.Web.Editors; using Umbraco.Web.Features; using Umbraco.Web.HealthCheck; @@ -202,8 +201,6 @@ namespace Umbraco.Web.Runtime composition.RegisterUnique(); - composition.SetCultureDictionaryFactory(); - // register *all* checks, except those marked [HideFromTypeFinder] of course composition.WithCollectionBuilder() .Add(() => composition.TypeLoader.GetTypes()); diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index a732dfeb10..e980a3c894 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -1044,8 +1044,6 @@ - - From 76b413e527ca5e53cc26b2e09a9332945975bd0b Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 25 Nov 2019 23:37:30 +1100 Subject: [PATCH 11/18] Removes all references of System.Web.Security from core project --- .../Cache/HttpRequestAppCache.cs | 1 - .../DictionaryExtensions.cs | 4 +- .../Models/Membership/IMembershipUser.cs | 10 +- .../Membership/MembershipUserExtensions.cs | 29 - .../Membership/UmbracoMembershipUser.cs | 50 - .../Security/BackOfficeUserStore.cs | 1 - .../Security/UmbracoBackOfficeIdentity.cs | 7 - src/Umbraco.Core/StringExtensions.cs | 1252 ----------------- src/Umbraco.Core/Umbraco.Core.csproj | 5 - .../Membership/MembershipProviderBaseTests.cs | 1 + .../Strings/StringExtensionsTests.cs | 1 + .../Membership/UmbracoMembershipMember.cs | 4 +- .../Security/MembershipProviderBase.cs | 4 +- .../Security/MembershipProviderExtensions.cs | 8 + .../Security/UmbracoMembershipProviderBase.cs | 3 +- src/Umbraco.Web/StringExtensions.cs | 69 + src/Umbraco.Web/Umbraco.Web.csproj | 4 + 17 files changed, 97 insertions(+), 1356 deletions(-) delete mode 100644 src/Umbraco.Core/Models/Membership/MembershipUserExtensions.cs delete mode 100644 src/Umbraco.Core/Models/Membership/UmbracoMembershipUser.cs rename src/{Umbraco.Core => Umbraco.Web}/Models/Membership/UmbracoMembershipMember.cs (98%) rename src/{Umbraco.Core => Umbraco.Web}/Security/MembershipProviderBase.cs (99%) rename src/{Umbraco.Core => Umbraco.Web}/Security/UmbracoMembershipProviderBase.cs (98%) create mode 100644 src/Umbraco.Web/StringExtensions.cs diff --git a/src/Umbraco.Abstractions/Cache/HttpRequestAppCache.cs b/src/Umbraco.Abstractions/Cache/HttpRequestAppCache.cs index 41c930ae86..bdb087f0d2 100644 --- a/src/Umbraco.Abstractions/Cache/HttpRequestAppCache.cs +++ b/src/Umbraco.Abstractions/Cache/HttpRequestAppCache.cs @@ -3,7 +3,6 @@ using System.Collections; using System.Collections.Generic; using System.Linq; using System.Threading; -using System.Web; using Umbraco.Core.Composing; namespace Umbraco.Core.Cache diff --git a/src/Umbraco.Abstractions/DictionaryExtensions.cs b/src/Umbraco.Abstractions/DictionaryExtensions.cs index 0c0049a4b9..29cbc221ca 100644 --- a/src/Umbraco.Abstractions/DictionaryExtensions.cs +++ b/src/Umbraco.Abstractions/DictionaryExtensions.cs @@ -4,8 +4,8 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; +using System.Net; using System.Text; -using System.Web; namespace Umbraco.Core { @@ -251,7 +251,7 @@ namespace Umbraco.Core var builder = new StringBuilder(); foreach (var i in d) { - builder.Append(String.Format("{0}={1}&", HttpUtility.UrlEncode(i.Key), i.Value == null ? string.Empty : HttpUtility.UrlEncode(i.Value.ToString()))); + builder.Append(String.Format("{0}={1}&", WebUtility.UrlEncode(i.Key), i.Value == null ? string.Empty : WebUtility.UrlEncode(i.Value.ToString()))); } return builder.ToString().TrimEnd('&'); } diff --git a/src/Umbraco.Abstractions/Models/Membership/IMembershipUser.cs b/src/Umbraco.Abstractions/Models/Membership/IMembershipUser.cs index 5ea857be51..50e5133282 100644 --- a/src/Umbraco.Abstractions/Models/Membership/IMembershipUser.cs +++ b/src/Umbraco.Abstractions/Models/Membership/IMembershipUser.cs @@ -8,21 +8,23 @@ namespace Umbraco.Core.Models.Membership /// public interface IMembershipUser : IEntity { - object ProviderUserKey { get; set; } + object ProviderUserKey { get; set; } // fixme: This will be obsolete when we remove membership providers + + string Username { get; set; } string Email { get; set; } /// /// Gets or sets the raw password value /// - string RawPasswordValue { get; set; } + string RawPasswordValue { get; set; } // fixme: This will be obsolete when we remove membership providers - string PasswordQuestion { get; set; } + string PasswordQuestion { get; set; } // fixme: This will be obsolete when we remove membership providers /// /// Gets or sets the raw password answer value /// - string RawPasswordAnswerValue { get; set; } + string RawPasswordAnswerValue { get; set; } // fixme: This will be obsolete when we remove membership providers string Comments { get; set; } bool IsApproved { get; set; } diff --git a/src/Umbraco.Core/Models/Membership/MembershipUserExtensions.cs b/src/Umbraco.Core/Models/Membership/MembershipUserExtensions.cs deleted file mode 100644 index d8e9558fae..0000000000 --- a/src/Umbraco.Core/Models/Membership/MembershipUserExtensions.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.Web.Security; -using Umbraco.Core.Composing; -using Umbraco.Core.Security; -using Umbraco.Core.Services; - -namespace Umbraco.Core.Models.Membership -{ - internal static class MembershipUserExtensions - { - internal static UmbracoMembershipMember AsConcreteMembershipUser(this IMembershipUser member, string providerName, bool providerKeyAsGuid = false) - { - var membershipMember = new UmbracoMembershipMember(member, providerName, providerKeyAsGuid); - return membershipMember; - } - - internal static IMembershipUser AsIMember(this UmbracoMembershipMember membershipMember) - { - var member = membershipMember; - if (member != null) - { - return member.Member; - } - - throw new NotImplementedException(); - } - - } -} diff --git a/src/Umbraco.Core/Models/Membership/UmbracoMembershipUser.cs b/src/Umbraco.Core/Models/Membership/UmbracoMembershipUser.cs deleted file mode 100644 index 6ca2df2a38..0000000000 --- a/src/Umbraco.Core/Models/Membership/UmbracoMembershipUser.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using System.Web.Security; - -namespace Umbraco.Core.Models.Membership -{ - internal class UmbracoMembershipUser : MembershipUser where T : IMembershipUser - { - private T _user; - - #region Constructors - /// - /// Initializes a new instance of the class. - /// - public UmbracoMembershipUser(T user) - { - _user = user; - } - - /// - /// Initializes a new instance of the class. - /// - /// Name of the provider. - /// The name. - /// The provider user key. - /// The email. - /// The password question. - /// The comment. - /// if set to true [is approved]. - /// if set to true [is locked out]. - /// The creation date. - /// The last login date. - /// The last activity date. - /// The last password changed date. - /// The last lockout date. - /// The full name. - /// The language. - /// - public UmbracoMembershipUser(string providerName, string name, object providerUserKey, string email, - string passwordQuestion, string comment, bool isApproved, bool isLockedOut, - DateTime creationDate, DateTime lastLoginDate, DateTime lastActivityDate, DateTime lastPasswordChangedDate, - DateTime lastLockoutDate, string fullName, string language, T user) - : base(providerName, name, providerUserKey, email, passwordQuestion, comment, isApproved, isLockedOut, - creationDate, lastLoginDate, lastActivityDate, lastPasswordChangedDate, lastLockoutDate) - { - _user = user; - } - - #endregion - } -} diff --git a/src/Umbraco.Core/Security/BackOfficeUserStore.cs b/src/Umbraco.Core/Security/BackOfficeUserStore.cs index ec2694f80e..0d74ae2bb7 100644 --- a/src/Umbraco.Core/Security/BackOfficeUserStore.cs +++ b/src/Umbraco.Core/Security/BackOfficeUserStore.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Data; using System.Linq; using System.Threading.Tasks; -using System.Web.Security; using Microsoft.AspNet.Identity; using Umbraco.Core.Configuration; using Umbraco.Core.Exceptions; diff --git a/src/Umbraco.Core/Security/UmbracoBackOfficeIdentity.cs b/src/Umbraco.Core/Security/UmbracoBackOfficeIdentity.cs index 09798efa83..7817e4729f 100644 --- a/src/Umbraco.Core/Security/UmbracoBackOfficeIdentity.cs +++ b/src/Umbraco.Core/Security/UmbracoBackOfficeIdentity.cs @@ -1,15 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Runtime.Serialization; using System.Security.Claims; -using System.Security.Principal; -using System.Web; -using System.Web.Security; using Microsoft.AspNet.Identity; -using Microsoft.Owin.Security; -using Newtonsoft.Json; -using Umbraco.Core.Configuration; namespace Umbraco.Core.Security { diff --git a/src/Umbraco.Core/StringExtensions.cs b/src/Umbraco.Core/StringExtensions.cs index 854a96a51f..1a84124dde 100644 --- a/src/Umbraco.Core/StringExtensions.cs +++ b/src/Umbraco.Core/StringExtensions.cs @@ -1,16 +1,7 @@ using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Globalization; using System.IO; using System.Linq; -using System.Security.Cryptography; -using System.Text; using System.Text.RegularExpressions; -using System.Web.Security; -using Newtonsoft.Json; -using Umbraco.Core.Configuration; using Umbraco.Core.Composing; using Umbraco.Core.Exceptions; using Umbraco.Core.IO; @@ -24,63 +15,6 @@ namespace Umbraco.Core /// public static class StringExtensions { -// -// private static readonly char[] ToCSharpHexDigitLower = "0123456789abcdef".ToCharArray(); -// private static readonly char[] ToCSharpEscapeChars; -// -// static StringExtensions() -// { -// var escapes = new[] { "\aa", "\bb", "\ff", "\nn", "\rr", "\tt", "\vv", "\"\"", "\\\\", "??", "\00" }; -// ToCSharpEscapeChars = new char[escapes.Max(e => e[0]) + 1]; -// foreach (var escape in escapes) -// ToCSharpEscapeChars[escape[0]] = escape[1]; -// } -// -// /// -// /// Convert a path to node ids in the order from right to left (deepest to shallowest) -// /// -// /// -// /// -// internal static int[] GetIdsFromPathReversed(this string path) -// { -// var nodeIds = path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) -// .Select(x => x.TryConvertTo()) -// .Where(x => x.Success) -// .Select(x => x.Result) -// .Reverse() -// .ToArray(); -// return nodeIds; -// } -// -// /// -// /// Removes new lines and tabs -// /// -// /// -// /// -// internal static string StripWhitespace(this string txt) -// { -// return Regex.Replace(txt, @"\s", string.Empty); -// } -// -// internal static string StripFileExtension(this string fileName) -// { -// //filenames cannot contain line breaks -// if (fileName.Contains(Environment.NewLine) || fileName.Contains("\r") || fileName.Contains("\n")) return fileName; -// -// var lastIndex = fileName.LastIndexOf('.'); -// if (lastIndex > 0) -// { -// var ext = fileName.Substring(lastIndex); -// //file extensions cannot contain whitespace -// if (ext.Contains(" ")) return fileName; -// -// return string.Format("{0}", fileName.Substring(0, fileName.IndexOf(ext, StringComparison.Ordinal))); -// } -// return fileName; -// -// -// } -// /// /// Based on the input string, this will detect if the string is a JS path or a JS snippet. /// If a path cannot be determined, then it is assumed to be a snippet the original text is returned @@ -126,928 +60,7 @@ namespace Umbraco.Core return Attempt.Fail(input); } -// -// /// -// /// This tries to detect a json string, this is not a fail safe way but it is quicker than doing -// /// a try/catch when deserializing when it is not json. -// /// -// /// -// /// -// public static bool DetectIsJson(this string input) -// { -// if (input.IsNullOrWhiteSpace()) return false; -// input = input.Trim(); -// return (input.StartsWith("{") && input.EndsWith("}")) -// || (input.StartsWith("[") && input.EndsWith("]")); -// } -// -// internal static readonly Lazy Whitespace = new Lazy(() => new Regex(@"\s+", RegexOptions.Compiled)); -// internal static readonly string[] JsonEmpties = { "[]", "{}" }; -// internal static bool DetectIsEmptyJson(this string input) -// { -// return JsonEmpties.Contains(Whitespace.Value.Replace(input, string.Empty)); -// } -// -// /// -// /// Returns a JObject/JArray instance if the string can be converted to json, otherwise returns the string -// /// -// /// -// /// -// internal static object ConvertToJsonIfPossible(this string input) -// { -// if (input.DetectIsJson() == false) -// { -// return input; -// } -// try -// { -// var obj = JsonConvert.DeserializeObject(input); -// return obj; -// } -// catch (Exception) -// { -// return input; -// } -// } -// -// internal static string ReplaceNonAlphanumericChars(this string input, string replacement) -// { -// //any character that is not alphanumeric, convert to a hyphen -// var mName = input; -// foreach (var c in mName.ToCharArray().Where(c => !char.IsLetterOrDigit(c))) -// { -// mName = mName.Replace(c.ToString(CultureInfo.InvariantCulture), replacement); -// } -// return mName; -// } -// -// internal static string ReplaceNonAlphanumericChars(this string input, char replacement) -// { -// var inputArray = input.ToCharArray(); -// var outputArray = new char[input.Length]; -// for (var i = 0; i < inputArray.Length; i++) -// outputArray[i] = char.IsLetterOrDigit(inputArray[i]) ? inputArray[i] : replacement; -// return new string(outputArray); -// } -// private static readonly char[] CleanForXssChars = "*?(){}[];:%<>/\\|&'\"".ToCharArray(); -// -// /// -// /// Cleans string to aid in preventing xss attacks. -// /// -// /// -// /// -// /// -// public static string CleanForXss(this string input, params char[] ignoreFromClean) -// { -// //remove any HTML -// input = input.StripHtml(); -// //strip out any potential chars involved with XSS -// return input.ExceptChars(new HashSet(CleanForXssChars.Except(ignoreFromClean))); -// } -// -// public static string ExceptChars(this string str, HashSet toExclude) -// { -// var sb = new StringBuilder(str.Length); -// foreach (var c in str.Where(c => toExclude.Contains(c) == false)) -// { -// sb.Append(c); -// } -// return sb.ToString(); -// } -// -// /// -// /// Returns a stream from a string -// /// -// /// -// /// -// internal static Stream GenerateStreamFromString(this string s) -// { -// var stream = new MemoryStream(); -// var writer = new StreamWriter(stream); -// writer.Write(s); -// writer.Flush(); -// stream.Position = 0; -// return stream; -// } -// -// /// -// /// This will append the query string to the URL -// /// -// /// -// /// -// /// -// /// -// /// This methods ensures that the resulting URL is structured correctly, that there's only one '?' and that things are -// /// delimited properly with '&' -// /// -// internal static string AppendQueryStringToUrl(this string url, params string[] queryStrings) -// { -// //remove any prefixed '&' or '?' -// for (var i = 0; i < queryStrings.Length; i++) -// { -// queryStrings[i] = queryStrings[i].TrimStart('?', '&').TrimEnd('&'); -// } -// -// var nonEmpty = queryStrings.Where(x => !x.IsNullOrWhiteSpace()).ToArray(); -// -// if (url.Contains("?")) -// { -// return url + string.Join("&", nonEmpty).EnsureStartsWith('&'); -// } -// return url + string.Join("&", nonEmpty).EnsureStartsWith('?'); -// } -// - /// - /// Encrypt the string using the MachineKey in medium trust - /// - /// The string value to be encrypted. - /// The encrypted string. - public static string EncryptWithMachineKey(this string value) - { - if (value == null) - return null; - string valueToEncrypt = value; - List parts = new List(); - - const int EncrpytBlockSize = 500; - - while (valueToEncrypt.Length > EncrpytBlockSize) - { - parts.Add(valueToEncrypt.Substring(0, EncrpytBlockSize)); - valueToEncrypt = valueToEncrypt.Remove(0, EncrpytBlockSize); - } - - if (valueToEncrypt.Length > 0) - { - parts.Add(valueToEncrypt); - } - - StringBuilder encrpytedValue = new StringBuilder(); - - foreach (var part in parts) - { - var encrpytedBlock = FormsAuthentication.Encrypt(new FormsAuthenticationTicket(0, string.Empty, DateTime.Now, DateTime.MaxValue, false, part)); - encrpytedValue.AppendLine(encrpytedBlock); - } - - return encrpytedValue.ToString().TrimEnd(); - } - - /// - /// Decrypt the encrypted string using the Machine key in medium trust - /// - /// The string value to be decrypted - /// The decrypted string. - public static string DecryptWithMachineKey(this string value) - { - if (value == null) - return null; - - string[] parts = value.Split('\n'); - - StringBuilder decryptedValue = new StringBuilder(); - - foreach (var part in parts) - { - decryptedValue.Append(FormsAuthentication.Decrypt(part.TrimEnd()).UserData); - } - - return decryptedValue.ToString(); - } -// -// //this is from SqlMetal and just makes it a bit of fun to allow pluralization -// public static string MakePluralName(this string name) -// { -// if ((name.EndsWith("x", StringComparison.OrdinalIgnoreCase) || name.EndsWith("ch", StringComparison.OrdinalIgnoreCase)) || (name.EndsWith("s", StringComparison.OrdinalIgnoreCase) || name.EndsWith("sh", StringComparison.OrdinalIgnoreCase))) -// { -// name = name + "es"; -// return name; -// } -// if ((name.EndsWith("y", StringComparison.OrdinalIgnoreCase) && (name.Length > 1)) && !IsVowel(name[name.Length - 2])) -// { -// name = name.Remove(name.Length - 1, 1); -// name = name + "ies"; -// return name; -// } -// if (!name.EndsWith("s", StringComparison.OrdinalIgnoreCase)) -// { -// name = name + "s"; -// } -// return name; -// } -// -// public static bool IsVowel(this char c) -// { -// switch (c) -// { -// case 'O': -// case 'U': -// case 'Y': -// case 'A': -// case 'E': -// case 'I': -// case 'o': -// case 'u': -// case 'y': -// case 'a': -// case 'e': -// case 'i': -// return true; -// } -// return false; -// } -// -// /// -// /// Trims the specified value from a string; accepts a string input whereas the in-built implementation only accepts char or char[]. -// /// -// /// The value. -// /// For removing. -// /// -// public static string Trim(this string value, string forRemoving) -// { -// if (string.IsNullOrEmpty(value)) return value; -// return value.TrimEnd(forRemoving).TrimStart(forRemoving); -// } -// -// public static string EncodeJsString(this string s) -// { -// var sb = new StringBuilder(); -// foreach (var c in s) -// { -// switch (c) -// { -// case '\"': -// sb.Append("\\\""); -// break; -// case '\\': -// sb.Append("\\\\"); -// break; -// case '\b': -// sb.Append("\\b"); -// break; -// case '\f': -// sb.Append("\\f"); -// break; -// case '\n': -// sb.Append("\\n"); -// break; -// case '\r': -// sb.Append("\\r"); -// break; -// case '\t': -// sb.Append("\\t"); -// break; -// default: -// int i = (int)c; -// if (i < 32 || i > 127) -// { -// sb.AppendFormat("\\u{0:X04}", i); -// } -// else -// { -// sb.Append(c); -// } -// break; -// } -// } -// return sb.ToString(); -// } -// -// public static string TrimEnd(this string value, string forRemoving) -// { -// if (string.IsNullOrEmpty(value)) return value; -// if (string.IsNullOrEmpty(forRemoving)) return value; -// -// while (value.EndsWith(forRemoving, StringComparison.InvariantCultureIgnoreCase)) -// { -// value = value.Remove(value.LastIndexOf(forRemoving, StringComparison.InvariantCultureIgnoreCase)); -// } -// return value; -// } -// -// public static string TrimStart(this string value, string forRemoving) -// { -// if (string.IsNullOrEmpty(value)) return value; -// if (string.IsNullOrEmpty(forRemoving)) return value; -// -// while (value.StartsWith(forRemoving, StringComparison.InvariantCultureIgnoreCase)) -// { -// value = value.Substring(forRemoving.Length); -// } -// return value; -// } -// -// public static string EnsureStartsWith(this string input, string toStartWith) -// { -// if (input.StartsWith(toStartWith)) return input; -// return toStartWith + input.TrimStart(toStartWith); -// } -// -// public static string EnsureStartsWith(this string input, char value) -// { -// return input.StartsWith(value.ToString(CultureInfo.InvariantCulture)) ? input : value + input; -// } -// -// public static string EnsureEndsWith(this string input, char value) -// { -// return input.EndsWith(value.ToString(CultureInfo.InvariantCulture)) ? input : input + value; -// } -// -// public static string EnsureEndsWith(this string input, string toEndWith) -// { -// return input.EndsWith(toEndWith.ToString(CultureInfo.InvariantCulture)) ? input : input + toEndWith; -// } -// -// public static bool IsLowerCase(this char ch) -// { -// return ch.ToString(CultureInfo.InvariantCulture) == ch.ToString(CultureInfo.InvariantCulture).ToLowerInvariant(); -// } -// -// public static bool IsUpperCase(this char ch) -// { -// return ch.ToString(CultureInfo.InvariantCulture) == ch.ToString(CultureInfo.InvariantCulture).ToUpperInvariant(); -// } -// -// /// Indicates whether a specified string is null, empty, or -// /// consists only of white-space characters. -// /// The value to check. -// /// Returns if the value is null, -// /// empty, or consists only of white-space characters, otherwise -// /// returns . -// public static bool IsNullOrWhiteSpace(this string value) => string.IsNullOrWhiteSpace(value); -// -// public static string IfNullOrWhiteSpace(this string str, string defaultValue) -// { -// return str.IsNullOrWhiteSpace() ? defaultValue : str; -// } -// -// /// The to delimited list. -// /// The list. -// /// The delimiter. -// /// the list -// [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Justification = "By design")] -// public static IList ToDelimitedList(this string list, string delimiter = ",") -// { -// var delimiters = new[] { delimiter }; -// return !list.IsNullOrWhiteSpace() -// ? list.Split(delimiters, StringSplitOptions.RemoveEmptyEntries) -// .Select(i => i.Trim()) -// .ToList() -// : new List(); -// } -// -// /// enum try parse. -// /// The str type. -// /// The ignore case. -// /// The result. -// /// The type -// /// The enum try parse. -// [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "By Design")] -// [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "2#", Justification = "By Design")] -// public static bool EnumTryParse(this string strType, bool ignoreCase, out T result) -// { -// try -// { -// result = (T)Enum.Parse(typeof(T), strType, ignoreCase); -// return true; -// } -// catch -// { -// result = default(T); -// return false; -// } -// } -// -// /// -// /// Parse string to Enum -// /// -// /// The enum type -// /// The string to parse -// /// The ignore case -// /// The parsed enum -// [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "By Design")] -// [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "2#", Justification = "By Design")] -// public static T EnumParse(this string strType, bool ignoreCase) -// { -// return (T)Enum.Parse(typeof(T), strType, ignoreCase); -// } -// -// /// -// /// Strips all HTML from a string. -// /// -// /// The text. -// /// Returns the string without any HTML tags. -// public static string StripHtml(this string text) -// { -// const string pattern = @"<(.|\n)*?>"; -// return Regex.Replace(text, pattern, string.Empty, RegexOptions.Compiled); -// } -// -// /// -// /// Encodes as GUID. -// /// -// /// The input. -// /// -// public static Guid EncodeAsGuid(this string input) -// { -// if (string.IsNullOrWhiteSpace(input)) throw new ArgumentNullException("input"); -// -// var convertToHex = input.ConvertToHex(); -// var hexLength = convertToHex.Length < 32 ? convertToHex.Length : 32; -// var hex = convertToHex.Substring(0, hexLength).PadLeft(32, '0'); -// var output = Guid.Empty; -// return Guid.TryParse(hex, out output) ? output : Guid.Empty; -// } -// -// /// -// /// Converts to hex. -// /// -// /// The input. -// /// -// public static string ConvertToHex(this string input) -// { -// if (string.IsNullOrEmpty(input)) return string.Empty; -// -// var sb = new StringBuilder(input.Length); -// foreach (var c in input) -// { -// sb.AppendFormat("{0:x2}", Convert.ToUInt32(c)); -// } -// return sb.ToString(); -// } -// -// public static string DecodeFromHex(this string hexValue) -// { -// var strValue = ""; -// while (hexValue.Length > 0) -// { -// strValue += Convert.ToChar(Convert.ToUInt32(hexValue.Substring(0, 2), 16)).ToString(); -// hexValue = hexValue.Substring(2, hexValue.Length - 2); -// } -// return strValue; -// } -// -// /// -// /// Encodes a string to a safe URL base64 string -// /// -// /// -// /// -// public static string ToUrlBase64(this string input) -// { -// if (input == null) throw new ArgumentNullException(nameof(input)); -// -// if (string.IsNullOrEmpty(input)) -// return string.Empty; -// -// //return Convert.ToBase64String(bytes).Replace(".", "-").Replace("/", "_").Replace("=", ","); -// var bytes = Encoding.UTF8.GetBytes(input); -// return UrlTokenEncode(bytes); -// } -// -// /// -// /// Decodes a URL safe base64 string back -// /// -// /// -// /// -// public static string FromUrlBase64(this string input) -// { -// if (input == null) throw new ArgumentNullException(nameof(input)); -// -// //if (input.IsInvalidBase64()) return null; -// -// try -// { -// //var decodedBytes = Convert.FromBase64String(input.Replace("-", ".").Replace("_", "/").Replace(",", "=")); -// var decodedBytes = UrlTokenDecode(input); -// return decodedBytes != null ? Encoding.UTF8.GetString(decodedBytes) : null; -// } -// catch (FormatException) -// { -// return null; -// } -// } -// -// /// -// /// formats the string with invariant culture -// /// -// /// The format. -// /// The args. -// /// -// public static string InvariantFormat(this string format, params object[] args) -// { -// return String.Format(CultureInfo.InvariantCulture, format, args); -// } -// -// /// -// /// Converts an integer to an invariant formatted string -// /// -// /// -// /// -// public static string ToInvariantString(this int str) -// { -// return str.ToString(CultureInfo.InvariantCulture); -// } -// -// public static string ToInvariantString(this long str) -// { -// return str.ToString(CultureInfo.InvariantCulture); -// } -// -// /// -// /// Compares 2 strings with invariant culture and case ignored -// /// -// /// The compare. -// /// The compare to. -// /// -// public static bool InvariantEquals(this string compare, string compareTo) -// { -// return String.Equals(compare, compareTo, StringComparison.InvariantCultureIgnoreCase); -// } -// -// public static bool InvariantStartsWith(this string compare, string compareTo) -// { -// return compare.StartsWith(compareTo, StringComparison.InvariantCultureIgnoreCase); -// } -// -// public static bool InvariantEndsWith(this string compare, string compareTo) -// { -// return compare.EndsWith(compareTo, StringComparison.InvariantCultureIgnoreCase); -// } -// -// public static bool InvariantContains(this string compare, string compareTo) -// { -// return compare.IndexOf(compareTo, StringComparison.OrdinalIgnoreCase) >= 0; -// } -// -// public static bool InvariantContains(this IEnumerable compare, string compareTo) -// { -// return compare.Contains(compareTo, StringComparer.InvariantCultureIgnoreCase); -// } -// -// public static int InvariantIndexOf(this string s, string value) -// { -// return s.IndexOf(value, StringComparison.OrdinalIgnoreCase); -// } -// -// public static int InvariantLastIndexOf(this string s, string value) -// { -// return s.LastIndexOf(value, StringComparison.OrdinalIgnoreCase); -// } -// -// -// /// -// /// Tries to parse a string into the supplied type by finding and using the Type's "Parse" method -// /// -// /// -// /// -// /// -// public static T ParseInto(this string val) -// { -// return (T)val.ParseInto(typeof(T)); -// } -// -// /// -// /// Tries to parse a string into the supplied type by finding and using the Type's "Parse" method -// /// -// /// -// /// -// /// -// public static object ParseInto(this string val, Type type) -// { -// if (string.IsNullOrEmpty(val) == false) -// { -// TypeConverter tc = TypeDescriptor.GetConverter(type); -// return tc.ConvertFrom(val); -// } -// return val; -// } -// -// /// -// /// Generates a hash of a string based on the FIPS compliance setting. -// /// -// /// The to hash. -// /// -// /// The hashed string. -// /// -// public static string GenerateHash(this string str) -// { -// return str.GenerateHash(CryptoConfig.AllowOnlyFipsAlgorithms ? "SHA1" : "MD5"); -// } -// -// /// -// /// Generate a hash of a string based on the specified hash algorithm. -// /// -// /// The hash algorithm implementation to use. -// /// The to hash. -// /// -// /// The hashed string. -// /// -// internal static string GenerateHash(this string str) -// where T : HashAlgorithm -// { -// return str.GenerateHash(typeof(T).FullName); -// } -// -// /// -// /// Generate a hash of a string based on the specified . -// /// -// /// The to hash. -// /// The hash algorithm implementation to use. -// /// -// /// The hashed string. -// /// -// /// No hashing type found by name . -// /// -// internal static string GenerateHash(this string str, string hashType) -// { -// var hasher = HashAlgorithm.Create(hashType); -// if (hasher == null) throw new InvalidOperationException($"No hashing type found by name {hashType}."); -// -// using (hasher) -// { -// var byteArray = Encoding.UTF8.GetBytes(str); -// var hashedByteArray = hasher.ComputeHash(byteArray); -// -// var sb = new StringBuilder(); -// foreach (var b in hashedByteArray) -// { -// sb.Append(b.ToString("x2")); -// } -// -// return sb.ToString(); -// } -// } -// -// /// -// /// Decodes a string that was encoded with UrlTokenEncode -// /// -// /// -// /// -// internal static byte[] UrlTokenDecode(string input) -// { -// if (input == null) -// throw new ArgumentNullException(nameof(input)); -// -// if (input.Length == 0) -// return Array.Empty(); -// -// // calc array size - must be groups of 4 -// var arrayLength = input.Length; -// var remain = arrayLength % 4; -// if (remain != 0) arrayLength += 4 - remain; -// -// var inArray = new char[arrayLength]; -// for (var i = 0; i < input.Length; i++) -// { -// var ch = input[i]; -// switch (ch) -// { -// case '-': // restore '-' as '+' -// inArray[i] = '+'; -// break; -// -// case '_': // restore '_' as '/' -// inArray[i] = '/'; -// break; -// -// default: // keep char unchanged -// inArray[i] = ch; -// break; -// } -// } -// -// // pad with '=' -// for (var j = input.Length; j < inArray.Length; j++) -// inArray[j] = '='; -// -// return Convert.FromBase64CharArray(inArray, 0, inArray.Length); -// } -// -// /// -// /// Encodes a string so that it is 'safe' for URLs, files, etc.. -// /// -// /// -// /// -// internal static string UrlTokenEncode(byte[] input) -// { -// if (input == null) -// throw new ArgumentNullException(nameof(input)); -// -// if (input.Length == 0) -// return string.Empty; -// -// // base-64 digits are A-Z, a-z, 0-9, + and / -// // the = char is used for trailing padding -// -// var str = Convert.ToBase64String(input); -// -// var pos = str.IndexOf('='); -// if (pos < 0) pos = str.Length; -// -// // replace chars that would cause problems in urls -// var chArray = new char[pos]; -// for (var i = 0; i < pos; i++) -// { -// var ch = str[i]; -// switch (ch) -// { -// case '+': // replace '+' with '-' -// chArray[i] = '-'; -// break; -// -// case '/': // replace '/' with '_' -// chArray[i] = '_'; -// break; -// -// default: // keep char unchanged -// chArray[i] = ch; -// break; -// } -// } -// -// return new string(chArray); -// } -// -// /// -// /// Ensures that the folder path ends with a DirectorySeparatorChar -// /// -// /// -// /// -// public static string NormaliseDirectoryPath(this string currentFolder) -// { -// currentFolder = currentFolder -// .IfNull(x => String.Empty) -// .TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar; -// return currentFolder; -// } -// -// /// -// /// Truncates the specified text string. -// /// -// /// The text. -// /// Length of the max. -// /// The suffix. -// /// -// public static string Truncate(this string text, int maxLength, string suffix = "...") -// { -// // replaces the truncated string to a ... -// var truncatedString = text; -// -// if (maxLength <= 0) return truncatedString; -// var strLength = maxLength - suffix.Length; -// -// if (strLength <= 0) return truncatedString; -// -// if (text == null || text.Length <= maxLength) return truncatedString; -// -// truncatedString = text.Substring(0, strLength); -// truncatedString = truncatedString.TrimEnd(); -// truncatedString += suffix; -// -// return truncatedString; -// } -// -// /// -// /// Strips carrage returns and line feeds from the specified text. -// /// -// /// The input. -// /// -// public static string StripNewLines(this string input) -// { -// return input.Replace("\r", "").Replace("\n", ""); -// } -// -// /// -// /// Converts to single line by replacing line breaks with spaces. -// /// -// public static string ToSingleLine(this string text) -// { -// if (string.IsNullOrEmpty(text)) return text; -// text = text.Replace("\r\n", " "); // remove CRLF -// text = text.Replace("\r", " "); // remove CR -// text = text.Replace("\n", " "); // remove LF -// return text; -// } -// -// public static string OrIfNullOrWhiteSpace(this string input, string alternative) -// { -// return !string.IsNullOrWhiteSpace(input) -// ? input -// : alternative; -// } -// -// /// -// /// Returns a copy of the string with the first character converted to uppercase. -// /// -// /// The string. -// /// The converted string. -// public static string ToFirstUpper(this string input) -// { -// return string.IsNullOrWhiteSpace(input) -// ? input -// : input.Substring(0, 1).ToUpper() + input.Substring(1); -// } -// -// /// -// /// Returns a copy of the string with the first character converted to lowercase. -// /// -// /// The string. -// /// The converted string. -// public static string ToFirstLower(this string input) -// { -// return string.IsNullOrWhiteSpace(input) -// ? input -// : input.Substring(0, 1).ToLower() + input.Substring(1); -// } -// -// /// -// /// Returns a copy of the string with the first character converted to uppercase using the casing rules of the specified culture. -// /// -// /// The string. -// /// The culture. -// /// The converted string. -// public static string ToFirstUpper(this string input, CultureInfo culture) -// { -// return string.IsNullOrWhiteSpace(input) -// ? input -// : input.Substring(0, 1).ToUpper(culture) + input.Substring(1); -// } -// -// /// -// /// Returns a copy of the string with the first character converted to lowercase using the casing rules of the specified culture. -// /// -// /// The string. -// /// The culture. -// /// The converted string. -// public static string ToFirstLower(this string input, CultureInfo culture) -// { -// return string.IsNullOrWhiteSpace(input) -// ? input -// : input.Substring(0, 1).ToLower(culture) + input.Substring(1); -// } -// -// /// -// /// Returns a copy of the string with the first character converted to uppercase using the casing rules of the invariant culture. -// /// -// /// The string. -// /// The converted string. -// public static string ToFirstUpperInvariant(this string input) -// { -// return string.IsNullOrWhiteSpace(input) -// ? input -// : input.Substring(0, 1).ToUpperInvariant() + input.Substring(1); -// } -// -// /// -// /// Returns a copy of the string with the first character converted to lowercase using the casing rules of the invariant culture. -// /// -// /// The string. -// /// The converted string. -// public static string ToFirstLowerInvariant(this string input) -// { -// return string.IsNullOrWhiteSpace(input) -// ? input -// : input.Substring(0, 1).ToLowerInvariant() + input.Substring(1); -// } -// -// /// -// /// Returns a new string in which all occurrences of specified strings are replaced by other specified strings. -// /// -// /// The string to filter. -// /// The replacements definition. -// /// The filtered string. -// public static string ReplaceMany(this string text, IDictionary replacements) -// { -// if (text == null) throw new ArgumentNullException(nameof(text)); -// if (replacements == null) throw new ArgumentNullException(nameof(replacements)); -// -// -// foreach (KeyValuePair item in replacements) -// text = text.Replace(item.Key, item.Value); -// -// return text; -// } -// -// /// -// /// Returns a new string in which all occurrences of specified characters are replaced by a specified character. -// /// -// /// The string to filter. -// /// The characters to replace. -// /// The replacement character. -// /// The filtered string. -// public static string ReplaceMany(this string text, char[] chars, char replacement) -// { -// if (text == null) throw new ArgumentNullException(nameof(text)); -// if (chars == null) throw new ArgumentNullException(nameof(chars)); -// -// -// for (int i = 0; i < chars.Length; i++) -// text = text.Replace(chars[i], replacement); -// -// return text; -// } -// // FORMAT STRINGS /// @@ -1212,271 +225,6 @@ namespace Umbraco.Core { return Current.ShortStringHelper.CleanStringForSafeFileName(text, culture); } -// -// /// -// /// An extension method that returns a new string in which all occurrences of a -// /// specified string in the current instance are replaced with another specified string. -// /// StringComparison specifies the type of search to use for the specified string. -// /// -// /// Current instance of the string -// /// Specified string to replace -// /// Specified string to inject -// /// String Comparison object to specify search type -// /// Updated string -// public static string Replace(this string source, string oldString, string newString, StringComparison stringComparison) -// { -// // This initialization ensures the first check starts at index zero of the source. On successive checks for -// // a match, the source is skipped to immediately after the last replaced occurrence for efficiency -// // and to avoid infinite loops when oldString and newString compare equal. -// int index = -1 * newString.Length; -// -// // Determine if there are any matches left in source, starting from just after the result of replacing the last match. -// while ((index = source.IndexOf(oldString, index + newString.Length, stringComparison)) >= 0) -// { -// // Remove the old text. -// source = source.Remove(index, oldString.Length); -// -// // Add the replacement text. -// source = source.Insert(index, newString); -// } -// -// return source; -// } -// -// /// -// /// Converts a literal string into a C# expression. -// /// -// /// Current instance of the string. -// /// The string in a C# format. -// public static string ToCSharpString(this string s) -// { -// if (s == null) return ""; -// -// // http://stackoverflow.com/questions/323640/can-i-convert-a-c-sharp-string-value-to-an-escaped-string-literal -// -// var sb = new StringBuilder(s.Length + 2); -// for (var rp = 0; rp < s.Length; rp++) -// { -// var c = s[rp]; -// if (c < ToCSharpEscapeChars.Length && '\0' != ToCSharpEscapeChars[c]) -// sb.Append('\\').Append(ToCSharpEscapeChars[c]); -// else if ('~' >= c && c >= ' ') -// sb.Append(c); -// else -// sb.Append(@"\x") -// .Append(ToCSharpHexDigitLower[c >> 12 & 0x0F]) -// .Append(ToCSharpHexDigitLower[c >> 8 & 0x0F]) -// .Append(ToCSharpHexDigitLower[c >> 4 & 0x0F]) -// .Append(ToCSharpHexDigitLower[c & 0x0F]); -// } -// -// return sb.ToString(); -// -// // requires full trust -// /* -// using (var writer = new StringWriter()) -// using (var provider = CodeDomProvider.CreateProvider("CSharp")) -// { -// provider.GenerateCodeFromExpression(new CodePrimitiveExpression(s), writer, null); -// return writer.ToString().Replace(string.Format("\" +{0}\t\"", Environment.NewLine), ""); -// } -// */ -// } -// -// public static string EscapeRegexSpecialCharacters(this string text) -// { -// var regexSpecialCharacters = new Dictionary -// { -// {".", @"\."}, -// {"(", @"\("}, -// {")", @"\)"}, -// {"]", @"\]"}, -// {"[", @"\["}, -// {"{", @"\{"}, -// {"}", @"\}"}, -// {"?", @"\?"}, -// {"!", @"\!"}, -// {"$", @"\$"}, -// {"^", @"\^"}, -// {"+", @"\+"}, -// {"*", @"\*"}, -// {"|", @"\|"}, -// {"<", @"\<"}, -// {">", @"\>"} -// }; -// return ReplaceMany(text, regexSpecialCharacters); -// } -// -// /// -// /// Checks whether a string "haystack" contains within it any of the strings in the "needles" collection and returns true if it does or false if it doesn't -// /// -// /// The string to check -// /// The collection of strings to check are contained within the first string -// /// The type of comparison to perform - defaults to -// /// True if any of the needles are contained with haystack; otherwise returns false -// /// Added fix to ensure the comparison is used - see http://issues.umbraco.org/issue/U4-11313 -// public static bool ContainsAny(this string haystack, IEnumerable needles, StringComparison comparison = StringComparison.CurrentCulture) -// { -// if (haystack == null) -// throw new ArgumentNullException("haystack"); -// -// if (string.IsNullOrEmpty(haystack) || needles == null || !needles.Any()) -// { -// return false; -// } -// -// return needles.Any(value => haystack.IndexOf(value, comparison) >= 0); -// } -// -// public static bool CsvContains(this string csv, string value) -// { -// if (string.IsNullOrEmpty(csv)) -// { -// return false; -// } -// var idCheckList = csv.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries); -// return idCheckList.Contains(value); -// } -// -// /// -// /// Converts a file name to a friendly name for a content item -// /// -// /// -// /// -// public static string ToFriendlyName(this string fileName) -// { -// // strip the file extension -// fileName = fileName.StripFileExtension(); -// -// // underscores and dashes to spaces -// fileName = fileName.ReplaceMany(new[] { '_', '-' }, ' '); -// -// // any other conversions ? -// -// // Pascalcase (to be done last) -// fileName = CultureInfo.InvariantCulture.TextInfo.ToTitleCase(fileName); -// -// // Replace multiple consecutive spaces with a single space -// fileName = string.Join(" ", fileName.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)); -// -// return fileName; -// } -// -// // From: http://stackoverflow.com/a/961504/5018 -// // filters control characters but allows only properly-formed surrogate sequences -// private static readonly Lazy InvalidXmlChars = new Lazy(() => -// new Regex( -// @"(? -// /// An extension method that returns a new string in which all occurrences of an -// /// unicode characters that are invalid in XML files are replaced with an empty string. -// /// -// /// Current instance of the string -// /// Updated string -// /// -// /// -// /// removes any unusual unicode characters that can't be encoded into XML -// /// -// internal static string ToValidXmlString(this string text) -// { -// return string.IsNullOrEmpty(text) ? text : InvalidXmlChars.Value.Replace(text, ""); -// } -// -// /// -// /// Converts a string to a Guid - WARNING, depending on the string, this may not be unique -// /// -// /// -// /// -// internal static Guid ToGuid(this string text) -// { -// return CreateGuidFromHash(UrlNamespace, -// text, -// CryptoConfig.AllowOnlyFipsAlgorithms -// ? 5 // SHA1 -// : 3); // MD5 -// } -// -// /// -// /// The namespace for URLs (from RFC 4122, Appendix C). -// /// -// /// See RFC 4122 -// /// -// internal static readonly Guid UrlNamespace = new Guid("6ba7b811-9dad-11d1-80b4-00c04fd430c8"); -// -// /// -// /// Creates a name-based UUID using the algorithm from RFC 4122 §4.3. -// /// -// /// See GuidUtility.cs for original implementation. -// /// -// /// The ID of the namespace. -// /// The name (within that namespace). -// /// The version number of the UUID to create; this value must be either -// /// 3 (for MD5 hashing) or 5 (for SHA-1 hashing). -// /// A UUID derived from the namespace and name. -// /// See Generating a deterministic GUID. -// internal static Guid CreateGuidFromHash(Guid namespaceId, string name, int version) -// { -// if (name == null) -// throw new ArgumentNullException("name"); -// if (version != 3 && version != 5) -// throw new ArgumentOutOfRangeException("version", "version must be either 3 or 5."); -// -// // convert the name to a sequence of octets (as defined by the standard or conventions of its namespace) (step 3) -// // ASSUME: UTF-8 encoding is always appropriate -// byte[] nameBytes = Encoding.UTF8.GetBytes(name); -// -// // convert the namespace UUID to network order (step 3) -// byte[] namespaceBytes = namespaceId.ToByteArray(); -// SwapByteOrder(namespaceBytes); -// -// // comput the hash of the name space ID concatenated with the name (step 4) -// byte[] hash; -// using (HashAlgorithm algorithm = version == 3 ? (HashAlgorithm)MD5.Create() : SHA1.Create()) -// { -// algorithm.TransformBlock(namespaceBytes, 0, namespaceBytes.Length, null, 0); -// algorithm.TransformFinalBlock(nameBytes, 0, nameBytes.Length); -// hash = algorithm.Hash; -// } -// -// // most bytes from the hash are copied straight to the bytes of the new GUID (steps 5-7, 9, 11-12) -// byte[] newGuid = new byte[16]; -// Array.Copy(hash, 0, newGuid, 0, 16); -// -// // set the four most significant bits (bits 12 through 15) of the time_hi_and_version field to the appropriate 4-bit version number from Section 4.1.3 (step 8) -// newGuid[6] = (byte)((newGuid[6] & 0x0F) | (version << 4)); -// -// // set the two most significant bits (bits 6 and 7) of the clock_seq_hi_and_reserved to zero and one, respectively (step 10) -// newGuid[8] = (byte)((newGuid[8] & 0x3F) | 0x80); -// -// // convert the resulting UUID to local byte order (step 13) -// SwapByteOrder(newGuid); -// return new Guid(newGuid); -// } -// -// // Converts a GUID (expressed as a byte array) to/from network order (MSB-first). -// internal static void SwapByteOrder(byte[] guid) -// { -// SwapBytes(guid, 0, 3); -// SwapBytes(guid, 1, 2); -// SwapBytes(guid, 4, 5); -// SwapBytes(guid, 6, 7); -// } -// -// private static void SwapBytes(byte[] guid, int left, int right) -// { -// byte temp = guid[left]; -// guid[left] = guid[right]; -// guid[right] = temp; -// } -// -// /// -// /// Turns an null-or-whitespace string into a null string. -// /// -// public static string NullOrWhiteSpaceAsNull(this string text) -// => string.IsNullOrWhiteSpace(text) ? null : text; /// /// Checks if a given path is a full path including drive letter diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 931d5ec023..a92f09e096 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -371,9 +371,6 @@ - - - @@ -745,11 +742,9 @@ - - diff --git a/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs b/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs index 85092d3fdb..d629479d95 100644 --- a/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs +++ b/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs @@ -7,6 +7,7 @@ using Moq; using NUnit.Framework; using Umbraco.Core.Security; using Umbraco.Tests.Testing; +using Umbraco.Web.Security; namespace Umbraco.Tests.Membership { diff --git a/src/Umbraco.Tests/Strings/StringExtensionsTests.cs b/src/Umbraco.Tests/Strings/StringExtensionsTests.cs index e0ad65cf8b..66d0a973a7 100644 --- a/src/Umbraco.Tests/Strings/StringExtensionsTests.cs +++ b/src/Umbraco.Tests/Strings/StringExtensionsTests.cs @@ -8,6 +8,7 @@ using Umbraco.Core; using Umbraco.Core.Composing; using Umbraco.Core.Strings; using Umbraco.Tests.Testing; +using Umbraco.Web; namespace Umbraco.Tests.Strings { diff --git a/src/Umbraco.Core/Models/Membership/UmbracoMembershipMember.cs b/src/Umbraco.Web/Models/Membership/UmbracoMembershipMember.cs similarity index 98% rename from src/Umbraco.Core/Models/Membership/UmbracoMembershipMember.cs rename to src/Umbraco.Web/Models/Membership/UmbracoMembershipMember.cs index debce3afe5..78a41a0043 100644 --- a/src/Umbraco.Core/Models/Membership/UmbracoMembershipMember.cs +++ b/src/Umbraco.Web/Models/Membership/UmbracoMembershipMember.cs @@ -1,8 +1,8 @@ using System; using System.Web.Security; -using Umbraco.Core.Configuration; +using Umbraco.Core.Models.Membership; -namespace Umbraco.Core.Models.Membership +namespace Umbraco.Web.Models.Membership { internal class UmbracoMembershipMember : MembershipUser diff --git a/src/Umbraco.Core/Security/MembershipProviderBase.cs b/src/Umbraco.Web/Security/MembershipProviderBase.cs similarity index 99% rename from src/Umbraco.Core/Security/MembershipProviderBase.cs rename to src/Umbraco.Web/Security/MembershipProviderBase.cs index a07ae8caa5..db627be685 100644 --- a/src/Umbraco.Core/Security/MembershipProviderBase.cs +++ b/src/Umbraco.Web/Security/MembershipProviderBase.cs @@ -5,13 +5,13 @@ using System.Configuration.Provider; using System.Text; using System.Text.RegularExpressions; using System.Web; -using System.Web.Configuration; using System.Web.Hosting; using System.Web.Security; +using Umbraco.Core; using Umbraco.Core.Composing; using Umbraco.Core.Logging; -namespace Umbraco.Core.Security +namespace Umbraco.Web.Security { /// /// A base membership provider class offering much of the underlying functionality for initializing and password encryption/hashing. diff --git a/src/Umbraco.Web/Security/MembershipProviderExtensions.cs b/src/Umbraco.Web/Security/MembershipProviderExtensions.cs index ba0abee7e5..9222bfa693 100644 --- a/src/Umbraco.Web/Security/MembershipProviderExtensions.cs +++ b/src/Umbraco.Web/Security/MembershipProviderExtensions.cs @@ -6,8 +6,10 @@ using System.Web.Hosting; using System.Web.Security; using Umbraco.Core; using Umbraco.Core.Models; +using Umbraco.Core.Models.Membership; using Umbraco.Core.Security; using Umbraco.Core.Services; +using Umbraco.Web.Models.Membership; using Umbraco.Web.Security.Providers; namespace Umbraco.Web.Security @@ -15,6 +17,12 @@ namespace Umbraco.Web.Security public static class MembershipProviderExtensions { + internal static UmbracoMembershipMember AsConcreteMembershipUser(this IMembershipUser member, string providerName, bool providerKeyAsGuid = false) + { + var membershipMember = new UmbracoMembershipMember(member, providerName, providerKeyAsGuid); + return membershipMember; + } + /// /// Extension method to check if a password can be reset based on a given provider and the current request (logged in user) /// diff --git a/src/Umbraco.Core/Security/UmbracoMembershipProviderBase.cs b/src/Umbraco.Web/Security/UmbracoMembershipProviderBase.cs similarity index 98% rename from src/Umbraco.Core/Security/UmbracoMembershipProviderBase.cs rename to src/Umbraco.Web/Security/UmbracoMembershipProviderBase.cs index fd5c68aa51..3ab0fad6cb 100644 --- a/src/Umbraco.Core/Security/UmbracoMembershipProviderBase.cs +++ b/src/Umbraco.Web/Security/UmbracoMembershipProviderBase.cs @@ -1,7 +1,8 @@ using System.Text; using System.Web.Security; +using Umbraco.Core.Security; -namespace Umbraco.Core.Security +namespace Umbraco.Web.Security { /// /// A base membership provider class for umbraco providers diff --git a/src/Umbraco.Web/StringExtensions.cs b/src/Umbraco.Web/StringExtensions.cs new file mode 100644 index 0000000000..a67b0e38c5 --- /dev/null +++ b/src/Umbraco.Web/StringExtensions.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Web.Security; + +namespace Umbraco.Web +{ + public static class StringExtensions + { + /// + /// Encrypt the string using the MachineKey in medium trust + /// + /// The string value to be encrypted. + /// The encrypted string. + public static string EncryptWithMachineKey(this string value) + { + if (value == null) + return null; + + string valueToEncrypt = value; + List parts = new List(); + + const int EncrpytBlockSize = 500; + + while (valueToEncrypt.Length > EncrpytBlockSize) + { + parts.Add(valueToEncrypt.Substring(0, EncrpytBlockSize)); + valueToEncrypt = valueToEncrypt.Remove(0, EncrpytBlockSize); + } + + if (valueToEncrypt.Length > 0) + { + parts.Add(valueToEncrypt); + } + + StringBuilder encrpytedValue = new StringBuilder(); + + foreach (var part in parts) + { + var encrpytedBlock = FormsAuthentication.Encrypt(new FormsAuthenticationTicket(0, string.Empty, DateTime.Now, DateTime.MaxValue, false, part)); + encrpytedValue.AppendLine(encrpytedBlock); + } + + return encrpytedValue.ToString().TrimEnd(); + } + + /// + /// Decrypt the encrypted string using the Machine key in medium trust + /// + /// The string value to be decrypted + /// The decrypted string. + public static string DecryptWithMachineKey(this string value) + { + if (value == null) + return null; + + string[] parts = value.Split('\n'); + + StringBuilder decryptedValue = new StringBuilder(); + + foreach (var part in parts) + { + decryptedValue.Append(FormsAuthentication.Decrypt(part.TrimEnd()).UserData); + } + + return decryptedValue.ToString(); + } + } +} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 25337d09d8..d18d6aa0f4 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -233,6 +233,7 @@ + @@ -257,12 +258,15 @@ + + + From a458df360f79f7ad3ee32b6f8bd7f7628e3bf95b Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 25 Nov 2019 23:53:12 +1100 Subject: [PATCH 12/18] Removes references to password question/answer --- .../Constants-Conventions.cs | 16 +---- .../Models/Membership/IMembershipUser.cs | 12 +--- src/Umbraco.Core/ConventionsHelper.cs | 18 ------ .../Migrations/Install/DatabaseDataCreator.cs | 3 - src/Umbraco.Core/Models/Member.cs | 60 ------------------- .../Persistence/Mappers/MemberMapper.cs | 2 - .../Implement/MemberRepository.cs | 4 -- .../IUmbracoMemberTypeMembershipProvider.cs | 2 - .../Services/Implement/MemberService.cs | 7 --- .../UmbracoServiceMembershipProviderTests.cs | 32 ---------- src/Umbraco.Tests/Models/MemberTests.cs | 6 -- .../Models/Mapping/MemberMapDefinition.cs | 1 - .../Membership/UmbracoMembershipMember.cs | 14 ++--- .../PublishedCache/NuCache/PublishedMember.cs | 3 - .../PublishedCache/PublishedMember.cs | 3 - .../Security/MembershipProviderBase.cs | 13 +--- .../Security/MembershipProviderExtensions.cs | 4 +- .../Providers/MembersMembershipProvider.cs | 16 +---- .../Providers/UmbracoMembershipProvider.cs | 29 +-------- 19 files changed, 13 insertions(+), 232 deletions(-) diff --git a/src/Umbraco.Abstractions/Constants-Conventions.cs b/src/Umbraco.Abstractions/Constants-Conventions.cs index 82572fa60e..40b06e37b2 100644 --- a/src/Umbraco.Abstractions/Constants-Conventions.cs +++ b/src/Umbraco.Abstractions/Constants-Conventions.cs @@ -137,21 +137,7 @@ namespace Umbraco.Core public static readonly string UmbracoMemberProviderName = "UmbracoMembershipProvider"; public static readonly string UmbracoRoleProviderName = "UmbracoRoleProvider"; - - /// - /// Property alias for a Members Password Question - /// - public const string PasswordQuestion = "umbracoMemberPasswordRetrievalQuestion"; - - public const string PasswordQuestionLabel = "Password Question"; - - /// - /// Property alias for Members Password Answer - /// - public const string PasswordAnswer = "umbracoMemberPasswordRetrievalAnswer"; - - public const string PasswordAnswerLabel = "Password Answer"; - + /// /// Property alias for the Comments on a Member /// diff --git a/src/Umbraco.Abstractions/Models/Membership/IMembershipUser.cs b/src/Umbraco.Abstractions/Models/Membership/IMembershipUser.cs index 50e5133282..4252900cee 100644 --- a/src/Umbraco.Abstractions/Models/Membership/IMembershipUser.cs +++ b/src/Umbraco.Abstractions/Models/Membership/IMembershipUser.cs @@ -8,23 +8,13 @@ namespace Umbraco.Core.Models.Membership /// public interface IMembershipUser : IEntity { - object ProviderUserKey { get; set; } // fixme: This will be obsolete when we remove membership providers - - string Username { get; set; } string Email { get; set; } /// /// Gets or sets the raw password value /// - string RawPasswordValue { get; set; } // fixme: This will be obsolete when we remove membership providers - - string PasswordQuestion { get; set; } // fixme: This will be obsolete when we remove membership providers - - /// - /// Gets or sets the raw password answer value - /// - string RawPasswordAnswerValue { get; set; } // fixme: This will be obsolete when we remove membership providers + string RawPasswordValue { get; set; } string Comments { get; set; } bool IsApproved { get; set; } diff --git a/src/Umbraco.Core/ConventionsHelper.cs b/src/Umbraco.Core/ConventionsHelper.cs index 834078f84d..5959ee2da5 100644 --- a/src/Umbraco.Core/ConventionsHelper.cs +++ b/src/Umbraco.Core/ConventionsHelper.cs @@ -67,24 +67,6 @@ namespace Umbraco.Core Name = Constants.Conventions.Member.LastPasswordChangeDateLabel, DataTypeId = Constants.DataTypes.LabelDateTime } - }, - { - Constants.Conventions.Member.PasswordAnswer, - new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Nvarchar, true, - Constants.Conventions.Member.PasswordAnswer) - { - Name = Constants.Conventions.Member.PasswordAnswerLabel, - DataTypeId = Constants.DataTypes.LabelString - } - }, - { - Constants.Conventions.Member.PasswordQuestion, - new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Nvarchar, true, - Constants.Conventions.Member.PasswordQuestion) - { - Name = Constants.Conventions.Member.PasswordQuestionLabel, - DataTypeId = Constants.DataTypes.LabelString - } } }; } diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs index 5dc937822b..008331f153 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs @@ -229,9 +229,6 @@ namespace Umbraco.Core.Migrations.Install _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 32, UniqueId = 32.ToGuid(), DataTypeId = Constants.DataTypes.LabelDateTime, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastLockoutDate, Name = Constants.Conventions.Member.LastLockoutDateLabel, SortOrder = 4, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 33, UniqueId = 33.ToGuid(), DataTypeId = Constants.DataTypes.LabelDateTime, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastLoginDate, Name = Constants.Conventions.Member.LastLoginDateLabel, SortOrder = 5, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 34, UniqueId = 34.ToGuid(), DataTypeId = Constants.DataTypes.LabelDateTime, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastPasswordChangeDate, Name = Constants.Conventions.Member.LastPasswordChangeDateLabel, SortOrder = 6, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); - _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 35, UniqueId = 35.ToGuid(), DataTypeId = Constants.DataTypes.LabelDateTime, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.PasswordQuestion, Name = Constants.Conventions.Member.PasswordQuestionLabel, SortOrder = 7, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte)ContentVariation.Nothing }); - _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 36, UniqueId = 36.ToGuid(), DataTypeId = Constants.DataTypes.LabelDateTime, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.PasswordAnswer, Name = Constants.Conventions.Member.PasswordAnswerLabel, SortOrder = 8, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte)ContentVariation.Nothing }); - } private void CreateLanguageData() diff --git a/src/Umbraco.Core/Models/Member.cs b/src/Umbraco.Core/Models/Member.cs index b473a154f1..78d224fc7b 100644 --- a/src/Umbraco.Core/Models/Member.cs +++ b/src/Umbraco.Core/Models/Member.cs @@ -166,66 +166,6 @@ namespace Umbraco.Core.Models // * Check if we are using the umbraco membership provider, if so then we need to use the configured fields - not the explicit fields below // * If any of the fields don't exist, what should we do? Currently it will throw an exception! - /// - /// Gets or sets the Password Question - /// - /// - /// Alias: umbracoMemberPasswordRetrievalQuestion - /// Part of the standard properties collection. - /// - [DataMember] - public string PasswordQuestion - { - get - { - var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.PasswordQuestion, "PasswordQuestion", default(string)); - if (a.Success == false) return a.Result; - - return Properties[Constants.Conventions.Member.PasswordQuestion].GetValue() == null - ? string.Empty - : Properties[Constants.Conventions.Member.PasswordQuestion].GetValue().ToString(); - } - set - { - if (WarnIfPropertyTypeNotFoundOnSet( - Constants.Conventions.Member.PasswordQuestion, - "PasswordQuestion") == false) return; - - Properties[Constants.Conventions.Member.PasswordQuestion].SetValue(value); - } - } - - /// - /// Gets or sets the raw password answer value - /// - /// - /// For security reasons this value should be encrypted, the encryption process is handled by the membership provider - /// Alias: umbracoMemberPasswordRetrievalAnswer - /// - /// Part of the standard properties collection. - /// - [IgnoreDataMember] - public string RawPasswordAnswerValue - { - get - { - var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.PasswordAnswer, "PasswordAnswer", default(string)); - if (a.Success == false) return a.Result; - - return Properties[Constants.Conventions.Member.PasswordAnswer].GetValue() == null - ? string.Empty - : Properties[Constants.Conventions.Member.PasswordAnswer].GetValue().ToString(); - } - set - { - if (WarnIfPropertyTypeNotFoundOnSet( - Constants.Conventions.Member.PasswordAnswer, - "PasswordAnswer") == false) return; - - Properties[Constants.Conventions.Member.PasswordAnswer].SetValue(value); - } - } - /// /// Gets or set the comments for the member /// diff --git a/src/Umbraco.Core/Persistence/Mappers/MemberMapper.cs b/src/Umbraco.Core/Persistence/Mappers/MemberMapper.cs index e0f29ce128..51c453aadc 100644 --- a/src/Umbraco.Core/Persistence/Mappers/MemberMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/MemberMapper.cs @@ -41,8 +41,6 @@ namespace Umbraco.Core.Persistence.Mappers DefineMap(nameof(Member.IsApproved), nameof(PropertyDataDto.IntegerValue)); DefineMap(nameof(Member.IsLockedOut), nameof(PropertyDataDto.IntegerValue)); DefineMap(nameof(Member.Comments), nameof(PropertyDataDto.TextValue)); - DefineMap(nameof(Member.RawPasswordAnswerValue), nameof(PropertyDataDto.VarcharValue)); - DefineMap(nameof(Member.PasswordQuestion), nameof(PropertyDataDto.VarcharValue)); DefineMap(nameof(Member.FailedPasswordAttempts), nameof(PropertyDataDto.IntegerValue)); DefineMap(nameof(Member.LastLockoutDate), nameof(PropertyDataDto.DateValue)); DefineMap(nameof(Member.LastLoginDate), nameof(PropertyDataDto.DateValue)); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs index 0f7984a1bd..7ddc55b6db 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs @@ -233,10 +233,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override void PersistNewItem(IMember entity) { - if (entity.ProviderUserKey == null) - { - entity.ProviderUserKey = entity.Key; - } entity.AddingEntity(); var member = (Member) entity; diff --git a/src/Umbraco.Core/Security/IUmbracoMemberTypeMembershipProvider.cs b/src/Umbraco.Core/Security/IUmbracoMemberTypeMembershipProvider.cs index f67b3d099d..bd165334f1 100644 --- a/src/Umbraco.Core/Security/IUmbracoMemberTypeMembershipProvider.cs +++ b/src/Umbraco.Core/Security/IUmbracoMemberTypeMembershipProvider.cs @@ -16,8 +16,6 @@ namespace Umbraco.Core.Security string CommentPropertyTypeAlias { get; } string LastLoginPropertyTypeAlias { get; } string LastPasswordChangedPropertyTypeAlias { get; } - string PasswordRetrievalQuestionPropertyTypeAlias { get; } - string PasswordRetrievalAnswerPropertyTypeAlias { get; } } } diff --git a/src/Umbraco.Core/Services/Implement/MemberService.cs b/src/Umbraco.Core/Services/Implement/MemberService.cs index 9f68d86970..0dccca8a64 100644 --- a/src/Umbraco.Core/Services/Implement/MemberService.cs +++ b/src/Umbraco.Core/Services/Implement/MemberService.cs @@ -1235,13 +1235,6 @@ namespace Umbraco.Core.Services.Implement foreach (var property in member.Properties) { - //ignore list - switch (property.Alias) - { - case Constants.Conventions.Member.PasswordQuestion: - continue; - } - var propertyExportModel = new MemberExportProperty { Id = property.Id, diff --git a/src/Umbraco.Tests/Membership/UmbracoServiceMembershipProviderTests.cs b/src/Umbraco.Tests/Membership/UmbracoServiceMembershipProviderTests.cs index 0099f3f9d9..5a33cfb731 100644 --- a/src/Umbraco.Tests/Membership/UmbracoServiceMembershipProviderTests.cs +++ b/src/Umbraco.Tests/Membership/UmbracoServiceMembershipProviderTests.cs @@ -74,38 +74,6 @@ namespace Umbraco.Tests.Membership Assert.IsNull(user); } - [Test] - public void Answer_Is_Encrypted() - { - IMember createdMember = null; - var memberType = MockedContentTypes.CreateSimpleMemberType(); - foreach (var p in ConventionsHelper.GetStandardPropertyTypeStubs()) - { - memberType.AddPropertyType(p.Value); - } - var memberTypeServiceMock = new Mock(); - memberTypeServiceMock.Setup(x => x.GetDefault()).Returns("Member"); - var membershipServiceMock = new Mock(); - membershipServiceMock.Setup(service => service.Exists("test")).Returns(false); - membershipServiceMock.Setup(service => service.GetByEmail("test@test.com")).Returns(() => null); - membershipServiceMock.Setup( - service => service.CreateWithIdentity(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - .Callback((string u, string e, string p, string m, bool isApproved) => - { - createdMember = new Member("test", e, u, p, memberType, isApproved); - }) - .Returns(() => createdMember); - var provider = new MembersMembershipProvider(membershipServiceMock.Object, memberTypeServiceMock.Object, TestHelper.GetUmbracoVersion()); - provider.Initialize("test", new NameValueCollection()); - - - MembershipCreateStatus status; - provider.CreateUser("test", "test", "testtest$1", "test@test.com", "test", "test", true, "test", out status); - - Assert.AreNotEqual("test", createdMember.RawPasswordAnswerValue); - Assert.AreEqual(provider.EncryptString("test"), createdMember.RawPasswordAnswerValue); - } - [Test] public void Password_Encrypted() { diff --git a/src/Umbraco.Tests/Models/MemberTests.cs b/src/Umbraco.Tests/Models/MemberTests.cs index 836ce54afa..f51efaaadd 100644 --- a/src/Umbraco.Tests/Models/MemberTests.cs +++ b/src/Umbraco.Tests/Models/MemberTests.cs @@ -50,9 +50,7 @@ namespace Umbraco.Tests.Models member.LastLockoutDate = DateTime.Now; member.LastLoginDate = DateTime.Now; member.LastPasswordChangeDate = DateTime.Now; - member.PasswordQuestion = "question"; member.ProviderUserKey = Guid.NewGuid(); - member.RawPasswordAnswerValue = "raw answer"; member.RawPasswordValue = "raw pass"; member.SortOrder = 5; member.Trashed = false; @@ -89,9 +87,7 @@ namespace Umbraco.Tests.Models Assert.AreEqual(clone.Trashed, member.Trashed); Assert.AreEqual(clone.UpdateDate, member.UpdateDate); Assert.AreEqual(clone.VersionId, member.VersionId); - Assert.AreEqual(clone.PasswordQuestion, member.PasswordQuestion); Assert.AreEqual(clone.ProviderUserKey, member.ProviderUserKey); - Assert.AreEqual(clone.RawPasswordAnswerValue, member.RawPasswordAnswerValue); Assert.AreEqual(clone.RawPasswordValue, member.RawPasswordValue); Assert.AreNotSame(clone.Properties, member.Properties); Assert.AreEqual(clone.Properties.Count(), member.Properties.Count()); @@ -137,9 +133,7 @@ namespace Umbraco.Tests.Models member.LastLockoutDate = DateTime.Now; member.LastLoginDate = DateTime.Now; member.LastPasswordChangeDate = DateTime.Now; - member.PasswordQuestion = "question"; member.ProviderUserKey = Guid.NewGuid(); - member.RawPasswordAnswerValue = "raw answer"; member.RawPasswordValue = "raw pass"; member.SortOrder = 5; member.Trashed = false; diff --git a/src/Umbraco.Web/Models/Mapping/MemberMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/MemberMapDefinition.cs index e6185ab80e..435d349aa3 100644 --- a/src/Umbraco.Web/Models/Mapping/MemberMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/MemberMapDefinition.cs @@ -65,7 +65,6 @@ namespace Umbraco.Web.Models.Mapping target.LastLockoutDate = source.LastLockoutDate; target.LastLoginDate = source.LastLoginDate; target.LastPasswordChangeDate = source.LastPasswordChangedDate; - target.ProviderUserKey = source.ProviderUserKey; target.RawPasswordValue = source.CreationDate > DateTime.MinValue ? Guid.NewGuid().ToString("N") : ""; target.UpdateDate = source.LastActivityDate; target.Username = source.UserName; diff --git a/src/Umbraco.Web/Models/Membership/UmbracoMembershipMember.cs b/src/Umbraco.Web/Models/Membership/UmbracoMembershipMember.cs index 78a41a0043..2d9a197706 100644 --- a/src/Umbraco.Web/Models/Membership/UmbracoMembershipMember.cs +++ b/src/Umbraco.Web/Models/Membership/UmbracoMembershipMember.cs @@ -10,7 +10,6 @@ namespace Umbraco.Web.Models.Membership private readonly IMembershipUser _member; private readonly string _userName; private readonly object _providerUserKey; - private readonly string _passwordQuestion; private readonly bool _isLockedOut; private readonly DateTime _lastLockoutDate; private readonly DateTime _creationDate; @@ -26,7 +25,7 @@ namespace Umbraco.Web.Models.Membership //NOTE: We are not calling the base constructor which will validate that a provider with the specified name exists which causes issues with unit tests. The ctor // validation for that doesn't need to be there anyways (have checked the source). - public UmbracoMembershipMember(IMembershipUser member, string providerName, bool providerKeyAsGuid = false) + public UmbracoMembershipMember(IMembershipUser member, string providerName) { _member = member; //NOTE: We are copying the values here so that everything is consistent with how the underlying built-in ASP.Net membership user @@ -34,11 +33,9 @@ namespace Umbraco.Web.Models.Membership if (member.Username != null) _userName = member.Username.Trim(); if (member.Email != null) - _email = member.Email.Trim(); - if (member.PasswordQuestion != null) - _passwordQuestion = member.PasswordQuestion.Trim(); + _email = member.Email.Trim(); _providerName = providerName; - _providerUserKey = providerKeyAsGuid ? member.ProviderUserKey : member.Id; + _providerUserKey = member.Key; _comment = member.Comments; _isApproved = member.IsApproved; _isLockedOut = member.IsLockedOut; @@ -71,10 +68,7 @@ namespace Umbraco.Web.Models.Membership set { _email = value; } } - public override string PasswordQuestion - { - get { return _passwordQuestion; } - } + public override string PasswordQuestion => string.Empty; public override string Comment { diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedMember.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedMember.cs index dc608fe391..9e47802c08 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedMember.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedMember.cs @@ -76,7 +76,6 @@ namespace Umbraco.Web.PublishedCache.NuCache // see also PublishedContentType AddIf(contentType, properties, "Email", member.Email); AddIf(contentType, properties, "Username", member.Username); - AddIf(contentType, properties, "PasswordQuestion", member.PasswordQuestion); AddIf(contentType, properties, "Comments", member.Comments); AddIf(contentType, properties, "IsApproved", member.IsApproved); AddIf(contentType, properties, "IsLockedOut", member.IsLockedOut); @@ -103,8 +102,6 @@ namespace Umbraco.Web.PublishedCache.NuCache public string UserName => _member.Username; - public string PasswordQuestion => _member.PasswordQuestion; - public string Comments => _member.Comments; public bool IsApproved => _member.IsApproved; diff --git a/src/Umbraco.Web/PublishedCache/PublishedMember.cs b/src/Umbraco.Web/PublishedCache/PublishedMember.cs index 6e9ec61c62..b26e9b90a5 100644 --- a/src/Umbraco.Web/PublishedCache/PublishedMember.cs +++ b/src/Umbraco.Web/PublishedCache/PublishedMember.cs @@ -51,8 +51,6 @@ namespace Umbraco.Web.PublishedCache public string UserName => _membershipUser.Username; - public string PasswordQuestion => _membershipUser.PasswordQuestion; - public string Comments => _membershipUser.Comments; public bool IsApproved => _membershipUser.IsApproved; @@ -98,7 +96,6 @@ namespace Umbraco.Web.PublishedCache EnsureMemberProperty(properties, aliases, "Email", Email); EnsureMemberProperty(properties, aliases, "UserName", UserName); - EnsureMemberProperty(properties, aliases, "PasswordQuestion", PasswordQuestion); EnsureMemberProperty(properties, aliases, "Comments", Comments); EnsureMemberProperty(properties, aliases, "IsApproved", IsApproved); EnsureMemberProperty(properties, aliases, "IsLockedOut", IsLockedOut); diff --git a/src/Umbraco.Web/Security/MembershipProviderBase.cs b/src/Umbraco.Web/Security/MembershipProviderBase.cs index db627be685..3bd8bf262b 100644 --- a/src/Umbraco.Web/Security/MembershipProviderBase.cs +++ b/src/Umbraco.Web/Security/MembershipProviderBase.cs @@ -74,7 +74,6 @@ namespace Umbraco.Web.Security private int _passwordAttemptWindow; private MembershipPasswordFormat _passwordFormat; private string _passwordStrengthRegularExpression; - private bool _requiresQuestionAndAnswer; private bool _requiresUniqueEmail; public bool UseLegacyEncoding { get; private set; } @@ -164,14 +163,9 @@ namespace Umbraco.Web.Security } /// - /// Gets a value indicating whether the membership provider is configured to require the user to answer a password question for password reset and retrieval. + /// Always returns false, question/answer is not supported /// - /// - /// true if a password answer is required for password reset and retrieval; otherwise, false. The default is true. - public override bool RequiresQuestionAndAnswer - { - get { return _requiresQuestionAndAnswer; } - } + public override bool RequiresQuestionAndAnswer => false; /// /// Gets a value indicating whether the membership provider is configured to require a unique e-mail address for each user name. @@ -225,7 +219,6 @@ namespace Umbraco.Web.Security _enablePasswordRetrieval = config.GetValue("enablePasswordRetrieval", false); _enablePasswordReset = config.GetValue("enablePasswordReset", true); - _requiresQuestionAndAnswer = config.GetValue("requiresQuestionAndAnswer", false); _requiresUniqueEmail = config.GetValue("requiresUniqueEmail", true); _maxInvalidPasswordAttempts = GetIntValue(config, "maxInvalidPasswordAttempts", 5, false, 0); _passwordAttemptWindow = GetIntValue(config, "passwordAttemptWindow", 10, false, 0); @@ -695,7 +688,7 @@ namespace Umbraco.Web.Security sb.AppendLine("_passwordAttemptWindow=" + _passwordAttemptWindow); sb.AppendLine("_passwordFormat=" + _passwordFormat); sb.AppendLine("_passwordStrengthRegularExpression=" + _passwordStrengthRegularExpression); - sb.AppendLine("_requiresQuestionAndAnswer=" + _requiresQuestionAndAnswer); + sb.AppendLine("_requiresQuestionAndAnswer=" + RequiresQuestionAndAnswer); sb.AppendLine("_requiresUniqueEmail=" + _requiresUniqueEmail); return sb.ToString(); } diff --git a/src/Umbraco.Web/Security/MembershipProviderExtensions.cs b/src/Umbraco.Web/Security/MembershipProviderExtensions.cs index 9222bfa693..43b970d0d1 100644 --- a/src/Umbraco.Web/Security/MembershipProviderExtensions.cs +++ b/src/Umbraco.Web/Security/MembershipProviderExtensions.cs @@ -17,9 +17,9 @@ namespace Umbraco.Web.Security public static class MembershipProviderExtensions { - internal static UmbracoMembershipMember AsConcreteMembershipUser(this IMembershipUser member, string providerName, bool providerKeyAsGuid = false) + internal static UmbracoMembershipMember AsConcreteMembershipUser(this IMembershipUser member, string providerName) { - var membershipMember = new UmbracoMembershipMember(member, providerName, providerKeyAsGuid); + var membershipMember = new UmbracoMembershipMember(member, providerName); return membershipMember; } diff --git a/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs b/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs index 8fef87f436..4d188b0073 100644 --- a/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs +++ b/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs @@ -31,8 +31,6 @@ namespace Umbraco.Web.Security.Providers CommentPropertyTypeAlias = Constants.Conventions.Member.Comments; LastLoginPropertyTypeAlias = Constants.Conventions.Member.LastLoginDate; LastPasswordChangedPropertyTypeAlias = Constants.Conventions.Member.LastPasswordChangeDate; - PasswordRetrievalQuestionPropertyTypeAlias = Constants.Conventions.Member.PasswordQuestion; - PasswordRetrievalAnswerPropertyTypeAlias = Constants.Conventions.Member.PasswordAnswer; _memberTypeService = memberTypeService; } @@ -40,13 +38,12 @@ namespace Umbraco.Web.Security.Providers private string _defaultMemberTypeAlias = "Member"; private volatile bool _hasDefaultMember; private static readonly object Locker = new object(); - private bool _providerKeyAsGuid; public override string ProviderName => "MembersMembershipProvider"; protected override MembershipUser ConvertToMembershipUser(IMember entity) { - return entity.AsConcreteMembershipUser(Name, _providerKeyAsGuid); + return entity.AsConcreteMembershipUser(Name); } public string LockPropertyTypeAlias { get; } @@ -56,8 +53,6 @@ namespace Umbraco.Web.Security.Providers public string CommentPropertyTypeAlias { get; } public string LastLoginPropertyTypeAlias { get; } public string LastPasswordChangedPropertyTypeAlias { get; } - public string PasswordRetrievalQuestionPropertyTypeAlias { get; } - public string PasswordRetrievalAnswerPropertyTypeAlias { get; } public override void Initialize(string name, NameValueCollection config) { @@ -74,15 +69,6 @@ namespace Umbraco.Web.Security.Providers _hasDefaultMember = true; } - //devs can configure the provider user key to be a guid if they want, by default it is int - if (config["providerKeyType"] != null) - { - if (config["providerKeyType"] == "guid") - { - _providerKeyAsGuid = true; - } - } - // these need to be lazy else we get a stack overflow since we cannot access Membership.HashAlgorithmType without initializing the providers _passwordConfig = new Lazy(() => new MembershipProviderPasswordConfiguration( diff --git a/src/Umbraco.Web/Security/Providers/UmbracoMembershipProvider.cs b/src/Umbraco.Web/Security/Providers/UmbracoMembershipProvider.cs index 9789f86978..ec51bf0547 100644 --- a/src/Umbraco.Web/Security/Providers/UmbracoMembershipProvider.cs +++ b/src/Umbraco.Web/Security/Providers/UmbracoMembershipProvider.cs @@ -113,18 +113,7 @@ namespace Umbraco.Web.Security.Providers /// protected override bool PerformChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer) { - var member = MemberService.GetByUsername(username); - if (member == null) - { - return false; - } - - member.PasswordQuestion = newPasswordQuestion; - member.RawPasswordAnswerValue = EncryptString(newPasswordAnswer); - - MemberService.Save(member); - - return true; + throw new NotSupportedException("Password question/answer is not supported"); } /// @@ -171,8 +160,6 @@ namespace Umbraco.Web.Security.Providers memberTypeAlias, isApproved); - member.PasswordQuestion = passwordQuestion; - member.RawPasswordAnswerValue = EncryptString(passwordAnswer); member.LastLoginDate = DateTime.Now; member.LastPasswordChangeDate = DateTime.Now; @@ -305,13 +292,6 @@ namespace Umbraco.Web.Security.Providers throw new ProviderException("The supplied user is not found"); } - var encAnswer = EncryptString(answer); - - if (RequiresQuestionAndAnswer && m.RawPasswordAnswerValue != encAnswer) - { - throw new ProviderException("Incorrect password answer"); - } - var decodedPassword = DecryptPassword(m.RawPasswordValue); return decodedPassword; @@ -432,13 +412,6 @@ namespace Umbraco.Web.Security.Providers throw new ProviderException("The member is locked out."); } - var encAnswer = EncryptString(answer); - - if (RequiresQuestionAndAnswer && m.RawPasswordAnswerValue != encAnswer) - { - throw new ProviderException("Incorrect password answer"); - } - string salt; var encodedPassword = PasswordSecurity.EncryptOrHashNewPassword(generatedPassword, out salt); m.RawPasswordValue = PasswordSecurity.FormatPasswordForStorage(encodedPassword, salt); From d41d5450455b4fc08989a127d22fabb6de36151e Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 25 Nov 2019 23:55:14 +1100 Subject: [PATCH 13/18] oops fix build --- src/Umbraco.Tests/Testing/UmbracoTestBase.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs index a321ce90cc..db36e1e018 100644 --- a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs +++ b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs @@ -42,7 +42,6 @@ using Umbraco.Web.Composing.CompositionExtensions; using Umbraco.Web.Sections; using Current = Umbraco.Core.Composing.Current; using FileSystems = Umbraco.Core.IO.FileSystems; -using Umbraco.Web.Dictionary; using Umbraco.Core.Dictionary; namespace Umbraco.Tests.Testing From 4fe20bb7c89aba178cd417b386891061558d6946 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 26 Nov 2019 00:03:45 +1100 Subject: [PATCH 14/18] Removes references to password question/answer/ProviderUserKey --- src/Umbraco.Core/Models/Member.cs | 17 ----------------- src/Umbraco.Core/Models/Membership/User.cs | 14 -------------- .../Persistence/Factories/ContentBaseFactory.cs | 2 -- src/Umbraco.Tests/Models/MemberTests.cs | 3 --- src/Umbraco.Tests/Models/UserTests.cs | 6 ------ src/Umbraco.Web/Models/ProfileModel.cs | 3 --- src/Umbraco.Web/Security/MembershipHelper.cs | 1 - 7 files changed, 46 deletions(-) diff --git a/src/Umbraco.Core/Models/Member.cs b/src/Umbraco.Core/Models/Member.cs index 78d224fc7b..3d4940c348 100644 --- a/src/Umbraco.Core/Models/Member.cs +++ b/src/Umbraco.Core/Models/Member.cs @@ -18,7 +18,6 @@ namespace Umbraco.Core.Models private string _username; private string _email; private string _rawPasswordValue; - private object _providerUserKey; /// /// Constructor for creating an empty Member object @@ -402,22 +401,6 @@ namespace Umbraco.Core.Models [DataMember] public virtual string ContentTypeAlias => ContentType.Alias; - /// - /// User key from the Provider. - /// - /// - /// When using standard umbraco provider this key will - /// correspond to the guid UniqueId/Key. - /// Otherwise it will the one available from the asp.net - /// membership provider. - /// - [DataMember] - public virtual object ProviderUserKey - { - get => _providerUserKey; - set => SetPropertyValueAndDetectChanges(value, ref _providerUserKey, nameof(ProviderUserKey)); - } - /* Internal experiment - only used for mapping queries. * Adding these to have first level properties instead of the Properties collection. */ diff --git a/src/Umbraco.Core/Models/Membership/User.cs b/src/Umbraco.Core/Models/Membership/User.cs index 3d071b0a18..6ec82325d5 100644 --- a/src/Umbraco.Core/Models/Membership/User.cs +++ b/src/Umbraco.Core/Models/Membership/User.cs @@ -124,13 +124,6 @@ namespace Umbraco.Core.Models.Membership #region Implementation of IMembershipUser - [IgnoreDataMember] - public object ProviderUserKey - { - get => Id; - set => throw new NotSupportedException("Cannot set the provider user key for a user"); - } - [DataMember] public DateTime? EmailConfirmedDate { @@ -204,13 +197,6 @@ namespace Umbraco.Core.Models.Membership set => SetPropertyValueAndDetectChanges(value, ref _failedLoginAttempts, nameof(FailedPasswordAttempts)); } - // TODO: Figure out how to support all of this! - we cannot have NotImplementedExceptions because these get used by the IMembershipMemberService service so - // we'll just have them as generic get/set which don't interact with the db. - - [IgnoreDataMember] - public string PasswordQuestion { get; set; } - [IgnoreDataMember] - public string RawPasswordAnswerValue { get; set; } [IgnoreDataMember] public string Comments { get; set; } diff --git a/src/Umbraco.Core/Persistence/Factories/ContentBaseFactory.cs b/src/Umbraco.Core/Persistence/Factories/ContentBaseFactory.cs index 434e0393cd..f5dc2612f4 100644 --- a/src/Umbraco.Core/Persistence/Factories/ContentBaseFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/ContentBaseFactory.cs @@ -143,8 +143,6 @@ namespace Umbraco.Core.Persistence.Factories content.CreateDate = nodeDto.CreateDate; content.UpdateDate = contentVersionDto.VersionDate; - content.ProviderUserKey = content.Key; // The `ProviderUserKey` is a membership provider thing - // reset dirty initial properties (U4-1946) content.ResetDirtyProperties(false); return content; diff --git a/src/Umbraco.Tests/Models/MemberTests.cs b/src/Umbraco.Tests/Models/MemberTests.cs index f51efaaadd..81aa00d3f9 100644 --- a/src/Umbraco.Tests/Models/MemberTests.cs +++ b/src/Umbraco.Tests/Models/MemberTests.cs @@ -50,7 +50,6 @@ namespace Umbraco.Tests.Models member.LastLockoutDate = DateTime.Now; member.LastLoginDate = DateTime.Now; member.LastPasswordChangeDate = DateTime.Now; - member.ProviderUserKey = Guid.NewGuid(); member.RawPasswordValue = "raw pass"; member.SortOrder = 5; member.Trashed = false; @@ -87,7 +86,6 @@ namespace Umbraco.Tests.Models Assert.AreEqual(clone.Trashed, member.Trashed); Assert.AreEqual(clone.UpdateDate, member.UpdateDate); Assert.AreEqual(clone.VersionId, member.VersionId); - Assert.AreEqual(clone.ProviderUserKey, member.ProviderUserKey); Assert.AreEqual(clone.RawPasswordValue, member.RawPasswordValue); Assert.AreNotSame(clone.Properties, member.Properties); Assert.AreEqual(clone.Properties.Count(), member.Properties.Count()); @@ -133,7 +131,6 @@ namespace Umbraco.Tests.Models member.LastLockoutDate = DateTime.Now; member.LastLoginDate = DateTime.Now; member.LastPasswordChangeDate = DateTime.Now; - member.ProviderUserKey = Guid.NewGuid(); member.RawPasswordValue = "raw pass"; member.SortOrder = 5; member.Trashed = false; diff --git a/src/Umbraco.Tests/Models/UserTests.cs b/src/Umbraco.Tests/Models/UserTests.cs index 603cd84a1a..08937ed7f7 100644 --- a/src/Umbraco.Tests/Models/UserTests.cs +++ b/src/Umbraco.Tests/Models/UserTests.cs @@ -43,9 +43,6 @@ namespace Umbraco.Tests.Models LastLoginDate = DateTime.Now, LastPasswordChangeDate = DateTime.Now, //Password = "test pass", - //PasswordAnswer = "answer", - PasswordQuestion = "question", - //ProviderUserKey = "user key", SessionTimeout = 5, StartContentIds = new[] { 3 }, StartMediaIds = new[] { 8 }, @@ -88,9 +85,6 @@ namespace Umbraco.Tests.Models LastLoginDate = DateTime.Now, LastPasswordChangeDate = DateTime.Now, //Password = "test pass", - //PasswordAnswer = "answer", - PasswordQuestion = "question", - //ProviderUserKey = "user key", SessionTimeout = 5, StartContentIds = new[] { 3 }, StartMediaIds = new[] { 8 }, diff --git a/src/Umbraco.Web/Models/ProfileModel.cs b/src/Umbraco.Web/Models/ProfileModel.cs index 8e66042d21..7b7b0ecb4a 100644 --- a/src/Umbraco.Web/Models/ProfileModel.cs +++ b/src/Umbraco.Web/Models/ProfileModel.cs @@ -54,9 +54,6 @@ namespace Umbraco.Web.Models [ReadOnly(true)] public string UserName { get; set; } - [ReadOnly(true)] - public string PasswordQuestion { get; set; } - [ReadOnly(true)] public string Comment { get; set; } diff --git a/src/Umbraco.Web/Security/MembershipHelper.cs b/src/Umbraco.Web/Security/MembershipHelper.cs index aa9a4c6798..fddbfb0f41 100644 --- a/src/Umbraco.Web/Security/MembershipHelper.cs +++ b/src/Umbraco.Web/Security/MembershipHelper.cs @@ -387,7 +387,6 @@ namespace Umbraco.Web.Security model.Email = membershipUser.Email; model.UserName = membershipUser.UserName; - model.PasswordQuestion = membershipUser.PasswordQuestion; model.Comment = membershipUser.Comment; model.IsApproved = membershipUser.IsApproved; model.IsLockedOut = membershipUser.IsLockedOut; From a6707cd367d7cfdf6a108a07cb6fd05045ee4b5f Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 26 Nov 2019 00:13:37 +1100 Subject: [PATCH 15/18] Cleans up some usings with System.Web --- src/Umbraco.Core/ContentExtensions.cs | 3 --- src/Umbraco.Core/EmailSender.cs | 2 -- .../PropertyEditors/ValueConverters/GridValueConverter.cs | 5 ----- 3 files changed, 10 deletions(-) diff --git a/src/Umbraco.Core/ContentExtensions.cs b/src/Umbraco.Core/ContentExtensions.cs index f9d06b70a3..df3f7a6611 100644 --- a/src/Umbraco.Core/ContentExtensions.cs +++ b/src/Umbraco.Core/ContentExtensions.cs @@ -7,14 +7,11 @@ using System.Web; using System.Xml.Linq; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using NPoco.Expressions; using Umbraco.Core.Composing; using Umbraco.Core.IO; using Umbraco.Core.Models; -using Umbraco.Core.Models.Entities; using Umbraco.Core.Models.Membership; using Umbraco.Core.Services; -using Umbraco.Core.Services.Implement; namespace Umbraco.Core { diff --git a/src/Umbraco.Core/EmailSender.cs b/src/Umbraco.Core/EmailSender.cs index 7dc75b116d..698034a7e9 100644 --- a/src/Umbraco.Core/EmailSender.cs +++ b/src/Umbraco.Core/EmailSender.cs @@ -1,9 +1,7 @@ using System; using System.Net.Mail; using System.Threading.Tasks; -using System.Web; using Umbraco.Core.Composing; -using Umbraco.Core.Configuration; using Umbraco.Core.Events; namespace Umbraco.Core diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/GridValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/GridValueConverter.cs index b3685457ec..886145ca7a 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/GridValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/GridValueConverter.cs @@ -1,14 +1,9 @@ using System; -using System.Collections.Generic; -using System.IO; using System.Linq; -using System.Web; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using Umbraco.Core.Configuration; using Umbraco.Core.Composing; using Umbraco.Core.Configuration.Grid; -using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models.PublishedContent; From 488f6925b889ec0841669a24002cf54901b7614d Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 26 Nov 2019 12:49:57 +1100 Subject: [PATCH 16/18] Fixes tests, removes some magic strings --- .../PublishedContent/PublishedContentType.cs | 19 +++++---- src/Umbraco.Core/Models/Member.cs | 28 ++++++------- .../Repositories/Implement/UserRepository.cs | 1 + src/Umbraco.Tests/App.config | 1 - .../UmbracoServiceMembershipProviderTests.cs | 36 +---------------- .../Services/MemberServiceTests.cs | 39 +++++++++---------- .../Testing/TestingTests/MockTests.cs | 11 ++++-- .../PublishedCache/NuCache/PublishedMember.cs | 18 ++++----- .../PublishedCache/PublishedMember.cs | 18 ++++----- 9 files changed, 68 insertions(+), 103 deletions(-) diff --git a/src/Umbraco.Abstractions/Models/PublishedContent/PublishedContentType.cs b/src/Umbraco.Abstractions/Models/PublishedContent/PublishedContentType.cs index b11e991118..cf2afb8706 100644 --- a/src/Umbraco.Abstractions/Models/PublishedContent/PublishedContentType.cs +++ b/src/Umbraco.Abstractions/Models/PublishedContent/PublishedContentType.cs @@ -102,16 +102,15 @@ namespace Umbraco.Core.Models.PublishedContent // TODO: this list somehow also exists in constants, see memberTypeRepository => remove duplicate! private static readonly Dictionary BuiltinMemberProperties = new Dictionary { - { "Email", Constants.DataTypes.Textbox }, - { "Username", Constants.DataTypes.Textbox }, - { "PasswordQuestion", Constants.DataTypes.Textbox }, - { "Comments", Constants.DataTypes.Textbox }, - { "IsApproved", Constants.DataTypes.Boolean }, - { "IsLockedOut", Constants.DataTypes.Boolean }, - { "LastLockoutDate", Constants.DataTypes.DateTime }, - { "CreateDate", Constants.DataTypes.DateTime }, - { "LastLoginDate", Constants.DataTypes.DateTime }, - { "LastPasswordChangeDate", Constants.DataTypes.DateTime }, + { nameof(IMember.Email), Constants.DataTypes.Textbox }, + { nameof(IMember.Username), Constants.DataTypes.Textbox }, + { nameof(IMember.Comments), Constants.DataTypes.Textbox }, + { nameof(IMember.IsApproved), Constants.DataTypes.Boolean }, + { nameof(IMember.IsLockedOut), Constants.DataTypes.Boolean }, + { nameof(IMember.LastLockoutDate), Constants.DataTypes.DateTime }, + { nameof(IMember.CreateDate), Constants.DataTypes.DateTime }, + { nameof(IMember.LastLoginDate), Constants.DataTypes.DateTime }, + { nameof(IMember.LastPasswordChangeDate), Constants.DataTypes.DateTime }, }; #region Content type diff --git a/src/Umbraco.Core/Models/Member.cs b/src/Umbraco.Core/Models/Member.cs index 3d4940c348..39724245ca 100644 --- a/src/Umbraco.Core/Models/Member.cs +++ b/src/Umbraco.Core/Models/Member.cs @@ -177,7 +177,7 @@ namespace Umbraco.Core.Models { get { - var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.Comments, "Comments", default(string)); + var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.Comments, nameof(Comments), default(string)); if (a.Success == false) return a.Result; return Properties[Constants.Conventions.Member.Comments].GetValue() == null @@ -188,7 +188,7 @@ namespace Umbraco.Core.Models { if (WarnIfPropertyTypeNotFoundOnSet( Constants.Conventions.Member.Comments, - "Comments") == false) return; + nameof(Comments)) == false) return; Properties[Constants.Conventions.Member.Comments].SetValue(value); } @@ -206,7 +206,7 @@ namespace Umbraco.Core.Models { get { - var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.IsApproved, "IsApproved", + var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.IsApproved, nameof(IsApproved), //This is the default value if the prop is not found true); if (a.Success == false) return a.Result; @@ -223,7 +223,7 @@ namespace Umbraco.Core.Models { if (WarnIfPropertyTypeNotFoundOnSet( Constants.Conventions.Member.IsApproved, - "IsApproved") == false) return; + nameof(IsApproved)) == false) return; Properties[Constants.Conventions.Member.IsApproved].SetValue(value); } @@ -241,7 +241,7 @@ namespace Umbraco.Core.Models { get { - var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.IsLockedOut, "IsLockedOut", false); + var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.IsLockedOut, nameof(IsLockedOut), false); if (a.Success == false) return a.Result; if (Properties[Constants.Conventions.Member.IsLockedOut].GetValue() == null) return false; var tryConvert = Properties[Constants.Conventions.Member.IsLockedOut].GetValue().TryConvertTo(); @@ -256,7 +256,7 @@ namespace Umbraco.Core.Models { if (WarnIfPropertyTypeNotFoundOnSet( Constants.Conventions.Member.IsLockedOut, - "IsLockedOut") == false) return; + nameof(IsLockedOut)) == false) return; Properties[Constants.Conventions.Member.IsLockedOut].SetValue(value); } @@ -274,7 +274,7 @@ namespace Umbraco.Core.Models { get { - var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.LastLoginDate, "LastLoginDate", default(DateTime)); + var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.LastLoginDate, nameof(LastLoginDate), default(DateTime)); if (a.Success == false) return a.Result; if (Properties[Constants.Conventions.Member.LastLoginDate].GetValue() == null) return default(DateTime); var tryConvert = Properties[Constants.Conventions.Member.LastLoginDate].GetValue().TryConvertTo(); @@ -289,7 +289,7 @@ namespace Umbraco.Core.Models { if (WarnIfPropertyTypeNotFoundOnSet( Constants.Conventions.Member.LastLoginDate, - "LastLoginDate") == false) return; + nameof(LastLoginDate)) == false) return; Properties[Constants.Conventions.Member.LastLoginDate].SetValue(value); } @@ -307,7 +307,7 @@ namespace Umbraco.Core.Models { get { - var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.LastPasswordChangeDate, "LastPasswordChangeDate", default(DateTime)); + var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.LastPasswordChangeDate, nameof(LastPasswordChangeDate), default(DateTime)); if (a.Success == false) return a.Result; if (Properties[Constants.Conventions.Member.LastPasswordChangeDate].GetValue() == null) return default(DateTime); var tryConvert = Properties[Constants.Conventions.Member.LastPasswordChangeDate].GetValue().TryConvertTo(); @@ -322,7 +322,7 @@ namespace Umbraco.Core.Models { if (WarnIfPropertyTypeNotFoundOnSet( Constants.Conventions.Member.LastPasswordChangeDate, - "LastPasswordChangeDate") == false) return; + nameof(LastPasswordChangeDate)) == false) return; Properties[Constants.Conventions.Member.LastPasswordChangeDate].SetValue(value); } @@ -340,7 +340,7 @@ namespace Umbraco.Core.Models { get { - var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.LastLockoutDate, "LastLockoutDate", default(DateTime)); + var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.LastLockoutDate, nameof(LastLockoutDate), default(DateTime)); if (a.Success == false) return a.Result; if (Properties[Constants.Conventions.Member.LastLockoutDate].GetValue() == null) return default(DateTime); var tryConvert = Properties[Constants.Conventions.Member.LastLockoutDate].GetValue().TryConvertTo(); @@ -355,7 +355,7 @@ namespace Umbraco.Core.Models { if (WarnIfPropertyTypeNotFoundOnSet( Constants.Conventions.Member.LastLockoutDate, - "LastLockoutDate") == false) return; + nameof(LastLockoutDate)) == false) return; Properties[Constants.Conventions.Member.LastLockoutDate].SetValue(value); } @@ -374,7 +374,7 @@ namespace Umbraco.Core.Models { get { - var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.FailedPasswordAttempts, "FailedPasswordAttempts", 0); + var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.FailedPasswordAttempts, nameof(FailedPasswordAttempts), 0); if (a.Success == false) return a.Result; if (Properties[Constants.Conventions.Member.FailedPasswordAttempts].GetValue() == null) return default(int); var tryConvert = Properties[Constants.Conventions.Member.FailedPasswordAttempts].GetValue().TryConvertTo(); @@ -389,7 +389,7 @@ namespace Umbraco.Core.Models { if (WarnIfPropertyTypeNotFoundOnSet( Constants.Conventions.Member.FailedPasswordAttempts, - "FailedPasswordAttempts") == false) return; + nameof(FailedPasswordAttempts)) == false) return; Properties[Constants.Conventions.Member.FailedPasswordAttempts].SetValue(value); } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs index ff8beecff5..f8e828a635 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs @@ -492,6 +492,7 @@ ORDER BY colName"; // list the columns to save, NOTE: would be nice to not have hard coded strings here but no real good way around that var colsToSave = new Dictionary { + //TODO: Change these to constants + nameof {"userDisabled", "IsApproved"}, {"userNoConsole", "IsLockedOut"}, {"startStructureID", "StartContentId"}, diff --git a/src/Umbraco.Tests/App.config b/src/Umbraco.Tests/App.config index 9eeb93384d..812e4383de 100644 --- a/src/Umbraco.Tests/App.config +++ b/src/Umbraco.Tests/App.config @@ -43,7 +43,6 @@ - diff --git a/src/Umbraco.Tests/Membership/UmbracoServiceMembershipProviderTests.cs b/src/Umbraco.Tests/Membership/UmbracoServiceMembershipProviderTests.cs index 5a33cfb731..c40c91445e 100644 --- a/src/Umbraco.Tests/Membership/UmbracoServiceMembershipProviderTests.cs +++ b/src/Umbraco.Tests/Membership/UmbracoServiceMembershipProviderTests.cs @@ -73,41 +73,7 @@ namespace Umbraco.Tests.Membership Assert.IsNull(user); } - - [Test] - public void Password_Encrypted() - { - IMember createdMember = null; - var memberType = MockedContentTypes.CreateSimpleMemberType(); - foreach (var p in ConventionsHelper.GetStandardPropertyTypeStubs()) - { - memberType.AddPropertyType(p.Value); - } - var memberTypeServiceMock = new Mock(); - memberTypeServiceMock.Setup(x => x.GetDefault()).Returns("Member"); - var membershipServiceMock = new Mock(); - membershipServiceMock.Setup(service => service.Exists("test")).Returns(false); - membershipServiceMock.Setup(service => service.GetByEmail("test@test.com")).Returns(() => null); - membershipServiceMock.Setup( - service => service.CreateWithIdentity(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - .Callback((string u, string e, string p, string m, bool isApproved) => - { - createdMember = new Member("test", e, u, p, memberType, isApproved); - }) - .Returns(() => createdMember); - - var provider = new MembersMembershipProvider(membershipServiceMock.Object, memberTypeServiceMock.Object, TestHelper.GetUmbracoVersion()); - provider.Initialize("test", new NameValueCollection { { "passwordFormat", "Encrypted" } }); - - - MembershipCreateStatus status; - provider.CreateUser("test", "test", "testtest$1", "test@test.com", "test", "test", true, "test", out status); - - Assert.AreNotEqual("test", createdMember.RawPasswordValue); - var decrypted = provider.DecryptPassword(createdMember.RawPasswordValue); - Assert.AreEqual("testtest$1", decrypted); - } - + [Test] public void Password_Hashed_With_Salt() { diff --git a/src/Umbraco.Tests/Services/MemberServiceTests.cs b/src/Umbraco.Tests/Services/MemberServiceTests.cs index a0799c6856..e567fb4e5e 100644 --- a/src/Umbraco.Tests/Services/MemberServiceTests.cs +++ b/src/Umbraco.Tests/Services/MemberServiceTests.cs @@ -65,33 +65,30 @@ namespace Umbraco.Tests.Services // TODO: see TODO in PublishedContentType, this list contains duplicates var aliases = new[] - { - "umbracoMemberPasswordRetrievalQuestion", - "umbracoMemberPasswordRetrievalAnswer", - "umbracoMemberComments", - "umbracoMemberFailedPasswordAttempts", - "umbracoMemberApproved", - "umbracoMemberLockedOut", - "umbracoMemberLastLockoutDate", - "umbracoMemberLastLogin", - "umbracoMemberLastPasswordChangeDate", - "Email", - "Username", - "PasswordQuestion", - "Comments", - "IsApproved", - "IsLockedOut", - "LastLockoutDate", - "CreateDate", - "LastLoginDate", - "LastPasswordChangeDate" + { + Constants.Conventions.Member.Comments, + Constants.Conventions.Member.FailedPasswordAttempts, + Constants.Conventions.Member.IsApproved, + Constants.Conventions.Member.IsLockedOut, + Constants.Conventions.Member.LastLockoutDate, + Constants.Conventions.Member.LastLoginDate, + Constants.Conventions.Member.LastPasswordChangeDate, + nameof(IMember.Email), + nameof(IMember.Username), + nameof(IMember.Comments), + nameof(IMember.IsApproved), + nameof(IMember.IsLockedOut), + nameof(IMember.LastLockoutDate), + nameof(IMember.CreateDate), + nameof(IMember.LastLoginDate), + nameof(IMember.LastPasswordChangeDate) }; var properties = pmember.Properties.ToList(); Assert.IsTrue(properties.Select(x => x.Alias).ContainsAll(aliases)); - var email = properties[aliases.IndexOf("Email")]; + var email = properties[aliases.IndexOf(nameof(IMember.Email))]; Assert.AreEqual("xemail", email.GetSourceValue()); } diff --git a/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs b/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs index 1c007d1e49..1bcbc1e420 100644 --- a/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs +++ b/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs @@ -99,12 +99,15 @@ namespace Umbraco.Tests.Testing.TestingTests { var umbracoContext = TestObjects.GetUmbracoContextMock(); - var membershipHelper = new MembershipHelper(umbracoContext.HttpContext, Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), AppCaches.Disabled, Mock.Of()); + var logger = Mock.Of(); + var memberService = Mock.Of(); + var memberTypeService = Mock.Of(); + var membershipProvider = new MembersMembershipProvider(memberService, memberTypeService, Mock.Of()); + var membershipHelper = new MembershipHelper(umbracoContext.HttpContext, Mock.Of(), membershipProvider, Mock.Of(), memberService, memberTypeService, Mock.Of(), Mock.Of(), AppCaches.Disabled, logger); var umbracoHelper = new UmbracoHelper(Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), membershipHelper); var umbracoMapper = new UmbracoMapper(new MapDefinitionCollection(new[] { Mock.Of() })); - - // ReSharper disable once UnusedVariable - var umbracoApiController = new FakeUmbracoApiController(Mock.Of(), Mock.Of(), Mock.Of(), ServiceContext.CreatePartial(), AppCaches.NoCache, Mock.Of(), Mock.Of(), umbracoHelper, umbracoMapper); + + var umbracoApiController = new FakeUmbracoApiController(Mock.Of(), Mock.Of(), Mock.Of(), ServiceContext.CreatePartial(), AppCaches.NoCache, logger, Mock.Of(), umbracoHelper, umbracoMapper); Assert.Pass(); } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedMember.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedMember.cs index 9e47802c08..118831d305 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedMember.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedMember.cs @@ -74,15 +74,15 @@ namespace Umbraco.Web.PublishedCache.NuCache .ToDictionary(x => x.Alias, x => new[] { new PropertyData { Value = x.GetValue(), Culture = string.Empty, Segment = string.Empty } }, StringComparer.OrdinalIgnoreCase); // see also PublishedContentType - AddIf(contentType, properties, "Email", member.Email); - AddIf(contentType, properties, "Username", member.Username); - AddIf(contentType, properties, "Comments", member.Comments); - AddIf(contentType, properties, "IsApproved", member.IsApproved); - AddIf(contentType, properties, "IsLockedOut", member.IsLockedOut); - AddIf(contentType, properties, "LastLockoutDate", member.LastLockoutDate); - AddIf(contentType, properties, "CreateDate", member.CreateDate); - AddIf(contentType, properties, "LastLoginDate", member.LastLoginDate); - AddIf(contentType, properties, "LastPasswordChangeDate", member.LastPasswordChangeDate); + AddIf(contentType, properties, nameof(IMember.Email), member.Email); + AddIf(contentType, properties, nameof(IMember.Username), member.Username); + AddIf(contentType, properties, nameof(IMember.Comments), member.Comments); + AddIf(contentType, properties, nameof(IMember.IsApproved), member.IsApproved); + AddIf(contentType, properties, nameof(IMember.IsLockedOut), member.IsLockedOut); + AddIf(contentType, properties, nameof(IMember.LastLockoutDate), member.LastLockoutDate); + AddIf(contentType, properties, nameof(IMember.CreateDate), member.CreateDate); + AddIf(contentType, properties, nameof(IMember.LastLoginDate), member.LastLoginDate); + AddIf(contentType, properties, nameof(IMember.LastPasswordChangeDate), member.LastPasswordChangeDate); return properties; } diff --git a/src/Umbraco.Web/PublishedCache/PublishedMember.cs b/src/Umbraco.Web/PublishedCache/PublishedMember.cs index b26e9b90a5..2c3ba1d7ea 100644 --- a/src/Umbraco.Web/PublishedCache/PublishedMember.cs +++ b/src/Umbraco.Web/PublishedCache/PublishedMember.cs @@ -94,15 +94,15 @@ namespace Umbraco.Web.PublishedCache { var aliases = properties.Select(x => x.Alias).ToList(); - EnsureMemberProperty(properties, aliases, "Email", Email); - EnsureMemberProperty(properties, aliases, "UserName", UserName); - EnsureMemberProperty(properties, aliases, "Comments", Comments); - EnsureMemberProperty(properties, aliases, "IsApproved", IsApproved); - EnsureMemberProperty(properties, aliases, "IsLockedOut", IsLockedOut); - EnsureMemberProperty(properties, aliases, "LastLockoutDate", LastLockoutDate); - EnsureMemberProperty(properties, aliases, "CreateDate", CreateDate); - EnsureMemberProperty(properties, aliases, "LastLoginDate", LastLoginDate); - EnsureMemberProperty(properties, aliases, "LastPasswordChangeDate", LastPasswordChangeDate); + EnsureMemberProperty(properties, aliases, nameof(IMember.Email), Email); + EnsureMemberProperty(properties, aliases, nameof(IMember.Username), UserName); + EnsureMemberProperty(properties, aliases, nameof(IMember.Comments), Comments); + EnsureMemberProperty(properties, aliases, nameof(IMember.IsApproved), IsApproved); + EnsureMemberProperty(properties, aliases, nameof(IMember.IsLockedOut), IsLockedOut); + EnsureMemberProperty(properties, aliases, nameof(IMember.LastLockoutDate), LastLockoutDate); + EnsureMemberProperty(properties, aliases, nameof(IMember.CreateDate), CreateDate); + EnsureMemberProperty(properties, aliases, nameof(IMember.LastLoginDate), LastLoginDate); + EnsureMemberProperty(properties, aliases, nameof(IMember.LastPasswordChangeDate), LastPasswordChangeDate); } private void EnsureMemberProperty(List properties, List aliases, string alias, object value) From 3b67731cbc55d242143c0215aaa2514e2009defa Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 26 Nov 2019 17:19:53 +1100 Subject: [PATCH 17/18] fixes new installation password reset --- src/Umbraco.Web/Install/InstallSteps/NewInstallStep.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web/Install/InstallSteps/NewInstallStep.cs b/src/Umbraco.Web/Install/InstallSteps/NewInstallStep.cs index 7f4ff496f1..98f958c844 100644 --- a/src/Umbraco.Web/Install/InstallSteps/NewInstallStep.cs +++ b/src/Umbraco.Web/Install/InstallSteps/NewInstallStep.cs @@ -61,10 +61,12 @@ namespace Umbraco.Web.Install.InstallSteps throw new InvalidOperationException($"No user found in membership provider with id of {Constants.Security.SuperUserId}."); } - var success = await _userManager.ChangePasswordAsync(membershipUser.Id, "default", user.Password.Trim()); - if (success.Succeeded == false) + //To change the password here we actually need to reset it since we don't have an old one to use to change + var resetToken = await _userManager.GeneratePasswordResetTokenAsync(membershipUser.Id); + var resetResult = await _userManager.ChangePasswordWithResetAsync(membershipUser.Id, resetToken, user.Password.Trim()); + if (!resetResult.Succeeded) { - throw new InvalidOperationException("Invalid password: " + string.Join(", ", success.Errors)); + throw new InvalidOperationException("Could not reset password: " + string.Join(", ", resetResult.Errors)); } admin.Email = user.Email.Trim(); From fd22afd3876ef44b92ebd69bce4a357831a612f7 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 26 Nov 2019 09:38:24 +0100 Subject: [PATCH 18/18] AB3917 - Removes unused variable.. --- src/Umbraco.Web/Editors/MemberController.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Web/Editors/MemberController.cs b/src/Umbraco.Web/Editors/MemberController.cs index ad8408e2eb..67234b3de2 100644 --- a/src/Umbraco.Web/Editors/MemberController.cs +++ b/src/Umbraco.Web/Editors/MemberController.cs @@ -612,9 +612,7 @@ namespace Umbraco.Web.Editors [HttpPost] public HttpResponseMessage DeleteByKey(Guid key) { - IMember foundMember; - MembershipUser foundMembershipUser; - foundMember = Services.MemberService.GetByKey(key); + var foundMember = Services.MemberService.GetByKey(key); if (foundMember == null) { return HandleContentNotFound(key, false);