From 093548b933d78a238d85ca56232a4c045a4e648a Mon Sep 17 00:00:00 2001 From: Emma Garland Date: Tue, 9 Feb 2021 16:14:32 +0000 Subject: [PATCH] Moved EmailConfirmed and SecurityStamp properties to shared IMembershipUser (for both IMember and IUser to use) --- src/Umbraco.Core/Models/Member.cs | 117 +++++++++++++----- .../Models/Membership/IMembershipUser.cs | 8 +- src/Umbraco.Core/Models/Membership/IUser.cs | 8 +- .../Security/IdentityMapDefinition.cs | 4 +- .../Security/MembersUserStore.cs | 24 ++-- 5 files changed, 107 insertions(+), 54 deletions(-) diff --git a/src/Umbraco.Core/Models/Member.cs b/src/Umbraco.Core/Models/Member.cs index 8a765b2f25..7a3b2fd20f 100644 --- a/src/Umbraco.Core/Models/Member.cs +++ b/src/Umbraco.Core/Models/Member.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.ComponentModel; using System.Runtime.Serialization; @@ -19,8 +19,11 @@ namespace Umbraco.Core.Models private string _email; private string _rawPasswordValue; private string _passwordConfig; + private DateTime? _emailConfirmedDate; + private string _securityStamp; /// + /// Initializes a new instance of the class. /// Constructor for creating an empty Member object /// /// ContentType for the current Content object @@ -29,13 +32,14 @@ namespace Umbraco.Core.Models { IsApproved = true; - //this cannot be null but can be empty + // this cannot be null but can be empty _rawPasswordValue = ""; _email = ""; _username = ""; } /// + /// Initializes a new instance of the class. /// Constructor for creating a Member object /// /// Name of the content @@ -43,18 +47,21 @@ namespace Umbraco.Core.Models public Member(string name, IMemberType contentType) : base(name, -1, contentType, new PropertyCollection()) { - if (name == null) throw new ArgumentNullException(nameof(name)); - if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(name)); + if (name == null) + throw new ArgumentNullException(nameof(name)); + if (string.IsNullOrWhiteSpace(name)) + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(name)); IsApproved = true; - //this cannot be null but can be empty + // this cannot be null but can be empty _rawPasswordValue = ""; _email = ""; _username = ""; } /// + /// Initializes a new instance of the class. /// Constructor for creating a Member object /// /// @@ -64,22 +71,29 @@ namespace Umbraco.Core.Models public Member(string name, string email, string username, IMemberType contentType, bool isApproved = true) : base(name, -1, contentType, new PropertyCollection()) { - if (name == null) throw new ArgumentNullException(nameof(name)); - if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(name)); - if (email == null) throw new ArgumentNullException(nameof(email)); - if (string.IsNullOrWhiteSpace(email)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(email)); - if (username == null) throw new ArgumentNullException(nameof(username)); - if (string.IsNullOrWhiteSpace(username)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(username)); + if (name == null) + throw new ArgumentNullException(nameof(name)); + if (string.IsNullOrWhiteSpace(name)) + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(name)); + if (email == null) + throw new ArgumentNullException(nameof(email)); + if (string.IsNullOrWhiteSpace(email)) + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(email)); + if (username == null) + throw new ArgumentNullException(nameof(username)); + if (string.IsNullOrWhiteSpace(username)) + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(username)); _email = email; _username = username; IsApproved = isApproved; - //this cannot be null but can be empty + // this cannot be null but can be empty _rawPasswordValue = ""; } /// + /// Initializes a new instance of the class. /// Constructor for creating a Member object /// /// @@ -99,6 +113,7 @@ namespace Umbraco.Core.Models } /// + /// Initializes a new instance of the class. /// Constructor for creating a Member object /// /// @@ -138,6 +153,13 @@ namespace Umbraco.Core.Models set => SetPropertyValueAndDetectChanges(value, ref _email, nameof(Email)); } + [DataMember] + public DateTime? EmailConfirmedDate + { + get => _emailConfirmedDate; + set => SetPropertyValueAndDetectChanges(value, ref _emailConfirmedDate, nameof(EmailConfirmedDate)); + } + /// /// Gets or sets the raw password value /// @@ -190,7 +212,8 @@ namespace Umbraco.Core.Models get { var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.Comments, nameof(Comments), default(string)); - if (a.Success == false) return a.Result; + if (a.Success == false) + return a.Result; return Properties[Constants.Conventions.Member.Comments].GetValue() == null ? string.Empty @@ -200,7 +223,8 @@ namespace Umbraco.Core.Models { if (WarnIfPropertyTypeNotFoundOnSet( Constants.Conventions.Member.Comments, - nameof(Comments)) == false) return; + nameof(Comments)) == false) + return; Properties[Constants.Conventions.Member.Comments].SetValue(value); } @@ -221,8 +245,10 @@ namespace Umbraco.Core.Models var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.IsApproved, nameof(IsApproved), //This is the default value if the prop is not found true); - if (a.Success == false) return a.Result; - if (Properties[Constants.Conventions.Member.IsApproved].GetValue() == null) return true; + if (a.Success == false) + return a.Result; + if (Properties[Constants.Conventions.Member.IsApproved].GetValue() == null) + return true; var tryConvert = Properties[Constants.Conventions.Member.IsApproved].GetValue().TryConvertTo(); if (tryConvert.Success) { @@ -235,7 +261,8 @@ namespace Umbraco.Core.Models { if (WarnIfPropertyTypeNotFoundOnSet( Constants.Conventions.Member.IsApproved, - nameof(IsApproved)) == false) return; + nameof(IsApproved)) == false) + return; Properties[Constants.Conventions.Member.IsApproved].SetValue(value); } @@ -254,8 +281,10 @@ namespace Umbraco.Core.Models get { var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.IsLockedOut, nameof(IsLockedOut), false); - if (a.Success == false) return a.Result; - if (Properties[Constants.Conventions.Member.IsLockedOut].GetValue() == null) return false; + if (a.Success == false) + return a.Result; + if (Properties[Constants.Conventions.Member.IsLockedOut].GetValue() == null) + return false; var tryConvert = Properties[Constants.Conventions.Member.IsLockedOut].GetValue().TryConvertTo(); if (tryConvert.Success) { @@ -268,7 +297,8 @@ namespace Umbraco.Core.Models { if (WarnIfPropertyTypeNotFoundOnSet( Constants.Conventions.Member.IsLockedOut, - nameof(IsLockedOut)) == false) return; + nameof(IsLockedOut)) == false) + return; Properties[Constants.Conventions.Member.IsLockedOut].SetValue(value); } @@ -287,8 +317,10 @@ namespace Umbraco.Core.Models get { var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.LastLoginDate, nameof(LastLoginDate), default(DateTime)); - if (a.Success == false) return a.Result; - if (Properties[Constants.Conventions.Member.LastLoginDate].GetValue() == null) return default(DateTime); + if (a.Success == false) + return a.Result; + if (Properties[Constants.Conventions.Member.LastLoginDate].GetValue() == null) + return default(DateTime); var tryConvert = Properties[Constants.Conventions.Member.LastLoginDate].GetValue().TryConvertTo(); if (tryConvert.Success) { @@ -301,7 +333,8 @@ namespace Umbraco.Core.Models { if (WarnIfPropertyTypeNotFoundOnSet( Constants.Conventions.Member.LastLoginDate, - nameof(LastLoginDate)) == false) return; + nameof(LastLoginDate)) == false) + return; Properties[Constants.Conventions.Member.LastLoginDate].SetValue(value); } @@ -320,8 +353,10 @@ namespace Umbraco.Core.Models get { var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.LastPasswordChangeDate, nameof(LastPasswordChangeDate), default(DateTime)); - if (a.Success == false) return a.Result; - if (Properties[Constants.Conventions.Member.LastPasswordChangeDate].GetValue() == null) return default(DateTime); + if (a.Success == false) + return a.Result; + if (Properties[Constants.Conventions.Member.LastPasswordChangeDate].GetValue() == null) + return default(DateTime); var tryConvert = Properties[Constants.Conventions.Member.LastPasswordChangeDate].GetValue().TryConvertTo(); if (tryConvert.Success) { @@ -334,7 +369,8 @@ namespace Umbraco.Core.Models { if (WarnIfPropertyTypeNotFoundOnSet( Constants.Conventions.Member.LastPasswordChangeDate, - nameof(LastPasswordChangeDate)) == false) return; + nameof(LastPasswordChangeDate)) == false) + return; Properties[Constants.Conventions.Member.LastPasswordChangeDate].SetValue(value); } @@ -353,8 +389,10 @@ namespace Umbraco.Core.Models get { var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.LastLockoutDate, nameof(LastLockoutDate), default(DateTime)); - if (a.Success == false) return a.Result; - if (Properties[Constants.Conventions.Member.LastLockoutDate].GetValue() == null) return default(DateTime); + if (a.Success == false) + return a.Result; + if (Properties[Constants.Conventions.Member.LastLockoutDate].GetValue() == null) + return default(DateTime); var tryConvert = Properties[Constants.Conventions.Member.LastLockoutDate].GetValue().TryConvertTo(); if (tryConvert.Success) { @@ -367,7 +405,8 @@ namespace Umbraco.Core.Models { if (WarnIfPropertyTypeNotFoundOnSet( Constants.Conventions.Member.LastLockoutDate, - nameof(LastLockoutDate)) == false) return; + nameof(LastLockoutDate)) == false) + return; Properties[Constants.Conventions.Member.LastLockoutDate].SetValue(value); } @@ -387,8 +426,10 @@ namespace Umbraco.Core.Models get { var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.FailedPasswordAttempts, nameof(FailedPasswordAttempts), 0); - if (a.Success == false) return a.Result; - if (Properties[Constants.Conventions.Member.FailedPasswordAttempts].GetValue() == null) return default(int); + if (a.Success == false) + return a.Result; + if (Properties[Constants.Conventions.Member.FailedPasswordAttempts].GetValue() == null) + return default(int); var tryConvert = Properties[Constants.Conventions.Member.FailedPasswordAttempts].GetValue().TryConvertTo(); if (tryConvert.Success) { @@ -401,7 +442,8 @@ namespace Umbraco.Core.Models { if (WarnIfPropertyTypeNotFoundOnSet( Constants.Conventions.Member.FailedPasswordAttempts, - nameof(FailedPasswordAttempts)) == false) return; + nameof(FailedPasswordAttempts)) == false) + return; Properties[Constants.Conventions.Member.FailedPasswordAttempts].SetValue(value); } @@ -413,6 +455,17 @@ namespace Umbraco.Core.Models [DataMember] public virtual string ContentTypeAlias => ContentType.Alias; + /// + /// The security stamp used by ASP.Net identity + /// + [IgnoreDataMember] + public string SecurityStamp + { + get => _securityStamp; + set => SetPropertyValueAndDetectChanges(value, ref _securityStamp, nameof(SecurityStamp)); + } + + /// /// Internal/Experimental - only used for mapping queries. /// diff --git a/src/Umbraco.Core/Models/Membership/IMembershipUser.cs b/src/Umbraco.Core/Models/Membership/IMembershipUser.cs index 9b1c8a0c07..c8ecc4b3c6 100644 --- a/src/Umbraco.Core/Models/Membership/IMembershipUser.cs +++ b/src/Umbraco.Core/Models/Membership/IMembershipUser.cs @@ -1,4 +1,4 @@ -using System; +using System; using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models.Membership @@ -10,6 +10,7 @@ namespace Umbraco.Core.Models.Membership { string Username { get; set; } string Email { get; set; } + DateTime? EmailConfirmedDate { get; set; } /// /// Gets or sets the raw password value @@ -38,6 +39,11 @@ namespace Umbraco.Core.Models.Membership /// int FailedPasswordAttempts { get; set; } + /// + /// Gets or sets the security stamp used by ASP.NET Identity + /// + string SecurityStamp { get; set; } + //object ProfileId { get; set; } //IEnumerable Groups { get; set; } } diff --git a/src/Umbraco.Core/Models/Membership/IUser.cs b/src/Umbraco.Core/Models/Membership/IUser.cs index 3a3a18b5ab..1554c3cef5 100644 --- a/src/Umbraco.Core/Models/Membership/IUser.cs +++ b/src/Umbraco.Core/Models/Membership/IUser.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.ComponentModel; using Umbraco.Core.Models.Entities; @@ -20,7 +20,6 @@ namespace Umbraco.Core.Models.Membership int[] StartMediaIds { get; set; } string Language { get; set; } - DateTime? EmailConfirmedDate { get; set; } DateTime? InvitedDate { get; set; } /// @@ -39,11 +38,6 @@ namespace Umbraco.Core.Models.Membership /// IProfile ProfileData { get; } - /// - /// The security stamp used by ASP.Net identity - /// - string SecurityStamp { get; set; } - /// /// Will hold the media file system relative path of the users custom avatar if they uploaded one /// diff --git a/src/Umbraco.Infrastructure/Security/IdentityMapDefinition.cs b/src/Umbraco.Infrastructure/Security/IdentityMapDefinition.cs index e79d346c8a..6477c184c6 100644 --- a/src/Umbraco.Infrastructure/Security/IdentityMapDefinition.cs +++ b/src/Umbraco.Infrastructure/Security/IdentityMapDefinition.cs @@ -96,13 +96,13 @@ namespace Umbraco.Infrastructure.Security target.UserName = source.Username; target.LastPasswordChangeDateUtc = source.LastPasswordChangeDate.ToUniversalTime(); target.LastLoginDateUtc = source.LastLoginDate.ToUniversalTime(); - //target.EmailConfirmed = source.EmailConfirmedDate.HasValue; + target.EmailConfirmed = source.EmailConfirmedDate.HasValue; target.Name = source.Name; target.AccessFailedCount = source.FailedPasswordAttempts; target.PasswordHash = GetPasswordHash(source.RawPasswordValue); target.PasswordConfig = source.PasswordConfiguration; target.IsApproved = source.IsApproved; - //target.SecurityStamp = source.SecurityStamp; + target.SecurityStamp = source.SecurityStamp; target.LockoutEnd = source.IsLockedOut ? DateTime.MaxValue.ToUniversalTime() : (DateTime?)null; // NB: same comments re AutoMapper as per BackOfficeUser diff --git a/src/Umbraco.Infrastructure/Security/MembersUserStore.cs b/src/Umbraco.Infrastructure/Security/MembersUserStore.cs index 744919cfa1..7690eed210 100644 --- a/src/Umbraco.Infrastructure/Security/MembersUserStore.cs +++ b/src/Umbraco.Infrastructure/Security/MembersUserStore.cs @@ -554,13 +554,13 @@ namespace Umbraco.Infrastructure.Security member.LastPasswordChangeDate = identityUserMember.LastPasswordChangeDateUtc.Value.ToLocalTime(); } - //if (identityUser.IsPropertyDirty(nameof(MembersIdentityUser.EmailConfirmed)) - // || (user.EmailConfirmedDate.HasValue && user.EmailConfirmedDate.Value != default && identityUser.EmailConfirmed == false) - // || ((user.EmailConfirmedDate.HasValue == false || user.EmailConfirmedDate.Value == default) && identityUser.EmailConfirmed)) - //{ - // anythingChanged = true; - // user.EmailConfirmedDate = identityUser.EmailConfirmed ? (DateTime?)DateTime.Now : null; - //} + if (identityUserMember.IsPropertyDirty(nameof(MembersIdentityUser.EmailConfirmed)) + || (member.EmailConfirmedDate.HasValue && member.EmailConfirmedDate.Value != default && identityUserMember.EmailConfirmed == false) + || ((member.EmailConfirmedDate.HasValue == false || member.EmailConfirmedDate.Value == default) && identityUserMember.EmailConfirmed)) + { + anythingChanged = true; + member.EmailConfirmedDate = identityUserMember.EmailConfirmed ? (DateTime?)DateTime.Now : null; + } if (identityUserMember.IsPropertyDirty(nameof(MembersIdentityUser.Name)) && member.Name != identityUserMember.Name && identityUserMember.Name.IsNullOrWhiteSpace() == false) @@ -610,11 +610,11 @@ namespace Umbraco.Infrastructure.Security member.PasswordConfiguration = identityUserMember.PasswordConfig; } - //if (user.SecurityStamp != identityUser.SecurityStamp) - //{ - // anythingChanged = true; - // user.SecurityStamp = identityUser.SecurityStamp; - //} + if (member.SecurityStamp != identityUserMember.SecurityStamp) + { + anythingChanged = true; + member.SecurityStamp = identityUserMember.SecurityStamp; + } // TODO: Fix this for Groups too (as per backoffice comment) if (identityUserMember.IsPropertyDirty(nameof(MembersIdentityUser.Roles)) || identityUserMember.IsPropertyDirty(nameof(MembersIdentityUser.Groups)))