aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Timeline/Controllers/ControllerAuthExtensions.cs30
-rw-r--r--Timeline/Controllers/UserController.cs99
-rw-r--r--Timeline/Models/Http/User.cs41
-rw-r--r--Timeline/Models/Http/UserController.cs27
-rw-r--r--Timeline/Models/PutResult.cs17
-rw-r--r--Timeline/Models/User.cs20
-rw-r--r--Timeline/Resources/Messages.Designer.cs36
-rw-r--r--Timeline/Resources/Messages.resx12
-rw-r--r--Timeline/Services/User.cs49
-rw-r--r--Timeline/Services/UserRoleConvert.cs (renamed from Timeline/Models/UserRoleConvert.cs)2
-rw-r--r--Timeline/Services/UserService.cs1
11 files changed, 207 insertions, 127 deletions
diff --git a/Timeline/Controllers/ControllerAuthExtensions.cs b/Timeline/Controllers/ControllerAuthExtensions.cs
new file mode 100644
index 00000000..81fd2428
--- /dev/null
+++ b/Timeline/Controllers/ControllerAuthExtensions.cs
@@ -0,0 +1,30 @@
+using Microsoft.AspNetCore.Mvc;
+using System.Security.Claims;
+using Timeline.Auth;
+using System;
+
+namespace Timeline.Controllers
+{
+ public static class ControllerAuthExtensions
+ {
+ public static bool IsAdministrator(this ControllerBase controller)
+ {
+ return controller.User != null && controller.User.IsAdministrator();
+ }
+
+ public static long GetUserId(this ControllerBase controller)
+ {
+ if (controller.User == null)
+ throw new InvalidOperationException("Failed to get user id because User is null.");
+
+ var claim = controller.User.FindFirst(ClaimTypes.NameIdentifier);
+ if (claim == null)
+ throw new InvalidOperationException("Failed to get user id because User has no NameIdentifier claim.");
+
+ if (long.TryParse(claim.Value, out var value))
+ return value;
+
+ throw new InvalidOperationException("Failed to get user id because NameIdentifier claim is not a number.");
+ }
+ }
+}
diff --git a/Timeline/Controllers/UserController.cs b/Timeline/Controllers/UserController.cs
index 3305952a..4c585198 100644
--- a/Timeline/Controllers/UserController.cs
+++ b/Timeline/Controllers/UserController.cs
@@ -1,15 +1,16 @@
using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
-using System;
+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 static Timeline.Resources.Controllers.UserController;
+using static Timeline.Resources.Messages;
namespace Timeline.Controllers
{
@@ -26,19 +27,20 @@ namespace Timeline.Controllers
_userService = userService;
}
- [HttpGet("users"), AdminAuthorize]
+ [HttpGet("users")]
public async Task<ActionResult<User[]>> List()
{
- return Ok(await _userService.GetUsers());
+ var users = await _userService.GetUsers();
+ return Ok(users.Select(u => u.EraseSecretAndFinalFill(Url, this.IsAdministrator())).ToArray());
}
- [HttpGet("users/{username}"), AdminAuthorize]
+ [HttpGet("users/{username}")]
public async Task<ActionResult<User>> Get([FromRoute][Username] string username)
{
try
{
var user = await _userService.GetUserByUsername(username);
- return Ok(user);
+ return Ok(user.EraseSecretAndFinalFill(Url, this.IsAdministrator()));
}
catch (UserNotExistException e)
{
@@ -47,33 +49,53 @@ namespace Timeline.Controllers
}
}
- [HttpPut("users/{username}"), AdminAuthorize]
- public async Task<ActionResult<CommonPutResponse>> Put([FromBody] UserPutRequest request, [FromRoute][Username] string username)
+ [HttpPatch("users/{username}"), Authorize]
+ public async Task<ActionResult> Patch([FromBody] UserPatchRequest body, [FromRoute][Username] string username)
{
- var result = await _userService.PutUser(username, request.Password, request.Administrator!.Value);
- switch (result)
+ static User Convert(UserPatchRequest body)
{
- case PutResult.Create:
- return CreatedAtAction("Get", new { username }, CommonPutResponse.Create());
- case PutResult.Modify:
- return Ok(CommonPutResponse.Modify());
- default:
- throw new Exception(ExceptionUnknownPutResult);
+ return new User
+ {
+ Username = body.Username,
+ Password = body.Password,
+ Administrator = body.Administrator,
+ Nickname = body.Nickname
+ };
}
- }
- [HttpPatch("users/{username}"), AdminAuthorize]
- public async Task<ActionResult> Patch([FromBody] UserPatchRequest request, [FromRoute][Username] string username)
- {
- try
+ if (this.IsAdministrator())
{
- await _userService.PatchUser(username, request.Password, request.Administrator);
- return Ok();
+ try
+ {
+ await _userService.ModifyUser(username, Convert(body));
+ return Ok();
+ }
+ catch (UserNotExistException e)
+ {
+ _logger.LogInformation(e, Log.Format(LogPatchUserNotExist, ("Username", username)));
+ return NotFound(ErrorResponse.UserCommon.NotExist());
+ }
}
- catch (UserNotExistException e)
+ else
{
- _logger.LogInformation(e, Log.Format(LogPatchUserNotExist, ("Username", username)));
- return NotFound(ErrorResponse.UserCommon.NotExist());
+ 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));
+
+ if (body.Administrator != null)
+ return StatusCode(StatusCodes.Status403Forbidden,
+ ErrorResponse.Common.CustomMessage_Forbid(UserController_Patch_Forbid_Administrator));
+
+ await _userService.ModifyUser(this.GetUserId(), Convert(body));
+ return Ok();
}
}
@@ -91,27 +113,10 @@ namespace Timeline.Controllers
}
}
- [HttpPost("userop/changeusername"), AdminAuthorize]
- public async Task<ActionResult> ChangeUsername([FromBody] ChangeUsernameRequest request)
+ [HttpPost("userop/create"), AdminAuthorize]
+ public async Task<ActionResult> CreateUser([FromBody] User body)
{
- try
- {
- await _userService.ChangeUsername(request.OldUsername, request.NewUsername);
- return Ok();
- }
- catch (UserNotExistException e)
- {
- _logger.LogInformation(e, Log.Format(LogChangeUsernameNotExist,
- ("Old Username", request.OldUsername), ("New Username", request.NewUsername)));
- return BadRequest(ErrorResponse.UserCommon.NotExist());
- }
- catch (ConfictException e)
- {
- _logger.LogInformation(e, Log.Format(LogChangeUsernameConflict,
- ("Old Username", request.OldUsername), ("New Username", request.NewUsername)));
- return BadRequest(ErrorResponse.UserController.ChangeUsername_Conflict());
- }
- // there is no need to catch bad format exception because it is already checked in model validation.
+
}
[HttpPost("userop/changepassword"), Authorize]
@@ -119,7 +124,7 @@ namespace Timeline.Controllers
{
try
{
- await _userService.ChangePassword(User.Identity.Name!, request.OldPassword, request.NewPassword);
+ await _userService.ChangePassword(this.GetUserId(), request.OldPassword, request.NewPassword);
return Ok();
}
catch (BadPasswordException e)
diff --git a/Timeline/Models/Http/User.cs b/Timeline/Models/Http/User.cs
deleted file mode 100644
index b3812f48..00000000
--- a/Timeline/Models/Http/User.cs
+++ /dev/null
@@ -1,41 +0,0 @@
-using System;
-using System.ComponentModel.DataAnnotations;
-using Timeline.Models.Validation;
-
-namespace Timeline.Models.Http
-{
- [Obsolete("Remove this.")]
- public class UserPutRequest
- {
- [Required]
- public string Password { get; set; } = default!;
- [Required]
- public bool? Administrator { get; set; }
- }
-
- [Obsolete("Remove this.")]
- public class UserPatchRequest
- {
- public string? Password { get; set; }
- public bool? Administrator { get; set; }
- }
-
- public class ChangeUsernameRequest
- {
- [Required]
- [Username]
- public string OldUsername { get; set; } = default!;
-
- [Required]
- [Username]
- public string NewUsername { get; set; } = default!;
- }
-
- public class ChangePasswordRequest
- {
- [Required]
- public string OldPassword { get; set; } = default!;
- [Required]
- public string NewPassword { get; set; } = default!;
- }
-}
diff --git a/Timeline/Models/Http/UserController.cs b/Timeline/Models/Http/UserController.cs
new file mode 100644
index 00000000..229ca1e5
--- /dev/null
+++ b/Timeline/Models/Http/UserController.cs
@@ -0,0 +1,27 @@
+using System.ComponentModel.DataAnnotations;
+using Timeline.Models.Validation;
+
+namespace Timeline.Models.Http
+{
+ public class UserPatchRequest
+ {
+ [Username]
+ public string? Username { get; set; }
+
+ [MinLength(1)]
+ public string? Password { get; set; }
+
+ [Nickname]
+ public string? Nickname { get; set; }
+
+ public bool? Administrator { get; set; }
+ }
+
+ public class ChangePasswordRequest
+ {
+ [Required(AllowEmptyStrings = false)]
+ public string OldPassword { get; set; } = default!;
+ [Required(AllowEmptyStrings = false)]
+ public string NewPassword { get; set; } = default!;
+ }
+}
diff --git a/Timeline/Models/PutResult.cs b/Timeline/Models/PutResult.cs
deleted file mode 100644
index cecf86e6..00000000
--- a/Timeline/Models/PutResult.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-namespace Timeline.Models
-{
- /// <summary>
- /// Represents the result of a "put" operation.
- /// </summary>
- public enum PutResult
- {
- /// <summary>
- /// Indicates the item did not exist and now is created.
- /// </summary>
- Create,
- /// <summary>
- /// Indicates the item exists already and is modified.
- /// </summary>
- Modify
- }
-}
diff --git a/Timeline/Models/User.cs b/Timeline/Models/User.cs
deleted file mode 100644
index 2cead892..00000000
--- a/Timeline/Models/User.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using Timeline.Models.Validation;
-
-namespace Timeline.Models
-{
- public class User
- {
- [Username]
- public string? Username { get; set; }
- public bool? Administrator { get; set; }
- public string? Nickname { get; set; }
- public string? AvatarUrl { get; set; }
-
-
- #region secret
- public long? Id { get; set; }
- public string? Password { get; set; }
- public long? Version { get; set; }
- #endregion secret
- }
-}
diff --git a/Timeline/Resources/Messages.Designer.cs b/Timeline/Resources/Messages.Designer.cs
index 8c13374f..15101661 100644
--- a/Timeline/Resources/Messages.Designer.cs
+++ b/Timeline/Resources/Messages.Designer.cs
@@ -97,6 +97,15 @@ namespace Timeline.Resources {
}
/// <summary>
+ /// Looks up a localized string similar to You are not the resource owner..
+ /// </summary>
+ internal static string Common_Forbid_NotSelf {
+ get {
+ return ResourceManager.GetString("Common_Forbid_NotSelf", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to Header Content-Length is missing or of bad format..
/// </summary>
internal static string Common_Header_ContentLength_Missing {
@@ -266,5 +275,32 @@ namespace Timeline.Resources {
return ResourceManager.GetString("UserController_ChangeUsername_Conflict", resourceCulture);
}
}
+
+ /// <summary>
+ /// Looks up a localized string similar to You can&apos;t set permission unless you are administrator..
+ /// </summary>
+ internal static string UserController_Patch_Forbid_Administrator {
+ get {
+ return ResourceManager.GetString("UserController_Patch_Forbid_Administrator", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to You can&apos;t set password unless you are administrator. If you want to change password, use /userop/changepassword ..
+ /// </summary>
+ internal static string UserController_Patch_Forbid_Password {
+ get {
+ return ResourceManager.GetString("UserController_Patch_Forbid_Password", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to You can&apos;t set username unless you are administrator..
+ /// </summary>
+ internal static string UserController_Patch_Forbid_Username {
+ get {
+ return ResourceManager.GetString("UserController_Patch_Forbid_Username", resourceCulture);
+ }
+ }
}
}
diff --git a/Timeline/Resources/Messages.resx b/Timeline/Resources/Messages.resx
index c5228ed5..db56ed02 100644
--- a/Timeline/Resources/Messages.resx
+++ b/Timeline/Resources/Messages.resx
@@ -129,6 +129,9 @@
<data name="Common_Forbid" xml:space="preserve">
<value>You have no permission to do the operation.</value>
</data>
+ <data name="Common_Forbid_NotSelf" xml:space="preserve">
+ <value>You are not the resource owner.</value>
+ </data>
<data name="Common_Header_ContentLength_Missing" xml:space="preserve">
<value>Header Content-Length is missing or of bad format.</value>
</data>
@@ -186,4 +189,13 @@
<data name="UserController_ChangeUsername_Conflict" xml:space="preserve">
<value>The new username already exists.</value>
</data>
+ <data name="UserController_Patch_Forbid_Administrator" xml:space="preserve">
+ <value>You can't set permission unless you are administrator.</value>
+ </data>
+ <data name="UserController_Patch_Forbid_Password" xml:space="preserve">
+ <value>You can't set password unless you are administrator. If you want to change password, use /userop/changepassword .</value>
+ </data>
+ <data name="UserController_Patch_Forbid_Username" xml:space="preserve">
+ <value>You can't set username unless you are administrator.</value>
+ </data>
</root> \ No newline at end of file
diff --git a/Timeline/Services/User.cs b/Timeline/Services/User.cs
new file mode 100644
index 00000000..f63a374e
--- /dev/null
+++ b/Timeline/Services/User.cs
@@ -0,0 +1,49 @@
+using Microsoft.AspNetCore.Mvc;
+using System;
+using Timeline.Controllers;
+
+namespace Timeline.Services
+{
+ public class User
+ {
+ public string? Username { get; set; }
+ public string? Nickname { get; set; }
+ public string? AvatarUrl { get; set; }
+
+ #region adminsecret
+ public bool? Administrator { get; set; }
+ #endregion adminsecret
+
+ #region secret
+ public long? Id { get; set; }
+ public string? Password { get; set; }
+ public long? Version { get; set; }
+ #endregion secret
+ }
+
+ public static class UserExtensions
+ {
+ public static User EraseSecretAndFinalFill(this User user, IUrlHelper urlHelper, bool adminstrator)
+ {
+ if (user == null)
+ throw new ArgumentNullException(nameof(user));
+
+ var result = new User
+ {
+ Username = user.Username,
+ Nickname = user.Nickname,
+ AvatarUrl = urlHelper.ActionLink(action: nameof(UserAvatarController.Get), controller: nameof(UserAvatarController), values: new
+ {
+ user.Username
+ })
+ };
+
+ if (adminstrator)
+ {
+ result.Administrator = user.Administrator;
+ }
+
+ return result;
+ }
+ }
+}
diff --git a/Timeline/Models/UserRoleConvert.cs b/Timeline/Services/UserRoleConvert.cs
index ade9a799..4fa4a7b8 100644
--- a/Timeline/Models/UserRoleConvert.cs
+++ b/Timeline/Services/UserRoleConvert.cs
@@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using Timeline.Entities;
-namespace Timeline.Models
+namespace Timeline.Services
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "No need.")]
public static class UserRoleConvert
diff --git a/Timeline/Services/UserService.cs b/Timeline/Services/UserService.cs
index 616e70ba..ff2306c5 100644
--- a/Timeline/Services/UserService.cs
+++ b/Timeline/Services/UserService.cs
@@ -6,7 +6,6 @@ using System.Linq;
using System.Threading.Tasks;
using Timeline.Entities;
using Timeline.Helpers;
-using Timeline.Models;
using Timeline.Models.Validation;
using static Timeline.Resources.Services.UserService;