using AutoMapper;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System.Linq;
using System.Threading.Tasks;
using Timeline.Auth;
using Timeline.Helpers;
using Timeline.Models;
using Timeline.Models.Http;
using Timeline.Models.Validation;
using Timeline.Services;
using Timeline.Services.Exceptions;
using static Timeline.Resources.Controllers.UserController;
using static Timeline.Resources.Messages;
namespace Timeline.Controllers
{
///
/// Operations about users.
///
[ApiController]
[ProducesErrorResponseType(typeof(CommonResponse))]
public class UserController : Controller
{
private readonly ILogger _logger;
private readonly IUserService _userService;
private readonly IUserCredentialService _userCredentialService;
private readonly IUserPermissionService _userPermissionService;
private readonly IUserDeleteService _userDeleteService;
private readonly IMapper _mapper;
///
public UserController(ILogger logger, IUserService userService, IUserCredentialService userCredentialService, IUserPermissionService userPermissionService, IUserDeleteService userDeleteService, IMapper mapper)
{
_logger = logger;
_userService = userService;
_userCredentialService = userCredentialService;
_userPermissionService = userPermissionService;
_userDeleteService = userDeleteService;
_mapper = mapper;
}
private HttpUser ConvertToUserInfo(UserInfo user) => _mapper.Map(user);
private bool UserHasUserManagementPermission => this.UserHasPermission(UserPermission.UserManagement);
///
/// Get all users.
///
/// All user list.
[HttpGet("users")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task> List()
{
var users = await _userService.GetUsers();
var result = users.Select(u => ConvertToUserInfo(u)).ToArray();
return Ok(result);
}
///
/// Get a user's info.
///
/// Username of the user.
/// User info.
[HttpGet("users/{username}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task> Get([FromRoute][Username] string username)
{
try
{
var id = await _userService.GetUserIdByUsername(username);
var user = await _userService.GetUser(id);
return Ok(ConvertToUserInfo(user));
}
catch (UserNotExistException e)
{
_logger.LogInformation(e, Log.Format(LogGetUserNotExist, ("Username", username)));
return NotFound(ErrorResponse.UserCommon.NotExist());
}
}
///
/// Change a user's property.
///
///
/// Username of the user to change.
/// The new user info.
[HttpPatch("users/{username}"), Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task> Patch([FromBody] HttpUserPatchRequest body, [FromRoute][Username] string username)
{
if (UserHasUserManagementPermission)
{
try
{
var id = await _userService.GetUserIdByUsername(username);
var user = await _userService.ModifyUser(id, _mapper.Map(body));
return Ok(ConvertToUserInfo(user));
}
catch (UserNotExistException e)
{
_logger.LogInformation(e, Log.Format(LogPatchUserNotExist, ("Username", username)));
return NotFound(ErrorResponse.UserCommon.NotExist());
}
catch (EntityAlreadyExistException e) when (e.EntityName == EntityNames.User)
{
return BadRequest(ErrorResponse.UserController.UsernameConflict());
}
}
else
{
if (User.Identity!.Name != username)
return StatusCode(StatusCodes.Status403Forbidden,
ErrorResponse.Common.CustomMessage_Forbid(Common_Forbid_NotSelf));
if (body.Username != null)
return StatusCode(StatusCodes.Status403Forbidden,
ErrorResponse.Common.CustomMessage_Forbid(UserController_Patch_Forbid_Username));
if (body.Password != null)
return StatusCode(StatusCodes.Status403Forbidden,
ErrorResponse.Common.CustomMessage_Forbid(UserController_Patch_Forbid_Password));
var user = await _userService.ModifyUser(this.GetUserId(), _mapper.Map(body));
return Ok(ConvertToUserInfo(user));
}
}
///
/// Delete a user and all his related data. You have to be administrator.
///
/// Username of the user to delete.
/// Info of deletion.
[HttpDelete("users/{username}"), PermissionAuthorize(UserPermission.UserManagement)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task> Delete([FromRoute][Username] string username)
{
try
{
var delete = await _userDeleteService.DeleteUser(username);
if (delete)
return Ok(CommonDeleteResponse.Delete());
else
return Ok(CommonDeleteResponse.NotExist());
}
catch (InvalidOperationOnRootUserException)
{
return BadRequest(ErrorResponse.UserController.Delete_RootUser());
}
}
///
/// Create a new user. You have to be administrator.
///
/// The new user's info.
[HttpPost("userop/createuser"), PermissionAuthorize(UserPermission.UserManagement)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task> CreateUser([FromBody] HttpCreateUserRequest body)
{
try
{
var user = await _userService.CreateUser(body.Username, body.Password);
return Ok(ConvertToUserInfo(user));
}
catch (EntityAlreadyExistException e) when (e.EntityName == EntityNames.User)
{
return BadRequest(ErrorResponse.UserController.UsernameConflict());
}
}
///
/// Change password with old password.
///
[HttpPost("userop/changepassword"), Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
public async Task ChangePassword([FromBody] HttpChangePasswordRequest request)
{
try
{
await _userCredentialService.ChangePassword(this.GetUserId(), request.OldPassword, request.NewPassword);
return Ok();
}
catch (BadPasswordException e)
{
_logger.LogInformation(e, Log.Format(LogChangePasswordBadPassword,
("Username", User.Identity!.Name), ("Old Password", request.OldPassword)));
return BadRequest(ErrorResponse.UserController.ChangePassword_BadOldPassword());
}
// User can't be non-existent or the token is bad.
}
[HttpPut("users/{username}/permissions/{permission}"), PermissionAuthorize(UserPermission.UserManagement)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task PutUserPermission([FromRoute][Username] string username, [FromRoute] UserPermission permission)
{
try
{
var id = await _userService.GetUserIdByUsername(username);
await _userPermissionService.AddPermissionToUserAsync(id, permission);
return Ok();
}
catch (UserNotExistException)
{
return NotFound(ErrorResponse.UserCommon.NotExist());
}
catch (InvalidOperationOnRootUserException)
{
return BadRequest(ErrorResponse.UserController.ChangePermission_RootUser());
}
}
[HttpDelete("users/{username}/permissions/{permission}"), PermissionAuthorize(UserPermission.UserManagement)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task DeleteUserPermission([FromRoute][Username] string username, [FromRoute] UserPermission permission)
{
try
{
var id = await _userService.GetUserIdByUsername(username);
await _userPermissionService.RemovePermissionFromUserAsync(id, permission);
return Ok();
}
catch (UserNotExistException)
{
return NotFound(ErrorResponse.UserCommon.NotExist());
}
catch (InvalidOperationOnRootUserException)
{
return BadRequest(ErrorResponse.UserController.ChangePermission_RootUser());
}
}
}
}