aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author杨宇千 <crupest@outlook.com>2019-10-31 00:56:46 +0800
committer杨宇千 <crupest@outlook.com>2019-10-31 00:56:46 +0800
commitd3a1bf5f2939049f11e77f91ad9ddea30d8acd64 (patch)
tree7d034bb824b50d892136c6f1225a15e8baa30741
parent006d799d2fe5f081c188f95a8590c4b75a93caae (diff)
downloadtimeline-d3a1bf5f2939049f11e77f91ad9ddea30d8acd64.tar.gz
timeline-d3a1bf5f2939049f11e77f91ad9ddea30d8acd64.tar.bz2
timeline-d3a1bf5f2939049f11e77f91ad9ddea30d8acd64.zip
Continue to construct feature and tests.
-rw-r--r--Timeline.Tests/Controllers/UserControllerTest.cs1
-rw-r--r--Timeline.Tests/Controllers/UserDetailControllerTest.cs5
-rw-r--r--Timeline.Tests/Helpers/HttpClientExtensions.cs12
-rw-r--r--Timeline.Tests/Helpers/ResponseAssertions.cs5
-rw-r--r--Timeline.Tests/IntegratedTests/UserAvatarTest.cs4
-rw-r--r--Timeline.Tests/IntegratedTests/UserDetailTest.cs83
-rw-r--r--Timeline.Tests/Properties/launchSettings.json52
-rw-r--r--Timeline.Tests/Timeline.Tests.csproj4
-rw-r--r--Timeline/Auth/Attribute.cs (renamed from Timeline/Authentication/Attribute.cs)2
-rw-r--r--Timeline/Auth/MyAuthenticationHandler.cs (renamed from Timeline/Authentication/AuthHandler.cs)18
-rw-r--r--Timeline/Auth/PrincipalExtensions.cs (renamed from Timeline/Authentication/PrincipalExtensions.cs)2
-rw-r--r--Timeline/Controllers/Testing/TestingAuthController.cs2
-rw-r--r--Timeline/Controllers/UserAvatarController.cs2
-rw-r--r--Timeline/Controllers/UserController.cs2
-rw-r--r--Timeline/Controllers/UserDetailController.cs5
-rw-r--r--Timeline/Filters/User.cs66
-rw-r--r--Timeline/Formatters/StringInputFormatter.cs27
-rw-r--r--Timeline/Resources/Filters.Designer.cs36
-rw-r--r--Timeline/Resources/Filters.resx12
-rw-r--r--Timeline/Resources/Filters.zh.resx3
-rw-r--r--Timeline/Startup.cs18
21 files changed, 302 insertions, 59 deletions
diff --git a/Timeline.Tests/Controllers/UserControllerTest.cs b/Timeline.Tests/Controllers/UserControllerTest.cs
index a9cce970..83b8cdcf 100644
--- a/Timeline.Tests/Controllers/UserControllerTest.cs
+++ b/Timeline.Tests/Controllers/UserControllerTest.cs
@@ -13,7 +13,6 @@ using Timeline.Models.Http;
using Timeline.Services;
using Timeline.Tests.Helpers;
using Timeline.Tests.Mock.Data;
-using Timeline.Tests.Mock.Services;
using Xunit;
using static Timeline.ErrorCodes.Http.User;
diff --git a/Timeline.Tests/Controllers/UserDetailControllerTest.cs b/Timeline.Tests/Controllers/UserDetailControllerTest.cs
index 99341c40..ffd88790 100644
--- a/Timeline.Tests/Controllers/UserDetailControllerTest.cs
+++ b/Timeline.Tests/Controllers/UserDetailControllerTest.cs
@@ -1,4 +1,5 @@
using FluentAssertions;
+using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Moq;
using System;
@@ -42,6 +43,8 @@ namespace Timeline.Tests.Controllers
var putNickname = typeof(UserDetailController).GetMethod(nameof(UserDetailController.PutNickname));
putNickname.Should().BeDecoratedWith<HttpPutAttribute>()
+ .And.BeDecoratedWith<AuthorizeAttribute>()
+ .And.BeDecoratedWith<SelfOrAdminAttribute>()
.And.BeDecoratedWith<CatchUserNotExistExceptionAttribute>();
putNickname.GetParameter("username").Should().BeDecoratedWith<UsernameAttribute>()
.And.BeDecoratedWith<FromRouteAttribute>();
@@ -53,6 +56,8 @@ namespace Timeline.Tests.Controllers
var deleteNickname = typeof(UserDetailController).GetMethod(nameof(UserDetailController.DeleteNickname));
deleteNickname.Should().BeDecoratedWith<HttpDeleteAttribute>()
+ .And.BeDecoratedWith<AuthorizeAttribute>()
+ .And.BeDecoratedWith<SelfOrAdminAttribute>()
.And.BeDecoratedWith<CatchUserNotExistExceptionAttribute>();
deleteNickname.GetParameter("username").Should().BeDecoratedWith<UsernameAttribute>()
.And.BeDecoratedWith<FromRouteAttribute>();
diff --git a/Timeline.Tests/Helpers/HttpClientExtensions.cs b/Timeline.Tests/Helpers/HttpClientExtensions.cs
index 38641f90..6513bbe7 100644
--- a/Timeline.Tests/Helpers/HttpClientExtensions.cs
+++ b/Timeline.Tests/Helpers/HttpClientExtensions.cs
@@ -35,5 +35,17 @@ namespace Timeline.Tests.Helpers
content.Headers.ContentType = new MediaTypeHeaderValue(mimeType);
return client.PutAsync(url, content);
}
+
+ public static Task<HttpResponseMessage> PutStringAsync(this HttpClient client, string url, string body, string mimeType = null)
+ {
+ return client.PutStringAsync(new Uri(url, UriKind.RelativeOrAbsolute), body, mimeType);
+ }
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope")]
+ public static Task<HttpResponseMessage> PutStringAsync(this HttpClient client, Uri url, string body, string mimeType = null)
+ {
+ var content = new StringContent(body, Encoding.UTF8, mimeType ?? MediaTypeNames.Text.Plain);
+ return client.PutAsync(url, content);
+ }
}
}
diff --git a/Timeline.Tests/Helpers/ResponseAssertions.cs b/Timeline.Tests/Helpers/ResponseAssertions.cs
index 08f10b2b..db86ff59 100644
--- a/Timeline.Tests/Helpers/ResponseAssertions.cs
+++ b/Timeline.Tests/Helpers/ResponseAssertions.cs
@@ -91,6 +91,11 @@ namespace Timeline.Tests.Helpers
var result = JsonConvert.DeserializeObject<T>(body);
return new AndWhichConstraint<HttpResponseMessage, T>(Subject, result);
}
+
+ internal void HaveStatusCode(object statusCode)
+ {
+ throw new NotImplementedException();
+ }
}
public static class AssertionResponseExtensions
diff --git a/Timeline.Tests/IntegratedTests/UserAvatarTest.cs b/Timeline.Tests/IntegratedTests/UserAvatarTest.cs
index ad2e11df..b338665e 100644
--- a/Timeline.Tests/IntegratedTests/UserAvatarTest.cs
+++ b/Timeline.Tests/IntegratedTests/UserAvatarTest.cs
@@ -22,12 +22,12 @@ using static Timeline.ErrorCodes.Http.UserAvatar;
namespace Timeline.Tests.IntegratedTests
{
- public class UserAvatarUnitTest : IClassFixture<WebApplicationFactory<Startup>>, IDisposable
+ public class UserAvatarTest : IClassFixture<WebApplicationFactory<Startup>>, IDisposable
{
private readonly TestApplication _testApp;
private readonly WebApplicationFactory<Startup> _factory;
- public UserAvatarUnitTest(WebApplicationFactory<Startup> factory)
+ public UserAvatarTest(WebApplicationFactory<Startup> factory)
{
_testApp = new TestApplication(factory);
_factory = _testApp.Factory;
diff --git a/Timeline.Tests/IntegratedTests/UserDetailTest.cs b/Timeline.Tests/IntegratedTests/UserDetailTest.cs
new file mode 100644
index 00000000..ff2c03a5
--- /dev/null
+++ b/Timeline.Tests/IntegratedTests/UserDetailTest.cs
@@ -0,0 +1,83 @@
+using FluentAssertions;
+using Microsoft.AspNetCore.Mvc.Testing;
+using System;
+using System.Net;
+using System.Threading.Tasks;
+using Timeline.Tests.Helpers;
+using Timeline.Tests.Helpers.Authentication;
+using Timeline.Tests.Mock.Data;
+using Xunit;
+
+namespace Timeline.Tests.IntegratedTests
+{
+ public class UserDetailTest : IClassFixture<WebApplicationFactory<Startup>>, IDisposable
+ {
+ private readonly TestApplication _testApp;
+ private readonly WebApplicationFactory<Startup> _factory;
+
+ public UserDetailTest(WebApplicationFactory<Startup> factory)
+ {
+ _testApp = new TestApplication(factory);
+ _factory = _testApp.Factory;
+ }
+
+ public void Dispose()
+ {
+ _testApp.Dispose();
+ }
+
+ [Fact]
+ public async Task PermissionTest()
+ {
+ { // unauthorize
+ using var client = _factory.CreateDefaultClient();
+ { // GET
+ var res = await client.GetAsync($"users/{MockUser.User.Username}/nickname");
+ res.Should().HaveStatusCode(HttpStatusCode.OK);
+ }
+ { // PUT
+ var res = await client.PutStringAsync($"users/{MockUser.User.Username}/nickname", "aaa");
+ res.Should().HaveStatusCode(HttpStatusCode.Unauthorized);
+ }
+ { // DELETE
+ var res = await client.DeleteAsync($"users/{MockUser.User.Username}/nickname");
+ res.Should().HaveStatusCode(HttpStatusCode.Unauthorized);
+ }
+ }
+ { // user
+ using var client = await _factory.CreateClientAsUser();
+ { // GET
+ var res = await client.GetAsync($"users/{MockUser.User.Username}/nickname");
+ res.Should().HaveStatusCode(HttpStatusCode.OK);
+ }
+ { // PUT self
+ var res = await client.PutStringAsync($"users/{MockUser.User.Username}/nickname", "aaa");
+ res.Should().HaveStatusCode(HttpStatusCode.OK);
+ }
+ { // PUT other
+ var res = await client.PutStringAsync($"users/{MockUser.Admin.Username}/nickname", "aaa");
+ res.Should().HaveStatusCode(HttpStatusCode.Forbidden);
+ }
+ { // DELETE self
+ var res = await client.DeleteAsync($"users/{MockUser.User.Username}/nickname");
+ res.Should().HaveStatusCode(HttpStatusCode.OK);
+ }
+ { // DELETE other
+ var res = await client.DeleteAsync($"users/{MockUser.Admin.Username}/nickname");
+ res.Should().HaveStatusCode(HttpStatusCode.Forbidden);
+ }
+ }
+ { // user
+ using var client = await _factory.CreateClientAsAdmin();
+ { // PUT other
+ var res = await client.PutStringAsync($"users/{MockUser.User.Username}/nickname", "aaa");
+ res.Should().HaveStatusCode(HttpStatusCode.OK);
+ }
+ { // DELETE other
+ var res = await client.DeleteAsync($"users/{MockUser.User.Username}/nickname");
+ res.Should().HaveStatusCode(HttpStatusCode.OK);
+ }
+ }
+ }
+ }
+}
diff --git a/Timeline.Tests/Properties/launchSettings.json b/Timeline.Tests/Properties/launchSettings.json
index 0c1cae5d..7a94d57a 100644
--- a/Timeline.Tests/Properties/launchSettings.json
+++ b/Timeline.Tests/Properties/launchSettings.json
@@ -1,27 +1,27 @@
-{
- "iisSettings": {
- "windowsAuthentication": false,
- "anonymousAuthentication": true,
- "iisExpress": {
- "applicationUrl": "http://localhost:11197/",
- "sslPort": 0
- }
- },
- "profiles": {
- "IIS Express": {
- "commandName": "IISExpress",
- "launchBrowser": true,
- "environmentVariables": {
- "ASPNETCORE_ENVIRONMENT": "Development"
- }
- },
- "Timeline.Tests": {
- "commandName": "Project",
- "launchBrowser": true,
- "environmentVariables": {
- "ASPNETCORE_ENVIRONMENT": "Development"
- },
- "applicationUrl": "https://localhost:11199/;http://localhost:11198/"
- }
- }
+{
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:52040/",
+ "sslPort": 0
+ }
+ },
+ "profiles": {
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": true,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "Timeline.Tests": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "applicationUrl": "http://localhost:52041/"
+ }
+ }
} \ No newline at end of file
diff --git a/Timeline.Tests/Timeline.Tests.csproj b/Timeline.Tests/Timeline.Tests.csproj
index 497a00b7..21e887eb 100644
--- a/Timeline.Tests/Timeline.Tests.csproj
+++ b/Timeline.Tests/Timeline.Tests.csproj
@@ -31,4 +31,8 @@
<ItemGroup>
<ProjectReference Include="..\Timeline\Timeline.csproj" />
</ItemGroup>
+
+ <ItemGroup>
+ <Folder Include="Properties\" />
+ </ItemGroup>
</Project>
diff --git a/Timeline/Authentication/Attribute.cs b/Timeline/Auth/Attribute.cs
index 370b37e1..86d0109b 100644
--- a/Timeline/Authentication/Attribute.cs
+++ b/Timeline/Auth/Attribute.cs
@@ -1,7 +1,7 @@
using Microsoft.AspNetCore.Authorization;
using Timeline.Entities;
-namespace Timeline.Authentication
+namespace Timeline.Auth
{
public class AdminAuthorizeAttribute : AuthorizeAttribute
{
diff --git a/Timeline/Authentication/AuthHandler.cs b/Timeline/Auth/MyAuthenticationHandler.cs
index 2b457eb1..f5dcd697 100644
--- a/Timeline/Authentication/AuthHandler.cs
+++ b/Timeline/Auth/MyAuthenticationHandler.cs
@@ -11,15 +11,15 @@ using Timeline.Models;
using Timeline.Services;
using static Timeline.Resources.Authentication.AuthHandler;
-namespace Timeline.Authentication
+namespace Timeline.Auth
{
- static class AuthConstants
+ public static class AuthenticationConstants
{
public const string Scheme = "Bearer";
public const string DisplayName = "My Jwt Auth Scheme";
}
- public class AuthOptions : AuthenticationSchemeOptions
+ public class MyAuthenticationOptions : AuthenticationSchemeOptions
{
/// <summary>
/// The query param key to search for token. If null then query params are not searched for token. Default to <c>"token"</c>.
@@ -27,15 +27,15 @@ namespace Timeline.Authentication
public string TokenQueryParamKey { get; set; } = "token";
}
- public class AuthHandler : AuthenticationHandler<AuthOptions>
+ public class MyAuthenticationHandler : AuthenticationHandler<MyAuthenticationOptions>
{
- private readonly ILogger<AuthHandler> _logger;
+ private readonly ILogger<MyAuthenticationHandler> _logger;
private readonly IUserService _userService;
- public AuthHandler(IOptionsMonitor<AuthOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, IUserService userService)
+ public MyAuthenticationHandler(IOptionsMonitor<MyAuthenticationOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, IUserService userService)
: base(options, logger, encoder, clock)
{
- _logger = logger.CreateLogger<AuthHandler>();
+ _logger = logger.CreateLogger<MyAuthenticationHandler>();
_userService = userService;
}
@@ -80,14 +80,14 @@ namespace Timeline.Authentication
{
var userInfo = await _userService.VerifyToken(token);
- var identity = new ClaimsIdentity(AuthConstants.Scheme);
+ var identity = new ClaimsIdentity(AuthenticationConstants.Scheme);
identity.AddClaim(new Claim(identity.NameClaimType, userInfo.Username, ClaimValueTypes.String));
identity.AddClaims(UserRoleConvert.ToArray(userInfo.Administrator).Select(role => new Claim(identity.RoleClaimType, role, ClaimValueTypes.String)));
var principal = new ClaimsPrincipal();
principal.AddIdentity(identity);
- return AuthenticateResult.Success(new AuthenticationTicket(principal, AuthConstants.Scheme));
+ return AuthenticateResult.Success(new AuthenticationTicket(principal, AuthenticationConstants.Scheme));
}
catch (Exception e) when (!(e is ArgumentException))
{
diff --git a/Timeline/Authentication/PrincipalExtensions.cs b/Timeline/Auth/PrincipalExtensions.cs
index 8d77ab62..ad7a887f 100644
--- a/Timeline/Authentication/PrincipalExtensions.cs
+++ b/Timeline/Auth/PrincipalExtensions.cs
@@ -1,7 +1,7 @@
using System.Security.Principal;
using Timeline.Entities;
-namespace Timeline.Authentication
+namespace Timeline.Auth
{
internal static class PrincipalExtensions
{
diff --git a/Timeline/Controllers/Testing/TestingAuthController.cs b/Timeline/Controllers/Testing/TestingAuthController.cs
index 67b5b2ef..4d3b3ec7 100644
--- a/Timeline/Controllers/Testing/TestingAuthController.cs
+++ b/Timeline/Controllers/Testing/TestingAuthController.cs
@@ -1,6 +1,6 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
-using Timeline.Authentication;
+using Timeline.Auth;
namespace Timeline.Controllers.Testing
{
diff --git a/Timeline/Controllers/UserAvatarController.cs b/Timeline/Controllers/UserAvatarController.cs
index 7c77897d..7625f962 100644
--- a/Timeline/Controllers/UserAvatarController.cs
+++ b/Timeline/Controllers/UserAvatarController.cs
@@ -6,7 +6,7 @@ using Microsoft.Net.Http.Headers;
using System;
using System.Linq;
using System.Threading.Tasks;
-using Timeline.Authentication;
+using Timeline.Auth;
using Timeline.Filters;
using Timeline.Helpers;
using Timeline.Models.Http;
diff --git a/Timeline/Controllers/UserController.cs b/Timeline/Controllers/UserController.cs
index 7b441c3a..0d950cd7 100644
--- a/Timeline/Controllers/UserController.cs
+++ b/Timeline/Controllers/UserController.cs
@@ -3,7 +3,7 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System.Globalization;
using System.Threading.Tasks;
-using Timeline.Authentication;
+using Timeline.Auth;
using Timeline.Helpers;
using Timeline.Models;
using Timeline.Models.Http;
diff --git a/Timeline/Controllers/UserDetailController.cs b/Timeline/Controllers/UserDetailController.cs
index ef13b462..9de9899e 100644
--- a/Timeline/Controllers/UserDetailController.cs
+++ b/Timeline/Controllers/UserDetailController.cs
@@ -4,6 +4,7 @@ using Timeline.Filters;
using Timeline.Models.Validation;
using Timeline.Services;
using System.ComponentModel.DataAnnotations;
+using Microsoft.AspNetCore.Authorization;
namespace Timeline.Controllers
{
@@ -25,6 +26,8 @@ namespace Timeline.Controllers
}
[HttpPut("users/{username}/nickname")]
+ [Authorize]
+ [SelfOrAdmin]
[CatchUserNotExistException]
public async Task<ActionResult> PutNickname([FromRoute][Username] string username,
[FromBody][StringLength(10, MinimumLength = 1)] string body)
@@ -34,6 +37,8 @@ namespace Timeline.Controllers
}
[HttpDelete("users/{username}/nickname")]
+ [Authorize]
+ [SelfOrAdmin]
[CatchUserNotExistException]
public async Task<ActionResult> DeleteNickname([FromRoute][Username] string username)
{
diff --git a/Timeline/Filters/User.cs b/Timeline/Filters/User.cs
index 22fae938..16c76750 100644
--- a/Timeline/Filters/User.cs
+++ b/Timeline/Filters/User.cs
@@ -1,7 +1,13 @@
-using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
using System;
+using Timeline.Auth;
using Timeline.Models.Http;
+using Timeline.Services;
+using static Timeline.Resources.Filters;
namespace Timeline
{
@@ -13,9 +19,10 @@ namespace Timeline
{
public static class User // bbb = 101
{
- public const int NotExist = 11010001;
- }
+ public const int NotExist = 11010101;
+ public const int NotSelfOrAdminForbid = 11010201;
+ }
}
}
}
@@ -23,20 +30,59 @@ namespace Timeline
namespace Timeline.Filters
{
+ public class SelfOrAdminAttribute : ActionFilterAttribute
+ {
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods")]
+ public override void OnActionExecuting(ActionExecutingContext context)
+ {
+ var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<SelfOrAdminAttribute>>();
+
+ var user = context.HttpContext.User;
+
+ if (user == null)
+ {
+ logger.LogError(LogSelfOrAdminNoUser);
+ return;
+ }
+
+ if (context.ModelState.TryGetValue("username", out var model))
+ {
+ if (model.RawValue is string username)
+ {
+ if (!user.IsAdministrator() && user.Identity.Name != username)
+ {
+ context.Result = new ObjectResult(
+ new CommonResponse(ErrorCodes.Http.Filter.User.NotSelfOrAdminForbid, MessageSelfOrAdminForbid))
+ { StatusCode = StatusCodes.Status403Forbidden };
+ }
+ }
+ else
+ {
+ logger.LogError(LogSelfOrAdminUsernameNotString);
+ }
+ }
+ else
+ {
+ logger.LogError(LogSelfOrAdminNoUsername);
+ }
+ }
+ }
+
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class CatchUserNotExistExceptionAttribute : ExceptionFilterAttribute
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "ASP.Net already checked.")]
public override void OnException(ExceptionContext context)
{
- var body = new CommonResponse(
- ErrorCodes.Http.Filter.User.NotExist,
- Resources.Filters.MessageUserNotExist);
+ if (context.Exception is UserNotExistException)
+ {
+ var body = new CommonResponse(ErrorCodes.Http.Filter.User.NotExist, MessageUserNotExist);
- if (context.HttpContext.Request.Method == "GET")
- context.Result = new NotFoundObjectResult(body);
- else
- context.Result = new BadRequestObjectResult(body);
+ if (context.HttpContext.Request.Method == "GET")
+ context.Result = new NotFoundObjectResult(body);
+ else
+ context.Result = new BadRequestObjectResult(body);
+ }
}
}
}
diff --git a/Timeline/Formatters/StringInputFormatter.cs b/Timeline/Formatters/StringInputFormatter.cs
new file mode 100644
index 00000000..90847e36
--- /dev/null
+++ b/Timeline/Formatters/StringInputFormatter.cs
@@ -0,0 +1,27 @@
+using Microsoft.AspNetCore.Mvc.Formatters;
+using Microsoft.Net.Http.Headers;
+using System.IO;
+using System.Net.Mime;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Timeline.Formatters
+{
+ public class StringInputFormatter : TextInputFormatter
+ {
+ public StringInputFormatter()
+ {
+ SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse(MediaTypeNames.Text.Plain));
+ SupportedEncodings.Add(Encoding.UTF8);
+ }
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods")]
+ public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context, Encoding effectiveEncoding)
+ {
+ var request = context.HttpContext.Request;
+ using var reader = new StreamReader(request.Body, effectiveEncoding);
+ var stringContent = await reader.ReadToEndAsync();
+ return await InputFormatterResult.SuccessAsync(stringContent);
+ }
+ }
+}
diff --git a/Timeline/Resources/Filters.Designer.cs b/Timeline/Resources/Filters.Designer.cs
index e3c8be41..3481e4ae 100644
--- a/Timeline/Resources/Filters.Designer.cs
+++ b/Timeline/Resources/Filters.Designer.cs
@@ -61,6 +61,33 @@ namespace Timeline.Resources {
}
/// <summary>
+ /// Looks up a localized string similar to You apply a SelfOrAdminAttribute on an action, but there is no user. Try add AuthorizeAttribute..
+ /// </summary>
+ internal static string LogSelfOrAdminNoUser {
+ get {
+ return ResourceManager.GetString("LogSelfOrAdminNoUser", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to You apply a SelfOrAdminAttribute on an action, but it does not have a model named username..
+ /// </summary>
+ internal static string LogSelfOrAdminNoUsername {
+ get {
+ return ResourceManager.GetString("LogSelfOrAdminNoUsername", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to You apply a SelfOrAdminAttribute on an action, found a model named username, but it is not string..
+ /// </summary>
+ internal static string LogSelfOrAdminUsernameNotString {
+ get {
+ return ResourceManager.GetString("LogSelfOrAdminUsernameNotString", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to Header Content-Length is missing or of bad format..
/// </summary>
internal static string MessageHeaderContentLengthMissing {
@@ -88,6 +115,15 @@ namespace Timeline.Resources {
}
/// <summary>
+ /// Looks up a localized string similar to You can&apos;t access the resource unless you are the owner or administrator..
+ /// </summary>
+ internal static string MessageSelfOrAdminForbid {
+ get {
+ return ResourceManager.GetString("MessageSelfOrAdminForbid", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to The user does not exist..
/// </summary>
internal static string MessageUserNotExist {
diff --git a/Timeline/Resources/Filters.resx b/Timeline/Resources/Filters.resx
index ba1fcee8..b91d4612 100644
--- a/Timeline/Resources/Filters.resx
+++ b/Timeline/Resources/Filters.resx
@@ -117,6 +117,15 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
+ <data name="LogSelfOrAdminNoUser" xml:space="preserve">
+ <value>You apply a SelfOrAdminAttribute on an action, but there is no user. Try add AuthorizeAttribute.</value>
+ </data>
+ <data name="LogSelfOrAdminNoUsername" xml:space="preserve">
+ <value>You apply a SelfOrAdminAttribute on an action, but it does not have a model named username.</value>
+ </data>
+ <data name="LogSelfOrAdminUsernameNotString" xml:space="preserve">
+ <value>You apply a SelfOrAdminAttribute on an action, found a model named username, but it is not string.</value>
+ </data>
<data name="MessageHeaderContentLengthMissing" xml:space="preserve">
<value>Header Content-Length is missing or of bad format.</value>
</data>
@@ -126,6 +135,9 @@
<data name="MessageHeaderContentTypeMissing" xml:space="preserve">
<value>Header Content-Type is required.</value>
</data>
+ <data name="MessageSelfOrAdminForbid" xml:space="preserve">
+ <value>You can't access the resource unless you are the owner or administrator.</value>
+ </data>
<data name="MessageUserNotExist" xml:space="preserve">
<value>The user does not exist.</value>
</data>
diff --git a/Timeline/Resources/Filters.zh.resx b/Timeline/Resources/Filters.zh.resx
index 690a3e39..159ac04a 100644
--- a/Timeline/Resources/Filters.zh.resx
+++ b/Timeline/Resources/Filters.zh.resx
@@ -126,6 +126,9 @@
<data name="MessageHeaderContentTypeMissing" xml:space="preserve">
<value>缺少必需的请求头Content-Type。</value>
</data>
+ <data name="MessageSelfOrAdminForbid" xml:space="preserve">
+ <value>你无权访问该资源除非你是资源的拥有者或者管理员。</value>
+ </data>
<data name="MessageUserNotExist" xml:space="preserve">
<value>用户不存在。</value>
</data>
diff --git a/Timeline/Startup.cs b/Timeline/Startup.cs
index b44add6f..f6abf36d 100644
--- a/Timeline/Startup.cs
+++ b/Timeline/Startup.cs
@@ -8,9 +8,10 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System.Collections.Generic;
using System.Globalization;
-using Timeline.Authentication;
+using Timeline.Auth;
using Timeline.Configs;
using Timeline.Entities;
+using Timeline.Formatters;
using Timeline.Helpers;
using Timeline.Services;
@@ -31,17 +32,22 @@ namespace Timeline
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
- services.AddControllers()
+ services.AddControllers(setup =>
+ {
+ setup.InputFormatters.Add(new StringInputFormatter());
+ })
.ConfigureApiBehaviorOptions(options =>
{
options.InvalidModelStateResponseFactory = InvalidModelResponseFactory.Factory;
})
- .AddNewtonsoftJson();
+ .AddNewtonsoftJson(); // TODO: Remove this.
services.Configure<JwtConfig>(Configuration.GetSection(nameof(JwtConfig)));
var jwtConfig = Configuration.GetSection(nameof(JwtConfig)).Get<JwtConfig>();
- services.AddAuthentication(AuthConstants.Scheme)
- .AddScheme<AuthOptions, AuthHandler>(AuthConstants.Scheme, AuthConstants.DisplayName, o => { });
+ services.AddAuthentication(AuthenticationConstants.Scheme)
+ .AddScheme<MyAuthenticationOptions, MyAuthenticationHandler>(AuthenticationConstants.Scheme, AuthenticationConstants.DisplayName, o => { });
+ services.AddAuthorization();
+
var corsConfig = Configuration.GetSection("Cors").Get<string[]>();
services.AddCors(setup =>
@@ -62,8 +68,8 @@ namespace Timeline
services.AddScoped<IJwtService, JwtService>();
services.AddTransient<IPasswordService, PasswordService>();
services.AddTransient<IClock, Clock>();
-
services.AddUserAvatarService();
+ services.AddScoped<IUserDetailService, UserDetailService>();
var databaseConfig = Configuration.GetSection(nameof(DatabaseConfig)).Get<DatabaseConfig>();