Merge remote-tracking branch 'origin/dev-v7' into dev-v7.7, removes the UnlockUser method of BackOfficeUserManager since the membership provider is no longer used to perform any unlocking

This commit is contained in:
Shannon
2017-09-18 19:38:10 +10:00
5 changed files with 183 additions and 155 deletions

View File

@@ -13,7 +13,7 @@ namespace Umbraco.Core.Auditing
/// <summary>
/// The action that got triggered from the audit event
/// </summary>
public AuditEvent Action { get; set; }
public AuditEvent Action { get; private set; }
/// <summary>
/// Current date/time in UTC format
@@ -23,27 +23,27 @@ namespace Umbraco.Core.Auditing
/// <summary>
/// The source IP address of the user performing the action
/// </summary>
public string IpAddress { get; set; }
public string IpAddress { get; private set; }
/// <summary>
/// The user affected by the event raised
/// </summary>
public int AffectedUser { get; set; }
public int AffectedUser { get; private set; }
/// <summary>
/// If a user is perfoming an action on a different user, then this will be set. Otherwise it will be -1
/// </summary>
public int PerformingUser { get; set; }
public int PerformingUser { get; private set; }
/// <summary>
/// An optional comment about the action being logged
/// </summary>
public string Comment { get; set; }
public string Comment { get; private set; }
/// <summary>
/// This property is always empty except in the LoginFailed event for an unknown user trying to login
/// </summary>
public string Username { get; set; }
public string Username { get; private set; }
/// <summary>
/// Sets the properties on the event being raised, all parameters are optional except for the action being performed
@@ -51,28 +51,26 @@ namespace Umbraco.Core.Auditing
/// <param name="action">An action based on the AuditEvent enum</param>
/// <param name="ipAddress">The client's IP address. This is usually automatically set but could be overridden if necessary</param>
/// <param name="performingUser">The Id of the user performing the action (if different from the user affected by the action)</param>
public IdentityAuditEventArgs(AuditEvent action, string ipAddress = "", int performingUser = -1)
public IdentityAuditEventArgs(AuditEvent action, string ipAddress, int performingUser = -1)
{
DateTimeUtc = DateTime.UtcNow;
Action = action;
IpAddress = string.IsNullOrWhiteSpace(ipAddress)
? GetCurrentRequestIpAddress()
: ipAddress;
IpAddress = ipAddress;
PerformingUser = performingUser == -1
? GetCurrentRequestBackofficeUserId()
: performingUser;
}
/// <summary>
/// Returns the current request IP address for logging if there is one
/// </summary>
/// <returns></returns>
protected string GetCurrentRequestIpAddress()
public IdentityAuditEventArgs(AuditEvent action, string ipAddress, string username, string comment)
{
var httpContext = HttpContext.Current == null ? (HttpContextBase)null : new HttpContextWrapper(HttpContext.Current);
return httpContext.GetCurrentRequestIpAddress();
DateTimeUtc = DateTime.UtcNow;
Action = action;
IpAddress = ipAddress;
Username = username;
Comment = comment;
}
/// <summary>

View File

