aboutsummaryrefslogtreecommitdiff
path: root/BackEnd/Timeline/Services/User
diff options
context:
space:
mode:
Diffstat (limited to 'BackEnd/Timeline/Services/User')
-rw-r--r--BackEnd/Timeline/Services/User/BasicUserService.cs39
-rw-r--r--BackEnd/Timeline/Services/User/CreateUserParams.cs17
-rw-r--r--BackEnd/Timeline/Services/User/IBasicUserService.cs36
-rw-r--r--BackEnd/Timeline/Services/User/IUserDeleteService.cs18
-rw-r--r--BackEnd/Timeline/Services/User/IUserPermissionService.cs35
-rw-r--r--BackEnd/Timeline/Services/User/IUserService.cs71
-rw-r--r--BackEnd/Timeline/Services/User/InvalidOperationOnRootUserException.cs2
-rw-r--r--BackEnd/Timeline/Services/User/ModifyUserParams.cs12
-rw-r--r--BackEnd/Timeline/Services/User/PasswordService.cs2
-rw-r--r--BackEnd/Timeline/Services/User/Resource.Designer.cs81
-rw-r--r--BackEnd/Timeline/Services/User/Resource.resx27
-rw-r--r--BackEnd/Timeline/Services/User/UserAvatarService.cs2
-rw-r--r--BackEnd/Timeline/Services/User/UserCredentialService.cs101
-rw-r--r--BackEnd/Timeline/Services/User/UserDeleteService.cs16
-rw-r--r--BackEnd/Timeline/Services/User/UserPermission.cs18
-rw-r--r--BackEnd/Timeline/Services/User/UserPermissionService.cs166
-rw-r--r--BackEnd/Timeline/Services/User/UserPermissions.cs119
-rw-r--r--BackEnd/Timeline/Services/User/UserService.cs148
18 files changed, 518 insertions, 392 deletions
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&apos;t change root user&apos;s permission..
+ /// </summary>
+ internal static string ExceptionChangeRootUserPermission {
+ get {
+ return ResourceManager.GetString("ExceptionChangeRootUserPermission", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Can&apos;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&apos;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&apos;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&apos;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);
+ }
}
}