From 1dfafd9400c158576f9ede8f3012356746cb5ae0 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 +++ BackEnd/Timeline/Entities/DatabaseContext.cs | 1 + BackEnd/Timeline/Services/UserPermissionService.cs | 213 +++++++++++++++++++++ 5 files changed, 295 insertions(+), 19 deletions(-) create mode 100644 BackEnd/Timeline.Tests/Services/DatabaseBasedTest.cs create mode 100644 BackEnd/Timeline.Tests/Services/UserPermissionTest.cs create mode 100644 BackEnd/Timeline/Services/UserPermissionService.cs 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); + } + + + } +} diff --git a/BackEnd/Timeline/Entities/DatabaseContext.cs b/BackEnd/Timeline/Entities/DatabaseContext.cs index ecadd703..e4203392 100644 --- a/BackEnd/Timeline/Entities/DatabaseContext.cs +++ b/BackEnd/Timeline/Entities/DatabaseContext.cs @@ -25,6 +25,7 @@ namespace Timeline.Entities public DbSet Users { get; set; } = default!; public DbSet UserAvatars { get; set; } = default!; + public DbSet UserPermission { get; set; } = default!; public DbSet Timelines { get; set; } = default!; public DbSet TimelinePosts { get; set; } = default!; public DbSet TimelineMembers { get; set; } = default!; diff --git a/BackEnd/Timeline/Services/UserPermissionService.cs b/BackEnd/Timeline/Services/UserPermissionService.cs new file mode 100644 index 00000000..466ee252 --- /dev/null +++ b/BackEnd/Timeline/Services/UserPermissionService.cs @@ -0,0 +1,213 @@ +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Timeline.Entities; +using Timeline.Services.Exceptions; + +namespace Timeline.Services +{ + 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. + /// + HighlightTimelineManangement + } + + /// + /// Represents a user's permissions. + /// + public class UserPermissions : IEnumerable + { + 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 HashSet(permissions); + } + + private readonly HashSet _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().ToUpperInvariant()).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 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. + /// Whether check the user's existence. + /// Thrown when is true and user does not exist. + Task AddPermissionToUserAsync(long userId, UserPermission permission, bool checkUserExistence = true); + + /// + /// 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. + Task RemovePermissionFromUserAsync(long userId, UserPermission permission, bool checkUserExistence = true); + } + + public class UserPermissionService : IUserPermissionService + { + private readonly DatabaseContext _database; + + public UserPermissionService(DatabaseContext database) + { + _database = database; + } + + private async Task CheckUserExistence(long userId, bool checkUserExistence) + { + if (checkUserExistence) + { + var existence = await _database.Users.AnyAsync(u => u.Id == userId); + if (!existence) + { + throw new UserNotExistException(userId); + } + } + } + + public async Task GetPermissionsOfUserAsync(long userId, bool checkUserExistence = true) + { + if (userId == 1) // The init administrator account. + { + return UserPermissions.AllPermissions; + } + + await CheckUserExistence(userId, checkUserExistence); + + var permissionNameList = await _database.UserPermission.Where(e => e.UserId == userId).Select(e => e.Permission).ToListAsync(); + + return UserPermissions.FromStringList(permissionNameList); + } + + public async Task AddPermissionToUserAsync(long userId, UserPermission permission, bool checkUserExistence) + { + if (userId == 1) // The init administrator account. + return; + + await CheckUserExistence(userId, checkUserExistence); + + var alreadyHas = await _database.UserPermission + .AnyAsync(e => e.UserId == userId && e.Permission.Equals(permission.ToString(), StringComparison.InvariantCultureIgnoreCase)); + + if (alreadyHas) return; + + _database.UserPermission.Add(new UserPermissionEntity { UserId = userId, Permission = permission.ToString() }); + + await _database.SaveChangesAsync(); + } + + public async Task RemovePermissionFromUserAsync(long userId, UserPermission permission, bool checkUserExistence) + { + if (userId == 1) // The init administrator account. + return; + + await CheckUserExistence(userId, checkUserExistence); + + var entity = await _database.UserPermission + .Where(e => e.UserId == userId && e.Permission.Equals(permission.ToString(), StringComparison.InvariantCultureIgnoreCase)) + .SingleOrDefaultAsync(); + + if (entity == null) return; + + _database.UserPermission.Remove(entity); + + await _database.SaveChangesAsync(); + } + } +} -- cgit v1.2.3