@@ -5,6 +5,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web.Security;
using System.Web;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin.Security.DataProtection;
@@ -506,52 +507,23 @@ namespace Umbraco.Core.Security
public override async Task<IdentityResult> ResetAccessFailedCountAsync(int userId)
{
var user = ApplicationContext.Current.Services.UserService.GetUserById(userId);
var lockoutStore = (IUserLockoutStore<BackOfficeIdentityUser, int>)Store;
var user = await FindByIdAsync(userId);
if (user == null)
{
throw new ProviderException(string.Format("No user with the id {0} found", userId));
}
throw new InvalidOperationException("No user found by user id " + userId);
if (user.FailedPasswordAttempts > 0)
{
user.FailedPasswordAttempts = 0;
ApplicationContext.Current.Services.UserService.Save(user);
RaiseResetAccessFailedCountEvent(userId);
}
var accessFailedCount = await GetAccessFailedCountAsync(user.Id);
return await Task.FromResult(IdentityResult.Success);
if (accessFailedCount == 0)
return IdentityResult.Success;
await lockoutStore.ResetAccessFailedCountAsync(user);
//raise the event now that it's reset
RaiseResetAccessFailedCountEvent(userId);
return await UpdateAsync(user);
}
/// <summary>
/// Clears a lock so that the membership user can be validated.
/// </summary>
/// <param name="memberService">The IMemberService to user for unlocking</param>
/// <param name="username">The membership user to clear the lock status for.</param>
/// <returns>
/// true if the membership user was successfully unlocked; otherwise, false.
/// </returns>
public bool UnlockUser<TEntity>(IMembershipMemberService<TEntity> memberService, string username) where TEntity : class, IMembershipUser
{
var member = memberService.GetByUsername(username);
if (member == null)
{
throw new ProviderException(string.Format("No member with the username '{0}' found", username));
}
// Non need to update
if (member.IsLockedOut == false) return true;
member.IsLockedOut = false;
member.FailedPasswordAttempts = 0;
memberService.Save(member);
RaiseAccountUnlockedEvent(member.Id);
return true;
}
public override Task<IdentityResult> AccessFailedAsync(int userId)
{
@@ -566,98 +538,61 @@ namespace Umbraco.Core.Security
internal void RaiseAccountLockedEvent(int userId)
{
OnAccountLocked(new IdentityAuditEventArgs(AuditEvent.AccountLocked)
{
AffectedUser = userId
});
OnAccountLocked(new IdentityAuditEventArgs(AuditEvent.AccountLocked, GetCurrentRequestIpAddress(), userId));
}
internal void RaiseAccountUnlockedEvent(int userId)
{
OnAccountUnlocked(new IdentityAuditEventArgs(AuditEvent.AccountUnlocked)
{
AffectedUser = userId
});
OnAccountUnlocked(new IdentityAuditEventArgs(AuditEvent.AccountUnlocked, GetCurrentRequestIpAddress(), userId));
}
internal void RaiseForgotPasswordRequestedEvent(int userId)
{
OnForgotPasswordRequested(new IdentityAuditEventArgs(AuditEvent.ForgotPasswordRequested)
{
AffectedUser = userId
});
OnForgotPasswordRequested(new IdentityAuditEventArgs(AuditEvent.ForgotPasswordRequested, GetCurrentRequestIpAddress(), userId));
}
internal void RaiseForgotPasswordChangedSuccessEvent(int userId)
{
OnForgotPasswordChangedSuccess(new IdentityAuditEventArgs(AuditEvent.ForgotPasswordChangedSuccess)
{
AffectedUser = userId
});
OnForgotPasswordChangedSuccess(new IdentityAuditEventArgs(AuditEvent.ForgotPasswordChangedSuccess, GetCurrentRequestIpAddress(), userId));
}
public void RaiseLoginFailedEvent(int userId)
internal void RaiseLoginFailedEvent(int userId)
{
OnLoginFailed(new IdentityAuditEventArgs(AuditEvent.LoginFailed)
{
AffectedUser = userId
});
OnLoginFailed(new IdentityAuditEventArgs(AuditEvent.LoginFailed, GetCurrentRequestIpAddress(), userId));
}
public void RaiseInvalidLoginAttemptEvent(string username)
internal void RaiseInvalidLoginAttemptEvent(string username)
{
OnLoginFailed(new IdentityAuditEventArgs(AuditEvent.LoginFailed)
{
Username = username,
Comment = string.Format("Attempted login for username '{0}' failed", username)
});
OnLoginFailed(new IdentityAuditEventArgs(AuditEvent.LoginFailed, GetCurrentRequestIpAddress(), username, string.Format("Attempted login for username '{0}' failed", username)));
}
internal void RaiseLoginRequiresVerificationEvent(int userId)
{
OnLoginRequiresVerification(new IdentityAuditEventArgs(AuditEvent.LoginRequiresVerification)
{
AffectedUser = userId
});
OnLoginRequiresVerification(new IdentityAuditEventArgs(AuditEvent.LoginRequiresVerification, GetCurrentRequestIpAddress(), userId));
}
internal void RaiseLoginSuccessEvent(int userId)
{
OnLoginSuccess(new IdentityAuditEventArgs(AuditEvent.LoginSucces)
{
AffectedUser = userId
});
OnLoginSuccess(new IdentityAuditEventArgs(AuditEvent.LoginSucces, GetCurrentRequestIpAddress(), userId));
}
internal void RaiseLogoutSuccessEvent(int userId)
{
OnLogoutSuccess(new IdentityAuditEventArgs(AuditEvent.LogoutSuccess)
{
AffectedUser = userId
});
OnLogoutSuccess(new IdentityAuditEventArgs(AuditEvent.LogoutSuccess, GetCurrentRequestIpAddress(), userId));
}
internal void RaisePasswordChangedEvent(int userId)
{
OnPasswordChanged(new IdentityAuditEventArgs(AuditEvent.PasswordChanged)
{
AffectedUser = userId
});
OnPasswordChanged(new IdentityAuditEventArgs(AuditEvent.PasswordChanged, GetCurrentRequestIpAddress(), userId));
}
internal void RaisePasswordResetEvent(int userId)
{
OnPasswordReset(new IdentityAuditEventArgs(AuditEvent.PasswordReset)
{
AffectedUser = userId
});
OnPasswordReset(new IdentityAuditEventArgs(AuditEvent.PasswordReset, GetCurrentRequestIpAddress(), userId));
}
internal void RaiseResetAccessFailedCountEvent(int userId)
{
OnResetAccessFailedCount(new IdentityAuditEventArgs(AuditEvent.ResetAccessFailedCount)
{
AffectedUser = userId
});
OnResetAccessFailedCount(new IdentityAuditEventArgs(AuditEvent.ResetAccessFailedCount, GetCurrentRequestIpAddress(), userId));
}
public static event EventHandler AccountLocked;
@@ -726,5 +661,16 @@ namespace Umbraco.Core.Security
{
if (ResetAccessFailedCount != null) ResetAccessFailedCount(this, e);
}
/// <summary>
/// Returns the current request IP address for logging if there is one
/// </summary>
/// <returns></returns>
protected virtual string GetCurrentRequestIpAddress()
{
//TODO: inject a service to get this value, we should not be relying on the old HttpContext.Current especially in the ASP.NET Identity world.
var httpContext = HttpContext.Current == null ? (HttpContextBase)null : new HttpContextWrapper(HttpContext.Current);
return httpContext.GetCurrentRequestIpAddress();
}
}
}

