diff options
13 files changed, 180 insertions, 19 deletions
diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs index 54cf18396..87e1630aa 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<List<UserDto>> { + [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; } } /// <summary> @@ -56,7 +62,7 @@ namespace MediaBrowser.Api /// </summary> [Route("/Users/{Id}/Authenticate", "POST")] [Api(Description = "Authenticates a user")] - public class AuthenticateUser : IReturnVoid + public class AuthenticateUser : IReturn<AuthenticationResult> { /// <summary> /// Gets or sets the id. @@ -74,6 +80,28 @@ namespace MediaBrowser.Api } /// <summary> + /// Class AuthenticateUser + /// </summary> + [Route("/Users/{Name}/AuthenticateByName", "POST")] + [Api(Description = "Authenticates a user")] + public class AuthenticateUserByName : IReturn<AuthenticationResult> + { + /// <summary> + /// Gets or sets the id. + /// </summary> + /// <value>The id.</value> + [ApiMember(Name = "Name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string Name { get; set; } + + /// <summary> + /// Gets or sets the password. + /// </summary> + /// <value>The password.</value> + [ApiMember(Name = "Password", IsRequired = true, DataType = "string", ParameterType = "body", Verb = "POST")] + public string Password { get; set; } + } + + /// <summary> /// Class UpdateUserPassword /// </summary> [Route("/Users/{Id}/Password", "POST")] @@ -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()); } /// <summary> @@ -218,7 +256,21 @@ namespace MediaBrowser.Api /// Posts the specified request. /// </summary> /// <param name="request">The request.</param> - 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<object> 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); } /// <summary> @@ -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 78ac12714..9c534f916 100644 --- a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj +++ b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj @@ -52,6 +52,9 @@ <Compile Include="..\MediaBrowser.Model\Configuration\BaseApplicationConfiguration.cs"> <Link>Configuration\BaseApplicationConfiguration.cs</Link> </Compile> + <Compile Include="..\MediaBrowser.Model\Configuration\ManualLoginCategory.cs"> + <Link>Configuration\ManualLoginCategory.cs</Link> + </Compile> <Compile Include="..\MediaBrowser.Model\Configuration\ServerConfiguration.cs"> <Link>Configuration\ServerConfiguration.cs</Link> </Compile> @@ -331,6 +334,9 @@ <Compile Include="..\MediaBrowser.Model\Updates\PackageVersionInfo.cs"> <Link>Updates\PackageVersionInfo.cs</Link> </Compile> + <Compile Include="..\MediaBrowser.Model\Users\AuthenticationResult.cs"> + <Link>Users\AuthenticationResult.cs</Link> + </Compile> <Compile Include="..\MediaBrowser.Model\Weather\WeatherUnits.cs"> <Link>Weather\WeatherUnits.cs</Link> </Compile> diff --git a/MediaBrowser.Model/Configuration/ManualLoginCategory.cs b/MediaBrowser.Model/Configuration/ManualLoginCategory.cs new file mode 100644 index 000000000..20e873437 --- /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 fadb4aefa..2cb0eef48 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 /// </summary> /// <value>The weather unit.</value> public WeatherUnits WeatherUnit { get; set; } - + /// <summary> /// Gets or sets a value indicating whether [enable HTTP level logging]. /// </summary> @@ -223,6 +223,8 @@ namespace MediaBrowser.Model.Configuration /// <value>The dashboard source path.</value> public string DashboardSourcePath { get; set; } + public ManualLoginCategory[] ManualLoginClients { get; set; } + /// <summary> /// Initializes a new instance of the <see cref="ServerConfiguration" /> class. /// </summary> @@ -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 45ef03b8d..35e9b3b0f 100644 --- a/MediaBrowser.Model/Configuration/UserConfiguration.cs +++ b/MediaBrowser.Model/Configuration/UserConfiguration.cs @@ -47,7 +47,15 @@ namespace MediaBrowser.Model.Configuration /// </summary> /// <value><c>true</c> if [use forced subtitles only]; otherwise, <c>false</c>.</value> public bool UseForcedSubtitlesOnly { get; set; } - + + /// <summary> + /// Gets or sets a value indicating whether this instance is hidden. + /// </summary> + /// <value><c>true</c> if this instance is hidden; otherwise, <c>false</c>.</value> + public bool IsHidden { get; set; } + + public bool IsDisabled { get; set; } + /// <summary> /// Initializes a new instance of the <see cref="UserConfiguration" /> class. /// </summary> diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index b59e54bcf..2e7d41f87 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -43,6 +43,7 @@ </Compile> <Compile Include="ApiClient\IApiClient.cs" /> <Compile Include="Configuration\BaseApplicationConfiguration.cs" /> + <Compile Include="Configuration\ManualLoginCategory.cs" /> <Compile Include="Configuration\ServerConfiguration.cs" /> <Compile Include="Dto\BaseItemPerson.cs" /> <Compile Include="Dto\ChapterInfoDto.cs" /> @@ -141,6 +142,7 @@ <Compile Include="Tasks\TaskTriggerInfo.cs" /> <Compile Include="Updates\PackageInfo.cs" /> <Compile Include="Updates\PackageVersionInfo.cs" /> + <Compile Include="Users\AuthenticationResult.cs" /> <Compile Include="Weather\WeatherUnits.cs" /> <Compile Include="Web\QueryStringDictionary.cs" /> <None Include="FodyWeavers.xml" /> diff --git a/MediaBrowser.Model/Users/AuthenticationResult.cs b/MediaBrowser.Model/Users/AuthenticationResult.cs new file mode 100644 index 000000000..ea6b57e06 --- /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 8aedca50f..0227f883a 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 dda9658d4..d9f28915a 100644 --- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs +++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs @@ -101,6 +101,11 @@ namespace MediaBrowser.Server.Implementations.Session /// <exception cref="System.ArgumentNullException">user</exception> 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 0281ee6ad..89aaec4b2 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 /// <returns>System.Object.</returns> 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 a90d2dcf8..4a0222139 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", @@ -1872,6 +1872,32 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) { /** * 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 * @param {String} password */ @@ -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 62dd59cc1..553c967f5 100644 --- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj +++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj @@ -81,6 +81,9 @@ </ItemGroup>
<ItemGroup>
<EmbeddedResource Include="ApiClient.js" />
+ <Content Include="dashboard-ui\allusersettings.html">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\boxsets.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -354,6 +357,9 @@ <Content Include="dashboard-ui\musicvideos.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <Content Include="dashboard-ui\scripts\allusersettings.js">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\scripts\alphapicker.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
diff --git a/MediaBrowser.WebDashboard/packages.config b/MediaBrowser.WebDashboard/packages.config index 439f8cb97..a8a737923 100644 --- a/MediaBrowser.WebDashboard/packages.config +++ b/MediaBrowser.WebDashboard/packages.config @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <packages> - <package id="MediaBrowser.ApiClient.Javascript" version="3.0.135" targetFramework="net45" /> + <package id="MediaBrowser.ApiClient.Javascript" version="3.0.138" targetFramework="net45" /> <package id="ServiceStack.Common" version="3.9.54" targetFramework="net45" /> <package id="ServiceStack.Text" version="3.9.54" targetFramework="net45" /> </packages>
\ No newline at end of file |
