diff --git a/src/Umbraco.Core/BackOffice/ClaimsPrincipalExtensions.cs b/src/Umbraco.Core/BackOffice/ClaimsPrincipalExtensions.cs
index 7cbca0428a..6a792ce69b 100644
--- a/src/Umbraco.Core/BackOffice/ClaimsPrincipalExtensions.cs
+++ b/src/Umbraco.Core/BackOffice/ClaimsPrincipalExtensions.cs
@@ -17,8 +17,6 @@ namespace Umbraco.Extensions
///
public static UmbracoBackOfficeIdentity GetUmbracoIdentity(this IPrincipal user)
{
- // TODO: It would be nice to get rid of this and only rely on Claims, not a strongly typed identity instance
-
//If it's already a UmbracoBackOfficeIdentity
if (user.Identity is UmbracoBackOfficeIdentity backOfficeIdentity) return backOfficeIdentity;
@@ -55,10 +53,10 @@ namespace Umbraco.Extensions
///
public static double GetRemainingAuthSeconds(this IPrincipal user, DateTimeOffset now)
{
- var claimsPrincipal = user as ClaimsPrincipal;
- if (claimsPrincipal == null) return 0;
+ var umbIdentity = user.GetUmbracoIdentity();
+ if (umbIdentity == null) return 0;
- var ticketExpires = claimsPrincipal.FindFirst(Constants.Security.TicketExpiresClaimType)?.Value;
+ var ticketExpires = umbIdentity.FindFirstValue(Constants.Security.TicketExpiresClaimType);
if (ticketExpires.IsNullOrWhiteSpace()) return 0;
var utcExpired = DateTimeOffset.Parse(ticketExpires, null, DateTimeStyles.RoundtripKind);
diff --git a/src/Umbraco.Core/Security/IBackofficeSecurity.cs b/src/Umbraco.Core/Security/IBackofficeSecurity.cs
index 12d6dc1688..4ba20f7bfa 100644
--- a/src/Umbraco.Core/Security/IBackofficeSecurity.cs
+++ b/src/Umbraco.Core/Security/IBackofficeSecurity.cs
@@ -32,6 +32,13 @@ namespace Umbraco.Core.Security
///
ValidateRequestAttempt ValidateCurrentUser(bool throwExceptions, bool requiresApproval = true);
+ ///
+ /// Authorizes the full request, checks for SSL and validates the current user
+ ///
+ /// set to true if you want exceptions to be thrown if failed
+ ///
+ ValidateRequestAttempt AuthorizeRequest(bool throwExceptions = false);
+
///
/// Checks if the specified user as access to the app
///
diff --git a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs
index e9f9c9fa69..bf1271c910 100644
--- a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs
+++ b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs
@@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Linq;
using System.Net;
-using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Identity;
@@ -207,26 +206,18 @@ namespace Umbraco.Web.BackOffice.Controllers
}
[HttpGet]
- public async Task GetRemainingTimeoutSeconds()
+ public double GetRemainingTimeoutSeconds()
{
- // force authentication to occur since this is not an authorized endpoint
- var result = await HttpContext.AuthenticateAsync(Constants.Security.BackOfficeAuthenticationType);
- if (!result.Succeeded)
- {
- return 0;
- }
-
+ var backOfficeIdentity = HttpContext.User.GetUmbracoIdentity();
var remainingSeconds = HttpContext.User.GetRemainingAuthSeconds();
- if (remainingSeconds <= 30)
+ if (remainingSeconds <= 30 && backOfficeIdentity != null)
{
- var username = result.Principal.FindFirst(ClaimTypes.Name)?.Value;
-
//NOTE: We are using 30 seconds because that is what is coded into angular to force logout to give some headway in
// the timeout process.
_logger.LogInformation(
"User logged will be logged out due to timeout: {Username}, IP Address: {IPAddress}",
- username ?? "unknown",
+ backOfficeIdentity.Name,
_ipResolver.GetCurrentRequestIpAddress());
}
@@ -238,11 +229,14 @@ namespace Umbraco.Web.BackOffice.Controllers
///
///
[HttpGet]
- public async Task IsAuthenticated()
+ public bool IsAuthenticated()
{
- // force authentication to occur since this is not an authorized endpoint
- var result = await HttpContext.AuthenticateAsync(Constants.Security.BackOfficeAuthenticationType);
- return result.Succeeded;
+ var attempt = _backofficeSecurityAccessor.BackOfficeSecurity.AuthorizeRequest();
+ if (attempt == ValidateRequestAttempt.Success)
+ {
+ return true;
+ }
+ return false;
}
///
@@ -592,6 +586,7 @@ namespace Umbraco.Web.BackOffice.Controllers
}
+
///
/// Return the for the given
///
diff --git a/src/Umbraco.Web.Common/ApplicationModels/AuthenticateAsBackOfficeSchemeConvention.cs b/src/Umbraco.Web.Common/ApplicationModels/AuthenticateAsBackOfficeSchemeConvention.cs
new file mode 100644
index 0000000000..838cc59ac4
--- /dev/null
+++ b/src/Umbraco.Web.Common/ApplicationModels/AuthenticateAsBackOfficeSchemeConvention.cs
@@ -0,0 +1,16 @@
+using Microsoft.AspNetCore.Mvc.ApplicationModels;
+using Umbraco.Web.Common.Filters;
+
+namespace Umbraco.Web.Common.ApplicationModels
+{
+ ///
+ /// Ensures all requests with this convention are authenticated with the back office scheme
+ ///
+ public class AuthenticateAsBackOfficeSchemeConvention : IActionModelConvention
+ {
+ public void Apply(ActionModel action)
+ {
+ action.Filters.Add(new EnsureUmbracoBackOfficeAuthentication());
+ }
+ }
+}
diff --git a/src/Umbraco.Web.Common/ApplicationModels/BackOfficeApplicationModelProvider.cs b/src/Umbraco.Web.Common/ApplicationModels/BackOfficeApplicationModelProvider.cs
index 11d82d4db5..dc0816e1e2 100644
--- a/src/Umbraco.Web.Common/ApplicationModels/BackOfficeApplicationModelProvider.cs
+++ b/src/Umbraco.Web.Common/ApplicationModels/BackOfficeApplicationModelProvider.cs
@@ -17,7 +17,8 @@ namespace Umbraco.Web.Common.ApplicationModels
{
ActionModelConventions = new List()
{
- new BackOfficeIdentityCultureConvention()
+ new BackOfficeIdentityCultureConvention(),
+ new AuthenticateAsBackOfficeSchemeConvention()
};
}
diff --git a/src/Umbraco.Web.Common/Filters/EnsureUmbracoBackOfficeAuthentication.cs b/src/Umbraco.Web.Common/Filters/EnsureUmbracoBackOfficeAuthentication.cs
new file mode 100644
index 0000000000..5ad43dc922
--- /dev/null
+++ b/src/Umbraco.Web.Common/Filters/EnsureUmbracoBackOfficeAuthentication.cs
@@ -0,0 +1,25 @@
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc.Filters;
+using Umbraco.Web.Common.ApplicationModels;
+using Umbraco.Web.Common.Attributes;
+
+namespace Umbraco.Web.Common.Filters
+{
+
+ ///
+ /// Assigned as part of the umbraco back office application model
+ /// to always ensure that back office authentication occurs for all controller/actions with
+ /// applied.
+ ///
+ public class EnsureUmbracoBackOfficeAuthentication : IAuthorizationFilter, IAuthorizeData
+ {
+ // Implements IAuthorizeData only to return the back office scheme
+ public string AuthenticationSchemes { get; set; } = Umbraco.Core.Constants.Security.BackOfficeAuthenticationType;
+ public string Policy { get; set; }
+ public string Roles { get; set; }
+
+ public void OnAuthorization(AuthorizationFilterContext context)
+ {
+ }
+ }
+}
diff --git a/src/Umbraco.Web.Common/Filters/UmbracoBackOfficeAuthorizeAttribute.cs b/src/Umbraco.Web.Common/Filters/UmbracoBackOfficeAuthorizeAttribute.cs
index 5ddf285601..40f534f289 100644
--- a/src/Umbraco.Web.Common/Filters/UmbracoBackOfficeAuthorizeAttribute.cs
+++ b/src/Umbraco.Web.Common/Filters/UmbracoBackOfficeAuthorizeAttribute.cs
@@ -6,15 +6,8 @@ namespace Umbraco.Web.Common.Filters
///
/// Ensures authorization is successful for a back office user.
///
- public class UmbracoBackOfficeAuthorizeAttribute : TypeFilterAttribute, IAuthorizeData
+ public class UmbracoBackOfficeAuthorizeAttribute : TypeFilterAttribute
{
- // Implements IAuthorizeData to return the back office scheme so that all requests with this attributes
- // get authenticated with this scheme.
- // TODO: We'll have to refactor this as part of the authz policy changes.
- public string AuthenticationSchemes { get; set; } = Umbraco.Core.Constants.Security.BackOfficeAuthenticationType;
- public string Policy { get; set; }
- public string Roles { get; set; }
-
///
/// Default constructor
///
diff --git a/src/Umbraco.Web.Common/Security/BackofficeSecurity.cs b/src/Umbraco.Web.Common/Security/BackofficeSecurity.cs
index 3d9d1e8622..23ff63a328 100644
--- a/src/Umbraco.Web.Common/Security/BackofficeSecurity.cs
+++ b/src/Umbraco.Web.Common/Security/BackofficeSecurity.cs
@@ -52,6 +52,18 @@ namespace Umbraco.Web.Common.Security
}
}
+ ///
+ public ValidateRequestAttempt AuthorizeRequest(bool throwExceptions = false)
+ {
+ // check for secure connection
+ if (_globalSettings.UseHttps && !_httpContextAccessor.GetRequiredHttpContext().Request.IsHttps)
+ {
+ if (throwExceptions) throw new SecurityException("This installation requires a secure connection (via SSL). Please update the URL to include https://");
+ return ValidateRequestAttempt.FailedNoSsl;
+ }
+ return ValidateCurrentUser(throwExceptions);
+ }
+
///
public Attempt GetUserId()
{
@@ -94,7 +106,6 @@ namespace Umbraco.Web.Common.Security
var user = CurrentUser;
- // TODO: All of this is done as part of identity/backofficesigninmanager
// Check for console access
if (user == null || (requiresApproval && user.IsApproved == false) || (user.IsLockedOut && RequestIsInUmbracoApplication(_httpContextAccessor, _globalSettings, _hostingEnvironment)))
{