< Summary - Jellyfin

Information
Class: Jellyfin.Api.Controllers.UserController
Assembly: Jellyfin.Api
File(s): /srv/git/jellyfin/Jellyfin.Api/Controllers/UserController.cs
Line coverage
70%
Covered lines: 40
Uncovered lines: 17
Coverable lines: 57
Total lines: 658
Line coverage: 70.1%
Branch coverage
55%
Covered branches: 11
Total branches: 20
Branch coverage: 55%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
GetUsers(...)100%11100%
GetPublicUsers()50%2.15266.66%
GetUserById(...)0%620%
AuthenticateWithQuickConnect(...)100%210%
GetCurrentUser()50%4.37471.42%
Get(...)66.66%16.391268.75%

File(s)

/srv/git/jellyfin/Jellyfin.Api/Controllers/UserController.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.ComponentModel.DataAnnotations;
 4using System.Linq;
 5using System.Threading.Tasks;
 6using Jellyfin.Api.Constants;
 7using Jellyfin.Api.Extensions;
 8using Jellyfin.Api.Helpers;
 9using Jellyfin.Api.Models.UserDtos;
 10using Jellyfin.Data.Enums;
 11using Jellyfin.Extensions;
 12using MediaBrowser.Common.Api;
 13using MediaBrowser.Common.Extensions;
 14using MediaBrowser.Common.Net;
 15using MediaBrowser.Controller.Authentication;
 16using MediaBrowser.Controller.Configuration;
 17using MediaBrowser.Controller.Devices;
 18using MediaBrowser.Controller.Library;
 19using MediaBrowser.Controller.Net;
 20using MediaBrowser.Controller.Playlists;
 21using MediaBrowser.Controller.QuickConnect;
 22using MediaBrowser.Controller.Session;
 23using MediaBrowser.Model.Configuration;
 24using MediaBrowser.Model.Dto;
 25using MediaBrowser.Model.Users;
 26using Microsoft.AspNetCore.Authorization;
 27using Microsoft.AspNetCore.Http;
 28using Microsoft.AspNetCore.Mvc;
 29using Microsoft.Extensions.Logging;
 30
 31namespace Jellyfin.Api.Controllers;
 32
 33/// <summary>
 34/// User controller.
 35/// </summary>
 36[Route("Users")]
 37public class UserController : BaseJellyfinApiController
 38{
 39    private readonly IUserManager _userManager;
 40    private readonly ISessionManager _sessionManager;
 41    private readonly INetworkManager _networkManager;
 42    private readonly IDeviceManager _deviceManager;
 43    private readonly IAuthorizationContext _authContext;
 44    private readonly IServerConfigurationManager _config;
 45    private readonly ILogger _logger;
 46    private readonly IQuickConnect _quickConnectManager;
 47    private readonly IPlaylistManager _playlistManager;
 48
 49    /// <summary>
 50    /// Initializes a new instance of the <see cref="UserController"/> class.
 51    /// </summary>
 52    /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
 53    /// <param name="sessionManager">Instance of the <see cref="ISessionManager"/> interface.</param>
 54    /// <param name="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param>
 55    /// <param name="deviceManager">Instance of the <see cref="IDeviceManager"/> interface.</param>
 56    /// <param name="authContext">Instance of the <see cref="IAuthorizationContext"/> interface.</param>
 57    /// <param name="config">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
 58    /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param>
 59    /// <param name="quickConnectManager">Instance of the <see cref="IQuickConnect"/> interface.</param>
 60    /// <param name="playlistManager">Instance of the <see cref="IPlaylistManager"/> interface.</param>
 5561    public UserController(
 5562        IUserManager userManager,
 5563        ISessionManager sessionManager,
 5564        INetworkManager networkManager,
 5565        IDeviceManager deviceManager,
 5566        IAuthorizationContext authContext,
 5567        IServerConfigurationManager config,
 5568        ILogger<UserController> logger,
 5569        IQuickConnect quickConnectManager,
 5570        IPlaylistManager playlistManager)
 71    {
 5572        _userManager = userManager;
 5573        _sessionManager = sessionManager;
 5574        _networkManager = networkManager;
 5575        _deviceManager = deviceManager;
 5576        _authContext = authContext;
 5577        _config = config;
 5578        _logger = logger;
 5579        _quickConnectManager = quickConnectManager;
 5580        _playlistManager = playlistManager;
 5581    }
 82
 83    /// <summary>
 84    /// Gets a list of users.
 85    /// </summary>
 86    /// <param name="isHidden">Optional filter by IsHidden=true or false.</param>
 87    /// <param name="isDisabled">Optional filter by IsDisabled=true or false.</param>
 88    /// <response code="200">Users returned.</response>
 89    /// <returns>An <see cref="IEnumerable{UserDto}"/> containing the users.</returns>
 90    [HttpGet]
 91    [Authorize]
 92    [ProducesResponseType(StatusCodes.Status200OK)]
 93    public ActionResult<IEnumerable<UserDto>> GetUsers(
 94        [FromQuery] bool? isHidden,
 95        [FromQuery] bool? isDisabled)
 96    {
 397        var users = Get(isHidden, isDisabled, false, false);
 398        return Ok(users);
 99    }
 100
 101    /// <summary>
 102    /// Gets a list of publicly visible users for display on a login screen.
 103    /// </summary>
 104    /// <response code="200">Public users returned.</response>
 105    /// <returns>An <see cref="IEnumerable{UserDto}"/> containing the public users.</returns>
 106    [HttpGet("Public")]
 107    [ProducesResponseType(StatusCodes.Status200OK)]
 108    public ActionResult<IEnumerable<UserDto>> GetPublicUsers()
 109    {
 110        // If the startup wizard hasn't been completed then just return all users
 1111        if (!_config.Configuration.IsStartupWizardCompleted)
 112        {
 1113            return Ok(Get(false, false, false, false));
 114        }
 115
 0116        return Ok(Get(false, false, true, true));
 117    }
 118
 119    /// <summary>
 120    /// Gets a user by Id.
 121    /// </summary>
 122    /// <param name="userId">The user id.</param>
 123    /// <response code="200">User returned.</response>
 124    /// <response code="404">User not found.</response>
 125    /// <returns>An <see cref="UserDto"/> with information about the user or a <see cref="NotFoundResult"/> if the user 
 126    [HttpGet("{userId}")]
 127    [Authorize(Policy = Policies.IgnoreParentalControl)]
 128    [ProducesResponseType(StatusCodes.Status200OK)]
 129    [ProducesResponseType(StatusCodes.Status404NotFound)]
 130    public ActionResult<UserDto> GetUserById([FromRoute, Required] Guid userId)
 131    {
 0132        var user = _userManager.GetUserById(userId);
 133
 0134        if (user is null)
 135        {
 0136            return NotFound("User not found");
 137        }
 138
 0139        var result = _userManager.GetUserDto(user, HttpContext.GetNormalizedRemoteIP().ToString());
 0140        return result;
 141    }
 142
 143    /// <summary>
 144    /// Deletes a user.
 145    /// </summary>
 146    /// <param name="userId">The user id.</param>
 147    /// <response code="204">User deleted.</response>
 148    /// <response code="404">User not found.</response>
 149    /// <returns>A <see cref="NoContentResult"/> indicating success or a <see cref="NotFoundResult"/> if the user was no
 150    [HttpDelete("{userId}")]
 151    [Authorize(Policy = Policies.RequiresElevation)]
 152    [ProducesResponseType(StatusCodes.Status204NoContent)]
 153    [ProducesResponseType(StatusCodes.Status404NotFound)]
 154    public async Task<ActionResult> DeleteUser([FromRoute, Required] Guid userId)
 155    {
 156        var user = _userManager.GetUserById(userId);
 157        if (user is null)
 158        {
 159            return NotFound();
 160        }
 161
 162        await _sessionManager.RevokeUserTokens(user.Id, null).ConfigureAwait(false);
 163        await _playlistManager.RemovePlaylistsAsync(userId).ConfigureAwait(false);
 164        await _userManager.DeleteUserAsync(userId).ConfigureAwait(false);
 165        return NoContent();
 166    }
 167
 168    /// <summary>
 169    /// Authenticates a user.
 170    /// </summary>
 171    /// <param name="userId">The user id.</param>
 172    /// <param name="pw">The password as plain text.</param>
 173    /// <response code="200">User authenticated.</response>
 174    /// <response code="403">Sha1-hashed password only is not allowed.</response>
 175    /// <response code="404">User not found.</response>
 176    /// <returns>A <see cref="Task"/> containing an <see cref="AuthenticationResult"/>.</returns>
 177    [HttpPost("{userId}/Authenticate")]
 178    [ProducesResponseType(StatusCodes.Status200OK)]
 179    [ProducesResponseType(StatusCodes.Status403Forbidden)]
 180    [ProducesResponseType(StatusCodes.Status404NotFound)]
 181    [ApiExplorerSettings(IgnoreApi = true)]
 182    [Obsolete("Authenticate with username instead")]
 183    public async Task<ActionResult<AuthenticationResult>> AuthenticateUser(
 184        [FromRoute, Required] Guid userId,
 185        [FromQuery, Required] string pw)
 186    {
 187        var user = _userManager.GetUserById(userId);
 188
 189        if (user is null)
 190        {
 191            return NotFound("User not found");
 192        }
 193
 194        AuthenticateUserByName request = new AuthenticateUserByName
 195        {
 196            Username = user.Username,
 197            Pw = pw
 198        };
 199        return await AuthenticateUserByName(request).ConfigureAwait(false);
 200    }
 201
 202    /// <summary>
 203    /// Authenticates a user by name.
 204    /// </summary>
 205    /// <param name="request">The <see cref="AuthenticateUserByName"/> request.</param>
 206    /// <response code="200">User authenticated.</response>
 207    /// <returns>A <see cref="Task"/> containing an <see cref="AuthenticationRequest"/> with information about the new s
 208    [HttpPost("AuthenticateByName")]
 209    [ProducesResponseType(StatusCodes.Status200OK)]
 210    public async Task<ActionResult<AuthenticationResult>> AuthenticateUserByName([FromBody, Required] AuthenticateUserBy
 211    {
 212        var auth = await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false);
 213
 214        try
 215        {
 216            var result = await _sessionManager.AuthenticateNewSession(new AuthenticationRequest
 217            {
 218                App = auth.Client,
 219                AppVersion = auth.Version,
 220                DeviceId = auth.DeviceId,
 221                DeviceName = auth.Device,
 222                Password = request.Pw,
 223                RemoteEndPoint = HttpContext.GetNormalizedRemoteIP().ToString(),
 224                Username = request.Username
 225            }).ConfigureAwait(false);
 226
 227            return result;
 228        }
 229        catch (SecurityException e)
 230        {
 231            // rethrow adding IP address to message
 232            throw new SecurityException($"[{HttpContext.GetNormalizedRemoteIP()}] {e.Message}", e);
 233        }
 234    }
 235
 236    /// <summary>
 237    /// Authenticates a user with quick connect.
 238    /// </summary>
 239    /// <param name="request">The <see cref="QuickConnectDto"/> request.</param>
 240    /// <response code="200">User authenticated.</response>
 241    /// <response code="400">Missing token.</response>
 242    /// <returns>A <see cref="Task"/> containing an <see cref="AuthenticationRequest"/> with information about the new s
 243    [HttpPost("AuthenticateWithQuickConnect")]
 244    [ProducesResponseType(StatusCodes.Status200OK)]
 245    public ActionResult<AuthenticationResult> AuthenticateWithQuickConnect([FromBody, Required] QuickConnectDto request)
 246    {
 247        try
 248        {
 0249            return _quickConnectManager.GetAuthorizedRequest(request.Secret);
 250        }
 0251        catch (SecurityException e)
 252        {
 253            // rethrow adding IP address to message
 0254            throw new SecurityException($"[{HttpContext.GetNormalizedRemoteIP()}] {e.Message}", e);
 255        }
 0256    }
 257
 258    /// <summary>
 259    /// Updates a user's password.
 260    /// </summary>
 261    /// <param name="userId">The user id.</param>
 262    /// <param name="request">The <see cref="UpdateUserPassword"/> request.</param>
 263    /// <response code="204">Password successfully reset.</response>
 264    /// <response code="403">User is not allowed to update the password.</response>
 265    /// <response code="404">User not found.</response>
 266    /// <returns>A <see cref="NoContentResult"/> indicating success or a <see cref="ForbidResult"/> or a <see cref="NotF
 267    [HttpPost("Password")]
 268    [Authorize]
 269    [ProducesResponseType(StatusCodes.Status204NoContent)]
 270    [ProducesResponseType(StatusCodes.Status403Forbidden)]
 271    [ProducesResponseType(StatusCodes.Status404NotFound)]
 272    public async Task<ActionResult> UpdateUserPassword(
 273        [FromQuery] Guid? userId,
 274        [FromBody, Required] UpdateUserPassword request)
 275    {
 276        var requestUserId = userId ?? User.GetUserId();
 277        var user = _userManager.GetUserById(requestUserId);
 278        if (user is null)
 279        {
 280            return NotFound();
 281        }
 282
 283        if (!RequestHelpers.AssertCanUpdateUser(User, user, true))
 284        {
 285            return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to update the password.");
 286        }
 287
 288        if (request.ResetPassword)
 289        {
 290            await _userManager.ResetPassword(user).ConfigureAwait(false);
 291        }
 292        else
 293        {
 294            if (!User.IsInRole(UserRoles.Administrator) || (userId.HasValue && User.GetUserId().Equals(userId.Value)))
 295            {
 296                var success = await _userManager.AuthenticateUser(
 297                    user.Username,
 298                    request.CurrentPw ?? string.Empty,
 299                    HttpContext.GetNormalizedRemoteIP().ToString(),
 300                    false).ConfigureAwait(false);
 301
 302                if (success is null)
 303                {
 304                    return StatusCode(StatusCodes.Status403Forbidden, "Invalid user or password entered.");
 305                }
 306            }
 307
 308            await _userManager.ChangePassword(user, request.NewPw ?? string.Empty).ConfigureAwait(false);
 309
 310            var currentToken = User.GetToken();
 311
 312            await _sessionManager.RevokeUserTokens(user.Id, currentToken).ConfigureAwait(false);
 313        }
 314
 315        return NoContent();
 316    }
 317
 318    /// <summary>
 319    /// Updates a user's password.
 320    /// </summary>
 321    /// <param name="userId">The user id.</param>
 322    /// <param name="request">The <see cref="UpdateUserPassword"/> request.</param>
 323    /// <response code="204">Password successfully reset.</response>
 324    /// <response code="403">User is not allowed to update the password.</response>
 325    /// <response code="404">User not found.</response>
 326    /// <returns>A <see cref="NoContentResult"/> indicating success or a <see cref="ForbidResult"/> or a <see cref="NotF
 327    [HttpPost("{userId}/Password")]
 328    [Authorize]
 329    [ProducesResponseType(StatusCodes.Status204NoContent)]
 330    [ProducesResponseType(StatusCodes.Status403Forbidden)]
 331    [ProducesResponseType(StatusCodes.Status404NotFound)]
 332    [Obsolete("Kept for backwards compatibility")]
 333    [ApiExplorerSettings(IgnoreApi = true)]
 334    public Task<ActionResult> UpdateUserPasswordLegacy(
 335        [FromRoute, Required] Guid userId,
 336        [FromBody, Required] UpdateUserPassword request)
 337        => UpdateUserPassword(userId, request);
 338
 339    /// <summary>
 340    /// Updates a user's easy password.
 341    /// </summary>
 342    /// <param name="userId">The user id.</param>
 343    /// <param name="request">The <see cref="UpdateUserEasyPassword"/> request.</param>
 344    /// <response code="204">Password successfully reset.</response>
 345    /// <response code="403">User is not allowed to update the password.</response>
 346    /// <response code="404">User not found.</response>
 347    /// <returns>A <see cref="NoContentResult"/> indicating success or a <see cref="ForbidResult"/> or a <see cref="NotF
 348    [HttpPost("{userId}/EasyPassword")]
 349    [Obsolete("Use Quick Connect instead")]
 350    [ApiExplorerSettings(IgnoreApi = true)]
 351    [Authorize]
 352    [ProducesResponseType(StatusCodes.Status204NoContent)]
 353    [ProducesResponseType(StatusCodes.Status403Forbidden)]
 354    [ProducesResponseType(StatusCodes.Status404NotFound)]
 355    public ActionResult UpdateUserEasyPassword(
 356        [FromRoute, Required] Guid userId,
 357        [FromBody, Required] UpdateUserEasyPassword request)
 358    {
 359        return Forbid();
 360    }
 361
 362    /// <summary>
 363    /// Updates a user.
 364    /// </summary>
 365    /// <param name="userId">The user id.</param>
 366    /// <param name="updateUser">The updated user model.</param>
 367    /// <response code="204">User updated.</response>
 368    /// <response code="400">User information was not supplied.</response>
 369    /// <response code="403">User update forbidden.</response>
 370    /// <returns>A <see cref="NoContentResult"/> indicating success or a <see cref="BadRequestResult"/> or a <see cref="
 371    [HttpPost]
 372    [Authorize]
 373    [ProducesResponseType(StatusCodes.Status204NoContent)]
 374    [ProducesResponseType(StatusCodes.Status400BadRequest)]
 375    [ProducesResponseType(StatusCodes.Status403Forbidden)]
 376    public async Task<ActionResult> UpdateUser(
 377        [FromQuery] Guid? userId,
 378        [FromBody, Required] UserDto updateUser)
 379    {
 380        var requestUserId = userId ?? User.GetUserId();
 381        var user = _userManager.GetUserById(requestUserId);
 382        if (user is null)
 383        {
 384            return NotFound();
 385        }
 386
 387        if (!RequestHelpers.AssertCanUpdateUser(User, user, true))
 388        {
 389            return StatusCode(StatusCodes.Status403Forbidden, "User update not allowed.");
 390        }
 391
 392        if (!string.Equals(user.Username, updateUser.Name, StringComparison.Ordinal))
 393        {
 394            await _userManager.RenameUser(user, updateUser.Name).ConfigureAwait(false);
 395        }
 396
 397        await _userManager.UpdateConfigurationAsync(requestUserId, updateUser.Configuration).ConfigureAwait(false);
 398
 399        return NoContent();
 400    }
 401
 402    /// <summary>
 403    /// Updates a user.
 404    /// </summary>
 405    /// <param name="userId">The user id.</param>
 406    /// <param name="updateUser">The updated user model.</param>
 407    /// <response code="204">User updated.</response>
 408    /// <response code="400">User information was not supplied.</response>
 409    /// <response code="403">User update forbidden.</response>
 410    /// <returns>A <see cref="NoContentResult"/> indicating success or a <see cref="BadRequestResult"/> or a <see cref="
 411    [HttpPost("{userId}")]
 412    [Authorize]
 413    [ProducesResponseType(StatusCodes.Status204NoContent)]
 414    [ProducesResponseType(StatusCodes.Status400BadRequest)]
 415    [ProducesResponseType(StatusCodes.Status403Forbidden)]
 416    [Obsolete("Kept for backwards compatibility")]
 417    [ApiExplorerSettings(IgnoreApi = true)]
 418    public Task<ActionResult> UpdateUserLegacy(
 419        [FromRoute, Required] Guid userId,
 420        [FromBody, Required] UserDto updateUser)
 421        => UpdateUser(userId, updateUser);
 422
 423    /// <summary>
 424    /// Updates a user policy.
 425    /// </summary>
 426    /// <param name="userId">The user id.</param>
 427    /// <param name="newPolicy">The new user policy.</param>
 428    /// <response code="204">User policy updated.</response>
 429    /// <response code="400">User policy was not supplied.</response>
 430    /// <response code="403">User policy update forbidden.</response>
 431    /// <returns>A <see cref="NoContentResult"/> indicating success or a <see cref="BadRequestResult"/> or a <see cref="
 432    [HttpPost("{userId}/Policy")]
 433    [Authorize(Policy = Policies.RequiresElevation)]
 434    [ProducesResponseType(StatusCodes.Status204NoContent)]
 435    [ProducesResponseType(StatusCodes.Status400BadRequest)]
 436    [ProducesResponseType(StatusCodes.Status403Forbidden)]
 437    public async Task<ActionResult> UpdateUserPolicy(
 438        [FromRoute, Required] Guid userId,
 439        [FromBody, Required] UserPolicy newPolicy)
 440    {
 441        var user = _userManager.GetUserById(userId);
 442        if (user is null)
 443        {
 444            return NotFound();
 445        }
 446
 447        // If removing admin access
 448        if (!newPolicy.IsAdministrator && user.HasPermission(PermissionKind.IsAdministrator))
 449        {
 450            if (_userManager.Users.Count(i => i.HasPermission(PermissionKind.IsAdministrator)) == 1)
 451            {
 452                return StatusCode(StatusCodes.Status403Forbidden, "There must be at least one user in the system with ad
 453            }
 454        }
 455
 456        // If disabling
 457        if (newPolicy.IsDisabled && user.HasPermission(PermissionKind.IsAdministrator))
 458        {
 459            return StatusCode(StatusCodes.Status403Forbidden, "Administrators cannot be disabled.");
 460        }
 461
 462        // If disabling
 463        if (newPolicy.IsDisabled && !user.HasPermission(PermissionKind.IsDisabled))
 464        {
 465            if (_userManager.Users.Count(i => !i.HasPermission(PermissionKind.IsDisabled)) == 1)
 466            {
 467                return StatusCode(StatusCodes.Status403Forbidden, "There must be at least one enabled user in the system
 468            }
 469
 470            var currentToken = User.GetToken();
 471            await _sessionManager.RevokeUserTokens(user.Id, currentToken).ConfigureAwait(false);
 472        }
 473
 474        await _userManager.UpdatePolicyAsync(userId, newPolicy).ConfigureAwait(false);
 475
 476        return NoContent();
 477    }
 478
 479    /// <summary>
 480    /// Updates a user configuration.
 481    /// </summary>
 482    /// <param name="userId">The user id.</param>
 483    /// <param name="userConfig">The new user configuration.</param>
 484    /// <response code="204">User configuration updated.</response>
 485    /// <response code="403">User configuration update forbidden.</response>
 486    /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
 487    [HttpPost("Configuration")]
 488    [Authorize]
 489    [ProducesResponseType(StatusCodes.Status204NoContent)]
 490    [ProducesResponseType(StatusCodes.Status403Forbidden)]
 491    public async Task<ActionResult> UpdateUserConfiguration(
 492        [FromQuery] Guid? userId,
 493        [FromBody, Required] UserConfiguration userConfig)
 494    {
 495        var requestUserId = userId ?? User.GetUserId();
 496        var user = _userManager.GetUserById(requestUserId);
 497        if (user is null)
 498        {
 499            return NotFound();
 500        }
 501
 502        if (!RequestHelpers.AssertCanUpdateUser(User, user, true))
 503        {
 504            return StatusCode(StatusCodes.Status403Forbidden, "User configuration update not allowed");
 505        }
 506
 507        await _userManager.UpdateConfigurationAsync(requestUserId, userConfig).ConfigureAwait(false);
 508
 509        return NoContent();
 510    }
 511
 512    /// <summary>
 513    /// Updates a user configuration.
 514    /// </summary>
 515    /// <param name="userId">The user id.</param>
 516    /// <param name="userConfig">The new user configuration.</param>
 517    /// <response code="204">User configuration updated.</response>
 518    /// <response code="403">User configuration update forbidden.</response>
 519    /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
 520    [HttpPost("{userId}/Configuration")]
 521    [Authorize]
 522    [Obsolete("Kept for backwards compatibility")]
 523    [ApiExplorerSettings(IgnoreApi = true)]
 524    [ProducesResponseType(StatusCodes.Status204NoContent)]
 525    [ProducesResponseType(StatusCodes.Status403Forbidden)]
 526    public Task<ActionResult> UpdateUserConfigurationLegacy(
 527        [FromRoute, Required] Guid userId,
 528        [FromBody, Required] UserConfiguration userConfig)
 529        => UpdateUserConfiguration(userId, userConfig);
 530
 531    /// <summary>
 532    /// Creates a user.
 533    /// </summary>
 534    /// <param name="request">The create user by name request body.</param>
 535    /// <response code="200">User created.</response>
 536    /// <returns>An <see cref="UserDto"/> of the new user.</returns>
 537    [HttpPost("New")]
 538    [Authorize(Policy = Policies.RequiresElevation)]
 539    [ProducesResponseType(StatusCodes.Status200OK)]
 540    public async Task<ActionResult<UserDto>> CreateUserByName([FromBody, Required] CreateUserByName request)
 541    {
 542        var newUser = await _userManager.CreateUserAsync(request.Name).ConfigureAwait(false);
 543
 544        // no need to authenticate password for new user
 545        if (request.Password is not null)
 546        {
 547            await _userManager.ChangePassword(newUser, request.Password).ConfigureAwait(false);
 548        }
 549
 550        var result = _userManager.GetUserDto(newUser, HttpContext.GetNormalizedRemoteIP().ToString());
 551
 552        return result;
 553    }
 554
 555    /// <summary>
 556    /// Initiates the forgot password process for a local user.
 557    /// </summary>
 558    /// <param name="forgotPasswordRequest">The forgot password request containing the entered username.</param>
 559    /// <response code="200">Password reset process started.</response>
 560    /// <returns>A <see cref="Task"/> containing a <see cref="ForgotPasswordResult"/>.</returns>
 561    [HttpPost("ForgotPassword")]
 562    [ProducesResponseType(StatusCodes.Status200OK)]
 563    public async Task<ActionResult<ForgotPasswordResult>> ForgotPassword([FromBody, Required] ForgotPasswordDto forgotPa
 564    {
 565        var ip = HttpContext.GetNormalizedRemoteIP();
 566        var isLocal = HttpContext.IsLocal()
 567                      || _networkManager.IsInLocalNetwork(ip);
 568
 569        if (!isLocal)
 570        {
 571            _logger.LogWarning("Password reset process initiated from outside the local network with IP: {IP}", ip);
 572        }
 573
 574        var result = await _userManager.StartForgotPasswordProcess(forgotPasswordRequest.EnteredUsername, isLocal).Confi
 575
 576        return result;
 577    }
 578
 579    /// <summary>
 580    /// Redeems a forgot password pin.
 581    /// </summary>
 582    /// <param name="forgotPasswordPinRequest">The forgot password pin request containing the entered pin.</param>
 583    /// <response code="200">Pin reset process started.</response>
 584    /// <returns>A <see cref="Task"/> containing a <see cref="PinRedeemResult"/>.</returns>
 585    [HttpPost("ForgotPassword/Pin")]
 586    [ProducesResponseType(StatusCodes.Status200OK)]
 587    public async Task<ActionResult<PinRedeemResult>> ForgotPasswordPin([FromBody, Required] ForgotPasswordPinDto forgotP
 588    {
 589        var result = await _userManager.RedeemPasswordResetPin(forgotPasswordPinRequest.Pin).ConfigureAwait(false);
 590        return result;
 591    }
 592
 593    /// <summary>
 594    /// Gets the user based on auth token.
 595    /// </summary>
 596    /// <response code="200">User returned.</response>
 597    /// <response code="400">Token is not owned by a user.</response>
 598    /// <returns>A <see cref="UserDto"/> for the authenticated user.</returns>
 599    [HttpGet("Me")]
 600    [Authorize]
 601    [ProducesResponseType(StatusCodes.Status200OK)]
 602    [ProducesResponseType(StatusCodes.Status400BadRequest)]
 603    public ActionResult<UserDto> GetCurrentUser()
 604    {
 21605        var userId = User.GetUserId();
 21606        if (userId.IsEmpty())
 607        {
 0608            return BadRequest();
 609        }
 610
 21611        var user = _userManager.GetUserById(userId);
 21612        if (user is null)
 613        {
 0614            return BadRequest();
 615        }
 616
 21617        return _userManager.GetUserDto(user);
 618    }
 619
 620    private IEnumerable<UserDto> Get(bool? isHidden, bool? isDisabled, bool filterByDevice, bool filterByNetwork)
 621    {
 4622        var users = _userManager.Users;
 623
 4624        if (isDisabled.HasValue)
 625        {
 1626            users = users.Where(i => i.HasPermission(PermissionKind.IsDisabled) == isDisabled.Value);
 627        }
 628
 4629        if (isHidden.HasValue)
 630        {
 1631            users = users.Where(i => i.HasPermission(PermissionKind.IsHidden) == isHidden.Value);
 632        }
 633
 4634        if (filterByDevice)
 635        {
 0636            var deviceId = User.GetDeviceId();
 637
 0638            if (!string.IsNullOrWhiteSpace(deviceId))
 639            {
 0640                users = users.Where(i => _deviceManager.CanAccessDevice(i, deviceId));
 641            }
 642        }
 643
 4644        if (filterByNetwork)
 645        {
 0646            if (!_networkManager.IsInLocalNetwork(HttpContext.GetNormalizedRemoteIP()))
 647            {
 0648                users = users.Where(i => i.HasPermission(PermissionKind.EnableRemoteAccess));
 649            }
 650        }
 651
 4652        var result = users
 4653            .OrderBy(u => u.Username)
 4654            .Select(i => _userManager.GetUserDto(i, HttpContext.GetNormalizedRemoteIP().ToString()));
 655
 4656        return result;
 657    }
 658}