diff options
-rw-r--r-- | Timeline/Controllers/UserController.cs | 34 | ||||
-rw-r--r-- | Timeline/Entities/Http/User.cs | 11 | ||||
-rw-r--r-- | Timeline/Services/QCloudCosService.cs | 71 | ||||
-rw-r--r-- | Timeline/Services/UserService.cs | 42 |
4 files changed, 154 insertions, 4 deletions
diff --git a/Timeline/Controllers/UserController.cs b/Timeline/Controllers/UserController.cs index eaa205de..a18e36e9 100644 --- a/Timeline/Controllers/UserController.cs +++ b/Timeline/Controllers/UserController.cs @@ -1,6 +1,8 @@ using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using System; +using System.IO; using System.Threading.Tasks; using Timeline.Entities; using Timeline.Entities.Http; @@ -82,14 +84,38 @@ namespace Timeline.Controllers [HttpGet("user/{username}/avatar"), Authorize] public async Task<IActionResult> GetAvatar([FromRoute] string username) { - var existence = (await _userService.GetUser(username)) != null; - if (!existence) - return NotFound(); - var url = await _userService.GetAvatarUrl(username); + if (url == null) + return NotFound(); return Redirect(url); } + [HttpPut("user/{username}/avatar"), Authorize] + [Consumes("image/png", "image/gif", "image/jpeg", "image/svg+xml")] + public async Task<IActionResult> PutAvatar([FromRoute] string username, [FromHeader(Name="Content-Type")] string contentType) + { + bool isAdmin = User.IsInRole("admin"); + if (!isAdmin) + { + if (username != User.Identity.Name) + return StatusCode(StatusCodes.Status403Forbidden, PutAvatarResponse.Forbidden); + } + + var stream = new MemoryStream(); + await Request.Body.CopyToAsync(stream); + var result = await _userService.PutAvatar(username, stream.ToArray(), contentType); + switch (result) + { + case PutAvatarResult.Success: + return Ok(PutAvatarResponse.Success); + case PutAvatarResult.UserNotExists: + return BadRequest(PutAvatarResponse.NotExists); + default: + throw new Exception("Unknown put avatar result."); + } + } + + [HttpPost("userop/changepassword"), Authorize] public async Task<IActionResult> ChangePassword([FromBody] ChangePasswordRequest request) { diff --git a/Timeline/Entities/Http/User.cs b/Timeline/Entities/Http/User.cs index d42ca088..31cafaa3 100644 --- a/Timeline/Entities/Http/User.cs +++ b/Timeline/Entities/Http/User.cs @@ -40,4 +40,15 @@ public static ReturnCodeMessageResponse BadOldPassword { get; } = new ReturnCodeMessageResponse(BadOldPasswordCode, "Old password is wrong."); public static ReturnCodeMessageResponse NotExists { get; } = new ReturnCodeMessageResponse(NotExistsCode, "Username does not exists, please update token."); } + + public static class PutAvatarResponse + { + public const int SuccessCode = 0; + public const int ForbiddenCode = 1; + public const int NotExistsCode = 2; + + public static ReturnCodeMessageResponse Success {get;} = new ReturnCodeMessageResponse(SuccessCode, "Success to upload avatar."); + public static ReturnCodeMessageResponse Forbidden {get;} = new ReturnCodeMessageResponse(ForbiddenCode, "You are not allowed to upload the user's avatar."); + public static ReturnCodeMessageResponse NotExists {get;} = new ReturnCodeMessageResponse(NotExistsCode, "The username does not exists. If you are a user, try update your token."); + } } diff --git a/Timeline/Services/QCloudCosService.cs b/Timeline/Services/QCloudCosService.cs index f4358714..078dd37b 100644 --- a/Timeline/Services/QCloudCosService.cs +++ b/Timeline/Services/QCloudCosService.cs @@ -6,6 +6,7 @@ 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; @@ -25,6 +26,14 @@ namespace Timeline.Services 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> @@ -229,6 +238,68 @@ namespace Timeline.Services } } + 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."); + } + } + public string GenerateObjectGetUrl(string bucket, string key) { if (bucket == null) diff --git a/Timeline/Services/UserService.cs b/Timeline/Services/UserService.cs index 4a47ca0f..9ebf2668 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; @@ -65,6 +66,18 @@ namespace Timeline.Services BadOldPassword } + public enum PutAvatarResult + { + /// <summary> + /// Success to upload avatar. + /// </summary> + Success, + /// <summary> + /// The user does not exists. + /// </summary> + UserNotExists + } + public interface IUserService { /// <summary> @@ -141,7 +154,14 @@ namespace Timeline.Services /// <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); + + Task<PutAvatarResult> PutAvatar(string username, byte[] data, string mimeType); } public class UserService : IUserService @@ -301,11 +321,33 @@ namespace Timeline.Services 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; + + await _cosService.PutObject("avatar", username, data, mimeType); + return PutAvatarResult.Success; + } } } |