Merge pull request #2059 from umbraco/temp-U4-8643
U4-8643 Usermanagement - Store password algorithm in Usertable
This commit is contained in:
@@ -40,6 +40,13 @@ namespace Umbraco.Core.Models.Rdbms
|
||||
[Column("userPassword")]
|
||||
[Length(500)]
|
||||
public string Password { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// This will represent a JSON structure of how the password has been created (i.e hash algorithm, iterations)
|
||||
/// </summary>
|
||||
[Column("passwordConfig")]
|
||||
[Length(500)]
|
||||
public string PasswordConfig { get; set; }
|
||||
|
||||
[Column("userEmail")]
|
||||
public string Email { get; set; }
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
using System.Linq;
|
||||
using System.Web.Security;
|
||||
using Newtonsoft.Json;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
using Umbraco.Core.Security;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSevenZero
|
||||
{
|
||||
@@ -28,6 +31,19 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSevenZe
|
||||
|
||||
if (columns.Any(x => x.TableName.InvariantEquals("umbracoUser") && x.ColumnName.InvariantEquals("invitedDate")) == false)
|
||||
Create.Column("invitedDate").OnTable("umbracoUser").AsDateTime().Nullable();
|
||||
|
||||
if (columns.Any(x => x.TableName.InvariantEquals("umbracoUser") && x.ColumnName.InvariantEquals("passwordConfig")) == false)
|
||||
{
|
||||
Create.Column("passwordConfig").OnTable("umbracoUser").AsString(500).Nullable();
|
||||
//Check if we have a known config, we only want to store config for hashing
|
||||
var membershipProvider = MembershipProviderExtensions.GetUsersMembershipProvider();
|
||||
if (membershipProvider.PasswordFormat == MembershipPasswordFormat.Hashed)
|
||||
{
|
||||
var json = JsonConvert.SerializeObject(new { hashAlgorithm = Membership.HashAlgorithmType });
|
||||
Execute.Sql("UPDATE umbracoUser SET passwordConfig = '" + json + "'");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public override void Down()
|
||||
|
||||
@@ -4,6 +4,8 @@ using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Text;
|
||||
using System.Web.Security;
|
||||
using Newtonsoft.Json;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models.EntityBase;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
@@ -15,6 +17,7 @@ using Umbraco.Core.Persistence.Querying;
|
||||
using Umbraco.Core.Persistence.Relators;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
using Umbraco.Core.Persistence.UnitOfWork;
|
||||
using Umbraco.Core.Security;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Repositories
|
||||
{
|
||||
@@ -305,9 +308,18 @@ ORDER BY colName";
|
||||
if (entity.SecurityStamp.IsNullOrWhiteSpace())
|
||||
{
|
||||
entity.SecurityStamp = Guid.NewGuid().ToString();
|
||||
}
|
||||
}
|
||||
|
||||
var userDto = UserFactory.BuildDto(entity);
|
||||
var userDto = UserFactory.BuildDto(entity);
|
||||
|
||||
//Check if we have a known config, we only want to store config for hashing
|
||||
//TODO: This logic will need to be updated when we do http://issues.umbraco.org/issue/U4-10089
|
||||
var membershipProvider = MembershipProviderExtensions.GetUsersMembershipProvider();
|
||||
if (membershipProvider.PasswordFormat == MembershipPasswordFormat.Hashed)
|
||||
{
|
||||
var json = JsonConvert.SerializeObject(new { hashAlgorithm = Membership.HashAlgorithmType });
|
||||
userDto.PasswordConfig = json;
|
||||
}
|
||||
|
||||
var id = Convert.ToInt32(Database.Insert(userDto));
|
||||
entity.Id = id;
|
||||
@@ -401,6 +413,15 @@ ORDER BY colName";
|
||||
userDto.SecurityStampToken = entity.SecurityStamp = Guid.NewGuid().ToString();
|
||||
changedCols.Add("securityStampToken");
|
||||
}
|
||||
|
||||
//Check if we have a known config, we only want to store config for hashing
|
||||
//TODO: This logic will need to be updated when we do http://issues.umbraco.org/issue/U4-10089
|
||||
var membershipProvider = MembershipProviderExtensions.GetUsersMembershipProvider();
|
||||
if (membershipProvider.PasswordFormat == MembershipPasswordFormat.Hashed)
|
||||
{
|
||||
var json = JsonConvert.SerializeObject(new { hashAlgorithm = Membership.HashAlgorithmType });
|
||||
userDto.PasswordConfig = json;
|
||||
}
|
||||
}
|
||||
|
||||
//only update the changed cols
|
||||
|
||||
@@ -141,7 +141,9 @@ namespace Umbraco.Core.Security
|
||||
/// Initializes the user manager with the correct options
|
||||
/// </summary>
|
||||
/// <param name="manager"></param>
|
||||
/// <param name="membershipProvider"></param>
|
||||
/// <param name="membershipProvider">
|
||||
/// The <see cref="MembershipProviderBase"/> for the users called UsersMembershipProvider
|
||||
/// </param>
|
||||
/// <param name="dataProtectionProvider"></param>
|
||||
/// <returns></returns>
|
||||
protected void InitUserManager(
|
||||
@@ -157,11 +159,10 @@ namespace Umbraco.Core.Security
|
||||
};
|
||||
|
||||
// Configure validation logic for passwords
|
||||
var provider = MembershipProviderExtensions.GetUsersMembershipProvider();
|
||||
manager.PasswordValidator = new MembershipProviderPasswordValidator(provider);
|
||||
manager.PasswordValidator = new MembershipProviderPasswordValidator(membershipProvider);
|
||||
|
||||
//use a custom hasher based on our membership provider
|
||||
manager.PasswordHasher = new MembershipPasswordHasher(membershipProvider);
|
||||
manager.PasswordHasher = GetDefaultPasswordHasher(membershipProvider);
|
||||
|
||||
if (dataProtectionProvider != null)
|
||||
{
|
||||
@@ -196,6 +197,76 @@ namespace Umbraco.Core.Security
|
||||
|
||||
//manager.SmsService = new SmsService();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This will determine which password hasher to use based on what is defined in config
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected virtual IPasswordHasher GetDefaultPasswordHasher(MembershipProviderBase provider)
|
||||
{
|
||||
//if the current user membership provider is unkown (this would be rare), then return the default password hasher
|
||||
if (provider.IsUmbracoUsersProvider() == false)
|
||||
return new PasswordHasher();
|
||||
|
||||
//if the configured provider has legacy features enabled, then return the membership provider password hasher
|
||||
if (provider.AllowManuallyChangingPassword || provider.DefaultUseLegacyEncoding)
|
||||
return new MembershipProviderPasswordHasher(provider);
|
||||
|
||||
//we can use the user aware password hasher (which will be the default and preferred way)
|
||||
return new UserAwareMembershipProviderPasswordHasher(provider);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets/sets the default back office user password checker
|
||||
/// </summary>
|
||||
public IBackOfficeUserPasswordChecker BackOfficeUserPasswordChecker { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Helper method to generate a password for a user based on the current password validator
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string GeneratePassword()
|
||||
{
|
||||
var passwordValidator = PasswordValidator as PasswordValidator;
|
||||
|
||||
if (passwordValidator == null)
|
||||
{
|
||||
var membershipPasswordHasher = PasswordHasher as IMembershipProviderPasswordHasher;
|
||||
|
||||
//get the real password validator, this should not be null but in some very rare cases it could be, in which case
|
||||
//we need to create a default password validator to use since we have no idea what it actually is or what it's rules are
|
||||
//this is an Edge Case!
|
||||
passwordValidator = PasswordValidator as PasswordValidator
|
||||
?? (membershipPasswordHasher != null
|
||||
? new MembershipProviderPasswordValidator(membershipPasswordHasher.MembershipProvider)
|
||||
: new PasswordValidator());
|
||||
}
|
||||
|
||||
var password = Membership.GeneratePassword(
|
||||
passwordValidator.RequiredLength,
|
||||
passwordValidator.RequireNonLetterOrDigit ? 2 : 0);
|
||||
|
||||
var random = new Random();
|
||||
|
||||
var passwordChars = password.ToCharArray();
|
||||
|
||||
if (passwordValidator.RequireDigit && passwordChars.ContainsAny(Enumerable.Range(48, 58).Select(x => (char)x)))
|
||||
password += Convert.ToChar(random.Next(48, 58)); // 0-9
|
||||
|
||||
if (passwordValidator.RequireLowercase && passwordChars.ContainsAny(Enumerable.Range(97, 123).Select(x => (char)x)))
|
||||
password += Convert.ToChar(random.Next(97, 123)); // a-z
|
||||
|
||||
if (passwordValidator.RequireUppercase && passwordChars.ContainsAny(Enumerable.Range(65, 91).Select(x => (char)x)))
|
||||
password += Convert.ToChar(random.Next(65, 91)); // A-Z
|
||||
|
||||
if (passwordValidator.RequireNonLetterOrDigit && passwordChars.ContainsAny(Enumerable.Range(33, 48).Select(x => (char)x)))
|
||||
password += Convert.ToChar(random.Next(33, 48)); // symbols !"#$%&'()*+,-./
|
||||
|
||||
return password;
|
||||
}
|
||||
|
||||
|
||||
#region Overrides for password logic
|
||||
|
||||
/// <summary>
|
||||
/// Logic used to validate a username and password
|
||||
@@ -243,44 +314,88 @@ namespace Umbraco.Core.Security
|
||||
return await base.CheckPasswordAsync(user, password);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets/sets the default back office user password checker
|
||||
/// </summary>
|
||||
public IBackOfficeUserPasswordChecker BackOfficeUserPasswordChecker { get; set; }
|
||||
public override Task<IdentityResult> ChangePasswordAsync(int userId, string currentPassword, string newPassword)
|
||||
{
|
||||
return base.ChangePasswordAsync(userId, currentPassword, newPassword);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method to generate a password for a user based on the current password validator
|
||||
/// Override to determine how to hash the password
|
||||
/// </summary>
|
||||
/// <param name="store"></param>
|
||||
/// <param name="user"></param>
|
||||
/// <param name="password"></param>
|
||||
/// <returns></returns>
|
||||
protected override async Task<bool> VerifyPasswordAsync(IUserPasswordStore<T, int> store, T user, string password)
|
||||
{
|
||||
var userAwarePasswordHasher = PasswordHasher as IUserAwarePasswordHasher<BackOfficeIdentityUser, int>;
|
||||
if (userAwarePasswordHasher == null)
|
||||
return await base.VerifyPasswordAsync(store, user, password);
|
||||
|
||||
var hash = await store.GetPasswordHashAsync(user);
|
||||
return userAwarePasswordHasher.VerifyHashedPassword(user, hash, password) != PasswordVerificationResult.Failed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override to determine how to hash the password
|
||||
/// </summary>
|
||||
/// <param name="passwordStore"></param>
|
||||
/// <param name="user"></param>
|
||||
/// <param name="newPassword"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// This method is called anytime the password needs to be hashed for storage (i.e. including when reset password is used)
|
||||
/// </remarks>
|
||||
protected override async Task<IdentityResult> UpdatePassword(IUserPasswordStore<T, int> passwordStore, T user, string newPassword)
|
||||
{
|
||||
var userAwarePasswordHasher = PasswordHasher as IUserAwarePasswordHasher<BackOfficeIdentityUser, int>;
|
||||
if (userAwarePasswordHasher == null)
|
||||
return await base.UpdatePassword(passwordStore, user, newPassword);
|
||||
|
||||
var result = await PasswordValidator.ValidateAsync(newPassword);
|
||||
if (result.Succeeded == false)
|
||||
return result;
|
||||
|
||||
await passwordStore.SetPasswordHashAsync(user, userAwarePasswordHasher.HashPassword(user, newPassword));
|
||||
await UpdateSecurityStampInternal(user);
|
||||
return IdentityResult.Success;
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is copied from the underlying .NET base class since they decied to not expose it
|
||||
/// </summary>
|
||||
/// <param name="user"></param>
|
||||
/// <returns></returns>
|
||||
private async Task UpdateSecurityStampInternal(BackOfficeIdentityUser user)
|
||||
{
|
||||
if (SupportsUserSecurityStamp == false)
|
||||
return;
|
||||
await GetSecurityStore().SetSecurityStampAsync(user, NewSecurityStamp());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is copied from the underlying .NET base class since they decied to not expose it
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string GeneratePassword()
|
||||
private IUserSecurityStampStore<BackOfficeIdentityUser, int> GetSecurityStore()
|
||||
{
|
||||
var passwordValidator = PasswordValidator as PasswordValidator;
|
||||
if (passwordValidator != null)
|
||||
{
|
||||
var password = Membership.GeneratePassword(
|
||||
passwordValidator.RequiredLength,
|
||||
passwordValidator.RequireNonLetterOrDigit ? 2 : 0);
|
||||
|
||||
var random = new Random();
|
||||
|
||||
var passwordChars = password.ToCharArray();
|
||||
|
||||
if (passwordValidator.RequireDigit && passwordChars.ContainsAny(Enumerable.Range(48, 58).Select(x => (char)x)))
|
||||
password += Convert.ToChar(random.Next(48, 58)); // 0-9
|
||||
|
||||
if (passwordValidator.RequireLowercase && passwordChars.ContainsAny(Enumerable.Range(97, 123).Select(x => (char)x)))
|
||||
password += Convert.ToChar(random.Next(97, 123)); // a-z
|
||||
|
||||
if (passwordValidator.RequireUppercase && passwordChars.ContainsAny(Enumerable.Range(65, 91).Select(x => (char)x)))
|
||||
password += Convert.ToChar(random.Next(65, 91)); // A-Z
|
||||
|
||||
if (passwordValidator.RequireNonLetterOrDigit && passwordChars.ContainsAny(Enumerable.Range(33, 48).Select(x => (char)x)))
|
||||
password += Convert.ToChar(random.Next(33, 48)); // symbols !"#$%&'()*+,-./
|
||||
|
||||
return password;
|
||||
}
|
||||
throw new NotSupportedException("Cannot generate a password since the type of the password validator (" + PasswordValidator.GetType() + ") is not known");
|
||||
var store = Store as IUserSecurityStampStore<BackOfficeIdentityUser, int>;
|
||||
if (store == null)
|
||||
throw new NotSupportedException("The current user store does not implement " + typeof(IUserSecurityStampStore<>));
|
||||
return store;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// This is copied from the underlying .NET base class since they decied to not expose it
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private static string NewSecurityStamp()
|
||||
{
|
||||
return Guid.NewGuid().ToString();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
using Microsoft.AspNet.Identity;
|
||||
|
||||
namespace Umbraco.Core.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// A password hasher that is based on the rules configured for a membership provider
|
||||
/// </summary>
|
||||
public interface IMembershipProviderPasswordHasher : IPasswordHasher
|
||||
{
|
||||
MembershipProviderBase MembershipProvider { get; }
|
||||
}
|
||||
}
|
||||
18
src/Umbraco.Core/Security/IUserAwarePasswordHasher.cs
Normal file
18
src/Umbraco.Core/Security/IUserAwarePasswordHasher.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using Microsoft.AspNet.Identity;
|
||||
|
||||
namespace Umbraco.Core.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// A password hasher that is User aware so that it can process the hashing based on the user's settings
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey"></typeparam>
|
||||
/// <typeparam name="TUser"></typeparam>
|
||||
public interface IUserAwarePasswordHasher<in TUser, TKey>
|
||||
where TUser : class, IUser<TKey>
|
||||
where TKey : IEquatable<TKey>
|
||||
{
|
||||
string HashPassword(TUser user, string password);
|
||||
PasswordVerificationResult VerifyHashedPassword(TUser user, string hashedPassword, string providedPassword);
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
using System;
|
||||
using Microsoft.AspNet.Identity;
|
||||
|
||||
namespace Umbraco.Core.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// A custom password hasher that conforms to the current password hashing done in Umbraco
|
||||
/// </summary>
|
||||
internal class MembershipPasswordHasher : IPasswordHasher
|
||||
{
|
||||
private readonly MembershipProviderBase _provider;
|
||||
|
||||
public MembershipPasswordHasher(MembershipProviderBase provider)
|
||||
{
|
||||
_provider = provider;
|
||||
}
|
||||
|
||||
public string HashPassword(string password)
|
||||
{
|
||||
return _provider.HashPasswordForStorage(password);
|
||||
}
|
||||
|
||||
public PasswordVerificationResult VerifyHashedPassword(string hashedPassword, string providedPassword)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(hashedPassword)) throw new ArgumentException("Value cannot be null or whitespace.", "hashedPassword");
|
||||
|
||||
return _provider.VerifyPassword(providedPassword, hashedPassword)
|
||||
? PasswordVerificationResult.Success
|
||||
: PasswordVerificationResult.Failed;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -33,19 +33,19 @@ namespace Umbraco.Core.Security
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Providers can override this setting, default is 7
|
||||
/// Providers can override this setting, default is 10
|
||||
/// </summary>
|
||||
public virtual int DefaultMinPasswordLength
|
||||
{
|
||||
get { return 7; }
|
||||
get { return 10; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Providers can override this setting, default is 1
|
||||
/// Providers can override this setting, default is 0
|
||||
/// </summary>
|
||||
public virtual int DefaultMinNonAlphanumericChars
|
||||
{
|
||||
get { return 1; }
|
||||
get { return 0; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -225,7 +225,7 @@ namespace Umbraco.Core.Security
|
||||
base.Initialize(name, config);
|
||||
|
||||
_enablePasswordRetrieval = config.GetValue("enablePasswordRetrieval", false);
|
||||
_enablePasswordReset = config.GetValue("enablePasswordReset", false);
|
||||
_enablePasswordReset = config.GetValue("enablePasswordReset", true);
|
||||
_requiresQuestionAndAnswer = config.GetValue("requiresQuestionAndAnswer", false);
|
||||
_requiresUniqueEmail = config.GetValue("requiresUniqueEmail", true);
|
||||
_maxInvalidPasswordAttempts = GetIntValue(config, "maxInvalidPasswordAttempts", 5, false, 0);
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
using Microsoft.AspNet.Identity;
|
||||
|
||||
namespace Umbraco.Core.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// A password hasher that conforms to the password hashing done with membership providers
|
||||
/// </summary>
|
||||
public class MembershipProviderPasswordHasher : IMembershipProviderPasswordHasher
|
||||
{
|
||||
/// <summary>
|
||||
/// Exposes the underlying MembershipProvider
|
||||
/// </summary>
|
||||
public MembershipProviderBase MembershipProvider { get; private set; }
|
||||
|
||||
public MembershipProviderPasswordHasher(MembershipProviderBase provider)
|
||||
{
|
||||
MembershipProvider = provider;
|
||||
}
|
||||
|
||||
public string HashPassword(string password)
|
||||
{
|
||||
return MembershipProvider.HashPasswordForStorage(password);
|
||||
}
|
||||
|
||||
public PasswordVerificationResult VerifyHashedPassword(string hashedPassword, string providedPassword)
|
||||
{
|
||||
return MembershipProvider.VerifyPassword(providedPassword, hashedPassword)
|
||||
? PasswordVerificationResult.Success
|
||||
: PasswordVerificationResult.Failed;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using Microsoft.AspNet.Identity;
|
||||
using Umbraco.Core.Models.Identity;
|
||||
|
||||
namespace Umbraco.Core.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// The default password hasher that is User aware so that it can process the hashing based on the user's settings
|
||||
/// </summary>
|
||||
public class UserAwareMembershipProviderPasswordHasher : MembershipProviderPasswordHasher, IUserAwarePasswordHasher<BackOfficeIdentityUser, int>
|
||||
{
|
||||
public UserAwareMembershipProviderPasswordHasher(MembershipProviderBase provider) : base(provider)
|
||||
{
|
||||
}
|
||||
|
||||
public string HashPassword(BackOfficeIdentityUser user, string password)
|
||||
{
|
||||
//TODO: Implement the logic for this, we need to lookup the password format for the user and hash accordingly: http://issues.umbraco.org/issue/U4-10089
|
||||
//NOTE: For now this just falls back to the hashing we are currently using
|
||||
return base.HashPassword(password);
|
||||
}
|
||||
|
||||
public PasswordVerificationResult VerifyHashedPassword(BackOfficeIdentityUser user, string hashedPassword, string providedPassword)
|
||||
{
|
||||
//TODO: Implement the logic for this, we need to lookup the password format for the user and hash accordingly: http://issues.umbraco.org/issue/U4-10089
|
||||
//NOTE: For now this just falls back to the hashing we are currently using
|
||||
return base.VerifyHashedPassword(hashedPassword, providedPassword);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -663,10 +663,13 @@
|
||||
<Compile Include="Security\BackOfficeUserValidator.cs" />
|
||||
<Compile Include="Security\IBackOfficeUserManagerMarker.cs" />
|
||||
<Compile Include="Security\IBackOfficeUserPasswordChecker.cs" />
|
||||
<Compile Include="Security\MembershipPasswordHasher.cs" />
|
||||
<Compile Include="Security\IMembershipProviderPasswordHasher.cs" />
|
||||
<Compile Include="Security\IUserAwarePasswordHasher.cs" />
|
||||
<Compile Include="Security\MembershipProviderPasswordHasher.cs" />
|
||||
<Compile Include="Security\EmailService.cs" />
|
||||
<Compile Include="Security\MembershipProviderPasswordValidator.cs" />
|
||||
<Compile Include="Security\OwinExtensions.cs" />
|
||||
<Compile Include="Security\UserAwareMembershipProviderPasswordHasher.cs" />
|
||||
<Compile Include="SemVersionExtensions.cs" />
|
||||
<Compile Include="Serialization\NoTypeConverterJsonConverter.cs" />
|
||||
<Compile Include="Serialization\StreamResultExtensions.cs" />
|
||||
|
||||
@@ -247,7 +247,7 @@
|
||||
<providers>
|
||||
<clear />
|
||||
<add name="UmbracoMembershipProvider" type="Umbraco.Web.Security.Providers.MembersMembershipProvider, Umbraco" minRequiredNonalphanumericCharacters="0" minRequiredPasswordLength="10" useLegacyEncoding="false" enablePasswordRetrieval="false" enablePasswordReset="false" requiresQuestionAndAnswer="false" defaultMemberTypeAlias="Member" passwordFormat="Hashed" allowManuallyChangingPassword="false" />
|
||||
<add name="UsersMembershipProvider" type="Umbraco.Web.Security.Providers.UsersMembershipProvider, Umbraco" minRequiredNonalphanumericCharacters="0" minRequiredPasswordLength="10" useLegacyEncoding="false" enablePasswordRetrieval="false" enablePasswordReset="false" requiresQuestionAndAnswer="false" passwordFormat="Hashed" allowManuallyChangingPassword="false" />
|
||||
<add name="UsersMembershipProvider" type="Umbraco.Web.Security.Providers.UsersMembershipProvider, Umbraco" />
|
||||
</providers>
|
||||
</membership>
|
||||
<!-- Role Provider -->
|
||||
|
||||
@@ -56,7 +56,9 @@ namespace Umbraco.Web.Editors
|
||||
/// <returns></returns>
|
||||
[WebApi.UmbracoAuthorize(requireApproval: false)]
|
||||
public IDictionary<string, object> GetMembershipProviderConfig()
|
||||
{
|
||||
{
|
||||
//TODO: Check if the current PasswordValidator is an IMembershipProviderPasswordValidator, if
|
||||
//it's not than we should return some generic defaults
|
||||
var provider = Core.Security.MembershipProviderExtensions.GetUsersMembershipProvider();
|
||||
return provider.GetConfiguration(Services.UserService);
|
||||
}
|
||||
|
||||
@@ -87,9 +87,10 @@ namespace Umbraco.Web.Editors
|
||||
/// <returns>
|
||||
/// If the password is being reset it will return the newly reset password, otherwise will return an empty value
|
||||
/// </returns>
|
||||
public ModelWithNotifications<string> PostChangePassword(ChangingPasswordModel data)
|
||||
public async Task<ModelWithNotifications<string>> PostChangePassword(ChangingPasswordModel data)
|
||||
{
|
||||
var passwordChangeResult = PasswordChangeControllerHelper.ChangePassword(Security.CurrentUser, data, ModelState, Members);
|
||||
var passwordChanger = new PasswordChanger(Logger, Services.UserService);
|
||||
var passwordChangeResult = await passwordChanger.ChangePasswordWithIdentityAsync(Security.CurrentUser, data, ModelState, UserManager);
|
||||
|
||||
if (passwordChangeResult.Success)
|
||||
{
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Web.Http.ModelBinding;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Web.Models;
|
||||
using Umbraco.Web.Security;
|
||||
|
||||
namespace Umbraco.Web.Editors
|
||||
{
|
||||
internal class PasswordChangeControllerHelper
|
||||
{
|
||||
|
||||
public static Attempt<PasswordChangedModel> ChangePassword(
|
||||
IUser currentUser,
|
||||
ChangingPasswordModel data,
|
||||
ModelStateDictionary modelState,
|
||||
MembershipHelper membersHelper)
|
||||
{
|
||||
var userProvider = Core.Security.MembershipProviderExtensions.GetUsersMembershipProvider();
|
||||
|
||||
if (userProvider.RequiresQuestionAndAnswer)
|
||||
{
|
||||
throw new NotSupportedException("Currently the user editor does not support providers that have RequiresQuestionAndAnswer specified");
|
||||
}
|
||||
|
||||
var passwordChangeResult = membersHelper.ChangePassword(currentUser.Username, data, userProvider);
|
||||
if (passwordChangeResult.Success == false)
|
||||
{
|
||||
//it wasn't successful, so add the change error to the model state
|
||||
var fieldName = passwordChangeResult.Result.ChangeError.MemberNames.FirstOrDefault() ?? "password";
|
||||
modelState.AddModelError(fieldName,
|
||||
passwordChangeResult.Result.ChangeError.ErrorMessage);
|
||||
}
|
||||
|
||||
return passwordChangeResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
246
src/Umbraco.Web/Editors/PasswordChanger.cs
Normal file
246
src/Umbraco.Web/Editors/PasswordChanger.cs
Normal file
@@ -0,0 +1,246 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web.Http.ModelBinding;
|
||||
using System.Web.Security;
|
||||
using Microsoft.AspNet.Identity;
|
||||
using umbraco.cms.businesslogic.packager;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Logging;
|
||||
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;
|
||||
|
||||
namespace Umbraco.Web.Editors
|
||||
{
|
||||
internal class PasswordChanger
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly IUserService _userService;
|
||||
|
||||
public PasswordChanger(ILogger logger, IUserService userService)
|
||||
{
|
||||
_logger = logger;
|
||||
_userService = userService;
|
||||
}
|
||||
|
||||
public async Task<Attempt<PasswordChangedModel>> ChangePasswordWithIdentityAsync(
|
||||
IUser currentUser,
|
||||
ChangingPasswordModel passwordModel,
|
||||
ModelStateDictionary modelState,
|
||||
BackOfficeUserManager<BackOfficeIdentityUser> userMgr)
|
||||
{
|
||||
if (passwordModel == null) throw new ArgumentNullException("passwordModel");
|
||||
if (userMgr == null) throw new ArgumentNullException("userMgr");
|
||||
|
||||
//check if this identity implementation is powered by an underlying membership provider (it will be in most cases)
|
||||
var membershipPasswordHasher = userMgr.PasswordHasher as IMembershipProviderPasswordHasher;
|
||||
|
||||
//check if this identity implementation is powered by an IUserAwarePasswordHasher (it will be by default in 7.7+ but not for upgrades)
|
||||
var userAwarePasswordHasher = userMgr.PasswordHasher as IUserAwarePasswordHasher<BackOfficeIdentityUser, int>;
|
||||
|
||||
if (membershipPasswordHasher != null && userAwarePasswordHasher == null)
|
||||
{
|
||||
//if this isn't using an IUserAwarePasswordHasher, then fallback to the old way
|
||||
if (membershipPasswordHasher.MembershipProvider.RequiresQuestionAndAnswer)
|
||||
throw new NotSupportedException("Currently the user editor does not support providers that have RequiresQuestionAndAnswer specified");
|
||||
return ChangePasswordWithMembershipProvider(currentUser.Username, passwordModel, membershipPasswordHasher.MembershipProvider);
|
||||
}
|
||||
|
||||
//if we are here, then a IUserAwarePasswordHasher is available, however we cannot proceed in that case if for some odd reason
|
||||
//the user has configured the membership provider to not be hashed. This will actually never occur because the BackOfficeUserManager
|
||||
//will throw if it's not hashed, but we should make sure to check anyways (i.e. in case we want to unit test!)
|
||||
if (membershipPasswordHasher != null && membershipPasswordHasher.MembershipProvider.PasswordFormat != MembershipPasswordFormat.Hashed)
|
||||
{
|
||||
throw new InvalidOperationException("The membership provider cannot have a password format of " + membershipPasswordHasher.MembershipProvider.PasswordFormat + " and be configured with secured hashed passwords");
|
||||
}
|
||||
|
||||
//Are we resetting the password??
|
||||
if (passwordModel.Reset.HasValue && passwordModel.Reset.Value)
|
||||
{
|
||||
//ok, we should be able to reset it
|
||||
var resetToken = await userMgr.GeneratePasswordResetTokenAsync(currentUser.Id);
|
||||
var newPass = userMgr.GeneratePassword();
|
||||
var resetResult = await userMgr.ResetPasswordAsync(currentUser.Id, resetToken, newPass);
|
||||
|
||||
if (resetResult.Succeeded == false)
|
||||
{
|
||||
var errors = string.Join(". ", resetResult.Errors);
|
||||
_logger.Warn<PasswordChanger>(string.Format("Could not reset member password {0}", errors));
|
||||
return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not reset password, errors: " + errors, new[] { "resetPassword" }) });
|
||||
}
|
||||
|
||||
return Attempt.Succeed(new PasswordChangedModel { ResetPassword = newPass });
|
||||
}
|
||||
|
||||
//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
|
||||
//TODO: What if the current user is admin? We should allow manually changing then?
|
||||
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 (passwordModel.OldPassword.IsNullOrWhiteSpace() == false)
|
||||
{
|
||||
//if an old password is suplied try to change it
|
||||
var changeResult = await userMgr.ChangePasswordAsync(currentUser.Id, passwordModel.OldPassword, passwordModel.NewPassword);
|
||||
if (changeResult.Succeeded == false)
|
||||
{
|
||||
var errors = string.Join(". ", changeResult.Errors);
|
||||
_logger.Warn<PasswordChanger>(string.Format("Could not change member password {0}", errors));
|
||||
return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, errors: " + errors, new[] { "value" }) });
|
||||
}
|
||||
return Attempt.Succeed(new PasswordChangedModel());
|
||||
}
|
||||
|
||||
//We shouldn't really get here
|
||||
return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, invalid information supplied", new[] { "value" }) });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes password for a member/user given the membership provider and the password change model
|
||||
/// </summary>
|
||||
/// <param name="username"></param>
|
||||
/// <param name="passwordModel"></param>
|
||||
/// <param name="membershipProvider"></param>
|
||||
/// <returns></returns>
|
||||
public Attempt<PasswordChangedModel> ChangePasswordWithMembershipProvider(string username, ChangingPasswordModel passwordModel, MembershipProvider membershipProvider)
|
||||
{
|
||||
// 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("passwordModel");
|
||||
if (membershipProvider == null) throw new ArgumentNullException("membershipProvider");
|
||||
|
||||
//Are we resetting the password??
|
||||
if (passwordModel.Reset.HasValue && passwordModel.Reset.Value)
|
||||
{
|
||||
var canReset = membershipProvider.CanResetPassword(_userService);
|
||||
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);
|
||||
|
||||
//return the generated pword
|
||||
return Attempt.Succeed(new PasswordChangedModel { ResetPassword = newPass });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.WarnWithException<PasswordChanger>("Could not reset member password", ex);
|
||||
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" }) });
|
||||
}
|
||||
|
||||
//This is an edge case and is only necessary for backwards compatibility:
|
||||
var umbracoBaseProvider = membershipProvider as MembershipProviderBase;
|
||||
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, "", passwordModel.NewPassword);
|
||||
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.WarnWithException<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" }) });
|
||||
}
|
||||
}
|
||||
|
||||
//The provider does not support manually chaning the password but no old password 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 suplied 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.WarnWithException<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" }) });
|
||||
}
|
||||
}
|
||||
|
||||
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.WarnWithException<PasswordChanger>("Could not change member password", ex1);
|
||||
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.WarnWithException<PasswordChanger>("Could not retrieve member password", ex2);
|
||||
return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, error: " + ex2.Message + " (see log for full details)", new[] { "value" }) });
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -401,7 +401,7 @@ namespace Umbraco.Web.Editors
|
||||
/// </summary>
|
||||
/// <param name="userSave"></param>
|
||||
/// <returns></returns>
|
||||
public UserDisplay PostSaveUser(UserSave userSave)
|
||||
public async Task<UserDisplay> PostSaveUser(UserSave userSave)
|
||||
{
|
||||
if (userSave == null) throw new ArgumentNullException("userSave");
|
||||
|
||||
@@ -457,7 +457,9 @@ namespace Umbraco.Web.Editors
|
||||
var resetPasswordValue = string.Empty;
|
||||
if (userSave.ChangePassword != null)
|
||||
{
|
||||
var passwordChangeResult = PasswordChangeControllerHelper.ChangePassword(found, userSave.ChangePassword, ModelState, Members);
|
||||
var passwordChanger = new PasswordChanger(Logger, Services.UserService);
|
||||
|
||||
var passwordChangeResult = await passwordChanger.ChangePasswordWithIdentityAsync(found, userSave.ChangePassword, ModelState, UserManager);
|
||||
if (passwordChangeResult.Success)
|
||||
{
|
||||
//depending on how the provider is configured, the password may be reset so let's store that for later
|
||||
|
||||
@@ -32,6 +32,7 @@ namespace Umbraco.Web.Install.InstallSteps
|
||||
_applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
//TODO: Change all logic in this step to use ASP.NET Identity NOT MembershipProviders
|
||||
private MembershipProvider CurrentProvider
|
||||
{
|
||||
get
|
||||
|
||||
@@ -13,6 +13,7 @@ using Umbraco.Core.Security;
|
||||
using Umbraco.Web.Models;
|
||||
using Umbraco.Web.PublishedCache;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Web.Editors;
|
||||
using Umbraco.Web.Security.Providers;
|
||||
using MPE = global::Umbraco.Core.Security.MembershipProviderExtensions;
|
||||
|
||||
@@ -655,128 +656,8 @@ namespace Umbraco.Web.Security
|
||||
/// <returns></returns>
|
||||
public virtual Attempt<PasswordChangedModel> ChangePassword(string username, ChangingPasswordModel passwordModel, MembershipProvider membershipProvider)
|
||||
{
|
||||
// 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("passwordModel");
|
||||
if (membershipProvider == null) throw new ArgumentNullException("membershipProvider");
|
||||
|
||||
//Are we resetting the password??
|
||||
if (passwordModel.Reset.HasValue && passwordModel.Reset.Value)
|
||||
{
|
||||
var canReset = membershipProvider.CanResetPassword(_applicationContext.Services.UserService);
|
||||
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);
|
||||
|
||||
//return the generated pword
|
||||
return Attempt.Succeed(new PasswordChangedModel { ResetPassword = newPass });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WarnWithException<WebSecurity>("Could not reset member password", ex);
|
||||
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" }) });
|
||||
}
|
||||
|
||||
//This is an edge case and is only necessary for backwards compatibility:
|
||||
var umbracoBaseProvider = membershipProvider as MembershipProviderBase;
|
||||
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, "", passwordModel.NewPassword);
|
||||
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)
|
||||
{
|
||||
LogHelper.WarnWithException<WebSecurity>("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" }) });
|
||||
}
|
||||
}
|
||||
|
||||
//The provider does not support manually chaning the password but no old password 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 suplied 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)
|
||||
{
|
||||
LogHelper.WarnWithException<WebSecurity>("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" }) });
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
LogHelper.WarnWithException<WebSecurity>("Could not change member password", ex1);
|
||||
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)
|
||||
{
|
||||
LogHelper.WarnWithException<WebSecurity>("Could not retrieve member password", ex2);
|
||||
return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, error: " + ex2.Message + " (see log for full details)", new[] { "value" }) });
|
||||
}
|
||||
var passwordChanger = new PasswordChanger(_applicationContext.ProfilingLogger.Logger, _applicationContext.Services.UserService);
|
||||
return passwordChanger.ChangePasswordWithMembershipProvider(username, passwordModel, membershipProvider);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -170,10 +170,14 @@ namespace Umbraco.Web.Security
|
||||
/// <param name="username"></param>
|
||||
/// <param name="password"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// This uses ASP.NET Identity to perform the validation
|
||||
/// </remarks>
|
||||
public virtual bool ValidateBackOfficeCredentials(string username, string password)
|
||||
{
|
||||
var membershipProvider = Core.Security.MembershipProviderExtensions.GetUsersMembershipProvider();
|
||||
return membershipProvider != null && membershipProvider.ValidateUser(username, password);
|
||||
var backofficeuser = Mapper.Map<BackOfficeIdentityUser>(CurrentUser);
|
||||
backofficeuser.UserName = username;
|
||||
return UserManager.CheckPasswordAsync(backofficeuser, password).Result;
|
||||
}
|
||||
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
|
||||
@@ -324,7 +324,7 @@
|
||||
<Compile Include="Editors\EditorValidator.cs" />
|
||||
<Compile Include="Editors\FromJsonPathAttribute.cs" />
|
||||
<Compile Include="Editors\IsCurrentUserModelFilterAttribute.cs" />
|
||||
<Compile Include="Editors\PasswordChangeControllerHelper.cs" />
|
||||
<Compile Include="Editors\PasswordChanger.cs" />
|
||||
<Compile Include="Editors\TemplateController.cs" />
|
||||
<Compile Include="Editors\ParameterSwapControllerActionSelector.cs" />
|
||||
<Compile Include="Editors\CodeFileController.cs" />
|
||||
|
||||
Reference in New Issue
Block a user