From a6145e54d901095e5b67704e3103843b4b515681 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 29 Jan 2015 01:06:24 -0500 Subject: [PATCH] support in-home easy password --- MediaBrowser.Api/UserService.cs | 78 ++++++++++++++++++- .../Networking/BaseNetworkManager.cs | 25 +++++- MediaBrowser.Controller/Entities/User.cs | 2 +- .../Library/IUserManager.cs | 15 ++++ MediaBrowser.Model/Dto/UserDto.cs | 6 ++ .../Library/UserManager.cs | 54 ++++++++----- .../Localization/JavaScript/javascript.json | 5 +- .../Localization/Server/server.json | 8 +- 8 files changed, 165 insertions(+), 28 deletions(-) diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs index 4f49dd37d..2c497d172 100644 --- a/MediaBrowser.Api/UserService.cs +++ b/MediaBrowser.Api/UserService.cs @@ -11,7 +11,6 @@ using MediaBrowser.Model.Connect; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Users; using ServiceStack; -using ServiceStack.Text.Controller; using System; using System.Collections.Generic; using System.Linq; @@ -148,6 +147,32 @@ namespace MediaBrowser.Api public bool ResetPassword { get; set; } } + /// + /// Class UpdateUserEasyPassword + /// + [Route("/Users/{Id}/EasyPassword", "POST", Summary = "Updates a user's easy password")] + [Authenticated] + public class UpdateUserEasyPassword : IReturnVoid + { + /// + /// Gets or sets the id. + /// + /// The id. + public string Id { get; set; } + + /// + /// Gets or sets the new password. + /// + /// The new password. + public string NewPassword { get; set; } + + /// + /// Gets or sets a value indicating whether [reset password]. + /// + /// true if [reset password]; otherwise, false. + public bool ResetPassword { get; set; } + } + /// /// Class UpdateUser /// @@ -410,6 +435,8 @@ namespace MediaBrowser.Api public async Task PostAsync(UpdateUserPassword request) { + AssertCanUpdateUser(request.Id); + var user = _userManager.GetUserById(request.Id); if (user == null) @@ -434,6 +461,33 @@ namespace MediaBrowser.Api } } + public void Post(UpdateUserEasyPassword request) + { + var task = PostAsync(request); + Task.WaitAll(task); + } + + public async Task PostAsync(UpdateUserEasyPassword request) + { + AssertCanUpdateUser(request.Id); + + var user = _userManager.GetUserById(request.Id); + + if (user == null) + { + throw new ResourceNotFoundException("User not found"); + } + + if (request.ResetPassword) + { + await _userManager.ResetEasyPassword(user).ConfigureAwait(false); + } + else + { + await _userManager.ChangeEasyPassword(user, request.NewPassword).ConfigureAwait(false); + } + } + /// /// Posts the specified request. /// @@ -449,7 +503,9 @@ namespace MediaBrowser.Api { // 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 = new Guid(GetPathValue(1)); + var id = GetPathValue(1); + + AssertCanUpdateUser(id); var dtoUser = request; @@ -499,11 +555,29 @@ namespace MediaBrowser.Api public void Post(UpdateUserConfiguration request) { + AssertCanUpdateUser(request.Id); + var task = _userManager.UpdateConfiguration(request.Id, request); Task.WaitAll(task); } + private void AssertCanUpdateUser(string userId) + { + var auth = AuthorizationContext.GetAuthorizationInfo(Request); + + // If they're going to update the record of another user, they must be an administrator + if (!string.Equals(userId, auth.UserId, StringComparison.OrdinalIgnoreCase)) + { + var authenticatedUser = _userManager.GetUserById(auth.UserId); + + if (!authenticatedUser.Policy.IsAdministrator) + { + throw new SecurityException("Unauthorized access."); + } + } + } + public void Post(UpdateUserPolicy request) { var task = UpdateUserPolicy(request); diff --git a/MediaBrowser.Common.Implementations/Networking/BaseNetworkManager.cs b/MediaBrowser.Common.Implementations/Networking/BaseNetworkManager.cs index 660ac09f9..6deda1293 100644 --- a/MediaBrowser.Common.Implementations/Networking/BaseNetworkManager.cs +++ b/MediaBrowser.Common.Implementations/Networking/BaseNetworkManager.cs @@ -50,7 +50,7 @@ namespace MediaBrowser.Common.Implementations.Networking { NetworkChange.NetworkAddressChanged -= NetworkChange_NetworkAddressChanged; NetworkChange.NetworkAvailabilityChanged -= NetworkChange_NetworkAvailabilityChanged; - + NetworkChange.NetworkAddressChanged += NetworkChange_NetworkAddressChanged; NetworkChange.NetworkAvailabilityChanged += NetworkChange_NetworkAvailabilityChanged; } @@ -106,6 +106,11 @@ namespace MediaBrowser.Common.Implementations.Networking // Private address space: // http://en.wikipedia.org/wiki/Private_network + if (endpoint.StartsWith("172.", StringComparison.OrdinalIgnoreCase)) + { + return Is172AddressPrivate(endpoint); + } + return // If url was requested with computer name, we may see this @@ -114,11 +119,23 @@ namespace MediaBrowser.Common.Implementations.Networking endpoint.StartsWith("localhost", StringComparison.OrdinalIgnoreCase) || endpoint.StartsWith("127.", StringComparison.OrdinalIgnoreCase) || endpoint.StartsWith("10.", StringComparison.OrdinalIgnoreCase) || - endpoint.StartsWith("192.", StringComparison.OrdinalIgnoreCase) || - endpoint.StartsWith("172.", StringComparison.OrdinalIgnoreCase) || + endpoint.StartsWith("192.168", StringComparison.OrdinalIgnoreCase) || endpoint.StartsWith("169.", StringComparison.OrdinalIgnoreCase); } + private bool Is172AddressPrivate(string endpoint) + { + for (var i = 16; i <= 31; i++) + { + if (endpoint.StartsWith("172." + i.ToString(CultureInfo.InvariantCulture) + ".", StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + + return false; + } + public bool IsInLocalNetwork(string endpoint) { return IsInLocalNetworkInternal(endpoint, true); @@ -175,7 +192,7 @@ namespace MediaBrowser.Common.Implementations.Networking return false; } - + public IEnumerable GetIpAddresses(string hostName) { return Dns.GetHostAddresses(hostName); diff --git a/MediaBrowser.Controller/Entities/User.cs b/MediaBrowser.Controller/Entities/User.cs index 1fca676a9..01a7486b3 100644 --- a/MediaBrowser.Controller/Entities/User.cs +++ b/MediaBrowser.Controller/Entities/User.cs @@ -32,7 +32,7 @@ namespace MediaBrowser.Controller.Entities /// /// The password. public string Password { get; set; } - public string LocalPassword { get; set; } + public string EasyPassword { 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 f5846973e..5aa58aa57 100644 --- a/MediaBrowser.Controller/Library/IUserManager.cs +++ b/MediaBrowser.Controller/Library/IUserManager.cs @@ -117,6 +117,13 @@ namespace MediaBrowser.Controller.Library /// Task. Task ResetPassword(User user); + /// + /// Resets the easy password. + /// + /// The user. + /// Task. + Task ResetEasyPassword(User user); + /// /// Changes the password. /// @@ -125,6 +132,14 @@ namespace MediaBrowser.Controller.Library /// Task. Task ChangePassword(User user, string newPasswordSha1); + /// + /// Changes the easy password. + /// + /// The user. + /// The new password sha1. + /// Task. + Task ChangeEasyPassword(User user, string newPasswordSha1); + /// /// Gets the user dto. /// diff --git a/MediaBrowser.Model/Dto/UserDto.cs b/MediaBrowser.Model/Dto/UserDto.cs index 9b6d92030..3e2d869d6 100644 --- a/MediaBrowser.Model/Dto/UserDto.cs +++ b/MediaBrowser.Model/Dto/UserDto.cs @@ -66,6 +66,12 @@ namespace MediaBrowser.Model.Dto /// /// true if this instance has configured password; otherwise, false. public bool HasConfiguredPassword { get; set; } + + /// + /// Gets or sets a value indicating whether this instance has configured easy password. + /// + /// true if this instance has configured easy password; otherwise, false. + public bool HasConfiguredEasyPassword { get; set; } /// /// Gets or sets the last login date. diff --git a/MediaBrowser.Server.Implementations/Library/UserManager.cs b/MediaBrowser.Server.Implementations/Library/UserManager.cs index 6b6f5b1b6..a001b25b6 100644 --- a/MediaBrowser.Server.Implementations/Library/UserManager.cs +++ b/MediaBrowser.Server.Implementations/Library/UserManager.cs @@ -275,9 +275,9 @@ namespace MediaBrowser.Server.Implementations.Library private string GetLocalPasswordHash(User user) { - return string.IsNullOrEmpty(user.LocalPassword) + return string.IsNullOrEmpty(user.EasyPassword) ? GetSha1String(string.Empty) - : user.LocalPassword; + : user.EasyPassword; } private bool IsPasswordEmpty(string passwordHash) @@ -355,18 +355,20 @@ namespace MediaBrowser.Server.Implementations.Library var passwordHash = GetPasswordHash(user); - var hasConfiguredDefaultPassword = !IsPasswordEmpty(passwordHash); + var hasConfiguredPassword = !IsPasswordEmpty(passwordHash); + var hasConfiguredEasyPassword = !IsPasswordEmpty(GetLocalPasswordHash(user)); var hasPassword = user.Configuration.EnableLocalPassword && !string.IsNullOrEmpty(remoteEndPoint) && _networkManager.IsInLocalNetwork(remoteEndPoint) ? - !IsPasswordEmpty(GetLocalPasswordHash(user)) : - hasConfiguredDefaultPassword; + hasConfiguredEasyPassword : + hasConfiguredPassword; var dto = new UserDto { Id = user.Id.ToString("N"), Name = user.Name, HasPassword = hasPassword, - HasConfiguredPassword = hasConfiguredDefaultPassword, + HasConfiguredPassword = hasConfiguredPassword, + HasConfiguredEasyPassword = hasConfiguredEasyPassword, LastActivityDate = user.LastActivityDate, LastLoginDate = user.LastLoginDate, Configuration = user.Configuration, @@ -613,18 +615,11 @@ namespace MediaBrowser.Server.Implementations.Library return ChangePassword(user, GetSha1String(string.Empty)); } - /// - /// Changes the password. - /// - /// The user. - /// The new password sha1. - /// Task. - /// - /// user - /// or - /// newPassword - /// - /// Passwords for guests cannot be changed. + public Task ResetEasyPassword(User user) + { + return ChangeEasyPassword(user, GetSha1String(string.Empty)); + } + public async Task ChangePassword(User user, string newPasswordSha1) { if (user == null) @@ -648,6 +643,29 @@ namespace MediaBrowser.Server.Implementations.Library EventHelper.FireEventIfNotNull(UserPasswordChanged, this, new GenericEventArgs(user), _logger); } + public async Task ChangeEasyPassword(User user, string newPasswordSha1) + { + if (user == null) + { + throw new ArgumentNullException("user"); + } + if (string.IsNullOrWhiteSpace(newPasswordSha1)) + { + throw new ArgumentNullException("newPasswordSha1"); + } + + if (user.ConnectLinkType.HasValue && user.ConnectLinkType.Value == UserLinkType.Guest) + { + throw new ArgumentException("Passwords for guests cannot be changed."); + } + + user.EasyPassword = newPasswordSha1; + + await UpdateUser(user).ConfigureAwait(false); + + EventHelper.FireEventIfNotNull(UserPasswordChanged, this, new GenericEventArgs(user), _logger); + } + /// /// Instantiates the new user. /// diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json index ede08a49c..2dcd6eca5 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json @@ -14,9 +14,12 @@ "FileReadError": "An error occurred while reading the file.", "DeleteUser": "Delete User", "DeleteUserConfirmation": "Are you sure you wish to delete this user?", - "PasswordResetHeader": "Password Reset", + "PasswordResetHeader": "Reset Password", "PasswordResetComplete": "The password has been reset.", + "PinCodeResetComplete": "The pin code has been reset.", "PasswordResetConfirmation": "Are you sure you wish to reset the password?", + "PinCodeResetConfirmation": "Are you sure you wish to reset the pin code?", + "HeaderPinCodeReset": "Reset Pin Code", "PasswordSaved": "Password saved.", "PasswordMatchError": "Password and password confirmation must match.", "OptionRelease": "Official Release", diff --git a/MediaBrowser.Server.Implementations/Localization/Server/server.json b/MediaBrowser.Server.Implementations/Localization/Server/server.json index 3e9c75220..93ef75dc3 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/server.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/server.json @@ -56,6 +56,7 @@ "HeaderVideo": "Video", "HeaderPaths": "Paths", "CategorySync": "Sync", + "HeaderEasyPinCode": "Easy Pin Code", "RegisterWithPayPal": "Register with PayPal", "HeaderSyncRequiresSupporterMembership": "Sync Requires a Supporter Membership", "HeaderEnjoyDayTrial": "Enjoy a 14 Day Free Trial", @@ -1132,11 +1133,14 @@ "LabelDisplayFoldersView": "Display a folders view to show plain media folders", "ViewTypeLiveTvRecordingGroups": "Recordings", "ViewTypeLiveTvChannels": "Channels", - "LabelAllowLocalAccessWithoutPassword": "Allow local access without a password", - "LabelAllowLocalAccessWithoutPasswordHelp": "When enabled, a password will not be required when signing in from within your home network.", + "LabelEasyPinCode": "Easy pin code:", + "EasyPasswordHelp": "Your easy pin code is used for offline access with supported Media Browser apps, and can also be used for easy in-network sign in.", + "LabelInNetworkSignInWithEasyPassword": "Enable in-network sign in with my easy pin code", + "LabelInNetworkSignInWithEasyPasswordHelp": "If enabled, you'll be able to use your easy pin code to sign in to Media Browser apps from inside your home network. Your regular password will only be needed away from home. If the pin code is left blank, you won't need a password within your home network.", "HeaderPassword": "Password", "HeaderLocalAccess": "Local Access", "HeaderViewOrder": "View Order", + "ButtonResetEasyPassword": "Reset easy pin code", "LabelSelectUserViewOrder": "Choose the order your views will be displayed in within Media Browser apps", "LabelMetadataRefreshMode": "Metadata refresh mode:", "LabelImageRefreshMode": "Image refresh mode:",