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); } ///