aboutsummaryrefslogtreecommitdiff
path: root/Timeline/Services
diff options
context:
space:
mode:
Diffstat (limited to 'Timeline/Services')
-rw-r--r--Timeline/Services/ConfictException.cs21
-rw-r--r--Timeline/Services/UserService.cs263
-rw-r--r--Timeline/Services/UserTokenException.cs3
-rw-r--r--Timeline/Services/UserTokenManager.cs4
-rw-r--r--Timeline/Services/UsernameConfictException.cs25
5 files changed, 183 insertions, 133 deletions
diff --git a/Timeline/Services/ConfictException.cs b/Timeline/Services/ConfictException.cs
new file mode 100644
index 00000000..dcd77366
--- /dev/null
+++ b/Timeline/Services/ConfictException.cs
@@ -0,0 +1,21 @@
+using System;
+
+namespace Timeline.Services
+{
+ /// <summary>
+ /// Thrown when a resource already exists and conflicts with the given resource.
+ /// </summary>
+ /// <remarks>
+ /// For example a username already exists and conflicts with the given username.
+ /// </remarks>
+ [Serializable]
+ public class ConfictException : Exception
+ {
+ public ConfictException() : base(Resources.Services.Exception.ConfictException) { }
+ public ConfictException(string message) : base(message) { }
+ public ConfictException(string message, Exception inner) : base(message, inner) { }
+ protected ConfictException(
+ System.Runtime.Serialization.SerializationInfo info,
+ System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
+ }
+}
diff --git a/Timeline/Services/UserService.cs b/Timeline/Services/UserService.cs
index c5595c99..616e70ba 100644
--- a/Timeline/Services/UserService.cs
+++ b/Timeline/Services/UserService.cs
@@ -21,7 +21,7 @@ namespace Timeline.Services
/// <param name="password">The password of the user to verify.</param>
/// <returns>The user info and auth info.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="username"/> or <paramref name="password"/> is null.</exception>
- /// <exception cref="ArgumentException">Thrown when username is of bad format.</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<User> VerifyCredential(string username, string password);
@@ -48,7 +48,7 @@ namespace Timeline.Services
/// List all users.
/// </summary>
/// <returns>The user info of users.</returns>
- Task<User[]> ListUsers();
+ Task<User[]> GetUsers();
/// <summary>
/// Create a user with given info.
@@ -58,11 +58,12 @@ namespace Timeline.Services
/// <returns>The id of the new user.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="info"/>is null.</exception>
/// <exception cref="ArgumentException">Thrown when some fields in <paramref name="info"/> is bad.</exception>
- /// <exception cref="UsernameConfictException">Thrown when a user with given username already exists.</exception>
+ /// <exception cref="ConfictException">Thrown when a user with given username already exists.</exception>
/// <remarks>
/// <see cref="User.Username"/> must not be null and must be a valid username.
/// <see cref="User.Password"/> must not be null or empty.
/// <see cref="User.Administrator"/> is false by default (null).
+ /// <see cref="User.Nickname"/> must be a valid nickname if set. It is empty by default.
/// Other fields are ignored.
/// </remarks>
Task<long> CreateUser(User info);
@@ -75,61 +76,70 @@ namespace Timeline.Services
/// <exception cref="ArgumentException">Thrown when some fields in <paramref name="info"/> is bad.</exception>
/// <exception cref="UserNotExistException">Thrown when user with given id does not exist.</exception>
/// <remarks>
- /// Only <see cref="User.Administrator"/>, <see cref="User.Password"/> and <see cref="User.Nickname"/> will be used.
+ /// Only <see cref="User.Username"/>, <see cref="User.Administrator"/>, <see cref="User.Password"/> and <see cref="User.Nickname"/> will be used.
/// If null, then not change.
/// Other fields are ignored.
/// After modified, even if nothing is changed, version will increase.
///
- /// <see cref="User.Password"/> can't be empty.
+ /// <see cref="User.Username"/> must be a valid username if set.
+ /// <see cref="User.Password"/> can't be empty if set.
+ /// <see cref="User.Nickname"/> must be a valid nickname if set.
///
/// Note: Whether <see cref="User.Version"/> is set or not, version will increase and not set to the specified value if there is one.
/// </remarks>
+ /// <seealso cref="ModifyUser(string, User)"/>
Task ModifyUser(long id, User? info);
/// <summary>
- /// Partially modify a user of given username.
+ /// Modify a user's info.
+ /// </summary>
+ /// <param name="username">The username of the user.</param>
+ /// <param name="info">The new info. May be null.</param>
+ /// <exception cref="ArgumentNullException">Thrown when <paramref name="username"/> is null.</exception>
+ /// <exception cref="ArgumentException">Thrown when <paramref name="username"/> is of bad format or some fields in <paramref name="info"/> is bad.</exception>
+ /// <exception cref="UserNotExistException">Thrown when user with given id does not exist.</exception>
+ /// <remarks>
+ /// Only <see cref="User.Administrator"/>, <see cref="User.Password"/> and <see cref="User.Nickname"/> will be used.
+ /// If null, then not change.
+ /// Other fields are ignored.
+ /// After modified, even if nothing is changed, version will increase.
+ ///
+ /// <see cref="User.Username"/> must be a valid username if set.
+ /// <see cref="User.Password"/> can't be empty if set.
+ /// <see cref="User.Nickname"/> must be a valid nickname if set.
///
- /// Note that whether actually modified or not, Version of the user will always increase.
+ /// Note: Whether <see cref="User.Version"/> is set or not, version will increase and not set to the specified value if there is one.
+ /// </remarks>
+ /// <seealso cref="ModifyUser(long, User)"/>
+ Task ModifyUser(string username, User? info);
+
+ /// <summary>
+ /// Delete a user of given id.
/// </summary>
- /// <param name="username">Username of the user to modify. Can't be null.</param>
- /// <param name="password">New password. Null if not modify.</param>
- /// <param name="administrator">Whether the user is administrator. Null if not modify.</param>
- /// <exception cref="ArgumentNullException">Thrown if <paramref name="username"/> is null.</exception>
- /// <exception cref="UsernameBadFormatException">Thrown when <paramref name="username"/> is of bad format.</exception>
- /// <exception cref="UserNotExistException">Thrown if the user with given username does not exist.</exception>
- Task PatchUser(string username, string? password, bool? administrator);
+ /// <param name="id">Id of the user to delete.</param>
+ /// <returns>True if user is deleted, false if user not exist.</returns>
+ Task<bool> DeleteUser(long id);
/// <summary>
/// Delete a user of given username.
/// </summary>
- /// <param name="username">Username of thet user to delete. Can't be null.</param>
+ /// <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="UsernameBadFormatException">Thrown when <paramref name="username"/> is of bad format.</exception>
- /// <exception cref="UserNotExistException">Thrown if the user with given username does not exist.</exception>
- Task DeleteUser(string username);
+ /// <exception cref="ArgumentException">Thrown when <paramref name="username"/> is of bad format.</exception>
+ Task<bool> DeleteUser(string username);
/// <summary>
/// Try to change a user's password with old password.
/// </summary>
- /// <param name="username">The name of user to change password of.</param>
- /// <param name="oldPassword">The user's old password.</param>
- /// <param name="newPassword">The user's new password.</param>
- /// <exception cref="ArgumentNullException">Thrown if <paramref name="username"/> or <paramref name="oldPassword"/> or <paramref name="newPassword"/> is null.</exception>
- /// <exception cref="UsernameBadFormatException">Thrown when <paramref name="username"/> is of bad format.</exception>
+ /// <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(string username, string oldPassword, string newPassword);
-
- /// <summary>
- /// Change a user's username.
- /// </summary>
- /// <param name="oldUsername">The user's old username.</param>
- /// <param name="newUsername">The new username.</param>
- /// <exception cref="ArgumentNullException">Thrown if <paramref name="oldUsername"/> or <paramref name="newUsername"/> is null.</exception>
- /// <exception cref="UserNotExistException">Thrown if the user with old username does not exist.</exception>
- /// <exception cref="UsernameBadFormatException">Thrown if the <paramref name="oldUsername"/> or <paramref name="newUsername"/> is of bad format.</exception>
- /// <exception cref="UsernameConfictException">Thrown if user with the new username already exists.</exception>
- Task ChangeUsername(string oldUsername, string newUsername);
+ Task ChangePassword(long id, string oldPassword, string newPassword);
}
public class UserService : IUserService
@@ -138,11 +148,10 @@ namespace Timeline.Services
private readonly DatabaseContext _databaseContext;
-
private readonly IPasswordService _passwordService;
private readonly UsernameValidator _usernameValidator = new UsernameValidator();
-
+ private readonly NicknameValidator _nicknameValidator = new NicknameValidator();
public UserService(ILogger<UserService> logger, DatabaseContext databaseContext, IPasswordService passwordService)
{
_logger = logger;
@@ -150,17 +159,35 @@ namespace Timeline.Services
_passwordService = passwordService;
}
- private void CheckUsernameFormat(string username, string? paramName, Func<string, string>? messageBuilder = null)
+ private void CheckUsernameFormat(string username, string? paramName)
{
if (!_usernameValidator.Validate(username, out var message))
{
- if (messageBuilder == null)
- throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, ExceptionUsernameBadFormat, message), paramName);
- else
- throw new ArgumentException(messageBuilder(message), paramName);
+ throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, ExceptionUsernameBadFormat, message), paramName);
+ }
+ }
+
+ private static void CheckPasswordFormat(string password, string? paramName)
+ {
+ if (password.Length == 0)
+ {
+ throw new ArgumentException(ExceptionPasswordEmpty, paramName);
}
}
+ private void CheckNicknameFormat(string nickname, string? paramName)
+ {
+ if (!_nicknameValidator.Validate(nickname, out var message))
+ {
+ throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, ExceptionNicknameBadFormat, message), paramName);
+ }
+ }
+
+ private static void ThrowUsernameConflict()
+ {
+ throw new ConfictException(ExceptionUsernameConflict);
+ }
+
private static User CreateUserFromEntity(UserEntity entity)
{
return new User
@@ -168,6 +195,7 @@ namespace Timeline.Services
Username = entity.Username,
Administrator = UserRoleConvert.ToBool(entity.Roles),
Nickname = string.IsNullOrEmpty(entity.Nickname) ? entity.Username : entity.Nickname,
+ Id = entity.Id,
Version = entity.Version
};
}
@@ -180,6 +208,7 @@ namespace Timeline.Services
throw new ArgumentNullException(nameof(password));
CheckUsernameFormat(username, nameof(username));
+ CheckPasswordFormat(password, nameof(password));
var entity = await _databaseContext.Users.Where(u => u.Username == username).SingleOrDefaultAsync();
@@ -217,7 +246,7 @@ namespace Timeline.Services
return CreateUserFromEntity(entity);
}
- public async Task<User[]> ListUsers()
+ public async Task<User[]> GetUsers()
{
var entities = await _databaseContext.Users.ToArrayAsync();
return entities.Select(user => CreateUserFromEntity(user)).ToArray();
@@ -228,20 +257,22 @@ namespace Timeline.Services
if (info == null)
throw new ArgumentNullException(nameof(info));
- if (string.IsNullOrEmpty(info.Username))
- throw new ArgumentException(ExceptionUsernameNullOrEmpty, nameof(info));
-
+ if (info.Username == null)
+ throw new ArgumentException(ExceptionUsernameNull, nameof(info));
CheckUsernameFormat(info.Username, nameof(info));
- if (string.IsNullOrEmpty(info.Password))
- throw new ArgumentException(ExceptionPasswordNullOrEmpty);
+ if (info.Password == null)
+ throw new ArgumentException(ExceptionPasswordNull, nameof(info));
+ CheckPasswordFormat(info.Password, nameof(info));
+
+ if (info.Nickname != null)
+ CheckNicknameFormat(info.Nickname, nameof(info));
var username = info.Username;
var conflict = await _databaseContext.Users.AnyAsync(u => u.Username == username);
-
if (conflict)
- throw new UsernameConfictException(username);
+ ThrowUsernameConflict();
var administrator = info.Administrator ?? false;
var password = info.Password;
@@ -262,17 +293,35 @@ namespace Timeline.Services
return newEntity.Id;
}
- public async Task ModifyUser(long id, User? info)
+ private void ValidateModifyUserInfo(User? info)
{
- if (info != null && info.Password != null && info.Password.Length == 0)
- throw new ArgumentException(ExceptionPasswordEmpty, nameof(info));
+ if (info != null)
+ {
+ if (info.Username != null)
+ CheckUsernameFormat(info.Username, nameof(info));
- var entity = await _databaseContext.Users.Where(u => u.Id == id).SingleOrDefaultAsync();
- if (entity == null)
- throw new UserNotExistException(id);
+ if (info.Password != null)
+ CheckPasswordFormat(info.Password, nameof(info));
+
+ if (info.Nickname != null)
+ CheckNicknameFormat(info.Nickname, nameof(info));
+ }
+ }
+ private async Task UpdateUserEntity(UserEntity entity, User? info)
+ {
if (info != null)
{
+ var username = info.Username;
+ if (username != null)
+ {
+ var conflict = await _databaseContext.Users.AnyAsync(u => u.Username == username);
+ if (conflict)
+ ThrowUsernameConflict();
+
+ entity.Username = username;
+ }
+
var password = info.Password;
if (password != null)
{
@@ -293,82 +342,90 @@ namespace Timeline.Services
}
entity.Version += 1;
+ }
+
+
+ public async Task ModifyUser(long id, User? info)
+ {
+ ValidateModifyUserInfo(info);
+
+ var entity = await _databaseContext.Users.Where(u => u.Id == id).SingleOrDefaultAsync();
+ if (entity == null)
+ throw new UserNotExistException(id);
+
+ await UpdateUserEntity(entity, info);
await _databaseContext.SaveChangesAsync();
_logger.LogInformation(LogDatabaseUpdate, ("Id", id));
}
- public async Task DeleteUser(string username)
+ public async Task ModifyUser(string username, User? info)
{
if (username == null)
throw new ArgumentNullException(nameof(username));
- CheckUsernameFormat(username);
+ CheckUsernameFormat(username, nameof(username));
- var user = await _databaseContext.Users.Where(u => u.Username == username).SingleOrDefaultAsync();
- if (user == null)
+ ValidateModifyUserInfo(info);
+
+ var entity = await _databaseContext.Users.Where(u => u.Username == username).SingleOrDefaultAsync();
+ if (entity == null)
throw new UserNotExistException(username);
- _databaseContext.Users.Remove(user);
+ await UpdateUserEntity(entity, info);
+
await _databaseContext.SaveChangesAsync();
- _logger.LogInformation(Log.Format(Resources.Services.UserService.LogDatabaseRemove,
- ("Id", user.Id)));
+ _logger.LogInformation(LogDatabaseUpdate, ("Username", username));
+ }
+
+ public async Task<bool> DeleteUser(long id)
+ {
+ var user = await _databaseContext.Users.Where(u => u.Id == id).SingleOrDefaultAsync();
+ if (user == null)
+ return false;
- //clear cache
- await _cache.RemoveCache(user.Id);
+ _databaseContext.Users.Remove(user);
+ await _databaseContext.SaveChangesAsync();
+ _logger.LogInformation(Log.Format(LogDatabaseRemove, ("Id", id), ("Username", user.Username)));
+ return true;
}
- public async Task ChangePassword(string username, string oldPassword, string newPassword)
+ public async Task<bool> DeleteUser(string username)
{
if (username == null)
throw new ArgumentNullException(nameof(username));
- if (oldPassword == null)
- throw new ArgumentNullException(nameof(oldPassword));
- if (newPassword == null)
- throw new ArgumentNullException(nameof(newPassword));
- CheckUsernameFormat(username);
+ CheckUsernameFormat(username, nameof(username));
var user = await _databaseContext.Users.Where(u => u.Username == username).SingleOrDefaultAsync();
if (user == null)
- throw new UserNotExistException(username);
-
- var verifyResult = _passwordService.VerifyPassword(user.Password, oldPassword);
- if (!verifyResult)
- throw new BadPasswordException(oldPassword);
+ return false;
- user.Password = _passwordService.HashPassword(newPassword);
- user.Version += 1;
+ _databaseContext.Users.Remove(user);
await _databaseContext.SaveChangesAsync();
- _logger.LogInformation(Log.Format(Resources.Services.UserService.LogDatabaseUpdate,
- ("Id", user.Id), ("Operation", "Change password")));
- //clear cache
- await _cache.RemoveCache(user.Id);
+ _logger.LogInformation(Log.Format(LogDatabaseRemove, ("Id", user.Id), ("Username", username)));
+ return true;
}
- public async Task ChangeUsername(string oldUsername, string newUsername)
+ public async Task ChangePassword(long id, string oldPassword, string newPassword)
{
- if (oldUsername == null)
- throw new ArgumentNullException(nameof(oldUsername));
- if (newUsername == null)
- throw new ArgumentNullException(nameof(newUsername));
- CheckUsernameFormat(oldUsername, Resources.Services.UserService.ExceptionOldUsernameBadFormat);
- CheckUsernameFormat(newUsername, Resources.Services.UserService.ExceptionNewUsernameBadFormat);
-
- var user = await _databaseContext.Users.Where(u => u.Username == oldUsername).SingleOrDefaultAsync();
- if (user == null)
- throw new UserNotExistException(oldUsername);
+ 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 conflictUser = await _databaseContext.Users.Where(u => u.Username == newUsername).SingleOrDefaultAsync();
- if (conflictUser != null)
- throw new UsernameConfictException(newUsername);
+ var entity = await _databaseContext.Users.Where(u => u.Id == id).SingleOrDefaultAsync();
- user.Username = newUsername;
- user.Version += 1;
- await _databaseContext.SaveChangesAsync();
- _logger.LogInformation(Log.Format(Resources.Services.UserService.LogDatabaseUpdate,
- ("Id", user.Id), ("Old Username", oldUsername), ("New Username", newUsername)));
- await _cache.RemoveCache(user.Id);
- }
+ 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 _databaseContext.SaveChangesAsync();
+ _logger.LogInformation(Log.Format(LogDatabaseUpdate, ("Id", id), ("Operation", "Change password")));
+ }
}
}
diff --git a/Timeline/Services/UserTokenException.cs b/Timeline/Services/UserTokenException.cs
index e63305b1..ed0bae1a 100644
--- a/Timeline/Services/UserTokenException.cs
+++ b/Timeline/Services/UserTokenException.cs
@@ -1,7 +1,4 @@
using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading.Tasks;
namespace Timeline.Services
{
diff --git a/Timeline/Services/UserTokenManager.cs b/Timeline/Services/UserTokenManager.cs
index a2c2980d..3e9ef3d4 100644
--- a/Timeline/Services/UserTokenManager.cs
+++ b/Timeline/Services/UserTokenManager.cs
@@ -62,7 +62,7 @@ namespace Timeline.Services
throw new ArgumentNullException(nameof(password));
var user = await _userService.VerifyCredential(username, password);
- var token = _userTokenService.GenerateToken(new UserTokenInfo { Id = user.Id, Version = user.Version, ExpireAt = expireAt });
+ var token = _userTokenService.GenerateToken(new UserTokenInfo { Id = user.Id!.Value, Version = user.Version!.Value, ExpireAt = expireAt });
return new UserTokenCreateResult { Token = token, User = user };
}
@@ -85,7 +85,7 @@ namespace Timeline.Services
var user = await _userService.GetUserById(tokenInfo.Id);
if (tokenInfo.Version < user.Version)
- throw new UserTokenBadVersionException(token, tokenInfo.Version, user.Version);
+ throw new UserTokenBadVersionException(token, tokenInfo.Version, user.Version.Value);
return user;
}
diff --git a/Timeline/Services/UsernameConfictException.cs b/Timeline/Services/UsernameConfictException.cs
deleted file mode 100644
index fde1eda6..00000000
--- a/Timeline/Services/UsernameConfictException.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-using System;
-using Timeline.Helpers;
-
-namespace Timeline.Services
-{
- /// <summary>
- /// Thrown when the user already exists.
- /// </summary>
- [Serializable]
- public class UsernameConfictException : Exception
- {
- public UsernameConfictException() : base(Resources.Services.Exception.UsernameConfictException) { }
- public UsernameConfictException(string username) : base(Log.Format(Resources.Services.Exception.UsernameConfictException, ("Username", username))) { Username = username; }
- public UsernameConfictException(string username, string message) : base(message) { Username = username; }
- public UsernameConfictException(string message, Exception inner) : base(message, inner) { }
- protected UsernameConfictException(
- System.Runtime.Serialization.SerializationInfo info,
- System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
-
- /// <summary>
- /// The username that already exists.
- /// </summary>
- public string? Username { get; set; }
- }
-}