From ec74a3c491d361bbaf9354b7f17be750b7b8823c Mon Sep 17 00:00:00 2001 From: 杨宇千 Date: Sat, 17 Aug 2019 19:25:08 +0800 Subject: Add validation. --- Timeline/Services/UserService.cs | 52 +--------------------------------------- 1 file changed, 1 insertion(+), 51 deletions(-) (limited to 'Timeline/Services/UserService.cs') diff --git a/Timeline/Services/UserService.cs b/Timeline/Services/UserService.cs index 0993d3dc..27145683 100644 --- a/Timeline/Services/UserService.cs +++ b/Timeline/Services/UserService.cs @@ -3,10 +3,10 @@ using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Logging; using System; using System.Linq; -using System.Text.RegularExpressions; using System.Threading.Tasks; using Timeline.Entities; using Timeline.Models; +using Timeline.Models.Validation; using static Timeline.Helpers.MyLogHelper; using static Timeline.Models.UserUtility; @@ -121,56 +121,6 @@ namespace Timeline.Services public string Username { get; private set; } } - public class UsernameValidator - { - public const int MaxLength = 26; - public const string RegexPattern = @"^[a-zA-Z0-9_][a-zA-Z0-9-_]*$"; - - private readonly Regex _regex = new Regex(RegexPattern); - - /// - /// Validate a username. - /// - /// The username. Can't be null. - /// Set as error message if there is error. Or null if no error. - /// True if validation passed. Otherwise false. - /// Thrown when is null. - public bool Validate(string username, out string message) - { - if (username == null) - throw new ArgumentNullException(nameof(username)); - - if (username.Length == 0) - { - message = "An empty string is not permitted."; - return false; - } - - if (username.Length > 26) - { - message = $"Too long, more than 26 characters is not premitted, found {username.Length}."; - return false; - } - - foreach ((char c, int i) in username.Select((c, i) => (c, i))) - if (char.IsWhiteSpace(c)) - { - message = $"A whitespace is found at {i} . Whitespace is not permited."; - return false; - } - - var match = _regex.Match(username); - if (!match.Success) - { - message = "Regex match failed."; - return false; - } - - message = null; - return true; - } - } - public interface IUserService { /// -- cgit v1.2.3 From 6edc70c00bca22e13ade23472d48d7b940f92eef Mon Sep 17 00:00:00 2001 From: 杨宇千 Date: Sat, 17 Aug 2019 19:56:42 +0800 Subject: Add change username. --- Timeline/Controllers/UserController.cs | 30 +++++++++++++++++- Timeline/Models/Http/User.cs | 10 ++++++ Timeline/Services/UserService.cs | 57 ++++++++++++++++++++++++++++++++++ 3 files changed, 96 insertions(+), 1 deletion(-) (limited to 'Timeline/Services/UserService.cs') diff --git a/Timeline/Controllers/UserController.cs b/Timeline/Controllers/UserController.cs index d38f96e1..bd13f0a3 100644 --- a/Timeline/Controllers/UserController.cs +++ b/Timeline/Controllers/UserController.cs @@ -22,7 +22,10 @@ namespace Timeline.Controllers public const int Patch_NotExist = -3001; - public const int ChangePassword_BadOldPassword = -4001; + public const int ChangeUsername_NotExist = -4001; + public const int ChangeUsername_AlreadyExist = -4002; + + public const int ChangePassword_BadOldPassword = -5001; } private readonly ILogger _logger; @@ -108,6 +111,31 @@ namespace Timeline.Controllers } } + [HttpPost("userop/changeusername"), AdminAuthorize] + public async Task ChangeUsername([FromBody] ChangeUsernameRequest request) + { + try + { + await _userService.ChangeUsername(request.OldUsername, request.NewUsername); + _logger.LogInformation(FormatLogMessage("A user changed username.", + Pair("Old Username", request.OldUsername), Pair("New Username", request.NewUsername))); + return Ok(); + } + catch (UserNotExistException e) + { + _logger.LogInformation(e, FormatLogMessage("Attempt to change a non-existent user's username failed.", + Pair("Old Username", request.OldUsername), Pair("New Username", request.NewUsername))); + return BadRequest(new CommonResponse(ErrorCodes.ChangeUsername_NotExist, $"The user {request.OldUsername} does not exist.")); + } + catch (UserAlreadyExistException e) + { + _logger.LogInformation(e, FormatLogMessage("Attempt to change a user's username to a existent one failed.", + Pair("Old Username", request.OldUsername), Pair("New Username", request.NewUsername))); + return BadRequest(new CommonResponse(ErrorCodes.ChangeUsername_AlreadyExist, $"The user {request.NewUsername} already exists.")); + } + // there is no need to catch bad format exception because it is already checked in model validation. + } + [HttpPost("userop/changepassword"), Authorize] public async Task ChangePassword([FromBody] ChangePasswordRequest request) { diff --git a/Timeline/Models/Http/User.cs b/Timeline/Models/Http/User.cs index d45543fb..4308a19c 100644 --- a/Timeline/Models/Http/User.cs +++ b/Timeline/Models/Http/User.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using Timeline.Models.Validation; namespace Timeline.Models.Http { @@ -16,6 +17,15 @@ namespace Timeline.Models.Http public bool? Administrator { get; set; } } + public class ChangeUsernameRequest + { + [Required] + public string OldUsername { get; set; } + + [Required, ValidateWith(typeof(UsernameValidator))] + public string NewUsername { get; set; } + } + public class ChangePasswordRequest { [Required] diff --git a/Timeline/Services/UserService.cs b/Timeline/Services/UserService.cs index 27145683..96c3e256 100644 --- a/Timeline/Services/UserService.cs +++ b/Timeline/Services/UserService.cs @@ -121,6 +121,26 @@ namespace Timeline.Services public string Username { get; private set; } } + + /// + /// Thrown when the user already exists. + /// + [Serializable] + public class UserAlreadyExistException : Exception + { + public UserAlreadyExistException(string username) : base($"User {username} already exists.") { Username = username; } + public UserAlreadyExistException(string username, string message) : base(message) { Username = username; } + public UserAlreadyExistException(string message, Exception inner) : base(message, inner) { } + protected UserAlreadyExistException( + System.Runtime.Serialization.SerializationInfo info, + System.Runtime.Serialization.StreamingContext context) : base(info, context) { } + + /// + /// The username that already exists. + /// + public string Username { get; set; } + } + public interface IUserService { /// @@ -204,6 +224,17 @@ namespace Timeline.Services /// 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); + + /// + /// Change a user's username. + /// + /// The user's old username. + /// The new username. + /// Thrown if or is null or empty. + /// Thrown if the user with old username does not exist. + /// Thrown if the new username is not accepted because of bad format. + /// Thrown if user with the new username already exists. + Task ChangeUsername(string oldUsername, string newUsername); } internal class UserCache @@ -432,5 +463,31 @@ namespace Timeline.Services //clear cache RemoveCache(user.Id); } + + public async Task ChangeUsername(string oldUsername, string newUsername) + { + if (string.IsNullOrEmpty(oldUsername)) + throw new ArgumentException("Old username is null or empty", nameof(oldUsername)); + if (string.IsNullOrEmpty(newUsername)) + throw new ArgumentException("New username is null or empty", nameof(newUsername)); + + if (!_usernameValidator.Validate(newUsername, out var message)) + throw new UsernameBadFormatException(newUsername, $"New username is of bad format. {message}"); + + var user = await _databaseContext.Users.Where(u => u.Name == oldUsername).SingleOrDefaultAsync(); + if (user == null) + throw new UserNotExistException(oldUsername); + + var conflictUser = await _databaseContext.Users.Where(u => u.Name == newUsername).SingleOrDefaultAsync(); + if (conflictUser != null) + throw new UserAlreadyExistException(newUsername); + + user.Name = newUsername; + user.Version += 1; + await _databaseContext.SaveChangesAsync(); + _logger.LogInformation(FormatLogMessage("A user entry changed name field.", + Pair("Id", user.Id), Pair("Old Username", oldUsername), Pair("New Username", newUsername))); + RemoveCache(user.Id); + } } } -- cgit v1.2.3