| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203 | 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<ProviderAuthenticationResult> Authenticate(string username, string password)        {            throw new NotImplementedException();        }                // This is the verson that we need to use for local users. Because reasons.        public Task<ProviderAuthenticationResult> Authenticate(string username, string password, User resolvedUser)        {            bool success = false;            if (resolvedUser == null)            {                throw new Exception("Invalid username or password");            }            // 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);            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))            {                return;            }            if (!user.Password.Contains("$"))            {                string hash = user.Password;                user.Password = string.Format("$SHA1${0}", hash);            }                        if (user.EasyPassword != null && !user.EasyPassword.Contains("$"))            {                string hash = user.EasyPassword;                user.EasyPassword = string.Format("$SHA1${0}", hash);            }        }        public Task<bool> HasPassword(User user)        {            var hasConfiguredPassword = !IsPasswordEmpty(user, GetPasswordHash(user));            return Task.FromResult(hasConfiguredPassword);        }        private bool IsPasswordEmpty(User user, string password)        {            return (string.IsNullOrEmpty(user.Password) && string.IsNullOrEmpty(password));        }        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.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));        }        /// <summary>        /// Gets the hashed string.        /// </summary>        public string GetHashedString(User user, string str)        {            PasswordHash passwordHash;            if (string.IsNullOrEmpty(user.Password))            {                passwordHash = new PasswordHash(_cryptographyProvider);            }            else            {                ConvertPasswordFormat(user);                passwordHash = new PasswordHash(user.Password);            }            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)));            }        }    }}
 |