From a665f5d894539cae5f4188e4a72ea9634b8c4ed0 Mon Sep 17 00:00:00 2001 From: crupest Date: Tue, 27 Apr 2021 19:29:20 +0800 Subject: refactor: ... --- BackEnd/Timeline.Tests/Helpers/TestDatabase.cs | 8 +- .../IntegratedTests/IntegratedTestBase.cs | 6 +- .../Timeline.Tests/IntegratedTests/TokenTest.cs | 4 +- .../Timeline.Tests/Services/SearchServiceTest.cs | 8 +- BackEnd/Timeline.Tests/Services/ServiceTestBase.cs | 4 +- BackEnd/Timeline/Controllers/TimelineController.cs | 6 +- BackEnd/Timeline/Controllers/TokenController.cs | 4 +- .../Timeline/Controllers/UserAvatarController.cs | 6 +- BackEnd/Timeline/Controllers/UserController.cs | 25 ++-- .../Timeline/Models/Http/HttpUserPostRequest.cs | 3 + .../Resources/Services/UserService.Designer.cs | 162 -------------------- .../Timeline/Resources/Services/UserService.resx | 153 ------------------- .../Services/Api/BookmarkTimelineService.cs | 10 +- .../Services/Api/HighlightTimelineService.cs | 4 +- .../BasicServicesServiceCollectionExtensions.cs | 1 + .../Timeline/Services/Imaging/ImageException.cs | 2 +- .../Services/Timeline/BasicTimelineService.cs | 2 +- .../Timeline/TimelinePostNotExistException.cs | 2 +- .../Services/Timeline/TimelinePostService.cs | 2 +- .../Timeline/Services/Timeline/TimelineService.cs | 4 +- .../Timeline/Services/Token/UserTokenManager.cs | 10 +- BackEnd/Timeline/Services/User/BasicUserService.cs | 39 +---- BackEnd/Timeline/Services/User/CreateUserParams.cs | 17 +++ .../Timeline/Services/User/IBasicUserService.cs | 36 +++++ .../Timeline/Services/User/IUserDeleteService.cs | 18 +++ .../Services/User/IUserPermissionService.cs | 35 +++++ BackEnd/Timeline/Services/User/IUserService.cs | 71 +++++++++ .../User/InvalidOperationOnRootUserException.cs | 2 +- BackEnd/Timeline/Services/User/ModifyUserParams.cs | 12 ++ BackEnd/Timeline/Services/User/PasswordService.cs | 2 +- .../Timeline/Services/User/Resource.Designer.cs | 81 ++++++++++ BackEnd/Timeline/Services/User/Resource.resx | 27 ++++ .../Timeline/Services/User/UserAvatarService.cs | 2 +- .../Services/User/UserCredentialService.cs | 101 ------------- .../Timeline/Services/User/UserDeleteService.cs | 16 +- BackEnd/Timeline/Services/User/UserPermission.cs | 18 +++ .../Services/User/UserPermissionService.cs | 166 +-------------------- BackEnd/Timeline/Services/User/UserPermissions.cs | 119 +++++++++++++++ BackEnd/Timeline/Services/User/UserService.cs | 148 +++++++++--------- BackEnd/Timeline/Startup.cs | 3 - BackEnd/Timeline/Timeline.csproj | 9 -- 41 files changed, 573 insertions(+), 775 deletions(-) delete mode 100644 BackEnd/Timeline/Resources/Services/UserService.Designer.cs delete mode 100644 BackEnd/Timeline/Resources/Services/UserService.resx create mode 100644 BackEnd/Timeline/Services/User/CreateUserParams.cs create mode 100644 BackEnd/Timeline/Services/User/IBasicUserService.cs create mode 100644 BackEnd/Timeline/Services/User/IUserDeleteService.cs create mode 100644 BackEnd/Timeline/Services/User/IUserPermissionService.cs create mode 100644 BackEnd/Timeline/Services/User/IUserService.cs create mode 100644 BackEnd/Timeline/Services/User/ModifyUserParams.cs delete mode 100644 BackEnd/Timeline/Services/User/UserCredentialService.cs create mode 100644 BackEnd/Timeline/Services/User/UserPermission.cs create mode 100644 BackEnd/Timeline/Services/User/UserPermissions.cs (limited to 'BackEnd') diff --git a/BackEnd/Timeline.Tests/Helpers/TestDatabase.cs b/BackEnd/Timeline.Tests/Helpers/TestDatabase.cs index 24f4a922..9bd690a2 100644 --- a/BackEnd/Timeline.Tests/Helpers/TestDatabase.cs +++ b/BackEnd/Timeline.Tests/Helpers/TestDatabase.cs @@ -26,12 +26,12 @@ namespace Timeline.Tests.Helpers var userService = new UserService(NullLogger.Instance, context, new PasswordService(), new Clock()); - await userService.ModifyUser( - await userService.GetUserIdByUsername("administrator"), + await userService.ModifyUserAsync( + await userService.GetUserIdByUsernameAsync("administrator"), new ModifyUserParams() { Username = "admin", Password = "adminpw", Nickname = "administrator" }); - var user = await userService.CreateUser("user", "userpw"); - await userService.ModifyUser(user.Id, new ModifyUserParams() { Nickname = "imuser" }); + var user = await userService.CreateUserAsync(new CreateUserParams("user", "userpw")); + await userService.ModifyUserAsync(user.Id, new ModifyUserParams() { Nickname = "imuser" }); } public async Task DisposeAsync() diff --git a/BackEnd/Timeline.Tests/IntegratedTests/IntegratedTestBase.cs b/BackEnd/Timeline.Tests/IntegratedTests/IntegratedTestBase.cs index 427881a0..588f2f93 100644 --- a/BackEnd/Timeline.Tests/IntegratedTests/IntegratedTestBase.cs +++ b/BackEnd/Timeline.Tests/IntegratedTests/IntegratedTestBase.cs @@ -65,7 +65,7 @@ namespace Timeline.Tests.IntegratedTests var userService = scope.ServiceProvider.GetRequiredService(); - await userService.ModifyUser(await userService.GetUserIdByUsername("administrator"), new ModifyUserParams + await userService.ModifyUserAsync(await userService.GetUserIdByUsernameAsync("administrator"), new ModifyUserParams { Username = "admin", Password = "adminpw", @@ -75,8 +75,8 @@ namespace Timeline.Tests.IntegratedTests foreach (var user in users) { var (username, password, nickname) = user; - var u = await userService.CreateUser(username, password); - await userService.ModifyUser(u.Id, new ModifyUserParams() { Nickname = nickname }); + var u = await userService.CreateUserAsync(new CreateUserParams(username, password)); + await userService.ModifyUserAsync(u.Id, new ModifyUserParams() { Nickname = nickname }); } } diff --git a/BackEnd/Timeline.Tests/IntegratedTests/TokenTest.cs b/BackEnd/Timeline.Tests/IntegratedTests/TokenTest.cs index 7206dab8..4d4835ca 100644 --- a/BackEnd/Timeline.Tests/IntegratedTests/TokenTest.cs +++ b/BackEnd/Timeline.Tests/IntegratedTests/TokenTest.cs @@ -92,8 +92,8 @@ namespace Timeline.Tests.IntegratedTests { // create a user for test var userService = scope.ServiceProvider.GetRequiredService(); - var id = await userService.GetUserIdByUsername("user1"); - await userService.ModifyUser(id, new ModifyUserParams { Password = "user1pw" }); + var id = await userService.GetUserIdByUsernameAsync("user1"); + await userService.ModifyUserAsync(id, new ModifyUserParams { Password = "user1pw" }); } await client.TestPostAssertErrorAsync(VerifyTokenUrl, diff --git a/BackEnd/Timeline.Tests/Services/SearchServiceTest.cs b/BackEnd/Timeline.Tests/Services/SearchServiceTest.cs index 0aa412b2..1e94076e 100644 --- a/BackEnd/Timeline.Tests/Services/SearchServiceTest.cs +++ b/BackEnd/Timeline.Tests/Services/SearchServiceTest.cs @@ -35,10 +35,10 @@ namespace Timeline.Tests.Services [Fact] public async Task UserSearch_Should_Work() { - await UserService.CreateUser("hahaha", "p"); - var u2 = await UserService.CreateUser("bababa", "p"); - await UserService.ModifyUser(u2.Id, new ModifyUserParams { Nickname = "hahaha" }); - await UserService.CreateUser("bbbbbb", "p"); + await UserService.CreateUserAsync(new CreateUserParams("hahaha", "p")); + var u2 = await UserService.CreateUserAsync(new CreateUserParams("bababa", "p")); + await UserService.ModifyUserAsync(u2.Id, new ModifyUserParams { Nickname = "hahaha" }); + await UserService.CreateUserAsync(new CreateUserParams("bbbbbb", "p")); var searchResult = await _service.SearchUser("hah"); searchResult.Items.Should().HaveCount(2); diff --git a/BackEnd/Timeline.Tests/Services/ServiceTestBase.cs b/BackEnd/Timeline.Tests/Services/ServiceTestBase.cs index 0f4efe95..2c1bdea8 100644 --- a/BackEnd/Timeline.Tests/Services/ServiceTestBase.cs +++ b/BackEnd/Timeline.Tests/Services/ServiceTestBase.cs @@ -37,8 +37,8 @@ namespace Timeline.Tests.Services UserService = new UserService(NullLogger.Instance, Database, new PasswordService(), Clock); TimelineService = new TimelineService(Database, UserService, Clock); - UserId = await UserService.GetUserIdByUsername("user"); - AdminId = await UserService.GetUserIdByUsername("admin"); + UserId = await UserService.GetUserIdByUsernameAsync("user"); + AdminId = await UserService.GetUserIdByUsernameAsync("admin"); await OnInitializeAsync(); OnInitialize(); diff --git a/BackEnd/Timeline/Controllers/TimelineController.cs b/BackEnd/Timeline/Controllers/TimelineController.cs index fc28daa4..2feea476 100644 --- a/BackEnd/Timeline/Controllers/TimelineController.cs +++ b/BackEnd/Timeline/Controllers/TimelineController.cs @@ -101,7 +101,7 @@ namespace Timeline.Controllers { try { - var relatedUserId = await _userService.GetUserIdByUsername(relate); + var relatedUserId = await _userService.GetUserIdByUsernameAsync(relate); var relationType = relateType is null ? TimelineUserRelationshipType.Default : Enum.Parse(relateType, true); @@ -190,7 +190,7 @@ namespace Timeline.Controllers try { - var userId = await _userService.GetUserIdByUsername(member); + var userId = await _userService.GetUserIdByUsernameAsync(member); var create = await _service.AddMember(timelineId, userId); return Ok(CommonPutResponse.Create(create)); } @@ -222,7 +222,7 @@ namespace Timeline.Controllers try { - var userId = await _userService.GetUserIdByUsername(member); + var userId = await _userService.GetUserIdByUsernameAsync(member); var delete = await _service.RemoveMember(timelineId, userId); return Ok(CommonDeleteResponse.Create(delete)); } diff --git a/BackEnd/Timeline/Controllers/TokenController.cs b/BackEnd/Timeline/Controllers/TokenController.cs index 1de45754..fc19c64a 100644 --- a/BackEnd/Timeline/Controllers/TokenController.cs +++ b/BackEnd/Timeline/Controllers/TokenController.cs @@ -23,16 +23,14 @@ namespace Timeline.Controllers [ProducesErrorResponseType(typeof(CommonResponse))] public class TokenController : Controller { - private readonly IUserCredentialService _userCredentialService; private readonly IUserTokenManager _userTokenManager; private readonly ILogger _logger; private readonly UserMapper _userMapper; private readonly IClock _clock; /// - public TokenController(IUserCredentialService userCredentialService, IUserTokenManager userTokenManager, ILogger logger, UserMapper userMapper, IClock clock) + public TokenController(IUserTokenManager userTokenManager, ILogger logger, UserMapper userMapper, IClock clock) { - _userCredentialService = userCredentialService; _userTokenManager = userTokenManager; _logger = logger; _userMapper = userMapper; diff --git a/BackEnd/Timeline/Controllers/UserAvatarController.cs b/BackEnd/Timeline/Controllers/UserAvatarController.cs index d0998fa7..5d4c70f3 100644 --- a/BackEnd/Timeline/Controllers/UserAvatarController.cs +++ b/BackEnd/Timeline/Controllers/UserAvatarController.cs @@ -55,7 +55,7 @@ namespace Timeline.Controllers long id; try { - id = await _userService.GetUserIdByUsername(username); + id = await _userService.GetUserIdByUsernameAsync(username); } catch (UserNotExistException e) { @@ -91,7 +91,7 @@ namespace Timeline.Controllers long id; try { - id = await _userService.GetUserIdByUsername(username); + id = await _userService.GetUserIdByUsernameAsync(username); } catch (UserNotExistException e) { @@ -150,7 +150,7 @@ namespace Timeline.Controllers long id; try { - id = await _userService.GetUserIdByUsername(username); + id = await _userService.GetUserIdByUsernameAsync(username); } catch (UserNotExistException e) { diff --git a/BackEnd/Timeline/Controllers/UserController.cs b/BackEnd/Timeline/Controllers/UserController.cs index 95f65d25..a48a1b73 100644 --- a/BackEnd/Timeline/Controllers/UserController.cs +++ b/BackEnd/Timeline/Controllers/UserController.cs @@ -26,18 +26,16 @@ namespace Timeline.Controllers { private readonly ILogger _logger; private readonly IUserService _userService; - private readonly IUserCredentialService _userCredentialService; private readonly IUserPermissionService _userPermissionService; private readonly IUserDeleteService _userDeleteService; private readonly UserMapper _userMapper; private readonly IMapper _mapper; /// - public UserController(ILogger logger, IUserService userService, IUserCredentialService userCredentialService, IUserPermissionService userPermissionService, IUserDeleteService userDeleteService, UserMapper userMapper, IMapper mapper) + public UserController(ILogger logger, IUserService userService, IUserPermissionService userPermissionService, IUserDeleteService userDeleteService, UserMapper userMapper, IMapper mapper) { _logger = logger; _userService = userService; - _userCredentialService = userCredentialService; _userPermissionService = userPermissionService; _userDeleteService = userDeleteService; _userMapper = userMapper; @@ -54,7 +52,7 @@ namespace Timeline.Controllers [ProducesResponseType(StatusCodes.Status200OK)] public async Task>> List() { - var users = await _userService.GetUsers(); + var users = await _userService.GetUsersAsync(); var result = await _userMapper.MapToHttp(users, Url); return result; } @@ -72,7 +70,8 @@ namespace Timeline.Controllers { try { - var user = await _userService.CreateUser(body.Username, body.Password); + var user = await _userService.CreateUserAsync( + new CreateUserParams(body.Username, body.Password) { Nickname = body.Nickname }); return await _userMapper.MapToHttp(user, Url); } catch (EntityAlreadyExistException e) when (e.EntityName == EntityNames.User) @@ -93,8 +92,8 @@ namespace Timeline.Controllers { try { - var id = await _userService.GetUserIdByUsername(username); - var user = await _userService.GetUser(id); + var id = await _userService.GetUserIdByUsernameAsync(username); + var user = await _userService.GetUserAsync(id); return await _userMapper.MapToHttp(user, Url); } catch (UserNotExistException e) @@ -122,8 +121,8 @@ namespace Timeline.Controllers { try { - var id = await _userService.GetUserIdByUsername(username); - var user = await _userService.ModifyUser(id, _mapper.Map(body)); + var id = await _userService.GetUserIdByUsernameAsync(username); + var user = await _userService.ModifyUserAsync(id, _mapper.Map(body)); return await _userMapper.MapToHttp(user, Url); } catch (UserNotExistException e) @@ -150,7 +149,7 @@ namespace Timeline.Controllers return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.CustomMessage_Forbid(UserController_Patch_Forbid_Password)); - var user = await _userService.ModifyUser(this.GetUserId(), _mapper.Map(body)); + var user = await _userService.ModifyUserAsync(this.GetUserId(), _mapper.Map(body)); return await _userMapper.MapToHttp(user, Url); } } @@ -192,7 +191,7 @@ namespace Timeline.Controllers { try { - await _userCredentialService.ChangePassword(this.GetUserId(), request.OldPassword, request.NewPassword); + await _userService.ChangePassword(this.GetUserId(), request.OldPassword, request.NewPassword); return Ok(); } catch (BadPasswordException e) @@ -214,7 +213,7 @@ namespace Timeline.Controllers { try { - var id = await _userService.GetUserIdByUsername(username); + var id = await _userService.GetUserIdByUsernameAsync(username); await _userPermissionService.AddPermissionToUserAsync(id, permission); return Ok(); } @@ -238,7 +237,7 @@ namespace Timeline.Controllers { try { - var id = await _userService.GetUserIdByUsername(username); + var id = await _userService.GetUserIdByUsernameAsync(username); await _userPermissionService.RemovePermissionFromUserAsync(id, permission); return Ok(); } diff --git a/BackEnd/Timeline/Models/Http/HttpUserPostRequest.cs b/BackEnd/Timeline/Models/Http/HttpUserPostRequest.cs index c8cc004b..4eb02928 100644 --- a/BackEnd/Timeline/Models/Http/HttpUserPostRequest.cs +++ b/BackEnd/Timeline/Models/Http/HttpUserPostRequest.cs @@ -20,5 +20,8 @@ namespace Timeline.Models.Http /// [Required, MinLength(1)] public string Password { get; set; } = default!; + + [Nickname] + public string? Nickname { get; set; } } } diff --git a/BackEnd/Timeline/Resources/Services/UserService.Designer.cs b/BackEnd/Timeline/Resources/Services/UserService.Designer.cs deleted file mode 100644 index 564dd26c..00000000 --- a/BackEnd/Timeline/Resources/Services/UserService.Designer.cs +++ /dev/null @@ -1,162 +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 UserService { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal UserService() { - } - - /// - /// 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.UserService", typeof(UserService).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 New username is of bad format.. - /// - internal static string ExceptionNewUsernameBadFormat { - get { - return ResourceManager.GetString("ExceptionNewUsernameBadFormat", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Nickname is of bad format, because {0}.. - /// - internal static string ExceptionNicknameBadFormat { - get { - return ResourceManager.GetString("ExceptionNicknameBadFormat", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Old username is of bad format.. - /// - internal static string ExceptionOldUsernameBadFormat { - get { - return ResourceManager.GetString("ExceptionOldUsernameBadFormat", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Password can't be empty.. - /// - internal static string ExceptionPasswordEmpty { - get { - return ResourceManager.GetString("ExceptionPasswordEmpty", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Password can't be null.. - /// - internal static string ExceptionPasswordNull { - get { - return ResourceManager.GetString("ExceptionPasswordNull", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Username is of bad format, because {0}.. - /// - internal static string ExceptionUsernameBadFormat { - get { - return ResourceManager.GetString("ExceptionUsernameBadFormat", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to A user with given username already exists.. - /// - internal static string ExceptionUsernameConflict { - get { - return ResourceManager.GetString("ExceptionUsernameConflict", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Username can't be null.. - /// - internal static string ExceptionUsernameNull { - get { - return ResourceManager.GetString("ExceptionUsernameNull", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to A new user entry is added to the database.. - /// - internal static string LogDatabaseCreate { - get { - return ResourceManager.GetString("LogDatabaseCreate", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to A user entry is removed from the database.. - /// - internal static string LogDatabaseRemove { - get { - return ResourceManager.GetString("LogDatabaseRemove", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to A user entry is updated to the database.. - /// - internal static string LogDatabaseUpdate { - get { - return ResourceManager.GetString("LogDatabaseUpdate", resourceCulture); - } - } - } -} diff --git a/BackEnd/Timeline/Resources/Services/UserService.resx b/BackEnd/Timeline/Resources/Services/UserService.resx deleted file mode 100644 index 1f3c0011..00000000 --- a/BackEnd/Timeline/Resources/Services/UserService.resx +++ /dev/null @@ -1,153 +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 - - - New username is of bad format. - - - Nickname is of bad format, because {0}. - - - Old username is of bad format. - - - Password can't be empty. - - - Password can't be null. - - - Username is of bad format, because {0}. - - - A user with given username already exists. - - - Username can't be null. - - - A new user entry is added to the database. - - - A user entry is removed from the database. - - - A user entry is updated to the database. - - \ No newline at end of file diff --git a/BackEnd/Timeline/Services/Api/BookmarkTimelineService.cs b/BackEnd/Timeline/Services/Api/BookmarkTimelineService.cs index 0d4cc0a6..cabc1db2 100644 --- a/BackEnd/Timeline/Services/Api/BookmarkTimelineService.cs +++ b/BackEnd/Timeline/Services/Api/BookmarkTimelineService.cs @@ -93,7 +93,7 @@ namespace Timeline.Services.Api public async Task AddBookmark(long userId, long timelineId) { - if (!await _userService.CheckUserExistence(userId)) + if (!await _userService.CheckUserExistenceAsync(userId)) throw new UserNotExistException(userId); if (!await _timelineService.CheckExistence(timelineId)) @@ -115,7 +115,7 @@ namespace Timeline.Services.Api public async Task> GetBookmarks(long userId) { - if (!await _userService.CheckUserExistence(userId)) + if (!await _userService.CheckUserExistenceAsync(userId)) throw new UserNotExistException(userId); var entities = await _database.BookmarkTimelines.Where(t => t.UserId == userId).OrderBy(t => t.Rank).Select(t => new { t.TimelineId }).ToListAsync(); @@ -125,7 +125,7 @@ namespace Timeline.Services.Api public async Task IsBookmark(long userId, long timelineId, bool checkUserExistence = true, bool checkTimelineExistence = true) { - if (checkUserExistence && !await _userService.CheckUserExistence(userId)) + if (checkUserExistence && !await _userService.CheckUserExistenceAsync(userId)) throw new UserNotExistException(userId); if (checkTimelineExistence && !await _timelineService.CheckExistence(timelineId)) @@ -136,7 +136,7 @@ namespace Timeline.Services.Api public async Task MoveBookmark(long userId, long timelineId, long newPosition) { - if (!await _userService.CheckUserExistence(userId)) + if (!await _userService.CheckUserExistenceAsync(userId)) throw new UserNotExistException(userId); if (!await _timelineService.CheckExistence(timelineId)) @@ -178,7 +178,7 @@ namespace Timeline.Services.Api public async Task RemoveBookmark(long userId, long timelineId) { - if (!await _userService.CheckUserExistence(userId)) + if (!await _userService.CheckUserExistenceAsync(userId)) throw new UserNotExistException(userId); if (!await _timelineService.CheckExistence(timelineId)) diff --git a/BackEnd/Timeline/Services/Api/HighlightTimelineService.cs b/BackEnd/Timeline/Services/Api/HighlightTimelineService.cs index 9ef8ea84..419aa68d 100644 --- a/BackEnd/Timeline/Services/Api/HighlightTimelineService.cs +++ b/BackEnd/Timeline/Services/Api/HighlightTimelineService.cs @@ -95,7 +95,7 @@ namespace Timeline.Services.Api if (!await _timelineService.CheckExistence(timelineId)) throw new TimelineNotExistException(timelineId); - if (operatorId.HasValue && !await _userService.CheckUserExistence(operatorId.Value)) + if (operatorId.HasValue && !await _userService.CheckUserExistenceAsync(operatorId.Value)) { throw new UserNotExistException(null, operatorId.Value, "User with given operator id does not exist.", null); } @@ -121,7 +121,7 @@ namespace Timeline.Services.Api if (!await _timelineService.CheckExistence(timelineId)) throw new TimelineNotExistException(timelineId); - if (operatorId.HasValue && !await _userService.CheckUserExistence(operatorId.Value)) + if (operatorId.HasValue && !await _userService.CheckUserExistenceAsync(operatorId.Value)) { throw new UserNotExistException(null, operatorId.Value, "User with given operator id does not exist.", null); } diff --git a/BackEnd/Timeline/Services/BasicServicesServiceCollectionExtensions.cs b/BackEnd/Timeline/Services/BasicServicesServiceCollectionExtensions.cs index 6465fb9f..e9221fef 100644 --- a/BackEnd/Timeline/Services/BasicServicesServiceCollectionExtensions.cs +++ b/BackEnd/Timeline/Services/BasicServicesServiceCollectionExtensions.cs @@ -13,6 +13,7 @@ namespace Timeline.Services { services.TryAddSingleton(); services.TryAddTransient(); + return services; } } } diff --git a/BackEnd/Timeline/Services/Imaging/ImageException.cs b/BackEnd/Timeline/Services/Imaging/ImageException.cs index 12aefa0a..18c8a4d3 100644 --- a/BackEnd/Timeline/Services/Imaging/ImageException.cs +++ b/BackEnd/Timeline/Services/Imaging/ImageException.cs @@ -38,7 +38,7 @@ namespace Timeline.Services.Imaging System.Runtime.Serialization.StreamingContext context) : base(info, context) { } private static string MakeMessage(ErrorReason? reason) => - string.Format(CultureInfo.InvariantCulture, Resource.ExceptionImage, reason switch + string.Format(CultureInfo.CurrentCulture, Resource.ExceptionImage, reason switch { ErrorReason.CantDecode => Resource.ExceptionImageReasonCantDecode, ErrorReason.UnmatchedFormat => Resource.ExceptionImageReasonUnmatchedFormat, diff --git a/BackEnd/Timeline/Services/Timeline/BasicTimelineService.cs b/BackEnd/Timeline/Services/Timeline/BasicTimelineService.cs index f917b176..d633be4d 100644 --- a/BackEnd/Timeline/Services/Timeline/BasicTimelineService.cs +++ b/BackEnd/Timeline/Services/Timeline/BasicTimelineService.cs @@ -94,7 +94,7 @@ namespace Timeline.Services.Timeline long userId; try { - userId = await _basicUserService.GetUserIdByUsername(timelineName); + userId = await _basicUserService.GetUserIdByUsernameAsync(timelineName); } catch (UserNotExistException e) { diff --git a/BackEnd/Timeline/Services/Timeline/TimelinePostNotExistException.cs b/BackEnd/Timeline/Services/Timeline/TimelinePostNotExistException.cs index e0e819aa..87c190ab 100644 --- a/BackEnd/Timeline/Services/Timeline/TimelinePostNotExistException.cs +++ b/BackEnd/Timeline/Services/Timeline/TimelinePostNotExistException.cs @@ -22,7 +22,7 @@ namespace Timeline.Services.Timeline private static string MakeMessage(bool isDelete) { - return string.Format(CultureInfo.InvariantCulture, Resource.ExceptionTimelinePostNoExist, isDelete ? Resource.ExceptionTimelinePostNoExistReasonDeleted : Resource.ExceptionTimelinePostNoExistReasonNotCreated); + return string.Format(CultureInfo.CurrentCulture, Resource.ExceptionTimelinePostNoExist, isDelete ? Resource.ExceptionTimelinePostNoExistReasonDeleted : Resource.ExceptionTimelinePostNoExistReasonNotCreated); } public long? TimelineId { get; set; } diff --git a/BackEnd/Timeline/Services/Timeline/TimelinePostService.cs b/BackEnd/Timeline/Services/Timeline/TimelinePostService.cs index 6a6273c5..d18e65e0 100644 --- a/BackEnd/Timeline/Services/Timeline/TimelinePostService.cs +++ b/BackEnd/Timeline/Services/Timeline/TimelinePostService.cs @@ -190,7 +190,7 @@ namespace Timeline.Services.Timeline private async Task CheckUserExistence(long userId) { - if (!await _basicUserService.CheckUserExistence(userId)) + if (!await _basicUserService.CheckUserExistenceAsync(userId)) throw new UserNotExistException(userId); } diff --git a/BackEnd/Timeline/Services/Timeline/TimelineService.cs b/BackEnd/Timeline/Services/Timeline/TimelineService.cs index 342ce234..0086726e 100644 --- a/BackEnd/Timeline/Services/Timeline/TimelineService.cs +++ b/BackEnd/Timeline/Services/Timeline/TimelineService.cs @@ -284,7 +284,7 @@ namespace Timeline.Services.Timeline if (!await CheckExistence(timelineId)) throw new TimelineNotExistException(timelineId); - if (!await _userService.CheckUserExistence(userId)) + if (!await _userService.CheckUserExistenceAsync(userId)) throw new UserNotExistException(userId); if (await _database.TimelineMembers.AnyAsync(m => m.TimelineId == timelineId && m.UserId == userId)) @@ -306,7 +306,7 @@ namespace Timeline.Services.Timeline if (!await CheckExistence(timelineId)) throw new TimelineNotExistException(timelineId); - if (!await _userService.CheckUserExistence(userId)) + if (!await _userService.CheckUserExistenceAsync(userId)) throw new UserNotExistException(userId); var entity = await _database.TimelineMembers.SingleOrDefaultAsync(m => m.TimelineId == timelineId && m.UserId == userId); diff --git a/BackEnd/Timeline/Services/Token/UserTokenManager.cs b/BackEnd/Timeline/Services/Token/UserTokenManager.cs index 4a5f08d2..31cc70f2 100644 --- a/BackEnd/Timeline/Services/Token/UserTokenManager.cs +++ b/BackEnd/Timeline/Services/Token/UserTokenManager.cs @@ -48,16 +48,14 @@ namespace Timeline.Services.Token private readonly ILogger _logger; private readonly IOptionsMonitor _tokenOptionsMonitor; private readonly IUserService _userService; - private readonly IUserCredentialService _userCredentialService; private readonly IUserTokenHandler _userTokenService; private readonly IClock _clock; - public UserTokenManager(ILogger logger, IOptionsMonitor tokenOptionsMonitor, IUserService userService, IUserCredentialService userCredentialService, IUserTokenHandler userTokenService, IClock clock) + public UserTokenManager(ILogger logger, IOptionsMonitor tokenOptionsMonitor, IUserService userService, IUserTokenHandler userTokenService, IClock clock) { _logger = logger; _tokenOptionsMonitor = tokenOptionsMonitor; _userService = userService; - _userCredentialService = userCredentialService; _userTokenService = userTokenService; _clock = clock; } @@ -71,8 +69,8 @@ namespace Timeline.Services.Token if (password == null) throw new ArgumentNullException(nameof(password)); - var userId = await _userCredentialService.VerifyCredential(username, password); - var user = await _userService.GetUser(userId); + var userId = await _userService.VerifyCredential(username, password); + var user = await _userService.GetUserAsync(userId); var token = _userTokenService.GenerateToken(new UserTokenInfo { @@ -98,7 +96,7 @@ namespace Timeline.Services.Token try { - var user = await _userService.GetUser(tokenInfo.Id); + var user = await _userService.GetUserAsync(tokenInfo.Id); if (tokenInfo.Version < user.Version) throw new UserTokenVersionExpiredException(token, tokenInfo.Version, user.Version); diff --git a/BackEnd/Timeline/Services/User/BasicUserService.cs b/BackEnd/Timeline/Services/User/BasicUserService.cs index a3763ef6..1f1b25f5 100644 --- a/BackEnd/Timeline/Services/User/BasicUserService.cs +++ b/BackEnd/Timeline/Services/User/BasicUserService.cs @@ -7,37 +7,6 @@ using Timeline.Models.Validation; namespace Timeline.Services.User { - /// - /// This service provide some basic user features, which should be used internally for other services. - /// - public interface IBasicUserService - { - /// - /// Check if a user exists. - /// - /// The id of the user. - /// True if exists. Otherwise false. - Task CheckUserExistence(long id); - - /// - /// Get the user id of given username. - /// - /// Username of the user. - /// The id of the user. - /// Thrown when is null. - /// Thrown when is of bad format. - /// Thrown when the user with given username does not exist. - Task GetUserIdByUsername(string username); - - /// - /// Get the username modified time of a user. - /// - /// User id. - /// The time. - /// Thrown when user does not exist. - Task GetUsernameLastModifiedTime(long userId); - } - public class BasicUserService : IBasicUserService { private readonly DatabaseContext _database; @@ -49,12 +18,12 @@ namespace Timeline.Services.User _database = database; } - public async Task CheckUserExistence(long id) + public async Task CheckUserExistenceAsync(long id) { return await _database.Users.AnyAsync(u => u.Id == id); } - public async Task GetUserIdByUsername(string username) + public async Task GetUserIdByUsernameAsync(string username) { if (username == null) throw new ArgumentNullException(nameof(username)); @@ -70,7 +39,7 @@ namespace Timeline.Services.User return entity.Id; } - public async Task GetUsernameLastModifiedTime(long userId) + public async Task GetUsernameLastModifiedTimeAsync(long userId) { var entity = await _database.Users.Where(u => u.Id == userId).Select(u => new { u.UsernameChangeTime }).SingleOrDefaultAsync(); @@ -85,7 +54,7 @@ namespace Timeline.Services.User { public static async Task ThrowIfUserNotExist(this IBasicUserService service, long userId) { - if (!await service.CheckUserExistence(userId)) + if (!await service.CheckUserExistenceAsync(userId)) { throw new UserNotExistException(userId); } diff --git a/BackEnd/Timeline/Services/User/CreateUserParams.cs b/BackEnd/Timeline/Services/User/CreateUserParams.cs new file mode 100644 index 00000000..e66f83dc --- /dev/null +++ b/BackEnd/Timeline/Services/User/CreateUserParams.cs @@ -0,0 +1,17 @@ +using System; + +namespace Timeline.Services.User +{ + public class CreateUserParams + { + public CreateUserParams(string username, string password) + { + Username = username ?? throw new ArgumentNullException(nameof(username)); + Password = password ?? throw new ArgumentNullException(nameof(password)); + } + + public string Username { get; set; } + public string Password { get; set; } + public string? Nickname { get; set; } + } +} diff --git a/BackEnd/Timeline/Services/User/IBasicUserService.cs b/BackEnd/Timeline/Services/User/IBasicUserService.cs new file mode 100644 index 00000000..0e30d733 --- /dev/null +++ b/BackEnd/Timeline/Services/User/IBasicUserService.cs @@ -0,0 +1,36 @@ +using System; +using System.Threading.Tasks; + +namespace Timeline.Services.User +{ + /// + /// This service provide some basic user features, which should be used internally for other services. + /// + public interface IBasicUserService + { + /// + /// Check if a user exists. + /// + /// The id of the user. + /// True if exists. Otherwise false. + Task CheckUserExistenceAsync(long id); + + /// + /// Get the user id of given username. + /// + /// Username of the user. + /// The id of the user. + /// Thrown when is null. + /// Thrown when is of bad format. + /// Thrown when the user with given username does not exist. + Task GetUserIdByUsernameAsync(string username); + + /// + /// Get the username modified time of a user. + /// + /// User id. + /// The time. + /// Thrown when user does not exist. + Task GetUsernameLastModifiedTimeAsync(long userId); + } +} diff --git a/BackEnd/Timeline/Services/User/IUserDeleteService.cs b/BackEnd/Timeline/Services/User/IUserDeleteService.cs new file mode 100644 index 00000000..ce9448ac --- /dev/null +++ b/BackEnd/Timeline/Services/User/IUserDeleteService.cs @@ -0,0 +1,18 @@ +using System; +using System.Threading.Tasks; + +namespace Timeline.Services.User +{ + public interface IUserDeleteService + { + /// + /// Delete a user of given username. + /// + /// 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 when deleting root user. + Task DeleteUser(string username); + } +} diff --git a/BackEnd/Timeline/Services/User/IUserPermissionService.cs b/BackEnd/Timeline/Services/User/IUserPermissionService.cs new file mode 100644 index 00000000..7ff1275b --- /dev/null +++ b/BackEnd/Timeline/Services/User/IUserPermissionService.cs @@ -0,0 +1,35 @@ +using System.Threading.Tasks; + +namespace Timeline.Services.User +{ + public interface IUserPermissionService + { + /// + /// Get permissions of a user. + /// + /// The id of the user. + /// Whether check the user's existence. + /// The permission list. + /// Thrown when is true and user does not exist. + Task GetPermissionsOfUserAsync(long userId, bool checkUserExistence = true); + + /// + /// Add a permission to user. + /// + /// The id of the user. + /// The new permission. + /// Thrown when user does not exist. + /// Thrown when change root user's permission. + Task AddPermissionToUserAsync(long userId, UserPermission permission); + + /// + /// Remove a permission from user. + /// + /// The id of the user. + /// The permission. + /// Whether check the user's existence. + /// Thrown when is true and user does not exist. + /// Thrown when change root user's permission. + Task RemovePermissionFromUserAsync(long userId, UserPermission permission, bool checkUserExistence = true); + } +} diff --git a/BackEnd/Timeline/Services/User/IUserService.cs b/BackEnd/Timeline/Services/User/IUserService.cs new file mode 100644 index 00000000..06155c55 --- /dev/null +++ b/BackEnd/Timeline/Services/User/IUserService.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Timeline.Entities; + +namespace Timeline.Services.User +{ + public interface IUserService : IBasicUserService + { + /// + /// Try to get a user by id. + /// + /// The id of the user. + /// The user info. + /// Thrown when the user with given id does not exist. + Task GetUserAsync(long id); + + /// + /// List all users. + /// + /// The user info of users. + Task> GetUsersAsync(); + + /// + /// Create a user with given info. + /// + /// Info of new user. + /// The the new user. + /// Thrown when is null. + /// Thrown when param field is illegal. + /// Thrown when a user with given username already exists. + Task CreateUserAsync(CreateUserParams param); + + /// + /// Modify a user. + /// + /// The id of the user. + /// The new information. + /// The new user info. + /// Thrown when some fields in is bad. + /// Thrown when user with given id does not exist. + /// + /// Version will increase if password is changed. + /// + Task ModifyUserAsync(long id, ModifyUserParams? param); + + /// + /// Try to verify the given username and password. + /// + /// The username of the user to verify. + /// The password of the user to verify. + /// User id. + /// Thrown when or is null. + /// 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); + + /// + /// Try to change a user's password with old password. + /// + /// 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(long id, string oldPassword, string newPassword); + } +} diff --git a/BackEnd/Timeline/Services/User/InvalidOperationOnRootUserException.cs b/BackEnd/Timeline/Services/User/InvalidOperationOnRootUserException.cs index c432febd..636985d0 100644 --- a/BackEnd/Timeline/Services/User/InvalidOperationOnRootUserException.cs +++ b/BackEnd/Timeline/Services/User/InvalidOperationOnRootUserException.cs @@ -6,7 +6,7 @@ namespace Timeline.Services.User [Serializable] public class InvalidOperationOnRootUserException : InvalidOperationException { - public InvalidOperationOnRootUserException() { } + public InvalidOperationOnRootUserException() : base(Resource.ExceptionInvalidOperationOnRootUser) { } public InvalidOperationOnRootUserException(string message) : base(message) { } public InvalidOperationOnRootUserException(string message, Exception inner) : base(message, inner) { } protected InvalidOperationOnRootUserException( diff --git a/BackEnd/Timeline/Services/User/ModifyUserParams.cs b/BackEnd/Timeline/Services/User/ModifyUserParams.cs new file mode 100644 index 00000000..296c3212 --- /dev/null +++ b/BackEnd/Timeline/Services/User/ModifyUserParams.cs @@ -0,0 +1,12 @@ +namespace Timeline.Services.User +{ + /// + /// Null means not change. + /// + public class ModifyUserParams + { + public string? Username { get; set; } + public string? Password { get; set; } + public string? Nickname { get; set; } + } +} diff --git a/BackEnd/Timeline/Services/User/PasswordService.cs b/BackEnd/Timeline/Services/User/PasswordService.cs index 1c14875f..5c2062dd 100644 --- a/BackEnd/Timeline/Services/User/PasswordService.cs +++ b/BackEnd/Timeline/Services/User/PasswordService.cs @@ -19,7 +19,7 @@ namespace Timeline.Services.User public HashedPasswordBadFromatException(string message, Exception inner) : base(message, inner) { } public HashedPasswordBadFromatException(string hashedPassword, string reason, Exception? inner = null) - : base(string.Format(CultureInfo.InvariantCulture, Resource.ExceptionHashedPasswordBadFormat, reason), inner) { HashedPassword = hashedPassword; } + : base(string.Format(CultureInfo.CurrentCulture, Resource.ExceptionHashedPasswordBadFormat, reason), inner) { HashedPassword = hashedPassword; } protected HashedPasswordBadFromatException( System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base(info, context) { } diff --git a/BackEnd/Timeline/Services/User/Resource.Designer.cs b/BackEnd/Timeline/Services/User/Resource.Designer.cs index 4f75b055..908e2732 100644 --- a/BackEnd/Timeline/Services/User/Resource.Designer.cs +++ b/BackEnd/Timeline/Services/User/Resource.Designer.cs @@ -69,6 +69,24 @@ namespace Timeline.Services.User { } } + /// + /// Looks up a localized string similar to Can't change root user's permission.. + /// + internal static string ExceptionChangeRootUserPermission { + get { + return ResourceManager.GetString("ExceptionChangeRootUserPermission", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Can't delete root user.. + /// + internal static string ExceptionDeleteRootUser { + get { + return ResourceManager.GetString("ExceptionDeleteRootUser", resourceCulture); + } + } + /// /// Looks up a localized string similar to The hashes password is of bad format. It might not be created by server. Reason: {0}. /// @@ -132,6 +150,15 @@ namespace Timeline.Services.User { } } + /// + /// Looks up a localized string similar to Can't perform such operation on root user.. + /// + internal static string ExceptionInvalidOperationOnRootUser { + get { + return ResourceManager.GetString("ExceptionInvalidOperationOnRootUser", resourceCulture); + } + } + /// /// Looks up a localized string similar to Nickname is of bad format. {0}. /// @@ -150,6 +177,15 @@ namespace Timeline.Services.User { } } + /// + /// Looks up a localized string similar to Password can't be null.. + /// + internal static string ExceptionPasswordNull { + get { + return ResourceManager.GetString("ExceptionPasswordNull", resourceCulture); + } + } + /// /// Looks up a localized string similar to User with given constraints already exists.. /// @@ -168,6 +204,15 @@ namespace Timeline.Services.User { } } + /// + /// Looks up a localized string similar to Username can't be null.. + /// + internal static string ExceptionUsernameNull { + get { + return ResourceManager.GetString("ExceptionUsernameNull", resourceCulture); + } + } + /// /// Looks up a localized string similar to Requested user does not exist.. /// @@ -176,5 +221,41 @@ namespace Timeline.Services.User { return ResourceManager.GetString("ExceptionUserNotExist", resourceCulture); } } + + /// + /// Looks up a localized string similar to User with username = {0}, id ={1} changed password.. + /// + internal static string LogChangePassowrd { + get { + return ResourceManager.GetString("LogChangePassowrd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A user is deleted with username = {0}, id = {1}.. + /// + internal static string LogDeleteUser { + get { + return ResourceManager.GetString("LogDeleteUser", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A new user is created with username = {0}, id = {1}.. + /// + internal static string LogUserCreated { + get { + return ResourceManager.GetString("LogUserCreated", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A user is modified with username = {0}, id = {1}.. + /// + internal static string LogUserModified { + get { + return ResourceManager.GetString("LogUserModified", resourceCulture); + } + } } } diff --git a/BackEnd/Timeline/Services/User/Resource.resx b/BackEnd/Timeline/Services/User/Resource.resx index 28e75b19..a734bd70 100644 --- a/BackEnd/Timeline/Services/User/Resource.resx +++ b/BackEnd/Timeline/Services/User/Resource.resx @@ -120,6 +120,12 @@ Password is wrong. + + Can't change root user's permission. + + + Can't delete root user. + The hashes password is of bad format. It might not be created by server. Reason: {0} @@ -141,19 +147,40 @@ Unknown format marker. + + Can't perform such operation on root user. + Nickname is of bad format. {0} Password can't be empty. + + Password can't be null. + User with given constraints already exists. Username is of bad format. {0} + + Username can't be null. + Requested user does not exist. + + User with username = {0}, id ={1} changed password. + + + A user is deleted with username = {0}, id = {1}. + + + A new user is created with username = {0}, id = {1}. + + + A user is modified with username = {0}, id = {1}. + \ No newline at end of file diff --git a/BackEnd/Timeline/Services/User/UserAvatarService.cs b/BackEnd/Timeline/Services/User/UserAvatarService.cs index e18a0560..9f59624d 100644 --- a/BackEnd/Timeline/Services/User/UserAvatarService.cs +++ b/BackEnd/Timeline/Services/User/UserAvatarService.cs @@ -144,7 +144,7 @@ namespace Timeline.Services.User public async Task GetAvatarDigest(long userId) { - var usernameChangeTime = await _basicUserService.GetUsernameLastModifiedTime(userId); + var usernameChangeTime = await _basicUserService.GetUsernameLastModifiedTimeAsync(userId); var entity = await _database.UserAvatars.Where(a => a.UserId == userId).Select(a => new { a.DataTag, a.LastModified }).SingleOrDefaultAsync(); diff --git a/BackEnd/Timeline/Services/User/UserCredentialService.cs b/BackEnd/Timeline/Services/User/UserCredentialService.cs deleted file mode 100644 index 6becc469..00000000 --- a/BackEnd/Timeline/Services/User/UserCredentialService.cs +++ /dev/null @@ -1,101 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; -using System; -using System.Linq; -using System.Threading.Tasks; -using Timeline.Entities; -using Timeline.Helpers; -using Timeline.Models.Validation; - -namespace Timeline.Services.User -{ - public interface IUserCredentialService - { - /// - /// Try to verify the given username and password. - /// - /// The username of the user to verify. - /// The password of the user to verify. - /// User id. - /// Thrown when or is null. - /// 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); - - /// - /// Try to change a user's password with old password. - /// - /// 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(long id, string oldPassword, string newPassword); - } - - public class UserCredentialService : IUserCredentialService - { - private readonly ILogger _logger; - private readonly DatabaseContext _database; - private readonly IPasswordService _passwordService; - - private readonly UsernameValidator _usernameValidator = new UsernameValidator(); - - public UserCredentialService(ILogger logger, DatabaseContext database, IPasswordService passwordService) - { - _logger = logger; - _database = database; - _passwordService = passwordService; - } - - public async Task VerifyCredential(string username, string password) - { - if (username == null) - throw new ArgumentNullException(nameof(username)); - if (password == null) - throw new ArgumentNullException(nameof(password)); - if (!_usernameValidator.Validate(username, out var message)) - throw new ArgumentException(message); - if (password.Length == 0) - throw new ArgumentException("Password can't be empty."); - - var entity = await _database.Users.Where(u => u.Username == username).Select(u => new { u.Id, u.Password }).SingleOrDefaultAsync(); - - if (entity == null) - throw new UserNotExistException(username); - - if (!_passwordService.VerifyPassword(entity.Password, password)) - throw new BadPasswordException(password); - - return entity.Id; - } - - public async Task ChangePassword(long id, string oldPassword, string newPassword) - { - if (oldPassword == null) - throw new ArgumentNullException(nameof(oldPassword)); - if (newPassword == null) - throw new ArgumentNullException(nameof(newPassword)); - if (oldPassword.Length == 0) - throw new ArgumentException("Old password can't be empty."); - if (newPassword.Length == 0) - throw new ArgumentException("New password can't be empty."); - - var entity = await _database.Users.Where(u => u.Id == id).SingleOrDefaultAsync(); - - 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 _database.SaveChangesAsync(); - _logger.LogInformation(Log.Format(Resources.Services.UserService.LogDatabaseUpdate, ("Id", id), ("Operation", "Change password"))); - } - } -} diff --git a/BackEnd/Timeline/Services/User/UserDeleteService.cs b/BackEnd/Timeline/Services/User/UserDeleteService.cs index 8da4678a..94b15d33 100644 --- a/BackEnd/Timeline/Services/User/UserDeleteService.cs +++ b/BackEnd/Timeline/Services/User/UserDeleteService.cs @@ -10,19 +10,6 @@ using Timeline.Services.Timeline; namespace Timeline.Services.User { - public interface IUserDeleteService - { - /// - /// Delete a user of given username. - /// - /// 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 when deleting root user. - Task DeleteUser(string username); - } - public class UserDeleteService : IUserDeleteService { private readonly ILogger _logger; @@ -55,13 +42,14 @@ namespace Timeline.Services.User return false; if (user.Id == 1) - throw new InvalidOperationOnRootUserException("Can't delete root user."); + throw new InvalidOperationOnRootUserException(Resource.ExceptionDeleteRootUser); await _timelinePostService.DeleteAllPostsOfUser(user.Id); _databaseContext.Users.Remove(user); await _databaseContext.SaveChangesAsync(); + _logger.LogWarning(Resource.LogDeleteUser, user.Username, user.Id); return true; } diff --git a/BackEnd/Timeline/Services/User/UserPermission.cs b/BackEnd/Timeline/Services/User/UserPermission.cs new file mode 100644 index 00000000..1404cdcd --- /dev/null +++ b/BackEnd/Timeline/Services/User/UserPermission.cs @@ -0,0 +1,18 @@ +namespace Timeline.Services.User +{ + public enum UserPermission + { + /// + /// This permission allows to manage user (creating, deleting or modifying). + /// + UserManagement, + /// + /// This permission allows to view and modify all timelines. + /// + AllTimelineManagement, + /// + /// This permission allow to add or remove highlight timelines. + /// + HighlightTimelineManagement + } +} diff --git a/BackEnd/Timeline/Services/User/UserPermissionService.cs b/BackEnd/Timeline/Services/User/UserPermissionService.cs index f292142d..f9911c7f 100644 --- a/BackEnd/Timeline/Services/User/UserPermissionService.cs +++ b/BackEnd/Timeline/Services/User/UserPermissionService.cs @@ -1,172 +1,10 @@ using Microsoft.EntityFrameworkCore; -using System; -using System.Collections; -using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Timeline.Entities; namespace Timeline.Services.User { - public enum UserPermission - { - /// - /// This permission allows to manage user (creating, deleting or modifying). - /// - UserManagement, - /// - /// This permission allows to view and modify all timelines. - /// - AllTimelineManagement, - /// - /// This permission allow to add or remove highlight timelines. - /// - HighlightTimelineManagement - } - - /// - /// Represents a user's permissions. - /// - public class UserPermissions : IEnumerable, IEquatable - { - public static UserPermissions AllPermissions { get; } = new UserPermissions(Enum.GetValues()); - - /// - /// Create an instance containing given permissions. - /// - /// Permission list. - public UserPermissions(params UserPermission[] permissions) : this(permissions as IEnumerable) - { - - } - - /// - /// Create an instance containing given permissions. - /// - /// Permission list. - /// Thrown when is null. - public UserPermissions(IEnumerable permissions) - { - if (permissions == null) throw new ArgumentNullException(nameof(permissions)); - _permissions = new SortedSet(permissions); - } - - private readonly SortedSet _permissions = new(); - - /// - /// Check if a permission is contained in the list. - /// - /// The permission to check. - /// True if contains. Otherwise false. - public bool Contains(UserPermission permission) - { - return _permissions.Contains(permission); - } - - /// - /// To a serializable string list. - /// - /// A string list. - public List ToStringList() - { - return _permissions.Select(p => p.ToString()).ToList(); - } - - /// - /// Convert a string list to user permissions. - /// - /// The string list. - /// An instance. - /// Thrown when is null. - /// Thrown when there is unknown permission name. - public static UserPermissions FromStringList(IEnumerable list) - { - List permissions = new(); - - foreach (var value in list) - { - if (Enum.TryParse(value, false, out var result)) - { - permissions.Add(result); - } - else - { - throw new ArgumentException("Unknown permission name.", nameof(list)); - } - } - - return new UserPermissions(permissions); - } - - public IEnumerator GetEnumerator() - { - return _permissions.GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return ((IEnumerable)_permissions).GetEnumerator(); - } - - public bool Equals(UserPermissions? other) - { - if (other == null) - return false; - - return _permissions.SequenceEqual(other._permissions); - } - - public override bool Equals(object? obj) - { - return Equals(obj as UserPermissions); - } - - public override int GetHashCode() - { - int result = 0; - foreach (var permission in Enum.GetValues()) - { - if (_permissions.Contains(permission)) - { - result += 1; - } - result <<= 1; - } - return result; - } - } - - public interface IUserPermissionService - { - /// - /// Get permissions of a user. - /// - /// The id of the user. - /// Whether check the user's existence. - /// The permission list. - /// Thrown when is true and user does not exist. - Task GetPermissionsOfUserAsync(long userId, bool checkUserExistence = true); - - /// - /// Add a permission to user. - /// - /// The id of the user. - /// The new permission. - /// Thrown when user does not exist. - /// Thrown when change root user's permission. - Task AddPermissionToUserAsync(long userId, UserPermission permission); - - /// - /// Remove a permission from user. - /// - /// The id of the user. - /// The permission. - /// Whether check the user's existence. - /// Thrown when is true and user does not exist. - /// Thrown when change root user's permission. - Task RemovePermissionFromUserAsync(long userId, UserPermission permission, bool checkUserExistence = true); - } - public class UserPermissionService : IUserPermissionService { private readonly DatabaseContext _database; @@ -205,7 +43,7 @@ namespace Timeline.Services.User public async Task AddPermissionToUserAsync(long userId, UserPermission permission) { if (userId == 1) - throw new InvalidOperationOnRootUserException("Can't change root user's permission."); + throw new InvalidOperationOnRootUserException(Resource.ExceptionChangeRootUserPermission); await CheckUserExistence(userId, true); @@ -222,7 +60,7 @@ namespace Timeline.Services.User public async Task RemovePermissionFromUserAsync(long userId, UserPermission permission, bool checkUserExistence = true) { if (userId == 1) - throw new InvalidOperationOnRootUserException("Can't change root user's permission."); + throw new InvalidOperationOnRootUserException(Resource.ExceptionChangeRootUserPermission); await CheckUserExistence(userId, checkUserExistence); diff --git a/BackEnd/Timeline/Services/User/UserPermissions.cs b/BackEnd/Timeline/Services/User/UserPermissions.cs new file mode 100644 index 00000000..1c27b4b8 --- /dev/null +++ b/BackEnd/Timeline/Services/User/UserPermissions.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace Timeline.Services.User +{ + /// + /// Represents a user's permissions. + /// + public class UserPermissions : IEnumerable, IEquatable + { + public static UserPermissions AllPermissions { get; } = new UserPermissions(Enum.GetValues()); + + /// + /// Create an instance containing given permissions. + /// + /// Permission list. + public UserPermissions(params UserPermission[] permissions) : this(permissions as IEnumerable) + { + + } + + /// + /// Create an instance containing given permissions. + /// + /// Permission list. + /// Thrown when is null. + public UserPermissions(IEnumerable permissions) + { + if (permissions == null) throw new ArgumentNullException(nameof(permissions)); + _permissions = new SortedSet(permissions); + } + + private readonly SortedSet _permissions = new(); + + /// + /// Check if a permission is contained in the list. + /// + /// The permission to check. + /// True if contains. Otherwise false. + public bool Contains(UserPermission permission) + { + return _permissions.Contains(permission); + } + + /// + /// To a serializable string list. + /// + /// A string list. + public List ToStringList() + { + return _permissions.Select(p => p.ToString()).ToList(); + } + + /// + /// Convert a string list to user permissions. + /// + /// The string list. + /// An instance. + /// Thrown when is null. + /// Thrown when there is unknown permission name. + public static UserPermissions FromStringList(IEnumerable list) + { + List permissions = new(); + + foreach (var value in list) + { + if (Enum.TryParse(value, false, out var result)) + { + permissions.Add(result); + } + else + { + throw new ArgumentException("Unknown permission name.", nameof(list)); + } + } + + return new UserPermissions(permissions); + } + + public IEnumerator GetEnumerator() + { + return _permissions.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)_permissions).GetEnumerator(); + } + + public bool Equals(UserPermissions? other) + { + if (other == null) + return false; + + return _permissions.SequenceEqual(other._permissions); + } + + public override bool Equals(object? obj) + { + return Equals(obj as UserPermissions); + } + + public override int GetHashCode() + { + int result = 0; + foreach (var permission in Enum.GetValues()) + { + if (_permissions.Contains(permission)) + { + result += 1; + } + result <<= 1; + } + return result; + } + } +} diff --git a/BackEnd/Timeline/Services/User/UserService.cs b/BackEnd/Timeline/Services/User/UserService.cs index bbbe15b0..6496b55b 100644 --- a/BackEnd/Timeline/Services/User/UserService.cs +++ b/BackEnd/Timeline/Services/User/UserService.cs @@ -10,57 +10,6 @@ using Timeline.Models.Validation; namespace Timeline.Services.User { - /// - /// Null means not change. - /// - public class ModifyUserParams - { - public string? Username { get; set; } - public string? Password { get; set; } - public string? Nickname { get; set; } - } - - public interface IUserService : IBasicUserService - { - /// - /// Try to get a user by id. - /// - /// The id of the user. - /// The user info. - /// Thrown when the user with given id does not exist. - Task GetUser(long id); - - /// - /// List all users. - /// - /// The user info of users. - Task> GetUsers(); - - /// - /// Create a user with given info. - /// - /// The username of new user. - /// The password of new user. - /// The the new user. - /// Thrown when or is null. - /// Thrown when or is of bad format. - /// Thrown when a user with given username already exists. - Task CreateUser(string username, string password); - - /// - /// Modify a user. - /// - /// The id of the user. - /// The new information. - /// The new user info. - /// Thrown when some fields in is bad. - /// Thrown when user with given id does not exist. - /// - /// Version will increase if password is changed. - /// - Task ModifyUser(long id, ModifyUserParams? param); - } - public class UserService : BasicUserService, IUserService { private readonly ILogger _logger; @@ -110,58 +59,63 @@ namespace Timeline.Services.User throw new UserAlreadyExistException(user); } - public async Task GetUser(long id) + public async Task GetUserAsync(long id) { var user = await _databaseContext.Users.Where(u => u.Id == id).SingleOrDefaultAsync(); - if (user == null) + if (user is null) throw new UserNotExistException(id); return user; } - public async Task> GetUsers() + public async Task> GetUsersAsync() { return await _databaseContext.Users.ToListAsync(); } - public async Task CreateUser(string username, string password) + public async Task CreateUserAsync(CreateUserParams param) { - if (username == null) - throw new ArgumentNullException(nameof(username)); - if (password == null) - throw new ArgumentNullException(nameof(password)); - - CheckUsernameFormat(username, nameof(username)); - CheckPasswordFormat(password, nameof(password)); - - var conflict = await _databaseContext.Users.AnyAsync(u => u.Username == username); + if (param is null) + throw new ArgumentNullException(nameof(param)); + if (param.Username is null) + throw new ArgumentException(Resource.ExceptionUsernameNull, nameof(param)); + if (param.Password is null) + throw new ArgumentException(Resource.ExceptionPasswordNull, nameof(param)); + CheckUsernameFormat(param.Username, nameof(param)); + CheckPasswordFormat(param.Password, nameof(param)); + if (param.Nickname is not null) + CheckNicknameFormat(param.Nickname, nameof(param)); + + var conflict = await _databaseContext.Users.AnyAsync(u => u.Username == param.Username); if (conflict) ThrowUsernameConflict(null); var newEntity = new UserEntity { - Username = username, - Password = _passwordService.HashPassword(password), + Username = param.Username, + Password = _passwordService.HashPassword(param.Password), + Nickname = param.Nickname, Version = 1 }; _databaseContext.Users.Add(newEntity); await _databaseContext.SaveChangesAsync(); + _logger.LogInformation(Resource.LogUserCreated, param.Username, newEntity.Id); return newEntity; } - public async Task ModifyUser(long id, ModifyUserParams? param) + public async Task ModifyUserAsync(long id, ModifyUserParams? param) { - if (param != null) + if (param is not null) { - if (param.Username != null) + if (param.Username is not null) CheckUsernameFormat(param.Username, nameof(param)); - if (param.Password != null) + if (param.Password is not null) CheckPasswordFormat(param.Password, nameof(param)); - if (param.Nickname != null) + if (param.Nickname is not null) CheckNicknameFormat(param.Nickname, nameof(param)); } @@ -169,13 +123,13 @@ namespace Timeline.Services.User if (entity == null) throw new UserNotExistException(id); - if (param != null) + if (param is not null) { var now = _clock.GetCurrentTime(); bool updateLastModified = false; var username = param.Username; - if (username != null && username != entity.Username) + if (username is not null && username != entity.Username) { var conflict = await _databaseContext.Users.AnyAsync(u => u.Username == username); if (conflict) @@ -187,14 +141,14 @@ namespace Timeline.Services.User } var password = param.Password; - if (password != null) + if (password is not null) { entity.Password = _passwordService.HashPassword(password); entity.Version += 1; } var nickname = param.Nickname; - if (nickname != null && nickname != entity.Nickname) + if (nickname is not null && nickname != entity.Nickname) { entity.Nickname = nickname; updateLastModified = true; @@ -206,9 +160,53 @@ namespace Timeline.Services.User } await _databaseContext.SaveChangesAsync(); + _logger.LogInformation(Resource.LogUserModified, entity.Username, id); } return entity; } + + public async Task VerifyCredential(string username, string password) + { + if (username is null) + throw new ArgumentNullException(nameof(username)); + if (password is null) + throw new ArgumentNullException(nameof(password)); + CheckUsernameFormat(username, nameof(username)); + CheckPasswordFormat(password, nameof(password)); + + var entity = await _databaseContext.Users.Where(u => u.Username == username).Select(u => new { u.Id, u.Password }).SingleOrDefaultAsync(); + + if (entity is null) + throw new UserNotExistException(username); + + if (!_passwordService.VerifyPassword(entity.Password, password)) + throw new BadPasswordException(password); + + return entity.Id; + } + + public async Task ChangePassword(long id, string oldPassword, string newPassword) + { + 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 entity = await _databaseContext.Users.Where(u => u.Id == id).SingleOrDefaultAsync(); + + if (entity is 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(Resource.LogChangePassowrd, entity.Username, id); + } } } diff --git a/BackEnd/Timeline/Startup.cs b/BackEnd/Timeline/Startup.cs index 58438d4c..c0873113 100644 --- a/BackEnd/Timeline/Startup.cs +++ b/BackEnd/Timeline/Startup.cs @@ -2,11 +2,9 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; using System; using System.ComponentModel; using System.Net.Mime; @@ -105,7 +103,6 @@ namespace Timeline services.AddTransient(); services.AddScoped(); services.AddScoped(); - services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddUserAvatarService(); diff --git a/BackEnd/Timeline/Timeline.csproj b/BackEnd/Timeline/Timeline.csproj index 15771977..dc41a7f8 100644 --- a/BackEnd/Timeline/Timeline.csproj +++ b/BackEnd/Timeline/Timeline.csproj @@ -133,11 +133,6 @@ True UserAvatarService.resx - - True - True - UserService.resx - True True @@ -250,10 +245,6 @@ ResXFileCodeGenerator UserAvatarService.Designer.cs - - ResXFileCodeGenerator - UserService.Designer.cs - ResXFileCodeGenerator UserTokenService.Designer.cs -- cgit v1.2.3