From 81c261b59016e15d320ad963882afe4d9c7b5f7d Mon Sep 17 00:00:00 2001 From: crupest Date: Wed, 29 Jan 2020 18:38:41 +0800 Subject: ... --- Timeline.Tests/Controllers/UserControllerTest.cs | 4 +- Timeline/Auth/MyAuthenticationHandler.cs | 4 +- Timeline/Controllers/UserController.cs | 4 +- Timeline/Models/User.cs | 1 + Timeline/Models/Validation/NicknameValidator.cs | 26 ++ Timeline/Resources/Messages.zh.resx | 189 --------------- Timeline/Resources/Models/Http/Common.zh.resx | 132 ----------- .../Validation/NicknameValidator.Designer.cs | 72 ++++++ .../Models/Validation/NicknameValidator.resx | 123 ++++++++++ .../Validation/PasswordValidator.Designer.cs | 72 ------ .../Models/Validation/PasswordValidator.resx | 123 ---------- .../Models/Validation/PasswordValidator.zh.resx | 123 ---------- .../Models/Validation/UsernameValidator.zh.resx | 129 ---------- .../Resources/Models/Validation/Validator.zh.resx | 129 ---------- Timeline/Resources/Services/Exception.Designer.cs | 18 +- Timeline/Resources/Services/Exception.resx | 6 +- .../Resources/Services/UserManager.Designer.cs | 72 ------ Timeline/Resources/Services/UserManager.resx | 123 ---------- .../Resources/Services/UserService.Designer.cs | 36 +-- Timeline/Resources/Services/UserService.resx | 18 +- Timeline/Services/ConfictException.cs | 21 ++ Timeline/Services/UserService.cs | 263 +++++++++++++-------- Timeline/Services/UserTokenException.cs | 3 - Timeline/Services/UserTokenManager.cs | 4 +- Timeline/Services/UsernameConfictException.cs | 25 -- Timeline/Startup.cs | 19 -- Timeline/Timeline.csproj | 17 +- 27 files changed, 454 insertions(+), 1302 deletions(-) create mode 100644 Timeline/Models/Validation/NicknameValidator.cs delete mode 100644 Timeline/Resources/Messages.zh.resx delete mode 100644 Timeline/Resources/Models/Http/Common.zh.resx create mode 100644 Timeline/Resources/Models/Validation/NicknameValidator.Designer.cs create mode 100644 Timeline/Resources/Models/Validation/NicknameValidator.resx delete mode 100644 Timeline/Resources/Models/Validation/PasswordValidator.Designer.cs delete mode 100644 Timeline/Resources/Models/Validation/PasswordValidator.resx delete mode 100644 Timeline/Resources/Models/Validation/PasswordValidator.zh.resx delete mode 100644 Timeline/Resources/Models/Validation/UsernameValidator.zh.resx delete mode 100644 Timeline/Resources/Models/Validation/Validator.zh.resx delete mode 100644 Timeline/Resources/Services/UserManager.Designer.cs delete mode 100644 Timeline/Resources/Services/UserManager.resx create mode 100644 Timeline/Services/ConfictException.cs delete mode 100644 Timeline/Services/UsernameConfictException.cs diff --git a/Timeline.Tests/Controllers/UserControllerTest.cs b/Timeline.Tests/Controllers/UserControllerTest.cs index 192d53dd..3890712a 100644 --- a/Timeline.Tests/Controllers/UserControllerTest.cs +++ b/Timeline.Tests/Controllers/UserControllerTest.cs @@ -40,7 +40,7 @@ namespace Timeline.Tests.Controllers new Models.User { Id = 1, Username = "aaa", Administrator = true, Version = 1 }, new Models.User { Id = 2, Username = "bbb", Administrator = false, Version = 1 } }; - _mockUserService.Setup(s => s.ListUsers()).ReturnsAsync(mockUserList); + _mockUserService.Setup(s => s.GetUsers()).ReturnsAsync(mockUserList); var action = await _controller.List(); action.Result.Should().BeAssignableTo() .Which.Value.Should().BeEquivalentTo( @@ -165,7 +165,7 @@ namespace Timeline.Tests.Controllers [Theory] [InlineData(typeof(UserNotExistException), ErrorCodes.UserCommon.NotExist)] - [InlineData(typeof(UsernameConfictException), ErrorCodes.UserController.ChangeUsername_Conflict)] + [InlineData(typeof(ConfictException), ErrorCodes.UserController.ChangeUsername_Conflict)] public async Task Op_ChangeUsername_Failure(Type exceptionType, int code) { const string oldUsername = "aaa"; diff --git a/Timeline/Auth/MyAuthenticationHandler.cs b/Timeline/Auth/MyAuthenticationHandler.cs index 5bae5117..e6b26c4b 100644 --- a/Timeline/Auth/MyAuthenticationHandler.cs +++ b/Timeline/Auth/MyAuthenticationHandler.cs @@ -82,9 +82,9 @@ namespace Timeline.Auth var userInfo = await _userTokenManager.VerifyToken(token); var identity = new ClaimsIdentity(AuthenticationConstants.Scheme); - identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, userInfo.Id.ToString(CultureInfo.InvariantCulture), ClaimValueTypes.Integer64)); + identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, userInfo.Id!.Value.ToString(CultureInfo.InvariantCulture), ClaimValueTypes.Integer64)); identity.AddClaim(new Claim(identity.NameClaimType, userInfo.Username, ClaimValueTypes.String)); - identity.AddClaims(UserRoleConvert.ToArray(userInfo.Administrator).Select(role => new Claim(identity.RoleClaimType, role, ClaimValueTypes.String))); + identity.AddClaims(UserRoleConvert.ToArray(userInfo.Administrator!.Value).Select(role => new Claim(identity.RoleClaimType, role, ClaimValueTypes.String))); var principal = new ClaimsPrincipal(); principal.AddIdentity(identity); diff --git a/Timeline/Controllers/UserController.cs b/Timeline/Controllers/UserController.cs index 5f1b7bd7..3305952a 100644 --- a/Timeline/Controllers/UserController.cs +++ b/Timeline/Controllers/UserController.cs @@ -29,7 +29,7 @@ namespace Timeline.Controllers [HttpGet("users"), AdminAuthorize] public async Task> List() { - return Ok(await _userService.ListUsers()); + return Ok(await _userService.GetUsers()); } [HttpGet("users/{username}"), AdminAuthorize] @@ -105,7 +105,7 @@ namespace Timeline.Controllers ("Old Username", request.OldUsername), ("New Username", request.NewUsername))); return BadRequest(ErrorResponse.UserCommon.NotExist()); } - catch (UsernameConfictException e) + catch (ConfictException e) { _logger.LogInformation(e, Log.Format(LogChangeUsernameConflict, ("Old Username", request.OldUsername), ("New Username", request.NewUsername))); diff --git a/Timeline/Models/User.cs b/Timeline/Models/User.cs index 05395022..2cead892 100644 --- a/Timeline/Models/User.cs +++ b/Timeline/Models/User.cs @@ -12,6 +12,7 @@ namespace Timeline.Models #region secret + public long? Id { get; set; } public string? Password { get; set; } public long? Version { get; set; } #endregion secret diff --git a/Timeline/Models/Validation/NicknameValidator.cs b/Timeline/Models/Validation/NicknameValidator.cs new file mode 100644 index 00000000..f6626a2a --- /dev/null +++ b/Timeline/Models/Validation/NicknameValidator.cs @@ -0,0 +1,26 @@ +using System; +using static Timeline.Resources.Models.Validation.NicknameValidator; + +namespace Timeline.Models.Validation +{ + public class NicknameValidator : Validator + { + [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "Already checked in base.")] + protected override (bool, string) DoValidate(string value) + { + if (value.Length > 10) + return (false, MessageTooLong); + + return (true, GetSuccessMessage()); + } + } + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)] + public class NicknameAttribute : ValidateWithAttribute + { + public NicknameAttribute() : base(typeof(NicknameValidator)) + { + + } + } +} diff --git a/Timeline/Resources/Messages.zh.resx b/Timeline/Resources/Messages.zh.resx deleted file mode 100644 index 6e52befd..00000000 --- a/Timeline/Resources/Messages.zh.resx +++ /dev/null @@ -1,189 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - 请求体太大。它不能超过{0}. - - - 实际的请求体长度比头中指示的大。 - - - 实际的请求体长度比头中指示的小。 - - - 你没有权限做此操作。 - - - 请求头Content-Length缺失或者格式不对。 - - - 请求头Content-Length不能为0。 - - - 请求头Content-Type缺失。 - - - 请求头If-Non-Match格式不对。 - - - 请求模型格式不对。 - - - 第{0}个做{1}操作的用户名格式错误。 - - - 第{0}个做{1}操作的用户不存在。 - - - 要删除的消息不存在。 - - - 用户名或密码错误。 - - - 符号格式错误。这个符号可能不是这个服务器创建的。 - - - 符号是一个旧版本。用户可能已经更新了信息。 - - - 符号过期了。 - - - 用户不存在。管理员可能已经删除了这个用户。 - - - 图片不是正方形。 - - - 解码图片失败。 - - - 图片格式与请求头中指示的不一样。 - - - 要操作的用户不存在。 - - - 旧密码错误。 - - - 新用户名已经存在。 - - \ No newline at end of file diff --git a/Timeline/Resources/Models/Http/Common.zh.resx b/Timeline/Resources/Models/Http/Common.zh.resx deleted file mode 100644 index de74ac3b..00000000 --- a/Timeline/Resources/Models/Http/Common.zh.resx +++ /dev/null @@ -1,132 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - 删除了一个项目。 - - - 要删除的项目不存在,什么都没有修改。 - - - 创建了一个新项目。 - - - 修改了一个已存在的项目。 - - \ No newline at end of file diff --git a/Timeline/Resources/Models/Validation/NicknameValidator.Designer.cs b/Timeline/Resources/Models/Validation/NicknameValidator.Designer.cs new file mode 100644 index 00000000..522f305a --- /dev/null +++ b/Timeline/Resources/Models/Validation/NicknameValidator.Designer.cs @@ -0,0 +1,72 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Timeline.Resources.Models.Validation { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class NicknameValidator { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal NicknameValidator() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Timeline.Resources.Models.Validation.NicknameValidator", typeof(NicknameValidator).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Nickname is too long.. + /// + internal static string MessageTooLong { + get { + return ResourceManager.GetString("MessageTooLong", resourceCulture); + } + } + } +} diff --git a/Timeline/Resources/Models/Validation/NicknameValidator.resx b/Timeline/Resources/Models/Validation/NicknameValidator.resx new file mode 100644 index 00000000..b191b505 --- /dev/null +++ b/Timeline/Resources/Models/Validation/NicknameValidator.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Nickname is too long. + + \ No newline at end of file diff --git a/Timeline/Resources/Models/Validation/PasswordValidator.Designer.cs b/Timeline/Resources/Models/Validation/PasswordValidator.Designer.cs deleted file mode 100644 index e7630d26..00000000 --- a/Timeline/Resources/Models/Validation/PasswordValidator.Designer.cs +++ /dev/null @@ -1,72 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Timeline.Resources.Models.Validation { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class PasswordValidator { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal PasswordValidator() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Timeline.Resources.Models.Validation.PasswordValidator", typeof(PasswordValidator).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to Password can't be empty.. - /// - internal static string MessageEmptyString { - get { - return ResourceManager.GetString("MessageEmptyString", resourceCulture); - } - } - } -} diff --git a/Timeline/Resources/Models/Validation/PasswordValidator.resx b/Timeline/Resources/Models/Validation/PasswordValidator.resx deleted file mode 100644 index f445cc75..00000000 --- a/Timeline/Resources/Models/Validation/PasswordValidator.resx +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Password can't be empty. - - \ No newline at end of file diff --git a/Timeline/Resources/Models/Validation/PasswordValidator.zh.resx b/Timeline/Resources/Models/Validation/PasswordValidator.zh.resx deleted file mode 100644 index 9eab7b4e..00000000 --- a/Timeline/Resources/Models/Validation/PasswordValidator.zh.resx +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - 密码不能为空。 - - \ No newline at end of file diff --git a/Timeline/Resources/Models/Validation/UsernameValidator.zh.resx b/Timeline/Resources/Models/Validation/UsernameValidator.zh.resx deleted file mode 100644 index 89d519b0..00000000 --- a/Timeline/Resources/Models/Validation/UsernameValidator.zh.resx +++ /dev/null @@ -1,129 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - 空字符串是不允许的。 - - - 无效的字符,只能使用字母、数字、下划线和连字符。 - - - 太长了,不能大于26个字符。 - - \ No newline at end of file diff --git a/Timeline/Resources/Models/Validation/Validator.zh.resx b/Timeline/Resources/Models/Validation/Validator.zh.resx deleted file mode 100644 index 2f98e7e3..00000000 --- a/Timeline/Resources/Models/Validation/Validator.zh.resx +++ /dev/null @@ -1,129 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - 值不是类型{0}的实例。 - - - 值不能为null. - - - 验证成功。 - - \ No newline at end of file diff --git a/Timeline/Resources/Services/Exception.Designer.cs b/Timeline/Resources/Services/Exception.Designer.cs index 671c4b93..cada1788 100644 --- a/Timeline/Resources/Services/Exception.Designer.cs +++ b/Timeline/Resources/Services/Exception.Designer.cs @@ -114,6 +114,15 @@ namespace Timeline.Resources.Services { } } + /// + /// Looks up a localized string similar to A present resource conflicts with the given resource.. + /// + internal static string ConfictException { + get { + return ResourceManager.GetString("ConfictException", resourceCulture); + } + } + /// /// Looks up a localized string similar to The hashes password is of bad format. It might not be created by server.. /// @@ -312,15 +321,6 @@ namespace Timeline.Resources.Services { } } - /// - /// Looks up a localized string similar to The username already exists.. - /// - internal static string UsernameConfictException { - get { - return ResourceManager.GetString("UsernameConfictException", resourceCulture); - } - } - /// /// Looks up a localized string similar to The user does not exist.. /// diff --git a/Timeline/Resources/Services/Exception.resx b/Timeline/Resources/Services/Exception.resx index 3ae14d4e..2cb0f11a 100644 --- a/Timeline/Resources/Services/Exception.resx +++ b/Timeline/Resources/Services/Exception.resx @@ -135,6 +135,9 @@ The password is wrong. + + A present resource conflicts with the given resource. + The hashes password is of bad format. It might not be created by server. @@ -201,9 +204,6 @@ The use is not a member of the timeline. - - The username already exists. - The user does not exist. diff --git a/Timeline/Resources/Services/UserManager.Designer.cs b/Timeline/Resources/Services/UserManager.Designer.cs deleted file mode 100644 index 424499f8..00000000 --- a/Timeline/Resources/Services/UserManager.Designer.cs +++ /dev/null @@ -1,72 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Timeline.Resources.Services { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class UserManager { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal UserManager() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Timeline.Resources.Services.UserManager", typeof(UserManager).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to A user has been created.. - /// - internal static string LogUserCreate { - get { - return ResourceManager.GetString("LogUserCreate", resourceCulture); - } - } - } -} diff --git a/Timeline/Resources/Services/UserManager.resx b/Timeline/Resources/Services/UserManager.resx deleted file mode 100644 index ecb89179..00000000 --- a/Timeline/Resources/Services/UserManager.resx +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - A user has been created. - - \ No newline at end of file diff --git a/Timeline/Resources/Services/UserService.Designer.cs b/Timeline/Resources/Services/UserService.Designer.cs index 1b85546d..cdf7f390 100644 --- a/Timeline/Resources/Services/UserService.Designer.cs +++ b/Timeline/Resources/Services/UserService.Designer.cs @@ -69,6 +69,15 @@ namespace Timeline.Resources.Services { } } + /// + /// Looks up a localized string similar to Nickname is of bad format, because {}.. + /// + internal static string ExceptionNicknameBadFormat { + get { + return ResourceManager.GetString("ExceptionNicknameBadFormat", resourceCulture); + } + } + /// /// Looks up a localized string similar to Old username is of bad format.. /// @@ -88,11 +97,11 @@ namespace Timeline.Resources.Services { } /// - /// Looks up a localized string similar to Password can't be null or empty.. + /// Looks up a localized string similar to Password can't be null.. /// - internal static string ExceptionPasswordNullOrEmpty { + internal static string ExceptionPasswordNull { get { - return ResourceManager.GetString("ExceptionPasswordNullOrEmpty", resourceCulture); + return ResourceManager.GetString("ExceptionPasswordNull", resourceCulture); } } @@ -106,29 +115,20 @@ namespace Timeline.Resources.Services { } /// - /// Looks up a localized string similar to Username can't be null or empty.. - /// - internal static string ExceptionUsernameNullOrEmpty { - get { - return ResourceManager.GetString("ExceptionUsernameNullOrEmpty", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to A cache entry is created.. + /// Looks up a localized string similar to A user with given username already exists.. /// - internal static string LogCacheCreate { + internal static string ExceptionUsernameConflict { get { - return ResourceManager.GetString("LogCacheCreate", resourceCulture); + return ResourceManager.GetString("ExceptionUsernameConflict", resourceCulture); } } /// - /// Looks up a localized string similar to A cache entry is removed.. + /// Looks up a localized string similar to Username can't be null.. /// - internal static string LogCacheRemove { + internal static string ExceptionUsernameNull { get { - return ResourceManager.GetString("LogCacheRemove", resourceCulture); + return ResourceManager.GetString("ExceptionUsernameNull", resourceCulture); } } diff --git a/Timeline/Resources/Services/UserService.resx b/Timeline/Resources/Services/UserService.resx index 26221770..09bd4abb 100644 --- a/Timeline/Resources/Services/UserService.resx +++ b/Timeline/Resources/Services/UserService.resx @@ -120,26 +120,26 @@ New username is of bad format. + + Nickname is of bad format, because {}. + Old username is of bad format. Password can't be empty. - - Password can't be null or empty. + + Password can't be null. Username is of bad format, because {}. - - Username can't be null or empty. - - - A cache entry is created. + + A user with given username already exists. - - A cache entry is removed. + + Username can't be null. A new user entry is added to the database. diff --git a/Timeline/Services/ConfictException.cs b/Timeline/Services/ConfictException.cs new file mode 100644 index 00000000..dcd77366 --- /dev/null +++ b/Timeline/Services/ConfictException.cs @@ -0,0 +1,21 @@ +using System; + +namespace Timeline.Services +{ + /// + /// Thrown when a resource already exists and conflicts with the given resource. + /// + /// + /// For example a username already exists and conflicts with the given username. + /// + [Serializable] + public class ConfictException : Exception + { + public ConfictException() : base(Resources.Services.Exception.ConfictException) { } + public ConfictException(string message) : base(message) { } + public ConfictException(string message, Exception inner) : base(message, inner) { } + protected ConfictException( + System.Runtime.Serialization.SerializationInfo info, + System.Runtime.Serialization.StreamingContext context) : base(info, context) { } + } +} diff --git a/Timeline/Services/UserService.cs b/Timeline/Services/UserService.cs index c5595c99..616e70ba 100644 --- a/Timeline/Services/UserService.cs +++ b/Timeline/Services/UserService.cs @@ -21,7 +21,7 @@ namespace Timeline.Services /// The password of the user to verify. /// The user info and auth info. /// Thrown when or is null. - /// Thrown when username is of bad format. + /// Thrown when is of bad format or is empty. /// Thrown when the user with given username does not exist. /// Thrown when password is wrong. Task VerifyCredential(string username, string password); @@ -48,7 +48,7 @@ namespace Timeline.Services /// List all users. /// /// The user info of users. - Task ListUsers(); + Task GetUsers(); /// /// Create a user with given info. @@ -58,11 +58,12 @@ namespace Timeline.Services /// The id of the new user. /// Thrown when is null. /// Thrown when some fields in is bad. - /// Thrown when a user with given username already exists. + /// Thrown when a user with given username already exists. /// /// must not be null and must be a valid username. /// must not be null or empty. /// is false by default (null). + /// must be a valid nickname if set. It is empty by default. /// Other fields are ignored. /// Task CreateUser(User info); @@ -75,61 +76,70 @@ namespace Timeline.Services /// Thrown when some fields in is bad. /// Thrown when user with given id does not exist. /// - /// Only , and will be used. + /// Only , , and will be used. /// If null, then not change. /// Other fields are ignored. /// After modified, even if nothing is changed, version will increase. /// - /// can't be empty. + /// must be a valid username if set. + /// can't be empty if set. + /// must be a valid nickname if set. /// /// Note: Whether is set or not, version will increase and not set to the specified value if there is one. /// + /// Task ModifyUser(long id, User? info); /// - /// Partially modify a user of given username. + /// Modify a user's info. + /// + /// The username of the user. + /// The new info. May be null. + /// Thrown when is null. + /// Thrown when is of bad format or some fields in is bad. + /// Thrown when user with given id does not exist. + /// + /// Only , and will be used. + /// If null, then not change. + /// Other fields are ignored. + /// After modified, even if nothing is changed, version will increase. + /// + /// must be a valid username if set. + /// can't be empty if set. + /// must be a valid nickname if set. /// - /// Note that whether actually modified or not, Version of the user will always increase. + /// Note: Whether is set or not, version will increase and not set to the specified value if there is one. + /// + /// + Task ModifyUser(string username, User? info); + + /// + /// Delete a user of given id. /// - /// 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 when is of bad format. - /// Thrown if the user with given username does not exist. - Task PatchUser(string username, string? password, bool? administrator); + /// Id of the user to delete. + /// True if user is deleted, false if user not exist. + Task DeleteUser(long id); /// /// Delete a user of given username. /// - /// Username of thet user to delete. Can't be null. + /// Username of the user to delete. Can't be null. + /// True if user is deleted, false if user not exist. /// Thrown if is null. - /// Thrown when is of bad format. - /// Thrown if the user with given username does not exist. - Task DeleteUser(string username); + /// Thrown when is of bad format. + Task DeleteUser(string username); /// /// Try to change a user's password with old password. /// - /// The name of user to change password of. - /// The user's old password. - /// The user's new password. - /// Thrown if or or is null. - /// Thrown when is of bad format. + /// The id of user to change password of. + /// Old password. + /// New password. + /// Thrown if or is null. + /// Thrown if or is empty. /// 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. - /// Thrown if the user with old username does not exist. - /// Thrown if the or is of bad format. - /// Thrown if user with the new username already exists. - Task ChangeUsername(string oldUsername, string newUsername); + Task ChangePassword(long id, string oldPassword, string newPassword); } public class UserService : IUserService @@ -138,11 +148,10 @@ namespace Timeline.Services private readonly DatabaseContext _databaseContext; - private readonly IPasswordService _passwordService; private readonly UsernameValidator _usernameValidator = new UsernameValidator(); - + private readonly NicknameValidator _nicknameValidator = new NicknameValidator(); public UserService(ILogger logger, DatabaseContext databaseContext, IPasswordService passwordService) { _logger = logger; @@ -150,17 +159,35 @@ namespace Timeline.Services _passwordService = passwordService; } - private void CheckUsernameFormat(string username, string? paramName, Func? messageBuilder = null) + private void CheckUsernameFormat(string username, string? paramName) { if (!_usernameValidator.Validate(username, out var message)) { - if (messageBuilder == null) - throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, ExceptionUsernameBadFormat, message), paramName); - else - throw new ArgumentException(messageBuilder(message), paramName); + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, ExceptionUsernameBadFormat, message), paramName); + } + } + + private static void CheckPasswordFormat(string password, string? paramName) + { + if (password.Length == 0) + { + throw new ArgumentException(ExceptionPasswordEmpty, paramName); } } + private void CheckNicknameFormat(string nickname, string? paramName) + { + if (!_nicknameValidator.Validate(nickname, out var message)) + { + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, ExceptionNicknameBadFormat, message), paramName); + } + } + + private static void ThrowUsernameConflict() + { + throw new ConfictException(ExceptionUsernameConflict); + } + private static User CreateUserFromEntity(UserEntity entity) { return new User @@ -168,6 +195,7 @@ namespace Timeline.Services Username = entity.Username, Administrator = UserRoleConvert.ToBool(entity.Roles), Nickname = string.IsNullOrEmpty(entity.Nickname) ? entity.Username : entity.Nickname, + Id = entity.Id, Version = entity.Version }; } @@ -180,6 +208,7 @@ namespace Timeline.Services throw new ArgumentNullException(nameof(password)); CheckUsernameFormat(username, nameof(username)); + CheckPasswordFormat(password, nameof(password)); var entity = await _databaseContext.Users.Where(u => u.Username == username).SingleOrDefaultAsync(); @@ -217,7 +246,7 @@ namespace Timeline.Services return CreateUserFromEntity(entity); } - public async Task ListUsers() + public async Task GetUsers() { var entities = await _databaseContext.Users.ToArrayAsync(); return entities.Select(user => CreateUserFromEntity(user)).ToArray(); @@ -228,20 +257,22 @@ namespace Timeline.Services if (info == null) throw new ArgumentNullException(nameof(info)); - if (string.IsNullOrEmpty(info.Username)) - throw new ArgumentException(ExceptionUsernameNullOrEmpty, nameof(info)); - + if (info.Username == null) + throw new ArgumentException(ExceptionUsernameNull, nameof(info)); CheckUsernameFormat(info.Username, nameof(info)); - if (string.IsNullOrEmpty(info.Password)) - throw new ArgumentException(ExceptionPasswordNullOrEmpty); + if (info.Password == null) + throw new ArgumentException(ExceptionPasswordNull, nameof(info)); + CheckPasswordFormat(info.Password, nameof(info)); + + if (info.Nickname != null) + CheckNicknameFormat(info.Nickname, nameof(info)); var username = info.Username; var conflict = await _databaseContext.Users.AnyAsync(u => u.Username == username); - if (conflict) - throw new UsernameConfictException(username); + ThrowUsernameConflict(); var administrator = info.Administrator ?? false; var password = info.Password; @@ -262,17 +293,35 @@ namespace Timeline.Services return newEntity.Id; } - public async Task ModifyUser(long id, User? info) + private void ValidateModifyUserInfo(User? info) { - if (info != null && info.Password != null && info.Password.Length == 0) - throw new ArgumentException(ExceptionPasswordEmpty, nameof(info)); + if (info != null) + { + if (info.Username != null) + CheckUsernameFormat(info.Username, nameof(info)); - var entity = await _databaseContext.Users.Where(u => u.Id == id).SingleOrDefaultAsync(); - if (entity == null) - throw new UserNotExistException(id); + if (info.Password != null) + CheckPasswordFormat(info.Password, nameof(info)); + + if (info.Nickname != null) + CheckNicknameFormat(info.Nickname, nameof(info)); + } + } + private async Task UpdateUserEntity(UserEntity entity, User? info) + { if (info != null) { + var username = info.Username; + if (username != null) + { + var conflict = await _databaseContext.Users.AnyAsync(u => u.Username == username); + if (conflict) + ThrowUsernameConflict(); + + entity.Username = username; + } + var password = info.Password; if (password != null) { @@ -293,82 +342,90 @@ namespace Timeline.Services } entity.Version += 1; + } + + + public async Task ModifyUser(long id, User? info) + { + ValidateModifyUserInfo(info); + + var entity = await _databaseContext.Users.Where(u => u.Id == id).SingleOrDefaultAsync(); + if (entity == null) + throw new UserNotExistException(id); + + await UpdateUserEntity(entity, info); await _databaseContext.SaveChangesAsync(); _logger.LogInformation(LogDatabaseUpdate, ("Id", id)); } - public async Task DeleteUser(string username) + public async Task ModifyUser(string username, User? info) { if (username == null) throw new ArgumentNullException(nameof(username)); - CheckUsernameFormat(username); + CheckUsernameFormat(username, nameof(username)); - var user = await _databaseContext.Users.Where(u => u.Username == username).SingleOrDefaultAsync(); - if (user == null) + ValidateModifyUserInfo(info); + + var entity = await _databaseContext.Users.Where(u => u.Username == username).SingleOrDefaultAsync(); + if (entity == null) throw new UserNotExistException(username); - _databaseContext.Users.Remove(user); + await UpdateUserEntity(entity, info); + await _databaseContext.SaveChangesAsync(); - _logger.LogInformation(Log.Format(Resources.Services.UserService.LogDatabaseRemove, - ("Id", user.Id))); + _logger.LogInformation(LogDatabaseUpdate, ("Username", username)); + } + + public async Task DeleteUser(long id) + { + var user = await _databaseContext.Users.Where(u => u.Id == id).SingleOrDefaultAsync(); + if (user == null) + return false; - //clear cache - await _cache.RemoveCache(user.Id); + _databaseContext.Users.Remove(user); + await _databaseContext.SaveChangesAsync(); + _logger.LogInformation(Log.Format(LogDatabaseRemove, ("Id", id), ("Username", user.Username))); + return true; } - public async Task ChangePassword(string username, string oldPassword, string newPassword) + public async Task DeleteUser(string username) { if (username == null) throw new ArgumentNullException(nameof(username)); - if (oldPassword == null) - throw new ArgumentNullException(nameof(oldPassword)); - if (newPassword == null) - throw new ArgumentNullException(nameof(newPassword)); - CheckUsernameFormat(username); + CheckUsernameFormat(username, nameof(username)); var user = await _databaseContext.Users.Where(u => u.Username == username).SingleOrDefaultAsync(); if (user == null) - throw new UserNotExistException(username); - - var verifyResult = _passwordService.VerifyPassword(user.Password, oldPassword); - if (!verifyResult) - throw new BadPasswordException(oldPassword); + return false; - user.Password = _passwordService.HashPassword(newPassword); - user.Version += 1; + _databaseContext.Users.Remove(user); await _databaseContext.SaveChangesAsync(); - _logger.LogInformation(Log.Format(Resources.Services.UserService.LogDatabaseUpdate, - ("Id", user.Id), ("Operation", "Change password"))); - //clear cache - await _cache.RemoveCache(user.Id); + _logger.LogInformation(Log.Format(LogDatabaseRemove, ("Id", user.Id), ("Username", username))); + return true; } - public async Task ChangeUsername(string oldUsername, string newUsername) + public async Task ChangePassword(long id, string oldPassword, string newPassword) { - if (oldUsername == null) - throw new ArgumentNullException(nameof(oldUsername)); - if (newUsername == null) - throw new ArgumentNullException(nameof(newUsername)); - CheckUsernameFormat(oldUsername, Resources.Services.UserService.ExceptionOldUsernameBadFormat); - CheckUsernameFormat(newUsername, Resources.Services.UserService.ExceptionNewUsernameBadFormat); - - var user = await _databaseContext.Users.Where(u => u.Username == oldUsername).SingleOrDefaultAsync(); - if (user == null) - throw new UserNotExistException(oldUsername); + if (oldPassword == null) + throw new ArgumentNullException(nameof(oldPassword)); + if (newPassword == null) + throw new ArgumentNullException(nameof(newPassword)); + CheckPasswordFormat(oldPassword, nameof(oldPassword)); + CheckPasswordFormat(newPassword, nameof(newPassword)); - var conflictUser = await _databaseContext.Users.Where(u => u.Username == newUsername).SingleOrDefaultAsync(); - if (conflictUser != null) - throw new UsernameConfictException(newUsername); + var entity = await _databaseContext.Users.Where(u => u.Id == id).SingleOrDefaultAsync(); - user.Username = newUsername; - user.Version += 1; - await _databaseContext.SaveChangesAsync(); - _logger.LogInformation(Log.Format(Resources.Services.UserService.LogDatabaseUpdate, - ("Id", user.Id), ("Old Username", oldUsername), ("New Username", newUsername))); - await _cache.RemoveCache(user.Id); - } + if (entity == null) + throw new UserNotExistException(id); + if (!_passwordService.VerifyPassword(entity.Password, oldPassword)) + throw new BadPasswordException(oldPassword); + entity.Password = _passwordService.HashPassword(newPassword); + entity.Version += 1; + await _databaseContext.SaveChangesAsync(); + _logger.LogInformation(Log.Format(LogDatabaseUpdate, ("Id", id), ("Operation", "Change password"))); + } } } diff --git a/Timeline/Services/UserTokenException.cs b/Timeline/Services/UserTokenException.cs index e63305b1..ed0bae1a 100644 --- a/Timeline/Services/UserTokenException.cs +++ b/Timeline/Services/UserTokenException.cs @@ -1,7 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; namespace Timeline.Services { diff --git a/Timeline/Services/UserTokenManager.cs b/Timeline/Services/UserTokenManager.cs index a2c2980d..3e9ef3d4 100644 --- a/Timeline/Services/UserTokenManager.cs +++ b/Timeline/Services/UserTokenManager.cs @@ -62,7 +62,7 @@ namespace Timeline.Services throw new ArgumentNullException(nameof(password)); var user = await _userService.VerifyCredential(username, password); - var token = _userTokenService.GenerateToken(new UserTokenInfo { Id = user.Id, Version = user.Version, ExpireAt = expireAt }); + var token = _userTokenService.GenerateToken(new UserTokenInfo { Id = user.Id!.Value, Version = user.Version!.Value, ExpireAt = expireAt }); return new UserTokenCreateResult { Token = token, User = user }; } @@ -85,7 +85,7 @@ namespace Timeline.Services var user = await _userService.GetUserById(tokenInfo.Id); if (tokenInfo.Version < user.Version) - throw new UserTokenBadVersionException(token, tokenInfo.Version, user.Version); + throw new UserTokenBadVersionException(token, tokenInfo.Version, user.Version.Value); return user; } diff --git a/Timeline/Services/UsernameConfictException.cs b/Timeline/Services/UsernameConfictException.cs deleted file mode 100644 index fde1eda6..00000000 --- a/Timeline/Services/UsernameConfictException.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using Timeline.Helpers; - -namespace Timeline.Services -{ - /// - /// Thrown when the user already exists. - /// - [Serializable] - public class UsernameConfictException : Exception - { - public UsernameConfictException() : base(Resources.Services.Exception.UsernameConfictException) { } - public UsernameConfictException(string username) : base(Log.Format(Resources.Services.Exception.UsernameConfictException, ("Username", username))) { Username = username; } - public UsernameConfictException(string username, string message) : base(message) { Username = username; } - public UsernameConfictException(string message, Exception inner) : base(message, inner) { } - protected UsernameConfictException( - System.Runtime.Serialization.SerializationInfo info, - System.Runtime.Serialization.StreamingContext context) : base(info, context) { } - - /// - /// The username that already exists. - /// - public string? Username { get; set; } - } -} diff --git a/Timeline/Startup.cs b/Timeline/Startup.cs index 379ce6ea..091a16e5 100644 --- a/Timeline/Startup.cs +++ b/Timeline/Startup.cs @@ -1,14 +1,11 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.HttpOverrides; -using Microsoft.AspNetCore.Localization; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using System; -using System.Collections.Generic; -using System.Globalization; using System.Text.Json.Serialization; using Timeline.Auth; using Timeline.Configs; @@ -89,7 +86,6 @@ namespace Timeline services.AddTransient(); services.AddTransient(); services.AddUserAvatarService(); - services.AddScoped(); services.AddScoped(); @@ -113,8 +109,6 @@ namespace Timeline options.UseMySql(databaseConfig.ConnectionString); }); } - - services.AddMemoryCache(); } @@ -128,19 +122,6 @@ namespace Timeline app.UseRouting(); - var supportedCultures = new List - { - new CultureInfo("en"), - new CultureInfo("zh") - }; - - app.UseRequestLocalization(new RequestLocalizationOptions - { - DefaultRequestCulture = new RequestCulture("en"), - SupportedCultures = supportedCultures, - SupportedUICultures = supportedCultures - }); - app.UseCors(); app.UseAuthentication(); diff --git a/Timeline/Timeline.csproj b/Timeline/Timeline.csproj index 195252d9..82b45094 100644 --- a/Timeline/Timeline.csproj +++ b/Timeline/Timeline.csproj @@ -82,10 +82,10 @@ True Common.resx - + True True - PasswordValidator.resx + NicknameValidator.resx True @@ -117,11 +117,6 @@ True UserDetailService.resx - - True - True - UserManager.resx - True True @@ -167,9 +162,9 @@ ResXFileCodeGenerator Common.Designer.cs - + ResXFileCodeGenerator - PasswordValidator.Designer.cs + NicknameValidator.Designer.cs ResXFileCodeGenerator @@ -195,10 +190,6 @@ ResXFileCodeGenerator UserDetailService.Designer.cs - - ResXFileCodeGenerator - UserManager.Designer.cs - ResXFileCodeGenerator UserService.Designer.cs -- cgit v1.2.3