View File

@@ -94,6 +94,18 @@ namespace Umbraco.Web.Editors
if (passwordChangeResult.Success)
{
var userMgr = this.TryGetOwinContext().Result.GetBackOfficeUserManager();
//raise the appropriate event
if (data.Reset.HasValue && data.Reset.Value)
{
userMgr.RaisePasswordResetEvent(Security.CurrentUser.Id);
}
else
{
userMgr.RaisePasswordChangedEvent(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"));

View File

@@ -17,7 +17,7 @@ using Umbraco.Core.Models.Identity;
namespace Umbraco.Web.Security.Providers
{
/// <summary>
/// Abstract Membership Provider that users any implementation of IMembershipMemberService{TEntity} service
@@ -28,7 +28,7 @@ namespace Umbraco.Web.Security.Providers
{
protected IMembershipMemberService<TEntity> MemberService { get; private set; }
protected UmbracoMembershipProvider(IMembershipMemberService<TEntity> memberService)
{
MemberService = memberService;
@@ -60,7 +60,7 @@ namespace Umbraco.Web.Security.Providers
/// <exception cref="T:System.ArgumentException">The name of the provider has a length of zero.</exception>
public override void Initialize(string name, NameValueCollection config)
{
if (config == null) {throw new ArgumentNullException("config");}
if (config == null) { throw new ArgumentNullException("config"); }
if (string.IsNullOrEmpty(name)) name = ProviderName;
@@ -88,7 +88,7 @@ namespace Umbraco.Web.Security.Providers
// in order to support updating passwords from the umbraco core, we can't validate the old password
var m = MemberService.GetByUsername(username);
if (m == null) return false;
string salt;
var encodedPassword = EncryptOrHashNewPassword(newPassword, out salt);
@@ -141,7 +141,7 @@ namespace Umbraco.Web.Security.Providers
/// <returns>
/// A <see cref="T:System.Web.Security.MembershipUser"></see> object populated with the information for the newly created user.
/// </returns>
protected override MembershipUser PerformCreateUser(string memberTypeAlias, string username, string password, string email, string passwordQuestion,
protected override MembershipUser PerformCreateUser(string memberTypeAlias, string username, string password, string email, string passwordQuestion,
string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status)
{
// See if the user already exists
@@ -166,11 +166,11 @@ namespace Umbraco.Web.Security.Providers
var member = MemberService.CreateWithIdentity(
username,
email,
FormatPasswordForStorage(encodedPassword, salt),
email,
FormatPasswordForStorage(encodedPassword, salt),
memberTypeAlias,
isApproved);
member.PasswordQuestion = passwordQuestion;
member.RawPasswordAnswerValue = EncryptString(passwordAnswer);
member.LastLoginDate = DateTime.Now;
@@ -214,7 +214,7 @@ namespace Umbraco.Web.Security.Providers
public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords)
{
var byEmail = MemberService.FindByEmail(emailToMatch, pageIndex, pageSize, out totalRecords, StringPropertyMatchType.Wildcard).ToArray();
var collection = new MembershipUserCollection();
foreach (var m in byEmail)
{
@@ -259,7 +259,7 @@ namespace Umbraco.Web.Security.Providers
var membersList = new MembershipUserCollection();
var pagedMembers = MemberService.GetAll(pageIndex, pageSize, out totalRecords);
foreach (var m in pagedMembers)
{
membersList.Add(ConvertToMembershipUser(m));
@@ -292,7 +292,7 @@ namespace Umbraco.Web.Security.Providers
/// The password for the specified user name.
/// </returns>
protected override string PerformGetPassword(string username, string answer)
{
{
var m = MemberService.GetByUsername(username);
if (m == null)
{
@@ -414,7 +414,7 @@ namespace Umbraco.Web.Security.Providers
// UpdateFailureCount(username, "passwordAnswer");
// throw new ProviderException("Password answer required for password reset.");
//}
var m = MemberService.GetByUsername(username);
if (m == null)
{
@@ -438,7 +438,7 @@ namespace Umbraco.Web.Security.Providers
m.RawPasswordValue = FormatPasswordForStorage(encodedPassword, salt);
m.LastPasswordChangeDate = DateTime.Now;
MemberService.Save(m);
return generatedPassword;
}
@@ -451,15 +451,21 @@ namespace Umbraco.Web.Security.Providers
/// </returns>
public override bool UnlockUser(string username)
{
var userManager = GetBackofficeUserManager();
return userManager != null && userManager.UnlockUser(MemberService, username);
}
var member = MemberService.GetByUsername(username);
if (member == null)
{
throw new ProviderException(string.Format("No member with the username '{0}' found", username));
}
internal BackOfficeUserManager<BackOfficeIdentityUser> GetBackofficeUserManager()
{
return HttpContext.Current == null
? null
: HttpContext.Current.GetOwinContext().GetBackOfficeUserManager();
// Non need to update
if (member.IsLockedOut == false) return true;
member.IsLockedOut = false;
member.FailedPasswordAttempts = 0;
MemberService.Save(member);
return true;
}
/// <summary>
@@ -485,7 +491,7 @@ namespace Umbraco.Web.Security.Providers
}
}
m.Email = user.Email;
m.IsApproved = user.IsApproved;
m.IsLockedOut = user.IsLockedOut;
if (user.IsLockedOut)
@@ -497,15 +503,9 @@ namespace Umbraco.Web.Security.Providers
MemberService.Save(m);
}
/// <summary>
/// Verifies that the specified user name and password exist in the data source.
/// </summary>
/// <param name="username">The name of the user to validate.</param>
/// <param name="password">The password for the specified user.</param>
/// <returns>
/// true if the specified username and password are valid; otherwise, false.
/// </returns>
public override bool ValidateUser(string username, string password)
internal virtual ValidateUserResult PerformValidateUser(string username, string password)
{
var member = MemberService.GetByUsername(username);
@@ -517,7 +517,10 @@ namespace Umbraco.Web.Security.Providers
username,
GetCurrentRequestIpAddress()));
return false;
return new ValidateUserResult
{
Authenticated = false
};
}
if (member.IsApproved == false)
@@ -528,7 +531,11 @@ namespace Umbraco.Web.Security.Providers
username,
GetCurrentRequestIpAddress()));
return false;
return new ValidateUserResult
{
Member = member,
Authenticated = false
};
}
if (member.IsLockedOut)
{
@@ -538,11 +545,14 @@ namespace Umbraco.Web.Security.Providers
username,
GetCurrentRequestIpAddress()));
return false;
return new ValidateUserResult
{
Member = member,
Authenticated = false
};
}
var authenticated = CheckPassword(password, member.RawPasswordValue);
var backofficeUserManager = GetBackofficeUserManager();
if (authenticated == false)
{
@@ -561,10 +571,7 @@ namespace Umbraco.Web.Security.Providers
string.Format(
"Login attempt failed for username {0} from IP address {1}, the user is now locked out, max invalid password attempts exceeded",
username,
GetCurrentRequestIpAddress()));
if(backofficeUserManager != null)
backofficeUserManager.RaiseAccountLockedEvent(member.Id);
GetCurrentRequestIpAddress()));
}
else
{
@@ -581,8 +588,6 @@ namespace Umbraco.Web.Security.Providers
{
//we have successfully logged in, reset the AccessFailedCount
member.FailedPasswordAttempts = 0;
if (backofficeUserManager != null)
backofficeUserManager.RaiseResetAccessFailedCountEvent(member.Id);
}
member.LastLoginDate = DateTime.Now;
@@ -591,10 +596,7 @@ namespace Umbraco.Web.Security.Providers
string.Format(
"Login attempt succeeded for username {0} from IP address {1}",
username,
GetCurrentRequestIpAddress()));
if (backofficeUserManager != null)
backofficeUserManager.RaiseLoginSuccessEvent(member.Id);
GetCurrentRequestIpAddress()));
}
//don't raise events for this! It just sets the member dates, if we do raise events this will
@@ -607,7 +609,31 @@ namespace Umbraco.Web.Security.Providers
if (UmbracoVersion.Current >= new Version(7, 3, 0, 0))
MemberService.Save(member, false);
return authenticated;
return new ValidateUserResult
{
Authenticated = authenticated,
Member = member
};
}
/// <summary>
/// Verifies that the specified user name and password exist in the data source.
/// </summary>
/// <param name="username">The name of the user to validate.</param>
/// <param name="password">The password for the specified user.</param>
/// <returns>
/// true if the specified username and password are valid; otherwise, false.
/// </returns>
public override bool ValidateUser(string username, string password)
{
var result = PerformValidateUser(username, password);
return result.Authenticated;
}
internal class ValidateUserResult
{
public TEntity Member { get; set; }
public bool Authenticated { get; set; }
}
}
}

