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:
@@ -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" />
|
||||
|
||||
Reference in New Issue
Block a user