2020-11-21 13:26:03 +00:00
using System ;
2020-06-17 19:08:58 +00:00
using System.Collections.Generic ;
using System.ComponentModel.DataAnnotations ;
using System.Linq ;
using System.Threading.Tasks ;
using Jellyfin.Api.Constants ;
2022-10-06 11:57:47 +00:00
using Jellyfin.Api.Extensions ;
2020-06-18 16:09:58 +00:00
using Jellyfin.Api.Helpers ;
using Jellyfin.Api.Models.UserDtos ;
2020-06-17 19:08:58 +00:00
using Jellyfin.Data.Enums ;
2024-01-17 15:51:39 +00:00
using Jellyfin.Extensions ;
2023-11-10 16:17:22 +00:00
using MediaBrowser.Common.Api ;
2020-09-10 12:16:41 +00:00
using MediaBrowser.Common.Extensions ;
2020-06-17 19:08:58 +00:00
using MediaBrowser.Common.Net ;
using MediaBrowser.Controller.Authentication ;
using MediaBrowser.Controller.Configuration ;
using MediaBrowser.Controller.Devices ;
using MediaBrowser.Controller.Library ;
using MediaBrowser.Controller.Net ;
2023-03-10 16:46:59 +00:00
using MediaBrowser.Controller.Playlists ;
2021-06-24 03:07:08 +00:00
using MediaBrowser.Controller.QuickConnect ;
2020-06-17 19:08:58 +00:00
using MediaBrowser.Controller.Session ;
2020-06-18 16:09:58 +00:00
using MediaBrowser.Model.Configuration ;
2020-06-17 19:08:58 +00:00
using MediaBrowser.Model.Dto ;
using MediaBrowser.Model.Users ;
using Microsoft.AspNetCore.Authorization ;
2020-06-18 16:09:58 +00:00
using Microsoft.AspNetCore.Http ;
2020-06-17 19:08:58 +00:00
using Microsoft.AspNetCore.Mvc ;
2021-03-23 16:16:10 +00:00
using Microsoft.Extensions.Logging ;
2020-06-17 19:08:58 +00:00
2023-01-31 11:18:10 +00:00
namespace Jellyfin.Api.Controllers ;
/// <summary>
/// User controller.
/// </summary>
[Route("Users")]
public class UserController : BaseJellyfinApiController
2020-06-17 19:08:58 +00:00
{
2023-01-31 11:18:10 +00:00
private readonly IUserManager _userManager ;
private readonly ISessionManager _sessionManager ;
private readonly INetworkManager _networkManager ;
private readonly IDeviceManager _deviceManager ;
private readonly IAuthorizationContext _authContext ;
private readonly IServerConfigurationManager _config ;
private readonly ILogger _logger ;
private readonly IQuickConnect _quickConnectManager ;
2023-03-10 16:46:59 +00:00
private readonly IPlaylistManager _playlistManager ;
2023-01-31 11:18:10 +00:00
/// <summary>
/// Initializes a new instance of the <see cref="UserController"/> class.
/// </summary>
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
/// <param name="sessionManager">Instance of the <see cref="ISessionManager"/> interface.</param>
/// <param name="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param>
/// <param name="deviceManager">Instance of the <see cref="IDeviceManager"/> interface.</param>
/// <param name="authContext">Instance of the <see cref="IAuthorizationContext"/> interface.</param>
/// <param name="config">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
/// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param>
/// <param name="quickConnectManager">Instance of the <see cref="IQuickConnect"/> interface.</param>
2023-03-10 16:46:59 +00:00
/// <param name="playlistManager">Instance of the <see cref="IPlaylistManager"/> interface.</param>
2023-01-31 11:18:10 +00:00
public UserController (
IUserManager userManager ,
ISessionManager sessionManager ,
INetworkManager networkManager ,
IDeviceManager deviceManager ,
IAuthorizationContext authContext ,
IServerConfigurationManager config ,
ILogger < UserController > logger ,
2023-03-10 16:46:59 +00:00
IQuickConnect quickConnectManager ,
IPlaylistManager playlistManager )
2023-01-31 11:18:10 +00:00
{
_userManager = userManager ;
_sessionManager = sessionManager ;
_networkManager = networkManager ;
_deviceManager = deviceManager ;
_authContext = authContext ;
_config = config ;
_logger = logger ;
_quickConnectManager = quickConnectManager ;
2023-03-10 16:46:59 +00:00
_playlistManager = playlistManager ;
2023-01-31 11:18:10 +00:00
}
/// <summary>
/// Gets a list of users.
/// </summary>
/// <param name="isHidden">Optional filter by IsHidden=true or false.</param>
/// <param name="isDisabled">Optional filter by IsDisabled=true or false.</param>
/// <response code="200">Users returned.</response>
/// <returns>An <see cref="IEnumerable{UserDto}"/> containing the users.</returns>
[HttpGet]
2023-02-08 22:55:26 +00:00
[Authorize]
2023-01-31 11:18:10 +00:00
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult < IEnumerable < UserDto > > GetUsers (
[FromQuery] bool? isHidden ,
[FromQuery] bool? isDisabled )
{
var users = Get ( isHidden , isDisabled , false , false ) ;
return Ok ( users ) ;
}
2020-06-17 19:08:58 +00:00
/// <summary>
2023-01-31 11:18:10 +00:00
/// Gets a list of publicly visible users for display on a login screen.
2020-06-17 19:08:58 +00:00
/// </summary>
2023-01-31 11:18:10 +00:00
/// <response code="200">Public users returned.</response>
/// <returns>An <see cref="IEnumerable{UserDto}"/> containing the public users.</returns>
[HttpGet("Public")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult < IEnumerable < UserDto > > GetPublicUsers ( )
2020-06-17 19:08:58 +00:00
{
2023-01-31 11:18:10 +00:00
// If the startup wizard hasn't been completed then just return all users
if ( ! _config . Configuration . IsStartupWizardCompleted )
2020-06-17 19:08:58 +00:00
{
2023-01-31 11:18:10 +00:00
return Ok ( Get ( false , false , false , false ) ) ;
2020-06-17 19:08:58 +00:00
}
2023-01-31 11:18:10 +00:00
return Ok ( Get ( false , false , true , true ) ) ;
}
/// <summary>
/// Gets a user by Id.
/// </summary>
/// <param name="userId">The user id.</param>
/// <response code="200">User returned.</response>
/// <response code="404">User not found.</response>
/// <returns>An <see cref="UserDto"/> with information about the user or a <see cref="NotFoundResult"/> if the user was not found.</returns>
[HttpGet("{userId}")]
[Authorize(Policy = Policies.IgnoreParentalControl)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult < UserDto > GetUserById ( [ FromRoute , Required ] Guid userId )
{
var user = _userManager . GetUserById ( userId ) ;
if ( user is null )
2020-06-17 19:08:58 +00:00
{
2023-01-31 11:18:10 +00:00
return NotFound ( "User not found" ) ;
2020-06-17 19:08:58 +00:00
}
2023-02-17 18:27:36 +00:00
var result = _userManager . GetUserDto ( user , HttpContext . GetNormalizedRemoteIP ( ) . ToString ( ) ) ;
2023-01-31 11:18:10 +00:00
return result ;
}
/// <summary>
/// Deletes a user.
/// </summary>
/// <param name="userId">The user id.</param>
/// <response code="204">User deleted.</response>
/// <response code="404">User not found.</response>
/// <returns>A <see cref="NoContentResult"/> indicating success or a <see cref="NotFoundResult"/> if the user was not found.</returns>
[HttpDelete("{userId}")]
[Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task < ActionResult > DeleteUser ( [ FromRoute , Required ] Guid userId )
{
var user = _userManager . GetUserById ( userId ) ;
2023-02-04 16:56:12 +00:00
if ( user is null )
{
return NotFound ( ) ;
}
2023-01-31 11:18:10 +00:00
await _sessionManager . RevokeUserTokens ( user . Id , null ) . ConfigureAwait ( false ) ;
2023-03-12 18:42:18 +00:00
await _playlistManager . RemovePlaylistsAsync ( userId ) . ConfigureAwait ( false ) ;
2023-01-31 11:18:10 +00:00
await _userManager . DeleteUserAsync ( userId ) . ConfigureAwait ( false ) ;
return NoContent ( ) ;
}
/// <summary>
/// Authenticates a user.
/// </summary>
/// <param name="userId">The user id.</param>
/// <param name="pw">The password as plain text.</param>
/// <response code="200">User authenticated.</response>
/// <response code="403">Sha1-hashed password only is not allowed.</response>
/// <response code="404">User not found.</response>
/// <returns>A <see cref="Task"/> containing an <see cref="AuthenticationResult"/>.</returns>
[HttpPost("{userId}/Authenticate")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
2024-03-03 20:51:31 +00:00
[ApiExplorerSettings(IgnoreApi = true)]
2023-01-31 11:18:10 +00:00
[Obsolete("Authenticate with username instead")]
public async Task < ActionResult < AuthenticationResult > > AuthenticateUser (
[FromRoute, Required] Guid userId ,
[FromQuery, Required] string pw )
{
var user = _userManager . GetUserById ( userId ) ;
2020-06-17 19:08:58 +00:00
2023-01-31 11:18:10 +00:00
if ( user is null )
{
return NotFound ( "User not found" ) ;
2020-06-17 19:08:58 +00:00
}
2023-01-31 11:18:10 +00:00
AuthenticateUserByName request = new AuthenticateUserByName
2020-06-17 19:08:58 +00:00
{
2023-01-31 11:18:10 +00:00
Username = user . Username ,
Pw = pw
} ;
return await AuthenticateUserByName ( request ) . ConfigureAwait ( false ) ;
}
/// <summary>
/// Authenticates a user by name.
/// </summary>
/// <param name="request">The <see cref="AuthenticateUserByName"/> request.</param>
/// <response code="200">User authenticated.</response>
/// <returns>A <see cref="Task"/> containing an <see cref="AuthenticationRequest"/> with information about the new session.</returns>
[HttpPost("AuthenticateByName")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task < ActionResult < AuthenticationResult > > AuthenticateUserByName ( [ FromBody , Required ] AuthenticateUserByName request )
{
var auth = await _authContext . GetAuthorizationInfo ( Request ) . ConfigureAwait ( false ) ;
2020-06-17 19:08:58 +00:00
2023-01-31 11:18:10 +00:00
try
{
var result = await _sessionManager . AuthenticateNewSession ( new AuthenticationRequest
2020-06-17 19:08:58 +00:00
{
2023-01-31 11:18:10 +00:00
App = auth . Client ,
AppVersion = auth . Version ,
DeviceId = auth . DeviceId ,
DeviceName = auth . Device ,
Password = request . Pw ,
2023-02-17 18:27:36 +00:00
RemoteEndPoint = HttpContext . GetNormalizedRemoteIP ( ) . ToString ( ) ,
2023-01-31 11:18:10 +00:00
Username = request . Username
} ) . ConfigureAwait ( false ) ;
2020-06-17 19:08:58 +00:00
2020-06-19 16:11:46 +00:00
return result ;
2020-06-17 19:08:58 +00:00
}
2023-01-31 11:18:10 +00:00
catch ( SecurityException e )
2020-06-17 19:08:58 +00:00
{
2023-01-31 11:18:10 +00:00
// rethrow adding IP address to message
2023-02-17 18:27:36 +00:00
throw new SecurityException ( $"[{HttpContext.GetNormalizedRemoteIP()}] {e.Message}" , e ) ;
2020-06-17 19:08:58 +00:00
}
2023-01-31 11:18:10 +00:00
}
2020-06-17 19:08:58 +00:00
2023-01-31 11:18:10 +00:00
/// <summary>
/// Authenticates a user with quick connect.
/// </summary>
/// <param name="request">The <see cref="QuickConnectDto"/> request.</param>
/// <response code="200">User authenticated.</response>
/// <response code="400">Missing token.</response>
/// <returns>A <see cref="Task"/> containing an <see cref="AuthenticationRequest"/> with information about the new session.</returns>
[HttpPost("AuthenticateWithQuickConnect")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult < AuthenticationResult > AuthenticateWithQuickConnect ( [ FromBody , Required ] QuickConnectDto request )
{
try
2020-06-17 19:08:58 +00:00
{
2023-01-31 11:18:10 +00:00
return _quickConnectManager . GetAuthorizedRequest ( request . Secret ) ;
2020-06-17 19:08:58 +00:00
}
2023-01-31 11:18:10 +00:00
catch ( SecurityException e )
2020-06-17 19:08:58 +00:00
{
2023-01-31 11:18:10 +00:00
// rethrow adding IP address to message
2023-02-17 18:27:36 +00:00
throw new SecurityException ( $"[{HttpContext.GetNormalizedRemoteIP()}] {e.Message}" , e ) ;
2023-01-31 11:18:10 +00:00
}
}
2020-06-17 19:08:58 +00:00
2023-01-31 11:18:10 +00:00
/// <summary>
/// Updates a user's password.
/// </summary>
/// <param name="userId">The user id.</param>
/// <param name="request">The <see cref="UpdateUserPassword"/> request.</param>
/// <response code="204">Password successfully reset.</response>
/// <response code="403">User is not allowed to update the password.</response>
/// <response code="404">User not found.</response>
/// <returns>A <see cref="NoContentResult"/> indicating success or a <see cref="ForbidResult"/> or a <see cref="NotFoundResult"/> on failure.</returns>
2024-03-03 20:51:31 +00:00
[HttpPost("Password")]
2023-02-08 22:55:26 +00:00
[Authorize]
2023-01-31 11:18:10 +00:00
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task < ActionResult > UpdateUserPassword (
2024-03-03 20:51:31 +00:00
[FromQuery] Guid ? userId ,
2023-01-31 11:18:10 +00:00
[FromBody, Required] UpdateUserPassword request )
{
2024-03-03 20:51:31 +00:00
var requestUserId = userId ? ? User . GetUserId ( ) ;
if ( ! RequestHelpers . AssertCanUpdateUser ( _userManager , User , requestUserId , true ) )
2023-01-31 11:18:10 +00:00
{
return StatusCode ( StatusCodes . Status403Forbidden , "User is not allowed to update the password." ) ;
2020-06-17 19:08:58 +00:00
}
2024-03-03 20:51:31 +00:00
var user = _userManager . GetUserById ( requestUserId ) ;
2023-01-31 11:18:10 +00:00
if ( user is null )
2020-08-13 20:35:04 +00:00
{
2023-01-31 11:18:10 +00:00
return NotFound ( "User not found" ) ;
2020-08-13 20:35:04 +00:00
}
2023-01-31 11:18:10 +00:00
if ( request . ResetPassword )
2020-06-17 19:08:58 +00:00
{
2023-01-31 11:18:10 +00:00
await _userManager . ResetPassword ( user ) . ConfigureAwait ( false ) ;
}
else
{
2024-03-03 20:51:31 +00:00
if ( ! User . IsInRole ( UserRoles . Administrator ) | | ( userId . HasValue & & User . GetUserId ( ) . Equals ( userId . Value ) ) )
2020-06-18 16:09:58 +00:00
{
2023-01-31 11:18:10 +00:00
var success = await _userManager . AuthenticateUser (
user . Username ,
2023-02-04 16:56:12 +00:00
request . CurrentPw ? ? string . Empty ,
request . CurrentPw ? ? string . Empty ,
2023-02-17 18:27:36 +00:00
HttpContext . GetNormalizedRemoteIP ( ) . ToString ( ) ,
2023-01-31 11:18:10 +00:00
false ) . ConfigureAwait ( false ) ;
if ( success is null )
{
return StatusCode ( StatusCodes . Status403Forbidden , "Invalid user or password entered." ) ;
}
2020-06-18 16:09:58 +00:00
}
2020-06-17 19:08:58 +00:00
2023-02-04 16:56:12 +00:00
await _userManager . ChangePassword ( user , request . NewPw ? ? string . Empty ) . ConfigureAwait ( false ) ;
2020-06-17 19:08:58 +00:00
2023-01-31 11:18:10 +00:00
var currentToken = User . GetToken ( ) ;
2020-06-17 19:08:58 +00:00
2023-01-31 11:18:10 +00:00
await _sessionManager . RevokeUserTokens ( user . Id , currentToken ) . ConfigureAwait ( false ) ;
}
2020-06-17 19:08:58 +00:00
2023-01-31 11:18:10 +00:00
return NoContent ( ) ;
}
2020-06-17 19:08:58 +00:00
2024-03-03 20:51:31 +00:00
/// <summary>
/// Updates a user's password.
/// </summary>
/// <param name="userId">The user id.</param>
/// <param name="request">The <see cref="UpdateUserPassword"/> request.</param>
/// <response code="204">Password successfully reset.</response>
/// <response code="403">User is not allowed to update the password.</response>
/// <response code="404">User not found.</response>
/// <returns>A <see cref="NoContentResult"/> indicating success or a <see cref="ForbidResult"/> or a <see cref="NotFoundResult"/> on failure.</returns>
[HttpPost("{userId}/Password")]
[Authorize]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[Obsolete("Kept for backwards compatibility")]
[ApiExplorerSettings(IgnoreApi = true)]
public Task < ActionResult > UpdateUserPasswordLegacy (
[FromRoute, Required] Guid userId ,
[FromBody, Required] UpdateUserPassword request )
= > UpdateUserPassword ( userId , request ) ;
2023-01-31 11:18:10 +00:00
/// <summary>
/// Updates a user's easy password.
/// </summary>
/// <param name="userId">The user id.</param>
/// <param name="request">The <see cref="UpdateUserEasyPassword"/> request.</param>
/// <response code="204">Password successfully reset.</response>
/// <response code="403">User is not allowed to update the password.</response>
/// <response code="404">User not found.</response>
/// <returns>A <see cref="NoContentResult"/> indicating success or a <see cref="ForbidResult"/> or a <see cref="NotFoundResult"/> on failure.</returns>
[HttpPost("{userId}/EasyPassword")]
2023-05-26 17:40:40 +00:00
[Obsolete("Use Quick Connect instead")]
2024-03-03 20:51:31 +00:00
[ApiExplorerSettings(IgnoreApi = true)]
2023-02-08 22:55:26 +00:00
[Authorize]
2023-01-31 11:18:10 +00:00
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
2023-05-26 17:40:40 +00:00
public ActionResult UpdateUserEasyPassword (
2023-01-31 11:18:10 +00:00
[FromRoute, Required] Guid userId ,
[FromBody, Required] UpdateUserEasyPassword request )
{
2023-05-26 17:45:40 +00:00
return Forbid ( ) ;
2023-01-31 11:18:10 +00:00
}
2020-06-17 19:08:58 +00:00
2023-01-31 11:18:10 +00:00
/// <summary>
/// Updates a user.
/// </summary>
/// <param name="userId">The user id.</param>
/// <param name="updateUser">The updated user model.</param>
/// <response code="204">User updated.</response>
/// <response code="400">User information was not supplied.</response>
/// <response code="403">User update forbidden.</response>
/// <returns>A <see cref="NoContentResult"/> indicating success or a <see cref="BadRequestResult"/> or a <see cref="ForbidResult"/> on failure.</returns>
2024-03-03 20:51:31 +00:00
[HttpPost]
2023-02-08 22:55:26 +00:00
[Authorize]
2023-01-31 11:18:10 +00:00
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task < ActionResult > UpdateUser (
2024-03-03 20:51:31 +00:00
[FromQuery] Guid ? userId ,
2023-01-31 11:18:10 +00:00
[FromBody, Required] UserDto updateUser )
{
2024-03-03 20:51:31 +00:00
var requestUserId = userId ? ? User . GetUserId ( ) ;
var user = _userManager . GetUserById ( requestUserId ) ;
2023-02-04 16:56:12 +00:00
if ( user is null )
{
return NotFound ( ) ;
}
2024-03-03 20:51:31 +00:00
if ( ! RequestHelpers . AssertCanUpdateUser ( _userManager , User , requestUserId , true ) )
2023-01-31 11:18:10 +00:00
{
return StatusCode ( StatusCodes . Status403Forbidden , "User update not allowed." ) ;
}
2020-06-17 19:08:58 +00:00
2023-01-31 11:18:10 +00:00
if ( ! string . Equals ( user . Username , updateUser . Name , StringComparison . Ordinal ) )
{
await _userManager . RenameUser ( user , updateUser . Name ) . ConfigureAwait ( false ) ;
2020-06-17 19:08:58 +00:00
}
2023-01-31 11:18:10 +00:00
await _userManager . UpdateConfigurationAsync ( user . Id , updateUser . Configuration ) . ConfigureAwait ( false ) ;
2020-06-18 16:09:58 +00:00
2023-01-31 11:18:10 +00:00
return NoContent ( ) ;
}
2024-03-03 20:51:31 +00:00
/// <summary>
/// Updates a user.
/// </summary>
/// <param name="userId">The user id.</param>
/// <param name="updateUser">The updated user model.</param>
/// <response code="204">User updated.</response>
/// <response code="400">User information was not supplied.</response>
/// <response code="403">User update forbidden.</response>
/// <returns>A <see cref="NoContentResult"/> indicating success or a <see cref="BadRequestResult"/> or a <see cref="ForbidResult"/> on failure.</returns>
[HttpPost("{userId}")]
[Authorize]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[Obsolete("Kept for backwards compatibility")]
[ApiExplorerSettings(IgnoreApi = true)]
public Task < ActionResult > UpdateUserLegacy (
[FromRoute, Required] Guid userId ,
[FromBody, Required] UserDto updateUser )
= > UpdateUser ( userId , updateUser ) ;
2023-01-31 11:18:10 +00:00
/// <summary>
/// Updates a user policy.
/// </summary>
/// <param name="userId">The user id.</param>
/// <param name="newPolicy">The new user policy.</param>
/// <response code="204">User policy updated.</response>
/// <response code="400">User policy was not supplied.</response>
/// <response code="403">User policy update forbidden.</response>
/// <returns>A <see cref="NoContentResult"/> indicating success or a <see cref="BadRequestResult"/> or a <see cref="ForbidResult"/> on failure..</returns>
[HttpPost("{userId}/Policy")]
[Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task < ActionResult > UpdateUserPolicy (
[FromRoute, Required] Guid userId ,
[FromBody, Required] UserPolicy newPolicy )
{
var user = _userManager . GetUserById ( userId ) ;
2023-02-04 16:56:12 +00:00
if ( user is null )
{
return NotFound ( ) ;
}
2020-06-18 16:09:58 +00:00
2023-01-31 11:18:10 +00:00
// If removing admin access
if ( ! newPolicy . IsAdministrator & & user . HasPermission ( PermissionKind . IsAdministrator ) )
{
if ( _userManager . Users . Count ( i = > i . HasPermission ( PermissionKind . IsAdministrator ) ) = = 1 )
2020-06-18 16:09:58 +00:00
{
2023-01-31 11:18:10 +00:00
return StatusCode ( StatusCodes . Status403Forbidden , "There must be at least one user in the system with administrative access." ) ;
2020-06-18 16:09:58 +00:00
}
2020-06-17 19:08:58 +00:00
}
2023-01-31 11:18:10 +00:00
// If disabling
if ( newPolicy . IsDisabled & & user . HasPermission ( PermissionKind . IsAdministrator ) )
2020-06-17 19:08:58 +00:00
{
2023-01-31 11:18:10 +00:00
return StatusCode ( StatusCodes . Status403Forbidden , "Administrators cannot be disabled." ) ;
}
2020-06-18 16:09:58 +00:00
2023-01-31 11:18:10 +00:00
// If disabling
if ( newPolicy . IsDisabled & & ! user . HasPermission ( PermissionKind . IsDisabled ) )
{
if ( _userManager . Users . Count ( i = > ! i . HasPermission ( PermissionKind . IsDisabled ) ) = = 1 )
2020-06-18 16:09:58 +00:00
{
2023-01-31 11:18:10 +00:00
return StatusCode ( StatusCodes . Status403Forbidden , "There must be at least one enabled user in the system." ) ;
2020-06-18 16:09:58 +00:00
}
2023-01-31 11:18:10 +00:00
var currentToken = User . GetToken ( ) ;
await _sessionManager . RevokeUserTokens ( user . Id , currentToken ) . ConfigureAwait ( false ) ;
}
2020-06-18 16:09:58 +00:00
2023-01-31 11:18:10 +00:00
await _userManager . UpdatePolicyAsync ( userId , newPolicy ) . ConfigureAwait ( false ) ;
2020-06-18 16:09:58 +00:00
2023-01-31 11:18:10 +00:00
return NoContent ( ) ;
}
2020-06-18 16:09:58 +00:00
2023-01-31 11:18:10 +00:00
/// <summary>
/// Updates a user configuration.
/// </summary>
/// <param name="userId">The user id.</param>
/// <param name="userConfig">The new user configuration.</param>
/// <response code="204">User configuration updated.</response>
/// <response code="403">User configuration update forbidden.</response>
/// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
2024-03-03 20:51:31 +00:00
[HttpPost("Configuration")]
2023-02-08 22:55:26 +00:00
[Authorize]
2023-01-31 11:18:10 +00:00
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task < ActionResult > UpdateUserConfiguration (
2024-03-03 20:51:31 +00:00
[FromQuery] Guid ? userId ,
2023-01-31 11:18:10 +00:00
[FromBody, Required] UserConfiguration userConfig )
{
2024-03-03 20:51:31 +00:00
var requestUserId = userId ? ? User . GetUserId ( ) ;
if ( ! RequestHelpers . AssertCanUpdateUser ( _userManager , User , requestUserId , true ) )
2023-01-31 11:18:10 +00:00
{
return StatusCode ( StatusCodes . Status403Forbidden , "User configuration update not allowed" ) ;
2020-06-17 19:08:58 +00:00
}
2024-03-03 20:51:31 +00:00
await _userManager . UpdateConfigurationAsync ( requestUserId , userConfig ) . ConfigureAwait ( false ) ;
2020-06-18 16:09:58 +00:00
2023-01-31 11:18:10 +00:00
return NoContent ( ) ;
}
2020-06-18 16:09:58 +00:00
2024-03-03 20:51:31 +00:00
/// <summary>
/// Updates a user configuration.
/// </summary>
/// <param name="userId">The user id.</param>
/// <param name="userConfig">The new user configuration.</param>
/// <response code="204">User configuration updated.</response>
/// <response code="403">User configuration update forbidden.</response>
/// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
[HttpPost("{userId}/Configuration")]
[Authorize]
[Obsolete("Kept for backwards compatibility")]
[ApiExplorerSettings(IgnoreApi = true)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public Task < ActionResult > UpdateUserConfigurationLegacy (
[FromRoute, Required] Guid userId ,
[FromBody, Required] UserConfiguration userConfig )
= > UpdateUserConfiguration ( userId , userConfig ) ;
2023-01-31 11:18:10 +00:00
/// <summary>
/// Creates a user.
/// </summary>
/// <param name="request">The create user by name request body.</param>
/// <response code="200">User created.</response>
/// <returns>An <see cref="UserDto"/> of the new user.</returns>
[HttpPost("New")]
[Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task < ActionResult < UserDto > > CreateUserByName ( [ FromBody , Required ] CreateUserByName request )
{
var newUser = await _userManager . CreateUserAsync ( request . Name ) . ConfigureAwait ( false ) ;
2020-06-17 19:08:58 +00:00
2023-01-31 11:18:10 +00:00
// no need to authenticate password for new user
if ( request . Password is not null )
2020-06-17 19:08:58 +00:00
{
2023-01-31 11:18:10 +00:00
await _userManager . ChangePassword ( newUser , request . Password ) . ConfigureAwait ( false ) ;
}
2020-06-17 19:08:58 +00:00
2023-02-17 18:27:36 +00:00
var result = _userManager . GetUserDto ( newUser , HttpContext . GetNormalizedRemoteIP ( ) . ToString ( ) ) ;
2020-06-17 19:08:58 +00:00
2023-01-31 11:18:10 +00:00
return result ;
}
2020-06-17 19:08:58 +00:00
2023-01-31 11:18:10 +00:00
/// <summary>
/// Initiates the forgot password process for a local user.
/// </summary>
/// <param name="forgotPasswordRequest">The forgot password request containing the entered username.</param>
/// <response code="200">Password reset process started.</response>
/// <returns>A <see cref="Task"/> containing a <see cref="ForgotPasswordResult"/>.</returns>
[HttpPost("ForgotPassword")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task < ActionResult < ForgotPasswordResult > > ForgotPassword ( [ FromBody , Required ] ForgotPasswordDto forgotPasswordRequest )
{
2023-02-17 18:27:36 +00:00
var ip = HttpContext . GetNormalizedRemoteIP ( ) ;
2023-01-31 11:18:10 +00:00
var isLocal = HttpContext . IsLocal ( )
| | _networkManager . IsInLocalNetwork ( ip ) ;
2020-06-17 19:08:58 +00:00
2023-07-31 19:49:51 +00:00
if ( ! isLocal )
2020-06-17 19:08:58 +00:00
{
2023-01-31 11:18:10 +00:00
_logger . LogWarning ( "Password reset process initiated from outside the local network with IP: {IP}" , ip ) ;
}
2021-03-23 16:16:10 +00:00
2023-01-31 11:18:10 +00:00
var result = await _userManager . StartForgotPasswordProcess ( forgotPasswordRequest . EnteredUsername , isLocal ) . ConfigureAwait ( false ) ;
2020-06-17 19:08:58 +00:00
2023-01-31 11:18:10 +00:00
return result ;
}
2020-06-17 19:08:58 +00:00
2023-01-31 11:18:10 +00:00
/// <summary>
/// Redeems a forgot password pin.
/// </summary>
/// <param name="forgotPasswordPinRequest">The forgot password pin request containing the entered pin.</param>
/// <response code="200">Pin reset process started.</response>
/// <returns>A <see cref="Task"/> containing a <see cref="PinRedeemResult"/>.</returns>
[HttpPost("ForgotPassword/Pin")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task < ActionResult < PinRedeemResult > > ForgotPasswordPin ( [ FromBody , Required ] ForgotPasswordPinDto forgotPasswordPinRequest )
{
var result = await _userManager . RedeemPasswordResetPin ( forgotPasswordPinRequest . Pin ) . ConfigureAwait ( false ) ;
return result ;
}
2020-06-17 19:08:58 +00:00
2023-01-31 11:18:10 +00:00
/// <summary>
/// Gets the user based on auth token.
/// </summary>
/// <response code="200">User returned.</response>
/// <response code="400">Token is not owned by a user.</response>
/// <returns>A <see cref="UserDto"/> for the authenticated user.</returns>
[HttpGet("Me")]
2023-02-08 22:55:26 +00:00
[Authorize]
2023-01-31 11:18:10 +00:00
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public ActionResult < UserDto > GetCurrentUser ( )
{
var userId = User . GetUserId ( ) ;
2024-01-17 15:51:39 +00:00
if ( userId . IsEmpty ( ) )
2020-06-17 19:08:58 +00:00
{
2023-01-31 11:18:10 +00:00
return BadRequest ( ) ;
2020-06-17 19:08:58 +00:00
}
2023-01-31 11:18:10 +00:00
var user = _userManager . GetUserById ( userId ) ;
if ( user is null )
2020-11-05 20:23:22 +00:00
{
2023-01-31 11:18:10 +00:00
return BadRequest ( ) ;
}
2020-11-05 20:23:22 +00:00
2023-01-31 11:18:10 +00:00
return _userManager . GetUserDto ( user ) ;
}
2020-11-05 20:23:22 +00:00
2023-01-31 11:18:10 +00:00
private IEnumerable < UserDto > Get ( bool? isHidden , bool? isDisabled , bool filterByDevice , bool filterByNetwork )
{
var users = _userManager . Users ;
2020-11-05 20:23:22 +00:00
2023-01-31 11:18:10 +00:00
if ( isDisabled . HasValue )
2020-06-17 19:08:58 +00:00
{
2023-01-31 11:18:10 +00:00
users = users . Where ( i = > i . HasPermission ( PermissionKind . IsDisabled ) = = isDisabled . Value ) ;
}
2020-06-17 19:08:58 +00:00
2023-01-31 11:18:10 +00:00
if ( isHidden . HasValue )
{
users = users . Where ( i = > i . HasPermission ( PermissionKind . IsHidden ) = = isHidden . Value ) ;
}
2020-06-17 19:08:58 +00:00
2023-01-31 11:18:10 +00:00
if ( filterByDevice )
{
var deviceId = User . GetDeviceId ( ) ;
2020-06-17 19:08:58 +00:00
2023-01-31 11:18:10 +00:00
if ( ! string . IsNullOrWhiteSpace ( deviceId ) )
2020-06-17 19:08:58 +00:00
{
2023-01-31 11:18:10 +00:00
users = users . Where ( i = > _deviceManager . CanAccessDevice ( i , deviceId ) ) ;
2020-06-17 19:08:58 +00:00
}
2023-01-31 11:18:10 +00:00
}
2020-06-17 19:08:58 +00:00
2023-01-31 11:18:10 +00:00
if ( filterByNetwork )
{
2023-02-17 18:27:36 +00:00
if ( ! _networkManager . IsInLocalNetwork ( HttpContext . GetNormalizedRemoteIP ( ) ) )
2020-06-17 19:08:58 +00:00
{
2023-01-31 11:18:10 +00:00
users = users . Where ( i = > i . HasPermission ( PermissionKind . EnableRemoteAccess ) ) ;
2020-06-17 19:08:58 +00:00
}
2023-01-31 11:18:10 +00:00
}
2020-06-17 19:08:58 +00:00
2023-01-31 11:18:10 +00:00
var result = users
. OrderBy ( u = > u . Username )
2023-02-17 18:27:36 +00:00
. Select ( i = > _userManager . GetUserDto ( i , HttpContext . GetNormalizedRemoteIP ( ) . ToString ( ) ) ) ;
2020-06-17 19:08:58 +00:00
2023-01-31 11:18:10 +00:00
return result ;
2020-06-17 19:08:58 +00:00
}
}