From 56eea6a626c439e631433500357ced09f72a6fb2 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Mon, 8 Jul 2013 12:13:21 -0400 Subject: [PATCH] added some user access settings --- MediaBrowser.Api/UserService.cs | 80 +++++++++++++++++-- .../MediaBrowser.Model.net35.csproj | 6 ++ .../Configuration/ManualLoginCategory.cs | 10 +++ .../Configuration/ServerConfiguration.cs | 8 +- .../Configuration/UserConfiguration.cs | 10 ++- MediaBrowser.Model/MediaBrowser.Model.csproj | 2 + .../Users/AuthenticationResult.cs | 9 +++ .../Library/UserManager.cs | 12 +-- .../Session/SessionManager.cs | 5 ++ .../Api/DashboardService.cs | 18 ++++- MediaBrowser.WebDashboard/ApiClient.js | 31 ++++++- .../MediaBrowser.WebDashboard.csproj | 6 ++ MediaBrowser.WebDashboard/packages.config | 2 +- 13 files changed, 180 insertions(+), 19 deletions(-) create mode 100644 MediaBrowser.Model/Configuration/ManualLoginCategory.cs create mode 100644 MediaBrowser.Model/Users/AuthenticationResult.cs diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs index 54cf183968..87e1630aaa 100644 --- a/MediaBrowser.Api/UserService.cs +++ b/MediaBrowser.Api/UserService.cs @@ -3,6 +3,7 @@ using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Serialization; +using MediaBrowser.Model.Users; using ServiceStack.ServiceHost; using ServiceStack.Text.Controller; using System; @@ -19,6 +20,11 @@ namespace MediaBrowser.Api [Api(Description = "Gets a list of users")] public class GetUsers : IReturn> { + [ApiMember(Name = "IsHidden", Description="Optional filter by IsHidden=true or false", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] + public bool? IsHidden { get; set; } + + [ApiMember(Name = "IsDisabled", Description = "Optional filter by IsDisabled=true or false", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] + public bool? IsDisabled { get; set; } } /// @@ -56,7 +62,7 @@ namespace MediaBrowser.Api /// [Route("/Users/{Id}/Authenticate", "POST")] [Api(Description = "Authenticates a user")] - public class AuthenticateUser : IReturnVoid + public class AuthenticateUser : IReturn { /// /// Gets or sets the id. @@ -73,6 +79,28 @@ namespace MediaBrowser.Api public string Password { get; set; } } + /// + /// Class AuthenticateUser + /// + [Route("/Users/{Name}/AuthenticateByName", "POST")] + [Api(Description = "Authenticates a user")] + public class AuthenticateUserByName : IReturn + { + /// + /// Gets or sets the id. + /// + /// The id. + [ApiMember(Name = "Name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string Name { get; set; } + + /// + /// Gets or sets the password. + /// + /// The password. + [ApiMember(Name = "Password", IsRequired = true, DataType = "string", ParameterType = "body", Verb = "POST")] + public string Password { get; set; } + } + /// /// Class UpdateUserPassword /// @@ -168,11 +196,21 @@ namespace MediaBrowser.Api { var dtoBuilder = new UserDtoBuilder(Logger); - var tasks = _userManager.Users.OrderBy(u => u.Name).Select(dtoBuilder.GetUserDto); + var users = _userManager.Users; + + if (request.IsDisabled.HasValue) + { + users = users.Where(i => i.Configuration.IsDisabled == request.IsDisabled.Value); + } + + if (request.IsHidden.HasValue) + { + users = users.Where(i => i.Configuration.IsHidden == request.IsHidden.Value); + } - var users = tasks.Select(i => i.Result).ToList(); + var tasks = users.OrderBy(u => u.Name).Select(dtoBuilder.GetUserDto).Select(i => i.Result); - return ToOptimizedResult(users); + return ToOptimizedResult(tasks.ToList()); } /// @@ -218,7 +256,21 @@ namespace MediaBrowser.Api /// Posts the specified request. /// /// The request. - public void Post(AuthenticateUser request) + public object Post(AuthenticateUser request) + { + var result = AuthenticateUser(request).Result; + + return ToOptimizedResult(result); + } + + public object Post(AuthenticateUserByName request) + { + var user = _userManager.Users.FirstOrDefault(i => string.Equals(request.Name, i.Name, StringComparison.OrdinalIgnoreCase)); + + return AuthenticateUser(new AuthenticateUser { Id = user.Id, Password = request.Password }).Result; + } + + private async Task AuthenticateUser(AuthenticateUser request) { var user = _userManager.GetUserById(request.Id); @@ -227,13 +279,20 @@ namespace MediaBrowser.Api throw new ResourceNotFoundException("User not found"); } - var success = _userManager.AuthenticateUser(user, request.Password).Result; + var success = await _userManager.AuthenticateUser(user, request.Password).ConfigureAwait(false); if (!success) { // Unauthorized throw new UnauthorizedAccessException("Invalid user or password entered."); } + + var result = new AuthenticationResult + { + User = await new UserDtoBuilder(Logger).GetUserDto(user).ConfigureAwait(false) + }; + + return ToOptimizedResult(result); } /// @@ -294,6 +353,15 @@ namespace MediaBrowser.Api } } + // If removing admin access + if (dtoUser.Configuration.IsDisabled && !user.Configuration.IsDisabled) + { + if (_userManager.Users.Count(i => !i.Configuration.IsDisabled) == 1) + { + throw new ArgumentException("There must be at least one enabled user in the system."); + } + } + var task = user.Name.Equals(dtoUser.Name, StringComparison.Ordinal) ? _userManager.UpdateUser(user) : _userManager.RenameUser(user, dtoUser.Name); Task.WaitAll(task); diff --git a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj index 78ac127141..9c534f9160 100644 --- a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj +++ b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj @@ -52,6 +52,9 @@ Configuration\BaseApplicationConfiguration.cs + + Configuration\ManualLoginCategory.cs + Configuration\ServerConfiguration.cs @@ -331,6 +334,9 @@ Updates\PackageVersionInfo.cs + + Users\AuthenticationResult.cs + Weather\WeatherUnits.cs diff --git a/MediaBrowser.Model/Configuration/ManualLoginCategory.cs b/MediaBrowser.Model/Configuration/ManualLoginCategory.cs new file mode 100644 index 0000000000..20e873437c --- /dev/null +++ b/MediaBrowser.Model/Configuration/ManualLoginCategory.cs @@ -0,0 +1,10 @@ + +namespace MediaBrowser.Model.Configuration +{ + public enum ManualLoginCategory + { + Mobile, + MediaBrowserTheater, + Roku + } +} diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index fadb4aefad..2cb0eef483 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -1,6 +1,6 @@ using MediaBrowser.Model.Entities; -using System; using MediaBrowser.Model.Weather; +using System; namespace MediaBrowser.Model.Configuration { @@ -20,7 +20,7 @@ namespace MediaBrowser.Model.Configuration /// /// The weather unit. public WeatherUnits WeatherUnit { get; set; } - + /// /// Gets or sets a value indicating whether [enable HTTP level logging]. /// @@ -223,6 +223,8 @@ namespace MediaBrowser.Model.Configuration /// The dashboard source path. public string DashboardSourcePath { get; set; } + public ManualLoginCategory[] ManualLoginClients { get; set; } + /// /// Initializes a new instance of the class. /// @@ -249,6 +251,8 @@ namespace MediaBrowser.Model.Configuration EnableInternetProviders = true; //initial installs will need these InternetProviderExcludeTypes = new string[] { }; + ManualLoginClients = new ManualLoginCategory[] { }; + MetadataRefreshDays = 30; PreferredMetadataLanguage = "en"; MetadataCountryCode = "US"; diff --git a/MediaBrowser.Model/Configuration/UserConfiguration.cs b/MediaBrowser.Model/Configuration/UserConfiguration.cs index 45ef03b8dc..35e9b3b0f8 100644 --- a/MediaBrowser.Model/Configuration/UserConfiguration.cs +++ b/MediaBrowser.Model/Configuration/UserConfiguration.cs @@ -47,7 +47,15 @@ namespace MediaBrowser.Model.Configuration /// /// true if [use forced subtitles only]; otherwise, false. public bool UseForcedSubtitlesOnly { get; set; } - + + /// + /// Gets or sets a value indicating whether this instance is hidden. + /// + /// true if this instance is hidden; otherwise, false. + public bool IsHidden { get; set; } + + public bool IsDisabled { get; set; } + /// /// Initializes a new instance of the class. /// diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index b59e54bcfb..2e7d41f878 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -43,6 +43,7 @@ + @@ -141,6 +142,7 @@ + diff --git a/MediaBrowser.Model/Users/AuthenticationResult.cs b/MediaBrowser.Model/Users/AuthenticationResult.cs new file mode 100644 index 0000000000..ea6b57e061 --- /dev/null +++ b/MediaBrowser.Model/Users/AuthenticationResult.cs @@ -0,0 +1,9 @@ +using MediaBrowser.Model.Dto; + +namespace MediaBrowser.Model.Users +{ + public class AuthenticationResult + { + public UserDto User { get; set; } + } +} diff --git a/MediaBrowser.Server.Implementations/Library/UserManager.cs b/MediaBrowser.Server.Implementations/Library/UserManager.cs index 8aedca50f1..0227f883a0 100644 --- a/MediaBrowser.Server.Implementations/Library/UserManager.cs +++ b/MediaBrowser.Server.Implementations/Library/UserManager.cs @@ -1,10 +1,7 @@ -using System.IO; -using MediaBrowser.Common.Events; +using MediaBrowser.Common.Events; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Session; @@ -12,12 +9,12 @@ using MediaBrowser.Model.Logging; using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Security.Cryptography; using System.Text; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Model.Session; namespace MediaBrowser.Server.Implementations.Library { @@ -174,6 +171,11 @@ namespace MediaBrowser.Server.Implementations.Library throw new ArgumentNullException("user"); } + if (user.Configuration.IsDisabled) + { + throw new UnauthorizedAccessException(string.Format("The {0} account is currently disabled. Please consult with your administrator.", user.Name)); + } + var existingPasswordString = string.IsNullOrEmpty(user.Password) ? GetSha1String(string.Empty) : user.Password; var success = string.Equals(existingPasswordString, password.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase); diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs index dda9658d45..d9f28915a9 100644 --- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs +++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs @@ -101,6 +101,11 @@ namespace MediaBrowser.Server.Implementations.Session /// user public Task LogConnectionActivity(string clientType, string deviceId, string deviceName, User user) { + if (user != null && user.Configuration.IsDisabled) + { + throw new UnauthorizedAccessException(string.Format("The {0} account is currently disabled. Please consult with your administrator.", user.Name)); + } + var activityDate = DateTime.UtcNow; GetConnection(clientType, deviceId, deviceName, user).LastActivityDate = activityDate; diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index 0281ee6adc..89aaec4b21 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -5,7 +5,6 @@ using MediaBrowser.Common.ScheduledTasks; using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; -using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Logging; @@ -221,7 +220,21 @@ namespace MediaBrowser.WebDashboard.Api /// System.Object. public object Get(GetDashboardConfigurationPages request) { - var pages = ServerEntryPoint.Instance.PluginConfigurationPages; + const string unavilableMessage = "The server is still loading. Please try again momentarily."; + + var instance = ServerEntryPoint.Instance; + + if (instance == null) + { + throw new InvalidOperationException(unavilableMessage); + } + + var pages = instance.PluginConfigurationPages; + + if (pages == null) + { + throw new InvalidOperationException(unavilableMessage); + } if (request.PageType.HasValue) { @@ -428,6 +441,7 @@ namespace MediaBrowser.WebDashboard.Api "librarybrowser.js", "aboutpage.js", + "allusersettings.js", "alphapicker.js", "addpluginpage.js", "advancedconfigurationpage.js", diff --git a/MediaBrowser.WebDashboard/ApiClient.js b/MediaBrowser.WebDashboard/ApiClient.js index a90d2dcf8b..4a0222139d 100644 --- a/MediaBrowser.WebDashboard/ApiClient.js +++ b/MediaBrowser.WebDashboard/ApiClient.js @@ -1432,9 +1432,9 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) { /** * Gets all users from the server */ - self.getUsers = function () { + self.getUsers = function (options) { - var url = self.getUrl("users"); + var url = self.getUrl("users", options || {}); return self.ajax({ type: "GET", @@ -1870,6 +1870,32 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) { return files; }; + /** + * Authenticates a user + * @param {String} name + * @param {String} password + */ + self.authenticateUserByName = function (name, password) { + + if (!name) { + throw new Error("null name"); + } + + var url = self.getUrl("Users/" + name + "/authenticatebyname"); + + var postData = { + password: MediaBrowser.SHA1(password || "") + }; + + return self.ajax({ + type: "POST", + url: url, + data: JSON.stringify(postData), + dataType: "json", + contentType: "application/json" + }); + }; + /** * Authenticates a user * @param {String} userId @@ -1891,6 +1917,7 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) { type: "POST", url: url, data: JSON.stringify(postData), + dataType: "json", contentType: "application/json" }); }; diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj index 62dd59cc19..553c967f5d 100644 --- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj +++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj @@ -81,6 +81,9 @@ + + PreserveNewest + PreserveNewest @@ -354,6 +357,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest diff --git a/MediaBrowser.WebDashboard/packages.config b/MediaBrowser.WebDashboard/packages.config index 439f8cb979..a8a7379230 100644 --- a/MediaBrowser.WebDashboard/packages.config +++ b/MediaBrowser.WebDashboard/packages.config @@ -1,6 +1,6 @@  - + \ No newline at end of file