made password resets an interface and per user
This commit is contained in:
parent
c2667f99f4
commit
09921a00aa
|
@ -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>());
|
||||||
}
|
}
|
||||||
|
|
|
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue
Block a user