From 09921a00aaad31c0ea4a0650e8d0ddb890dca735 Mon Sep 17 00:00:00 2001 From: Phallacy Date: Fri, 22 Mar 2019 00:01:23 -0700 Subject: [PATCH 01/13] made password resets an interface and per user --- .../ApplicationHost.cs | 2 +- .../Library/DefaultPasswordResetProvider.cs | 118 +++++++++++ .../Library/UserManager.cs | 192 ++++++------------ MediaBrowser.Api/Session/SessionsService.cs | 11 + .../Authentication/IPasswordResetProvider.cs | 20 ++ .../Library/IUserManager.cs | 3 +- MediaBrowser.Model/Users/UserPolicy.cs | 1 + 7 files changed, 220 insertions(+), 127 deletions(-) create mode 100644 Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs create mode 100644 MediaBrowser.Controller/Authentication/IPasswordResetProvider.cs diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 484942946..fc1b2eda8 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1088,7 +1088,7 @@ namespace Emby.Server.Implementations MediaSourceManager.AddParts(GetExports()); NotificationManager.AddParts(GetExports(), GetExports()); - UserManager.AddParts(GetExports()); + UserManager.AddParts(GetExports(), GetExports()); IsoManager.AddParts(GetExports()); } diff --git a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs new file mode 100644 index 000000000..ae6fe8239 --- /dev/null +++ b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs @@ -0,0 +1,118 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Threading.Tasks; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Authentication; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Serialization; +using MediaBrowser.Model.Users; +using ServiceStack; +using TvDbSharper.Dto; + +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 IJsonSerializer _jsonSerializer; + private IUserManager _userManager; + + public DefaultPasswordResetProvider(IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IUserManager userManager) + { + _passwordResetFileBaseDir = configurationManager.ApplicationPaths.ProgramDataPath; + _passwordResetFileBase = Path.Combine(_passwordResetFileBaseDir, _passwordResetFileBaseName); + _jsonSerializer = jsonSerializer; + _userManager = userManager; + } + + public async Task RedeemPasswordResetPin(string pin) + { + HashSet usersreset = new HashSet(); + foreach (var resetfile in Directory.EnumerateFiles(_passwordResetFileBaseDir, $"{_passwordResetFileBaseName}*")) + { + var spr = (SerializablePasswordReset) _jsonSerializer.DeserializeFromFile(typeof(SerializablePasswordReset), resetfile); + if (spr.ExpirationDate > DateTime.Now) + { + File.Delete(resetfile); + } + else + { + if (spr.Pin == pin) + { + var resetUser = _userManager.GetUserByName(spr.UserName); + if (!string.IsNullOrEmpty(resetUser.Password)) + { + await _userManager.ChangePassword(resetUser, pin).ConfigureAwait(false); + usersreset.Add(resetUser.Name); + } + } + } + } + + 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() + }; + } + throw new System.NotImplementedException(); + } + + public async Task StartForgotPasswordProcess(MediaBrowser.Controller.Entities.User user, bool isInNetwork) + { + string pin = new Random().Next(99999999).ToString("00000000",CultureInfo.InvariantCulture); + DateTime expireTime = DateTime.Now.AddMinutes(30); + string filePath = _passwordResetFileBase + user.Name.ToLowerInvariant() + ".json"; + SerializablePasswordReset spr = new SerializablePasswordReset + { + ExpirationDate = expireTime, + Pin = pin, + PinFile = filePath, + UserName = user.Name + }; + + try + { + await Task.Run(() => File.WriteAllText(filePath, _jsonSerializer.SerializeToString(spr))).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/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index 4cf703add..500bb8d66 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -79,6 +79,10 @@ namespace Emby.Server.Implementations.Library private IAuthenticationProvider[] _authenticationProviders; private DefaultAuthenticationProvider _defaultAuthenticationProvider; + private IPasswordResetProvider[] _passwordResetProviders; + private DefaultPasswordResetProvider _defaultPasswordResetProvider; + private Dictionary _activeResets = new Dictionary(); + public UserManager( ILoggerFactory loggerFactory, IServerConfigurationManager configurationManager, @@ -102,8 +106,6 @@ namespace Emby.Server.Implementations.Library _fileSystem = fileSystem; ConfigurationManager = configurationManager; _users = Array.Empty(); - - DeletePinFile(); } public NameIdPair[] GetAuthenticationProviders() @@ -120,11 +122,29 @@ namespace Emby.Server.Implementations.Library .ToArray(); } - public void AddParts(IEnumerable 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 authenticationProviders,IEnumerable passwordResetProviders) { _authenticationProviders = authenticationProviders.ToArray(); _defaultAuthenticationProvider = _authenticationProviders.OfType().First(); + + _passwordResetProviders = passwordResetProviders.ToArray(); + + _defaultPasswordResetProvider = passwordResetProviders.OfType().First(); } #region UserUpdated Event @@ -342,11 +362,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).First(); + } + private IAuthenticationProvider[] GetAuthenticationProviders(User user) { var authenticationProviderId = user == null ? null : user.Policy.AuthenticationProviderId; @@ -366,6 +396,25 @@ namespace Emby.Server.Implementations.Library return providers; } + private IPasswordResetProvider[] GetPasswordResetProviders(User user) + { + var passwordResetProviderId = user == null ? null : 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 AuthenticateWithProvider(IAuthenticationProvider provider, string username, string password, User resolvedUser) { try @@ -844,159 +893,52 @@ 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 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) + if (user != null && isInNetwork) { - action = ForgotPasswordAction.ContactAdmin; + var passwordResetProvider = GetPasswordResetProvider(user); + _activeResets.Add(user.Name, passwordResetProvider); + 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 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) + foreach (var provider in _passwordResetProviders) { - _lastPin = null; - _lastPasswordPinCreationResult = null; - - foreach (var user in Users) + var result = await provider.RedeemPasswordResetPin(pin).ConfigureAwait(false); + if (result.Success) { - 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 result; } } return new PinRedeemResult { - Success = valid, - UsersReset = usersReset.ToArray() + Success = false, + UsersReset = Array.Empty() }; } - 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); diff --git a/MediaBrowser.Api/Session/SessionsService.cs b/MediaBrowser.Api/Session/SessionsService.cs index f011e6e41..4109b12bf 100644 --- a/MediaBrowser.Api/Session/SessionsService.cs +++ b/MediaBrowser.Api/Session/SessionsService.cs @@ -245,6 +245,12 @@ namespace MediaBrowser.Api.Session { } + [Route("/Auth/PasswordResetProviders", "GET")] + [Authenticated(Roles = "Admin")] + public class GetPasswordResetProviders : IReturn + { + } + [Route("/Auth/Keys/{Key}", "DELETE")] [Authenticated(Roles = "Admin")] public class RevokeKey @@ -294,6 +300,11 @@ namespace MediaBrowser.Api.Session return _userManager.GetAuthenticationProviders(); } + public object Get(GetPasswordResetProviders request) + { + return _userManager.GetPasswordResetProviders(); + } + public void Delete(RevokeKey request) { _sessionManager.RevokeToken(request.Key); diff --git a/MediaBrowser.Controller/Authentication/IPasswordResetProvider.cs b/MediaBrowser.Controller/Authentication/IPasswordResetProvider.cs new file mode 100644 index 000000000..9e5cd8816 --- /dev/null +++ b/MediaBrowser.Controller/Authentication/IPasswordResetProvider.cs @@ -0,0 +1,20 @@ +using System; +using System.Threading.Tasks; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.Users; + +namespace MediaBrowser.Controller.Authentication +{ + public interface IPasswordResetProvider + { + string Name { get; } + bool IsEnabled { get; } + Task StartForgotPasswordProcess(User user, bool isInNetwork); + Task RedeemPasswordResetPin(string pin); + } + public class PasswordPinCreationResult + { + public string PinFile { get; set; } + public DateTime ExpirationDate { get; set; } + } +} diff --git a/MediaBrowser.Controller/Library/IUserManager.cs b/MediaBrowser.Controller/Library/IUserManager.cs index 925d91a37..7f7370893 100644 --- a/MediaBrowser.Controller/Library/IUserManager.cs +++ b/MediaBrowser.Controller/Library/IUserManager.cs @@ -200,8 +200,9 @@ namespace MediaBrowser.Controller.Library /// System.String. string MakeValidUsername(string username); - void AddParts(IEnumerable authenticationProviders); + void AddParts(IEnumerable authenticationProviders, IEnumerable passwordResetProviders); NameIdPair[] GetAuthenticationProviders(); + NameIdPair[] GetPasswordResetProviders(); } } diff --git a/MediaBrowser.Model/Users/UserPolicy.cs b/MediaBrowser.Model/Users/UserPolicy.cs index 5415fd5e8..f63ab2bb4 100644 --- a/MediaBrowser.Model/Users/UserPolicy.cs +++ b/MediaBrowser.Model/Users/UserPolicy.cs @@ -75,6 +75,7 @@ namespace MediaBrowser.Model.Users public int RemoteClientBitrateLimit { get; set; } public string AuthenticationProviderId { get; set; } + public string PasswordResetProviderId { get; set; } public UserPolicy() { From 758e35baba95278fb3b55a89dc9295e6f6dad5ac Mon Sep 17 00:00:00 2001 From: Phallacy Date: Sun, 24 Mar 2019 00:30:16 -0700 Subject: [PATCH 02/13] greaterthen/lessthen reversal fix --- .../Library/DefaultPasswordResetProvider.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs index ae6fe8239..2e537c7e5 100644 --- a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs @@ -45,7 +45,7 @@ namespace Emby.Server.Implementations.Library foreach (var resetfile in Directory.EnumerateFiles(_passwordResetFileBaseDir, $"{_passwordResetFileBaseName}*")) { var spr = (SerializablePasswordReset) _jsonSerializer.DeserializeFromFile(typeof(SerializablePasswordReset), resetfile); - if (spr.ExpirationDate > DateTime.Now) + if (spr.ExpirationDate < DateTime.Now) { File.Delete(resetfile); } @@ -111,8 +111,8 @@ namespace Emby.Server.Implementations.Library private class SerializablePasswordReset : PasswordPinCreationResult { public string Pin { get; set; } - + public string UserName { get; set; } } } -} +} From 26fe4040bfc9ef5f9e723e3c9a410fb24fb8b9b1 Mon Sep 17 00:00:00 2001 From: Phallacy Date: Sun, 24 Mar 2019 11:40:00 -0700 Subject: [PATCH 03/13] fixes some usings --- .../Library/DefaultPasswordResetProvider.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs index 2e537c7e5..1ae8960ee 100644 --- a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs @@ -1,20 +1,15 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Globalization; using System.IO; +using System.Linq; using System.Threading.Tasks; -using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller; using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Users; -using ServiceStack; -using TvDbSharper.Dto; namespace Emby.Server.Implementations.Library { From 4e2841f0d747a9501d454fab7c7df5ce4ff86890 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Sun, 24 Mar 2019 11:41:03 -0700 Subject: [PATCH 04/13] Update Emby.Server.Implementations/Library/UserManager.cs Co-Authored-By: LogicalPhallacy <44458166+LogicalPhallacy@users.noreply.github.com> --- Emby.Server.Implementations/Library/UserManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index 500bb8d66..bddec70ed 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -398,7 +398,7 @@ namespace Emby.Server.Implementations.Library private IPasswordResetProvider[] GetPasswordResetProviders(User user) { - var passwordResetProviderId = user == null ? null : user.Policy.PasswordResetProviderId; + var passwordResetProviderId = user?.Policy.PasswordResetProviderId; var providers = _passwordResetProviders.Where(i => i.IsEnabled).ToArray(); From 86772bd7bdd570264565c0078ddc66964860f389 Mon Sep 17 00:00:00 2001 From: Phallacy Date: Sun, 24 Mar 2019 12:17:32 -0700 Subject: [PATCH 05/13] removes needless dictionary --- Emby.Server.Implementations/Library/UserManager.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index bddec70ed..05ec750ba 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -81,7 +81,6 @@ namespace Emby.Server.Implementations.Library private IPasswordResetProvider[] _passwordResetProviders; private DefaultPasswordResetProvider _defaultPasswordResetProvider; - private Dictionary _activeResets = new Dictionary(); public UserManager( ILoggerFactory loggerFactory, @@ -908,7 +907,6 @@ namespace Emby.Server.Implementations.Library if (user != null && isInNetwork) { var passwordResetProvider = GetPasswordResetProvider(user); - _activeResets.Add(user.Name, passwordResetProvider); return await passwordResetProvider.StartForgotPasswordProcess(user, isInNetwork).ConfigureAwait(false); } else From 740c95d557515cedd3912983f7aec50bdfefb0d4 Mon Sep 17 00:00:00 2001 From: LogicalPhallacy <44458166+LogicalPhallacy@users.noreply.github.com> Date: Mon, 25 Mar 2019 21:40:10 -0700 Subject: [PATCH 06/13] Apply minor suggestions from code review Co-Authored-By: LogicalPhallacy <44458166+LogicalPhallacy@users.noreply.github.com> --- .../Library/DefaultPasswordResetProvider.cs | 223 +++++++++--------- .../Library/UserManager.cs | 2 +- 2 files changed, 113 insertions(+), 112 deletions(-) diff --git a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs index 1ae8960ee..46f3732d6 100644 --- a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs @@ -1,113 +1,114 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller.Authentication; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Library; -using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.Users; - -namespace Emby.Server.Implementations.Library -{ - public class DefaultPasswordResetProvider : IPasswordResetProvider - { - 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 IJsonSerializer _jsonSerializer; - private IUserManager _userManager; - - public DefaultPasswordResetProvider(IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IUserManager userManager) - { - _passwordResetFileBaseDir = configurationManager.ApplicationPaths.ProgramDataPath; - _passwordResetFileBase = Path.Combine(_passwordResetFileBaseDir, _passwordResetFileBaseName); - _jsonSerializer = jsonSerializer; - _userManager = userManager; - } - - public async Task RedeemPasswordResetPin(string pin) - { - HashSet usersreset = new HashSet(); - foreach (var resetfile in Directory.EnumerateFiles(_passwordResetFileBaseDir, $"{_passwordResetFileBaseName}*")) - { - var spr = (SerializablePasswordReset) _jsonSerializer.DeserializeFromFile(typeof(SerializablePasswordReset), resetfile); - if (spr.ExpirationDate < DateTime.Now) - { - File.Delete(resetfile); - } - else - { - if (spr.Pin == pin) - { - var resetUser = _userManager.GetUserByName(spr.UserName); - if (!string.IsNullOrEmpty(resetUser.Password)) - { - await _userManager.ChangePassword(resetUser, pin).ConfigureAwait(false); - usersreset.Add(resetUser.Name); - } - } - } - } - - 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() - }; - } - throw new System.NotImplementedException(); - } - - public async Task StartForgotPasswordProcess(MediaBrowser.Controller.Entities.User user, bool isInNetwork) - { - string pin = new Random().Next(99999999).ToString("00000000",CultureInfo.InvariantCulture); - DateTime expireTime = DateTime.Now.AddMinutes(30); - string filePath = _passwordResetFileBase + user.Name.ToLowerInvariant() + ".json"; - SerializablePasswordReset spr = new SerializablePasswordReset - { - ExpirationDate = expireTime, - Pin = pin, - PinFile = filePath, - UserName = user.Name - }; - - try - { - await Task.Run(() => File.WriteAllText(filePath, _jsonSerializer.SerializeToString(spr))).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; } +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.Authentication; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Serialization; +using MediaBrowser.Model.Users; - public string UserName { get; set; } - } - } +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 IJsonSerializer _jsonSerializer; + private IUserManager _userManager; + + public DefaultPasswordResetProvider(IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IUserManager userManager) + { + _passwordResetFileBaseDir = configurationManager.ApplicationPaths.ProgramDataPath; + _passwordResetFileBase = Path.Combine(_passwordResetFileBaseDir, _passwordResetFileBaseName); + _jsonSerializer = jsonSerializer; + _userManager = userManager; + } + + public async Task RedeemPasswordResetPin(string pin) + { + HashSet usersreset = new HashSet(); + foreach (var resetfile in Directory.EnumerateFiles(_passwordResetFileBaseDir, $"{_passwordResetFileBaseName}*")) + { + var spr = (SerializablePasswordReset) _jsonSerializer.DeserializeFromFile(typeof(SerializablePasswordReset), resetfile); + if (spr.ExpirationDate < DateTime.Now) + { + File.Delete(resetfile); + } + else + { + if (spr.Pin == pin) + { + var resetUser = _userManager.GetUserByName(spr.UserName); + if (!string.IsNullOrEmpty(resetUser.Password)) + { + await _userManager.ChangePassword(resetUser, pin).ConfigureAwait(false); + usersreset.Add(resetUser.Name); + } + } + } + } + + 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() + }; + } + + throw new System.NotImplementedException(); + } + + public async Task StartForgotPasswordProcess(MediaBrowser.Controller.Entities.User user, bool isInNetwork) + { + string pin = new Random().Next(99999999).ToString("00000000",CultureInfo.InvariantCulture); + DateTime expireTime = DateTime.Now.AddMinutes(30); + string filePath = _passwordResetFileBase + user.Name.ToLowerInvariant() + ".json"; + SerializablePasswordReset spr = new SerializablePasswordReset + { + ExpirationDate = expireTime, + Pin = pin, + PinFile = filePath, + UserName = user.Name + }; + + try + { + await Task.Run(() => File.WriteAllText(filePath, _jsonSerializer.SerializeToString(spr))).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/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index 05ec750ba..75c82ca71 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -373,7 +373,7 @@ namespace Emby.Server.Implementations.Library private IPasswordResetProvider GetPasswordResetProvider(User user) { - return GetPasswordResetProviders(user).First(); + return GetPasswordResetProviders(user)[0]; } private IAuthenticationProvider[] GetAuthenticationProviders(User user) From 6be8624373bba6cf25a659390874613a4ea6ba79 Mon Sep 17 00:00:00 2001 From: Phallacy Date: Mon, 25 Mar 2019 22:17:23 -0700 Subject: [PATCH 07/13] async improvements and post reset cleanups --- .../Library/DefaultPasswordResetProvider.cs | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs index 46f3732d6..a589d6168 100644 --- a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs @@ -10,6 +10,7 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Users; +using Microsoft.Win32.SafeHandles; namespace Emby.Server.Implementations.Library { @@ -39,21 +40,19 @@ namespace Emby.Server.Implementations.Library HashSet usersreset = new HashSet(); foreach (var resetfile in Directory.EnumerateFiles(_passwordResetFileBaseDir, $"{_passwordResetFileBaseName}*")) { - var spr = (SerializablePasswordReset) _jsonSerializer.DeserializeFromFile(typeof(SerializablePasswordReset), resetfile); + var spr = await _jsonSerializer.DeserializeFromStreamAsync(File.OpenRead(resetfile)).ConfigureAwait(false); if (spr.ExpirationDate < DateTime.Now) { File.Delete(resetfile); } - else + else if (spr.Pin == pin) { - if (spr.Pin == pin) + var resetUser = _userManager.GetUserByName(spr.UserName); + if (resetUser != null) { - var resetUser = _userManager.GetUserByName(spr.UserName); - if (!string.IsNullOrEmpty(resetUser.Password)) - { - await _userManager.ChangePassword(resetUser, pin).ConfigureAwait(false); - usersreset.Add(resetUser.Name); - } + await _userManager.ChangePassword(resetUser, pin).ConfigureAwait(false); + usersreset.Add(resetUser.Name); + File.Delete(resetfile); } } } @@ -70,15 +69,13 @@ namespace Emby.Server.Implementations.Library UsersReset = usersreset.ToArray() }; } - - throw new System.NotImplementedException(); } public async Task StartForgotPasswordProcess(MediaBrowser.Controller.Entities.User user, bool isInNetwork) { string pin = new Random().Next(99999999).ToString("00000000",CultureInfo.InvariantCulture); DateTime expireTime = DateTime.Now.AddMinutes(30); - string filePath = _passwordResetFileBase + user.Name.ToLowerInvariant() + ".json"; + string filePath = _passwordResetFileBase + user.InternalId + ".json"; SerializablePasswordReset spr = new SerializablePasswordReset { ExpirationDate = expireTime, @@ -88,8 +85,10 @@ namespace Emby.Server.Implementations.Library }; try - { - await Task.Run(() => File.WriteAllText(filePath, _jsonSerializer.SerializeToString(spr))).ConfigureAwait(false); + { + FileStream fileStream = File.OpenWrite(filePath); + _jsonSerializer.SerializeToStream(spr,fileStream); + await fileStream.FlushAsync().ConfigureAwait(false); } catch (Exception e) { From b07c146fd96d9ed7676adffda0333ec85f0c05b6 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Wed, 27 Mar 2019 16:17:18 -0700 Subject: [PATCH 08/13] Update Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs Co-Authored-By: LogicalPhallacy <44458166+LogicalPhallacy@users.noreply.github.com> --- .../Library/DefaultPasswordResetProvider.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs index a589d6168..da6596743 100644 --- a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs @@ -40,7 +40,10 @@ namespace Emby.Server.Implementations.Library HashSet usersreset = new HashSet(); foreach (var resetfile in Directory.EnumerateFiles(_passwordResetFileBaseDir, $"{_passwordResetFileBaseName}*")) { - var spr = await _jsonSerializer.DeserializeFromStreamAsync(File.OpenRead(resetfile)).ConfigureAwait(false); + using (var str = File.OpenRead(resetfile)) + { + var spr = await _jsonSerializer.DeserializeFromStreamAsync(str).ConfigureAwait(false); + } if (spr.ExpirationDate < DateTime.Now) { File.Delete(resetfile); @@ -51,7 +54,7 @@ namespace Emby.Server.Implementations.Library if (resetUser != null) { await _userManager.ChangePassword(resetUser, pin).ConfigureAwait(false); - usersreset.Add(resetUser.Name); + usersreset.Add(resetUser.Name); File.Delete(resetfile); } } @@ -85,8 +88,8 @@ namespace Emby.Server.Implementations.Library }; try - { - FileStream fileStream = File.OpenWrite(filePath); + { + FileStream fileStream = File.OpenWrite(filePath); _jsonSerializer.SerializeToStream(spr,fileStream); await fileStream.FlushAsync().ConfigureAwait(false); } From 5e8496bc593399f062169c90b1820c1b8b75a73e Mon Sep 17 00:00:00 2001 From: Phallacy Date: Wed, 27 Mar 2019 22:46:25 -0700 Subject: [PATCH 09/13] minor fixes and usings --- .../Library/DefaultPasswordResetProvider.cs | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs index da6596743..63ebc7c72 100644 --- a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs @@ -10,7 +10,6 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Users; -using Microsoft.Win32.SafeHandles; namespace Emby.Server.Implementations.Library { @@ -37,13 +36,15 @@ namespace Emby.Server.Implementations.Library public async Task RedeemPasswordResetPin(string pin) { + SerializablePasswordReset spr; HashSet usersreset = new HashSet(); foreach (var resetfile in Directory.EnumerateFiles(_passwordResetFileBaseDir, $"{_passwordResetFileBaseName}*")) { using (var str = File.OpenRead(resetfile)) { - var spr = await _jsonSerializer.DeserializeFromStreamAsync(str).ConfigureAwait(false); - } + spr = await _jsonSerializer.DeserializeFromStreamAsync(str).ConfigureAwait(false); + } + if (spr.ExpirationDate < DateTime.Now) { File.Delete(resetfile); @@ -51,12 +52,14 @@ namespace Emby.Server.Implementations.Library else if (spr.Pin == pin) { var resetUser = _userManager.GetUserByName(spr.UserName); - if (resetUser != null) + if (resetUser == null) { - await _userManager.ChangePassword(resetUser, pin).ConfigureAwait(false); - usersreset.Add(resetUser.Name); - File.Delete(resetfile); + 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); } } @@ -76,7 +79,7 @@ namespace Emby.Server.Implementations.Library public async Task StartForgotPasswordProcess(MediaBrowser.Controller.Entities.User user, bool isInNetwork) { - string pin = new Random().Next(99999999).ToString("00000000",CultureInfo.InvariantCulture); + string pin = new Random().Next(99999999).ToString("00000000", CultureInfo.InvariantCulture); DateTime expireTime = DateTime.Now.AddMinutes(30); string filePath = _passwordResetFileBase + user.InternalId + ".json"; SerializablePasswordReset spr = new SerializablePasswordReset @@ -89,9 +92,11 @@ namespace Emby.Server.Implementations.Library try { - FileStream fileStream = File.OpenWrite(filePath); - _jsonSerializer.SerializeToStream(spr,fileStream); - await fileStream.FlushAsync().ConfigureAwait(false); + using (FileStream fileStream = File.OpenWrite(filePath)) + { + _jsonSerializer.SerializeToStream(spr, fileStream); + await fileStream.FlushAsync().ConfigureAwait(false); + } } catch (Exception e) { From 48b50a22a43dde00c795fb01521fcd731c323de7 Mon Sep 17 00:00:00 2001 From: Phallacy Date: Thu, 28 Mar 2019 08:15:53 -0700 Subject: [PATCH 10/13] switched to a hexa string with crypto random backing --- .../Library/DefaultPasswordResetProvider.cs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs index 63ebc7c72..b726fa2d0 100644 --- a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs @@ -8,6 +8,7 @@ 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; @@ -25,13 +26,15 @@ namespace Emby.Server.Implementations.Library private IJsonSerializer _jsonSerializer; private IUserManager _userManager; + private ICryptoProvider _crypto; - public DefaultPasswordResetProvider(IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IUserManager userManager) + 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 RedeemPasswordResetPin(string pin) @@ -49,7 +52,7 @@ namespace Emby.Server.Implementations.Library { File.Delete(resetfile); } - else if (spr.Pin == pin) + else if (spr.Pin.Equals(pin, StringComparison.InvariantCultureIgnoreCase)) { var resetUser = _userManager.GetUserByName(spr.UserName); if (resetUser == null) @@ -79,7 +82,14 @@ namespace Emby.Server.Implementations.Library public async Task StartForgotPasswordProcess(MediaBrowser.Controller.Entities.User user, bool isInNetwork) { - string pin = new Random().Next(99999999).ToString("00000000", CultureInfo.InvariantCulture); + string pin = string.Empty; + using (var cryptoRandom = System.Security.Cryptography.RandomNumberGenerator.Create()) + { + byte[] bytes = new byte[4]; + cryptoRandom.GetBytes(bytes); + pin = bytes.ToString(); + } + DateTime expireTime = DateTime.Now.AddMinutes(30); string filePath = _passwordResetFileBase + user.InternalId + ".json"; SerializablePasswordReset spr = new SerializablePasswordReset From b56031b9f3ccfd4a8ac0413657f45645fe2e0f1e Mon Sep 17 00:00:00 2001 From: Phallacy Date: Thu, 28 Mar 2019 20:49:11 -0700 Subject: [PATCH 11/13] fix byte string --- .../Library/DefaultPasswordResetProvider.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs index b726fa2d0..56540cc08 100644 --- a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs @@ -3,6 +3,7 @@ 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; @@ -87,7 +88,7 @@ namespace Emby.Server.Implementations.Library { byte[] bytes = new byte[4]; cryptoRandom.GetBytes(bytes); - pin = bytes.ToString(); + pin = BitConverter.ToString(bytes); } DateTime expireTime = DateTime.Now.AddMinutes(30); From 2d396cb589722bf8a950f80abb6d6137fe084a52 Mon Sep 17 00:00:00 2001 From: Phallacy Date: Fri, 29 Mar 2019 07:10:49 -0700 Subject: [PATCH 12/13] adds readonly to properties --- .../Library/DefaultPasswordResetProvider.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs index 56540cc08..256399d2f 100644 --- a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs @@ -25,9 +25,9 @@ namespace Emby.Server.Implementations.Library private readonly string _passwordResetFileBaseDir; private readonly string _passwordResetFileBaseName = "passwordreset"; - private IJsonSerializer _jsonSerializer; - private IUserManager _userManager; - private ICryptoProvider _crypto; + private readonly IJsonSerializer _jsonSerializer; + private readonly IUserManager _userManager; + private readonly ICryptoProvider _crypto; public DefaultPasswordResetProvider(IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IUserManager userManager, ICryptoProvider cryptoProvider) { From 13e94a8b1b78d570a528eee65ff777412f0e83c8 Mon Sep 17 00:00:00 2001 From: LogicalPhallacy <44458166+LogicalPhallacy@users.noreply.github.com> Date: Fri, 29 Mar 2019 12:48:07 -0700 Subject: [PATCH 13/13] Remove dashes from pins --- .../Library/DefaultPasswordResetProvider.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs index 256399d2f..c6d475520 100644 --- a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs @@ -47,13 +47,13 @@ namespace Emby.Server.Implementations.Library using (var str = File.OpenRead(resetfile)) { spr = await _jsonSerializer.DeserializeFromStreamAsync(str).ConfigureAwait(false); - } + } if (spr.ExpirationDate < DateTime.Now) { File.Delete(resetfile); } - else if (spr.Pin.Equals(pin, StringComparison.InvariantCultureIgnoreCase)) + else if (spr.Pin.Replace('-', '').Equals(pin.Replace('-', ''), StringComparison.InvariantCultureIgnoreCase)) { var resetUser = _userManager.GetUserByName(spr.UserName); if (resetUser == null) @@ -85,11 +85,11 @@ namespace Emby.Server.Implementations.Library { 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";