diff options
author | crupest <crupest@outlook.com> | 2021-04-27 19:29:20 +0800 |
---|---|---|
committer | crupest <crupest@outlook.com> | 2021-04-27 19:29:20 +0800 |
commit | a665f5d894539cae5f4188e4a72ea9634b8c4ed0 (patch) | |
tree | 03aa9d578e13213018c178ba47755167e2ccc2d4 /BackEnd/Timeline/Services | |
parent | 89e3c2103fa478ebc659a1b914e75d5138c06c08 (diff) | |
download | timeline-a665f5d894539cae5f4188e4a72ea9634b8c4ed0.tar.gz timeline-a665f5d894539cae5f4188e4a72ea9634b8c4ed0.tar.bz2 timeline-a665f5d894539cae5f4188e4a72ea9634b8c4ed0.zip |
refactor: ...
Diffstat (limited to 'BackEnd/Timeline/Services')
27 files changed, 536 insertions, 411 deletions
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<bool> 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<List<long>> 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<bool> 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<bool> 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<IPathProvider, PathProvider>();
services.TryAddTransient<IClock, Clock>();
+ 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<UserTokenManager> _logger;
private readonly IOptionsMonitor<TokenOptions> _tokenOptionsMonitor;
private readonly IUserService _userService;
- private readonly IUserCredentialService _userCredentialService;
private readonly IUserTokenHandler _userTokenService;
private readonly IClock _clock;
- public UserTokenManager(ILogger<UserTokenManager> logger, IOptionsMonitor<TokenOptions> tokenOptionsMonitor, IUserService userService, IUserCredentialService userCredentialService, IUserTokenHandler userTokenService, IClock clock)
+ public UserTokenManager(ILogger<UserTokenManager> logger, IOptionsMonitor<TokenOptions> 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
{
- /// <summary>
- /// This service provide some basic user features, which should be used internally for other services.
- /// </summary>
- public interface IBasicUserService
- {
- /// <summary>
- /// Check if a user exists.
- /// </summary>
- /// <param name="id">The id of the user.</param>
- /// <returns>True if exists. Otherwise false.</returns>
- Task<bool> CheckUserExistence(long id);
-
- /// <summary>
- /// Get the user id of given username.
- /// </summary>
- /// <param name="username">Username of the user.</param>
- /// <returns>The id of the user.</returns>
- /// <exception cref="ArgumentNullException">Thrown when <paramref name="username"/> is null.</exception>
- /// <exception cref="ArgumentException">Thrown when <paramref name="username"/> is of bad format.</exception>
- /// <exception cref="UserNotExistException">Thrown when the user with given username does not exist.</exception>
- Task<long> GetUserIdByUsername(string username);
-
- /// <summary>
- /// Get the username modified time of a user.
- /// </summary>
- /// <param name="userId">User id.</param>
- /// <returns>The time.</returns>
- /// <exception cref="UserNotExistException">Thrown when user does not exist.</exception>
- Task<DateTime> GetUsernameLastModifiedTime(long userId);
- }
-
public class BasicUserService : IBasicUserService
{
private readonly DatabaseContext _database;
@@ -49,12 +18,12 @@ namespace Timeline.Services.User _database = database;
}
- public async Task<bool> CheckUserExistence(long id)
+ public async Task<bool> CheckUserExistenceAsync(long id)
{
return await _database.Users.AnyAsync(u => u.Id == id);
}
- public async Task<long> GetUserIdByUsername(string username)
+ public async Task<long> 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<DateTime> GetUsernameLastModifiedTime(long userId)
+ public async Task<DateTime> 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
+{
+ /// <summary>
+ /// This service provide some basic user features, which should be used internally for other services.
+ /// </summary>
+ public interface IBasicUserService
+ {
+ /// <summary>
+ /// Check if a user exists.
+ /// </summary>
+ /// <param name="id">The id of the user.</param>
+ /// <returns>True if exists. Otherwise false.</returns>
+ Task<bool> CheckUserExistenceAsync(long id);
+
+ /// <summary>
+ /// Get the user id of given username.
+ /// </summary>
+ /// <param name="username">Username of the user.</param>
+ /// <returns>The id of the user.</returns>
+ /// <exception cref="ArgumentNullException">Thrown when <paramref name="username"/> is null.</exception>
+ /// <exception cref="ArgumentException">Thrown when <paramref name="username"/> is of bad format.</exception>
+ /// <exception cref="UserNotExistException">Thrown when the user with given username does not exist.</exception>
+ Task<long> GetUserIdByUsernameAsync(string username);
+
+ /// <summary>
+ /// Get the username modified time of a user.
+ /// </summary>
+ /// <param name="userId">User id.</param>
+ /// <returns>The time.</returns>
+ /// <exception cref="UserNotExistException">Thrown when user does not exist.</exception>
+ Task<DateTime> 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
+ {
+ /// <summary>
+ /// Delete a user of given username.
+ /// </summary>
+ /// <param name="username">Username of the user to delete. Can't be null.</param>
+ /// <returns>True if user is deleted, false if user not exist.</returns>
+ /// <exception cref="ArgumentNullException">Thrown if <paramref name="username"/> is null.</exception>
+ /// <exception cref="ArgumentException">Thrown when <paramref name="username"/> is of bad format.</exception>
+ /// <exception cref="InvalidOperationOnRootUserException">Thrown when deleting root user.</exception>
+ Task<bool> 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
+ {
+ /// <summary>
+ /// Get permissions of a user.
+ /// </summary>
+ /// <param name="userId">The id of the user.</param>
+ /// <param name="checkUserExistence">Whether check the user's existence.</param>
+ /// <returns>The permission list.</returns>
+ /// <exception cref="UserNotExistException">Thrown when <paramref name="checkUserExistence"/> is true and user does not exist.</exception>
+ Task<UserPermissions> GetPermissionsOfUserAsync(long userId, bool checkUserExistence = true);
+
+ /// <summary>
+ /// Add a permission to user.
+ /// </summary>
+ /// <param name="userId">The id of the user.</param>
+ /// <param name="permission">The new permission.</param>
+ /// <exception cref="UserNotExistException">Thrown when user does not exist.</exception>
+ /// <exception cref="InvalidOperationOnRootUserException">Thrown when change root user's permission.</exception>
+ Task AddPermissionToUserAsync(long userId, UserPermission permission);
+
+ /// <summary>
+ /// Remove a permission from user.
+ /// </summary>
+ /// <param name="userId">The id of the user.</param>
+ /// <param name="permission">The permission.</param>
+ /// <param name="checkUserExistence">Whether check the user's existence.</param>
+ /// <exception cref="UserNotExistException">Thrown when <paramref name="checkUserExistence"/> is true and user does not exist.</exception>
+ /// <exception cref="InvalidOperationOnRootUserException">Thrown when change root user's permission.</exception>
+ 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
+ {
+ /// <summary>
+ /// Try to get a user by id.
+ /// </summary>
+ /// <param name="id">The id of the user.</param>
+ /// <returns>The user info.</returns>
+ /// <exception cref="UserNotExistException">Thrown when the user with given id does not exist.</exception>
+ Task<UserEntity> GetUserAsync(long id);
+
+ /// <summary>
+ /// List all users.
+ /// </summary>
+ /// <returns>The user info of users.</returns>
+ Task<List<UserEntity>> GetUsersAsync();
+
+ /// <summary>
+ /// Create a user with given info.
+ /// </summary>
+ /// <param name="param">Info of new user.</param>
+ /// <returns>The the new user.</returns>
+ /// <exception cref="ArgumentNullException">Thrown when <paramref name="param"/> is null.</exception>
+ /// <exception cref="ArgumentException">Thrown when param field is illegal.</exception>
+ /// <exception cref="EntityAlreadyExistException">Thrown when a user with given username already exists.</exception>
+ Task<UserEntity> CreateUserAsync(CreateUserParams param);
+
+ /// <summary>
+ /// Modify a user.
+ /// </summary>
+ /// <param name="id">The id of the user.</param>
+ /// <param name="param">The new information.</param>
+ /// <returns>The new user info.</returns>
+ /// <exception cref="ArgumentException">Thrown when some fields in <paramref name="param"/> is bad.</exception>
+ /// <exception cref="UserNotExistException">Thrown when user with given id does not exist.</exception>
+ /// <remarks>
+ /// Version will increase if password is changed.
+ /// </remarks>
+ Task<UserEntity> ModifyUserAsync(long id, ModifyUserParams? param);
+
+ /// <summary>
+ /// Try to verify the given username and password.
+ /// </summary>
+ /// <param name="username">The username of the user to verify.</param>
+ /// <param name="password">The password of the user to verify.</param>
+ /// <returns>User id.</returns>
+ /// <exception cref="ArgumentNullException">Thrown when <paramref name="username"/> or <paramref name="password"/> is null.</exception>
+ /// <exception cref="ArgumentException">Thrown when <paramref name="username"/> is of bad format or <paramref name="password"/> is empty.</exception>
+ /// <exception cref="UserNotExistException">Thrown when the user with given username does not exist.</exception>
+ /// <exception cref="BadPasswordException">Thrown when password is wrong.</exception>
+ Task<long> VerifyCredential(string username, string password);
+
+ /// <summary>
+ /// Try to change a user's password with old password.
+ /// </summary>
+ /// <param name="id">The id of user to change password of.</param>
+ /// <param name="oldPassword">Old password.</param>
+ /// <param name="newPassword">New password.</param>
+ /// <exception cref="ArgumentNullException">Thrown if <paramref name="oldPassword"/> or <paramref name="newPassword"/> is null.</exception>
+ /// <exception cref="ArgumentException">Thrown if <paramref name="oldPassword"/> or <paramref name="newPassword"/> is empty.</exception>
+ /// <exception cref="UserNotExistException">Thrown if the user with given username does not exist.</exception>
+ /// <exception cref="BadPasswordException">Thrown if the old password is wrong.</exception>
+ 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
+{
+ /// <summary>
+ /// Null means not change.
+ /// </summary>
+ 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 @@ -70,6 +70,24 @@ namespace Timeline.Services.User { }
/// <summary>
+ /// Looks up a localized string similar to Can't change root user's permission..
+ /// </summary>
+ internal static string ExceptionChangeRootUserPermission {
+ get {
+ return ResourceManager.GetString("ExceptionChangeRootUserPermission", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Can't delete root user..
+ /// </summary>
+ internal static string ExceptionDeleteRootUser {
+ get {
+ return ResourceManager.GetString("ExceptionDeleteRootUser", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to The hashes password is of bad format. It might not be created by server. Reason: {0}.
/// </summary>
internal static string ExceptionHashedPasswordBadFormat {
@@ -133,6 +151,15 @@ namespace Timeline.Services.User { }
/// <summary>
+ /// Looks up a localized string similar to Can't perform such operation on root user..
+ /// </summary>
+ internal static string ExceptionInvalidOperationOnRootUser {
+ get {
+ return ResourceManager.GetString("ExceptionInvalidOperationOnRootUser", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to Nickname is of bad format. {0}.
/// </summary>
internal static string ExceptionNicknameBadFormat {
@@ -151,6 +178,15 @@ namespace Timeline.Services.User { }
/// <summary>
+ /// Looks up a localized string similar to Password can't be null..
+ /// </summary>
+ internal static string ExceptionPasswordNull {
+ get {
+ return ResourceManager.GetString("ExceptionPasswordNull", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to User with given constraints already exists..
/// </summary>
internal static string ExceptionUserAlreadyExist {
@@ -169,6 +205,15 @@ namespace Timeline.Services.User { }
/// <summary>
+ /// Looks up a localized string similar to Username can't be null..
+ /// </summary>
+ internal static string ExceptionUsernameNull {
+ get {
+ return ResourceManager.GetString("ExceptionUsernameNull", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to Requested user does not exist..
/// </summary>
internal static string ExceptionUserNotExist {
@@ -176,5 +221,41 @@ namespace Timeline.Services.User { return ResourceManager.GetString("ExceptionUserNotExist", resourceCulture);
}
}
+
+ /// <summary>
+ /// Looks up a localized string similar to User with username = {0}, id ={1} changed password..
+ /// </summary>
+ internal static string LogChangePassowrd {
+ get {
+ return ResourceManager.GetString("LogChangePassowrd", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to A user is deleted with username = {0}, id = {1}..
+ /// </summary>
+ internal static string LogDeleteUser {
+ get {
+ return ResourceManager.GetString("LogDeleteUser", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to A new user is created with username = {0}, id = {1}..
+ /// </summary>
+ internal static string LogUserCreated {
+ get {
+ return ResourceManager.GetString("LogUserCreated", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to A user is modified with username = {0}, id = {1}..
+ /// </summary>
+ 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 @@ <data name="ExceptionBadPassword" xml:space="preserve">
<value>Password is wrong.</value>
</data>
+ <data name="ExceptionChangeRootUserPermission" xml:space="preserve">
+ <value>Can't change root user's permission.</value>
+ </data>
+ <data name="ExceptionDeleteRootUser" xml:space="preserve">
+ <value>Can't delete root user.</value>
+ </data>
<data name="ExceptionHashedPasswordBadFormat" xml:space="preserve">
<value>The hashes password is of bad format. It might not be created by server. Reason: {0}</value>
</data>
@@ -141,19 +147,40 @@ <data name="ExceptionHashedPasswordBadFormatReasonUnknownMarker" xml:space="preserve">
<value>Unknown format marker.</value>
</data>
+ <data name="ExceptionInvalidOperationOnRootUser" xml:space="preserve">
+ <value>Can't perform such operation on root user.</value>
+ </data>
<data name="ExceptionNicknameBadFormat" xml:space="preserve">
<value>Nickname is of bad format. {0}</value>
</data>
<data name="ExceptionPasswordEmpty" xml:space="preserve">
<value>Password can't be empty.</value>
</data>
+ <data name="ExceptionPasswordNull" xml:space="preserve">
+ <value>Password can't be null.</value>
+ </data>
<data name="ExceptionUserAlreadyExist" xml:space="preserve">
<value>User with given constraints already exists.</value>
</data>
<data name="ExceptionUsernameBadFormat" xml:space="preserve">
<value>Username is of bad format. {0}</value>
</data>
+ <data name="ExceptionUsernameNull" xml:space="preserve">
+ <value>Username can't be null.</value>
+ </data>
<data name="ExceptionUserNotExist" xml:space="preserve">
<value>Requested user does not exist.</value>
</data>
+ <data name="LogChangePassowrd" xml:space="preserve">
+ <value>User with username = {0}, id ={1} changed password.</value>
+ </data>
+ <data name="LogDeleteUser" xml:space="preserve">
+ <value>A user is deleted with username = {0}, id = {1}.</value>
+ </data>
+ <data name="LogUserCreated" xml:space="preserve">
+ <value>A new user is created with username = {0}, id = {1}.</value>
+ </data>
+ <data name="LogUserModified" xml:space="preserve">
+ <value>A user is modified with username = {0}, id = {1}.</value>
+ </data>
</root>
\ 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<ICacheableDataDigest> 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
- {
- /// <summary>
- /// Try to verify the given username and password.
- /// </summary>
- /// <param name="username">The username of the user to verify.</param>
- /// <param name="password">The password of the user to verify.</param>
- /// <returns>User id.</returns>
- /// <exception cref="ArgumentNullException">Thrown when <paramref name="username"/> or <paramref name="password"/> is null.</exception>
- /// <exception cref="ArgumentException">Thrown when <paramref name="username"/> is of bad format or <paramref name="password"/> is empty.</exception>
- /// <exception cref="UserNotExistException">Thrown when the user with given username does not exist.</exception>
- /// <exception cref="BadPasswordException">Thrown when password is wrong.</exception>
- Task<long> VerifyCredential(string username, string password);
-
- /// <summary>
- /// Try to change a user's password with old password.
- /// </summary>
- /// <param name="id">The id of user to change password of.</param>
- /// <param name="oldPassword">Old password.</param>
- /// <param name="newPassword">New password.</param>
- /// <exception cref="ArgumentNullException">Thrown if <paramref name="oldPassword"/> or <paramref name="newPassword"/> is null.</exception>
- /// <exception cref="ArgumentException">Thrown if <paramref name="oldPassword"/> or <paramref name="newPassword"/> is empty.</exception>
- /// <exception cref="UserNotExistException">Thrown if the user with given username does not exist.</exception>
- /// <exception cref="BadPasswordException">Thrown if the old password is wrong.</exception>
- Task ChangePassword(long id, string oldPassword, string newPassword);
- }
-
- public class UserCredentialService : IUserCredentialService
- {
- private readonly ILogger<UserCredentialService> _logger;
- private readonly DatabaseContext _database;
- private readonly IPasswordService _passwordService;
-
- private readonly UsernameValidator _usernameValidator = new UsernameValidator();
-
- public UserCredentialService(ILogger<UserCredentialService> logger, DatabaseContext database, IPasswordService passwordService)
- {
- _logger = logger;
- _database = database;
- _passwordService = passwordService;
- }
-
- public async Task<long> 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
- {
- /// <summary>
- /// Delete a user of given username.
- /// </summary>
- /// <param name="username">Username of the user to delete. Can't be null.</param>
- /// <returns>True if user is deleted, false if user not exist.</returns>
- /// <exception cref="ArgumentNullException">Thrown if <paramref name="username"/> is null.</exception>
- /// <exception cref="ArgumentException">Thrown when <paramref name="username"/> is of bad format.</exception>
- /// <exception cref="InvalidOperationOnRootUserException">Thrown when deleting root user.</exception>
- Task<bool> DeleteUser(string username);
- }
-
public class UserDeleteService : IUserDeleteService
{
private readonly ILogger<UserDeleteService> _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
+ {
+ /// <summary>
+ /// This permission allows to manage user (creating, deleting or modifying).
+ /// </summary>
+ UserManagement,
+ /// <summary>
+ /// This permission allows to view and modify all timelines.
+ /// </summary>
+ AllTimelineManagement,
+ /// <summary>
+ /// This permission allow to add or remove highlight timelines.
+ /// </summary>
+ 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
- {
- /// <summary>
- /// This permission allows to manage user (creating, deleting or modifying).
- /// </summary>
- UserManagement,
- /// <summary>
- /// This permission allows to view and modify all timelines.
- /// </summary>
- AllTimelineManagement,
- /// <summary>
- /// This permission allow to add or remove highlight timelines.
- /// </summary>
- HighlightTimelineManagement
- }
-
- /// <summary>
- /// Represents a user's permissions.
- /// </summary>
- public class UserPermissions : IEnumerable<UserPermission>, IEquatable<UserPermissions>
- {
- public static UserPermissions AllPermissions { get; } = new UserPermissions(Enum.GetValues<UserPermission>());
-
- /// <summary>
- /// Create an instance containing given permissions.
- /// </summary>
- /// <param name="permissions">Permission list.</param>
- public UserPermissions(params UserPermission[] permissions) : this(permissions as IEnumerable<UserPermission>)
- {
-
- }
-
- /// <summary>
- /// Create an instance containing given permissions.
- /// </summary>
- /// <param name="permissions">Permission list.</param>
- /// <exception cref="ArgumentNullException">Thrown when <paramref name="permissions"/> is null.</exception>
- public UserPermissions(IEnumerable<UserPermission> permissions)
- {
- if (permissions == null) throw new ArgumentNullException(nameof(permissions));
- _permissions = new SortedSet<UserPermission>(permissions);
- }
-
- private readonly SortedSet<UserPermission> _permissions = new();
-
- /// <summary>
- /// Check if a permission is contained in the list.
- /// </summary>
- /// <param name="permission">The permission to check.</param>
- /// <returns>True if contains. Otherwise false.</returns>
- public bool Contains(UserPermission permission)
- {
- return _permissions.Contains(permission);
- }
-
- /// <summary>
- /// To a serializable string list.
- /// </summary>
- /// <returns>A string list.</returns>
- public List<string> ToStringList()
- {
- return _permissions.Select(p => p.ToString()).ToList();
- }
-
- /// <summary>
- /// Convert a string list to user permissions.
- /// </summary>
- /// <param name="list">The string list.</param>
- /// <returns>An instance.</returns>
- /// <exception cref="ArgumentNullException">Thrown when <paramref name="list"/> is null.</exception>
- /// <exception cref="ArgumentException">Thrown when there is unknown permission name.</exception>
- public static UserPermissions FromStringList(IEnumerable<string> list)
- {
- List<UserPermission> permissions = new();
-
- foreach (var value in list)
- {
- if (Enum.TryParse<UserPermission>(value, false, out var result))
- {
- permissions.Add(result);
- }
- else
- {
- throw new ArgumentException("Unknown permission name.", nameof(list));
- }
- }
-
- return new UserPermissions(permissions);
- }
-
- public IEnumerator<UserPermission> 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<UserPermission>())
- {
- if (_permissions.Contains(permission))
- {
- result += 1;
- }
- result <<= 1;
- }
- return result;
- }
- }
-
- public interface IUserPermissionService
- {
- /// <summary>
- /// Get permissions of a user.
- /// </summary>
- /// <param name="userId">The id of the user.</param>
- /// <param name="checkUserExistence">Whether check the user's existence.</param>
- /// <returns>The permission list.</returns>
- /// <exception cref="UserNotExistException">Thrown when <paramref name="checkUserExistence"/> is true and user does not exist.</exception>
- Task<UserPermissions> GetPermissionsOfUserAsync(long userId, bool checkUserExistence = true);
-
- /// <summary>
- /// Add a permission to user.
- /// </summary>
- /// <param name="userId">The id of the user.</param>
- /// <param name="permission">The new permission.</param>
- /// <exception cref="UserNotExistException">Thrown when user does not exist.</exception>
- /// <exception cref="InvalidOperationOnRootUserException">Thrown when change root user's permission.</exception>
- Task AddPermissionToUserAsync(long userId, UserPermission permission);
-
- /// <summary>
- /// Remove a permission from user.
- /// </summary>
- /// <param name="userId">The id of the user.</param>
- /// <param name="permission">The permission.</param>
- /// <param name="checkUserExistence">Whether check the user's existence.</param>
- /// <exception cref="UserNotExistException">Thrown when <paramref name="checkUserExistence"/> is true and user does not exist.</exception>
- /// <exception cref="InvalidOperationOnRootUserException">Thrown when change root user's permission.</exception>
- 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
+{
+ /// <summary>
+ /// Represents a user's permissions.
+ /// </summary>
+ public class UserPermissions : IEnumerable<UserPermission>, IEquatable<UserPermissions>
+ {
+ public static UserPermissions AllPermissions { get; } = new UserPermissions(Enum.GetValues<UserPermission>());
+
+ /// <summary>
+ /// Create an instance containing given permissions.
+ /// </summary>
+ /// <param name="permissions">Permission list.</param>
+ public UserPermissions(params UserPermission[] permissions) : this(permissions as IEnumerable<UserPermission>)
+ {
+
+ }
+
+ /// <summary>
+ /// Create an instance containing given permissions.
+ /// </summary>
+ /// <param name="permissions">Permission list.</param>
+ /// <exception cref="ArgumentNullException">Thrown when <paramref name="permissions"/> is null.</exception>
+ public UserPermissions(IEnumerable<UserPermission> permissions)
+ {
+ if (permissions == null) throw new ArgumentNullException(nameof(permissions));
+ _permissions = new SortedSet<UserPermission>(permissions);
+ }
+
+ private readonly SortedSet<UserPermission> _permissions = new();
+
+ /// <summary>
+ /// Check if a permission is contained in the list.
+ /// </summary>
+ /// <param name="permission">The permission to check.</param>
+ /// <returns>True if contains. Otherwise false.</returns>
+ public bool Contains(UserPermission permission)
+ {
+ return _permissions.Contains(permission);
+ }
+
+ /// <summary>
+ /// To a serializable string list.
+ /// </summary>
+ /// <returns>A string list.</returns>
+ public List<string> ToStringList()
+ {
+ return _permissions.Select(p => p.ToString()).ToList();
+ }
+
+ /// <summary>
+ /// Convert a string list to user permissions.
+ /// </summary>
+ /// <param name="list">The string list.</param>
+ /// <returns>An instance.</returns>
+ /// <exception cref="ArgumentNullException">Thrown when <paramref name="list"/> is null.</exception>
+ /// <exception cref="ArgumentException">Thrown when there is unknown permission name.</exception>
+ public static UserPermissions FromStringList(IEnumerable<string> list)
+ {
+ List<UserPermission> permissions = new();
+
+ foreach (var value in list)
+ {
+ if (Enum.TryParse<UserPermission>(value, false, out var result))
+ {
+ permissions.Add(result);
+ }
+ else
+ {
+ throw new ArgumentException("Unknown permission name.", nameof(list));
+ }
+ }
+
+ return new UserPermissions(permissions);
+ }
+
+ public IEnumerator<UserPermission> 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<UserPermission>())
+ {
+ 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
{
- /// <summary>
- /// Null means not change.
- /// </summary>
- public class ModifyUserParams
- {
- public string? Username { get; set; }
- public string? Password { get; set; }
- public string? Nickname { get; set; }
- }
-
- public interface IUserService : IBasicUserService
- {
- /// <summary>
- /// Try to get a user by id.
- /// </summary>
- /// <param name="id">The id of the user.</param>
- /// <returns>The user info.</returns>
- /// <exception cref="UserNotExistException">Thrown when the user with given id does not exist.</exception>
- Task<UserEntity> GetUser(long id);
-
- /// <summary>
- /// List all users.
- /// </summary>
- /// <returns>The user info of users.</returns>
- Task<List<UserEntity>> GetUsers();
-
- /// <summary>
- /// Create a user with given info.
- /// </summary>
- /// <param name="username">The username of new user.</param>
- /// <param name="password">The password of new user.</param>
- /// <returns>The the new user.</returns>
- /// <exception cref="ArgumentNullException">Thrown when <paramref name="username"/> or <paramref name="password"/> is null.</exception>
- /// <exception cref="ArgumentException">Thrown when <paramref name="username"/> or <paramref name="password"/> is of bad format.</exception>
- /// <exception cref="EntityAlreadyExistException">Thrown when a user with given username already exists.</exception>
- Task<UserEntity> CreateUser(string username, string password);
-
- /// <summary>
- /// Modify a user.
- /// </summary>
- /// <param name="id">The id of the user.</param>
- /// <param name="param">The new information.</param>
- /// <returns>The new user info.</returns>
- /// <exception cref="ArgumentException">Thrown when some fields in <paramref name="param"/> is bad.</exception>
- /// <exception cref="UserNotExistException">Thrown when user with given id does not exist.</exception>
- /// <remarks>
- /// Version will increase if password is changed.
- /// </remarks>
- Task<UserEntity> ModifyUser(long id, ModifyUserParams? param);
- }
-
public class UserService : BasicUserService, IUserService
{
private readonly ILogger<UserService> _logger;
@@ -110,58 +59,63 @@ namespace Timeline.Services.User throw new UserAlreadyExistException(user);
}
- public async Task<UserEntity> GetUser(long id)
+ public async Task<UserEntity> 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<List<UserEntity>> GetUsers()
+ public async Task<List<UserEntity>> GetUsersAsync()
{
return await _databaseContext.Users.ToListAsync();
}
- public async Task<UserEntity> CreateUser(string username, string password)
+ public async Task<UserEntity> 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<UserEntity> ModifyUser(long id, ModifyUserParams? param)
+ public async Task<UserEntity> 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<long> 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);
+ }
}
}
|