diff options
Diffstat (limited to 'Emby.Server.Implementations/Library')
8 files changed, 349 insertions, 196 deletions
diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs index 3d15a8afb..fe09b07ff 100644 --- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs @@ -19,7 +19,7 @@ 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 @@ -27,7 +27,7 @@ namespace Emby.Server.Implementations.Library { 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) { @@ -103,7 +103,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; @@ -165,6 +165,34 @@ namespace Emby.Server.Implementations.Library return user.Password; } + public void ChangeEasyPassword(User user, string newPassword, string newPasswordHash) + { + ConvertPasswordFormat(user); + + if (newPassword != null) + { + newPasswordHash = string.Format("$SHA1${0}", GetHashedString(user, newPassword)); + } + + if (string.IsNullOrWhiteSpace(newPasswordHash)) + { + throw new ArgumentNullException(nameof(newPasswordHash)); + } + + user.EasyPassword = newPasswordHash; + } + + public string GetEasyPasswordHash(User user) + { + // This should be removed in the future. This was added to let user login after + // Jellyfin 10.3.3 failed to save a well formatted PIN. + ConvertPasswordFormat(user); + + return string.IsNullOrEmpty(user.EasyPassword) + ? null + : (new PasswordHash(user.EasyPassword)).Hash; + } + public string GetHashedStringChangeAuth(string newPassword, PasswordHash passwordHash) { passwordHash.HashBytes = Encoding.UTF8.GetBytes(newPassword); diff --git a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs new file mode 100644 index 000000000..e218749d9 --- /dev/null +++ b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs @@ -0,0 +1,132 @@ +using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Controller.Authentication;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Model.Cryptography;
+using MediaBrowser.Model.Serialization;
+using MediaBrowser.Model.Users;
+
+namespace Emby.Server.Implementations.Library
+{
+ public class DefaultPasswordResetProvider : IPasswordResetProvider
+ {
+ public string Name => "Default Password Reset Provider";
+
+ public bool IsEnabled => true;
+
+ private readonly string _passwordResetFileBase;
+ private readonly string _passwordResetFileBaseDir;
+ private readonly string _passwordResetFileBaseName = "passwordreset";
+
+ private readonly IJsonSerializer _jsonSerializer;
+ private readonly IUserManager _userManager;
+ private readonly ICryptoProvider _crypto;
+
+ public DefaultPasswordResetProvider(IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IUserManager userManager, ICryptoProvider cryptoProvider)
+ {
+ _passwordResetFileBaseDir = configurationManager.ApplicationPaths.ProgramDataPath;
+ _passwordResetFileBase = Path.Combine(_passwordResetFileBaseDir, _passwordResetFileBaseName);
+ _jsonSerializer = jsonSerializer;
+ _userManager = userManager;
+ _crypto = cryptoProvider;
+ }
+
+ public async Task<PinRedeemResult> RedeemPasswordResetPin(string pin)
+ {
+ SerializablePasswordReset spr;
+ HashSet<string> usersreset = new HashSet<string>();
+ foreach (var resetfile in Directory.EnumerateFiles(_passwordResetFileBaseDir, $"{_passwordResetFileBaseName}*"))
+ {
+ using (var str = File.OpenRead(resetfile))
+ {
+ spr = await _jsonSerializer.DeserializeFromStreamAsync<SerializablePasswordReset>(str).ConfigureAwait(false);
+ }
+
+ if (spr.ExpirationDate < DateTime.Now)
+ {
+ File.Delete(resetfile);
+ }
+ else if (spr.Pin.Replace("-", "").Equals(pin.Replace("-", ""), StringComparison.InvariantCultureIgnoreCase))
+ {
+ var resetUser = _userManager.GetUserByName(spr.UserName);
+ if (resetUser == null)
+ {
+ throw new Exception($"User with a username of {spr.UserName} not found");
+ }
+
+ await _userManager.ChangePassword(resetUser, pin).ConfigureAwait(false);
+ usersreset.Add(resetUser.Name);
+ File.Delete(resetfile);
+ }
+ }
+
+ if (usersreset.Count < 1)
+ {
+ throw new ResourceNotFoundException($"No Users found with a password reset request matching pin {pin}");
+ }
+ else
+ {
+ return new PinRedeemResult
+ {
+ Success = true,
+ UsersReset = usersreset.ToArray()
+ };
+ }
+ }
+
+ public async Task<ForgotPasswordResult> StartForgotPasswordProcess(MediaBrowser.Controller.Entities.User user, bool isInNetwork)
+ {
+ string pin = string.Empty;
+ using (var cryptoRandom = System.Security.Cryptography.RandomNumberGenerator.Create())
+ {
+ byte[] bytes = new byte[4];
+ cryptoRandom.GetBytes(bytes);
+ pin = BitConverter.ToString(bytes);
+ }
+
+ DateTime expireTime = DateTime.Now.AddMinutes(30);
+ string filePath = _passwordResetFileBase + user.InternalId + ".json";
+ SerializablePasswordReset spr = new SerializablePasswordReset
+ {
+ ExpirationDate = expireTime,
+ Pin = pin,
+ PinFile = filePath,
+ UserName = user.Name
+ };
+
+ try
+ {
+ using (FileStream fileStream = File.OpenWrite(filePath))
+ {
+ _jsonSerializer.SerializeToStream(spr, fileStream);
+ await fileStream.FlushAsync().ConfigureAwait(false);
+ }
+ }
+ catch (Exception e)
+ {
+ throw new Exception($"Error serializing or writing password reset for {user.Name} to location: {filePath}", e);
+ }
+
+ return new ForgotPasswordResult
+ {
+ Action = ForgotPasswordAction.PinCode,
+ PinExpirationDate = expireTime,
+ PinFile = filePath
+ };
+ }
+
+ private class SerializablePasswordReset : PasswordPinCreationResult
+ {
+ public string Pin { get; set; }
+
+ public string UserName { get; set; }
+ }
+ }
+}
diff --git a/Emby.Server.Implementations/Library/InvalidAuthProvider.cs b/Emby.Server.Implementations/Library/InvalidAuthProvider.cs new file mode 100644 index 000000000..25d233137 --- /dev/null +++ b/Emby.Server.Implementations/Library/InvalidAuthProvider.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using MediaBrowser.Controller.Authentication; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Net; + +namespace Emby.Server.Implementations.Library +{ + public class InvalidAuthProvider : IAuthenticationProvider + { + public string Name => "InvalidOrMissingAuthenticationProvider"; + + public bool IsEnabled => true; + + public Task<ProviderAuthenticationResult> Authenticate(string username, string password) + { + throw new SecurityException("User Account cannot login with this provider. The Normal provider for this user cannot be found"); + } + + public Task<bool> HasPassword(User user) + { + return Task.FromResult(true); + } + + public Task ChangePassword(User user, string newPassword) + { + return Task.CompletedTask; + } + + public void ChangeEasyPassword(User user, string newPassword, string newPasswordHash) + { + // Nothing here + } + + public string GetPasswordHash(User user) + { + return string.Empty; + } + + public string GetEasyPasswordHash(User user) + { + return string.Empty; + } + } +} diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 1673e3777..4b5063ada 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -2368,7 +2368,7 @@ namespace Emby.Server.Implementations.Library public int? GetSeasonNumberFromPath(string path) { - return new SeasonPathParser(GetNamingOptions()).Parse(path, true, true).SeasonNumber; + return new SeasonPathParser().Parse(path, true, true).SeasonNumber; } public bool FillMissingEpisodeNumbersFromPath(Episode episode, bool forceRefresh) diff --git a/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs index db270c398..8171c010b 100644 --- a/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs @@ -14,6 +14,18 @@ namespace Emby.Server.Implementations.Library.Resolvers { private readonly IImageProcessor _imageProcessor; private readonly ILibraryManager _libraryManager; + private static readonly HashSet<string> _ignoreFiles = new HashSet<string>(StringComparer.OrdinalIgnoreCase) + { + "folder", + "thumb", + "landscape", + "fanart", + "backdrop", + "poster", + "cover", + "logo", + "default" + }; public PhotoResolver(IImageProcessor imageProcessor, ILibraryManager libraryManager) { @@ -31,10 +43,10 @@ namespace Emby.Server.Implementations.Library.Resolvers if (!args.IsDirectory) { // Must be an image file within a photo collection - var collectionType = args.GetCollectionType(); + var collectionType = args.CollectionType; - if (string.Equals(collectionType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase) || - (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase) && args.GetLibraryOptions().EnablePhotos)) + if (string.Equals(collectionType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase) + || (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase) && args.GetLibraryOptions().EnablePhotos)) { if (IsImageFile(args.Path, _imageProcessor)) { @@ -74,43 +86,29 @@ namespace Emby.Server.Implementations.Library.Resolvers } internal static bool IsOwnedByResolvedMedia(ILibraryManager libraryManager, LibraryOptions libraryOptions, string file, string imageFilename) + => imageFilename.StartsWith(Path.GetFileNameWithoutExtension(file), StringComparison.OrdinalIgnoreCase); + + internal static bool IsImageFile(string path, IImageProcessor imageProcessor) { - if (imageFilename.StartsWith(Path.GetFileNameWithoutExtension(file), StringComparison.OrdinalIgnoreCase)) + if (path == null) { - return true; + throw new ArgumentNullException(nameof(path)); } - return false; - } - - private static readonly HashSet<string> IgnoreFiles = new HashSet<string>(StringComparer.OrdinalIgnoreCase) - { - "folder", - "thumb", - "landscape", - "fanart", - "backdrop", - "poster", - "cover", - "logo", - "default" - }; - - internal static bool IsImageFile(string path, IImageProcessor imageProcessor) - { - var filename = Path.GetFileNameWithoutExtension(path) ?? string.Empty; + var filename = Path.GetFileNameWithoutExtension(path); - if (IgnoreFiles.Contains(filename)) + if (_ignoreFiles.Contains(filename)) { return false; } - if (IgnoreFiles.Any(i => filename.IndexOf(i, StringComparison.OrdinalIgnoreCase) != -1)) + if (_ignoreFiles.Any(i => filename.IndexOf(i, StringComparison.OrdinalIgnoreCase) != -1)) { return false; } - return imageProcessor.SupportedInputFormats.Contains(Path.GetExtension(path).TrimStart('.'), StringComparer.Ordinal); + string extension = Path.GetExtension(path).TrimStart('.'); + return imageProcessor.SupportedInputFormats.Contains(extension, StringComparer.OrdinalIgnoreCase); } } } diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs index ce1386e91..3b9e48d97 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs @@ -52,7 +52,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV var path = args.Path; - var seasonParserResult = new SeasonPathParser(namingOptions).Parse(path, true, true); + var seasonParserResult = new SeasonPathParser().Parse(path, true, true); var season = new Season { diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs index 5c95534ec..1f873d7c6 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs @@ -194,9 +194,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV /// <returns><c>true</c> if [is season folder] [the specified path]; otherwise, <c>false</c>.</returns> private static bool IsSeasonFolder(string path, bool isTvContentType, ILibraryManager libraryManager) { - var namingOptions = ((LibraryManager)libraryManager).GetNamingOptions(); - - var seasonNumber = new SeasonPathParser(namingOptions).Parse(path, isTvContentType, isTvContentType).SeasonNumber; + var seasonNumber = new SeasonPathParser().Parse(path, isTvContentType, isTvContentType).SeasonNumber; return seasonNumber.HasValue; } diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index 4cf703add..ff375e590 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -79,6 +79,11 @@ namespace Emby.Server.Implementations.Library private IAuthenticationProvider[] _authenticationProviders; private DefaultAuthenticationProvider _defaultAuthenticationProvider; + private InvalidAuthProvider _invalidAuthProvider; + + private IPasswordResetProvider[] _passwordResetProviders; + private DefaultPasswordResetProvider _defaultPasswordResetProvider; + public UserManager( ILoggerFactory loggerFactory, IServerConfigurationManager configurationManager, @@ -102,8 +107,6 @@ namespace Emby.Server.Implementations.Library _fileSystem = fileSystem; ConfigurationManager = configurationManager; _users = Array.Empty<User>(); - - DeletePinFile(); } public NameIdPair[] GetAuthenticationProviders() @@ -120,11 +123,31 @@ namespace Emby.Server.Implementations.Library .ToArray(); } - public void AddParts(IEnumerable<IAuthenticationProvider> authenticationProviders) + public NameIdPair[] GetPasswordResetProviders() + { + return _passwordResetProviders + .Where(i => i.IsEnabled) + .OrderBy(i => i is DefaultPasswordResetProvider ? 0 : 1) + .ThenBy(i => i.Name) + .Select(i => new NameIdPair + { + Name = i.Name, + Id = GetPasswordResetProviderId(i) + }) + .ToArray(); + } + + public void AddParts(IEnumerable<IAuthenticationProvider> authenticationProviders,IEnumerable<IPasswordResetProvider> passwordResetProviders) { _authenticationProviders = authenticationProviders.ToArray(); _defaultAuthenticationProvider = _authenticationProviders.OfType<DefaultAuthenticationProvider>().First(); + + _invalidAuthProvider = _authenticationProviders.OfType<InvalidAuthProvider>().First(); + + _passwordResetProviders = passwordResetProviders.ToArray(); + + _defaultPasswordResetProvider = passwordResetProviders.OfType<DefaultPasswordResetProvider>().First(); } #region UserUpdated Event @@ -258,27 +281,37 @@ namespace Emby.Server.Implementations.Library .FirstOrDefault(i => string.Equals(username, i.Name, StringComparison.OrdinalIgnoreCase)); var success = false; + string updatedUsername = null; IAuthenticationProvider authenticationProvider = null; if (user != null) { var authResult = await AuthenticateLocalUser(username, password, hashedPassword, user, remoteEndPoint).ConfigureAwait(false); authenticationProvider = authResult.Item1; - success = authResult.Item2; + updatedUsername = authResult.Item2; + success = authResult.Item3; } else { // user is null var authResult = await AuthenticateLocalUser(username, password, hashedPassword, null, remoteEndPoint).ConfigureAwait(false); authenticationProvider = authResult.Item1; - success = authResult.Item2; + updatedUsername = authResult.Item2; + success = authResult.Item3; if (success && authenticationProvider != null && !(authenticationProvider is DefaultAuthenticationProvider)) { - user = await CreateUser(username).ConfigureAwait(false); + // We should trust the user that the authprovider says, not what was typed + if (updatedUsername != username) + { + username = updatedUsername; + } + + // Search the database for the user again; the authprovider might have created it + user = Users + .FirstOrDefault(i => string.Equals(username, i.Name, StringComparison.OrdinalIgnoreCase)); - var hasNewUserPolicy = authenticationProvider as IHasNewUserPolicy; - if (hasNewUserPolicy != null) + if (authenticationProvider is IHasNewUserPolicy hasNewUserPolicy) { var policy = hasNewUserPolicy.GetNewUserPolicy(); UpdateUserPolicy(user, policy, true); @@ -342,11 +375,21 @@ namespace Emby.Server.Implementations.Library return provider.GetType().FullName; } + private static string GetPasswordResetProviderId(IPasswordResetProvider provider) + { + return provider.GetType().FullName; + } + private IAuthenticationProvider GetAuthenticationProvider(User user) { return GetAuthenticationProviders(user).First(); } + private IPasswordResetProvider GetPasswordResetProvider(User user) + { + return GetPasswordResetProviders(user)[0]; + } + private IAuthenticationProvider[] GetAuthenticationProviders(User user) { var authenticationProviderId = user == null ? null : user.Policy.AuthenticationProviderId; @@ -360,38 +403,67 @@ namespace Emby.Server.Implementations.Library if (providers.Length == 0) { - providers = new IAuthenticationProvider[] { _defaultAuthenticationProvider }; + // Assign the user to the InvalidAuthProvider since no configured auth provider was valid/found + _logger.LogWarning("User {UserName} was found with invalid/missing Authentication Provider {AuthenticationProviderId}. Assigning user to InvalidAuthProvider until this is corrected", user.Name, user.Policy.AuthenticationProviderId); + providers = new IAuthenticationProvider[] { _invalidAuthProvider }; } return providers; } - private async Task<bool> AuthenticateWithProvider(IAuthenticationProvider provider, string username, string password, User resolvedUser) + private IPasswordResetProvider[] GetPasswordResetProviders(User user) + { + var passwordResetProviderId = user?.Policy.PasswordResetProviderId; + + var providers = _passwordResetProviders.Where(i => i.IsEnabled).ToArray(); + + if (!string.IsNullOrEmpty(passwordResetProviderId)) + { + providers = providers.Where(i => string.Equals(passwordResetProviderId, GetPasswordResetProviderId(i), StringComparison.OrdinalIgnoreCase)).ToArray(); + } + + if (providers.Length == 0) + { + providers = new IPasswordResetProvider[] { _defaultPasswordResetProvider }; + } + + return providers; + } + + private async Task<Tuple<string, bool>> AuthenticateWithProvider(IAuthenticationProvider provider, string username, string password, User resolvedUser) { try { var requiresResolvedUser = provider as IRequiresResolvedUser; + ProviderAuthenticationResult authenticationResult = null; if (requiresResolvedUser != null) { - await requiresResolvedUser.Authenticate(username, password, resolvedUser).ConfigureAwait(false); + authenticationResult = await requiresResolvedUser.Authenticate(username, password, resolvedUser).ConfigureAwait(false); } else { - await provider.Authenticate(username, password).ConfigureAwait(false); + authenticationResult = await provider.Authenticate(username, password).ConfigureAwait(false); + } + + if(authenticationResult.Username != username) + { + _logger.LogDebug("Authentication provider provided updated username {1}", authenticationResult.Username); + username = authenticationResult.Username; } - return true; + return new Tuple<string, bool>(username, true); } catch (Exception ex) { _logger.LogError(ex, "Error authenticating with provider {provider}", provider.Name); - return false; + return new Tuple<string, bool>(username, false); } } - private async Task<Tuple<IAuthenticationProvider, bool>> AuthenticateLocalUser(string username, string password, string hashedPassword, User user, string remoteEndPoint) + private async Task<Tuple<IAuthenticationProvider, string, bool>> AuthenticateLocalUser(string username, string password, string hashedPassword, User user, string remoteEndPoint) { + string updatedUsername = null; bool success = false; IAuthenticationProvider authenticationProvider = null; @@ -404,17 +476,20 @@ namespace Emby.Server.Implementations.Library if (password == null) { // legacy - success = string.Equals(_defaultAuthenticationProvider.GetPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase); + success = string.Equals(GetAuthenticationProvider(user).GetPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase); } else { foreach (var provider in GetAuthenticationProviders(user)) { - success = await AuthenticateWithProvider(provider, username, password, user).ConfigureAwait(false); + var providerAuthResult = await AuthenticateWithProvider(provider, username, password, user).ConfigureAwait(false); + updatedUsername = providerAuthResult.Item1; + success = providerAuthResult.Item2; if (success) { authenticationProvider = provider; + username = updatedUsername; break; } } @@ -427,16 +502,16 @@ namespace Emby.Server.Implementations.Library if (password == null) { // legacy - success = string.Equals(GetLocalPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase); + success = string.Equals(GetAuthenticationProvider(user).GetEasyPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase); } else { - success = string.Equals(GetLocalPasswordHash(user), _defaultAuthenticationProvider.GetHashedString(user, password), StringComparison.OrdinalIgnoreCase); + success = string.Equals(GetAuthenticationProvider(user).GetEasyPasswordHash(user), _defaultAuthenticationProvider.GetHashedString(user, password), StringComparison.OrdinalIgnoreCase); } } } - return new Tuple<IAuthenticationProvider, bool>(authenticationProvider, success); + return new Tuple<IAuthenticationProvider, string, bool>(authenticationProvider, username, success); } private void UpdateInvalidLoginAttemptCount(User user, int newValue) @@ -476,13 +551,6 @@ namespace Emby.Server.Implementations.Library } } - private string GetLocalPasswordHash(User user) - { - return string.IsNullOrEmpty(user.EasyPassword) - ? null - : user.EasyPassword; - } - /// <summary> /// Loads the users from the repository /// </summary> @@ -526,7 +594,7 @@ namespace Emby.Server.Implementations.Library } bool hasConfiguredPassword = GetAuthenticationProvider(user).HasPassword(user).Result; - bool hasConfiguredEasyPassword = string.IsNullOrEmpty(GetLocalPasswordHash(user)); + bool hasConfiguredEasyPassword = !string.IsNullOrEmpty(GetAuthenticationProvider(user).GetEasyPasswordHash(user)); bool hasPassword = user.Configuration.EnableLocalPassword && !string.IsNullOrEmpty(remoteEndPoint) && _networkManager.IsInLocalNetwork(remoteEndPoint) ? hasConfiguredEasyPassword : @@ -814,17 +882,7 @@ namespace Emby.Server.Implementations.Library throw new ArgumentNullException(nameof(user)); } - if (newPassword != null) - { - newPasswordHash = _defaultAuthenticationProvider.GetHashedString(user, newPassword); - } - - if (string.IsNullOrWhiteSpace(newPasswordHash)) - { - throw new ArgumentNullException(nameof(newPasswordHash)); - } - - user.EasyPassword = newPasswordHash; + GetAuthenticationProvider(user).ChangeEasyPassword(user, newPassword, newPasswordHash); UpdateUser(user); @@ -844,159 +902,51 @@ namespace Emby.Server.Implementations.Library Id = Guid.NewGuid(), DateCreated = DateTime.UtcNow, DateModified = DateTime.UtcNow, - UsesIdForConfigurationPath = true, - //Salt = BCrypt.GenerateSalt() + UsesIdForConfigurationPath = true }; } - private string PasswordResetFile => Path.Combine(ConfigurationManager.ApplicationPaths.ProgramDataPath, "passwordreset.txt"); - - private string _lastPin; - private PasswordPinCreationResult _lastPasswordPinCreationResult; - private int _pinAttempts; - - private async Task<PasswordPinCreationResult> 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<ForgotPasswordResult> 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) + if (user != null && isInNetwork) { - action = ForgotPasswordAction.ContactAdmin; + var passwordResetProvider = GetPasswordResetProvider(user); + return await passwordResetProvider.StartForgotPasswordProcess(user, isInNetwork).ConfigureAwait(false); } else { - if (isInNetwork) + return new ForgotPasswordResult { - action = ForgotPasswordAction.PinCode; - } - - var result = await CreatePasswordResetPin().ConfigureAwait(false); - pinFile = result.PinFile; - expirationDate = result.ExpirationDate; + Action = action, + PinFile = string.Empty + }; } - - return new ForgotPasswordResult - { - Action = action, - PinFile = pinFile, - PinExpirationDate = expirationDate - }; } public async Task<PinRedeemResult> RedeemPasswordResetPin(string pin) { - DeletePinFile(); - - var usersReset = new List<string>(); - - 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 + foreach (var provider in _passwordResetProviders) { - _pinAttempts++; - if (_pinAttempts >= 3) + var result = await provider.RedeemPasswordResetPin(pin).ConfigureAwait(false); + if (result.Success) { - _lastPin = null; - _lastPasswordPinCreationResult = null; + return result; } } return new PinRedeemResult { - Success = valid, - UsersReset = usersReset.ToArray() + Success = false, + UsersReset = Array.Empty<string>() }; } - 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); |
