aboutsummaryrefslogtreecommitdiff
path: root/Timeline/Services
diff options
context:
space:
mode:
Diffstat (limited to 'Timeline/Services')
-rw-r--r--Timeline/Services/JwtService.cs22
-rw-r--r--Timeline/Services/QCloudCosService.cs329
-rw-r--r--Timeline/Services/UserService.cs275
3 files changed, 597 insertions, 29 deletions
diff --git a/Timeline/Services/JwtService.cs b/Timeline/Services/JwtService.cs
index 91e7f879..bf470354 100644
--- a/Timeline/Services/JwtService.cs
+++ b/Timeline/Services/JwtService.cs
@@ -7,25 +7,28 @@ using System.Linq;
using System.Security.Claims;
using System.Text;
using Timeline.Configs;
+using Timeline.Entities;
namespace Timeline.Services
{
public interface IJwtService
{
/// <summary>
- /// Create a JWT token for a given user id.
+ /// Create a JWT token for a given user info.
/// </summary>
- /// <param name="userId">The user id used to generate token.</param>
+ /// <param name="userId">The user id contained in generate token.</param>
+ /// <param name="username">The username contained in token.</param>
+ /// <param name="roles">The roles contained in token.</param>
/// <returns>Return the generated token.</returns>
- string GenerateJwtToken(long userId, string[] roles);
+ string GenerateJwtToken(long userId, string username, string[] roles);
/// <summary>
/// Verify a JWT token.
/// Return null is <paramref name="token"/> is null.
/// </summary>
/// <param name="token">The token string to verify.</param>
- /// <returns>Return null if <paramref name="token"/> is null or token is invalid. Return the saved user id otherwise.</returns>
- long? VerifyJwtToken(string token);
+ /// <returns>Return null if <paramref name="token"/> is null or token is invalid. Return the saved user info otherwise.</returns>
+ UserInfo VerifyJwtToken(string token);
}
@@ -41,12 +44,13 @@ namespace Timeline.Services
_logger = logger;
}
- public string GenerateJwtToken(long id, string[] roles)
+ public string GenerateJwtToken(long id, string username, string[] roles)
{
var jwtConfig = _jwtConfig.CurrentValue;
var identity = new ClaimsIdentity();
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, id.ToString()));
+ identity.AddClaim(new Claim(identity.NameClaimType, username));
identity.AddClaims(roles.Select(role => new Claim(identity.RoleClaimType, role)));
var tokenDescriptor = new SecurityTokenDescriptor()
@@ -67,13 +71,12 @@ namespace Timeline.Services
}
- public long? VerifyJwtToken(string token)
+ public UserInfo VerifyJwtToken(string token)
{
if (token == null)
return null;
var config = _jwtConfig.CurrentValue;
-
try
{
var principal = _tokenHandler.ValidateToken(token, new TokenValidationParameters
@@ -87,7 +90,8 @@ namespace Timeline.Services
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(config.SigningKey))
}, out SecurityToken validatedToken);
- return long.Parse(principal.FindAll(ClaimTypes.NameIdentifier).Single().Value);
+ return new UserInfo(principal.Identity.Name,
+ principal.FindAll(ClaimTypes.Role).Select(c => c.Value).ToArray());
}
catch (Exception e)
{
diff --git a/Timeline/Services/QCloudCosService.cs b/Timeline/Services/QCloudCosService.cs
new file mode 100644
index 00000000..b37631e5
--- /dev/null
+++ b/Timeline/Services/QCloudCosService.cs
@@ -0,0 +1,329 @@
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Net;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Security.Cryptography;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+using Timeline.Configs;
+
+namespace Timeline.Services
+{
+ public interface IQCloudCosService
+ {
+ /// <summary>
+ /// Test if an object in the bucket exists.
+ /// </summary>
+ /// <param name="bucket">The bucket name.</param>
+ /// <param name="key">The object key.</param>
+ /// <returns>True if exists. False if not.</returns>
+ Task<bool> IsObjectExists(string bucket, string key);
+
+ /// <summary>
+ /// Upload an object use put method.
+ /// </summary>
+ /// <param name="bucket">The bucket name.</param>
+ /// <param name="key">The object key.</param>
+ /// <param name="data">The data to upload.</param>
+ Task PutObject(string bucket, string key, byte[] data, string contentType);
+
+ /// <summary>
+ /// Generate a presignated url to access the object.
+ /// </summary>
+ /// <param name="bucket">The bucket name.</param>
+ /// <param name="key">The object key.</param>
+ /// <returns>The presignated url.</returns>
+ string GenerateObjectGetUrl(string bucket, string key);
+ }
+
+ public class QCloudCosService : IQCloudCosService
+ {
+ private readonly IOptionsMonitor<QCloudCosConfig> _config;
+ private readonly ILogger<QCloudCosService> _logger;
+ private readonly IHttpClientFactory _httpClientFactory;
+
+ public QCloudCosService(IOptionsMonitor<QCloudCosConfig> config, ILogger<QCloudCosService> logger, IHttpClientFactory httpClientFactory)
+ {
+ _config = config;
+ _logger = logger;
+ _httpClientFactory = httpClientFactory;
+ }
+
+ private const string BucketNamePattern = @"^(([a-z0-9][a-z0-9-]*[a-z0-9])|[a-z0-9])$";
+
+ public static bool ValidateBucketName(string bucketName)
+ {
+ return Regex.IsMatch(bucketName, BucketNamePattern);
+ }
+
+ public class QCloudCredentials
+ {
+ public string SecretId { get; set; }
+ public string SecretKey { get; set; }
+ }
+
+ public class RequestInfo
+ {
+ public string Method { get; set; }
+ public string Uri { get; set; }
+ public IEnumerable<KeyValuePair<string, string>> Parameters { get; set; }
+ public IEnumerable<KeyValuePair<string, string>> Headers { get; set; }
+ }
+
+ public class TimeDuration
+ {
+ public TimeDuration()
+ {
+
+ }
+
+ public TimeDuration(DateTimeOffset start, DateTimeOffset end)
+ {
+ Start = start;
+ End = end;
+ }
+
+ public DateTimeOffset Start { get; set; }
+ public DateTimeOffset End { get; set; }
+ }
+
+ public static string GenerateSign(QCloudCredentials credentials, RequestInfo request, TimeDuration signValidTime)
+ {
+ Debug.Assert(credentials != null);
+ Debug.Assert(credentials.SecretId != null);
+ Debug.Assert(credentials.SecretKey != null);
+ Debug.Assert(request != null);
+ Debug.Assert(request.Method != null);
+ Debug.Assert(request.Uri != null);
+ Debug.Assert(signValidTime != null);
+ Debug.Assert(signValidTime.Start < signValidTime.End, "Start must be before End in sign valid time.");
+
+ List<(string key, string value)> Transform(IEnumerable<KeyValuePair<string, string>> raw)
+ {
+ if (raw == null)
+ return new List<(string key, string value)>();
+
+ var sorted = raw.Select(p => (key: p.Key.ToLower(), value: WebUtility.UrlEncode(p.Value))).ToList();
+ sorted.Sort((left, right) => string.CompareOrdinal(left.key, right.key));
+ return sorted;
+ }
+
+ var transformedParameters = Transform(request.Parameters);
+ var transformedHeaders = Transform(request.Headers);
+
+ List<(string, string)> result = new List<(string, string)>();
+
+ const string signAlgorithm = "sha1";
+ result.Add(("q-sign-algorithm", signAlgorithm));
+
+ result.Add(("q-ak", credentials.SecretId));
+
+ var signTime = $"{signValidTime.Start.ToUnixTimeSeconds().ToString()};{signValidTime.End.ToUnixTimeSeconds().ToString()}";
+ var keyTime = signTime;
+ result.Add(("q-sign-time", signTime));
+ result.Add(("q-key-time", keyTime));
+
+ result.Add(("q-header-list", string.Join(';', transformedHeaders.Select(h => h.key))));
+ result.Add(("q-url-param-list", string.Join(';', transformedParameters.Select(p => p.key))));
+
+ HMACSHA1 hmac = new HMACSHA1();
+
+ string ByteArrayToString(byte[] bytes)
+ {
+ return BitConverter.ToString(bytes).Replace("-", "").ToLower();
+ }
+
+ hmac.Key = Encoding.UTF8.GetBytes(credentials.SecretKey);
+ var signKey = ByteArrayToString(hmac.ComputeHash(Encoding.UTF8.GetBytes(keyTime)));
+
+ string Join(IEnumerable<(string key, string value)> raw)
+ {
+ return string.Join('&', raw.Select(p => string.Concat(p.key, "=", p.value)));
+ }
+
+ var httpString = new StringBuilder()
+ .Append(request.Method.ToLower()).Append('\n')
+ .Append(request.Uri).Append('\n')
+ .Append(Join(transformedParameters)).Append('\n')
+ .Append(Join(transformedHeaders)).Append('\n')
+ .ToString();
+
+ string Sha1(string data)
+ {
+ var sha1 = SHA1.Create().ComputeHash(Encoding.UTF8.GetBytes(data));
+ return ByteArrayToString(sha1);
+ }
+
+ var stringToSign = new StringBuilder()
+ .Append(signAlgorithm).Append('\n')
+ .Append(signTime).Append('\n')
+ .Append(Sha1(httpString)).Append('\n')
+ .ToString();
+
+ hmac.Key = Encoding.UTF8.GetBytes(signKey);
+ var signature = ByteArrayToString(hmac.ComputeHash(
+ Encoding.UTF8.GetBytes(stringToSign)));
+
+ result.Add(("q-signature", signature));
+
+ return Join(result);
+ }
+
+ private QCloudCredentials GetCredentials()
+ {
+ var config = _config.CurrentValue;
+ return new QCloudCredentials
+ {
+ SecretId = config.SecretId,
+ SecretKey = config.SecretKey
+ };
+ }
+
+ private string GetHost(string bucket)
+ {
+ var config = _config.CurrentValue;
+ return $"{bucket}-{config.AppId}.cos.{config.Region}.myqcloud.com";
+ }
+
+ public async Task<bool> IsObjectExists(string bucket, string key)
+ {
+ if (bucket == null)
+ throw new ArgumentNullException(nameof(bucket));
+ if (key == null)
+ throw new ArgumentNullException(nameof(key));
+ if (!ValidateBucketName(bucket))
+ throw new ArgumentException($"Bucket name is not valid. Param is {bucket} .", nameof(bucket));
+
+ var client = _httpClientFactory.CreateClient();
+
+ var host = GetHost(bucket);
+ var encodedKey = WebUtility.UrlEncode(key);
+
+ var request = new HttpRequestMessage();
+ request.Method = HttpMethod.Head;
+ request.RequestUri = new Uri($"https://{host}/{encodedKey}");
+ request.Headers.Host = host;
+ request.Headers.Date = DateTimeOffset.Now;
+ request.Headers.TryAddWithoutValidation("Authorization", GenerateSign(GetCredentials(), new RequestInfo
+ {
+ Method = "head",
+ Uri = "/" + encodedKey,
+ Headers = new Dictionary<string, string>
+ {
+ ["Host"] = host
+ }
+ }, new TimeDuration(DateTimeOffset.Now, DateTimeOffset.Now.AddMinutes(2))));
+
+ try
+ {
+ var response = await client.SendAsync(request);
+
+ if (response.IsSuccessStatusCode)
+ return true;
+ if (response.StatusCode == HttpStatusCode.NotFound)
+ return false;
+
+ throw new Exception($"Unknown response code. {response.ToString()}");
+ }
+ catch (Exception e)
+ {
+ _logger.LogError(e, "An error occured when test a cos object existence.");
+ throw;
+ }
+ }
+
+ public async Task PutObject(string bucket, string key, byte[] data, string contentType)
+ {
+ if (bucket == null)
+ throw new ArgumentNullException(nameof(bucket));
+ if (key == null)
+ throw new ArgumentNullException(nameof(key));
+ if (!ValidateBucketName(bucket))
+ throw new ArgumentException($"Bucket name is not valid. Param is {bucket} .", nameof(bucket));
+ if (data == null)
+ throw new ArgumentNullException(nameof(data));
+
+ var host = GetHost(bucket);
+ var encodedKey = WebUtility.UrlEncode(key);
+ var md5 = Convert.ToBase64String(MD5.Create().ComputeHash(data));
+
+ const string kContentMD5HeaderName = "Content-MD5";
+ const string kContentTypeHeaderName = "Content-Type";
+
+ var httpRequest = new HttpRequestMessage()
+ {
+ Method = HttpMethod.Put,
+ RequestUri = new Uri($"https://{host}/{encodedKey}")
+ };
+ httpRequest.Headers.Host = host;
+ httpRequest.Headers.Date = DateTimeOffset.Now;
+ var httpContent = new ByteArrayContent(data);
+ httpContent.Headers.Add(kContentMD5HeaderName, md5);
+ httpRequest.Content = httpContent;
+
+ var signedHeaders = new Dictionary<string, string>
+ {
+ ["Host"] = host,
+ [kContentMD5HeaderName] = md5
+ };
+
+ if (contentType != null)
+ {
+ httpContent.Headers.Add(kContentTypeHeaderName, contentType);
+ signedHeaders.Add(kContentTypeHeaderName, contentType);
+ }
+
+ httpRequest.Headers.TryAddWithoutValidation("Authorization", GenerateSign(GetCredentials(), new RequestInfo
+ {
+ Method = "put",
+ Uri = "/" + encodedKey,
+ Headers = signedHeaders
+ }, new TimeDuration(DateTimeOffset.Now, DateTimeOffset.Now.AddMinutes(10))));
+
+ var client = _httpClientFactory.CreateClient();
+
+ try
+ {
+ var response = await client.SendAsync(httpRequest);
+ if (!response.IsSuccessStatusCode)
+ throw new Exception($"Not success status code. {response.ToString()}");
+ }
+ catch (Exception e)
+ {
+ _logger.LogError(e, "An error occured when test a cos object existence.");
+ throw;
+ }
+ }
+
+ public string GenerateObjectGetUrl(string bucket, string key)
+ {
+ if (bucket == null)
+ throw new ArgumentNullException(nameof(bucket));
+ if (key == null)
+ throw new ArgumentNullException(nameof(key));
+ if (!ValidateBucketName(bucket))
+ throw new ArgumentException($"Bucket name is not valid. Param is {bucket} .", nameof(bucket));
+
+ var host = GetHost(bucket);
+ var encodedKey = WebUtility.UrlEncode(key);
+
+ var signature = GenerateSign(GetCredentials(), new RequestInfo
+ {
+ Method = "get",
+ Uri = "/" + encodedKey,
+ Headers = new Dictionary<string, string>
+ {
+ ["Host"] = host
+ }
+ }, new TimeDuration(DateTimeOffset.Now, DateTimeOffset.Now.AddMinutes(6)));
+
+ return $"https://{host}/{encodedKey}?{signature}";
+ }
+ }
+}
diff --git a/Timeline/Services/UserService.cs b/Timeline/Services/UserService.cs
index ad36c37b..8ab3bc54 100644
--- a/Timeline/Services/UserService.cs
+++ b/Timeline/Services/UserService.cs
@@ -1,5 +1,6 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
+using System;
using System.Linq;
using System.Threading.Tasks;
using Timeline.Entities;
@@ -13,10 +14,68 @@ namespace Timeline.Services
public UserInfo UserInfo { get; set; }
}
- public enum CreateUserResult
+ public enum PutUserResult
{
+ /// <summary>
+ /// A new user is created.
+ /// </summary>
+ Created,
+ /// <summary>
+ /// A existing user is modified.
+ /// </summary>
+ Modified
+ }
+
+ public enum PatchUserResult
+ {
+ /// <summary>
+ /// Succeed to modify user.
+ /// </summary>
Success,
- AlreadyExists
+ /// <summary>
+ /// A user of given username does not exist.
+ /// </summary>
+ NotExists
+ }
+
+ public enum DeleteUserResult
+ {
+ /// <summary>
+ /// A existing user is deleted.
+ /// </summary>
+ Deleted,
+ /// <summary>
+ /// A user of given username does not exist.
+ /// </summary>
+ NotExists
+ }
+
+ public enum ChangePasswordResult
+ {
+ /// <summary>
+ /// Success to change password.
+ /// </summary>
+ Success,
+ /// <summary>
+ /// The user does not exists.
+ /// </summary>
+ NotExists,
+ /// <summary>
+ /// Old password is wrong.
+ /// </summary>
+ BadOldPassword
+ }
+
+ public enum PutAvatarResult
+ {
+ /// <summary>
+ /// Success to upload avatar.
+ /// </summary>
+ Success,
+ /// <summary>
+ /// The user does not exists.
+ /// </summary>
+ UserNotExists
}
public interface IUserService
@@ -38,7 +97,79 @@ namespace Timeline.Services
/// <returns>Return null if verification failed. The user info if verification succeeded.</returns>
Task<UserInfo> VerifyToken(string token);
- Task<CreateUserResult> CreateUser(string username, string password, string[] roles);
+ /// <summary>
+ /// Get the user info of given username.
+ /// </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>
+ Task<UserInfo> GetUser(string username);
+
+ /// <summary>
+ /// List all users.
+ /// </summary>
+ /// <returns>The user info of users.</returns>
+ Task<UserInfo[]> ListUsers();
+
+ /// <summary>
+ /// Create or modify a user with given username.
+ /// Return <see cref="PutUserResult.Created"/> if a new user is created.
+ /// Return <see cref="PutUserResult.Modified"/> if a existing user is modified.
+ /// </summary>
+ /// <param name="username">Username of user.</param>
+ /// <param name="password">Password of user.</param>
+ /// <param name="roles">Array of roles of user.</param>
+ /// <returns>Return <see cref="PutUserResult.Created"/> if a new user is created.
+ /// Return <see cref="PutUserResult.Modified"/> if a existing user is modified.</returns>
+ Task<PutUserResult> PutUser(string username, string password, string[] roles);
+
+ /// <summary>
+ /// Partially modify a use of given username.
+ /// </summary>
+ /// <param name="username">Username of the user to modify.</param>
+ /// <param name="password">New password. If not modify, then null.</param>
+ /// <param name="roles">New roles. If not modify, then null.</param>
+ /// <returns>Return <see cref="PatchUserResult.Success"/> if modification succeeds.
+ /// Return <see cref="PatchUserResult.NotExists"/> if the user of given username doesn't exist.</returns>
+ Task<PatchUserResult> PatchUser(string username, string password, string[] roles);
+
+ /// <summary>
+ /// Delete a user of given username.
+ /// Return <see cref="DeleteUserResult.Deleted"/> if the user is deleted.
+ /// Return <see cref="DeleteUserResult.NotExists"/> if the user of given username
+ /// does not exist.
+ /// </summary>
+ /// <param name="username">Username of thet user to delete.</param>
+ /// <returns><see cref="DeleteUserResult.Deleted"/> if the user is deleted.
+ /// <see cref="DeleteUserResult.NotExists"/> if the user doesn't exist.</returns>
+ Task<DeleteUserResult> 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>
+ /// <returns><see cref="ChangePasswordResult.Success"/> if success.
+ /// <see cref="ChangePasswordResult.NotExists"/> if user does not exist.
+ /// <see cref="ChangePasswordResult.BadOldPassword"/> if old password is wrong.</returns>
+ Task<ChangePasswordResult> ChangePassword(string username, string oldPassword, string newPassword);
+
+ /// <summary>
+ /// Get the true avatar url of a user.
+ /// </summary>
+ /// <param name="username">The name of user.</param>
+ /// <returns>The url if user exists. Null if user does not exist.</returns>
+ Task<string> GetAvatarUrl(string username);
+
+ /// <summary>
+ /// Put a avatar of a user.
+ /// </summary>
+ /// <param name="username">The name of user.</param>
+ /// <param name="data">The data of avatar image.</param>
+ /// <param name="mimeType">The mime type of the image.</param>
+ /// <returns>Return <see cref="PutAvatarResult.Success"/> if success.
+ /// Return <see cref="PutAvatarResult.UserNotExists"/> if user does not exist.</returns>
+ Task<PutAvatarResult> PutAvatar(string username, byte[] data, string mimeType);
}
public class UserService : IUserService
@@ -47,19 +178,19 @@ namespace Timeline.Services
private readonly DatabaseContext _databaseContext;
private readonly IJwtService _jwtService;
private readonly IPasswordService _passwordService;
+ private readonly IQCloudCosService _cosService;
- public UserService(ILogger<UserService> logger, DatabaseContext databaseContext, IJwtService jwtService, IPasswordService passwordService)
+ public UserService(ILogger<UserService> logger, DatabaseContext databaseContext, IJwtService jwtService, IPasswordService passwordService, IQCloudCosService cosService)
{
_logger = logger;
_databaseContext = databaseContext;
_jwtService = jwtService;
_passwordService = passwordService;
+ _cosService = cosService;
}
public async Task<CreateTokenResult> CreateToken(string username, string password)
{
- var users = _databaseContext.Users.ToList();
-
var user = await _databaseContext.Users.Where(u => u.Name == username).SingleOrDefaultAsync();
if (user == null)
@@ -72,11 +203,11 @@ namespace Timeline.Services
if (verifyResult)
{
- var userInfo = new UserInfo(user);
+ var userInfo = UserInfo.Create(user);
return new CreateTokenResult
{
- Token = _jwtService.GenerateJwtToken(user.Id, userInfo.Roles),
+ Token = _jwtService.GenerateJwtToken(user.Id, userInfo.Username, userInfo.Roles),
UserInfo = userInfo
};
}
@@ -89,38 +220,142 @@ namespace Timeline.Services
public async Task<UserInfo> VerifyToken(string token)
{
- var userId = _jwtService.VerifyJwtToken(token);
+ var userInfo = _jwtService.VerifyJwtToken(token);
- if (userId == null)
+ if (userInfo == null)
{
_logger.LogInformation($"Verify token falied. Reason: invalid token. Token: {token} .");
return null;
}
- var user = await _databaseContext.Users.Where(u => u.Id == userId.Value).SingleOrDefaultAsync();
+ return await Task.FromResult(userInfo);
+ }
+
+ public async Task<UserInfo> GetUser(string username)
+ {
+ return await _databaseContext.Users
+ .Where(user => user.Name == username)
+ .Select(user => UserInfo.Create(user.Name, user.RoleString))
+ .SingleOrDefaultAsync();
+ }
+
+ public async Task<UserInfo[]> ListUsers()
+ {
+ return await _databaseContext.Users
+ .Select(user => UserInfo.Create(user.Name, user.RoleString))
+ .ToArrayAsync();
+ }
+
+ public async Task<PutUserResult> PutUser(string username, string password, string[] roles)
+ {
+ var user = await _databaseContext.Users.Where(u => u.Name == username).SingleOrDefaultAsync();
if (user == null)
{
- _logger.LogInformation($"Verify token falied. Reason: invalid user id. UserId: {userId} Token: {token} .");
- return null;
+ await _databaseContext.AddAsync(new User
+ {
+ Name = username,
+ EncryptedPassword = _passwordService.HashPassword(password),
+ RoleString = string.Join(',', roles)
+ });
+ await _databaseContext.SaveChangesAsync();
+ return PutUserResult.Created;
}
- return new UserInfo(user);
+ user.EncryptedPassword = _passwordService.HashPassword(password);
+ user.RoleString = string.Join(',', roles);
+ await _databaseContext.SaveChangesAsync();
+
+ return PutUserResult.Modified;
}
- public async Task<CreateUserResult> CreateUser(string username, string password, string[] roles)
+ public async Task<PatchUserResult> PatchUser(string username, string password, string[] roles)
{
- var exists = (await _databaseContext.Users.Where(u => u.Name == username).ToListAsync()).Count != 0;
+ var user = await _databaseContext.Users.Where(u => u.Name == username).SingleOrDefaultAsync();
- if (exists)
+ if (user == null)
+ return PatchUserResult.NotExists;
+
+ bool modified = false;
+
+ if (password != null)
{
- return CreateUserResult.AlreadyExists;
+ modified = true;
+ user.EncryptedPassword = _passwordService.HashPassword(password);
}
- await _databaseContext.Users.AddAsync(new User { Name = username, EncryptedPassword = _passwordService.HashPassword(password), RoleString = string.Join(',', roles) });
+ if (roles != null)
+ {
+ modified = true;
+ user.RoleString = string.Join(',', roles);
+ }
+
+ if (modified)
+ {
+ await _databaseContext.SaveChangesAsync();
+ }
+
+ return PatchUserResult.Success;
+ }
+
+ public async Task<DeleteUserResult> 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<ChangePasswordResult> 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<string> GetAvatarUrl(string username)
+ {
+ if (username == null)
+ throw new ArgumentNullException(nameof(username));
+
+ if ((await GetUser(username)) == null)
+ return null;
+
+ var exists = await _cosService.IsObjectExists("avatar", username);
+ if (exists)
+ return _cosService.GenerateObjectGetUrl("avatar", username);
+ else
+ return _cosService.GenerateObjectGetUrl("avatar", "__default");
+ }
+
+ public async Task<PutAvatarResult> PutAvatar(string username, byte[] data, string mimeType)
+ {
+ if (username == null)
+ throw new ArgumentNullException(nameof(username));
+ if (data == null)
+ throw new ArgumentNullException(nameof(data));
+ if (mimeType == null)
+ throw new ArgumentNullException(nameof(mimeType));
+
+ if ((await GetUser(username)) == null)
+ return PutAvatarResult.UserNotExists;
- return CreateUserResult.Success;
+ await _cosService.PutObject("avatar", username, data, mimeType);
+ return PutAvatarResult.Success;
}
}
}