Merge remote-tracking branch 'origin/netcore/dev' into netcore/feature/core-cannot-use-system-web
# Conflicts: # src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs # src/Umbraco.Tests/Testing/TestingTests/MockTests.cs # src/Umbraco.Web/Security/AppBuilderExtensions.cs # src/Umbraco.Web/Security/BackOfficeUserManager.cs # src/Umbraco.Web/UmbracoDefaultOwinStartup.cs
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
namespace Umbraco.Core.Configuration
|
||||
{
|
||||
/// <summary>
|
||||
/// The password configuration for members
|
||||
/// </summary>
|
||||
public interface IMemberPasswordConfiguration : IPasswordConfiguration
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,5 @@
|
||||
/// </summary>
|
||||
public interface IUserPasswordConfiguration : IPasswordConfiguration
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
using Umbraco.Core.Configuration;
|
||||
|
||||
namespace Umbraco.Core.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates a password
|
||||
/// </summary>
|
||||
public interface IPasswordGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates a password
|
||||
/// </summary>
|
||||
/// <param name="passwordConfiguration"></param>
|
||||
/// <returns></returns>
|
||||
string GeneratePassword(IPasswordConfiguration passwordConfiguration);
|
||||
}
|
||||
}
|
||||
@@ -11,28 +11,34 @@ namespace Umbraco.Core.Security
|
||||
/// <remarks>
|
||||
/// This uses logic copied from the old MembershipProvider.GeneratePassword logic
|
||||
/// </remarks>
|
||||
public class PasswordGenerator : IPasswordGenerator
|
||||
public class PasswordGenerator
|
||||
{
|
||||
public string GeneratePassword(IPasswordConfiguration passwordConfiguration)
|
||||
private readonly IPasswordConfiguration _passwordConfiguration;
|
||||
|
||||
public PasswordGenerator(IPasswordConfiguration passwordConfiguration)
|
||||
{
|
||||
_passwordConfiguration = passwordConfiguration;
|
||||
}
|
||||
public string GeneratePassword()
|
||||
{
|
||||
var password = PasswordStore.GeneratePassword(
|
||||
passwordConfiguration.RequiredLength,
|
||||
passwordConfiguration.RequireNonLetterOrDigit ? 2 : 0);
|
||||
_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)))
|
||||
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)))
|
||||
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)))
|
||||
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)))
|
||||
if (_passwordConfiguration.RequireNonLetterOrDigit && passwordChars.ContainsAny(Enumerable.Range(33, 48).Select(x => (char)x)))
|
||||
password += Convert.ToChar(random.Next(33, 48)); // symbols !"#$%&'()*+,-./
|
||||
|
||||
return password;
|
||||
|
||||
@@ -25,7 +25,8 @@ namespace Umbraco.Core.Configuration
|
||||
configs.Add<IUmbracoSettingsSection>("umbracoConfiguration/settings");
|
||||
configs.Add<IHealthChecks>("umbracoConfiguration/HealthChecks");
|
||||
|
||||
configs.Add<IUserPasswordConfiguration>(() => new DefaultUserPasswordConfig());
|
||||
configs.Add<IUserPasswordConfiguration>(() => new DefaultPasswordConfig());
|
||||
configs.Add<IMemberPasswordConfiguration>(() => new DefaultPasswordConfig());
|
||||
configs.Add<ICoreDebug>(() => new CoreDebug());
|
||||
configs.Add<IConnectionStrings>(() => new ConnectionStrings());
|
||||
configs.AddCoreConfigs(_ioHelper);
|
||||
@@ -34,9 +35,10 @@ namespace Umbraco.Core.Configuration
|
||||
}
|
||||
|
||||
// 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
|
||||
// TODO: Make this configurable somewhere - we've removed membership providers for users, 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.
|
||||
// TODO: Currently it doesn't actually seem possible to replace any sub-configuration unless totally replacing the IConfigsFactory??
|
||||
internal class DefaultPasswordConfig : IUserPasswordConfiguration, IMemberPasswordConfiguration
|
||||
{
|
||||
public int RequiredLength => 12;
|
||||
public bool RequireNonLetterOrDigit => false;
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Umbraco.Core.Configuration
|
||||
get
|
||||
{
|
||||
var settings = ConfigurationManager.ConnectionStrings[key];
|
||||
|
||||
if (settings == null) return null;
|
||||
return new ConfigConnectionString(settings.ConnectionString, settings.ProviderName, settings.Name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,9 +129,6 @@ namespace Umbraco.Core.Composing
|
||||
public static IRuntimeState RuntimeState
|
||||
=> Factory.GetInstance<IRuntimeState>();
|
||||
|
||||
public static IPasswordGenerator PasswordGenerator
|
||||
=> Factory.GetInstance<IPasswordGenerator>();
|
||||
|
||||
public static TypeLoader TypeLoader
|
||||
=> Factory.GetInstance<TypeLoader>();
|
||||
|
||||
|
||||
@@ -136,7 +136,6 @@ namespace Umbraco.Core.Runtime
|
||||
|
||||
composition.SetCultureDictionaryFactory<DefaultCultureDictionaryFactory>();
|
||||
composition.Register(f => f.GetInstance<ICultureDictionaryFactory>().CreateDictionary(), Lifetime.Singleton);
|
||||
composition.RegisterUnique<IPasswordGenerator, PasswordGenerator>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
using System.Collections.Specialized;
|
||||
|
||||
namespace Umbraco.Core.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// An interface for exposing the content type properties for storing membership data in when
|
||||
/// a membership provider's data is backed by an Umbraco content type.
|
||||
/// </summary>
|
||||
public interface IUmbracoMemberTypeMembershipProvider
|
||||
{
|
||||
|
||||
string LockPropertyTypeAlias { get; }
|
||||
string LastLockedOutPropertyTypeAlias { get; }
|
||||
string FailedPasswordAttemptsPropertyTypeAlias { get; }
|
||||
string ApprovedPropertyTypeAlias { get; }
|
||||
string CommentPropertyTypeAlias { get; }
|
||||
string LastLoginPropertyTypeAlias { get; }
|
||||
string LastPasswordChangedPropertyTypeAlias { get; }
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,54 +1,96 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Umbraco.Core.Configuration;
|
||||
|
||||
namespace Umbraco.Core.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles password hashing and formatting
|
||||
/// </summary>
|
||||
public class PasswordSecurity
|
||||
{
|
||||
private readonly IPasswordConfiguration _passwordConfiguration;
|
||||
public IPasswordConfiguration PasswordConfiguration { get; }
|
||||
public PasswordGenerator _generator;
|
||||
public ConfiguredPasswordValidator _validator;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="passwordConfiguration"></param>
|
||||
public PasswordSecurity(IPasswordConfiguration passwordConfiguration)
|
||||
{
|
||||
_passwordConfiguration = passwordConfiguration;
|
||||
PasswordConfiguration = passwordConfiguration;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the password passes validation rules
|
||||
/// </summary>
|
||||
/// <param name="password"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<Attempt<IEnumerable<string>>> IsValidPasswordAsync(string password)
|
||||
{
|
||||
if (_validator == null)
|
||||
_validator = new ConfiguredPasswordValidator(PasswordConfiguration);
|
||||
var result = await _validator.ValidateAsync(password);
|
||||
if (result.Succeeded)
|
||||
return Attempt<IEnumerable<string>>.Succeed();
|
||||
|
||||
return Attempt<IEnumerable<string>>.Fail(result.Errors);
|
||||
}
|
||||
|
||||
public string GeneratePassword()
|
||||
{
|
||||
if (_generator == null)
|
||||
_generator = new PasswordGenerator(PasswordConfiguration);
|
||||
return _generator.GeneratePassword();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a hashed password value used to store in a data store
|
||||
/// </summary>
|
||||
/// <param name="password"></param>
|
||||
/// <returns></returns>
|
||||
public string HashPasswordForStorage(string password)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(password))
|
||||
throw new ArgumentException("password cannot be empty", nameof(password));
|
||||
|
||||
string salt;
|
||||
var hashed = HashNewPassword(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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="hashedPassword"></param>
|
||||
/// <param name="salt"></param>
|
||||
/// <returns></returns>
|
||||
public string FormatPasswordForStorage(string hashedPassword, string salt)
|
||||
{
|
||||
if (PasswordConfiguration.UseLegacyEncoding)
|
||||
{
|
||||
return hashedPassword;
|
||||
}
|
||||
|
||||
return salt + hashedPassword;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hashes a password with a given salt
|
||||
/// </summary>
|
||||
/// <param name="pass"></param>
|
||||
/// <param name="salt"></param>
|
||||
/// <returns></returns>
|
||||
public string FormatPasswordForStorage(string pass, string salt)
|
||||
{
|
||||
if (_passwordConfiguration.UseLegacyEncoding)
|
||||
{
|
||||
return pass;
|
||||
}
|
||||
|
||||
return salt + pass;
|
||||
}
|
||||
|
||||
public string HashPassword(string pass, string salt)
|
||||
{
|
||||
//if we are doing it the old way
|
||||
|
||||
if (_passwordConfiguration.UseLegacyEncoding)
|
||||
if (PasswordConfiguration.UseLegacyEncoding)
|
||||
{
|
||||
return LegacyEncodePassword(pass);
|
||||
}
|
||||
@@ -103,21 +145,25 @@ namespace Umbraco.Core.Security
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks the password.
|
||||
/// Verifies if the password matches the expected hash+salt of the stored password string
|
||||
/// </summary>
|
||||
/// <param name="password">The password.</param>
|
||||
/// <param name="dbPassword">The dbPassword.</param>
|
||||
/// <param name="dbPassword">The value of the password stored in a data store.</param>
|
||||
/// <returns></returns>
|
||||
public bool CheckPassword(string password, string dbPassword)
|
||||
public bool VerifyPassword(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);
|
||||
|
||||
if (dbPassword.StartsWith(Constants.Security.EmptyPasswordPrefix))
|
||||
return false;
|
||||
|
||||
var storedHashedPass = ParseStoredHashPassword(dbPassword, out var salt);
|
||||
var hashed = HashPassword(password, salt);
|
||||
return storedHashedPass == hashed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// hash a new password with a new salt
|
||||
/// Create a new password hash and a new salt
|
||||
/// </summary>
|
||||
/// <param name="newPassword"></param>
|
||||
/// <param name="salt"></param>
|
||||
@@ -129,15 +175,15 @@ namespace Umbraco.Core.Security
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the hashed password without the salt if it is hashed
|
||||
/// Parses out the hashed password and the salt from the stored password string value
|
||||
/// </summary>
|
||||
/// <param name="storedString"></param>
|
||||
/// <param name="salt">returns the salt</param>
|
||||
/// <returns></returns>
|
||||
public string StoredPassword(string storedString, out string salt)
|
||||
public string ParseStoredHashPassword(string storedString, out string salt)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(storedString)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(storedString));
|
||||
if (_passwordConfiguration.UseLegacyEncoding)
|
||||
if (PasswordConfiguration.UseLegacyEncoding)
|
||||
{
|
||||
salt = string.Empty;
|
||||
return storedString;
|
||||
@@ -155,9 +201,14 @@ namespace Umbraco.Core.Security
|
||||
return Convert.ToBase64String(numArray);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the hash algorithm to use
|
||||
/// </summary>
|
||||
/// <param name="password"></param>
|
||||
/// <returns></returns>
|
||||
public HashAlgorithm GetHashAlgorithm(string password)
|
||||
{
|
||||
if (_passwordConfiguration.UseLegacyEncoding)
|
||||
if (PasswordConfiguration.UseLegacyEncoding)
|
||||
{
|
||||
return new HMACSHA1
|
||||
{
|
||||
@@ -166,12 +217,12 @@ namespace Umbraco.Core.Security
|
||||
};
|
||||
}
|
||||
|
||||
if (_passwordConfiguration.HashAlgorithmType.IsNullOrWhiteSpace())
|
||||
if (PasswordConfiguration.HashAlgorithmType.IsNullOrWhiteSpace())
|
||||
throw new InvalidOperationException("No hash algorithm type specified");
|
||||
|
||||
var alg = HashAlgorithm.Create(_passwordConfiguration.HashAlgorithmType);
|
||||
var alg = HashAlgorithm.Create(PasswordConfiguration.HashAlgorithmType);
|
||||
if (alg == null)
|
||||
throw new InvalidOperationException($"The hash algorithm specified {_passwordConfiguration.HashAlgorithmType} cannot be resolved");
|
||||
throw new InvalidOperationException($"The hash algorithm specified {PasswordConfiguration.HashAlgorithmType} cannot be resolved");
|
||||
|
||||
return alg;
|
||||
}
|
||||
@@ -183,9 +234,8 @@ namespace Umbraco.Core.Security
|
||||
/// <returns>The encoded password.</returns>
|
||||
private string LegacyEncodePassword(string password)
|
||||
{
|
||||
string encodedPassword = password;
|
||||
var hashAlgorith = GetHashAlgorithm(password);
|
||||
encodedPassword = Convert.ToBase64String(hashAlgorith.ComputeHash(Encoding.Unicode.GetBytes(password)));
|
||||
var encodedPassword = Convert.ToBase64String(hashAlgorith.ComputeHash(Encoding.Unicode.GetBytes(password)));
|
||||
return encodedPassword;
|
||||
}
|
||||
|
||||
|
||||
@@ -1120,77 +1120,6 @@ namespace Umbraco.Core.Services.Implement
|
||||
#region Membership
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A helper method that will create a basic/generic member for use with a generic membership provider
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
internal static IMember CreateGenericMembershipProviderMember(string name, string email, string username, string password)
|
||||
{
|
||||
var identity = int.MaxValue;
|
||||
|
||||
var memType = new MemberType(-1);
|
||||
var propGroup = new PropertyGroup(MemberType.SupportsPublishingConst)
|
||||
{
|
||||
Name = "Membership",
|
||||
Id = --identity
|
||||
};
|
||||
propGroup.PropertyTypes.Add(new PropertyType(Constants.PropertyEditors.Aliases.TextBox, ValueStorageType.Ntext, Constants.Conventions.Member.Comments)
|
||||
{
|
||||
Name = Constants.Conventions.Member.CommentsLabel,
|
||||
SortOrder = 0,
|
||||
Id = --identity,
|
||||
Key = identity.ToGuid()
|
||||
});
|
||||
propGroup.PropertyTypes.Add(new PropertyType(Constants.PropertyEditors.Aliases.Boolean, ValueStorageType.Integer, Constants.Conventions.Member.IsApproved)
|
||||
{
|
||||
Name = Constants.Conventions.Member.IsApprovedLabel,
|
||||
SortOrder = 3,
|
||||
Id = --identity,
|
||||
Key = identity.ToGuid()
|
||||
});
|
||||
propGroup.PropertyTypes.Add(new PropertyType(Constants.PropertyEditors.Aliases.Boolean, ValueStorageType.Integer, Constants.Conventions.Member.IsLockedOut)
|
||||
{
|
||||
Name = Constants.Conventions.Member.IsLockedOutLabel,
|
||||
SortOrder = 4,
|
||||
Id = --identity,
|
||||
Key = identity.ToGuid()
|
||||
});
|
||||
propGroup.PropertyTypes.Add(new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Date, Constants.Conventions.Member.LastLockoutDate)
|
||||
{
|
||||
Name = Constants.Conventions.Member.LastLockoutDateLabel,
|
||||
SortOrder = 5,
|
||||
Id = --identity,
|
||||
Key = identity.ToGuid()
|
||||
});
|
||||
propGroup.PropertyTypes.Add(new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Date, Constants.Conventions.Member.LastLoginDate)
|
||||
{
|
||||
Name = Constants.Conventions.Member.LastLoginDateLabel,
|
||||
SortOrder = 6,
|
||||
Id = --identity,
|
||||
Key = identity.ToGuid()
|
||||
});
|
||||
propGroup.PropertyTypes.Add(new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Date, Constants.Conventions.Member.LastPasswordChangeDate)
|
||||
{
|
||||
Name = Constants.Conventions.Member.LastPasswordChangeDateLabel,
|
||||
SortOrder = 7,
|
||||
Id = --identity,
|
||||
Key = identity.ToGuid()
|
||||
});
|
||||
|
||||
memType.PropertyGroups.Add(propGroup);
|
||||
|
||||
// should we "create member"?
|
||||
var member = new Member(name, email, username, password, memType);
|
||||
|
||||
//we've assigned ids to the property types and groups but we also need to assign fake ids to the properties themselves.
|
||||
foreach (var property in member.Properties)
|
||||
{
|
||||
property.Id = --identity;
|
||||
}
|
||||
|
||||
return member;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exports a member.
|
||||
/// </summary>
|
||||
|
||||
@@ -735,7 +735,6 @@
|
||||
<Compile Include="Security\BackOfficeUserValidator.cs" />
|
||||
<Compile Include="Security\ContentPermissionsHelper.cs" />
|
||||
<Compile Include="Security\EmailService.cs" />
|
||||
<Compile Include="Security\IUmbracoMemberTypeMembershipProvider.cs" />
|
||||
<Compile Include="Security\IUserAwarePasswordHasher.cs" />
|
||||
<Compile Include="Security\IUserSessionStore.cs" />
|
||||
<Compile Include="Security\MachineKeyGenerator.cs" />
|
||||
|
||||
@@ -17,28 +17,6 @@ namespace Umbraco.Tests.Membership
|
||||
[UmbracoTest(WithApplication = true)]
|
||||
public class MembershipProviderBaseTests : UmbracoTestBase
|
||||
{
|
||||
[Test]
|
||||
public void Change_Password_Without_AllowManuallyChangingPassword_And_No_Pass_Validation()
|
||||
{
|
||||
var providerMock = new Mock<MembershipProviderBase>(TestHelper.GetHostingEnvironment()) { CallBase = true };
|
||||
providerMock.Setup(@base => @base.AllowManuallyChangingPassword).Returns(false);
|
||||
var provider = providerMock.Object;
|
||||
|
||||
Assert.Throws<NotSupportedException>(() => provider.ChangePassword("test", "", "test"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Change_Password_With_AllowManuallyChangingPassword_And_Invalid_Creds()
|
||||
{
|
||||
var providerMock = new Mock<MembershipProviderBase>(TestHelper.GetHostingEnvironment()) { CallBase = true };
|
||||
providerMock.Setup(@base => @base.AllowManuallyChangingPassword).Returns(false);
|
||||
providerMock.Setup(@base => @base.ValidateUser("test", "test")).Returns(false);
|
||||
var provider = providerMock.Object;
|
||||
|
||||
Assert.IsFalse(provider.ChangePassword("test", "test", "test"));
|
||||
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ChangePasswordQuestionAndAnswer_Without_RequiresQuestionAndAnswer()
|
||||
{
|
||||
@@ -49,18 +27,6 @@ namespace Umbraco.Tests.Membership
|
||||
Assert.Throws<NotSupportedException>(() => provider.ChangePasswordQuestionAndAnswer("test", "test", "test", "test"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ChangePasswordQuestionAndAnswer_Without_AllowManuallyChangingPassword_And_Invalid_Creds()
|
||||
{
|
||||
var providerMock = new Mock<MembershipProviderBase>(TestHelper.GetHostingEnvironment()) { CallBase = true };
|
||||
providerMock.Setup(@base => @base.RequiresQuestionAndAnswer).Returns(true);
|
||||
providerMock.Setup(@base => @base.AllowManuallyChangingPassword).Returns(false);
|
||||
providerMock.Setup(@base => @base.ValidateUser("test", "test")).Returns(false);
|
||||
var provider = providerMock.Object;
|
||||
|
||||
Assert.IsFalse(provider.ChangePasswordQuestionAndAnswer("test", "test", "test", "test"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CreateUser_Not_Whitespace()
|
||||
{
|
||||
|
||||
@@ -106,7 +106,7 @@ namespace Umbraco.Tests.Membership
|
||||
Assert.AreNotEqual("test", createdMember.RawPasswordValue);
|
||||
|
||||
string salt;
|
||||
var storedPassword = provider.PasswordSecurity.StoredPassword(createdMember.RawPasswordValue, out salt);
|
||||
var storedPassword = provider.PasswordSecurity.ParseStoredHashPassword(createdMember.RawPasswordValue, out salt);
|
||||
var hashedPassword = provider.PasswordSecurity.HashPassword("testtest$1", salt);
|
||||
Assert.AreEqual(hashedPassword, storedPassword);
|
||||
}
|
||||
|
||||
@@ -148,7 +148,7 @@ namespace Umbraco.Tests.Routing
|
||||
var handler = new RenderRouteHandler(umbracoContext, new TestControllerFactory(umbracoContextAccessor, Mock.Of<ILogger>(), context =>
|
||||
{
|
||||
var membershipHelper = new MembershipHelper(
|
||||
umbracoContext.HttpContext, Mock.Of<IPublishedMemberCache>(), Mock.Of<MembersMembershipProvider>(), Mock.Of<RoleProvider>(), Mock.Of<IMemberService>(), Mock.Of<IMemberTypeService>(), Mock.Of<IUserService>(), Mock.Of<IPublicAccessService>(), AppCaches.Disabled, Mock.Of<ILogger>());
|
||||
umbracoContext.HttpContext, Mock.Of<IPublishedMemberCache>(), Mock.Of<MembersMembershipProvider>(), Mock.Of<RoleProvider>(), Mock.Of<IMemberService>(), Mock.Of<IMemberTypeService>(), Mock.Of<IPublicAccessService>(), AppCaches.Disabled, Mock.Of<ILogger>());
|
||||
return new CustomDocumentController(Factory.GetInstance<IGlobalSettings>(),
|
||||
umbracoContextAccessor,
|
||||
Factory.GetInstance<ServiceContext>(),
|
||||
|
||||
@@ -40,7 +40,7 @@ namespace Umbraco.Tests.Security
|
||||
var hashed = passwordSecurity.HashNewPassword(pass, out salt);
|
||||
var storedPassword = passwordSecurity.FormatPasswordForStorage(hashed, salt);
|
||||
|
||||
var result = passwordSecurity.CheckPassword("ThisIsAHashedPassword", storedPassword);
|
||||
var result = passwordSecurity.VerifyPassword("ThisIsAHashedPassword", storedPassword);
|
||||
|
||||
Assert.IsTrue(result);
|
||||
}
|
||||
@@ -55,7 +55,7 @@ namespace Umbraco.Tests.Security
|
||||
var hashed = passwordSecurity.HashNewPassword(pass, out salt);
|
||||
var storedPassword = passwordSecurity.FormatPasswordForStorage(hashed, salt);
|
||||
|
||||
var result = passwordSecurity.CheckPassword("ThisIsAHashedPassword", storedPassword);
|
||||
var result = passwordSecurity.VerifyPassword("ThisIsAHashedPassword", storedPassword);
|
||||
|
||||
Assert.IsTrue(result);
|
||||
}
|
||||
@@ -82,7 +82,7 @@ namespace Umbraco.Tests.Security
|
||||
var stored = salt + "ThisIsAHashedPassword";
|
||||
|
||||
string initSalt;
|
||||
var result = passwordSecurity.StoredPassword(stored, out initSalt);
|
||||
var result = passwordSecurity.ParseStoredHashPassword(stored, out initSalt);
|
||||
|
||||
Assert.AreEqual("ThisIsAHashedPassword", result);
|
||||
}
|
||||
|
||||
@@ -151,7 +151,7 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting
|
||||
urlHelper.Setup(provider => provider.GetUrl(It.IsAny<UmbracoContext>(), It.IsAny<IPublishedContent>(), It.IsAny<UrlMode>(), It.IsAny<string>(), It.IsAny<Uri>()))
|
||||
.Returns(UrlInfo.Url("/hello/world/1234"));
|
||||
|
||||
var membershipHelper = new MembershipHelper(umbCtx.HttpContext, Mock.Of<IPublishedMemberCache>(), Mock.Of<MembersMembershipProvider>(), Mock.Of<RoleProvider>(), Mock.Of<IMemberService>(), Mock.Of<IMemberTypeService>(), Mock.Of<IUserService>(), Mock.Of<IPublicAccessService>(), AppCaches.Disabled, Mock.Of<ILogger>());
|
||||
var membershipHelper = new MembershipHelper(umbCtx.HttpContext, Mock.Of<IPublishedMemberCache>(), Mock.Of<MembersMembershipProvider>(), Mock.Of<RoleProvider>(), Mock.Of<IMemberService>(), Mock.Of<IMemberTypeService>(), Mock.Of<IPublicAccessService>(), AppCaches.Disabled, Mock.Of<ILogger>());
|
||||
|
||||
var umbHelper = new UmbracoHelper(Mock.Of<IPublishedContent>(),
|
||||
Mock.Of<ITagQuery>(),
|
||||
|
||||
@@ -71,7 +71,7 @@ namespace Umbraco.Tests.Testing.TestingTests
|
||||
Mock.Of<ICultureDictionaryFactory>(),
|
||||
Mock.Of<IUmbracoComponentRenderer>(),
|
||||
Mock.Of<IPublishedContentQuery>(),
|
||||
new MembershipHelper(umbracoContext.HttpContext, Mock.Of<IPublishedMemberCache>(), Mock.Of<MembersMembershipProvider>(), Mock.Of<RoleProvider>(), Mock.Of<IMemberService>(), Mock.Of<IMemberTypeService>(), Mock.Of<IUserService>(), Mock.Of<IPublicAccessService>(), AppCaches.Disabled, Mock.Of<ILogger>()));
|
||||
new MembershipHelper(umbracoContext.HttpContext, Mock.Of<IPublishedMemberCache>(), Mock.Of<MembersMembershipProvider>(), Mock.Of<RoleProvider>(), Mock.Of<IMemberService>(), Mock.Of<IMemberTypeService>(), Mock.Of<IPublicAccessService>(), AppCaches.Disabled, Mock.Of<ILogger>()));
|
||||
Assert.Pass();
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ namespace Umbraco.Tests.Testing.TestingTests
|
||||
var memberService = Mock.Of<IMemberService>();
|
||||
var memberTypeService = Mock.Of<IMemberTypeService>();
|
||||
var membershipProvider = new MembersMembershipProvider(memberService, memberTypeService, Mock.Of<IUmbracoVersion>(), TestHelper.GetHostingEnvironment(), TestHelper.GetIpResolver());
|
||||
var membershipHelper = new MembershipHelper(umbracoContext.HttpContext, Mock.Of<IPublishedMemberCache>(), membershipProvider, Mock.Of<RoleProvider>(), memberService, memberTypeService, Mock.Of<IUserService>(), Mock.Of<IPublicAccessService>(), AppCaches.Disabled, logger);
|
||||
var membershipHelper = new MembershipHelper(umbracoContext.HttpContext, Mock.Of<IPublishedMemberCache>(), membershipProvider, Mock.Of<RoleProvider>(), memberService, memberTypeService, Mock.Of<IPublicAccessService>(), AppCaches.Disabled, logger);
|
||||
var umbracoHelper = new UmbracoHelper(Mock.Of<IPublishedContent>(), Mock.Of<ITagQuery>(), Mock.Of<ICultureDictionaryFactory>(), Mock.Of<IUmbracoComponentRenderer>(), Mock.Of<IPublishedContentQuery>(), membershipHelper);
|
||||
var umbracoMapper = new UmbracoMapper(new MapDefinitionCollection(new[] { Mock.Of<IMapDefinition>() }));
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ namespace Umbraco.Tests.Web.Controllers
|
||||
}
|
||||
IOHelper.ForceNotHosted = true;
|
||||
var usersController = new AuthenticationController(
|
||||
new DefaultUserPasswordConfig(),
|
||||
new DefaultPasswordConfig(),
|
||||
Factory.GetInstance<IGlobalSettings>(),
|
||||
umbracoContextAccessor,
|
||||
Factory.GetInstance<ISqlContext>(),
|
||||
|
||||
@@ -121,7 +121,7 @@ namespace Umbraco.Tests.Web.Mvc
|
||||
Mock.Of<ICultureDictionaryFactory>(),
|
||||
Mock.Of<IUmbracoComponentRenderer>(),
|
||||
Mock.Of<IPublishedContentQuery>(query => query.Content(2) == content.Object),
|
||||
new MembershipHelper(umbracoContext.HttpContext, Mock.Of<IPublishedMemberCache>(), Mock.Of<MembersMembershipProvider>(), Mock.Of<RoleProvider>(), Mock.Of<IMemberService>(), Mock.Of<IMemberTypeService>(), Mock.Of<IUserService>(), Mock.Of<IPublicAccessService>(), AppCaches.Disabled, Mock.Of<ILogger>()));
|
||||
new MembershipHelper(umbracoContext.HttpContext, Mock.Of<IPublishedMemberCache>(), Mock.Of<MembersMembershipProvider>(), Mock.Of<RoleProvider>(), Mock.Of<IMemberService>(), Mock.Of<IMemberTypeService>(), Mock.Of<IPublicAccessService>(), AppCaches.Disabled, Mock.Of<ILogger>()));
|
||||
|
||||
var ctrl = new TestSurfaceController(umbracoContextAccessor, helper);
|
||||
var result = ctrl.GetContent(2) as PublishedContentResult;
|
||||
|
||||
+1
-1
@@ -90,7 +90,7 @@
|
||||
$location.search('invite', null);
|
||||
}),
|
||||
//get the membership provider config for password policies
|
||||
authResource.getPasswordConfig().then(function (data) {
|
||||
authResource.getPasswordConfig(0).then(function (data) {
|
||||
vm.invitedUserPasswordModel.passwordPolicies = data;
|
||||
|
||||
//localize the text
|
||||
|
||||
+4
-36
@@ -15,21 +15,14 @@
|
||||
var unsubscribe = [];
|
||||
|
||||
function resetModel(isNew) {
|
||||
//the model config will contain an object, if it does not we'll create defaults
|
||||
//NOTE: We will not support doing the password regex on the client side because the regex on the server side
|
||||
//based on the membership provider cannot always be ported to js from .net directly.
|
||||
//the model config will contain an object, if it does not we'll create defaults
|
||||
/*
|
||||
{
|
||||
hasPassword: true/false,
|
||||
requiresQuestionAnswer: true/false,
|
||||
enableReset: true/false,
|
||||
enablePasswordRetrieval: true/false,
|
||||
minPasswordLength: 10
|
||||
}
|
||||
*/
|
||||
|
||||
vm.showReset = false;
|
||||
|
||||
//set defaults if they are not available
|
||||
if (vm.config.disableToggle === undefined) {
|
||||
vm.config.disableToggle = false;
|
||||
@@ -37,20 +30,6 @@
|
||||
if (vm.config.hasPassword === undefined) {
|
||||
vm.config.hasPassword = false;
|
||||
}
|
||||
if (vm.config.enablePasswordRetrieval === undefined) {
|
||||
vm.config.enablePasswordRetrieval = true;
|
||||
}
|
||||
if (vm.config.requiresQuestionAnswer === undefined) {
|
||||
vm.config.requiresQuestionAnswer = false;
|
||||
}
|
||||
//don't enable reset if it is new - that doesn't make sense
|
||||
if (isNew === "true") {
|
||||
vm.config.enableReset = false;
|
||||
}
|
||||
else if (vm.config.enableReset === undefined) {
|
||||
vm.config.enableReset = true;
|
||||
}
|
||||
|
||||
if (vm.config.minPasswordLength === undefined) {
|
||||
vm.config.minPasswordLength = 0;
|
||||
}
|
||||
@@ -60,9 +39,7 @@
|
||||
//if it's not an object then just create a new one
|
||||
vm.passwordValues = {
|
||||
newPassword: null,
|
||||
oldPassword: null,
|
||||
reset: null,
|
||||
answer: null
|
||||
oldPassword: null
|
||||
};
|
||||
}
|
||||
else {
|
||||
@@ -73,8 +50,6 @@
|
||||
vm.passwordValues.newPassword = null;
|
||||
vm.passwordValues.oldPassword = null;
|
||||
}
|
||||
vm.passwordValues.reset = null;
|
||||
vm.passwordValues.answer = null;
|
||||
}
|
||||
|
||||
//the value to compare to match passwords
|
||||
@@ -105,11 +80,7 @@
|
||||
}));
|
||||
|
||||
unsubscribe.push($scope.$on("formSubmitting", function () {
|
||||
//if there was a previously generated password displaying, clear it
|
||||
if (vm.changing && vm.passwordValues) {
|
||||
vm.passwordValues.generatedPassword = null;
|
||||
}
|
||||
else if (!vm.changing) {
|
||||
if (!vm.changing) {
|
||||
//we are not changing, so the model needs to be null
|
||||
vm.passwordValues = null;
|
||||
}
|
||||
@@ -130,8 +101,6 @@
|
||||
function doChange() {
|
||||
resetModel();
|
||||
vm.changing = true;
|
||||
//if there was a previously generated password displaying, clear it
|
||||
vm.passwordValues.generatedPassword = null;
|
||||
vm.passwordValues.confirm = null;
|
||||
};
|
||||
|
||||
@@ -143,8 +112,7 @@
|
||||
|
||||
function showOldPass() {
|
||||
return vm.config.hasPassword &&
|
||||
!vm.config.allowManuallyChangingPassword &&
|
||||
!vm.config.enablePasswordRetrieval && !vm.showReset;
|
||||
!vm.config.allowManuallyChangingPassword;
|
||||
};
|
||||
|
||||
// TODO: I don't think we need this or the cancel button, this can be up to the editor rendering this component
|
||||
|
||||
@@ -204,13 +204,13 @@ function authResource($q, $http, umbRequestHelper, angularHelper) {
|
||||
* @description
|
||||
* Gets the configuration of the user membership provider which is used to configure the change password form
|
||||
*/
|
||||
getPasswordConfig: function () {
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.get(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"authenticationApiBaseUrl",
|
||||
"GetPasswordConfig")),
|
||||
'Failed to retrieve membership provider config');
|
||||
getPasswordConfig: function (userId) {
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.get(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"authenticationApiBaseUrl",
|
||||
"GetPasswordConfig", { userId: userId })),
|
||||
'Failed to retrieve membership provider config');
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
if (!model) {
|
||||
return null;
|
||||
}
|
||||
var trimmed = _.omit(model, ["confirm", "generatedPassword"]);
|
||||
var trimmed = _.omit(model, ["confirm"]);
|
||||
|
||||
//ensure that the pass value is null if all child properties are null
|
||||
var allNull = true;
|
||||
@@ -304,33 +304,6 @@
|
||||
}
|
||||
saveModel.memberGroups = selectedGroups;
|
||||
|
||||
//turn the dictionary into an array of pairs
|
||||
var memberProviderPropAliases = _.pairs(displayModel.fieldConfig);
|
||||
_.each(displayModel.tabs, function (tab) {
|
||||
_.each(tab.properties, function (prop) {
|
||||
var foundAlias = _.find(memberProviderPropAliases, function (item) {
|
||||
return prop.alias === item[1];
|
||||
});
|
||||
if (foundAlias) {
|
||||
//we know the current property matches an alias, now we need to determine which membership provider property it was for
|
||||
// by looking at the key
|
||||
switch (foundAlias[0]) {
|
||||
case "umbracoMemberLockedOut":
|
||||
saveModel.isLockedOut = Object.toBoolean(prop.value);
|
||||
break;
|
||||
case "umbracoMemberApproved":
|
||||
saveModel.isApproved = Object.toBoolean(prop.value);
|
||||
break;
|
||||
case "umbracoMemberComments":
|
||||
saveModel.comments = prop.value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
return saveModel;
|
||||
},
|
||||
|
||||
|
||||
@@ -87,6 +87,17 @@ angular.module("umbraco")
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
//go get the config for the membership provider and add it to the model
|
||||
authResource.getPasswordConfig(user.id).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
|
||||
// disable reset password functionality beacuse it does not make sense inside the backoffice
|
||||
$scope.changePasswordModel.config.hasPassword = true;
|
||||
$scope.changePasswordModel.config.disableToggle = true;
|
||||
});
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -103,6 +114,12 @@ angular.module("umbraco")
|
||||
});
|
||||
}
|
||||
|
||||
//create the initial model for change password
|
||||
$scope.changePasswordModel = {
|
||||
config: {},
|
||||
value: {}
|
||||
};
|
||||
|
||||
updateUserInfo();
|
||||
|
||||
//remove all event handlers
|
||||
@@ -113,25 +130,6 @@ angular.module("umbraco")
|
||||
|
||||
});
|
||||
|
||||
/* ---------- UPDATE PASSWORD ---------- */
|
||||
|
||||
//create the initial model for change password
|
||||
$scope.changePasswordModel = {
|
||||
config: {},
|
||||
value: {}
|
||||
};
|
||||
|
||||
//go get the config for the membership provider and add it to the model
|
||||
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
|
||||
// disable reset password functionality beacuse it does not make sense inside the backoffice
|
||||
$scope.changePasswordModel.config.hasPassword = true;
|
||||
$scope.changePasswordModel.config.disableToggle = true;
|
||||
$scope.changePasswordModel.config.enableReset = false;
|
||||
});
|
||||
|
||||
$scope.changePassword = function() {
|
||||
|
||||
if (formHelper.submitForm({ scope: $scope })) {
|
||||
@@ -143,11 +141,6 @@ angular.module("umbraco")
|
||||
//reset old data
|
||||
clearPasswordFields();
|
||||
|
||||
//if the password has been reset, then update our model
|
||||
if (data.value) {
|
||||
$scope.changePasswordModel.value.generatedPassword = data.value;
|
||||
}
|
||||
|
||||
formHelper.resetForm({ scope: $scope });
|
||||
|
||||
$scope.changePasswordButtonState = "success";
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
<div>
|
||||
<div class="alert alert-success text-center" ng-hide="!vm.passwordValues.generatedPassword">
|
||||
<small>Password has been reset to:</small>
|
||||
<br />
|
||||
<strong>{{vm.passwordValues.generatedPassword}}</strong>
|
||||
</div>
|
||||
<div ng-switch="vm.changing">
|
||||
<div ng-switch-when="false">
|
||||
<button type="button" ng-click="vm.doChange()" class="btn umb-button__button btn-action">
|
||||
@@ -13,15 +8,7 @@
|
||||
<div ng-switch-when="true">
|
||||
|
||||
<ng-form name="changePasswordForm">
|
||||
<umb-control-group alias="resetPassword" label="@user_resetPassword" ng-show="vm.config.enableReset">
|
||||
<umb-checkbox model="vm.passwordValues.reset" server-validation-field="resetPassword"
|
||||
on-change="vm.showReset = !vm.showReset" />
|
||||
<span ng-messages="changePasswordForm.resetPassword.$error" show-validation-on-submit>
|
||||
<span class="help-inline" ng-message="valServerField">{{changePasswordForm.resetPassword.errorMsg}}</span>
|
||||
</span>
|
||||
|
||||
</umb-control-group>
|
||||
|
||||
|
||||
<!-- we need to show the old pass field when the provider cannot retrieve the password -->
|
||||
<umb-control-group alias="oldPassword" label="@user_oldPassword" ng-if="vm.showOldPass()" required="true">
|
||||
<input type="password" name="oldPassword" ng-model="vm.passwordValues.oldPassword"
|
||||
@@ -35,7 +22,7 @@
|
||||
</span>
|
||||
</umb-control-group>
|
||||
|
||||
<umb-control-group alias="password" label="@user_newPassword" ng-if="!vm.showReset" required="true">
|
||||
<umb-control-group alias="password" label="@user_newPassword" required="true">
|
||||
<input type="password" name="password" ng-model="vm.passwordValues.newPassword"
|
||||
class="input-block-level umb-textstring textstring"
|
||||
required
|
||||
@@ -49,7 +36,7 @@
|
||||
</span>
|
||||
</umb-control-group>
|
||||
|
||||
<umb-control-group alias="confirmPassword" label="@user_confirmNewPassword" ng-if="!vm.showReset" required="true">
|
||||
<umb-control-group alias="confirmPassword" label="@user_confirmNewPassword" required="true">
|
||||
<input type="password" name="confirmPassword" ng-model="vm.passwordValues.confirm"
|
||||
class="input-block-level umb-textstring textstring"
|
||||
val-compare="password"
|
||||
|
||||
@@ -145,13 +145,6 @@ function MemberEditController($scope, $routeParams, $location, appState, memberR
|
||||
|
||||
$scope.page.saveButtonState = "busy";
|
||||
|
||||
//anytime a user is changing a member's password without the oldPassword, we are in effect resetting it so we need to set that flag here
|
||||
var passwordProp = _.find(contentEditingHelper.getAllProps($scope.content), function (e) { return e.alias === '_umb_password' });
|
||||
if (passwordProp && passwordProp.value && (typeof passwordProp.value.reset !== 'undefined') && !passwordProp.value.reset) {
|
||||
// if the admin is not explicitly resetting the password, flag it for resetting if a new password is being entered
|
||||
passwordProp.value.reset = !passwordProp.value.oldPassword && passwordProp.config.allowManuallyChangingPassword;
|
||||
}
|
||||
|
||||
memberResource.save($scope.content, create, fileManager.getFiles())
|
||||
.then(function(data) {
|
||||
|
||||
|
||||
+3
-17
@@ -4,15 +4,10 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.ChangePasswordCont
|
||||
$scope.isNew = $routeParams.create;
|
||||
|
||||
function resetModel() {
|
||||
//the model config will contain an object, if it does not we'll create defaults
|
||||
//NOTE: We will not support doing the password regex on the client side because the regex on the server side
|
||||
//based on the membership provider cannot always be ported to js from .net directly.
|
||||
//the model config will contain an object, if it does not we'll create defaults
|
||||
/*
|
||||
{
|
||||
hasPassword: true/false,
|
||||
requiresQuestionAnswer: true/false,
|
||||
enableReset: true/false,
|
||||
enablePasswordRetrieval: true/false,
|
||||
hasPassword: true/false,
|
||||
minPasswordLength: 10
|
||||
}
|
||||
*/
|
||||
@@ -23,16 +18,7 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.ChangePasswordCont
|
||||
}
|
||||
if (!$scope.model.config || $scope.model.config.hasPassword === undefined) {
|
||||
$scope.model.config.hasPassword = false;
|
||||
}
|
||||
if (!$scope.model.config || $scope.model.config.enablePasswordRetrieval === undefined) {
|
||||
$scope.model.config.enablePasswordRetrieval = true;
|
||||
}
|
||||
if (!$scope.model.config || $scope.model.config.requiresQuestionAnswer === undefined) {
|
||||
$scope.model.config.requiresQuestionAnswer = false;
|
||||
}
|
||||
if (!$scope.model.config || $scope.model.config.enableReset === undefined) {
|
||||
$scope.model.config.enableReset = true;
|
||||
}
|
||||
}
|
||||
if (!$scope.model.config || $scope.model.config.minPasswordLength === undefined) {
|
||||
$scope.model.config.minPasswordLength = 0;
|
||||
}
|
||||
|
||||
@@ -80,27 +80,13 @@
|
||||
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.getPasswordConfig().then(function (data) {
|
||||
authResource.getPasswordConfig(user.id).then(function (data) {
|
||||
vm.changePasswordModel.config = data;
|
||||
|
||||
//the user has a password if they are not states: Invited, NoCredentials
|
||||
vm.changePasswordModel.config.hasPassword = vm.user.userState !== 3 && vm.user.userState !== 4;
|
||||
|
||||
vm.changePasswordModel.config.disableToggle = true;
|
||||
|
||||
//this is only relavent for membership providers now (it's basically obsolete)
|
||||
vm.changePasswordModel.config.enableReset = false;
|
||||
|
||||
//in the ASP.NET Identity world, this config option will allow an admin user to change another user's password
|
||||
//if the user has access to the user section. So if this editor is being access, the user of course has access to this section.
|
||||
//the authorization check is also done on the server side when submitted.
|
||||
|
||||
// only update the setting if not the current logged in user, otherwise leave the value as it is
|
||||
// currently set in the web.config
|
||||
if (!vm.user.isCurrentUser)
|
||||
{
|
||||
vm.changePasswordModel.config.allowManuallyChangingPassword = true;
|
||||
}
|
||||
|
||||
vm.loading = false;
|
||||
});
|
||||
@@ -134,16 +120,7 @@
|
||||
|
||||
if (formHelper.submitForm({ scope: $scope })) {
|
||||
|
||||
//anytime a user is changing another user's password, we are in effect resetting it so we need to set that flag here
|
||||
if (vm.user.changePassword) {
|
||||
//NOTE: the check for allowManuallyChangingPassword is due to this legacy user membership provider setting, if that is true, then the current user
|
||||
//can change their own password without entering their current one (this is a legacy setting since that is a security issue but we need to maintain compat).
|
||||
//if allowManuallyChangingPassword=false, then we are using default settings and the user will need to enter their old password to change their own password.
|
||||
vm.user.changePassword.reset = (!vm.user.changePassword.oldPassword && !vm.user.isCurrentUser) || vm.changePasswordModel.config.allowManuallyChangingPassword;
|
||||
}
|
||||
|
||||
vm.page.saveButtonState = "busy";
|
||||
vm.user.resetPasswordValue = null;
|
||||
|
||||
//save current nav to be restored later so that the tabs dont change
|
||||
var currentNav = vm.user.navigation;
|
||||
|
||||
@@ -60,9 +60,9 @@ namespace Umbraco.Web.Editors
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[WebApi.UmbracoAuthorize(requireApproval: false)]
|
||||
public IDictionary<string, object> GetPasswordConfig()
|
||||
public IDictionary<string, object> GetPasswordConfig(int userId)
|
||||
{
|
||||
return _passwordConfiguration.GetConfiguration();
|
||||
return _passwordConfiguration.GetConfiguration(userId != UmbracoContext.Security.CurrentUser.Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -2,17 +2,12 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Web.Http.Controllers;
|
||||
using System.Web.Http.ModelBinding;
|
||||
using System.Web.Security;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
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
|
||||
{
|
||||
|
||||
@@ -8,7 +8,6 @@ using System.Text;
|
||||
using System.Web.Http;
|
||||
using System.Web.Http.Controllers;
|
||||
using System.Web.Http.ModelBinding;
|
||||
using System.Web.Security;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Configuration;
|
||||
|
||||
@@ -167,20 +167,13 @@ namespace Umbraco.Web.Editors
|
||||
/// </returns>
|
||||
public async Task<ModelWithNotifications<string>> PostChangePassword(ChangingPasswordModel data)
|
||||
{
|
||||
var passwordChanger = new PasswordChanger(Logger, Services.UserService, UmbracoContext.HttpContext);
|
||||
var passwordChanger = new PasswordChanger(Logger);
|
||||
var passwordChangeResult = await passwordChanger.ChangePasswordWithIdentityAsync(Security.CurrentUser, Security.CurrentUser, data, UserManager);
|
||||
|
||||
if (passwordChangeResult.Success)
|
||||
{
|
||||
var userMgr = this.TryGetOwinContext().Result.GetBackOfficeUserManager();
|
||||
|
||||
//raise the reset event
|
||||
// TODO: I don't think this is required anymore since from 7.7 we no longer display the reset password checkbox since that didn't make sense.
|
||||
if (data.Reset.HasValue && data.Reset.Value)
|
||||
{
|
||||
userMgr.RaisePasswordResetEvent(Security.CurrentUser.Id);
|
||||
}
|
||||
|
||||
//even if we weren't resetting this, it is the correct value (null), otherwise if we were resetting then it will contain the new pword
|
||||
var result = new ModelWithNotifications<string>(passwordChangeResult.Result.ResetPassword);
|
||||
result.AddSuccessNotification(Services.TextService.Localize("user/password"), Services.TextService.Localize("user/passwordChanged"));
|
||||
|
||||
@@ -5,7 +5,6 @@ using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Web.Http.Controllers;
|
||||
using System.Web.Http.ModelBinding;
|
||||
using System.Web.Security;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
@@ -21,11 +20,18 @@ namespace Umbraco.Web.Editors.Filters
|
||||
internal class MemberSaveModelValidator : ContentModelValidator<IMember, MemberSave, IContentProperties<ContentPropertyBasic>>
|
||||
{
|
||||
private readonly IMemberTypeService _memberTypeService;
|
||||
private readonly IMemberService _memberService;
|
||||
|
||||
public MemberSaveModelValidator(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, ILocalizedTextService textService, IMemberTypeService memberTypeService)
|
||||
public MemberSaveModelValidator(
|
||||
ILogger logger,
|
||||
IUmbracoContextAccessor umbracoContextAccessor,
|
||||
ILocalizedTextService textService,
|
||||
IMemberTypeService memberTypeService,
|
||||
IMemberService memberService)
|
||||
: base(logger, umbracoContextAccessor, textService)
|
||||
{
|
||||
_memberTypeService = memberTypeService ?? throw new ArgumentNullException(nameof(memberTypeService));
|
||||
_memberService = memberService ?? throw new ArgumentNullException(nameof(memberService));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -52,10 +58,7 @@ namespace Umbraco.Web.Editors.Filters
|
||||
$"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}email");
|
||||
}
|
||||
|
||||
//default provider!
|
||||
var membershipProvider = MembershipProviderExtensions.GetMembersMembershipProvider();
|
||||
|
||||
var validEmail = ValidateUniqueEmail(model, membershipProvider);
|
||||
var validEmail = ValidateUniqueEmail(model);
|
||||
if (validEmail == false)
|
||||
{
|
||||
modelState.AddPropertyError(
|
||||
@@ -63,7 +66,7 @@ namespace Umbraco.Web.Editors.Filters
|
||||
$"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}email");
|
||||
}
|
||||
|
||||
var validLogin = ValidateUniqueLogin(model, membershipProvider);
|
||||
var validLogin = ValidateUniqueLogin(model);
|
||||
if (validLogin == false)
|
||||
{
|
||||
modelState.AddPropertyError(
|
||||
@@ -121,13 +124,11 @@ namespace Umbraco.Web.Editors.Filters
|
||||
return ValidateProperties(propertiesToValidate, model.PersistedContent.Properties.ToList(), actionContext);
|
||||
}
|
||||
|
||||
internal bool ValidateUniqueLogin(MemberSave model, MembershipProvider membershipProvider)
|
||||
internal bool ValidateUniqueLogin(MemberSave model)
|
||||
{
|
||||
if (model == null) throw new ArgumentNullException(nameof(model));
|
||||
if (membershipProvider == null) throw new ArgumentNullException(nameof(membershipProvider));
|
||||
|
||||
int totalRecs;
|
||||
var existingByName = membershipProvider.FindUsersByName(model.Username.Trim(), 0, int.MaxValue, out totalRecs);
|
||||
var existingByName = _memberService.GetByUsername(model.Username.Trim());
|
||||
switch (model.Action)
|
||||
{
|
||||
case ContentSaveAction.Save:
|
||||
@@ -136,8 +137,7 @@ namespace Umbraco.Web.Editors.Filters
|
||||
if (model.PersistedContent.Username.InvariantEquals(model.Username.Trim()) == false)
|
||||
{
|
||||
//they are changing their login name
|
||||
if (existingByName.Cast<MembershipUser>().Select(x => x.UserName)
|
||||
.Any(x => x == model.Username.Trim()))
|
||||
if (existingByName != null && existingByName.Username == model.Username.Trim())
|
||||
{
|
||||
//the user cannot use this login
|
||||
return false;
|
||||
@@ -146,8 +146,7 @@ namespace Umbraco.Web.Editors.Filters
|
||||
break;
|
||||
case ContentSaveAction.SaveNew:
|
||||
//check if the user's login already exists
|
||||
if (existingByName.Cast<MembershipUser>().Select(x => x.UserName)
|
||||
.Any(x => x == model.Username.Trim()))
|
||||
if (existingByName != null && existingByName.Username == model.Username.Trim())
|
||||
{
|
||||
//the user cannot use this login
|
||||
return false;
|
||||
@@ -161,18 +160,11 @@ namespace Umbraco.Web.Editors.Filters
|
||||
return true;
|
||||
}
|
||||
|
||||
internal bool ValidateUniqueEmail(MemberSave model, MembershipProvider membershipProvider)
|
||||
internal bool ValidateUniqueEmail(MemberSave model)
|
||||
{
|
||||
if (model == null) throw new ArgumentNullException(nameof(model));
|
||||
if (membershipProvider == null) throw new ArgumentNullException(nameof(membershipProvider));
|
||||
|
||||
if (membershipProvider.RequiresUniqueEmail == false)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
int totalRecs;
|
||||
var existingByEmail = membershipProvider.FindUsersByEmail(model.Email.Trim(), 0, int.MaxValue, out totalRecs);
|
||||
var existingByEmail = _memberService.GetByEmail(model.Email.Trim());
|
||||
switch (model.Action)
|
||||
{
|
||||
case ContentSaveAction.Save:
|
||||
@@ -180,8 +172,7 @@ namespace Umbraco.Web.Editors.Filters
|
||||
if (model.PersistedContent.Email.InvariantEquals(model.Email.Trim()) == false)
|
||||
{
|
||||
//they are changing their email
|
||||
if (existingByEmail.Cast<MembershipUser>().Select(x => x.Email)
|
||||
.Any(x => x.InvariantEquals(model.Email.Trim())))
|
||||
if (existingByEmail != null && existingByEmail.Email.InvariantEquals(model.Email.Trim()))
|
||||
{
|
||||
//the user cannot use this email
|
||||
return false;
|
||||
@@ -190,8 +181,7 @@ namespace Umbraco.Web.Editors.Filters
|
||||
break;
|
||||
case ContentSaveAction.SaveNew:
|
||||
//check if the user's email already exists
|
||||
if (existingByEmail.Cast<MembershipUser>().Select(x => x.Email)
|
||||
.Any(x => x.InvariantEquals(model.Email.Trim())))
|
||||
if (existingByEmail != null && existingByEmail.Email.InvariantEquals(model.Email.Trim()))
|
||||
{
|
||||
//the user cannot use this email
|
||||
return false;
|
||||
|
||||
@@ -17,23 +17,25 @@ namespace Umbraco.Web.Editors.Filters
|
||||
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
|
||||
private readonly ILocalizedTextService _textService;
|
||||
private readonly IMemberTypeService _memberTypeService;
|
||||
private readonly IMemberService _memberService;
|
||||
|
||||
public MemberSaveValidationAttribute()
|
||||
: this(Current.Logger, Current.UmbracoContextAccessor, Current.Services.TextService, Current.Services.MemberTypeService)
|
||||
: this(Current.Logger, Current.UmbracoContextAccessor, Current.Services.TextService, Current.Services.MemberTypeService, Current.Services.MemberService)
|
||||
{ }
|
||||
|
||||
public MemberSaveValidationAttribute(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, ILocalizedTextService textService, IMemberTypeService memberTypeService)
|
||||
public MemberSaveValidationAttribute(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, ILocalizedTextService textService, IMemberTypeService memberTypeService, IMemberService memberService)
|
||||
{
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor));
|
||||
_textService = textService ?? throw new ArgumentNullException(nameof(textService));
|
||||
_memberTypeService = memberTypeService ?? throw new ArgumentNullException(nameof(memberTypeService));
|
||||
_memberService = memberService ?? throw new ArgumentNullException(nameof(memberService));;
|
||||
}
|
||||
|
||||
public override void OnActionExecuting(HttpActionContext actionContext)
|
||||
{
|
||||
var model = (MemberSave)actionContext.ActionArguments["contentItem"];
|
||||
var contentItemValidator = new MemberSaveModelValidator(_logger, _umbracoContextAccessor, _textService, _memberTypeService);
|
||||
var contentItemValidator = new MemberSaveModelValidator(_logger, _umbracoContextAccessor,_textService, _memberTypeService, _memberService);
|
||||
//now do each validation step
|
||||
if (contentItemValidator.ValidateExistingContent(model, actionContext))
|
||||
if (contentItemValidator.ValidateProperties(model, model, actionContext))
|
||||
|
||||
@@ -7,13 +7,10 @@ using System.Net.Http.Formatting;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Web.Http;
|
||||
using System.Web.Http.ModelBinding;
|
||||
using System.Web.Security;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Services.Implement;
|
||||
using Umbraco.Web.WebApi;
|
||||
@@ -32,6 +29,8 @@ using Umbraco.Web.Editors.Filters;
|
||||
using Constants = Umbraco.Core.Constants;
|
||||
using Umbraco.Core.Dictionary;
|
||||
using Umbraco.Web.Security;
|
||||
using Umbraco.Core.Security;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Umbraco.Web.Editors
|
||||
{
|
||||
@@ -44,15 +43,17 @@ namespace Umbraco.Web.Editors
|
||||
[OutgoingNoHyphenGuidFormat]
|
||||
public class MemberController : ContentControllerBase
|
||||
{
|
||||
public MemberController(ICultureDictionary cultureDictionary, PropertyEditorCollection propertyEditors, IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper)
|
||||
public MemberController(IMemberPasswordConfiguration passwordConfig, ICultureDictionary cultureDictionary, PropertyEditorCollection propertyEditors, IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper)
|
||||
: base(cultureDictionary, globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper)
|
||||
{
|
||||
_passwordConfig = passwordConfig ?? throw new ArgumentNullException(nameof(passwordConfig));
|
||||
_propertyEditors = propertyEditors ?? throw new ArgumentNullException(nameof(propertyEditors));
|
||||
}
|
||||
|
||||
private readonly MembershipProvider _provider = MembershipProviderExtensions.GetMembersMembershipProvider();
|
||||
private readonly IMemberPasswordConfiguration _passwordConfig;
|
||||
private readonly PropertyEditorCollection _propertyEditors;
|
||||
|
||||
private PasswordSecurity _passwordSecurity;
|
||||
private PasswordSecurity PasswordSecurity => _passwordSecurity ?? (_passwordSecurity = new PasswordSecurity(_passwordConfig));
|
||||
|
||||
public PagedResult<MemberBasic> GetPagedResults(
|
||||
int pageNumber = 1,
|
||||
@@ -149,10 +150,10 @@ namespace Umbraco.Web.Editors
|
||||
throw new HttpResponseException(HttpStatusCode.NotFound);
|
||||
}
|
||||
|
||||
var provider = MembershipProviderExtensions.GetMembersMembershipProvider();
|
||||
var passwordGenerator = new PasswordGenerator(_passwordConfig);
|
||||
|
||||
emptyContent = new Member(contentType);
|
||||
emptyContent.AdditionalData["NewPassword"] = Membership.GeneratePassword(provider.MinRequiredPasswordLength, provider.MinRequiredNonAlphanumericCharacters);
|
||||
emptyContent.AdditionalData["NewPassword"] = passwordGenerator.GeneratePassword();
|
||||
return Mapper.Map<MemberDisplay>(emptyContent);
|
||||
}
|
||||
|
||||
@@ -163,7 +164,7 @@ namespace Umbraco.Web.Editors
|
||||
[FileUploadCleanupFilter]
|
||||
[OutgoingEditorModelEvent]
|
||||
[MemberSaveValidation]
|
||||
public MemberDisplay PostSave(
|
||||
public async Task<MemberDisplay> PostSave(
|
||||
[ModelBinder(typeof(MemberBinder))]
|
||||
MemberSave contentItem)
|
||||
{
|
||||
@@ -178,6 +179,8 @@ namespace Umbraco.Web.Editors
|
||||
//map the properties to the persisted entity
|
||||
MapPropertyValues(contentItem);
|
||||
|
||||
await ValidateMemberDataAsync(contentItem);
|
||||
|
||||
//Unlike content/media - if there are errors for a member, we do NOT proceed to save them, we cannot so return the errors
|
||||
if (ModelState.IsValid == false)
|
||||
{
|
||||
@@ -186,53 +189,31 @@ namespace Umbraco.Web.Editors
|
||||
throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay));
|
||||
}
|
||||
|
||||
// TODO: WE need to support this! - requires UI updates, etc...
|
||||
if (_provider.RequiresQuestionAndAnswer)
|
||||
{
|
||||
throw new NotSupportedException("Currently the member editor does not support providers that have RequiresQuestionAndAnswer specified");
|
||||
}
|
||||
|
||||
//We're gonna look up the current roles now because the below code can cause
|
||||
// events to be raised and developers could be manually adding roles to members in
|
||||
// their handlers. If we don't look this up now there's a chance we'll just end up
|
||||
// removing the roles they've assigned.
|
||||
var currRoles = Roles.GetRolesForUser(contentItem.PersistedContent.Username);
|
||||
var currRoles = Services.MemberService.GetAllRoles(contentItem.PersistedContent.Username);
|
||||
//find the ones to remove and remove them
|
||||
var rolesToRemove = currRoles.Except(contentItem.Groups).ToArray();
|
||||
|
||||
string generatedPassword = null;
|
||||
//Depending on the action we need to first do a create or update using the membership provider
|
||||
// this ensures that passwords are formatted correctly and also performs the validation on the provider itself.
|
||||
switch (contentItem.Action)
|
||||
{
|
||||
case ContentSaveAction.Save:
|
||||
generatedPassword = UpdateWithMembershipProvider(contentItem);
|
||||
UpdateMemberData(contentItem);
|
||||
break;
|
||||
case ContentSaveAction.SaveNew:
|
||||
MembershipCreateStatus status;
|
||||
CreateWithMembershipProvider(contentItem, out status);
|
||||
|
||||
// save the ID of the creator
|
||||
contentItem.PersistedContent.CreatorId = Security.CurrentUser.Id;
|
||||
contentItem.PersistedContent = CreateMemberData(contentItem);
|
||||
break;
|
||||
default:
|
||||
//we don't support anything else for members
|
||||
throw new HttpResponseException(HttpStatusCode.NotFound);
|
||||
}
|
||||
|
||||
//If we've had problems creating/updating the user with the provider then return the error
|
||||
if (ModelState.IsValid == false)
|
||||
{
|
||||
var forDisplay = Mapper.Map<MemberDisplay>(contentItem.PersistedContent);
|
||||
forDisplay.Errors = ModelState.ToErrorDictionary();
|
||||
throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay));
|
||||
}
|
||||
|
||||
//save the IMember -
|
||||
//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;
|
||||
//TODO: There's 3 things saved here and we should do this all in one transaction, which we can do here by wrapping in a scope
|
||||
// but it would be nicer to have this taken care of within the Save method itself
|
||||
|
||||
//create/save the IMember
|
||||
Services.MemberService.Save(contentItem.PersistedContent);
|
||||
@@ -241,21 +222,16 @@ namespace Umbraco.Web.Editors
|
||||
// if we are changing the username, it must be persisted before looking up the member roles).
|
||||
if (rolesToRemove.Any())
|
||||
{
|
||||
Roles.RemoveUserFromRoles(contentItem.PersistedContent.Username, rolesToRemove);
|
||||
Services.MemberService.DissociateRoles(new[] { contentItem.PersistedContent.Username }, rolesToRemove);
|
||||
}
|
||||
//find the ones to add and add them
|
||||
var toAdd = contentItem.Groups.Except(currRoles).ToArray();
|
||||
if (toAdd.Any())
|
||||
{
|
||||
//add the ones submitted
|
||||
Roles.AddUserToRoles(contentItem.PersistedContent.Username, toAdd);
|
||||
Services.MemberService.AssignRoles(new[] { contentItem.PersistedContent.Username }, toAdd);
|
||||
}
|
||||
|
||||
//set the generated password (if there was one) - in order to do this we'll chuck the generated password into the
|
||||
// additional data of the IUmbracoEntity of the persisted item - then we can retrieve this in the model mapper and set
|
||||
// the value to be given to the UI. Hooray for AdditionalData :)
|
||||
contentItem.PersistedContent.AdditionalData["GeneratedPassword"] = generatedPassword;
|
||||
|
||||
//return the updated model
|
||||
var display = Mapper.Map<MemberDisplay>(contentItem.PersistedContent);
|
||||
|
||||
@@ -296,32 +272,38 @@ namespace Umbraco.Web.Editors
|
||||
null); // member are all invariant
|
||||
}
|
||||
|
||||
private IMember CreateMemberData(MemberSave contentItem)
|
||||
{
|
||||
var memberType = Services.MemberTypeService.Get(contentItem.ContentTypeAlias);
|
||||
if (memberType == null)
|
||||
throw new InvalidOperationException($"No member type found with alias {contentItem.ContentTypeAlias}");
|
||||
var member = new Member(contentItem.Name, contentItem.Email, contentItem.Username, memberType, true)
|
||||
{
|
||||
CreatorId = Security.CurrentUser.Id,
|
||||
RawPasswordValue = PasswordSecurity.HashPasswordForStorage(contentItem.Password.NewPassword),
|
||||
Comments = contentItem.Comments,
|
||||
IsApproved = contentItem.IsApproved
|
||||
};
|
||||
|
||||
return member;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the membership user using the membership provider (for things like email, etc...)
|
||||
/// If a password change is detected then we'll try that too.
|
||||
/// Update the member security data
|
||||
/// </summary>
|
||||
/// <param name="contentItem"></param>
|
||||
/// <returns>
|
||||
/// If the password has been reset then this method will return the reset/generated password, otherwise will return null.
|
||||
/// </returns>
|
||||
private string UpdateWithMembershipProvider(MemberSave contentItem)
|
||||
private void UpdateMemberData(MemberSave contentItem)
|
||||
{
|
||||
//Get the member from the provider
|
||||
contentItem.PersistedContent.WriterId = Security.CurrentUser.Id;
|
||||
|
||||
var membershipUser = _provider.GetUser(contentItem.PersistedContent.Key, false);
|
||||
if (membershipUser == null)
|
||||
{
|
||||
//This should never happen! so we'll let it YSOD if it does.
|
||||
throw new InvalidOperationException("Could not get member from membership provider " + _provider.Name + " with key " + contentItem.PersistedContent.Key);
|
||||
}
|
||||
|
||||
var shouldReFetchMember = false;
|
||||
var providedUserName = contentItem.PersistedContent.Username;
|
||||
|
||||
//if the user doesn't have access to sensitive values, then we need to check if any of the built in member property types
|
||||
//have been marked as sensitive. If that is the case we cannot change these persisted values no matter what value has been posted.
|
||||
//There's only 3 special ones we need to deal with that are part of the MemberSave instance
|
||||
if (Security.CurrentUser.HasAccessToSensitiveData() == false)
|
||||
// If the user doesn't have access to sensitive values, then we need to check if any of the built in member property types
|
||||
// have been marked as sensitive. If that is the case we cannot change these persisted values no matter what value has been posted.
|
||||
// There's only 3 special ones we need to deal with that are part of the MemberSave instance: Comments, IsApproved, IsLockedOut
|
||||
// but we will take care of this in a generic way below so that it works for all props.
|
||||
if (!Security.CurrentUser.HasAccessToSensitiveData())
|
||||
{
|
||||
var memberType = Services.MemberTypeService.Get(contentItem.PersistedContent.ContentTypeId);
|
||||
var sensitiveProperties = memberType
|
||||
@@ -330,161 +312,37 @@ namespace Umbraco.Web.Editors
|
||||
|
||||
foreach (var sensitiveProperty in sensitiveProperties)
|
||||
{
|
||||
//if found, change the value of the contentItem model to the persisted value so it remains unchanged
|
||||
switch (sensitiveProperty.Alias)
|
||||
var destProp = contentItem.Properties.FirstOrDefault(x => x.Alias == sensitiveProperty.Alias);
|
||||
if (destProp != null)
|
||||
{
|
||||
case Constants.Conventions.Member.Comments:
|
||||
contentItem.Comments = contentItem.PersistedContent.Comments;
|
||||
break;
|
||||
case Constants.Conventions.Member.IsApproved:
|
||||
contentItem.IsApproved = contentItem.PersistedContent.IsApproved;
|
||||
break;
|
||||
case Constants.Conventions.Member.IsLockedOut:
|
||||
contentItem.IsLockedOut = contentItem.PersistedContent.IsLockedOut;
|
||||
break;
|
||||
//if found, change the value of the contentItem model to the persisted value so it remains unchanged
|
||||
var origValue = contentItem.PersistedContent.GetValue(sensitiveProperty.Alias);
|
||||
destProp.Value = origValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Update the membership user if it has changed
|
||||
try
|
||||
{
|
||||
var requiredUpdating = Members.UpdateMember(membershipUser, _provider,
|
||||
contentItem.Email.Trim(),
|
||||
contentItem.IsApproved,
|
||||
comment: contentItem.Comments);
|
||||
|
||||
if (requiredUpdating.Success)
|
||||
{
|
||||
//re-map these values
|
||||
shouldReFetchMember = true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Warn<MemberController>(ex, "Could not update member, the provider returned an error");
|
||||
ModelState.AddPropertyError(
|
||||
//specify 'default' just so that it shows up as a notification - is not assigned to a property
|
||||
new ValidationResult("Could not update member, the provider returned an error: " + ex.Message + " (see log for full details)"), "default");
|
||||
}
|
||||
var isLockedOut = contentItem.IsLockedOut;
|
||||
|
||||
//if they were locked but now they are trying to be unlocked
|
||||
if (membershipUser.IsLockedOut && contentItem.IsLockedOut == false)
|
||||
if (contentItem.PersistedContent.IsLockedOut && isLockedOut == false)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = _provider.UnlockUser(membershipUser.UserName);
|
||||
if (result == false)
|
||||
{
|
||||
//it wasn't successful - but it won't really tell us why.
|
||||
ModelState.AddModelError("custom", "Could not unlock the user");
|
||||
}
|
||||
else
|
||||
{
|
||||
shouldReFetchMember = true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModelState.AddModelError("custom", ex);
|
||||
}
|
||||
contentItem.PersistedContent.IsLockedOut = false;
|
||||
contentItem.PersistedContent.FailedPasswordAttempts = 0;
|
||||
}
|
||||
else if (membershipUser.IsLockedOut == false && contentItem.IsLockedOut)
|
||||
else if (!contentItem.PersistedContent.IsLockedOut && isLockedOut)
|
||||
{
|
||||
//NOTE: This should not ever happen unless someone is mucking around with the request data.
|
||||
//An admin cannot simply lock a user, they get locked out by password attempts, but an admin can un-approve them
|
||||
ModelState.AddModelError("custom", "An admin cannot lock a user");
|
||||
}
|
||||
|
||||
//password changes ?
|
||||
//no password changes then exit ?
|
||||
if (contentItem.Password == null)
|
||||
{
|
||||
//If the provider has changed some values, these values need to be reflected in the member object
|
||||
//that will get mapped to the display object
|
||||
if (shouldReFetchMember)
|
||||
{
|
||||
RefetchMemberData(contentItem, LookupType.ByKey);
|
||||
RestoreProvidedUserName(contentItem, providedUserName);
|
||||
}
|
||||
return;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
var passwordChangeResult = Members.ChangePassword(membershipUser.UserName, contentItem.Password, _provider);
|
||||
if (passwordChangeResult.Success)
|
||||
{
|
||||
//If the provider has changed some values, these values need to be reflected in the member object
|
||||
//that will get mapped to the display object
|
||||
if (shouldReFetchMember)
|
||||
{
|
||||
RefetchMemberData(contentItem, LookupType.ByKey);
|
||||
RestoreProvidedUserName(contentItem, providedUserName);
|
||||
}
|
||||
|
||||
//even if we weren't resetting this, it is the correct value (null), otherwise if we were resetting then it will contain the new pword
|
||||
return passwordChangeResult.Result.ResetPassword;
|
||||
}
|
||||
|
||||
//it wasn't successful, so add the change error to the model state
|
||||
ModelState.AddPropertyError(
|
||||
passwordChangeResult.Result.ChangeError,
|
||||
string.Format("{0}password", Constants.PropertyEditors.InternalGenericPropertiesPrefix));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private enum LookupType
|
||||
{
|
||||
ByKey,
|
||||
ByUserName
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Re-fetches the database data to map to the PersistedContent object and re-assigns the already mapped the posted properties so that the display object is up-to-date
|
||||
/// </summary>
|
||||
/// <param name="contentItem"></param>
|
||||
/// <param name="lookup"></param>
|
||||
/// <remarks>
|
||||
/// This is done during an update if the membership provider has changed some underlying data - we need to ensure that our model is consistent with that data
|
||||
/// </remarks>
|
||||
private void RefetchMemberData(MemberSave contentItem, LookupType lookup)
|
||||
{
|
||||
var currProps = contentItem.PersistedContent.Properties.ToArray();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
UpdateName(contentItem);
|
||||
|
||||
// re-assign the mapped values that are not part of the membership provider properties.
|
||||
var builtInAliases = ConventionsHelper.GetStandardPropertyTypeStubs().Select(x => x.Key).ToArray();
|
||||
foreach (var p in contentItem.PersistedContent.Properties)
|
||||
{
|
||||
var valueMapped = currProps.FirstOrDefault(x => x.Alias == p.Alias);
|
||||
if (builtInAliases.Contains(p.Alias) == false && valueMapped != null)
|
||||
{
|
||||
p.SetValue(valueMapped.GetValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Following a refresh of member data called during an update if the membership provider has changed some underlying data,
|
||||
/// we don't want to lose the provided, and potentially changed, username
|
||||
/// </summary>
|
||||
/// <param name="contentItem"></param>
|
||||
/// <param name="providedUserName"></param>
|
||||
private static void RestoreProvidedUserName(MemberSave contentItem, string providedUserName)
|
||||
{
|
||||
contentItem.PersistedContent.Username = providedUserName;
|
||||
// set the password
|
||||
contentItem.PersistedContent.RawPasswordValue = PasswordSecurity.HashPasswordForStorage(contentItem.Password.NewPassword);
|
||||
}
|
||||
|
||||
private static void UpdateName(MemberSave memberSave)
|
||||
@@ -496,111 +354,48 @@ namespace Umbraco.Web.Editors
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is going to create the user with the membership provider and check for validation
|
||||
/// </summary>
|
||||
/// <param name="contentItem"></param>
|
||||
/// <param name="status"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// Depending on if the Umbraco membership provider is active or not, the process differs slightly:
|
||||
///
|
||||
/// * If the umbraco membership provider is used - we create the membership user first with the membership provider, since
|
||||
/// it's the umbraco membership provider, this writes to the umbraco tables. When that is complete we re-fetch the IMember
|
||||
/// model data from the db. In this case we don't care what the provider user key is.
|
||||
/// * If we're using a non-umbraco membership provider - we check if there is a 'Member' member type - if so
|
||||
/// we create an empty IMember instance first (of type 'Member'), this gives us a unique ID (GUID)
|
||||
/// that we then use to create the member in the custom membership provider. This acts as the link between Umbraco data and
|
||||
/// the custom membership provider data. This gives us the ability to eventually have custom membership properties but still use
|
||||
/// a custom membership provider. If there is no 'Member' member type, then we will simply just create the membership provider member
|
||||
/// with no link to our data.
|
||||
///
|
||||
/// If this is successful, it will go and re-fetch the IMember from the db because it will now have an ID because the Umbraco provider
|
||||
/// uses the umbraco data store - then of course we need to re-map it to the saved property values.
|
||||
/// </remarks>
|
||||
private MembershipUser CreateWithMembershipProvider(MemberSave contentItem, out MembershipCreateStatus status)
|
||||
// TODO: This logic should be pulled into the service layer
|
||||
private async Task<bool> ValidateMemberDataAsync(MemberSave contentItem)
|
||||
{
|
||||
MembershipUser membershipUser;
|
||||
|
||||
//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)
|
||||
if (contentItem.Name.IsNullOrWhiteSpace())
|
||||
{
|
||||
case MembershipCreateStatus.Success:
|
||||
|
||||
//map the key back
|
||||
contentItem.Key = membershipUser.ProviderUserKey.TryConvertTo<Guid>().Result;
|
||||
contentItem.PersistedContent.Key = contentItem.Key;
|
||||
|
||||
//if the comments are there then we need to save them
|
||||
if (contentItem.Comments.IsNullOrWhiteSpace() == false)
|
||||
{
|
||||
membershipUser.Comment = contentItem.Comments;
|
||||
_provider.UpdateUser(membershipUser);
|
||||
}
|
||||
|
||||
RefetchMemberData(contentItem, LookupType.ByUserName);
|
||||
|
||||
break;
|
||||
case MembershipCreateStatus.InvalidUserName:
|
||||
ModelState.AddPropertyError(
|
||||
ModelState.AddPropertyError(
|
||||
new ValidationResult("Invalid user name", new[] { "value" }),
|
||||
string.Format("{0}login", Constants.PropertyEditors.InternalGenericPropertiesPrefix));
|
||||
break;
|
||||
case MembershipCreateStatus.InvalidPassword:
|
||||
ModelState.AddPropertyError(
|
||||
new ValidationResult("Invalid password", new[] { "value" }),
|
||||
string.Format("{0}password", Constants.PropertyEditors.InternalGenericPropertiesPrefix));
|
||||
break;
|
||||
case MembershipCreateStatus.InvalidQuestion:
|
||||
case MembershipCreateStatus.InvalidAnswer:
|
||||
throw new NotSupportedException("Currently the member editor does not support providers that have RequiresQuestionAndAnswer specified");
|
||||
case MembershipCreateStatus.InvalidEmail:
|
||||
ModelState.AddPropertyError(
|
||||
new ValidationResult("Invalid email", new[] { "value" }),
|
||||
string.Format("{0}email", Constants.PropertyEditors.InternalGenericPropertiesPrefix));
|
||||
break;
|
||||
case MembershipCreateStatus.DuplicateUserName:
|
||||
ModelState.AddPropertyError(
|
||||
new ValidationResult("Username is already in use", new[] { "value" }),
|
||||
string.Format("{0}login", Constants.PropertyEditors.InternalGenericPropertiesPrefix));
|
||||
break;
|
||||
case MembershipCreateStatus.DuplicateEmail:
|
||||
ModelState.AddPropertyError(
|
||||
new ValidationResult("Email address is already in use", new[] { "value" }),
|
||||
string.Format("{0}email", Constants.PropertyEditors.InternalGenericPropertiesPrefix));
|
||||
break;
|
||||
case MembershipCreateStatus.InvalidProviderUserKey:
|
||||
ModelState.AddPropertyError(
|
||||
//specify 'default' just so that it shows up as a notification - is not assigned to a property
|
||||
new ValidationResult("Invalid provider user key"), "default");
|
||||
break;
|
||||
case MembershipCreateStatus.DuplicateProviderUserKey:
|
||||
ModelState.AddPropertyError(
|
||||
//specify 'default' just so that it shows up as a notification - is not assigned to a property
|
||||
new ValidationResult("Duplicate provider user key"), "default");
|
||||
break;
|
||||
case MembershipCreateStatus.ProviderError:
|
||||
case MembershipCreateStatus.UserRejected:
|
||||
ModelState.AddPropertyError(
|
||||
//specify 'default' just so that it shows up as a notification - is not assigned to a property
|
||||
new ValidationResult("User could not be created (rejected by provider)"), "default");
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
return false;
|
||||
}
|
||||
|
||||
return membershipUser;
|
||||
if (contentItem.Password != null && !contentItem.Password.NewPassword.IsNullOrWhiteSpace())
|
||||
{
|
||||
var validPassword = await PasswordSecurity.IsValidPasswordAsync(contentItem.Password.NewPassword);
|
||||
if (!validPassword)
|
||||
{
|
||||
ModelState.AddPropertyError(
|
||||
new ValidationResult("Invalid password: " + string.Join(", ", validPassword.Result), new[] { "value" }),
|
||||
string.Format("{0}password", Constants.PropertyEditors.InternalGenericPropertiesPrefix));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var byUsername = Services.MemberService.GetByUsername(contentItem.Username);
|
||||
if (byUsername != null && byUsername.Key != contentItem.Key)
|
||||
{
|
||||
ModelState.AddPropertyError(
|
||||
new ValidationResult("Username is already in use", new[] { "value" }),
|
||||
string.Format("{0}login", Constants.PropertyEditors.InternalGenericPropertiesPrefix));
|
||||
return false;
|
||||
}
|
||||
|
||||
var byEmail = Services.MemberService.GetByEmail(contentItem.Email);
|
||||
if (byEmail != null && byEmail.Key != contentItem.Key)
|
||||
{
|
||||
ModelState.AddPropertyError(
|
||||
new ValidationResult("Email address is already in use", new[] { "value" }),
|
||||
string.Format("{0}email", Constants.PropertyEditors.InternalGenericPropertiesPrefix));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -3,14 +3,10 @@ using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Web.Http;
|
||||
using System.Web.Security;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Models;
|
||||
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;
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Web.Http;
|
||||
using System.Web.Security;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Configuration;
|
||||
@@ -12,11 +11,9 @@ using Umbraco.Core.Dictionary;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Persistence;
|
||||
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;
|
||||
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using System.Web.Security;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Identity;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Models;
|
||||
using Umbraco.Web.Security;
|
||||
using IUser = Umbraco.Core.Models.Membership.IUser;
|
||||
@@ -18,14 +15,10 @@ namespace Umbraco.Web.Editors
|
||||
internal class PasswordChanger
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly IUserService _userService;
|
||||
private readonly HttpContextBase _httpContext;
|
||||
|
||||
public PasswordChanger(ILogger logger, IUserService userService, HttpContextBase httpContext)
|
||||
public PasswordChanger(ILogger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
_userService = userService;
|
||||
_httpContext = httpContext;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -45,55 +38,43 @@ namespace Umbraco.Web.Editors
|
||||
if (passwordModel == null) throw new ArgumentNullException(nameof(passwordModel));
|
||||
if (userMgr == null) throw new ArgumentNullException(nameof(userMgr));
|
||||
|
||||
//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.
|
||||
if (passwordModel.Reset.HasValue && passwordModel.Reset.Value)
|
||||
{
|
||||
//if it's the current user, the current user cannot reset their own password
|
||||
if (currentUser.Username == savingUser.Username)
|
||||
{
|
||||
return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Password reset is not allowed", new[] { "resetPassword" }) });
|
||||
}
|
||||
|
||||
//if the current user has access to reset/manually change the password
|
||||
if (currentUser.HasSectionAccess(Umbraco.Core.Constants.Applications.Users) == false)
|
||||
{
|
||||
return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("The current user is not authorized", new[] { "resetPassword" }) });
|
||||
}
|
||||
|
||||
//ok, we should be able to reset it
|
||||
var resetToken = await userMgr.GeneratePasswordResetTokenAsync(savingUser.Id);
|
||||
var newPass = passwordModel.NewPassword.IsNullOrWhiteSpace()
|
||||
? userMgr.GeneratePassword()
|
||||
: passwordModel.NewPassword;
|
||||
|
||||
var resetResult = await userMgr.ChangePasswordWithResetAsync(savingUser.Id, resetToken, newPass);
|
||||
|
||||
if (resetResult.Succeeded == false)
|
||||
{
|
||||
var errors = string.Join(". ", resetResult.Errors);
|
||||
_logger.Warn<PasswordChanger>("Could not reset user password {PasswordErrors}", errors);
|
||||
return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult(errors, new[] { "resetPassword" }) });
|
||||
}
|
||||
|
||||
return Attempt.Succeed(new PasswordChangedModel());
|
||||
}
|
||||
|
||||
//we're not resetting it so we need to try to change it.
|
||||
|
||||
if (passwordModel.NewPassword.IsNullOrWhiteSpace())
|
||||
{
|
||||
return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Cannot set an empty password", new[] { "value" }) });
|
||||
}
|
||||
|
||||
//we cannot arbitrarily change the password without knowing the old one and no old password was supplied - need to return an error
|
||||
//Are we just changing another user's password?
|
||||
if (passwordModel.OldPassword.IsNullOrWhiteSpace())
|
||||
{
|
||||
//if password retrieval is not enabled but there is no old password we cannot continue
|
||||
return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Password cannot be changed without the old password", new[] { "oldPassword" }) });
|
||||
//if it's the current user, the current user cannot reset their own password
|
||||
if (currentUser.Username == savingUser.Username)
|
||||
{
|
||||
return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Password reset is not allowed", new[] { "value" }) });
|
||||
}
|
||||
|
||||
//if the current user has access to reset/manually change the password
|
||||
if (currentUser.HasSectionAccess(Umbraco.Core.Constants.Applications.Users) == false)
|
||||
{
|
||||
return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("The current user is not authorized", new[] { "value" }) });
|
||||
}
|
||||
|
||||
//ok, we should be able to reset it
|
||||
var resetToken = await userMgr.GeneratePasswordResetTokenAsync(savingUser.Id);
|
||||
|
||||
var resetResult = await userMgr.ChangePasswordWithResetAsync(savingUser.Id, resetToken, passwordModel.NewPassword);
|
||||
|
||||
if (resetResult.Succeeded == false)
|
||||
{
|
||||
var errors = string.Join(". ", resetResult.Errors);
|
||||
_logger.Warn<PasswordChanger>("Could not reset user password {PasswordErrors}", errors);
|
||||
return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult(errors, new[] { "value" }) });
|
||||
}
|
||||
|
||||
return Attempt.Succeed(new PasswordChangedModel());
|
||||
}
|
||||
|
||||
//we're changing our own password...
|
||||
|
||||
//get the user
|
||||
var backOfficeIdentityUser = await userMgr.FindByIdAsync(savingUser.Id);
|
||||
if (backOfficeIdentityUser == null)
|
||||
@@ -120,179 +101,5 @@ namespace Umbraco.Web.Editors
|
||||
return Attempt.Succeed(new PasswordChangedModel());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes password for a member/user given the membership provider and the password change model
|
||||
/// </summary>
|
||||
/// <param name="username">The username of the user having their password changed</param>
|
||||
/// <param name="passwordModel"></param>
|
||||
/// <param name="membershipProvider"></param>
|
||||
/// <returns></returns>
|
||||
public Attempt<PasswordChangedModel> ChangePasswordWithMembershipProvider(
|
||||
string username,
|
||||
ChangingPasswordModel passwordModel,
|
||||
MembershipProvider membershipProvider)
|
||||
{
|
||||
var umbracoBaseProvider = membershipProvider as MembershipProviderBase;
|
||||
|
||||
// YES! It is completely insane how many options you have to take into account based on the membership provider. yikes!
|
||||
|
||||
if (passwordModel == null) throw new ArgumentNullException(nameof(passwordModel));
|
||||
if (membershipProvider == null) throw new ArgumentNullException(nameof(membershipProvider));
|
||||
|
||||
var backofficeUserManager = _httpContext.GetOwinContext().GetBackOfficeUserManager();
|
||||
var userId = -1;
|
||||
|
||||
if (backofficeUserManager == null)
|
||||
{
|
||||
// should never happen
|
||||
throw new InvalidOperationException("Could not resolve the back office user manager");
|
||||
}
|
||||
|
||||
var profile = _userService.GetProfileByUserName(username);
|
||||
if (profile != null)
|
||||
int.TryParse(profile.Id.ToString(), out userId);
|
||||
|
||||
//Are we resetting the password?
|
||||
//This flag indicates that either an admin user is changing another user's password without knowing the original password
|
||||
// or that the password needs to be reset to an auto-generated one.
|
||||
if (passwordModel.Reset.HasValue && passwordModel.Reset.Value)
|
||||
{
|
||||
//if a new password is supplied then it's an admin user trying to change another user's password without knowing the original password
|
||||
//this is only possible when using a membership provider if the membership provider supports AllowManuallyChangingPassword
|
||||
if (passwordModel.NewPassword.IsNullOrWhiteSpace() == false)
|
||||
{
|
||||
if (umbracoBaseProvider != null && umbracoBaseProvider.AllowManuallyChangingPassword)
|
||||
{
|
||||
//this provider allows manually changing the password without the old password, so we can just do it
|
||||
try
|
||||
{
|
||||
var result = umbracoBaseProvider.ChangePassword(username, string.Empty, passwordModel.NewPassword);
|
||||
|
||||
if (result && backofficeUserManager != null && userId >= 0)
|
||||
backofficeUserManager.RaisePasswordChangedEvent(userId);
|
||||
|
||||
return result == false
|
||||
? Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, invalid username or password", new[] { "value" }) })
|
||||
: Attempt.Succeed(new PasswordChangedModel());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warn<PasswordChanger>("Could not change member password", ex);
|
||||
return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, error: " + ex.Message + " (see log for full details)", new[] { "value" }) });
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Provider does not support manually changing passwords", new[] { "value" }) });
|
||||
}
|
||||
}
|
||||
|
||||
//we've made it here which means we need to generate a new password
|
||||
|
||||
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" }) });
|
||||
}
|
||||
if (membershipProvider.RequiresQuestionAndAnswer && passwordModel.Answer.IsNullOrWhiteSpace())
|
||||
{
|
||||
return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Password reset requires a password answer", new[] { "resetPassword" }) });
|
||||
}
|
||||
|
||||
//ok, we should be able to reset it
|
||||
try
|
||||
{
|
||||
var newPass = membershipProvider.ResetPassword(
|
||||
username,
|
||||
membershipProvider.RequiresQuestionAndAnswer ? passwordModel.Answer : null);
|
||||
|
||||
if (userId >= 0)
|
||||
backofficeUserManager.RaisePasswordResetEvent(userId);
|
||||
|
||||
//return the generated pword
|
||||
return Attempt.Succeed(new PasswordChangedModel { ResetPassword = newPass });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warn<PasswordChanger>(ex, "Could not reset member password");
|
||||
return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not reset password, error: " + ex.Message + " (see log for full details)", new[] { "resetPassword" }) });
|
||||
}
|
||||
}
|
||||
|
||||
//we're not resetting it so we need to try to change it.
|
||||
|
||||
if (passwordModel.NewPassword.IsNullOrWhiteSpace())
|
||||
{
|
||||
return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Cannot set an empty password", new[] { "value" }) });
|
||||
}
|
||||
|
||||
//without being able to retrieve the original password,
|
||||
//we cannot arbitrarily change the password without knowing the old one and no old password was supplied - need to return an error
|
||||
if (passwordModel.OldPassword.IsNullOrWhiteSpace() && membershipProvider.EnablePasswordRetrieval == false)
|
||||
{
|
||||
//if password retrieval is not enabled but there is no old password we cannot continue
|
||||
return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Password cannot be changed without the old password", new[] { "oldPassword" }) });
|
||||
}
|
||||
|
||||
if (passwordModel.OldPassword.IsNullOrWhiteSpace() == false)
|
||||
{
|
||||
//if an old password is supplied try to change it
|
||||
|
||||
try
|
||||
{
|
||||
var result = membershipProvider.ChangePassword(username, passwordModel.OldPassword, passwordModel.NewPassword);
|
||||
|
||||
if (result && backofficeUserManager != null && userId >= 0)
|
||||
backofficeUserManager.RaisePasswordChangedEvent(userId);
|
||||
|
||||
return result == false
|
||||
? Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, invalid username or password", new[] { "oldPassword" }) })
|
||||
: Attempt.Succeed(new PasswordChangedModel());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warn<PasswordChanger>(ex, "Could not change member password");
|
||||
return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, error: " + ex.Message + " (see log for full details)", new[] { "value" }) });
|
||||
}
|
||||
}
|
||||
|
||||
if (membershipProvider.EnablePasswordRetrieval == false)
|
||||
{
|
||||
//we cannot continue if we cannot get the current password
|
||||
return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Password cannot be changed without the old password", new[] { "oldPassword" }) });
|
||||
}
|
||||
if (membershipProvider.RequiresQuestionAndAnswer && passwordModel.Answer.IsNullOrWhiteSpace())
|
||||
{
|
||||
//if the question answer is required but there isn't one, we cannot continue
|
||||
return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Password cannot be changed without the password answer", new[] { "value" }) });
|
||||
}
|
||||
|
||||
//lets try to get the old one so we can change it
|
||||
try
|
||||
{
|
||||
var oldPassword = membershipProvider.GetPassword(
|
||||
username,
|
||||
membershipProvider.RequiresQuestionAndAnswer ? passwordModel.Answer : null);
|
||||
|
||||
try
|
||||
{
|
||||
var result = membershipProvider.ChangePassword(username, oldPassword, passwordModel.NewPassword);
|
||||
return result == false
|
||||
? Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password", new[] { "value" }) })
|
||||
: Attempt.Succeed(new PasswordChangedModel());
|
||||
}
|
||||
catch (Exception ex1)
|
||||
{
|
||||
_logger.Warn<PasswordChanger>(ex1, "Could not change member password");
|
||||
return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, error: " + ex1.Message + " (see log for full details)", new[] { "value" }) });
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception ex2)
|
||||
{
|
||||
_logger.Warn<PasswordChanger>(ex2, "Could not retrieve member password");
|
||||
return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, error: " + ex2.Message + " (see log for full details)", new[] { "value" }) });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -551,7 +551,7 @@ namespace Umbraco.Web.Editors
|
||||
|
||||
if (userSave.ChangePassword != null)
|
||||
{
|
||||
var passwordChanger = new PasswordChanger(Logger, Services.UserService, UmbracoContext.HttpContext);
|
||||
var passwordChanger = new PasswordChanger(Logger);
|
||||
|
||||
//this will change the password and raise appropriate events
|
||||
var passwordChangeResult = await passwordChanger.ChangePasswordWithIdentityAsync(Security.CurrentUser, found, userSave.ChangePassword, UserManager);
|
||||
|
||||
@@ -4,7 +4,6 @@ using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using System.Web.Security;
|
||||
using Newtonsoft.Json;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Composing;
|
||||
@@ -14,7 +13,6 @@ 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
|
||||
{
|
||||
|
||||
@@ -19,35 +19,5 @@ namespace Umbraco.Web.Models
|
||||
[DataMember(Name = "oldPassword")]
|
||||
public string OldPassword { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Set to true if the password is to be reset
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This operator is different between using ASP.NET Identity APIs and Membership APIs.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// When using Membership APIs, this is only valid when: EnablePasswordReset = true and it will reset the password to something auto generated.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// When using ASP.NET Identity APIs this needs to be set if an administrator user that has access to the Users section is changing another users
|
||||
/// password. This flag is required to indicate that the oldPassword value is not required and that we are in fact performing a password reset and
|
||||
/// then a password change if the executing user has access to do so.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
[DataMember(Name = "reset")]
|
||||
public bool? Reset { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The password answer - required for reset when: RequiresQuestionAndAnswer = true
|
||||
/// </summary>
|
||||
[DataMember(Name = "answer")]
|
||||
public string Answer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// This is filled in on the server side if the password has been reset/generated
|
||||
/// </summary>
|
||||
[DataMember(Name = "generatedPassword")]
|
||||
public string GeneratedPassword { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Umbraco.Web.Models.ContentEditing
|
||||
{
|
||||
@@ -12,24 +8,11 @@ namespace Umbraco.Web.Models.ContentEditing
|
||||
[DataContract(Name = "content", Namespace = "")]
|
||||
public class MemberDisplay : ListViewAwareContentItemDisplayBase<ContentPropertyDisplay>
|
||||
{
|
||||
public MemberDisplay()
|
||||
{
|
||||
MemberProviderFieldMapping = new Dictionary<string, string>();
|
||||
}
|
||||
|
||||
[DataMember(Name = "username")]
|
||||
public string Username { get; set; }
|
||||
|
||||
[DataMember(Name = "email")]
|
||||
public string Email { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 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,
|
||||
/// or if we are editing a member that is not an Umbraco member (custom provider)
|
||||
/// </summary>
|
||||
[DataMember(Name = "fieldConfig")]
|
||||
public IDictionary<string, string> MemberProviderFieldMapping { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Editors;
|
||||
using Umbraco.Core.Models.Validation;
|
||||
using Umbraco.Web.WebApi.Filters;
|
||||
using Umbraco.Core;
|
||||
|
||||
namespace Umbraco.Web.Models.ContentEditing
|
||||
{
|
||||
@@ -19,6 +22,7 @@ namespace Umbraco.Web.Models.ContentEditing
|
||||
|
||||
[DataMember(Name = "email", IsRequired = true)]
|
||||
[RequiredForPersistence(AllowEmptyStrings = false, ErrorMessage = "Required")]
|
||||
[EmailAddress]
|
||||
public string Email { get; set; }
|
||||
|
||||
[DataMember(Name = "password")]
|
||||
@@ -27,16 +31,27 @@ namespace Umbraco.Web.Models.ContentEditing
|
||||
[DataMember(Name = "memberGroups")]
|
||||
public IEnumerable<string> Groups { get; set; }
|
||||
|
||||
[DataMember(Name = "comments")]
|
||||
public string Comments { get; set; }
|
||||
/// <summary>
|
||||
/// Returns the value from the Comments property
|
||||
/// </summary>
|
||||
public string Comments => GetPropertyValue<string>(Constants.Conventions.Member.Comments);
|
||||
|
||||
[DataMember(Name = "isLockedOut")]
|
||||
public bool IsLockedOut { get; set; }
|
||||
/// <summary>
|
||||
/// Returns the value from the IsLockedOut property
|
||||
/// </summary>
|
||||
public bool IsLockedOut => GetPropertyValue<bool>(Constants.Conventions.Member.IsLockedOut);
|
||||
|
||||
[DataMember(Name = "isApproved")]
|
||||
public bool IsApproved { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the value from the IsApproved property
|
||||
/// </summary>
|
||||
public bool IsApproved => GetPropertyValue<bool>(Constants.Conventions.Member.IsApproved);
|
||||
|
||||
// TODO: Need to add question / answer support
|
||||
private T GetPropertyValue<T>(string alias)
|
||||
{
|
||||
var prop = Properties.FirstOrDefault(x => x.Alias == alias);
|
||||
if (prop == null) return default;
|
||||
var converted = prop.Value.TryConvertTo<T>();
|
||||
return converted.ResultOr(default);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Web.Security;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Security;
|
||||
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
|
||||
{
|
||||
@@ -32,43 +22,11 @@ namespace Umbraco.Web.Models.Mapping
|
||||
|
||||
public void DefineMaps(UmbracoMapper mapper)
|
||||
{
|
||||
mapper.Define<MembershipUser, MemberDisplay>((source, context) => new MemberDisplay(), Map);
|
||||
mapper.Define<MembershipUser, IMember>((source, context) => MemberService.CreateGenericMembershipProviderMember(source.UserName, source.Email, source.UserName, ""), Map);
|
||||
mapper.Define<IMember, MemberDisplay>((source, context) => new MemberDisplay(), Map);
|
||||
mapper.Define<IMember, MemberBasic>((source, context) => new MemberBasic(), Map);
|
||||
mapper.Define<MembershipUser, MemberBasic>((source, context) => new MemberBasic(), Map);
|
||||
mapper.Define<IMemberGroup, MemberGroupDisplay>((source, context) => new MemberGroupDisplay(), Map);
|
||||
mapper.Define<IMember, ContentPropertyCollectionDto>((source, context) => new ContentPropertyCollectionDto(), Map);
|
||||
}
|
||||
|
||||
private void Map(MembershipUser source, MemberDisplay target, MapperContext context)
|
||||
{
|
||||
//first convert to IMember
|
||||
var member = context.Map<IMember>(source);
|
||||
//then convert to MemberDisplay
|
||||
context.Map<IMember, MemberDisplay>(member);
|
||||
}
|
||||
|
||||
// TODO: SD: I can't remember why this mapping is here?
|
||||
// Umbraco.Code.MapAll -Properties -CreatorId -Level -Name -CultureInfos -ParentId
|
||||
// Umbraco.Code.MapAll -Path -SortOrder -DeleteDate -WriterId -VersionId -PasswordQuestion
|
||||
// Umbraco.Code.MapAll -RawPasswordAnswerValue -FailedPasswordAttempts
|
||||
private void Map(MembershipUser source, IMember target, MapperContext context)
|
||||
{
|
||||
target.Comments = source.Comment;
|
||||
target.CreateDate = source.CreationDate;
|
||||
target.Email = source.Email;
|
||||
target.Id = int.MaxValue;
|
||||
target.IsApproved = source.IsApproved;
|
||||
target.IsLockedOut = source.IsLockedOut;
|
||||
target.Key = source.ProviderUserKey.TryConvertTo<Guid>().Result;
|
||||
target.LastLockoutDate = source.LastLockoutDate;
|
||||
target.LastLoginDate = source.LastLoginDate;
|
||||
target.LastPasswordChangeDate = source.LastPasswordChangedDate;
|
||||
target.RawPasswordValue = source.CreationDate > DateTime.MinValue ? Guid.NewGuid().ToString("N") : "";
|
||||
target.UpdateDate = source.LastActivityDate;
|
||||
target.Username = source.UserName;
|
||||
}
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -Properties -Errors -Edited -Updater -Alias -IsChildOfListView
|
||||
// Umbraco.Code.MapAll -Trashed -IsContainer -VariesByCulture
|
||||
@@ -82,7 +40,6 @@ namespace Umbraco.Web.Models.Mapping
|
||||
target.Icon = source.ContentType.Icon;
|
||||
target.Id = source.Id;
|
||||
target.Key = source.Key;
|
||||
target.MemberProviderFieldMapping = GetMemberProviderFieldMapping();
|
||||
target.Name = source.Name;
|
||||
target.Owner = _commonMapper.GetOwner(source, context);
|
||||
target.ParentId = source.ParentId;
|
||||
@@ -118,23 +75,6 @@ namespace Umbraco.Web.Models.Mapping
|
||||
target.Username = source.Username;
|
||||
}
|
||||
|
||||
//TODO: SD: I can't remember why this mapping is here?
|
||||
// Umbraco.Code.MapAll -Udi -Properties -ParentId -Path -SortOrder -Edited -Updater
|
||||
// Umbraco.Code.MapAll -Trashed -Alias -ContentTypeId -ContentTypeAlias -VariesByCulture
|
||||
private void Map(MembershipUser source, MemberBasic target, MapperContext context)
|
||||
{
|
||||
target.CreateDate = source.CreationDate;
|
||||
target.Email = source.Email;
|
||||
target.Icon = Constants.Icons.Member;
|
||||
target.Id = int.MaxValue;
|
||||
target.Key = source.ProviderUserKey.TryConvertTo<Guid>().Result;
|
||||
target.Name = source.UserName;
|
||||
target.Owner = new UserProfile { Name = "Admin", UserId = -1 };
|
||||
target.State = ContentSavedState.Draft;
|
||||
target.UpdateDate = source.LastActivityDate;
|
||||
target.Username = source.UserName;
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -Icon -Trashed -ParentId -Alias
|
||||
private void Map(IMemberGroup source, MemberGroupDisplay target, MapperContext context)
|
||||
{
|
||||
@@ -151,18 +91,5 @@ namespace Umbraco.Web.Models.Mapping
|
||||
target.Properties = context.MapEnumerable<IProperty, ContentPropertyDto>(source.Properties);
|
||||
}
|
||||
|
||||
private static IDictionary<string, string> GetMemberProviderFieldMapping()
|
||||
{
|
||||
var provider = MembershipProviderExtensions.GetMembersMembershipProvider();
|
||||
|
||||
var umbracoProvider = (IUmbracoMemberTypeMembershipProvider)provider;
|
||||
|
||||
return new Dictionary<string, string>
|
||||
{
|
||||
{Constants.Conventions.Member.IsLockedOut, umbracoProvider.LockPropertyTypeAlias},
|
||||
{Constants.Conventions.Member.IsApproved, umbracoProvider.ApprovedPropertyTypeAlias},
|
||||
{Constants.Conventions.Member.Comments, umbracoProvider.CommentPropertyTypeAlias}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Web.Security;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Umbraco.Core.Dictionary;
|
||||
using Umbraco.Web.Security;
|
||||
using Umbraco.Web.Security.Providers;
|
||||
using Umbraco.Core.Configuration;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
@@ -29,22 +27,25 @@ namespace Umbraco.Web.Models.Mapping
|
||||
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
|
||||
private readonly ILocalizedTextService _localizedTextService;
|
||||
private readonly IMemberTypeService _memberTypeService;
|
||||
private readonly IUserService _userService;
|
||||
private readonly IMemberService _memberService;
|
||||
private readonly IMemberGroupService _memberGroupService;
|
||||
private readonly IMemberPasswordConfiguration _memberPasswordConfiguration;
|
||||
|
||||
public MemberTabsAndPropertiesMapper(ICultureDictionary cultureDictionary, IUmbracoContextAccessor umbracoContextAccessor, ILocalizedTextService localizedTextService, IUserService userService, IMemberTypeService memberTypeService)
|
||||
public MemberTabsAndPropertiesMapper(ICultureDictionary cultureDictionary, IUmbracoContextAccessor umbracoContextAccessor, ILocalizedTextService localizedTextService, IMemberTypeService memberTypeService, IMemberService memberService, IMemberGroupService memberGroupService, IMemberPasswordConfiguration memberPasswordConfiguration)
|
||||
: base(cultureDictionary, localizedTextService)
|
||||
{
|
||||
_umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor));
|
||||
_localizedTextService = localizedTextService ?? throw new ArgumentNullException(nameof(localizedTextService));
|
||||
_userService = userService ?? throw new ArgumentNullException(nameof(userService));
|
||||
_memberTypeService = memberTypeService ?? throw new ArgumentNullException(nameof(memberTypeService));
|
||||
_memberService = memberService ?? throw new ArgumentNullException(nameof(memberService));
|
||||
_memberGroupService = memberGroupService ?? throw new ArgumentNullException(nameof(memberGroupService));
|
||||
_memberPasswordConfiguration = memberPasswordConfiguration;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <remarks>Overridden to deal with custom member properties and permissions.</remarks>
|
||||
public override IEnumerable<Tab<ContentPropertyDisplay>> Map(IMember source, MapperContext context)
|
||||
{
|
||||
var provider = MembershipProviderExtensions.GetMembersMembershipProvider();
|
||||
|
||||
var memberType = _memberTypeService.Get(source.ContentTypeId);
|
||||
|
||||
@@ -55,12 +56,10 @@ namespace Umbraco.Web.Models.Mapping
|
||||
|
||||
var resolved = base.Map(source, context);
|
||||
|
||||
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);
|
||||
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";
|
||||
@@ -96,7 +95,6 @@ namespace Umbraco.Web.Models.Mapping
|
||||
protected override IEnumerable<ContentPropertyDisplay> GetCustomGenericProperties(IContentBase content)
|
||||
{
|
||||
var member = (IMember)content;
|
||||
var membersProvider = MembershipProviderExtensions.GetMembersMembershipProvider();
|
||||
|
||||
var genericProperties = new List<ContentPropertyDisplay>
|
||||
{
|
||||
@@ -127,18 +125,16 @@ namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}password",
|
||||
Label = _localizedTextService.Localize("password"),
|
||||
// NOTE: The value here is a json value - but the only property we care about is the generatedPassword one if it exists, the newPassword exists
|
||||
// only when creating a new member and we want to have a generated password pre-filled.
|
||||
|
||||
Value = new Dictionary<string, object>
|
||||
{
|
||||
// TODO: why ignoreCase, what are we doing here?!
|
||||
{"generatedPassword", member.GetAdditionalDataValueIgnoreCase("GeneratedPassword", null)},
|
||||
// TODO: why ignoreCase, what are we doing here?!
|
||||
{"newPassword", member.GetAdditionalDataValueIgnoreCase("NewPassword", null)},
|
||||
},
|
||||
// TODO: Hard coding this because the changepassword doesn't necessarily need to be a resolvable (real) property editor
|
||||
View = "changepassword",
|
||||
// initialize the dictionary with the configuration from the default membership provider
|
||||
Config = GetPasswordConfig(membersProvider, member)
|
||||
Config = GetPasswordConfig(member)
|
||||
},
|
||||
new ContentPropertyDisplay
|
||||
{
|
||||
@@ -153,16 +149,17 @@ namespace Umbraco.Web.Models.Mapping
|
||||
return genericProperties;
|
||||
}
|
||||
|
||||
private Dictionary<string, object> GetPasswordConfig(MembersMembershipProvider membersProvider, IMember member)
|
||||
private Dictionary<string, object> GetPasswordConfig(IMember member)
|
||||
{
|
||||
var result = new Dictionary<string, object>(membersProvider.PasswordConfiguration.GetConfiguration())
|
||||
var result = new Dictionary<string, object>(_memberPasswordConfiguration.GetConfiguration(true))
|
||||
{
|
||||
// the password change toggle will only be displayed if there is already a password assigned.
|
||||
{"hasPassword", member.RawPasswordValue.IsNullOrWhiteSpace() == false}
|
||||
};
|
||||
|
||||
result["enableReset"] = membersProvider.CanResetPassword(_userService);
|
||||
result["allowManuallyChangingPassword"] = membersProvider.AllowManuallyChangingPassword;
|
||||
// This will always be true for members since we always want to allow admins to change a password - so long as that
|
||||
// user has access to edit members (but that security is taken care of separately)
|
||||
result["allowManuallyChangingPassword"] = true;
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -230,12 +227,13 @@ namespace Umbraco.Web.Models.Mapping
|
||||
return prop;
|
||||
}
|
||||
|
||||
internal static IDictionary<string, bool> GetMemberGroupValue(string username)
|
||||
internal IDictionary<string, bool> GetMemberGroupValue(string username)
|
||||
{
|
||||
var userRoles = username.IsNullOrWhiteSpace() ? null : Roles.GetRolesForUser(username);
|
||||
var userRoles = username.IsNullOrWhiteSpace() ? null : _memberService.GetAllRoles(username);
|
||||
|
||||
// create a dictionary of all roles (except internal roles) + "false"
|
||||
var result = Roles.GetAllRoles().Distinct()
|
||||
var result = _memberGroupService.GetAll()
|
||||
.Select(x => x.Name)
|
||||
// if a role starts with __umbracoRole we won't show it as it's an internal role used for public access
|
||||
.Where(x => x.StartsWith(Constants.Conventions.Member.InternalRolePrefix) == false)
|
||||
.OrderBy(x => x, StringComparer.OrdinalIgnoreCase)
|
||||
|
||||
@@ -14,7 +14,8 @@ namespace Umbraco.Web
|
||||
/// <param name="userService"></param>
|
||||
/// <returns></returns>
|
||||
public static IDictionary<string, object> GetConfiguration(
|
||||
this IPasswordConfiguration passwordConfiguration)
|
||||
this IPasswordConfiguration passwordConfiguration,
|
||||
bool allowManuallyChangingPassword = false)
|
||||
{
|
||||
return new Dictionary<string, object>
|
||||
{
|
||||
@@ -24,15 +25,9 @@ namespace Umbraco.Web
|
||||
// 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}, // TODO: Actually, this is still used for the member editor, see MemberTabsAndPropertiesMapper.GetPasswordConfig, need to remove that eventually
|
||||
{"enablePasswordRetrieval", false},
|
||||
{"requiresQuestionAnswer", false}
|
||||
|
||||
|
||||
// A flag to indicate if the current password box should be shown or not, only a user that has access to change other user/member passwords
|
||||
// doesn't have to specify the current password for the user/member. A user changing their own password must specify their current password.
|
||||
{"allowManuallyChangingPassword", allowManuallyChangingPassword},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Web.Security;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Configuration.UmbracoSettings;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
|
||||
@@ -43,7 +43,6 @@ namespace Umbraco.Web.Security
|
||||
IGlobalSettings globalSettings,
|
||||
// TODO: This could probably be optional?
|
||||
IPasswordConfiguration passwordConfiguration,
|
||||
IPasswordGenerator passwordGenerator,
|
||||
IIpResolver ipResolver)
|
||||
{
|
||||
if (services == null) throw new ArgumentNullException(nameof(services));
|
||||
@@ -59,7 +58,6 @@ namespace Umbraco.Web.Security
|
||||
contentSettings,
|
||||
globalSettings,
|
||||
passwordConfiguration,
|
||||
passwordGenerator,
|
||||
ipResolver));
|
||||
|
||||
app.SetBackOfficeUserManagerType<BackOfficeUserManager, BackOfficeIdentityUser>();
|
||||
@@ -85,7 +83,6 @@ namespace Umbraco.Web.Security
|
||||
BackOfficeUserStore customUserStore,
|
||||
// TODO: This could probably be optional?
|
||||
IPasswordConfiguration passwordConfiguration,
|
||||
IPasswordGenerator passwordGenerator,
|
||||
IIpResolver ipResolver)
|
||||
{
|
||||
if (runtimeState == null) throw new ArgumentNullException(nameof(runtimeState));
|
||||
@@ -98,7 +95,6 @@ namespace Umbraco.Web.Security
|
||||
customUserStore,
|
||||
contentSettings,
|
||||
passwordConfiguration,
|
||||
passwordGenerator,
|
||||
ipResolver));
|
||||
|
||||
app.SetBackOfficeUserManagerType<BackOfficeUserManager, BackOfficeIdentityUser>();
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using System.Web.Security;
|
||||
using Microsoft.AspNet.Identity;
|
||||
using Microsoft.AspNet.Identity.Owin;
|
||||
using Microsoft.Owin.Security.DataProtection;
|
||||
@@ -30,9 +28,8 @@ namespace Umbraco.Web.Security
|
||||
IdentityFactoryOptions<BackOfficeUserManager> options,
|
||||
IContentSection contentSectionConfig,
|
||||
IPasswordConfiguration passwordConfiguration,
|
||||
IPasswordGenerator passwordGenerator,
|
||||
IIpResolver ipResolver)
|
||||
: base(store, passwordConfiguration, passwordGenerator, ipResolver)
|
||||
: base(store, passwordConfiguration, ipResolver)
|
||||
{
|
||||
if (options == null) throw new ArgumentNullException("options");
|
||||
InitUserManager(this, passwordConfiguration, options.DataProtectionProvider, contentSectionConfig);
|
||||
@@ -60,7 +57,6 @@ namespace Umbraco.Web.Security
|
||||
IContentSection contentSectionConfig,
|
||||
IGlobalSettings globalSettings,
|
||||
IPasswordConfiguration passwordConfiguration,
|
||||
IPasswordGenerator passwordGenerator,
|
||||
IIpResolver ipResolver)
|
||||
{
|
||||
if (options == null) throw new ArgumentNullException("options");
|
||||
@@ -68,7 +64,7 @@ namespace Umbraco.Web.Security
|
||||
if (externalLoginService == null) throw new ArgumentNullException("externalLoginService");
|
||||
|
||||
var store = new BackOfficeUserStore(userService, entityService, externalLoginService, globalSettings, mapper);
|
||||
var manager = new BackOfficeUserManager(store, options, contentSectionConfig, passwordConfiguration, passwordGenerator, ipResolver);
|
||||
var manager = new BackOfficeUserManager(store, options, contentSectionConfig, passwordConfiguration, ipResolver);
|
||||
return manager;
|
||||
}
|
||||
|
||||
@@ -85,10 +81,9 @@ namespace Umbraco.Web.Security
|
||||
BackOfficeUserStore customUserStore,
|
||||
IContentSection contentSectionConfig,
|
||||
IPasswordConfiguration passwordConfiguration,
|
||||
IPasswordGenerator passwordGenerator,
|
||||
IIpResolver ipResolver)
|
||||
{
|
||||
var manager = new BackOfficeUserManager(customUserStore, options, contentSectionConfig, passwordConfiguration, passwordGenerator, ipResolver);
|
||||
var manager = new BackOfficeUserManager(customUserStore, options, contentSectionConfig, passwordConfiguration, ipResolver);
|
||||
return manager;
|
||||
}
|
||||
#endregion
|
||||
@@ -102,14 +97,14 @@ namespace Umbraco.Web.Security
|
||||
public class BackOfficeUserManager<T> : UserManager<T, int>
|
||||
where T : BackOfficeIdentityUser
|
||||
{
|
||||
private PasswordGenerator _passwordGenerator;
|
||||
|
||||
public BackOfficeUserManager(IUserStore<T, int> store,
|
||||
IPasswordConfiguration passwordConfiguration,
|
||||
IPasswordGenerator passwordGenerator,
|
||||
IIpResolver ipResolver)
|
||||
: base(store)
|
||||
{
|
||||
PasswordConfiguration = passwordConfiguration;
|
||||
PasswordGenerator = passwordGenerator;
|
||||
IpResolver = ipResolver;
|
||||
}
|
||||
|
||||
@@ -150,24 +145,6 @@ namespace Umbraco.Web.Security
|
||||
return userIdentity;
|
||||
}
|
||||
|
||||
///// <summary>
|
||||
///// Initializes the user manager with the correct options
|
||||
///// </summary>
|
||||
///// <param name="manager"></param>
|
||||
///// <param name="passwordConfig"></param>
|
||||
///// <param name="contentSectionConfig"></param>
|
||||
///// <param name="options"></param>
|
||||
///// <returns></returns>
|
||||
//protected void InitUserManager(
|
||||
// BackOfficeUserManager manager,
|
||||
// IPasswordConfiguration passwordConfig,
|
||||
// IContentSection contentSectionConfig,
|
||||
// IdentityFactoryOptions<BackOfficeUserManager> options)
|
||||
//{
|
||||
// //NOTE: This method is mostly here for backwards compat
|
||||
// base.InitUserManager(manager, passwordConfig, options.DataProtectionProvider, contentSectionConfig);
|
||||
//}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the user manager with the correct options
|
||||
/// </summary>
|
||||
@@ -265,7 +242,6 @@ namespace Umbraco.Web.Security
|
||||
/// </summary>
|
||||
public IBackOfficeUserPasswordChecker BackOfficeUserPasswordChecker { get; set; }
|
||||
public IPasswordConfiguration PasswordConfiguration { get; }
|
||||
public IPasswordGenerator PasswordGenerator { get; }
|
||||
public IIpResolver IpResolver { get; }
|
||||
|
||||
/// <summary>
|
||||
@@ -274,7 +250,8 @@ namespace Umbraco.Web.Security
|
||||
/// <returns></returns>
|
||||
public string GeneratePassword()
|
||||
{
|
||||
var password = PasswordGenerator.GeneratePassword(PasswordConfiguration);
|
||||
if (_passwordGenerator == null) _passwordGenerator = new PasswordGenerator(PasswordConfiguration);
|
||||
var password = _passwordGenerator.GeneratePassword();
|
||||
return password;
|
||||
}
|
||||
|
||||
@@ -348,8 +325,6 @@ namespace Umbraco.Web.Security
|
||||
public override Task<IdentityResult> ResetPasswordAsync(int userId, string token, string newPassword)
|
||||
{
|
||||
var result = base.ResetPasswordAsync(userId, token, newPassword);
|
||||
if (result.Result.Succeeded)
|
||||
RaisePasswordResetEvent(userId);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -585,12 +560,6 @@ namespace Umbraco.Web.Security
|
||||
OnPasswordChanged(new IdentityAuditEventArgs(AuditEvent.PasswordChanged, IpResolver.GetCurrentRequestIpAddress(), affectedUser: userId));
|
||||
}
|
||||
|
||||
// TODO: I don't think this is required anymore since from 7.7 we no longer display the reset password checkbox since that didn't make sense.
|
||||
internal void RaisePasswordResetEvent(int userId)
|
||||
{
|
||||
OnPasswordReset(new IdentityAuditEventArgs(AuditEvent.PasswordReset, IpResolver.GetCurrentRequestIpAddress(), affectedUser: userId));
|
||||
}
|
||||
|
||||
internal void RaiseResetAccessFailedCountEvent(int userId)
|
||||
{
|
||||
OnResetAccessFailedCount(new IdentityAuditEventArgs(AuditEvent.ResetAccessFailedCount, IpResolver.GetCurrentRequestIpAddress(), affectedUser: userId));
|
||||
|
||||
@@ -16,6 +16,7 @@ using Umbraco.Core.Models.PublishedContent;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Editors;
|
||||
using Umbraco.Web.Security.Providers;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Umbraco.Web.Security
|
||||
{
|
||||
@@ -28,7 +29,6 @@ namespace Umbraco.Web.Security
|
||||
private readonly RoleProvider _roleProvider;
|
||||
private readonly IMemberService _memberService;
|
||||
private readonly IMemberTypeService _memberTypeService;
|
||||
private readonly IUserService _userService;
|
||||
private readonly IPublicAccessService _publicAccessService;
|
||||
private readonly AppCaches _appCaches;
|
||||
private readonly ILogger _logger;
|
||||
@@ -43,7 +43,6 @@ namespace Umbraco.Web.Security
|
||||
RoleProvider roleProvider,
|
||||
IMemberService memberService,
|
||||
IMemberTypeService memberTypeService,
|
||||
IUserService userService,
|
||||
IPublicAccessService publicAccessService,
|
||||
AppCaches appCaches,
|
||||
ILogger logger
|
||||
@@ -53,7 +52,6 @@ namespace Umbraco.Web.Security
|
||||
MemberCache = memberCache;
|
||||
_memberService = memberService;
|
||||
_memberTypeService = memberTypeService;
|
||||
_userService = userService;
|
||||
_publicAccessService = publicAccessService;
|
||||
_appCaches = appCaches;
|
||||
_logger = logger;
|
||||
@@ -645,8 +643,8 @@ namespace Umbraco.Web.Security
|
||||
/// <returns></returns>
|
||||
public virtual Attempt<PasswordChangedModel> ChangePassword(string username, ChangingPasswordModel passwordModel, MembershipProvider membershipProvider)
|
||||
{
|
||||
var passwordChanger = new PasswordChanger(_logger, _userService, HttpContext);
|
||||
return passwordChanger.ChangePasswordWithMembershipProvider(username, passwordModel, membershipProvider);
|
||||
var passwordChanger = new PasswordChanger(_logger);
|
||||
return ChangePasswordWithMembershipProvider(username, passwordModel, membershipProvider);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -735,5 +733,63 @@ namespace Umbraco.Web.Security
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes password for a member/user given the membership provider and the password change model
|
||||
/// </summary>
|
||||
/// <param name="username">The username of the user having their password changed</param>
|
||||
/// <param name="passwordModel"></param>
|
||||
/// <param name="membershipProvider"></param>
|
||||
/// <returns></returns>
|
||||
private Attempt<PasswordChangedModel> ChangePasswordWithMembershipProvider(
|
||||
string username,
|
||||
ChangingPasswordModel passwordModel,
|
||||
MembershipProvider membershipProvider)
|
||||
{
|
||||
var umbracoBaseProvider = membershipProvider as MembershipProviderBase;
|
||||
|
||||
// YES! It is completely insane how many options you have to take into account based on the membership provider. yikes!
|
||||
|
||||
if (passwordModel == null) throw new ArgumentNullException(nameof(passwordModel));
|
||||
if (membershipProvider == null) throw new ArgumentNullException(nameof(membershipProvider));
|
||||
var userId = -1;
|
||||
|
||||
|
||||
//we're not resetting it so we need to try to change it.
|
||||
|
||||
if (passwordModel.NewPassword.IsNullOrWhiteSpace())
|
||||
{
|
||||
return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Cannot set an empty password", new[] { "value" }) });
|
||||
}
|
||||
|
||||
if (membershipProvider.EnablePasswordRetrieval)
|
||||
{
|
||||
return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Membership providers using encrypted passwords and password retrieval are not supported", new[] { "value" }) });
|
||||
}
|
||||
|
||||
//without being able to retrieve the original password
|
||||
if (passwordModel.OldPassword.IsNullOrWhiteSpace())
|
||||
{
|
||||
//if password retrieval is not enabled but there is no old password we cannot continue
|
||||
return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Password cannot be changed without the old password", new[] { "oldPassword" }) });
|
||||
}
|
||||
|
||||
//if an old password is supplied try to change it
|
||||
|
||||
try
|
||||
{
|
||||
var result = membershipProvider.ChangePassword(username, passwordModel.OldPassword, passwordModel.NewPassword);
|
||||
|
||||
return result == false
|
||||
? Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, invalid username or password", new[] { "oldPassword" }) })
|
||||
: Attempt.Succeed(new PasswordChangedModel());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warn<PasswordChanger>(ex, "Could not change member password");
|
||||
return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, error: " + ex.Message + " (see log for full details)", new[] { "value" }) });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,16 +52,6 @@ namespace Umbraco.Web.Security
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Providers can override this setting, by default this is false which means that the provider will
|
||||
/// authenticate the username + password when ChangePassword is called. This property exists purely for
|
||||
/// backwards compatibility.
|
||||
/// </summary>
|
||||
public virtual bool AllowManuallyChangingPassword
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the raw password value for a given user
|
||||
/// </summary>
|
||||
@@ -307,24 +297,10 @@ namespace Umbraco.Web.Security
|
||||
/// <returns>
|
||||
/// true if the password was updated successfully; otherwise, false.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// Checks to ensure the AllowManuallyChangingPassword rule is adhered to
|
||||
/// </remarks>
|
||||
public override bool ChangePassword(string username, string oldPassword, string newPassword)
|
||||
{
|
||||
string rawPasswordValue = string.Empty;
|
||||
if (oldPassword.IsNullOrWhiteSpace() && AllowManuallyChangingPassword == false)
|
||||
{
|
||||
//we need to lookup the member since this could be a brand new member without a password set
|
||||
var rawPassword = GetRawPassword(username);
|
||||
rawPasswordValue = rawPassword.Success ? rawPassword.Result : string.Empty;
|
||||
if (rawPassword.Success == false || rawPasswordValue.StartsWith(Constants.Security.EmptyPasswordPrefix) == false)
|
||||
{
|
||||
//If the old password is empty and AllowManuallyChangingPassword is false, than this provider cannot just arbitrarily change the password
|
||||
throw new NotSupportedException("This provider does not support manually changing the password");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var args = new ValidatePasswordEventArgs(username, newPassword, false);
|
||||
OnValidatingPassword(args);
|
||||
|
||||
@@ -339,14 +315,13 @@ namespace Umbraco.Web.Security
|
||||
// * the member is new and doesn't have a password set
|
||||
// * during installation to set the admin password
|
||||
var installing = Current.RuntimeState.Level == RuntimeLevel.Install;
|
||||
if (AllowManuallyChangingPassword == false
|
||||
&& (rawPasswordValue.StartsWith(Constants.Security.EmptyPasswordPrefix)
|
||||
|| (installing && oldPassword == "default")))
|
||||
if (rawPasswordValue.StartsWith(Constants.Security.EmptyPasswordPrefix)
|
||||
|| (installing && oldPassword == "default"))
|
||||
{
|
||||
return PerformChangePassword(username, oldPassword, newPassword);
|
||||
}
|
||||
|
||||
if (AllowManuallyChangingPassword == false)
|
||||
if (!oldPassword.IsNullOrWhiteSpace())
|
||||
{
|
||||
if (ValidateUser(username, oldPassword) == false) return false;
|
||||
}
|
||||
@@ -385,7 +360,7 @@ namespace Umbraco.Web.Security
|
||||
throw new NotSupportedException("Updating the password Question and Answer is not available if requiresQuestionAndAnswer is not set in web.config");
|
||||
}
|
||||
|
||||
if (AllowManuallyChangingPassword == false)
|
||||
if (!password.IsNullOrWhiteSpace())
|
||||
{
|
||||
if (ValidateUser(username, password) == false)
|
||||
{
|
||||
|
||||
@@ -23,40 +23,6 @@ namespace Umbraco.Web.Security
|
||||
return membershipMember;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extension method to check if a password can be reset based on a given provider and the current request (logged in user)
|
||||
/// </summary>
|
||||
/// <param name="provider"></param>
|
||||
/// <param name="userService"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// An Admin can always reset the password
|
||||
/// </remarks>
|
||||
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<int>().Result);
|
||||
if (user == null) throw new InvalidOperationException("No user with username " + identity.Username + " found");
|
||||
var userIsAdmin = user.IsAdmin();
|
||||
if (userIsAdmin)
|
||||
{
|
||||
canReset = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return canReset;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Method to get the Umbraco Members membership provider based on its alias
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Umbraco.Web.Security.Providers
|
||||
/// <summary>
|
||||
/// Custom Membership Provider for Umbraco Members (User authentication for Frontend applications NOT umbraco CMS)
|
||||
/// </summary>
|
||||
public class MembersMembershipProvider : UmbracoMembershipProvider<IMembershipMemberService, IMember>, IUmbracoMemberTypeMembershipProvider
|
||||
public class MembersMembershipProvider : UmbracoMembershipProvider<IMembershipMemberService, IMember>
|
||||
{
|
||||
public MembersMembershipProvider()
|
||||
: this(Current.Services.MemberService, Current.Services.MemberTypeService, Current.UmbracoVersion, Current.HostingEnvironment, Current.IpResolver)
|
||||
|
||||
@@ -44,16 +44,6 @@ namespace Umbraco.Web.Security.Providers
|
||||
|
||||
protected abstract MembershipUser ConvertToMembershipUser(TEntity entity);
|
||||
|
||||
private bool _allowManuallyChangingPassword = false;
|
||||
|
||||
/// <summary>
|
||||
/// For backwards compatibility, this provider supports this option by default it is false
|
||||
/// </summary>
|
||||
public override bool AllowManuallyChangingPassword
|
||||
{
|
||||
get { return _allowManuallyChangingPassword; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the provider.
|
||||
/// </summary>
|
||||
@@ -72,8 +62,6 @@ namespace Umbraco.Web.Security.Providers
|
||||
|
||||
// Initialize base provider class
|
||||
base.Initialize(name, config);
|
||||
|
||||
_allowManuallyChangingPassword = config.GetValue("allowManuallyChangingPassword", false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -531,7 +519,7 @@ namespace Umbraco.Web.Security.Providers
|
||||
};
|
||||
}
|
||||
|
||||
var authenticated = PasswordSecurity.CheckPassword(password, member.RawPasswordValue);
|
||||
var authenticated = PasswordSecurity.VerifyPassword(password, member.RawPasswordValue);
|
||||
|
||||
if (authenticated == false)
|
||||
{
|
||||
|
||||
@@ -6,7 +6,6 @@ using System.Net.Http;
|
||||
using System.Net.Http.Formatting;
|
||||
using System.Web.Http;
|
||||
using System.Web.Http.ModelBinding;
|
||||
using System.Web.Security;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Services;
|
||||
@@ -37,11 +36,9 @@ namespace Umbraco.Web.Trees
|
||||
public MemberTreeController(UmbracoTreeSearcher treeSearcher)
|
||||
{
|
||||
_treeSearcher = treeSearcher;
|
||||
_provider = MembershipProviderExtensions.GetMembersMembershipProvider();
|
||||
}
|
||||
|
||||
private readonly UmbracoTreeSearcher _treeSearcher;
|
||||
private readonly MembershipProvider _provider;
|
||||
|
||||
/// <summary>
|
||||
/// Gets an individual tree node
|
||||
|
||||
@@ -30,7 +30,6 @@ namespace Umbraco.Web
|
||||
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;
|
||||
protected IIpResolver IpResolver => Current.IpResolver;
|
||||
@@ -90,7 +89,6 @@ namespace Umbraco.Web
|
||||
UmbracoSettings.Content,
|
||||
GlobalSettings,
|
||||
UserPasswordConfig,
|
||||
PasswordGenerator,
|
||||
IpResolver);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user