From eaf6d126db56b7caedd311033403b1f721bb80bc Mon Sep 17 00:00:00 2001 From: crupest Date: Fri, 31 Jan 2020 00:10:23 +0800 Subject: ... --- .../IntegratedTests/PersonalTimelineTest.cs | 75 +++++++++++- Timeline.Tests/IntegratedTests/UserAvatarTest.cs | 18 +-- Timeline/Controllers/ControllerAuthExtensions.cs | 15 +-- Timeline/Filters/Timeline.cs | 2 +- Timeline/Models/Http/UserInfo.cs | 8 +- .../ControllerAuthExtensions.Designer.cs | 81 +++++++++++++ .../Controllers/ControllerAuthExtensions.resx | 126 +++++++++++++++++++++ Timeline/Services/TimelineService.cs | 3 + Timeline/Timeline.csproj | 9 ++ 9 files changed, 311 insertions(+), 26 deletions(-) create mode 100644 Timeline/Resources/Controllers/ControllerAuthExtensions.Designer.cs create mode 100644 Timeline/Resources/Controllers/ControllerAuthExtensions.resx diff --git a/Timeline.Tests/IntegratedTests/PersonalTimelineTest.cs b/Timeline.Tests/IntegratedTests/PersonalTimelineTest.cs index d787d87d..dacfea62 100644 --- a/Timeline.Tests/IntegratedTests/PersonalTimelineTest.cs +++ b/Timeline.Tests/IntegratedTests/PersonalTimelineTest.cs @@ -30,6 +30,74 @@ namespace Timeline.Tests.IntegratedTests body.Visibility.Should().Be(TimelineVisibility.Register); body.Description.Should().Be(""); body.Members.Should().NotBeNull().And.BeEmpty(); + } + + [Fact] + public async Task InvalidModel_BadUsername() + { + using var client = await CreateClientAsAdministrator(); + { + var res = await client.GetAsync("users/user!!!/timeline"); + res.Should().BeInvalidModel(); + } + { + var res = await client.PatchAsJsonAsync("users/user!!!/timeline", new TimelinePatchRequest { }); + res.Should().BeInvalidModel(); + } + { + var res = await client.PutAsync("users/user!!!/timeline/members/user1", null); + res.Should().BeInvalidModel(); + } + { + var res = await client.DeleteAsync("users/user!!!/timeline/members/user1"); + res.Should().BeInvalidModel(); + } + { + var res = await client.GetAsync("users/user!!!/timeline/posts"); + res.Should().BeInvalidModel(); + } + { + var res = await client.PostAsJsonAsync("users/user!!!/timeline/posts", new TimelinePostCreateRequest { Content = "aaa" }); + res.Should().BeInvalidModel(); + } + { + var res = await client.DeleteAsync("users/user!!!/timeline/posts/123"); + res.Should().BeInvalidModel(); + } + } + + [Fact] + public async Task NotFound() + { + using var client = await CreateClientAsAdministrator(); + { + var res = await client.GetAsync("users/usernotexist/timeline"); + res.Should().HaveStatusCode(404).And.HaveCommonBody(ErrorCodes.UserCommon.NotExist); + } + { + var res = await client.PatchAsJsonAsync("users/usernotexist/timeline", new TimelinePatchRequest { }); + res.Should().HaveStatusCode(404).And.HaveCommonBody(ErrorCodes.UserCommon.NotExist); + } + { + var res = await client.PutAsync("users/usernotexist/timeline/members/user1", null); + res.Should().HaveStatusCode(404).And.HaveCommonBody(ErrorCodes.UserCommon.NotExist); + } + { + var res = await client.DeleteAsync("users/usernotexist/timeline/members/user1"); + res.Should().HaveStatusCode(404).And.HaveCommonBody(ErrorCodes.UserCommon.NotExist); + } + { + var res = await client.GetAsync("users/usernotexist/timeline/posts"); + res.Should().HaveStatusCode(404).And.HaveCommonBody(ErrorCodes.UserCommon.NotExist); + } + { + var res = await client.PostAsJsonAsync("users/usernotexist/timeline/posts", new TimelinePostCreateRequest { Content = "aaa" }); + res.Should().HaveStatusCode(404).And.HaveCommonBody(ErrorCodes.UserCommon.NotExist); + } + { + var res = await client.DeleteAsync("users/usernotexist/timeline/posts/123"); + res.Should().HaveStatusCode(404).And.HaveCommonBody(ErrorCodes.UserCommon.NotExist); + } } [Fact] @@ -162,10 +230,11 @@ namespace Timeline.Tests.IntegratedTests { const string userUrl = "users/user1/timeline/posts"; const string adminUrl = "users/admin/timeline/posts"; - { + { + using var client = await CreateClientAsUser(); - var res = await client.PatchAsync("users/user1/timeline", - new StringContent(@"{""visibility"":""abcdefg""}", System.Text.Encoding.UTF8, System.Net.Mime.MediaTypeNames.Application.Json)); + using var content = new StringContent(@"{""visibility"":""abcdefg""}", System.Text.Encoding.UTF8, System.Net.Mime.MediaTypeNames.Application.Json); + var res = await client.PatchAsync("users/user1/timeline", content); res.Should().BeInvalidModel(); } { // default visibility is registered diff --git a/Timeline.Tests/IntegratedTests/UserAvatarTest.cs b/Timeline.Tests/IntegratedTests/UserAvatarTest.cs index 67c2dd9a..fa0120f1 100644 --- a/Timeline.Tests/IntegratedTests/UserAvatarTest.cs +++ b/Timeline.Tests/IntegratedTests/UserAvatarTest.cs @@ -75,7 +75,7 @@ namespace Timeline.Tests.IntegratedTests await GetReturnDefault("admin"); { - var request = new HttpRequestMessage() + using var request = new HttpRequestMessage() { RequestUri = new Uri(client.BaseAddress, "users/user1/avatar"), Method = HttpMethod.Get, @@ -87,7 +87,7 @@ namespace Timeline.Tests.IntegratedTests } { - var request = new HttpRequestMessage() + using var request = new HttpRequestMessage() { RequestUri = new Uri(client.BaseAddress, "users/user1/avatar"), Method = HttpMethod.Get, @@ -98,7 +98,7 @@ namespace Timeline.Tests.IntegratedTests } { - var request = new HttpRequestMessage() + using var request = new HttpRequestMessage() { RequestUri = new Uri(client.BaseAddress, "users/user1/avatar"), Method = HttpMethod.Get, @@ -109,7 +109,7 @@ namespace Timeline.Tests.IntegratedTests } { - var content = new ByteArrayContent(new[] { (byte)0x00 }); + using var content = new ByteArrayContent(new[] { (byte)0x00 }); content.Headers.ContentType = new MediaTypeHeaderValue("image/png"); var res = await client.PutAsync("users/user1/avatar", content); res.Should().HaveStatusCode(HttpStatusCode.BadRequest) @@ -117,7 +117,7 @@ namespace Timeline.Tests.IntegratedTests } { - var content = new ByteArrayContent(new[] { (byte)0x00 }); + using var content = new ByteArrayContent(new[] { (byte)0x00 }); content.Headers.ContentLength = 1; var res = await client.PutAsync("users/user1/avatar", content); res.Should().HaveStatusCode(HttpStatusCode.BadRequest) @@ -125,7 +125,7 @@ namespace Timeline.Tests.IntegratedTests } { - var content = new ByteArrayContent(new[] { (byte)0x00 }); + using var content = new ByteArrayContent(new[] { (byte)0x00 }); content.Headers.ContentLength = 0; content.Headers.ContentType = new MediaTypeHeaderValue("image/png"); var res = await client.PutAsync("users/user1/avatar", content); @@ -139,7 +139,7 @@ namespace Timeline.Tests.IntegratedTests } { - var content = new ByteArrayContent(new[] { (byte)0x00 }); + using var content = new ByteArrayContent(new[] { (byte)0x00 }); content.Headers.ContentLength = 1000 * 1000 * 11; content.Headers.ContentType = new MediaTypeHeaderValue("image/png"); var res = await client.PutAsync("users/user1/avatar", content); @@ -148,7 +148,7 @@ namespace Timeline.Tests.IntegratedTests } { - var content = new ByteArrayContent(new[] { (byte)0x00 }); + using var content = new ByteArrayContent(new[] { (byte)0x00 }); content.Headers.ContentLength = 2; content.Headers.ContentType = new MediaTypeHeaderValue("image/png"); var res = await client.PutAsync("users/user1/avatar", content); @@ -157,7 +157,7 @@ namespace Timeline.Tests.IntegratedTests } { - var content = new ByteArrayContent(new[] { (byte)0x00, (byte)0x01 }); + using var content = new ByteArrayContent(new[] { (byte)0x00, (byte)0x01 }); content.Headers.ContentLength = 1; content.Headers.ContentType = new MediaTypeHeaderValue("image/png"); var res = await client.PutAsync("users/user1/avatar", content); diff --git a/Timeline/Controllers/ControllerAuthExtensions.cs b/Timeline/Controllers/ControllerAuthExtensions.cs index 34fd4d99..00a65454 100644 --- a/Timeline/Controllers/ControllerAuthExtensions.cs +++ b/Timeline/Controllers/ControllerAuthExtensions.cs @@ -1,7 +1,8 @@ using Microsoft.AspNetCore.Mvc; +using System; using System.Security.Claims; using Timeline.Auth; -using System; +using static Timeline.Resources.Controllers.ControllerAuthExtensions; namespace Timeline.Controllers { @@ -14,24 +15,18 @@ namespace Timeline.Controllers public static long GetUserId(this ControllerBase controller) { - if (controller.User == null) - throw new InvalidOperationException("Failed to get user id because User is null."); - var claim = controller.User.FindFirst(ClaimTypes.NameIdentifier); if (claim == null) - throw new InvalidOperationException("Failed to get user id because User has no NameIdentifier claim."); + throw new InvalidOperationException(ExceptionNoUserIdentifierClaim); if (long.TryParse(claim.Value, out var value)) return value; - throw new InvalidOperationException("Failed to get user id because NameIdentifier claim is not a number."); + throw new InvalidOperationException(ExceptionUserIdentifierClaimBadFormat); } public static long? GetOptionalUserId(this ControllerBase controller) { - if (controller.User == null) - return null; - var claim = controller.User.FindFirst(ClaimTypes.NameIdentifier); if (claim == null) return null; @@ -39,7 +34,7 @@ namespace Timeline.Controllers if (long.TryParse(claim.Value, out var value)) return value; - throw new InvalidOperationException("Failed to get user id because NameIdentifier claim is not a number."); + throw new InvalidOperationException(ExceptionUserIdentifierClaimBadFormat); } } } diff --git a/Timeline/Filters/Timeline.cs b/Timeline/Filters/Timeline.cs index 729dbec7..ed78e645 100644 --- a/Timeline/Filters/Timeline.cs +++ b/Timeline/Filters/Timeline.cs @@ -13,7 +13,7 @@ namespace Timeline.Filters { if (e.InnerException is UserNotExistException) { - context.Result = new BadRequestObjectResult(ErrorResponse.UserCommon.NotExist()); + context.Result = new NotFoundObjectResult(ErrorResponse.UserCommon.NotExist()); } else { diff --git a/Timeline/Models/Http/UserInfo.cs b/Timeline/Models/Http/UserInfo.cs index 62d989a2..07ac0aad 100644 --- a/Timeline/Models/Http/UserInfo.cs +++ b/Timeline/Models/Http/UserInfo.cs @@ -31,11 +31,13 @@ namespace Timeline.Models.Http public class UserInfoAvatarUrlValueResolver : IValueResolver { - private readonly IActionContextAccessor _actionContextAccessor; - private readonly IUrlHelperFactory _urlHelperFactory; + private readonly IActionContextAccessor? _actionContextAccessor; + private readonly IUrlHelperFactory? _urlHelperFactory; public UserInfoAvatarUrlValueResolver() { + _actionContextAccessor = null; + _urlHelperFactory = null; } public UserInfoAvatarUrlValueResolver(IActionContextAccessor actionContextAccessor, IUrlHelperFactory urlHelperFactory) @@ -51,7 +53,7 @@ namespace Timeline.Models.Http return $"/users/{destination.Username}/avatar"; } - var urlHelper = _urlHelperFactory.GetUrlHelper(_actionContextAccessor.ActionContext); + var urlHelper = _urlHelperFactory!.GetUrlHelper(_actionContextAccessor.ActionContext); return urlHelper.ActionLink(nameof(UserAvatarController.Get), nameof(UserAvatarController), new { destination.Username }); } } diff --git a/Timeline/Resources/Controllers/ControllerAuthExtensions.Designer.cs b/Timeline/Resources/Controllers/ControllerAuthExtensions.Designer.cs new file mode 100644 index 00000000..70a1d605 --- /dev/null +++ b/Timeline/Resources/Controllers/ControllerAuthExtensions.Designer.cs @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Timeline.Resources.Controllers { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class ControllerAuthExtensions { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal ControllerAuthExtensions() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Timeline.Resources.Controllers.ControllerAuthExtensions", typeof(ControllerAuthExtensions).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Failed to get user id because User has no NameIdentifier claim.. + /// + internal static string ExceptionNoUserIdentifierClaim { + get { + return ResourceManager.GetString("ExceptionNoUserIdentifierClaim", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Failed to get user id because NameIdentifier claim is not a number.. + /// + internal static string ExceptionUserIdentifierClaimBadFormat { + get { + return ResourceManager.GetString("ExceptionUserIdentifierClaimBadFormat", resourceCulture); + } + } + } +} diff --git a/Timeline/Resources/Controllers/ControllerAuthExtensions.resx b/Timeline/Resources/Controllers/ControllerAuthExtensions.resx new file mode 100644 index 00000000..03e6d95a --- /dev/null +++ b/Timeline/Resources/Controllers/ControllerAuthExtensions.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Failed to get user id because User has no NameIdentifier claim. + + + Failed to get user id because NameIdentifier claim is not a number. + + \ No newline at end of file diff --git a/Timeline/Services/TimelineService.cs b/Timeline/Services/TimelineService.cs index 16402f3e..85445973 100644 --- a/Timeline/Services/TimelineService.cs +++ b/Timeline/Services/TimelineService.cs @@ -348,6 +348,9 @@ namespace Timeline.Services if (name == null) throw new ArgumentNullException(nameof(name)); + // Currently we don't use the result. But we need to check the timeline. + var _ = await FindTimelineId(name); + var post = await Database.TimelinePosts.Where(p => p.Id == id).SingleOrDefaultAsync(); if (post == null) diff --git a/Timeline/Timeline.csproj b/Timeline/Timeline.csproj index 25d73068..1a3a07cd 100644 --- a/Timeline/Timeline.csproj +++ b/Timeline/Timeline.csproj @@ -44,6 +44,11 @@ True AuthHandler.resx + + True + True + ControllerAuthExtensions.resx + True True @@ -121,6 +126,10 @@ ResXFileCodeGenerator AuthHandler.Designer.cs + + ResXFileCodeGenerator + ControllerAuthExtensions.Designer.cs + ResXFileCodeGenerator TimelineController.Designer.cs -- cgit v1.2.3