aboutsummaryrefslogtreecommitdiff
path: root/Emby.Server.Implementations/Library
diff options
context:
space:
mode:
authorLogicalPhallacy <44458166+LogicalPhallacy@users.noreply.github.com>2019-10-28 21:54:40 -0700
committerGitHub <noreply@github.com>2019-10-28 21:54:40 -0700
commit8edb1c49d8d1835566bd30d8bf5460ab707b1ede (patch)
tree03c6b38523efcc4f29691cea3cdc4def0e8d26d8 /Emby.Server.Implementations/Library
parent984e415c66cbd995d12ea95a3a9d3e2561ce4869 (diff)
parentc9f4a74af02e08b895cd6a8b8a408b1c0edfb6c4 (diff)
Merge pull request #6 from jellyfin/master
Bringing my branch up to sync
Diffstat (limited to 'Emby.Server.Implementations/Library')
-rw-r--r--Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs1
-rw-r--r--Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs178
-rw-r--r--Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs263
-rw-r--r--Emby.Server.Implementations/Library/ExclusiveLiveStream.cs3
-rw-r--r--Emby.Server.Implementations/Library/InvalidAuthProvider.cs11
-rw-r--r--Emby.Server.Implementations/Library/LibraryManager.cs35
-rw-r--r--Emby.Server.Implementations/Library/LiveStreamHelper.cs2
-rw-r--r--Emby.Server.Implementations/Library/MediaSourceManager.cs6
-rw-r--r--Emby.Server.Implementations/Library/UserDataManager.cs2
-rw-r--r--Emby.Server.Implementations/Library/UserManager.cs473
-rw-r--r--Emby.Server.Implementations/Library/UserViewManager.cs39
-rw-r--r--Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs3
-rw-r--r--Emby.Server.Implementations/Library/Validators/PeopleValidator.cs4
-rw-r--r--Emby.Server.Implementations/Library/Validators/StudiosValidator.cs3
14 files changed, 468 insertions, 555 deletions
diff --git a/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs b/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs
index f1ae2fc9c..8bdb38784 100644
--- a/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs
+++ b/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs
@@ -57,7 +57,6 @@ namespace Emby.Server.Implementations.Library
}
var filename = fileInfo.Name;
- var path = fileInfo.FullName;
// Ignore hidden files on UNIX
if (Environment.OSVersion.Platform != PlatformID.Win32NT
diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs
index fe09b07ff..c95b00ede 100644
--- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs
+++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs
@@ -2,24 +2,30 @@ using System;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
+using MediaBrowser.Common.Cryptography;
using MediaBrowser.Controller.Authentication;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Cryptography;
+using static MediaBrowser.Common.HexHelper;
namespace Emby.Server.Implementations.Library
{
public class DefaultAuthenticationProvider : IAuthenticationProvider, IRequiresResolvedUser
{
private readonly ICryptoProvider _cryptographyProvider;
- public DefaultAuthenticationProvider(ICryptoProvider crypto)
+
+ public DefaultAuthenticationProvider(ICryptoProvider cryptographyProvider)
{
- _cryptographyProvider = crypto;
+ _cryptographyProvider = cryptographyProvider;
}
+ /// <inheritdoc />
public string Name => "Default";
+ /// <inheritdoc />
public bool IsEnabled => true;
+ /// <inheritdoc />
// This is dumb and an artifact of the backwards way auth providers were designed.
// This version of authenticate was never meant to be called, but needs to be here for interface compat
// Only the providers that don't provide local user support use this
@@ -28,17 +34,18 @@ namespace Emby.Server.Implementations.Library
throw new NotImplementedException();
}
- // This is the verson that we need to use for local users. Because reasons.
+ /// <inheritdoc />
+ // This is the version that we need to use for local users. Because reasons.
public Task<ProviderAuthenticationResult> Authenticate(string username, string password, User resolvedUser)
{
bool success = false;
if (resolvedUser == null)
{
- throw new Exception("Invalid username or password");
+ throw new ArgumentNullException(nameof(resolvedUser));
}
- // As long as jellyfin supports passwordless users, we need this little block here to accomodate
- if (IsPasswordEmpty(resolvedUser, password))
+ // As long as jellyfin supports passwordless users, we need this little block here to accommodate
+ if (!HasPassword(resolvedUser) && string.IsNullOrEmpty(password))
{
return Task.FromResult(new ProviderAuthenticationResult
{
@@ -46,41 +53,27 @@ namespace Emby.Server.Implementations.Library
});
}
- ConvertPasswordFormat(resolvedUser);
byte[] passwordbytes = Encoding.UTF8.GetBytes(password);
- PasswordHash readyHash = new PasswordHash(resolvedUser.Password);
- byte[] calculatedHash;
- string calculatedHashString;
- if (_cryptographyProvider.GetSupportedHashMethods().Contains(readyHash.Id) || _cryptographyProvider.DefaultHashMethod == readyHash.Id)
+ PasswordHash readyHash = PasswordHash.Parse(resolvedUser.Password);
+ if (_cryptographyProvider.GetSupportedHashMethods().Contains(readyHash.Id)
+ || _cryptographyProvider.DefaultHashMethod == readyHash.Id)
{
- if (string.IsNullOrEmpty(readyHash.Salt))
- {
- calculatedHash = _cryptographyProvider.ComputeHash(readyHash.Id, passwordbytes);
- calculatedHashString = BitConverter.ToString(calculatedHash).Replace("-", string.Empty);
- }
- else
- {
- calculatedHash = _cryptographyProvider.ComputeHash(readyHash.Id, passwordbytes, readyHash.SaltBytes);
- calculatedHashString = BitConverter.ToString(calculatedHash).Replace("-", string.Empty);
- }
+ byte[] calculatedHash = _cryptographyProvider.ComputeHash(readyHash.Id, passwordbytes, readyHash.Salt);
- if (calculatedHashString == readyHash.Hash)
+ if (calculatedHash.SequenceEqual(readyHash.Hash))
{
success = true;
- // throw new Exception("Invalid username or password");
}
}
else
{
- throw new Exception(string.Format($"Requested crypto method not available in provider: {readyHash.Id}"));
+ throw new AuthenticationException($"Requested crypto method not available in provider: {readyHash.Id}");
}
- // var success = string.Equals(GetPasswordHash(resolvedUser), GetHashedString(resolvedUser, password), StringComparison.OrdinalIgnoreCase);
-
if (!success)
{
- throw new Exception("Invalid username or password");
+ throw new AuthenticationException("Invalid username or password");
}
return Task.FromResult(new ProviderAuthenticationResult
@@ -89,89 +82,31 @@ namespace Emby.Server.Implementations.Library
});
}
- // This allows us to move passwords forward to the newformat without breaking. They are still insecure, unsalted, and dumb before a password change
- // but at least they are in the new format.
- private void ConvertPasswordFormat(User user)
- {
- if (string.IsNullOrEmpty(user.Password))
- {
- return;
- }
-
- if (!user.Password.Contains("$"))
- {
- string hash = user.Password;
- user.Password = string.Format("$SHA1${0}", hash);
- }
-
- if (user.EasyPassword != null && !user.EasyPassword.Contains("$"))
- {
- string hash = user.EasyPassword;
- user.EasyPassword = string.Format("$SHA1${0}", hash);
- }
- }
-
- public Task<bool> HasPassword(User user)
- {
- var hasConfiguredPassword = !IsPasswordEmpty(user, GetPasswordHash(user));
- return Task.FromResult(hasConfiguredPassword);
- }
-
- private bool IsPasswordEmpty(User user, string password)
- {
- return (string.IsNullOrEmpty(user.Password) && string.IsNullOrEmpty(password));
- }
+ /// <inheritdoc />
+ public bool HasPassword(User user)
+ => !string.IsNullOrEmpty(user.Password);
+ /// <inheritdoc />
public Task ChangePassword(User user, string newPassword)
{
- ConvertPasswordFormat(user);
- // This is needed to support changing a no password user to a password user
- if (string.IsNullOrEmpty(user.Password))
+ if (string.IsNullOrEmpty(newPassword))
{
- PasswordHash newPasswordHash = new PasswordHash(_cryptographyProvider);
- newPasswordHash.SaltBytes = _cryptographyProvider.GenerateSalt();
- newPasswordHash.Salt = PasswordHash.ConvertToByteString(newPasswordHash.SaltBytes);
- newPasswordHash.Id = _cryptographyProvider.DefaultHashMethod;
- newPasswordHash.Hash = GetHashedStringChangeAuth(newPassword, newPasswordHash);
- user.Password = newPasswordHash.ToString();
+ user.Password = null;
return Task.CompletedTask;
}
- PasswordHash passwordHash = new PasswordHash(user.Password);
- if (passwordHash.Id == "SHA1" && string.IsNullOrEmpty(passwordHash.Salt))
- {
- passwordHash.SaltBytes = _cryptographyProvider.GenerateSalt();
- passwordHash.Salt = PasswordHash.ConvertToByteString(passwordHash.SaltBytes);
- passwordHash.Id = _cryptographyProvider.DefaultHashMethod;
- passwordHash.Hash = GetHashedStringChangeAuth(newPassword, passwordHash);
- }
- else if (newPassword != null)
- {
- passwordHash.Hash = GetHashedString(user, newPassword);
- }
-
- if (string.IsNullOrWhiteSpace(passwordHash.Hash))
- {
- throw new ArgumentNullException(nameof(passwordHash.Hash));
- }
-
- user.Password = passwordHash.ToString();
+ PasswordHash newPasswordHash = _cryptographyProvider.CreatePasswordHash(newPassword);
+ user.Password = newPasswordHash.ToString();
return Task.CompletedTask;
}
- public string GetPasswordHash(User user)
- {
- return user.Password;
- }
-
+ /// <inheritdoc />
public void ChangeEasyPassword(User user, string newPassword, string newPasswordHash)
{
- ConvertPasswordFormat(user);
-
if (newPassword != null)
{
- newPasswordHash = string.Format("$SHA1${0}", GetHashedString(user, newPassword));
+ newPasswordHash = _cryptographyProvider.CreatePasswordHash(newPassword).ToString();
}
if (string.IsNullOrWhiteSpace(newPasswordHash))
@@ -182,21 +117,12 @@ namespace Emby.Server.Implementations.Library
user.EasyPassword = newPasswordHash;
}
+ /// <inheritdoc />
public string GetEasyPasswordHash(User user)
{
- // This should be removed in the future. This was added to let user login after
- // Jellyfin 10.3.3 failed to save a well formatted PIN.
- ConvertPasswordFormat(user);
-
return string.IsNullOrEmpty(user.EasyPassword)
? null
- : (new PasswordHash(user.EasyPassword)).Hash;
- }
-
- public string GetHashedStringChangeAuth(string newPassword, PasswordHash passwordHash)
- {
- passwordHash.HashBytes = Encoding.UTF8.GetBytes(newPassword);
- return PasswordHash.ConvertToByteString(_cryptographyProvider.ComputeHash(passwordHash));
+ : ToHexString(PasswordHash.Parse(user.EasyPassword).Hash);
}
/// <summary>
@@ -204,28 +130,36 @@ namespace Emby.Server.Implementations.Library
/// </summary>
public string GetHashedString(User user, string str)
{
- PasswordHash passwordHash;
if (string.IsNullOrEmpty(user.Password))
{
- passwordHash = new PasswordHash(_cryptographyProvider);
- }
- else
- {
- ConvertPasswordFormat(user);
- passwordHash = new PasswordHash(user.Password);
+ return _cryptographyProvider.CreatePasswordHash(str).ToString();
}
- if (passwordHash.SaltBytes != null)
- {
- // the password is modern format with PBKDF and we should take advantage of that
- passwordHash.HashBytes = Encoding.UTF8.GetBytes(str);
- return PasswordHash.ConvertToByteString(_cryptographyProvider.ComputeHash(passwordHash));
- }
- else
+ // TODO: make use of iterations parameter?
+ PasswordHash passwordHash = PasswordHash.Parse(user.Password);
+ return new PasswordHash(
+ passwordHash.Id,
+ _cryptographyProvider.ComputeHash(
+ passwordHash.Id,
+ Encoding.UTF8.GetBytes(str),
+ passwordHash.Salt),
+ passwordHash.Salt,
+ passwordHash.Parameters.ToDictionary(x => x.Key, y => y.Value)).ToString();
+ }
+
+ public byte[] GetHashed(User user, string str)
+ {
+ if (string.IsNullOrEmpty(user.Password))
{
- // the password has no salt and should be called with the older method for safety
- return PasswordHash.ConvertToByteString(_cryptographyProvider.ComputeHash(passwordHash.Id, Encoding.UTF8.GetBytes(str)));
+ return _cryptographyProvider.CreatePasswordHash(str).Hash;
}
+
+ // TODO: make use of iterations parameter?
+ PasswordHash passwordHash = PasswordHash.Parse(user.Password);
+ return _cryptographyProvider.ComputeHash(
+ passwordHash.Id,
+ Encoding.UTF8.GetBytes(str),
+ passwordHash.Salt);
}
}
}
diff --git a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs
index e218749d9..fa6bbcf91 100644
--- a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs
+++ b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs
@@ -1,132 +1,131 @@
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using MediaBrowser.Common.Extensions;
-using MediaBrowser.Controller.Authentication;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Model.Cryptography;
-using MediaBrowser.Model.Serialization;
-using MediaBrowser.Model.Users;
-
-namespace Emby.Server.Implementations.Library
-{
- public class DefaultPasswordResetProvider : IPasswordResetProvider
- {
- public string Name => "Default Password Reset Provider";
-
- public bool IsEnabled => true;
-
- private readonly string _passwordResetFileBase;
- private readonly string _passwordResetFileBaseDir;
- private readonly string _passwordResetFileBaseName = "passwordreset";
-
- private readonly IJsonSerializer _jsonSerializer;
- private readonly IUserManager _userManager;
- private readonly ICryptoProvider _crypto;
-
- public DefaultPasswordResetProvider(IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IUserManager userManager, ICryptoProvider cryptoProvider)
- {
- _passwordResetFileBaseDir = configurationManager.ApplicationPaths.ProgramDataPath;
- _passwordResetFileBase = Path.Combine(_passwordResetFileBaseDir, _passwordResetFileBaseName);
- _jsonSerializer = jsonSerializer;
- _userManager = userManager;
- _crypto = cryptoProvider;
- }
-
- public async Task<PinRedeemResult> RedeemPasswordResetPin(string pin)
- {
- SerializablePasswordReset spr;
- HashSet<string> usersreset = new HashSet<string>();
- foreach (var resetfile in Directory.EnumerateFiles(_passwordResetFileBaseDir, $"{_passwordResetFileBaseName}*"))
- {
- using (var str = File.OpenRead(resetfile))
- {
- spr = await _jsonSerializer.DeserializeFromStreamAsync<SerializablePasswordReset>(str).ConfigureAwait(false);
- }
-
- if (spr.ExpirationDate < DateTime.Now)
- {
- File.Delete(resetfile);
- }
- else if (spr.Pin.Replace("-", "").Equals(pin.Replace("-", ""), StringComparison.InvariantCultureIgnoreCase))
- {
- var resetUser = _userManager.GetUserByName(spr.UserName);
- if (resetUser == null)
- {
- throw new Exception($"User with a username of {spr.UserName} not found");
- }
-
- await _userManager.ChangePassword(resetUser, pin).ConfigureAwait(false);
- usersreset.Add(resetUser.Name);
- File.Delete(resetfile);
- }
- }
-
- if (usersreset.Count < 1)
- {
- throw new ResourceNotFoundException($"No Users found with a password reset request matching pin {pin}");
- }
- else
- {
- return new PinRedeemResult
- {
- Success = true,
- UsersReset = usersreset.ToArray()
- };
- }
- }
-
- public async Task<ForgotPasswordResult> StartForgotPasswordProcess(MediaBrowser.Controller.Entities.User user, bool isInNetwork)
- {
- string pin = string.Empty;
- using (var cryptoRandom = System.Security.Cryptography.RandomNumberGenerator.Create())
- {
- byte[] bytes = new byte[4];
- cryptoRandom.GetBytes(bytes);
- pin = BitConverter.ToString(bytes);
- }
-
- DateTime expireTime = DateTime.Now.AddMinutes(30);
- string filePath = _passwordResetFileBase + user.InternalId + ".json";
- SerializablePasswordReset spr = new SerializablePasswordReset
- {
- ExpirationDate = expireTime,
- Pin = pin,
- PinFile = filePath,
- UserName = user.Name
- };
-
- try
- {
- using (FileStream fileStream = File.OpenWrite(filePath))
- {
- _jsonSerializer.SerializeToStream(spr, fileStream);
- await fileStream.FlushAsync().ConfigureAwait(false);
- }
- }
- catch (Exception e)
- {
- throw new Exception($"Error serializing or writing password reset for {user.Name} to location: {filePath}", e);
- }
-
- return new ForgotPasswordResult
- {
- Action = ForgotPasswordAction.PinCode,
- PinExpirationDate = expireTime,
- PinFile = filePath
- };
- }
-
- private class SerializablePasswordReset : PasswordPinCreationResult
- {
- public string Pin { get; set; }
-
- public string UserName { get; set; }
- }
- }
-}
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Security.Cryptography;
+using System.Threading.Tasks;
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Controller.Authentication;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Model.Serialization;
+using MediaBrowser.Model.Users;
+
+namespace Emby.Server.Implementations.Library
+{
+ public class DefaultPasswordResetProvider : IPasswordResetProvider
+ {
+ private const string BaseResetFileName = "passwordreset";
+
+ private readonly IJsonSerializer _jsonSerializer;
+ private readonly IUserManager _userManager;
+
+ private readonly string _passwordResetFileBase;
+ private readonly string _passwordResetFileBaseDir;
+
+ public DefaultPasswordResetProvider(
+ IServerConfigurationManager configurationManager,
+ IJsonSerializer jsonSerializer,
+ IUserManager userManager)
+ {
+ _passwordResetFileBaseDir = configurationManager.ApplicationPaths.ProgramDataPath;
+ _passwordResetFileBase = Path.Combine(_passwordResetFileBaseDir, BaseResetFileName);
+ _jsonSerializer = jsonSerializer;
+ _userManager = userManager;
+ }
+
+ /// <inheritdoc />
+ public string Name => "Default Password Reset Provider";
+
+ /// <inheritdoc />
+ public bool IsEnabled => true;
+
+ /// <inheritdoc />
+ public async Task<PinRedeemResult> RedeemPasswordResetPin(string pin)
+ {
+ SerializablePasswordReset spr;
+ List<string> usersreset = new List<string>();
+ foreach (var resetfile in Directory.EnumerateFiles(_passwordResetFileBaseDir, $"{BaseResetFileName}*"))
+ {
+ using (var str = File.OpenRead(resetfile))
+ {
+ spr = await _jsonSerializer.DeserializeFromStreamAsync<SerializablePasswordReset>(str).ConfigureAwait(false);
+ }
+
+ if (spr.ExpirationDate < DateTime.Now)
+ {
+ File.Delete(resetfile);
+ }
+ else if (string.Equals(
+ spr.Pin.Replace("-", string.Empty),
+ pin.Replace("-", string.Empty),
+ StringComparison.InvariantCultureIgnoreCase))
+ {
+ var resetUser = _userManager.GetUserByName(spr.UserName);
+ if (resetUser == null)
+ {
+ throw new ResourceNotFoundException($"User with a username of {spr.UserName} not found");
+ }
+
+ await _userManager.ChangePassword(resetUser, pin).ConfigureAwait(false);
+ usersreset.Add(resetUser.Name);
+ File.Delete(resetfile);
+ }
+ }
+
+ if (usersreset.Count < 1)
+ {
+ throw new ResourceNotFoundException($"No Users found with a password reset request matching pin {pin}");
+ }
+ else
+ {
+ return new PinRedeemResult
+ {
+ Success = true,
+ UsersReset = usersreset.ToArray()
+ };
+ }
+ }
+
+ /// <inheritdoc />
+ public async Task<ForgotPasswordResult> StartForgotPasswordProcess(MediaBrowser.Controller.Entities.User user, bool isInNetwork)
+ {
+ string pin = string.Empty;
+ using (var cryptoRandom = RandomNumberGenerator.Create())
+ {
+ byte[] bytes = new byte[4];
+ cryptoRandom.GetBytes(bytes);
+ pin = BitConverter.ToString(bytes);
+ }
+
+ DateTime expireTime = DateTime.Now.AddMinutes(30);
+ string filePath = _passwordResetFileBase + user.InternalId + ".json";
+ SerializablePasswordReset spr = new SerializablePasswordReset
+ {
+ ExpirationDate = expireTime,
+ Pin = pin,
+ PinFile = filePath,
+ UserName = user.Name
+ };
+
+ using (FileStream fileStream = File.OpenWrite(filePath))
+ {
+ _jsonSerializer.SerializeToStream(spr, fileStream);
+ await fileStream.FlushAsync().ConfigureAwait(false);
+ }
+
+ return new ForgotPasswordResult
+ {
+ Action = ForgotPasswordAction.PinCode,
+ PinExpirationDate = expireTime,
+ PinFile = filePath
+ };
+ }
+
+ private class SerializablePasswordReset : PasswordPinCreationResult
+ {
+ public string Pin { get; set; }
+
+ public string UserName { get; set; }
+ }
+ }
+}
diff --git a/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs b/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs
index 45a33a296..a3c879f12 100644
--- a/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs
+++ b/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs
@@ -1,4 +1,5 @@
using System;
+using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Library;
@@ -26,7 +27,7 @@ namespace Emby.Server.Implementations.Library
EnableStreamSharing = false;
_closeFn = closeFn;
ConsumerCount = 1;
- UniqueId = Guid.NewGuid().ToString("N");
+ UniqueId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
}
public Task Close()
diff --git a/Emby.Server.Implementations/Library/InvalidAuthProvider.cs b/Emby.Server.Implementations/Library/InvalidAuthProvider.cs
index 25d233137..6956369dc 100644
--- a/Emby.Server.Implementations/Library/InvalidAuthProvider.cs
+++ b/Emby.Server.Implementations/Library/InvalidAuthProvider.cs
@@ -1,6 +1,3 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
using System.Threading.Tasks;
using MediaBrowser.Controller.Authentication;
using MediaBrowser.Controller.Entities;
@@ -16,12 +13,12 @@ namespace Emby.Server.Implementations.Library
public Task<ProviderAuthenticationResult> Authenticate(string username, string password)
{
- throw new SecurityException("User Account cannot login with this provider. The Normal provider for this user cannot be found");
+ throw new AuthenticationException("User Account cannot login with this provider. The Normal provider for this user cannot be found");
}
- public Task<bool> HasPassword(User user)
+ public bool HasPassword(User user)
{
- return Task.FromResult(true);
+ return true;
}
public Task ChangePassword(User user, string newPassword)
@@ -31,7 +28,7 @@ namespace Emby.Server.Implementations.Library
public void ChangeEasyPassword(User user, string newPassword, string newPasswordHash)
{
- // Nothing here
+ // Nothing here
}
public string GetPasswordHash(User user)
diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs
index 4b5063ada..87e951f25 100644
--- a/Emby.Server.Implementations/Library/LibraryManager.cs
+++ b/Emby.Server.Implementations/Library/LibraryManager.cs
@@ -779,12 +779,23 @@ namespace Emby.Server.Implementations.Library
{
var userRootPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath;
+ _logger.LogDebug("Creating userRootPath at {path}", userRootPath);
Directory.CreateDirectory(userRootPath);
- var tmpItem = GetItemById(GetNewItemId(userRootPath, typeof(UserRootFolder))) as UserRootFolder;
+ var newItemId = GetNewItemId(userRootPath, typeof(UserRootFolder));
+ UserRootFolder tmpItem = null;
+ try
+ {
+ tmpItem = GetItemById(newItemId) as UserRootFolder;
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error creating UserRootFolder {path}", newItemId);
+ }
if (tmpItem == null)
{
+ _logger.LogDebug("Creating new userRootFolder with DeepCopy");
tmpItem = ((Folder)ResolvePath(_fileSystem.GetDirectoryInfo(userRootPath))).DeepCopy<Folder, UserRootFolder>();
}
@@ -796,6 +807,7 @@ namespace Emby.Server.Implementations.Library
}
_userRootFolder = tmpItem;
+ _logger.LogDebug("Setting userRootFolder: {folder}", _userRootFolder);
}
}
}
@@ -1146,8 +1158,10 @@ namespace Emby.Server.Implementations.Library
public List<VirtualFolderInfo> GetVirtualFolders(bool includeRefreshState)
{
+ _logger.LogDebug("Getting topLibraryFolders");
var topLibraryFolders = GetUserRootFolder().Children.ToList();
+ _logger.LogDebug("Getting refreshQueue");
var refreshQueue = includeRefreshState ? _providerManagerFactory().GetRefreshQueue() : null;
return _fileSystem.GetDirectoryPaths(ConfigurationManager.ApplicationPaths.DefaultUserViewsPath)
@@ -1187,12 +1201,12 @@ namespace Emby.Server.Implementations.Library
if (libraryFolder != null && libraryFolder.HasImage(ImageType.Primary))
{
- info.PrimaryImageItemId = libraryFolder.Id.ToString("N");
+ info.PrimaryImageItemId = libraryFolder.Id.ToString("N", CultureInfo.InvariantCulture);
}
if (libraryFolder != null)
{
- info.ItemId = libraryFolder.Id.ToString("N");
+ info.ItemId = libraryFolder.Id.ToString("N", CultureInfo.InvariantCulture);
info.LibraryOptions = GetLibraryOptions(libraryFolder);
if (refreshQueue != null)
@@ -1441,7 +1455,7 @@ namespace Emby.Server.Implementations.Library
return new QueryResult<BaseItem>
{
- Items = list.ToArray()
+ Items = list
};
}
@@ -1977,8 +1991,7 @@ namespace Emby.Server.Implementations.Library
public LibraryOptions GetLibraryOptions(BaseItem item)
{
- var collectionFolder = item as CollectionFolder;
- if (collectionFolder == null)
+ if (!(item is CollectionFolder collectionFolder))
{
collectionFolder = GetCollectionFolders(item)
.OfType<CollectionFolder>()
@@ -2135,12 +2148,12 @@ namespace Emby.Server.Implementations.Library
string viewType,
string sortName)
{
- var parentIdString = parentId.Equals(Guid.Empty) ? null : parentId.ToString("N");
- var idValues = "38_namedview_" + name + user.Id.ToString("N") + (parentIdString ?? string.Empty) + (viewType ?? string.Empty);
+ var parentIdString = parentId.Equals(Guid.Empty) ? null : parentId.ToString("N", CultureInfo.InvariantCulture);
+ var idValues = "38_namedview_" + name + user.Id.ToString("N", CultureInfo.InvariantCulture) + (parentIdString ?? string.Empty) + (viewType ?? string.Empty);
var id = GetNewItemId(idValues, typeof(UserView));
- var path = Path.Combine(ConfigurationManager.ApplicationPaths.InternalMetadataPath, "views", id.ToString("N"));
+ var path = Path.Combine(ConfigurationManager.ApplicationPaths.InternalMetadataPath, "views", id.ToString("N", CultureInfo.InvariantCulture));
var item = GetItemById(id) as UserView;
@@ -2271,7 +2284,7 @@ namespace Emby.Server.Implementations.Library
throw new ArgumentNullException(nameof(name));
}
- var parentIdString = parentId.Equals(Guid.Empty) ? null : parentId.ToString("N");
+ var parentIdString = parentId.Equals(Guid.Empty) ? null : parentId.ToString("N", CultureInfo.InvariantCulture);
var idValues = "37_namedview_" + name + (parentIdString ?? string.Empty) + (viewType ?? string.Empty);
if (!string.IsNullOrEmpty(uniqueId))
{
@@ -2280,7 +2293,7 @@ namespace Emby.Server.Implementations.Library
var id = GetNewItemId(idValues, typeof(UserView));
- var path = Path.Combine(ConfigurationManager.ApplicationPaths.InternalMetadataPath, "views", id.ToString("N"));
+ var path = Path.Combine(ConfigurationManager.ApplicationPaths.InternalMetadataPath, "views", id.ToString("N", CultureInfo.InvariantCulture));
var item = GetItemById(id) as UserView;
diff --git a/Emby.Server.Implementations/Library/LiveStreamHelper.cs b/Emby.Server.Implementations/Library/LiveStreamHelper.cs
index c3082a78a..33e6f2434 100644
--- a/Emby.Server.Implementations/Library/LiveStreamHelper.cs
+++ b/Emby.Server.Implementations/Library/LiveStreamHelper.cs
@@ -40,7 +40,7 @@ namespace Emby.Server.Implementations.Library
var now = DateTime.UtcNow;
MediaInfo mediaInfo = null;
- var cacheFilePath = string.IsNullOrEmpty(cacheKey) ? null : Path.Combine(_appPaths.CachePath, "mediainfo", cacheKey.GetMD5().ToString("N") + ".json");
+ var cacheFilePath = string.IsNullOrEmpty(cacheKey) ? null : Path.Combine(_appPaths.CachePath, "mediainfo", cacheKey.GetMD5().ToString("N", CultureInfo.InvariantCulture) + ".json");
if (!string.IsNullOrEmpty(cacheKey))
{
diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs
index 24ab8e761..d83e1fc02 100644
--- a/Emby.Server.Implementations/Library/MediaSourceManager.cs
+++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs
@@ -269,7 +269,7 @@ namespace Emby.Server.Implementations.Library
private static void SetKeyProperties(IMediaSourceProvider provider, MediaSourceInfo mediaSource)
{
- var prefix = provider.GetType().FullName.GetMD5().ToString("N") + LiveStreamIdDelimeter;
+ var prefix = provider.GetType().FullName.GetMD5().ToString("N", CultureInfo.InvariantCulture) + LiveStreamIdDelimeter;
if (!string.IsNullOrEmpty(mediaSource.OpenToken) && !mediaSource.OpenToken.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
{
@@ -626,7 +626,7 @@ namespace Emby.Server.Implementations.Library
var now = DateTime.UtcNow;
MediaInfo mediaInfo = null;
- var cacheFilePath = string.IsNullOrEmpty(cacheKey) ? null : Path.Combine(_appPaths.CachePath, "mediainfo", cacheKey.GetMD5().ToString("N") + ".json");
+ var cacheFilePath = string.IsNullOrEmpty(cacheKey) ? null : Path.Combine(_appPaths.CachePath, "mediainfo", cacheKey.GetMD5().ToString("N", CultureInfo.InvariantCulture) + ".json");
if (!string.IsNullOrEmpty(cacheKey))
{
@@ -854,7 +854,7 @@ namespace Emby.Server.Implementations.Library
var keys = key.Split(new[] { LiveStreamIdDelimeter }, 2);
- var provider = _providers.FirstOrDefault(i => string.Equals(i.GetType().FullName.GetMD5().ToString("N"), keys[0], StringComparison.OrdinalIgnoreCase));
+ var provider = _providers.FirstOrDefault(i => string.Equals(i.GetType().FullName.GetMD5().ToString("N", CultureInfo.InvariantCulture), keys[0], StringComparison.OrdinalIgnoreCase));
var splitIndex = key.IndexOf(LiveStreamIdDelimeter);
var keyId = key.Substring(splitIndex + 1);
diff --git a/Emby.Server.Implementations/Library/UserDataManager.cs b/Emby.Server.Implementations/Library/UserDataManager.cs
index dfa1edaff..36adc0b9c 100644
--- a/Emby.Server.Implementations/Library/UserDataManager.cs
+++ b/Emby.Server.Implementations/Library/UserDataManager.cs
@@ -152,7 +152,7 @@ namespace Emby.Server.Implementations.Library
/// <returns>System.String.</returns>
private static string GetCacheKey(long internalUserId, Guid itemId)
{
- return internalUserId.ToString(CultureInfo.InvariantCulture) + "-" + itemId.ToString("N");
+ return internalUserId.ToString(CultureInfo.InvariantCulture) + "-" + itemId.ToString("N", CultureInfo.InvariantCulture);
}
public UserItemData GetUserData(User user, BaseItem item)
diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs
index 1701ced42..52b2f56ff 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;
@@ -7,24 +8,22 @@ using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Common.Cryptography;
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;
using MediaBrowser.Controller.Security;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Configuration;
-using MediaBrowser.Model.Cryptography;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Events;
@@ -32,6 +31,7 @@ using MediaBrowser.Model.IO;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Users;
using Microsoft.Extensions.Logging;
+using static MediaBrowser.Common.HexHelper;
namespace Emby.Server.Implementations.Library
{
@@ -41,34 +41,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,6 +61,8 @@ namespace Emby.Server.Implementations.Library
private readonly IServerApplicationHost _appHost;
private readonly IFileSystem _fileSystem;
+ private ConcurrentDictionary<Guid, User> _users;
+
private IAuthenticationProvider[] _authenticationProviders;
private DefaultAuthenticationProvider _defaultAuthenticationProvider;
@@ -85,8 +72,7 @@ namespace Emby.Server.Implementations.Library
private DefaultPasswordResetProvider _defaultPasswordResetProvider;
public UserManager(
- ILoggerFactory loggerFactory,
- IServerConfigurationManager configurationManager,
+ ILogger<UserManager> logger,
IUserRepository userRepository,
IXmlSerializer xmlSerializer,
INetworkManager networkManager,
@@ -96,8 +82,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;
@@ -105,8 +91,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()
@@ -137,7 +166,7 @@ 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();
@@ -150,54 +179,21 @@ namespace Emby.Server.Implementations.Library
_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>
@@ -206,15 +202,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));
@@ -222,8 +216,9 @@ namespace Emby.Server.Implementations.Library
public void Initialize()
{
- var users = LoadUsers();
- _users = users.ToArray();
+ LoadUsers();
+
+ var users = Users;
// If there are no local users with admin rights, make them all admins
if (!users.Any(i => i.Policy.IsAdministrator))
@@ -240,14 +235,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)
{
@@ -266,6 +259,7 @@ namespace Emby.Server.Implementations.Library
builder.Append(c);
}
}
+
return builder.ToString();
}
@@ -276,35 +270,31 @@ 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;
IAuthenticationProvider authenticationProvider = null;
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;
+ 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;
+ string 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
@@ -331,22 +321,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
@@ -357,11 +351,12 @@ namespace Emby.Server.Implementations.Library
user.LastActivityDate = user.LastLoginDate = DateTime.UtcNow;
UpdateUser(user);
}
- UpdateInvalidLoginAttemptCount(user, 0);
+
+ ResetInvalidLoginAttemptCount(user);
}
else
{
- UpdateInvalidLoginAttemptCount(user, user.Policy.InvalidLoginAttemptCount + 1);
+ IncrementInvalidLoginAttemptCount(user);
}
_logger.LogInformation("Authentication request for {0} {1}.", user.Name, success ? "has succeeded" : "has been denied");
@@ -381,7 +376,7 @@ namespace Emby.Server.Implementations.Library
private IAuthenticationProvider GetAuthenticationProvider(User user)
{
- return GetAuthenticationProviders(user).First();
+ return GetAuthenticationProviders(user)[0];
}
private IPasswordResetProvider GetPasswordResetProvider(User user)
@@ -391,7 +386,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();
@@ -429,139 +424,112 @@ 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;
- if (password != null && user != null)
+ foreach (var provider in GetAuthenticationProviders(user))
{
- // Doesn't look like this is even possible to be used, because of password == null checks below
- hashedPassword = _defaultAuthenticationProvider.GetHashedString(user, password);
- }
+ var providerAuthResult = await AuthenticateWithProvider(provider, username, password, user).ConfigureAwait(false);
+ var updatedUsername = providerAuthResult.username;
+ success = providerAuthResult.success;
- if (password == null)
- {
- // legacy
- success = string.Equals(GetAuthenticationProvider(user).GetPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase);
- }
- else
- {
- foreach (var provider in GetAuthenticationProviders(user))
+ if (success)
{
- var providerAuthResult = await AuthenticateWithProvider(provider, username, password, user).ConfigureAwait(false);
- updatedUsername = providerAuthResult.Item1;
- success = providerAuthResult.Item2;
-
- if (success)
- {
- authenticationProvider = provider;
- username = updatedUsername;
- break;
- }
+ authenticationProvider = provider;
+ username = updatedUsername;
+ break;
}
}
- if (user != null)
+ if (!success
+ && _networkManager.IsInLocalNetwork(remoteEndPoint)
+ && user.Configuration.EnableLocalPassword)
{
- if (!success && _networkManager.IsInLocalNetwork(remoteEndPoint) && user.Configuration.EnableLocalPassword)
- {
- if (password == null)
- {
- // legacy
- success = string.Equals(GetAuthenticationProvider(user).GetEasyPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase);
- }
- else
- {
- success = string.Equals(GetAuthenticationProvider(user).GetEasyPasswordHash(user), _defaultAuthenticationProvider.GetHashedString(user, password), StringComparison.OrdinalIgnoreCase);
- }
- }
+ 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 void UpdateInvalidLoginAttemptCount(User user, int newValue)
+ private string GetLocalPasswordHash(User user)
{
- if (user.Policy.InvalidLoginAttemptCount == newValue || newValue <= 0)
- {
- return;
- }
-
- user.Policy.InvalidLoginAttemptCount = newValue;
-
- // Check for users without a value here and then fill in the default value
- // also protect from an always lockout if misconfigured
- if (user.Policy.LoginAttemptsBeforeLockout == null || user.Policy.LoginAttemptsBeforeLockout == 0)
- {
- user.Policy.LoginAttemptsBeforeLockout = user.Policy.IsAdministrator ? 5 : 3;
- }
-
- var maxCount = user.Policy.LoginAttemptsBeforeLockout;
+ return string.IsNullOrEmpty(user.EasyPassword)
+ ? null
+ : ToHexString(PasswordHash.Parse(user.EasyPassword).Hash);
+ }
- var fireLockout = false;
+ private void ResetInvalidLoginAttemptCount(User user)
+ {
+ user.Policy.InvalidLoginAttemptCount = 0;
+ UpdateUserPolicy(user, user.Policy, false);
+ }
- // -1 can be used to specify no lockout value
- if (maxCount != -1 && newValue >= maxCount)
+ private void IncrementInvalidLoginAttemptCount(User user)
+ {
+ int invalidLogins = ++user.Policy.InvalidLoginAttemptCount;
+ int maxInvalidLogins = user.Policy.LoginAttemptsBeforeLockout;
+ if (maxInvalidLogins > 0
+ && invalidLogins >= maxInvalidLogins)
{
- _logger.LogDebug("Disabling user {0} due to {1} unsuccessful login attempts.", user.Name, newValue);
user.Policy.IsDisabled = true;
-
- fireLockout = true;
+ UserLockedOut?.Invoke(this, new GenericEventArgs<User>(user));
+ _logger.LogWarning(
+ "Disabling user {UserName} due to {Attempts} unsuccessful login attempts.",
+ user.Name,
+ invalidLogins);
}
UpdateUserPolicy(user, user.Policy, false);
-
- if (fireLockout)
- {
- UserLockedOut?.Invoke(this, new GenericEventArgs<User>(user));
- }
}
/// <summary>
- /// Loads the users from the repository
+ /// Loads the users from the repository.
/// </summary>
- /// <returns>IEnumerable{User}.</returns>
- private List<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)
{
- return users;
+ _users = new ConcurrentDictionary<Guid, User>(
+ users.Select(x => new KeyValuePair<Guid, User>(x.Id, x)));
+ return;
}
var defaultName = Environment.UserName;
@@ -576,14 +544,15 @@ namespace Emby.Server.Implementations.Library
user.DateLastSaved = DateTime.UtcNow;
- UserRepository.CreateUser(user);
+ _userRepository.CreateUser(user);
user.Policy.IsAdministrator = true;
user.Policy.EnableContentDeletion = true;
user.Policy.EnableRemoteControlOfOtherUsers = true;
UpdateUserPolicy(user, user.Policy, false);
- return new List<User> { user };
+ _users = new ConcurrentDictionary<Guid, User>();
+ _users[user.Id] = user;
}
public UserDto GetUserDto(User user, string remoteEndPoint = null)
@@ -593,7 +562,7 @@ namespace Emby.Server.Implementations.Library
throw new ArgumentNullException(nameof(user));
}
- bool hasConfiguredPassword = GetAuthenticationProvider(user).HasPassword(user).Result;
+ 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) ?
@@ -614,7 +583,7 @@ namespace Emby.Server.Implementations.Library
Policy = user.Policy
};
- if (!hasPassword && Users.Count() == 1)
+ if (!hasPassword && _users.Count == 1)
{
dto.EnableAutoLogin = true;
}
@@ -689,22 +658,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);
}
@@ -722,23 +695,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>
@@ -746,7 +726,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))
{
@@ -763,28 +743,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>
@@ -794,57 +763,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>
@@ -901,8 +872,7 @@ namespace Emby.Server.Implementations.Library
Name = name,
Id = Guid.NewGuid(),
DateCreated = DateTime.UtcNow,
- DateModified = DateTime.UtcNow,
- UsesIdForConfigurationPath = true
+ DateModified = DateTime.UtcNow
};
}
@@ -984,7 +954,6 @@ namespace Emby.Server.Implementations.Library
};
}
- private readonly object _policySyncLock = new object();
public void UpdateUserPolicy(Guid userId, UserPolicy userPolicy)
{
var user = GetUserById(userId);
diff --git a/Emby.Server.Implementations/Library/UserViewManager.cs b/Emby.Server.Implementations/Library/UserViewManager.cs
index e9ce682ee..88e2a8fa6 100644
--- a/Emby.Server.Implementations/Library/UserViewManager.cs
+++ b/Emby.Server.Implementations/Library/UserViewManager.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Globalization;
using System.Linq;
using System.Threading;
using MediaBrowser.Controller.Channels;
@@ -117,7 +118,7 @@ namespace Emby.Server.Implementations.Library
if (!query.IncludeHidden)
{
- list = list.Where(i => !user.Configuration.MyMediaExcludes.Contains(i.Id.ToString("N"))).ToList();
+ list = list.Where(i => !user.Configuration.MyMediaExcludes.Contains(i.Id.ToString("N", CultureInfo.InvariantCulture))).ToList();
}
var sorted = _libraryManager.Sort(list, user, new[] { ItemSortBy.SortName }, SortOrder.Ascending).ToList();
@@ -127,7 +128,7 @@ namespace Emby.Server.Implementations.Library
return list
.OrderBy(i =>
{
- var index = orders.IndexOf(i.Id.ToString("N"));
+ var index = orders.IndexOf(i.Id.ToString("N", CultureInfo.InvariantCulture));
if (index == -1)
{
@@ -136,7 +137,7 @@ namespace Emby.Server.Implementations.Library
{
if (!view.DisplayParentId.Equals(Guid.Empty))
{
- index = orders.IndexOf(view.DisplayParentId.ToString("N"));
+ index = orders.IndexOf(view.DisplayParentId.ToString("N", CultureInfo.InvariantCulture));
}
}
}
@@ -223,7 +224,7 @@ namespace Emby.Server.Implementations.Library
return list;
}
- private List<BaseItem> GetItemsForLatestItems(User user, LatestItemsQuery request, DtoOptions options)
+ private IReadOnlyList<BaseItem> GetItemsForLatestItems(User user, LatestItemsQuery request, DtoOptions options)
{
var parentId = request.ParentId;
@@ -235,24 +236,22 @@ namespace Emby.Server.Implementations.Library
if (!parentId.Equals(Guid.Empty))
{
var parentItem = _libraryManager.GetItemById(parentId);
- var parentItemChannel = parentItem as Channel;
- if (parentItemChannel != null)
+ if (parentItem is Channel)
{
- return _channelManager.GetLatestChannelItemsInternal(new InternalItemsQuery(user)
- {
- ChannelIds = new[] { parentId },
- IsPlayed = request.IsPlayed,
- StartIndex = request.StartIndex,
- Limit = request.Limit,
- IncludeItemTypes = request.IncludeItemTypes,
- EnableTotalRecordCount = false
-
-
- }, CancellationToken.None).Result.Items.ToList();
+ return _channelManager.GetLatestChannelItemsInternal(
+ new InternalItemsQuery(user)
+ {
+ ChannelIds = new[] { parentId },
+ IsPlayed = request.IsPlayed,
+ StartIndex = request.StartIndex,
+ Limit = request.Limit,
+ IncludeItemTypes = request.IncludeItemTypes,
+ EnableTotalRecordCount = false
+ },
+ CancellationToken.None).GetAwaiter().GetResult().Items;
}
- var parent = parentItem as Folder;
- if (parent != null)
+ if (parentItem is Folder parent)
{
parents.Add(parent);
}
@@ -269,7 +268,7 @@ namespace Emby.Server.Implementations.Library
{
parents = _libraryManager.GetUserRootFolder().GetChildren(user, true)
.Where(i => i is Folder)
- .Where(i => !user.Configuration.LatestItemsExcludes.Contains(i.Id.ToString("N")))
+ .Where(i => !user.Configuration.LatestItemsExcludes.Contains(i.Id.ToString("N", CultureInfo.InvariantCulture)))
.ToList();
}
diff --git a/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs b/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs
index 294348660..b584cc649 100644
--- a/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs
+++ b/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs
@@ -1,4 +1,5 @@
using System;
+using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -91,7 +92,7 @@ namespace Emby.Server.Implementations.Library.Validators
continue;
}
- _logger.LogInformation("Deleting dead {2} {0} {1}.", item.Id.ToString("N"), item.Name, item.GetType().Name);
+ _logger.LogInformation("Deleting dead {2} {0} {1}.", item.Id.ToString("N", CultureInfo.InvariantCulture), item.Name, item.GetType().Name);
_libraryManager.DeleteItem(item, new DeleteOptions
{
diff --git a/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs b/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs
index 7899cf01b..d00c6cde1 100644
--- a/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs
+++ b/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs
@@ -1,7 +1,7 @@
using System;
+using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
-using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
@@ -96,7 +96,7 @@ namespace Emby.Server.Implementations.Library.Validators
foreach (var item in deadEntities)
{
- _logger.LogInformation("Deleting dead {2} {0} {1}.", item.Id.ToString("N"), item.Name, item.GetType().Name);
+ _logger.LogInformation("Deleting dead {2} {0} {1}.", item.Id.ToString("N", CultureInfo.InvariantCulture), item.Name, item.GetType().Name);
_libraryManager.DeleteItem(item, new DeleteOptions
{
diff --git a/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs b/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs
index da4645a11..93ded9e7b 100644
--- a/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs
+++ b/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs
@@ -1,4 +1,5 @@
using System;
+using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Entities;
@@ -76,7 +77,7 @@ namespace Emby.Server.Implementations.Library.Validators
foreach (var item in deadEntities)
{
- _logger.LogInformation("Deleting dead {2} {0} {1}.", item.Id.ToString("N"), item.Name, item.GetType().Name);
+ _logger.LogInformation("Deleting dead {2} {0} {1}.", item.Id.ToString("N", CultureInfo.InvariantCulture), item.Name, item.GetType().Name);
_libraryManager.DeleteItem(item, new DeleteOptions
{