From 5bfab13dc5a268714aad2426a2b68ab5561a6407 Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Sun, 26 Sep 2021 20:42:27 +1000 Subject: [PATCH] Fixes #11189 - protected content not working (#11193) * Fixes #11189 * Fixes #11183 * Fix test Null_When_No_Content_On_PublishedRequest. Believe this is reasonable. * Update src/Umbraco.Web.Website/Routing/PublicAccessRequestHandler.cs Co-authored-by: Paul Johnson Co-authored-by: Bjarke Berg --- .../UmbracoRouteValueTransformerTests.cs | 5 ++-- .../Security/ConfigureMemberCookieOptions.cs | 15 ++++++++++++ .../Routing/PublicAccessRequestHandler.cs | 15 ++++++++++++ .../Routing/UmbracoRouteValueTransformer.cs | 24 ++++++++++--------- 4 files changed, 46 insertions(+), 13 deletions(-) diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformerTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformerTests.cs index 11a24230516a..be439dc9baf4 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformerTests.cs @@ -163,7 +163,7 @@ public async Task Assigns_PublishedRequest_To_UmbracoContext() public async Task Null_When_No_Content_On_PublishedRequest() { IUmbracoContext umbracoContext = GetUmbracoContext(true); - IPublishedRequest request = Mock.Of(); + IPublishedRequest request = Mock.Of(x => x.PublishedContent == null); UmbracoRouteValueTransformer transformer = GetTransformerWithRunState( Mock.Of(x => x.TryGetUmbracoContext(out umbracoContext)), @@ -172,9 +172,10 @@ public async Task Null_When_No_Content_On_PublishedRequest() var httpContext = new DefaultHttpContext(); RouteValueDictionary result = await transformer.TransformAsync(httpContext, new RouteValueDictionary()); + Assert.IsNull(result); UmbracoRouteValues routeVals = httpContext.Features.Get(); - Assert.IsNull(routeVals); + Assert.AreEqual(routeVals.PublishedRequest.GetRouteResult(), UmbracoRouteResult.NotFound); } [Test] diff --git a/src/Umbraco.Web.Common/Security/ConfigureMemberCookieOptions.cs b/src/Umbraco.Web.Common/Security/ConfigureMemberCookieOptions.cs index ba5f0621b9f4..c4649611d3b0 100644 --- a/src/Umbraco.Web.Common/Security/ConfigureMemberCookieOptions.cs +++ b/src/Umbraco.Web.Common/Security/ConfigureMemberCookieOptions.cs @@ -1,8 +1,10 @@ +using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Routing; using Umbraco.Cms.Core.Services; +using Umbraco.Extensions; namespace Umbraco.Cms.Web.Common.Security { @@ -34,6 +36,19 @@ public void Configure(CookieAuthenticationOptions options) options.LogoutPath = null; options.CookieManager = new MemberCookieManager(_runtimeState, _umbracoRequestPaths); + + options.Events = new CookieAuthenticationEvents + { + OnSignedIn = ctx => + { + // occurs when sign in is successful and after the ticket is written to the outbound cookie + + // When we are signed in with the cookie, assign the principal to the current HttpContext + ctx.HttpContext.SetPrincipalForRequest(ctx.Principal); + + return Task.CompletedTask; + } + }; } } } diff --git a/src/Umbraco.Web.Website/Routing/PublicAccessRequestHandler.cs b/src/Umbraco.Web.Website/Routing/PublicAccessRequestHandler.cs index 88bb6622bd61..d70bc6ce5d72 100644 --- a/src/Umbraco.Web.Website/Routing/PublicAccessRequestHandler.cs +++ b/src/Umbraco.Web.Website/Routing/PublicAccessRequestHandler.cs @@ -1,6 +1,8 @@ using System; using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Models; @@ -65,6 +67,19 @@ public async Task RewriteForPublishedContentAccessAsync(Http { _logger.LogDebug("EnsurePublishedContentAccess: Page is protected, check for access"); + // manually authenticate the request + AuthenticateResult authResult = await httpContext.AuthenticateAsync(IdentityConstants.ApplicationScheme); + if (authResult.Succeeded) + { + // set the user to the auth result. we need to do this here because this occurs + // before the authentication middleware. + // NOTE: It would be possible to just pass the authResult to the HasMemberAccessToContentAsync method + // instead of relying directly on the user assigned to the http context, and then the auth middleware + // will run anyways and assign the user. Perhaps that is a little cleaner, but would require more code + // changes right now, and really it's not any different in the end result. + httpContext.SetPrincipalForRequest(authResult.Principal); + } + publicAccessStatus = await _publicAccessChecker.HasMemberAccessToContentAsync(publishedContent.Id); switch (publicAccessStatus) { diff --git a/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs b/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs index 0bca8d7215b4..717a6b490a27 100644 --- a/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs +++ b/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs @@ -130,17 +130,7 @@ public override async ValueTask TransformAsync(HttpContext IPublishedRequest publishedRequest = await RouteRequestAsync(umbracoContext); - umbracoRouteValues = await _routeValuesFactory.CreateAsync(httpContext, publishedRequest); - - if (!umbracoRouteValues?.PublishedRequest?.HasPublishedContent() ?? false) - { - // No content was found, not by any registered 404 handlers and - // not by the IContentLastChanceFinder. In this case we want to return - // our default 404 page but we cannot return route values now because - // it's possible that a developer is handling dynamic routes too. - // Our 404 page will be handled with the NotFoundSelectorPolicy - return null; - } + umbracoRouteValues = await _routeValuesFactory.CreateAsync(httpContext, publishedRequest); // now we need to do some public access checks umbracoRouteValues = await _publicAccessRequestHandler.RewriteForPublishedContentAccessAsync(httpContext, umbracoRouteValues); @@ -155,6 +145,18 @@ public override async ValueTask TransformAsync(HttpContext return HandlePostedValues(postedInfo, httpContext); } + UmbracoRouteResult? routeResult = umbracoRouteValues?.PublishedRequest?.GetRouteResult(); + + if (!routeResult.HasValue || routeResult == UmbracoRouteResult.NotFound) + { + // No content was found, not by any registered 404 handlers and + // not by the IContentLastChanceFinder. In this case we want to return + // our default 404 page but we cannot return route values now because + // it's possible that a developer is handling dynamic routes too. + // Our 404 page will be handled with the NotFoundSelectorPolicy + return null; + } + // See https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.routing.dynamicroutevaluetransformer.transformasync?view=aspnetcore-5.0#Microsoft_AspNetCore_Mvc_Routing_DynamicRouteValueTransformer_TransformAsync_Microsoft_AspNetCore_Http_HttpContext_Microsoft_AspNetCore_Routing_RouteValueDictionary_ // We should apparenlty not be modified these values. // So we create new ones.