using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using System.Linq; using System.Threading.Tasks; using Timeline.Entities; using Timeline.Models; namespace Timeline.Services { public class CreateTokenResult { public string Token { get; set; } public UserInfo UserInfo { get; set; } } public enum PutUserResult { /// /// A new user is created. /// Created, /// /// A existing user is modified. /// Modified } public enum PatchUserResult { /// /// Succeed to modify user. /// Success, /// /// A user of given username does not exist. /// NotExists } public enum DeleteUserResult { /// /// A existing user is deleted. /// Deleted, /// /// A user of given username does not exist. /// NotExists } public enum ChangePasswordResult { /// /// Success to change password. /// Success, /// /// The user does not exists. /// NotExists, /// /// Old password is wrong. /// BadOldPassword } public interface IUserService { /// /// Try to anthenticate with the given username and password. /// If success, create a token and return the user info. /// /// The username of the user to be anthenticated. /// The password of the user to be anthenticated. /// Return null if anthentication failed. An containing the created token and user info if anthentication succeeded. Task CreateToken(string username, string password); /// /// Verify the given token. /// If success, return the user info. /// /// The token to verify. /// Return null if verification failed. The user info if verification succeeded. Task VerifyToken(string token); /// /// Get the user info of given username. /// /// Username of the user. /// The info of the user. Null if the user of given username does not exists. Task GetUser(string username); /// /// List all users. /// /// The user info of users. Task ListUsers(); /// /// Create or modify a user with given username. /// Return if a new user is created. /// Return if a existing user is modified. /// /// Username of user. /// Password of user. /// Array of roles of user. /// Return if a new user is created. /// Return if a existing user is modified. Task PutUser(string username, string password, string[] roles); /// /// Partially modify a use of given username. /// /// Username of the user to modify. /// New password. If not modify, then null. /// New roles. If not modify, then null. /// Return if modification succeeds. /// Return if the user of given username doesn't exist. Task PatchUser(string username, string password, string[] roles); /// /// Delete a user of given username. /// Return if the user is deleted. /// Return if the user of given username /// does not exist. /// /// Username of thet user to delete. /// if the user is deleted. /// if the user doesn't exist. Task DeleteUser(string username); /// /// Try to change a user's password with old password. /// /// The name of user to change password of. /// The user's old password. /// The user's new password. /// if success. /// if user does not exist. /// if old password is wrong. Task ChangePassword(string username, string oldPassword, string newPassword); Task GetAvatarUrl(string username); } public class UserService : IUserService { private readonly ILogger _logger; private readonly DatabaseContext _databaseContext; private readonly IJwtService _jwtService; private readonly IPasswordService _passwordService; private readonly IQCloudCosService _cosService; public UserService(ILogger logger, DatabaseContext databaseContext, IJwtService jwtService, IPasswordService passwordService, IQCloudCosService cosService) { _logger = logger; _databaseContext = databaseContext; _jwtService = jwtService; _passwordService = passwordService; _cosService = cosService; } public async Task CreateToken(string username, string password) { var user = await _databaseContext.Users.Where(u => u.Name == username).SingleOrDefaultAsync(); if (user == null) { _logger.LogInformation($"Create token failed with invalid username. Username = {username} Password = {password} ."); return null; } var verifyResult = _passwordService.VerifyPassword(user.EncryptedPassword, password); if (verifyResult) { var userInfo = UserInfo.Create(user); return new CreateTokenResult { Token = _jwtService.GenerateJwtToken(user.Id, userInfo.Username, userInfo.Roles), UserInfo = userInfo }; } else { _logger.LogInformation($"Create token failed with invalid password. Username = {username} Password = {password} ."); return null; } } public async Task VerifyToken(string token) { var userInfo = _jwtService.VerifyJwtToken(token); if (userInfo == null) { _logger.LogInformation($"Verify token falied. Reason: invalid token. Token: {token} ."); return null; } return await Task.FromResult(userInfo); } public async Task GetUser(string username) { return await _databaseContext.Users .Where(user => user.Name == username) .Select(user => UserInfo.Create(user.Name, user.RoleString)) .SingleOrDefaultAsync(); } public async Task ListUsers() { return await _databaseContext.Users .Select(user => UserInfo.Create(user.Name, user.RoleString)) .ToArrayAsync(); } public async Task PutUser(string username, string password, string[] roles) { var user = await _databaseContext.Users.Where(u => u.Name == username).SingleOrDefaultAsync(); if (user == null) { await _databaseContext.AddAsync(new User { Name = username, EncryptedPassword = _passwordService.HashPassword(password), RoleString = string.Join(',', roles) }); await _databaseContext.SaveChangesAsync(); return PutUserResult.Created; } user.EncryptedPassword = _passwordService.HashPassword(password); user.RoleString = string.Join(',', roles); await _databaseContext.SaveChangesAsync(); return PutUserResult.Modified; } public async Task PatchUser(string username, string password, string[] roles) { var user = await _databaseContext.Users.Where(u => u.Name == username).SingleOrDefaultAsync(); if (user == null) return PatchUserResult.NotExists; bool modified = false; if (password != null) { modified = true; user.EncryptedPassword = _passwordService.HashPassword(password); } if (roles != null) { modified = true; user.RoleString = string.Join(',', roles); } if (modified) { await _databaseContext.SaveChangesAsync(); } return PatchUserResult.Success; } public async Task DeleteUser(string username) { var user = await _databaseContext.Users.Where(u => u.Name == username).SingleOrDefaultAsync(); if (user == null) { return DeleteUserResult.NotExists; } _databaseContext.Users.Remove(user); await _databaseContext.SaveChangesAsync(); return DeleteUserResult.Deleted; } public async Task ChangePassword(string username, string oldPassword, string newPassword) { var user = await _databaseContext.Users.Where(u => u.Name == username).SingleOrDefaultAsync(); if (user == null) return ChangePasswordResult.NotExists; var verifyResult = _passwordService.VerifyPassword(user.EncryptedPassword, oldPassword); if (!verifyResult) return ChangePasswordResult.BadOldPassword; user.EncryptedPassword = _passwordService.HashPassword(newPassword); await _databaseContext.SaveChangesAsync(); return ChangePasswordResult.Success; } public async Task GetAvatarUrl(string username) { var exists = await _cosService.ObjectExists("avatar", username); if (exists) return _cosService.GetObjectUrl("avatar", username); else return _cosService.GetObjectUrl("avatar", "__default"); } } }