|
@@ -79,6 +79,10 @@ namespace Emby.Server.Implementations.Library
|
|
|
private IAuthenticationProvider[] _authenticationProviders;
|
|
|
private DefaultAuthenticationProvider _defaultAuthenticationProvider;
|
|
|
|
|
|
+ private IPasswordResetProvider[] _passwordResetProviders;
|
|
|
+ private DefaultPasswordResetProvider _defaultPasswordResetProvider;
|
|
|
+ private Dictionary<string, IPasswordResetProvider> _activeResets = new Dictionary<string, IPasswordResetProvider>();
|
|
|
+
|
|
|
public UserManager(
|
|
|
ILoggerFactory loggerFactory,
|
|
|
IServerConfigurationManager configurationManager,
|
|
@@ -102,8 +106,6 @@ namespace Emby.Server.Implementations.Library
|
|
|
_fileSystem = fileSystem;
|
|
|
ConfigurationManager = configurationManager;
|
|
|
_users = Array.Empty<User>();
|
|
|
-
|
|
|
- DeletePinFile();
|
|
|
}
|
|
|
|
|
|
public NameIdPair[] GetAuthenticationProviders()
|
|
@@ -120,11 +122,29 @@ 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();
|
|
|
+
|
|
|
+ _passwordResetProviders = passwordResetProviders.ToArray();
|
|
|
+
|
|
|
+ _defaultPasswordResetProvider = passwordResetProviders.OfType<DefaultPasswordResetProvider>().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<bool> 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()
|
|
|
- };
|
|
|
- }
|
|
|
-
|
|
|
- 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
|
|
|
+ UsesIdForConfigurationPath = true
|
|
|
};
|
|
|
-
|
|
|
- _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);
|
|
|
+ _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<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)
|
|
|
+ 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<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);
|