made password resets an interface and per user

This commit is contained in:
Phallacy 2019-03-22 00:01:23 -07:00
parent c2667f99f4
commit 09921a00aa
7 changed files with 220 additions and 127 deletions

View File

@ -1088,7 +1088,7 @@ namespace Emby.Server.Implementations
MediaSourceManager.AddParts(GetExports<IMediaSourceProvider>()); MediaSourceManager.AddParts(GetExports<IMediaSourceProvider>());
NotificationManager.AddParts(GetExports<INotificationService>(), GetExports<INotificationTypeFactory>()); NotificationManager.AddParts(GetExports<INotificationService>(), GetExports<INotificationTypeFactory>());
UserManager.AddParts(GetExports<IAuthenticationProvider>()); UserManager.AddParts(GetExports<IAuthenticationProvider>(), GetExports<IPasswordResetProvider>());
IsoManager.AddParts(GetExports<IIsoMounter>()); IsoManager.AddParts(GetExports<IIsoMounter>());
} }

View File

@ -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<PinRedeemResult> RedeemPasswordResetPin(string pin)
{
HashSet<string> usersreset = new HashSet<string>();
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<ForgotPasswordResult> 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; }
}
}
}

View File

@ -79,6 +79,10 @@ namespace Emby.Server.Implementations.Library
private IAuthenticationProvider[] _authenticationProviders; private IAuthenticationProvider[] _authenticationProviders;
private DefaultAuthenticationProvider _defaultAuthenticationProvider; private DefaultAuthenticationProvider _defaultAuthenticationProvider;
private IPasswordResetProvider[] _passwordResetProviders;
private DefaultPasswordResetProvider _defaultPasswordResetProvider;
private Dictionary<string, IPasswordResetProvider> _activeResets = new Dictionary<string, IPasswordResetProvider>();
public UserManager( public UserManager(
ILoggerFactory loggerFactory, ILoggerFactory loggerFactory,
IServerConfigurationManager configurationManager, IServerConfigurationManager configurationManager,
@ -102,8 +106,6 @@ namespace Emby.Server.Implementations.Library
_fileSystem = fileSystem; _fileSystem = fileSystem;
ConfigurationManager = configurationManager; ConfigurationManager = configurationManager;
_users = Array.Empty<User>(); _users = Array.Empty<User>();
DeletePinFile();
} }
public NameIdPair[] GetAuthenticationProviders() public NameIdPair[] GetAuthenticationProviders()
@ -120,11 +122,29 @@ namespace Emby.Server.Implementations.Library
.ToArray(); .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(); _authenticationProviders = authenticationProviders.ToArray();
_defaultAuthenticationProvider = _authenticationProviders.OfType<DefaultAuthenticationProvider>().First(); _defaultAuthenticationProvider = _authenticationProviders.OfType<DefaultAuthenticationProvider>().First();
_passwordResetProviders = passwordResetProviders.ToArray();
_defaultPasswordResetProvider = passwordResetProviders.OfType<DefaultPasswordResetProvider>().First();
} }
#region UserUpdated Event #region UserUpdated Event
@ -342,11 +362,21 @@ namespace Emby.Server.Implementations.Library
return provider.GetType().FullName; return provider.GetType().FullName;
} }
private static string GetPasswordResetProviderId(IPasswordResetProvider provider)
{
return provider.GetType().FullName;
}
private IAuthenticationProvider GetAuthenticationProvider(User user) private IAuthenticationProvider GetAuthenticationProvider(User user)
{ {
return GetAuthenticationProviders(user).First(); return GetAuthenticationProviders(user).First();
} }
private IPasswordResetProvider GetPasswordResetProvider(User user)
{
return GetPasswordResetProviders(user).First();
}
private IAuthenticationProvider[] GetAuthenticationProviders(User user) private IAuthenticationProvider[] GetAuthenticationProviders(User user)
{ {
var authenticationProviderId = user == null ? null : user.Policy.AuthenticationProviderId; var authenticationProviderId = user == null ? null : user.Policy.AuthenticationProviderId;
@ -366,6 +396,25 @@ namespace Emby.Server.Implementations.Library
return providers; 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<bool> AuthenticateWithProvider(IAuthenticationProvider provider, string username, string password, User resolvedUser) private async Task<bool> AuthenticateWithProvider(IAuthenticationProvider provider, string username, string password, User resolvedUser)
{ {
try try
@ -844,159 +893,52 @@ namespace Emby.Server.Implementations.Library
Id = Guid.NewGuid(), Id = Guid.NewGuid(),
DateCreated = DateTime.UtcNow, DateCreated = DateTime.UtcNow,
DateModified = DateTime.UtcNow, DateModified = DateTime.UtcNow,
UsesIdForConfigurationPath = true, 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<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) public async Task<ForgotPasswordResult> StartForgotPasswordProcess(string enteredUsername, bool isInNetwork)
{ {
DeletePinFile();
var user = string.IsNullOrWhiteSpace(enteredUsername) ? var user = string.IsNullOrWhiteSpace(enteredUsername) ?
null : null :
GetUserByName(enteredUsername); GetUserByName(enteredUsername);
var action = ForgotPasswordAction.InNetworkRequired; 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 else
{ {
if (isInNetwork)
{
action = ForgotPasswordAction.PinCode;
}
var result = await CreatePasswordResetPin().ConfigureAwait(false);
pinFile = result.PinFile;
expirationDate = result.ExpirationDate;
}
return new ForgotPasswordResult return new ForgotPasswordResult
{ {
Action = action, Action = action,
PinFile = pinFile, PinFile = string.Empty
PinExpirationDate = expirationDate
}; };
} }
}
public async Task<PinRedeemResult> RedeemPasswordResetPin(string pin) public async Task<PinRedeemResult> RedeemPasswordResetPin(string pin)
{ {
DeletePinFile(); foreach (var provider in _passwordResetProviders)
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; var result = await provider.RedeemPasswordResetPin(pin).ConfigureAwait(false);
_lastPasswordPinCreationResult = null; if (result.Success)
foreach (var user in Users)
{ {
await ResetPassword(user).ConfigureAwait(false); return result;
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 return new PinRedeemResult
{ {
Success = valid, Success = false,
UsersReset = usersReset.ToArray() 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) public UserPolicy GetUserPolicy(User user)
{ {
var path = GetPolicyFilePath(user); var path = GetPolicyFilePath(user);

View File

@ -245,6 +245,12 @@ namespace MediaBrowser.Api.Session
{ {
} }
[Route("/Auth/PasswordResetProviders", "GET")]
[Authenticated(Roles = "Admin")]
public class GetPasswordResetProviders : IReturn<NameIdPair[]>
{
}
[Route("/Auth/Keys/{Key}", "DELETE")] [Route("/Auth/Keys/{Key}", "DELETE")]
[Authenticated(Roles = "Admin")] [Authenticated(Roles = "Admin")]
public class RevokeKey public class RevokeKey
@ -294,6 +300,11 @@ namespace MediaBrowser.Api.Session
return _userManager.GetAuthenticationProviders(); return _userManager.GetAuthenticationProviders();
} }
public object Get(GetPasswordResetProviders request)
{
return _userManager.GetPasswordResetProviders();
}
public void Delete(RevokeKey request) public void Delete(RevokeKey request)
{ {
_sessionManager.RevokeToken(request.Key); _sessionManager.RevokeToken(request.Key);

View File

@ -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<ForgotPasswordResult> StartForgotPasswordProcess(User user, bool isInNetwork);
Task<PinRedeemResult> RedeemPasswordResetPin(string pin);
}
public class PasswordPinCreationResult
{
public string PinFile { get; set; }
public DateTime ExpirationDate { get; set; }
}
}

View File

@ -200,8 +200,9 @@ namespace MediaBrowser.Controller.Library
/// <returns>System.String.</returns> /// <returns>System.String.</returns>
string MakeValidUsername(string username); string MakeValidUsername(string username);
void AddParts(IEnumerable<IAuthenticationProvider> authenticationProviders); void AddParts(IEnumerable<IAuthenticationProvider> authenticationProviders, IEnumerable<IPasswordResetProvider> passwordResetProviders);
NameIdPair[] GetAuthenticationProviders(); NameIdPair[] GetAuthenticationProviders();
NameIdPair[] GetPasswordResetProviders();
} }
} }

View File

@ -75,6 +75,7 @@ namespace MediaBrowser.Model.Users
public int RemoteClientBitrateLimit { get; set; } public int RemoteClientBitrateLimit { get; set; }
public string AuthenticationProviderId { get; set; } public string AuthenticationProviderId { get; set; }
public string PasswordResetProviderId { get; set; }
public UserPolicy() public UserPolicy()
{ {