From 393daddb124ab6eae7506fd7db48e8333f28ad9c Mon Sep 17 00:00:00 2001 From: 杨宇千 Date: Sun, 21 Jul 2019 22:58:27 +0800 Subject: WIP: change UserService. --- Timeline/Entities/UserUtility.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) (limited to 'Timeline/Entities/UserUtility.cs') diff --git a/Timeline/Entities/UserUtility.cs b/Timeline/Entities/UserUtility.cs index 9a272948..1de7ac7d 100644 --- a/Timeline/Entities/UserUtility.cs +++ b/Timeline/Entities/UserUtility.cs @@ -2,6 +2,7 @@ using System; using System.Linq; using Timeline.Entities; using Timeline.Models; +using Timeline.Services; namespace Timeline.Entities { @@ -38,12 +39,23 @@ namespace Timeline.Entities return RoleArrayToRoleString(IsAdminToRoleArray(isAdmin)); } + public static bool RoleStringToIsAdmin(string roleString) + { + return RoleArrayToIsAdmin(RoleStringToRoleArray(roleString)); + } + public static UserInfo CreateUserInfo(User user) { if (user == null) throw new ArgumentNullException(nameof(user)); - return new UserInfo(user.Name, RoleArrayToIsAdmin(RoleStringToRoleArray(user.RoleString))); + return new UserInfo(user.Name, RoleStringToIsAdmin(user.RoleString)); } + internal static UserCache CreateUserCache(User user) + { + if (user == null) + throw new ArgumentNullException(nameof(user)); + return new UserCache { Username = user.Name, IsAdmin = RoleStringToIsAdmin(user.RoleString), Version = user.Version }; + } } } -- cgit v1.2.3 From 6191dbfdfa4f048e2f5a78729b49c8cf8a654ae3 Mon Sep 17 00:00:00 2001 From: 杨宇千 Date: Tue, 23 Jul 2019 16:47:53 +0800 Subject: WIP: Develop UserService. Remove unused components. --- Timeline/Configs/JwtConfig.cs | 4 +- Timeline/Entities/PutResult.cs | 17 +++ Timeline/Entities/UserUtility.cs | 1 - Timeline/Formatters/StringInputFormatter.cs | 29 ---- Timeline/Services/JwtService.cs | 3 +- Timeline/Services/QCloudCosService.cs | 1 - Timeline/Services/UserService.cs | 204 +++++++--------------------- Timeline/Startup.cs | 12 +- 8 files changed, 72 insertions(+), 199 deletions(-) create mode 100644 Timeline/Entities/PutResult.cs delete mode 100644 Timeline/Formatters/StringInputFormatter.cs (limited to 'Timeline/Entities/UserUtility.cs') diff --git a/Timeline/Configs/JwtConfig.cs b/Timeline/Configs/JwtConfig.cs index 1b395650..4d5ef97f 100644 --- a/Timeline/Configs/JwtConfig.cs +++ b/Timeline/Configs/JwtConfig.cs @@ -8,8 +8,8 @@ namespace Timeline.Configs /// /// Set the default value of expire offset of jwt token. - /// Unit is second. Default is 3600 seconds, aka 1 hour. + /// Unit is second. Default is 3600 * 24 seconds, aka 1 day. /// - public long DefaultExpireOffset { get; set; } = 3600; + public long DefaultExpireOffset { get; set; } = 3600 * 24; } } diff --git a/Timeline/Entities/PutResult.cs b/Timeline/Entities/PutResult.cs new file mode 100644 index 00000000..4ed48572 --- /dev/null +++ b/Timeline/Entities/PutResult.cs @@ -0,0 +1,17 @@ +namespace Timeline.Entities +{ + /// + /// Represents the result of a "put" operation. + /// + public enum PutResult + { + /// + /// Indicates the item did not exist and now is created. + /// + Created, + /// + /// Indicates the item exists already and is modified. + /// + Modified + } +} diff --git a/Timeline/Entities/UserUtility.cs b/Timeline/Entities/UserUtility.cs index 1de7ac7d..c8e82fba 100644 --- a/Timeline/Entities/UserUtility.cs +++ b/Timeline/Entities/UserUtility.cs @@ -1,6 +1,5 @@ using System; using System.Linq; -using Timeline.Entities; using Timeline.Models; using Timeline.Services; diff --git a/Timeline/Formatters/StringInputFormatter.cs b/Timeline/Formatters/StringInputFormatter.cs deleted file mode 100644 index ca9216d7..00000000 --- a/Timeline/Formatters/StringInputFormatter.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Microsoft.AspNetCore.Mvc.Formatters; -using Microsoft.Net.Http.Headers; -using System.IO; -using System.Text; -using System.Threading.Tasks; - -namespace Timeline.Formatters -{ - public class StringInputFormatter : TextInputFormatter - { - public StringInputFormatter() - { - SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/plain")); - - SupportedEncodings.Add(Encoding.UTF8); - SupportedEncodings.Add(Encoding.Unicode); - } - - public override Task ReadRequestBodyAsync(InputFormatterContext context, Encoding effectiveEncoding) - { - var request = context.HttpContext.Request; - using (var reader = new StreamReader(request.Body, effectiveEncoding)) - { - var stringContent = reader.ReadToEnd(); - return InputFormatterResult.SuccessAsync(stringContent); - } - } - } -} diff --git a/Timeline/Services/JwtService.cs b/Timeline/Services/JwtService.cs index b070ad62..f721971b 100644 --- a/Timeline/Services/JwtService.cs +++ b/Timeline/Services/JwtService.cs @@ -1,5 +1,4 @@ -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; using System; using System.IdentityModel.Tokens.Jwt; diff --git a/Timeline/Services/QCloudCosService.cs b/Timeline/Services/QCloudCosService.cs index b37631e5..748173c4 100644 --- a/Timeline/Services/QCloudCosService.cs +++ b/Timeline/Services/QCloudCosService.cs @@ -6,7 +6,6 @@ using System.Diagnostics; using System.Linq; using System.Net; using System.Net.Http; -using System.Net.Http.Headers; using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; diff --git a/Timeline/Services/UserService.cs b/Timeline/Services/UserService.cs index 49c9747d..ec8e5091 100644 --- a/Timeline/Services/UserService.cs +++ b/Timeline/Services/UserService.cs @@ -50,70 +50,6 @@ namespace Timeline.Services System.Runtime.Serialization.StreamingContext context) : base(info, context) { } } - public enum PutUserResult - { - /// - /// A new user is created. - /// - Created, - /// - /// A existing user is modified. - /// - Modified - } - - public enum PatchUserResult - { - /// - /// Succeed to modify user. - /// - Success, - /// - /// A user of given username does not exist. - /// - NotExists - } - - public enum DeleteUserResult - { - /// - /// A existing user is deleted. - /// - Deleted, - /// - /// A user of given username does not exist. - /// - NotExists - } - - public enum ChangePasswordResult - { - /// - /// Success to change password. - /// - Success, - /// - /// The user does not exists. - /// - NotExists, - /// - /// Old password is wrong. - /// - BadOldPassword - } - - public enum PutAvatarResult - { - /// - /// Success to upload avatar. - /// - Success, - /// - /// The user does not exists. - /// - UserNotExists - } - public interface IUserService { /// @@ -160,31 +96,29 @@ namespace Timeline.Services /// /// Username of user. /// Password of user. - /// Array of roles of user. - /// Return if a new user is created. - /// Return if a existing user is modified. - Task PutUser(string username, string password, bool isAdmin); + /// Whether the user is administrator. + /// Return if a new user is created. + /// Return if a existing user is modified. + /// Thrown when or is null. + Task PutUser(string username, string password, bool isAdmin); /// - /// Partially modify a use of given username. + /// Partially modify a user of given username. /// - /// Username of the user to modify. - /// New password. If not modify, then null. - /// New roles. If not modify, then null. - /// Return if modification succeeds. - /// Return if the user of given username doesn't exist. - Task PatchUser(string username, string password, bool? isAdmin); + /// Username of the user to modify. Can't be null. + /// New password. Null if not modify. + /// Whether the user is administrator. Null if not modify. + /// Thrown if is null. + /// Thrown if the user with given username does not exist. + Task PatchUser(string username, string password, bool? isAdmin); /// /// Delete a user of given username. - /// Return if the user is deleted. - /// Return if the user of given username - /// does not exist. /// - /// Username of thet user to delete. - /// if the user is deleted. - /// if the user doesn't exist. - Task DeleteUser(string username); + /// Username of thet user to delete. Can't be null. + /// Thrown if is null. + /// Thrown if the user with given username does not exist. + Task DeleteUser(string username); /// /// Try to change a user's password with old password. @@ -192,27 +126,10 @@ namespace Timeline.Services /// The name of user to change password of. /// The user's old password. /// The user's new password. - /// if success. - /// if user does not exist. - /// if old password is wrong. - Task ChangePassword(string username, string oldPassword, string newPassword); - - /// - /// Get the true avatar url of a user. - /// - /// The name of user. - /// The url if user exists. Null if user does not exist. - Task GetAvatarUrl(string username); - - /// - /// Put a avatar of a user. - /// - /// The name of user. - /// The data of avatar image. - /// The mime type of the image. - /// Return if success. - /// Return if user does not exist. - Task PutAvatar(string username, byte[] data, string mimeType); + /// Thrown if or or is null. + /// Thrown if the user with given username does not exist. + /// Thrown if the old password is wrong. + Task ChangePassword(string username, string oldPassword, string newPassword); } internal class UserCache @@ -348,8 +265,13 @@ namespace Timeline.Services .ToArrayAsync(); } - public async Task PutUser(string username, string password, bool isAdmin) + public async Task PutUser(string username, string password, bool isAdmin) { + if (username == null) + throw new ArgumentNullException(nameof(username)); + if (password == null) + throw new ArgumentNullException(nameof(password)); + var user = await _databaseContext.Users.Where(u => u.Name == username).SingleOrDefaultAsync(); if (user == null) @@ -362,7 +284,7 @@ namespace Timeline.Services Version = 0 }); await _databaseContext.SaveChangesAsync(); - return PutUserResult.Created; + return PutResult.Created; } user.EncryptedPassword = _passwordService.HashPassword(password); @@ -373,15 +295,17 @@ namespace Timeline.Services //clear cache RemoveCache(user.Id); - return PutUserResult.Modified; + return PutResult.Modified; } - public async Task PatchUser(string username, string password, bool? isAdmin) + public async Task PatchUser(string username, string password, bool? isAdmin) { - var user = await _databaseContext.Users.Where(u => u.Name == username).SingleOrDefaultAsync(); + if (username == null) + throw new ArgumentNullException(nameof(username)); + var user = await _databaseContext.Users.Where(u => u.Name == username).SingleOrDefaultAsync(); if (user == null) - return PatchUserResult.NotExists; + throw new UserNotExistException(); bool modified = false; @@ -399,78 +323,50 @@ namespace Timeline.Services if (modified) { + user.Version += 1; await _databaseContext.SaveChangesAsync(); //clear cache RemoveCache(user.Id); } - - return PatchUserResult.Success; } - public async Task DeleteUser(string username) + public async Task DeleteUser(string username) { - var user = await _databaseContext.Users.Where(u => u.Name == username).SingleOrDefaultAsync(); + if (username == null) + throw new ArgumentNullException(nameof(username)); + var user = await _databaseContext.Users.Where(u => u.Name == username).SingleOrDefaultAsync(); if (user == null) - { - return DeleteUserResult.NotExists; - } + throw new UserNotExistException(); _databaseContext.Users.Remove(user); await _databaseContext.SaveChangesAsync(); //clear cache RemoveCache(user.Id); - - return DeleteUserResult.Deleted; } - public async Task ChangePassword(string username, string oldPassword, string newPassword) + public async Task ChangePassword(string username, string oldPassword, string newPassword) { + if (username == null) + throw new ArgumentNullException(nameof(username)); + if (oldPassword == null) + throw new ArgumentNullException(nameof(oldPassword)); + if (newPassword == null) + throw new ArgumentNullException(nameof(newPassword)); + var user = await _databaseContext.Users.Where(u => u.Name == username).SingleOrDefaultAsync(); if (user == null) - return ChangePasswordResult.NotExists; + throw new UserNotExistException(); var verifyResult = _passwordService.VerifyPassword(user.EncryptedPassword, oldPassword); if (!verifyResult) - return ChangePasswordResult.BadOldPassword; + throw new BadPasswordException(); user.EncryptedPassword = _passwordService.HashPassword(newPassword); + user.Version += 1; await _databaseContext.SaveChangesAsync(); //clear cache RemoveCache(user.Id); - - return ChangePasswordResult.Success; - } - - public async Task GetAvatarUrl(string username) - { - if (username == null) - throw new ArgumentNullException(nameof(username)); - - if ((await GetUser(username)) == null) - return null; - - var exists = await _cosService.IsObjectExists("avatar", username); - if (exists) - return _cosService.GenerateObjectGetUrl("avatar", username); - else - return _cosService.GenerateObjectGetUrl("avatar", "__default"); - } - - public async Task PutAvatar(string username, byte[] data, string mimeType) - { - if (username == null) - throw new ArgumentNullException(nameof(username)); - if (data == null) - throw new ArgumentNullException(nameof(data)); - if (mimeType == null) - throw new ArgumentNullException(nameof(mimeType)); - - if ((await GetUser(username)) == null) - return PutAvatarResult.UserNotExists; - - await _cosService.PutObject("avatar", username, data, mimeType); - return PutAvatarResult.Success; } } } diff --git a/Timeline/Startup.cs b/Timeline/Startup.cs index 374b918a..a6965190 100644 --- a/Timeline/Startup.cs +++ b/Timeline/Startup.cs @@ -1,4 +1,3 @@ -using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.HttpOverrides; @@ -9,7 +8,6 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Timeline.Authenticate; using Timeline.Configs; -using Timeline.Formatters; using Timeline.Models; using Timeline.Services; @@ -31,10 +29,7 @@ namespace Timeline // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { - services.AddMvc(options => - { - options.InputFormatters.Add(new StringInputFormatter()); - }).SetCompatibilityVersion(CompatibilityVersion.Version_2_2); + services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); services.AddCors(options => { @@ -50,7 +45,7 @@ namespace Timeline services.Configure(Configuration.GetSection(nameof(JwtConfig))); var jwtConfig = Configuration.GetSection(nameof(JwtConfig)).Get(); - services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + services.AddAuthentication(AuthConstants.Scheme) .AddScheme(AuthConstants.Scheme, AuthConstants.DisplayName, o => { }); services.AddScoped(); @@ -73,9 +68,6 @@ namespace Timeline services.AddHttpClient(); - services.Configure(Configuration.GetSection(nameof(QCloudCosConfig))); - services.AddSingleton(); - services.AddMemoryCache(); } -- cgit v1.2.3 From c84faf2b8f5a55ae170e92ead6516e573a318e65 Mon Sep 17 00:00:00 2001 From: 杨宇千 Date: Tue, 23 Jul 2019 18:01:04 +0800 Subject: WIP: Change UserController. --- Timeline/Authenticate/Attribute.cs | 21 ++++++ Timeline/Controllers/TokenController.cs | 30 ++++---- Timeline/Controllers/UserController.cs | 130 +++++++++++++++----------------- Timeline/Entities/Http/Common.cs | 24 +++++- Timeline/Entities/Http/User.cs | 40 ---------- Timeline/Entities/UserUtility.cs | 4 +- Timeline/Models/DatabaseContext.cs | 6 ++ 7 files changed, 124 insertions(+), 131 deletions(-) create mode 100644 Timeline/Authenticate/Attribute.cs (limited to 'Timeline/Entities/UserUtility.cs') diff --git a/Timeline/Authenticate/Attribute.cs b/Timeline/Authenticate/Attribute.cs new file mode 100644 index 00000000..50b2681d --- /dev/null +++ b/Timeline/Authenticate/Attribute.cs @@ -0,0 +1,21 @@ +using Microsoft.AspNetCore.Authorization; +using Timeline.Models; + +namespace Timeline.Authenticate +{ + public class AdminAuthorizeAttribute : AuthorizeAttribute + { + public AdminAuthorizeAttribute() + { + Roles = UserRoles.Admin; + } + } + + public class UserAuthorizeAttribute : AuthorizeAttribute + { + public UserAuthorizeAttribute() + { + Roles = UserRoles.User; + } + } +} diff --git a/Timeline/Controllers/TokenController.cs b/Timeline/Controllers/TokenController.cs index 3a364ffe..023bd53f 100644 --- a/Timeline/Controllers/TokenController.cs +++ b/Timeline/Controllers/TokenController.cs @@ -21,12 +21,12 @@ namespace Timeline.Controllers private static class ErrorCodes { - public const int Create_UserNotExist = 1001; - public const int Create_BadPassword = 1002; + public const int Create_UserNotExist = -1001; + public const int Create_BadPassword = -1002; - public const int Verify_BadToken = 2001; - public const int Verify_UserNotExist = 2002; - public const int Verify_BadVersion = 2003; + public const int Verify_BadToken = -2001; + public const int Verify_UserNotExist = -2002; + public const int Verify_BadVersion = -2003; } private readonly IUserService _userService; @@ -55,14 +55,14 @@ namespace Timeline.Controllers catch(UserNotExistException e) { var code = ErrorCodes.Create_UserNotExist; - _logger.LogInformation(LoggingEventIds.LogInFailed, e, "Attemp to login failed. Code: {} Username: {} Password: {} .", code, request.Username, request.Password); - return BadRequest(new CommonErrorResponse(code, "Bad username or password.")); + _logger.LogInformation(LoggingEventIds.LogInFailed, e, "Attemp to login failed because user does not exist. Code: {} Username: {} Password: {} .", code, request.Username, request.Password); + return BadRequest(new CommonResponse(code, "Bad username or password.")); } catch (BadPasswordException e) { var code = ErrorCodes.Create_BadPassword; - _logger.LogInformation(LoggingEventIds.LogInFailed, e, "Attemp to login failed. Code: {} Username: {} Password: {} .", code, request.Username, request.Password); - return BadRequest(new CommonErrorResponse(code, "Bad username or password.")); + _logger.LogInformation(LoggingEventIds.LogInFailed, e, "Attemp to login failed because password is wrong. Code: {} Username: {} Password: {} .", code, request.Username, request.Password); + return BadRequest(new CommonResponse(code, "Bad username or password.")); } } @@ -82,20 +82,20 @@ namespace Timeline.Controllers catch (JwtTokenVerifyException e) { var code = ErrorCodes.Verify_BadToken; - _logger.LogInformation(LoggingEventIds.VerifyFailed, e, "Attemp to verify a bad token. Code: {} Token: {}.", code, request.Token); - return BadRequest(new CommonErrorResponse(code, "A token of bad format.")); + _logger.LogInformation(LoggingEventIds.VerifyFailed, e, "Attemp to verify a bad token because of bad format. Code: {} Token: {}.", code, request.Token); + return BadRequest(new CommonResponse(code, "A token of bad format.")); } catch (UserNotExistException e) { var code = ErrorCodes.Verify_UserNotExist; - _logger.LogInformation(LoggingEventIds.VerifyFailed, e, "Attemp to verify a bad token. Code: {} Token: {}.", code, request.Token); - return BadRequest(new CommonErrorResponse(code, "The user does not exist. Administrator might have deleted this user.")); + _logger.LogInformation(LoggingEventIds.VerifyFailed, e, "Attemp to verify a bad token because user does not exist. Code: {} Token: {}.", code, request.Token); + return BadRequest(new CommonResponse(code, "The user does not exist. Administrator might have deleted this user.")); } catch (BadTokenVersionException e) { var code = ErrorCodes.Verify_BadToken; - _logger.LogInformation(LoggingEventIds.VerifyFailed, e, "Attemp to verify a bad token. Code: {} Token: {}.", code, request.Token); - return BadRequest(new CommonErrorResponse(code, "The token is expired. Try recreate a token.")); + _logger.LogInformation(LoggingEventIds.VerifyFailed, e, "Attemp to verify a bad token because version is old. Code: {} Token: {}.", code, request.Token); + return BadRequest(new CommonResponse(code, "The token is expired. Try recreate a token.")); } } } diff --git a/Timeline/Controllers/UserController.cs b/Timeline/Controllers/UserController.cs index 6f708e8a..413999ce 100644 --- a/Timeline/Controllers/UserController.cs +++ b/Timeline/Controllers/UserController.cs @@ -1,9 +1,9 @@ using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; using System; -using System.IO; using System.Threading.Tasks; +using Timeline.Authenticate; using Timeline.Entities; using Timeline.Entities.Http; using Timeline.Services; @@ -12,125 +12,113 @@ namespace Timeline.Controllers { public class UserController : Controller { + private static class ErrorCodes + { + public const int Get_NotExists = -1001; + + public const int Put_NoPassword = -2001; + + public const int Patch_NotExists = -3001; + + public const int ChangePassword_BadOldPassword = -4001; + } + + private readonly ILogger _logger; private readonly IUserService _userService; - public UserController(IUserService userService) + public UserController(ILogger logger, IUserService userService) { + _logger = logger; _userService = userService; } - [HttpGet("users"), Authorize(Roles = "admin")] + [HttpGet("users"), AdminAuthorize] public async Task> List() { return Ok(await _userService.ListUsers()); } - [HttpGet("user/{username}"), Authorize] + [HttpGet("user/{username}"), AdminAuthorize] public async Task Get([FromRoute] string username) { var user = await _userService.GetUser(username); if (user == null) { - return NotFound(); + _logger.LogInformation("Attempt to get a non-existent user. Username: {} .", username); + return NotFound(new CommonResponse(ErrorCodes.Get_NotExists, "The user does not exist.")); } return Ok(user); } - [HttpPut("user/{username}"), Authorize(Roles = "admin")] + [HttpPut("user/{username}"), AdminAuthorize] public async Task Put([FromBody] UserPutRequest request, [FromRoute] string username) { + if (request.Password == null) + { + _logger.LogInformation("Attempt to put a user without a password. Username: {} .", username); + return BadRequest(); + } + var result = await _userService.PutUser(username, request.Password, request.IsAdmin); switch (result) { - case PutUserResult.Created: - return CreatedAtAction("Get", new { username }, UserPutResponse.Created); - case PutUserResult.Modified: - return Ok(UserPutResponse.Modified); + case PutResult.Created: + _logger.LogInformation("Created a user. Username: {} .", username); + return CreatedAtAction("Get", new { username }, CommonPutResponse.Created); + case PutResult.Modified: + _logger.LogInformation("Modified a user. Username: {} .", username); + return Ok(CommonPutResponse.Modified); default: throw new Exception("Unreachable code."); } } - [HttpPatch("user/{username}"), Authorize(Roles = "admin")] + [HttpPatch("user/{username}"), AdminAuthorize] public async Task Patch([FromBody] UserPatchRequest request, [FromRoute] string username) { - var result = await _userService.PatchUser(username, request.Password, request.IsAdmin); - switch (result) + try { - case PatchUserResult.Success: - return Ok(); - case PatchUserResult.NotExists: - return NotFound(); - default: - throw new Exception("Unreachable code."); + await _userService.PatchUser(username, request.Password, request.IsAdmin); + return Ok(); } - } - - [HttpDelete("user/{username}"), Authorize(Roles = "admin")] - public async Task Delete([FromRoute] string username) - { - var result = await _userService.DeleteUser(username); - switch (result) + catch (UserNotExistException e) { - case DeleteUserResult.Deleted: - return Ok(UserDeleteResponse.Deleted); - case DeleteUserResult.NotExists: - return Ok(UserDeleteResponse.NotExists); - default: - throw new Exception("Uncreachable code."); + _logger.LogInformation(e, "Attempt to patch a non-existent user. Username: {} .", username); + return BadRequest(new CommonResponse(ErrorCodes.Patch_NotExists, "The user does not exist.")); } } - [HttpGet("user/{username}/avatar"), Authorize] - public async Task GetAvatar([FromRoute] string username) - { - var url = await _userService.GetAvatarUrl(username); - if (url == null) - return NotFound(); - return Redirect(url); - } - - [HttpPut("user/{username}/avatar"), Authorize] - [Consumes("image/png", "image/gif", "image/jpeg", "image/svg+xml")] - public async Task PutAvatar([FromRoute] string username, [FromHeader(Name="Content-Type")] string contentType) + [HttpDelete("user/{username}"), AdminAuthorize] + public async Task Delete([FromRoute] string username) { - bool isAdmin = User.IsInRole("admin"); - if (!isAdmin) + try { - if (username != User.Identity.Name) - return StatusCode(StatusCodes.Status403Forbidden, PutAvatarResponse.Forbidden); + await _userService.DeleteUser(username); + _logger.LogInformation("A user is deleted. Username: {} .", username); + return Ok(CommonDeleteResponse.Deleted); } - - var stream = new MemoryStream(); - await Request.Body.CopyToAsync(stream); - var result = await _userService.PutAvatar(username, stream.ToArray(), contentType); - switch (result) + catch (UserNotExistException e) { - case PutAvatarResult.Success: - return Ok(PutAvatarResponse.Success); - case PutAvatarResult.UserNotExists: - return BadRequest(PutAvatarResponse.NotExists); - default: - throw new Exception("Unknown put avatar result."); + _logger.LogInformation(e, "Attempt to delete a non-existent user. Username: {} .", username); + return Ok(CommonDeleteResponse.NotExists); } } - [HttpPost("userop/changepassword"), Authorize] public async Task ChangePassword([FromBody] ChangePasswordRequest request) { - var result = await _userService.ChangePassword(User.Identity.Name, request.OldPassword, request.NewPassword); - switch (result) + try { - case ChangePasswordResult.Success: - return Ok(ChangePasswordResponse.Success); - case ChangePasswordResult.BadOldPassword: - return Ok(ChangePasswordResponse.BadOldPassword); - case ChangePasswordResult.NotExists: - return Ok(ChangePasswordResponse.NotExists); - default: - throw new Exception("Uncreachable code."); + await _userService.ChangePassword(User.Identity.Name, request.OldPassword, request.NewPassword); + _logger.LogInformation("A user changed password. Username: {} .", User.Identity.Name); + return Ok(); + } + catch (BadPasswordException e) + { + _logger.LogInformation(e, "A user attempt to change password but old password is wrong. Username: {} .", User.Identity.Name); + return BadRequest(new CommonResponse(ErrorCodes.ChangePassword_BadOldPassword, "Old password is wrong.")); } + // User can't be non-existent or the token is bad. } } } diff --git a/Timeline/Entities/Http/Common.cs b/Timeline/Entities/Http/Common.cs index 7708927a..3a45a0ae 100644 --- a/Timeline/Entities/Http/Common.cs +++ b/Timeline/Entities/Http/Common.cs @@ -1,13 +1,13 @@ namespace Timeline.Entities.Http { - public class CommonErrorResponse + public class CommonResponse { - public CommonErrorResponse() + public CommonResponse() { } - public CommonErrorResponse(int code, string message) + public CommonResponse(int code, string message) { Code = code; Message = message; @@ -16,4 +16,22 @@ public int Code { get; set; } public string Message { get; set; } } + + public static class CommonPutResponse + { + public const int CreatedCode = 0; + public const int ModifiedCode = 1; + + public static CommonResponse Created { get; } = new CommonResponse(CreatedCode, "A new item is created."); + public static CommonResponse Modified { get; } = new CommonResponse(ModifiedCode, "An existent item is modified."); + } + + public static class CommonDeleteResponse + { + public const int DeletedCode = 0; + public const int NotExistsCode = 1; + + public static CommonResponse Deleted { get; } = new CommonResponse(DeletedCode, "An existent item is deleted."); + public static CommonResponse NotExists { get; } = new CommonResponse(NotExistsCode, "The item does not exist."); + } } diff --git a/Timeline/Entities/Http/User.cs b/Timeline/Entities/Http/User.cs index f5d233cd..91423c7b 100644 --- a/Timeline/Entities/Http/User.cs +++ b/Timeline/Entities/Http/User.cs @@ -12,49 +12,9 @@ public bool? IsAdmin { get; set; } } - public static class UserPutResponse - { - public const int CreatedCode = 0; - public const int ModifiedCode = 1; - - public static CommonErrorResponse Created { get; } = new CommonErrorResponse(CreatedCode, "A new user is created."); - public static CommonErrorResponse Modified { get; } = new CommonErrorResponse(ModifiedCode, "A existing user is modified."); - } - - public static class UserDeleteResponse - { - public const int DeletedCode = 0; - public const int NotExistsCode = 1; - - public static CommonErrorResponse Deleted { get; } = new CommonErrorResponse(DeletedCode, "A existing user is deleted."); - public static CommonErrorResponse NotExists { get; } = new CommonErrorResponse(NotExistsCode, "User with given name does not exists."); - } - public class ChangePasswordRequest { public string OldPassword { get; set; } public string NewPassword { get; set; } } - - public static class ChangePasswordResponse - { - public const int SuccessCode = 0; - public const int BadOldPasswordCode = 1; - public const int NotExistsCode = 2; - - public static CommonErrorResponse Success { get; } = new CommonErrorResponse(SuccessCode, "Success to change password."); - public static CommonErrorResponse BadOldPassword { get; } = new CommonErrorResponse(BadOldPasswordCode, "Old password is wrong."); - public static CommonErrorResponse NotExists { get; } = new CommonErrorResponse(NotExistsCode, "Username does not exists, please update token."); - } - - public static class PutAvatarResponse - { - public const int SuccessCode = 0; - public const int ForbiddenCode = 1; - public const int NotExistsCode = 2; - - public static CommonErrorResponse Success { get; } = new CommonErrorResponse(SuccessCode, "Success to upload avatar."); - public static CommonErrorResponse Forbidden { get; } = new CommonErrorResponse(ForbiddenCode, "You are not allowed to upload the user's avatar."); - public static CommonErrorResponse NotExists { get; } = new CommonErrorResponse(NotExistsCode, "The username does not exists. If you are a user, try update your token."); - } } diff --git a/Timeline/Entities/UserUtility.cs b/Timeline/Entities/UserUtility.cs index c8e82fba..cbbd391c 100644 --- a/Timeline/Entities/UserUtility.cs +++ b/Timeline/Entities/UserUtility.cs @@ -7,8 +7,8 @@ namespace Timeline.Entities { public static class UserUtility { - public const string UserRole = "user"; - public const string AdminRole = "admin"; + public const string UserRole = UserRoles.User; + public const string AdminRole = UserRoles.Admin; public static string[] UserRoleArray { get; } = new string[] { UserRole }; public static string[] AdminRoleArray { get; } = new string[] { UserRole, AdminRole }; diff --git a/Timeline/Models/DatabaseContext.cs b/Timeline/Models/DatabaseContext.cs index 87c0fd17..afd5a333 100644 --- a/Timeline/Models/DatabaseContext.cs +++ b/Timeline/Models/DatabaseContext.cs @@ -4,6 +4,12 @@ using System.ComponentModel.DataAnnotations.Schema; namespace Timeline.Models { + public static class UserRoles + { + public const string Admin = "admin"; + public const string User = "user"; + } + [Table("user")] public class User { -- cgit v1.2.3