2021-02-01 16:07:42 +01:00
using System ;
2018-06-29 19:52:40 +02:00
using System.Collections.Generic ;
2020-05-20 11:33:10 +10:00
using System.ComponentModel ;
2018-06-29 19:52:40 +02:00
using System.Runtime.Serialization ;
2020-09-16 13:08:27 +02:00
using Microsoft.Extensions.Logging ;
2021-02-18 11:06:02 +01:00
using Umbraco.Extensions ;
2020-09-16 13:08:27 +02:00
2021-02-18 11:06:02 +01:00
namespace Umbraco.Cms.Core.Models
2018-06-29 19:52:40 +02:00
{
/// <summary>
/// Represents a Member object
/// </summary>
[Serializable]
[DataContract(IsReference = true)]
public class Member : ContentBase , IMember
{
2022-01-21 11:43:58 +01:00
private IDictionary < string , object? > ? _additionalData ;
2022-02-15 15:48:41 +01:00
private string _username ;
private string _email ;
2022-01-21 11:43:58 +01:00
private string? _rawPasswordValue ;
private string? _passwordConfig ;
2021-02-09 16:14:32 +00:00
private DateTime ? _emailConfirmedDate ;
2022-01-21 11:43:58 +01:00
private string? _securityStamp ;
2022-04-21 14:47:27 +02:00
private int _failedPasswordAttempts ;
private bool _isApproved ;
private bool _isLockedOut ;
private DateTime ? _lastLockoutDate ;
private DateTime ? _lastLoginDate ;
private DateTime ? _lastPasswordChangeDate ;
2018-06-29 19:52:40 +02:00
/// <summary>
2021-02-09 16:14:32 +00:00
/// Initializes a new instance of the <see cref="Member"/> class.
2018-06-29 19:52:40 +02:00
/// Constructor for creating an empty Member object
/// </summary>
/// <param name="contentType">ContentType for the current Content object</param>
public Member ( IMemberType contentType )
: base ( "" , - 1 , contentType , new PropertyCollection ( ) )
{
IsApproved = true ;
2021-02-09 16:14:32 +00:00
// this cannot be null but can be empty
2018-06-29 19:52:40 +02:00
_rawPasswordValue = "" ;
_email = "" ;
_username = "" ;
}
/// <summary>
2021-02-09 16:14:32 +00:00
/// Initializes a new instance of the <see cref="Member"/> class.
2018-06-29 19:52:40 +02:00
/// Constructor for creating a Member object
/// </summary>
/// <param name="name">Name of the content</param>
/// <param name="contentType">ContentType for the current Content object</param>
public Member ( string name , IMemberType contentType )
: base ( name , - 1 , contentType , new PropertyCollection ( ) )
{
2021-02-09 16:14:32 +00:00
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 ) ) ;
2018-06-29 19:52:40 +02:00
IsApproved = true ;
2021-02-09 16:14:32 +00:00
// this cannot be null but can be empty
2018-06-29 19:52:40 +02:00
_rawPasswordValue = "" ;
_email = "" ;
_username = "" ;
}
/// <summary>
2021-02-09 16:14:32 +00:00
/// Initializes a new instance of the <see cref="Member"/> class.
2018-06-29 19:52:40 +02:00
/// Constructor for creating a Member object
/// </summary>
/// <param name="name"></param>
/// <param name="email"></param>
/// <param name="username"></param>
/// <param name="contentType"></param>
public Member ( string name , string email , string username , IMemberType contentType , bool isApproved = true )
: base ( name , - 1 , contentType , new PropertyCollection ( ) )
{
2021-02-09 16:14:32 +00:00
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 ) ) ;
2018-06-29 19:52:40 +02:00
_email = email ;
_username = username ;
IsApproved = isApproved ;
2021-02-09 16:14:32 +00:00
// this cannot be null but can be empty
2018-06-29 19:52:40 +02:00
_rawPasswordValue = "" ;
}
2021-02-01 16:07:42 +01:00
/// <summary>
2021-02-09 16:14:32 +00:00
/// Initializes a new instance of the <see cref="Member"/> class.
2021-02-01 16:07:42 +01:00
/// Constructor for creating a Member object
/// </summary>
/// <param name="name"></param>
/// <param name="email"></param>
/// <param name="username"></param>
/// <param name="contentType"></param>
/// <param name="userId"></param>
/// <param name="isApproved"></param>
public Member ( string name , string email , string username , IMemberType contentType , int userId , 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 ) ) ;
_email = email ;
_username = username ;
CreatorId = userId ;
IsApproved = isApproved ;
//this cannot be null but can be empty
_rawPasswordValue = "" ;
}
2018-06-29 19:52:40 +02:00
/// <summary>
/// Constructor for creating a Member object
/// </summary>
/// <param name="name"></param>
/// <param name="email"></param>
/// <param name="username"></param>
/// <param name="rawPasswordValue">
/// The password value passed in to this parameter should be the encoded/encrypted/hashed format of the member's password
/// </param>
/// <param name="contentType"></param>
2022-02-24 09:24:56 +01:00
public Member ( string? name , string email , string username , string? rawPasswordValue , IMemberType ? contentType )
2018-06-29 19:52:40 +02:00
: base ( name , - 1 , contentType , new PropertyCollection ( ) )
{
_email = email ;
_username = username ;
_rawPasswordValue = rawPasswordValue ;
IsApproved = true ;
}
/// <summary>
2021-02-09 16:14:32 +00:00
/// Initializes a new instance of the <see cref="Member"/> class.
2018-06-29 19:52:40 +02:00
/// Constructor for creating a Member object
/// </summary>
/// <param name="name"></param>
/// <param name="email"></param>
/// <param name="username"></param>
/// <param name="rawPasswordValue">
/// The password value passed in to this parameter should be the encoded/encrypted/hashed format of the member's password
/// </param>
/// <param name="contentType"></param>
/// <param name="isApproved"></param>
public Member ( string name , string email , string username , string rawPasswordValue , IMemberType contentType , bool isApproved )
: base ( name , - 1 , contentType , new PropertyCollection ( ) )
{
_email = email ;
_username = username ;
_rawPasswordValue = rawPasswordValue ;
IsApproved = isApproved ;
}
2019-02-04 10:09:32 +01:00
2021-02-01 16:07:42 +01:00
/// <summary>
/// Constructor for creating a Member object
/// </summary>
/// <param name="name"></param>
/// <param name="email"></param>
/// <param name="username"></param>
/// <param name="rawPasswordValue">
/// The password value passed in to this parameter should be the encoded/encrypted/hashed format of the member's password
/// </param>
/// <param name="contentType"></param>
/// <param name="isApproved"></param>
/// <param name="userId"></param>
public Member ( string name , string email , string username , string rawPasswordValue , IMemberType contentType , bool isApproved , int userId )
: base ( name , - 1 , contentType , new PropertyCollection ( ) )
{
_email = email ;
_username = username ;
_rawPasswordValue = rawPasswordValue ;
IsApproved = isApproved ;
CreatorId = userId ;
}
2018-06-29 19:52:40 +02:00
/// <summary>
/// Gets or sets the Username
/// </summary>
[DataMember]
2022-02-15 15:48:41 +01:00
public string Username
2018-06-29 19:52:40 +02:00
{
2019-02-04 19:52:04 +11:00
get = > _username ;
2022-02-15 15:48:41 +01:00
set = > SetPropertyValueAndDetectChanges ( value , ref _username ! , nameof ( Username ) ) ;
2018-06-29 19:52:40 +02:00
}
/// <summary>
/// Gets or sets the Email
/// </summary>
[DataMember]
2022-02-15 15:48:41 +01:00
public string Email
2018-06-29 19:52:40 +02:00
{
2019-02-04 19:52:04 +11:00
get = > _email ;
2022-02-15 15:48:41 +01:00
set = > SetPropertyValueAndDetectChanges ( value , ref _email ! , nameof ( Email ) ) ;
2018-06-29 19:52:40 +02:00
}
2021-02-09 16:14:32 +00:00
[DataMember]
public DateTime ? EmailConfirmedDate
{
get = > _emailConfirmedDate ;
set = > SetPropertyValueAndDetectChanges ( value , ref _emailConfirmedDate , nameof ( EmailConfirmedDate ) ) ;
}
2018-06-29 19:52:40 +02:00
/// <summary>
/// Gets or sets the raw password value
/// </summary>
[IgnoreDataMember]
2022-01-21 11:43:58 +01:00
public string? RawPasswordValue
2018-06-29 19:52:40 +02:00
{
2019-02-04 19:52:04 +11:00
get = > _rawPasswordValue ;
2018-06-29 19:52:40 +02:00
set
{
if ( value = = null )
{
//special case, this is used to ensure that the password is not updated when persisting, in this case
//we don't want to track changes either
_rawPasswordValue = null ;
}
else
{
2019-02-04 19:52:04 +11:00
SetPropertyValueAndDetectChanges ( value , ref _rawPasswordValue , nameof ( RawPasswordValue ) ) ;
2018-06-29 19:52:40 +02:00
}
}
}
2020-05-27 13:48:26 +10:00
[IgnoreDataMember]
2022-01-21 11:43:58 +01:00
public string? PasswordConfiguration
2020-05-27 13:48:26 +10:00
{
get = > _passwordConfig ;
set = > SetPropertyValueAndDetectChanges ( value , ref _passwordConfig , nameof ( PasswordConfiguration ) ) ;
}
2018-06-29 19:52:40 +02:00
/// <summary>
/// Gets or sets the Groups that Member is part of
/// </summary>
[DataMember]
2022-01-21 11:43:58 +01:00
public IEnumerable < string > ? Groups { get ; set ; }
2018-06-29 19:52:40 +02:00
2019-01-27 01:17:32 -05:00
// TODO: When get/setting all of these properties we MUST:
2018-06-29 19:52:40 +02:00
// * Check if we are using the umbraco membership provider, if so then we need to use the configured fields - not the explicit fields below
// * If any of the fields don't exist, what should we do? Currently it will throw an exception!
/// <summary>
/// Gets or set the comments for the member
/// </summary>
/// <remarks>
/// Alias: umbracoMemberComments
/// Part of the standard properties collection.
/// </remarks>
[DataMember]
2022-01-21 11:43:58 +01:00
public string? Comments
2018-06-29 19:52:40 +02:00
{
get
{
2019-11-26 12:49:57 +11:00
var a = WarnIfPropertyTypeNotFoundOnGet ( Constants . Conventions . Member . Comments , nameof ( Comments ) , default ( string ) ) ;
2021-02-09 16:14:32 +00:00
if ( a . Success = = false )
return a . Result ;
2018-06-29 19:52:40 +02:00
2022-02-09 13:24:35 +01:00
return Properties [ Constants . Conventions . Member . Comments ] ? . GetValue ( ) = = null
2018-06-29 19:52:40 +02:00
? string . Empty
2022-02-09 13:24:35 +01:00
: Properties [ Constants . Conventions . Member . Comments ] ? . GetValue ( ) ? . ToString ( ) ;
2018-06-29 19:52:40 +02:00
}
set
{
if ( WarnIfPropertyTypeNotFoundOnSet (
Constants . Conventions . Member . Comments ,
2021-02-09 16:14:32 +00:00
nameof ( Comments ) ) = = false )
return ;
2018-06-29 19:52:40 +02:00
2022-02-09 13:24:35 +01:00
Properties [ Constants . Conventions . Member . Comments ] ? . SetValue ( value ) ;
2018-06-29 19:52:40 +02:00
}
}
/// <summary>
2022-04-21 14:47:27 +02:00
/// Gets or sets a value indicating whether the Member is approved
2018-06-29 19:52:40 +02:00
/// </summary>
[DataMember]
public bool IsApproved
{
2022-04-21 14:47:27 +02:00
get = > _isApproved ;
set = > SetPropertyValueAndDetectChanges ( value , ref _isApproved , nameof ( IsApproved ) ) ;
2018-06-29 19:52:40 +02:00
}
/// <summary>
/// Gets or sets a boolean indicating whether the Member is locked out
/// </summary>
/// <remarks>
/// Alias: umbracoMemberLockedOut
/// Part of the standard properties collection.
/// </remarks>
[DataMember]
public bool IsLockedOut
{
2022-04-21 14:47:27 +02:00
get = > _isLockedOut ;
set = > SetPropertyValueAndDetectChanges ( value , ref _isLockedOut , nameof ( IsLockedOut ) ) ;
2018-06-29 19:52:40 +02:00
}
/// <summary>
/// Gets or sets the date for last login
/// </summary>
/// <remarks>
/// Alias: umbracoMemberLastLogin
/// Part of the standard properties collection.
/// </remarks>
[DataMember]
2022-04-21 14:47:27 +02:00
public DateTime ? LastLoginDate
2018-06-29 19:52:40 +02:00
{
2022-04-21 14:47:27 +02:00
get = > _lastLoginDate ;
set = > SetPropertyValueAndDetectChanges ( value , ref _lastLoginDate , nameof ( LastLoginDate ) ) ;
2018-06-29 19:52:40 +02:00
}
/// <summary>
/// Gest or sets the date for last password change
/// </summary>
/// <remarks>
/// Alias: umbracoMemberLastPasswordChangeDate
/// Part of the standard properties collection.
/// </remarks>
[DataMember]
2022-04-21 14:47:27 +02:00
public DateTime ? LastPasswordChangeDate
2018-06-29 19:52:40 +02:00
{
2022-04-21 14:47:27 +02:00
get = > _lastPasswordChangeDate ;
set = > SetPropertyValueAndDetectChanges ( value , ref _lastPasswordChangeDate , nameof ( LastPasswordChangeDate ) ) ;
2018-06-29 19:52:40 +02:00
}
/// <summary>
/// Gets or sets the date for when Member was locked out
/// </summary>
/// <remarks>
/// Alias: umbracoMemberLastLockoutDate
/// Part of the standard properties collection.
/// </remarks>
[DataMember]
2022-04-21 14:47:27 +02:00
public DateTime ? LastLockoutDate
2018-06-29 19:52:40 +02:00
{
2022-04-21 14:47:27 +02:00
get = > _lastLockoutDate ;
set = > SetPropertyValueAndDetectChanges ( value , ref _lastLockoutDate , nameof ( LastLockoutDate ) ) ;
2018-06-29 19:52:40 +02:00
}
/// <summary>
/// Gets or sets the number of failed password attempts.
/// This is the number of times the password was entered incorrectly upon login.
/// </summary>
/// <remarks>
/// Alias: umbracoMemberFailedPasswordAttempts
/// Part of the standard properties collection.
/// </remarks>
[DataMember]
public int FailedPasswordAttempts
{
2022-04-21 14:47:27 +02:00
get = > _failedPasswordAttempts ;
set = > SetPropertyValueAndDetectChanges ( value , ref _failedPasswordAttempts , nameof ( FailedPasswordAttempts ) ) ;
2018-06-29 19:52:40 +02:00
}
/// <summary>
/// String alias of the default ContentType
/// </summary>
[DataMember]
2019-02-05 14:06:48 +01:00
public virtual string ContentTypeAlias = > ContentType . Alias ;
2018-06-29 19:52:40 +02:00
2021-02-09 16:14:32 +00:00
/// <summary>
/// The security stamp used by ASP.Net identity
/// </summary>
[IgnoreDataMember]
2022-01-21 11:43:58 +01:00
public string? SecurityStamp
2021-02-09 16:14:32 +00:00
{
get = > _securityStamp ;
set = > SetPropertyValueAndDetectChanges ( value , ref _securityStamp , nameof ( SecurityStamp ) ) ;
}
2020-05-20 11:33:10 +10:00
/// <summary>
2020-09-16 09:58:07 +02:00
/// Internal/Experimental - only used for mapping queries.
2020-05-20 11:33:10 +10:00
/// </summary>
/// <remarks>
/// Adding these to have first level properties instead of the Properties collection.
/// </remarks>
2018-06-29 19:52:40 +02:00
[IgnoreDataMember]
2020-05-20 11:33:10 +10:00
[EditorBrowsable(EditorBrowsableState.Never)]
2022-01-21 11:43:58 +01:00
public string? LongStringPropertyValue { get ; set ; }
2020-05-20 11:33:10 +10:00
/// <summary>
2020-09-16 09:58:07 +02:00
/// Internal/Experimental - only used for mapping queries.
2020-05-20 11:33:10 +10:00
/// </summary>
/// <remarks>
/// Adding these to have first level properties instead of the Properties collection.
/// </remarks>
2018-06-29 19:52:40 +02:00
[IgnoreDataMember]
2020-05-20 11:33:10 +10:00
[EditorBrowsable(EditorBrowsableState.Never)]
2022-01-21 11:43:58 +01:00
public string? ShortStringPropertyValue { get ; set ; }
2020-05-20 11:33:10 +10:00
/// <summary>
2020-09-16 09:58:07 +02:00
/// Internal/Experimental - only used for mapping queries.
2020-05-20 11:33:10 +10:00
/// </summary>
/// <remarks>
/// Adding these to have first level properties instead of the Properties collection.
/// </remarks>
2018-06-29 19:52:40 +02:00
[IgnoreDataMember]
2020-05-20 11:33:10 +10:00
[EditorBrowsable(EditorBrowsableState.Never)]
public int IntegerPropertyValue { get ; set ; }
/// <summary>
2020-09-16 09:58:07 +02:00
/// Internal/Experimental - only used for mapping queries.
2020-05-20 11:33:10 +10:00
/// </summary>
/// <remarks>
/// Adding these to have first level properties instead of the Properties collection.
/// </remarks>
2018-06-29 19:52:40 +02:00
[IgnoreDataMember]
2020-05-20 11:33:10 +10:00
[EditorBrowsable(EditorBrowsableState.Never)]
public bool BoolPropertyValue { get ; set ; }
/// <summary>
2020-09-16 09:58:07 +02:00
/// Internal/Experimental - only used for mapping queries.
2020-05-20 11:33:10 +10:00
/// </summary>
/// <remarks>
/// Adding these to have first level properties instead of the Properties collection.
/// </remarks>
2018-06-29 19:52:40 +02:00
[IgnoreDataMember]
2020-05-20 11:33:10 +10:00
[EditorBrowsable(EditorBrowsableState.Never)]
public DateTime DateTimePropertyValue { get ; set ; }
/// <summary>
2020-09-16 09:58:07 +02:00
/// Internal/Experimental - only used for mapping queries.
2020-05-20 11:33:10 +10:00
/// </summary>
/// <remarks>
/// Adding these to have first level properties instead of the Properties collection.
/// </remarks>
2018-06-29 19:52:40 +02:00
[IgnoreDataMember]
2020-05-20 11:33:10 +10:00
[EditorBrowsable(EditorBrowsableState.Never)]
2022-01-21 11:43:58 +01:00
public string? PropertyTypeAlias { get ; set ; }
2018-06-29 19:52:40 +02:00
private Attempt < T > WarnIfPropertyTypeNotFoundOnGet < T > ( string propertyAlias , string propertyName , T defaultVal )
{
void DoLog ( string logPropertyAlias , string logPropertyName )
2018-08-16 12:00:12 +01:00
{
2020-11-10 08:50:47 +00:00
StaticApplicationLogging . Logger . LogWarning ( "Trying to access the '{PropertyName}' property on '{MemberType}' " +
2018-08-16 12:00:12 +01:00
"but the {PropertyAlias} property does not exist on the member type so a default value is returned. " +
"Ensure that you have a property type with alias: {PropertyAlias} configured on your member type in order to use the '{PropertyName}' property on the model correctly." ,
logPropertyName ,
typeof ( Member ) ,
logPropertyAlias ) ;
}
2018-06-29 19:52:40 +02:00
// if the property doesn't exist,
if ( Properties . Contains ( propertyAlias ) = = false )
{
// put a warn in the log if this entity has been persisted
// then return a failure
if ( HasIdentity )
DoLog ( propertyAlias , propertyName ) ;
return Attempt < T > . Fail ( defaultVal ) ;
}
return Attempt < T > . Succeed ( ) ;
}
private bool WarnIfPropertyTypeNotFoundOnSet ( string propertyAlias , string propertyName )
{
void DoLog ( string logPropertyAlias , string logPropertyName )
2018-08-16 12:00:12 +01:00
{
2020-11-10 08:50:47 +00:00
StaticApplicationLogging . Logger . LogWarning ( "An attempt was made to set a value on the property '{PropertyName}' on type '{MemberType}' but the " +
2018-08-16 12:00:12 +01:00
"property type {PropertyAlias} does not exist on the member type, ensure that this property type exists so that setting this property works correctly." ,
logPropertyName ,
typeof ( Member ) ,
logPropertyAlias ) ;
}
2018-06-29 19:52:40 +02:00
// if the property doesn't exist,
if ( Properties . Contains ( propertyAlias ) = = false )
{
// put a warn in the log if this entity has been persisted
// then return a failure
if ( HasIdentity )
DoLog ( propertyAlias , propertyName ) ;
return false ;
}
return true ;
}
/// <inheritdoc />
[DataMember]
[DoNotClone]
2022-01-21 11:43:58 +01:00
public IDictionary < string , object? > ? AdditionalData = > _additionalData ? ? ( _additionalData = new Dictionary < string , object? > ( ) ) ;
2018-06-29 19:52:40 +02:00
/// <inheritdoc />
[IgnoreDataMember]
2020-09-16 09:58:07 +02:00
public bool HasAdditionalData = > _additionalData ! = null ;
2018-06-29 19:52:40 +02:00
}
}