diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index e3c36fc021..c9de3c01b1 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -180,11 +180,6 @@ namespace Emby.Server.Implementations.Library } } - public Task AuthenticateUser(string username, string passwordSha1, string remoteEndPoint) - { - return AuthenticateUser(username, passwordSha1, null, remoteEndPoint); - } - public bool IsValidUsername(string username) { // Usernames can contain letters (a-z), numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.) @@ -223,7 +218,7 @@ namespace Emby.Server.Implementations.Library return builder.ToString(); } - public async Task AuthenticateUser(string username, string passwordSha1, string passwordMd5, string remoteEndPoint) + public async Task AuthenticateUser(string username, string password, string hashedPassword, string passwordMd5, string remoteEndPoint) { if (string.IsNullOrWhiteSpace(username)) { @@ -237,23 +232,23 @@ namespace Emby.Server.Implementations.Library 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 = string.Equals(GetPasswordHash(user), passwordSha1.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase); - - if (!success && _networkManager.IsInLocalNetwork(remoteEndPoint) && user.Configuration.EnableLocalPassword) - { - success = string.Equals(GetLocalPasswordHash(user), passwordSha1.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase); - } + success = AuthenticateLocalUser(user, password, hashedPassword, remoteEndPoint); } // Maybe user accidently entered connect credentials. let's be flexible - if (!success && user.ConnectLinkType.HasValue && !string.IsNullOrWhiteSpace(passwordMd5) && !string.IsNullOrWhiteSpace(user.ConnectUserName)) + if (!success && user.ConnectLinkType.HasValue && !string.IsNullOrWhiteSpace(user.ConnectUserName)) { try { - await _connectFactory().Authenticate(user.ConnectUserName, passwordMd5).ConfigureAwait(false); + await _connectFactory().Authenticate(user.ConnectUserName, password, passwordMd5).ConfigureAwait(false); success = true; } catch @@ -268,7 +263,7 @@ namespace Emby.Server.Implementations.Library { try { - var connectAuthResult = await _connectFactory().Authenticate(username, passwordMd5).ConfigureAwait(false); + var connectAuthResult = await _connectFactory().Authenticate(username, password, passwordMd5).ConfigureAwait(false); user = Users.FirstOrDefault(i => string.Equals(i.ConnectUserId, connectAuthResult.User.Id, StringComparison.OrdinalIgnoreCase)); @@ -307,6 +302,36 @@ namespace Emby.Server.Implementations.Library return success ? user : null; } + private bool AuthenticateLocalUser(User user, string password, string hashedPassword, string remoteEndPoint) + { + bool success; + + if (password == null) + { + // legacy + success = string.Equals(GetPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase); + } + else + { + success = string.Equals(GetPasswordHash(user), GetHashedString(user, password), StringComparison.OrdinalIgnoreCase); + } + + 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), GetHashedString(user, password), StringComparison.OrdinalIgnoreCase); + } + } + + return success; + } + private void UpdateInvalidLoginAttemptCount(User user, int newValue) { if (user.Policy.InvalidLoginAttemptCount != newValue || newValue > 0) @@ -342,29 +367,39 @@ namespace Emby.Server.Implementations.Library private string GetPasswordHash(User user) { return string.IsNullOrEmpty(user.Password) - ? GetSha1String(string.Empty) + ? GetEmptyHashedString(user) : user.Password; } private string GetLocalPasswordHash(User user) { return string.IsNullOrEmpty(user.EasyPassword) - ? GetSha1String(string.Empty) + ? GetEmptyHashedString(user) : user.EasyPassword; } - private bool IsPasswordEmpty(string passwordHash) + private bool IsPasswordEmpty(User user, string passwordHash) { - return string.Equals(passwordHash, GetSha1String(string.Empty), StringComparison.OrdinalIgnoreCase); + return string.Equals(passwordHash, GetEmptyHashedString(user), StringComparison.OrdinalIgnoreCase); + } + + private string GetEmptyHashedString(User user) + { + return GetHashedString(user, string.Empty); } /// - /// Gets the sha1 string. + /// Gets the hashed string. /// - /// The STR. - /// System.String. - private string GetSha1String(string str) + 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); } @@ -407,8 +442,8 @@ namespace Emby.Server.Implementations.Library var passwordHash = GetPasswordHash(user); - var hasConfiguredPassword = !IsPasswordEmpty(passwordHash); - var hasConfiguredEasyPassword = !IsPasswordEmpty(GetLocalPasswordHash(user)); + var hasConfiguredPassword = !IsPasswordEmpty(user, passwordHash); + var hasConfiguredEasyPassword = !IsPasswordEmpty(user, GetLocalPasswordHash(user)); var hasPassword = user.Configuration.EnableLocalPassword && !string.IsNullOrEmpty(remoteEndPoint) && _networkManager.IsInLocalNetwork(remoteEndPoint) ? hasConfiguredEasyPassword : @@ -460,14 +495,6 @@ namespace Emby.Server.Implementations.Library { var dto = GetUserDto(user); - var offlinePasswordHash = GetLocalPasswordHash(user); - dto.HasPassword = !IsPasswordEmpty(offlinePasswordHash); - - dto.OfflinePasswordSalt = Guid.NewGuid().ToString("N"); - - // Hash the pin with the device Id to create a unique result for this device - dto.OfflinePassword = GetSha1String((offlinePasswordHash + dto.OfflinePasswordSalt).ToLower()); - dto.ServerName = _appHost.FriendlyName; return dto; @@ -682,23 +709,29 @@ namespace Emby.Server.Implementations.Library /// Task. public void ResetPassword(User user) { - ChangePassword(user, GetSha1String(string.Empty)); + ChangePassword(user, string.Empty, null); } public void ResetEasyPassword(User user) { - ChangeEasyPassword(user, GetSha1String(string.Empty)); + ChangeEasyPassword(user, string.Empty, null); } - public void ChangePassword(User user, string newPasswordSha1) + public void ChangePassword(User user, string newPassword, string newPasswordHash) { if (user == null) { throw new ArgumentNullException("user"); } - if (string.IsNullOrWhiteSpace(newPasswordSha1)) + + if (newPassword != null) + { + newPasswordHash = GetHashedString(user, newPassword); + } + + if (string.IsNullOrWhiteSpace(newPasswordHash)) { - throw new ArgumentNullException("newPasswordSha1"); + throw new ArgumentNullException("newPasswordHash"); } if (user.ConnectLinkType.HasValue && user.ConnectLinkType.Value == UserLinkType.Guest) @@ -706,25 +739,31 @@ namespace Emby.Server.Implementations.Library throw new ArgumentException("Passwords for guests cannot be changed."); } - user.Password = newPasswordSha1; + user.Password = newPasswordHash; UpdateUser(user); EventHelper.FireEventIfNotNull(UserPasswordChanged, this, new GenericEventArgs(user), _logger); } - public void ChangeEasyPassword(User user, string newPasswordSha1) + public void ChangeEasyPassword(User user, string newPassword, string newPasswordHash) { if (user == null) { throw new ArgumentNullException("user"); } - if (string.IsNullOrWhiteSpace(newPasswordSha1)) + + if (newPassword != null) + { + newPasswordHash = GetHashedString(user, newPassword); + } + + if (string.IsNullOrWhiteSpace(newPasswordHash)) { - throw new ArgumentNullException("newPasswordSha1"); + throw new ArgumentNullException("newPasswordHash"); } - user.EasyPassword = newPasswordSha1; + user.EasyPassword = newPasswordHash; UpdateUser(user); @@ -744,7 +783,8 @@ namespace Emby.Server.Implementations.Library Id = Guid.NewGuid(), DateCreated = DateTime.UtcNow, DateModified = DateTime.UtcNow, - UsesIdForConfigurationPath = true + UsesIdForConfigurationPath = true, + //Salt = BCrypt.GenerateSalt() }; } diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs index 512356b43a..ddefb08dff 100644 --- a/MediaBrowser.Api/UserService.cs +++ b/MediaBrowser.Api/UserService.cs @@ -97,6 +97,9 @@ namespace MediaBrowser.Api [ApiMember(Name = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] public string Id { get; set; } + [ApiMember(Name = "Pw", IsRequired = true, DataType = "string", ParameterType = "body", Verb = "POST")] + public string Pw { get; set; } + /// /// Gets or sets the password. /// @@ -125,6 +128,9 @@ namespace MediaBrowser.Api [ApiMember(Name = "Password", IsRequired = true, DataType = "string", ParameterType = "body", Verb = "POST")] public string Password { get; set; } + [ApiMember(Name = "Pw", IsRequired = true, DataType = "string", ParameterType = "body", Verb = "POST")] + public string Pw { get; set; } + [ApiMember(Name = "PasswordMd5", IsRequired = true, DataType = "string", ParameterType = "body", Verb = "POST")] public string PasswordMd5 { get; set; } } @@ -148,12 +154,16 @@ namespace MediaBrowser.Api /// The password. public string CurrentPassword { get; set; } + public string CurrentPw { get; set; } + /// /// Gets or sets the new password. /// /// The new password. public string NewPassword { get; set; } + public string NewPw { get; set; } + /// /// Gets or sets a value indicating whether [reset password]. /// @@ -180,6 +190,8 @@ namespace MediaBrowser.Api /// The new password. public string NewPassword { get; set; } + public string NewPw { get; set; } + /// /// Gets or sets a value indicating whether [reset password]. /// @@ -408,7 +420,8 @@ namespace MediaBrowser.Api return Post(new AuthenticateUserByName { Username = user.Name, - Password = request.Password + Password = request.Password, + Pw = request.Pw }); } @@ -422,6 +435,7 @@ namespace MediaBrowser.Api AppVersion = auth.Version, DeviceId = auth.DeviceId, DeviceName = auth.Device, + Password = request.Pw, PasswordSha1 = request.Password, PasswordMd5 = request.PasswordMd5, RemoteEndPoint = Request.RemoteIp, @@ -459,14 +473,14 @@ namespace MediaBrowser.Api } else { - var success = await _userManager.AuthenticateUser(user.Name, request.CurrentPassword, Request.RemoteIp).ConfigureAwait(false); + var success = await _userManager.AuthenticateUser(user.Name, request.CurrentPw, request.CurrentPassword, null, Request.RemoteIp).ConfigureAwait(false); if (success == null) { throw new ArgumentException("Invalid user or password entered."); } - _userManager.ChangePassword(user, request.NewPassword); + _userManager.ChangePassword(user, request.NewPw, request.NewPassword); var currentToken = _authContext.GetAuthorizationInfo(Request).Token; @@ -491,7 +505,7 @@ namespace MediaBrowser.Api } else { - _userManager.ChangeEasyPassword(user, request.NewPassword); + _userManager.ChangeEasyPassword(user, request.NewPw, request.NewPassword); } } @@ -501,8 +515,6 @@ namespace MediaBrowser.Api /// The request. public void Post(UpdateUser request) { - // We need to parse this manually because we told service stack not to with IRequiresRequestStream - // https://code.google.com/p/servicestack/source/browse/trunk/Common/ServiceStack.Text/ServiceStack.Text/Controller/PathInfo.cs var id = GetPathValue(1); AssertCanUpdateUser(_authContext, _userManager, id, false); diff --git a/MediaBrowser.Controller/Connect/IConnectManager.cs b/MediaBrowser.Controller/Connect/IConnectManager.cs index f899c72623..70bdc52e63 100644 --- a/MediaBrowser.Controller/Connect/IConnectManager.cs +++ b/MediaBrowser.Controller/Connect/IConnectManager.cs @@ -58,10 +58,7 @@ namespace MediaBrowser.Controller.Connect /// /// Authenticates the specified username. /// - /// The username. - /// The password MD5. - /// Task. - Task Authenticate(string username, string passwordMd5); + Task Authenticate(string username, string password, string passwordMd5); /// /// Gets the local user. diff --git a/MediaBrowser.Controller/Entities/User.cs b/MediaBrowser.Controller/Entities/User.cs index 3c89037cca..cca3091c15 100644 --- a/MediaBrowser.Controller/Entities/User.cs +++ b/MediaBrowser.Controller/Entities/User.cs @@ -30,6 +30,7 @@ namespace MediaBrowser.Controller.Entities /// The password. public string Password { get; set; } public string EasyPassword { get; set; } + public string Salt { get; set; } public string ConnectUserName { get; set; } public string ConnectUserId { get; set; } diff --git a/MediaBrowser.Controller/Library/IUserManager.cs b/MediaBrowser.Controller/Library/IUserManager.cs index 6da3e53aab..d4232c77e0 100644 --- a/MediaBrowser.Controller/Library/IUserManager.cs +++ b/MediaBrowser.Controller/Library/IUserManager.cs @@ -58,16 +58,6 @@ namespace MediaBrowser.Controller.Library /// User. User GetUserByName(string name); - /// - /// Authenticates a User and returns a result indicating whether or not it succeeded - /// - /// The username. - /// The password sha1. - /// The remote end point. - /// Task{System.Boolean}. - /// user - Task AuthenticateUser(string username, string passwordSha1, string remoteEndPoint); - /// /// Refreshes metadata for each user /// @@ -135,18 +125,12 @@ namespace MediaBrowser.Controller.Library /// /// Changes the password. /// - /// The user. - /// The new password sha1. - /// Task. - void ChangePassword(User user, string newPasswordSha1); + void ChangePassword(User user, string newPassword, string newPasswordSha1); /// /// Changes the easy password. /// - /// The user. - /// The new password sha1. - /// Task. - void ChangeEasyPassword(User user, string newPasswordSha1); + void ChangeEasyPassword(User user, string newPassword, string newPasswordSha1); /// /// Gets the user dto. @@ -159,12 +143,7 @@ namespace MediaBrowser.Controller.Library /// /// Authenticates the user. /// - /// The username. - /// The password sha1. - /// The password MD5. - /// The remote end point. - /// Task<System.Boolean>. - Task AuthenticateUser(string username, string passwordSha1, string passwordMd5, string remoteEndPoint); + Task AuthenticateUser(string username, string password, string passwordSha1, string passwordMd5, string remoteEndPoint); /// /// Starts the forgot password process. diff --git a/MediaBrowser.Controller/Session/AuthenticationRequest.cs b/MediaBrowser.Controller/Session/AuthenticationRequest.cs index 362f5b2b95..1b684fa8f2 100644 --- a/MediaBrowser.Controller/Session/AuthenticationRequest.cs +++ b/MediaBrowser.Controller/Session/AuthenticationRequest.cs @@ -5,6 +5,7 @@ namespace MediaBrowser.Controller.Session { public string Username { get; set; } public string UserId { get; set; } + public string Password { get; set; } public string PasswordSha1 { get; set; } public string PasswordMd5 { get; set; } public string App { get; set; }