V10: Migrate member properties to columns on the member table (#12205)
* Add new columns to the member table * Add missing IsApproved column * Add joins with nested query * Add query for selecting new column values from existing members * Update the member data from the same query * Make escapes work for sqlite * Use GetFieldNameForUpdate instead of GetFieldName * Left join on memberDto * Remove the now unused property types and data * Don't create member columns as properties anymore * Store old properties in fields on member Also switch the dates to nullable since they can be null * Map columns when mapping from DTO to Member object * Display columns in the member content app * Fix null exception when creating new user * Hide value if user doesn't have access to sensitive data * Remove hardcoded member properties * Obsolete old member alias constants * Map and persist member properties * Correctly update LastLogin when member logs in * Map IsApproved and IsLockedOut when saving member in backoffice * Update the query mappers for members * Fix member service tracks dirty changes test * Remove no longer existing property types from member type builder * Fix null error in UpdateMemberProperties * Fix builder tests * Fix SetupMemberTestData in MemberControllerUnitTests * Let LastLoginDate be null and handle check in controller There's no reason to default a perfectly nullable property to default(DateTime) * Add translation key for is approved and use that instead of constant * Obsolete old label constants * Fix whitespace post merge * Fix up test comments * Apply suggestions from code review Co-authored-by: Elitsa Marinovska <21998037+elit0451@users.noreply.github.com> * Fix member properties always being sensitive Co-authored-by: Elitsa Marinovska <elm@umbraco.dk> Co-authored-by: Elitsa Marinovska <21998037+elit0451@users.noreply.github.com>
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Cms.Core
|
||||
{
|
||||
public static partial class Constants
|
||||
@@ -187,43 +189,49 @@ namespace Umbraco.Cms.Core
|
||||
/// <summary>
|
||||
/// Property alias for the Approved boolean of a Member
|
||||
/// </summary>
|
||||
[Obsolete("IsApproved is no longer property data, access the property directly on IMember instead, scheduled for removal in V11")]
|
||||
public const string IsApproved = "umbracoMemberApproved";
|
||||
|
||||
[Obsolete("Use the stateApproved translation in the user area instead, scheduled for removal in V11")]
|
||||
public const string IsApprovedLabel = "Is Approved";
|
||||
|
||||
/// <summary>
|
||||
/// Property alias for the Locked out boolean of a Member
|
||||
/// </summary>
|
||||
[Obsolete("IsLockedOut is no longer property data, access the property directly on IMember instead, scheduled for removal in V11")]
|
||||
public const string IsLockedOut = "umbracoMemberLockedOut";
|
||||
|
||||
[Obsolete("Use the stateLockedOut translation in the user area instead, scheduled for removal in V11")]
|
||||
public const string IsLockedOutLabel = "Is Locked Out";
|
||||
|
||||
/// <summary>
|
||||
/// Property alias for the last date the Member logged in
|
||||
/// </summary>
|
||||
[Obsolete("LastLoginDate is no longer property data, access the property directly on IMember instead, scheduled for removal in V11")]
|
||||
public const string LastLoginDate = "umbracoMemberLastLogin";
|
||||
|
||||
[Obsolete("Use the lastLogin translation in the user area instead, scheduled for removal in V11")]
|
||||
public const string LastLoginDateLabel = "Last Login Date";
|
||||
|
||||
/// <summary>
|
||||
/// Property alias for the last date a Member changed its password
|
||||
/// </summary>
|
||||
[Obsolete("LastPasswordChangeDate is no longer property data, access the property directly on IMember instead, scheduled for removal in V11")]
|
||||
public const string LastPasswordChangeDate = "umbracoMemberLastPasswordChangeDate";
|
||||
|
||||
[Obsolete("Use the lastPasswordChangeDate translation in the user area instead, scheduled for removal in V11")]
|
||||
public const string LastPasswordChangeDateLabel = "Last Password Change Date";
|
||||
|
||||
/// <summary>
|
||||
/// Property alias for the last date a Member was locked out
|
||||
/// </summary>
|
||||
[Obsolete("LastLockoutDate is no longer property data, access the property directly on IMember instead, scheduled for removal in V11")]
|
||||
public const string LastLockoutDate = "umbracoMemberLastLockoutDate";
|
||||
|
||||
[Obsolete("Use the lastLockoutDate translation in the user area instead, scheduled for removal in V11")]
|
||||
public const string LastLockoutDateLabel = "Last Lockout Date";
|
||||
|
||||
/// <summary>
|
||||
/// Property alias for the number of failed login attempts
|
||||
/// </summary>
|
||||
[Obsolete("FailedPasswordAttempts is no longer property data, access the property directly on IMember instead, scheduled for removal in V11")]
|
||||
public const string FailedPasswordAttempts = "umbracoMemberFailedPasswordAttempts";
|
||||
|
||||
[Obsolete("Use the failedPasswordAttempts translation in the user area instead, scheduled for removal in V11")]
|
||||
public const string FailedPasswordAttemptsLabel = "Failed Password Attempts";
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -11,64 +11,16 @@ namespace Umbraco.Cms.Core
|
||||
{
|
||||
{
|
||||
Constants.Conventions.Member.Comments,
|
||||
new PropertyType(shortStringHelper, Constants.PropertyEditors.Aliases.TextArea, ValueStorageType.Ntext, true,
|
||||
new PropertyType(
|
||||
shortStringHelper,
|
||||
Constants.PropertyEditors.Aliases.TextArea,
|
||||
ValueStorageType.Ntext,
|
||||
true,
|
||||
Constants.Conventions.Member.Comments)
|
||||
{
|
||||
Name = Constants.Conventions.Member.CommentsLabel
|
||||
Name = Constants.Conventions.Member.CommentsLabel,
|
||||
}
|
||||
},
|
||||
{
|
||||
Constants.Conventions.Member.FailedPasswordAttempts,
|
||||
new PropertyType(shortStringHelper, Constants.PropertyEditors.Aliases.Label, ValueStorageType.Integer, true,
|
||||
Constants.Conventions.Member.FailedPasswordAttempts)
|
||||
{
|
||||
Name = Constants.Conventions.Member.FailedPasswordAttemptsLabel,
|
||||
DataTypeId = Constants.DataTypes.LabelInt
|
||||
}
|
||||
},
|
||||
{
|
||||
Constants.Conventions.Member.IsApproved,
|
||||
new PropertyType(shortStringHelper, Constants.PropertyEditors.Aliases.Boolean, ValueStorageType.Integer, true,
|
||||
Constants.Conventions.Member.IsApproved)
|
||||
{
|
||||
Name = Constants.Conventions.Member.IsApprovedLabel
|
||||
}
|
||||
},
|
||||
{
|
||||
Constants.Conventions.Member.IsLockedOut,
|
||||
new PropertyType(shortStringHelper, Constants.PropertyEditors.Aliases.Boolean, ValueStorageType.Integer, true,
|
||||
Constants.Conventions.Member.IsLockedOut)
|
||||
{
|
||||
Name = Constants.Conventions.Member.IsLockedOutLabel
|
||||
}
|
||||
},
|
||||
{
|
||||
Constants.Conventions.Member.LastLockoutDate,
|
||||
new PropertyType(shortStringHelper, Constants.PropertyEditors.Aliases.Label, ValueStorageType.Date, true,
|
||||
Constants.Conventions.Member.LastLockoutDate)
|
||||
{
|
||||
Name = Constants.Conventions.Member.LastLockoutDateLabel,
|
||||
DataTypeId = Constants.DataTypes.LabelDateTime
|
||||
}
|
||||
},
|
||||
{
|
||||
Constants.Conventions.Member.LastLoginDate,
|
||||
new PropertyType(shortStringHelper, Constants.PropertyEditors.Aliases.Label, ValueStorageType.Date, true,
|
||||
Constants.Conventions.Member.LastLoginDate)
|
||||
{
|
||||
Name = Constants.Conventions.Member.LastLoginDateLabel,
|
||||
DataTypeId = Constants.DataTypes.LabelDateTime
|
||||
}
|
||||
},
|
||||
{
|
||||
Constants.Conventions.Member.LastPasswordChangeDate,
|
||||
new PropertyType(shortStringHelper, Constants.PropertyEditors.Aliases.Label, ValueStorageType.Date, true,
|
||||
Constants.Conventions.Member.LastPasswordChangeDate)
|
||||
{
|
||||
Name = Constants.Conventions.Member.LastPasswordChangeDateLabel,
|
||||
DataTypeId = Constants.DataTypes.LabelDateTime
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,12 @@ namespace Umbraco.Cms.Core.Models.ContentEditing
|
||||
[DataMember(Name = "email")]
|
||||
public string Email { get; set; }
|
||||
|
||||
[DataMember(Name = "isLockedOut")]
|
||||
public bool IsLockedOut { get; set; }
|
||||
|
||||
[DataMember(Name = "isApproved")]
|
||||
public bool IsApproved { get; set; }
|
||||
|
||||
//[DataMember(Name = "membershipScenario")]
|
||||
//public MembershipScenario MembershipScenario { get; set; }
|
||||
|
||||
|
||||
@@ -31,15 +31,11 @@ namespace Umbraco.Cms.Core.Models.ContentEditing
|
||||
/// </summary>
|
||||
public string Comments => GetPropertyValue<string>(Constants.Conventions.Member.Comments);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the value from the IsLockedOut property
|
||||
/// </summary>
|
||||
public bool IsLockedOut => GetPropertyValue<bool>(Constants.Conventions.Member.IsLockedOut);
|
||||
[DataMember(Name = "isLockedOut")]
|
||||
public bool IsLockedOut { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the value from the IsApproved property
|
||||
/// </summary>
|
||||
public bool IsApproved => GetPropertyValue<bool>(Constants.Conventions.Member.IsApproved);
|
||||
[DataMember(Name = "isApproved")]
|
||||
public bool IsApproved { get; set; }
|
||||
|
||||
private T GetPropertyValue<T>(string alias)
|
||||
{
|
||||
|
||||
@@ -64,11 +64,11 @@ namespace Umbraco.Cms.Core.Models.ContentEditing
|
||||
|
||||
[DataMember(Name = "lastLockoutDate")]
|
||||
[ReadOnly(true)]
|
||||
public DateTime LastLockoutDate { get; set; }
|
||||
public DateTime? LastLockoutDate { get; set; }
|
||||
|
||||
[DataMember(Name = "lastPasswordChangeDate")]
|
||||
[ReadOnly(true)]
|
||||
public DateTime LastPasswordChangeDate { get; set; }
|
||||
public DateTime? LastPasswordChangeDate { get; set; }
|
||||
|
||||
[DataMember(Name = "createDate")]
|
||||
[ReadOnly(true)]
|
||||
|
||||
@@ -65,13 +65,6 @@ namespace Umbraco.Cms.Core.Models.Mapping
|
||||
|
||||
var resolved = base.Map(source, context);
|
||||
|
||||
// IMember.IsLockedOut can't be set to true, so make it readonly when that's the case (you can only unlock)
|
||||
var isLockedOutProperty = resolved.SelectMany(x => x.Properties).FirstOrDefault(x => x.Alias == Constants.Conventions.Member.IsLockedOut);
|
||||
if (isLockedOutProperty?.Value != null && isLockedOutProperty.Value.ToString() != "1")
|
||||
{
|
||||
isLockedOutProperty.Readonly = true;
|
||||
}
|
||||
|
||||
return resolved;
|
||||
}
|
||||
|
||||
@@ -189,7 +182,7 @@ namespace Umbraco.Cms.Core.Models.Mapping
|
||||
var properties = new List<ContentPropertyDisplay>
|
||||
{
|
||||
GetLoginProperty(member, _localizedTextService),
|
||||
new ContentPropertyDisplay
|
||||
new()
|
||||
{
|
||||
Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}email",
|
||||
Label = _localizedTextService.Localize("general","email"),
|
||||
@@ -197,7 +190,7 @@ namespace Umbraco.Cms.Core.Models.Mapping
|
||||
View = "email",
|
||||
Validation = { Mandatory = true }
|
||||
},
|
||||
new ContentPropertyDisplay
|
||||
new()
|
||||
{
|
||||
Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}password",
|
||||
Label = _localizedTextService.Localize(null,"password"),
|
||||
@@ -209,7 +202,7 @@ namespace Umbraco.Cms.Core.Models.Mapping
|
||||
View = "changepassword",
|
||||
Config = GetPasswordConfig(member) // Initialize the dictionary with the configuration from the default membership provider
|
||||
},
|
||||
new ContentPropertyDisplay
|
||||
new()
|
||||
{
|
||||
Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}membergroup",
|
||||
Label = _localizedTextService.Localize("content","membergroup"),
|
||||
@@ -218,9 +211,80 @@ namespace Umbraco.Cms.Core.Models.Mapping
|
||||
Config = new Dictionary<string, object>
|
||||
{
|
||||
{ "IsRequired", true }
|
||||
},
|
||||
},
|
||||
|
||||
// These properties used to live on the member as property data, defaulting to sensitive, so we set them to sensitive here too
|
||||
new()
|
||||
{
|
||||
Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}failedPasswordAttempts",
|
||||
Label = _localizedTextService.Localize("user", "failedPasswordAttempts"),
|
||||
Value = member.FailedPasswordAttempts,
|
||||
View = "readonlyvalue",
|
||||
IsSensitive = true,
|
||||
},
|
||||
|
||||
new()
|
||||
{
|
||||
Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}approved",
|
||||
Label = _localizedTextService.Localize("user", "stateApproved"),
|
||||
Value = member.IsApproved,
|
||||
View = "boolean",
|
||||
IsSensitive = true,
|
||||
Readonly = false,
|
||||
},
|
||||
|
||||
new()
|
||||
{
|
||||
Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}lockedOut",
|
||||
Label = _localizedTextService.Localize("user", "stateLockedOut"),
|
||||
Value = member.IsLockedOut,
|
||||
View = "boolean",
|
||||
IsSensitive = true,
|
||||
Readonly = !member.IsLockedOut, // IMember.IsLockedOut can't be set to true, so make it readonly when that's the case (you can only unlock)
|
||||
},
|
||||
|
||||
new()
|
||||
{
|
||||
Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}lastLockoutDate",
|
||||
Label = _localizedTextService.Localize("user", "lastLockoutDate"),
|
||||
Value = member.LastLockoutDate?.ToString(),
|
||||
View = "readonlyvalue",
|
||||
IsSensitive = true,
|
||||
},
|
||||
|
||||
new()
|
||||
{
|
||||
Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}lastLoginDate",
|
||||
Label = _localizedTextService.Localize("user", "lastLogin"),
|
||||
Value = member.LastLoginDate?.ToString(),
|
||||
View = "readonlyvalue",
|
||||
IsSensitive = true,
|
||||
},
|
||||
|
||||
new()
|
||||
{
|
||||
Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}lastPasswordChangeDate",
|
||||
Label = _localizedTextService.Localize("user", "lastPasswordChangeDate"),
|
||||
Value = member.LastPasswordChangeDate?.ToString(),
|
||||
View = "readonlyvalue",
|
||||
IsSensitive = true,
|
||||
},
|
||||
};
|
||||
|
||||
if (_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.HasAccessToSensitiveData() is false)
|
||||
{
|
||||
// Current user doesn't have access to sensitive data so explicitly set the views and remove the value from sensitive data
|
||||
foreach (var property in properties)
|
||||
{
|
||||
if (property.IsSensitive)
|
||||
{
|
||||
property.Value = null;
|
||||
property.View = "sensitivevalue";
|
||||
property.Readonly = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
@@ -294,7 +294,7 @@ namespace Umbraco.Cms.Core.Models.Mapping
|
||||
target.Id = source.Id;
|
||||
target.Key = source.Key;
|
||||
target.LastLockoutDate = source.LastLockoutDate;
|
||||
target.LastLoginDate = source.LastLoginDate == default ? null : (DateTime?)source.LastLoginDate;
|
||||
target.LastLoginDate = source.LastLoginDate == default(DateTime) ? null : (DateTime?)source.LastLoginDate;
|
||||
target.LastPasswordChangeDate = source.LastPasswordChangeDate;
|
||||
target.Name = source.Name;
|
||||
target.Navigation = CreateUserEditorNavigation();
|
||||
|
||||
@@ -21,6 +21,12 @@ namespace Umbraco.Cms.Core.Models
|
||||
private string _passwordConfig;
|
||||
private DateTime? _emailConfirmedDate;
|
||||
private string _securityStamp;
|
||||
private int _failedPasswordAttempts;
|
||||
private bool _isApproved;
|
||||
private bool _isLockedOut;
|
||||
private DateTime? _lastLockoutDate;
|
||||
private DateTime? _lastLoginDate;
|
||||
private DateTime? _lastPasswordChangeDate;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Member"/> class.
|
||||
@@ -281,41 +287,13 @@ namespace Umbraco.Cms.Core.Models
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a boolean indicating whether the Member is approved
|
||||
/// Gets or sets a value indicating whether the Member is approved
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Alias: umbracoMemberApproved
|
||||
/// Part of the standard properties collection.
|
||||
/// </remarks>
|
||||
[DataMember]
|
||||
public bool IsApproved
|
||||
{
|
||||
get
|
||||
{
|
||||
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;
|
||||
var tryConvert = Properties[Constants.Conventions.Member.IsApproved].GetValue().TryConvertTo<bool>();
|
||||
if (tryConvert.Success)
|
||||
{
|
||||
return tryConvert.Result;
|
||||
}
|
||||
//if the property exists but it cannot be converted, we will assume true
|
||||
return true;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (WarnIfPropertyTypeNotFoundOnSet(
|
||||
Constants.Conventions.Member.IsApproved,
|
||||
nameof(IsApproved)) == false)
|
||||
return;
|
||||
|
||||
Properties[Constants.Conventions.Member.IsApproved].SetValue(value);
|
||||
}
|
||||
get => _isApproved;
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _isApproved, nameof(IsApproved));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -328,30 +306,8 @@ namespace Umbraco.Cms.Core.Models
|
||||
[DataMember]
|
||||
public bool IsLockedOut
|
||||
{
|
||||
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;
|
||||
var tryConvert = Properties[Constants.Conventions.Member.IsLockedOut].GetValue().TryConvertTo<bool>();
|
||||
if (tryConvert.Success)
|
||||
{
|
||||
return tryConvert.Result;
|
||||
}
|
||||
return false;
|
||||
// TODO: Use TryConvertTo<T> instead
|
||||
}
|
||||
set
|
||||
{
|
||||
if (WarnIfPropertyTypeNotFoundOnSet(
|
||||
Constants.Conventions.Member.IsLockedOut,
|
||||
nameof(IsLockedOut)) == false)
|
||||
return;
|
||||
|
||||
Properties[Constants.Conventions.Member.IsLockedOut].SetValue(value);
|
||||
}
|
||||
get => _isLockedOut;
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _isLockedOut, nameof(IsLockedOut));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -362,32 +318,10 @@ namespace Umbraco.Cms.Core.Models
|
||||
/// Part of the standard properties collection.
|
||||
/// </remarks>
|
||||
[DataMember]
|
||||
public DateTime LastLoginDate
|
||||
public DateTime? LastLoginDate
|
||||
{
|
||||
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);
|
||||
var tryConvert = Properties[Constants.Conventions.Member.LastLoginDate].GetValue().TryConvertTo<DateTime>();
|
||||
if (tryConvert.Success)
|
||||
{
|
||||
return tryConvert.Result;
|
||||
}
|
||||
return default(DateTime);
|
||||
// TODO: Use TryConvertTo<T> instead
|
||||
}
|
||||
set
|
||||
{
|
||||
if (WarnIfPropertyTypeNotFoundOnSet(
|
||||
Constants.Conventions.Member.LastLoginDate,
|
||||
nameof(LastLoginDate)) == false)
|
||||
return;
|
||||
|
||||
Properties[Constants.Conventions.Member.LastLoginDate].SetValue(value);
|
||||
}
|
||||
get => _lastLoginDate;
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _lastLoginDate, nameof(LastLoginDate));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -398,32 +332,10 @@ namespace Umbraco.Cms.Core.Models
|
||||
/// Part of the standard properties collection.
|
||||
/// </remarks>
|
||||
[DataMember]
|
||||
public DateTime LastPasswordChangeDate
|
||||
public DateTime? LastPasswordChangeDate
|
||||
{
|
||||
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);
|
||||
var tryConvert = Properties[Constants.Conventions.Member.LastPasswordChangeDate].GetValue().TryConvertTo<DateTime>();
|
||||
if (tryConvert.Success)
|
||||
{
|
||||
return tryConvert.Result;
|
||||
}
|
||||
return default(DateTime);
|
||||
// TODO: Use TryConvertTo<T> instead
|
||||
}
|
||||
set
|
||||
{
|
||||
if (WarnIfPropertyTypeNotFoundOnSet(
|
||||
Constants.Conventions.Member.LastPasswordChangeDate,
|
||||
nameof(LastPasswordChangeDate)) == false)
|
||||
return;
|
||||
|
||||
Properties[Constants.Conventions.Member.LastPasswordChangeDate].SetValue(value);
|
||||
}
|
||||
get => _lastPasswordChangeDate;
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _lastPasswordChangeDate, nameof(LastPasswordChangeDate));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -434,32 +346,10 @@ namespace Umbraco.Cms.Core.Models
|
||||
/// Part of the standard properties collection.
|
||||
/// </remarks>
|
||||
[DataMember]
|
||||
public DateTime LastLockoutDate
|
||||
public DateTime? LastLockoutDate
|
||||
{
|
||||
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);
|
||||
var tryConvert = Properties[Constants.Conventions.Member.LastLockoutDate].GetValue().TryConvertTo<DateTime>();
|
||||
if (tryConvert.Success)
|
||||
{
|
||||
return tryConvert.Result;
|
||||
}
|
||||
return default(DateTime);
|
||||
// TODO: Use TryConvertTo<T> instead
|
||||
}
|
||||
set
|
||||
{
|
||||
if (WarnIfPropertyTypeNotFoundOnSet(
|
||||
Constants.Conventions.Member.LastLockoutDate,
|
||||
nameof(LastLockoutDate)) == false)
|
||||
return;
|
||||
|
||||
Properties[Constants.Conventions.Member.LastLockoutDate].SetValue(value);
|
||||
}
|
||||
get => _lastLockoutDate;
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _lastLockoutDate, nameof(LastLockoutDate));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -473,30 +363,8 @@ namespace Umbraco.Cms.Core.Models
|
||||
[DataMember]
|
||||
public int FailedPasswordAttempts
|
||||
{
|
||||
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);
|
||||
var tryConvert = Properties[Constants.Conventions.Member.FailedPasswordAttempts].GetValue().TryConvertTo<int>();
|
||||
if (tryConvert.Success)
|
||||
{
|
||||
return tryConvert.Result;
|
||||
}
|
||||
return default(int);
|
||||
// TODO: Use TryConvertTo<T> instead
|
||||
}
|
||||
set
|
||||
{
|
||||
if (WarnIfPropertyTypeNotFoundOnSet(
|
||||
Constants.Conventions.Member.FailedPasswordAttempts,
|
||||
nameof(FailedPasswordAttempts)) == false)
|
||||
return;
|
||||
|
||||
Properties[Constants.Conventions.Member.FailedPasswordAttempts].SetValue(value);
|
||||
}
|
||||
get => _failedPasswordAttempts;
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _failedPasswordAttempts, nameof(FailedPasswordAttempts));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -25,9 +25,9 @@ namespace Umbraco.Cms.Core.Models.Membership
|
||||
string Comments { get; set; }
|
||||
bool IsApproved { get; set; }
|
||||
bool IsLockedOut { get; set; }
|
||||
DateTime LastLoginDate { get; set; }
|
||||
DateTime LastPasswordChangeDate { get; set; }
|
||||
DateTime LastLockoutDate { get; set; }
|
||||
DateTime? LastLoginDate { get; set; }
|
||||
DateTime? LastPasswordChangeDate { get; set; }
|
||||
DateTime? LastLockoutDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the number of failed password attempts.
|
||||
|
||||
@@ -116,9 +116,9 @@ namespace Umbraco.Cms.Core.Models.Membership
|
||||
private bool _isApproved;
|
||||
private bool _isLockedOut;
|
||||
private string _language;
|
||||
private DateTime _lastPasswordChangedDate;
|
||||
private DateTime _lastLoginDate;
|
||||
private DateTime _lastLockoutDate;
|
||||
private DateTime? _lastPasswordChangedDate;
|
||||
private DateTime? _lastLoginDate;
|
||||
private DateTime? _lastLockoutDate;
|
||||
|
||||
//Custom comparer for enumerable
|
||||
private static readonly DelegateEqualityComparer<IEnumerable<int>> IntegerEnumerableComparer =
|
||||
@@ -184,21 +184,21 @@ namespace Umbraco.Cms.Core.Models.Membership
|
||||
}
|
||||
|
||||
[IgnoreDataMember]
|
||||
public DateTime LastLoginDate
|
||||
public DateTime? LastLoginDate
|
||||
{
|
||||
get => _lastLoginDate;
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _lastLoginDate, nameof(LastLoginDate));
|
||||
}
|
||||
|
||||
[IgnoreDataMember]
|
||||
public DateTime LastPasswordChangeDate
|
||||
public DateTime? LastPasswordChangeDate
|
||||
{
|
||||
get => _lastPasswordChangedDate;
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _lastPasswordChangedDate, nameof(LastPasswordChangeDate));
|
||||
}
|
||||
|
||||
[IgnoreDataMember]
|
||||
public DateTime LastLockoutDate
|
||||
public DateTime? LastLockoutDate
|
||||
{
|
||||
get => _lastLockoutDate;
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _lastLockoutDate, nameof(LastLockoutDate));
|
||||
|
||||
@@ -51,6 +51,7 @@ namespace Umbraco.Cms.Core.Persistence.Repositories
|
||||
/// updating their login date. This operation must be fast and cannot use database locks which is fine if we are only executing a single query
|
||||
/// for this data since there won't be any other data contention issues.
|
||||
/// </remarks>
|
||||
[Obsolete("This is now a NoOp since last login date is no longer an umbraco property, set the date on the IMember directly and Save it instead, scheduled for removal in V11.")]
|
||||
void SetLastLogin(string username, DateTime date);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,10 +67,10 @@ namespace Umbraco.Cms.Core.Services
|
||||
query = Query<IMember>();
|
||||
break;
|
||||
case MemberCountType.LockedOut:
|
||||
query = Query<IMember>().Where(x => x.PropertyTypeAlias == Constants.Conventions.Member.IsLockedOut && ((Member) x).BoolPropertyValue);
|
||||
query = Query<IMember>().Where(x => x.IsLockedOut == true);
|
||||
break;
|
||||
case MemberCountType.Approved:
|
||||
query = Query<IMember>().Where(x => x.PropertyTypeAlias == Constants.Conventions.Member.IsApproved && ((Member) x).BoolPropertyValue);
|
||||
query = Query<IMember>().Where(x => x.IsApproved == true);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(countType));
|
||||
@@ -748,13 +748,9 @@ namespace Umbraco.Cms.Core.Services
|
||||
#region Save
|
||||
|
||||
/// <inheritdoc />
|
||||
[Obsolete("This is now a NoOp since last login date is no longer an umbraco property, set the date on the IMember directly and Save it instead, scheduled for removal in V11.")]
|
||||
public void SetLastLogin(string username, DateTime date)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
_memberRepository.SetLastLogin(username, date);
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -638,13 +638,15 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install
|
||||
// Membership property types.
|
||||
if (_database.Exists<PropertyTypeGroupDto>(11))
|
||||
{
|
||||
_database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 28, UniqueId = 28.ToGuid(), DataTypeId = Cms.Core.Constants.DataTypes.Textarea, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Cms.Core.Constants.Conventions.Member.Comments, Name = Cms.Core.Constants.Conventions.Member.CommentsLabel, SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte)ContentVariation.Nothing });
|
||||
_database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 29, UniqueId = 29.ToGuid(), DataTypeId = Cms.Core.Constants.DataTypes.LabelInt, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Cms.Core.Constants.Conventions.Member.FailedPasswordAttempts, Name = Cms.Core.Constants.Conventions.Member.FailedPasswordAttemptsLabel, SortOrder = 1, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte)ContentVariation.Nothing });
|
||||
_database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 30, UniqueId = 30.ToGuid(), DataTypeId = Cms.Core.Constants.DataTypes.Boolean, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Cms.Core.Constants.Conventions.Member.IsApproved, Name = Cms.Core.Constants.Conventions.Member.IsApprovedLabel, SortOrder = 2, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte)ContentVariation.Nothing });
|
||||
_database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 31, UniqueId = 31.ToGuid(), DataTypeId = Cms.Core.Constants.DataTypes.Boolean, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Cms.Core.Constants.Conventions.Member.IsLockedOut, Name = Cms.Core.Constants.Conventions.Member.IsLockedOutLabel, SortOrder = 3, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte)ContentVariation.Nothing });
|
||||
_database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 32, UniqueId = 32.ToGuid(), DataTypeId = Cms.Core.Constants.DataTypes.LabelDateTime, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Cms.Core.Constants.Conventions.Member.LastLockoutDate, Name = Cms.Core.Constants.Conventions.Member.LastLockoutDateLabel, SortOrder = 4, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte)ContentVariation.Nothing });
|
||||
_database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 33, UniqueId = 33.ToGuid(), DataTypeId = Cms.Core.Constants.DataTypes.LabelDateTime, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Cms.Core.Constants.Conventions.Member.LastLoginDate, Name = Cms.Core.Constants.Conventions.Member.LastLoginDateLabel, SortOrder = 5, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte)ContentVariation.Nothing });
|
||||
_database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 34, UniqueId = 34.ToGuid(), DataTypeId = Cms.Core.Constants.DataTypes.LabelDateTime, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Cms.Core.Constants.Conventions.Member.LastPasswordChangeDate, Name = Cms.Core.Constants.Conventions.Member.LastPasswordChangeDateLabel, SortOrder = 6, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte)ContentVariation.Nothing });
|
||||
_database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyType, "id", false,
|
||||
new PropertyTypeDto
|
||||
{
|
||||
Id = 28, UniqueId = 28.ToGuid(), DataTypeId = Cms.Core.Constants.DataTypes.Textarea,
|
||||
ContentTypeId = 1044, PropertyTypeGroupId = 11,
|
||||
Alias = Cms.Core.Constants.Conventions.Member.Comments,
|
||||
Name = Cms.Core.Constants.Conventions.Member.CommentsLabel, SortOrder = 0, Mandatory = false,
|
||||
ValidationRegExp = null, Description = null, Variations = (byte)ContentVariation.Nothing
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Configuration;
|
||||
using Umbraco.Cms.Core.Semver;
|
||||
using Umbraco.Cms.Infrastructure.Migrations.Upgrade.Common;
|
||||
using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_10_0_0;
|
||||
using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_8_0_0;
|
||||
using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_8_0_1;
|
||||
using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_8_1_0;
|
||||
@@ -284,6 +285,9 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade
|
||||
// TO 9.4.0
|
||||
To<AddScheduledPublishingLock>("{DBBA1EA0-25A1-4863-90FB-5D306FB6F1E1}");
|
||||
To<UpdateRelationTypesToHandleDependencies>("{DED98755-4059-41BB-ADBD-3FEAB12D1D7B}");
|
||||
|
||||
// TO 10.0.0
|
||||
To<AddMemberPropertiesAsColumns>("{B7E0D53C-2B0E-418B-AB07-2DDE486E225F}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,242 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using NPoco;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Infrastructure.Persistence;
|
||||
using Umbraco.Cms.Infrastructure.Persistence.Dtos;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_10_0_0;
|
||||
|
||||
public class AddMemberPropertiesAsColumns : MigrationBase
|
||||
{
|
||||
public AddMemberPropertiesAsColumns(IMigrationContext context)
|
||||
: base(context)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Migrate()
|
||||
{
|
||||
var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToList();
|
||||
|
||||
AddColumnIfNotExists<MemberDto>(columns, "failedPasswordAttempts");
|
||||
AddColumnIfNotExists<MemberDto>(columns, "isLockedOut");
|
||||
AddColumnIfNotExists<MemberDto>(columns, "isApproved");
|
||||
AddColumnIfNotExists<MemberDto>(columns, "lastLoginDate");
|
||||
AddColumnIfNotExists<MemberDto>(columns, "lastLockoutDate");
|
||||
AddColumnIfNotExists<MemberDto>(columns, "lastPasswordChangeDate");
|
||||
|
||||
Sql<ISqlContext> newestContentVersionQuery = Database.SqlContext.Sql()
|
||||
.Select($"MAX({GetQuotedSelector("cv", "id")}) as {SqlSyntax.GetQuotedColumnName("id")}", GetQuotedSelector("cv", "nodeId"))
|
||||
.From<ContentVersionDto>("cv")
|
||||
.GroupBy(GetQuotedSelector("cv", "nodeId"));
|
||||
|
||||
Sql<ISqlContext> passwordAttemptsQuery = Database.SqlContext.Sql()
|
||||
.Select(GetSubQueryColumns())
|
||||
.From<PropertyTypeDto>("pt")
|
||||
.Where($"{GetQuotedSelector("pt", "Alias")} = 'umbracoMemberFailedPasswordAttempts'");
|
||||
|
||||
Sql<ISqlContext> memberApprovedQuery = Database.SqlContext.Sql()
|
||||
.Select(GetSubQueryColumns())
|
||||
.From<PropertyTypeDto>("pt")
|
||||
.Where($"{GetQuotedSelector("pt", "Alias")} = 'umbracoMemberApproved'");
|
||||
|
||||
Sql<ISqlContext> memberLockedOutQuery = Database.SqlContext.Sql()
|
||||
.Select(GetSubQueryColumns())
|
||||
.From<PropertyTypeDto>("pt")
|
||||
.Where($"{GetQuotedSelector("pt", "Alias")} = 'umbracoMemberLockedOut'");
|
||||
|
||||
Sql<ISqlContext> memberLastLockoutDateQuery = Database.SqlContext.Sql()
|
||||
.Select(GetSubQueryColumns())
|
||||
.From<PropertyTypeDto>("pt")
|
||||
.Where($"{GetQuotedSelector("pt", "Alias")} = 'umbracoMemberLastLockoutDate'");
|
||||
|
||||
Sql<ISqlContext> memberLastLoginDateQuery = Database.SqlContext.Sql()
|
||||
.Select(GetSubQueryColumns())
|
||||
.From<PropertyTypeDto>("pt")
|
||||
.Where($"{GetQuotedSelector("pt", "Alias")} = 'umbracoMemberLastLogin'");
|
||||
|
||||
Sql<ISqlContext> memberLastPasswordChangeDateQuery = Database.SqlContext.Sql()
|
||||
.Select(GetSubQueryColumns())
|
||||
.From<PropertyTypeDto>("pt")
|
||||
.Where($"{GetQuotedSelector("pt", "Alias")} = 'umbracoMemberLastPasswordChangeDate'");
|
||||
|
||||
StringBuilder queryBuilder = new StringBuilder();
|
||||
queryBuilder.AppendLine($"UPDATE {Constants.DatabaseSchema.Tables.Member}");
|
||||
queryBuilder.AppendLine("SET");
|
||||
queryBuilder.AppendLine($"\t{Database.SqlContext.SqlSyntax.GetFieldNameForUpdate<MemberDto>(x => x.FailedPasswordAttempts)} = {GetQuotedSelector("umbracoPropertyData", "intValue")},");
|
||||
queryBuilder.AppendLine($"\t{Database.SqlContext.SqlSyntax.GetFieldNameForUpdate<MemberDto>(x => x.IsApproved)} = {GetQuotedSelector("pdmp", "intValue")},");
|
||||
queryBuilder.AppendLine($"\t{Database.SqlContext.SqlSyntax.GetFieldNameForUpdate<MemberDto>(x => x.IsLockedOut)} = {GetQuotedSelector("pdlo", "intValue")},");
|
||||
queryBuilder.AppendLine($"\t{Database.SqlContext.SqlSyntax.GetFieldNameForUpdate<MemberDto>(x => x.LastLockoutDate)} = {GetQuotedSelector("pdlout", "dateValue")},");
|
||||
queryBuilder.AppendLine($"\t{Database.SqlContext.SqlSyntax.GetFieldNameForUpdate<MemberDto>(x => x.LastLoginDate)} = {GetQuotedSelector("pdlin", "dateValue")},");
|
||||
queryBuilder.Append($"\t{Database.SqlContext.SqlSyntax.GetFieldNameForUpdate<MemberDto>(x => x.LastPasswordChangeDate)} = {GetQuotedSelector("pdlpc", "dateValue")}");
|
||||
|
||||
Sql<ISqlContext> updateMemberColumnsQuery = Database.SqlContext.Sql(queryBuilder.ToString())
|
||||
.From<NodeDto>()
|
||||
.InnerJoin<ContentDto>()
|
||||
.On<NodeDto, ContentDto>((left, right) => left.NodeId == right.NodeId)
|
||||
.InnerJoin<ContentTypeDto>()
|
||||
.On<ContentDto, ContentTypeDto>((left, right) => left.ContentTypeId == right.NodeId)
|
||||
.InnerJoin(newestContentVersionQuery, "umbracoContentVersion")
|
||||
.On<NodeDto, ContentVersionDto>((left, right) => left.NodeId == right.NodeId)
|
||||
.InnerJoin<MemberDto>("m")
|
||||
.On<ContentDto, MemberDto>((left, right) => left.NodeId == right.NodeId, null, "m")
|
||||
.LeftJoin(passwordAttemptsQuery, "failedAttemptsType")
|
||||
.On<ContentDto, FailedAttempts>((left, right) => left.ContentTypeId == right.ContentTypeId)
|
||||
.LeftJoin<DataTypeDto>()
|
||||
.On<FailedAttempts, DataTypeDto>((left, right) => left.DataTypeId == right.NodeId)
|
||||
.LeftJoin<PropertyDataDto>()
|
||||
.On<PropertyDataDto, FailedAttempts, ContentVersionDto>((left, middle, right) => left.PropertyTypeId == middle.Id && left.VersionId == right.Id)
|
||||
.LeftJoin(memberApprovedQuery, "memberApprovedType")
|
||||
.On<ContentDto, MemberApproved>((left, right) => left.ContentTypeId == right.ContentTypeId)
|
||||
.LeftJoin<DataTypeDto>("dtmp")
|
||||
.On<MemberApproved, DataTypeDto>((left, right) => left.DataTypeId == right.NodeId, null, "dtmp")
|
||||
.LeftJoin<PropertyDataDto>("pdmp")
|
||||
.On<PropertyDataDto, MemberApproved, ContentVersionDto>((left, middle, right) => left.PropertyTypeId == middle.Id && left.VersionId == right.Id, "pdmp")
|
||||
.LeftJoin(memberLockedOutQuery, "memberLockedOutType")
|
||||
.On<ContentDto, MemberLockedOut>((left, right) => left.ContentTypeId == right.ContentTypeId)
|
||||
.LeftJoin<DataTypeDto>("dtlo")
|
||||
.On<MemberLockedOut, DataTypeDto>((left, right) => left.DataTypeId == right.NodeId, null, "dtlo")
|
||||
.LeftJoin<PropertyDataDto>("pdlo")
|
||||
.On<PropertyDataDto, MemberLockedOut, ContentVersionDto>((left, middle, right) => left.PropertyTypeId == middle.Id && left.VersionId == right.Id, "pdlo")
|
||||
.LeftJoin(memberLastLockoutDateQuery, "lastLockOutDateType")
|
||||
.On<ContentDto, LastLockoutDate>((left, right) => left.ContentTypeId == right.ContentTypeId)
|
||||
.LeftJoin<DataTypeDto>("dtlout")
|
||||
.On<LastLockoutDate, DataTypeDto>((left, right) => left.DataTypeId == right.NodeId, null, "dtlout")
|
||||
.LeftJoin<PropertyDataDto>("pdlout")
|
||||
.On<PropertyDataDto, LastLockoutDate, ContentVersionDto>((left, middle, right) => left.PropertyTypeId == middle.Id && left.VersionId == right.Id, "pdlout")
|
||||
.LeftJoin(memberLastLoginDateQuery, "lastLoginDateType")
|
||||
.On<ContentDto, LastLoginDate>((left, right) => left.ContentTypeId == right.ContentTypeId)
|
||||
.LeftJoin<DataTypeDto>("dtlin")
|
||||
.On<LastLoginDate, DataTypeDto>((left, right) => left.DataTypeId == right.NodeId, null, "dtlin")
|
||||
.LeftJoin<PropertyDataDto>("pdlin")
|
||||
.On<PropertyDataDto, LastLoginDate, ContentVersionDto>((left, middle, right) => left.PropertyTypeId == middle.Id && left.VersionId == right.Id, "pdlin")
|
||||
.LeftJoin(memberLastPasswordChangeDateQuery, "lastPasswordChangeType")
|
||||
.On<ContentDto, LastPasswordChange>((left, right) => left.ContentTypeId == right.ContentTypeId)
|
||||
.LeftJoin<DataTypeDto>("dtlpc")
|
||||
.On<LastPasswordChange, DataTypeDto>((left, right) => left.DataTypeId == right.NodeId, null, "dtlpc")
|
||||
.LeftJoin<PropertyDataDto>("pdlpc")
|
||||
.On<PropertyDataDto, LastPasswordChange, ContentVersionDto>((left, middle, right) => left.PropertyTypeId == middle.Id && left.VersionId == right.Id, "pdlpc")
|
||||
.Where<NodeDto>(x => x.NodeObjectType == Constants.ObjectTypes.Member);
|
||||
|
||||
Database.Execute(updateMemberColumnsQuery);
|
||||
|
||||
// Removing old property types and values, since these are no longer needed
|
||||
// Hard coding the aliases, since we want to be able to delete the constants...
|
||||
string[] propertyTypesToDelete =
|
||||
{
|
||||
"umbracoMemberFailedPasswordAttempts",
|
||||
"umbracoMemberApproved",
|
||||
"umbracoMemberLockedOut",
|
||||
"umbracoMemberLastLockoutDate",
|
||||
"umbracoMemberLastLogin",
|
||||
"umbracoMemberLastPasswordChangeDate"
|
||||
};
|
||||
|
||||
Sql<ISqlContext> idQuery = Database.SqlContext.Sql().Select<PropertyTypeDto>(x => x.Id)
|
||||
.From<PropertyTypeDto>()
|
||||
.WhereIn<PropertyTypeDto>(x => x.Alias, propertyTypesToDelete);
|
||||
List<int> idsToDelete = Database.Fetch<int>(idQuery);
|
||||
|
||||
// Firstly deleting the property data due to FK constraints
|
||||
Sql<ISqlContext> propertyDataDelete = Database.SqlContext.Sql()
|
||||
.Delete<PropertyDataDto>()
|
||||
.WhereIn<PropertyDataDto>(x => x.PropertyTypeId, idsToDelete);
|
||||
Database.Execute(propertyDataDelete);
|
||||
|
||||
// Then we can remove the property
|
||||
Sql<ISqlContext> propertyTypeDelete = Database.SqlContext.Sql()
|
||||
.Delete<PropertyTypeDto>()
|
||||
.WhereIn<PropertyTypeDto>(x => x.Id, idsToDelete);
|
||||
Database.Execute(propertyTypeDelete);
|
||||
}
|
||||
|
||||
private string GetQuotedSelector(string tableName, string columnName)
|
||||
=> $"{SqlSyntax.GetQuotedTableName(tableName)}.{SqlSyntax.GetQuotedColumnName(columnName)}";
|
||||
|
||||
private object[] GetSubQueryColumns() => new object[]
|
||||
{
|
||||
SqlSyntax.GetQuotedColumnName("contentTypeId"),
|
||||
SqlSyntax.GetQuotedColumnName("dataTypeId"),
|
||||
SqlSyntax.GetQuotedColumnName("id"),
|
||||
};
|
||||
|
||||
[TableName("failedAttemptsType")]
|
||||
private class FailedAttempts
|
||||
{
|
||||
[Column("contentTypeId")]
|
||||
public int ContentTypeId { get; set; }
|
||||
|
||||
[Column("dataTypeId")]
|
||||
public int DataTypeId { get; set; }
|
||||
|
||||
[Column("id")]
|
||||
public int Id { get; set; }
|
||||
}
|
||||
|
||||
[TableName("memberApprovedType")]
|
||||
private class MemberApproved
|
||||
{
|
||||
[Column("contentTypeId")]
|
||||
public int ContentTypeId { get; set; }
|
||||
|
||||
[Column("dataTypeId")]
|
||||
public int DataTypeId { get; set; }
|
||||
|
||||
[Column("id")]
|
||||
public int Id { get; set; }
|
||||
}
|
||||
|
||||
[TableName("memberLockedOutType")]
|
||||
private class MemberLockedOut
|
||||
{
|
||||
[Column("contentTypeId")]
|
||||
public int ContentTypeId { get; set; }
|
||||
|
||||
[Column("dataTypeId")]
|
||||
public int DataTypeId { get; set; }
|
||||
|
||||
[Column("id")]
|
||||
public int Id { get; set; }
|
||||
}
|
||||
|
||||
[TableName("lastLockOutDateType")]
|
||||
private class LastLockoutDate
|
||||
{
|
||||
[Column("contentTypeId")]
|
||||
public int ContentTypeId { get; set; }
|
||||
|
||||
[Column("dataTypeId")]
|
||||
public int DataTypeId { get; set; }
|
||||
|
||||
[Column("id")]
|
||||
public int Id { get; set; }
|
||||
}
|
||||
|
||||
[TableName("lastLoginDateType")]
|
||||
private class LastLoginDate
|
||||
{
|
||||
[Column("contentTypeId")]
|
||||
public int ContentTypeId { get; set; }
|
||||
|
||||
[Column("dataTypeId")]
|
||||
public int DataTypeId { get; set; }
|
||||
|
||||
[Column("id")]
|
||||
public int Id { get; set; }
|
||||
}
|
||||
|
||||
[TableName("lastPasswordChangeType")]
|
||||
private class LastPasswordChange
|
||||
{
|
||||
[Column("contentTypeId")]
|
||||
public int ContentTypeId { get; set; }
|
||||
|
||||
[Column("dataTypeId")]
|
||||
public int DataTypeId { get; set; }
|
||||
|
||||
[Column("id")]
|
||||
public int Id { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,30 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Dtos
|
||||
[NullSetting(NullSetting = NullSettings.Null)]
|
||||
public DateTime? EmailConfirmedDate { get; set; }
|
||||
|
||||
// TODO: It would be SOOOOO much better to store all core member data here instead of hiding it in Umbraco properties
|
||||
[Column("failedPasswordAttempts")]
|
||||
[NullSetting(NullSetting = NullSettings.Null)]
|
||||
public int? FailedPasswordAttempts { get; set; }
|
||||
|
||||
[Column("isLockedOut")]
|
||||
[Constraint(Default = 0)]
|
||||
[NullSetting(NullSetting = NullSettings.Null)]
|
||||
public bool IsLockedOut { get; set; }
|
||||
|
||||
[Column("isApproved")]
|
||||
[Constraint(Default = 1)]
|
||||
public bool IsApproved { get; set; }
|
||||
|
||||
[Column("lastLoginDate")]
|
||||
[NullSetting(NullSetting = NullSettings.Null)]
|
||||
public DateTime? LastLoginDate { get; set; }
|
||||
|
||||
[Column("lastLockoutDate")]
|
||||
[NullSetting(NullSetting = NullSettings.Null)]
|
||||
public DateTime? LastLockoutDate { get; set; }
|
||||
|
||||
[Column("lastPasswordChangeDate")]
|
||||
[NullSetting(NullSetting = NullSettings.Null)]
|
||||
public DateTime? LastPasswordChangeDate { get; set; }
|
||||
|
||||
[ResultColumn]
|
||||
[Reference(ReferenceType.OneToOne, ReferenceMemberName = "NodeId")]
|
||||
|
||||
@@ -130,7 +130,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Factories
|
||||
content.PasswordConfiguration = dto.PasswordConfig;
|
||||
content.Key = nodeDto.UniqueId;
|
||||
content.VersionId = contentVersionDto.Id;
|
||||
|
||||
|
||||
// TODO: missing names?
|
||||
|
||||
content.Path = nodeDto.Path;
|
||||
@@ -143,6 +143,12 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Factories
|
||||
content.WriterId = contentVersionDto.UserId ?? Cms.Core.Constants.Security.UnknownUserId;
|
||||
content.CreateDate = nodeDto.CreateDate;
|
||||
content.UpdateDate = contentVersionDto.VersionDate;
|
||||
content.FailedPasswordAttempts = dto.FailedPasswordAttempts ?? default;
|
||||
content.IsLockedOut = dto.IsLockedOut;
|
||||
content.IsApproved = dto.IsApproved;
|
||||
content.LastLoginDate = dto.LastLoginDate;
|
||||
content.LastLockoutDate = dto.LastLockoutDate;
|
||||
content.LastPasswordChangeDate = dto.LastPasswordChangeDate;
|
||||
|
||||
// reset dirty initial properties (U4-1946)
|
||||
content.ResetDirtyProperties(false);
|
||||
@@ -219,7 +225,13 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Factories
|
||||
EmailConfirmedDate = entity.EmailConfirmedDate,
|
||||
ContentDto = contentDto,
|
||||
ContentVersionDto = BuildContentVersionDto(entity, contentDto),
|
||||
PasswordConfig = entity.PasswordConfiguration
|
||||
PasswordConfig = entity.PasswordConfiguration,
|
||||
FailedPasswordAttempts = entity.FailedPasswordAttempts,
|
||||
IsApproved = entity.IsApproved,
|
||||
IsLockedOut = entity.IsLockedOut,
|
||||
LastLockoutDate = entity.LastLockoutDate,
|
||||
LastLoginDate = entity.LastLoginDate,
|
||||
LastPasswordChangeDate = entity.LastPasswordChangeDate,
|
||||
};
|
||||
return dto;
|
||||
}
|
||||
|
||||
@@ -28,9 +28,9 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Factories
|
||||
user.Language = dto.UserLanguage;
|
||||
user.SecurityStamp = dto.SecurityStampToken;
|
||||
user.FailedPasswordAttempts = dto.FailedLoginAttempts ?? 0;
|
||||
user.LastLockoutDate = dto.LastLockoutDate ?? DateTime.MinValue;
|
||||
user.LastLoginDate = dto.LastLoginDate ?? DateTime.MinValue;
|
||||
user.LastPasswordChangeDate = dto.LastPasswordChangeDate ?? DateTime.MinValue;
|
||||
user.LastLockoutDate = dto.LastLockoutDate;
|
||||
user.LastLoginDate = dto.LastLoginDate;
|
||||
user.LastPasswordChangeDate = dto.LastPasswordChangeDate;
|
||||
user.CreateDate = dto.CreateDate;
|
||||
user.UpdateDate = dto.UpdateDate;
|
||||
user.Avatar = dto.Avatar;
|
||||
|
||||
@@ -35,14 +35,14 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Mappers
|
||||
DefineMap<Member, MemberDto>(nameof(Member.Email), nameof(MemberDto.Email));
|
||||
DefineMap<Member, MemberDto>(nameof(Member.Username), nameof(MemberDto.LoginName));
|
||||
DefineMap<Member, MemberDto>(nameof(Member.RawPasswordValue), nameof(MemberDto.Password));
|
||||
DefineMap<Member, MemberDto>(nameof(Member.IsApproved), nameof(MemberDto.IsApproved));
|
||||
DefineMap<Member, MemberDto>(nameof(Member.IsLockedOut), nameof(MemberDto.IsLockedOut));
|
||||
DefineMap<Member, MemberDto>(nameof(Member.FailedPasswordAttempts), nameof(MemberDto.FailedPasswordAttempts));
|
||||
DefineMap<Member, MemberDto>(nameof(Member.LastLockoutDate), nameof(MemberDto.LastLockoutDate));
|
||||
DefineMap<Member, MemberDto>(nameof(Member.LastLoginDate), nameof(MemberDto.LastLoginDate));
|
||||
DefineMap<Member, MemberDto>(nameof(Member.LastPasswordChangeDate), nameof(MemberDto.LastPasswordChangeDate));
|
||||
|
||||
DefineMap<Member, PropertyDataDto>(nameof(Member.IsApproved), nameof(PropertyDataDto.IntegerValue));
|
||||
DefineMap<Member, PropertyDataDto>(nameof(Member.IsLockedOut), nameof(PropertyDataDto.IntegerValue));
|
||||
DefineMap<Member, PropertyDataDto>(nameof(Member.Comments), nameof(PropertyDataDto.TextValue));
|
||||
DefineMap<Member, PropertyDataDto>(nameof(Member.FailedPasswordAttempts), nameof(PropertyDataDto.IntegerValue));
|
||||
DefineMap<Member, PropertyDataDto>(nameof(Member.LastLockoutDate), nameof(PropertyDataDto.DateValue));
|
||||
DefineMap<Member, PropertyDataDto>(nameof(Member.LastLoginDate), nameof(PropertyDataDto.DateValue));
|
||||
DefineMap<Member, PropertyDataDto>(nameof(Member.LastPasswordChangeDate), nameof(PropertyDataDto.DateValue));
|
||||
|
||||
/* Internal experiment */
|
||||
DefineMap<Member, PropertyDataDto>(nameof(Member.DateTimePropertyValue), nameof(PropertyDataDto.DateValue));
|
||||
|
||||
@@ -405,6 +405,24 @@ namespace Umbraco.Extensions
|
||||
return sql.InnerJoin(join);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Appends an INNER JOIN clause using a nested query.
|
||||
/// </summary>
|
||||
/// <param name="sql">The SQL statement.</param>
|
||||
/// <param name="nestedSelect">The nested sql query.</param>
|
||||
/// <param name="alias">An optional alias for the joined table.</param>
|
||||
/// <returns>A SqlJoin statement.</returns>
|
||||
public static Sql<ISqlContext>.SqlJoinClause<ISqlContext> InnerJoin(this Sql<ISqlContext> sql, Sql<ISqlContext> nestedSelect, string alias = null)
|
||||
{
|
||||
var join = $"({nestedSelect.SQL})";
|
||||
if (alias is not null)
|
||||
{
|
||||
join += " " + sql.SqlContext.SqlSyntax.GetQuotedTableName(alias);
|
||||
}
|
||||
|
||||
return sql.InnerJoin(join);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Appends a LEFT JOIN clause to the Sql statement.
|
||||
/// </summary>
|
||||
@@ -437,6 +455,24 @@ namespace Umbraco.Extensions
|
||||
string alias = null) =>
|
||||
sql.SqlContext.SqlSyntax.LeftJoinWithNestedJoin<TDto>(sql, nestedJoin, alias);
|
||||
|
||||
/// <summary>
|
||||
/// Appends an LEFT JOIN clause using a nested query.
|
||||
/// </summary>
|
||||
/// <param name="sql">The SQL statement.</param>
|
||||
/// <param name="nestedSelect">The nested sql query.</param>
|
||||
/// <param name="alias">An optional alias for the joined table.</param>
|
||||
/// <returns>A SqlJoin statement.</returns>
|
||||
public static Sql<ISqlContext>.SqlJoinClause<ISqlContext> LeftJoin(this Sql<ISqlContext> sql, Sql<ISqlContext> nestedSelect, string alias = null)
|
||||
{
|
||||
var join = $"({nestedSelect.SQL})";
|
||||
if (alias is not null)
|
||||
{
|
||||
join += " " + sql.SqlContext.SqlSyntax.GetQuotedTableName(alias);
|
||||
}
|
||||
|
||||
return sql.LeftJoin(join);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Appends a RIGHT JOIN clause to the Sql statement.
|
||||
/// </summary>
|
||||
|
||||
@@ -207,58 +207,11 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[Obsolete(
|
||||
"This is now a NoOp since last login date is no longer an umbraco property, set the date on the IMember directly and Save it instead, scheduled for removal in V11.")]
|
||||
public void SetLastLogin(string username, DateTime date)
|
||||
{
|
||||
// Important - these queries are designed to execute without an exclusive WriteLock taken in our distributed lock
|
||||
// table. However due to the data that we are updating which relies on version data we cannot update this data
|
||||
// without taking some locks, otherwise we'll end up with strange situations because when a member is updated, that operation
|
||||
// deletes and re-inserts all property data. So if there are concurrent transactions, one deleting and re-inserting and another trying
|
||||
// to update there can be problems. This is only an issue for cmsPropertyData, not umbracoContentVersion because that table just
|
||||
// maintains a single row and it isn't deleted/re-inserted.
|
||||
// So the important part here is the ForUpdate() call on the select to fetch the property data to update.
|
||||
|
||||
// Update the cms property value for the member
|
||||
|
||||
SqlTemplate sqlSelectTemplateProperty = SqlContext.Templates.Get(
|
||||
"Umbraco.Core.MemberRepository.SetLastLogin1", s => s
|
||||
.Select<PropertyDataDto>(x => x.Id)
|
||||
.From<PropertyDataDto>()
|
||||
.InnerJoin<PropertyTypeDto>()
|
||||
.On<PropertyTypeDto, PropertyDataDto>((l, r) => l.Id == r.PropertyTypeId)
|
||||
.InnerJoin<ContentVersionDto>()
|
||||
.On<ContentVersionDto, PropertyDataDto>((l, r) => l.Id == r.VersionId)
|
||||
.InnerJoin<NodeDto>().On<NodeDto, ContentVersionDto>((l, r) => l.NodeId == r.NodeId)
|
||||
.InnerJoin<MemberDto>().On<MemberDto, NodeDto>((l, r) => l.NodeId == r.NodeId)
|
||||
.Where<NodeDto>(x => x.NodeObjectType == SqlTemplate.Arg<Guid>("nodeObjectType"))
|
||||
.Where<PropertyTypeDto>(x => x.Alias == SqlTemplate.Arg<string>("propertyTypeAlias"))
|
||||
.Where<MemberDto>(x => x.LoginName == SqlTemplate.Arg<string>("username"))
|
||||
.ForUpdate());
|
||||
Sql<ISqlContext> sqlSelectProperty = sqlSelectTemplateProperty.Sql(Constants.ObjectTypes.Member,
|
||||
Constants.Conventions.Member.LastLoginDate, username);
|
||||
|
||||
Sql<ISqlContext> update = Sql()
|
||||
.Update<PropertyDataDto>(u => u
|
||||
.Set(x => x.DateValue, date))
|
||||
.WhereIn<PropertyDataDto>(x => x.Id, sqlSelectProperty);
|
||||
|
||||
Database.Execute(update);
|
||||
|
||||
// Update the umbracoContentVersion value for the member
|
||||
|
||||
SqlTemplate sqlSelectTemplateVersion = SqlContext.Templates.Get(
|
||||
"Umbraco.Core.MemberRepository.SetLastLogin2", s => s
|
||||
.Select<ContentVersionDto>(x => x.Id)
|
||||
.From<ContentVersionDto>()
|
||||
.InnerJoin<NodeDto>().On<NodeDto, ContentVersionDto>((l, r) => l.NodeId == r.NodeId)
|
||||
.InnerJoin<MemberDto>().On<MemberDto, NodeDto>((l, r) => l.NodeId == r.NodeId)
|
||||
.Where<NodeDto>(x => x.NodeObjectType == SqlTemplate.Arg<Guid>("nodeObjectType"))
|
||||
.Where<MemberDto>(x => x.LoginName == SqlTemplate.Arg<string>("username")));
|
||||
Sql<ISqlContext> sqlSelectVersion = sqlSelectTemplateVersion.Sql(Constants.ObjectTypes.Member, username);
|
||||
|
||||
Database.Execute(Sql()
|
||||
.Update<ContentVersionDto>(u => u
|
||||
.Set(x => x.VersionDate, date))
|
||||
.WhereIn<ContentVersionDto>(x => x.Id, sqlSelectVersion));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -772,12 +725,43 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
|
||||
changedCols.Add("LoginName");
|
||||
}
|
||||
|
||||
if (entity.IsPropertyDirty(nameof(entity.FailedPasswordAttempts)))
|
||||
{
|
||||
changedCols.Add(nameof(entity.FailedPasswordAttempts));
|
||||
}
|
||||
|
||||
if (entity.IsPropertyDirty(nameof(entity.IsApproved)))
|
||||
{
|
||||
changedCols.Add(nameof(entity.IsApproved));
|
||||
}
|
||||
|
||||
if (entity.IsPropertyDirty(nameof(entity.IsLockedOut)))
|
||||
{
|
||||
changedCols.Add(nameof(entity.IsLockedOut));
|
||||
}
|
||||
|
||||
if (entity.IsPropertyDirty(nameof(entity.LastLockoutDate)))
|
||||
{
|
||||
changedCols.Add(nameof(entity.LastLockoutDate));
|
||||
}
|
||||
|
||||
if (entity.IsPropertyDirty(nameof(entity.LastLoginDate)))
|
||||
{
|
||||
changedCols.Add(nameof(entity.LastLoginDate));
|
||||
}
|
||||
|
||||
if (entity.IsPropertyDirty(nameof(entity.LastPasswordChangeDate)))
|
||||
{
|
||||
changedCols.Add(nameof(entity.LastPasswordChangeDate));
|
||||
}
|
||||
|
||||
// this can occur from an upgrade
|
||||
if (memberDto.PasswordConfig.IsNullOrWhiteSpace())
|
||||
{
|
||||
memberDto.PasswordConfig = DefaultPasswordConfigJson;
|
||||
changedCols.Add("passwordConfig");
|
||||
}else if (memberDto.PasswordConfig == Constants.Security.UnknownPasswordConfigJson)
|
||||
}
|
||||
else if (memberDto.PasswordConfig == Constants.Security.UnknownPasswordConfigJson)
|
||||
{
|
||||
changedCols.Add("passwordConfig");
|
||||
}
|
||||
|
||||
@@ -496,12 +496,12 @@ namespace Umbraco.Cms.Core.Security
|
||||
// don't assign anything if nothing has changed as this will trigger the track changes of the model
|
||||
if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.LastLoginDateUtc))
|
||||
|| (user.LastLoginDate != default && identityUser.LastLoginDateUtc.HasValue == false)
|
||||
|| (identityUser.LastLoginDateUtc.HasValue && user.LastLoginDate.ToUniversalTime() != identityUser.LastLoginDateUtc.Value))
|
||||
|| (identityUser.LastLoginDateUtc.HasValue && user.LastLoginDate?.ToUniversalTime() != identityUser.LastLoginDateUtc.Value))
|
||||
{
|
||||
anythingChanged = true;
|
||||
|
||||
// if the LastLoginDate is being set to MinValue, don't convert it ToLocalTime
|
||||
DateTime dt = identityUser.LastLoginDateUtc == DateTime.MinValue ? DateTime.MinValue : identityUser.LastLoginDateUtc.Value.ToLocalTime();
|
||||
DateTime? dt = identityUser.LastLoginDateUtc?.ToLocalTime();
|
||||
user.LastLoginDate = dt;
|
||||
}
|
||||
|
||||
@@ -513,11 +513,11 @@ namespace Umbraco.Cms.Core.Security
|
||||
}
|
||||
|
||||
if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.LastPasswordChangeDateUtc))
|
||||
|| (user.LastPasswordChangeDate != default && identityUser.LastPasswordChangeDateUtc.HasValue == false)
|
||||
|| (identityUser.LastPasswordChangeDateUtc.HasValue && user.LastPasswordChangeDate.ToUniversalTime() != identityUser.LastPasswordChangeDateUtc.Value))
|
||||
|| (user.LastPasswordChangeDate.HasValue && user.LastPasswordChangeDate.Value != default && identityUser.LastPasswordChangeDateUtc.HasValue == false)
|
||||
|| (identityUser.LastPasswordChangeDateUtc.HasValue && user.LastPasswordChangeDate?.ToUniversalTime() != identityUser.LastPasswordChangeDateUtc.Value))
|
||||
{
|
||||
anythingChanged = true;
|
||||
user.LastPasswordChangeDate = identityUser.LastPasswordChangeDateUtc.Value.ToLocalTime();
|
||||
user.LastPasswordChangeDate = identityUser.LastPasswordChangeDateUtc?.ToLocalTime();
|
||||
}
|
||||
|
||||
if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.EmailConfirmed))
|
||||
|
||||
@@ -72,8 +72,8 @@ namespace Umbraco.Cms.Core.Security
|
||||
target.CalculatedContentStartNodeIds = source.CalculateContentStartNodeIds(_entityService, _appCaches);
|
||||
target.Email = source.Email;
|
||||
target.UserName = source.Username;
|
||||
target.LastPasswordChangeDateUtc = source.LastPasswordChangeDate.ToUniversalTime();
|
||||
target.LastLoginDateUtc = source.LastLoginDate.ToUniversalTime();
|
||||
target.LastPasswordChangeDateUtc = source.LastPasswordChangeDate?.ToUniversalTime();
|
||||
target.LastLoginDateUtc = source.LastLoginDate?.ToUniversalTime();
|
||||
target.InviteDateUtc = source.InvitedDate?.ToUniversalTime();
|
||||
target.EmailConfirmed = source.EmailConfirmedDate.HasValue;
|
||||
target.Name = source.Name;
|
||||
@@ -93,8 +93,8 @@ namespace Umbraco.Cms.Core.Security
|
||||
{
|
||||
target.Email = source.Email;
|
||||
target.UserName = source.Username;
|
||||
target.LastPasswordChangeDateUtc = source.LastPasswordChangeDate.ToUniversalTime();
|
||||
target.LastLoginDateUtc = source.LastLoginDate.ToUniversalTime();
|
||||
target.LastPasswordChangeDateUtc = source.LastPasswordChangeDate?.ToUniversalTime();
|
||||
target.LastLoginDateUtc = source.LastLoginDate?.ToUniversalTime();
|
||||
target.EmailConfirmed = source.EmailConfirmedDate.HasValue;
|
||||
target.Name = source.Name;
|
||||
target.AccessFailedCount = source.FailedPasswordAttempts;
|
||||
@@ -104,7 +104,7 @@ namespace Umbraco.Cms.Core.Security
|
||||
target.SecurityStamp = source.SecurityStamp;
|
||||
target.LockoutEnd = source.IsLockedOut ? DateTime.MaxValue.ToUniversalTime() : (DateTime?)null;
|
||||
target.Comments = source.Comments;
|
||||
target.LastLockoutDateUtc = source.LastLockoutDate == DateTime.MinValue ? null : source.LastLockoutDate.ToUniversalTime();
|
||||
target.LastLockoutDateUtc = source.LastLockoutDate == DateTime.MinValue ? null : source.LastLockoutDate?.ToUniversalTime();
|
||||
target.CreatedDateUtc = source.CreateDate.ToUniversalTime();
|
||||
target.Key = source.Key;
|
||||
target.MemberTypeAlias = source.ContentTypeAlias;
|
||||
|
||||
@@ -174,6 +174,7 @@ namespace Umbraco.Cms.Core.Security
|
||||
//TODO: should this be thrown, or an identity result?
|
||||
throw new InvalidOperationException("The user id must be an integer to work with the Umbraco");
|
||||
}
|
||||
|
||||
using IScope scope = _scopeProvider.CreateScope(autoComplete: true);
|
||||
|
||||
IMember found = _memberService.GetById(asInt);
|
||||
@@ -183,17 +184,10 @@ namespace Umbraco.Cms.Core.Security
|
||||
var isLoginsPropertyDirty = user.IsPropertyDirty(nameof(MemberIdentityUser.Logins));
|
||||
var isTokensPropertyDirty = user.IsPropertyDirty(nameof(MemberIdentityUser.LoginTokens));
|
||||
|
||||
MemberDataChangeType memberChangeType = UpdateMemberProperties(found, user);
|
||||
if (memberChangeType == MemberDataChangeType.FullSave)
|
||||
if (UpdateMemberProperties(found, user))
|
||||
{
|
||||
_memberService.Save(found);
|
||||
}
|
||||
else if (memberChangeType == MemberDataChangeType.LoginOnly)
|
||||
{
|
||||
// If the member is only logging in, just issue that command without
|
||||
// any write locks so we are creating a bottleneck.
|
||||
_memberService.SetLastLogin(found.Username, DateTime.Now);
|
||||
}
|
||||
|
||||
if (isLoginsPropertyDirty)
|
||||
{
|
||||
@@ -588,19 +582,16 @@ namespace Umbraco.Cms.Core.Security
|
||||
return user;
|
||||
}
|
||||
|
||||
private MemberDataChangeType UpdateMemberProperties(IMember member, MemberIdentityUser identityUser)
|
||||
private bool UpdateMemberProperties(IMember member, MemberIdentityUser identityUser)
|
||||
{
|
||||
MemberDataChangeType changeType = MemberDataChangeType.None;
|
||||
var anythingChanged = false;
|
||||
|
||||
// don't assign anything if nothing has changed as this will trigger the track changes of the model
|
||||
if (identityUser.IsPropertyDirty(nameof(MemberIdentityUser.LastLoginDateUtc))
|
||||
|| (member.LastLoginDate != default && identityUser.LastLoginDateUtc.HasValue == false)
|
||||
|| (identityUser.LastLoginDateUtc.HasValue && member.LastLoginDate.ToUniversalTime() != identityUser.LastLoginDateUtc.Value))
|
||||
|| (identityUser.LastLoginDateUtc.HasValue && member.LastLoginDate?.ToUniversalTime() != identityUser.LastLoginDateUtc.Value))
|
||||
{
|
||||
// If the LastLoginDate is default on the member we have to do a full save.
|
||||
// This is because the umbraco property data for the member doesn't exist yet in this case
|
||||
// meaning we can't just update that property data, but have to do a full save to create it
|
||||
changeType = member.LastLoginDate == default ? MemberDataChangeType.FullSave : MemberDataChangeType.LoginOnly;
|
||||
anythingChanged = true;
|
||||
|
||||
// if the LastLoginDate is being set to MinValue, don't convert it ToLocalTime
|
||||
DateTime dt = identityUser.LastLoginDateUtc == DateTime.MinValue ? DateTime.MinValue : identityUser.LastLoginDateUtc.Value.ToLocalTime();
|
||||
@@ -609,16 +600,16 @@ namespace Umbraco.Cms.Core.Security
|
||||
|
||||
if (identityUser.IsPropertyDirty(nameof(MemberIdentityUser.LastPasswordChangeDateUtc))
|
||||
|| (member.LastPasswordChangeDate != default && identityUser.LastPasswordChangeDateUtc.HasValue == false)
|
||||
|| (identityUser.LastPasswordChangeDateUtc.HasValue && member.LastPasswordChangeDate.ToUniversalTime() != identityUser.LastPasswordChangeDateUtc.Value))
|
||||
|| (identityUser.LastPasswordChangeDateUtc.HasValue && member.LastPasswordChangeDate?.ToUniversalTime() != identityUser.LastPasswordChangeDateUtc.Value))
|
||||
{
|
||||
changeType = MemberDataChangeType.FullSave;
|
||||
anythingChanged = true;
|
||||
member.LastPasswordChangeDate = identityUser.LastPasswordChangeDateUtc.Value.ToLocalTime();
|
||||
}
|
||||
|
||||
if (identityUser.IsPropertyDirty(nameof(MemberIdentityUser.Comments))
|
||||
&& member.Comments != identityUser.Comments && identityUser.Comments.IsNullOrWhiteSpace() == false)
|
||||
{
|
||||
changeType = MemberDataChangeType.FullSave;
|
||||
anythingChanged = true;
|
||||
member.Comments = identityUser.Comments;
|
||||
}
|
||||
|
||||
@@ -626,34 +617,34 @@ namespace Umbraco.Cms.Core.Security
|
||||
|| (member.EmailConfirmedDate.HasValue && member.EmailConfirmedDate.Value != default && identityUser.EmailConfirmed == false)
|
||||
|| ((member.EmailConfirmedDate.HasValue == false || member.EmailConfirmedDate.Value == default) && identityUser.EmailConfirmed))
|
||||
{
|
||||
changeType = MemberDataChangeType.FullSave;
|
||||
anythingChanged = true;
|
||||
member.EmailConfirmedDate = identityUser.EmailConfirmed ? (DateTime?)DateTime.Now : null;
|
||||
}
|
||||
|
||||
if (identityUser.IsPropertyDirty(nameof(MemberIdentityUser.Name))
|
||||
&& member.Name != identityUser.Name && identityUser.Name.IsNullOrWhiteSpace() == false)
|
||||
{
|
||||
changeType = MemberDataChangeType.FullSave;
|
||||
anythingChanged = true;
|
||||
member.Name = identityUser.Name;
|
||||
}
|
||||
|
||||
if (identityUser.IsPropertyDirty(nameof(MemberIdentityUser.Email))
|
||||
&& member.Email != identityUser.Email && identityUser.Email.IsNullOrWhiteSpace() == false)
|
||||
{
|
||||
changeType = MemberDataChangeType.FullSave;
|
||||
anythingChanged = true;
|
||||
member.Email = identityUser.Email;
|
||||
}
|
||||
|
||||
if (identityUser.IsPropertyDirty(nameof(MemberIdentityUser.AccessFailedCount))
|
||||
&& member.FailedPasswordAttempts != identityUser.AccessFailedCount)
|
||||
{
|
||||
changeType = MemberDataChangeType.FullSave;
|
||||
anythingChanged = true;
|
||||
member.FailedPasswordAttempts = identityUser.AccessFailedCount;
|
||||
}
|
||||
|
||||
if (member.IsLockedOut != identityUser.IsLockedOut)
|
||||
{
|
||||
changeType = MemberDataChangeType.FullSave;
|
||||
anythingChanged = true;
|
||||
member.IsLockedOut = identityUser.IsLockedOut;
|
||||
|
||||
if (member.IsLockedOut)
|
||||
@@ -665,40 +656,40 @@ namespace Umbraco.Cms.Core.Security
|
||||
|
||||
if (member.IsApproved != identityUser.IsApproved)
|
||||
{
|
||||
changeType = MemberDataChangeType.FullSave;
|
||||
anythingChanged = true;
|
||||
member.IsApproved = identityUser.IsApproved;
|
||||
}
|
||||
|
||||
if (identityUser.IsPropertyDirty(nameof(MemberIdentityUser.UserName))
|
||||
&& member.Username != identityUser.UserName && identityUser.UserName.IsNullOrWhiteSpace() == false)
|
||||
{
|
||||
changeType = MemberDataChangeType.FullSave;
|
||||
anythingChanged = true;
|
||||
member.Username = identityUser.UserName;
|
||||
}
|
||||
|
||||
if (identityUser.IsPropertyDirty(nameof(MemberIdentityUser.PasswordHash))
|
||||
&& member.RawPasswordValue != identityUser.PasswordHash && identityUser.PasswordHash.IsNullOrWhiteSpace() == false)
|
||||
{
|
||||
changeType = MemberDataChangeType.FullSave;
|
||||
anythingChanged = true;
|
||||
member.RawPasswordValue = identityUser.PasswordHash;
|
||||
member.PasswordConfiguration = identityUser.PasswordConfig;
|
||||
}
|
||||
|
||||
if (member.PasswordConfiguration != identityUser.PasswordConfig)
|
||||
{
|
||||
changeType = MemberDataChangeType.FullSave;
|
||||
anythingChanged = true;
|
||||
member.PasswordConfiguration = identityUser.PasswordConfig;
|
||||
}
|
||||
|
||||
if (member.SecurityStamp != identityUser.SecurityStamp)
|
||||
{
|
||||
changeType = MemberDataChangeType.FullSave;
|
||||
anythingChanged = true;
|
||||
member.SecurityStamp = identityUser.SecurityStamp;
|
||||
}
|
||||
|
||||
if (identityUser.IsPropertyDirty(nameof(MemberIdentityUser.Roles)))
|
||||
{
|
||||
changeType = MemberDataChangeType.FullSave;
|
||||
anythingChanged = true;
|
||||
|
||||
var identityUserRoles = identityUser.Roles.Select(x => x.RoleId).ToArray();
|
||||
_memberService.ReplaceRoles(new[] { member.Id }, identityUserRoles);
|
||||
@@ -707,7 +698,7 @@ namespace Umbraco.Cms.Core.Security
|
||||
// reset all changes
|
||||
identityUser.ResetDirtyProperties(false);
|
||||
|
||||
return changeType;
|
||||
return anythingChanged;
|
||||
}
|
||||
|
||||
public IPublishedContent GetPublishedMember(MemberIdentityUser user)
|
||||
@@ -725,13 +716,6 @@ namespace Umbraco.Cms.Core.Security
|
||||
return publishedSnapshot.Members.Get(member);
|
||||
}
|
||||
|
||||
private enum MemberDataChangeType
|
||||
{
|
||||
None,
|
||||
LoginOnly,
|
||||
FullSave
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overridden to support Umbraco's own data storage requirements
|
||||
/// </summary>
|
||||
|
||||
@@ -109,13 +109,13 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache
|
||||
|
||||
public bool IsLockedOut => Member.IsLockedOut;
|
||||
|
||||
public DateTime LastLockoutDate => Member.LastLockoutDate;
|
||||
public DateTime? LastLockoutDate => Member.LastLockoutDate;
|
||||
|
||||
public DateTime CreationDate => Member.CreateDate;
|
||||
|
||||
public DateTime LastLoginDate => Member.LastLoginDate;
|
||||
public DateTime? LastLoginDate => Member.LastLoginDate;
|
||||
|
||||
public DateTime LastPasswordChangedDate => Member.LastPasswordChangeDate;
|
||||
public DateTime? LastPasswordChangedDate => Member.LastPasswordChangeDate;
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -847,7 +847,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
|
||||
// Check user hasn't logged in. If they have they may have made content changes which will mean
|
||||
// the Id is associated with audit trails, versions etc. and can't be removed.
|
||||
if (user.LastLoginDate != default(DateTime))
|
||||
if (user.LastLoginDate is not null && user.LastLoginDate != default(DateTime))
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
@@ -64,6 +64,8 @@ namespace Umbraco.Cms.Web.BackOffice.Mapping
|
||||
//Membership
|
||||
target.Username = source.Username;
|
||||
target.Email = source.Email;
|
||||
target.IsLockedOut = source.IsLockedOut;
|
||||
target.IsApproved = source.IsApproved;
|
||||
target.MembershipProperties = _tabsAndPropertiesMapper.MapMembershipProperties(source, context);
|
||||
}
|
||||
|
||||
|
||||
@@ -290,6 +290,12 @@
|
||||
case '_umb_membergroup':
|
||||
saveModel.memberGroups = _.keys(_.pick(prop.value, value => value === true));
|
||||
break;
|
||||
case '_umb_approved':
|
||||
saveModel.isApproved = prop.value;
|
||||
break;
|
||||
case '_umb_lockedOut':
|
||||
saveModel.isLockedOut = prop.value;
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1916,6 +1916,7 @@ Mange hilsner fra Umbraco robotten
|
||||
<key alias="stateActive">Aktiv</key>
|
||||
<key alias="stateDisabled">Deaktiveret</key>
|
||||
<key alias="stateLockedOut">Låst ude</key>
|
||||
<key alias="stateApproved">Godkendt</key>
|
||||
<key alias="stateInvited">Inviteret</key>
|
||||
<key alias="stateInactive">Inaktiv</key>
|
||||
<key alias="sortNameAscending">Navn (A-Å)</key>
|
||||
|
||||
@@ -1520,7 +1520,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
|
||||
<key alias="invalidUserPermissionsText">Insufficient user permissions, could not complete the operation</key>
|
||||
<key alias="operationCancelledHeader">Cancelled</key>
|
||||
<key alias="operationCancelledText">Operation was cancelled by a 3rd party add-in</key>
|
||||
<key alias="folderUploadNotAllowed">This file is being uploaded as part of a folder, but creating a new folder is not allowed here</key>
|
||||
<key alias="folderUploadNotAllowed">This file is being uploaded as part of a folder, but creating a new folder is not allowed here</key>
|
||||
<key alias="folderCreationNotAllowed">Creating a new folder is not allowed here</key>
|
||||
<key alias="contentPublishedFailedByEvent">Publishing was cancelled by a 3rd party add-in</key>
|
||||
<key alias="contentTypeDublicatePropertyType">Property type already exists</key>
|
||||
@@ -2217,6 +2217,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
|
||||
<key alias="stateActive">Active</key>
|
||||
<key alias="stateDisabled">Disabled</key>
|
||||
<key alias="stateLockedOut">Locked out</key>
|
||||
<key alias="stateApproved">Approved</key>
|
||||
<key alias="stateInvited">Invited</key>
|
||||
<key alias="stateInactive">Inactive</key>
|
||||
<key alias="sortNameAscending">Name (A-Z)</key>
|
||||
|
||||
@@ -2293,6 +2293,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
|
||||
<key alias="stateActive">Active</key>
|
||||
<key alias="stateDisabled">Disabled</key>
|
||||
<key alias="stateLockedOut">Locked out</key>
|
||||
<key alias="stateApproved">Approved</key>
|
||||
<key alias="stateInvited">Invited</key>
|
||||
<key alias="stateInactive">Inactive</key>
|
||||
<key alias="sortNameAscending">Name (A-Z)</key>
|
||||
|
||||
@@ -35,47 +35,11 @@ namespace Umbraco.Cms.Tests.Common.Builders
|
||||
.WithId(99)
|
||||
.WithName(Constants.Conventions.Member.StandardPropertiesGroupName)
|
||||
.AddPropertyType()
|
||||
.WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.TextArea)
|
||||
.WithValueStorageType(ValueStorageType.Ntext)
|
||||
.WithAlias(Constants.Conventions.Member.Comments)
|
||||
.WithName(Constants.Conventions.Member.CommentsLabel)
|
||||
.Done()
|
||||
.AddPropertyType()
|
||||
.WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.Boolean)
|
||||
.WithValueStorageType(ValueStorageType.Integer)
|
||||
.WithAlias(Constants.Conventions.Member.IsApproved)
|
||||
.WithName(Constants.Conventions.Member.IsApprovedLabel)
|
||||
.Done()
|
||||
.AddPropertyType()
|
||||
.WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.Boolean)
|
||||
.WithValueStorageType(ValueStorageType.Integer)
|
||||
.WithAlias(Constants.Conventions.Member.IsLockedOut)
|
||||
.WithName(Constants.Conventions.Member.IsLockedOutLabel)
|
||||
.Done()
|
||||
.AddPropertyType()
|
||||
.WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.Label)
|
||||
.WithValueStorageType(ValueStorageType.Date)
|
||||
.WithAlias(Constants.Conventions.Member.LastLoginDate)
|
||||
.WithName(Constants.Conventions.Member.LastLoginDateLabel)
|
||||
.Done()
|
||||
.AddPropertyType()
|
||||
.WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.Label)
|
||||
.WithValueStorageType(ValueStorageType.Date)
|
||||
.WithAlias(Constants.Conventions.Member.LastPasswordChangeDate)
|
||||
.WithName(Constants.Conventions.Member.LastPasswordChangeDateLabel)
|
||||
.Done()
|
||||
.AddPropertyType()
|
||||
.WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.Label)
|
||||
.WithValueStorageType(ValueStorageType.Date)
|
||||
.WithAlias(Constants.Conventions.Member.LastLockoutDate)
|
||||
.WithName(Constants.Conventions.Member.LastLockoutDateLabel)
|
||||
.Done()
|
||||
.AddPropertyType()
|
||||
.WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.Label)
|
||||
.WithValueStorageType(ValueStorageType.Integer)
|
||||
.WithAlias(Constants.Conventions.Member.FailedPasswordAttempts)
|
||||
.WithName(Constants.Conventions.Member.FailedPasswordAttemptsLabel)
|
||||
.Done();
|
||||
.WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.TextArea)
|
||||
.WithValueStorageType(ValueStorageType.Ntext)
|
||||
.WithAlias(Constants.Conventions.Member.Comments)
|
||||
.WithName(Constants.Conventions.Member.CommentsLabel)
|
||||
.Done();
|
||||
_propertyGroupBuilders.Add(builder);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -83,12 +83,14 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services
|
||||
IMember member = new Member("xname", "xemail", "xusername", "xrawpassword", memberType, true)
|
||||
{
|
||||
LastLoginDate = now,
|
||||
UpdateDate = now
|
||||
UpdateDate = now,
|
||||
};
|
||||
MemberService.Save(member);
|
||||
|
||||
DateTime newDate = now.AddDays(10);
|
||||
MemberService.SetLastLogin(member.Username, newDate);
|
||||
member.LastLoginDate = newDate;
|
||||
member.UpdateDate = newDate;
|
||||
MemberService.Save(member);
|
||||
|
||||
// re-get
|
||||
member = MemberService.GetById(member.Id);
|
||||
@@ -97,6 +99,121 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services
|
||||
Assert.That(member.UpdateDate, Is.EqualTo(newDate).Within(1).Seconds);
|
||||
}
|
||||
|
||||
// These might seem like some somewhat pointless tests, but there's some funky things going on in MemberRepository when saving.
|
||||
[Test]
|
||||
public void Can_Set_Failed_Password_Attempts()
|
||||
{
|
||||
IMemberType memberType = MemberTypeService.Get("member");
|
||||
IMember member = new Member("xname", "xemail", "xusername", "xrawpassword", memberType, true)
|
||||
{
|
||||
FailedPasswordAttempts = 1,
|
||||
};
|
||||
MemberService.Save(member);
|
||||
|
||||
member.FailedPasswordAttempts = 2;
|
||||
MemberService.Save(member);
|
||||
|
||||
member = MemberService.GetById(member.Id);
|
||||
|
||||
Assert.AreEqual(2, member.FailedPasswordAttempts);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Set_Is_Approved()
|
||||
{
|
||||
IMemberType memberType = MemberTypeService.Get("member");
|
||||
IMember member = new Member("xname", "xemail", "xusername", "xrawpassword", memberType, true);
|
||||
MemberService.Save(member);
|
||||
|
||||
member.IsApproved = false;
|
||||
MemberService.Save(member);
|
||||
|
||||
member = MemberService.GetById(member.Id);
|
||||
|
||||
Assert.IsFalse(member.IsApproved);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Set_Is_Locked_Out()
|
||||
{
|
||||
IMemberType memberType = MemberTypeService.Get("member");
|
||||
IMember member = new Member("xname", "xemail", "xusername", "xrawpassword", memberType, true)
|
||||
{
|
||||
IsLockedOut = false,
|
||||
};
|
||||
MemberService.Save(member);
|
||||
|
||||
member.IsLockedOut = true;
|
||||
MemberService.Save(member);
|
||||
|
||||
member = MemberService.GetById(member.Id);
|
||||
|
||||
Assert.IsTrue(member.IsLockedOut);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Set_Last_Lockout_Date()
|
||||
{
|
||||
DateTime now = DateTime.Now;
|
||||
IMemberType memberType = MemberTypeService.Get("member");
|
||||
IMember member = new Member("xname", "xemail", "xusername", "xrawpassword", memberType, true)
|
||||
{
|
||||
LastLockoutDate = now,
|
||||
};
|
||||
MemberService.Save(member);
|
||||
|
||||
DateTime newDate = now.AddDays(10);
|
||||
member.LastLockoutDate = newDate;
|
||||
MemberService.Save(member);
|
||||
|
||||
// re-get
|
||||
member = MemberService.GetById(member.Id);
|
||||
|
||||
Assert.That(member.LastLockoutDate, Is.EqualTo(newDate).Within(1).Seconds);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_set_Last_Login_Date()
|
||||
{
|
||||
DateTime now = DateTime.Now;
|
||||
IMemberType memberType = MemberTypeService.Get("member");
|
||||
IMember member = new Member("xname", "xemail", "xusername", "xrawpassword", memberType, true)
|
||||
{
|
||||
LastLoginDate = now,
|
||||
};
|
||||
MemberService.Save(member);
|
||||
|
||||
DateTime newDate = now.AddDays(10);
|
||||
member.LastLoginDate = newDate;
|
||||
MemberService.Save(member);
|
||||
|
||||
// re-get
|
||||
member = MemberService.GetById(member.Id);
|
||||
|
||||
Assert.That(member.LastLoginDate, Is.EqualTo(newDate).Within(1).Seconds);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Set_Last_Password_Change_Date()
|
||||
{
|
||||
DateTime now = DateTime.Now;
|
||||
IMemberType memberType = MemberTypeService.Get("member");
|
||||
IMember member = new Member("xname", "xemail", "xusername", "xrawpassword", memberType, true)
|
||||
{
|
||||
LastPasswordChangeDate = now,
|
||||
};
|
||||
MemberService.Save(member);
|
||||
|
||||
DateTime newDate = now.AddDays(10);
|
||||
member.LastPasswordChangeDate = newDate;
|
||||
MemberService.Save(member);
|
||||
|
||||
// re-get
|
||||
member = MemberService.GetById(member.Id);
|
||||
|
||||
Assert.That(member.LastPasswordChangeDate, Is.EqualTo(newDate).Within(1).Seconds);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Create_Member_With_Properties()
|
||||
{
|
||||
@@ -116,17 +233,9 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services
|
||||
|
||||
// contains the umbracoMember... properties created when installing, on the member type
|
||||
// contains the other properties, that PublishedContentType adds (BuiltinMemberProperties)
|
||||
//
|
||||
// TODO: see TODO in PublishedContentType, this list contains duplicates
|
||||
string[] aliases = new[]
|
||||
{
|
||||
Constants.Conventions.Member.Comments,
|
||||
Constants.Conventions.Member.FailedPasswordAttempts,
|
||||
Constants.Conventions.Member.IsApproved,
|
||||
Constants.Conventions.Member.IsLockedOut,
|
||||
Constants.Conventions.Member.LastLockoutDate,
|
||||
Constants.Conventions.Member.LastLoginDate,
|
||||
Constants.Conventions.Member.LastPasswordChangeDate,
|
||||
nameof(IMember.Email),
|
||||
nameof(IMember.Username),
|
||||
nameof(IMember.Comments),
|
||||
@@ -589,14 +698,25 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services
|
||||
// just a c# property of the Member object
|
||||
resolved.Email = "changed@test.com";
|
||||
|
||||
// NOTE: This will not trigger a property isDirty for the same reason above, but this is a new change, so leave this to make sure.
|
||||
resolved.FailedPasswordAttempts = 1234;
|
||||
|
||||
// NOTE: this WILL trigger a property isDirty because setting this c# property actually sets a value of
|
||||
// the underlying 'Property'
|
||||
resolved.FailedPasswordAttempts = 1234;
|
||||
resolved.Comments = "This will make it dirty";
|
||||
|
||||
var dirtyMember = (ICanBeDirty)resolved;
|
||||
var dirtyProperties = resolved.Properties.Where(x => x.IsDirty()).ToList();
|
||||
Assert.IsTrue(dirtyMember.IsDirty());
|
||||
Assert.AreEqual(1, dirtyProperties.Count);
|
||||
|
||||
// Assert that email and failed password attempts is still set as dirty on the member it self
|
||||
Assert.IsTrue(dirtyMember.IsPropertyDirty(nameof(resolved.Email)));
|
||||
Assert.IsTrue(dirtyMember.IsPropertyDirty(nameof(resolved.FailedPasswordAttempts)));
|
||||
|
||||
// Comment will also be marked as dirty on the member object because content base merges dirty properties.
|
||||
Assert.IsTrue(dirtyMember.IsPropertyDirty(Constants.Conventions.Member.Comments));
|
||||
Assert.AreEqual(3, dirtyMember.GetDirtyProperties().Count());
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -1205,7 +1325,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services
|
||||
MemberService.Save(members);
|
||||
|
||||
Member customMember = MemberBuilder.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hello");
|
||||
customMember.SetValue(Constants.Conventions.Member.IsLockedOut, true);
|
||||
customMember.IsLockedOut = true;
|
||||
MemberService.Save(customMember);
|
||||
|
||||
int found = MemberService.GetCount(MemberCountType.LockedOut);
|
||||
@@ -1222,7 +1342,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services
|
||||
MemberService.Save(members);
|
||||
|
||||
Member customMember = MemberBuilder.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hello");
|
||||
customMember.SetValue(Constants.Conventions.Member.IsApproved, false);
|
||||
customMember.IsApproved = false;
|
||||
MemberService.Save(customMember);
|
||||
|
||||
int found = MemberService.GetCount(MemberCountType.Approved);
|
||||
@@ -1250,48 +1370,6 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services
|
||||
Assert.IsTrue(found.Comments.IsNullOrWhiteSpace());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Because we are forcing some of the built-ins to be Labels which have an underlying db type as nvarchar but we need
|
||||
/// to ensure that the dates/int get saved to the correct column anyways.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void Setting_DateTime_Property_On_Built_In_Member_Property_Saves_To_Correct_Column()
|
||||
{
|
||||
IMemberType memberType = MemberTypeBuilder.CreateSimpleMemberType();
|
||||
MemberTypeService.Save(memberType);
|
||||
Member member = MemberBuilder.CreateSimpleMember(memberType, "test", "test@test.com", "test", "test");
|
||||
DateTime date = DateTime.Now;
|
||||
member.LastLoginDate = DateTime.Now;
|
||||
MemberService.Save(member);
|
||||
|
||||
IMember result = MemberService.GetById(member.Id);
|
||||
Assert.AreEqual(
|
||||
date.TruncateTo(DateTimeExtensions.DateTruncate.Second),
|
||||
result.LastLoginDate.TruncateTo(DateTimeExtensions.DateTruncate.Second));
|
||||
|
||||
// now ensure the col is correct
|
||||
ISqlContext sqlContext = GetRequiredService<ISqlContext>();
|
||||
Sql<ISqlContext> sql = sqlContext.Sql().Select<PropertyDataDto>()
|
||||
.From<PropertyDataDto>()
|
||||
.InnerJoin<PropertyTypeDto>().On<PropertyDataDto, PropertyTypeDto>(dto => dto.PropertyTypeId, dto => dto.Id)
|
||||
.InnerJoin<ContentVersionDto>().On<PropertyDataDto, ContentVersionDto>((left, right) => left.VersionId == right.Id)
|
||||
.Where<ContentVersionDto>(dto => dto.NodeId == member.Id)
|
||||
.Where<PropertyTypeDto>(dto => dto.Alias == Constants.Conventions.Member.LastLoginDate);
|
||||
|
||||
List<PropertyDataDto> colResult;
|
||||
using (IScope scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
colResult = ScopeAccessor.AmbientScope.Database.Fetch<PropertyDataDto>(sql);
|
||||
scope.Complete();
|
||||
}
|
||||
|
||||
Assert.AreEqual(1, colResult.Count);
|
||||
Assert.IsTrue(colResult.First().DateValue.HasValue);
|
||||
Assert.IsFalse(colResult.First().IntegerValue.HasValue);
|
||||
Assert.IsNull(colResult.First().TextValue);
|
||||
Assert.IsNull(colResult.First().VarcharValue);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void New_Member_Approved_By_Default()
|
||||
{
|
||||
|
||||
@@ -137,14 +137,14 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Tests.Common.Builders
|
||||
Assert.AreEqual(testLastLoginDate, member.LastLoginDate);
|
||||
Assert.AreEqual(testLastPasswordChangeDate, member.LastPasswordChangeDate);
|
||||
Assert.AreEqual(testGroups, member.Groups.ToArray());
|
||||
Assert.AreEqual(10, member.Properties.Count); // 7 from membership properties group, 3 custom
|
||||
Assert.AreEqual(4, member.Properties.Count); // 1 from membership properties group, 3 custom
|
||||
Assert.AreEqual(testPropertyData1.Value, member.GetValue<string>(testPropertyData1.Key));
|
||||
Assert.AreEqual(testPropertyData2.Value, member.GetValue<string>(testPropertyData2.Key));
|
||||
Assert.AreEqual(testPropertyData3.Value, member.GetValue<string>(testPropertyData3.Key));
|
||||
|
||||
IOrderedEnumerable<int> propertyIds = member.Properties.Select(x => x.Id).OrderBy(x => x);
|
||||
Assert.AreEqual(testPropertyIdsIncrementingFrom + 1, propertyIds.Min());
|
||||
Assert.AreEqual(testPropertyIdsIncrementingFrom + 10, propertyIds.Max());
|
||||
Assert.AreEqual(testPropertyIdsIncrementingFrom + 4, propertyIds.Max());
|
||||
|
||||
Assert.AreEqual(2, member.AdditionalData.Count);
|
||||
Assert.AreEqual(testAdditionalData1.Value, member.AdditionalData[testAdditionalData1.Key]);
|
||||
|
||||
@@ -100,11 +100,11 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Tests.Common.Builders
|
||||
Assert.AreEqual(testThumbnail, memberType.Thumbnail);
|
||||
Assert.AreEqual(testTrashed, memberType.Trashed);
|
||||
Assert.IsFalse(memberType.IsContainer);
|
||||
Assert.AreEqual(9, memberType.PropertyTypes.Count()); // 7 from membership properties group, 2 custom
|
||||
Assert.AreEqual(3, memberType.PropertyTypes.Count()); // 1 from membership properties group, 2 custom
|
||||
|
||||
IOrderedEnumerable<int> propertyTypeIds = memberType.PropertyTypes.Select(x => x.Id).OrderBy(x => x);
|
||||
Assert.AreEqual(testPropertyTypeIdsIncrementingFrom + 1, propertyTypeIds.Min());
|
||||
Assert.AreEqual(testPropertyTypeIdsIncrementingFrom + 9, propertyTypeIds.Max());
|
||||
Assert.AreEqual(testPropertyTypeIdsIncrementingFrom + 3, propertyTypeIds.Max());
|
||||
|
||||
Assert.IsTrue(memberType.MemberCanEditProperty(testPropertyType1.Alias));
|
||||
Assert.IsFalse(memberType.MemberCanViewProperty(testPropertyType1.Alias));
|
||||
|
||||
@@ -594,7 +594,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers
|
||||
Path = member.Path
|
||||
};
|
||||
|
||||
memberDisplay = new MemberDisplay()
|
||||
memberDisplay = new MemberDisplay
|
||||
{
|
||||
Id = memberId,
|
||||
SortOrder = member.SortOrder,
|
||||
@@ -609,33 +609,57 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers
|
||||
ContentTypeName = member.ContentType.Name,
|
||||
Icon = fakeMemberData.Icon,
|
||||
Path = member.Path,
|
||||
Tabs = new List<Tab<ContentPropertyDisplay>>()
|
||||
Tabs = new List<Tab<ContentPropertyDisplay>>
|
||||
{
|
||||
new Tab<ContentPropertyDisplay>()
|
||||
new()
|
||||
{
|
||||
Alias = "test",
|
||||
Id = 77,
|
||||
Properties = new List<ContentPropertyDisplay>()
|
||||
Properties = new List<ContentPropertyDisplay>
|
||||
{
|
||||
new ContentPropertyDisplay()
|
||||
new()
|
||||
{
|
||||
Alias = "_umb_login"
|
||||
Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}login",
|
||||
},
|
||||
new ContentPropertyDisplay()
|
||||
new()
|
||||
{
|
||||
Alias= "_umb_email"
|
||||
Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}email",
|
||||
},
|
||||
new ContentPropertyDisplay()
|
||||
new()
|
||||
{
|
||||
Alias = "_umb_password"
|
||||
Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}password",
|
||||
},
|
||||
new ContentPropertyDisplay()
|
||||
new()
|
||||
{
|
||||
Alias = "_umb_membergroup"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}membergroup",
|
||||
},
|
||||
new()
|
||||
{
|
||||
Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}failedPasswordAttempts",
|
||||
},
|
||||
new()
|
||||
{
|
||||
Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}approved",
|
||||
},
|
||||
new()
|
||||
{
|
||||
Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}lockedOut",
|
||||
},
|
||||
new()
|
||||
{
|
||||
Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}lastLockoutDate",
|
||||
},
|
||||
new()
|
||||
{
|
||||
Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}lastLoginDate",
|
||||
},
|
||||
new()
|
||||
{
|
||||
Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}lastPasswordChangeDate",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return member;
|
||||
|
||||
Reference in New Issue
Block a user