aboutsummaryrefslogtreecommitdiff
path: root/Timeline/Services/UserService.cs
diff options
context:
space:
mode:
Diffstat (limited to 'Timeline/Services/UserService.cs')
-rw-r--r--Timeline/Services/UserService.cs245
1 files changed, 74 insertions, 171 deletions
diff --git a/Timeline/Services/UserService.cs b/Timeline/Services/UserService.cs
index 347b8cbb..8f354fc7 100644
--- a/Timeline/Services/UserService.cs
+++ b/Timeline/Services/UserService.cs
@@ -5,140 +5,16 @@ using System;
using System.Linq;
using System.Threading.Tasks;
using Timeline.Entities;
+using Timeline.Helpers;
using Timeline.Models;
using Timeline.Models.Validation;
-using static Timeline.Helpers.MyLogHelper;
-using static Timeline.Models.UserUtility;
namespace Timeline.Services
{
public class CreateTokenResult
{
- public string Token { get; set; }
- public UserInfo User { get; set; }
- }
-
- [Serializable]
- public class UserNotExistException : Exception
- {
- private const string message = "The user does not exist.";
-
- public UserNotExistException(string username)
- : base(FormatLogMessage(message, Pair("Username", username)))
- {
- Username = username;
- }
-
- public UserNotExistException(long id)
- : base(FormatLogMessage(message, Pair("Id", id)))
- {
- Id = id;
- }
-
- public UserNotExistException(string message, Exception inner) : base(message, inner) { }
-
- protected UserNotExistException(
- System.Runtime.Serialization.SerializationInfo info,
- System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
-
- /// <summary>
- /// The username that does not exist. May be null then <see cref="Id"/> is not null.
- /// </summary>
- public string Username { get; private set; }
-
- /// <summary>
- /// The id that does not exist. May be null then <see cref="Username"/> is not null.
- /// </summary>
- public long? Id { get; private set; }
- }
-
- [Serializable]
- public class BadPasswordException : Exception
- {
- public BadPasswordException(string badPassword)
- : base(FormatLogMessage("Password is wrong.", Pair("Bad Password", badPassword)))
- {
- Password = badPassword;
- }
-
- public BadPasswordException(string message, Exception inner) : base(message, inner) { }
-
- protected BadPasswordException(
- System.Runtime.Serialization.SerializationInfo info,
- System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
-
- /// <summary>
- /// The wrong password.
- /// </summary>
- public string Password { get; private set; }
- }
-
-
- [Serializable]
- public class BadTokenVersionException : Exception
- {
- public BadTokenVersionException(long tokenVersion, long requiredVersion)
- : base(FormatLogMessage("Token version is expired.",
- Pair("Token Version", tokenVersion),
- Pair("Required Version", requiredVersion)))
- {
- TokenVersion = tokenVersion;
- RequiredVersion = requiredVersion;
- }
-
- public BadTokenVersionException(string message, Exception inner) : base(message, inner) { }
-
- protected BadTokenVersionException(
- System.Runtime.Serialization.SerializationInfo info,
- System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
-
- /// <summary>
- /// The version in the token.
- /// </summary>
- public long TokenVersion { get; private set; }
-
- /// <summary>
- /// The version required.
- /// </summary>
- public long RequiredVersion { get; private set; }
- }
-
- /// <summary>
- /// Thrown when username is of bad format.
- /// </summary>
- [Serializable]
- public class UsernameBadFormatException : Exception
- {
- public UsernameBadFormatException(string username, string message) : base(message) { Username = username; }
- public UsernameBadFormatException(string username, string message, Exception inner) : base(message, inner) { Username = username; }
- protected UsernameBadFormatException(
- System.Runtime.Serialization.SerializationInfo info,
- System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
-
- /// <summary>
- /// Username of bad format.
- /// </summary>
- public string Username { get; private set; }
- }
-
-
- /// <summary>
- /// Thrown when the user already exists.
- /// </summary>
- [Serializable]
- public class UserAlreadyExistException : Exception
- {
- public UserAlreadyExistException(string username) : base($"User {username} already exists.") { Username = username; }
- public UserAlreadyExistException(string username, string message) : base(message) { Username = username; }
- public UserAlreadyExistException(string message, Exception inner) : base(message, inner) { }
- protected UserAlreadyExistException(
- 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; }
+ public string Token { get; set; } = default!;
+ public UserInfo User { get; set; } = default!;
}
public interface IUserService
@@ -152,6 +28,7 @@ namespace Timeline.Services
/// <param name="expires">The expired time point. Null then use default. See <see cref="JwtService.GenerateJwtToken(TokenInfo, DateTime?)"/> for what is default.</param>
/// <returns>An <see cref="CreateTokenResult"/> containing the created token and user info.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="username"/> or <paramref name="password"/> is null.</exception>
+ /// <exception cref="UsernameBadFormatException">Thrown when username is of bad format.</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<CreateTokenResult> CreateToken(string username, string password, DateTime? expires = null);
@@ -163,9 +40,8 @@ namespace Timeline.Services
/// <param name="token">The token to verify.</param>
/// <returns>The user info specified by the token.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="token"/> is null.</exception>
- /// <exception cref="JwtTokenVerifyException">Thrown when the token is of bad format. Thrown by <see cref="JwtService.VerifyJwtToken(string)"/>.</exception>
+ /// <exception cref="JwtVerifyException">Thrown when the token is of bad format. Thrown by <see cref="JwtService.VerifyJwtToken(string)"/>.</exception>
/// <exception cref="UserNotExistException">Thrown when the user specified by the token does not exist. Usually it has been deleted after the token was issued.</exception>
- /// <exception cref="BadTokenVersionException">Thrown when the version in the token is expired. User needs to recreate the token.</exception>
Task<UserInfo> VerifyToken(string token);
/// <summary>
@@ -173,6 +49,8 @@ namespace Timeline.Services
/// </summary>
/// <param name="username">Username of the user.</param>
/// <returns>The info of the user. Null if the user of given username does not exists.</returns>
+ /// <exception cref="ArgumentNullException">Thrown when <paramref name="username"/> is null.</exception>
+ /// <exception cref="UsernameBadFormatException">Thrown when <paramref name="username"/> is of bad format.</exception>
Task<UserInfo> GetUser(string username);
/// <summary>
@@ -188,10 +66,12 @@ namespace Timeline.Services
/// <param name="username">Username of user.</param>
/// <param name="password">Password of user.</param>
/// <param name="administrator">Whether the user is administrator.</param>
- /// <returns>Return <see cref="PutResult.Created"/> if a new user is created.
- /// Return <see cref="PutResult.Modified"/> if a existing user is modified.</returns>
- /// <exception cref="UsernameBadFormatException">Thrown when <paramref name="username"/> is of bad format.</exception>
+ /// <returns>
+ /// Return <see cref="PutResult.Create"/> if a new user is created.
+ /// Return <see cref="PutResult.Modify"/> if a existing user is modified.
+ /// </returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="username"/> or <paramref name="password"/> is null.</exception>
+ /// <exception cref="UsernameBadFormatException">Thrown when <paramref name="username"/> is of bad format.</exception>
Task<PutResult> PutUser(string username, string password, bool administrator);
/// <summary>
@@ -203,14 +83,16 @@ namespace Timeline.Services
/// <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);
+ Task PatchUser(string username, string? password, bool? administrator);
/// <summary>
/// Delete a user of given username.
/// </summary>
/// <param name="username">Username of thet user to delete. Can't be null.</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 DeleteUser(string username);
@@ -221,6 +103,7 @@ namespace Timeline.Services
/// <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>
/// <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);
@@ -230,16 +113,16 @@ namespace Timeline.Services
/// </summary>
/// <param name="oldUsername">The user's old username.</param>
/// <param name="newUsername">The new username.</param>
- /// <exception cref="ArgumentException">Thrown if <paramref name="oldUsername"/> or <paramref name="newUsername"/> is null or empty.</exception>
+ /// <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 new username is not accepted because of bad format.</exception>
- /// <exception cref="UserAlreadyExistException">Thrown if user with the new username already exists.</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);
}
internal class UserCache
{
- public string Username { get; set; }
+ public string Username { get; set; } = default!;
public bool Administrator { get; set; }
public long Version { get; set; }
@@ -272,13 +155,25 @@ namespace Timeline.Services
_usernameValidator = new UsernameValidator();
}
- private string GenerateCacheKeyByUserId(long id) => $"user:{id}";
+ private static string GenerateCacheKeyByUserId(long id) => $"user:{id}";
private void RemoveCache(long id)
{
var key = GenerateCacheKeyByUserId(id);
_memoryCache.Remove(key);
- _logger.LogInformation(FormatLogMessage("A cache entry is removed.", Pair("Key", key)));
+ _logger.LogInformation(Log.Format(Resources.Services.UserService.LogCacheRemove, ("Key", key)));
+ }
+
+ private void CheckUsernameFormat(string username, string? message = null)
+ {
+ var (result, messageGenerator) = _usernameValidator.Validate(username);
+ if (!result)
+ {
+ if (message == null)
+ throw new UsernameBadFormatException(username, messageGenerator(null));
+ else
+ throw new UsernameBadFormatException(username, message + messageGenerator(null));
+ }
}
public async Task<CreateTokenResult> CreateToken(string username, string password, DateTime? expires)
@@ -287,6 +182,7 @@ namespace Timeline.Services
throw new ArgumentNullException(nameof(username));
if (password == null)
throw new ArgumentNullException(nameof(password));
+ CheckUsernameFormat(username);
// We need password info, so always check the database.
var user = await _databaseContext.Users.Where(u => u.Name == username).SingleOrDefaultAsync();
@@ -306,7 +202,7 @@ namespace Timeline.Services
return new CreateTokenResult
{
Token = token,
- User = CreateUserInfo(user)
+ User = UserConvert.CreateUserInfo(user)
};
}
@@ -329,29 +225,33 @@ namespace Timeline.Services
throw new UserNotExistException(id);
// create cache
- cache = CreateUserCache(user);
+ cache = UserConvert.CreateUserCache(user);
_memoryCache.CreateEntry(key).SetValue(cache);
- _logger.LogInformation(FormatLogMessage("A cache entry is created.", Pair("Key", key)));
+ _logger.LogInformation(Log.Format(Resources.Services.UserService.LogCacheCreate, ("Key", key)));
}
if (tokenInfo.Version != cache.Version)
- throw new BadTokenVersionException(tokenInfo.Version, cache.Version);
+ throw new JwtVerifyException(new JwtBadVersionException(tokenInfo.Version, cache.Version), JwtVerifyException.ErrorCodes.OldVersion);
return cache.ToUserInfo();
}
public async Task<UserInfo> GetUser(string username)
{
+ if (username == null)
+ throw new ArgumentNullException(nameof(username));
+ CheckUsernameFormat(username);
+
return await _databaseContext.Users
.Where(user => user.Name == username)
- .Select(user => CreateUserInfo(user))
+ .Select(user => UserConvert.CreateUserInfo(user))
.SingleOrDefaultAsync();
}
public async Task<UserInfo[]> ListUsers()
{
return await _databaseContext.Users
- .Select(user => CreateUserInfo(user))
+ .Select(user => UserConvert.CreateUserInfo(user))
.ToArrayAsync();
}
@@ -361,11 +261,7 @@ namespace Timeline.Services
throw new ArgumentNullException(nameof(username));
if (password == null)
throw new ArgumentNullException(nameof(password));
-
- if (!_usernameValidator.Validate(username, out var message))
- {
- throw new UsernameBadFormatException(username, message);
- }
+ CheckUsernameFormat(username);
var user = await _databaseContext.Users.Where(u => u.Name == username).SingleOrDefaultAsync();
@@ -375,31 +271,34 @@ namespace Timeline.Services
{
Name = username,
EncryptedPassword = _passwordService.HashPassword(password),
- RoleString = IsAdminToRoleString(administrator),
- Avatar = UserAvatar.Create(DateTime.Now)
+ RoleString = UserRoleConvert.ToString(administrator),
+ Avatar = null
};
await _databaseContext.AddAsync(newUser);
await _databaseContext.SaveChangesAsync();
- _logger.LogInformation(FormatLogMessage("A new user entry is added to the database.", Pair("Id", newUser.Id)));
- return PutResult.Created;
+ _logger.LogInformation(Log.Format(Resources.Services.UserService.LogDatabaseCreate,
+ ("Id", newUser.Id), ("Username", username), ("Administrator", administrator)));
+ return PutResult.Create;
}
user.EncryptedPassword = _passwordService.HashPassword(password);
- user.RoleString = IsAdminToRoleString(administrator);
+ user.RoleString = UserRoleConvert.ToString(administrator);
user.Version += 1;
await _databaseContext.SaveChangesAsync();
- _logger.LogInformation(FormatLogMessage("A user entry is updated to the database.", Pair("Id", user.Id)));
+ _logger.LogInformation(Log.Format(Resources.Services.UserService.LogDatabaseUpdate,
+ ("Id", user.Id), ("Username", username), ("Administrator", administrator)));
//clear cache
RemoveCache(user.Id);
- return PutResult.Modified;
+ return PutResult.Modify;
}
- public async Task PatchUser(string username, string password, bool? administrator)
+ public async Task PatchUser(string username, string? password, bool? administrator)
{
if (username == null)
throw new ArgumentNullException(nameof(username));
+ CheckUsernameFormat(username);
var user = await _databaseContext.Users.Where(u => u.Name == username).SingleOrDefaultAsync();
if (user == null)
@@ -412,12 +311,12 @@ namespace Timeline.Services
if (administrator != null)
{
- user.RoleString = IsAdminToRoleString(administrator.Value);
+ user.RoleString = UserRoleConvert.ToString(administrator.Value);
}
user.Version += 1;
await _databaseContext.SaveChangesAsync();
- _logger.LogInformation(FormatLogMessage("A user entry is updated to the database.", Pair("Id", user.Id)));
+ _logger.LogInformation(Resources.Services.UserService.LogDatabaseUpdate, ("Id", user.Id));
//clear cache
RemoveCache(user.Id);
@@ -427,6 +326,7 @@ namespace Timeline.Services
{
if (username == null)
throw new ArgumentNullException(nameof(username));
+ CheckUsernameFormat(username);
var user = await _databaseContext.Users.Where(u => u.Name == username).SingleOrDefaultAsync();
if (user == null)
@@ -434,7 +334,8 @@ namespace Timeline.Services
_databaseContext.Users.Remove(user);
await _databaseContext.SaveChangesAsync();
- _logger.LogInformation(FormatLogMessage("A user entry is removed from the database.", Pair("Id", user.Id)));
+ _logger.LogInformation(Log.Format(Resources.Services.UserService.LogDatabaseRemove,
+ ("Id", user.Id)));
//clear cache
RemoveCache(user.Id);
@@ -448,6 +349,7 @@ namespace Timeline.Services
throw new ArgumentNullException(nameof(oldPassword));
if (newPassword == null)
throw new ArgumentNullException(nameof(newPassword));
+ CheckUsernameFormat(username);
var user = await _databaseContext.Users.Where(u => u.Name == username).SingleOrDefaultAsync();
if (user == null)
@@ -460,19 +362,20 @@ namespace Timeline.Services
user.EncryptedPassword = _passwordService.HashPassword(newPassword);
user.Version += 1;
await _databaseContext.SaveChangesAsync();
+ _logger.LogInformation(Log.Format(Resources.Services.UserService.LogDatabaseUpdate,
+ ("Id", user.Id), ("Operation", "Change password")));
//clear cache
RemoveCache(user.Id);
}
public async Task ChangeUsername(string oldUsername, string newUsername)
{
- if (string.IsNullOrEmpty(oldUsername))
- throw new ArgumentException("Old username is null or empty", nameof(oldUsername));
- if (string.IsNullOrEmpty(newUsername))
- throw new ArgumentException("New username is null or empty", nameof(newUsername));
-
- if (!_usernameValidator.Validate(newUsername, out var message))
- throw new UsernameBadFormatException(newUsername, $"New username is of bad format. {message}");
+ 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.Name == oldUsername).SingleOrDefaultAsync();
if (user == null)
@@ -480,13 +383,13 @@ namespace Timeline.Services
var conflictUser = await _databaseContext.Users.Where(u => u.Name == newUsername).SingleOrDefaultAsync();
if (conflictUser != null)
- throw new UserAlreadyExistException(newUsername);
+ throw new UsernameConfictException(newUsername);
user.Name = newUsername;
user.Version += 1;
await _databaseContext.SaveChangesAsync();
- _logger.LogInformation(FormatLogMessage("A user entry changed name field.",
- Pair("Id", user.Id), Pair("Old Username", oldUsername), Pair("New Username", newUsername)));
+ _logger.LogInformation(Log.Format(Resources.Services.UserService.LogDatabaseUpdate,
+ ("Id", user.Id), ("Old Username", oldUsername), ("New Username", newUsername)));
RemoveCache(user.Id);
}
}