From 4519ce26e2250cb233836296d292ddb7b3cf6346 Mon Sep 17 00:00:00 2001 From: Phallacy Date: Thu, 31 Jan 2019 00:24:53 -0800 Subject: Upgrade crypto provider, retarget better framework --- .../Cryptography/CryptographyProvider.cs | 171 ++++++++++++++++----- .../Emby.Server.Implementations.csproj | 2 +- Emby.Server.Implementations/Library/UserManager.cs | 31 ++-- 3 files changed, 147 insertions(+), 57 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs index 09fdbc856..ca6ae2bb2 100644 --- a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs +++ b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs @@ -1,40 +1,131 @@ -using System; -using System.IO; -using System.Security.Cryptography; -using System.Text; -using MediaBrowser.Model.Cryptography; - -namespace Emby.Server.Implementations.Cryptography -{ - public class CryptographyProvider : ICryptoProvider - { - public Guid GetMD5(string str) - { - return new Guid(ComputeMD5(Encoding.Unicode.GetBytes(str))); - } - - public byte[] ComputeSHA1(byte[] bytes) - { - using (var provider = SHA1.Create()) - { - return provider.ComputeHash(bytes); - } - } - - public byte[] ComputeMD5(Stream str) - { - using (var provider = MD5.Create()) - { - return provider.ComputeHash(str); - } - } - - public byte[] ComputeMD5(byte[] bytes) - { - using (var provider = MD5.Create()) - { - return provider.ComputeHash(bytes); - } - } - } -} +using System; +using System.Collections.Generic; +using System.IO; +using System.Security.Cryptography; +using System.Text; +using MediaBrowser.Model.Cryptography; + +namespace Emby.Server.Implementations.Cryptography +{ + public class CryptographyProvider : ICryptoProvider + { + private List SupportedHashMethods = new List(); + private string DefaultHashMethod = "SHA256"; + private RandomNumberGenerator rng; + private int defaultiterations = 1000; + public CryptographyProvider() + { + //Currently supported hash methods from https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptoconfig?view=netcore-2.1 + //there might be a better way to autogenerate this list as dotnet updates, but I couldn't find one + SupportedHashMethods = new List + { + "MD5" + ,"System.Security.Cryptography.MD5" + ,"SHA" + ,"SHA1" + ,"System.Security.Cryptography.SHA1" + ,"SHA256" + ,"SHA-256" + ,"System.Security.Cryptography.SHA256" + ,"SHA384" + ,"SHA-384" + ,"System.Security.Cryptography.SHA384" + ,"SHA512" + ,"SHA-512" + ,"System.Security.Cryptography.SHA512" + }; + rng = RandomNumberGenerator.Create(); + } + + public Guid GetMD5(string str) + { + return new Guid(ComputeMD5(Encoding.Unicode.GetBytes(str))); + } + + public byte[] ComputeSHA1(byte[] bytes) + { + using (var provider = SHA1.Create()) + { + return provider.ComputeHash(bytes); + } + } + + public byte[] ComputeMD5(Stream str) + { + using (var provider = MD5.Create()) + { + return provider.ComputeHash(str); + } + } + + public byte[] ComputeMD5(byte[] bytes) + { + using (var provider = MD5.Create()) + { + return provider.ComputeHash(bytes); + } + } + + public IEnumerable GetSupportedHashMethods() + { + return SupportedHashMethods; + } + + private byte[] PBKDF2(string method, byte[] bytes, byte[] salt) + { + using (var r = new Rfc2898DeriveBytes(bytes, salt, defaultiterations, new HashAlgorithmName(method))) + { + return r.GetBytes(32); + } + } + + public byte[] ComputeHash(string HashMethod, byte[] bytes) + { + return ComputeHash(HashMethod, bytes, new byte[0]); + } + + public byte[] ComputeHashWithDefaultMethod(byte[] bytes) + { + return ComputeHash(DefaultHashMethod, bytes); + } + + public byte[] ComputeHash(string HashMethod, byte[] bytes, byte[] salt) + { + if (SupportedHashMethods.Contains(HashMethod)) + { + if (salt.Length == 0) + { + using (var h = HashAlgorithm.Create(HashMethod)) + { + return h.ComputeHash(bytes); + } + } + else + { + return PBKDF2(HashMethod, bytes, salt); + } + } + else + { + throw new CryptographicException(String.Format("Requested hash method is not supported: {0}", HashMethod)); + } + } + + public byte[] ComputeHashWithDefaultMethod(byte[] bytes, byte[] salt) + { + return PBKDF2(DefaultHashMethod, bytes, salt); + } + + public byte[] ComputeHash(PasswordHash hash) + { + return ComputeHash(hash.Id, hash.HashBytes, hash.SaltBytes); + } + + public byte[] GenerateSalt() + { + byte[] salt = new byte[8]; + rng.GetBytes(salt); + return salt; + } + } +} diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 3aa617b02..df7963b02 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -35,7 +35,7 @@ - netstandard2.0 + netcoreapp2.1 false diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index 05fce4542..70639dad5 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -4,6 +4,7 @@ using System.Globalization; using System.IO; using System.Linq; using System.Text; +using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Events; @@ -220,22 +221,20 @@ namespace Emby.Server.Implementations.Library } } - public bool IsValidUsername(string username) - { - // Usernames can contain letters (a-z), numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.) - foreach (var currentChar in username) - { - if (!IsValidUsernameCharacter(currentChar)) - { - return false; - } - } - return true; - } - - private static bool IsValidUsernameCharacter(char i) - { - return !char.Equals(i, '<') && !char.Equals(i, '>'); + public bool IsValidUsername(string username) + { + //The old way was dumb, we should make it less dumb, lets do so. + //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 + string UserNameRegex = "^[\\w-'._@]*$"; + // Usernames can contain letters (a-z + whatever else unicode is cool with), numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.) + return Regex.IsMatch(username, UserNameRegex); + } + + private static bool IsValidUsernameCharacter(char i) + { + string UserNameRegex = "^[\\w-'._@]*$"; + return Regex.IsMatch(i.ToString(), UserNameRegex); } public string MakeValidUsername(string username) -- cgit v1.2.3 From 05bbf71b6d97614888efe103f763753e4487cc2c Mon Sep 17 00:00:00 2001 From: Phallacy Date: Tue, 12 Feb 2019 02:16:03 -0800 Subject: sha256 with salt auth and sha1 interop --- .../Cryptography/CryptographyProvider.cs | 2 +- .../Library/DefaultAuthenticationProvider.cs | 167 +- Emby.Server.Implementations/Library/UserManager.cs | 2416 ++++++++++---------- MediaBrowser.Model/Cryptography/ICryptoProvider.cs | 29 +- MediaBrowser.Model/Cryptography/PasswordHash.cs | 37 +- 5 files changed, 1388 insertions(+), 1263 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs index ca6ae2bb2..4f2bc1b03 100644 --- a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs +++ b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs @@ -10,7 +10,7 @@ namespace Emby.Server.Implementations.Cryptography public class CryptographyProvider : ICryptoProvider { private List SupportedHashMethods = new List(); - private string DefaultHashMethod = "SHA256"; + public string DefaultHashMethod => "SHA256"; private RandomNumberGenerator rng; private int defaultiterations = 1000; public CryptographyProvider() diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs index 4013ac0c8..92346c65a 100644 --- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Text; using System.Threading.Tasks; using MediaBrowser.Controller.Authentication; @@ -19,31 +20,110 @@ namespace Emby.Server.Implementations.Library public bool IsEnabled => true; + + //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 public Task Authenticate(string username, string password) { throw new NotImplementedException(); } - public Task Authenticate(string username, string password, User resolvedUser) - { - if (resolvedUser == null) - { - throw new Exception("Invalid username or password"); - } - - var success = string.Equals(GetPasswordHash(resolvedUser), GetHashedString(resolvedUser, password), StringComparison.OrdinalIgnoreCase); - if (!success) - { - throw new Exception("Invalid username or password"); - } + //This is the verson that we need to use for local users. Because reasons. + public Task Authenticate(string username, string password, User resolvedUser) + { + ConvertPasswordFormat(resolvedUser); + byte[] passwordbytes = Encoding.UTF8.GetBytes(password); + bool success = false; + if (resolvedUser == null) + { + success = false; + throw new Exception("Invalid username or password"); + } + if (!resolvedUser.Password.Contains("$")) + { + ConvertPasswordFormat(resolvedUser); + } + PasswordHash ReadyHash = new PasswordHash(resolvedUser.Password); + byte[] CalculatedHash; + string CalculatedHashString; + if (_cryptographyProvider.GetSupportedHashMethods().Any(i => i == 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); + } + if (CalculatedHashString == ReadyHash.Hash) + { + success = true; + //throw new Exception("Invalid username or password"); + } + } + else + { + success = false; + throw new Exception(String.Format("Requested crypto method not available in provider: {0}", ReadyHash.Id)); + } + + //var success = string.Equals(GetPasswordHash(resolvedUser), GetHashedString(resolvedUser, password), StringComparison.OrdinalIgnoreCase); + + if (!success) + { + throw new Exception("Invalid username or password"); + } + + return Task.FromResult(new ProviderAuthenticationResult + { + Username = username + }); + } - return Task.FromResult(new ProviderAuthenticationResult - { - Username = username - }); + //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)) + { + 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); + } + } } + // OLD VERSION //public Task Authenticate(string username, string password, User resolvedUser) + // OLD VERSION //{ + // OLD VERSION // if (resolvedUser == null) + // OLD VERSION // { + // OLD VERSION // throw new Exception("Invalid username or password"); + // OLD VERSION // } + // OLD VERSION // + // OLD VERSION // var success = string.Equals(GetPasswordHash(resolvedUser), GetHashedString(resolvedUser, password), StringComparison.OrdinalIgnoreCase); + // OLD VERSION // + // OLD VERSION // if (!success) + // OLD VERSION // { + // OLD VERSION // throw new Exception("Invalid username or password"); + // OLD VERSION // } + // OLD VERSION // + // OLD VERSION // return Task.FromResult(new ProviderAuthenticationResult + // OLD VERSION // { + // OLD VERSION // Username = username + // OLD VERSION // }); + // OLD VERSION //} + public Task HasPassword(User user) { var hasConfiguredPassword = !IsPasswordEmpty(user, GetPasswordHash(user)); @@ -57,19 +137,26 @@ namespace Emby.Server.Implementations.Library public Task ChangePassword(User user, string newPassword) { - string newPasswordHash = null; - - if (newPassword != null) + //string newPasswordHash = null; + ConvertPasswordFormat(user); + PasswordHash passwordHash = new PasswordHash(user.Password); + if(passwordHash.Id == "SHA1" && string.IsNullOrEmpty(passwordHash.Salt)) { - newPasswordHash = GetHashedString(user, newPassword); + passwordHash.SaltBytes = _cryptographyProvider.GenerateSalt(); + passwordHash.Salt = BitConverter.ToString(passwordHash.SaltBytes).Replace("-",""); + passwordHash.Id = _cryptographyProvider.DefaultHashMethod; + passwordHash.Hash = GetHashedStringChangeAuth(newPassword, passwordHash); + }else if (newPassword != null) + { + passwordHash.Hash = GetHashedString(user, newPassword); } - if (string.IsNullOrWhiteSpace(newPasswordHash)) + if (string.IsNullOrWhiteSpace(passwordHash.Hash)) { - throw new ArgumentNullException(nameof(newPasswordHash)); + throw new ArgumentNullException(nameof(passwordHash.Hash)); } - user.Password = newPasswordHash; + user.Password = passwordHash.ToString(); return Task.CompletedTask; } @@ -86,19 +173,39 @@ namespace Emby.Server.Implementations.Library return GetHashedString(user, string.Empty); } + public string GetHashedStringChangeAuth(string NewPassword, PasswordHash passwordHash) + { + return BitConverter.ToString(_cryptographyProvider.ComputeHash(passwordHash.Id, Encoding.UTF8.GetBytes(NewPassword), passwordHash.SaltBytes)).Replace("-", string.Empty); + } + /// /// Gets the hashed string. /// - public string GetHashedString(User user, string str) - { - var salt = user.Salt; - if (salt != null) + public string GetHashedString(User user, string str) + { + //This is legacy. Deprecated in the auth method. + //return BitConverter.ToString(_cryptoProvider2.ComputeSHA1(Encoding.UTF8.GetBytes(str))).Replace("-", string.Empty); + PasswordHash passwordHash; + if (String.IsNullOrEmpty(user.Password)) + { + passwordHash = new PasswordHash(_cryptographyProvider); + } + else { - // return BCrypt.HashPassword(str, salt); + ConvertPasswordFormat(user); + passwordHash = new PasswordHash(user.Password); + } + if (passwordHash.SaltBytes != null) + { + return BitConverter.ToString(_cryptographyProvider.ComputeHash(passwordHash.Id, Encoding.UTF8.GetBytes(str), passwordHash.SaltBytes)).Replace("-",string.Empty); + } + else + { + return BitConverter.ToString(_cryptographyProvider.ComputeHash(passwordHash.Id, Encoding.UTF8.GetBytes(str))).Replace("-", string.Empty); + //throw new Exception("User does not have a hash, this should not be possible"); } - // legacy - return BitConverter.ToString(_cryptographyProvider.ComputeSHA1(Encoding.UTF8.GetBytes(str))).Replace("-", string.Empty); + } } } diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index 40eda52c6..a139c4e73 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -1,222 +1,222 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; -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; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.Users; -using Microsoft.Extensions.Logging; - -namespace Emby.Server.Implementations.Library -{ - /// - /// Class UserManager - /// - public class UserManager : IUserManager - { - /// - /// Gets the users. - /// - /// The users. - public IEnumerable Users => _users; - - private User[] _users; - - /// - /// The _logger - /// - private readonly ILogger _logger; - - /// - /// Gets or sets the configuration manager. - /// - /// The configuration manager. - private IServerConfigurationManager ConfigurationManager { get; set; } - - /// - /// Gets the active user repository - /// - /// The user repository. - private IUserRepository UserRepository { get; set; } - public event EventHandler> UserPasswordChanged; - - private readonly IXmlSerializer _xmlSerializer; - private readonly IJsonSerializer _jsonSerializer; - - private readonly INetworkManager _networkManager; - - private readonly Func _imageProcessorFactory; - private readonly Func _dtoServiceFactory; - private readonly IServerApplicationHost _appHost; - private readonly IFileSystem _fileSystem; - private readonly ICryptoProvider _cryptographyProvider; - - private IAuthenticationProvider[] _authenticationProviders; - private DefaultAuthenticationProvider _defaultAuthenticationProvider; - - public UserManager( - ILoggerFactory loggerFactory, - IServerConfigurationManager configurationManager, - IUserRepository userRepository, - IXmlSerializer xmlSerializer, - INetworkManager networkManager, - Func imageProcessorFactory, - Func dtoServiceFactory, - IServerApplicationHost appHost, - IJsonSerializer jsonSerializer, - IFileSystem fileSystem, - ICryptoProvider cryptographyProvider) - { - _logger = loggerFactory.CreateLogger(nameof(UserManager)); - UserRepository = userRepository; - _xmlSerializer = xmlSerializer; - _networkManager = networkManager; - _imageProcessorFactory = imageProcessorFactory; - _dtoServiceFactory = dtoServiceFactory; - _appHost = appHost; - _jsonSerializer = jsonSerializer; - _fileSystem = fileSystem; - _cryptographyProvider = cryptographyProvider; - ConfigurationManager = configurationManager; - _users = Array.Empty(); - - DeletePinFile(); - } - - public NameIdPair[] GetAuthenticationProviders() - { - return _authenticationProviders - .Where(i => i.IsEnabled) - .OrderBy(i => i is DefaultAuthenticationProvider ? 0 : 1) - .ThenBy(i => i.Name) - .Select(i => new NameIdPair - { - Name = i.Name, - Id = GetAuthenticationProviderId(i) - }) - .ToArray(); - } - - public void AddParts(IEnumerable authenticationProviders) - { - _authenticationProviders = authenticationProviders.ToArray(); - - _defaultAuthenticationProvider = _authenticationProviders.OfType().First(); - } - - #region UserUpdated Event - /// - /// Occurs when [user updated]. - /// - public event EventHandler> UserUpdated; - public event EventHandler> UserPolicyUpdated; - public event EventHandler> UserConfigurationUpdated; - public event EventHandler> UserLockedOut; - - /// - /// Called when [user updated]. - /// - /// The user. - private void OnUserUpdated(User user) - { - UserUpdated?.Invoke(this, new GenericEventArgs { Argument = user }); - } - #endregion - - #region UserDeleted Event - /// - /// Occurs when [user deleted]. - /// - public event EventHandler> UserDeleted; - /// - /// Called when [user deleted]. - /// - /// The user. - private void OnUserDeleted(User user) - { - UserDeleted?.Invoke(this, new GenericEventArgs { Argument = user }); - } - #endregion - - /// - /// Gets a User by Id - /// - /// The id. - /// User. - /// - public User GetUserById(Guid id) - { - if (id.Equals(Guid.Empty)) - { - throw new ArgumentNullException(nameof(id)); - } - - return Users.FirstOrDefault(u => u.Id == id); - } - - /// - /// Gets the user by identifier. - /// - /// The identifier. - /// User. - public User GetUserById(string id) - { - return GetUserById(new Guid(id)); - } - - public User GetUserByName(string name) - { - if (string.IsNullOrWhiteSpace(name)) - { - throw new ArgumentNullException(nameof(name)); - } - - return Users.FirstOrDefault(u => string.Equals(u.Name, name, StringComparison.OrdinalIgnoreCase)); - } - - public void Initialize() - { - _users = LoadUsers(); - - var users = Users.ToList(); - - // If there are no local users with admin rights, make them all admins - if (!users.Any(i => i.Policy.IsAdministrator)) - { - foreach (var user in users) - { - user.Policy.IsAdministrator = true; - UpdateUserPolicy(user, user.Policy, false); - } - } - } - +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +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; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Serialization; +using MediaBrowser.Model.Users; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.Library +{ + /// + /// Class UserManager + /// + public class UserManager : IUserManager + { + /// + /// Gets the users. + /// + /// The users. + public IEnumerable Users => _users; + + private User[] _users; + + /// + /// The _logger + /// + private readonly ILogger _logger; + + /// + /// Gets or sets the configuration manager. + /// + /// The configuration manager. + private IServerConfigurationManager ConfigurationManager { get; set; } + + /// + /// Gets the active user repository + /// + /// The user repository. + private IUserRepository UserRepository { get; set; } + public event EventHandler> UserPasswordChanged; + + private readonly IXmlSerializer _xmlSerializer; + private readonly IJsonSerializer _jsonSerializer; + + private readonly INetworkManager _networkManager; + + private readonly Func _imageProcessorFactory; + private readonly Func _dtoServiceFactory; + private readonly IServerApplicationHost _appHost; + private readonly IFileSystem _fileSystem; + private readonly ICryptoProvider _cryptographyProvider; + + private IAuthenticationProvider[] _authenticationProviders; + private DefaultAuthenticationProvider _defaultAuthenticationProvider; + + public UserManager( + ILoggerFactory loggerFactory, + IServerConfigurationManager configurationManager, + IUserRepository userRepository, + IXmlSerializer xmlSerializer, + INetworkManager networkManager, + Func imageProcessorFactory, + Func dtoServiceFactory, + IServerApplicationHost appHost, + IJsonSerializer jsonSerializer, + IFileSystem fileSystem, + ICryptoProvider cryptographyProvider) + { + _logger = loggerFactory.CreateLogger(nameof(UserManager)); + UserRepository = userRepository; + _xmlSerializer = xmlSerializer; + _networkManager = networkManager; + _imageProcessorFactory = imageProcessorFactory; + _dtoServiceFactory = dtoServiceFactory; + _appHost = appHost; + _jsonSerializer = jsonSerializer; + _fileSystem = fileSystem; + _cryptographyProvider = cryptographyProvider; + ConfigurationManager = configurationManager; + _users = Array.Empty(); + + DeletePinFile(); + } + + public NameIdPair[] GetAuthenticationProviders() + { + return _authenticationProviders + .Where(i => i.IsEnabled) + .OrderBy(i => i is DefaultAuthenticationProvider ? 0 : 1) + .ThenBy(i => i.Name) + .Select(i => new NameIdPair + { + Name = i.Name, + Id = GetAuthenticationProviderId(i) + }) + .ToArray(); + } + + public void AddParts(IEnumerable authenticationProviders) + { + _authenticationProviders = authenticationProviders.ToArray(); + + _defaultAuthenticationProvider = _authenticationProviders.OfType().First(); + } + + #region UserUpdated Event + /// + /// Occurs when [user updated]. + /// + public event EventHandler> UserUpdated; + public event EventHandler> UserPolicyUpdated; + public event EventHandler> UserConfigurationUpdated; + public event EventHandler> UserLockedOut; + + /// + /// Called when [user updated]. + /// + /// The user. + private void OnUserUpdated(User user) + { + UserUpdated?.Invoke(this, new GenericEventArgs { Argument = user }); + } + #endregion + + #region UserDeleted Event + /// + /// Occurs when [user deleted]. + /// + public event EventHandler> UserDeleted; + /// + /// Called when [user deleted]. + /// + /// The user. + private void OnUserDeleted(User user) + { + UserDeleted?.Invoke(this, new GenericEventArgs { Argument = user }); + } + #endregion + + /// + /// Gets a User by Id + /// + /// The id. + /// User. + /// + public User GetUserById(Guid id) + { + if (id.Equals(Guid.Empty)) + { + throw new ArgumentNullException(nameof(id)); + } + + return Users.FirstOrDefault(u => u.Id == id); + } + + /// + /// Gets the user by identifier. + /// + /// The identifier. + /// User. + public User GetUserById(string id) + { + return GetUserById(new Guid(id)); + } + + public User GetUserByName(string name) + { + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentNullException(nameof(name)); + } + + return Users.FirstOrDefault(u => string.Equals(u.Name, name, StringComparison.OrdinalIgnoreCase)); + } + + public void Initialize() + { + _users = LoadUsers(); + + var users = Users.ToList(); + + // If there are no local users with admin rights, make them all admins + if (!users.Any(i => i.Policy.IsAdministrator)) + { + foreach (var user in users) + { + user.Policy.IsAdministrator = true; + UpdateUserPolicy(user, user.Policy, false); + } + } + } + public bool IsValidUsername(string username) { //The old way was dumb, we should make it less dumb, lets do so. @@ -231,992 +231,992 @@ namespace Emby.Server.Implementations.Library { string UserNameRegex = "^[\\w-'._@]*$"; return Regex.IsMatch(i.ToString(), UserNameRegex); - } - - public string MakeValidUsername(string username) - { - if (IsValidUsername(username)) - { - return username; - } - - // Usernames can contain letters (a-z), numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.) - var builder = new StringBuilder(); - - foreach (var c in username) - { - if (IsValidUsernameCharacter(c)) - { - builder.Append(c); - } - } - return builder.ToString(); - } - - public async Task AuthenticateUser(string username, string password, string hashedPassword, string remoteEndPoint, bool isUserSession) - { - if (string.IsNullOrWhiteSpace(username)) - { - throw new ArgumentNullException(nameof(username)); - } - - var user = Users - .FirstOrDefault(i => string.Equals(username, i.Name, StringComparison.OrdinalIgnoreCase)); - - var success = false; - IAuthenticationProvider authenticationProvider = null; - - if (user != null) - { - var authResult = await AuthenticateLocalUser(username, password, hashedPassword, user, remoteEndPoint).ConfigureAwait(false); - authenticationProvider = authResult.Item1; - success = authResult.Item2; - } - else - { - // user is null - var authResult = await AuthenticateLocalUser(username, password, hashedPassword, null, remoteEndPoint).ConfigureAwait(false); - authenticationProvider = authResult.Item1; - success = authResult.Item2; - - if (success && authenticationProvider != null && !(authenticationProvider is DefaultAuthenticationProvider)) - { - user = await CreateUser(username).ConfigureAwait(false); - - var hasNewUserPolicy = authenticationProvider as IHasNewUserPolicy; - if (hasNewUserPolicy != null) - { - var policy = hasNewUserPolicy.GetNewUserPolicy(); - UpdateUserPolicy(user, policy, true); - } - } - } - - if (success && user != null && authenticationProvider != null) - { - var providerId = GetAuthenticationProviderId(authenticationProvider); - - if (!string.Equals(providerId, user.Policy.AuthenticationProviderId, StringComparison.OrdinalIgnoreCase)) - { - user.Policy.AuthenticationProviderId = providerId; - UpdateUserPolicy(user, user.Policy, true); - } - } - - if (user == null) - { - throw new SecurityException("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)); - } - - if (!user.Policy.EnableRemoteAccess && !_networkManager.IsInLocalNetwork(remoteEndPoint)) - { - throw new SecurityException("Forbidden."); - } - - if (!user.IsParentalScheduleAllowed()) - { - throw new SecurityException("User is not allowed access at this time."); - } - - // Update LastActivityDate and LastLoginDate, then save - if (success) - { - if (isUserSession) - { - user.LastActivityDate = user.LastLoginDate = DateTime.UtcNow; - UpdateUser(user); - } - UpdateInvalidLoginAttemptCount(user, 0); - } - else - { - UpdateInvalidLoginAttemptCount(user, user.Policy.InvalidLoginAttemptCount + 1); - } - - _logger.LogInformation("Authentication request for {0} {1}.", user.Name, success ? "has succeeded" : "has been denied"); - - return success ? user : null; - } - - private static string GetAuthenticationProviderId(IAuthenticationProvider provider) - { - return provider.GetType().FullName; - } - - private IAuthenticationProvider GetAuthenticationProvider(User user) - { - return GetAuthenticationProviders(user).First(); - } - - private IAuthenticationProvider[] GetAuthenticationProviders(User user) - { - var authenticationProviderId = user == null ? null : user.Policy.AuthenticationProviderId; - - var providers = _authenticationProviders.Where(i => i.IsEnabled).ToArray(); - - if (!string.IsNullOrEmpty(authenticationProviderId)) - { - providers = providers.Where(i => string.Equals(authenticationProviderId, GetAuthenticationProviderId(i), StringComparison.OrdinalIgnoreCase)).ToArray(); - } - - if (providers.Length == 0) - { - providers = new IAuthenticationProvider[] { _defaultAuthenticationProvider }; - } - - return providers; - } - - private async Task AuthenticateWithProvider(IAuthenticationProvider provider, string username, string password, User resolvedUser) - { - try - { - var requiresResolvedUser = provider as IRequiresResolvedUser; - if (requiresResolvedUser != null) - { - await requiresResolvedUser.Authenticate(username, password, resolvedUser).ConfigureAwait(false); - } - else - { - await provider.Authenticate(username, password).ConfigureAwait(false); - } - - return true; - } - catch (Exception ex) - { - _logger.LogError(ex, "Error authenticating with provider {provider}", provider.Name); - - return false; - } - } - - private async Task> AuthenticateLocalUser(string username, string password, string hashedPassword, User user, string remoteEndPoint) - { - bool success = false; - IAuthenticationProvider authenticationProvider = null; - - if (password != null && user != null) - { - // Doesn't look like this is even possible to be used, because of password == null checks below - hashedPassword = _defaultAuthenticationProvider.GetHashedString(user, password); - } - - if (password == null) - { - // legacy - success = string.Equals(_defaultAuthenticationProvider.GetPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase); - } - else - { - foreach (var provider in GetAuthenticationProviders(user)) - { - success = await AuthenticateWithProvider(provider, username, password, user).ConfigureAwait(false); - - if (success) - { - authenticationProvider = provider; - break; - } - } - } - - if (user != null) - { - if (!success && _networkManager.IsInLocalNetwork(remoteEndPoint) && user.Configuration.EnableLocalPassword) - { - 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); - } - } - } - - return new Tuple(authenticationProvider, success); - } - - private void UpdateInvalidLoginAttemptCount(User user, int newValue) - { - if (user.Policy.InvalidLoginAttemptCount == newValue || newValue <= 0) - { - return; - } - - user.Policy.InvalidLoginAttemptCount = newValue; - - var maxCount = user.Policy.IsAdministrator ? 3 : 5; - - var fireLockout = false; - - if (newValue >= maxCount) - { - _logger.LogDebug("Disabling user {0} due to {1} unsuccessful login attempts.", user.Name, newValue); - user.Policy.IsDisabled = true; - - fireLockout = true; - } - - UpdateUserPolicy(user, user.Policy, false); - - if (fireLockout) - { - UserLockedOut?.Invoke(this, new GenericEventArgs(user)); - } - } - - private string GetLocalPasswordHash(User user) - { - return string.IsNullOrEmpty(user.EasyPassword) - ? _defaultAuthenticationProvider.GetEmptyHashedString(user) - : user.EasyPassword; - } - - private bool IsPasswordEmpty(User user, string passwordHash) - { - return string.Equals(passwordHash, _defaultAuthenticationProvider.GetEmptyHashedString(user), StringComparison.OrdinalIgnoreCase); - } - - /// - /// Loads the users from the repository - /// - /// IEnumerable{User}. - private User[] LoadUsers() - { - var users = UserRepository.RetrieveAllUsers(); - - // There always has to be at least one user. - if (users.Count == 0) - { - var defaultName = Environment.UserName; - if (string.IsNullOrWhiteSpace(defaultName)) - { - defaultName = "MyJellyfinUser"; - } - var name = MakeValidUsername(defaultName); - - var user = InstantiateNewUser(name); - - user.DateLastSaved = DateTime.UtcNow; - - UserRepository.CreateUser(user); - - users.Add(user); - - user.Policy.IsAdministrator = true; - user.Policy.EnableContentDeletion = true; - user.Policy.EnableRemoteControlOfOtherUsers = true; - UpdateUserPolicy(user, user.Policy, false); - } - - return users.ToArray(); - } - - public UserDto GetUserDto(User user, string remoteEndPoint = null) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - var hasConfiguredPassword = GetAuthenticationProvider(user).HasPassword(user).Result; - var hasConfiguredEasyPassword = !IsPasswordEmpty(user, GetLocalPasswordHash(user)); - - var hasPassword = user.Configuration.EnableLocalPassword && !string.IsNullOrEmpty(remoteEndPoint) && _networkManager.IsInLocalNetwork(remoteEndPoint) ? - hasConfiguredEasyPassword : - hasConfiguredPassword; - - var dto = new UserDto - { - Id = user.Id, - Name = user.Name, - HasPassword = hasPassword, - HasConfiguredPassword = hasConfiguredPassword, - HasConfiguredEasyPassword = hasConfiguredEasyPassword, - LastActivityDate = user.LastActivityDate, - LastLoginDate = user.LastLoginDate, - Configuration = user.Configuration, - ServerId = _appHost.SystemId, - Policy = user.Policy - }; - - if (!hasPassword && Users.Count() == 1) - { - dto.EnableAutoLogin = true; - } - - var image = user.GetImageInfo(ImageType.Primary, 0); - - if (image != null) - { - dto.PrimaryImageTag = GetImageCacheTag(user, image); - - try - { - _dtoServiceFactory().AttachPrimaryImageAspectRatio(dto, user); - } - catch (Exception ex) - { - // Have to use a catch-all unfortunately because some .net image methods throw plain Exceptions - _logger.LogError(ex, "Error generating PrimaryImageAspectRatio for {user}", user.Name); - } - } - - return dto; - } - - public UserDto GetOfflineUserDto(User user) - { - var dto = GetUserDto(user); - - dto.ServerName = _appHost.FriendlyName; - - return dto; - } - - private string GetImageCacheTag(BaseItem item, ItemImageInfo image) - { - try - { - return _imageProcessorFactory().GetImageCacheTag(item, image); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error getting {imageType} image info for {imagePath}", image.Type, image.Path); - return null; - } - } - - /// - /// Refreshes metadata for each user - /// - /// The cancellation token. - /// Task. - public async Task RefreshUsersMetadata(CancellationToken cancellationToken) - { - foreach (var user in Users) - { - await user.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)), cancellationToken).ConfigureAwait(false); - } - } - - /// - /// Renames the user. - /// - /// The user. - /// The new name. - /// Task. - /// user - /// - public async Task RenameUser(User user, string newName) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - if (string.IsNullOrEmpty(newName)) - { - throw new ArgumentNullException(nameof(newName)); - } - - if (Users.Any(u => u.Id != user.Id && u.Name.Equals(newName, StringComparison.OrdinalIgnoreCase))) - { - throw new ArgumentException(string.Format("A user with the name '{0}' already exists.", newName)); - } - - if (user.Name.Equals(newName, StringComparison.Ordinal)) - { - throw new ArgumentException("The new and old names must be different."); - } - - await user.Rename(newName); - - OnUserUpdated(user); - } - - /// - /// Updates the user. - /// - /// The user. - /// user - /// - public void UpdateUser(User user) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - if (user.Id.Equals(Guid.Empty) || !Users.Any(u => u.Id.Equals(user.Id))) - { - throw new ArgumentException(string.Format("User with name '{0}' and Id {1} does not exist.", user.Name, user.Id)); - } - - user.DateModified = DateTime.UtcNow; - user.DateLastSaved = DateTime.UtcNow; - - UserRepository.UpdateUser(user); - - OnUserUpdated(user); - } - - public event EventHandler> UserCreated; - - private readonly SemaphoreSlim _userListLock = new SemaphoreSlim(1, 1); - - /// - /// Creates the user. - /// - /// The name. - /// User. - /// name - /// - public async Task CreateUser(string name) - { - if (string.IsNullOrWhiteSpace(name)) - { - throw new ArgumentNullException(nameof(name)); - } - - if (!IsValidUsername(name)) - { - throw new ArgumentException("Usernames can contain letters (a-z), numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)"); - } - - if (Users.Any(u => u.Name.Equals(name, StringComparison.OrdinalIgnoreCase))) - { - 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 list = Users.ToList(); - list.Add(user); - _users = list.ToArray(); - - user.DateLastSaved = DateTime.UtcNow; - - UserRepository.CreateUser(user); - - EventHelper.QueueEventIfNotNull(UserCreated, this, new GenericEventArgs { Argument = user }, _logger); - - return user; - } - finally - { - _userListLock.Release(); - } - } - - /// - /// Deletes the user. - /// - /// The user. - /// Task. - /// user - /// - public async Task DeleteUser(User user) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - var allUsers = Users.ToList(); - - if (allUsers.FirstOrDefault(u => u.Id == user.Id) == null) - { - 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)); - } - - if (allUsers.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)); - } - - if (user.Policy.IsAdministrator && allUsers.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)); - } - - await _userListLock.WaitAsync(CancellationToken.None).ConfigureAwait(false); - - 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); - } - finally - { - _userListLock.Release(); - } - } - - /// - /// Resets the password by clearing it. - /// - /// Task. - public Task ResetPassword(User user) - { - return ChangePassword(user, string.Empty); - } - - public void ResetEasyPassword(User user) - { - ChangeEasyPassword(user, string.Empty, null); - } - - public async Task ChangePassword(User user, string newPassword) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - await GetAuthenticationProvider(user).ChangePassword(user, newPassword).ConfigureAwait(false); - - UpdateUser(user); - - UserPasswordChanged?.Invoke(this, new GenericEventArgs(user)); - } - - public void ChangeEasyPassword(User user, string newPassword, string newPasswordHash) - { - if (user == null) - { - 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; - - UpdateUser(user); - - UserPasswordChanged?.Invoke(this, new GenericEventArgs(user)); - } - - /// - /// Instantiates the new user. - /// - /// The name. - /// User. - private static User InstantiateNewUser(string name) - { - return new User - { - Name = name, - Id = Guid.NewGuid(), - DateCreated = DateTime.UtcNow, - DateModified = DateTime.UtcNow, - UsesIdForConfigurationPath = true, - //Salt = BCrypt.GenerateSalt() - }; - } - - private string PasswordResetFile => Path.Combine(ConfigurationManager.ApplicationPaths.ProgramDataPath, "passwordreset.txt"); - - private string _lastPin; - private PasswordPinCreationResult _lastPasswordPinCreationResult; - private int _pinAttempts; - - private async Task CreatePasswordResetPin() - { - var num = new Random().Next(1, 9999); - - var path = PasswordResetFile; - - var pin = num.ToString("0000", CultureInfo.InvariantCulture); - _lastPin = pin; - - var time = TimeSpan.FromMinutes(5); - var expiration = DateTime.UtcNow.Add(time); - - var text = new StringBuilder(); - - var localAddress = (await _appHost.GetLocalApiUrl(CancellationToken.None).ConfigureAwait(false)) ?? string.Empty; - - text.AppendLine("Use your web browser to visit:"); - text.AppendLine(string.Empty); - text.AppendLine(localAddress + "/web/index.html#!/forgotpasswordpin.html"); - text.AppendLine(string.Empty); - text.AppendLine("Enter the following pin code:"); - text.AppendLine(string.Empty); - text.AppendLine(pin); - text.AppendLine(string.Empty); - - var localExpirationTime = expiration.ToLocalTime(); - // Tuesday, 22 August 2006 06:30 AM - text.AppendLine("The pin code will expire at " + localExpirationTime.ToString("f1", CultureInfo.CurrentCulture)); - - File.WriteAllText(path, text.ToString(), Encoding.UTF8); - - var result = new PasswordPinCreationResult - { - PinFile = path, - ExpirationDate = expiration - }; - - _lastPasswordPinCreationResult = result; - _pinAttempts = 0; - - return result; - } - - public async Task StartForgotPasswordProcess(string enteredUsername, bool isInNetwork) - { - DeletePinFile(); - - var user = string.IsNullOrWhiteSpace(enteredUsername) ? - null : - GetUserByName(enteredUsername); - - var action = ForgotPasswordAction.InNetworkRequired; - string pinFile = null; - DateTime? expirationDate = null; - - if (user != null && !user.Policy.IsAdministrator) - { - action = ForgotPasswordAction.ContactAdmin; - } - else - { - if (isInNetwork) - { - action = ForgotPasswordAction.PinCode; - } - - var result = await CreatePasswordResetPin().ConfigureAwait(false); - pinFile = result.PinFile; - expirationDate = result.ExpirationDate; - } - - return new ForgotPasswordResult - { - Action = action, - PinFile = pinFile, - PinExpirationDate = expirationDate - }; - } - - public async Task RedeemPasswordResetPin(string pin) - { - DeletePinFile(); - - var usersReset = new List(); - - var valid = !string.IsNullOrWhiteSpace(_lastPin) && - string.Equals(_lastPin, pin, StringComparison.OrdinalIgnoreCase) && - _lastPasswordPinCreationResult != null && - _lastPasswordPinCreationResult.ExpirationDate > DateTime.UtcNow; - - if (valid) - { - _lastPin = null; - _lastPasswordPinCreationResult = null; - - foreach (var user in Users) - { - await ResetPassword(user).ConfigureAwait(false); - - if (user.Policy.IsDisabled) - { - user.Policy.IsDisabled = false; - UpdateUserPolicy(user, user.Policy, true); - } - usersReset.Add(user.Name); - } - } - else - { - _pinAttempts++; - if (_pinAttempts >= 3) - { - _lastPin = null; - _lastPasswordPinCreationResult = null; - } - } - - return new PinRedeemResult - { - Success = valid, - UsersReset = usersReset.ToArray() - }; - } - - private void DeletePinFile() - { - try - { - _fileSystem.DeleteFile(PasswordResetFile); - } - catch - { - - } - } - - class PasswordPinCreationResult - { - public string PinFile { get; set; } - public DateTime ExpirationDate { get; set; } - } - - public UserPolicy GetUserPolicy(User user) - { - var path = GetPolicyFilePath(user); - - if (!File.Exists(path)) - { - return GetDefaultPolicy(user); - } - - try - { - lock (_policySyncLock) - { - return (UserPolicy)_xmlSerializer.DeserializeFromFile(typeof(UserPolicy), path); - } - } - catch (IOException) - { - return GetDefaultPolicy(user); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error reading policy file: {path}", path); - - return GetDefaultPolicy(user); - } - } - - private static UserPolicy GetDefaultPolicy(User user) - { - return new UserPolicy - { - EnableContentDownloading = true, - EnableSyncTranscoding = true - }; - } - - private readonly object _policySyncLock = new object(); - public void UpdateUserPolicy(Guid userId, UserPolicy userPolicy) - { - var user = GetUserById(userId); - UpdateUserPolicy(user, userPolicy, true); - } - - private void UpdateUserPolicy(User user, UserPolicy userPolicy, bool fireEvent) - { - // The xml serializer will output differently if the type is not exact - if (userPolicy.GetType() != typeof(UserPolicy)) - { - var json = _jsonSerializer.SerializeToString(userPolicy); - userPolicy = _jsonSerializer.DeserializeFromString(json); - } - - var path = GetPolicyFilePath(user); - - Directory.CreateDirectory(Path.GetDirectoryName(path)); - - lock (_policySyncLock) - { - _xmlSerializer.SerializeToFile(userPolicy, path); - user.Policy = userPolicy; - } - - if (fireEvent) - { - UserPolicyUpdated?.Invoke(this, new GenericEventArgs { Argument = user }); - } - } - - private void DeleteUserPolicy(User user) - { - var path = GetPolicyFilePath(user); - - try - { - lock (_policySyncLock) - { - _fileSystem.DeleteFile(path); - } - } - catch (IOException) - { - - } - catch (Exception ex) - { - _logger.LogError(ex, "Error deleting policy file"); - } - } - - private static string GetPolicyFilePath(User user) - { - return Path.Combine(user.ConfigurationDirectoryPath, "policy.xml"); - } - - private static string GetConfigurationFilePath(User user) - { - return Path.Combine(user.ConfigurationDirectoryPath, "config.xml"); - } - - public UserConfiguration GetUserConfiguration(User user) - { - var path = GetConfigurationFilePath(user); - - if (!File.Exists(path)) - { - return new UserConfiguration(); - } - - try - { - lock (_configSyncLock) - { - return (UserConfiguration)_xmlSerializer.DeserializeFromFile(typeof(UserConfiguration), path); - } - } - catch (IOException) - { - return new UserConfiguration(); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error reading policy file: {path}", path); - - return new UserConfiguration(); - } - } - - private readonly object _configSyncLock = new object(); - public void UpdateConfiguration(Guid userId, UserConfiguration config) - { - var user = GetUserById(userId); - UpdateConfiguration(user, config); - } - - public void UpdateConfiguration(User user, UserConfiguration config) - { - UpdateConfiguration(user, config, true); - } - - private void UpdateConfiguration(User user, UserConfiguration config, bool fireEvent) - { - var path = GetConfigurationFilePath(user); - - // The xml serializer will output differently if the type is not exact - if (config.GetType() != typeof(UserConfiguration)) - { - var json = _jsonSerializer.SerializeToString(config); - config = _jsonSerializer.DeserializeFromString(json); - } - - Directory.CreateDirectory(Path.GetDirectoryName(path)); - - lock (_configSyncLock) - { - _xmlSerializer.SerializeToFile(config, path); - user.Configuration = config; - } - - if (fireEvent) - { - UserConfigurationUpdated?.Invoke(this, new GenericEventArgs { Argument = user }); - } - } - } - - public class DeviceAccessEntryPoint : IServerEntryPoint - { - private IUserManager _userManager; - private IAuthenticationRepository _authRepo; - private IDeviceManager _deviceManager; - private ISessionManager _sessionManager; - - public DeviceAccessEntryPoint(IUserManager userManager, IAuthenticationRepository authRepo, IDeviceManager deviceManager, ISessionManager sessionManager) - { - _userManager = userManager; - _authRepo = authRepo; - _deviceManager = deviceManager; - _sessionManager = sessionManager; - } - - public Task RunAsync() - { - _userManager.UserPolicyUpdated += _userManager_UserPolicyUpdated; - - return Task.CompletedTask; - } - - private void _userManager_UserPolicyUpdated(object sender, GenericEventArgs e) - { - var user = e.Argument; - if (!user.Policy.EnableAllDevices) - { - UpdateDeviceAccess(user); - } - } - - private void UpdateDeviceAccess(User user) - { - var existing = _authRepo.Get(new AuthenticationInfoQuery - { - UserId = user.Id - - }).Items; - - foreach (var authInfo in existing) - { - if (!string.IsNullOrEmpty(authInfo.DeviceId) && !_deviceManager.CanAccessDevice(user, authInfo.DeviceId)) - { - _sessionManager.Logout(authInfo); - } - } - } - - public void Dispose() - { - - } - } -} + } + + public string MakeValidUsername(string username) + { + if (IsValidUsername(username)) + { + return username; + } + + // Usernames can contain letters (a-z), numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.) + var builder = new StringBuilder(); + + foreach (var c in username) + { + if (IsValidUsernameCharacter(c)) + { + builder.Append(c); + } + } + return builder.ToString(); + } + + public async Task AuthenticateUser(string username, string password, string hashedPassword, string remoteEndPoint, bool isUserSession) + { + if (string.IsNullOrWhiteSpace(username)) + { + throw new ArgumentNullException(nameof(username)); + } + + var user = Users + .FirstOrDefault(i => string.Equals(username, i.Name, StringComparison.OrdinalIgnoreCase)); + + var success = false; + IAuthenticationProvider authenticationProvider = null; + + if (user != null) + { + var authResult = await AuthenticateLocalUser(username, password, hashedPassword, user, remoteEndPoint).ConfigureAwait(false); + authenticationProvider = authResult.Item1; + success = authResult.Item2; + } + else + { + // user is null + var authResult = await AuthenticateLocalUser(username, password, hashedPassword, null, remoteEndPoint).ConfigureAwait(false); + authenticationProvider = authResult.Item1; + success = authResult.Item2; + + if (success && authenticationProvider != null && !(authenticationProvider is DefaultAuthenticationProvider)) + { + user = await CreateUser(username).ConfigureAwait(false); + + var hasNewUserPolicy = authenticationProvider as IHasNewUserPolicy; + if (hasNewUserPolicy != null) + { + var policy = hasNewUserPolicy.GetNewUserPolicy(); + UpdateUserPolicy(user, policy, true); + } + } + } + + if (success && user != null && authenticationProvider != null) + { + var providerId = GetAuthenticationProviderId(authenticationProvider); + + if (!string.Equals(providerId, user.Policy.AuthenticationProviderId, StringComparison.OrdinalIgnoreCase)) + { + user.Policy.AuthenticationProviderId = providerId; + UpdateUserPolicy(user, user.Policy, true); + } + } + + if (user == null) + { + throw new SecurityException("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)); + } + + if (!user.Policy.EnableRemoteAccess && !_networkManager.IsInLocalNetwork(remoteEndPoint)) + { + throw new SecurityException("Forbidden."); + } + + if (!user.IsParentalScheduleAllowed()) + { + throw new SecurityException("User is not allowed access at this time."); + } + + // Update LastActivityDate and LastLoginDate, then save + if (success) + { + if (isUserSession) + { + user.LastActivityDate = user.LastLoginDate = DateTime.UtcNow; + UpdateUser(user); + } + UpdateInvalidLoginAttemptCount(user, 0); + } + else + { + UpdateInvalidLoginAttemptCount(user, user.Policy.InvalidLoginAttemptCount + 1); + } + + _logger.LogInformation("Authentication request for {0} {1}.", user.Name, success ? "has succeeded" : "has been denied"); + + return success ? user : null; + } + + private static string GetAuthenticationProviderId(IAuthenticationProvider provider) + { + return provider.GetType().FullName; + } + + private IAuthenticationProvider GetAuthenticationProvider(User user) + { + return GetAuthenticationProviders(user).First(); + } + + private IAuthenticationProvider[] GetAuthenticationProviders(User user) + { + var authenticationProviderId = user == null ? null : user.Policy.AuthenticationProviderId; + + var providers = _authenticationProviders.Where(i => i.IsEnabled).ToArray(); + + if (!string.IsNullOrEmpty(authenticationProviderId)) + { + providers = providers.Where(i => string.Equals(authenticationProviderId, GetAuthenticationProviderId(i), StringComparison.OrdinalIgnoreCase)).ToArray(); + } + + if (providers.Length == 0) + { + providers = new IAuthenticationProvider[] { _defaultAuthenticationProvider }; + } + + return providers; + } + + private async Task AuthenticateWithProvider(IAuthenticationProvider provider, string username, string password, User resolvedUser) + { + try + { + var requiresResolvedUser = provider as IRequiresResolvedUser; + if (requiresResolvedUser != null) + { + await requiresResolvedUser.Authenticate(username, password, resolvedUser).ConfigureAwait(false); + } + else + { + await provider.Authenticate(username, password).ConfigureAwait(false); + } + + return true; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error authenticating with provider {provider}", provider.Name); + + return false; + } + } + + private async Task> AuthenticateLocalUser(string username, string password, string hashedPassword, User user, string remoteEndPoint) + { + bool success = false; + IAuthenticationProvider authenticationProvider = null; + + if (password != null && user != null) + { + // Doesn't look like this is even possible to be used, because of password == null checks below + hashedPassword = _defaultAuthenticationProvider.GetHashedString(user, password); + } + + if (password == null) + { + // legacy + success = string.Equals(_defaultAuthenticationProvider.GetPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase); + } + else + { + foreach (var provider in GetAuthenticationProviders(user)) + { + success = await AuthenticateWithProvider(provider, username, password, user).ConfigureAwait(false); + + if (success) + { + authenticationProvider = provider; + break; + } + } + } + + if (user != null) + { + if (!success && _networkManager.IsInLocalNetwork(remoteEndPoint) && user.Configuration.EnableLocalPassword) + { + 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); + } + } + } + + return new Tuple(authenticationProvider, success); + } + + private void UpdateInvalidLoginAttemptCount(User user, int newValue) + { + if (user.Policy.InvalidLoginAttemptCount == newValue || newValue <= 0) + { + return; + } + + user.Policy.InvalidLoginAttemptCount = newValue; + + var maxCount = user.Policy.IsAdministrator ? 3 : 5; + + var fireLockout = false; + + if (newValue >= maxCount) + { + _logger.LogDebug("Disabling user {0} due to {1} unsuccessful login attempts.", user.Name, newValue); + user.Policy.IsDisabled = true; + + fireLockout = true; + } + + UpdateUserPolicy(user, user.Policy, false); + + if (fireLockout) + { + UserLockedOut?.Invoke(this, new GenericEventArgs(user)); + } + } + + private string GetLocalPasswordHash(User user) + { + return string.IsNullOrEmpty(user.EasyPassword) + ? _defaultAuthenticationProvider.GetEmptyHashedString(user) + : user.EasyPassword; + } + + private bool IsPasswordEmpty(User user, string passwordHash) + { + return string.Equals(passwordHash, _defaultAuthenticationProvider.GetEmptyHashedString(user), StringComparison.OrdinalIgnoreCase); + } + + /// + /// Loads the users from the repository + /// + /// IEnumerable{User}. + private User[] LoadUsers() + { + var users = UserRepository.RetrieveAllUsers(); + + // There always has to be at least one user. + if (users.Count == 0) + { + var defaultName = Environment.UserName; + if (string.IsNullOrWhiteSpace(defaultName)) + { + defaultName = "MyJellyfinUser"; + } + var name = MakeValidUsername(defaultName); + + var user = InstantiateNewUser(name); + + user.DateLastSaved = DateTime.UtcNow; + + UserRepository.CreateUser(user); + + users.Add(user); + + user.Policy.IsAdministrator = true; + user.Policy.EnableContentDeletion = true; + user.Policy.EnableRemoteControlOfOtherUsers = true; + UpdateUserPolicy(user, user.Policy, false); + } + + return users.ToArray(); + } + + public UserDto GetUserDto(User user, string remoteEndPoint = null) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + var hasConfiguredPassword = GetAuthenticationProvider(user).HasPassword(user).Result; + var hasConfiguredEasyPassword = !IsPasswordEmpty(user, GetLocalPasswordHash(user)); + + var hasPassword = user.Configuration.EnableLocalPassword && !string.IsNullOrEmpty(remoteEndPoint) && _networkManager.IsInLocalNetwork(remoteEndPoint) ? + hasConfiguredEasyPassword : + hasConfiguredPassword; + + var dto = new UserDto + { + Id = user.Id, + Name = user.Name, + HasPassword = hasPassword, + HasConfiguredPassword = hasConfiguredPassword, + HasConfiguredEasyPassword = hasConfiguredEasyPassword, + LastActivityDate = user.LastActivityDate, + LastLoginDate = user.LastLoginDate, + Configuration = user.Configuration, + ServerId = _appHost.SystemId, + Policy = user.Policy + }; + + if (!hasPassword && Users.Count() == 1) + { + dto.EnableAutoLogin = true; + } + + var image = user.GetImageInfo(ImageType.Primary, 0); + + if (image != null) + { + dto.PrimaryImageTag = GetImageCacheTag(user, image); + + try + { + _dtoServiceFactory().AttachPrimaryImageAspectRatio(dto, user); + } + catch (Exception ex) + { + // Have to use a catch-all unfortunately because some .net image methods throw plain Exceptions + _logger.LogError(ex, "Error generating PrimaryImageAspectRatio for {user}", user.Name); + } + } + + return dto; + } + + public UserDto GetOfflineUserDto(User user) + { + var dto = GetUserDto(user); + + dto.ServerName = _appHost.FriendlyName; + + return dto; + } + + private string GetImageCacheTag(BaseItem item, ItemImageInfo image) + { + try + { + return _imageProcessorFactory().GetImageCacheTag(item, image); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error getting {imageType} image info for {imagePath}", image.Type, image.Path); + return null; + } + } + + /// + /// Refreshes metadata for each user + /// + /// The cancellation token. + /// Task. + public async Task RefreshUsersMetadata(CancellationToken cancellationToken) + { + foreach (var user in Users) + { + await user.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)), cancellationToken).ConfigureAwait(false); + } + } + + /// + /// Renames the user. + /// + /// The user. + /// The new name. + /// Task. + /// user + /// + public async Task RenameUser(User user, string newName) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + if (string.IsNullOrEmpty(newName)) + { + throw new ArgumentNullException(nameof(newName)); + } + + if (Users.Any(u => u.Id != user.Id && u.Name.Equals(newName, StringComparison.OrdinalIgnoreCase))) + { + throw new ArgumentException(string.Format("A user with the name '{0}' already exists.", newName)); + } + + if (user.Name.Equals(newName, StringComparison.Ordinal)) + { + throw new ArgumentException("The new and old names must be different."); + } + + await user.Rename(newName); + + OnUserUpdated(user); + } + + /// + /// Updates the user. + /// + /// The user. + /// user + /// + public void UpdateUser(User user) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + if (user.Id.Equals(Guid.Empty) || !Users.Any(u => u.Id.Equals(user.Id))) + { + throw new ArgumentException(string.Format("User with name '{0}' and Id {1} does not exist.", user.Name, user.Id)); + } + + user.DateModified = DateTime.UtcNow; + user.DateLastSaved = DateTime.UtcNow; + + UserRepository.UpdateUser(user); + + OnUserUpdated(user); + } + + public event EventHandler> UserCreated; + + private readonly SemaphoreSlim _userListLock = new SemaphoreSlim(1, 1); + + /// + /// Creates the user. + /// + /// The name. + /// User. + /// name + /// + public async Task CreateUser(string name) + { + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentNullException(nameof(name)); + } + + if (!IsValidUsername(name)) + { + throw new ArgumentException("Usernames can contain unicode symbols, numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)"); + } + + if (Users.Any(u => u.Name.Equals(name, StringComparison.OrdinalIgnoreCase))) + { + 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 list = Users.ToList(); + list.Add(user); + _users = list.ToArray(); + + user.DateLastSaved = DateTime.UtcNow; + + UserRepository.CreateUser(user); + + EventHelper.QueueEventIfNotNull(UserCreated, this, new GenericEventArgs { Argument = user }, _logger); + + return user; + } + finally + { + _userListLock.Release(); + } + } + + /// + /// Deletes the user. + /// + /// The user. + /// Task. + /// user + /// + public async Task DeleteUser(User user) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + var allUsers = Users.ToList(); + + if (allUsers.FirstOrDefault(u => u.Id == user.Id) == null) + { + 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)); + } + + if (allUsers.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)); + } + + if (user.Policy.IsAdministrator && allUsers.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)); + } + + await _userListLock.WaitAsync(CancellationToken.None).ConfigureAwait(false); + + 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); + } + finally + { + _userListLock.Release(); + } + } + + /// + /// Resets the password by clearing it. + /// + /// Task. + public Task ResetPassword(User user) + { + return ChangePassword(user, string.Empty); + } + + public void ResetEasyPassword(User user) + { + ChangeEasyPassword(user, string.Empty, null); + } + + public async Task ChangePassword(User user, string newPassword) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + await GetAuthenticationProvider(user).ChangePassword(user, newPassword).ConfigureAwait(false); + + UpdateUser(user); + + UserPasswordChanged?.Invoke(this, new GenericEventArgs(user)); + } + + public void ChangeEasyPassword(User user, string newPassword, string newPasswordHash) + { + if (user == null) + { + 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; + + UpdateUser(user); + + UserPasswordChanged?.Invoke(this, new GenericEventArgs(user)); + } + + /// + /// Instantiates the new user. + /// + /// The name. + /// User. + private static User InstantiateNewUser(string name) + { + return new User + { + Name = name, + Id = Guid.NewGuid(), + DateCreated = DateTime.UtcNow, + DateModified = DateTime.UtcNow, + UsesIdForConfigurationPath = true, + //Salt = BCrypt.GenerateSalt() + }; + } + + private string PasswordResetFile => Path.Combine(ConfigurationManager.ApplicationPaths.ProgramDataPath, "passwordreset.txt"); + + private string _lastPin; + private PasswordPinCreationResult _lastPasswordPinCreationResult; + private int _pinAttempts; + + private async Task CreatePasswordResetPin() + { + var num = new Random().Next(1, 9999); + + var path = PasswordResetFile; + + var pin = num.ToString("0000", CultureInfo.InvariantCulture); + _lastPin = pin; + + var time = TimeSpan.FromMinutes(5); + var expiration = DateTime.UtcNow.Add(time); + + var text = new StringBuilder(); + + var localAddress = (await _appHost.GetLocalApiUrl(CancellationToken.None).ConfigureAwait(false)) ?? string.Empty; + + text.AppendLine("Use your web browser to visit:"); + text.AppendLine(string.Empty); + text.AppendLine(localAddress + "/web/index.html#!/forgotpasswordpin.html"); + text.AppendLine(string.Empty); + text.AppendLine("Enter the following pin code:"); + text.AppendLine(string.Empty); + text.AppendLine(pin); + text.AppendLine(string.Empty); + + var localExpirationTime = expiration.ToLocalTime(); + // Tuesday, 22 August 2006 06:30 AM + text.AppendLine("The pin code will expire at " + localExpirationTime.ToString("f1", CultureInfo.CurrentCulture)); + + File.WriteAllText(path, text.ToString(), Encoding.UTF8); + + var result = new PasswordPinCreationResult + { + PinFile = path, + ExpirationDate = expiration + }; + + _lastPasswordPinCreationResult = result; + _pinAttempts = 0; + + return result; + } + + public async Task StartForgotPasswordProcess(string enteredUsername, bool isInNetwork) + { + DeletePinFile(); + + var user = string.IsNullOrWhiteSpace(enteredUsername) ? + null : + GetUserByName(enteredUsername); + + var action = ForgotPasswordAction.InNetworkRequired; + string pinFile = null; + DateTime? expirationDate = null; + + if (user != null && !user.Policy.IsAdministrator) + { + action = ForgotPasswordAction.ContactAdmin; + } + else + { + if (isInNetwork) + { + action = ForgotPasswordAction.PinCode; + } + + var result = await CreatePasswordResetPin().ConfigureAwait(false); + pinFile = result.PinFile; + expirationDate = result.ExpirationDate; + } + + return new ForgotPasswordResult + { + Action = action, + PinFile = pinFile, + PinExpirationDate = expirationDate + }; + } + + public async Task RedeemPasswordResetPin(string pin) + { + DeletePinFile(); + + var usersReset = new List(); + + var valid = !string.IsNullOrWhiteSpace(_lastPin) && + string.Equals(_lastPin, pin, StringComparison.OrdinalIgnoreCase) && + _lastPasswordPinCreationResult != null && + _lastPasswordPinCreationResult.ExpirationDate > DateTime.UtcNow; + + if (valid) + { + _lastPin = null; + _lastPasswordPinCreationResult = null; + + foreach (var user in Users) + { + await ResetPassword(user).ConfigureAwait(false); + + if (user.Policy.IsDisabled) + { + user.Policy.IsDisabled = false; + UpdateUserPolicy(user, user.Policy, true); + } + usersReset.Add(user.Name); + } + } + else + { + _pinAttempts++; + if (_pinAttempts >= 3) + { + _lastPin = null; + _lastPasswordPinCreationResult = null; + } + } + + return new PinRedeemResult + { + Success = valid, + UsersReset = usersReset.ToArray() + }; + } + + private void DeletePinFile() + { + try + { + _fileSystem.DeleteFile(PasswordResetFile); + } + catch + { + + } + } + + class PasswordPinCreationResult + { + public string PinFile { get; set; } + public DateTime ExpirationDate { get; set; } + } + + public UserPolicy GetUserPolicy(User user) + { + var path = GetPolicyFilePath(user); + + if (!File.Exists(path)) + { + return GetDefaultPolicy(user); + } + + try + { + lock (_policySyncLock) + { + return (UserPolicy)_xmlSerializer.DeserializeFromFile(typeof(UserPolicy), path); + } + } + catch (IOException) + { + return GetDefaultPolicy(user); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error reading policy file: {path}", path); + + return GetDefaultPolicy(user); + } + } + + private static UserPolicy GetDefaultPolicy(User user) + { + return new UserPolicy + { + EnableContentDownloading = true, + EnableSyncTranscoding = true + }; + } + + private readonly object _policySyncLock = new object(); + public void UpdateUserPolicy(Guid userId, UserPolicy userPolicy) + { + var user = GetUserById(userId); + UpdateUserPolicy(user, userPolicy, true); + } + + private void UpdateUserPolicy(User user, UserPolicy userPolicy, bool fireEvent) + { + // The xml serializer will output differently if the type is not exact + if (userPolicy.GetType() != typeof(UserPolicy)) + { + var json = _jsonSerializer.SerializeToString(userPolicy); + userPolicy = _jsonSerializer.DeserializeFromString(json); + } + + var path = GetPolicyFilePath(user); + + Directory.CreateDirectory(Path.GetDirectoryName(path)); + + lock (_policySyncLock) + { + _xmlSerializer.SerializeToFile(userPolicy, path); + user.Policy = userPolicy; + } + + if (fireEvent) + { + UserPolicyUpdated?.Invoke(this, new GenericEventArgs { Argument = user }); + } + } + + private void DeleteUserPolicy(User user) + { + var path = GetPolicyFilePath(user); + + try + { + lock (_policySyncLock) + { + _fileSystem.DeleteFile(path); + } + } + catch (IOException) + { + + } + catch (Exception ex) + { + _logger.LogError(ex, "Error deleting policy file"); + } + } + + private static string GetPolicyFilePath(User user) + { + return Path.Combine(user.ConfigurationDirectoryPath, "policy.xml"); + } + + private static string GetConfigurationFilePath(User user) + { + return Path.Combine(user.ConfigurationDirectoryPath, "config.xml"); + } + + public UserConfiguration GetUserConfiguration(User user) + { + var path = GetConfigurationFilePath(user); + + if (!File.Exists(path)) + { + return new UserConfiguration(); + } + + try + { + lock (_configSyncLock) + { + return (UserConfiguration)_xmlSerializer.DeserializeFromFile(typeof(UserConfiguration), path); + } + } + catch (IOException) + { + return new UserConfiguration(); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error reading policy file: {path}", path); + + return new UserConfiguration(); + } + } + + private readonly object _configSyncLock = new object(); + public void UpdateConfiguration(Guid userId, UserConfiguration config) + { + var user = GetUserById(userId); + UpdateConfiguration(user, config); + } + + public void UpdateConfiguration(User user, UserConfiguration config) + { + UpdateConfiguration(user, config, true); + } + + private void UpdateConfiguration(User user, UserConfiguration config, bool fireEvent) + { + var path = GetConfigurationFilePath(user); + + // The xml serializer will output differently if the type is not exact + if (config.GetType() != typeof(UserConfiguration)) + { + var json = _jsonSerializer.SerializeToString(config); + config = _jsonSerializer.DeserializeFromString(json); + } + + Directory.CreateDirectory(Path.GetDirectoryName(path)); + + lock (_configSyncLock) + { + _xmlSerializer.SerializeToFile(config, path); + user.Configuration = config; + } + + if (fireEvent) + { + UserConfigurationUpdated?.Invoke(this, new GenericEventArgs { Argument = user }); + } + } + } + + public class DeviceAccessEntryPoint : IServerEntryPoint + { + private IUserManager _userManager; + private IAuthenticationRepository _authRepo; + private IDeviceManager _deviceManager; + private ISessionManager _sessionManager; + + public DeviceAccessEntryPoint(IUserManager userManager, IAuthenticationRepository authRepo, IDeviceManager deviceManager, ISessionManager sessionManager) + { + _userManager = userManager; + _authRepo = authRepo; + _deviceManager = deviceManager; + _sessionManager = sessionManager; + } + + public Task RunAsync() + { + _userManager.UserPolicyUpdated += _userManager_UserPolicyUpdated; + + return Task.CompletedTask; + } + + private void _userManager_UserPolicyUpdated(object sender, GenericEventArgs e) + { + var user = e.Argument; + if (!user.Policy.EnableAllDevices) + { + UpdateDeviceAccess(user); + } + } + + private void UpdateDeviceAccess(User user) + { + var existing = _authRepo.Get(new AuthenticationInfoQuery + { + UserId = user.Id + + }).Items; + + foreach (var authInfo in existing) + { + if (!string.IsNullOrEmpty(authInfo.DeviceId) && !_deviceManager.CanAccessDevice(user, authInfo.DeviceId)) + { + _sessionManager.Logout(authInfo); + } + } + } + + public void Dispose() + { + + } + } +} diff --git a/MediaBrowser.Model/Cryptography/ICryptoProvider.cs b/MediaBrowser.Model/Cryptography/ICryptoProvider.cs index ec7e57fec..8accc696e 100644 --- a/MediaBrowser.Model/Cryptography/ICryptoProvider.cs +++ b/MediaBrowser.Model/Cryptography/ICryptoProvider.cs @@ -1,15 +1,15 @@ -using System; -using System.IO; -using System.Collections.Generic; - -namespace MediaBrowser.Model.Cryptography -{ - public interface ICryptoProvider - { - Guid GetMD5(string str); - byte[] ComputeMD5(Stream str); - byte[] ComputeMD5(byte[] bytes); - byte[] ComputeSHA1(byte[] bytes); +using System; +using System.IO; +using System.Collections.Generic; + +namespace MediaBrowser.Model.Cryptography +{ + public interface ICryptoProvider + { + Guid GetMD5(string str); + byte[] ComputeMD5(Stream str); + byte[] ComputeMD5(byte[] bytes); + byte[] ComputeSHA1(byte[] bytes); IEnumerable GetSupportedHashMethods(); byte[] ComputeHash(string HashMethod, byte[] bytes); byte[] ComputeHashWithDefaultMethod(byte[] bytes); @@ -17,5 +17,6 @@ namespace MediaBrowser.Model.Cryptography byte[] ComputeHashWithDefaultMethod(byte[] bytes, byte[] salt); byte[] ComputeHash(PasswordHash hash); byte[] GenerateSalt(); - } -} + string DefaultHashMethod { get; } + } +} diff --git a/MediaBrowser.Model/Cryptography/PasswordHash.cs b/MediaBrowser.Model/Cryptography/PasswordHash.cs index d37220ab2..524484b10 100644 --- a/MediaBrowser.Model/Cryptography/PasswordHash.cs +++ b/MediaBrowser.Model/Cryptography/PasswordHash.cs @@ -33,15 +33,15 @@ namespace MediaBrowser.Model.Cryptography if (a.Length == 4) { Salt = a[2]; - SaltBytes = Convert.FromBase64CharArray(Salt.ToCharArray(), 0, Salt.Length); + SaltBytes = FromByteString(Salt); Hash = a[3]; - HashBytes = Convert.FromBase64CharArray(Hash.ToCharArray(), 0, Hash.Length); + HashBytes = FromByteString(Hash); } else { Salt = string.Empty; Hash = a[3]; - HashBytes = Convert.FromBase64CharArray(Hash.ToCharArray(), 0, Hash.Length); + HashBytes = FromByteString(Hash); } } else @@ -49,15 +49,15 @@ namespace MediaBrowser.Model.Cryptography if (a.Length == 4) { Salt = a[2]; - SaltBytes = Convert.FromBase64CharArray(Salt.ToCharArray(), 0, Salt.Length); + SaltBytes = FromByteString(Salt); Hash = a[3]; - HashBytes = Convert.FromBase64CharArray(Hash.ToCharArray(), 0, Hash.Length); + HashBytes = FromByteString(Hash); } else { Salt = string.Empty; Hash = a[2]; - HashBytes = Convert.FromBase64CharArray(Hash.ToCharArray(), 0, Hash.Length); + HashBytes = FromByteString(Hash); } } @@ -68,7 +68,17 @@ namespace MediaBrowser.Model.Cryptography { Id = "SHA256"; SaltBytes = cryptoProvider2.GenerateSalt(); - Salt = Convert.ToBase64String(SaltBytes); + Salt = BitConverter.ToString(SaltBytes).Replace("-", ""); + } + + private byte[] FromByteString(string ByteString) + { + List Bytes = new List(); + for (int i = 0; i < ByteString.Length; i += 2) + { + Bytes.Add(Convert.ToByte(ByteString.Substring(i, 2),16)); + } + return Bytes.ToArray(); } private string SerializeParameters() { @@ -77,7 +87,7 @@ namespace MediaBrowser.Model.Cryptography { ReturnString += String.Format(",{0}={1}", KVP.Key, KVP.Value); } - if (ReturnString[0] == ',') + if ((!string.IsNullOrEmpty(ReturnString)) && ReturnString[0] == ',') { ReturnString = ReturnString.Remove(0, 1); } @@ -85,8 +95,15 @@ namespace MediaBrowser.Model.Cryptography } public override string ToString() - { - return String.Format("${0}${1}${2}${3}", Id, SerializeParameters(), Salt, Hash); + { + string OutString = "$"; + OutString += Id; + if (!string.IsNullOrEmpty(SerializeParameters())) + OutString += $"${SerializeParameters()}"; + if (!string.IsNullOrEmpty(Salt)) + OutString += $"${Salt}"; + OutString += $"${Hash}"; + return OutString; } } -- cgit v1.2.3 From 1ffd443d5aaec408170eaec31923a1cbbe1bb929 Mon Sep 17 00:00:00 2001 From: Phallacy Date: Tue, 12 Feb 2019 22:30:26 -0800 Subject: fixed nul user check to be first per justaman --- .../Library/DefaultAuthenticationProvider.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs index 92346c65a..255fd8252 100644 --- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs @@ -32,15 +32,16 @@ namespace Emby.Server.Implementations.Library //This is the verson that we need to use for local users. Because reasons. public Task Authenticate(string username, string password, User resolvedUser) - { - ConvertPasswordFormat(resolvedUser); - byte[] passwordbytes = Encoding.UTF8.GetBytes(password); - bool success = false; + { + bool success = false; if (resolvedUser == null) { success = false; throw new Exception("Invalid username or password"); } + ConvertPasswordFormat(resolvedUser); + byte[] passwordbytes = Encoding.UTF8.GetBytes(password); + if (!resolvedUser.Password.Contains("$")) { ConvertPasswordFormat(resolvedUser); -- cgit v1.2.3 From 77602aff889e605f8178ecf95592c0d75102e59f Mon Sep 17 00:00:00 2001 From: Phallacy Date: Wed, 13 Feb 2019 00:33:00 -0800 Subject: Minor fixes re:PR870, added null checks from PR876 --- .../Cryptography/CryptographyProvider.cs | 38 +++++++---- .../Data/SqliteUserRepository.cs | 32 +++++++++ .../Library/DefaultAuthenticationProvider.cs | 71 +++++++------------- Emby.Server.Implementations/Library/UserManager.cs | 6 +- MediaBrowser.Model/Cryptography/PasswordHash.cs | 75 +++++++++++++--------- 5 files changed, 124 insertions(+), 98 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs index 4f2bc1b03..7817989e7 100644 --- a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs +++ b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs @@ -9,7 +9,7 @@ namespace Emby.Server.Implementations.Cryptography { public class CryptographyProvider : ICryptoProvider { - private List SupportedHashMethods = new List(); + private HashSet SupportedHashMethods; public string DefaultHashMethod => "SHA256"; private RandomNumberGenerator rng; private int defaultiterations = 1000; @@ -17,7 +17,7 @@ namespace Emby.Server.Implementations.Cryptography { //Currently supported hash methods from https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptoconfig?view=netcore-2.1 //there might be a better way to autogenerate this list as dotnet updates, but I couldn't find one - SupportedHashMethods = new List + SupportedHashMethods = new HashSet() { "MD5" ,"System.Security.Cryptography.MD5" @@ -71,9 +71,9 @@ namespace Emby.Server.Implementations.Cryptography return SupportedHashMethods; } - private byte[] PBKDF2(string method, byte[] bytes, byte[] salt) - { - using (var r = new Rfc2898DeriveBytes(bytes, salt, defaultiterations, new HashAlgorithmName(method))) + private byte[] PBKDF2(string method, byte[] bytes, byte[] salt, int iterations) + { + using (var r = new Rfc2898DeriveBytes(bytes, salt, iterations, new HashAlgorithmName(method))) { return r.GetBytes(32); } @@ -102,30 +102,40 @@ namespace Emby.Server.Implementations.Cryptography } else { - return PBKDF2(HashMethod, bytes, salt); + return PBKDF2(HashMethod, bytes, salt,defaultiterations); } } else { throw new CryptographicException(String.Format("Requested hash method is not supported: {0}", HashMethod)); } - } + } public byte[] ComputeHashWithDefaultMethod(byte[] bytes, byte[] salt) { - return PBKDF2(DefaultHashMethod, bytes, salt); + return PBKDF2(DefaultHashMethod, bytes, salt, defaultiterations); } public byte[] ComputeHash(PasswordHash hash) - { - return ComputeHash(hash.Id, hash.HashBytes, hash.SaltBytes); - } - + { + int iterations = defaultiterations; + if (!hash.Parameters.ContainsKey("iterations")) + { + hash.Parameters.Add("iterations", defaultiterations.ToString()); + } + else + { + try { iterations = int.Parse(hash.Parameters["iterations"]); } + catch (Exception e) { iterations = defaultiterations; throw new Exception($"Couldn't successfully parse iterations value from string:{hash.Parameters["iterations"]}", e); } + } + return PBKDF2(hash.Id, hash.HashBytes, hash.SaltBytes,iterations); + } + public byte[] GenerateSalt() { - byte[] salt = new byte[8]; + byte[] salt = new byte[64]; rng.GetBytes(salt); return salt; - } + } } } diff --git a/Emby.Server.Implementations/Data/SqliteUserRepository.cs b/Emby.Server.Implementations/Data/SqliteUserRepository.cs index db359d7dd..b3d457342 100644 --- a/Emby.Server.Implementations/Data/SqliteUserRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserRepository.cs @@ -55,6 +55,7 @@ namespace Emby.Server.Implementations.Data { TryMigrateToLocalUsersTable(connection); } + RemoveEmptyPasswordHashes(); } } @@ -73,6 +74,37 @@ namespace Emby.Server.Implementations.Data } } + private void RemoveEmptyPasswordHashes() + { + foreach (var user in RetrieveAllUsers()) + { + // If the user password is the sha1 hash of the empty string, remove it + if (!string.Equals(user.Password, "DA39A3EE5E6B4B0D3255BFEF95601890AFD80709") || !string.Equals(user.Password, "$SHA1$DA39A3EE5E6B4B0D3255BFEF95601890AFD80709")) + { + continue; + } + + user.Password = null; + var serialized = _jsonSerializer.SerializeToBytes(user); + + using (WriteLock.Write()) + using (var connection = CreateConnection()) + { + connection.RunInTransaction(db => + { + using (var statement = db.PrepareStatement("update LocalUsersv2 set data=@data where Id=@InternalId")) + { + statement.TryBind("@InternalId", user.InternalId); + statement.TryBind("@data", serialized); + statement.MoveNext(); + } + + }, TransactionMode); + } + } + + } + /// /// Save a user in the repo /// diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs index 255fd8252..ca6217016 100644 --- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs @@ -36,32 +36,27 @@ namespace Emby.Server.Implementations.Library bool success = false; if (resolvedUser == null) { - success = false; throw new Exception("Invalid username or password"); } ConvertPasswordFormat(resolvedUser); byte[] passwordbytes = Encoding.UTF8.GetBytes(password); - - if (!resolvedUser.Password.Contains("$")) - { - ConvertPasswordFormat(resolvedUser); - } - PasswordHash ReadyHash = new PasswordHash(resolvedUser.Password); + + PasswordHash readyHash = new PasswordHash(resolvedUser.Password); byte[] CalculatedHash; string CalculatedHashString; - if (_cryptographyProvider.GetSupportedHashMethods().Any(i => i == ReadyHash.Id)) + if (_cryptographyProvider.GetSupportedHashMethods().Any(i => i == readyHash.Id)) { - if (String.IsNullOrEmpty(ReadyHash.Salt)) + if (String.IsNullOrEmpty(readyHash.Salt)) { - CalculatedHash = _cryptographyProvider.ComputeHash(ReadyHash.Id, passwordbytes); + CalculatedHash = _cryptographyProvider.ComputeHash(readyHash.Id, passwordbytes); CalculatedHashString = BitConverter.ToString(CalculatedHash).Replace("-", string.Empty); } else { - CalculatedHash = _cryptographyProvider.ComputeHash(ReadyHash.Id, passwordbytes, ReadyHash.SaltBytes); + CalculatedHash = _cryptographyProvider.ComputeHash(readyHash.Id, passwordbytes, readyHash.SaltBytes); CalculatedHashString = BitConverter.ToString(CalculatedHash).Replace("-", string.Empty); } - if (CalculatedHashString == ReadyHash.Hash) + if (CalculatedHashString == readyHash.Hash) { success = true; //throw new Exception("Invalid username or password"); @@ -69,8 +64,7 @@ namespace Emby.Server.Implementations.Library } else { - success = false; - throw new Exception(String.Format("Requested crypto method not available in provider: {0}", ReadyHash.Id)); + throw new Exception(String.Format("Requested crypto method not available in provider: {0}", readyHash.Id)); } //var success = string.Equals(GetPasswordHash(resolvedUser), GetHashedString(resolvedUser, password), StringComparison.OrdinalIgnoreCase); @@ -105,26 +99,6 @@ namespace Emby.Server.Implementations.Library } } - // OLD VERSION //public Task Authenticate(string username, string password, User resolvedUser) - // OLD VERSION //{ - // OLD VERSION // if (resolvedUser == null) - // OLD VERSION // { - // OLD VERSION // throw new Exception("Invalid username or password"); - // OLD VERSION // } - // OLD VERSION // - // OLD VERSION // var success = string.Equals(GetPasswordHash(resolvedUser), GetHashedString(resolvedUser, password), StringComparison.OrdinalIgnoreCase); - // OLD VERSION // - // OLD VERSION // if (!success) - // OLD VERSION // { - // OLD VERSION // throw new Exception("Invalid username or password"); - // OLD VERSION // } - // OLD VERSION // - // OLD VERSION // return Task.FromResult(new ProviderAuthenticationResult - // OLD VERSION // { - // OLD VERSION // Username = username - // OLD VERSION // }); - // OLD VERSION //} - public Task HasPassword(User user) { var hasConfiguredPassword = !IsPasswordEmpty(user, GetPasswordHash(user)); @@ -133,7 +107,7 @@ namespace Emby.Server.Implementations.Library private bool IsPasswordEmpty(User user, string passwordHash) { - return string.Equals(passwordHash, GetEmptyHashedString(user), StringComparison.OrdinalIgnoreCase); + return string.IsNullOrEmpty(passwordHash); } public Task ChangePassword(User user, string newPassword) @@ -144,7 +118,7 @@ namespace Emby.Server.Implementations.Library if(passwordHash.Id == "SHA1" && string.IsNullOrEmpty(passwordHash.Salt)) { passwordHash.SaltBytes = _cryptographyProvider.GenerateSalt(); - passwordHash.Salt = BitConverter.ToString(passwordHash.SaltBytes).Replace("-",""); + passwordHash.Salt = PasswordHash.ConvertToByteString(passwordHash.SaltBytes); passwordHash.Id = _cryptographyProvider.DefaultHashMethod; passwordHash.Hash = GetHashedStringChangeAuth(newPassword, passwordHash); }else if (newPassword != null) @@ -164,19 +138,18 @@ namespace Emby.Server.Implementations.Library public string GetPasswordHash(User user) { - return string.IsNullOrEmpty(user.Password) - ? GetEmptyHashedString(user) - : user.Password; + return user.Password; } public string GetEmptyHashedString(User user) { - return GetHashedString(user, string.Empty); + return null; } - public string GetHashedStringChangeAuth(string NewPassword, PasswordHash passwordHash) + public string GetHashedStringChangeAuth(string newPassword, PasswordHash passwordHash) { - return BitConverter.ToString(_cryptographyProvider.ComputeHash(passwordHash.Id, Encoding.UTF8.GetBytes(NewPassword), passwordHash.SaltBytes)).Replace("-", string.Empty); + passwordHash.HashBytes = Encoding.UTF8.GetBytes(newPassword); + return PasswordHash.ConvertToByteString(_cryptographyProvider.ComputeHash(passwordHash)); } /// @@ -184,8 +157,6 @@ namespace Emby.Server.Implementations.Library /// public string GetHashedString(User user, string str) { - //This is legacy. Deprecated in the auth method. - //return BitConverter.ToString(_cryptoProvider2.ComputeSHA1(Encoding.UTF8.GetBytes(str))).Replace("-", string.Empty); PasswordHash passwordHash; if (String.IsNullOrEmpty(user.Password)) { @@ -197,13 +168,15 @@ namespace Emby.Server.Implementations.Library passwordHash = new PasswordHash(user.Password); } if (passwordHash.SaltBytes != null) - { - return BitConverter.ToString(_cryptographyProvider.ComputeHash(passwordHash.Id, Encoding.UTF8.GetBytes(str), passwordHash.SaltBytes)).Replace("-",string.Empty); + { + //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 - { - return BitConverter.ToString(_cryptographyProvider.ComputeHash(passwordHash.Id, Encoding.UTF8.GetBytes(str))).Replace("-", string.Empty); - //throw new Exception("User does not have a hash, this should not be possible"); + { + //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))); } diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index a139c4e73..b8777a480 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -217,9 +217,8 @@ namespace Emby.Server.Implementations.Library } } - public bool IsValidUsername(string username) + public static bool IsValidUsername(string username) { - //The old way was dumb, we should make it less dumb, lets do so. //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 string UserNameRegex = "^[\\w-'._@]*$"; @@ -229,8 +228,7 @@ namespace Emby.Server.Implementations.Library private static bool IsValidUsernameCharacter(char i) { - string UserNameRegex = "^[\\w-'._@]*$"; - return Regex.IsMatch(i.ToString(), UserNameRegex); + return IsValidUsername(i.ToString()); } public string MakeValidUsername(string username) diff --git a/MediaBrowser.Model/Cryptography/PasswordHash.cs b/MediaBrowser.Model/Cryptography/PasswordHash.cs index 524484b10..cd61657c1 100644 --- a/MediaBrowser.Model/Cryptography/PasswordHash.cs +++ b/MediaBrowser.Model/Cryptography/PasswordHash.cs @@ -16,70 +16,78 @@ namespace MediaBrowser.Model.Cryptography public byte[] SaltBytes; public string Hash; public byte[] HashBytes; - public PasswordHash(string StorageString) + public PasswordHash(string storageString) { - string[] a = StorageString.Split('$'); - Id = a[1]; - if (a[2].Contains("=")) + string[] SplitStorageString = storageString.Split('$'); + Id = SplitStorageString[1]; + if (SplitStorageString[2].Contains("=")) { - foreach (string paramset in (a[2].Split(','))) + foreach (string paramset in (SplitStorageString[2].Split(','))) { if (!String.IsNullOrEmpty(paramset)) { - string[] fields = paramset.Split('='); - Parameters.Add(fields[0], fields[1]); + string[] fields = paramset.Split('='); + if(fields.Length == 2) + { + Parameters.Add(fields[0], fields[1]); + } } } - if (a.Length == 4) + if (SplitStorageString.Length == 5) { - Salt = a[2]; - SaltBytes = FromByteString(Salt); - Hash = a[3]; - HashBytes = FromByteString(Hash); + Salt = SplitStorageString[3]; + SaltBytes = ConvertFromByteString(Salt); + Hash = SplitStorageString[4]; + HashBytes = ConvertFromByteString(Hash); } else { Salt = string.Empty; - Hash = a[3]; - HashBytes = FromByteString(Hash); + Hash = SplitStorageString[3]; + HashBytes = ConvertFromByteString(Hash); } } else { - if (a.Length == 4) + if (SplitStorageString.Length == 4) { - Salt = a[2]; - SaltBytes = FromByteString(Salt); - Hash = a[3]; - HashBytes = FromByteString(Hash); + Salt = SplitStorageString[2]; + SaltBytes = ConvertFromByteString(Salt); + Hash = SplitStorageString[3]; + HashBytes = ConvertFromByteString(Hash); } else { Salt = string.Empty; - Hash = a[2]; - HashBytes = FromByteString(Hash); + Hash = SplitStorageString[2]; + HashBytes = ConvertFromByteString(Hash); } } } - public PasswordHash(ICryptoProvider cryptoProvider2) + public PasswordHash(ICryptoProvider cryptoProvider) { - Id = "SHA256"; - SaltBytes = cryptoProvider2.GenerateSalt(); - Salt = BitConverter.ToString(SaltBytes).Replace("-", ""); + Id = cryptoProvider.DefaultHashMethod; + SaltBytes = cryptoProvider.GenerateSalt(); + Salt = ConvertToByteString(SaltBytes); } - private byte[] FromByteString(string ByteString) + public static byte[] ConvertFromByteString(string byteString) { List Bytes = new List(); - for (int i = 0; i < ByteString.Length; i += 2) + for (int i = 0; i < byteString.Length; i += 2) { - Bytes.Add(Convert.ToByte(ByteString.Substring(i, 2),16)); + Bytes.Add(Convert.ToByte(byteString.Substring(i, 2),16)); } return Bytes.ToArray(); - } + } + public static string ConvertToByteString(byte[] bytes) + { + return BitConverter.ToString(bytes).Replace("-", ""); + } + private string SerializeParameters() { string ReturnString = String.Empty; @@ -98,10 +106,15 @@ namespace MediaBrowser.Model.Cryptography { string OutString = "$"; OutString += Id; - if (!string.IsNullOrEmpty(SerializeParameters())) - OutString += $"${SerializeParameters()}"; + string paramstring = SerializeParameters(); + if (!string.IsNullOrEmpty(paramstring)) + { + OutString += $"${paramstring}"; + } if (!string.IsNullOrEmpty(Salt)) + { OutString += $"${Salt}"; + } OutString += $"${Hash}"; return OutString; } -- cgit v1.2.3 From 9e58e31de08b4d8e7922038a9f291e720a778b8f Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Wed, 13 Feb 2019 00:43:48 -0800 Subject: Update Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs fix to styling Co-Authored-By: LogicalPhallacy <44458166+LogicalPhallacy@users.noreply.github.com> --- .../Library/DefaultAuthenticationProvider.cs | 226 ++++++++++----------- 1 file changed, 113 insertions(+), 113 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs index ca6217016..750807b10 100644 --- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs @@ -1,39 +1,39 @@ -using System; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using MediaBrowser.Controller.Authentication; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Model.Cryptography; - -namespace Emby.Server.Implementations.Library -{ - public class DefaultAuthenticationProvider : IAuthenticationProvider, IRequiresResolvedUser - { - private readonly ICryptoProvider _cryptographyProvider; - public DefaultAuthenticationProvider(ICryptoProvider crypto) - { - _cryptographyProvider = crypto; - } - - public string Name => "Default"; - - public bool IsEnabled => true; - - - //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 - public Task Authenticate(string username, string password) - { - throw new NotImplementedException(); - } - - - //This is the verson that we need to use for local users. Because reasons. +using System; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MediaBrowser.Controller.Authentication; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.Cryptography; + +namespace Emby.Server.Implementations.Library +{ + public class DefaultAuthenticationProvider : IAuthenticationProvider, IRequiresResolvedUser + { + private readonly ICryptoProvider _cryptographyProvider; + public DefaultAuthenticationProvider(ICryptoProvider crypto) + { + _cryptographyProvider = crypto; + } + + public string Name => "Default"; + + public bool IsEnabled => true; + + + //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 + public Task Authenticate(string username, string password) + { + throw new NotImplementedException(); + } + + + //This is the verson that we need to use for local users. Because reasons. public Task Authenticate(string username, string password, User resolvedUser) - { - bool success = false; + { + bool success = false; if (resolvedUser == null) { throw new Exception("Invalid username or password"); @@ -42,18 +42,18 @@ namespace Emby.Server.Implementations.Library byte[] passwordbytes = Encoding.UTF8.GetBytes(password); PasswordHash readyHash = new PasswordHash(resolvedUser.Password); - byte[] CalculatedHash; + byte[] CalculatedHash; string CalculatedHashString; if (_cryptographyProvider.GetSupportedHashMethods().Any(i => i == readyHash.Id)) { if (String.IsNullOrEmpty(readyHash.Salt)) { - CalculatedHash = _cryptographyProvider.ComputeHash(readyHash.Id, passwordbytes); + CalculatedHash = _cryptographyProvider.ComputeHash(readyHash.Id, passwordbytes); CalculatedHashString = BitConverter.ToString(CalculatedHash).Replace("-", string.Empty); } else { - CalculatedHash = _cryptographyProvider.ComputeHash(readyHash.Id, passwordbytes, readyHash.SaltBytes); + CalculatedHash = _cryptographyProvider.ComputeHash(readyHash.Id, passwordbytes, readyHash.SaltBytes); CalculatedHashString = BitConverter.ToString(CalculatedHash).Replace("-", string.Empty); } if (CalculatedHashString == readyHash.Hash) @@ -64,7 +64,7 @@ namespace Emby.Server.Implementations.Library } else { - throw new Exception(String.Format("Requested crypto method not available in provider: {0}", readyHash.Id)); + throw new Exception(String.Format("Requested crypto method not available in provider: {0}", readyHash.Id)); } //var success = string.Equals(GetPasswordHash(resolvedUser), GetHashedString(resolvedUser, password), StringComparison.OrdinalIgnoreCase); @@ -78,10 +78,10 @@ namespace Emby.Server.Implementations.Library { Username = username }); - } - - //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. + } + + //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)) @@ -90,71 +90,71 @@ namespace Emby.Server.Implementations.Library { 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 HasPassword(User user) - { - var hasConfiguredPassword = !IsPasswordEmpty(user, GetPasswordHash(user)); - return Task.FromResult(hasConfiguredPassword); - } - - private bool IsPasswordEmpty(User user, string passwordHash) - { - return string.IsNullOrEmpty(passwordHash); - } - - public Task ChangePassword(User user, string newPassword) - { - //string newPasswordHash = null; - ConvertPasswordFormat(user); - 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(); - - return Task.CompletedTask; - } - - public string GetPasswordHash(User user) - { - return user.Password; - } - - public string GetEmptyHashedString(User user) - { - return null; - } - - public string GetHashedStringChangeAuth(string newPassword, PasswordHash passwordHash) - { - passwordHash.HashBytes = Encoding.UTF8.GetBytes(newPassword); - return PasswordHash.ConvertToByteString(_cryptographyProvider.ComputeHash(passwordHash)); - } - - /// - /// Gets the hashed string. - /// + } + + public Task HasPassword(User user) + { + var hasConfiguredPassword = !IsPasswordEmpty(user, GetPasswordHash(user)); + return Task.FromResult(hasConfiguredPassword); + } + + private bool IsPasswordEmpty(User user, string passwordHash) + { + return string.IsNullOrEmpty(passwordHash); + } + + public Task ChangePassword(User user, string newPassword) + { + //string newPasswordHash = null; + ConvertPasswordFormat(user); + 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(); + + return Task.CompletedTask; + } + + public string GetPasswordHash(User user) + { + return user.Password; + } + + public string GetEmptyHashedString(User user) + { + return null; + } + + public string GetHashedStringChangeAuth(string newPassword, PasswordHash passwordHash) + { + passwordHash.HashBytes = Encoding.UTF8.GetBytes(newPassword); + return PasswordHash.ConvertToByteString(_cryptographyProvider.ComputeHash(passwordHash)); + } + + /// + /// Gets the hashed string. + /// public string GetHashedString(User user, string str) { PasswordHash passwordHash; @@ -163,23 +163,23 @@ namespace Emby.Server.Implementations.Library passwordHash = new PasswordHash(_cryptographyProvider); } else - { + { ConvertPasswordFormat(user); passwordHash = new PasswordHash(user.Password); } if (passwordHash.SaltBytes != null) - { - //the password is modern format with PBKDF and we should take advantage of that - passwordHash.HashBytes = Encoding.UTF8.GetBytes(str); + { + //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 - { + { //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))); - } - - - } - } -} + } + + + } + } +} -- cgit v1.2.3 From d8e6808d77eb70025b4a26538a1deb814cbe2831 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Wed, 13 Feb 2019 00:44:07 -0800 Subject: Update Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs fix to styling Co-Authored-By: LogicalPhallacy <44458166+LogicalPhallacy@users.noreply.github.com> --- Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs | 1 + 1 file changed, 1 insertion(+) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs index 750807b10..33428c05e 100644 --- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs @@ -91,6 +91,7 @@ namespace Emby.Server.Implementations.Library string hash = user.Password; user.Password = String.Format("$SHA1${0}", hash); } + if (user.EasyPassword != null && !user.EasyPassword.Contains("$")) { string hash = user.EasyPassword; -- cgit v1.2.3 From bca569da420075030fd7de6f9ed8d3abcb7a67cb Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Wed, 13 Feb 2019 22:10:37 +0100 Subject: Reduce the amount of db calls during the post scan event --- .../Channels/ChannelPostScanTask.cs | 48 ++++++++-------------- 1 file changed, 18 insertions(+), 30 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs b/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs index ad6c537ef..ec85ffa4d 100644 --- a/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs +++ b/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs @@ -35,64 +35,52 @@ namespace Emby.Server.Implementations.Channels public static string GetUserDistinctValue(User user) { var channels = user.Policy.EnabledChannels - .OrderBy(i => i) - .ToList(); + .OrderBy(i => i); - return string.Join("|", channels.ToArray()); + return string.Join("|", channels); } private void CleanDatabase(CancellationToken cancellationToken) { var installedChannelIds = ((ChannelManager)_channelManager).GetInstalledChannelIds(); - var databaseIds = _libraryManager.GetItemIds(new InternalItemsQuery + var databaseIds = _libraryManager.GetItemList(new InternalItemsQuery { - IncludeItemTypes = new[] { typeof(Channel).Name } + IncludeItemTypes = new[] { typeof(Channel).Name }, + ExcludeItemIds = installedChannelIds.ToArray() }); - var invalidIds = databaseIds - .Except(installedChannelIds) - .ToList(); - - foreach (var id in invalidIds) + foreach (var channel in databaseIds.Cast()) { cancellationToken.ThrowIfCancellationRequested(); - CleanChannel(id, cancellationToken); + CleanChannel(channel, cancellationToken); } } - private void CleanChannel(Guid id, CancellationToken cancellationToken) + private void CleanChannel(Channel channel, CancellationToken cancellationToken) { - _logger.LogInformation("Cleaning channel {0} from database", id); + _logger.LogInformation("Cleaning channel {0} from database", channel.Id); // Delete all channel items - var allIds = _libraryManager.GetItemIds(new InternalItemsQuery + var items = _libraryManager.GetItemList(new InternalItemsQuery { - ChannelIds = new[] { id } + ChannelIds = new[] { channel.Id } }); - foreach (var deleteId in allIds) + foreach (var item in items) { cancellationToken.ThrowIfCancellationRequested(); - DeleteItem(deleteId); - } - - // Finally, delete the channel itself - DeleteItem(id); - } + _libraryManager.DeleteItem(item, new DeleteOptions + { + DeleteFileLocation = false - private void DeleteItem(Guid id) - { - var item = _libraryManager.GetItemById(id); - - if (item == null) - { - return; + }, false); } - _libraryManager.DeleteItem(item, new DeleteOptions + // Finally, delete the channel itself + _libraryManager.DeleteItem(channel, new DeleteOptions { DeleteFileLocation = false -- cgit v1.2.3 From 0fbc4545d1b9f8b3e5af3324d92ab0c0ef0fafe2 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Thu, 14 Feb 2019 17:02:46 +0100 Subject: Address comments --- Emby.Server.Implementations/Channels/ChannelPostScanTask.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs b/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs index ec85ffa4d..3c7cbb115 100644 --- a/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs +++ b/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs @@ -44,17 +44,17 @@ namespace Emby.Server.Implementations.Channels { var installedChannelIds = ((ChannelManager)_channelManager).GetInstalledChannelIds(); - var databaseIds = _libraryManager.GetItemList(new InternalItemsQuery + var uninstalledChannels = _libraryManager.GetItemList(new InternalItemsQuery { IncludeItemTypes = new[] { typeof(Channel).Name }, ExcludeItemIds = installedChannelIds.ToArray() }); - foreach (var channel in databaseIds.Cast()) + foreach (var channel in uninstalledChannels) { cancellationToken.ThrowIfCancellationRequested(); - CleanChannel(channel, cancellationToken); + CleanChannel((Channel)channel, cancellationToken); } } -- cgit v1.2.3 From 9f3aa2cead95ec0a66a518919c179eea4cad5d9c Mon Sep 17 00:00:00 2001 From: LogicalPhallacy <44458166+LogicalPhallacy@users.noreply.github.com> Date: Mon, 18 Feb 2019 00:31:03 -0800 Subject: Apply suggestions from code review Adding minor stylistic suggestions from Bond-009 Co-Authored-By: LogicalPhallacy <44458166+LogicalPhallacy@users.noreply.github.com> --- .../Cryptography/CryptographyProvider.cs | 41 +- .../Data/SqliteUserRepository.cs | 469 +++++++++++---------- .../Library/DefaultAuthenticationProvider.cs | 9 +- 3 files changed, 264 insertions(+), 255 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs index 7817989e7..436443f06 100644 --- a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs +++ b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs @@ -72,7 +72,7 @@ namespace Emby.Server.Implementations.Cryptography } private byte[] PBKDF2(string method, byte[] bytes, byte[] salt, int iterations) - { + { using (var r = new Rfc2898DeriveBytes(bytes, salt, iterations, new HashAlgorithmName(method))) { return r.GetBytes(32); @@ -107,9 +107,9 @@ namespace Emby.Server.Implementations.Cryptography } else { - throw new CryptographicException(String.Format("Requested hash method is not supported: {0}", HashMethod)); + throw new CryptographicException($"Requested hash method is not supported: {HashMethod}")); } - } + } public byte[] ComputeHashWithDefaultMethod(byte[] bytes, byte[] salt) { @@ -117,25 +117,32 @@ namespace Emby.Server.Implementations.Cryptography } public byte[] ComputeHash(PasswordHash hash) - { - int iterations = defaultiterations; - if (!hash.Parameters.ContainsKey("iterations")) - { - hash.Parameters.Add("iterations", defaultiterations.ToString()); - } - else - { - try { iterations = int.Parse(hash.Parameters["iterations"]); } - catch (Exception e) { iterations = defaultiterations; throw new Exception($"Couldn't successfully parse iterations value from string:{hash.Parameters["iterations"]}", e); } + { + int iterations = defaultiterations; + if (!hash.Parameters.ContainsKey("iterations")) + { + hash.Parameters.Add("iterations", defaultiterations.ToString(CultureInfo.InvariantCulture)); + } + else + { + try + { + iterations = int.Parse(hash.Parameters["iterations"]); + } + catch (Exception e) + { + iterations = defaultiterations; + throw new Exception($"Couldn't successfully parse iterations value from string:{hash.Parameters["iterations"]}", e); + } } - return PBKDF2(hash.Id, hash.HashBytes, hash.SaltBytes,iterations); - } - + return PBKDF2(hash.Id, hash.HashBytes, hash.SaltBytes, iterations); + } + public byte[] GenerateSalt() { byte[] salt = new byte[64]; rng.GetBytes(salt); return salt; - } + } } } diff --git a/Emby.Server.Implementations/Data/SqliteUserRepository.cs b/Emby.Server.Implementations/Data/SqliteUserRepository.cs index b3d457342..1b6deae7d 100644 --- a/Emby.Server.Implementations/Data/SqliteUserRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserRepository.cs @@ -1,85 +1,86 @@ -using System; -using System.Collections.Generic; -using System.IO; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Persistence; -using MediaBrowser.Model.Serialization; -using Microsoft.Extensions.Logging; -using SQLitePCL.pretty; - -namespace Emby.Server.Implementations.Data -{ - /// - /// Class SQLiteUserRepository - /// - public class SqliteUserRepository : BaseSqliteRepository, IUserRepository - { - private readonly IJsonSerializer _jsonSerializer; - - public SqliteUserRepository( - ILoggerFactory loggerFactory, - IServerApplicationPaths appPaths, - IJsonSerializer jsonSerializer) - : base(loggerFactory.CreateLogger(nameof(SqliteUserRepository))) - { - _jsonSerializer = jsonSerializer; - - DbFilePath = Path.Combine(appPaths.DataPath, "users.db"); - } - - /// - /// Gets the name of the repository - /// - /// The name. - public string Name => "SQLite"; - - /// - /// Opens the connection to the database - /// - /// Task. - public void Initialize() - { - using (var connection = CreateConnection()) - { - RunDefaultInitialization(connection); - - var localUsersTableExists = TableExists(connection, "LocalUsersv2"); - - connection.RunQueries(new[] { - "create table if not exists LocalUsersv2 (Id INTEGER PRIMARY KEY, guid GUID NOT NULL, data BLOB NOT NULL)", - "drop index if exists idx_users" - }); - - if (!localUsersTableExists && TableExists(connection, "Users")) - { - TryMigrateToLocalUsersTable(connection); - } - RemoveEmptyPasswordHashes(); - } - } - - private void TryMigrateToLocalUsersTable(ManagedConnection connection) - { - try - { - connection.RunQueries(new[] - { - "INSERT INTO LocalUsersv2 (guid, data) SELECT guid,data from users" - }); - } - catch (Exception ex) - { - Logger.LogError(ex, "Error migrating users database"); - } - } - +using System; +using System.Collections.Generic; +using System.IO; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Model.Serialization; +using Microsoft.Extensions.Logging; +using SQLitePCL.pretty; + +namespace Emby.Server.Implementations.Data +{ + /// + /// Class SQLiteUserRepository + /// + public class SqliteUserRepository : BaseSqliteRepository, IUserRepository + { + private readonly IJsonSerializer _jsonSerializer; + + public SqliteUserRepository( + ILoggerFactory loggerFactory, + IServerApplicationPaths appPaths, + IJsonSerializer jsonSerializer) + : base(loggerFactory.CreateLogger(nameof(SqliteUserRepository))) + { + _jsonSerializer = jsonSerializer; + + DbFilePath = Path.Combine(appPaths.DataPath, "users.db"); + } + + /// + /// Gets the name of the repository + /// + /// The name. + public string Name => "SQLite"; + + /// + /// Opens the connection to the database + /// + /// Task. + public void Initialize() + { + using (var connection = CreateConnection()) + { + RunDefaultInitialization(connection); + + var localUsersTableExists = TableExists(connection, "LocalUsersv2"); + + connection.RunQueries(new[] { + "create table if not exists LocalUsersv2 (Id INTEGER PRIMARY KEY, guid GUID NOT NULL, data BLOB NOT NULL)", + "drop index if exists idx_users" + }); + + if (!localUsersTableExists && TableExists(connection, "Users")) + { + TryMigrateToLocalUsersTable(connection); + } + RemoveEmptyPasswordHashes(); + } + } + + private void TryMigrateToLocalUsersTable(ManagedConnection connection) + { + try + { + connection.RunQueries(new[] + { + "INSERT INTO LocalUsersv2 (guid, data) SELECT guid,data from users" + }); + } + catch (Exception ex) + { + Logger.LogError(ex, "Error migrating users database"); + } + } + private void RemoveEmptyPasswordHashes() { foreach (var user in RetrieveAllUsers()) { // If the user password is the sha1 hash of the empty string, remove it - if (!string.Equals(user.Password, "DA39A3EE5E6B4B0D3255BFEF95601890AFD80709") || !string.Equals(user.Password, "$SHA1$DA39A3EE5E6B4B0D3255BFEF95601890AFD80709")) + if (!string.Equals(user.Password, "DA39A3EE5E6B4B0D3255BFEF95601890AFD80709", StringComparison.Ordinal) + || !string.Equals(user.Password, "$SHA1$DA39A3EE5E6B4B0D3255BFEF95601890AFD80709", StringComparison.Ordinal)) { continue; } @@ -103,160 +104,160 @@ namespace Emby.Server.Implementations.Data } } - } - - /// - /// Save a user in the repo - /// - public void CreateUser(User user) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - var serialized = _jsonSerializer.SerializeToBytes(user); - - using (WriteLock.Write()) - { - using (var connection = CreateConnection()) - { - connection.RunInTransaction(db => - { - using (var statement = db.PrepareStatement("insert into LocalUsersv2 (guid, data) values (@guid, @data)")) - { - statement.TryBind("@guid", user.Id.ToGuidBlob()); - statement.TryBind("@data", serialized); - - statement.MoveNext(); - } - - var createdUser = GetUser(user.Id, false); - - if (createdUser == null) - { - throw new ApplicationException("created user should never be null"); - } - - user.InternalId = createdUser.InternalId; - - }, TransactionMode); - } - } - } - - public void UpdateUser(User user) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - var serialized = _jsonSerializer.SerializeToBytes(user); - - using (WriteLock.Write()) - { - using (var connection = CreateConnection()) - { - connection.RunInTransaction(db => - { - using (var statement = db.PrepareStatement("update LocalUsersv2 set data=@data where Id=@InternalId")) - { - statement.TryBind("@InternalId", user.InternalId); - statement.TryBind("@data", serialized); - statement.MoveNext(); - } - - }, TransactionMode); - } - } - } - - private User GetUser(Guid guid, bool openLock) - { - using (openLock ? WriteLock.Read() : null) - { - using (var connection = CreateConnection(true)) - { - using (var statement = connection.PrepareStatement("select id,guid,data from LocalUsersv2 where guid=@guid")) - { - statement.TryBind("@guid", guid); - - foreach (var row in statement.ExecuteQuery()) - { - return GetUser(row); - } - } - } - } - - return null; - } - - private User GetUser(IReadOnlyList row) - { - var id = row[0].ToInt64(); - var guid = row[1].ReadGuidFromBlob(); - - using (var stream = new MemoryStream(row[2].ToBlob())) - { - stream.Position = 0; - var user = _jsonSerializer.DeserializeFromStream(stream); - user.InternalId = id; - user.Id = guid; - return user; - } - } - - /// - /// Retrieve all users from the database - /// - /// IEnumerable{User}. - public List RetrieveAllUsers() - { - var list = new List(); - - using (WriteLock.Read()) - { - using (var connection = CreateConnection(true)) - { - foreach (var row in connection.Query("select id,guid,data from LocalUsersv2")) - { - list.Add(GetUser(row)); - } - } - } - - return list; - } - - /// - /// Deletes the user. - /// - /// The user. - /// Task. - /// user - public void DeleteUser(User user) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - using (WriteLock.Write()) - { - using (var connection = CreateConnection()) - { - connection.RunInTransaction(db => - { - using (var statement = db.PrepareStatement("delete from LocalUsersv2 where Id=@id")) - { - statement.TryBind("@id", user.InternalId); - statement.MoveNext(); - } - }, TransactionMode); - } - } - } - } -} + } + + /// + /// Save a user in the repo + /// + public void CreateUser(User user) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + var serialized = _jsonSerializer.SerializeToBytes(user); + + using (WriteLock.Write()) + { + using (var connection = CreateConnection()) + { + connection.RunInTransaction(db => + { + using (var statement = db.PrepareStatement("insert into LocalUsersv2 (guid, data) values (@guid, @data)")) + { + statement.TryBind("@guid", user.Id.ToGuidBlob()); + statement.TryBind("@data", serialized); + + statement.MoveNext(); + } + + var createdUser = GetUser(user.Id, false); + + if (createdUser == null) + { + throw new ApplicationException("created user should never be null"); + } + + user.InternalId = createdUser.InternalId; + + }, TransactionMode); + } + } + } + + public void UpdateUser(User user) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + var serialized = _jsonSerializer.SerializeToBytes(user); + + using (WriteLock.Write()) + { + using (var connection = CreateConnection()) + { + connection.RunInTransaction(db => + { + using (var statement = db.PrepareStatement("update LocalUsersv2 set data=@data where Id=@InternalId")) + { + statement.TryBind("@InternalId", user.InternalId); + statement.TryBind("@data", serialized); + statement.MoveNext(); + } + + }, TransactionMode); + } + } + } + + private User GetUser(Guid guid, bool openLock) + { + using (openLock ? WriteLock.Read() : null) + { + using (var connection = CreateConnection(true)) + { + using (var statement = connection.PrepareStatement("select id,guid,data from LocalUsersv2 where guid=@guid")) + { + statement.TryBind("@guid", guid); + + foreach (var row in statement.ExecuteQuery()) + { + return GetUser(row); + } + } + } + } + + return null; + } + + private User GetUser(IReadOnlyList row) + { + var id = row[0].ToInt64(); + var guid = row[1].ReadGuidFromBlob(); + + using (var stream = new MemoryStream(row[2].ToBlob())) + { + stream.Position = 0; + var user = _jsonSerializer.DeserializeFromStream(stream); + user.InternalId = id; + user.Id = guid; + return user; + } + } + + /// + /// Retrieve all users from the database + /// + /// IEnumerable{User}. + public List RetrieveAllUsers() + { + var list = new List(); + + using (WriteLock.Read()) + { + using (var connection = CreateConnection(true)) + { + foreach (var row in connection.Query("select id,guid,data from LocalUsersv2")) + { + list.Add(GetUser(row)); + } + } + } + + return list; + } + + /// + /// Deletes the user. + /// + /// The user. + /// Task. + /// user + public void DeleteUser(User user) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + using (WriteLock.Write()) + { + using (var connection = CreateConnection()) + { + connection.RunInTransaction(db => + { + using (var statement = db.PrepareStatement("delete from LocalUsersv2 where Id=@id")) + { + statement.TryBind("@id", user.InternalId); + statement.MoveNext(); + } + }, TransactionMode); + } + } + } + } +} diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs index 33428c05e..016de6db7 100644 --- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs @@ -44,7 +44,7 @@ namespace Emby.Server.Implementations.Library PasswordHash readyHash = new PasswordHash(resolvedUser.Password); byte[] CalculatedHash; string CalculatedHashString; - if (_cryptographyProvider.GetSupportedHashMethods().Any(i => i == readyHash.Id)) + if (_cryptographyProvider.GetSupportedHashMethods().Contains(readyHash.Id)) { if (String.IsNullOrEmpty(readyHash.Salt)) { @@ -64,7 +64,7 @@ namespace Emby.Server.Implementations.Library } else { - throw new Exception(String.Format("Requested crypto method not available in provider: {0}", readyHash.Id)); + throw new Exception(String.Format($"Requested crypto method not available in provider: {readyHash.Id}")); } //var success = string.Equals(GetPasswordHash(resolvedUser), GetHashedString(resolvedUser, password), StringComparison.OrdinalIgnoreCase); @@ -95,7 +95,7 @@ namespace Emby.Server.Implementations.Library if (user.EasyPassword != null && !user.EasyPassword.Contains("$")) { string hash = user.EasyPassword; - user.EasyPassword = String.Format("$SHA1${0}", hash); + user.EasyPassword = string.Format("$SHA1${0}", hash); } } } @@ -122,7 +122,8 @@ namespace Emby.Server.Implementations.Library passwordHash.Salt = PasswordHash.ConvertToByteString(passwordHash.SaltBytes); passwordHash.Id = _cryptographyProvider.DefaultHashMethod; passwordHash.Hash = GetHashedStringChangeAuth(newPassword, passwordHash); - }else if (newPassword != null) + } + else if (newPassword != null) { passwordHash.Hash = GetHashedString(user, newPassword); } -- cgit v1.2.3 From 48e7274d3783e57f89f6e1cc76fcd8696e987ec5 Mon Sep 17 00:00:00 2001 From: Phallacy Date: Mon, 18 Feb 2019 01:26:01 -0800 Subject: added justaman notes, fixed new bug from emty has removals --- .../Cryptography/CryptographyProvider.cs | 5 +-- .../Library/DefaultAuthenticationProvider.cs | 42 ++++++++++++++++------ Emby.Server.Implementations/Library/UserManager.cs | 4 +-- MediaBrowser.Model/Cryptography/PasswordHash.cs | 42 ++++++++++++---------- 4 files changed, 59 insertions(+), 34 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs index 436443f06..c4f034631 100644 --- a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs +++ b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Security.Cryptography; using System.Text; @@ -102,12 +103,12 @@ namespace Emby.Server.Implementations.Cryptography } else { - return PBKDF2(HashMethod, bytes, salt,defaultiterations); + return PBKDF2(HashMethod, bytes, salt, defaultiterations); } } else { - throw new CryptographicException($"Requested hash method is not supported: {HashMethod}")); + throw new CryptographicException($"Requested hash method is not supported: {HashMethod}"); } } diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs index 016de6db7..80026d97c 100644 --- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs @@ -37,7 +37,17 @@ namespace Emby.Server.Implementations.Library if (resolvedUser == null) { throw new Exception("Invalid username or password"); - } + } + + //As long as jellyfin supports passwordless users, we need this little block here to accomodate + if (IsPasswordEmpty(resolvedUser, password)) + { + return Task.FromResult(new ProviderAuthenticationResult + { + Username = username + }); + } + ConvertPasswordFormat(resolvedUser); byte[] passwordbytes = Encoding.UTF8.GetBytes(password); @@ -106,15 +116,30 @@ namespace Emby.Server.Implementations.Library return Task.FromResult(hasConfiguredPassword); } - private bool IsPasswordEmpty(User user, string passwordHash) - { - return string.IsNullOrEmpty(passwordHash); + private bool IsPasswordEmpty(User user, string password) + { + if (string.IsNullOrEmpty(user.Password)) + { + return string.IsNullOrEmpty(password); + } + return false; } public Task ChangePassword(User user, string newPassword) { - //string newPasswordHash = null; - ConvertPasswordFormat(user); + ConvertPasswordFormat(user); + //This is needed to support changing a no password user to a password user + if (string.IsNullOrEmpty(user.Password)) + { + 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(); + return Task.CompletedTask; + } + PasswordHash passwordHash = new PasswordHash(user.Password); if (passwordHash.Id == "SHA1" && string.IsNullOrEmpty(passwordHash.Salt)) { @@ -143,11 +168,6 @@ namespace Emby.Server.Implementations.Library return user.Password; } - public string GetEmptyHashedString(User user) - { - return null; - } - public string GetHashedStringChangeAuth(string newPassword, PasswordHash passwordHash) { passwordHash.HashBytes = Encoding.UTF8.GetBytes(newPassword); diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index b8777a480..3daed0c08 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -475,13 +475,13 @@ namespace Emby.Server.Implementations.Library private string GetLocalPasswordHash(User user) { return string.IsNullOrEmpty(user.EasyPassword) - ? _defaultAuthenticationProvider.GetEmptyHashedString(user) + ? null : user.EasyPassword; } private bool IsPasswordEmpty(User user, string passwordHash) { - return string.Equals(passwordHash, _defaultAuthenticationProvider.GetEmptyHashedString(user), StringComparison.OrdinalIgnoreCase); + return string.IsNullOrEmpty(passwordHash); } /// diff --git a/MediaBrowser.Model/Cryptography/PasswordHash.cs b/MediaBrowser.Model/Cryptography/PasswordHash.cs index cd61657c1..3a817543b 100644 --- a/MediaBrowser.Model/Cryptography/PasswordHash.cs +++ b/MediaBrowser.Model/Cryptography/PasswordHash.cs @@ -18,48 +18,52 @@ namespace MediaBrowser.Model.Cryptography public byte[] HashBytes; public PasswordHash(string storageString) { - string[] SplitStorageString = storageString.Split('$'); - Id = SplitStorageString[1]; - if (SplitStorageString[2].Contains("=")) + string[] splitted = storageString.Split('$'); + Id = splitted[1]; + if (splitted[2].Contains("=")) { - foreach (string paramset in (SplitStorageString[2].Split(','))) + foreach (string paramset in (splitted[2].Split(','))) { if (!String.IsNullOrEmpty(paramset)) { string[] fields = paramset.Split('='); - if(fields.Length == 2) + if (fields.Length == 2) { Parameters.Add(fields[0], fields[1]); + } + else + { + throw new Exception($"Malformed parameter in password hash string {paramset}"); } } } - if (SplitStorageString.Length == 5) + if (splitted.Length == 5) { - Salt = SplitStorageString[3]; + Salt = splitted[3]; SaltBytes = ConvertFromByteString(Salt); - Hash = SplitStorageString[4]; + Hash = splitted[4]; HashBytes = ConvertFromByteString(Hash); } else { Salt = string.Empty; - Hash = SplitStorageString[3]; + Hash = splitted[3]; HashBytes = ConvertFromByteString(Hash); } } else { - if (SplitStorageString.Length == 4) + if (splitted.Length == 4) { - Salt = SplitStorageString[2]; + Salt = splitted[2]; SaltBytes = ConvertFromByteString(Salt); - Hash = SplitStorageString[3]; + Hash = splitted[3]; HashBytes = ConvertFromByteString(Hash); } else { Salt = string.Empty; - Hash = SplitStorageString[2]; + Hash = splitted[2]; HashBytes = ConvertFromByteString(Hash); } @@ -83,6 +87,7 @@ namespace MediaBrowser.Model.Cryptography } return Bytes.ToArray(); } + public static string ConvertToByteString(byte[] bytes) { return BitConverter.ToString(bytes).Replace("-", ""); @@ -104,19 +109,18 @@ namespace MediaBrowser.Model.Cryptography public override string ToString() { - string OutString = "$"; - OutString += Id; + string outString = "$" +Id; string paramstring = SerializeParameters(); if (!string.IsNullOrEmpty(paramstring)) { - OutString += $"${paramstring}"; + outString += $"${paramstring}"; } if (!string.IsNullOrEmpty(Salt)) { - OutString += $"${Salt}"; + outString += $"${Salt}"; } - OutString += $"${Hash}"; - return OutString; + outString += $"${Hash}"; + return outString; } } -- cgit v1.2.3 From 56e306334201e06f2066e1e7ca1246508346bfa9 Mon Sep 17 00:00:00 2001 From: Phallacy Date: Mon, 18 Feb 2019 10:56:01 -0800 Subject: little fixes for JustAMan --- Emby.Server.Implementations/Cryptography/CryptographyProvider.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs index c4f034631..dc528c280 100644 --- a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs +++ b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs @@ -132,8 +132,7 @@ namespace Emby.Server.Implementations.Cryptography } catch (Exception e) { - iterations = defaultiterations; - throw new Exception($"Couldn't successfully parse iterations value from string:{hash.Parameters["iterations"]}", e); + throw new InvalidDataException($"Couldn't successfully parse iterations value from string: {hash.Parameters["iterations"]}", e); } } return PBKDF2(hash.Id, hash.HashBytes, hash.SaltBytes, iterations); -- cgit v1.2.3 From 6bbb968b578fe42224227b70e78825bbed5cfc6f Mon Sep 17 00:00:00 2001 From: Phallacy Date: Wed, 20 Feb 2019 00:00:26 -0800 Subject: minor changes and return to netstandard --- .../Cryptography/CryptographyProvider.cs | 5 +- .../Data/SqliteUserRepository.cs | 3 +- .../Emby.Server.Implementations.csproj | 2 +- .../Library/DefaultAuthenticationProvider.cs | 33 +++++---- Emby.Server.Implementations/Library/UserManager.cs | 3 +- MediaBrowser.Model/Cryptography/PasswordHash.cs | 81 ++++++++++++---------- 6 files changed, 72 insertions(+), 55 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs index dc528c280..2f2fd9592 100644 --- a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs +++ b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs @@ -73,8 +73,9 @@ namespace Emby.Server.Implementations.Cryptography } private byte[] PBKDF2(string method, byte[] bytes, byte[] salt, int iterations) - { - using (var r = new Rfc2898DeriveBytes(bytes, salt, iterations, new HashAlgorithmName(method))) + { + //downgrading for now as we need this library to be dotnetstandard compliant + using (var r = new Rfc2898DeriveBytes(bytes, salt, iterations)) { return r.GetBytes(32); } diff --git a/Emby.Server.Implementations/Data/SqliteUserRepository.cs b/Emby.Server.Implementations/Data/SqliteUserRepository.cs index 1b6deae7d..3df91f71c 100644 --- a/Emby.Server.Implementations/Data/SqliteUserRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserRepository.cs @@ -54,7 +54,8 @@ namespace Emby.Server.Implementations.Data if (!localUsersTableExists && TableExists(connection, "Users")) { TryMigrateToLocalUsersTable(connection); - } + } + RemoveEmptyPasswordHashes(); } } diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 86b2efe54..8356a9501 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -34,7 +34,7 @@ - netcoreapp2.1 + netstandard2.0 false diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs index 80026d97c..2ac3ef424 100644 --- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs @@ -56,7 +56,7 @@ namespace Emby.Server.Implementations.Library string CalculatedHashString; if (_cryptographyProvider.GetSupportedHashMethods().Contains(readyHash.Id)) { - if (String.IsNullOrEmpty(readyHash.Salt)) + if (string.IsNullOrEmpty(readyHash.Salt)) { CalculatedHash = _cryptographyProvider.ComputeHash(readyHash.Id, passwordbytes); CalculatedHashString = BitConverter.ToString(CalculatedHash).Replace("-", string.Empty); @@ -65,7 +65,8 @@ namespace Emby.Server.Implementations.Library { CalculatedHash = _cryptographyProvider.ComputeHash(readyHash.Id, passwordbytes, readyHash.SaltBytes); CalculatedHashString = BitConverter.ToString(CalculatedHash).Replace("-", string.Empty); - } + } + if (CalculatedHashString == readyHash.Hash) { success = true; @@ -95,18 +96,20 @@ namespace Emby.Server.Implementations.Library private void ConvertPasswordFormat(User user) { if (!string.IsNullOrEmpty(user.Password)) + { + return; + } + + if (!user.Password.Contains("$")) { - 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); - } + 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); } } @@ -122,6 +125,7 @@ namespace Emby.Server.Implementations.Library { return string.IsNullOrEmpty(password); } + return false; } @@ -188,7 +192,8 @@ namespace Emby.Server.Implementations.Library { ConvertPasswordFormat(user); passwordHash = new PasswordHash(user.Password); - } + } + if (passwordHash.SaltBytes != null) { //the password is modern format with PBKDF and we should take advantage of that diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index 3daed0c08..b74006233 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -221,9 +221,8 @@ 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 - string UserNameRegex = "^[\\w-'._@]*$"; // Usernames can contain letters (a-z + whatever else unicode is cool with), numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.) - return Regex.IsMatch(username, UserNameRegex); + return Regex.IsMatch(username, "^[\\w-'._@]*$"); } private static bool IsValidUsernameCharacter(char i) diff --git a/MediaBrowser.Model/Cryptography/PasswordHash.cs b/MediaBrowser.Model/Cryptography/PasswordHash.cs index 3a817543b..49bd510e9 100644 --- a/MediaBrowser.Model/Cryptography/PasswordHash.cs +++ b/MediaBrowser.Model/Cryptography/PasswordHash.cs @@ -10,26 +10,33 @@ namespace MediaBrowser.Model.Cryptography //https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md //$[$=(,=)*][$[$]] - public string Id; - public Dictionary Parameters = new Dictionary(); - public string Salt; - public byte[] SaltBytes; - public string Hash; - public byte[] HashBytes; + private string id; + private Dictionary parameters = new Dictionary(); + private string salt; + private byte[] saltBytes; + private string hash; + private byte[] hashBytes; + public string Id { get => id; set => id = value; } + public Dictionary Parameters { get => parameters; set => parameters = value; } + public string Salt { get => salt; set => salt = value; } + public byte[] SaltBytes { get => saltBytes; set => saltBytes = value; } + public string Hash { get => hash; set => hash = value; } + public byte[] HashBytes { get => hashBytes; set => hashBytes = value; } + public PasswordHash(string storageString) { string[] splitted = storageString.Split('$'); - Id = splitted[1]; + id = splitted[1]; if (splitted[2].Contains("=")) { foreach (string paramset in (splitted[2].Split(','))) { - if (!String.IsNullOrEmpty(paramset)) + if (!string.IsNullOrEmpty(paramset)) { string[] fields = paramset.Split('='); if (fields.Length == 2) { - Parameters.Add(fields[0], fields[1]); + parameters.Add(fields[0], fields[1]); } else { @@ -39,32 +46,32 @@ namespace MediaBrowser.Model.Cryptography } if (splitted.Length == 5) { - Salt = splitted[3]; - SaltBytes = ConvertFromByteString(Salt); - Hash = splitted[4]; - HashBytes = ConvertFromByteString(Hash); + salt = splitted[3]; + saltBytes = ConvertFromByteString(salt); + hash = splitted[4]; + hashBytes = ConvertFromByteString(hash); } else { - Salt = string.Empty; - Hash = splitted[3]; - HashBytes = ConvertFromByteString(Hash); + salt = string.Empty; + hash = splitted[3]; + hashBytes = ConvertFromByteString(hash); } } else { if (splitted.Length == 4) { - Salt = splitted[2]; - SaltBytes = ConvertFromByteString(Salt); - Hash = splitted[3]; - HashBytes = ConvertFromByteString(Hash); + salt = splitted[2]; + saltBytes = ConvertFromByteString(salt); + hash = splitted[3]; + hashBytes = ConvertFromByteString(hash); } else { - Salt = string.Empty; - Hash = splitted[2]; - HashBytes = ConvertFromByteString(Hash); + salt = string.Empty; + hash = splitted[2]; + hashBytes = ConvertFromByteString(hash); } } @@ -73,9 +80,9 @@ namespace MediaBrowser.Model.Cryptography public PasswordHash(ICryptoProvider cryptoProvider) { - Id = cryptoProvider.DefaultHashMethod; - SaltBytes = cryptoProvider.GenerateSalt(); - Salt = ConvertToByteString(SaltBytes); + id = cryptoProvider.DefaultHashMethod; + saltBytes = cryptoProvider.GenerateSalt(); + salt = ConvertToByteString(SaltBytes); } public static byte[] ConvertFromByteString(string byteString) @@ -95,31 +102,35 @@ namespace MediaBrowser.Model.Cryptography private string SerializeParameters() { - string ReturnString = String.Empty; - foreach (var KVP in Parameters) + string ReturnString = string.Empty; + foreach (var KVP in parameters) { - ReturnString += String.Format(",{0}={1}", KVP.Key, KVP.Value); - } + ReturnString += $",{KVP.Key}={KVP.Value}"; + } + if ((!string.IsNullOrEmpty(ReturnString)) && ReturnString[0] == ',') { ReturnString = ReturnString.Remove(0, 1); - } + } + return ReturnString; } public override string ToString() { - string outString = "$" +Id; + string outString = "$" +id; string paramstring = SerializeParameters(); if (!string.IsNullOrEmpty(paramstring)) { outString += $"${paramstring}"; } - if (!string.IsNullOrEmpty(Salt)) + + if (!string.IsNullOrEmpty(salt)) { - outString += $"${Salt}"; + outString += $"${salt}"; } - outString += $"${Hash}"; + + outString += $"${hash}"; return outString; } } -- cgit v1.2.3 From 098de6b0501eaa0375fc5bfd7cff369815b57718 Mon Sep 17 00:00:00 2001 From: Phallacy Date: Wed, 20 Feb 2019 01:17:30 -0800 Subject: made newlines into linux newlines --- .../Cryptography/CryptographyProvider.cs | 294 ++++++------ .../Data/SqliteUserRepository.cs | 526 ++++++++++----------- .../Library/DefaultAuthenticationProvider.cs | 362 +++++++------- 3 files changed, 590 insertions(+), 592 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs index 2f2fd9592..ea719309c 100644 --- a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs +++ b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs @@ -1,149 +1,149 @@ -using System; -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Globalization; -using System.IO; -using System.Security.Cryptography; -using System.Text; -using MediaBrowser.Model.Cryptography; - -namespace Emby.Server.Implementations.Cryptography -{ - public class CryptographyProvider : ICryptoProvider - { - private HashSet SupportedHashMethods; - public string DefaultHashMethod => "SHA256"; - private RandomNumberGenerator rng; - private int defaultiterations = 1000; - public CryptographyProvider() - { - //Currently supported hash methods from https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptoconfig?view=netcore-2.1 - //there might be a better way to autogenerate this list as dotnet updates, but I couldn't find one - SupportedHashMethods = new HashSet() - { - "MD5" - ,"System.Security.Cryptography.MD5" - ,"SHA" - ,"SHA1" - ,"System.Security.Cryptography.SHA1" - ,"SHA256" - ,"SHA-256" - ,"System.Security.Cryptography.SHA256" - ,"SHA384" - ,"SHA-384" - ,"System.Security.Cryptography.SHA384" - ,"SHA512" - ,"SHA-512" - ,"System.Security.Cryptography.SHA512" - }; - rng = RandomNumberGenerator.Create(); - } - - public Guid GetMD5(string str) - { - return new Guid(ComputeMD5(Encoding.Unicode.GetBytes(str))); - } - - public byte[] ComputeSHA1(byte[] bytes) - { - using (var provider = SHA1.Create()) - { - return provider.ComputeHash(bytes); - } - } - - public byte[] ComputeMD5(Stream str) - { - using (var provider = MD5.Create()) - { - return provider.ComputeHash(str); - } - } - - public byte[] ComputeMD5(byte[] bytes) - { - using (var provider = MD5.Create()) - { - return provider.ComputeHash(bytes); - } - } - - public IEnumerable GetSupportedHashMethods() - { - return SupportedHashMethods; - } - - private byte[] PBKDF2(string method, byte[] bytes, byte[] salt, int iterations) +using System.IO; +using System.Security.Cryptography; +using System.Text; +using MediaBrowser.Model.Cryptography; + +namespace Emby.Server.Implementations.Cryptography +{ + public class CryptographyProvider : ICryptoProvider + { + private HashSet SupportedHashMethods; + public string DefaultHashMethod => "SHA256"; + private RandomNumberGenerator rng; + private int defaultiterations = 1000; + public CryptographyProvider() { - //downgrading for now as we need this library to be dotnetstandard compliant - using (var r = new Rfc2898DeriveBytes(bytes, salt, iterations)) - { - return r.GetBytes(32); - } - } - - public byte[] ComputeHash(string HashMethod, byte[] bytes) - { - return ComputeHash(HashMethod, bytes, new byte[0]); - } - - public byte[] ComputeHashWithDefaultMethod(byte[] bytes) - { - return ComputeHash(DefaultHashMethod, bytes); - } - - public byte[] ComputeHash(string HashMethod, byte[] bytes, byte[] salt) - { - if (SupportedHashMethods.Contains(HashMethod)) - { - if (salt.Length == 0) - { - using (var h = HashAlgorithm.Create(HashMethod)) - { - return h.ComputeHash(bytes); - } - } - else - { - return PBKDF2(HashMethod, bytes, salt, defaultiterations); - } - } - else - { - throw new CryptographicException($"Requested hash method is not supported: {HashMethod}"); - } - } - - public byte[] ComputeHashWithDefaultMethod(byte[] bytes, byte[] salt) - { - return PBKDF2(DefaultHashMethod, bytes, salt, defaultiterations); - } - - public byte[] ComputeHash(PasswordHash hash) - { - int iterations = defaultiterations; - if (!hash.Parameters.ContainsKey("iterations")) - { - hash.Parameters.Add("iterations", defaultiterations.ToString(CultureInfo.InvariantCulture)); - } - else - { - try - { - iterations = int.Parse(hash.Parameters["iterations"]); - } - catch (Exception e) - { - throw new InvalidDataException($"Couldn't successfully parse iterations value from string: {hash.Parameters["iterations"]}", e); - } - } - return PBKDF2(hash.Id, hash.HashBytes, hash.SaltBytes, iterations); - } - - public byte[] GenerateSalt() - { - byte[] salt = new byte[64]; - rng.GetBytes(salt); - return salt; - } - } -} + //Currently supported hash methods from https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptoconfig?view=netcore-2.1 + //there might be a better way to autogenerate this list as dotnet updates, but I couldn't find one + SupportedHashMethods = new HashSet() + { + "MD5" + ,"System.Security.Cryptography.MD5" + ,"SHA" + ,"SHA1" + ,"System.Security.Cryptography.SHA1" + ,"SHA256" + ,"SHA-256" + ,"System.Security.Cryptography.SHA256" + ,"SHA384" + ,"SHA-384" + ,"System.Security.Cryptography.SHA384" + ,"SHA512" + ,"SHA-512" + ,"System.Security.Cryptography.SHA512" + }; + rng = RandomNumberGenerator.Create(); + } + + public Guid GetMD5(string str) + { + return new Guid(ComputeMD5(Encoding.Unicode.GetBytes(str))); + } + + public byte[] ComputeSHA1(byte[] bytes) + { + using (var provider = SHA1.Create()) + { + return provider.ComputeHash(bytes); + } + } + + public byte[] ComputeMD5(Stream str) + { + using (var provider = MD5.Create()) + { + return provider.ComputeHash(str); + } + } + + public byte[] ComputeMD5(byte[] bytes) + { + using (var provider = MD5.Create()) + { + return provider.ComputeHash(bytes); + } + } + + public IEnumerable GetSupportedHashMethods() + { + return SupportedHashMethods; + } + + private byte[] PBKDF2(string method, byte[] bytes, byte[] salt, int iterations) + { + //downgrading for now as we need this library to be dotnetstandard compliant + using (var r = new Rfc2898DeriveBytes(bytes, salt, iterations)) + { + return r.GetBytes(32); + } + } + + public byte[] ComputeHash(string HashMethod, byte[] bytes) + { + return ComputeHash(HashMethod, bytes, new byte[0]); + } + + public byte[] ComputeHashWithDefaultMethod(byte[] bytes) + { + return ComputeHash(DefaultHashMethod, bytes); + } + + public byte[] ComputeHash(string HashMethod, byte[] bytes, byte[] salt) + { + if (SupportedHashMethods.Contains(HashMethod)) + { + if (salt.Length == 0) + { + using (var h = HashAlgorithm.Create(HashMethod)) + { + return h.ComputeHash(bytes); + } + } + else + { + return PBKDF2(HashMethod, bytes, salt, defaultiterations); + } + } + else + { + throw new CryptographicException($"Requested hash method is not supported: {HashMethod}"); + } + } + + public byte[] ComputeHashWithDefaultMethod(byte[] bytes, byte[] salt) + { + return PBKDF2(DefaultHashMethod, bytes, salt, defaultiterations); + } + + public byte[] ComputeHash(PasswordHash hash) + { + int iterations = defaultiterations; + if (!hash.Parameters.ContainsKey("iterations")) + { + hash.Parameters.Add("iterations", defaultiterations.ToString(CultureInfo.InvariantCulture)); + } + else + { + try + { + iterations = int.Parse(hash.Parameters["iterations"]); + } + catch (Exception e) + { + throw new InvalidDataException($"Couldn't successfully parse iterations value from string: {hash.Parameters["iterations"]}", e); + } + } + return PBKDF2(hash.Id, hash.HashBytes, hash.SaltBytes, iterations); + } + + public byte[] GenerateSalt() + { + byte[] salt = new byte[64]; + rng.GetBytes(salt); + return salt; + } + } +} diff --git a/Emby.Server.Implementations/Data/SqliteUserRepository.cs b/Emby.Server.Implementations/Data/SqliteUserRepository.cs index 3df91f71c..182df0edc 100644 --- a/Emby.Server.Implementations/Data/SqliteUserRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserRepository.cs @@ -1,264 +1,264 @@ -using System; -using System.Collections.Generic; -using System.IO; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Persistence; -using MediaBrowser.Model.Serialization; -using Microsoft.Extensions.Logging; -using SQLitePCL.pretty; - -namespace Emby.Server.Implementations.Data -{ - /// - /// Class SQLiteUserRepository - /// - public class SqliteUserRepository : BaseSqliteRepository, IUserRepository - { - private readonly IJsonSerializer _jsonSerializer; - - public SqliteUserRepository( - ILoggerFactory loggerFactory, - IServerApplicationPaths appPaths, - IJsonSerializer jsonSerializer) - : base(loggerFactory.CreateLogger(nameof(SqliteUserRepository))) - { - _jsonSerializer = jsonSerializer; - - DbFilePath = Path.Combine(appPaths.DataPath, "users.db"); - } - - /// - /// Gets the name of the repository - /// - /// The name. - public string Name => "SQLite"; - - /// - /// Opens the connection to the database - /// - /// Task. - public void Initialize() - { - using (var connection = CreateConnection()) - { - RunDefaultInitialization(connection); - - var localUsersTableExists = TableExists(connection, "LocalUsersv2"); - - connection.RunQueries(new[] { - "create table if not exists LocalUsersv2 (Id INTEGER PRIMARY KEY, guid GUID NOT NULL, data BLOB NOT NULL)", - "drop index if exists idx_users" - }); - - if (!localUsersTableExists && TableExists(connection, "Users")) - { - TryMigrateToLocalUsersTable(connection); +using System; +using System.Collections.Generic; +using System.IO; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Model.Serialization; +using Microsoft.Extensions.Logging; +using SQLitePCL.pretty; + +namespace Emby.Server.Implementations.Data +{ + /// + /// Class SQLiteUserRepository + /// + public class SqliteUserRepository : BaseSqliteRepository, IUserRepository + { + private readonly IJsonSerializer _jsonSerializer; + + public SqliteUserRepository( + ILoggerFactory loggerFactory, + IServerApplicationPaths appPaths, + IJsonSerializer jsonSerializer) + : base(loggerFactory.CreateLogger(nameof(SqliteUserRepository))) + { + _jsonSerializer = jsonSerializer; + + DbFilePath = Path.Combine(appPaths.DataPath, "users.db"); + } + + /// + /// Gets the name of the repository + /// + /// The name. + public string Name => "SQLite"; + + /// + /// Opens the connection to the database + /// + /// Task. + public void Initialize() + { + using (var connection = CreateConnection()) + { + RunDefaultInitialization(connection); + + var localUsersTableExists = TableExists(connection, "LocalUsersv2"); + + connection.RunQueries(new[] { + "create table if not exists LocalUsersv2 (Id INTEGER PRIMARY KEY, guid GUID NOT NULL, data BLOB NOT NULL)", + "drop index if exists idx_users" + }); + + if (!localUsersTableExists && TableExists(connection, "Users")) + { + TryMigrateToLocalUsersTable(connection); } - - RemoveEmptyPasswordHashes(); - } - } - - private void TryMigrateToLocalUsersTable(ManagedConnection connection) - { - try - { - connection.RunQueries(new[] - { - "INSERT INTO LocalUsersv2 (guid, data) SELECT guid,data from users" - }); - } - catch (Exception ex) - { - Logger.LogError(ex, "Error migrating users database"); - } - } - - private void RemoveEmptyPasswordHashes() - { - foreach (var user in RetrieveAllUsers()) - { - // If the user password is the sha1 hash of the empty string, remove it - if (!string.Equals(user.Password, "DA39A3EE5E6B4B0D3255BFEF95601890AFD80709", StringComparison.Ordinal) - || !string.Equals(user.Password, "$SHA1$DA39A3EE5E6B4B0D3255BFEF95601890AFD80709", StringComparison.Ordinal)) - { - continue; - } - - user.Password = null; - var serialized = _jsonSerializer.SerializeToBytes(user); - - using (WriteLock.Write()) - using (var connection = CreateConnection()) - { - connection.RunInTransaction(db => - { - using (var statement = db.PrepareStatement("update LocalUsersv2 set data=@data where Id=@InternalId")) - { - statement.TryBind("@InternalId", user.InternalId); - statement.TryBind("@data", serialized); - statement.MoveNext(); - } - - }, TransactionMode); - } - } - - } - - /// - /// Save a user in the repo - /// - public void CreateUser(User user) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - var serialized = _jsonSerializer.SerializeToBytes(user); - - using (WriteLock.Write()) - { - using (var connection = CreateConnection()) - { - connection.RunInTransaction(db => - { - using (var statement = db.PrepareStatement("insert into LocalUsersv2 (guid, data) values (@guid, @data)")) - { - statement.TryBind("@guid", user.Id.ToGuidBlob()); - statement.TryBind("@data", serialized); - - statement.MoveNext(); - } - - var createdUser = GetUser(user.Id, false); - - if (createdUser == null) - { - throw new ApplicationException("created user should never be null"); - } - - user.InternalId = createdUser.InternalId; - - }, TransactionMode); - } - } - } - - public void UpdateUser(User user) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - var serialized = _jsonSerializer.SerializeToBytes(user); - - using (WriteLock.Write()) - { - using (var connection = CreateConnection()) - { - connection.RunInTransaction(db => - { - using (var statement = db.PrepareStatement("update LocalUsersv2 set data=@data where Id=@InternalId")) - { - statement.TryBind("@InternalId", user.InternalId); - statement.TryBind("@data", serialized); - statement.MoveNext(); - } - - }, TransactionMode); - } - } - } - - private User GetUser(Guid guid, bool openLock) - { - using (openLock ? WriteLock.Read() : null) - { - using (var connection = CreateConnection(true)) - { - using (var statement = connection.PrepareStatement("select id,guid,data from LocalUsersv2 where guid=@guid")) - { - statement.TryBind("@guid", guid); - - foreach (var row in statement.ExecuteQuery()) - { - return GetUser(row); - } - } - } - } - - return null; - } - - private User GetUser(IReadOnlyList row) - { - var id = row[0].ToInt64(); - var guid = row[1].ReadGuidFromBlob(); - - using (var stream = new MemoryStream(row[2].ToBlob())) - { - stream.Position = 0; - var user = _jsonSerializer.DeserializeFromStream(stream); - user.InternalId = id; - user.Id = guid; - return user; - } - } - - /// - /// Retrieve all users from the database - /// - /// IEnumerable{User}. - public List RetrieveAllUsers() - { - var list = new List(); - - using (WriteLock.Read()) - { - using (var connection = CreateConnection(true)) - { - foreach (var row in connection.Query("select id,guid,data from LocalUsersv2")) - { - list.Add(GetUser(row)); - } - } - } - - return list; - } - - /// - /// Deletes the user. - /// - /// The user. - /// Task. - /// user - public void DeleteUser(User user) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - using (WriteLock.Write()) - { - using (var connection = CreateConnection()) - { - connection.RunInTransaction(db => - { - using (var statement = db.PrepareStatement("delete from LocalUsersv2 where Id=@id")) - { - statement.TryBind("@id", user.InternalId); - statement.MoveNext(); - } - }, TransactionMode); - } - } - } - } -} + + RemoveEmptyPasswordHashes(); + } + } + + private void TryMigrateToLocalUsersTable(ManagedConnection connection) + { + try + { + connection.RunQueries(new[] + { + "INSERT INTO LocalUsersv2 (guid, data) SELECT guid,data from users" + }); + } + catch (Exception ex) + { + Logger.LogError(ex, "Error migrating users database"); + } + } + + private void RemoveEmptyPasswordHashes() + { + foreach (var user in RetrieveAllUsers()) + { + // If the user password is the sha1 hash of the empty string, remove it + if (!string.Equals(user.Password, "DA39A3EE5E6B4B0D3255BFEF95601890AFD80709", StringComparison.Ordinal) + || !string.Equals(user.Password, "$SHA1$DA39A3EE5E6B4B0D3255BFEF95601890AFD80709", StringComparison.Ordinal)) + { + continue; + } + + user.Password = null; + var serialized = _jsonSerializer.SerializeToBytes(user); + + using (WriteLock.Write()) + using (var connection = CreateConnection()) + { + connection.RunInTransaction(db => + { + using (var statement = db.PrepareStatement("update LocalUsersv2 set data=@data where Id=@InternalId")) + { + statement.TryBind("@InternalId", user.InternalId); + statement.TryBind("@data", serialized); + statement.MoveNext(); + } + + }, TransactionMode); + } + } + + } + + /// + /// Save a user in the repo + /// + public void CreateUser(User user) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + var serialized = _jsonSerializer.SerializeToBytes(user); + + using (WriteLock.Write()) + { + using (var connection = CreateConnection()) + { + connection.RunInTransaction(db => + { + using (var statement = db.PrepareStatement("insert into LocalUsersv2 (guid, data) values (@guid, @data)")) + { + statement.TryBind("@guid", user.Id.ToGuidBlob()); + statement.TryBind("@data", serialized); + + statement.MoveNext(); + } + + var createdUser = GetUser(user.Id, false); + + if (createdUser == null) + { + throw new ApplicationException("created user should never be null"); + } + + user.InternalId = createdUser.InternalId; + + }, TransactionMode); + } + } + } + + public void UpdateUser(User user) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + var serialized = _jsonSerializer.SerializeToBytes(user); + + using (WriteLock.Write()) + { + using (var connection = CreateConnection()) + { + connection.RunInTransaction(db => + { + using (var statement = db.PrepareStatement("update LocalUsersv2 set data=@data where Id=@InternalId")) + { + statement.TryBind("@InternalId", user.InternalId); + statement.TryBind("@data", serialized); + statement.MoveNext(); + } + + }, TransactionMode); + } + } + } + + private User GetUser(Guid guid, bool openLock) + { + using (openLock ? WriteLock.Read() : null) + { + using (var connection = CreateConnection(true)) + { + using (var statement = connection.PrepareStatement("select id,guid,data from LocalUsersv2 where guid=@guid")) + { + statement.TryBind("@guid", guid); + + foreach (var row in statement.ExecuteQuery()) + { + return GetUser(row); + } + } + } + } + + return null; + } + + private User GetUser(IReadOnlyList row) + { + var id = row[0].ToInt64(); + var guid = row[1].ReadGuidFromBlob(); + + using (var stream = new MemoryStream(row[2].ToBlob())) + { + stream.Position = 0; + var user = _jsonSerializer.DeserializeFromStream(stream); + user.InternalId = id; + user.Id = guid; + return user; + } + } + + /// + /// Retrieve all users from the database + /// + /// IEnumerable{User}. + public List RetrieveAllUsers() + { + var list = new List(); + + using (WriteLock.Read()) + { + using (var connection = CreateConnection(true)) + { + foreach (var row in connection.Query("select id,guid,data from LocalUsersv2")) + { + list.Add(GetUser(row)); + } + } + } + + return list; + } + + /// + /// Deletes the user. + /// + /// The user. + /// Task. + /// user + public void DeleteUser(User user) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + using (WriteLock.Write()) + { + using (var connection = CreateConnection()) + { + connection.RunInTransaction(db => + { + using (var statement = db.PrepareStatement("delete from LocalUsersv2 where Id=@id")) + { + statement.TryBind("@id", user.InternalId); + statement.MoveNext(); + } + }, TransactionMode); + } + } + } + } +} diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs index 2ac3ef424..b58374adb 100644 --- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs @@ -1,42 +1,42 @@ -using System; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using MediaBrowser.Controller.Authentication; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Model.Cryptography; - -namespace Emby.Server.Implementations.Library -{ - public class DefaultAuthenticationProvider : IAuthenticationProvider, IRequiresResolvedUser - { - private readonly ICryptoProvider _cryptographyProvider; - public DefaultAuthenticationProvider(ICryptoProvider crypto) - { - _cryptographyProvider = crypto; - } - - public string Name => "Default"; - - public bool IsEnabled => true; - - - //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 - public Task Authenticate(string username, string password) - { - throw new NotImplementedException(); - } - - - //This is the verson that we need to use for local users. Because reasons. - public Task Authenticate(string username, string password, User resolvedUser) - { - bool success = false; - if (resolvedUser == null) - { - throw new Exception("Invalid username or password"); +using System; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MediaBrowser.Controller.Authentication; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.Cryptography; + +namespace Emby.Server.Implementations.Library +{ + public class DefaultAuthenticationProvider : IAuthenticationProvider, IRequiresResolvedUser + { + private readonly ICryptoProvider _cryptographyProvider; + public DefaultAuthenticationProvider(ICryptoProvider crypto) + { + _cryptographyProvider = crypto; + } + + public string Name => "Default"; + + public bool IsEnabled => true; + + + //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 + public Task Authenticate(string username, string password) + { + throw new NotImplementedException(); + } + + + //This is the verson that we need to use for local users. Because reasons. + public Task Authenticate(string username, string password, User resolvedUser) + { + bool success = false; + if (resolvedUser == null) + { + throw new Exception("Invalid username or password"); } //As long as jellyfin supports passwordless users, we need this little block here to accomodate @@ -47,166 +47,164 @@ namespace Emby.Server.Implementations.Library Username = username }); } - - ConvertPasswordFormat(resolvedUser); - byte[] passwordbytes = Encoding.UTF8.GetBytes(password); - - PasswordHash readyHash = new PasswordHash(resolvedUser.Password); - byte[] CalculatedHash; - string CalculatedHashString; - if (_cryptographyProvider.GetSupportedHashMethods().Contains(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); + + ConvertPasswordFormat(resolvedUser); + byte[] passwordbytes = Encoding.UTF8.GetBytes(password); + + PasswordHash readyHash = new PasswordHash(resolvedUser.Password); + byte[] CalculatedHash; + string CalculatedHashString; + if (_cryptographyProvider.GetSupportedHashMethods().Contains(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); } - - if (CalculatedHashString == 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}")); - } - - //var success = string.Equals(GetPasswordHash(resolvedUser), GetHashedString(resolvedUser, password), StringComparison.OrdinalIgnoreCase); - - if (!success) - { - throw new Exception("Invalid username or password"); - } - - return Task.FromResult(new ProviderAuthenticationResult - { - Username = username - }); - } - - //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)) + + if (CalculatedHashString == 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}")); + } + + //var success = string.Equals(GetPasswordHash(resolvedUser), GetHashedString(resolvedUser, password), StringComparison.OrdinalIgnoreCase); + + if (!success) + { + throw new Exception("Invalid username or password"); + } + + return Task.FromResult(new ProviderAuthenticationResult + { + Username = username + }); + } + + //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 HasPassword(User user) - { - var hasConfiguredPassword = !IsPasswordEmpty(user, GetPasswordHash(user)); - return Task.FromResult(hasConfiguredPassword); - } - - private bool IsPasswordEmpty(User user, string password) + + 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 HasPassword(User user) + { + var hasConfiguredPassword = !IsPasswordEmpty(user, GetPasswordHash(user)); + return Task.FromResult(hasConfiguredPassword); + } + + private bool IsPasswordEmpty(User user, string password) { if (string.IsNullOrEmpty(user.Password)) { return string.IsNullOrEmpty(password); } - return false; - } - - public Task ChangePassword(User user, string newPassword) - { + return false; + } + + 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)) { PasswordHash newPasswordHash = new PasswordHash(_cryptographyProvider); - newPasswordHash.SaltBytes = _cryptographyProvider.GenerateSalt(); - newPasswordHash.Salt = PasswordHash.ConvertToByteString(newPasswordHash.SaltBytes); - newPasswordHash.Id = _cryptographyProvider.DefaultHashMethod; + newPasswordHash.SaltBytes = _cryptographyProvider.GenerateSalt(); + newPasswordHash.Salt = PasswordHash.ConvertToByteString(newPasswordHash.SaltBytes); + newPasswordHash.Id = _cryptographyProvider.DefaultHashMethod; newPasswordHash.Hash = GetHashedStringChangeAuth(newPassword, newPasswordHash); user.Password = newPasswordHash.ToString(); 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(); - - return Task.CompletedTask; - } - - public string GetPasswordHash(User user) - { - return user.Password; - } - - public string GetHashedStringChangeAuth(string newPassword, PasswordHash passwordHash) - { - passwordHash.HashBytes = Encoding.UTF8.GetBytes(newPassword); - return PasswordHash.ConvertToByteString(_cryptographyProvider.ComputeHash(passwordHash)); - } - - /// - /// Gets the hashed string. - /// - 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); - } - - 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 - { - //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))); - } - - - } - } -} + 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(); + + return Task.CompletedTask; + } + + public string GetPasswordHash(User user) + { + return user.Password; + } + + public string GetHashedStringChangeAuth(string newPassword, PasswordHash passwordHash) + { + passwordHash.HashBytes = Encoding.UTF8.GetBytes(newPassword); + return PasswordHash.ConvertToByteString(_cryptographyProvider.ComputeHash(passwordHash)); + } + + /// + /// Gets the hashed string. + /// + 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); + } + + 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 + { + //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))); + } + } + } +} -- cgit v1.2.3 From a23f04623ed2738ab1205717674614e9eed6b548 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Sat, 16 Feb 2019 12:39:53 +0100 Subject: Remove IEncryptionManager --- Emby.Server.Implementations/ApplicationHost.cs | 4 +- .../Security/EncryptionManager.cs | 57 ---------------------- .../Security/IEncryptionManager.cs | 19 -------- .../Subtitles/OpenSubtitleDownloader.cs | 19 ++++---- 4 files changed, 11 insertions(+), 88 deletions(-) delete mode 100644 Emby.Server.Implementations/Security/EncryptionManager.cs delete mode 100644 MediaBrowser.Controller/Security/IEncryptionManager.cs (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 8daba0585..dcf2098d4 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -739,10 +739,8 @@ namespace Emby.Server.Implementations TVSeriesManager = new TVSeriesManager(UserManager, UserDataManager, LibraryManager, ServerConfigurationManager); serviceCollection.AddSingleton(TVSeriesManager); - var encryptionManager = new EncryptionManager(); - serviceCollection.AddSingleton(encryptionManager); - DeviceManager = new DeviceManager(AuthenticationRepository, JsonSerializer, LibraryManager, LocalizationManager, UserManager, FileSystemManager, LibraryMonitor, ServerConfigurationManager); + serviceCollection.AddSingleton(DeviceManager); MediaSourceManager = new MediaSourceManager(ItemRepository, ApplicationPaths, LocalizationManager, UserManager, LibraryManager, LoggerFactory, JsonSerializer, FileSystemManager, UserDataManager, () => MediaEncoder); diff --git a/Emby.Server.Implementations/Security/EncryptionManager.cs b/Emby.Server.Implementations/Security/EncryptionManager.cs deleted file mode 100644 index fa8872ccc..000000000 --- a/Emby.Server.Implementations/Security/EncryptionManager.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System; -using System.Text; -using MediaBrowser.Controller.Security; - -namespace Emby.Server.Implementations.Security -{ - public class EncryptionManager : IEncryptionManager - { - /// - /// Encrypts the string. - /// - /// The value. - /// System.String. - /// value - public string EncryptString(string value) - { - if (value == null) - { - throw new ArgumentNullException(nameof(value)); - } - - return EncryptStringUniversal(value); - } - - /// - /// Decrypts the string. - /// - /// The value. - /// System.String. - /// value - public string DecryptString(string value) - { - if (value == null) - { - throw new ArgumentNullException(nameof(value)); - } - - return DecryptStringUniversal(value); - } - - private static string EncryptStringUniversal(string value) - { - // Yes, this isn't good, but ProtectedData in mono is throwing exceptions, so use this for now - - var bytes = Encoding.UTF8.GetBytes(value); - return Convert.ToBase64String(bytes); - } - - private static string DecryptStringUniversal(string value) - { - // Yes, this isn't good, but ProtectedData in mono is throwing exceptions, so use this for now - - var bytes = Convert.FromBase64String(value); - return Encoding.UTF8.GetString(bytes, 0, bytes.Length); - } - } -} diff --git a/MediaBrowser.Controller/Security/IEncryptionManager.cs b/MediaBrowser.Controller/Security/IEncryptionManager.cs deleted file mode 100644 index 68680fdf3..000000000 --- a/MediaBrowser.Controller/Security/IEncryptionManager.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace MediaBrowser.Controller.Security -{ - public interface IEncryptionManager - { - /// - /// Encrypts the string. - /// - /// The value. - /// System.String. - string EncryptString(string value); - - /// - /// Decrypts the string. - /// - /// The value. - /// System.String. - string DecryptString(string value); - } -} diff --git a/MediaBrowser.MediaEncoding/Subtitles/OpenSubtitleDownloader.cs b/MediaBrowser.MediaEncoding/Subtitles/OpenSubtitleDownloader.cs index 6a5162b8d..c76ff3fed 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/OpenSubtitleDownloader.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/OpenSubtitleDownloader.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; +using System.Text; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Configuration; @@ -29,17 +30,15 @@ namespace MediaBrowser.MediaEncoding.Subtitles private readonly CultureInfo _usCulture = new CultureInfo("en-US"); private readonly IServerConfigurationManager _config; - private readonly IEncryptionManager _encryption; private readonly IJsonSerializer _json; private readonly IFileSystem _fileSystem; - public OpenSubtitleDownloader(ILoggerFactory loggerFactory, IHttpClient httpClient, IServerConfigurationManager config, IEncryptionManager encryption, IJsonSerializer json, IFileSystem fileSystem) + public OpenSubtitleDownloader(ILoggerFactory loggerFactory, IHttpClient httpClient, IServerConfigurationManager config, IJsonSerializer json, IFileSystem fileSystem) { _logger = loggerFactory.CreateLogger(GetType().Name); _httpClient = httpClient; _config = config; - _encryption = encryption; _json = json; _fileSystem = fileSystem; @@ -63,16 +62,17 @@ namespace MediaBrowser.MediaEncoding.Subtitles !string.IsNullOrWhiteSpace(options.OpenSubtitlesPasswordHash) && !options.OpenSubtitlesPasswordHash.StartsWith(PasswordHashPrefix, StringComparison.OrdinalIgnoreCase)) { - options.OpenSubtitlesPasswordHash = EncryptPassword(options.OpenSubtitlesPasswordHash); + options.OpenSubtitlesPasswordHash = ToBase64EncodedString(options.OpenSubtitlesPasswordHash); } } - private string EncryptPassword(string password) + private static string ToBase64EncodedString(string password) { - return PasswordHashPrefix + _encryption.EncryptString(password); + var bytes = Encoding.UTF8.GetBytes(password); + return PasswordHashPrefix + bytes; } - private string DecryptPassword(string password) + private static string DecodeBase64EncodedString(string password) { if (password == null || !password.StartsWith(PasswordHashPrefix, StringComparison.OrdinalIgnoreCase)) @@ -80,7 +80,8 @@ namespace MediaBrowser.MediaEncoding.Subtitles return string.Empty; } - return _encryption.DecryptString(password.Substring(2)); + var bytes = Convert.FromBase64String(password.Substring(2)); + return Encoding.UTF8.GetString(bytes, 0, bytes.Length); } public string Name => "Open Subtitles"; @@ -186,7 +187,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles var options = GetOptions(); var user = options.OpenSubtitlesUsername ?? string.Empty; - var password = DecryptPassword(options.OpenSubtitlesPasswordHash); + var password = DecodeBase64EncodedString(options.OpenSubtitlesPasswordHash); var loginResponse = await OpenSubtitles.LogInAsync(user, password, "en", cancellationToken).ConfigureAwait(false); -- cgit v1.2.3 From 320707d44c403b167f6365299054b1a86a9d15cf Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Wed, 20 Feb 2019 16:49:03 +0100 Subject: Reduce string allocations at startup --- .../Services/ServicePath.cs | 161 +++++++++------------ .../Services/StringMapTypeDeserializer.cs | 52 ++++--- 2 files changed, 91 insertions(+), 122 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Services/ServicePath.cs b/Emby.Server.Implementations/Services/ServicePath.cs index f575baca3..ccb28e8df 100644 --- a/Emby.Server.Implementations/Services/ServicePath.cs +++ b/Emby.Server.Implementations/Services/ServicePath.cs @@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Services private const char ComponentSeperator = '.'; private const string VariablePrefix = "{"; - readonly bool[] componentsWithSeparators; + private readonly bool[] componentsWithSeparators; private readonly string restPath; public bool IsWildCardPath { get; private set; } @@ -54,10 +54,6 @@ namespace Emby.Server.Implementations.Services public string Description { get; private set; } public bool IsHidden { get; private set; } - public int Priority { get; set; } //passed back to RouteAttribute - - public IEnumerable PathVariables => this.variablesNames.Where(e => !string.IsNullOrWhiteSpace(e)); - public static string[] GetPathPartsForMatching(string pathInfo) { return pathInfo.ToLowerInvariant().Split(new[] { PathSeperatorChar }, StringSplitOptions.RemoveEmptyEntries); @@ -83,9 +79,12 @@ namespace Emby.Server.Implementations.Services { list.Add(hashPrefix + part); - var subParts = part.Split(ComponentSeperator); - if (subParts.Length == 1) continue; + if (part.IndexOf(ComponentSeperator) == -1) + { + continue; + } + var subParts = part.Split(ComponentSeperator); foreach (var subPart in subParts) { list.Add(hashPrefix + subPart); @@ -114,7 +113,7 @@ namespace Emby.Server.Implementations.Services { if (string.IsNullOrEmpty(component)) continue; - if (StringContains(component, VariablePrefix) + if (component.IndexOf(VariablePrefix, StringComparison.OrdinalIgnoreCase) != -1 && component.IndexOf(ComponentSeperator) != -1) { hasSeparators.Add(true); @@ -165,7 +164,11 @@ namespace Emby.Server.Implementations.Services for (var i = 0; i < components.Length - 1; i++) { - if (!this.isWildcard[i]) continue; + if (!this.isWildcard[i]) + { + continue; + } + if (this.literalsToMatch[i + 1] == null) { throw new ArgumentException( @@ -173,7 +176,7 @@ namespace Emby.Server.Implementations.Services } } - this.wildcardCount = this.isWildcard.Count(x => x); + this.wildcardCount = this.isWildcard.Length; this.IsWildCardPath = this.wildcardCount > 0; this.FirstMatchHashKey = !this.IsWildCardPath @@ -181,19 +184,14 @@ namespace Emby.Server.Implementations.Services : WildCardChar + PathSeperator + firstLiteralMatch; this.typeDeserializer = new StringMapTypeDeserializer(createInstanceFn, getParseFn, this.RequestType); - RegisterCaseInsenstivePropertyNameMappings(); - } - private void RegisterCaseInsenstivePropertyNameMappings() - { - foreach (var propertyInfo in GetSerializableProperties(RequestType)) - { - var propertyName = propertyInfo.Name; - propertyNamesMap.Add(propertyName.ToLowerInvariant(), propertyName); - } + _propertyNamesMap = new HashSet( + GetSerializableProperties(RequestType).Select(x => x.Name), + StringComparer.OrdinalIgnoreCase); } - internal static string[] IgnoreAttributesNamed = new[] { + internal static string[] IgnoreAttributesNamed = new[] + { "IgnoreDataMemberAttribute", "JsonIgnoreAttribute" }; @@ -201,19 +199,12 @@ namespace Emby.Server.Implementations.Services private static Type excludeType = typeof(Stream); - internal static List GetSerializableProperties(Type type) + internal static IEnumerable GetSerializableProperties(Type type) { - var list = new List(); - var props = GetPublicProperties(type); - - foreach (var prop in props) + foreach (var prop in GetPublicProperties(type)) { - if (prop.GetMethod == null) - { - continue; - } - - if (excludeType == prop.PropertyType) + if (prop.GetMethod == null + || excludeType == prop.PropertyType) { continue; } @@ -230,23 +221,21 @@ namespace Emby.Server.Implementations.Services if (!ignored) { - list.Add(prop); + yield return prop; } } - - // else return those properties that are not decorated with IgnoreDataMember - return list; } - private static List GetPublicProperties(Type type) + private static IEnumerable GetPublicProperties(Type type) { - if (type.GetTypeInfo().IsInterface) + if (type.IsInterface) { var propertyInfos = new List(); - - var considered = new List(); + var considered = new List() + { + type + }; var queue = new Queue(); - considered.Add(type); queue.Enqueue(type); while (queue.Count > 0) @@ -254,15 +243,16 @@ namespace Emby.Server.Implementations.Services var subType = queue.Dequeue(); foreach (var subInterface in subType.GetTypeInfo().ImplementedInterfaces) { - if (considered.Contains(subInterface)) continue; + if (considered.Contains(subInterface)) + { + continue; + } considered.Add(subInterface); queue.Enqueue(subInterface); } - var typeProperties = GetTypesPublicProperties(subType); - - var newPropertyInfos = typeProperties + var newPropertyInfos = GetTypesPublicProperties(subType) .Where(x => !propertyInfos.Contains(x)); propertyInfos.InsertRange(0, newPropertyInfos); @@ -271,28 +261,22 @@ namespace Emby.Server.Implementations.Services return propertyInfos; } - var list = new List(); - - foreach (var t in GetTypesPublicProperties(type)) - { - if (t.GetIndexParameters().Length == 0) - { - list.Add(t); - } - } - return list; + return GetTypesPublicProperties(type) + .Where(x => x.GetIndexParameters().Length == 0); } - private static PropertyInfo[] GetTypesPublicProperties(Type subType) + private static IEnumerable GetTypesPublicProperties(Type subType) { - var pis = new List(); foreach (var pi in subType.GetRuntimeProperties()) { var mi = pi.GetMethod ?? pi.SetMethod; - if (mi != null && mi.IsStatic) continue; - pis.Add(pi); + if (mi != null && mi.IsStatic) + { + continue; + } + + yield return pi; } - return pis.ToArray(); } /// @@ -302,7 +286,7 @@ namespace Emby.Server.Implementations.Services private readonly StringMapTypeDeserializer typeDeserializer; - private readonly Dictionary propertyNamesMap = new Dictionary(); + private readonly HashSet _propertyNamesMap; public int MatchScore(string httpMethod, string[] withPathInfoParts) { @@ -312,13 +296,10 @@ namespace Emby.Server.Implementations.Services return -1; } - var score = 0; - //Routes with least wildcard matches get the highest score - score += Math.Max((100 - wildcardMatchCount), 1) * 1000; - - //Routes with less variable (and more literal) matches - score += Math.Max((10 - VariableArgsCount), 1) * 100; + var score = Math.Max((100 - wildcardMatchCount), 1) * 1000 + //Routes with less variable (and more literal) matches + + Math.Max((10 - VariableArgsCount), 1) * 100; //Exact verb match is better than ANY if (Verbs.Length == 1 && string.Equals(httpMethod, Verbs[0], StringComparison.OrdinalIgnoreCase)) @@ -333,11 +314,6 @@ namespace Emby.Server.Implementations.Services return score; } - private bool StringContains(string str1, string str2) - { - return str1.IndexOf(str2, StringComparison.OrdinalIgnoreCase) != -1; - } - /// /// For performance withPathInfoParts should already be a lower case string /// to minimize redundant matching operations. @@ -374,7 +350,8 @@ namespace Emby.Server.Implementations.Services if (i < this.TotalComponentsCount - 1) { // Continue to consume up until a match with the next literal - while (pathIx < withPathInfoParts.Length && !LiteralsEqual(withPathInfoParts[pathIx], this.literalsToMatch[i + 1])) + while (pathIx < withPathInfoParts.Length + && !string.Equals(withPathInfoParts[pathIx], this.literalsToMatch[i + 1], StringComparison.InvariantCultureIgnoreCase)) { pathIx++; wildcardMatchCount++; @@ -403,10 +380,12 @@ namespace Emby.Server.Implementations.Services continue; } - if (withPathInfoParts.Length <= pathIx || !LiteralsEqual(withPathInfoParts[pathIx], literalToMatch)) + if (withPathInfoParts.Length <= pathIx + || !string.Equals(withPathInfoParts[pathIx], literalToMatch, StringComparison.InvariantCultureIgnoreCase)) { return false; } + pathIx++; } } @@ -414,35 +393,26 @@ namespace Emby.Server.Implementations.Services return pathIx == withPathInfoParts.Length; } - private static bool LiteralsEqual(string str1, string str2) - { - // Most cases - if (string.Equals(str1, str2, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - - // Handle turkish i - str1 = str1.ToUpperInvariant(); - str2 = str2.ToUpperInvariant(); - - // Invariant IgnoreCase would probably be better but it's not available in PCL - return string.Equals(str1, str2, StringComparison.CurrentCultureIgnoreCase); - } - private bool ExplodeComponents(ref string[] withPathInfoParts) { var totalComponents = new List(); for (var i = 0; i < withPathInfoParts.Length; i++) { var component = withPathInfoParts[i]; - if (string.IsNullOrEmpty(component)) continue; + if (string.IsNullOrEmpty(component)) + { + continue; + } if (this.PathComponentsCount != this.TotalComponentsCount && this.componentsWithSeparators[i]) { var subComponents = component.Split(ComponentSeperator); - if (subComponents.Length < 2) return false; + if (subComponents.Length < 2) + { + return false; + } + totalComponents.AddRange(subComponents); } else @@ -483,7 +453,7 @@ namespace Emby.Server.Implementations.Services continue; } - if (!this.propertyNamesMap.TryGetValue(variableName.ToLowerInvariant(), out var propertyNameOnRequest)) + if (!this._propertyNamesMap.Contains(variableName)) { if (string.Equals("ignore", variableName, StringComparison.OrdinalIgnoreCase)) { @@ -507,6 +477,7 @@ namespace Emby.Server.Implementations.Services { sb.Append(PathSeperatorChar + requestComponents[j]); } + value = sb.ToString(); } else @@ -517,13 +488,13 @@ namespace Emby.Server.Implementations.Services var stopLiteral = i == this.TotalComponentsCount - 1 ? null : this.literalsToMatch[i + 1]; if (!string.Equals(requestComponents[pathIx], stopLiteral, StringComparison.OrdinalIgnoreCase)) { - var sb = new StringBuilder(); - sb.Append(value); + var sb = new StringBuilder(value); pathIx++; while (!string.Equals(requestComponents[pathIx], stopLiteral, StringComparison.OrdinalIgnoreCase)) { sb.Append(PathSeperatorChar + requestComponents[pathIx++]); } + value = sb.ToString(); } else @@ -538,7 +509,7 @@ namespace Emby.Server.Implementations.Services pathIx++; } - requestKeyValuesMap[propertyNameOnRequest] = value; + requestKeyValuesMap[variableName] = value; } if (queryStringAndFormData != null) diff --git a/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs b/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs index d13935fba..f835aa1b5 100644 --- a/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs +++ b/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs @@ -11,15 +11,16 @@ namespace Emby.Server.Implementations.Services { internal class PropertySerializerEntry { - public PropertySerializerEntry(Action propertySetFn, Func propertyParseStringFn) + public PropertySerializerEntry(Action propertySetFn, Func propertyParseStringFn, Type propertyType) { PropertySetFn = propertySetFn; PropertyParseStringFn = propertyParseStringFn; + PropertyType = PropertyType; } - public Action PropertySetFn; - public Func PropertyParseStringFn; - public Type PropertyType; + public Action PropertySetFn { get; private set; } + public Func PropertyParseStringFn { get; private set; } + public Type PropertyType { get; private set; } } private readonly Type type; @@ -29,7 +30,9 @@ namespace Emby.Server.Implementations.Services public Func GetParseFn(Type propertyType) { if (propertyType == typeof(string)) + { return s => s; + } return _GetParseFn(propertyType); } @@ -48,7 +51,7 @@ namespace Emby.Server.Implementations.Services var propertySetFn = TypeAccessor.GetSetPropertyMethod(type, propertyInfo); var propertyType = propertyInfo.PropertyType; var propertyParseStringFn = GetParseFn(propertyType); - var propertySerializer = new PropertySerializerEntry(propertySetFn, propertyParseStringFn) { PropertyType = propertyType }; + var propertySerializer = new PropertySerializerEntry(propertySetFn, propertyParseStringFn, propertyType); propertySetterMap[propertyInfo.Name] = propertySerializer; } @@ -56,34 +59,21 @@ namespace Emby.Server.Implementations.Services public object PopulateFromMap(object instance, IDictionary keyValuePairs) { - string propertyName = null; - string propertyTextValue = null; PropertySerializerEntry propertySerializerEntry = null; if (instance == null) + { instance = _CreateInstanceFn(type); + } foreach (var pair in keyValuePairs) { - propertyName = pair.Key; - propertyTextValue = pair.Value; - - if (string.IsNullOrEmpty(propertyTextValue)) - { - continue; - } + string propertyName = pair.Key; + string propertyTextValue = pair.Value; - if (!propertySetterMap.TryGetValue(propertyName, out propertySerializerEntry)) - { - if (propertyName == "v") - { - continue; - } - - continue; - } - - if (propertySerializerEntry.PropertySetFn == null) + if (string.IsNullOrEmpty(propertyTextValue) + || !propertySetterMap.TryGetValue(propertyName, out propertySerializerEntry) + || propertySerializerEntry.PropertySetFn == null) { continue; } @@ -99,6 +89,7 @@ namespace Emby.Server.Implementations.Services { continue; } + propertySerializerEntry.PropertySetFn(instance, value); } @@ -107,7 +98,11 @@ namespace Emby.Server.Implementations.Services public static string LeftPart(string strVal, char needle) { - if (strVal == null) return null; + if (strVal == null) + { + return null; + } + var pos = strVal.IndexOf(needle); return pos == -1 ? strVal @@ -119,7 +114,10 @@ namespace Emby.Server.Implementations.Services { public static Action GetSetPropertyMethod(Type type, PropertyInfo propertyInfo) { - if (!propertyInfo.CanWrite || propertyInfo.GetIndexParameters().Length > 0) return null; + if (!propertyInfo.CanWrite || propertyInfo.GetIndexParameters().Length > 0) + { + return null; + } var setMethodInfo = propertyInfo.SetMethod; return (instance, value) => setMethodInfo.Invoke(instance, new[] { value }); -- cgit v1.2.3 From cf4e64f4309a40ff50607d418970bc2767a275bb Mon Sep 17 00:00:00 2001 From: Xu Fasheng Date: Thu, 21 Feb 2019 22:35:31 +0800 Subject: Add option to toggle if ignore virtual interfaces Some VPN like ZerotierOne owns IP address but no gateway, and there is no good idea in NetworkManager.GetIPsDefault() to filter such virtual interfaces, so just provide one option to let user decide it. --- Emby.Dlna/Main/DlnaEntryPoint.cs | 2 +- Emby.Server.Implementations/ApplicationHost.cs | 2 +- Emby.Server.Implementations/Networking/NetworkManager.cs | 12 ++++++------ MediaBrowser.Common/Net/INetworkManager.cs | 2 +- MediaBrowser.Model/Configuration/ServerConfiguration.cs | 2 ++ RSSDP/RSSDP.csproj | 1 + RSSDP/SsdpCommunicationsServer.cs | 8 ++++++-- 7 files changed, 18 insertions(+), 11 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs index a20006578..4eb4cde81 100644 --- a/Emby.Dlna/Main/DlnaEntryPoint.cs +++ b/Emby.Dlna/Main/DlnaEntryPoint.cs @@ -171,7 +171,7 @@ namespace Emby.Dlna.Main { var enableMultiSocketBinding = _environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows; - _communicationsServer = new SsdpCommunicationsServer(_socketFactory, _networkManager, _logger, enableMultiSocketBinding) + _communicationsServer = new SsdpCommunicationsServer(_config, _socketFactory, _networkManager, _logger, enableMultiSocketBinding) { IsShared = true }; diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 042b04b3b..e9f43a0ea 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1577,7 +1577,7 @@ namespace Emby.Server.Implementations if (addresses.Count == 0) { - addresses.AddRange(NetworkManager.GetLocalIpAddresses()); + addresses.AddRange(NetworkManager.GetLocalIpAddresses(ServerConfigurationManager.Configuration.IgnoreVirtualInterfaces)); } var resultList = new List(); diff --git a/Emby.Server.Implementations/Networking/NetworkManager.cs b/Emby.Server.Implementations/Networking/NetworkManager.cs index 60cc9b88e..b7a125f20 100644 --- a/Emby.Server.Implementations/Networking/NetworkManager.cs +++ b/Emby.Server.Implementations/Networking/NetworkManager.cs @@ -79,13 +79,13 @@ namespace Emby.Server.Implementations.Networking private IpAddressInfo[] _localIpAddresses; private readonly object _localIpAddressSyncLock = new object(); - public IpAddressInfo[] GetLocalIpAddresses() + public IpAddressInfo[] GetLocalIpAddresses(bool ignoreVirtualInterface = true) { lock (_localIpAddressSyncLock) { if (_localIpAddresses == null) { - var addresses = GetLocalIpAddressesInternal().Result.Select(ToIpAddressInfo).ToArray(); + var addresses = GetLocalIpAddressesInternal(ignoreVirtualInterface).Result.Select(ToIpAddressInfo).ToArray(); _localIpAddresses = addresses; @@ -95,9 +95,9 @@ namespace Emby.Server.Implementations.Networking } } - private async Task> GetLocalIpAddressesInternal() + private async Task> GetLocalIpAddressesInternal(bool ignoreVirtualInterface) { - var list = GetIPsDefault() + var list = GetIPsDefault(ignoreVirtualInterface) .ToList(); if (list.Count == 0) @@ -383,7 +383,7 @@ namespace Emby.Server.Implementations.Networking return Dns.GetHostAddressesAsync(hostName); } - private List GetIPsDefault() + private List GetIPsDefault(bool ignoreVirtualInterface) { NetworkInterface[] interfaces; @@ -414,7 +414,7 @@ namespace Emby.Server.Implementations.Networking // Try to exclude virtual adapters // http://stackoverflow.com/questions/8089685/c-sharp-finding-my-machines-local-ip-address-and-not-the-vms var addr = ipProperties.GatewayAddresses.FirstOrDefault(); - if (addr == null || string.Equals(addr.Address.ToString(), "0.0.0.0", StringComparison.OrdinalIgnoreCase)) + if (addr == null || (ignoreVirtualInterface && string.Equals(addr.Address.ToString(), "0.0.0.0", StringComparison.OrdinalIgnoreCase))) { return new List(); } diff --git a/MediaBrowser.Common/Net/INetworkManager.cs b/MediaBrowser.Common/Net/INetworkManager.cs index 72fb6e2b8..3364230d1 100644 --- a/MediaBrowser.Common/Net/INetworkManager.cs +++ b/MediaBrowser.Common/Net/INetworkManager.cs @@ -53,7 +53,7 @@ namespace MediaBrowser.Common.Net /// true if [is in local network] [the specified endpoint]; otherwise, false. bool IsInLocalNetwork(string endpoint); - IpAddressInfo[] GetLocalIpAddresses(); + IpAddressInfo[] GetLocalIpAddresses(bool ignoreVirtualInterface); IpAddressInfo ParseIpAddress(string ipAddress); diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index ed5800329..0ba36b4b9 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -178,6 +178,7 @@ namespace MediaBrowser.Model.Configuration public string[] LocalNetworkSubnets { get; set; } public string[] LocalNetworkAddresses { get; set; } public string[] CodecsUsed { get; set; } + public bool IgnoreVirtualInterfaces { get; set; } public bool EnableExternalContentInSuggestions { get; set; } public bool RequireHttps { get; set; } public bool IsBehindProxy { get; set; } @@ -205,6 +206,7 @@ namespace MediaBrowser.Model.Configuration CodecsUsed = Array.Empty(); ImageExtractionTimeoutMs = 0; PathSubstitutions = Array.Empty(); + IgnoreVirtualInterfaces = false; EnableSimpleArtistDetection = true; DisplaySpecialsWithinSeasons = true; diff --git a/RSSDP/RSSDP.csproj b/RSSDP/RSSDP.csproj index f06d4687b..456a93aa8 100644 --- a/RSSDP/RSSDP.csproj +++ b/RSSDP/RSSDP.csproj @@ -3,6 +3,7 @@ + diff --git a/RSSDP/SsdpCommunicationsServer.cs b/RSSDP/SsdpCommunicationsServer.cs index 04e76ef59..9da906b49 100644 --- a/RSSDP/SsdpCommunicationsServer.cs +++ b/RSSDP/SsdpCommunicationsServer.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using MediaBrowser.Common.Net; using Microsoft.Extensions.Logging; using MediaBrowser.Model.Net; +using MediaBrowser.Controller.Configuration; namespace Rssdp.Infrastructure { @@ -45,6 +46,7 @@ namespace Rssdp.Infrastructure private readonly ILogger _logger; private ISocketFactory _SocketFactory; private readonly INetworkManager _networkManager; + private readonly IServerConfigurationManager _config; private int _LocalPort; private int _MulticastTtl; @@ -74,9 +76,11 @@ namespace Rssdp.Infrastructure /// Minimum constructor. /// /// The argument is null. - public SsdpCommunicationsServer(ISocketFactory socketFactory, INetworkManager networkManager, ILogger logger, bool enableMultiSocketBinding) + public SsdpCommunicationsServer(IServerConfigurationManager config, ISocketFactory socketFactory, + INetworkManager networkManager, ILogger logger, bool enableMultiSocketBinding) : this(socketFactory, 0, SsdpConstants.SsdpDefaultMulticastTimeToLive, networkManager, logger, enableMultiSocketBinding) { + _config = config; } /// @@ -363,7 +367,7 @@ namespace Rssdp.Infrastructure if (_enableMultiSocketBinding) { - foreach (var address in _networkManager.GetLocalIpAddresses()) + foreach (var address in _networkManager.GetLocalIpAddresses(_config.Configuration.IgnoreVirtualInterfaces)) { if (address.AddressFamily == IpAddressFamily.InterNetworkV6) { -- cgit v1.2.3 From cbd0e71c077e6233bcbc751f9a2f1ee742000ba3 Mon Sep 17 00:00:00 2001 From: Xu Fasheng Date: Fri, 22 Feb 2019 12:06:49 +0800 Subject: Send DLNA devices message to only the matched interface This will be the right way for multiple interfaces, or the client will receive all devices message with different IP addresses and could not detect which one could access. And provide one option DlnaOptions.SendOnlyMatchedHost to fallback to old behaviour if this commit missed something. --- Emby.Dlna/Configuration/DlnaOptions.cs | 2 + Emby.Dlna/Main/DlnaEntryPoint.cs | 4 +- .../Networking/NetworkManager.cs | 60 ++++++++++++++++++++++ MediaBrowser.Common/Net/INetworkManager.cs | 3 ++ MediaBrowser.Model/Net/IpAddressInfo.cs | 1 + RSSDP/ISsdpCommunicationsServer.cs | 6 +-- RSSDP/SsdpCommunicationsServer.cs | 13 ++--- RSSDP/SsdpDeviceLocator.cs | 2 +- RSSDP/SsdpDevicePublisher.cs | 30 +++++++++-- RSSDP/SsdpRootDevice.cs | 10 ++++ 10 files changed, 116 insertions(+), 15 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Dlna/Configuration/DlnaOptions.cs b/Emby.Dlna/Configuration/DlnaOptions.cs index 0ebb490a1..c7cb364a8 100644 --- a/Emby.Dlna/Configuration/DlnaOptions.cs +++ b/Emby.Dlna/Configuration/DlnaOptions.cs @@ -7,6 +7,7 @@ namespace Emby.Dlna.Configuration public bool EnableServer { get; set; } public bool EnableDebugLog { get; set; } public bool BlastAliveMessages { get; set; } + public bool SendOnlyMatchedHost { get; set; } public int ClientDiscoveryIntervalSeconds { get; set; } public int BlastAliveMessageIntervalSeconds { get; set; } public string DefaultUserId { get; set; } @@ -16,6 +17,7 @@ namespace Emby.Dlna.Configuration EnablePlayTo = true; EnableServer = true; BlastAliveMessages = true; + SendOnlyMatchedHost = true; ClientDiscoveryIntervalSeconds = 60; BlastAliveMessageIntervalSeconds = 1800; } diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs index 8eff7f773..427b3a5c0 100644 --- a/Emby.Dlna/Main/DlnaEntryPoint.cs +++ b/Emby.Dlna/Main/DlnaEntryPoint.cs @@ -230,7 +230,7 @@ namespace Emby.Dlna.Main try { - _Publisher = new SsdpDevicePublisher(_communicationsServer, _environmentInfo.OperatingSystemName, _environmentInfo.OperatingSystemVersion); + _Publisher = new SsdpDevicePublisher(_communicationsServer, _networkManager, _environmentInfo.OperatingSystemName, _environmentInfo.OperatingSystemVersion, _config.GetDlnaConfiguration().SendOnlyMatchedHost); _Publisher.LogFunction = LogMessage; _Publisher.SupportPnpRootDevice = false; @@ -269,6 +269,8 @@ namespace Emby.Dlna.Main { CacheLifetime = TimeSpan.FromSeconds(1800), //How long SSDP clients can cache this info. Location = uri, // Must point to the URL that serves your devices UPnP description document. + Address = address, + SubnetMask = _networkManager.GetLocalIpSubnetMask(address), FriendlyName = "Jellyfin", Manufacturer = "Jellyfin", ModelName = "Jellyfin Server", diff --git a/Emby.Server.Implementations/Networking/NetworkManager.cs b/Emby.Server.Implementations/Networking/NetworkManager.cs index b7a125f20..8696d1896 100644 --- a/Emby.Server.Implementations/Networking/NetworkManager.cs +++ b/Emby.Server.Implementations/Networking/NetworkManager.cs @@ -636,6 +636,66 @@ namespace Emby.Server.Implementations.Networking return false; } + public bool IsInSameSubnet(IpAddressInfo address1, IpAddressInfo address2, IpAddressInfo subnetMask) + { + IPAddress network1 = GetNetworkAddress(ToIPAddress(address1), ToIPAddress(subnetMask)); + IPAddress network2 = GetNetworkAddress(ToIPAddress(address2), ToIPAddress(subnetMask)); + return network1.Equals(network2); + } + + private IPAddress GetNetworkAddress(IPAddress address, IPAddress subnetMask) + { + byte[] ipAdressBytes = address.GetAddressBytes(); + byte[] subnetMaskBytes = subnetMask.GetAddressBytes(); + + if (ipAdressBytes.Length != subnetMaskBytes.Length) + { + throw new ArgumentException("Lengths of IP address and subnet mask do not match."); + } + + byte[] broadcastAddress = new byte[ipAdressBytes.Length]; + for (int i = 0; i < broadcastAddress.Length; i++) + { + broadcastAddress[i] = (byte)(ipAdressBytes[i] & (subnetMaskBytes[i])); + } + return new IPAddress(broadcastAddress); + } + + public IpAddressInfo GetLocalIpSubnetMask(IpAddressInfo address) + { + NetworkInterface[] interfaces; + IPAddress ipaddress = ToIPAddress(address); + + try + { + var validStatuses = new[] { OperationalStatus.Up, OperationalStatus.Unknown }; + + interfaces = NetworkInterface.GetAllNetworkInterfaces() + .Where(i => validStatuses.Contains(i.OperationalStatus)) + .ToArray(); + } + catch (Exception ex) + { + Logger.LogError(ex, "Error in GetAllNetworkInterfaces"); + return null; + } + + foreach (NetworkInterface ni in interfaces) + { + if (ni.GetIPProperties().GatewayAddresses.FirstOrDefault() != null) + { + foreach (UnicastIPAddressInformation ip in ni.GetIPProperties().UnicastAddresses) + { + if (ip.Address.Equals(ipaddress) && ip.IPv4Mask != null) + { + return ToIpAddressInfo(ip.IPv4Mask); + } + } + } + } + return null; + } + public static IpEndPointInfo ToIpEndPointInfo(IPEndPoint endpoint) { if (endpoint == null) diff --git a/MediaBrowser.Common/Net/INetworkManager.cs b/MediaBrowser.Common/Net/INetworkManager.cs index 3364230d1..34c6f5866 100644 --- a/MediaBrowser.Common/Net/INetworkManager.cs +++ b/MediaBrowser.Common/Net/INetworkManager.cs @@ -62,5 +62,8 @@ namespace MediaBrowser.Common.Net Task GetHostAddressesAsync(string host); bool IsAddressInSubnets(string addressString, string[] subnets); + + bool IsInSameSubnet(IpAddressInfo address1, IpAddressInfo address2, IpAddressInfo subnetMask); + IpAddressInfo GetLocalIpSubnetMask(IpAddressInfo address); } } diff --git a/MediaBrowser.Model/Net/IpAddressInfo.cs b/MediaBrowser.Model/Net/IpAddressInfo.cs index 7a278d4d4..87fa55bca 100644 --- a/MediaBrowser.Model/Net/IpAddressInfo.cs +++ b/MediaBrowser.Model/Net/IpAddressInfo.cs @@ -10,6 +10,7 @@ namespace MediaBrowser.Model.Net public static IpAddressInfo IPv6Loopback = new IpAddressInfo("::1", IpAddressFamily.InterNetworkV6); public string Address { get; set; } + public IpAddressInfo SubnetMask { get; set; } public IpAddressFamily AddressFamily { get; set; } public IpAddressInfo(string address, IpAddressFamily addressFamily) diff --git a/RSSDP/ISsdpCommunicationsServer.cs b/RSSDP/ISsdpCommunicationsServer.cs index ef75f997f..c99d684a1 100644 --- a/RSSDP/ISsdpCommunicationsServer.cs +++ b/RSSDP/ISsdpCommunicationsServer.cs @@ -45,8 +45,8 @@ namespace Rssdp.Infrastructure /// /// Sends a message to the SSDP multicast address and port. /// - Task SendMulticastMessage(string message, CancellationToken cancellationToken); - Task SendMulticastMessage(string message, int sendCount, CancellationToken cancellationToken); + Task SendMulticastMessage(string message, IpAddressInfo fromLocalIpAddress, CancellationToken cancellationToken); + Task SendMulticastMessage(string message, int sendCount, IpAddressInfo fromLocalIpAddress, CancellationToken cancellationToken); #endregion @@ -63,4 +63,4 @@ namespace Rssdp.Infrastructure #endregion } -} \ No newline at end of file +} diff --git a/RSSDP/SsdpCommunicationsServer.cs b/RSSDP/SsdpCommunicationsServer.cs index 9da906b49..ea4d79c99 100644 --- a/RSSDP/SsdpCommunicationsServer.cs +++ b/RSSDP/SsdpCommunicationsServer.cs @@ -240,15 +240,15 @@ namespace Rssdp.Infrastructure } } - public Task SendMulticastMessage(string message, CancellationToken cancellationToken) + public Task SendMulticastMessage(string message, IpAddressInfo fromLocalIpAddress, CancellationToken cancellationToken) { - return SendMulticastMessage(message, SsdpConstants.UdpResendCount, cancellationToken); + return SendMulticastMessage(message, SsdpConstants.UdpResendCount, fromLocalIpAddress, cancellationToken); } /// /// Sends a message to the SSDP multicast address and port. /// - public async Task SendMulticastMessage(string message, int sendCount, CancellationToken cancellationToken) + public async Task SendMulticastMessage(string message, int sendCount, IpAddressInfo fromLocalIpAddress, CancellationToken cancellationToken) { if (message == null) throw new ArgumentNullException(nameof(message)); @@ -268,7 +268,7 @@ namespace Rssdp.Infrastructure IpAddress = new IpAddressInfo(SsdpConstants.MulticastLocalAdminAddress, IpAddressFamily.InterNetwork), Port = SsdpConstants.MulticastPort - }, cancellationToken).ConfigureAwait(false); + }, fromLocalIpAddress, cancellationToken).ConfigureAwait(false); await Task.Delay(100, cancellationToken).ConfigureAwait(false); } @@ -336,14 +336,15 @@ namespace Rssdp.Infrastructure #region Private Methods - private Task SendMessageIfSocketNotDisposed(byte[] messageData, IpEndPointInfo destination, CancellationToken cancellationToken) + private Task SendMessageIfSocketNotDisposed(byte[] messageData, IpEndPointInfo destination, IpAddressInfo fromLocalIpAddress, CancellationToken cancellationToken) { var sockets = _sendSockets; if (sockets != null) { sockets = sockets.ToList(); - var tasks = sockets.Select(s => SendFromSocket(s, messageData, destination, cancellationToken)); + var tasks = sockets.Where(s => (fromLocalIpAddress == null || fromLocalIpAddress.Equals(s.LocalIPAddress))) + .Select(s => SendFromSocket(s, messageData, destination, cancellationToken)); return Task.WhenAll(tasks); } diff --git a/RSSDP/SsdpDeviceLocator.cs b/RSSDP/SsdpDeviceLocator.cs index 128bdfcbb..e17e14c1a 100644 --- a/RSSDP/SsdpDeviceLocator.cs +++ b/RSSDP/SsdpDeviceLocator.cs @@ -354,7 +354,7 @@ namespace Rssdp.Infrastructure var message = BuildMessage(header, values); - return _CommunicationsServer.SendMulticastMessage(message, cancellationToken); + return _CommunicationsServer.SendMulticastMessage(message, null, cancellationToken); } private void ProcessSearchResponseMessage(HttpResponseMessage message, IpAddressInfo localIpAddress) diff --git a/RSSDP/SsdpDevicePublisher.cs b/RSSDP/SsdpDevicePublisher.cs index ce64ba117..076246b24 100644 --- a/RSSDP/SsdpDevicePublisher.cs +++ b/RSSDP/SsdpDevicePublisher.cs @@ -7,6 +7,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.Net; +using MediaBrowser.Common.Net; using Rssdp; namespace Rssdp.Infrastructure @@ -16,10 +17,12 @@ namespace Rssdp.Infrastructure /// public class SsdpDevicePublisher : DisposableManagedObjectBase, ISsdpDevicePublisher { + private readonly INetworkManager _networkManager; private ISsdpCommunicationsServer _CommsServer; private string _OSName; private string _OSVersion; + private bool _sendOnlyMatchedHost; private bool _SupportPnpRootDevice; @@ -37,9 +40,11 @@ namespace Rssdp.Infrastructure /// /// Default constructor. /// - public SsdpDevicePublisher(ISsdpCommunicationsServer communicationsServer, string osName, string osVersion) + public SsdpDevicePublisher(ISsdpCommunicationsServer communicationsServer, INetworkManager networkManager, + string osName, string osVersion, bool sendOnlyMatchedHost) { if (communicationsServer == null) throw new ArgumentNullException(nameof(communicationsServer)); + if (networkManager == null) throw new ArgumentNullException(nameof(networkManager)); if (osName == null) throw new ArgumentNullException(nameof(osName)); if (osName.Length == 0) throw new ArgumentException("osName cannot be an empty string.", nameof(osName)); if (osVersion == null) throw new ArgumentNullException(nameof(osVersion)); @@ -51,10 +56,12 @@ namespace Rssdp.Infrastructure _RecentSearchRequests = new Dictionary(StringComparer.OrdinalIgnoreCase); _Random = new Random(); + _networkManager = networkManager; _CommsServer = communicationsServer; _CommsServer.RequestReceived += CommsServer_RequestReceived; _OSName = osName; _OSVersion = osVersion; + _sendOnlyMatchedHost = sendOnlyMatchedHost; _CommsServer.BeginListeningForBroadcasts(); } @@ -250,7 +257,11 @@ namespace Rssdp.Infrastructure foreach (var device in deviceList) { - SendDeviceSearchResponses(device, remoteEndPoint, receivedOnlocalIpAddress, cancellationToken); + if (!_sendOnlyMatchedHost || + _networkManager.IsInSameSubnet(device.ToRootDevice().Address, remoteEndPoint.IpAddress, device.ToRootDevice().SubnetMask)) + { + SendDeviceSearchResponses(device, remoteEndPoint, receivedOnlocalIpAddress, cancellationToken); + } } } else @@ -427,7 +438,12 @@ namespace Rssdp.Infrastructure var message = BuildMessage(header, values); - _CommsServer.SendMulticastMessage(message, cancellationToken); + if (_sendOnlyMatchedHost) + { + _CommsServer.SendMulticastMessage(message, _sendOnlyMatchedHost ? rootDevice.Address : null, cancellationToken); + } else { + _CommsServer.SendMulticastMessage(message, null, cancellationToken); + } //WriteTrace(String.Format("Sent alive notification"), device); } @@ -472,7 +488,13 @@ namespace Rssdp.Infrastructure var sendCount = IsDisposed ? 1 : 3; WriteTrace(String.Format("Sent byebye notification"), device); - return _CommsServer.SendMulticastMessage(message, sendCount, cancellationToken); + if (_sendOnlyMatchedHost) + { + return _CommsServer.SendMulticastMessage(message, sendCount, + _sendOnlyMatchedHost ? device.ToRootDevice().Address : null, cancellationToken); + } else { + return _CommsServer.SendMulticastMessage(message, sendCount, null, cancellationToken); + } } private void DisposeRebroadcastTimer() diff --git a/RSSDP/SsdpRootDevice.cs b/RSSDP/SsdpRootDevice.cs index a2b0f60f5..d918b9040 100644 --- a/RSSDP/SsdpRootDevice.cs +++ b/RSSDP/SsdpRootDevice.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Text; using System.Xml; using Rssdp.Infrastructure; +using MediaBrowser.Model.Net; namespace Rssdp { @@ -52,6 +53,15 @@ namespace Rssdp /// public Uri Location { get; set; } + /// + /// Gets or sets the Address used to check if the received message from same interface with this device/tree. Required. + /// + public IpAddressInfo Address { get; set; } + + /// + /// Gets or sets the SubnetMask used to check if the received message from same interface with this device/tree. Required. + /// + public IpAddressInfo SubnetMask { get; set; } /// /// The base URL to use for all relative url's provided in other propertise (and those of child devices). Optional. -- cgit v1.2.3 From 1731bf7372a13ea8c656eb9f895508b7b4c66784 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Tue, 26 Feb 2019 20:47:23 +0100 Subject: Remove ordering items --- Emby.Server.Implementations/Dto/DtoService.cs | 15 ++-------- .../Session/SessionManager.cs | 2 +- MediaBrowser.Api/BaseApiService.cs | 13 ++------ MediaBrowser.Api/FilterService.cs | 3 +- MediaBrowser.Api/UserLibrary/ItemsService.cs | 35 ++++++++-------------- MediaBrowser.Controller/Dto/DtoOptions.cs | 14 ++------- MediaBrowser.Controller/Dto/IDtoService.cs | 4 +-- MediaBrowser.Controller/Entities/Folder.cs | 24 ++------------- 8 files changed, 27 insertions(+), 83 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index 2233d3d40..7b28a22a8 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -5,8 +5,6 @@ using System.Linq; using System.Threading.Tasks; using MediaBrowser.Common; using MediaBrowser.Controller.Channels; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -21,8 +19,6 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Extensions; -using MediaBrowser.Model.IO; using MediaBrowser.Model.Querying; using Microsoft.Extensions.Logging; @@ -83,15 +79,8 @@ namespace Emby.Server.Implementations.Dto return GetBaseItemDto(item, options, user, owner); } - public BaseItemDto[] GetBaseItemDtos(List items, DtoOptions options, User user = null, BaseItem owner = null) - { - return GetBaseItemDtos(items, items.Count, options, user, owner); - } - - public BaseItemDto[] GetBaseItemDtos(BaseItem[] items, DtoOptions options, User user = null, BaseItem owner = null) - { - return GetBaseItemDtos(items, items.Length, options, user, owner); - } + public BaseItemDto[] GetBaseItemDtos(IReadOnlyList items, DtoOptions options, User user = null, BaseItem owner = null) + => GetBaseItemDtos(items, items.Count, options, user, owner); public BaseItemDto[] GetBaseItemDtos(IEnumerable items, int itemCount, DtoOptions options, User user = null, BaseItem owner = null) { diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index fa0ab62d3..03e7b2654 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1090,7 +1090,7 @@ namespace Emby.Server.Implementations.Session await SendMessageToSession(session, "Play", command, cancellationToken).ConfigureAwait(false); } - private IList TranslateItemForPlayback(Guid id, User user) + private IEnumerable TranslateItemForPlayback(Guid id, User user) { var item = _libraryManager.GetItemById(id); diff --git a/MediaBrowser.Api/BaseApiService.cs b/MediaBrowser.Api/BaseApiService.cs index a037357ed..69673a49c 100644 --- a/MediaBrowser.Api/BaseApiService.cs +++ b/MediaBrowser.Api/BaseApiService.cs @@ -172,16 +172,9 @@ namespace MediaBrowser.Api if (!string.IsNullOrWhiteSpace(hasDtoOptions.EnableImageTypes)) { - if (string.IsNullOrEmpty(hasDtoOptions.EnableImageTypes)) - { - options.ImageTypes = Array.Empty(); - } - else - { - options.ImageTypes = hasDtoOptions.EnableImageTypes.Split(new [] { ',' }, StringSplitOptions.RemoveEmptyEntries) - .Select(v => (ImageType)Enum.Parse(typeof(ImageType), v, true)) - .ToArray(); - } + options.ImageTypes = hasDtoOptions.EnableImageTypes.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) + .Select(v => (ImageType)Enum.Parse(typeof(ImageType), v, true)) + .ToArray(); } } diff --git a/MediaBrowser.Api/FilterService.cs b/MediaBrowser.Api/FilterService.cs index 9caf07cea..201efe737 100644 --- a/MediaBrowser.Api/FilterService.cs +++ b/MediaBrowser.Api/FilterService.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; @@ -180,7 +181,7 @@ namespace MediaBrowser.Api return ToOptimizedResult(filters); } - private QueryFiltersLegacy GetFilters(BaseItem[] items) + private QueryFiltersLegacy GetFilters(IReadOnlyCollection items) { var result = new QueryFiltersLegacy(); diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs index 84475467f..3c7ad1d0a 100644 --- a/MediaBrowser.Api/UserLibrary/ItemsService.cs +++ b/MediaBrowser.Api/UserLibrary/ItemsService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Globalization; using System.Linq; using MediaBrowser.Controller.Dto; @@ -197,29 +198,27 @@ namespace MediaBrowser.Api.UserLibrary request.ParentId = null; } - var item = string.IsNullOrEmpty(request.ParentId) ? - null : - _libraryManager.GetItemById(request.ParentId); + BaseItem item = null; - if (item == null) + if (!string.IsNullOrEmpty(request.ParentId)) { - item = string.IsNullOrEmpty(request.ParentId) ? - user == null ? _libraryManager.RootFolder : _libraryManager.GetUserRootFolder() : - _libraryManager.GetItemById(request.ParentId); + item = _libraryManager.GetItemById(request.ParentId); } - // Default list type = children + if (item == null) + { + item = _libraryManager.GetUserRootFolder(); + } - var folder = item as Folder; + Folder folder = item as Folder; if (folder == null) { - folder = user == null ? _libraryManager.RootFolder : _libraryManager.GetUserRootFolder(); + folder = _libraryManager.GetUserRootFolder(); } var hasCollectionType = folder as IHasCollectionType; - var isPlaylistQuery = (hasCollectionType != null && string.Equals(hasCollectionType.CollectionType, CollectionType.Playlists, StringComparison.OrdinalIgnoreCase)); - - if (isPlaylistQuery) + if (hasCollectionType != null + && string.Equals(hasCollectionType.CollectionType, CollectionType.Playlists, StringComparison.OrdinalIgnoreCase)) { request.Recursive = true; request.IncludeItemTypes = "Playlist"; @@ -235,20 +234,12 @@ namespace MediaBrowser.Api.UserLibrary }; } - if (request.Recursive || !string.IsNullOrEmpty(request.Ids) || user == null) - { - return folder.GetItems(GetItemsQuery(request, dtoOptions, user)); - } - - var userRoot = item as UserRootFolder; - - if (userRoot == null) + if (request.Recursive || !string.IsNullOrEmpty(request.Ids) || !(item is UserRootFolder)) { return folder.GetItems(GetItemsQuery(request, dtoOptions, user)); } var itemsArray = folder.GetChildren(user, true).ToArray(); - return new QueryResult { Items = itemsArray, diff --git a/MediaBrowser.Controller/Dto/DtoOptions.cs b/MediaBrowser.Controller/Dto/DtoOptions.cs index aa99f6b58..cdaf95f5c 100644 --- a/MediaBrowser.Controller/Dto/DtoOptions.cs +++ b/MediaBrowser.Controller/Dto/DtoOptions.cs @@ -36,9 +36,7 @@ namespace MediaBrowser.Controller.Dto .ToArray(); public bool ContainsField(ItemFields field) - { - return AllItemFields.Contains(field); - } + => Fields.Contains(field); public DtoOptions(bool allFields) { @@ -47,15 +45,7 @@ namespace MediaBrowser.Controller.Dto EnableUserData = true; AddCurrentProgram = true; - if (allFields) - { - Fields = AllItemFields; - } - else - { - Fields = new ItemFields[] { }; - } - + Fields = allFields ? AllItemFields : Array.Empty(); ImageTypes = AllImageTypes; } diff --git a/MediaBrowser.Controller/Dto/IDtoService.cs b/MediaBrowser.Controller/Dto/IDtoService.cs index df5ec5dd0..4b6fd58fe 100644 --- a/MediaBrowser.Controller/Dto/IDtoService.cs +++ b/MediaBrowser.Controller/Dto/IDtoService.cs @@ -57,9 +57,7 @@ namespace MediaBrowser.Controller.Dto /// The options. /// The user. /// The owner. - BaseItemDto[] GetBaseItemDtos(BaseItem[] items, DtoOptions options, User user = null, BaseItem owner = null); - - BaseItemDto[] GetBaseItemDtos(List items, DtoOptions options, User user = null, BaseItem owner = null); + BaseItemDto[] GetBaseItemDtos(IReadOnlyList items, DtoOptions options, User user = null, BaseItem owner = null); /// /// Gets the item by name dto. diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index 8bfadbee6..e49ff20ba 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -810,37 +810,19 @@ namespace MediaBrowser.Controller.Entities { if (query.ItemIds.Length > 0) { - var result = LibraryManager.GetItemsResult(query); - - if (query.OrderBy.Length == 0) - { - var ids = query.ItemIds.ToList(); - - // Try to preserve order - result.Items = result.Items.OrderBy(i => ids.IndexOf(i.Id)).ToArray(); - } - return result; + return LibraryManager.GetItemsResult(query); } return GetItemsInternal(query); } - public BaseItem[] GetItemList(InternalItemsQuery query) + public IReadOnlyList GetItemList(InternalItemsQuery query) { query.EnableTotalRecordCount = false; if (query.ItemIds.Length > 0) { - var result = LibraryManager.GetItemList(query); - - if (query.OrderBy.Length == 0) - { - var ids = query.ItemIds.ToList(); - - // Try to preserve order - return result.OrderBy(i => ids.IndexOf(i.Id)).ToArray(); - } - return result.ToArray(); + return LibraryManager.GetItemList(query); } return GetItemsInternal(query).Items; -- cgit v1.2.3 From 7429c07c05ad12fbacef7952574edd75c294eb8a Mon Sep 17 00:00:00 2001 From: Xu Fasheng Date: Wed, 27 Feb 2019 20:16:54 +0800 Subject: Remove redundant parenthesis --- Emby.Server.Implementations/Networking/NetworkManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Networking/NetworkManager.cs b/Emby.Server.Implementations/Networking/NetworkManager.cs index 8696d1896..ace93ebde 100644 --- a/Emby.Server.Implementations/Networking/NetworkManager.cs +++ b/Emby.Server.Implementations/Networking/NetworkManager.cs @@ -414,7 +414,7 @@ namespace Emby.Server.Implementations.Networking // Try to exclude virtual adapters // http://stackoverflow.com/questions/8089685/c-sharp-finding-my-machines-local-ip-address-and-not-the-vms var addr = ipProperties.GatewayAddresses.FirstOrDefault(); - if (addr == null || (ignoreVirtualInterface && string.Equals(addr.Address.ToString(), "0.0.0.0", StringComparison.OrdinalIgnoreCase))) + if (addr == null || ignoreVirtualInterface && string.Equals(addr.Address.ToString(), "0.0.0.0", StringComparison.OrdinalIgnoreCase)) { return new List(); } -- cgit v1.2.3 From edba82db373da8fbab8159d6ab2483052ebab231 Mon Sep 17 00:00:00 2001 From: Phallacy Date: Wed, 27 Feb 2019 23:05:12 -0800 Subject: fixed logic flip in auth empty check and fixed crypto algo choice --- .../Cryptography/CryptographyProvider.cs | 34 +++++++++++++++------- .../Library/DefaultAuthenticationProvider.cs | 2 +- 2 files changed, 24 insertions(+), 12 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs index ea719309c..3c9403ba8 100644 --- a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs +++ b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs @@ -4,6 +4,7 @@ using System.Globalization; using System.IO; using System.Security.Cryptography; using System.Text; +using System.Linq; using MediaBrowser.Model.Cryptography; namespace Emby.Server.Implementations.Cryptography @@ -11,16 +12,18 @@ namespace Emby.Server.Implementations.Cryptography public class CryptographyProvider : ICryptoProvider { private HashSet SupportedHashMethods; - public string DefaultHashMethod => "SHA256"; + public string DefaultHashMethod => "PBKDF2"; private RandomNumberGenerator rng; private int defaultiterations = 1000; public CryptographyProvider() { + //FIXME: When we get DotNet Standard 2.1 we need to revisit how we do the crypto //Currently supported hash methods from https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptoconfig?view=netcore-2.1 //there might be a better way to autogenerate this list as dotnet updates, but I couldn't find one + //Please note the default method of PBKDF2 is not included, it cannot be used to generate hashes cleanly as it is actually a pbkdf with sha1 SupportedHashMethods = new HashSet() { - "MD5" + "MD5" ,"System.Security.Cryptography.MD5" ,"SHA" ,"SHA1" @@ -75,10 +78,15 @@ namespace Emby.Server.Implementations.Cryptography private byte[] PBKDF2(string method, byte[] bytes, byte[] salt, int iterations) { //downgrading for now as we need this library to be dotnetstandard compliant - using (var r = new Rfc2898DeriveBytes(bytes, salt, iterations)) + //with this downgrade we'll add a check to make sure we're on the downgrade method at the moment + if(method == DefaultHashMethod) { - return r.GetBytes(32); + using (var r = new Rfc2898DeriveBytes(bytes, salt, iterations)) + { + return r.GetBytes(32); + } } + throw new CryptographicException($"Cannot currently use PBKDF2 with requested hash method: {method}"); } public byte[] ComputeHash(string HashMethod, byte[] bytes) @@ -93,18 +101,22 @@ namespace Emby.Server.Implementations.Cryptography public byte[] ComputeHash(string HashMethod, byte[] bytes, byte[] salt) { - if (SupportedHashMethods.Contains(HashMethod)) + if(HashMethod == DefaultHashMethod) + { + return PBKDF2(HashMethod, bytes, salt, defaultiterations); + } + else if (SupportedHashMethods.Contains(HashMethod)) { - if (salt.Length == 0) + using (var h = HashAlgorithm.Create(HashMethod)) { - using (var h = HashAlgorithm.Create(HashMethod)) + if (salt.Length == 0) { return h.ComputeHash(bytes); } - } - else - { - return PBKDF2(HashMethod, bytes, salt, defaultiterations); + else + { + return h.ComputeHash(bytes.Concat(salt).ToArray()); + } } } else diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs index b58374adb..7ccdccc0a 100644 --- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs @@ -95,7 +95,7 @@ namespace Emby.Server.Implementations.Library //but at least they are in the new format. private void ConvertPasswordFormat(User user) { - if (!string.IsNullOrEmpty(user.Password)) + if (string.IsNullOrEmpty(user.Password)) { return; } -- cgit v1.2.3 From 9993dafe54fe4310d1008434405198d822ef51cc Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Fri, 1 Mar 2019 17:12:22 +0100 Subject: Don't mix LINQ and roreach loops for readability --- DvdLib/Ifo/Dvd.cs | 16 +++++------ Emby.Dlna/ContentDirectory/ControlHandler.cs | 4 +-- Emby.Dlna/Didl/DidlBuilder.cs | 3 +- Emby.Dlna/DlnaManager.cs | 21 +++++++------- Emby.Dlna/Main/DlnaEntryPoint.cs | 2 +- Emby.Dlna/PlayTo/TransportCommands.cs | 19 +++++++++++++ Emby.Naming/TV/EpisodePathParser.cs | 32 ++++++++++++++-------- .../Activity/ActivityManager.cs | 7 ++++- Emby.Server.Implementations/ApplicationHost.cs | 2 +- .../Channels/ChannelManager.cs | 11 ++------ .../Data/SqliteUserDataRepository.cs | 4 +-- 11 files changed, 74 insertions(+), 47 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/DvdLib/Ifo/Dvd.cs b/DvdLib/Ifo/Dvd.cs index f784be83e..90125fa3e 100644 --- a/DvdLib/Ifo/Dvd.cs +++ b/DvdLib/Ifo/Dvd.cs @@ -26,17 +26,17 @@ namespace DvdLib.Ifo if (vmgPath == null) { - var allIfos = allFiles.Where(i => string.Equals(i.Extension, ".ifo", StringComparison.OrdinalIgnoreCase)); - - foreach (var ifo in allIfos) + foreach (var ifo in allFiles) { - var num = ifo.Name.Split('_').ElementAtOrDefault(1); - var numbersRead = new List(); + if (!string.Equals(ifo.Extension, ".ifo", StringComparison.OrdinalIgnoreCase)) + { + continue; + } - if (!string.IsNullOrEmpty(num) && ushort.TryParse(num, out var ifoNumber) && !numbersRead.Contains(ifoNumber)) + var nums = ifo.Name.Split(new [] { '_' }, StringSplitOptions.RemoveEmptyEntries); + if (nums.Length >= 2 && ushort.TryParse(nums[1], out var ifoNumber)) { ReadVTS(ifoNumber, ifo.FullName); - numbersRead.Add(ifoNumber); } } } @@ -76,7 +76,7 @@ namespace DvdLib.Ifo } } - private void ReadVTS(ushort vtsNum, List allFiles) + private void ReadVTS(ushort vtsNum, IEnumerable allFiles) { var filename = string.Format("VTS_{0:00}_0.IFO", vtsNum); diff --git a/Emby.Dlna/ContentDirectory/ControlHandler.cs b/Emby.Dlna/ContentDirectory/ControlHandler.cs index 1150afdba..84f38ff76 100644 --- a/Emby.Dlna/ContentDirectory/ControlHandler.cs +++ b/Emby.Dlna/ContentDirectory/ControlHandler.cs @@ -260,7 +260,7 @@ namespace Emby.Dlna.ContentDirectory if (item.IsDisplayedAsFolder || serverItem.StubType.HasValue) { - var childrenResult = (GetUserItems(item, serverItem.StubType, user, sortCriteria, start, requestedCount)); + var childrenResult = GetUserItems(item, serverItem.StubType, user, sortCriteria, start, requestedCount); _didlBuilder.WriteFolderElement(writer, item, serverItem.StubType, null, childrenResult.TotalRecordCount, filter, id); } @@ -273,7 +273,7 @@ namespace Emby.Dlna.ContentDirectory } else { - var childrenResult = (GetUserItems(item, serverItem.StubType, user, sortCriteria, start, requestedCount)); + var childrenResult = GetUserItems(item, serverItem.StubType, user, sortCriteria, start, requestedCount); totalCount = childrenResult.TotalRecordCount; provided = childrenResult.Items.Length; diff --git a/Emby.Dlna/Didl/DidlBuilder.cs b/Emby.Dlna/Didl/DidlBuilder.cs index 605f4f37b..1268f3d5c 100644 --- a/Emby.Dlna/Didl/DidlBuilder.cs +++ b/Emby.Dlna/Didl/DidlBuilder.cs @@ -818,10 +818,9 @@ namespace Emby.Dlna.Didl { AddCommonFields(item, itemStubType, context, writer, filter); - var hasArtists = item as IHasArtist; var hasAlbumArtists = item as IHasAlbumArtist; - if (hasArtists != null) + if (item is IHasArtist hasArtists) { foreach (var artist in hasArtists.Artists) { diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs index f53d27451..d6ee5d13a 100644 --- a/Emby.Dlna/DlnaManager.cs +++ b/Emby.Dlna/DlnaManager.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Reflection; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; @@ -15,7 +16,6 @@ using MediaBrowser.Controller.Drawing; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Drawing; using MediaBrowser.Model.IO; -using MediaBrowser.Model.Reflection; using MediaBrowser.Model.Serialization; using Microsoft.Extensions.Logging; @@ -29,7 +29,7 @@ namespace Emby.Dlna private readonly ILogger _logger; private readonly IJsonSerializer _jsonSerializer; private readonly IServerApplicationHost _appHost; - private readonly IAssemblyInfo _assemblyInfo; + private static readonly Assembly _assembly = typeof(DlnaManager).Assembly; private readonly Dictionary> _profiles = new Dictionary>(StringComparer.Ordinal); @@ -39,8 +39,7 @@ namespace Emby.Dlna IApplicationPaths appPaths, ILoggerFactory loggerFactory, IJsonSerializer jsonSerializer, - IServerApplicationHost appHost, - IAssemblyInfo assemblyInfo) + IServerApplicationHost appHost) { _xmlSerializer = xmlSerializer; _fileSystem = fileSystem; @@ -48,7 +47,6 @@ namespace Emby.Dlna _logger = loggerFactory.CreateLogger("Dlna"); _jsonSerializer = jsonSerializer; _appHost = appHost; - _assemblyInfo = assemblyInfo; } public async Task InitProfilesAsync() @@ -368,15 +366,18 @@ namespace Emby.Dlna var systemProfilesPath = SystemProfilesPath; - foreach (var name in _assemblyInfo.GetManifestResourceNames(GetType()) - .Where(i => i.StartsWith(namespaceName)) - .ToList()) + foreach (var name in _assembly.GetManifestResourceNames()) { + if (!name.StartsWith(namespaceName)) + { + continue; + } + var filename = Path.GetFileName(name).Substring(namespaceName.Length); var path = Path.Combine(systemProfilesPath, filename); - using (var stream = _assemblyInfo.GetManifestResourceStream(GetType(), name)) + using (var stream = _assembly.GetManifestResourceStream(name)) { var fileInfo = _fileSystem.GetFileInfo(path); @@ -514,7 +515,7 @@ namespace Emby.Dlna return new ImageStream { Format = format, - Stream = _assemblyInfo.GetManifestResourceStream(GetType(), resource) + Stream = _assembly.GetManifestResourceStream(resource) }; } } diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs index 5a7c9b617..57ed0097a 100644 --- a/Emby.Dlna/Main/DlnaEntryPoint.cs +++ b/Emby.Dlna/Main/DlnaEntryPoint.cs @@ -246,7 +246,7 @@ namespace Emby.Dlna.Main private async Task RegisterServerEndpoints() { - var addresses = (await _appHost.GetLocalIpAddresses(CancellationToken.None).ConfigureAwait(false)).ToList(); + var addresses = await _appHost.GetLocalIpAddresses(CancellationToken.None).ConfigureAwait(false); var udn = CreateUuid(_appHost.SystemId); diff --git a/Emby.Dlna/PlayTo/TransportCommands.cs b/Emby.Dlna/PlayTo/TransportCommands.cs index b96fa43e5..4f9e398e9 100644 --- a/Emby.Dlna/PlayTo/TransportCommands.cs +++ b/Emby.Dlna/PlayTo/TransportCommands.cs @@ -107,12 +107,18 @@ namespace Emby.Dlna.PlayTo foreach (var arg in action.ArgumentList) { if (arg.Direction == "out") + { continue; + } if (arg.Name == "InstanceID") + { stateString += BuildArgumentXml(arg, "0"); + } else + { stateString += BuildArgumentXml(arg, null); + } } return string.Format(CommandBase, action.Name, xmlNamespace, stateString); @@ -125,11 +131,18 @@ namespace Emby.Dlna.PlayTo foreach (var arg in action.ArgumentList) { if (arg.Direction == "out") + { continue; + } + if (arg.Name == "InstanceID") + { stateString += BuildArgumentXml(arg, "0"); + } else + { stateString += BuildArgumentXml(arg, value.ToString(), commandParameter); + } } return string.Format(CommandBase, action.Name, xmlNamesapce, stateString); @@ -142,11 +155,17 @@ namespace Emby.Dlna.PlayTo foreach (var arg in action.ArgumentList) { if (arg.Name == "InstanceID") + { stateString += BuildArgumentXml(arg, "0"); + } else if (dictionary.ContainsKey(arg.Name)) + { stateString += BuildArgumentXml(arg, dictionary[arg.Name]); + } else + { stateString += BuildArgumentXml(arg, value.ToString()); + } } return string.Format(CommandBase, action.Name, xmlNamesapce, stateString); diff --git a/Emby.Naming/TV/EpisodePathParser.cs b/Emby.Naming/TV/EpisodePathParser.cs index 9485d697b..a8f81a3b8 100644 --- a/Emby.Naming/TV/EpisodePathParser.cs +++ b/Emby.Naming/TV/EpisodePathParser.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; -using System.Text.RegularExpressions; using Emby.Naming.Common; namespace Emby.Naming.TV @@ -22,7 +21,9 @@ namespace Emby.Naming.TV // There were no failed tests without this block, but to be safe, we can keep it until // the regex which require file extensions are modified so that they don't need them. if (IsDirectory) + { path += ".mp4"; + } EpisodePathParserResult result = null; @@ -35,6 +36,7 @@ namespace Emby.Naming.TV continue; } } + if (isNamed.HasValue) { if (expression.IsNamed != isNamed.Value) @@ -42,6 +44,7 @@ namespace Emby.Naming.TV continue; } } + if (isOptimistic.HasValue) { if (expression.IsOptimistic != isOptimistic.Value) @@ -191,13 +194,20 @@ namespace Emby.Naming.TV private void FillAdditional(string path, EpisodePathParserResult info, IEnumerable expressions) { - var results = expressions - .Where(i => i.IsNamed) - .Select(i => Parse(path, i)) - .Where(i => i.Success); - - foreach (var result in results) + foreach (var i in expressions) { + if (!i.IsNamed) + { + continue; + } + + var result = Parse(path, i); + + if (!result.Success) + { + continue; + } + if (string.IsNullOrEmpty(info.SeriesName)) { info.SeriesName = result.SeriesName; @@ -208,12 +218,10 @@ namespace Emby.Naming.TV info.EndingEpsiodeNumber = result.EndingEpsiodeNumber; } - if (!string.IsNullOrEmpty(info.SeriesName)) + if (!string.IsNullOrEmpty(info.SeriesName) + && (!info.EpisodeNumber.HasValue || info.EndingEpsiodeNumber.HasValue)) { - if (!info.EpisodeNumber.HasValue || info.EndingEpsiodeNumber.HasValue) - { - break; - } + break; } } } diff --git a/Emby.Server.Implementations/Activity/ActivityManager.cs b/Emby.Server.Implementations/Activity/ActivityManager.cs index 6febcc2f7..0c513ea12 100644 --- a/Emby.Server.Implementations/Activity/ActivityManager.cs +++ b/Emby.Server.Implementations/Activity/ActivityManager.cs @@ -39,8 +39,13 @@ namespace Emby.Server.Implementations.Activity { var result = _repo.GetActivityLogEntries(minDate, hasUserId, startIndex, limit); - foreach (var item in result.Items.Where(i => !i.UserId.Equals(Guid.Empty))) + foreach (var item in result.Items) { + if (item.UserId == Guid.Empty) + { + continue; + } + var user = _userManager.GetUserById(item.UserId); if (user != null) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index b5a64cbdd..94d2cd5da 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -769,7 +769,7 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(SessionManager); serviceCollection.AddSingleton( - new DlnaManager(XmlSerializer, FileSystemManager, ApplicationPaths, LoggerFactory, JsonSerializer, this, assemblyInfo)); + new DlnaManager(XmlSerializer, FileSystemManager, ApplicationPaths, LoggerFactory, JsonSerializer, this)); CollectionManager = new CollectionManager(LibraryManager, ApplicationPaths, LocalizationManager, FileSystemManager, LibraryMonitor, LoggerFactory, ProviderManager); serviceCollection.AddSingleton(CollectionManager); diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs index 949b89226..7e50650d7 100644 --- a/Emby.Server.Implementations/Channels/ChannelManager.cs +++ b/Emby.Server.Implementations/Channels/ChannelManager.cs @@ -243,8 +243,7 @@ namespace Emby.Server.Implementations.Channels { foreach (var item in returnItems) { - var task = RefreshLatestChannelItems(GetChannelProvider(item), CancellationToken.None); - Task.WaitAll(task); + RefreshLatestChannelItems(GetChannelProvider(item), CancellationToken.None).GetAwaiter().GetResult(); } } @@ -303,9 +302,7 @@ namespace Emby.Server.Implementations.Channels } numComplete++; - double percent = numComplete; - percent /= allChannelsList.Count; - + double percent = (double)numComplete / allChannelsList.Count; progress.Report(100 * percent); } @@ -658,9 +655,7 @@ namespace Emby.Server.Implementations.Channels foreach (var item in result.Items) { - var folder = item as Folder; - - if (folder != null) + if (item is Folder folder) { await GetChannelItemsInternal(new InternalItemsQuery { diff --git a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs index 7a9b72244..4109b7ad1 100644 --- a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs @@ -119,9 +119,9 @@ namespace Emby.Server.Implementations.Data { list.Add(row[0].ReadGuidFromBlob()); } - catch + catch (Exception ex) { - + Logger.LogError(ex, "Error while getting user"); } } } -- cgit v1.2.3 From ed07ed44ae99d3c53a95774cdbd6254c9cb0a76e Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Fri, 1 Mar 2019 19:30:48 +0100 Subject: Simplify rating loading --- .../Localization/LocalizationManager.cs | 153 ++++----------------- .../Localization/Ratings/au.csv | 8 ++ .../Localization/Ratings/be.csv | 6 + .../Localization/Ratings/de.csv | 10 ++ .../Localization/Ratings/ru.csv | 5 + 5 files changed, 54 insertions(+), 128 deletions(-) create mode 100644 Emby.Server.Implementations/Localization/Ratings/au.csv create mode 100644 Emby.Server.Implementations/Localization/Ratings/be.csv create mode 100644 Emby.Server.Implementations/Localization/Ratings/de.csv create mode 100644 Emby.Server.Implementations/Localization/Ratings/ru.csv (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs index 31217730b..2c59579a8 100644 --- a/Emby.Server.Implementations/Localization/LocalizationManager.cs +++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs @@ -62,10 +62,6 @@ namespace Emby.Server.Implementations.Localization { const string ratingsResource = "Emby.Server.Implementations.Localization.Ratings."; - Directory.CreateDirectory(LocalizationPath); - - var existingFiles = GetRatingsFiles(LocalizationPath).Select(Path.GetFileName); - // Extract from the assembly foreach (var resource in _assembly.GetManifestResourceNames()) { @@ -74,100 +70,42 @@ namespace Emby.Server.Implementations.Localization continue; } - string filename = "ratings-" + resource.Substring(ratingsResource.Length); - - if (existingFiles.Contains(filename)) - { - continue; - } + string countryCode = resource.Substring(ratingsResource.Length, 2); + var dict = new Dictionary(StringComparer.OrdinalIgnoreCase); - using (var stream = _assembly.GetManifestResourceStream(resource)) + using (var str = _assembly.GetManifestResourceStream(resource)) + using (var reader = new StreamReader(str)) { - string target = Path.Combine(LocalizationPath, filename); - _logger.LogInformation("Extracting ratings to {0}", target); - - using (var fs = _fileSystem.GetFileStream(target, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read)) + string line; + while ((line = await reader.ReadLineAsync()) != null) { - await stream.CopyToAsync(fs); + if (string.IsNullOrWhiteSpace(line)) + { + continue; + } + + string[] parts = line.Split(','); + if (parts.Length == 2 + && int.TryParse(parts[1], NumberStyles.Integer, UsCulture, out var value)) + { + dict.Add(parts[0], (new ParentalRating { Name = parts[0], Value = value })); + } +#if DEBUG + else + { + _logger.LogWarning("Misformed line in ratings file for country {CountryCode}", countryCode); + } +#endif } } - } + _logger.LogWarning("{t}", countryCode); - foreach (var file in GetRatingsFiles(LocalizationPath)) - { - await LoadRatings(file); + _allParentalRatings[countryCode] = dict; } - LoadAdditionalRatings(); - await LoadCultures(); } - private void LoadAdditionalRatings() - { - LoadRatings("au", new[] - { - new ParentalRating("AU-G", 1), - new ParentalRating("AU-PG", 5), - new ParentalRating("AU-M", 6), - new ParentalRating("AU-MA15+", 7), - new ParentalRating("AU-M15+", 8), - new ParentalRating("AU-R18+", 9), - new ParentalRating("AU-X18+", 10), - new ParentalRating("AU-RC", 11) - }); - - LoadRatings("be", new[] - { - new ParentalRating("BE-AL", 1), - new ParentalRating("BE-MG6", 2), - new ParentalRating("BE-6", 3), - new ParentalRating("BE-9", 5), - new ParentalRating("BE-12", 6), - new ParentalRating("BE-16", 8) - }); - - LoadRatings("de", new[] - { - new ParentalRating("DE-0", 1), - new ParentalRating("FSK-0", 1), - new ParentalRating("DE-6", 5), - new ParentalRating("FSK-6", 5), - new ParentalRating("DE-12", 7), - new ParentalRating("FSK-12", 7), - new ParentalRating("DE-16", 8), - new ParentalRating("FSK-16", 8), - new ParentalRating("DE-18", 9), - new ParentalRating("FSK-18", 9) - }); - - LoadRatings("ru", new[] - { - new ParentalRating("RU-0+", 1), - new ParentalRating("RU-6+", 3), - new ParentalRating("RU-12+", 7), - new ParentalRating("RU-16+", 9), - new ParentalRating("RU-18+", 10) - }); - } - - private void LoadRatings(string country, ParentalRating[] ratings) - { - _allParentalRatings[country] = ratings.ToDictionary(i => i.Name); - } - - private IEnumerable GetRatingsFiles(string directory) - => _fileSystem.GetFilePaths(directory, false) - .Where(i => string.Equals(Path.GetExtension(i), ".csv", StringComparison.OrdinalIgnoreCase)) - .Where(i => Path.GetFileName(i).StartsWith("ratings-", StringComparison.OrdinalIgnoreCase)); - - /// - /// Gets the localization path. - /// - /// The localization path. - public string LocalizationPath - => Path.Combine(_configurationManager.ApplicationPaths.ProgramDataPath, "localization"); - public string NormalizeFormKD(string text) => text.Normalize(NormalizationForm.FormKD); @@ -288,47 +226,6 @@ namespace Emby.Server.Implementations.Localization return value; } - /// - /// Loads the ratings. - /// - /// The file. - /// Dictionary{System.StringParentalRating}. - private async Task LoadRatings(string file) - { - Dictionary dict - = new Dictionary(StringComparer.OrdinalIgnoreCase); - - using (var str = File.OpenRead(file)) - using (var reader = new StreamReader(str)) - { - string line; - while ((line = await reader.ReadLineAsync()) != null) - { - if (string.IsNullOrWhiteSpace(line)) - { - continue; - } - - string[] parts = line.Split(','); - if (parts.Length == 2 - && int.TryParse(parts[1], NumberStyles.Integer, UsCulture, out var value)) - { - dict.Add(parts[0], (new ParentalRating { Name = parts[0], Value = value })); - } -#if DEBUG - else - { - _logger.LogWarning("Misformed line in {Path}", file); - } -#endif - } - } - - var countryCode = Path.GetFileNameWithoutExtension(file).Split('-')[1]; - - _allParentalRatings[countryCode] = dict; - } - private static readonly string[] _unratedValues = { "n/a", "unrated", "not rated" }; /// diff --git a/Emby.Server.Implementations/Localization/Ratings/au.csv b/Emby.Server.Implementations/Localization/Ratings/au.csv new file mode 100644 index 000000000..940375e26 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Ratings/au.csv @@ -0,0 +1,8 @@ +AU-G,1 +AU-PG,5 +AU-M,6 +AU-MA15+,7 +AU-M15+,8 +AU-R18+,9 +AU-X18+,10 +AU-RC,11 diff --git a/Emby.Server.Implementations/Localization/Ratings/be.csv b/Emby.Server.Implementations/Localization/Ratings/be.csv new file mode 100644 index 000000000..d3937caf7 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Ratings/be.csv @@ -0,0 +1,6 @@ +BE-AL,1 +BE-MG6,2 +BE-6,3 +BE-9,5 +BE-12,6 +BE-16,8 diff --git a/Emby.Server.Implementations/Localization/Ratings/de.csv b/Emby.Server.Implementations/Localization/Ratings/de.csv new file mode 100644 index 000000000..f944a140d --- /dev/null +++ b/Emby.Server.Implementations/Localization/Ratings/de.csv @@ -0,0 +1,10 @@ +DE-0,1 +FSK-0,1 +DE-6,5 +FSK-6,5 +DE-12,7 +FSK-12,7 +DE-16,8 +FSK-16,8 +DE-18,9 +FSK-18,9 diff --git a/Emby.Server.Implementations/Localization/Ratings/ru.csv b/Emby.Server.Implementations/Localization/Ratings/ru.csv new file mode 100644 index 000000000..1bc94affd --- /dev/null +++ b/Emby.Server.Implementations/Localization/Ratings/ru.csv @@ -0,0 +1,5 @@ +RU-0+,1 +RU-6+,3 +RU-12+,7 +RU-16+,9 +RU-18+,10 -- cgit v1.2.3 From 5368112d903fc29a1ab3e4fa11abed08358a6cbb Mon Sep 17 00:00:00 2001 From: Lynxy Date: Fri, 1 Mar 2019 22:28:25 -0500 Subject: Correct the list of series types --- Emby.Server.Implementations/Data/SqliteItemRepository.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 70e5fa640..06f6563a3 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -2279,11 +2279,10 @@ namespace Emby.Server.Implementations.Data private static readonly HashSet _seriesTypes = new HashSet(StringComparer.OrdinalIgnoreCase) { - "Audio", - "MusicAlbum", - "MusicVideo", + "Book", "AudioBook", - "AudioPodcast" + "Episode", + "Season" }; private bool HasSeriesFields(InternalItemsQuery query) -- cgit v1.2.3 From 0419deeec41cb7021c22a65fc4b63153075fd969 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Mon, 4 Mar 2019 20:18:35 +0100 Subject: Update LocalizationManager.cs --- Emby.Server.Implementations/Localization/LocalizationManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs index 2c59579a8..d55b258a3 100644 --- a/Emby.Server.Implementations/Localization/LocalizationManager.cs +++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs @@ -88,7 +88,7 @@ namespace Emby.Server.Implementations.Localization if (parts.Length == 2 && int.TryParse(parts[1], NumberStyles.Integer, UsCulture, out var value)) { - dict.Add(parts[0], (new ParentalRating { Name = parts[0], Value = value })); + dict.Add(parts[0], new ParentalRating { Name = parts[0], Value = value }); } #if DEBUG else -- cgit v1.2.3 From 2c26517172ca2c2f1df1c83d9300ad7c66667866 Mon Sep 17 00:00:00 2001 From: Phallacy Date: Mon, 4 Mar 2019 23:58:25 -0800 Subject: minor style fixes --- .../Cryptography/CryptographyProvider.cs | 42 +++++----- .../Library/DefaultAuthenticationProvider.cs | 39 ++++----- Emby.Server.Implementations/Library/UserManager.cs | 2 +- MediaBrowser.Model/Cryptography/ICryptoProvider.cs | 42 +++++----- MediaBrowser.Model/Cryptography/PasswordHash.cs | 92 +++++++++++++--------- 5 files changed, 115 insertions(+), 102 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs index 3c9403ba8..cf1ea6efa 100644 --- a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs +++ b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs @@ -11,17 +11,21 @@ namespace Emby.Server.Implementations.Cryptography { public class CryptographyProvider : ICryptoProvider { - private HashSet SupportedHashMethods; + private HashSet _supportedHashMethods; + public string DefaultHashMethod => "PBKDF2"; - private RandomNumberGenerator rng; - private int defaultiterations = 1000; + + private RandomNumberGenerator _randomNumberGenerator; + + private int _defaultIterations = 1000; + public CryptographyProvider() { //FIXME: When we get DotNet Standard 2.1 we need to revisit how we do the crypto //Currently supported hash methods from https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptoconfig?view=netcore-2.1 //there might be a better way to autogenerate this list as dotnet updates, but I couldn't find one //Please note the default method of PBKDF2 is not included, it cannot be used to generate hashes cleanly as it is actually a pbkdf with sha1 - SupportedHashMethods = new HashSet() + _supportedHashMethods = new HashSet() { "MD5" ,"System.Security.Cryptography.MD5" @@ -38,7 +42,7 @@ namespace Emby.Server.Implementations.Cryptography ,"SHA-512" ,"System.Security.Cryptography.SHA512" }; - rng = RandomNumberGenerator.Create(); + _randomNumberGenerator = RandomNumberGenerator.Create(); } public Guid GetMD5(string str) @@ -72,7 +76,7 @@ namespace Emby.Server.Implementations.Cryptography public IEnumerable GetSupportedHashMethods() { - return SupportedHashMethods; + return _supportedHashMethods; } private byte[] PBKDF2(string method, byte[] bytes, byte[] salt, int iterations) @@ -86,12 +90,13 @@ namespace Emby.Server.Implementations.Cryptography return r.GetBytes(32); } } + throw new CryptographicException($"Cannot currently use PBKDF2 with requested hash method: {method}"); } - public byte[] ComputeHash(string HashMethod, byte[] bytes) + public byte[] ComputeHash(string hashMethod, byte[] bytes) { - return ComputeHash(HashMethod, bytes, new byte[0]); + return ComputeHash(hashMethod, bytes, new byte[0]); } public byte[] ComputeHashWithDefaultMethod(byte[] bytes) @@ -99,15 +104,15 @@ namespace Emby.Server.Implementations.Cryptography return ComputeHash(DefaultHashMethod, bytes); } - public byte[] ComputeHash(string HashMethod, byte[] bytes, byte[] salt) + public byte[] ComputeHash(string hashMethod, byte[] bytes, byte[] salt) { - if(HashMethod == DefaultHashMethod) + if(hashMethod == DefaultHashMethod) { - return PBKDF2(HashMethod, bytes, salt, defaultiterations); + return PBKDF2(hashMethod, bytes, salt, _defaultIterations); } - else if (SupportedHashMethods.Contains(HashMethod)) + else if (_supportedHashMethods.Contains(hashMethod)) { - using (var h = HashAlgorithm.Create(HashMethod)) + using (var h = HashAlgorithm.Create(hashMethod)) { if (salt.Length == 0) { @@ -121,21 +126,21 @@ namespace Emby.Server.Implementations.Cryptography } else { - throw new CryptographicException($"Requested hash method is not supported: {HashMethod}"); + throw new CryptographicException($"Requested hash method is not supported: {hashMethod}"); } } public byte[] ComputeHashWithDefaultMethod(byte[] bytes, byte[] salt) { - return PBKDF2(DefaultHashMethod, bytes, salt, defaultiterations); + return PBKDF2(DefaultHashMethod, bytes, salt, _defaultIterations); } public byte[] ComputeHash(PasswordHash hash) { - int iterations = defaultiterations; + int iterations = _defaultIterations; if (!hash.Parameters.ContainsKey("iterations")) { - hash.Parameters.Add("iterations", defaultiterations.ToString(CultureInfo.InvariantCulture)); + hash.Parameters.Add("iterations", _defaultIterations.ToString(CultureInfo.InvariantCulture)); } else { @@ -148,13 +153,14 @@ namespace Emby.Server.Implementations.Cryptography throw new InvalidDataException($"Couldn't successfully parse iterations value from string: {hash.Parameters["iterations"]}", e); } } + return PBKDF2(hash.Id, hash.HashBytes, hash.SaltBytes, iterations); } public byte[] GenerateSalt() { byte[] salt = new byte[64]; - rng.GetBytes(salt); + _randomNumberGenerator.GetBytes(salt); return salt; } } diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs index 7ccdccc0a..8f10b5a84 100644 --- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs @@ -19,18 +19,16 @@ namespace Emby.Server.Implementations.Library public string Name => "Default"; public bool IsEnabled => true; - - - //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 + + // 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 public Task Authenticate(string username, string password) { throw new NotImplementedException(); } - - - //This is the verson that we need to use for local users. Because reasons. + + // This is the verson that we need to use for local users. Because reasons. public Task Authenticate(string username, string password, User resolvedUser) { bool success = false; @@ -39,7 +37,7 @@ namespace Emby.Server.Implementations.Library throw new Exception("Invalid username or password"); } - //As long as jellyfin supports passwordless users, we need this little block here to accomodate + // As long as jellyfin supports passwordless users, we need this little block here to accomodate if (IsPasswordEmpty(resolvedUser, password)) { return Task.FromResult(new ProviderAuthenticationResult @@ -70,7 +68,7 @@ namespace Emby.Server.Implementations.Library if (CalculatedHashString == readyHash.Hash) { success = true; - //throw new Exception("Invalid username or password"); + // throw new Exception("Invalid username or password"); } } else @@ -78,7 +76,7 @@ namespace Emby.Server.Implementations.Library throw new Exception(String.Format($"Requested crypto method not available in provider: {readyHash.Id}")); } - //var success = string.Equals(GetPasswordHash(resolvedUser), GetHashedString(resolvedUser, password), StringComparison.OrdinalIgnoreCase); + // var success = string.Equals(GetPasswordHash(resolvedUser), GetHashedString(resolvedUser, password), StringComparison.OrdinalIgnoreCase); if (!success) { @@ -91,8 +89,8 @@ 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. + // 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)) @@ -121,18 +119,13 @@ namespace Emby.Server.Implementations.Library private bool IsPasswordEmpty(User user, string password) { - if (string.IsNullOrEmpty(user.Password)) - { - return string.IsNullOrEmpty(password); - } - - return false; + return (string.IsNullOrEmpty(user.Password) && string.IsNullOrEmpty(password)); } public Task ChangePassword(User user, string newPassword) { ConvertPasswordFormat(user); - //This is needed to support changing a no password user to a password user + // This is needed to support changing a no password user to a password user if (string.IsNullOrEmpty(user.Password)) { PasswordHash newPasswordHash = new PasswordHash(_cryptographyProvider); @@ -184,7 +177,7 @@ namespace Emby.Server.Implementations.Library public string GetHashedString(User user, string str) { PasswordHash passwordHash; - if (String.IsNullOrEmpty(user.Password)) + if (string.IsNullOrEmpty(user.Password)) { passwordHash = new PasswordHash(_cryptographyProvider); } @@ -196,13 +189,13 @@ namespace Emby.Server.Implementations.Library if (passwordHash.SaltBytes != null) { - //the password is modern format with PBKDF and we should take advantage of that + // 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 { - //the password has no salt and should be called with the older method for safety + // 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))); } } diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index 0f188ca75..57bf16364 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -75,7 +75,7 @@ namespace Emby.Server.Implementations.Library private readonly Func _dtoServiceFactory; private readonly IServerApplicationHost _appHost; private readonly IFileSystem _fileSystem; - + private IAuthenticationProvider[] _authenticationProviders; private DefaultAuthenticationProvider _defaultAuthenticationProvider; diff --git a/MediaBrowser.Model/Cryptography/ICryptoProvider.cs b/MediaBrowser.Model/Cryptography/ICryptoProvider.cs index 8accc696e..5988112c2 100644 --- a/MediaBrowser.Model/Cryptography/ICryptoProvider.cs +++ b/MediaBrowser.Model/Cryptography/ICryptoProvider.cs @@ -1,22 +1,22 @@ -using System; -using System.IO; -using System.Collections.Generic; - -namespace MediaBrowser.Model.Cryptography -{ - public interface ICryptoProvider - { - Guid GetMD5(string str); - byte[] ComputeMD5(Stream str); - byte[] ComputeMD5(byte[] bytes); - byte[] ComputeSHA1(byte[] bytes); - IEnumerable GetSupportedHashMethods(); - byte[] ComputeHash(string HashMethod, byte[] bytes); - byte[] ComputeHashWithDefaultMethod(byte[] bytes); - byte[] ComputeHash(string HashMethod, byte[] bytes, byte[] salt); - byte[] ComputeHashWithDefaultMethod(byte[] bytes, byte[] salt); - byte[] ComputeHash(PasswordHash hash); +using System; +using System.IO; +using System.Collections.Generic; + +namespace MediaBrowser.Model.Cryptography +{ + public interface ICryptoProvider + { + Guid GetMD5(string str); + byte[] ComputeMD5(Stream str); + byte[] ComputeMD5(byte[] bytes); + byte[] ComputeSHA1(byte[] bytes); + IEnumerable GetSupportedHashMethods(); + byte[] ComputeHash(string HashMethod, byte[] bytes); + byte[] ComputeHashWithDefaultMethod(byte[] bytes); + byte[] ComputeHash(string HashMethod, byte[] bytes, byte[] salt); + byte[] ComputeHashWithDefaultMethod(byte[] bytes, byte[] salt); + byte[] ComputeHash(PasswordHash hash); byte[] GenerateSalt(); - string DefaultHashMethod { get; } - } -} + string DefaultHashMethod { get; } + } +} diff --git a/MediaBrowser.Model/Cryptography/PasswordHash.cs b/MediaBrowser.Model/Cryptography/PasswordHash.cs index 49bd510e9..a52840404 100644 --- a/MediaBrowser.Model/Cryptography/PasswordHash.cs +++ b/MediaBrowser.Model/Cryptography/PasswordHash.cs @@ -6,27 +6,40 @@ namespace MediaBrowser.Model.Cryptography { public class PasswordHash { - //Defined from this hash storage spec - //https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md - //$[$=(,=)*][$[$]] + // Defined from this hash storage spec + // https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md + // $[$=(,=)*][$[$]] + // with one slight amendment to ease the transition, we're writing out the bytes in hex + // rather than making them a BASE64 string with stripped padding - private string id; - private Dictionary parameters = new Dictionary(); - private string salt; - private byte[] saltBytes; - private string hash; - private byte[] hashBytes; - public string Id { get => id; set => id = value; } - public Dictionary Parameters { get => parameters; set => parameters = value; } - public string Salt { get => salt; set => salt = value; } - public byte[] SaltBytes { get => saltBytes; set => saltBytes = value; } - public string Hash { get => hash; set => hash = value; } - public byte[] HashBytes { get => hashBytes; set => hashBytes = value; } + private string _id; + + private Dictionary _parameters = new Dictionary(); + + private string _salt; + + private byte[] _saltBytes; + + private string _hash; + + private byte[] _hashBytes; + + public string Id { get => _id; set => _id = value; } + + public Dictionary Parameters { get => _parameters; set => _parameters = value; } + + public string Salt { get => _salt; set => _salt = value; } + + public byte[] SaltBytes { get => _saltBytes; set => _saltBytes = value; } + + public string Hash { get => _hash; set => _hash = value; } + + public byte[] HashBytes { get => _hashBytes; set => _hashBytes = value; } public PasswordHash(string storageString) { string[] splitted = storageString.Split('$'); - id = splitted[1]; + _id = splitted[1]; if (splitted[2].Contains("=")) { foreach (string paramset in (splitted[2].Split(','))) @@ -36,7 +49,7 @@ namespace MediaBrowser.Model.Cryptography string[] fields = paramset.Split('='); if (fields.Length == 2) { - parameters.Add(fields[0], fields[1]); + _parameters.Add(fields[0], fields[1]); } else { @@ -46,32 +59,32 @@ namespace MediaBrowser.Model.Cryptography } if (splitted.Length == 5) { - salt = splitted[3]; - saltBytes = ConvertFromByteString(salt); - hash = splitted[4]; - hashBytes = ConvertFromByteString(hash); + _salt = splitted[3]; + _saltBytes = ConvertFromByteString(_salt); + _hash = splitted[4]; + _hashBytes = ConvertFromByteString(_hash); } else { - salt = string.Empty; - hash = splitted[3]; - hashBytes = ConvertFromByteString(hash); + _salt = string.Empty; + _hash = splitted[3]; + _hashBytes = ConvertFromByteString(_hash); } } else { if (splitted.Length == 4) { - salt = splitted[2]; - saltBytes = ConvertFromByteString(salt); - hash = splitted[3]; - hashBytes = ConvertFromByteString(hash); + _salt = splitted[2]; + _saltBytes = ConvertFromByteString(_salt); + _hash = splitted[3]; + _hashBytes = ConvertFromByteString(_hash); } else { - salt = string.Empty; - hash = splitted[2]; - hashBytes = ConvertFromByteString(hash); + _salt = string.Empty; + _hash = splitted[2]; + _hashBytes = ConvertFromByteString(_hash); } } @@ -80,9 +93,9 @@ namespace MediaBrowser.Model.Cryptography public PasswordHash(ICryptoProvider cryptoProvider) { - id = cryptoProvider.DefaultHashMethod; - saltBytes = cryptoProvider.GenerateSalt(); - salt = ConvertToByteString(SaltBytes); + _id = cryptoProvider.DefaultHashMethod; + _saltBytes = cryptoProvider.GenerateSalt(); + _salt = ConvertToByteString(SaltBytes); } public static byte[] ConvertFromByteString(string byteString) @@ -92,6 +105,7 @@ namespace MediaBrowser.Model.Cryptography { Bytes.Add(Convert.ToByte(byteString.Substring(i, 2),16)); } + return Bytes.ToArray(); } @@ -103,7 +117,7 @@ namespace MediaBrowser.Model.Cryptography private string SerializeParameters() { string ReturnString = string.Empty; - foreach (var KVP in parameters) + foreach (var KVP in _parameters) { ReturnString += $",{KVP.Key}={KVP.Value}"; } @@ -118,19 +132,19 @@ namespace MediaBrowser.Model.Cryptography public override string ToString() { - string outString = "$" +id; + string outString = "$" +_id; string paramstring = SerializeParameters(); if (!string.IsNullOrEmpty(paramstring)) { outString += $"${paramstring}"; } - if (!string.IsNullOrEmpty(salt)) + if (!string.IsNullOrEmpty(_salt)) { - outString += $"${salt}"; + outString += $"${_salt}"; } - outString += $"${hash}"; + outString += $"${_hash}"; return outString; } } -- cgit v1.2.3 From 20775116f76b12bf77672fa37c4ea5f82b69f157 Mon Sep 17 00:00:00 2001 From: PloughPuff Date: Fri, 8 Feb 2019 13:35:26 +0000 Subject: Reworked FFmpeg path discovery and always display to user 1) Reworked FFmpeg and FFprobe path discovery (CLI switch, Custom xml, system $PATH, UI update trigger). Removed FFMpeg folder from Emby.Server.Implementations. All path discovery now in MediaEncoder. 2) Always display FFmpeg path to user in Transcode page. 3) Allow user to remove a Custome FFmpeg path and return to using system $PATH (or --ffmpeg if available). 4) Remove unused code associated with 'prebuilt' FFmpeg. 5) Much improved logging during path discovery. --- Emby.Server.Implementations/ApplicationHost.cs | 72 +--- Emby.Server.Implementations/FFMpeg/FFMpegInfo.cs | 24 -- .../FFMpeg/FFMpegInstallInfo.cs | 17 - Emby.Server.Implementations/FFMpeg/FFMpegLoader.cs | 132 ------- .../Encoder/EncoderValidator.cs | 2 +- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 433 ++++++++++----------- .../Configuration/EncodingOptions.cs | 3 +- 7 files changed, 212 insertions(+), 471 deletions(-) delete mode 100644 Emby.Server.Implementations/FFMpeg/FFMpegInfo.cs delete mode 100644 Emby.Server.Implementations/FFMpeg/FFMpegInstallInfo.cs delete mode 100644 Emby.Server.Implementations/FFMpeg/FFMpegLoader.cs (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 94d2cd5da..2c0d0e746 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -28,7 +28,6 @@ using Emby.Server.Implementations.Data; using Emby.Server.Implementations.Devices; using Emby.Server.Implementations.Diagnostics; using Emby.Server.Implementations.Dto; -using Emby.Server.Implementations.FFMpeg; using Emby.Server.Implementations.HttpServer; using Emby.Server.Implementations.HttpServer.Security; using Emby.Server.Implementations.IO; @@ -792,7 +791,8 @@ namespace Emby.Server.Implementations ChapterManager = new ChapterManager(LibraryManager, LoggerFactory, ServerConfigurationManager, ItemRepository); serviceCollection.AddSingleton(ChapterManager); - RegisterMediaEncoder(serviceCollection); + MediaEncoder = new MediaBrowser.MediaEncoding.Encoder.MediaEncoder(LoggerFactory, JsonSerializer, StartupOptions.FFmpegPath, StartupOptions.FFprobePath, ServerConfigurationManager, FileSystemManager, () => SubtitleEncoder, () => MediaSourceManager, ProcessFactory, 5000); + serviceCollection.AddSingleton(MediaEncoder); EncodingManager = new MediaEncoder.EncodingManager(FileSystemManager, LoggerFactory, MediaEncoder, ChapterManager, LibraryManager); serviceCollection.AddSingleton(EncodingManager); @@ -908,83 +908,25 @@ namespace Emby.Server.Implementations return new ImageProcessor(LoggerFactory, ServerConfigurationManager.ApplicationPaths, FileSystemManager, ImageEncoder, () => LibraryManager, () => MediaEncoder); } - protected virtual FFMpegInstallInfo GetFfmpegInstallInfo() - { - var info = new FFMpegInstallInfo(); - - // Windows builds: http://ffmpeg.zeranoe.com/builds/ - // Linux builds: http://johnvansickle.com/ffmpeg/ - // OS X builds: http://ffmpegmac.net/ - // OS X x64: http://www.evermeet.cx/ffmpeg/ - - if (EnvironmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Linux) - { - info.FFMpegFilename = "ffmpeg"; - info.FFProbeFilename = "ffprobe"; - info.ArchiveType = "7z"; - info.Version = "20170308"; - } - else if (EnvironmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows) - { - info.FFMpegFilename = "ffmpeg.exe"; - info.FFProbeFilename = "ffprobe.exe"; - info.Version = "20170308"; - info.ArchiveType = "7z"; - } - else if (EnvironmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.OSX) - { - info.FFMpegFilename = "ffmpeg"; - info.FFProbeFilename = "ffprobe"; - info.ArchiveType = "7z"; - info.Version = "20170308"; - } - - return info; - } - - protected virtual FFMpegInfo GetFFMpegInfo() - { - return new FFMpegLoader(ApplicationPaths, FileSystemManager, GetFfmpegInstallInfo()) - .GetFFMpegInfo(StartupOptions); - } - /// /// Registers the media encoder. /// /// Task. - private void RegisterMediaEncoder(IServiceCollection serviceCollection) + private void RegisterMediaEncoder(IAssemblyInfo assemblyInfo) { - string encoderPath = null; - string probePath = null; - - var info = GetFFMpegInfo(); - - encoderPath = info.EncoderPath; - probePath = info.ProbePath; - var hasExternalEncoder = string.Equals(info.Version, "external", StringComparison.OrdinalIgnoreCase); - - var mediaEncoder = new MediaBrowser.MediaEncoding.Encoder.MediaEncoder( + MediaEncoder = new MediaBrowser.MediaEncoding.Encoder.MediaEncoder( LoggerFactory, JsonSerializer, - encoderPath, - probePath, - hasExternalEncoder, + StartupOptions.FFmpegPath, + StartupOptions.FFprobePath, ServerConfigurationManager, FileSystemManager, - LiveTvManager, - IsoManager, - LibraryManager, - ChannelManager, - SessionManager, () => SubtitleEncoder, () => MediaSourceManager, - HttpClient, - ZipClient, ProcessFactory, 5000); - MediaEncoder = mediaEncoder; - serviceCollection.AddSingleton(MediaEncoder); + RegisterSingleInstance(MediaEncoder); } /// diff --git a/Emby.Server.Implementations/FFMpeg/FFMpegInfo.cs b/Emby.Server.Implementations/FFMpeg/FFMpegInfo.cs deleted file mode 100644 index 60cd7b3d7..000000000 --- a/Emby.Server.Implementations/FFMpeg/FFMpegInfo.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace Emby.Server.Implementations.FFMpeg -{ - /// - /// Class FFMpegInfo - /// - public class FFMpegInfo - { - /// - /// Gets or sets the path. - /// - /// The path. - public string EncoderPath { get; set; } - /// - /// Gets or sets the probe path. - /// - /// The probe path. - public string ProbePath { get; set; } - /// - /// Gets or sets the version. - /// - /// The version. - public string Version { get; set; } - } -} diff --git a/Emby.Server.Implementations/FFMpeg/FFMpegInstallInfo.cs b/Emby.Server.Implementations/FFMpeg/FFMpegInstallInfo.cs deleted file mode 100644 index fa9cb5e01..000000000 --- a/Emby.Server.Implementations/FFMpeg/FFMpegInstallInfo.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Emby.Server.Implementations.FFMpeg -{ - public class FFMpegInstallInfo - { - public string Version { get; set; } - public string FFMpegFilename { get; set; } - public string FFProbeFilename { get; set; } - public string ArchiveType { get; set; } - - public FFMpegInstallInfo() - { - Version = "Path"; - FFMpegFilename = "ffmpeg"; - FFProbeFilename = "ffprobe"; - } - } -} diff --git a/Emby.Server.Implementations/FFMpeg/FFMpegLoader.cs b/Emby.Server.Implementations/FFMpeg/FFMpegLoader.cs deleted file mode 100644 index bbf51dd24..000000000 --- a/Emby.Server.Implementations/FFMpeg/FFMpegLoader.cs +++ /dev/null @@ -1,132 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using MediaBrowser.Common.Configuration; -using MediaBrowser.Model.IO; - -namespace Emby.Server.Implementations.FFMpeg -{ - public class FFMpegLoader - { - private readonly IApplicationPaths _appPaths; - private readonly IFileSystem _fileSystem; - private readonly FFMpegInstallInfo _ffmpegInstallInfo; - - public FFMpegLoader(IApplicationPaths appPaths, IFileSystem fileSystem, FFMpegInstallInfo ffmpegInstallInfo) - { - _appPaths = appPaths; - _fileSystem = fileSystem; - _ffmpegInstallInfo = ffmpegInstallInfo; - } - - public FFMpegInfo GetFFMpegInfo(IStartupOptions options) - { - var customffMpegPath = options.FFmpegPath; - var customffProbePath = options.FFprobePath; - - if (!string.IsNullOrWhiteSpace(customffMpegPath) && !string.IsNullOrWhiteSpace(customffProbePath)) - { - return new FFMpegInfo - { - ProbePath = customffProbePath, - EncoderPath = customffMpegPath, - Version = "external" - }; - } - - var downloadInfo = _ffmpegInstallInfo; - - var prebuiltFolder = _appPaths.ProgramSystemPath; - var prebuiltffmpeg = Path.Combine(prebuiltFolder, downloadInfo.FFMpegFilename); - var prebuiltffprobe = Path.Combine(prebuiltFolder, downloadInfo.FFProbeFilename); - if (File.Exists(prebuiltffmpeg) && File.Exists(prebuiltffprobe)) - { - return new FFMpegInfo - { - ProbePath = prebuiltffprobe, - EncoderPath = prebuiltffmpeg, - Version = "external" - }; - } - - var version = downloadInfo.Version; - - if (string.Equals(version, "0", StringComparison.OrdinalIgnoreCase)) - { - return new FFMpegInfo(); - } - - var rootEncoderPath = Path.Combine(_appPaths.ProgramDataPath, "ffmpeg"); - var versionedDirectoryPath = Path.Combine(rootEncoderPath, version); - - var info = new FFMpegInfo - { - ProbePath = Path.Combine(versionedDirectoryPath, downloadInfo.FFProbeFilename), - EncoderPath = Path.Combine(versionedDirectoryPath, downloadInfo.FFMpegFilename), - Version = version - }; - - Directory.CreateDirectory(versionedDirectoryPath); - - var excludeFromDeletions = new List { versionedDirectoryPath }; - - if (!File.Exists(info.ProbePath) || !File.Exists(info.EncoderPath)) - { - // ffmpeg not present. See if there's an older version we can start with - var existingVersion = GetExistingVersion(info, rootEncoderPath); - - // No older version. Need to download and block until complete - if (existingVersion == null) - { - return new FFMpegInfo(); - } - else - { - info = existingVersion; - versionedDirectoryPath = Path.GetDirectoryName(info.EncoderPath); - excludeFromDeletions.Add(versionedDirectoryPath); - } - } - - // Allow just one of these to be overridden, if desired. - if (!string.IsNullOrWhiteSpace(customffMpegPath)) - { - info.EncoderPath = customffMpegPath; - } - if (!string.IsNullOrWhiteSpace(customffProbePath)) - { - info.ProbePath = customffProbePath; - } - - return info; - } - - private FFMpegInfo GetExistingVersion(FFMpegInfo info, string rootEncoderPath) - { - var encoderFilename = Path.GetFileName(info.EncoderPath); - var probeFilename = Path.GetFileName(info.ProbePath); - - foreach (var directory in _fileSystem.GetDirectoryPaths(rootEncoderPath)) - { - var allFiles = _fileSystem.GetFilePaths(directory, true).ToList(); - - var encoder = allFiles.FirstOrDefault(i => string.Equals(Path.GetFileName(i), encoderFilename, StringComparison.OrdinalIgnoreCase)); - var probe = allFiles.FirstOrDefault(i => string.Equals(Path.GetFileName(i), probeFilename, StringComparison.OrdinalIgnoreCase)); - - if (!string.IsNullOrWhiteSpace(encoder) && - !string.IsNullOrWhiteSpace(probe)) - { - return new FFMpegInfo - { - EncoderPath = encoder, - ProbePath = probe, - Version = Path.GetFileName(Path.GetDirectoryName(probe)) - }; - } - } - - return null; - } - } -} diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs index f725d2c01..1eeea87a0 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs @@ -19,7 +19,7 @@ namespace MediaBrowser.MediaEncoding.Encoder _processFactory = processFactory; } - public (IEnumerable decoders, IEnumerable encoders) Validate(string encoderPath) + public (IEnumerable decoders, IEnumerable encoders) GetAvailableCoders(string encoderPath) { _logger.LogInformation("Validating media encoder at {EncoderPath}", encoderPath); diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 7f29c06b4..36d72cad9 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -7,13 +7,9 @@ using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Session; using MediaBrowser.MediaEncoding.Probing; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Diagnostics; @@ -32,323 +28,288 @@ namespace MediaBrowser.MediaEncoding.Encoder public class MediaEncoder : IMediaEncoder, IDisposable { /// - /// The _logger - /// - private readonly ILogger _logger; - - /// - /// Gets the json serializer. + /// Gets the encoder path. /// - /// The json serializer. - private readonly IJsonSerializer _jsonSerializer; + /// The encoder path. + public string EncoderPath => FFmpegPath; /// - /// The _thumbnail resource pool + /// External: path supplied via command line + /// Custom: coming from UI or config/encoding.xml file + /// System: FFmpeg found in system $PATH + /// null: No FFmpeg found /// - private readonly SemaphoreSlim _thumbnailResourcePool = new SemaphoreSlim(1, 1); - - public string FFMpegPath { get; private set; } - - public string FFProbePath { get; private set; } + public string EncoderLocationType { get; private set; } + private readonly ILogger _logger; + private readonly IJsonSerializer _jsonSerializer; + private string FFmpegPath { get; set; } + private string FFprobePath { get; set; } protected readonly IServerConfigurationManager ConfigurationManager; protected readonly IFileSystem FileSystem; - protected readonly ILiveTvManager LiveTvManager; - protected readonly IIsoManager IsoManager; - protected readonly ILibraryManager LibraryManager; - protected readonly IChannelManager ChannelManager; - protected readonly ISessionManager SessionManager; protected readonly Func SubtitleEncoder; protected readonly Func MediaSourceManager; - private readonly IHttpClient _httpClient; - private readonly IZipClient _zipClient; private readonly IProcessFactory _processFactory; + private readonly int DefaultImageExtractionTimeoutMs; + private readonly string StartupOptionFFmpegPath; + private readonly string StartupOptionFFprobePath; + private readonly SemaphoreSlim _thumbnailResourcePool = new SemaphoreSlim(1, 1); private readonly List _runningProcesses = new List(); - private readonly bool _hasExternalEncoder; - private readonly string _originalFFMpegPath; - private readonly string _originalFFProbePath; - private readonly int DefaultImageExtractionTimeoutMs; public MediaEncoder( ILoggerFactory loggerFactory, IJsonSerializer jsonSerializer, - string ffMpegPath, - string ffProbePath, - bool hasExternalEncoder, + string startupOptionsFFmpegPath, + string startupOptionsFFprobePath, IServerConfigurationManager configurationManager, IFileSystem fileSystem, - ILiveTvManager liveTvManager, - IIsoManager isoManager, - ILibraryManager libraryManager, - IChannelManager channelManager, - ISessionManager sessionManager, Func subtitleEncoder, Func mediaSourceManager, - IHttpClient httpClient, - IZipClient zipClient, IProcessFactory processFactory, int defaultImageExtractionTimeoutMs) { _logger = loggerFactory.CreateLogger(nameof(MediaEncoder)); _jsonSerializer = jsonSerializer; + StartupOptionFFmpegPath = startupOptionsFFmpegPath; + StartupOptionFFprobePath = startupOptionsFFprobePath; ConfigurationManager = configurationManager; FileSystem = fileSystem; - LiveTvManager = liveTvManager; - IsoManager = isoManager; - LibraryManager = libraryManager; - ChannelManager = channelManager; - SessionManager = sessionManager; SubtitleEncoder = subtitleEncoder; - MediaSourceManager = mediaSourceManager; - _httpClient = httpClient; - _zipClient = zipClient; _processFactory = processFactory; DefaultImageExtractionTimeoutMs = defaultImageExtractionTimeoutMs; - FFProbePath = ffProbePath; - FFMpegPath = ffMpegPath; - _originalFFProbePath = ffProbePath; - _originalFFMpegPath = ffMpegPath; - _hasExternalEncoder = hasExternalEncoder; } - public string EncoderLocationType + /// + /// Run at startup or if the user removes a Custom path from transcode page. + /// Sets global variables FFmpegPath and EncoderLocationType. + /// If startup options --ffprobe is given then FFprobePath is set too. + /// + public void Init() { - get + // 1) If given, use the --ffmpeg CLI switch + if (ValidatePathFFmpeg("From CLI Switch", StartupOptionFFmpegPath)) { - if (_hasExternalEncoder) - { - return "External"; - } - - if (string.IsNullOrWhiteSpace(FFMpegPath)) - { - return null; - } - - if (IsSystemInstalledPath(FFMpegPath)) - { - return "System"; - } - - return "Custom"; + _logger.LogInformation("FFmpeg: Using path from command line switch --ffmpeg"); + EncoderLocationType = "External"; } - } - private bool IsSystemInstalledPath(string path) - { - if (path.IndexOf("/", StringComparison.Ordinal) == -1 && path.IndexOf("\\", StringComparison.Ordinal) == -1) + // 2) Try Custom path stroed in config/encoding xml file under tag + else if (ValidatePathFFmpeg("From Config File", ConfigurationManager.GetConfiguration("encoding").EncoderAppPathCustom)) { - return true; + _logger.LogInformation("FFmpeg: Using path from config/encoding.xml file"); + EncoderLocationType = "Custom"; } - return false; - } - - public void Init() - { - InitPaths(); - - if (!string.IsNullOrWhiteSpace(FFMpegPath)) + // 3) Search system $PATH environment variable for valid FFmpeg + else if (ValidatePathFFmpeg("From $PATH", ExistsOnSystemPath("ffmpeg"))) { - var result = new EncoderValidator(_logger, _processFactory).Validate(FFMpegPath); - - SetAvailableDecoders(result.decoders); - SetAvailableEncoders(result.encoders); + _logger.LogInformation("FFmpeg: Using system $PATH for FFmpeg"); + EncoderLocationType = "System"; + } + else + { + _logger.LogError("FFmpeg: No suitable executable found"); + FFmpegPath = null; + EncoderLocationType = null; } - } - - private void InitPaths() - { - ConfigureEncoderPaths(); - if (_hasExternalEncoder) + // If given, use the --ffprobe CLI switch + if (ValidatePathFFprobe("CLI Switch", StartupOptionFFprobePath)) { - LogPaths(); - return; + _logger.LogInformation("FFprobe: Using path from command line switch --ffprobe"); + } + else + { + // FFprobe path from command line is no good, so set to null and let ReInit() try + // and set using the FFmpeg path. + FFprobePath = null; } - // If the path was passed in, save it into config now. - var encodingOptions = GetEncodingOptions(); - var appPath = encodingOptions.EncoderAppPath; + ReInit(); + } - var valueToSave = FFMpegPath; + /// + /// Writes the currently used FFmpeg to config/encoding.xml file. + /// Sets the FFprobe path if not currently set. + /// Interrogates the FFmpeg tool to identify what encoders/decodres are available. + /// + private void ReInit() + { + // Write the FFmpeg path to the config/encoding.xml file so it appears in UI + var config = ConfigurationManager.GetConfiguration("encoding"); + config.EncoderAppPath = FFmpegPath ?? string.Empty; + ConfigurationManager.SaveConfiguration("encoding", config); - if (!string.IsNullOrWhiteSpace(valueToSave)) + // Only if mpeg path is set, try and set path to probe + if (FFmpegPath != null) { - // if using system variable, don't save this. - if (IsSystemInstalledPath(valueToSave) || _hasExternalEncoder) + // Probe would be null here if no valid --ffprobe path was given + // at startup, or we're performing ReInit following mpeg path update from UI + if (FFprobePath == null) { - valueToSave = null; + // Use the mpeg path to create a probe path + if (ValidatePathFFprobe("Copied from FFmpeg:", GetProbePathFromEncoderPath(FFmpegPath))) + { + _logger.LogInformation("FFprobe: Using FFprobe in same folders as FFmpeg"); + } + else + { + _logger.LogError("FFprobe: No suitable executable found"); + } } - } - if (!string.Equals(valueToSave, appPath, StringComparison.Ordinal)) - { - encodingOptions.EncoderAppPath = valueToSave; - ConfigurationManager.SaveConfiguration("encoding", encodingOptions); + // Interrogate to understand what coders it supports + var result = new EncoderValidator(_logger, _processFactory).GetAvailableCoders(FFmpegPath); + + SetAvailableDecoders(result.decoders); + SetAvailableEncoders(result.encoders); } + + // Stamp FFmpeg paths to the log file + LogPaths(); } + /// + /// Triggered from the Settings > Trascoding UI page when users sumits Custom FFmpeg path to use. + /// + /// + /// public void UpdateEncoderPath(string path, string pathType) { - if (_hasExternalEncoder) - { - return; - } - _logger.LogInformation("Attempting to update encoder path to {0}. pathType: {1}", path ?? string.Empty, pathType ?? string.Empty); - Tuple newPaths; - - if (string.Equals(pathType, "system", StringComparison.OrdinalIgnoreCase)) + if (!string.Equals(pathType, "custom", StringComparison.OrdinalIgnoreCase)) { - path = "ffmpeg"; - - newPaths = TestForInstalledVersions(); + throw new ArgumentException("Unexpected pathType value"); } - else if (string.Equals(pathType, "custom", StringComparison.OrdinalIgnoreCase)) + else { if (string.IsNullOrWhiteSpace(path)) { - throw new ArgumentNullException(nameof(path)); - } + // User had cleared the cutom path in UI. Clear the Custom config + // setting and peform full Init to relook any CLI switches and system $PATH + var config = ConfigurationManager.GetConfiguration("encoding"); + config.EncoderAppPathCustom = string.Empty; + ConfigurationManager.SaveConfiguration("encoding", config); - if (!File.Exists(path) && !Directory.Exists(path)) + Init(); + } + else if (!File.Exists(path) && !Directory.Exists(path)) { + // Given path is neither file or folder throw new ResourceNotFoundException(); } - newPaths = GetEncoderPaths(path); - } - else - { - throw new ArgumentException("Unexpected pathType value"); - } - - if (string.IsNullOrWhiteSpace(newPaths.Item1)) - { - throw new ResourceNotFoundException("ffmpeg not found"); - } - if (string.IsNullOrWhiteSpace(newPaths.Item2)) - { - throw new ResourceNotFoundException("ffprobe not found"); - } - - path = newPaths.Item1; + else + { + // Supplied path could be either file path or folder path. + // Resolve down to file path and validate + path = GetEncoderPath(path); - if (!ValidateVersion(path, true)) - { - throw new ResourceNotFoundException("ffmpeg version 3.0 or greater is required."); - } + if (path == null) + { + throw new ResourceNotFoundException("FFmpeg not found"); + } + else if (!ValidatePathFFmpeg("New From UI", path)) + { + throw new ResourceNotFoundException("Failed validation checks. Version 4.0 or greater is required"); + } + else + { + EncoderLocationType = "Custom"; - var config = GetEncodingOptions(); - config.EncoderAppPath = path; - ConfigurationManager.SaveConfiguration("encoding", config); + // Write the validated mpeg path to the xml as + // This ensures its not lost on new startup + var config = ConfigurationManager.GetConfiguration("encoding"); + config.EncoderAppPathCustom = FFmpegPath; + ConfigurationManager.SaveConfiguration("encoding", config); - Init(); - } + FFprobePath = null; // Clear probe path so it gets relooked in ReInit() - private bool ValidateVersion(string path, bool logOutput) - { - return new EncoderValidator(_logger, _processFactory).ValidateVersion(path, logOutput); + ReInit(); + } + } + } } - private void ConfigureEncoderPaths() + private bool ValidatePath(string type, string path) { - if (_hasExternalEncoder) + if (!string.IsNullOrEmpty(path)) { - return; - } - - var appPath = GetEncodingOptions().EncoderAppPath; + if (File.Exists(path)) + { + var valid = new EncoderValidator(_logger, _processFactory).ValidateVersion(path, true); - if (string.IsNullOrWhiteSpace(appPath)) - { - appPath = Path.Combine(ConfigurationManager.ApplicationPaths.ProgramDataPath, "ffmpeg"); + if (valid == true) + { + return true; + } + else + { + _logger.LogError("{0}: Failed validation checks. Version 4.0 or greater is required: {1}", type, path); + } + } + else + { + _logger.LogError("{0}: File not found: {1}", type, path); + } } - var newPaths = GetEncoderPaths(appPath); - if (string.IsNullOrWhiteSpace(newPaths.Item1) || string.IsNullOrWhiteSpace(newPaths.Item2) || IsSystemInstalledPath(appPath)) - { - newPaths = TestForInstalledVersions(); - } + return false; + } - if (!string.IsNullOrWhiteSpace(newPaths.Item1) && !string.IsNullOrWhiteSpace(newPaths.Item2)) + private bool ValidatePathFFmpeg(string comment, string path) + { + if (ValidatePath("FFmpeg: " + comment, path) == true) { - FFMpegPath = newPaths.Item1; - FFProbePath = newPaths.Item2; + FFmpegPath = path; + return true; } - LogPaths(); + return false; } - private Tuple GetEncoderPaths(string configuredPath) + private bool ValidatePathFFprobe(string comment, string path) { - var appPath = configuredPath; - - if (!string.IsNullOrWhiteSpace(appPath)) + if (ValidatePath("FFprobe: " + comment, path) == true) { - if (Directory.Exists(appPath)) - { - return GetPathsFromDirectory(appPath); - } - - if (File.Exists(appPath)) - { - return new Tuple(appPath, GetProbePathFromEncoderPath(appPath)); - } + FFprobePath = path; + return true; } - return new Tuple(null, null); + return false; } - private Tuple TestForInstalledVersions() + private string GetEncoderPath(string path) { - string encoderPath = null; - string probePath = null; - - if (_hasExternalEncoder && ValidateVersion(_originalFFMpegPath, true)) + if (Directory.Exists(path)) { - encoderPath = _originalFFMpegPath; - probePath = _originalFFProbePath; + return GetEncoderPathFromDirectory(path); } - if (string.IsNullOrWhiteSpace(encoderPath)) + if (File.Exists(path)) { - if (ValidateVersion("ffmpeg", true) && ValidateVersion("ffprobe", false)) - { - encoderPath = "ffmpeg"; - probePath = "ffprobe"; - } + return path; } - return new Tuple(encoderPath, probePath); + return null; } - private Tuple GetPathsFromDirectory(string path) + private string GetEncoderPathFromDirectory(string path) { - // Since we can't predict the file extension, first try directly within the folder - // If that doesn't pan out, then do a recursive search - var files = FileSystem.GetFilePaths(path); - - var excludeExtensions = new[] { ".c" }; - - var ffmpegPath = files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffmpeg", StringComparison.OrdinalIgnoreCase) && !excludeExtensions.Contains(Path.GetExtension(i) ?? string.Empty)); - var ffprobePath = files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffprobe", StringComparison.OrdinalIgnoreCase) && !excludeExtensions.Contains(Path.GetExtension(i) ?? string.Empty)); - - if (string.IsNullOrWhiteSpace(ffmpegPath) || !File.Exists(ffmpegPath)) + try { - files = FileSystem.GetFilePaths(path, true); + var files = FileSystem.GetFilePaths(path); - ffmpegPath = files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffmpeg", StringComparison.OrdinalIgnoreCase) && !excludeExtensions.Contains(Path.GetExtension(i) ?? string.Empty)); + var excludeExtensions = new[] { ".c" }; - if (!string.IsNullOrWhiteSpace(ffmpegPath)) - { - ffprobePath = GetProbePathFromEncoderPath(ffmpegPath); - } + return files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffmpeg", StringComparison.OrdinalIgnoreCase) && !excludeExtensions.Contains(Path.GetExtension(i) ?? string.Empty)); + } + catch (Exception) + { + // Trap all exceptions, like DirNotExists, and return null + return null; } - - return new Tuple(ffmpegPath, ffprobePath); } private string GetProbePathFromEncoderPath(string appPath) @@ -357,15 +318,31 @@ namespace MediaBrowser.MediaEncoding.Encoder .FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffprobe", StringComparison.OrdinalIgnoreCase)); } - private void LogPaths() + /// + /// Search the system $PATH environment variable looking for given filename. + /// + /// + /// + private string ExistsOnSystemPath(string fileName) { - _logger.LogInformation("FFMpeg: {0}", FFMpegPath ?? "not found"); - _logger.LogInformation("FFProbe: {0}", FFProbePath ?? "not found"); + var values = Environment.GetEnvironmentVariable("PATH"); + + foreach (var path in values.Split(Path.PathSeparator)) + { + var candidatePath = GetEncoderPathFromDirectory(path); + + if (ValidatePath("Found on PATH", candidatePath)) + { + return candidatePath; + } + } + return null; } - private EncodingOptions GetEncodingOptions() + private void LogPaths() { - return ConfigurationManager.GetConfiguration("encoding"); + _logger.LogInformation("FFMpeg: {0}", FFmpegPath ?? "not found"); + _logger.LogInformation("FFProbe: {0}", FFprobePath ?? "not found"); } private List _encoders = new List(); @@ -412,12 +389,6 @@ namespace MediaBrowser.MediaEncoding.Encoder return true; } - /// - /// Gets the encoder path. - /// - /// The encoder path. - public string EncoderPath => FFMpegPath; - /// /// Gets the media info. /// @@ -489,7 +460,7 @@ namespace MediaBrowser.MediaEncoding.Encoder // Must consume both or ffmpeg may hang due to deadlocks. See comments below. RedirectStandardOutput = true, - FileName = FFProbePath, + FileName = FFprobePath, Arguments = string.Format(args, probeSizeArgument, inputPath).Trim(), IsHidden = true, @@ -691,7 +662,7 @@ namespace MediaBrowser.MediaEncoding.Encoder { CreateNoWindow = true, UseShellExecute = false, - FileName = FFMpegPath, + FileName = FFmpegPath, Arguments = args, IsHidden = true, ErrorDialog = false, @@ -814,7 +785,7 @@ namespace MediaBrowser.MediaEncoding.Encoder { CreateNoWindow = true, UseShellExecute = false, - FileName = FFMpegPath, + FileName = FFmpegPath, Arguments = args, IsHidden = true, ErrorDialog = false, diff --git a/MediaBrowser.Model/Configuration/EncodingOptions.cs b/MediaBrowser.Model/Configuration/EncodingOptions.cs index 8584bd3dd..ff697437a 100644 --- a/MediaBrowser.Model/Configuration/EncodingOptions.cs +++ b/MediaBrowser.Model/Configuration/EncodingOptions.cs @@ -8,7 +8,8 @@ namespace MediaBrowser.Model.Configuration public bool EnableThrottling { get; set; } public int ThrottleDelaySeconds { get; set; } public string HardwareAccelerationType { get; set; } - public string EncoderAppPath { get; set; } + public string EncoderAppPathCustom { get; set; } // FFmpeg path as set by the user via the UI + public string EncoderAppPath { get; set; } // The current FFmpeg path being used by the system public string VaapiDevice { get; set; } public int H264Crf { get; set; } public string H264Preset { get; set; } -- cgit v1.2.3 From ed69e690b89e6a3e6e22bbf448af08a25c38e71b Mon Sep 17 00:00:00 2001 From: PloughPuff Date: Tue, 12 Feb 2019 22:05:42 +0000 Subject: Review comments Address review comments from JustAMan, Bond-009 and cvium. --- Emby.Server.Implementations/ApplicationHost.cs | 35 +-- .../MediaEncoding/IMediaEncoder.cs | 3 +- .../Encoder/EncoderValidator.cs | 8 + MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 247 ++++++++++----------- MediaBrowser.Model/System/SystemInfo.cs | 17 +- 5 files changed, 150 insertions(+), 160 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 2c0d0e746..dd29f2ade 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -791,7 +791,17 @@ namespace Emby.Server.Implementations ChapterManager = new ChapterManager(LibraryManager, LoggerFactory, ServerConfigurationManager, ItemRepository); serviceCollection.AddSingleton(ChapterManager); - MediaEncoder = new MediaBrowser.MediaEncoding.Encoder.MediaEncoder(LoggerFactory, JsonSerializer, StartupOptions.FFmpegPath, StartupOptions.FFprobePath, ServerConfigurationManager, FileSystemManager, () => SubtitleEncoder, () => MediaSourceManager, ProcessFactory, 5000); + MediaEncoder = new MediaBrowser.MediaEncoding.Encoder.MediaEncoder( + LoggerFactory, + JsonSerializer, + StartupOptions.FFmpegPath, + StartupOptions.FFprobePath, + ServerConfigurationManager, + FileSystemManager, + () => SubtitleEncoder, + () => MediaSourceManager, + ProcessFactory, + 5000); serviceCollection.AddSingleton(MediaEncoder); EncodingManager = new MediaEncoder.EncodingManager(FileSystemManager, LoggerFactory, MediaEncoder, ChapterManager, LibraryManager); @@ -908,27 +918,6 @@ namespace Emby.Server.Implementations return new ImageProcessor(LoggerFactory, ServerConfigurationManager.ApplicationPaths, FileSystemManager, ImageEncoder, () => LibraryManager, () => MediaEncoder); } - /// - /// Registers the media encoder. - /// - /// Task. - private void RegisterMediaEncoder(IAssemblyInfo assemblyInfo) - { - MediaEncoder = new MediaBrowser.MediaEncoding.Encoder.MediaEncoder( - LoggerFactory, - JsonSerializer, - StartupOptions.FFmpegPath, - StartupOptions.FFprobePath, - ServerConfigurationManager, - FileSystemManager, - () => SubtitleEncoder, - () => MediaSourceManager, - ProcessFactory, - 5000); - - RegisterSingleInstance(MediaEncoder); - } - /// /// Gets the user repository. /// @@ -1404,7 +1393,7 @@ namespace Emby.Server.Implementations ServerName = FriendlyName, LocalAddress = localAddress, SupportsLibraryMonitor = true, - EncoderLocationType = MediaEncoder.EncoderLocationType, + EncoderLocation = MediaEncoder.EncoderLocation, SystemArchitecture = EnvironmentInfo.SystemArchitecture, SystemUpdateLevel = SystemUpdateLevel, PackageName = StartupOptions.PackageName diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index 057e43910..8852dac05 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -6,6 +6,7 @@ using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; +using MediaBrowser.Model.System; namespace MediaBrowser.Controller.MediaEncoding { @@ -14,7 +15,7 @@ namespace MediaBrowser.Controller.MediaEncoding /// public interface IMediaEncoder : ITranscoderSupport { - string EncoderLocationType { get; } + FFmpegLocation EncoderLocation { get; } /// /// Gets the encoder path. diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs index 1eeea87a0..3eed891cb 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs @@ -48,6 +48,10 @@ namespace MediaBrowser.MediaEncoding.Encoder if (string.IsNullOrWhiteSpace(output)) { + if (logOutput) + { + _logger.LogError("FFmpeg validation: The process returned no result"); + } return false; } @@ -55,6 +59,10 @@ namespace MediaBrowser.MediaEncoding.Encoder if (output.IndexOf("Libav developers", StringComparison.OrdinalIgnoreCase) != -1) { + if (logOutput) + { + _logger.LogError("FFmpeg validation: avconv instead of ffmpeg is not supported"); + } return false; } diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 36d72cad9..9aad67ec7 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; +using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Configuration; @@ -18,6 +19,7 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Serialization; +using MediaBrowser.Model.System; using Microsoft.Extensions.Logging; namespace MediaBrowser.MediaEncoding.Encoder @@ -34,17 +36,16 @@ namespace MediaBrowser.MediaEncoding.Encoder public string EncoderPath => FFmpegPath; /// - /// External: path supplied via command line - /// Custom: coming from UI or config/encoding.xml file - /// System: FFmpeg found in system $PATH - /// null: No FFmpeg found + /// The location of the discovered FFmpeg tool. /// - public string EncoderLocationType { get; private set; } + public FFmpegLocation EncoderLocation { get; private set; } + + private FFmpegLocation ProbeLocation; private readonly ILogger _logger; private readonly IJsonSerializer _jsonSerializer; - private string FFmpegPath { get; set; } - private string FFprobePath { get; set; } + private string FFmpegPath; + private string FFprobePath; protected readonly IServerConfigurationManager ConfigurationManager; protected readonly IFileSystem FileSystem; protected readonly Func SubtitleEncoder; @@ -54,6 +55,11 @@ namespace MediaBrowser.MediaEncoding.Encoder private readonly string StartupOptionFFmpegPath; private readonly string StartupOptionFFprobePath; + /// + /// Enum to identify the two types of FF utilities of interest. + /// + private enum FFtype { Mpeg, Probe }; + private readonly SemaphoreSlim _thumbnailResourcePool = new SemaphoreSlim(1, 1); private readonly List _runningProcesses = new List(); @@ -82,48 +88,24 @@ namespace MediaBrowser.MediaEncoding.Encoder /// /// Run at startup or if the user removes a Custom path from transcode page. - /// Sets global variables FFmpegPath and EncoderLocationType. - /// If startup options --ffprobe is given then FFprobePath is set too. + /// Sets global variables FFmpegPath. + /// Precedence is: Config > CLI > $PATH /// public void Init() { - // 1) If given, use the --ffmpeg CLI switch - if (ValidatePathFFmpeg("From CLI Switch", StartupOptionFFmpegPath)) - { - _logger.LogInformation("FFmpeg: Using path from command line switch --ffmpeg"); - EncoderLocationType = "External"; - } - - // 2) Try Custom path stroed in config/encoding xml file under tag - else if (ValidatePathFFmpeg("From Config File", ConfigurationManager.GetConfiguration("encoding").EncoderAppPathCustom)) - { - _logger.LogInformation("FFmpeg: Using path from config/encoding.xml file"); - EncoderLocationType = "Custom"; - } - - // 3) Search system $PATH environment variable for valid FFmpeg - else if (ValidatePathFFmpeg("From $PATH", ExistsOnSystemPath("ffmpeg"))) + // 1) Custom path stored in config/encoding xml file under tag takes precedence + if (!ValidatePath(FFtype.Mpeg, ConfigurationManager.GetConfiguration("encoding").EncoderAppPathCustom, FFmpegLocation.Custom)) { - _logger.LogInformation("FFmpeg: Using system $PATH for FFmpeg"); - EncoderLocationType = "System"; - } - else - { - _logger.LogError("FFmpeg: No suitable executable found"); - FFmpegPath = null; - EncoderLocationType = null; - } - - // If given, use the --ffprobe CLI switch - if (ValidatePathFFprobe("CLI Switch", StartupOptionFFprobePath)) - { - _logger.LogInformation("FFprobe: Using path from command line switch --ffprobe"); - } - else - { - // FFprobe path from command line is no good, so set to null and let ReInit() try - // and set using the FFmpeg path. - FFprobePath = null; + // 2) Check if the --ffmpeg CLI switch has been given + if (!ValidatePath(FFtype.Mpeg, StartupOptionFFmpegPath, FFmpegLocation.SetByArgument)) + { + // 3) Search system $PATH environment variable for valid FFmpeg + if (!ValidatePath(FFtype.Mpeg, ExistsOnSystemPath("ffmpeg"), FFmpegLocation.System)) + { + EncoderLocation = FFmpegLocation.NotFound; + FFmpegPath = null; + } + } } ReInit(); @@ -136,27 +118,27 @@ namespace MediaBrowser.MediaEncoding.Encoder /// private void ReInit() { - // Write the FFmpeg path to the config/encoding.xml file so it appears in UI + // Write the FFmpeg path to the config/encoding.xml file as so it appears in UI var config = ConfigurationManager.GetConfiguration("encoding"); config.EncoderAppPath = FFmpegPath ?? string.Empty; ConfigurationManager.SaveConfiguration("encoding", config); + // Clear probe settings in case probe validation fails + ProbeLocation = FFmpegLocation.NotFound; + FFprobePath = null; + // Only if mpeg path is set, try and set path to probe if (FFmpegPath != null) { - // Probe would be null here if no valid --ffprobe path was given - // at startup, or we're performing ReInit following mpeg path update from UI - if (FFprobePath == null) + if (EncoderLocation == FFmpegLocation.Custom || StartupOptionFFprobePath == null) { - // Use the mpeg path to create a probe path - if (ValidatePathFFprobe("Copied from FFmpeg:", GetProbePathFromEncoderPath(FFmpegPath))) - { - _logger.LogInformation("FFprobe: Using FFprobe in same folders as FFmpeg"); - } - else - { - _logger.LogError("FFprobe: No suitable executable found"); - } + // If mpeg was read from config, or CLI switch not given, try and set probe from mpeg path + ValidatePath(FFtype.Probe, GetProbePathFromEncoderPath(FFmpegPath), EncoderLocation); + } + else + { + // Else try and set probe path from CLI switch + ValidatePath(FFtype.Probe, StartupOptionFFmpegPath, FFmpegLocation.SetByArgument); } // Interrogate to understand what coders it supports @@ -183,108 +165,95 @@ namespace MediaBrowser.MediaEncoding.Encoder { throw new ArgumentException("Unexpected pathType value"); } - else + + if (string.IsNullOrWhiteSpace(path)) { - if (string.IsNullOrWhiteSpace(path)) - { - // User had cleared the cutom path in UI. Clear the Custom config - // setting and peform full Init to relook any CLI switches and system $PATH - var config = ConfigurationManager.GetConfiguration("encoding"); - config.EncoderAppPathCustom = string.Empty; - ConfigurationManager.SaveConfiguration("encoding", config); + // User had cleared the custom path in UI. Clear the Custom config + // setting and perform full Init to reinspect any CLI switches and system $PATH + var config = ConfigurationManager.GetConfiguration("encoding"); + config.EncoderAppPathCustom = string.Empty; + ConfigurationManager.SaveConfiguration("encoding", config); - Init(); - } - else if (!File.Exists(path) && !Directory.Exists(path)) + Init(); + } + else if (!File.Exists(path) && !Directory.Exists(path)) + { + // Given path is neither file or folder + throw new ResourceNotFoundException(); + } + else + { + // Supplied path could be either file path or folder path. + // Resolve down to file path and validate + if (!ValidatePath(FFtype.Mpeg, GetEncoderPath(path), FFmpegLocation.Custom)) { - // Given path is neither file or folder - throw new ResourceNotFoundException(); + throw new ResourceNotFoundException("Failed validation checks."); } else { - // Supplied path could be either file path or folder path. - // Resolve down to file path and validate - path = GetEncoderPath(path); - - if (path == null) - { - throw new ResourceNotFoundException("FFmpeg not found"); - } - else if (!ValidatePathFFmpeg("New From UI", path)) - { - throw new ResourceNotFoundException("Failed validation checks. Version 4.0 or greater is required"); - } - else - { - EncoderLocationType = "Custom"; - - // Write the validated mpeg path to the xml as - // This ensures its not lost on new startup - var config = ConfigurationManager.GetConfiguration("encoding"); - config.EncoderAppPathCustom = FFmpegPath; - ConfigurationManager.SaveConfiguration("encoding", config); - - FFprobePath = null; // Clear probe path so it gets relooked in ReInit() + // Write the validated mpeg path to the xml as + // This ensures its not lost on new startup + var config = ConfigurationManager.GetConfiguration("encoding"); + config.EncoderAppPathCustom = FFmpegPath; + ConfigurationManager.SaveConfiguration("encoding", config); - ReInit(); - } + ReInit(); } } } - private bool ValidatePath(string type, string path) + /// + /// Validates the supplied FQPN to ensure it is a FFxxx utility. + /// If checks pass, global variable FFmpegPath (or FFprobePath) and + /// EncoderLocation (or ProbeLocation) are updated. + /// + /// Either mpeg or probe + /// FQPN to test + /// Location (External, Custom, System) of tool + /// + private bool ValidatePath(FFtype type, string path, FFmpegLocation location) { + bool rc = false; + if (!string.IsNullOrEmpty(path)) { if (File.Exists(path)) { - var valid = new EncoderValidator(_logger, _processFactory).ValidateVersion(path, true); + rc = new EncoderValidator(_logger, _processFactory).ValidateVersion(path, false); - if (valid == true) + // Only update the global variables if the checks passed + if (rc) { - return true; + if (type == FFtype.Mpeg) + { + FFmpegPath = path; + EncoderLocation = location; + } + else + { + FFprobePath = path; + ProbeLocation = location; + } } else { - _logger.LogError("{0}: Failed validation checks. Version 4.0 or greater is required: {1}", type, path); + _logger.LogError("{0}: {1}: Failed version check: {2}", type.ToString(), location.ToString(), path); } } else { - _logger.LogError("{0}: File not found: {1}", type, path); + _logger.LogError("{0}: {1}: File not found: {2}", type.ToString(), location.ToString(), path); } } - return false; - } - - private bool ValidatePathFFmpeg(string comment, string path) - { - if (ValidatePath("FFmpeg: " + comment, path) == true) - { - FFmpegPath = path; - return true; - } - - return false; - } - - private bool ValidatePathFFprobe(string comment, string path) - { - if (ValidatePath("FFprobe: " + comment, path) == true) - { - FFprobePath = path; - return true; - } - - return false; + return rc; } private string GetEncoderPath(string path) { if (Directory.Exists(path)) { - return GetEncoderPathFromDirectory(path); + return GetEncoderPathFromDirectory(path, "ffmpeg"); } if (File.Exists(path)) @@ -295,7 +264,7 @@ namespace MediaBrowser.MediaEncoding.Encoder return null; } - private string GetEncoderPathFromDirectory(string path) + private string GetEncoderPathFromDirectory(string path, string filename) { try { @@ -303,7 +272,8 @@ namespace MediaBrowser.MediaEncoding.Encoder var excludeExtensions = new[] { ".c" }; - return files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffmpeg", StringComparison.OrdinalIgnoreCase) && !excludeExtensions.Contains(Path.GetExtension(i) ?? string.Empty)); + return files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), filename, StringComparison.OrdinalIgnoreCase) + && !excludeExtensions.Contains(Path.GetExtension(i) ?? string.Empty)); } catch (Exception) { @@ -314,8 +284,15 @@ namespace MediaBrowser.MediaEncoding.Encoder private string GetProbePathFromEncoderPath(string appPath) { - return FileSystem.GetFilePaths(Path.GetDirectoryName(appPath)) - .FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffprobe", StringComparison.OrdinalIgnoreCase)); + if (!string.IsNullOrEmpty(appPath)) + { + string pattern = @"[^\/\\]+?(\.[^\/\\\n.]+)?$"; + string substitution = @"ffprobe$1"; + + return Regex.Replace(appPath, pattern, substitution); + } + + return null; } /// @@ -323,15 +300,15 @@ namespace MediaBrowser.MediaEncoding.Encoder /// /// /// - private string ExistsOnSystemPath(string fileName) + private string ExistsOnSystemPath(string filename) { var values = Environment.GetEnvironmentVariable("PATH"); foreach (var path in values.Split(Path.PathSeparator)) { - var candidatePath = GetEncoderPathFromDirectory(path); + var candidatePath = GetEncoderPathFromDirectory(path, filename); - if (ValidatePath("Found on PATH", candidatePath)) + if (!string.IsNullOrEmpty(candidatePath)) { return candidatePath; } @@ -341,8 +318,8 @@ namespace MediaBrowser.MediaEncoding.Encoder private void LogPaths() { - _logger.LogInformation("FFMpeg: {0}", FFmpegPath ?? "not found"); - _logger.LogInformation("FFProbe: {0}", FFprobePath ?? "not found"); + _logger.LogInformation("FFmpeg: {0}: {1}", EncoderLocation.ToString(), FFmpegPath ?? string.Empty); + _logger.LogInformation("FFprobe: {0}: {1}", ProbeLocation.ToString(), FFprobePath ?? string.Empty); } private List _encoders = new List(); diff --git a/MediaBrowser.Model/System/SystemInfo.cs b/MediaBrowser.Model/System/SystemInfo.cs index 581a1069c..6482f2c84 100644 --- a/MediaBrowser.Model/System/SystemInfo.cs +++ b/MediaBrowser.Model/System/SystemInfo.cs @@ -4,6 +4,21 @@ using MediaBrowser.Model.Updates; namespace MediaBrowser.Model.System { + /// + /// Enum describing the location of the FFmpeg tool. + /// + public enum FFmpegLocation + { + /// No path to FFmpeg found. + NotFound, + /// Path supplied via command line using switch --ffmpeg. + SetByArgument, + /// User has supplied path via Transcoding UI page. + Custom, + /// FFmpeg tool found on system $PATH. + System + }; + /// /// Class SystemInfo /// @@ -122,7 +137,7 @@ namespace MediaBrowser.Model.System /// true if this instance has update available; otherwise, false. public bool HasUpdateAvailable { get; set; } - public string EncoderLocationType { get; set; } + public FFmpegLocation EncoderLocation { get; set; } public Architecture SystemArchitecture { get; set; } -- cgit v1.2.3 From 2617a49b78c99f72ba36e53a4c97c4e042116a53 Mon Sep 17 00:00:00 2001 From: PloughPuff Date: Thu, 28 Feb 2019 22:47:56 +0000 Subject: Renamed Init() to SetFFmpegPath() --- Emby.Server.Implementations/ApplicationHost.cs | 2 +- MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs | 2 +- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index dd29f2ade..325df3293 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -534,7 +534,7 @@ namespace Emby.Server.Implementations ConfigurationManager.ConfigurationUpdated += OnConfigurationUpdated; - MediaEncoder.Init(); + MediaEncoder.SetFFmpegPath(); //if (string.IsNullOrWhiteSpace(MediaEncoder.EncoderPath)) //{ diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index 8852dac05..d4ac3b7c3 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -92,7 +92,7 @@ namespace MediaBrowser.Controller.MediaEncoding /// System.String. string EscapeSubtitleFilterPath(string path); - void Init(); + void SetFFmpegPath(); void UpdateEncoderPath(string path, string pathType); bool SupportsEncoder(string encoder); diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 51b4f6e39..292457788 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -84,7 +84,7 @@ namespace MediaBrowser.MediaEncoding.Encoder /// Sets global variables FFmpegPath. /// Precedence is: Config > CLI > $PATH /// - public void Init() + public void SetFFmpegPath() { // ToDo - Finalise removal of the --ffprobe switch if (!string.IsNullOrEmpty(StartupOptionFFprobePath)) @@ -169,8 +169,8 @@ namespace MediaBrowser.MediaEncoding.Encoder config.EncoderAppPath = newPath; ConfigurationManager.SaveConfiguration("encoding", config); - // Trigger Init so we validate the new path and setup probe path - Init(); + // Trigger SetFFmpegPath so we validate the new path and setup probe path + SetFFmpegPath(); } /// -- cgit v1.2.3 From bef665be364ce1477d09ed268f68c19e0099922f Mon Sep 17 00:00:00 2001 From: Phallacy Date: Tue, 5 Mar 2019 23:45:05 -0800 Subject: Minor fixes to address style issues --- .../Library/DefaultAuthenticationProvider.cs | 14 +++++++------- Emby.Server.Implementations/Library/UserManager.cs | 15 +++++---------- MediaBrowser.Model/Cryptography/PasswordHash.cs | 7 ++++--- 3 files changed, 16 insertions(+), 20 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs index 8f10b5a84..3ac604b40 100644 --- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs @@ -50,22 +50,22 @@ namespace Emby.Server.Implementations.Library byte[] passwordbytes = Encoding.UTF8.GetBytes(password); PasswordHash readyHash = new PasswordHash(resolvedUser.Password); - byte[] CalculatedHash; - string CalculatedHashString; + byte[] calculatedHash; + string calculatedHashString; if (_cryptographyProvider.GetSupportedHashMethods().Contains(readyHash.Id)) { if (string.IsNullOrEmpty(readyHash.Salt)) { - CalculatedHash = _cryptographyProvider.ComputeHash(readyHash.Id, passwordbytes); - CalculatedHashString = BitConverter.ToString(CalculatedHash).Replace("-", string.Empty); + 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); + calculatedHash = _cryptographyProvider.ComputeHash(readyHash.Id, passwordbytes, readyHash.SaltBytes); + calculatedHashString = BitConverter.ToString(calculatedHash).Replace("-", string.Empty); } - if (CalculatedHashString == readyHash.Hash) + if (calculatedHashString == readyHash.Hash) { success = true; // throw new Exception("Invalid username or password"); diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index 57bf16364..efb1ef4a5 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -475,11 +475,6 @@ namespace Emby.Server.Implementations.Library : user.EasyPassword; } - private bool IsPasswordEmpty(User user, string passwordHash) - { - return string.IsNullOrEmpty(passwordHash); - } - /// /// Loads the users from the repository /// @@ -522,14 +517,14 @@ namespace Emby.Server.Implementations.Library throw new ArgumentNullException(nameof(user)); } - var hasConfiguredPassword = GetAuthenticationProvider(user).HasPassword(user).Result; - var hasConfiguredEasyPassword = !IsPasswordEmpty(user, GetLocalPasswordHash(user)); + bool hasConfiguredPassword = GetAuthenticationProvider(user).HasPassword(user).Result; + bool hasConfiguredEasyPassword = string.IsNullOrEmpty(GetLocalPasswordHash(user)); - var hasPassword = user.Configuration.EnableLocalPassword && !string.IsNullOrEmpty(remoteEndPoint) && _networkManager.IsInLocalNetwork(remoteEndPoint) ? + bool hasPassword = user.Configuration.EnableLocalPassword && !string.IsNullOrEmpty(remoteEndPoint) && _networkManager.IsInLocalNetwork(remoteEndPoint) ? hasConfiguredEasyPassword : hasConfiguredPassword; - var dto = new UserDto + UserDto dto = new UserDto { Id = user.Id, Name = user.Name, @@ -548,7 +543,7 @@ namespace Emby.Server.Implementations.Library dto.EnableAutoLogin = true; } - var image = user.GetImageInfo(ImageType.Primary, 0); + ItemImageInfo image = user.GetImageInfo(ImageType.Primary, 0); if (image != null) { diff --git a/MediaBrowser.Model/Cryptography/PasswordHash.cs b/MediaBrowser.Model/Cryptography/PasswordHash.cs index a52840404..7a1be833d 100644 --- a/MediaBrowser.Model/Cryptography/PasswordHash.cs +++ b/MediaBrowser.Model/Cryptography/PasswordHash.cs @@ -100,13 +100,14 @@ namespace MediaBrowser.Model.Cryptography public static byte[] ConvertFromByteString(string byteString) { - List Bytes = new List(); + List bytes = new List(); for (int i = 0; i < byteString.Length; i += 2) { - Bytes.Add(Convert.ToByte(byteString.Substring(i, 2),16)); + // TODO: NetStandard2.1 switch this to use a span instead of a substring. + bytes.Add(Convert.ToByte(byteString.Substring(i, 2),16)); } - return Bytes.ToArray(); + return bytes.ToArray(); } public static string ConvertToByteString(byte[] bytes) -- cgit v1.2.3 From 04db0369d46c4b3961086b1d30946bd990a5767c Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Wed, 6 Mar 2019 17:31:52 +0100 Subject: Update LocalizationManager.cs --- Emby.Server.Implementations/Localization/LocalizationManager.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs index d55b258a3..762649b71 100644 --- a/Emby.Server.Implementations/Localization/LocalizationManager.cs +++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs @@ -93,12 +93,11 @@ namespace Emby.Server.Implementations.Localization #if DEBUG else { - _logger.LogWarning("Misformed line in ratings file for country {CountryCode}", countryCode); + _logger.LogWarning("Malformed line in ratings file for country {CountryCode}", countryCode); } #endif } } - _logger.LogWarning("{t}", countryCode); _allParentalRatings[countryCode] = dict; } -- cgit v1.2.3 From c31b0b311b339475650aa8812eb57152cac32d80 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Thu, 7 Mar 2019 02:41:44 -0800 Subject: Apply suggestions from code review more minor fixes before I do larger fixes Co-Authored-By: LogicalPhallacy <44458166+LogicalPhallacy@users.noreply.github.com> --- .../Cryptography/CryptographyProvider.cs | 6 +- .../Library/DefaultAuthenticationProvider.cs | 2 +- MediaBrowser.Model/Cryptography/PasswordHash.cs | 132 ++++++++++----------- 3 files changed, 70 insertions(+), 70 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs index cf1ea6efa..2e882cefe 100644 --- a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs +++ b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs @@ -83,7 +83,7 @@ namespace Emby.Server.Implementations.Cryptography { //downgrading for now as we need this library to be dotnetstandard compliant //with this downgrade we'll add a check to make sure we're on the downgrade method at the moment - if(method == DefaultHashMethod) + if (method == DefaultHashMethod) { using (var r = new Rfc2898DeriveBytes(bytes, salt, iterations)) { @@ -96,7 +96,7 @@ namespace Emby.Server.Implementations.Cryptography public byte[] ComputeHash(string hashMethod, byte[] bytes) { - return ComputeHash(hashMethod, bytes, new byte[0]); + return ComputeHash(hashMethod, bytes, Array.Empty()); } public byte[] ComputeHashWithDefaultMethod(byte[] bytes) @@ -106,7 +106,7 @@ namespace Emby.Server.Implementations.Cryptography public byte[] ComputeHash(string hashMethod, byte[] bytes, byte[] salt) { - if(hashMethod == DefaultHashMethod) + if (hashMethod == DefaultHashMethod) { return PBKDF2(hashMethod, bytes, salt, _defaultIterations); } diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs index 3ac604b40..526509f43 100644 --- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs @@ -101,7 +101,7 @@ namespace Emby.Server.Implementations.Library if (!user.Password.Contains("$")) { string hash = user.Password; - user.Password = String.Format("$SHA1${0}", hash); + user.Password = string.Format("$SHA1${0}", hash); } if (user.EasyPassword != null && !user.EasyPassword.Contains("$")) diff --git a/MediaBrowser.Model/Cryptography/PasswordHash.cs b/MediaBrowser.Model/Cryptography/PasswordHash.cs index 7a1be833d..72bdc6745 100644 --- a/MediaBrowser.Model/Cryptography/PasswordHash.cs +++ b/MediaBrowser.Model/Cryptography/PasswordHash.cs @@ -8,34 +8,34 @@ namespace MediaBrowser.Model.Cryptography { // Defined from this hash storage spec // https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md - // $[$=(,=)*][$[$]] - // with one slight amendment to ease the transition, we're writing out the bytes in hex + // $[$=(,=)*][$[$]] + // with one slight amendment to ease the transition, we're writing out the bytes in hex // rather than making them a BASE64 string with stripped padding - private string _id; + private string _id; - private Dictionary _parameters = new Dictionary(); + private Dictionary _parameters = new Dictionary(); - private string _salt; + private string _salt; - private byte[] _saltBytes; + private byte[] _saltBytes; - private string _hash; + private string _hash; + + private byte[] _hashBytes; + + public string Id { get => _id; set => _id = value; } + + public Dictionary Parameters { get => _parameters; set => _parameters = value; } + + public string Salt { get => _salt; set => _salt = value; } + + public byte[] SaltBytes { get => _saltBytes; set => _saltBytes = value; } + + public string Hash { get => _hash; set => _hash = value; } + + public byte[] HashBytes { get => _hashBytes; set => _hashBytes = value; } - private byte[] _hashBytes; - - public string Id { get => _id; set => _id = value; } - - public Dictionary Parameters { get => _parameters; set => _parameters = value; } - - public string Salt { get => _salt; set => _salt = value; } - - public byte[] SaltBytes { get => _saltBytes; set => _saltBytes = value; } - - public string Hash { get => _hash; set => _hash = value; } - - public byte[] HashBytes { get => _hashBytes; set => _hashBytes = value; } - public PasswordHash(string storageString) { string[] splitted = storageString.Split('$'); @@ -46,14 +46,14 @@ namespace MediaBrowser.Model.Cryptography { if (!string.IsNullOrEmpty(paramset)) { - string[] fields = paramset.Split('='); - if (fields.Length == 2) - { - _parameters.Add(fields[0], fields[1]); - } - else - { - throw new Exception($"Malformed parameter in password hash string {paramset}"); + string[] fields = paramset.Split('='); + if (fields.Length == 2) + { + _parameters.Add(fields[0], fields[1]); + } + else + { + throw new Exception($"Malformed parameter in password hash string {paramset}"); } } } @@ -89,31 +89,31 @@ namespace MediaBrowser.Model.Cryptography } - } - + } + public PasswordHash(ICryptoProvider cryptoProvider) { _id = cryptoProvider.DefaultHashMethod; _saltBytes = cryptoProvider.GenerateSalt(); - _salt = ConvertToByteString(SaltBytes); - } - - public static byte[] ConvertFromByteString(string byteString) - { - List bytes = new List(); - for (int i = 0; i < byteString.Length; i += 2) - { - // TODO: NetStandard2.1 switch this to use a span instead of a substring. - bytes.Add(Convert.ToByte(byteString.Substring(i, 2),16)); - } - - return bytes.ToArray(); - } - - public static string ConvertToByteString(byte[] bytes) - { - return BitConverter.ToString(bytes).Replace("-", ""); - } + _salt = ConvertToByteString(SaltBytes); + } + + public static byte[] ConvertFromByteString(string byteString) + { + List bytes = new List(); + for (int i = 0; i < byteString.Length; i += 2) + { + // TODO: NetStandard2.1 switch this to use a span instead of a substring. + bytes.Add(Convert.ToByte(byteString.Substring(i, 2), 16)); + } + + return bytes.ToArray(); + } + + public static string ConvertToByteString(byte[] bytes) + { + return BitConverter.ToString(bytes).Replace("-", ""); + } private string SerializeParameters() { @@ -121,33 +121,33 @@ namespace MediaBrowser.Model.Cryptography foreach (var KVP in _parameters) { ReturnString += $",{KVP.Key}={KVP.Value}"; - } + } if ((!string.IsNullOrEmpty(ReturnString)) && ReturnString[0] == ',') { ReturnString = ReturnString.Remove(0, 1); - } + } return ReturnString; } public override string ToString() - { - string outString = "$" +_id; - string paramstring = SerializeParameters(); - if (!string.IsNullOrEmpty(paramstring)) - { - outString += $"${paramstring}"; - } - - if (!string.IsNullOrEmpty(_salt)) - { - outString += $"${_salt}"; - } - + { + string outString = "$" + _id; + string paramstring = SerializeParameters(); + if (!string.IsNullOrEmpty(paramstring)) + { + outString += $"${paramstring}"; + } + + if (!string.IsNullOrEmpty(_salt)) + { + outString += $"${_salt}"; + } + outString += $"${_hash}"; return outString; } } -} +} -- cgit v1.2.3 From 8f4895e8a5bd1549f41bc1d4d2b31d03cff689ad Mon Sep 17 00:00:00 2001 From: Phallacy Date: Thu, 7 Mar 2019 03:11:41 -0800 Subject: more fixes for perf and style --- .../Cryptography/CryptographyProvider.cs | 35 ++++++++++++---------- MediaBrowser.Model/Cryptography/PasswordHash.cs | 16 +++++----- 2 files changed, 27 insertions(+), 24 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs index 2e882cefe..e27738f69 100644 --- a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs +++ b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs @@ -17,7 +17,7 @@ namespace Emby.Server.Implementations.Cryptography private RandomNumberGenerator _randomNumberGenerator; - private int _defaultIterations = 1000; + private const int _defaultIterations = 1000; public CryptographyProvider() { @@ -27,20 +27,20 @@ namespace Emby.Server.Implementations.Cryptography //Please note the default method of PBKDF2 is not included, it cannot be used to generate hashes cleanly as it is actually a pbkdf with sha1 _supportedHashMethods = new HashSet() { - "MD5" - ,"System.Security.Cryptography.MD5" - ,"SHA" - ,"SHA1" - ,"System.Security.Cryptography.SHA1" - ,"SHA256" - ,"SHA-256" - ,"System.Security.Cryptography.SHA256" - ,"SHA384" - ,"SHA-384" - ,"System.Security.Cryptography.SHA384" - ,"SHA512" - ,"SHA-512" - ,"System.Security.Cryptography.SHA512" + "MD5", + "System.Security.Cryptography.MD5", + "SHA", + "SHA1", + "System.Security.Cryptography.SHA1", + "SHA256", + "SHA-256", + "System.Security.Cryptography.SHA256", + "SHA384", + "SHA-384", + "System.Security.Cryptography.SHA384", + "SHA512", + "SHA-512", + "System.Security.Cryptography.SHA512" }; _randomNumberGenerator = RandomNumberGenerator.Create(); } @@ -120,7 +120,10 @@ namespace Emby.Server.Implementations.Cryptography } else { - return h.ComputeHash(bytes.Concat(salt).ToArray()); + byte[] salted = new byte[bytes.Length + salt.Length]; + Array.Copy(bytes, salted, bytes.Length); + Array.Copy(salt, 0, salted, bytes.Length, salt.Length); + return h.ComputeHash(salted); } } } diff --git a/MediaBrowser.Model/Cryptography/PasswordHash.cs b/MediaBrowser.Model/Cryptography/PasswordHash.cs index 72bdc6745..a9d0f6744 100644 --- a/MediaBrowser.Model/Cryptography/PasswordHash.cs +++ b/MediaBrowser.Model/Cryptography/PasswordHash.cs @@ -100,14 +100,14 @@ namespace MediaBrowser.Model.Cryptography public static byte[] ConvertFromByteString(string byteString) { - List bytes = new List(); + byte[] bytes = new byte[byteString.Length / 2]; for (int i = 0; i < byteString.Length; i += 2) { // TODO: NetStandard2.1 switch this to use a span instead of a substring. - bytes.Add(Convert.ToByte(byteString.Substring(i, 2), 16)); + bytes[i / 2] = Convert.ToByte(byteString.Substring(i, 2), 16); } - return bytes.ToArray(); + return bytes; } public static string ConvertToByteString(byte[] bytes) @@ -117,18 +117,18 @@ namespace MediaBrowser.Model.Cryptography private string SerializeParameters() { - string ReturnString = string.Empty; + string returnString = string.Empty; foreach (var KVP in _parameters) { - ReturnString += $",{KVP.Key}={KVP.Value}"; + returnString += $",{KVP.Key}={KVP.Value}"; } - if ((!string.IsNullOrEmpty(ReturnString)) && ReturnString[0] == ',') + if ((!string.IsNullOrEmpty(returnString)) && returnString[0] == ',') { - ReturnString = ReturnString.Remove(0, 1); + returnString = returnString.Remove(0, 1); } - return ReturnString; + return returnString; } public override string ToString() -- cgit v1.2.3 From dfb1d704edf296a34a6e752f44c9d0f22889b21f Mon Sep 17 00:00:00 2001 From: Phallacy Date: Thu, 7 Mar 2019 03:32:05 -0800 Subject: made hashset static and readonly --- .../Cryptography/CryptographyProvider.cs | 29 +++++++++++----------- 1 file changed, 14 insertions(+), 15 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs index e27738f69..982bba625 100644 --- a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs +++ b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs @@ -11,21 +11,7 @@ namespace Emby.Server.Implementations.Cryptography { public class CryptographyProvider : ICryptoProvider { - private HashSet _supportedHashMethods; - - public string DefaultHashMethod => "PBKDF2"; - - private RandomNumberGenerator _randomNumberGenerator; - - private const int _defaultIterations = 1000; - - public CryptographyProvider() - { - //FIXME: When we get DotNet Standard 2.1 we need to revisit how we do the crypto - //Currently supported hash methods from https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptoconfig?view=netcore-2.1 - //there might be a better way to autogenerate this list as dotnet updates, but I couldn't find one - //Please note the default method of PBKDF2 is not included, it cannot be used to generate hashes cleanly as it is actually a pbkdf with sha1 - _supportedHashMethods = new HashSet() + private static readonly HashSet _supportedHashMethods = new HashSet() { "MD5", "System.Security.Cryptography.MD5", @@ -42,6 +28,19 @@ namespace Emby.Server.Implementations.Cryptography "SHA-512", "System.Security.Cryptography.SHA512" }; + + public string DefaultHashMethod => "PBKDF2"; + + private RandomNumberGenerator _randomNumberGenerator; + + private const int _defaultIterations = 1000; + + public CryptographyProvider() + { + //FIXME: When we get DotNet Standard 2.1 we need to revisit how we do the crypto + //Currently supported hash methods from https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptoconfig?view=netcore-2.1 + //there might be a better way to autogenerate this list as dotnet updates, but I couldn't find one + //Please note the default method of PBKDF2 is not included, it cannot be used to generate hashes cleanly as it is actually a pbkdf with sha1 _randomNumberGenerator = RandomNumberGenerator.Create(); } -- cgit v1.2.3 From f486f5966f2fb9a3cf266ee816b8c247f0de5482 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Thu, 7 Mar 2019 09:56:03 -0800 Subject: Update Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs Co-Authored-By: LogicalPhallacy <44458166+LogicalPhallacy@users.noreply.github.com> --- Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs index 526509f43..3ec1f81d3 100644 --- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs @@ -73,7 +73,7 @@ namespace Emby.Server.Implementations.Library } else { - throw new Exception(String.Format($"Requested crypto method not available in provider: {readyHash.Id}")); + throw new Exception(string.Format($"Requested crypto method not available in provider: {readyHash.Id}")); } // var success = string.Equals(GetPasswordHash(resolvedUser), GetHashedString(resolvedUser, password), StringComparison.OrdinalIgnoreCase); -- cgit v1.2.3