aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Timeline/Controllers/UserController.cs34
-rw-r--r--Timeline/Entities/Http/User.cs11
-rw-r--r--Timeline/Services/QCloudCosService.cs71
-rw-r--r--Timeline/Services/UserService.cs42
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;
+ }
}
}