U4-4219 Can't Preview protected pages
This commit is contained in:
@@ -103,10 +103,18 @@ namespace Umbraco.Core.Security
|
||||
var backOfficeIdentity = http.User.Identity as UmbracoBackOfficeIdentity;
|
||||
if (backOfficeIdentity != null) return backOfficeIdentity;
|
||||
|
||||
//Check if there's more than one identity assigned and see if it's a UmbracoBackOfficeIdentity and use that
|
||||
var claimsPrincipal = http.User as ClaimsPrincipal;
|
||||
if (claimsPrincipal != null)
|
||||
{
|
||||
backOfficeIdentity = claimsPrincipal.Identities.OfType<UmbracoBackOfficeIdentity>().FirstOrDefault();
|
||||
if (backOfficeIdentity != null) return backOfficeIdentity;
|
||||
}
|
||||
|
||||
//Otherwise convert to a UmbracoBackOfficeIdentity if it's auth'd and has the back office session
|
||||
var claimsIdentity = http.User.Identity as ClaimsIdentity;
|
||||
if (claimsIdentity != null && claimsIdentity.IsAuthenticated)
|
||||
{
|
||||
{
|
||||
try
|
||||
{
|
||||
return UmbracoBackOfficeIdentity.FromClaimsIdentity(claimsIdentity);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Web;
|
||||
using Microsoft.AspNet.Identity;
|
||||
@@ -31,9 +32,7 @@ namespace Umbraco.Web.Security.Identity
|
||||
{
|
||||
app.SetLoggerFactory(new OwinLoggerFactory());
|
||||
}
|
||||
|
||||
#region Backoffice
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Configure Default Identity User Manager for Umbraco
|
||||
/// </summary>
|
||||
@@ -131,28 +130,18 @@ namespace Umbraco.Web.Security.Identity
|
||||
identity => identity.GetUserId<int>()),
|
||||
};
|
||||
|
||||
var authOptions = new UmbracoBackOfficeCookieAuthOptions(
|
||||
UmbracoConfig.For.UmbracoSettings().Security,
|
||||
GlobalSettings.TimeOutInMinutes,
|
||||
GlobalSettings.UseSSL)
|
||||
{
|
||||
Provider = cookieAuthProvider
|
||||
};
|
||||
var authOptions = CreateCookieAuthOptions();
|
||||
authOptions.Provider = cookieAuthProvider;
|
||||
|
||||
app.UseUmbracoBackOfficeCookieAuthentication(authOptions, appContext);
|
||||
|
||||
//don't apply if app isnot ready
|
||||
if (appContext.IsUpgrading || appContext.IsConfigured)
|
||||
{
|
||||
var getSecondsOptions = new UmbracoBackOfficeCookieAuthOptions(
|
||||
var getSecondsOptions = CreateCookieAuthOptions(
|
||||
//This defines the explicit path read cookies from for this middleware
|
||||
new[]{string.Format("{0}/backoffice/UmbracoApi/Authentication/GetRemainingTimeoutSeconds", GlobalSettings.Path)},
|
||||
UmbracoConfig.For.UmbracoSettings().Security,
|
||||
GlobalSettings.TimeOutInMinutes,
|
||||
GlobalSettings.UseSSL)
|
||||
{
|
||||
Provider = cookieAuthProvider
|
||||
};
|
||||
new[] {string.Format("{0}/backoffice/UmbracoApi/Authentication/GetRemainingTimeoutSeconds", GlobalSettings.Path)});
|
||||
getSecondsOptions.Provider = cookieAuthProvider;
|
||||
|
||||
//This is a custom middleware, we need to return the user's remaining logged in seconds
|
||||
app.Use<GetUserSecondsMiddleWare>(
|
||||
@@ -173,21 +162,21 @@ namespace Umbraco.Web.Security.Identity
|
||||
|
||||
//First the normal cookie middleware
|
||||
app.Use(typeof(CookieAuthenticationMiddleware), app, options);
|
||||
app.UseStageMarker(PipelineStage.Authenticate);
|
||||
|
||||
//don't apply if app isnot ready
|
||||
if (appContext.IsUpgrading || appContext.IsConfigured)
|
||||
{
|
||||
//Then our custom middlewares
|
||||
app.Use(typeof(ForceRenewalCookieAuthenticationMiddleware), app, options, new SingletonUmbracoContextAccessor());
|
||||
app.UseStageMarker(PipelineStage.Authenticate);
|
||||
app.Use(typeof(FixWindowsAuthMiddlware));
|
||||
app.UseStageMarker(PipelineStage.Authenticate);
|
||||
app.Use(typeof(FixWindowsAuthMiddlware));
|
||||
}
|
||||
//Marks all of the above middlewares to execute on Authenticate
|
||||
app.UseStageMarker(PipelineStage.Authenticate);
|
||||
|
||||
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the cookie middleware for validating external logins is assigned to the pipeline with the correct
|
||||
@@ -217,11 +206,53 @@ namespace Umbraco.Web.Security.Identity
|
||||
|
||||
return app;
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// In order for preview to work this needs to be called
|
||||
/// </summary>
|
||||
/// <param name="app"></param>
|
||||
/// <param name="appContext"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// This ensures that during a preview request that the back office use is also Authenticated and that the back office Identity
|
||||
/// is added as a secondary identity to the current IPrincipal so it can be used to Authorize the previewed document.
|
||||
/// </remarks>
|
||||
public static IAppBuilder UseUmbracoPreviewAuthentication(this IAppBuilder app, ApplicationContext appContext)
|
||||
{
|
||||
//don't apply if app isnot ready
|
||||
if (appContext.IsUpgrading || appContext.IsConfigured)
|
||||
{
|
||||
var authOptions = CreateCookieAuthOptions();
|
||||
app.Use(typeof(PreviewAuthenticationMiddleware), authOptions);
|
||||
|
||||
//Marks the above middlewares to execute on PostAuthenticate
|
||||
//NOTE: The above middleware needs to execute after the RoleManagerModule executes which is also during PostAuthenticate,
|
||||
// currently I've had 100% success with ensuring this fires after RoleManagerModule though not sure if that's always a
|
||||
// guarantee.
|
||||
app.UseStageMarker(PipelineStage.PostAuthenticate);
|
||||
}
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
public static void SanitizeThreadCulture(this IAppBuilder app)
|
||||
{
|
||||
Thread.CurrentThread.SanitizeThreadCulture();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create the default umb cookie auth options
|
||||
/// </summary>
|
||||
/// <param name="explicitPaths"></param>
|
||||
/// <returns></returns>
|
||||
private static UmbracoBackOfficeCookieAuthOptions CreateCookieAuthOptions(string[] explicitPaths = null)
|
||||
{
|
||||
var authOptions = new UmbracoBackOfficeCookieAuthOptions(
|
||||
explicitPaths,
|
||||
UmbracoConfig.For.UmbracoSettings().Security,
|
||||
GlobalSettings.TimeOutInMinutes,
|
||||
GlobalSettings.UseSSL);
|
||||
return authOptions;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -101,9 +101,7 @@ namespace Umbraco.Web.Security.Identity
|
||||
//check back office
|
||||
|| request.Uri.IsBackOfficeRequest(HttpRuntime.AppDomainAppVirtualPath)
|
||||
//check installer
|
||||
|| request.Uri.IsInstallerRequest()
|
||||
//detect in preview
|
||||
|| (request.HasPreviewCookie() && request.Uri != null && request.Uri.AbsolutePath.StartsWith(IOHelper.ResolveUrl(SystemDirectories.Umbraco)) == false)
|
||||
|| request.Uri.IsInstallerRequest()
|
||||
//check for base
|
||||
|| BaseRest.BaseRestHandler.IsBaseRestRequest(originalRequestUrl))
|
||||
{
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Security.Claims;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using Microsoft.AspNet.Identity;
|
||||
using Microsoft.AspNet.Identity.Owin;
|
||||
using Microsoft.Owin;
|
||||
using Microsoft.Owin.Extensions;
|
||||
using Microsoft.Owin.Logging;
|
||||
using Microsoft.Owin.Security;
|
||||
using Microsoft.Owin.Security.Cookies;
|
||||
using Owin;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models.Identity;
|
||||
using Umbraco.Core.Security;
|
||||
using Constants = Umbraco.Core.Constants;
|
||||
|
||||
namespace Umbraco.Web.Security.Identity
|
||||
{
|
||||
internal class PreviewAuthenticationMiddleware : OwinMiddleware
|
||||
{
|
||||
private readonly UmbracoBackOfficeCookieAuthOptions _cookieOptions;
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates the middleware with an optional pointer to the next component.
|
||||
/// </summary>
|
||||
/// <param name="next"/>
|
||||
/// <param name="cookieOptions"></param>
|
||||
public PreviewAuthenticationMiddleware(OwinMiddleware next,
|
||||
UmbracoBackOfficeCookieAuthOptions cookieOptions) : base(next)
|
||||
{
|
||||
_cookieOptions = cookieOptions;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process an individual request.
|
||||
/// </summary>
|
||||
/// <param name="context"/>
|
||||
/// <returns/>
|
||||
public override async Task Invoke(IOwinContext context)
|
||||
{
|
||||
var request = context.Request;
|
||||
if (request.Uri.IsClientSideRequest() == false)
|
||||
{
|
||||
var claimsPrincipal = context.Request.User as ClaimsPrincipal;
|
||||
var isPreview = request.HasPreviewCookie()
|
||||
&& claimsPrincipal != null
|
||||
&& request.Uri != null
|
||||
&& request.Uri.IsBackOfficeRequest(HttpRuntime.AppDomainAppVirtualPath) == false;
|
||||
if (isPreview)
|
||||
{
|
||||
//If we've gotten this far it means a preview cookie has been set and a front-end umbraco document request is executing.
|
||||
// In this case, authentication will not have occurred for an Umbraco back office User, however we need to perform the authentication
|
||||
// for the user here so that the preview capability can be authorized otherwise only the non-preview page will be rendered.
|
||||
|
||||
var cookie = request.Cookies[_cookieOptions.CookieName];
|
||||
if (cookie.IsNullOrWhiteSpace() == false)
|
||||
{
|
||||
var unprotected = _cookieOptions.TicketDataFormat.Unprotect(cookie);
|
||||
if (unprotected != null)
|
||||
{
|
||||
//Ok, we've got a real ticket, now we can add this ticket's identity to the current
|
||||
// Principal, this means we'll have 2 identities assigned to the principal which we can
|
||||
// use to authorize the preview and allow for a back office User.
|
||||
claimsPrincipal.AddIdentity(UmbracoBackOfficeIdentity.FromClaimsIdentity(unprotected.Identity));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Next != null)
|
||||
{
|
||||
await Next.Invoke(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -366,6 +366,7 @@
|
||||
<Compile Include="Media\EmbedProviders\OEmbedPhoto.cs" />
|
||||
<Compile Include="Security\Identity\IUmbracoBackOfficeTwoFactorOptions.cs" />
|
||||
<Compile Include="Models\Mapping\PropertyTypeGroupResolver.cs" />
|
||||
<Compile Include="Security\Identity\PreviewAuthenticationMiddleware.cs" />
|
||||
<Compile Include="SingletonHttpContextAccessor.cs" />
|
||||
<Compile Include="Trees\ContentTypeTreeController.cs" />
|
||||
<Compile Include="Trees\MediaTypeTreeController.cs" />
|
||||
|
||||
@@ -466,14 +466,11 @@ namespace Umbraco.Web
|
||||
var request = GetRequestFromContext();
|
||||
if (request == null || request.Url == null)
|
||||
return false;
|
||||
|
||||
var currentUrl = request.Url.AbsolutePath;
|
||||
// zb-00004 #29956 : refactor cookies names & handling
|
||||
|
||||
return
|
||||
//StateHelper.Cookies.Preview.HasValue // has preview cookie
|
||||
HttpContext.Request.HasPreviewCookie()
|
||||
&& currentUrl.StartsWith(IOHelper.ResolveUrl(SystemDirectories.Umbraco)) == false
|
||||
&& UmbracoUser != null; // has user
|
||||
&& request.Url.IsBackOfficeRequest(HttpRuntime.AppDomainAppVirtualPath) == false
|
||||
&& Security.CurrentUser != null; // has user
|
||||
}
|
||||
|
||||
private HttpRequestBase GetRequestFromContext()
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using Microsoft.Owin;
|
||||
using System.Web;
|
||||
using Microsoft.Owin;
|
||||
using Microsoft.Owin.Extensions;
|
||||
using Microsoft.Owin.Logging;
|
||||
using Owin;
|
||||
using Umbraco.Core;
|
||||
@@ -35,7 +37,9 @@ namespace Umbraco.Web
|
||||
// cookie configuration, this must be declared after it.
|
||||
app
|
||||
.UseUmbracoBackOfficeCookieAuthentication(ApplicationContext.Current)
|
||||
.UseUmbracoBackOfficeExternalCookieAuthentication(ApplicationContext.Current);
|
||||
.UseUmbracoBackOfficeExternalCookieAuthentication(ApplicationContext.Current)
|
||||
.UseUmbracoPreviewAuthentication(ApplicationContext.Current);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user