Initial changes to password model
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
using System.Runtime.Serialization;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Umbraco.Web.Models
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// A model representing the data required to set a member/user password depending on the provider installed.
|
||||
@@ -20,9 +20,30 @@ namespace Umbraco.Web.Models
|
||||
public string OldPassword { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The id of the user - required to allow changing password without the entire UserSave model
|
||||
/// The ID of the current user/member requesting the password change
|
||||
/// For users, required to allow changing password without the entire UserSave model
|
||||
/// </summary>
|
||||
[DataMember(Name = "id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The username of the user/member who is changing the password
|
||||
/// </summary>
|
||||
public string CurrentUsername { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The ID of the user/member whose password is being changed
|
||||
/// </summary>
|
||||
public int SavingUserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The username of the user/memeber whose password is being changed
|
||||
/// </summary>
|
||||
public string SavingUsername { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// True if the current user has access to change the password for the member/user
|
||||
/// </summary>
|
||||
public bool CurrentUserHasSectionAccess { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
using Umbraco.Core.Models;
|
||||
|
||||
namespace Umbraco.Web.Models.ContentEditing
|
||||
{
|
||||
|
||||
10
src/Umbraco.Core/Models/IMemberUserAdapter.cs
Normal file
10
src/Umbraco.Core/Models/IMemberUserAdapter.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
public interface IMemberUserAdapter : IUser
|
||||
{
|
||||
IEnumerable<string> AllowedSections { get; }
|
||||
}
|
||||
}
|
||||
96
src/Umbraco.Core/Models/MemberUserAdapter.cs
Normal file
96
src/Umbraco.Core/Models/MemberUserAdapter.cs
Normal file
@@ -0,0 +1,96 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
public class MemberUserAdapter : IMemberUserAdapter
|
||||
{
|
||||
private readonly IMember _member;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MemberUserAdapter"/> class.
|
||||
/// Member adaptor to use existing user change password functionality and other shared functions
|
||||
/// </summary>
|
||||
/// <param name="member">The member to adapt</param>
|
||||
public MemberUserAdapter(IMember member)
|
||||
{
|
||||
_member = member;
|
||||
}
|
||||
|
||||
// This is the only reason we currently need this adaptor
|
||||
public IEnumerable<string> AllowedSections => new List<string>()
|
||||
{
|
||||
Constants.Applications.Users
|
||||
};
|
||||
|
||||
|
||||
public UserState UserState { get; }
|
||||
public string Name { get; set; }
|
||||
public int SessionTimeout { get; set; }
|
||||
public int[] StartContentIds { get; set; }
|
||||
public int[] StartMediaIds { get; set; }
|
||||
public string Language { get; set; }
|
||||
public DateTime? InvitedDate { get; set; }
|
||||
public IEnumerable<IReadOnlyUserGroup> Groups { get; }
|
||||
public void RemoveGroup(string @group) => throw new NotImplementedException();
|
||||
|
||||
public void ClearGroups() => throw new NotImplementedException();
|
||||
|
||||
public void AddGroup(IReadOnlyUserGroup @group) => throw new NotImplementedException();
|
||||
|
||||
public IProfile ProfileData { get; }
|
||||
public string Avatar { get; set; }
|
||||
public string TourData { get; set; }
|
||||
public T FromUserCache<T>(string cacheKey) where T : class => throw new NotImplementedException();
|
||||
|
||||
public void ToUserCache<T>(string cacheKey, T vals) where T : class => throw new NotImplementedException();
|
||||
|
||||
public object DeepClone() => throw new NotImplementedException();
|
||||
|
||||
public int Id { get; set; }
|
||||
public Guid Key { get; set; }
|
||||
public DateTime CreateDate { get; set; }
|
||||
public DateTime UpdateDate { get; set; }
|
||||
public DateTime? DeleteDate { get; set; }
|
||||
public bool HasIdentity { get; }
|
||||
public void ResetIdentity() => throw new NotImplementedException();
|
||||
|
||||
public string Username { get; set; }
|
||||
public string Email { get; set; }
|
||||
public DateTime? EmailConfirmedDate { get; set; }
|
||||
public string RawPasswordValue { get; set; }
|
||||
public string PasswordConfiguration { get; set; }
|
||||
public string Comments { get; set; }
|
||||
public bool IsApproved { get; set; }
|
||||
public bool IsLockedOut { get; set; }
|
||||
public DateTime LastLoginDate { get; set; }
|
||||
public DateTime LastPasswordChangeDate { get; set; }
|
||||
public DateTime LastLockoutDate { get; set; }
|
||||
public int FailedPasswordAttempts { get; set; }
|
||||
public string SecurityStamp { get; set; }
|
||||
public bool IsDirty() => throw new NotImplementedException();
|
||||
|
||||
public bool IsPropertyDirty(string propName) => throw new NotImplementedException();
|
||||
|
||||
public IEnumerable<string> GetDirtyProperties() => throw new NotImplementedException();
|
||||
|
||||
public void ResetDirtyProperties() => throw new NotImplementedException();
|
||||
|
||||
public void DisableChangeTracking() => throw new NotImplementedException();
|
||||
|
||||
public void EnableChangeTracking() => throw new NotImplementedException();
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
public bool WasDirty() => throw new NotImplementedException();
|
||||
|
||||
public bool WasPropertyDirty(string propertyName) => throw new NotImplementedException();
|
||||
|
||||
public void ResetWereDirtyProperties() => throw new NotImplementedException();
|
||||
|
||||
public void ResetDirtyProperties(bool rememberDirty) => throw new NotImplementedException();
|
||||
|
||||
public IEnumerable<string> GetWereDirtyProperties() => throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -263,14 +263,7 @@ namespace Umbraco.Infrastructure.Security
|
||||
/// </summary>
|
||||
/// <returns>A generated password</returns>
|
||||
string GeneratePassword();
|
||||
|
||||
/// <summary>
|
||||
/// Hashes a password for a null user based on the default password hasher
|
||||
/// </summary>
|
||||
/// <param name="password">The password to hash</param>
|
||||
/// <returns>The hashed password</returns>
|
||||
string HashPassword(string password);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Used to validate the password without an identity user
|
||||
/// Validation code is based on the default ValidatePasswordAsync code
|
||||
|
||||
@@ -100,19 +100,7 @@ namespace Umbraco.Infrastructure.Security
|
||||
string password = _passwordGenerator.GeneratePassword();
|
||||
return password;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a hashed password based on the default password hasher
|
||||
/// No existing identity user is required and this does not validate the password
|
||||
/// </summary>
|
||||
/// <param name="password">The password to hash</param>
|
||||
/// <returns>The hashed password</returns>
|
||||
public string HashPassword(string password)
|
||||
{
|
||||
string hashedPassword = PasswordHasher.HashPassword(null, password);
|
||||
return hashedPassword;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Used to validate the password without an identity user
|
||||
/// Validation code is based on the default ValidatePasswordAsync code
|
||||
|
||||
@@ -20,8 +20,8 @@ using Umbraco.Core.Events;
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.ContentEditing;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using Umbraco.Core.PropertyEditors.Validators;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Core.Serialization;
|
||||
using Umbraco.Core.Services;
|
||||
@@ -33,6 +33,7 @@ using Umbraco.Tests.UnitTests.Umbraco.Core.ShortStringHelper;
|
||||
using Umbraco.Web;
|
||||
using Umbraco.Web.BackOffice.Controllers;
|
||||
using Umbraco.Web.BackOffice.Mapping;
|
||||
using Umbraco.Web.BackOffice.Security;
|
||||
using Umbraco.Web.Common.ActionsResults;
|
||||
using Umbraco.Web.ContentApps;
|
||||
using Umbraco.Web.Models;
|
||||
@@ -69,11 +70,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers
|
||||
IMemberTypeService memberTypeService,
|
||||
IMemberGroupService memberGroupService,
|
||||
IDataTypeService dataTypeService,
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
|
||||
IPasswordChanger<MembersIdentityUser> passwordChanger)
|
||||
{
|
||||
// arrange
|
||||
Member member = SetupMemberTestData(out MemberSave fakeMemberData, out MemberDisplay memberDisplay, ContentSaveAction.SaveNew);
|
||||
MemberController sut = CreateSut(memberService, memberTypeService, memberGroupService, umbracoMembersUserManager, dataTypeService, backOfficeSecurityAccessor);
|
||||
MemberController sut = CreateSut(memberService, memberTypeService, memberGroupService, umbracoMembersUserManager, dataTypeService, backOfficeSecurityAccessor, passwordChanger);
|
||||
sut.ModelState.AddModelError("key", "Invalid model state");
|
||||
|
||||
Mock.Get(umbracoMembersUserManager)
|
||||
@@ -105,7 +107,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers
|
||||
IMemberGroupService memberGroupService,
|
||||
IDataTypeService dataTypeService,
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
|
||||
IBackOfficeSecurity backOfficeSecurity)
|
||||
IBackOfficeSecurity backOfficeSecurity,
|
||||
IPasswordChanger<MembersIdentityUser> passwordChanger)
|
||||
{
|
||||
// arrange
|
||||
Member member = SetupMemberTestData(out MemberSave fakeMemberData, out MemberDisplay memberDisplay, ContentSaveAction.SaveNew);
|
||||
@@ -123,7 +126,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers
|
||||
.Returns(() => member);
|
||||
Mock.Get(memberService).Setup(x => x.GetByUsername(It.IsAny<string>())).Returns(() => member);
|
||||
|
||||
MemberController sut = CreateSut(memberService, memberTypeService, memberGroupService, umbracoMembersUserManager, dataTypeService, backOfficeSecurityAccessor);
|
||||
MemberController sut = CreateSut(memberService, memberTypeService, memberGroupService, umbracoMembersUserManager, dataTypeService, backOfficeSecurityAccessor, passwordChanger);
|
||||
|
||||
// act
|
||||
ActionResult<MemberDisplay> result = await sut.PostSave(fakeMemberData);
|
||||
@@ -143,7 +146,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers
|
||||
IMemberGroupService memberGroupService,
|
||||
IDataTypeService dataTypeService,
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
|
||||
IBackOfficeSecurity backOfficeSecurity)
|
||||
IBackOfficeSecurity backOfficeSecurity,
|
||||
IPasswordChanger<MembersIdentityUser> passwordChanger)
|
||||
{
|
||||
// arrange
|
||||
Member member = SetupMemberTestData(out MemberSave fakeMemberData, out MemberDisplay memberDisplay, ContentSaveAction.SaveNew);
|
||||
@@ -161,7 +165,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers
|
||||
.Returns(() => member);
|
||||
Mock.Get(memberService).Setup(x => x.GetByUsername(It.IsAny<string>())).Returns(() => member);
|
||||
|
||||
MemberController sut = CreateSut(memberService, memberTypeService, memberGroupService, umbracoMembersUserManager, dataTypeService, backOfficeSecurityAccessor);
|
||||
MemberController sut = CreateSut(memberService, memberTypeService, memberGroupService, umbracoMembersUserManager, dataTypeService, backOfficeSecurityAccessor, passwordChanger);
|
||||
|
||||
// act
|
||||
ActionResult<MemberDisplay> result = await sut.PostSave(fakeMemberData);
|
||||
@@ -182,7 +186,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers
|
||||
IMemberGroupService memberGroupService,
|
||||
IDataTypeService dataTypeService,
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
|
||||
IBackOfficeSecurity backOfficeSecurity)
|
||||
IBackOfficeSecurity backOfficeSecurity,
|
||||
IPasswordChanger<MembersIdentityUser> passwordChanger)
|
||||
{
|
||||
// arrange
|
||||
Member member = SetupMemberTestData(out MemberSave fakeMemberData, out MemberDisplay memberDisplay, ContentSaveAction.Save);
|
||||
@@ -194,9 +199,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers
|
||||
.ReturnsAsync(() => IdentityResult.Success);
|
||||
|
||||
string password = "fakepassword9aw89rnyco3938cyr^%&*()i8Y";
|
||||
Mock.Get(umbracoMembersUserManager)
|
||||
.Setup(x => x.HashPassword(It.IsAny<string>()))
|
||||
.Returns(password);
|
||||
Mock.Get(passwordChanger)
|
||||
.Setup(x => x.ChangePasswordWithIdentityAsync(It.IsAny<ChangingPasswordModel>(), umbracoMembersUserManager))
|
||||
.ReturnsAsync(() => new Attempt<PasswordChangedModel>());
|
||||
Mock.Get(umbracoMembersUserManager)
|
||||
.Setup(x => x.UpdateAsync(It.IsAny<MembersIdentityUser>()))
|
||||
.ReturnsAsync(() => IdentityResult.Success);
|
||||
@@ -208,7 +213,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers
|
||||
.Returns(() => null)
|
||||
.Returns(() => member);
|
||||
|
||||
MemberController sut = CreateSut(memberService, memberTypeService, memberGroupService, umbracoMembersUserManager, dataTypeService, backOfficeSecurityAccessor);
|
||||
MemberController sut = CreateSut(memberService, memberTypeService, memberGroupService, umbracoMembersUserManager, dataTypeService, backOfficeSecurityAccessor, passwordChanger);
|
||||
|
||||
// act
|
||||
ActionResult<MemberDisplay> result = await sut.PostSave(fakeMemberData);
|
||||
@@ -228,7 +233,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers
|
||||
IMemberGroupService memberGroupService,
|
||||
IDataTypeService dataTypeService,
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
|
||||
IBackOfficeSecurity backOfficeSecurity)
|
||||
IBackOfficeSecurity backOfficeSecurity,
|
||||
IPasswordChanger<MembersIdentityUser> passwordChanger)
|
||||
{
|
||||
// arrange
|
||||
Member member = SetupMemberTestData(out MemberSave fakeMemberData, out MemberDisplay memberDisplay, ContentSaveAction.SaveNew);
|
||||
@@ -245,7 +251,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers
|
||||
x => x.GetByEmail(It.IsAny<string>()))
|
||||
.Returns(() => member);
|
||||
|
||||
MemberController sut = CreateSut(memberService, memberTypeService, memberGroupService, umbracoMembersUserManager, dataTypeService, backOfficeSecurityAccessor);
|
||||
MemberController sut = CreateSut(memberService, memberTypeService, memberGroupService, umbracoMembersUserManager, dataTypeService, backOfficeSecurityAccessor, passwordChanger);
|
||||
string reason = "Validation failed";
|
||||
|
||||
// act
|
||||
@@ -267,7 +273,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers
|
||||
IMemberGroupService memberGroupService,
|
||||
IDataTypeService dataTypeService,
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
|
||||
IBackOfficeSecurity backOfficeSecurity)
|
||||
IBackOfficeSecurity backOfficeSecurity,
|
||||
IPasswordChanger<MembersIdentityUser> passwordChanger)
|
||||
{
|
||||
// arrange
|
||||
string password = "fakepassword9aw89rnyco3938cyr^%&*()i8Y";
|
||||
@@ -277,16 +284,16 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers
|
||||
{
|
||||
roleName
|
||||
};
|
||||
var membersIdentityUser = new MembersIdentityUser();
|
||||
var membersIdentityUser = new MembersIdentityUser(123);
|
||||
Mock.Get(umbracoMembersUserManager)
|
||||
.Setup(x => x.FindByIdAsync(It.IsAny<string>()))
|
||||
.ReturnsAsync(() => membersIdentityUser);
|
||||
Mock.Get(umbracoMembersUserManager)
|
||||
.Setup(x => x.ValidatePasswordAsync(It.IsAny<string>()))
|
||||
.ReturnsAsync(() => IdentityResult.Success);
|
||||
Mock.Get(umbracoMembersUserManager)
|
||||
.Setup(x => x.HashPassword(It.IsAny<string>()))
|
||||
.Returns(password);
|
||||
Mock.Get(passwordChanger)
|
||||
.Setup(x => x.ChangePasswordWithIdentityAsync(It.IsAny<ChangingPasswordModel>(), umbracoMembersUserManager))
|
||||
.ReturnsAsync(() => Attempt.Succeed(new PasswordChangedModel()));
|
||||
Mock.Get(umbracoMembersUserManager)
|
||||
.Setup(x => x.UpdateAsync(It.IsAny<MembersIdentityUser>()))
|
||||
.ReturnsAsync(() => IdentityResult.Success);
|
||||
@@ -299,7 +306,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers
|
||||
.Returns(() => null)
|
||||
.Returns(() => member);
|
||||
Mock.Get(memberService).Setup(x => x.GetByUsername(It.IsAny<string>())).Returns(() => member);
|
||||
MemberController sut = CreateSut(memberService, memberTypeService, memberGroupService, umbracoMembersUserManager, dataTypeService, backOfficeSecurityAccessor);
|
||||
MemberController sut = CreateSut(memberService, memberTypeService, memberGroupService, umbracoMembersUserManager, dataTypeService, backOfficeSecurityAccessor, passwordChanger);
|
||||
|
||||
// act
|
||||
ActionResult<MemberDisplay> result = await sut.PostSave(fakeMemberData);
|
||||
@@ -309,8 +316,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers
|
||||
Assert.IsNotNull(result.Value);
|
||||
Mock.Get(umbracoMembersUserManager)
|
||||
.Verify(u => u.GetRolesAsync(membersIdentityUser));
|
||||
Mock.Get(umbracoMembersUserManager)
|
||||
.Verify(u => u.AddToRolesAsync(membersIdentityUser, new[] { roleName }));
|
||||
Mock.Get(umbracoMembersUserManager)
|
||||
.Verify(u => u.AddToRolesAsync(membersIdentityUser, new[] { roleName }));
|
||||
Mock.Get(memberService)
|
||||
.Verify(m => m.Save(It.IsAny<Member>(), true));
|
||||
AssertMemberDisplayPropertiesAreEqual(memberDisplay, result.Value);
|
||||
@@ -325,17 +332,18 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers
|
||||
/// <param name="membersUserManager">Members user manager</param>
|
||||
/// <param name="dataTypeService">Data type service</param>
|
||||
/// <param name="backOfficeSecurityAccessor">Back office security accessor</param>
|
||||
/// <param name="mockPasswordChanger">Password changer class</param>
|
||||
/// <returns>A member controller for the tests</returns>
|
||||
private MemberController CreateSut(
|
||||
IMemberService memberService,
|
||||
IMemberTypeService memberTypeService,
|
||||
IMemberGroupService memberGroupService,
|
||||
IMemberManager membersUserManager,
|
||||
IUmbracoUserManager<MembersIdentityUser> membersUserManager,
|
||||
IDataTypeService dataTypeService,
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
|
||||
IPasswordChanger<MembersIdentityUser> mockPasswordChanger)
|
||||
{
|
||||
var mockShortStringHelper = new MockShortStringHelper();
|
||||
|
||||
var textService = new Mock<ILocalizedTextService>();
|
||||
var contentTypeBaseServiceProvider = new Mock<IContentTypeBaseServiceProvider>();
|
||||
contentTypeBaseServiceProvider.Setup(x => x.GetContentTypeOf(It.IsAny<IContentBase>())).Returns(new ContentType(mockShortStringHelper, 123));
|
||||
@@ -410,13 +418,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers
|
||||
_mapper,
|
||||
memberService,
|
||||
memberTypeService,
|
||||
membersUserManager,
|
||||
(IMemberManager) membersUserManager,
|
||||
dataTypeService,
|
||||
backOfficeSecurityAccessor,
|
||||
new ConfigurationEditorJsonSerializer());
|
||||
new ConfigurationEditorJsonSerializer(),
|
||||
mockPasswordChanger);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Setup all standard member data for test
|
||||
/// </summary>
|
||||
@@ -428,10 +436,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers
|
||||
// arrange
|
||||
MemberType memberType = MemberTypeBuilder.CreateSimpleMemberType();
|
||||
Member member = MemberBuilder.CreateSimpleMember(memberType, "Test Member", "test@example.com", "123", "test");
|
||||
int memberId = 123;
|
||||
var memberId = 123;
|
||||
member.Id = memberId;
|
||||
|
||||
//TODO: replace with builder for MemberSave and MemberDisplay
|
||||
// TODO: replace with builder for MemberSave and MemberDisplay
|
||||
fakeMemberData = new MemberSave()
|
||||
{
|
||||
Id = memberId,
|
||||
|
||||
@@ -17,6 +17,7 @@ using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Media;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Strings;
|
||||
@@ -42,7 +43,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
private readonly ContentSettings _contentSettings;
|
||||
private readonly IHostingEnvironment _hostingEnvironment;
|
||||
private readonly IImageUrlGenerator _imageUrlGenerator;
|
||||
private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor;
|
||||
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
|
||||
private readonly IUserService _userService;
|
||||
private readonly UmbracoMapper _umbracoMapper;
|
||||
private readonly IBackOfficeUserManager _backOfficeUserManager;
|
||||
@@ -50,6 +51,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
private readonly ILocalizedTextService _localizedTextService;
|
||||
private readonly AppCaches _appCaches;
|
||||
private readonly IShortStringHelper _shortStringHelper;
|
||||
private readonly IPasswordChanger<BackOfficeIdentityUser> _passwordChanger;
|
||||
|
||||
public CurrentUserController(
|
||||
IMediaFileSystem mediaFileSystem,
|
||||
@@ -63,13 +65,14 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
ILoggerFactory loggerFactory,
|
||||
ILocalizedTextService localizedTextService,
|
||||
AppCaches appCaches,
|
||||
IShortStringHelper shortStringHelper)
|
||||
IShortStringHelper shortStringHelper,
|
||||
IPasswordChanger<BackOfficeIdentityUser> passwordChanger)
|
||||
{
|
||||
_mediaFileSystem = mediaFileSystem;
|
||||
_contentSettings = contentSettings.Value;
|
||||
_hostingEnvironment = hostingEnvironment;
|
||||
_imageUrlGenerator = imageUrlGenerator;
|
||||
_backofficeSecurityAccessor = backofficeSecurityAccessor;
|
||||
_backOfficeSecurityAccessor = backofficeSecurityAccessor;
|
||||
_userService = userService;
|
||||
_umbracoMapper = umbracoMapper;
|
||||
_backOfficeUserManager = backOfficeUserManager;
|
||||
@@ -77,6 +80,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
_localizedTextService = localizedTextService;
|
||||
_appCaches = appCaches;
|
||||
_shortStringHelper = shortStringHelper;
|
||||
_passwordChanger = passwordChanger;
|
||||
}
|
||||
|
||||
|
||||
@@ -89,7 +93,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
public Dictionary<int, string[]> GetPermissions(int[] nodeIds)
|
||||
{
|
||||
var permissions = _userService
|
||||
.GetPermissions(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser, nodeIds);
|
||||
.GetPermissions(_backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser, nodeIds);
|
||||
|
||||
var permissionsDictionary = new Dictionary<int, string[]>();
|
||||
foreach (var nodeId in nodeIds)
|
||||
@@ -110,7 +114,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
[HttpGet]
|
||||
public bool HasPermission(string permissionToCheck, int nodeId)
|
||||
{
|
||||
var p = _userService.GetPermissions(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser, nodeId).GetAllPermissions();
|
||||
var p = _userService.GetPermissions(_backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser, nodeId).GetAllPermissions();
|
||||
if (p.Contains(permissionToCheck.ToString(CultureInfo.InvariantCulture)))
|
||||
{
|
||||
return true;
|
||||
@@ -129,15 +133,15 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
if (status == null) throw new ArgumentNullException(nameof(status));
|
||||
|
||||
List<UserTourStatus> userTours;
|
||||
if (_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.TourData.IsNullOrWhiteSpace())
|
||||
if (_backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser.TourData.IsNullOrWhiteSpace())
|
||||
{
|
||||
userTours = new List<UserTourStatus> { status };
|
||||
_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.TourData = JsonConvert.SerializeObject(userTours);
|
||||
_userService.Save(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser);
|
||||
_backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser.TourData = JsonConvert.SerializeObject(userTours);
|
||||
_userService.Save(_backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser);
|
||||
return userTours;
|
||||
}
|
||||
|
||||
userTours = JsonConvert.DeserializeObject<IEnumerable<UserTourStatus>>(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.TourData).ToList();
|
||||
userTours = JsonConvert.DeserializeObject<IEnumerable<UserTourStatus>>(_backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser.TourData).ToList();
|
||||
var found = userTours.FirstOrDefault(x => x.Alias == status.Alias);
|
||||
if (found != null)
|
||||
{
|
||||
@@ -145,8 +149,8 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
userTours.Remove(found);
|
||||
}
|
||||
userTours.Add(status);
|
||||
_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.TourData = JsonConvert.SerializeObject(userTours);
|
||||
_userService.Save(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser);
|
||||
_backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser.TourData = JsonConvert.SerializeObject(userTours);
|
||||
_userService.Save(_backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser);
|
||||
return userTours;
|
||||
}
|
||||
|
||||
@@ -156,10 +160,10 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
/// <returns></returns>
|
||||
public IEnumerable<UserTourStatus> GetUserTours()
|
||||
{
|
||||
if (_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.TourData.IsNullOrWhiteSpace())
|
||||
if (_backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser.TourData.IsNullOrWhiteSpace())
|
||||
return Enumerable.Empty<UserTourStatus>();
|
||||
|
||||
var userTours = JsonConvert.DeserializeObject<IEnumerable<UserTourStatus>>(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.TourData);
|
||||
var userTours = JsonConvert.DeserializeObject<IEnumerable<UserTourStatus>>(_backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser.TourData);
|
||||
return userTours;
|
||||
}
|
||||
|
||||
@@ -175,7 +179,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
[AllowAnonymous]
|
||||
public async Task<ActionResult<UserDetail>> PostSetInvitedUserPassword([FromBody]string newPassword)
|
||||
{
|
||||
var user = await _backOfficeUserManager.FindByIdAsync(_backofficeSecurityAccessor.BackOfficeSecurity.GetUserId().ResultOr(0).ToString());
|
||||
var user = await _backOfficeUserManager.FindByIdAsync(_backOfficeSecurityAccessor.BackOfficeSecurity.GetUserId().ResultOr(0).ToString());
|
||||
if (user == null) throw new InvalidOperationException("Could not find user");
|
||||
|
||||
var result = await _backOfficeUserManager.AddPasswordAsync(user, newPassword);
|
||||
@@ -190,13 +194,13 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
}
|
||||
|
||||
//They've successfully set their password, we can now update their user account to be approved
|
||||
_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.IsApproved = true;
|
||||
_backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser.IsApproved = true;
|
||||
//They've successfully set their password, and will now get fully logged into the back office, so the lastlogindate is set so the backoffice shows they have logged in
|
||||
_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.LastLoginDate = DateTime.UtcNow;
|
||||
_userService.Save(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser);
|
||||
_backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser.LastLoginDate = DateTime.UtcNow;
|
||||
_userService.Save(_backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser);
|
||||
|
||||
//now we can return their full object since they are now really logged into the back office
|
||||
var userDisplay = _umbracoMapper.Map<UserDetail>(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser);
|
||||
var userDisplay = _umbracoMapper.Map<UserDetail>(_backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser);
|
||||
|
||||
userDisplay.SecondsUntilTimeout = HttpContext.User.GetRemainingAuthSeconds();
|
||||
return userDisplay;
|
||||
@@ -206,31 +210,38 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
public IActionResult PostSetAvatar(IList<IFormFile> file)
|
||||
{
|
||||
//borrow the logic from the user controller
|
||||
return UsersController.PostSetAvatarInternal(file, _userService, _appCaches.RuntimeCache, _mediaFileSystem, _shortStringHelper, _contentSettings, _hostingEnvironment, _imageUrlGenerator, _backofficeSecurityAccessor.BackOfficeSecurity.GetUserId().ResultOr(0));
|
||||
return UsersController.PostSetAvatarInternal(file, _userService, _appCaches.RuntimeCache, _mediaFileSystem, _shortStringHelper, _contentSettings, _hostingEnvironment, _imageUrlGenerator, _backOfficeSecurityAccessor.BackOfficeSecurity.GetUserId().ResultOr(0));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the users password
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <param name="changingPasswordModel">The changing password model</param>
|
||||
/// <returns>
|
||||
/// If the password is being reset it will return the newly reset password, otherwise will return an empty value
|
||||
/// </returns>
|
||||
public async Task<ActionResult<ModelWithNotifications<string>>> PostChangePassword(ChangingPasswordModel data)
|
||||
public async Task<ActionResult<ModelWithNotifications<string>>> PostChangePassword(ChangingPasswordModel changingPasswordModel)
|
||||
{
|
||||
// TODO: Why don't we inject this? Then we can just inject a logger
|
||||
var passwordChanger = new PasswordChanger(_loggerFactory.CreateLogger<PasswordChanger>());
|
||||
var passwordChangeResult = await passwordChanger.ChangePasswordWithIdentityAsync(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser, data, _backOfficeUserManager);
|
||||
IUser currentUser = _backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser;
|
||||
changingPasswordModel.CurrentUserHasSectionAccess = currentUser.HasSectionAccess(Constants.Applications.Users);
|
||||
|
||||
// the current user has access to change their password
|
||||
changingPasswordModel.CurrentUserHasSectionAccess = true;
|
||||
changingPasswordModel.CurrentUsername = currentUser.Username;
|
||||
changingPasswordModel.SavingUsername = currentUser.Username;
|
||||
changingPasswordModel.SavingUserId = currentUser.Id;
|
||||
|
||||
Attempt<PasswordChangedModel> passwordChangeResult = await _passwordChanger.ChangePasswordWithIdentityAsync(changingPasswordModel, _backOfficeUserManager);
|
||||
|
||||
if (passwordChangeResult.Success)
|
||||
{
|
||||
//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
|
||||
// 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(_localizedTextService.Localize("user/password"), _localizedTextService.Localize("user/passwordChanged"));
|
||||
return result;
|
||||
}
|
||||
|
||||
foreach (var memberName in passwordChangeResult.Result.ChangeError.MemberNames)
|
||||
foreach (string memberName in passwordChangeResult.Result.ChangeError.MemberNames)
|
||||
{
|
||||
ModelState.AddModelError(memberName, passwordChangeResult.Result.ChangeError.ErrorMessage);
|
||||
}
|
||||
@@ -243,7 +254,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
[ValidateAngularAntiForgeryToken]
|
||||
public async Task<Dictionary<string, string>> GetCurrentUserLinkedLogins()
|
||||
{
|
||||
var identityUser = await _backOfficeUserManager.FindByIdAsync(_backofficeSecurityAccessor.BackOfficeSecurity.GetUserId().ResultOr(0).ToString());
|
||||
var identityUser = await _backOfficeUserManager.FindByIdAsync(_backOfficeSecurityAccessor.BackOfficeSecurity.GetUserId().ResultOr(0).ToString());
|
||||
|
||||
// deduplicate in case there are duplicates (there shouldn't be now since we have a unique constraint on the external logins
|
||||
// but there didn't used to be)
|
||||
|
||||
@@ -28,11 +28,13 @@ using Umbraco.Infrastructure.Security;
|
||||
using Umbraco.Infrastructure.Services.Implement;
|
||||
using Umbraco.Web.BackOffice.Filters;
|
||||
using Umbraco.Web.BackOffice.ModelBinders;
|
||||
using Umbraco.Web.BackOffice.Security;
|
||||
using Umbraco.Web.Common.ActionsResults;
|
||||
using Umbraco.Web.Common.Attributes;
|
||||
using Umbraco.Web.Common.Authorization;
|
||||
using Umbraco.Web.Common.Filters;
|
||||
using Umbraco.Web.ContentApps;
|
||||
using Umbraco.Web.Models;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Web.BackOffice.Controllers
|
||||
@@ -56,6 +58,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
private readonly IShortStringHelper _shortStringHelper;
|
||||
private readonly IPasswordChanger<MembersIdentityUser> _passwordChanger;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MemberController"/> class.
|
||||
@@ -73,6 +76,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
/// <param name="dataTypeService">The data-type service</param>
|
||||
/// <param name="backOfficeSecurityAccessor">The back office security accessor</param>
|
||||
/// <param name="jsonSerializer">The JSON serializer</param>
|
||||
/// <param name="passwordChanger">The password changer</param>
|
||||
public MemberController(
|
||||
ICultureDictionary cultureDictionary,
|
||||
ILoggerFactory loggerFactory,
|
||||
@@ -86,7 +90,8 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
IMemberManager memberManager,
|
||||
IDataTypeService dataTypeService,
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
|
||||
IJsonSerializer jsonSerializer)
|
||||
IJsonSerializer jsonSerializer,
|
||||
IPasswordChanger<MembersIdentityUser> passwordChanger)
|
||||
: base(cultureDictionary, loggerFactory, shortStringHelper, eventMessages, localizedTextService, jsonSerializer)
|
||||
{
|
||||
_propertyEditors = propertyEditors;
|
||||
@@ -99,6 +104,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
|
||||
_jsonSerializer = jsonSerializer;
|
||||
_shortStringHelper = shortStringHelper;
|
||||
_passwordChanger = passwordChanger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -390,7 +396,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
}
|
||||
|
||||
//TODO: do we need to resave the key?
|
||||
//contentItem.PersistedContent.Key = contentItem.Key;
|
||||
// contentItem.PersistedContent.Key = contentItem.Key;
|
||||
|
||||
// now the member has been saved via identity, resave the member with mapped content properties
|
||||
_memberService.Save(member);
|
||||
@@ -450,7 +456,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
MembersIdentityUser identityMember = await _memberManager.FindByIdAsync(contentItem.Id.ToString());
|
||||
if (identityMember == null)
|
||||
{
|
||||
return new ValidationErrorResult("Member was not found");
|
||||
return new ValidationErrorResult("Identity member was not found");
|
||||
}
|
||||
|
||||
if (contentItem.Password != null)
|
||||
@@ -461,14 +467,52 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
return new ValidationErrorResult(validatePassword.Errors.ToErrorMessage());
|
||||
}
|
||||
|
||||
string newPassword = _memberManager.HashPassword(contentItem.Password.NewPassword);
|
||||
identityMember.PasswordHash = newPassword;
|
||||
contentItem.PersistedContent.RawPasswordValue = identityMember.PasswordHash;
|
||||
if (identityMember.LastPasswordChangeDateUtc != null)
|
||||
Attempt<int> intId = identityMember.Id.TryConvertTo<int>();
|
||||
if (intId.Success == false)
|
||||
{
|
||||
contentItem.PersistedContent.LastPasswordChangeDate = DateTime.UtcNow;
|
||||
return new ValidationErrorResult("Member ID was not valid");
|
||||
}
|
||||
|
||||
IMember foundMember = _memberService.GetById(intId.Result);
|
||||
if (foundMember == null)
|
||||
{
|
||||
return new ValidationErrorResult("Member was not found");
|
||||
}
|
||||
|
||||
IUser currentUser = _backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser;
|
||||
var changingPasswordModel = new ChangingPasswordModel
|
||||
{
|
||||
Id = intId.Result,
|
||||
OldPassword = contentItem.Password.OldPassword,
|
||||
NewPassword = contentItem.Password.NewPassword,
|
||||
CurrentUsername = currentUser.Username,
|
||||
SavingUserId = foundMember.Id,
|
||||
SavingUsername = foundMember.Username,
|
||||
CurrentUserHasSectionAccess = currentUser.HasSectionAccess(Constants.Applications.Members)
|
||||
};
|
||||
|
||||
Attempt<PasswordChangedModel> passwordChangeResult = await _passwordChanger.ChangePasswordWithIdentityAsync(changingPasswordModel, _memberManager);
|
||||
|
||||
if (passwordChangeResult.Success)
|
||||
{
|
||||
contentItem.PersistedContent.RawPasswordValue = passwordChangeResult.Result.ResetPassword;
|
||||
if (identityMember.LastPasswordChangeDateUtc != null)
|
||||
{
|
||||
contentItem.PersistedContent.LastPasswordChangeDate = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
identityMember.LastPasswordChangeDateUtc = contentItem.PersistedContent.LastPasswordChangeDate;
|
||||
}
|
||||
|
||||
if (passwordChangeResult.Result.ChangeError?.MemberNames != null)
|
||||
{
|
||||
foreach (string memberName in passwordChangeResult.Result.ChangeError?.MemberNames)
|
||||
{
|
||||
ModelState.AddModelError(memberName, passwordChangeResult.Result.ChangeError?.ErrorMessage ?? string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
return new ValidationErrorResult(new SimpleValidationModel(ModelState.ToErrorDictionary()));
|
||||
}
|
||||
|
||||
IdentityResult updatedResult = await _memberManager.UpdateAsync(identityMember);
|
||||
|
||||
@@ -23,6 +23,7 @@ using Umbraco.Core.Mail;
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Media;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Identity;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Security;
|
||||
@@ -56,7 +57,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
private readonly IImageUrlGenerator _imageUrlGenerator;
|
||||
private readonly SecuritySettings _securitySettings;
|
||||
private readonly IEmailSender _emailSender;
|
||||
private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor;
|
||||
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
|
||||
private readonly AppCaches _appCaches;
|
||||
private readonly IShortStringHelper _shortStringHelper;
|
||||
private readonly IUserService _userService;
|
||||
@@ -68,6 +69,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
private readonly LinkGenerator _linkGenerator;
|
||||
private readonly IBackOfficeExternalLoginProviders _externalLogins;
|
||||
private readonly UserEditorAuthorizationHelper _userEditorAuthorizationHelper;
|
||||
private readonly IPasswordChanger<BackOfficeIdentityUser> _passwordChanger;
|
||||
private readonly ILogger<UsersController> _logger;
|
||||
|
||||
public UsersController(
|
||||
@@ -89,7 +91,8 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
ILoggerFactory loggerFactory,
|
||||
LinkGenerator linkGenerator,
|
||||
IBackOfficeExternalLoginProviders externalLogins,
|
||||
UserEditorAuthorizationHelper userEditorAuthorizationHelper)
|
||||
UserEditorAuthorizationHelper userEditorAuthorizationHelper,
|
||||
IPasswordChanger<BackOfficeIdentityUser> passwordChanger)
|
||||
{
|
||||
_mediaFileSystem = mediaFileSystem;
|
||||
_contentSettings = contentSettings.Value;
|
||||
@@ -98,7 +101,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
_imageUrlGenerator = imageUrlGenerator;
|
||||
_securitySettings = securitySettings.Value;
|
||||
_emailSender = emailSender;
|
||||
_backofficeSecurityAccessor = backofficeSecurityAccessor;
|
||||
_backOfficeSecurityAccessor = backofficeSecurityAccessor;
|
||||
_appCaches = appCaches;
|
||||
_shortStringHelper = shortStringHelper;
|
||||
_userService = userService;
|
||||
@@ -110,6 +113,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
_linkGenerator = linkGenerator;
|
||||
_externalLogins = externalLogins;
|
||||
_userEditorAuthorizationHelper = userEditorAuthorizationHelper;
|
||||
_passwordChanger = passwordChanger;
|
||||
_logger = _loggerFactory.CreateLogger<UsersController>();
|
||||
}
|
||||
|
||||
@@ -119,7 +123,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
/// <returns></returns>
|
||||
public ActionResult<string[]> GetCurrentUserAvatarUrls()
|
||||
{
|
||||
var urls = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.GetUserAvatarUrls(_appCaches.RuntimeCache, _mediaFileSystem, _imageUrlGenerator);
|
||||
var urls = _backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser.GetUserAvatarUrls(_appCaches.RuntimeCache, _mediaFileSystem, _imageUrlGenerator);
|
||||
if (urls == null)
|
||||
return new ValidationErrorResult("Could not access Gravatar endpoint");
|
||||
|
||||
@@ -285,7 +289,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
|
||||
var hideDisabledUsers = _securitySettings.HideDisabledUsersInBackOffice;
|
||||
var excludeUserGroups = new string[0];
|
||||
var isAdmin = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.IsAdmin();
|
||||
var isAdmin = _backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser.IsAdmin();
|
||||
if (isAdmin == false)
|
||||
{
|
||||
//this user is not an admin so in that case we need to exclude all admin users
|
||||
@@ -294,7 +298,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
|
||||
var filterQuery = _sqlContext.Query<IUser>();
|
||||
|
||||
if (!_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.IsSuper())
|
||||
if (!_backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser.IsSuper())
|
||||
{
|
||||
// only super can see super - but don't use IsSuper, cannot be mapped to SQL
|
||||
//filterQuery.Where(x => !x.IsSuper());
|
||||
@@ -359,7 +363,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
}
|
||||
|
||||
//Perform authorization here to see if the current user can actually save this user with the info being requested
|
||||
var canSaveUser = _userEditorAuthorizationHelper.IsAuthorized(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser, null, null, null, userSave.UserGroups);
|
||||
var canSaveUser = _userEditorAuthorizationHelper.IsAuthorized(_backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser, null, null, null, userSave.UserGroups);
|
||||
if (canSaveUser == false)
|
||||
{
|
||||
return Unauthorized(canSaveUser.Result);
|
||||
@@ -448,7 +452,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
}
|
||||
|
||||
//Perform authorization here to see if the current user can actually save this user with the info being requested
|
||||
var canSaveUser = _userEditorAuthorizationHelper.IsAuthorized(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser, user, null, null, userSave.UserGroups);
|
||||
var canSaveUser = _userEditorAuthorizationHelper.IsAuthorized(_backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser, user, null, null, userSave.UserGroups);
|
||||
if (canSaveUser == false)
|
||||
{
|
||||
return new ValidationErrorResult(canSaveUser.Result, StatusCodes.Status401Unauthorized);
|
||||
@@ -511,7 +515,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
{
|
||||
//send the email
|
||||
|
||||
await SendUserInviteEmailAsync(display, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Name, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Email, user, userSave.Message);
|
||||
await SendUserInviteEmailAsync(display, _backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Name, _backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Email, user, userSave.Message);
|
||||
|
||||
}
|
||||
|
||||
@@ -605,7 +609,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
return NotFound();
|
||||
|
||||
//Perform authorization here to see if the current user can actually save this user with the info being requested
|
||||
var canSaveUser = _userEditorAuthorizationHelper.IsAuthorized(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser, found, userSave.StartContentIds, userSave.StartMediaIds, userSave.UserGroups);
|
||||
var canSaveUser = _userEditorAuthorizationHelper.IsAuthorized(_backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser, found, userSave.StartContentIds, userSave.StartMediaIds, userSave.UserGroups);
|
||||
if (canSaveUser == false)
|
||||
{
|
||||
return Unauthorized(canSaveUser.Result);
|
||||
@@ -665,7 +669,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
var display = _umbracoMapper.Map<UserDisplay>(user);
|
||||
|
||||
// determine if the user has changed their own language;
|
||||
var currentUser = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser;
|
||||
var currentUser = _backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser;
|
||||
var userHasChangedOwnLanguage =
|
||||
user.Id == currentUser.Id && currentUser.Language != user.Language;
|
||||
|
||||
@@ -691,21 +695,25 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
return new ValidationErrorResult(new SimpleValidationModel(ModelState.ToErrorDictionary()));
|
||||
}
|
||||
|
||||
var intId = changingPasswordModel.Id.TryConvertTo<int>();
|
||||
Attempt<int> intId = changingPasswordModel.Id.TryConvertTo<int>();
|
||||
if (intId.Success == false)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var found = _userService.GetUserById(intId.Result);
|
||||
IUser found = _userService.GetUserById(intId.Result);
|
||||
if (found == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
// TODO: Why don't we inject this? Then we can just inject a logger
|
||||
var passwordChanger = new PasswordChanger(_loggerFactory.CreateLogger<PasswordChanger>());
|
||||
var passwordChangeResult = await passwordChanger.ChangePasswordWithIdentityAsync(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser, found, changingPasswordModel, _userManager);
|
||||
IUser currentUser = _backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser;
|
||||
changingPasswordModel.CurrentUserHasSectionAccess = currentUser.HasSectionAccess(Constants.Applications.Users);
|
||||
changingPasswordModel.CurrentUsername = currentUser.Username;
|
||||
changingPasswordModel.SavingUserId = found.Id;
|
||||
changingPasswordModel.SavingUsername = found.Username;
|
||||
|
||||
Attempt<PasswordChangedModel> passwordChangeResult = await _passwordChanger.ChangePasswordWithIdentityAsync(changingPasswordModel, _userManager);
|
||||
|
||||
if (passwordChangeResult.Success)
|
||||
{
|
||||
@@ -714,7 +722,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
return result;
|
||||
}
|
||||
|
||||
foreach (var memberName in passwordChangeResult.Result.ChangeError.MemberNames)
|
||||
foreach (string memberName in passwordChangeResult.Result.ChangeError.MemberNames)
|
||||
{
|
||||
ModelState.AddModelError(memberName, passwordChangeResult.Result.ChangeError.ErrorMessage);
|
||||
}
|
||||
@@ -730,7 +738,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
[Authorize(Policy = AuthorizationPolicies.AdminUserEditsRequireAdmin)]
|
||||
public IActionResult PostDisableUsers([FromQuery]int[] userIds)
|
||||
{
|
||||
var tryGetCurrentUserId = _backofficeSecurityAccessor.BackOfficeSecurity.GetUserId();
|
||||
var tryGetCurrentUserId = _backOfficeSecurityAccessor.BackOfficeSecurity.GetUserId();
|
||||
if (tryGetCurrentUserId && userIds.Contains(tryGetCurrentUserId.Result))
|
||||
{
|
||||
return ValidationErrorResult.CreateNotificationValidationErrorResult("The current user cannot disable itself");
|
||||
|
||||
@@ -6,6 +6,7 @@ using Microsoft.Extensions.Logging;
|
||||
using Umbraco.Core.DependencyInjection;
|
||||
using Umbraco.Core.Hosting;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Models.Identity;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Extensions;
|
||||
using Umbraco.Infrastructure.DependencyInjection;
|
||||
@@ -81,6 +82,7 @@ namespace Umbraco.Web.BackOffice.DependencyInjection
|
||||
builder.Services.AddUnique<PreviewAuthenticationMiddleware>();
|
||||
builder.Services.AddUnique<BackOfficeExternalLoginProviderErrorMiddleware>();
|
||||
builder.Services.AddUnique<IBackOfficeAntiforgery, BackOfficeAntiforgery>();
|
||||
builder.Services.AddUnique<IPasswordChanger<UmbracoIdentityUser>, PasswordChanger<UmbracoIdentityUser>>();
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
16
src/Umbraco.Web.BackOffice/Security/IPasswordChanger.cs
Normal file
16
src/Umbraco.Web.BackOffice/Security/IPasswordChanger.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System.Threading.Tasks;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Identity;
|
||||
using Umbraco.Infrastructure.Security;
|
||||
using Umbraco.Web.Models;
|
||||
|
||||
namespace Umbraco.Web.BackOffice.Security
|
||||
{
|
||||
public interface IPasswordChanger<TUser> where TUser : UmbracoIdentityUser
|
||||
{
|
||||
public Task<Attempt<PasswordChangedModel>> ChangePasswordWithIdentityAsync(
|
||||
ChangingPasswordModel passwordModel,
|
||||
IUmbracoUserManager<TUser> userMgr);
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,31 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Extensions;
|
||||
using Umbraco.Core.Models.Identity;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Infrastructure.Security;
|
||||
using Umbraco.Web.Models;
|
||||
using IUser = Umbraco.Core.Models.Membership.IUser;
|
||||
|
||||
namespace Umbraco.Web.BackOffice.Security
|
||||
{
|
||||
internal class PasswordChanger
|
||||
/// <summary>
|
||||
/// Changes the password for an identity user
|
||||
/// </summary>
|
||||
internal class PasswordChanger<TUser> : IPasswordChanger<TUser>
|
||||
where TUser : UmbracoIdentityUser
|
||||
{
|
||||
private readonly ILogger<PasswordChanger> _logger;
|
||||
private readonly ILogger<PasswordChanger<TUser>> _logger;
|
||||
|
||||
public PasswordChanger(ILogger<PasswordChanger> logger)
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PasswordChanger"/> class.
|
||||
/// Password changing functionality
|
||||
/// </summary>
|
||||
/// <param name="logger">Logger for this class</param>
|
||||
public PasswordChanger(ILogger<PasswordChanger<TUser>> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
@@ -24,55 +33,60 @@ namespace Umbraco.Web.BackOffice.Security
|
||||
/// <summary>
|
||||
/// Changes the password for a user based on the many different rules and config options
|
||||
/// </summary>
|
||||
/// <param name="currentUser">The user performing the password save action</param>
|
||||
/// <param name="savingUser">The user who's password is being changed</param>
|
||||
/// <param name="passwordModel"></param>
|
||||
/// <param name="userMgr"></param>
|
||||
/// <returns></returns>
|
||||
/// <param name="changingPasswordModel">The changing password model</param>
|
||||
/// <param name="userMgr">The identity manager to use to update the password</param>
|
||||
/// Create an adapter to pass through everything - adapting the member into a user for this functionality
|
||||
/// <returns>The outcome of the password changed model</returns>
|
||||
public async Task<Attempt<PasswordChangedModel>> ChangePasswordWithIdentityAsync(
|
||||
IUser currentUser,
|
||||
IUser savingUser,
|
||||
ChangingPasswordModel passwordModel,
|
||||
IBackOfficeUserManager userMgr)
|
||||
ChangingPasswordModel changingPasswordModel,
|
||||
IUmbracoUserManager<TUser> userMgr)
|
||||
{
|
||||
if (passwordModel == null) throw new ArgumentNullException(nameof(passwordModel));
|
||||
if (userMgr == null) throw new ArgumentNullException(nameof(userMgr));
|
||||
if (changingPasswordModel == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(changingPasswordModel));
|
||||
}
|
||||
|
||||
if (passwordModel.NewPassword.IsNullOrWhiteSpace())
|
||||
if (userMgr == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(userMgr));
|
||||
}
|
||||
|
||||
if (changingPasswordModel.NewPassword.IsNullOrWhiteSpace())
|
||||
{
|
||||
return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Cannot set an empty password", new[] { "value" }) });
|
||||
}
|
||||
|
||||
var backOfficeIdentityUser = await userMgr.FindByIdAsync(savingUser.Id.ToString());
|
||||
if (backOfficeIdentityUser == null)
|
||||
TUser identityUser = await userMgr.FindByIdAsync(changingPasswordModel.SavingUserId.ToString());
|
||||
if (identityUser == null)
|
||||
{
|
||||
//this really shouldn't ever happen... but just in case
|
||||
// this really shouldn't ever happen... but just in case
|
||||
return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Password could not be verified", new[] { "oldPassword" }) });
|
||||
}
|
||||
|
||||
//Are we just changing another user's password?
|
||||
if (passwordModel.OldPassword.IsNullOrWhiteSpace())
|
||||
// Are we just changing another user's password?
|
||||
if (changingPasswordModel.OldPassword.IsNullOrWhiteSpace())
|
||||
{
|
||||
//if it's the current user, the current user cannot reset their own password
|
||||
if (currentUser.Username == savingUser.Username)
|
||||
// if it's the current user, the current user cannot reset their own password
|
||||
// For members, this should not happen
|
||||
if (changingPasswordModel.CurrentUsername == changingPasswordModel.SavingUsername)
|
||||
{
|
||||
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)
|
||||
// if the current user has access to reset/manually change the password
|
||||
if (changingPasswordModel.CurrentUserHasSectionAccess)
|
||||
{
|
||||
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(backOfficeIdentityUser);
|
||||
// ok, we should be able to reset it
|
||||
string resetToken = await userMgr.GeneratePasswordResetTokenAsync(identityUser);
|
||||
|
||||
var resetResult = await userMgr.ChangePasswordWithResetAsync(savingUser.Id.ToString(), resetToken, passwordModel.NewPassword);
|
||||
IdentityResult resetResult = await userMgr.ChangePasswordWithResetAsync(changingPasswordModel.SavingUserId.ToString(), resetToken, changingPasswordModel.NewPassword);
|
||||
|
||||
if (resetResult.Succeeded == false)
|
||||
{
|
||||
var errors = resetResult.Errors.ToErrorMessage();
|
||||
string errors = resetResult.Errors.ToErrorMessage();
|
||||
_logger.LogWarning("Could not reset user password {PasswordErrors}", errors);
|
||||
return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult(errors, new[] { "value" }) });
|
||||
}
|
||||
@@ -80,24 +94,25 @@ namespace Umbraco.Web.BackOffice.Security
|
||||
return Attempt.Succeed(new PasswordChangedModel());
|
||||
}
|
||||
|
||||
//is the old password correct?
|
||||
var validateResult = await userMgr.CheckPasswordAsync(backOfficeIdentityUser, passwordModel.OldPassword);
|
||||
// is the old password correct?
|
||||
bool validateResult = await userMgr.CheckPasswordAsync(identityUser, changingPasswordModel.OldPassword);
|
||||
if (validateResult == false)
|
||||
{
|
||||
//no, fail with an error message for "oldPassword"
|
||||
// no, fail with an error message for "oldPassword"
|
||||
return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Incorrect password", new[] { "oldPassword" }) });
|
||||
}
|
||||
//can we change to the new password?
|
||||
var changeResult = await userMgr.ChangePasswordAsync(backOfficeIdentityUser, passwordModel.OldPassword, passwordModel.NewPassword);
|
||||
|
||||
// can we change to the new password?
|
||||
IdentityResult changeResult = await userMgr.ChangePasswordAsync(identityUser, changingPasswordModel.OldPassword, changingPasswordModel.NewPassword);
|
||||
if (changeResult.Succeeded == false)
|
||||
{
|
||||
//no, fail with error messages for "password"
|
||||
var errors = changeResult.Errors.ToErrorMessage();
|
||||
// no, fail with error messages for "password"
|
||||
string errors = changeResult.Errors.ToErrorMessage();
|
||||
_logger.LogWarning("Could not change user password {PasswordErrors}", errors);
|
||||
return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult(errors, new[] { "password" }) });
|
||||
}
|
||||
|
||||
return Attempt.Succeed(new PasswordChangedModel());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user