Files
Umbraco-CMS/src/Umbraco.Web/Models/Identity/BackOfficeIdentityUser.cs

435 lines
15 KiB
C#
Raw Normal View History

2017-07-20 11:21:28 +02:00
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
2017-08-14 18:21:48 +02:00
using System.Linq;
using Umbraco.Core;
using Umbraco.Core.Configuration;
using Umbraco.Core.Models.Entities;
using Umbraco.Core.Models.Identity;
2017-08-14 18:21:48 +02:00
using Umbraco.Core.Models.Membership;
namespace Umbraco.Web.Models.Identity
{
2017-08-14 18:21:48 +02:00
public class BackOfficeIdentityUser : IdentityUser<int, IIdentityUserLogin, IdentityUserRole<string>, IdentityUserClaim<int>>, IRememberBeingDirty
{
private string _email;
private string _userName;
private int _id;
private bool _hasIdentity;
private DateTime? _lastLoginDateUtc;
private bool _emailConfirmed;
private string _name;
private int _accessFailedCount;
private string _passwordHash;
private string _culture;
private ObservableCollection<IIdentityUserLogin> _logins;
private Lazy<IEnumerable<IIdentityUserLogin>> _getLogins;
private IReadOnlyUserGroup[] _groups;
private string[] _allowedSections;
private int[] _startMediaIds;
private int[] _startContentIds;
Merge remote-tracking branch 'origin/dev-v7' into temp8 # Conflicts: # src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs # src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs # src/Umbraco.Core/Models/Identity/IdentityModelMappings.cs # src/Umbraco.Core/Persistence/PetaPoco.cs # src/Umbraco.Core/Persistence/PetaPocoSqlExtensions.cs # src/Umbraco.Core/Security/BackOfficeUserManager.cs # src/Umbraco.Core/Services/ContentService.cs # src/Umbraco.Core/Services/FileService.cs # src/Umbraco.Core/Services/IFileService.cs # src/Umbraco.Tests/Configurations/UmbracoSettings/ContentElementTests.cs # src/Umbraco.Tests/Configurations/UmbracoSettings/umbracoSettings.config # src/Umbraco.Tests/FrontEnd/UmbracoHelperTests.cs # src/Umbraco.Tests/Persistence/Querying/PetaPocoSqlTests.cs # src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs # src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js # src/Umbraco.Web.UI.Client/src/less/property-editors.less # src/Umbraco.Web.UI.Client/src/routes.js # src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.controller.js # src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.html # src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js # src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.html # src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html # src/Umbraco.Web.UI.Client/src/views/content/copy.html # src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js # src/Umbraco.Web.UI.Client/src/views/propertyeditors/email/email.html # src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js # src/Umbraco.Web.UI.Client/src/views/propertyeditors/tags/tags.controller.js # src/Umbraco.Web.UI.Client/src/views/propertyeditors/tags/tags.html # src/Umbraco.Web.UI.Client/src/views/users/user.controller.js # src/Umbraco.Web.UI/config/splashes/noNodes.aspx # src/Umbraco.Web.UI/config/umbracoSettings.Release.config # src/Umbraco.Web.UI/config/umbracoSettings.config # src/Umbraco.Web.UI/umbraco/config/lang/en.xml # src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml # src/Umbraco.Web.UI/umbraco/config/lang/fr.xml # src/Umbraco.Web/Editors/ContentController.cs # src/Umbraco.Web/Editors/MemberTypeController.cs # src/Umbraco.Web/Editors/TemplateController.cs # src/Umbraco.Web/PublishedContentExtensions.cs # src/Umbraco.Web/Routing/PublishedContentRequest.cs # src/Umbraco.Web/Routing/UrlProviderExtensions.cs # src/Umbraco.Web/UmbracoHelper.cs # src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs
2018-07-18 13:19:14 +10:00
private DateTime? _lastPasswordChangeDateUtc;
2017-08-14 18:21:48 +02:00
/// <summary>
/// Used to construct a new instance without an identity
/// </summary>
/// <param name="username"></param>
/// <param name="email">This is allowed to be null (but would need to be filled in if trying to persist this instance)</param>
/// <param name="culture"></param>
/// <returns></returns>
public static BackOfficeIdentityUser CreateNew(IGlobalSettings globalSettings, string username, string email, string culture)
2017-08-14 18:21:48 +02:00
{
if (string.IsNullOrWhiteSpace(username)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(username));
if (string.IsNullOrWhiteSpace(culture)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(culture));
2017-08-14 18:21:48 +02:00
var user = new BackOfficeIdentityUser(globalSettings, Array.Empty<IReadOnlyUserGroup>());
2017-08-14 18:21:48 +02:00
user.DisableChangeTracking();
user._userName = username;
user._email = email;
//we are setting minvalue here because the default is "0" which is the id of the admin user
//which we cannot allow because the admin user will always exist
user._id = int.MinValue;
user._hasIdentity = false;
user._culture = culture;
user.EnableChangeTracking();
return user;
}
private BackOfficeIdentityUser(IGlobalSettings globalSettings, IReadOnlyUserGroup[] groups)
2017-08-14 18:21:48 +02:00
{
2019-03-20 17:04:46 +01:00
_startMediaIds = Array.Empty<int>();
_startContentIds = Array.Empty<int>();
_allowedSections = Array.Empty<string>();
_culture = globalSettings.DefaultUILanguage;
2019-03-20 17:04:46 +01:00
// must initialize before setting groups
2018-03-21 09:06:32 +01:00
_roles = new ObservableCollection<IdentityUserRole<string>>();
_roles.CollectionChanged += _roles_CollectionChanged;
2019-03-20 17:04:46 +01:00
// use the property setters - they do more than just setting a field
Groups = groups;
2017-08-14 18:21:48 +02:00
}
/// <summary>
/// Creates an existing user with the specified groups
/// </summary>
/// <param name="globalSettings"></param>
2017-08-14 18:21:48 +02:00
/// <param name="userId"></param>
/// <param name="groups"></param>
public BackOfficeIdentityUser(IGlobalSettings globalSettings, int userId, IEnumerable<IReadOnlyUserGroup> groups)
: this(globalSettings, groups.ToArray())
{
2019-03-20 17:04:46 +01:00
// use the property setters - they do more than just setting a field
Id = userId;
}
2017-08-14 18:21:48 +02:00
/// <summary>
2019-01-22 18:03:39 -05:00
/// Returns true if an Id has been set on this object this will be false if the object is new and not persisted to the database
2017-08-14 18:21:48 +02:00
/// </summary>
public bool HasIdentity => _hasIdentity;
2017-08-14 18:21:48 +02:00
public int[] CalculatedMediaStartNodeIds { get; internal set; }
public int[] CalculatedContentStartNodeIds { get; internal set; }
public override int Id
{
get => _id;
2017-08-14 18:21:48 +02:00
set
{
_id = value;
_hasIdentity = true;
}
}
/// <summary>
/// Override Email so we can track changes to it
/// </summary>
public override string Email
{
get => _email;
set => _beingDirty.SetPropertyValueAndDetectChanges(value, ref _email, nameof(Email));
2017-08-14 18:21:48 +02:00
}
/// <summary>
/// Override UserName so we can track changes to it
/// </summary>
public override string UserName
{
get => _userName;
set => _beingDirty.SetPropertyValueAndDetectChanges(value, ref _userName, nameof(UserName));
2017-08-14 18:21:48 +02:00
}
/// <summary>
/// LastPasswordChangeDateUtc so we can track changes to it
/// </summary>
public override DateTime? LastPasswordChangeDateUtc
{
get { return _lastPasswordChangeDateUtc; }
set { _beingDirty.SetPropertyValueAndDetectChanges(value, ref _lastPasswordChangeDateUtc, nameof(LastPasswordChangeDateUtc)); }
}
2017-08-14 18:21:48 +02:00
/// <summary>
/// Override LastLoginDateUtc so we can track changes to it
/// </summary>
public override DateTime? LastLoginDateUtc
{
get => _lastLoginDateUtc;
set => _beingDirty.SetPropertyValueAndDetectChanges(value, ref _lastLoginDateUtc, nameof(LastLoginDateUtc));
2017-08-14 18:21:48 +02:00
}
/// <summary>
/// Override EmailConfirmed so we can track changes to it
/// </summary>
public override bool EmailConfirmed
{
get => _emailConfirmed;
set => _beingDirty.SetPropertyValueAndDetectChanges(value, ref _emailConfirmed, nameof(EmailConfirmed));
2017-08-14 18:21:48 +02:00
}
/// <summary>
/// Gets/sets the user's real name
/// </summary>
2017-08-14 18:21:48 +02:00
public string Name
{
get => _name;
set => _beingDirty.SetPropertyValueAndDetectChanges(value, ref _name, nameof(Name));
2017-08-14 18:21:48 +02:00
}
/// <summary>
/// Override AccessFailedCount so we can track changes to it
/// </summary>
public override int AccessFailedCount
{
get => _accessFailedCount;
set => _beingDirty.SetPropertyValueAndDetectChanges(value, ref _accessFailedCount, nameof(AccessFailedCount));
2017-08-14 18:21:48 +02:00
}
/// <summary>
/// Override PasswordHash so we can track changes to it
/// </summary>
public override string PasswordHash
{
get => _passwordHash;
set => _beingDirty.SetPropertyValueAndDetectChanges(value, ref _passwordHash, nameof(PasswordHash));
2017-08-14 18:21:48 +02:00
}
/// <summary>
/// Content start nodes assigned to the User (not ones assigned to the user's groups)
/// </summary>
public int[] StartContentIds
{
get => _startContentIds;
2017-08-14 18:21:48 +02:00
set
{
if (value == null) value = new int[0];
_beingDirty.SetPropertyValueAndDetectChanges(value, ref _startContentIds, nameof(StartContentIds), StartIdsComparer);
2017-08-14 18:21:48 +02:00
}
}
/// <summary>
/// Media start nodes assigned to the User (not ones assigned to the user's groups)
/// </summary>
public int[] StartMediaIds
{
get => _startMediaIds;
2017-08-14 18:21:48 +02:00
set
{
if (value == null) value = new int[0];
_beingDirty.SetPropertyValueAndDetectChanges(value, ref _startMediaIds, nameof(StartMediaIds), StartIdsComparer);
2017-08-14 18:21:48 +02:00
}
}
/// <summary>
/// This is a readonly list of the user's allowed sections which are based on it's user groups
/// </summary>
public string[] AllowedSections
{
get { return _allowedSections ?? (_allowedSections = _groups.SelectMany(x => x.AllowedSections).Distinct().ToArray()); }
}
public string Culture
{
get => _culture;
set => _beingDirty.SetPropertyValueAndDetectChanges(value, ref _culture, nameof(Culture));
2017-08-14 18:21:48 +02:00
}
public IReadOnlyUserGroup[] Groups
{
get => _groups;
2017-08-14 18:21:48 +02:00
set
{
//so they recalculate
_allowedSections = null;
2019-03-20 17:04:46 +01:00
_groups = value;
2017-08-14 18:21:48 +02:00
//now clear all roles and re-add them
_roles.CollectionChanged -= _roles_CollectionChanged;
_roles.Clear();
foreach (var identityUserRole in _groups.Select(x => new IdentityUserRole<string>
{
RoleId = x.Alias,
UserId = Id.ToString()
}))
{
_roles.Add(identityUserRole);
}
_roles.CollectionChanged += _roles_CollectionChanged;
_beingDirty.SetPropertyValueAndDetectChanges(value, ref _groups, nameof(Groups), GroupsComparer);
2017-08-14 18:21:48 +02:00
}
}
/// <summary>
/// Lockout is always enabled
/// </summary>
public override bool LockoutEnabled
{
get { return true; }
2017-07-20 11:21:28 +02:00
set
{
2017-07-20 11:21:28 +02:00
//do nothing
}
}
/// <summary>
/// Based on the user's lockout end date, this will determine if they are locked out
/// </summary>
internal bool IsLockedOut
{
get
{
var isLocked = LockoutEndDateUtc.HasValue && LockoutEndDateUtc.Value.ToLocalTime() >= DateTime.Now;
return isLocked;
}
}
2017-09-15 18:22:19 +02:00
/// <summary>
/// This is a 1:1 mapping with IUser.IsApproved
/// </summary>
internal bool IsApproved { get; set; }
/// <summary>
/// Overridden to make the retrieval lazy
/// </summary>
public override ICollection<IIdentityUserLogin> Logins
{
get
{
if (_getLogins != null && _getLogins.IsValueCreated == false)
{
_logins = new ObservableCollection<IIdentityUserLogin>();
foreach (var l in _getLogins.Value)
{
_logins.Add(l);
}
//now assign events
_logins.CollectionChanged += Logins_CollectionChanged;
}
return _logins;
}
}
void Logins_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
_beingDirty.OnPropertyChanged(nameof(Logins));
}
2017-08-14 18:21:48 +02:00
private void _roles_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
_beingDirty.OnPropertyChanged(nameof(Roles));
2017-08-14 18:21:48 +02:00
}
private readonly ObservableCollection<IdentityUserRole<string>> _roles;
/// <summary>
/// helper method to easily add a role without having to deal with IdentityUserRole{T}
/// </summary>
/// <param name="role"></param>
/// <remarks>
/// Adding a role this way will not reflect on the user's group's collection or it's allowed sections until the user is persisted
/// </remarks>
public void AddRole(string role)
{
Roles.Add(new IdentityUserRole<string>
{
UserId = Id.ToString(),
2017-08-14 18:21:48 +02:00
RoleId = role
});
}
/// <summary>
/// Override Roles because the value of these are the user's group aliases
/// </summary>
public override ICollection<IdentityUserRole<string>> Roles => _roles;
/// <summary>
/// Used to set a lazy call back to populate the user's Login list
/// </summary>
/// <param name="callback"></param>
public void SetLoginsCallback(Lazy<IEnumerable<IIdentityUserLogin>> callback)
{
_getLogins = callback ?? throw new ArgumentNullException(nameof(callback));
}
2017-08-14 18:21:48 +02:00
#region BeingDirty
2017-08-14 18:21:48 +02:00
private readonly BeingDirty _beingDirty = new BeingDirty();
/// <inheritdoc />
public bool IsDirty()
2017-08-14 18:21:48 +02:00
{
return _beingDirty.IsDirty();
2017-08-14 18:21:48 +02:00
}
/// <inheritdoc />
public bool IsPropertyDirty(string propName)
2017-08-14 18:21:48 +02:00
{
return _beingDirty.IsPropertyDirty(propName);
2017-08-14 18:21:48 +02:00
}
/// <inheritdoc />
public IEnumerable<string> GetDirtyProperties()
2017-08-14 18:21:48 +02:00
{
return _beingDirty.GetDirtyProperties();
2017-08-14 18:21:48 +02:00
}
/// <inheritdoc />
public void ResetDirtyProperties()
2017-08-14 18:21:48 +02:00
{
_beingDirty.ResetDirtyProperties();
2017-08-14 18:21:48 +02:00
}
/// <inheritdoc />
public bool WasDirty()
2017-08-14 18:21:48 +02:00
{
return _beingDirty.WasDirty();
2017-08-14 18:21:48 +02:00
}
/// <inheritdoc />
public bool WasPropertyDirty(string propertyName)
2017-08-14 18:21:48 +02:00
{
return _beingDirty.WasPropertyDirty(propertyName);
2017-08-14 18:21:48 +02:00
}
/// <inheritdoc />
public void ResetWereDirtyProperties()
2017-08-14 18:21:48 +02:00
{
_beingDirty.ResetWereDirtyProperties();
2017-08-14 18:21:48 +02:00
}
/// <inheritdoc />
public void ResetDirtyProperties(bool rememberDirty)
2017-08-14 18:21:48 +02:00
{
_beingDirty.ResetDirtyProperties(rememberDirty);
2017-08-14 18:21:48 +02:00
}
2018-03-22 17:41:13 +01:00
/// <inheritdoc />
public IEnumerable<string> GetWereDirtyProperties()
=> _beingDirty.GetWereDirtyProperties();
/// <summary>
/// Disables change tracking.
/// </summary>
public void DisableChangeTracking()
2017-08-14 18:21:48 +02:00
{
_beingDirty.DisableChangeTracking();
}
/// <summary>
/// Enables change tracking.
/// </summary>
public void EnableChangeTracking()
{
_beingDirty.EnableChangeTracking();
2017-08-14 18:21:48 +02:00
}
public event PropertyChangedEventHandler PropertyChanged
{
add
{
_beingDirty.PropertyChanged += value;
}
remove
{
_beingDirty.PropertyChanged -= value;
}
}
#endregion
//Custom comparer for enumerables
private static readonly DelegateEqualityComparer<IReadOnlyUserGroup[]> GroupsComparer = new DelegateEqualityComparer<IReadOnlyUserGroup[]>(
(groups, enumerable) => groups.Select(x => x.Alias).UnsortedSequenceEqual(enumerable.Select(x => x.Alias)),
groups => groups.GetHashCode());
2019-02-04 10:09:32 +01:00
private static readonly DelegateEqualityComparer<int[]> StartIdsComparer = new DelegateEqualityComparer<int[]>(
(groups, enumerable) => groups.UnsortedSequenceEqual(enumerable),
groups => groups.GetHashCode());
}
2017-07-20 11:21:28 +02:00
}