diff options
Diffstat (limited to 'Emby.Server.Implementations/Library/UserManager.cs')
| -rw-r--r-- | Emby.Server.Implementations/Library/UserManager.cs | 369 |
1 files changed, 270 insertions, 99 deletions
diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index 71c953b2c..b13a255aa 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -28,6 +28,11 @@ using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.Cryptography; using MediaBrowser.Model.IO; +using MediaBrowser.Controller.Authentication; +using MediaBrowser.Controller.Security; +using MediaBrowser.Controller.Devices; +using MediaBrowser.Controller.Session; +using MediaBrowser.Controller.Plugins; namespace Emby.Server.Implementations.Library { @@ -40,7 +45,9 @@ namespace Emby.Server.Implementations.Library /// Gets the users. /// </summary> /// <value>The users.</value> - public IEnumerable<User> Users { get; private set; } + public IEnumerable<User> Users { get { return _users; } } + + private User[] _users; /// <summary> /// The _logger @@ -72,6 +79,9 @@ namespace Emby.Server.Implementations.Library private readonly IFileSystem _fileSystem; private readonly ICryptoProvider _cryptographyProvider; + private IAuthenticationProvider[] _authenticationProviders; + private DefaultAuthenticationProvider _defaultAuthenticationProvider; + public UserManager(ILogger logger, IServerConfigurationManager configurationManager, IUserRepository userRepository, IXmlSerializer xmlSerializer, INetworkManager networkManager, Func<IImageProcessor> imageProcessorFactory, Func<IDtoService> dtoServiceFactory, Func<IConnectManager> connectFactory, IServerApplicationHost appHost, IJsonSerializer jsonSerializer, IFileSystem fileSystem, ICryptoProvider cryptographyProvider) { _logger = logger; @@ -86,16 +96,38 @@ namespace Emby.Server.Implementations.Library _fileSystem = fileSystem; _cryptographyProvider = cryptographyProvider; ConfigurationManager = configurationManager; - Users = new List<User>(); + _users = Array.Empty<User>(); DeletePinFile(); } + public NameIdPair[] GetAuthenticationProviders() + { + return _authenticationProviders + .Where(i => i.IsEnabled) + .OrderBy(i => i is DefaultAuthenticationProvider ? 0 : 1) + .ThenBy(i => i.Name) + .Select(i => new NameIdPair + { + Name = i.Name, + Id = GetAuthenticationProviderId(i) + }) + .ToArray(); + } + + public void AddParts(IEnumerable<IAuthenticationProvider> authenticationProviders) + { + _authenticationProviders = authenticationProviders.ToArray(); + + _defaultAuthenticationProvider = _authenticationProviders.OfType<DefaultAuthenticationProvider>().First(); + } + #region UserUpdated Event /// <summary> /// Occurs when [user updated]. /// </summary> public event EventHandler<GenericEventArgs<User>> UserUpdated; + public event EventHandler<GenericEventArgs<User>> UserPolicyUpdated; public event EventHandler<GenericEventArgs<User>> UserConfigurationUpdated; public event EventHandler<GenericEventArgs<User>> UserLockedOut; @@ -132,7 +164,7 @@ namespace Emby.Server.Implementations.Library /// <exception cref="System.ArgumentNullException"></exception> public User GetUserById(Guid id) { - if (id == Guid.Empty) + if (id.Equals(Guid.Empty)) { throw new ArgumentNullException("id"); } @@ -162,7 +194,7 @@ namespace Emby.Server.Implementations.Library public void Initialize() { - Users = LoadUsers(); + _users = LoadUsers(); var users = Users.ToList(); @@ -218,7 +250,7 @@ namespace Emby.Server.Implementations.Library return builder.ToString(); } - public async Task<User> AuthenticateUser(string username, string password, string hashedPassword, string passwordMd5, string remoteEndPoint, bool isUserSession) + public async Task<User> AuthenticateUser(string username, string password, string hashedPassword, string remoteEndPoint, bool isUserSession) { if (string.IsNullOrWhiteSpace(username)) { @@ -229,18 +261,16 @@ namespace Emby.Server.Implementations.Library .FirstOrDefault(i => string.Equals(username, i.Name, StringComparison.OrdinalIgnoreCase)); var success = false; + IAuthenticationProvider authenticationProvider = null; if (user != null) { - if (password != null) - { - hashedPassword = GetHashedString(user, password); - } - // Authenticate using local credentials if not a guest if (!user.ConnectLinkType.HasValue || user.ConnectLinkType.Value != UserLinkType.Guest) { - success = AuthenticateLocalUser(user, password, hashedPassword, remoteEndPoint); + var authResult = await AuthenticateLocalUser(username, password, hashedPassword, user, remoteEndPoint).ConfigureAwait(false); + authenticationProvider = authResult.Item1; + success = authResult.Item2; } // Maybe user accidently entered connect credentials. let's be flexible @@ -248,7 +278,7 @@ namespace Emby.Server.Implementations.Library { try { - await _connectFactory().Authenticate(user.ConnectUserName, password, passwordMd5).ConfigureAwait(false); + await _connectFactory().Authenticate(user.ConnectUserName, password).ConfigureAwait(false); success = true; } catch @@ -257,13 +287,43 @@ namespace Emby.Server.Implementations.Library } } } + else + { + // user is null + var authResult = await AuthenticateLocalUser(username, password, hashedPassword, null, remoteEndPoint).ConfigureAwait(false); + authenticationProvider = authResult.Item1; + success = authResult.Item2; + + if (success && authenticationProvider != null && !(authenticationProvider is DefaultAuthenticationProvider)) + { + user = await CreateUser(username).ConfigureAwait(false); + + var hasNewUserPolicy = authenticationProvider as IHasNewUserPolicy; + if (hasNewUserPolicy != null) + { + var policy = hasNewUserPolicy.GetNewUserPolicy(); + UpdateUserPolicy(user, policy, true); + } + } + } + + if (success && user != null && authenticationProvider != null) + { + var providerId = GetAuthenticationProviderId(authenticationProvider); + + if (!string.Equals(providerId, user.Policy.AuthenticationProviderId, StringComparison.OrdinalIgnoreCase)) + { + user.Policy.AuthenticationProviderId = providerId; + UpdateUserPolicy(user, user.Policy, true); + } + } // Try originally entered username if (!success && (user == null || !string.Equals(user.ConnectUserName, username, StringComparison.OrdinalIgnoreCase))) { try { - var connectAuthResult = await _connectFactory().Authenticate(username, password, passwordMd5).ConfigureAwait(false); + var connectAuthResult = await _connectFactory().Authenticate(username, password).ConfigureAwait(false); user = Users.FirstOrDefault(i => string.Equals(i.ConnectUserId, connectAuthResult.User.Id, StringComparison.OrdinalIgnoreCase)); @@ -285,6 +345,19 @@ namespace Emby.Server.Implementations.Library throw new SecurityException(string.Format("The {0} account is currently disabled. Please consult with your administrator.", user.Name)); } + if (user != null) + { + if (!user.Policy.EnableRemoteAccess && !_networkManager.IsInLocalNetwork(remoteEndPoint)) + { + throw new SecurityException("Forbidden."); + } + + if (!user.IsParentalScheduleAllowed()) + { + throw new SecurityException("User is not allowed access at this time."); + } + } + // Update LastActivityDate and LastLoginDate, then save if (success) { @@ -305,34 +378,106 @@ namespace Emby.Server.Implementations.Library return success ? user : null; } - private bool AuthenticateLocalUser(User user, string password, string hashedPassword, string remoteEndPoint) + private string GetAuthenticationProviderId(IAuthenticationProvider provider) { - bool success; + return provider.GetType().FullName; + } - if (password == null) + private IAuthenticationProvider GetAuthenticationProvider(User user) + { + return GetAuthenticationProviders(user).First(); + } + + private IAuthenticationProvider[] GetAuthenticationProviders(User user) + { + var authenticationProviderId = user == null ? null : user.Policy.AuthenticationProviderId; + + var providers = _authenticationProviders.Where(i => i.IsEnabled).ToArray(); + + if (!string.IsNullOrEmpty(authenticationProviderId)) { - // legacy - success = string.Equals(GetPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase); + providers = providers.Where(i => string.Equals(authenticationProviderId, GetAuthenticationProviderId(i), StringComparison.OrdinalIgnoreCase)).ToArray(); } - else + + if (providers.Length == 0) { - success = string.Equals(GetPasswordHash(user), GetHashedString(user, password), StringComparison.OrdinalIgnoreCase); + providers = new IAuthenticationProvider[] { _defaultAuthenticationProvider }; } - if (!success && _networkManager.IsInLocalNetwork(remoteEndPoint) && user.Configuration.EnableLocalPassword) + return providers; + } + + private async Task<bool> AuthenticateWithProvider(IAuthenticationProvider provider, string username, string password, User resolvedUser) + { + try { - if (password == null) + var requiresResolvedUser = provider as IRequiresResolvedUser; + if (requiresResolvedUser != null) { - // legacy - success = string.Equals(GetLocalPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase); + await requiresResolvedUser.Authenticate(username, password, resolvedUser).ConfigureAwait(false); } else { - success = string.Equals(GetLocalPasswordHash(user), GetHashedString(user, password), StringComparison.OrdinalIgnoreCase); + await provider.Authenticate(username, password).ConfigureAwait(false); + } + + return true; + } + catch (Exception ex) + { + _logger.ErrorException("Error authenticating with provider {0}", ex, provider.Name); + + return false; + } + } + + private async Task<Tuple<IAuthenticationProvider, bool>> AuthenticateLocalUser(string username, string password, string hashedPassword, User user, string remoteEndPoint) + { + bool success = false; + IAuthenticationProvider authenticationProvider = null; + + if (password != null && user != null) + { + // Doesn't look like this is even possible to be used, because of password == null checks below + hashedPassword = _defaultAuthenticationProvider.GetHashedString(user, password); + } + + if (password == null) + { + // legacy + success = string.Equals(_defaultAuthenticationProvider.GetPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase); + } + else + { + foreach (var provider in GetAuthenticationProviders(user)) + { + success = await AuthenticateWithProvider(provider, username, password, user).ConfigureAwait(false); + + if (success) + { + authenticationProvider = provider; + break; + } } } - return success; + if (user != null) + { + if (!success && _networkManager.IsInLocalNetwork(remoteEndPoint) && user.Configuration.EnableLocalPassword) + { + if (password == null) + { + // legacy + success = string.Equals(GetLocalPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase); + } + else + { + success = string.Equals(GetLocalPasswordHash(user), _defaultAuthenticationProvider.GetHashedString(user, password), StringComparison.OrdinalIgnoreCase); + } + } + } + + return new Tuple<IAuthenticationProvider, bool>(authenticationProvider, success); } private void UpdateInvalidLoginAttemptCount(User user, int newValue) @@ -367,63 +512,41 @@ namespace Emby.Server.Implementations.Library } } - private string GetPasswordHash(User user) - { - return string.IsNullOrEmpty(user.Password) - ? GetEmptyHashedString(user) - : user.Password; - } - private string GetLocalPasswordHash(User user) { return string.IsNullOrEmpty(user.EasyPassword) - ? GetEmptyHashedString(user) + ? _defaultAuthenticationProvider.GetEmptyHashedString(user) : user.EasyPassword; } private bool IsPasswordEmpty(User user, string passwordHash) { - return string.Equals(passwordHash, GetEmptyHashedString(user), StringComparison.OrdinalIgnoreCase); - } - - private string GetEmptyHashedString(User user) - { - return GetHashedString(user, string.Empty); - } - - /// <summary> - /// Gets the hashed string. - /// </summary> - private string GetHashedString(User user, string str) - { - var salt = user.Salt; - if (salt != null) - { - // return BCrypt.HashPassword(str, salt); - } - - // legacy - return BitConverter.ToString(_cryptographyProvider.ComputeSHA1(Encoding.UTF8.GetBytes(str))).Replace("-", string.Empty); + return string.Equals(passwordHash, _defaultAuthenticationProvider.GetEmptyHashedString(user), StringComparison.OrdinalIgnoreCase); } /// <summary> /// Loads the users from the repository /// </summary> /// <returns>IEnumerable{User}.</returns> - private List<User> LoadUsers() + private User[] LoadUsers() { - var users = UserRepository.RetrieveAllUsers().ToList(); + var users = UserRepository.RetrieveAllUsers(); // There always has to be at least one user. if (users.Count == 0) { - var name = MakeValidUsername(Environment.UserName); + var defaultName = Environment.UserName; + if (string.IsNullOrWhiteSpace(defaultName)) + { + defaultName = "MyEmbyUser"; + } + var name = MakeValidUsername(defaultName); var user = InstantiateNewUser(name); user.DateLastSaved = DateTime.UtcNow; - UserRepository.SaveUser(user, CancellationToken.None); + UserRepository.CreateUser(user); users.Add(user); @@ -433,7 +556,7 @@ namespace Emby.Server.Implementations.Library UpdateUserPolicy(user, user.Policy, false); } - return users; + return users.ToArray(); } public UserDto GetUserDto(User user, string remoteEndPoint = null) @@ -443,9 +566,7 @@ namespace Emby.Server.Implementations.Library throw new ArgumentNullException("user"); } - var passwordHash = GetPasswordHash(user); - - var hasConfiguredPassword = !IsPasswordEmpty(user, passwordHash); + var hasConfiguredPassword = GetAuthenticationProvider(user).HasPassword(user).Result; var hasConfiguredEasyPassword = !IsPasswordEmpty(user, GetLocalPasswordHash(user)); var hasPassword = user.Configuration.EnableLocalPassword && !string.IsNullOrEmpty(remoteEndPoint) && _networkManager.IsInLocalNetwork(remoteEndPoint) ? @@ -454,7 +575,7 @@ namespace Emby.Server.Implementations.Library var dto = new UserDto { - Id = user.Id.ToString("N"), + Id = user.Id, Name = user.Name, HasPassword = hasPassword, HasConfiguredPassword = hasConfiguredPassword, @@ -577,7 +698,7 @@ namespace Emby.Server.Implementations.Library throw new ArgumentNullException("user"); } - if (user.Id == Guid.Empty || !Users.Any(u => u.Id.Equals(user.Id))) + if (user.Id.Equals(Guid.Empty) || !Users.Any(u => u.Id.Equals(user.Id))) { throw new ArgumentException(string.Format("User with name '{0}' and Id {1} does not exist.", user.Name, user.Id)); } @@ -585,7 +706,7 @@ namespace Emby.Server.Implementations.Library user.DateModified = DateTime.UtcNow; user.DateLastSaved = DateTime.UtcNow; - UserRepository.SaveUser(user, CancellationToken.None); + UserRepository.UpdateUser(user); OnUserUpdated(user); } @@ -626,11 +747,11 @@ namespace Emby.Server.Implementations.Library var list = Users.ToList(); list.Add(user); - Users = list; + _users = list.ToArray(); user.DateLastSaved = DateTime.UtcNow; - UserRepository.SaveUser(user, CancellationToken.None); + UserRepository.CreateUser(user); EventHelper.QueueEventIfNotNull(UserCreated, this, new GenericEventArgs<User> { Argument = user }, _logger); @@ -658,7 +779,7 @@ namespace Emby.Server.Implementations.Library if (user.ConnectLinkType.HasValue) { - await _connectFactory().RemoveConnect(user.Id.ToString("N")).ConfigureAwait(false); + await _connectFactory().RemoveConnect(user).ConfigureAwait(false); } var allUsers = Users.ToList(); @@ -684,7 +805,7 @@ namespace Emby.Server.Implementations.Library { var configPath = GetConfigurationFilePath(user); - UserRepository.DeleteUser(user, CancellationToken.None); + UserRepository.DeleteUser(user); try { @@ -697,7 +818,7 @@ namespace Emby.Server.Implementations.Library DeleteUserPolicy(user); - Users = allUsers.Where(i => i.Id != user.Id).ToList(); + _users = allUsers.Where(i => i.Id != user.Id).ToArray(); OnUserDeleted(user); } @@ -711,9 +832,9 @@ namespace Emby.Server.Implementations.Library /// Resets the password by clearing it. /// </summary> /// <returns>Task.</returns> - public void ResetPassword(User user) + public Task ResetPassword(User user) { - ChangePassword(user, string.Empty, null); + return ChangePassword(user, string.Empty); } public void ResetEasyPassword(User user) @@ -721,29 +842,19 @@ namespace Emby.Server.Implementations.Library ChangeEasyPassword(user, string.Empty, null); } - public void ChangePassword(User user, string newPassword, string newPasswordHash) + public async Task ChangePassword(User user, string newPassword) { if (user == null) { throw new ArgumentNullException("user"); } - if (newPassword != null) - { - newPasswordHash = GetHashedString(user, newPassword); - } - - if (string.IsNullOrWhiteSpace(newPasswordHash)) - { - throw new ArgumentNullException("newPasswordHash"); - } - if (user.ConnectLinkType.HasValue && user.ConnectLinkType.Value == UserLinkType.Guest) { throw new ArgumentException("Passwords for guests cannot be changed."); } - user.Password = newPasswordHash; + await GetAuthenticationProvider(user).ChangePassword(user, newPassword).ConfigureAwait(false); UpdateUser(user); @@ -759,7 +870,7 @@ namespace Emby.Server.Implementations.Library if (newPassword != null) { - newPasswordHash = GetHashedString(user, newPassword); + newPasswordHash = _defaultAuthenticationProvider.GetHashedString(user, newPassword); } if (string.IsNullOrWhiteSpace(newPasswordHash)) @@ -801,7 +912,7 @@ namespace Emby.Server.Implementations.Library private PasswordPinCreationResult _lastPasswordPinCreationResult; private int _pinAttempts; - private PasswordPinCreationResult CreatePasswordResetPin() + private async Task<PasswordPinCreationResult> CreatePasswordResetPin() { var num = new Random().Next(1, 9999); @@ -815,7 +926,7 @@ namespace Emby.Server.Implementations.Library var text = new StringBuilder(); - var localAddress = _appHost.GetLocalApiUrl(CancellationToken.None).Result ?? string.Empty; + var localAddress = (await _appHost.GetLocalApiUrl(CancellationToken.None).ConfigureAwait(false)) ?? string.Empty; text.AppendLine("Use your web browser to visit:"); text.AppendLine(string.Empty); @@ -844,7 +955,7 @@ namespace Emby.Server.Implementations.Library return result; } - public ForgotPasswordResult StartForgotPasswordProcess(string enteredUsername, bool isInNetwork) + public async Task<ForgotPasswordResult> StartForgotPasswordProcess(string enteredUsername, bool isInNetwork) { DeletePinFile(); @@ -872,7 +983,7 @@ namespace Emby.Server.Implementations.Library action = ForgotPasswordAction.PinCode; } - var result = CreatePasswordResetPin(); + var result = await CreatePasswordResetPin().ConfigureAwait(false); pinFile = result.PinFile; expirationDate = result.ExpirationDate; } @@ -885,7 +996,7 @@ namespace Emby.Server.Implementations.Library }; } - public PinRedeemResult RedeemPasswordResetPin(string pin) + public async Task<PinRedeemResult> RedeemPasswordResetPin(string pin) { DeletePinFile(); @@ -906,7 +1017,7 @@ namespace Emby.Server.Implementations.Library foreach (var user in users) { - ResetPassword(user); + await ResetPassword(user).ConfigureAwait(false); if (user.Policy.IsDisabled) { @@ -953,7 +1064,7 @@ namespace Emby.Server.Implementations.Library public UserPolicy GetUserPolicy(User user) { - var path = GetPolifyFilePath(user); + var path = GetPolicyFilePath(user); try { @@ -988,7 +1099,7 @@ namespace Emby.Server.Implementations.Library } private readonly object _policySyncLock = new object(); - public void UpdateUserPolicy(string userId, UserPolicy userPolicy) + public void UpdateUserPolicy(Guid userId, UserPolicy userPolicy) { var user = GetUserById(userId); UpdateUserPolicy(user, userPolicy, true); @@ -1003,7 +1114,7 @@ namespace Emby.Server.Implementations.Library userPolicy = _jsonSerializer.DeserializeFromString<UserPolicy>(json); } - var path = GetPolifyFilePath(user); + var path = GetPolicyFilePath(user); _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path)); @@ -1013,12 +1124,15 @@ namespace Emby.Server.Implementations.Library user.Policy = userPolicy; } - UpdateConfiguration(user, user.Configuration, true); + if (fireEvent) + { + EventHelper.FireEventIfNotNull(UserPolicyUpdated, this, new GenericEventArgs<User> { Argument = user }, _logger); + } } private void DeleteUserPolicy(User user) { - var path = GetPolifyFilePath(user); + var path = GetPolicyFilePath(user); try { @@ -1037,7 +1151,7 @@ namespace Emby.Server.Implementations.Library } } - private string GetPolifyFilePath(User user) + private string GetPolicyFilePath(User user) { return Path.Combine(user.ConfigurationDirectoryPath, "policy.xml"); } @@ -1075,9 +1189,14 @@ namespace Emby.Server.Implementations.Library } private readonly object _configSyncLock = new object(); - public void UpdateConfiguration(string userId, UserConfiguration config) + public void UpdateConfiguration(Guid userId, UserConfiguration config) { var user = GetUserById(userId); + UpdateConfiguration(user, config); + } + + public void UpdateConfiguration(User user, UserConfiguration config) + { UpdateConfiguration(user, config, true); } @@ -1106,4 +1225,56 @@ namespace Emby.Server.Implementations.Library } } } + + public class DeviceAccessEntryPoint : IServerEntryPoint + { + private IUserManager _userManager; + private IAuthenticationRepository _authRepo; + private IDeviceManager _deviceManager; + private ISessionManager _sessionManager; + + public DeviceAccessEntryPoint(IUserManager userManager, IAuthenticationRepository authRepo, IDeviceManager deviceManager, ISessionManager sessionManager) + { + _userManager = userManager; + _authRepo = authRepo; + _deviceManager = deviceManager; + _sessionManager = sessionManager; + } + + public void Run() + { + _userManager.UserPolicyUpdated += _userManager_UserPolicyUpdated; + } + + private void _userManager_UserPolicyUpdated(object sender, GenericEventArgs<User> e) + { + var user = e.Argument; + if (!user.Policy.EnableAllDevices) + { + UpdateDeviceAccess(user); + } + } + + private void UpdateDeviceAccess(User user) + { + var existing = _authRepo.Get(new AuthenticationInfoQuery + { + UserId = user.Id + + }).Items; + + foreach (var authInfo in existing) + { + if (!string.IsNullOrEmpty(authInfo.DeviceId) && !_deviceManager.CanAccessDevice(user, authInfo.DeviceId)) + { + _sessionManager.Logout(authInfo); + } + } + } + + public void Dispose() + { + + } + } }
\ No newline at end of file |
