From 0af4e8c9e788a3bbf4d6879a7f42660cb47ddedb Mon Sep 17 00:00:00 2001 From: crupest Date: Thu, 12 Nov 2020 18:31:41 +0800 Subject: feat: Add user permission service. TODO: Add unit tests. --- .../Timeline.Tests/Services/DatabaseBasedTest.cs | 47 ++++++++++++++++++++++ .../Timeline.Tests/Services/TimelineServiceTest.cs | 25 +++--------- .../Timeline.Tests/Services/UserPermissionTest.cs | 28 +++++++++++++ 3 files changed, 81 insertions(+), 19 deletions(-) create mode 100644 BackEnd/Timeline.Tests/Services/DatabaseBasedTest.cs create mode 100644 BackEnd/Timeline.Tests/Services/UserPermissionTest.cs (limited to 'BackEnd/Timeline.Tests/Services') diff --git a/BackEnd/Timeline.Tests/Services/DatabaseBasedTest.cs b/BackEnd/Timeline.Tests/Services/DatabaseBasedTest.cs new file mode 100644 index 00000000..838787e9 --- /dev/null +++ b/BackEnd/Timeline.Tests/Services/DatabaseBasedTest.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Timeline.Entities; +using Timeline.Tests.Helpers; +using Xunit; + +namespace Timeline.Tests.Services +{ + public abstract class DatabaseBasedTest : IAsyncLifetime + { + protected TestDatabase TestDatabase { get; } = new TestDatabase(); + protected DatabaseContext Database { get; private set; } + + public async Task InitializeAsync() + { + await TestDatabase.InitializeAsync(); + Database = TestDatabase.CreateContext(); + await OnDatabaseCreatedAsync(); + OnDatabaseCreated(); + } + + public async Task DisposeAsync() + { + BeforeDatabaseDestroy(); + await BeforeDatabaseDestroyAsync(); + await Database.DisposeAsync(); + await TestDatabase.DisposeAsync(); + } + + + protected virtual void OnDatabaseCreated() { } + protected virtual void BeforeDatabaseDestroy() { } + + + protected virtual Task OnDatabaseCreatedAsync() + { + return Task.CompletedTask; + } + + protected virtual Task BeforeDatabaseDestroyAsync() + { + return Task.CompletedTask; + } + } +} diff --git a/BackEnd/Timeline.Tests/Services/TimelineServiceTest.cs b/BackEnd/Timeline.Tests/Services/TimelineServiceTest.cs index 5a774b78..19d2781a 100644 --- a/BackEnd/Timeline.Tests/Services/TimelineServiceTest.cs +++ b/BackEnd/Timeline.Tests/Services/TimelineServiceTest.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using Timeline.Entities; using Timeline.Models; using Timeline.Services; using Timeline.Services.Exceptions; @@ -13,12 +12,8 @@ using Xunit; namespace Timeline.Tests.Services { - public class TimelineServiceTest : IAsyncLifetime, IDisposable + public class TimelineServiceTest : DatabaseBasedTest, IDisposable { - private readonly TestDatabase _testDatabase = new TestDatabase(); - - private DatabaseContext _databaseContext; - private readonly PasswordService _passwordService = new PasswordService(); private readonly ETagGenerator _eTagGenerator = new ETagGenerator(); @@ -39,20 +34,12 @@ namespace Timeline.Tests.Services { } - public async Task InitializeAsync() - { - await _testDatabase.InitializeAsync(); - _databaseContext = _testDatabase.CreateContext(); - _dataManager = new DataManager(_databaseContext, _eTagGenerator); - _userService = new UserService(NullLogger.Instance, _databaseContext, _passwordService, _clock); - _timelineService = new TimelineService(NullLogger.Instance, _databaseContext, _dataManager, _userService, _imageValidator, _clock); - _userDeleteService = new UserDeleteService(NullLogger.Instance, _databaseContext, _timelineService); - } - - public async Task DisposeAsync() + protected override void OnDatabaseCreated() { - await _testDatabase.DisposeAsync(); - await _databaseContext.DisposeAsync(); + _dataManager = new DataManager(Database, _eTagGenerator); + _userService = new UserService(NullLogger.Instance, Database, _passwordService, _clock); + _timelineService = new TimelineService(NullLogger.Instance, Database, _dataManager, _userService, _imageValidator, _clock); + _userDeleteService = new UserDeleteService(NullLogger.Instance, Database, _timelineService); } public void Dispose() diff --git a/BackEnd/Timeline.Tests/Services/UserPermissionTest.cs b/BackEnd/Timeline.Tests/Services/UserPermissionTest.cs new file mode 100644 index 00000000..4bcbc633 --- /dev/null +++ b/BackEnd/Timeline.Tests/Services/UserPermissionTest.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Timeline.Entities; +using Timeline.Services; +using Timeline.Tests.Helpers; +using Xunit; + +namespace Timeline.Tests.Services +{ + public class UserPermissionTest : DatabaseBasedTest + { + private UserPermissionService _service; + + public UserPermissionTest() + { + + } + + protected override void OnDatabaseCreated() + { + _service = new UserPermissionService(Database); + } + + + } +} -- cgit v1.2.3 From b81a66ff49f5d9305108e92a009449ee5994862e Mon Sep 17 00:00:00 2001 From: crupest Date: Thu, 12 Nov 2020 20:03:31 +0800 Subject: test: Write tests for user permission service. --- .../Timeline.Tests/Services/DatabaseBasedTest.cs | 12 ++- .../Services/UserPermissionServiceTest.cs | 118 +++++++++++++++++++++ .../Timeline.Tests/Services/UserPermissionTest.cs | 28 ----- BackEnd/Timeline/Services/UserPermissionService.cs | 15 ++- 4 files changed, 132 insertions(+), 41 deletions(-) create mode 100644 BackEnd/Timeline.Tests/Services/UserPermissionServiceTest.cs delete mode 100644 BackEnd/Timeline.Tests/Services/UserPermissionTest.cs (limited to 'BackEnd/Timeline.Tests/Services') diff --git a/BackEnd/Timeline.Tests/Services/DatabaseBasedTest.cs b/BackEnd/Timeline.Tests/Services/DatabaseBasedTest.cs index 838787e9..7c97158c 100644 --- a/BackEnd/Timeline.Tests/Services/DatabaseBasedTest.cs +++ b/BackEnd/Timeline.Tests/Services/DatabaseBasedTest.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +using System.Threading.Tasks; using Timeline.Entities; using Timeline.Tests.Helpers; using Xunit; @@ -10,9 +7,14 @@ namespace Timeline.Tests.Services { public abstract class DatabaseBasedTest : IAsyncLifetime { - protected TestDatabase TestDatabase { get; } = new TestDatabase(); + protected TestDatabase TestDatabase { get; } protected DatabaseContext Database { get; private set; } + protected DatabaseBasedTest(bool databaseCreateUsers = true) + { + TestDatabase = new TestDatabase(databaseCreateUsers); + } + public async Task InitializeAsync() { await TestDatabase.InitializeAsync(); diff --git a/BackEnd/Timeline.Tests/Services/UserPermissionServiceTest.cs b/BackEnd/Timeline.Tests/Services/UserPermissionServiceTest.cs new file mode 100644 index 00000000..cea11b34 --- /dev/null +++ b/BackEnd/Timeline.Tests/Services/UserPermissionServiceTest.cs @@ -0,0 +1,118 @@ +using FluentAssertions; +using System; +using System.Threading.Tasks; +using Timeline.Services; +using Timeline.Services.Exceptions; +using Xunit; + +namespace Timeline.Tests.Services +{ + public class UserPermissionServiceTest : DatabaseBasedTest + { + private UserPermissionService _service; + + public UserPermissionServiceTest() + { + + } + + protected override void OnDatabaseCreated() + { + _service = new UserPermissionService(Database); + } + + [Fact] + public async Task GetPermissionsOfRootUserShouldReturnAll() + { + var permission = await _service.GetPermissionsOfUserAsync(1); + permission.Should().BeEquivalentTo(Enum.GetValues()); + } + + [Fact] + public async Task GetPermissionsOfNonRootUserShouldReturnNone() + { + var permission = await _service.GetPermissionsOfUserAsync(2); + permission.Should().BeEmpty(); + } + + [Fact] + public async Task GetPermissionsOfInexistentUserShouldThrow() + { + await _service.Awaiting(s => s.GetPermissionsOfUserAsync(10)).Should().ThrowAsync(); + } + + [Fact] + public async Task GetPermissionsOfInexistentUserShouldNotThrowIfNotCheck() + { + await _service.Awaiting(s => s.GetPermissionsOfUserAsync(10, false)).Should().NotThrowAsync(); + } + + [Fact] + public async Task ModifyPermissionOnRootUserShouldHaveNoEffect() + { + await _service.AddPermissionToUserAsync(1, UserPermission.AllTimelineManagement); + { + var permission = await _service.GetPermissionsOfUserAsync(1); + permission.Should().BeEquivalentTo(Enum.GetValues()); + } + await _service.RemovePermissionFromUserAsync(1, UserPermission.AllTimelineManagement); + { + var permission = await _service.GetPermissionsOfUserAsync(1); + permission.Should().BeEquivalentTo(Enum.GetValues()); + } + } + + [Fact] + public async Task ModifyPermissionOnNonRootUserShouldWork() + { + await _service.AddPermissionToUserAsync(2, UserPermission.AllTimelineManagement); + { + var permission = await _service.GetPermissionsOfUserAsync(2); + permission.Should().BeEquivalentTo(UserPermission.AllTimelineManagement); + } + await _service.AddPermissionToUserAsync(2, UserPermission.HighlightTimelineManangement); + { + var permission = await _service.GetPermissionsOfUserAsync(2); + permission.Should().BeEquivalentTo(UserPermission.AllTimelineManagement, UserPermission.HighlightTimelineManangement); + } + + // Add duplicate permission should work. + await _service.AddPermissionToUserAsync(2, UserPermission.HighlightTimelineManangement); + { + var permission = await _service.GetPermissionsOfUserAsync(2); + permission.Should().BeEquivalentTo(UserPermission.AllTimelineManagement, UserPermission.HighlightTimelineManangement); + } + + await _service.RemovePermissionFromUserAsync(2, UserPermission.HighlightTimelineManangement); + { + var permission = await _service.GetPermissionsOfUserAsync(2); + permission.Should().BeEquivalentTo(UserPermission.AllTimelineManagement); + } + + // Remove non-owned permission should work. + await _service.RemovePermissionFromUserAsync(2, UserPermission.HighlightTimelineManangement); + { + var permission = await _service.GetPermissionsOfUserAsync(2); + permission.Should().BeEquivalentTo(UserPermission.AllTimelineManagement); + } + } + + [Fact] + public async Task AddPermissionToInexistentUserShouldThrown() + { + await _service.Awaiting(s => s.AddPermissionToUserAsync(10, UserPermission.HighlightTimelineManangement)).Should().ThrowAsync(); + } + + [Fact] + public async Task RemovePermissionFromInexistentUserShouldThrown() + { + await _service.Awaiting(s => s.RemovePermissionFromUserAsync(10, UserPermission.HighlightTimelineManangement)).Should().ThrowAsync(); + } + + [Fact] + public async Task RemovePermissionFromInexistentUserShouldNotThrownIfNotCheck() + { + await _service.Awaiting(s => s.RemovePermissionFromUserAsync(10, UserPermission.HighlightTimelineManangement, false)).Should().NotThrowAsync(); + } + } +} diff --git a/BackEnd/Timeline.Tests/Services/UserPermissionTest.cs b/BackEnd/Timeline.Tests/Services/UserPermissionTest.cs deleted file mode 100644 index 4bcbc633..00000000 --- a/BackEnd/Timeline.Tests/Services/UserPermissionTest.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Timeline.Entities; -using Timeline.Services; -using Timeline.Tests.Helpers; -using Xunit; - -namespace Timeline.Tests.Services -{ - public class UserPermissionTest : DatabaseBasedTest - { - private UserPermissionService _service; - - public UserPermissionTest() - { - - } - - protected override void OnDatabaseCreated() - { - _service = new UserPermissionService(Database); - } - - - } -} diff --git a/BackEnd/Timeline/Services/UserPermissionService.cs b/BackEnd/Timeline/Services/UserPermissionService.cs index 466ee252..deedf0a6 100644 --- a/BackEnd/Timeline/Services/UserPermissionService.cs +++ b/BackEnd/Timeline/Services/UserPermissionService.cs @@ -126,9 +126,8 @@ namespace Timeline.Services /// /// The id of the user. /// The new permission. - /// Whether check the user's existence. - /// Thrown when is true and user does not exist. - Task AddPermissionToUserAsync(long userId, UserPermission permission, bool checkUserExistence = true); + /// Thrown when user does not exist. + Task AddPermissionToUserAsync(long userId, UserPermission permission); /// /// Remove a permission from user. @@ -175,15 +174,15 @@ namespace Timeline.Services return UserPermissions.FromStringList(permissionNameList); } - public async Task AddPermissionToUserAsync(long userId, UserPermission permission, bool checkUserExistence) + public async Task AddPermissionToUserAsync(long userId, UserPermission permission) { if (userId == 1) // The init administrator account. return; - await CheckUserExistence(userId, checkUserExistence); + await CheckUserExistence(userId, true); var alreadyHas = await _database.UserPermission - .AnyAsync(e => e.UserId == userId && e.Permission.Equals(permission.ToString(), StringComparison.InvariantCultureIgnoreCase)); + .AnyAsync(e => e.UserId == userId && e.Permission == permission.ToString()); if (alreadyHas) return; @@ -192,7 +191,7 @@ namespace Timeline.Services await _database.SaveChangesAsync(); } - public async Task RemovePermissionFromUserAsync(long userId, UserPermission permission, bool checkUserExistence) + public async Task RemovePermissionFromUserAsync(long userId, UserPermission permission, bool checkUserExistence = true) { if (userId == 1) // The init administrator account. return; @@ -200,7 +199,7 @@ namespace Timeline.Services await CheckUserExistence(userId, checkUserExistence); var entity = await _database.UserPermission - .Where(e => e.UserId == userId && e.Permission.Equals(permission.ToString(), StringComparison.InvariantCultureIgnoreCase)) + .Where(e => e.UserId == userId && e.Permission == permission.ToString()) .SingleOrDefaultAsync(); if (entity == null) return; -- cgit v1.2.3 From 34dea0b713aaac265909fe24eeb9483c9ec8fe2a Mon Sep 17 00:00:00 2001 From: crupest Date: Thu, 12 Nov 2020 23:21:31 +0800 Subject: ... --- BackEnd/Timeline.Tests/Helpers/TestDatabase.cs | 21 +++---------- .../IntegratedTests/IntegratedTestBase.cs | 25 +++++---------- .../Timeline.Tests/IntegratedTests/TokenTest.cs | 3 +- BackEnd/Timeline.Tests/IntegratedTests/UserTest.cs | 36 +++++----------------- .../Timeline.Tests/Services/TimelineServiceTest.cs | 9 ++++-- .../Controllers/ControllerAuthExtensions.cs | 5 +-- BackEnd/Timeline/Controllers/TimelineController.cs | 20 ++++++------ .../Timeline/Controllers/UserAvatarController.cs | 6 ++-- BackEnd/Timeline/Controllers/UserController.cs | 24 +++++++-------- BackEnd/Timeline/Models/Http/UserController.cs | 21 ++----------- BackEnd/Timeline/Services/TimelineService.cs | 14 ++++----- BackEnd/Timeline/Services/UserService.cs | 4 +-- 12 files changed, 67 insertions(+), 121 deletions(-) (limited to 'BackEnd/Timeline.Tests/Services') diff --git a/BackEnd/Timeline.Tests/Helpers/TestDatabase.cs b/BackEnd/Timeline.Tests/Helpers/TestDatabase.cs index f0c26180..74db74aa 100644 --- a/BackEnd/Timeline.Tests/Helpers/TestDatabase.cs +++ b/BackEnd/Timeline.Tests/Helpers/TestDatabase.cs @@ -4,7 +4,6 @@ using Microsoft.Extensions.Logging.Abstractions; using System.Threading.Tasks; using Timeline.Entities; using Timeline.Migrations; -using Timeline.Models; using Timeline.Services; using Xunit; @@ -36,23 +35,13 @@ namespace Timeline.Tests.Helpers if (_createUser) { var passwordService = new PasswordService(); - var userService = new UserService(NullLogger.Instance, context, passwordService, new Clock()); + var userService = new UserService(NullLogger.Instance, context, passwordService, new Clock(), new UserPermissionService(context)); - await userService.CreateUser(new User - { - Username = "admin", - Password = "adminpw", - Administrator = true, - Nickname = "administrator" - }); + var admin = await userService.CreateUser("admin", "adminpw"); + await userService.ModifyUser(admin.Id, new ModifyUserParams() { Nickname = "administrator" }); - await userService.CreateUser(new User - { - Username = "user", - Password = "userpw", - Administrator = false, - Nickname = "imuser" - }); + var user = await userService.CreateUser("user", "userpw"); + await userService.ModifyUser(user.Id, new ModifyUserParams() { Nickname = "imuser" }); } } } diff --git a/BackEnd/Timeline.Tests/IntegratedTests/IntegratedTestBase.cs b/BackEnd/Timeline.Tests/IntegratedTests/IntegratedTestBase.cs index 7cf27297..f75ce69c 100644 --- a/BackEnd/Timeline.Tests/IntegratedTests/IntegratedTestBase.cs +++ b/BackEnd/Timeline.Tests/IntegratedTests/IntegratedTestBase.cs @@ -7,7 +7,6 @@ using System.Net.Http; using System.Text.Json; using System.Text.Json.Serialization; using System.Threading.Tasks; -using Timeline.Models; using Timeline.Models.Converters; using Timeline.Models.Http; using Timeline.Services; @@ -60,26 +59,14 @@ namespace Timeline.Tests.IntegratedTests using (var scope = TestApp.Host.Services.CreateScope()) { - var users = new List() + var users = new List<(string username, string password, string nickname)>() { - new User - { - Username = "admin", - Password = "adminpw", - Administrator = true, - Nickname = "administrator" - } + ("admin", "adminpw", "administrator") }; for (int i = 1; i <= _userCount; i++) { - users.Add(new User - { - Username = $"user{i}", - Password = $"user{i}pw", - Administrator = false, - Nickname = $"imuser{i}" - }); + users.Add(($"user{i}", $"user{i}pw", $"imuser{i}")); } var userInfoList = new List(); @@ -87,7 +74,9 @@ namespace Timeline.Tests.IntegratedTests var userService = scope.ServiceProvider.GetRequiredService(); foreach (var user in users) { - await userService.CreateUser(user); + var (username, password, nickname) = user; + var u = await userService.CreateUser(username, password); + await userService.ModifyUser(u.Id, new ModifyUserParams() { Nickname = nickname }); } using var client = await CreateDefaultClient(); @@ -99,7 +88,7 @@ namespace Timeline.Tests.IntegratedTests options.Converters.Add(new JsonDateTimeConverter()); foreach (var user in users) { - var s = await client.GetStringAsync($"users/{user.Username}"); + var s = await client.GetStringAsync($"users/{user.username}"); userInfoList.Add(JsonSerializer.Deserialize(s, options)); } diff --git a/BackEnd/Timeline.Tests/IntegratedTests/TokenTest.cs b/BackEnd/Timeline.Tests/IntegratedTests/TokenTest.cs index 480d66cd..f4a406d1 100644 --- a/BackEnd/Timeline.Tests/IntegratedTests/TokenTest.cs +++ b/BackEnd/Timeline.Tests/IntegratedTests/TokenTest.cs @@ -103,7 +103,8 @@ namespace Timeline.Tests.IntegratedTests { // create a user for test var userService = scope.ServiceProvider.GetRequiredService(); - await userService.ModifyUser("user1", new User { Password = "user1pw" }); + var id = await userService.GetUserIdByUsername("user1"); + await userService.ModifyUser(id, new ModifyUserParams { Password = "user1pw" }); } (await client.PostAsJsonAsync(VerifyTokenUrl, diff --git a/BackEnd/Timeline.Tests/IntegratedTests/UserTest.cs b/BackEnd/Timeline.Tests/IntegratedTests/UserTest.cs index 9dfcc6a5..329e53f5 100644 --- a/BackEnd/Timeline.Tests/IntegratedTests/UserTest.cs +++ b/BackEnd/Timeline.Tests/IntegratedTests/UserTest.cs @@ -2,6 +2,7 @@ using FluentAssertions; using System.Collections.Generic; using System.Net; using System.Net.Http; +using System.Net.Http.Json; using System.Threading.Tasks; using Timeline.Models.Http; using Timeline.Tests.Helpers; @@ -129,13 +130,11 @@ namespace Timeline.Tests.IntegratedTests { Username = "newuser", Password = "newpw", - Administrator = true, Nickname = "aaa" }); var body = res.Should().HaveStatusCode(200) .And.HaveJsonBody() .Which; - body.Administrator.Should().Be(true); body.Nickname.Should().Be("aaa"); } @@ -144,14 +143,14 @@ namespace Timeline.Tests.IntegratedTests var body = res.Should().HaveStatusCode(200) .And.HaveJsonBody() .Which; - body.Administrator.Should().Be(true); body.Nickname.Should().Be("aaa"); } { + var token = userClient.DefaultRequestHeaders.Authorization.Parameter; // Token should expire. - var res = await userClient.GetAsync("testing/auth/Authorize"); - res.Should().HaveStatusCode(HttpStatusCode.Unauthorized); + var res = await userClient.PostAsJsonAsync("token/verify", new() { Token = token }); + res.Should().HaveStatusCode(HttpStatusCode.BadRequest); } { @@ -235,14 +234,6 @@ namespace Timeline.Tests.IntegratedTests res.Should().HaveStatusCode(HttpStatusCode.Forbidden); } - [Fact] - public async Task Patch_Administrator_Forbid() - { - using var client = await CreateClientAsUser(); - var res = await client.PatchAsJsonAsync("users/user1", new UserPatchRequest { Administrator = true }); - res.Should().HaveStatusCode(HttpStatusCode.Forbidden); - } - [Fact] public async Task Delete_Deleted() { @@ -301,22 +292,16 @@ namespace Timeline.Tests.IntegratedTests { Username = "aaa", Password = "bbb", - Administrator = true, - Nickname = "ccc" }); var body = res.Should().HaveStatusCode(200) .And.HaveJsonBody().Which; body.Username.Should().Be("aaa"); - body.Nickname.Should().Be("ccc"); - body.Administrator.Should().BeTrue(); } { var res = await client.GetAsync("users/aaa"); var body = res.Should().HaveStatusCode(200) .And.HaveJsonBody().Which; body.Username.Should().Be("aaa"); - body.Nickname.Should().Be("ccc"); - body.Administrator.Should().BeTrue(); } { // Test password. @@ -326,12 +311,10 @@ namespace Timeline.Tests.IntegratedTests public static IEnumerable Op_CreateUser_InvalidModel_Data() { - yield return new[] { new CreateUserRequest { Username = "aaa", Password = "bbb" } }; - yield return new[] { new CreateUserRequest { Username = "aaa", Administrator = true } }; - yield return new[] { new CreateUserRequest { Password = "bbb", Administrator = true } }; - yield return new[] { new CreateUserRequest { Username = "a!a", Password = "bbb", Administrator = true } }; - yield return new[] { new CreateUserRequest { Username = "aaa", Password = "", Administrator = true } }; - yield return new[] { new CreateUserRequest { Username = "aaa", Password = "bbb", Administrator = true, Nickname = new string('a', 40) } }; + yield return new[] { new CreateUserRequest { Username = "aaa" } }; + yield return new[] { new CreateUserRequest { Password = "bbb" } }; + yield return new[] { new CreateUserRequest { Username = "a!a", Password = "bbb" } }; + yield return new[] { new CreateUserRequest { Username = "aaa", Password = "" } }; } [Theory] @@ -354,7 +337,6 @@ namespace Timeline.Tests.IntegratedTests { Username = "user1", Password = "bbb", - Administrator = false }); res.Should().HaveStatusCode(400) .And.HaveCommonBody(ErrorCodes.UserController.UsernameConflict); @@ -370,7 +352,6 @@ namespace Timeline.Tests.IntegratedTests { Username = "aaa", Password = "bbb", - Administrator = false }); res.Should().HaveStatusCode(HttpStatusCode.Unauthorized); } @@ -385,7 +366,6 @@ namespace Timeline.Tests.IntegratedTests { Username = "aaa", Password = "bbb", - Administrator = false }); res.Should().HaveStatusCode(HttpStatusCode.Forbidden); } diff --git a/BackEnd/Timeline.Tests/Services/TimelineServiceTest.cs b/BackEnd/Timeline.Tests/Services/TimelineServiceTest.cs index 19d2781a..73fdd32f 100644 --- a/BackEnd/Timeline.Tests/Services/TimelineServiceTest.cs +++ b/BackEnd/Timeline.Tests/Services/TimelineServiceTest.cs @@ -24,6 +24,8 @@ namespace Timeline.Tests.Services private DataManager _dataManager; + private UserPermissionService _userPermissionService; + private UserService _userService; private TimelineService _timelineService; @@ -37,7 +39,8 @@ namespace Timeline.Tests.Services protected override void OnDatabaseCreated() { _dataManager = new DataManager(Database, _eTagGenerator); - _userService = new UserService(NullLogger.Instance, Database, _passwordService, _clock); + _userPermissionService = new UserPermissionService(Database); + _userService = new UserService(NullLogger.Instance, Database, _passwordService, _clock, _userPermissionService); _timelineService = new TimelineService(NullLogger.Instance, Database, _dataManager, _userService, _imageValidator, _clock); _userDeleteService = new UserDeleteService(NullLogger.Instance, Database, _timelineService); } @@ -207,13 +210,13 @@ namespace Timeline.Tests.Services } { - await _userService.ModifyUser(userId, new User { Nickname = "haha" }); + await _userService.ModifyUser(userId, new ModifyUserParams { Nickname = "haha" }); var posts = await _timelineService.GetPosts(timelineName, time2); posts.Should().HaveCount(0); } { - await _userService.ModifyUser(userId, new User { Username = "haha" }); + await _userService.ModifyUser(userId, new ModifyUserParams { Username = "haha" }); var posts = await _timelineService.GetPosts(timelineName, time2); posts.Should().HaveCount(4); } diff --git a/BackEnd/Timeline/Controllers/ControllerAuthExtensions.cs b/BackEnd/Timeline/Controllers/ControllerAuthExtensions.cs index 00a65454..9096978d 100644 --- a/BackEnd/Timeline/Controllers/ControllerAuthExtensions.cs +++ b/BackEnd/Timeline/Controllers/ControllerAuthExtensions.cs @@ -2,15 +2,16 @@ using System; using System.Security.Claims; using Timeline.Auth; +using Timeline.Services; using static Timeline.Resources.Controllers.ControllerAuthExtensions; namespace Timeline.Controllers { public static class ControllerAuthExtensions { - public static bool IsAdministrator(this ControllerBase controller) + public static bool UserHasPermission(this ControllerBase controller, UserPermission permission) { - return controller.User != null && controller.User.IsAdministrator(); + return controller.User != null && controller.User.HasPermission(permission); } public static long GetUserId(this ControllerBase controller) diff --git a/BackEnd/Timeline/Controllers/TimelineController.cs b/BackEnd/Timeline/Controllers/TimelineController.cs index 9a3147ea..45060b5d 100644 --- a/BackEnd/Timeline/Controllers/TimelineController.cs +++ b/BackEnd/Timeline/Controllers/TimelineController.cs @@ -43,6 +43,8 @@ namespace Timeline.Controllers _mapper = mapper; } + private bool UserHasAllTimelineManagementPermission => this.UserHasPermission(UserPermission.AllTimelineManagement); + /// /// List all timelines. /// @@ -180,7 +182,7 @@ namespace Timeline.Controllers [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task>> PostListGet([FromRoute][GeneralTimelineName] string name, [FromQuery] DateTime? modifiedSince, [FromQuery] bool? includeDeleted) { - if (!this.IsAdministrator() && !await _service.HasReadPermission(name, this.GetOptionalUserId())) + if (!UserHasAllTimelineManagementPermission && !await _service.HasReadPermission(name, this.GetOptionalUserId())) { return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid()); } @@ -208,7 +210,7 @@ namespace Timeline.Controllers public async Task PostDataGet([FromRoute][GeneralTimelineName] string name, [FromRoute] long id, [FromHeader(Name = "If-None-Match")] string? ifNoneMatch) { _ = ifNoneMatch; - if (!this.IsAdministrator() && !await _service.HasReadPermission(name, this.GetOptionalUserId())) + if (!UserHasAllTimelineManagementPermission && !await _service.HasReadPermission(name, this.GetOptionalUserId())) { return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid()); } @@ -246,7 +248,7 @@ namespace Timeline.Controllers public async Task> PostPost([FromRoute][GeneralTimelineName] string name, [FromBody] TimelinePostCreateRequest body) { var id = this.GetUserId(); - if (!this.IsAdministrator() && !await _service.IsMemberOf(name, id)) + if (!UserHasAllTimelineManagementPermission && !await _service.IsMemberOf(name, id)) { return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid()); } @@ -313,7 +315,7 @@ namespace Timeline.Controllers [ProducesResponseType(StatusCodes.Status403Forbidden)] public async Task> PostDelete([FromRoute][GeneralTimelineName] string name, [FromRoute] long id) { - if (!this.IsAdministrator() && !await _service.HasPostModifyPermission(name, id, this.GetUserId())) + if (!UserHasAllTimelineManagementPermission && !await _service.HasPostModifyPermission(name, id, this.GetUserId())) { return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid()); } @@ -342,7 +344,7 @@ namespace Timeline.Controllers [ProducesResponseType(StatusCodes.Status403Forbidden)] public async Task> TimelinePatch([FromRoute][GeneralTimelineName] string name, [FromBody] TimelinePatchRequest body) { - if (!this.IsAdministrator() && !(await _service.HasManagePermission(name, this.GetUserId()))) + if (!UserHasAllTimelineManagementPermission && !(await _service.HasManagePermission(name, this.GetUserId()))) { return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid()); } @@ -365,7 +367,7 @@ namespace Timeline.Controllers [ProducesResponseType(StatusCodes.Status403Forbidden)] public async Task TimelineMemberPut([FromRoute][GeneralTimelineName] string name, [FromRoute][Username] string member) { - if (!this.IsAdministrator() && !(await _service.HasManagePermission(name, this.GetUserId()))) + if (!UserHasAllTimelineManagementPermission && !(await _service.HasManagePermission(name, this.GetUserId()))) { return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid()); } @@ -393,7 +395,7 @@ namespace Timeline.Controllers [ProducesResponseType(StatusCodes.Status403Forbidden)] public async Task TimelineMemberDelete([FromRoute][GeneralTimelineName] string name, [FromRoute][Username] string member) { - if (!this.IsAdministrator() && !(await _service.HasManagePermission(name, this.GetUserId()))) + if (!UserHasAllTimelineManagementPermission && !(await _service.HasManagePermission(name, this.GetUserId()))) { return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid()); } @@ -448,7 +450,7 @@ namespace Timeline.Controllers [ProducesResponseType(StatusCodes.Status403Forbidden)] public async Task> TimelineDelete([FromRoute][TimelineName] string name) { - if (!this.IsAdministrator() && !(await _service.HasManagePermission(name, this.GetUserId()))) + if (!UserHasAllTimelineManagementPermission && !(await _service.HasManagePermission(name, this.GetUserId()))) { return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid()); } @@ -472,7 +474,7 @@ namespace Timeline.Controllers [ProducesResponseType(StatusCodes.Status403Forbidden)] public async Task> TimelineOpChangeName([FromBody] TimelineChangeNameRequest body) { - if (!this.IsAdministrator() && !(await _service.HasManagePermission(body.OldName, this.GetUserId()))) + if (!UserHasAllTimelineManagementPermission && !(await _service.HasManagePermission(body.OldName, this.GetUserId()))) { return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid()); } diff --git a/BackEnd/Timeline/Controllers/UserAvatarController.cs b/BackEnd/Timeline/Controllers/UserAvatarController.cs index bc4afa30..44d45b76 100644 --- a/BackEnd/Timeline/Controllers/UserAvatarController.cs +++ b/BackEnd/Timeline/Controllers/UserAvatarController.cs @@ -86,7 +86,7 @@ namespace Timeline.Controllers [ProducesResponseType(StatusCodes.Status403Forbidden)] public async Task Put([FromRoute][Username] string username, [FromBody] ByteData body) { - if (!User.IsAdministrator() && User.Identity.Name != username) + if (!this.UserHasPermission(UserPermission.UserManagement) && User.Identity!.Name != username) { _logger.LogInformation(Log.Format(LogPutForbid, ("Operator Username", User.Identity.Name), ("Username To Put Avatar", username))); @@ -149,10 +149,10 @@ namespace Timeline.Controllers [Authorize] public async Task Delete([FromRoute][Username] string username) { - if (!User.IsAdministrator() && User.Identity.Name != username) + if (!this.UserHasPermission(UserPermission.UserManagement) && User.Identity!.Name != username) { _logger.LogInformation(Log.Format(LogDeleteForbid, - ("Operator Username", User.Identity.Name), ("Username To Delete Avatar", username))); + ("Operator Username", User.Identity!.Name), ("Username To Delete Avatar", username))); return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid()); } diff --git a/BackEnd/Timeline/Controllers/UserController.cs b/BackEnd/Timeline/Controllers/UserController.cs index 02c09aab..524e5559 100644 --- a/BackEnd/Timeline/Controllers/UserController.cs +++ b/BackEnd/Timeline/Controllers/UserController.cs @@ -65,7 +65,8 @@ namespace Timeline.Controllers { try { - var user = await _userService.GetUserByUsername(username); + var id = await _userService.GetUserIdByUsername(username); + var user = await _userService.GetUser(id); return Ok(ConvertToUserInfo(user)); } catch (UserNotExistException e) @@ -89,11 +90,12 @@ namespace Timeline.Controllers [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task> Patch([FromBody] UserPatchRequest body, [FromRoute][Username] string username) { - if (this.IsAdministrator()) + if (this.UserHasPermission(UserPermission.UserManagement)) { try { - var user = await _userService.ModifyUser(username, _mapper.Map(body)); + var id = await _userService.GetUserIdByUsername(username); + var user = await _userService.ModifyUser(id, _mapper.Map(body)); return Ok(ConvertToUserInfo(user)); } catch (UserNotExistException e) @@ -108,7 +110,7 @@ namespace Timeline.Controllers } else { - if (User.Identity.Name != username) + if (User.Identity!.Name != username) return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.CustomMessage_Forbid(Common_Forbid_NotSelf)); @@ -120,11 +122,7 @@ namespace Timeline.Controllers return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.CustomMessage_Forbid(UserController_Patch_Forbid_Password)); - if (body.Administrator != null) - return StatusCode(StatusCodes.Status403Forbidden, - ErrorResponse.Common.CustomMessage_Forbid(UserController_Patch_Forbid_Administrator)); - - var user = await _userService.ModifyUser(this.GetUserId(), _mapper.Map(body)); + var user = await _userService.ModifyUser(this.GetUserId(), _mapper.Map(body)); return Ok(ConvertToUserInfo(user)); } } @@ -134,7 +132,7 @@ namespace Timeline.Controllers /// /// Username of the user to delete. /// Info of deletion. - [HttpDelete("users/{username}"), AdminAuthorize] + [HttpDelete("users/{username}"), PermissionAuthorize(UserPermission.UserManagement)] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status403Forbidden)] @@ -151,7 +149,7 @@ namespace Timeline.Controllers /// Create a new user. You have to be administrator. /// /// The new user's info. - [HttpPost("userop/createuser"), AdminAuthorize] + [HttpPost("userop/createuser"), PermissionAuthorize(UserPermission.UserManagement)] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] @@ -160,7 +158,7 @@ namespace Timeline.Controllers { try { - var user = await _userService.CreateUser(_mapper.Map(body)); + var user = await _userService.CreateUser(body.Username, body.Password); return Ok(ConvertToUserInfo(user)); } catch (EntityAlreadyExistException e) when (e.EntityName == EntityNames.User) @@ -186,7 +184,7 @@ namespace Timeline.Controllers catch (BadPasswordException e) { _logger.LogInformation(e, Log.Format(LogChangePasswordBadPassword, - ("Username", User.Identity.Name), ("Old Password", request.OldPassword))); + ("Username", User.Identity!.Name), ("Old Password", request.OldPassword))); return BadRequest(ErrorResponse.UserController.ChangePassword_BadOldPassword()); } // User can't be non-existent or the token is bad. diff --git a/BackEnd/Timeline/Models/Http/UserController.cs b/BackEnd/Timeline/Models/Http/UserController.cs index 6bc5a66e..92a63874 100644 --- a/BackEnd/Timeline/Models/Http/UserController.cs +++ b/BackEnd/Timeline/Models/Http/UserController.cs @@ -2,6 +2,7 @@ using AutoMapper; using System.ComponentModel.DataAnnotations; using Timeline.Controllers; using Timeline.Models.Validation; +using Timeline.Services; namespace Timeline.Models.Http { @@ -27,11 +28,6 @@ namespace Timeline.Models.Http /// [Nickname] public string? Nickname { get; set; } - - /// - /// Whether to be administrator. Null if not change. Need to be administrator. - /// - public bool? Administrator { get; set; } } /// @@ -50,18 +46,6 @@ namespace Timeline.Models.Http /// [Required, MinLength(1)] public string Password { get; set; } = default!; - - /// - /// Whether the new user is administrator. - /// - [Required] - public bool? Administrator { get; set; } - - /// - /// Nickname of the new user. - /// - [Nickname] - public string? Nickname { get; set; } } /// @@ -86,8 +70,7 @@ namespace Timeline.Models.Http { public UserControllerAutoMapperProfile() { - CreateMap(MemberList.Source); - CreateMap(MemberList.Source); + CreateMap(); } } } diff --git a/BackEnd/Timeline/Services/TimelineService.cs b/BackEnd/Timeline/Services/TimelineService.cs index 4bcae596..04870dcf 100644 --- a/BackEnd/Timeline/Services/TimelineService.cs +++ b/BackEnd/Timeline/Services/TimelineService.cs @@ -414,12 +414,12 @@ namespace Timeline.Services /// Remember to include Members when query. private async Task MapTimelineFromEntity(TimelineEntity entity) { - var owner = await _userService.GetUserById(entity.OwnerId); + var owner = await _userService.GetUser(entity.OwnerId); var members = new List(); foreach (var memberEntity in entity.Members) { - members.Add(await _userService.GetUserById(memberEntity.UserId)); + members.Add(await _userService.GetUser(memberEntity.UserId)); } var name = entity.Name ?? ("@" + owner.Username); @@ -441,7 +441,7 @@ namespace Timeline.Services private async Task MapTimelinePostFromEntity(TimelinePostEntity entity, string timelineName) { - User? author = entity.AuthorId.HasValue ? await _userService.GetUserById(entity.AuthorId.Value) : null; + User? author = entity.AuthorId.HasValue ? await _userService.GetUser(entity.AuthorId.Value) : null; ITimelinePostContent? content = null; @@ -699,7 +699,7 @@ namespace Timeline.Services var timelineId = await FindTimelineId(timelineName); var timelineEntity = await _database.Timelines.Where(t => t.Id == timelineId).SingleAsync(); - var author = await _userService.GetUserById(authorId); + var author = await _userService.GetUser(authorId); var currentTime = _clock.GetCurrentTime(); var finalTime = time ?? currentTime; @@ -742,7 +742,7 @@ namespace Timeline.Services var timelineId = await FindTimelineId(timelineName); var timelineEntity = await _database.Timelines.Where(t => t.Id == timelineId).SingleAsync(); - var author = await _userService.GetUserById(authorId); + var author = await _userService.GetUser(authorId); var imageFormat = await _imageValidator.Validate(data); @@ -1098,14 +1098,14 @@ namespace Timeline.Services ValidateTimelineName(name, nameof(name)); - var user = await _userService.GetUserById(owner); + var user = await _userService.GetUser(owner); var conflict = await _database.Timelines.AnyAsync(t => t.Name == name); if (conflict) throw new EntityAlreadyExistException(EntityNames.Timeline, null, ExceptionTimelineNameConflict); - var newEntity = CreateNewTimelineEntity(name, user.Id!.Value); + var newEntity = CreateNewTimelineEntity(name, user.Id); _database.Timelines.Add(newEntity); await _database.SaveChangesAsync(); diff --git a/BackEnd/Timeline/Services/UserService.cs b/BackEnd/Timeline/Services/UserService.cs index ed637ba3..b925742e 100644 --- a/BackEnd/Timeline/Services/UserService.cs +++ b/BackEnd/Timeline/Services/UserService.cs @@ -17,7 +17,7 @@ namespace Timeline.Services /// /// Null means not change. /// - public record ModifyUserParams(string? Username, string? Password, string? Nickname); + public record ModifyUserParams(string? Username = null, string? Password = null, string? Nickname = null); public interface IUserService { @@ -74,7 +74,7 @@ namespace Timeline.Services /// The id of the user. /// The new information. /// The new user info. - /// Thrown when some fields in is bad. + /// Thrown when some fields in is bad. /// Thrown when user with given id does not exist. /// /// Version will increase if password is changed. -- cgit v1.2.3