View File

@@ -2,9 +2,11 @@
using System.Configuration.Provider;
using System.Security.Cryptography;
using System.Text;
using System.Web;
using System.Web.Security;
using Umbraco.Core;
using Umbraco.Core.Configuration;
using Umbraco.Core.Models.Identity;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Security;
using Umbraco.Core.Services;
@@ -115,5 +117,49 @@ namespace Umbraco.Web.Security.Providers
return _defaultMemberTypeAlias;
}
}
/// <summary>
/// Override in order to raise appropriate events via the <see cref="BackOfficeUserManager"/>
/// </summary>
/// <param name="username"></param>
/// <param name="password"></param>
/// <returns></returns>
internal override ValidateUserResult PerformValidateUser(string username, string password)
{
var result = base.PerformValidateUser(username, password);
var userManager = GetBackofficeUserManager();
if (userManager == null) return result;
if (result.Authenticated == false)
{
var count = result.Member.FailedPasswordAttempts;
if (count >= MaxInvalidPasswordAttempts)
{
userManager.RaiseAccountLockedEvent(result.Member.Id);
}
}
else
{
if (result.Member.FailedPasswordAttempts > 0)
{
//we have successfully logged in, if the failed password attempts was modified it means it was reset
if (result.Member.WasPropertyDirty("FailedPasswordAttempts"))
{
userManager.RaiseResetAccessFailedCountEvent(result.Member.Id);
}
}
}
return result;
}
internal BackOfficeUserManager<BackOfficeIdentityUser> GetBackofficeUserManager()
{
return HttpContext.Current == null
? null
: HttpContext.Current.GetOwinContext().GetBackOfficeUserManager();
}
}
}