tidy up how a user is created when invited, ensure the special empty password is used.

This commit is contained in:
Shannon
2017-06-15 00:46:23 +02:00
parent 6d22f7f86f
commit 0858e04345
7 changed files with 48 additions and 41 deletions
+2
View File
@@ -16,6 +16,8 @@ namespace Umbraco.Core
public const string BackOfficeTokenAuthenticationType = "UmbracoBackOfficeToken";
public const string BackOfficeTwoFactorAuthenticationType = "UmbracoTwoFactorCookie";
internal const string EmptyPasswordPrefix = "___UIDEMPTYPWORD__";
/// <summary>
/// The prefix used for external identity providers for their authentication type
/// </summary>
@@ -42,7 +42,7 @@ namespace Umbraco.Core.Models.Identity
private string GetPasswordHash(string storedPass)
{
return storedPass.StartsWith("___UIDEMPTYPWORD__") ? null : storedPass;
return storedPass.StartsWith(Constants.Security.EmptyPasswordPrefix) ? null : storedPass;
}
}
}
+2 -1
View File
@@ -46,7 +46,7 @@ namespace Umbraco.Core.Models.Membership
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", "name");
if (string.IsNullOrWhiteSpace(email)) throw new ArgumentException("Value cannot be null or whitespace.", "email");
if (string.IsNullOrWhiteSpace(username)) throw new ArgumentException("Value cannot be null or whitespace.", "username");
if (string.IsNullOrWhiteSpace(rawPasswordValue)) throw new ArgumentException("Value cannot be null or whitespace.", "rawPasswordValue");
if (string.IsNullOrEmpty(rawPasswordValue)) throw new ArgumentException("Value cannot be null or empty.", "rawPasswordValue");
_name = name;
_email = email;
@@ -57,6 +57,7 @@ namespace Umbraco.Core.Models.Membership
_isLockedOut = false;
_startContentIds = new int[] { };
_startMediaIds = new int[] { };
}
/// <summary>
@@ -68,32 +68,25 @@ namespace Umbraco.Core.Security
ThrowIfDisposed();
if (user == null) throw new ArgumentNullException("user");
var member = new User
{
DefaultToLiveEditing = false,
Email = user.Email,
Language = user.Culture ?? Configuration.GlobalSettings.DefaultUILanguage,
Name = user.Name,
Username = user.UserName,
StartContentIds = user.StartContentIds ?? new int[] { },
StartMediaIds = user.StartMediaIds ?? new int[] { },
IsLockedOut = user.IsLockedOut,
IsApproved = true
};
UpdateMemberProperties(member, user);
//the password must be 'something' it could be empty if authenticating
// with an external provider so we'll just generate one and prefix it, the
// prefix will help us determine if the password hasn't actually been specified yet.
if (member.RawPasswordValue.IsNullOrWhiteSpace())
{
//this will hash the guid with a salt so should be nicely random
var aspHasher = new PasswordHasher();
member.RawPasswordValue = "___UIDEMPTYPWORD__" +
aspHasher.HashPassword(Guid.NewGuid().ToString("N"));
//this will hash the guid with a salt so should be nicely random
var aspHasher = new PasswordHasher();
var emptyPasswordValue = Constants.Security.EmptyPasswordPrefix +
aspHasher.HashPassword(Guid.NewGuid().ToString("N"));
}
var member = new User(user.Name, user.Email, user.UserName, emptyPasswordValue)
{
DefaultToLiveEditing = false,
Language = user.Culture ?? Configuration.GlobalSettings.DefaultUILanguage,
StartContentIds = user.StartContentIds ?? new int[] { },
StartMediaIds = user.StartMediaIds ?? new int[] { },
IsLockedOut = user.IsLockedOut,
};
UpdateMemberProperties(member, user);
_userService.Save(member);
if (member.Id == 0) throw new DataException("Could not create the user, check logs for details");
@@ -206,7 +199,7 @@ namespace Umbraco.Core.Security
{
ThrowIfDisposed();
if (user == null) throw new ArgumentNullException("user");
if (passwordHash.IsNullOrWhiteSpace()) throw new ArgumentNullException("passwordHash");
if (string.IsNullOrEmpty(passwordHash)) throw new ArgumentException("Value cannot be null or empty.", "passwordHash");
user.PasswordHash = passwordHash;
@@ -222,7 +215,7 @@ namespace Umbraco.Core.Security
{
ThrowIfDisposed();
if (user == null) throw new ArgumentNullException("user");
return Task.FromResult(user.PasswordHash);
}
@@ -236,7 +229,7 @@ namespace Umbraco.Core.Security
ThrowIfDisposed();
if (user == null) throw new ArgumentNullException("user");
return Task.FromResult(user.PasswordHash.IsNullOrWhiteSpace() == false);
return Task.FromResult(string.IsNullOrEmpty(user.PasswordHash) == false);
}
/// <summary>
@@ -280,9 +280,7 @@
contentEditingHelper.contentEditorPerformSave({
statusMessage: localizeSaving,
saveMethod: function(obj, isCreate, files) {
usersResource.inviteUser(a, b, c);
},
saveMethod: usersResource.inviteUser,
scope: $scope,
content: vm.newUser,
// We do not redirect on failure for users - this is because it is not possible to actually save a user
+22 -6
View File
@@ -270,7 +270,7 @@ namespace Umbraco.Web.Editors
throw new HttpResponseException(
Request.CreateNotificationValidationErrorResponse("No Email server is configured"));
}
var user = Services.UserService.GetByEmail(userSave.Email);
if (user != null && (user.LastLoginDate != default(DateTime) || user.EmailConfirmedDate.HasValue))
{
@@ -278,13 +278,29 @@ namespace Umbraco.Web.Editors
throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState));
}
//map to new user or map to existing user (if they've already been invited but didn't accept it)
user = user == null ? Mapper.Map<IUser>(userSave) : Mapper.Map(userSave, user);
if (user == null)
{
//we want to create the user with the UserManager, this ensures the 'empty' (special) password
//format is applied without us having to duplicate that logic
var created = await UserManager.CreateAsync(new BackOfficeIdentityUser
{
Email = userSave.Email,
Name = userSave.Name,
UserName = userSave.Email
});
if (created.Succeeded == false)
{
throw new HttpResponseException(
Request.CreateNotificationValidationErrorResponse(string.Join(", ", created.Errors)));
}
//This is the default but just ensure it's true, until they click on the invite and set their password they will not be approved
//and therefore not authenticated to the back office
user.IsApproved = false;
//now re-look the user back up
user = Services.UserService.GetByEmail(userSave.Email);
}
//map the save info over onto the user
user = Mapper.Map(userSave, user);
//Save the user first
Services.UserService.Save(user);
var display = Mapper.Map<UserDisplay>(user);
@@ -36,11 +36,8 @@ namespace Umbraco.Web.Models.Mapping
}
});
config.CreateMap<UserInvite, IUser>()
.ConstructUsing(invite => new User(invite.Name, invite.Email, invite.Email, Guid.NewGuid().ToString("N")))
.IgnoreAllUnmapped()
//generate a new token
.ForMember(user => user.SecurityStamp, expression => expression.MapFrom(x => (DateTime.Now + x.Email).ToSHA1()))
config.CreateMap<UserInvite, IUser>()
.IgnoreAllUnmapped()
//all invited users will not be approved, completing the invite will approve the user
.ForMember(user => user.IsApproved, expression => expression.UseValue(false))
.AfterMap((invite, user) =>