using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Users; using ServiceStack; using ServiceStack.Text.Controller; using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Threading.Tasks; namespace MediaBrowser.Api { /// /// Class GetUsers /// [Route("/Users", "GET", Summary = "Gets a list of users")] [Authenticated] 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; } } [Route("/Users/Public", "GET", Summary = "Gets a list of publicly visible users for display on a login screen.")] public class GetPublicUsers : IReturn> { } /// /// Class GetUser /// [Route("/Users/{Id}", "GET", Summary = "Gets a user by Id")] [Authenticated] public class GetUser : IReturn { /// /// Gets or sets the id. /// /// The id. [ApiMember(Name = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] public Guid Id { get; set; } } /// /// Class DeleteUser /// [Route("/Users/{Id}", "DELETE", Summary = "Deletes a user")] [Authenticated] public class DeleteUser : IReturnVoid { /// /// Gets or sets the id. /// /// The id. [ApiMember(Name = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] public Guid Id { get; set; } } /// /// Class AuthenticateUser /// [Route("/Users/{Id}/Authenticate", "POST", Summary = "Authenticates a user")] public class AuthenticateUser : IReturn { /// /// Gets or sets the id. /// /// The id. [ApiMember(Name = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] public Guid Id { 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 AuthenticateUser /// [Route("/Users/AuthenticateByName", "POST", Summary = "Authenticates a user")] public class AuthenticateUserByName : IReturn { /// /// Gets or sets the id. /// /// The id. [ApiMember(Name = "Username", IsRequired = true, DataType = "string", ParameterType = "body", Verb = "POST")] public string Username { 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 /// [Route("/Users/{Id}/Password", "POST", Summary = "Updates a user's password")] [Authenticated] public class UpdateUserPassword : IReturnVoid { /// /// Gets or sets the id. /// /// The id. public Guid Id { get; set; } /// /// Gets or sets the password. /// /// The password. public string CurrentPassword { 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 /// [Route("/Users/{Id}", "POST", Summary = "Updates a user")] [Authenticated] public class UpdateUser : UserDto, IReturnVoid { } /// /// Class CreateUser /// [Route("/Users", "POST", Summary = "Creates a user")] [Authenticated] public class CreateUser : UserDto, IReturn { } /// /// Class UsersService /// public class UserService : BaseApiService, IHasAuthorization { /// /// The _user manager /// private readonly IUserManager _userManager; private readonly IDtoService _dtoService; private readonly ISessionManager _sessionMananger; private readonly IServerConfigurationManager _config; private readonly INetworkManager _networkManager; public IAuthorizationContext AuthorizationContext { get; set; } /// /// Initializes a new instance of the class. /// /// The user manager. /// The dto service. /// The session mananger. /// xmlSerializer public UserService(IUserManager userManager, IDtoService dtoService, ISessionManager sessionMananger, IServerConfigurationManager config, INetworkManager networkManager) { _userManager = userManager; _dtoService = dtoService; _sessionMananger = sessionMananger; _config = config; _networkManager = networkManager; } public object Get(GetPublicUsers request) { var authInfo = AuthorizationContext.GetAuthorizationInfo(Request); var isDashboard = string.Equals(authInfo.Client, "Dashboard", StringComparison.OrdinalIgnoreCase); if ((Request.IsLocal && isDashboard) || !_config.Configuration.IsStartupWizardCompleted) { return Get(new GetUsers { IsDisabled = false }); } // TODO: Uncomment this once all clients can handle an empty user list. return Get(new GetUsers { IsHidden = false, IsDisabled = false }); //// TODO: Add or is authenticated //if (Request.IsLocal || IsInLocalNetwork(Request.RemoteIp)) //{ // return Get(new GetUsers // { // IsHidden = false, // IsDisabled = false // }); //} //// Return empty when external //return ToOptimizedResult(new List()); } private bool IsInLocalNetwork(string remoteEndpoint) { if (string.IsNullOrWhiteSpace(remoteEndpoint)) { throw new ArgumentNullException("remoteEndpoint"); } IPAddress address; if (!IPAddress.TryParse(remoteEndpoint, out address)) { return true; } const int lengthMatch = 4; if (remoteEndpoint.Length >= lengthMatch) { var prefix = remoteEndpoint.Substring(0, lengthMatch); if (_networkManager.GetLocalIpAddresses() .Any(i => i.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))) { return true; } } // Private address space: // http://en.wikipedia.org/wiki/Private_network return // If url was requested with computer name, we may see this remoteEndpoint.IndexOf("::", StringComparison.OrdinalIgnoreCase) != -1 || remoteEndpoint.StartsWith("10.", StringComparison.OrdinalIgnoreCase) || remoteEndpoint.StartsWith("192.", StringComparison.OrdinalIgnoreCase) || remoteEndpoint.StartsWith("172.", StringComparison.OrdinalIgnoreCase) || remoteEndpoint.StartsWith("169.", StringComparison.OrdinalIgnoreCase); } /// /// Gets the specified request. /// /// The request. /// System.Object. public object Get(GetUsers request) { 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 result = users .OrderBy(u => u.Name) .Select(_dtoService.GetUserDto) .ToList(); return ToOptimizedSerializedResultUsingCache(result); } /// /// Gets the specified request. /// /// The request. /// System.Object. public object Get(GetUser request) { var user = _userManager.GetUserById(request.Id); if (user == null) { throw new ResourceNotFoundException("User not found"); } var result = _dtoService.GetUserDto(user); return ToOptimizedSerializedResultUsingCache(result); } /// /// Deletes the specified request. /// /// The request. public void Delete(DeleteUser request) { var user = _userManager.GetUserById(request.Id); if (user == null) { throw new ResourceNotFoundException("User not found"); } var revokeTask = _sessionMananger.RevokeUserTokens(user.Id.ToString("N")); Task.WaitAll(revokeTask); var task = _userManager.DeleteUser(user); Task.WaitAll(task); } /// /// Posts the specified request. /// /// The request. public object Post(AuthenticateUser request) { var user = _userManager.GetUserById(request.Id); if (user == null) { throw new ResourceNotFoundException("User not found"); } return Post(new AuthenticateUserByName { Username = user.Name, Password = request.Password }); } public object Post(AuthenticateUserByName request) { var auth = AuthorizationContext.GetAuthorizationInfo(Request); if (string.IsNullOrWhiteSpace(auth.Client)) { auth.Client = "Unknown app"; } if (string.IsNullOrWhiteSpace(auth.Device)) { auth.Device = "Unknown device"; } if (string.IsNullOrWhiteSpace(auth.Version)) { auth.Version = "Unknown version"; } if (string.IsNullOrWhiteSpace(auth.DeviceId)) { auth.DeviceId = "Unknown device id"; } var result = _sessionMananger.AuthenticateNewSession(request.Username, request.Password, auth.Client, auth.Version, auth.DeviceId, auth.Device, Request.RemoteIp, Request.IsLocal).Result; return ToOptimizedResult(result); } /// /// Posts the specified request. /// /// The request. public void Post(UpdateUserPassword request) { var user = _userManager.GetUserById(request.Id); if (user == null) { throw new ResourceNotFoundException("User not found"); } if (request.ResetPassword) { var task = _userManager.ResetPassword(user); Task.WaitAll(task); } else { var success = _userManager.AuthenticateUser(user.Name, request.CurrentPassword).Result; if (!success) { throw new UnauthorizedAccessException("Invalid user or password entered."); } var task = _userManager.ChangePassword(user, request.NewPassword); Task.WaitAll(task); } } /// /// Posts the specified request. /// /// 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 pathInfo = PathInfo.Parse(Request.PathInfo); var id = new Guid(pathInfo.GetArgumentValue(1)); var dtoUser = request; var user = _userManager.GetUserById(id); // If removing admin access if (!dtoUser.Configuration.IsAdministrator && user.Configuration.IsAdministrator) { if (_userManager.Users.Count(i => i.Configuration.IsAdministrator) == 1) { throw new ArgumentException("There must be at least one user in the system with administrative access."); } } // If disabling if (dtoUser.Configuration.IsDisabled && user.Configuration.IsAdministrator) { throw new ArgumentException("Administrators cannot be disabled."); } // If disabling 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 revokeTask = _sessionMananger.RevokeUserTokens(user.Id.ToString("N")); Task.WaitAll(revokeTask); } var task = user.Name.Equals(dtoUser.Name, StringComparison.Ordinal) ? _userManager.UpdateUser(user) : _userManager.RenameUser(user, dtoUser.Name); Task.WaitAll(task); user.UpdateConfiguration(dtoUser.Configuration); } /// /// Posts the specified request. /// /// The request. /// System.Object. public object Post(CreateUser request) { var dtoUser = request; var newUser = _userManager.CreateUser(dtoUser.Name).Result; newUser.UpdateConfiguration(dtoUser.Configuration); var result = _dtoService.GetUserDto(newUser); return ToOptimizedResult(result); } } }