aboutsummaryrefslogtreecommitdiff
path: root/Emby.Server.Implementations/Library/UserManager.cs
diff options
context:
space:
mode:
authorJoshua M. Boniface <joshua@boniface.me>2019-08-19 14:57:48 -0400
committerGitHub <noreply@github.com>2019-08-19 14:57:48 -0400
commitd95c04787cc4486f4ea5caaef20f6ac407a3f84a (patch)
tree7e3193614c5a132ae63034c2bb7e07956a5670e6 /Emby.Server.Implementations/Library/UserManager.cs
parent3ba709fcc32d7255a2cb2466dde8c2479130a2bc (diff)
parentd99278da1dcac4d3c60739e864597aa01f916636 (diff)
Merge branch 'master' into h265
Diffstat (limited to 'Emby.Server.Implementations/Library/UserManager.cs')
-rw-r--r--Emby.Server.Implementations/Library/UserManager.cs458
1 files changed, 222 insertions, 236 deletions
diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs
index b396ee51a..a7ea13ca6 100644
--- a/Emby.Server.Implementations/Library/UserManager.cs
+++ b/Emby.Server.Implementations/Library/UserManager.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
@@ -11,13 +12,11 @@ using MediaBrowser.Common.Events;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Authentication;
-using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Plugins;
using MediaBrowser.Controller.Providers;
@@ -41,34 +40,19 @@ namespace Emby.Server.Implementations.Library
public class UserManager : IUserManager
{
/// <summary>
- /// Gets the users.
- /// </summary>
- /// <value>The users.</value>
- public IEnumerable<User> Users => _users;
-
- private User[] _users;
-
- /// <summary>
/// The _logger
/// </summary>
private readonly ILogger _logger;
- /// <summary>
- /// Gets or sets the configuration manager.
- /// </summary>
- /// <value>The configuration manager.</value>
- private IServerConfigurationManager ConfigurationManager { get; set; }
+ private readonly object _policySyncLock = new object();
/// <summary>
/// Gets the active user repository
/// </summary>
/// <value>The user repository.</value>
- private IUserRepository UserRepository { get; set; }
- public event EventHandler<GenericEventArgs<User>> UserPasswordChanged;
-
+ private readonly IUserRepository _userRepository;
private readonly IXmlSerializer _xmlSerializer;
private readonly IJsonSerializer _jsonSerializer;
-
private readonly INetworkManager _networkManager;
private readonly Func<IImageProcessor> _imageProcessorFactory;
@@ -76,15 +60,18 @@ namespace Emby.Server.Implementations.Library
private readonly IServerApplicationHost _appHost;
private readonly IFileSystem _fileSystem;
+ private ConcurrentDictionary<Guid, User> _users;
+
private IAuthenticationProvider[] _authenticationProviders;
private DefaultAuthenticationProvider _defaultAuthenticationProvider;
+ private InvalidAuthProvider _invalidAuthProvider;
+
private IPasswordResetProvider[] _passwordResetProviders;
private DefaultPasswordResetProvider _defaultPasswordResetProvider;
public UserManager(
- ILoggerFactory loggerFactory,
- IServerConfigurationManager configurationManager,
+ ILogger<UserManager> logger,
IUserRepository userRepository,
IXmlSerializer xmlSerializer,
INetworkManager networkManager,
@@ -94,8 +81,8 @@ namespace Emby.Server.Implementations.Library
IJsonSerializer jsonSerializer,
IFileSystem fileSystem)
{
- _logger = loggerFactory.CreateLogger(nameof(UserManager));
- UserRepository = userRepository;
+ _logger = logger;
+ _userRepository = userRepository;
_xmlSerializer = xmlSerializer;
_networkManager = networkManager;
_imageProcessorFactory = imageProcessorFactory;
@@ -103,8 +90,51 @@ namespace Emby.Server.Implementations.Library
_appHost = appHost;
_jsonSerializer = jsonSerializer;
_fileSystem = fileSystem;
- ConfigurationManager = configurationManager;
- _users = Array.Empty<User>();
+ _users = null;
+ }
+
+ public event EventHandler<GenericEventArgs<User>> UserPasswordChanged;
+
+ /// <summary>
+ /// Occurs when [user updated].
+ /// </summary>
+ public event EventHandler<GenericEventArgs<User>> UserUpdated;
+
+ public event EventHandler<GenericEventArgs<User>> UserPolicyUpdated;
+
+ public event EventHandler<GenericEventArgs<User>> UserConfigurationUpdated;
+
+ public event EventHandler<GenericEventArgs<User>> UserLockedOut;
+
+ public event EventHandler<GenericEventArgs<User>> UserCreated;
+
+ /// <summary>
+ /// Occurs when [user deleted].
+ /// </summary>
+ public event EventHandler<GenericEventArgs<User>> UserDeleted;
+
+ /// <inheritdoc />
+ public IEnumerable<User> Users => _users.Values;
+
+ /// <inheritdoc />
+ public IEnumerable<Guid> UsersIds => _users.Keys;
+
+ /// <summary>
+ /// Called when [user updated].
+ /// </summary>
+ /// <param name="user">The user.</param>
+ private void OnUserUpdated(User user)
+ {
+ UserUpdated?.Invoke(this, new GenericEventArgs<User> { Argument = user });
+ }
+
+ /// <summary>
+ /// Called when [user deleted].
+ /// </summary>
+ /// <param name="user">The user.</param>
+ private void OnUserDeleted(User user)
+ {
+ UserDeleted?.Invoke(this, new GenericEventArgs<User> { Argument = user });
}
public NameIdPair[] GetAuthenticationProviders()
@@ -135,65 +165,34 @@ namespace Emby.Server.Implementations.Library
.ToArray();
}
- public void AddParts(IEnumerable<IAuthenticationProvider> authenticationProviders,IEnumerable<IPasswordResetProvider> passwordResetProviders)
+ public void AddParts(IEnumerable<IAuthenticationProvider> authenticationProviders, IEnumerable<IPasswordResetProvider> passwordResetProviders)
{
_authenticationProviders = authenticationProviders.ToArray();
_defaultAuthenticationProvider = _authenticationProviders.OfType<DefaultAuthenticationProvider>().First();
+ _invalidAuthProvider = _authenticationProviders.OfType<InvalidAuthProvider>().First();
+
_passwordResetProviders = passwordResetProviders.ToArray();
_defaultPasswordResetProvider = passwordResetProviders.OfType<DefaultPasswordResetProvider>().First();
}
- #region UserUpdated Event
- /// <summary>
- /// Occurs when [user updated].
- /// </summary>
- public event EventHandler<GenericEventArgs<User>> UserUpdated;
- public event EventHandler<GenericEventArgs<User>> UserPolicyUpdated;
- public event EventHandler<GenericEventArgs<User>> UserConfigurationUpdated;
- public event EventHandler<GenericEventArgs<User>> UserLockedOut;
-
- /// <summary>
- /// Called when [user updated].
- /// </summary>
- /// <param name="user">The user.</param>
- private void OnUserUpdated(User user)
- {
- UserUpdated?.Invoke(this, new GenericEventArgs<User> { Argument = user });
- }
- #endregion
-
- #region UserDeleted Event
/// <summary>
- /// Occurs when [user deleted].
- /// </summary>
- public event EventHandler<GenericEventArgs<User>> UserDeleted;
- /// <summary>
- /// Called when [user deleted].
- /// </summary>
- /// <param name="user">The user.</param>
- private void OnUserDeleted(User user)
- {
- UserDeleted?.Invoke(this, new GenericEventArgs<User> { Argument = user });
- }
- #endregion
-
- /// <summary>
- /// Gets a User by Id
+ /// Gets a User by Id.
/// </summary>
/// <param name="id">The id.</param>
/// <returns>User.</returns>
- /// <exception cref="ArgumentNullException"></exception>
+ /// <exception cref="ArgumentException"></exception>
public User GetUserById(Guid id)
{
if (id == Guid.Empty)
{
- throw new ArgumentException(nameof(id), "Guid can't be empty");
+ throw new ArgumentException("Guid can't be empty", nameof(id));
}
- return Users.FirstOrDefault(u => u.Id == id);
+ _users.TryGetValue(id, out User user);
+ return user;
}
/// <summary>
@@ -202,15 +201,13 @@ namespace Emby.Server.Implementations.Library
/// <param name="id">The identifier.</param>
/// <returns>User.</returns>
public User GetUserById(string id)
- {
- return GetUserById(new Guid(id));
- }
+ => GetUserById(new Guid(id));
public User GetUserByName(string name)
{
if (string.IsNullOrWhiteSpace(name))
{
- throw new ArgumentNullException(nameof(name));
+ throw new ArgumentException("Invalid username", nameof(name));
}
return Users.FirstOrDefault(u => string.Equals(u.Name, name, StringComparison.OrdinalIgnoreCase));
@@ -218,9 +215,9 @@ namespace Emby.Server.Implementations.Library
public void Initialize()
{
- _users = LoadUsers();
+ LoadUsers();
- var users = Users.ToList();
+ var users = Users;
// If there are no local users with admin rights, make them all admins
if (!users.Any(i => i.Policy.IsAdministrator))
@@ -237,14 +234,12 @@ namespace Emby.Server.Implementations.Library
{
// This is some regex that matches only on unicode "word" characters, as well as -, _ and @
// In theory this will cut out most if not all 'control' characters which should help minimize any weirdness
- // Usernames can contain letters (a-z + whatever else unicode is cool with), numbers (0-9), at-signs (@), dashes (-), underscores (_), apostrophes ('), and periods (.)
+ // Usernames can contain letters (a-z + whatever else unicode is cool with), numbers (0-9), at-signs (@), dashes (-), underscores (_), apostrophes ('), and periods (.)
return Regex.IsMatch(username, @"^[\w\-'._@]*$");
}
private static bool IsValidUsernameCharacter(char i)
- {
- return IsValidUsername(i.ToString());
- }
+ => IsValidUsername(i.ToString(CultureInfo.InvariantCulture));
public string MakeValidUsername(string username)
{
@@ -263,6 +258,7 @@ namespace Emby.Server.Implementations.Library
builder.Append(c);
}
}
+
return builder.ToString();
}
@@ -273,8 +269,7 @@ namespace Emby.Server.Implementations.Library
throw new ArgumentNullException(nameof(username));
}
- var user = Users
- .FirstOrDefault(i => string.Equals(username, i.Name, StringComparison.OrdinalIgnoreCase));
+ var user = Users.FirstOrDefault(i => string.Equals(username, i.Name, StringComparison.OrdinalIgnoreCase));
var success = false;
string updatedUsername = null;
@@ -283,32 +278,30 @@ namespace Emby.Server.Implementations.Library
if (user != null)
{
var authResult = await AuthenticateLocalUser(username, password, hashedPassword, user, remoteEndPoint).ConfigureAwait(false);
- authenticationProvider = authResult.Item1;
- updatedUsername = authResult.Item2;
- success = authResult.Item3;
+ authenticationProvider = authResult.authenticationProvider;
+ updatedUsername = authResult.username;
+ success = authResult.success;
}
else
{
// user is null
var authResult = await AuthenticateLocalUser(username, password, hashedPassword, null, remoteEndPoint).ConfigureAwait(false);
- authenticationProvider = authResult.Item1;
- updatedUsername = authResult.Item2;
- success = authResult.Item3;
+ authenticationProvider = authResult.authenticationProvider;
+ updatedUsername = authResult.username;
+ success = authResult.success;
- if (success && authenticationProvider != null && !(authenticationProvider is DefaultAuthenticationProvider))
+ if (success
+ && authenticationProvider != null
+ && !(authenticationProvider is DefaultAuthenticationProvider))
{
// We should trust the user that the authprovider says, not what was typed
- if (updatedUsername != username)
- {
- username = updatedUsername;
- }
+ username = updatedUsername;
// Search the database for the user again; the authprovider might have created it
user = Users
.FirstOrDefault(i => string.Equals(username, i.Name, StringComparison.OrdinalIgnoreCase));
- var hasNewUserPolicy = authenticationProvider as IHasNewUserPolicy;
- if (hasNewUserPolicy != null)
+ if (authenticationProvider is IHasNewUserPolicy hasNewUserPolicy)
{
var policy = hasNewUserPolicy.GetNewUserPolicy();
UpdateUserPolicy(user, policy, true);
@@ -329,22 +322,26 @@ namespace Emby.Server.Implementations.Library
if (user == null)
{
- throw new SecurityException("Invalid username or password entered.");
+ throw new AuthenticationException("Invalid username or password entered.");
}
if (user.Policy.IsDisabled)
{
- throw new SecurityException(string.Format("The {0} account is currently disabled. Please consult with your administrator.", user.Name));
+ throw new AuthenticationException(
+ string.Format(
+ CultureInfo.InvariantCulture,
+ "The {0} account is currently disabled. Please consult with your administrator.",
+ user.Name));
}
if (!user.Policy.EnableRemoteAccess && !_networkManager.IsInLocalNetwork(remoteEndPoint))
{
- throw new SecurityException("Forbidden.");
+ throw new AuthenticationException("Forbidden.");
}
if (!user.IsParentalScheduleAllowed())
{
- throw new SecurityException("User is not allowed access at this time.");
+ throw new AuthenticationException("User is not allowed access at this time.");
}
// Update LastActivityDate and LastLoginDate, then save
@@ -355,6 +352,7 @@ namespace Emby.Server.Implementations.Library
user.LastActivityDate = user.LastLoginDate = DateTime.UtcNow;
UpdateUser(user);
}
+
UpdateInvalidLoginAttemptCount(user, 0);
}
else
@@ -379,7 +377,7 @@ namespace Emby.Server.Implementations.Library
private IAuthenticationProvider GetAuthenticationProvider(User user)
{
- return GetAuthenticationProviders(user).First();
+ return GetAuthenticationProviders(user)[0];
}
private IPasswordResetProvider GetPasswordResetProvider(User user)
@@ -389,7 +387,7 @@ namespace Emby.Server.Implementations.Library
private IAuthenticationProvider[] GetAuthenticationProviders(User user)
{
- var authenticationProviderId = user == null ? null : user.Policy.AuthenticationProviderId;
+ var authenticationProviderId = user?.Policy.AuthenticationProviderId;
var providers = _authenticationProviders.Where(i => i.IsEnabled).ToArray();
@@ -400,7 +398,9 @@ namespace Emby.Server.Implementations.Library
if (providers.Length == 0)
{
- providers = new IAuthenticationProvider[] { _defaultAuthenticationProvider };
+ // Assign the user to the InvalidAuthProvider since no configured auth provider was valid/found
+ _logger.LogWarning("User {UserName} was found with invalid/missing Authentication Provider {AuthenticationProviderId}. Assigning user to InvalidAuthProvider until this is corrected", user.Name, user.Policy.AuthenticationProviderId);
+ providers = new IAuthenticationProvider[] { _invalidAuthProvider };
}
return providers;
@@ -425,40 +425,33 @@ namespace Emby.Server.Implementations.Library
return providers;
}
- private async Task<Tuple<string, bool>> AuthenticateWithProvider(IAuthenticationProvider provider, string username, string password, User resolvedUser)
+ private async Task<(string username, bool success)> AuthenticateWithProvider(IAuthenticationProvider provider, string username, string password, User resolvedUser)
{
try
{
- var requiresResolvedUser = provider as IRequiresResolvedUser;
- ProviderAuthenticationResult authenticationResult = null;
- if (requiresResolvedUser != null)
- {
- authenticationResult = await requiresResolvedUser.Authenticate(username, password, resolvedUser).ConfigureAwait(false);
- }
- else
- {
- authenticationResult = await provider.Authenticate(username, password).ConfigureAwait(false);
- }
- if(authenticationResult.Username != username)
+ var authenticationResult = provider is IRequiresResolvedUser requiresResolvedUser
+ ? await requiresResolvedUser.Authenticate(username, password, resolvedUser).ConfigureAwait(false)
+ : await provider.Authenticate(username, password).ConfigureAwait(false);
+
+ if (authenticationResult.Username != username)
{
_logger.LogDebug("Authentication provider provided updated username {1}", authenticationResult.Username);
username = authenticationResult.Username;
}
- return new Tuple<string, bool>(username, true);
+ return (username, true);
}
- catch (Exception ex)
+ catch (AuthenticationException ex)
{
- _logger.LogError(ex, "Error authenticating with provider {provider}", provider.Name);
+ _logger.LogError(ex, "Error authenticating with provider {Provider}", provider.Name);
- return new Tuple<string, bool>(username, false);
+ return (username, false);
}
}
- private async Task<Tuple<IAuthenticationProvider, string, bool>> AuthenticateLocalUser(string username, string password, string hashedPassword, User user, string remoteEndPoint)
+ private async Task<(IAuthenticationProvider authenticationProvider, string username, bool success)> AuthenticateLocalUser(string username, string password, string hashedPassword, User user, string remoteEndPoint)
{
- string updatedUsername = null;
bool success = false;
IAuthenticationProvider authenticationProvider = null;
@@ -471,15 +464,15 @@ namespace Emby.Server.Implementations.Library
if (password == null)
{
// legacy
- success = string.Equals(_defaultAuthenticationProvider.GetPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase);
+ success = string.Equals(user.Password, hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase);
}
else
{
foreach (var provider in GetAuthenticationProviders(user))
{
var providerAuthResult = await AuthenticateWithProvider(provider, username, password, user).ConfigureAwait(false);
- updatedUsername = providerAuthResult.Item1;
- success = providerAuthResult.Item2;
+ var updatedUsername = providerAuthResult.username;
+ success = providerAuthResult.success;
if (success)
{
@@ -490,23 +483,30 @@ namespace Emby.Server.Implementations.Library
}
}
- if (user != null)
+ if (user != null
+ && !success
+ && _networkManager.IsInLocalNetwork(remoteEndPoint)
+ && user.Configuration.EnableLocalPassword)
{
- if (!success && _networkManager.IsInLocalNetwork(remoteEndPoint) && user.Configuration.EnableLocalPassword)
+ if (password == null)
{
- if (password == null)
- {
- // legacy
- success = string.Equals(GetLocalPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase);
- }
- else
- {
- success = string.Equals(GetLocalPasswordHash(user), _defaultAuthenticationProvider.GetHashedString(user, password), StringComparison.OrdinalIgnoreCase);
- }
+ // legacy
+ success = string.Equals(GetLocalPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase);
+ }
+ else
+ {
+ success = string.Equals(GetLocalPasswordHash(user), _defaultAuthenticationProvider.GetHashedString(user, password), StringComparison.OrdinalIgnoreCase);
}
}
- return new Tuple<IAuthenticationProvider, string, bool>(authenticationProvider, username, success);
+ return (authenticationProvider, username, success);
+ }
+
+ private string GetLocalPasswordHash(User user)
+ {
+ return string.IsNullOrEmpty(user.EasyPassword)
+ ? null
+ : PasswordHash.ConvertToByteString(new PasswordHash(user.EasyPassword).Hash);
}
private void UpdateInvalidLoginAttemptCount(User user, int newValue)
@@ -546,46 +546,42 @@ namespace Emby.Server.Implementations.Library
}
}
- private string GetLocalPasswordHash(User user)
- {
- return string.IsNullOrEmpty(user.EasyPassword)
- ? null
- : (new PasswordHash(user.EasyPassword)).Hash;
- }
-
/// <summary>
- /// Loads the users from the repository
+ /// Loads the users from the repository.
/// </summary>
- /// <returns>IEnumerable{User}.</returns>
- private User[] LoadUsers()
+ private void LoadUsers()
{
- var users = UserRepository.RetrieveAllUsers();
+ var users = _userRepository.RetrieveAllUsers();
// There always has to be at least one user.
- if (users.Count == 0)
+ if (users.Count != 0)
{
- var defaultName = Environment.UserName;
- if (string.IsNullOrWhiteSpace(defaultName))
- {
- defaultName = "MyJellyfinUser";
- }
- var name = MakeValidUsername(defaultName);
+ _users = new ConcurrentDictionary<Guid, User>(
+ users.Select(x => new KeyValuePair<Guid, User>(x.Id, x)));
+ return;
+ }
+
+ var defaultName = Environment.UserName;
+ if (string.IsNullOrWhiteSpace(defaultName))
+ {
+ defaultName = "MyJellyfinUser";
+ }
- var user = InstantiateNewUser(name);
+ var name = MakeValidUsername(defaultName);
- user.DateLastSaved = DateTime.UtcNow;
+ var user = InstantiateNewUser(name);
- UserRepository.CreateUser(user);
+ user.DateLastSaved = DateTime.UtcNow;
- users.Add(user);
+ _userRepository.CreateUser(user);
- user.Policy.IsAdministrator = true;
- user.Policy.EnableContentDeletion = true;
- user.Policy.EnableRemoteControlOfOtherUsers = true;
- UpdateUserPolicy(user, user.Policy, false);
- }
+ user.Policy.IsAdministrator = true;
+ user.Policy.EnableContentDeletion = true;
+ user.Policy.EnableRemoteControlOfOtherUsers = true;
+ UpdateUserPolicy(user, user.Policy, false);
- return users.ToArray();
+ _users = new ConcurrentDictionary<Guid, User>();
+ _users[user.Id] = user;
}
public UserDto GetUserDto(User user, string remoteEndPoint = null)
@@ -595,8 +591,8 @@ namespace Emby.Server.Implementations.Library
throw new ArgumentNullException(nameof(user));
}
- bool hasConfiguredPassword = GetAuthenticationProvider(user).HasPassword(user).Result;
- bool hasConfiguredEasyPassword = !string.IsNullOrEmpty(GetLocalPasswordHash(user));
+ bool hasConfiguredPassword = GetAuthenticationProvider(user).HasPassword(user);
+ bool hasConfiguredEasyPassword = !string.IsNullOrEmpty(GetAuthenticationProvider(user).GetEasyPasswordHash(user));
bool hasPassword = user.Configuration.EnableLocalPassword && !string.IsNullOrEmpty(remoteEndPoint) && _networkManager.IsInLocalNetwork(remoteEndPoint) ?
hasConfiguredEasyPassword :
@@ -616,7 +612,7 @@ namespace Emby.Server.Implementations.Library
Policy = user.Policy
};
- if (!hasPassword && Users.Count() == 1)
+ if (!hasPassword && _users.Count == 1)
{
dto.EnableAutoLogin = true;
}
@@ -691,22 +687,26 @@ namespace Emby.Server.Implementations.Library
throw new ArgumentNullException(nameof(user));
}
- if (string.IsNullOrEmpty(newName))
+ if (string.IsNullOrWhiteSpace(newName))
{
- throw new ArgumentNullException(nameof(newName));
+ throw new ArgumentException("Invalid username", nameof(newName));
}
- if (Users.Any(u => u.Id != user.Id && u.Name.Equals(newName, StringComparison.OrdinalIgnoreCase)))
+ if (user.Name.Equals(newName, StringComparison.OrdinalIgnoreCase))
{
- throw new ArgumentException(string.Format("A user with the name '{0}' already exists.", newName));
+ throw new ArgumentException("The new and old names must be different.");
}
- if (user.Name.Equals(newName, StringComparison.Ordinal))
+ if (Users.Any(
+ u => u.Id != user.Id && u.Name.Equals(newName, StringComparison.OrdinalIgnoreCase)))
{
- throw new ArgumentException("The new and old names must be different.");
+ throw new ArgumentException(string.Format(
+ CultureInfo.InvariantCulture,
+ "A user with the name '{0}' already exists.",
+ newName));
}
- await user.Rename(newName);
+ await user.Rename(newName).ConfigureAwait(false);
OnUserUpdated(user);
}
@@ -724,23 +724,30 @@ namespace Emby.Server.Implementations.Library
throw new ArgumentNullException(nameof(user));
}
- if (user.Id.Equals(Guid.Empty) || !Users.Any(u => u.Id.Equals(user.Id)))
+ if (user.Id == Guid.Empty)
{
- throw new ArgumentException(string.Format("User with name '{0}' and Id {1} does not exist.", user.Name, user.Id));
+ throw new ArgumentException("Id can't be empty.", nameof(user));
+ }
+
+ if (!_users.ContainsKey(user.Id))
+ {
+ throw new ArgumentException(
+ string.Format(
+ CultureInfo.InvariantCulture,
+ "A user '{0}' with Id {1} does not exist.",
+ user.Name,
+ user.Id),
+ nameof(user));
}
user.DateModified = DateTime.UtcNow;
user.DateLastSaved = DateTime.UtcNow;
- UserRepository.UpdateUser(user);
+ _userRepository.UpdateUser(user);
OnUserUpdated(user);
}
- public event EventHandler<GenericEventArgs<User>> UserCreated;
-
- private readonly SemaphoreSlim _userListLock = new SemaphoreSlim(1, 1);
-
/// <summary>
/// Creates the user.
/// </summary>
@@ -748,7 +755,7 @@ namespace Emby.Server.Implementations.Library
/// <returns>User.</returns>
/// <exception cref="ArgumentNullException">name</exception>
/// <exception cref="ArgumentException"></exception>
- public async Task<User> CreateUser(string name)
+ public User CreateUser(string name)
{
if (string.IsNullOrWhiteSpace(name))
{
@@ -765,28 +772,17 @@ namespace Emby.Server.Implementations.Library
throw new ArgumentException(string.Format("A user with the name '{0}' already exists.", name));
}
- await _userListLock.WaitAsync(CancellationToken.None).ConfigureAwait(false);
-
- try
- {
- var user = InstantiateNewUser(name);
+ var user = InstantiateNewUser(name);
- var list = Users.ToList();
- list.Add(user);
- _users = list.ToArray();
+ _users[user.Id] = user;
- user.DateLastSaved = DateTime.UtcNow;
+ user.DateLastSaved = DateTime.UtcNow;
- UserRepository.CreateUser(user);
+ _userRepository.CreateUser(user);
- EventHelper.QueueEventIfNotNull(UserCreated, this, new GenericEventArgs<User> { Argument = user }, _logger);
+ EventHelper.QueueEventIfNotNull(UserCreated, this, new GenericEventArgs<User> { Argument = user }, _logger);
- return user;
- }
- finally
- {
- _userListLock.Release();
- }
+ return user;
}
/// <summary>
@@ -796,57 +792,59 @@ namespace Emby.Server.Implementations.Library
/// <returns>Task.</returns>
/// <exception cref="ArgumentNullException">user</exception>
/// <exception cref="ArgumentException"></exception>
- public async Task DeleteUser(User user)
+ public void DeleteUser(User user)
{
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
- var allUsers = Users.ToList();
-
- if (allUsers.FirstOrDefault(u => u.Id == user.Id) == null)
+ if (!_users.ContainsKey(user.Id))
{
- throw new ArgumentException(string.Format("The user cannot be deleted because there is no user with the Name {0} and Id {1}.", user.Name, user.Id));
+ throw new ArgumentException(string.Format(
+ CultureInfo.InvariantCulture,
+ "The user cannot be deleted because there is no user with the Name {0} and Id {1}.",
+ user.Name,
+ user.Id));
}
- if (allUsers.Count == 1)
+ if (_users.Count == 1)
{
- throw new ArgumentException(string.Format("The user '{0}' cannot be deleted because there must be at least one user in the system.", user.Name));
+ throw new ArgumentException(string.Format(
+ CultureInfo.InvariantCulture,
+ "The user '{0}' cannot be deleted because there must be at least one user in the system.",
+ user.Name));
}
- if (user.Policy.IsAdministrator && allUsers.Count(i => i.Policy.IsAdministrator) == 1)
+ if (user.Policy.IsAdministrator
+ && Users.Count(i => i.Policy.IsAdministrator) == 1)
{
- throw new ArgumentException(string.Format("The user '{0}' cannot be deleted because there must be at least one admin user in the system.", user.Name));
+ throw new ArgumentException(
+ string.Format(
+ CultureInfo.InvariantCulture,
+ "The user '{0}' cannot be deleted because there must be at least one admin user in the system.",
+ user.Name),
+ nameof(user));
}
- await _userListLock.WaitAsync(CancellationToken.None).ConfigureAwait(false);
+ var configPath = GetConfigurationFilePath(user);
+
+ _userRepository.DeleteUser(user);
try
{
- var configPath = GetConfigurationFilePath(user);
-
- UserRepository.DeleteUser(user);
-
- try
- {
- _fileSystem.DeleteFile(configPath);
- }
- catch (IOException ex)
- {
- _logger.LogError(ex, "Error deleting file {path}", configPath);
- }
-
- DeleteUserPolicy(user);
-
- _users = allUsers.Where(i => i.Id != user.Id).ToArray();
-
- OnUserDeleted(user);
+ _fileSystem.DeleteFile(configPath);
}
- finally
+ catch (IOException ex)
{
- _userListLock.Release();
+ _logger.LogError(ex, "Error deleting file {path}", configPath);
}
+
+ DeleteUserPolicy(user);
+
+ _users.TryRemove(user.Id, out _);
+
+ OnUserDeleted(user);
}
/// <summary>
@@ -884,17 +882,7 @@ namespace Emby.Server.Implementations.Library
throw new ArgumentNullException(nameof(user));
}
- if (newPassword != null)
- {
- newPasswordHash = _defaultAuthenticationProvider.GetHashedString(user, newPassword);
- }
-
- if (string.IsNullOrWhiteSpace(newPasswordHash))
- {
- throw new ArgumentNullException(nameof(newPasswordHash));
- }
-
- user.EasyPassword = newPasswordHash;
+ GetAuthenticationProvider(user).ChangeEasyPassword(user, newPassword, newPasswordHash);
UpdateUser(user);
@@ -913,8 +901,7 @@ namespace Emby.Server.Implementations.Library
Name = name,
Id = Guid.NewGuid(),
DateCreated = DateTime.UtcNow,
- DateModified = DateTime.UtcNow,
- UsesIdForConfigurationPath = true
+ DateModified = DateTime.UtcNow
};
}
@@ -996,7 +983,6 @@ namespace Emby.Server.Implementations.Library
};
}
- private readonly object _policySyncLock = new object();
public void UpdateUserPolicy(Guid userId, UserPolicy userPolicy)
{
var user = GetUserById(userId);