diff --git a/src/Umbraco.Core/Extensions/ClaimsIdentityExtensions.cs b/src/Umbraco.Core/Extensions/ClaimsIdentityExtensions.cs index 3d58253dd9..7569b64cb7 100644 --- a/src/Umbraco.Core/Extensions/ClaimsIdentityExtensions.cs +++ b/src/Umbraco.Core/Extensions/ClaimsIdentityExtensions.cs @@ -2,8 +2,11 @@ // See LICENSE for more details. using System; +using System.Collections.Generic; +using System.Linq; using System.Security.Claims; using System.Security.Principal; +using Umbraco.Cms.Core; namespace Umbraco.Extensions { @@ -72,5 +75,271 @@ namespace Umbraco.Extensions return identity.FindFirst(claimType)?.Value; } + + /// + /// Returns the required claim types for a back office identity + /// + /// + /// This does not include the role claim type or allowed apps type since that is a collection and in theory could be empty + /// + public static IEnumerable RequiredBackOfficeClaimTypes => new[] + { + ClaimTypes.NameIdentifier, //id + ClaimTypes.Name, //username + ClaimTypes.GivenName, + // Constants.Security.StartContentNodeIdClaimType, These seem to be able to be null... + // Constants.Security.StartMediaNodeIdClaimType, + ClaimTypes.Locality, + Constants.Security.SecurityStampClaimType + }; + + /// + /// Verify that a ClaimsIdentity has all the required claim types + /// + /// + /// Verified identity wrapped in a ClaimsIdentity with BackOfficeAuthentication type + /// True if ClaimsIdentity + public static bool VerifyBackOfficeIdentity(this ClaimsIdentity identity, out ClaimsIdentity verifiedIdentity) + { + // Validate that all required claims exist + foreach (var claimType in RequiredBackOfficeClaimTypes) + { + if (identity.HasClaim(x => x.Type == claimType) == false || + identity.HasClaim(x => x.Type == claimType && x.Value.IsNullOrWhiteSpace())) + { + verifiedIdentity = null; + return false; + } + } + + verifiedIdentity = new ClaimsIdentity(identity.Claims, Constants.Security.BackOfficeAuthenticationType); + return true; + } + + /// + /// Add the required claims to be a BackOffice ClaimsIdentity + /// + /// this + /// The users Id + /// Username + /// Real name + /// Start content nodes + /// Start media nodes + /// The locality of the user + /// Security stamp + /// Allowed apps + /// Roles + public static void AddRequiredClaims(this ClaimsIdentity identity, string userId, string username, + string realName, IEnumerable startContentNodes, IEnumerable startMediaNodes, string culture, + string securityStamp, IEnumerable allowedApps, IEnumerable roles) + { + //This is the id that 'identity' uses to check for the user id + if (identity.HasClaim(x => x.Type == ClaimTypes.NameIdentifier) == false) + { + identity.AddClaim(new Claim( + ClaimTypes.NameIdentifier, + userId, + ClaimValueTypes.String, + Constants.Security.BackOfficeAuthenticationType, + Constants.Security.BackOfficeAuthenticationType, + identity)); + } + + if (identity.HasClaim(x => x.Type == ClaimTypes.Name) == false) + { + identity.AddClaim(new Claim( + ClaimTypes.Name, + username, + ClaimValueTypes.String, + Constants.Security.BackOfficeAuthenticationType, + Constants.Security.BackOfficeAuthenticationType, + identity)); + } + + if (identity.HasClaim(x => x.Type == ClaimTypes.GivenName) == false) + { + identity.AddClaim(new Claim( + ClaimTypes.GivenName, + realName, + ClaimValueTypes.String, + Constants.Security.BackOfficeAuthenticationType, + Constants.Security.BackOfficeAuthenticationType, + identity)); + } + + if (identity.HasClaim(x => x.Type == Constants.Security.StartContentNodeIdClaimType) == false && + startContentNodes != null) + { + foreach (var startContentNode in startContentNodes) + { + identity.AddClaim(new Claim( + Constants.Security.StartContentNodeIdClaimType, + startContentNode.ToInvariantString(), + ClaimValueTypes.Integer32, + Constants.Security.BackOfficeAuthenticationType, + Constants.Security.BackOfficeAuthenticationType, + identity)); + } + } + + if (identity.HasClaim(x => x.Type == Constants.Security.StartMediaNodeIdClaimType) == false && + startMediaNodes != null) + { + foreach (var startMediaNode in startMediaNodes) + { + identity.AddClaim(new Claim( + Constants.Security.StartMediaNodeIdClaimType, + startMediaNode.ToInvariantString(), + ClaimValueTypes.Integer32, + Constants.Security.BackOfficeAuthenticationType, + Constants.Security.BackOfficeAuthenticationType, + identity)); + } + } + + if (identity.HasClaim(x => x.Type == ClaimTypes.Locality) == false) + { + identity.AddClaim(new Claim( + ClaimTypes.Locality, + culture, + ClaimValueTypes.String, + Constants.Security.BackOfficeAuthenticationType, + Constants.Security.BackOfficeAuthenticationType, + identity)); + } + + // The security stamp claim is also required + if (identity.HasClaim(x => x.Type == Constants.Security.SecurityStampClaimType) == false) + { + identity.AddClaim(new Claim( + Constants.Security.SecurityStampClaimType, + securityStamp, + ClaimValueTypes.String, + Constants.Security.BackOfficeAuthenticationType, + Constants.Security.BackOfficeAuthenticationType, + identity)); + } + + // Add each app as a separate claim + if (identity.HasClaim(x => x.Type == Constants.Security.AllowedApplicationsClaimType) == false && + allowedApps != null) + { + foreach (var application in allowedApps) + { + identity.AddClaim(new Claim( + Constants.Security.AllowedApplicationsClaimType, + application, + ClaimValueTypes.String, + Constants.Security.BackOfficeAuthenticationType, + Constants.Security.BackOfficeAuthenticationType, + identity)); + } + } + + // Claims are added by the ClaimsIdentityFactory because our UserStore supports roles, however this identity might + // not be made with that factory if it was created with a different ticket so perform the check + if (identity.HasClaim(x => x.Type == ClaimsIdentity.DefaultRoleClaimType) == false && roles != null) + { + // Manually add them + foreach (var roleName in roles) + { + identity.AddClaim(new Claim( + identity.RoleClaimType, + roleName, + ClaimValueTypes.String, + Constants.Security.BackOfficeAuthenticationType, + Constants.Security.BackOfficeAuthenticationType, + identity)); + } + } + } + + /// + /// Get the start content nodes from a ClaimsIdentity + /// + /// + /// Array of start content nodes + public static int[] GetStartContentNodes(this ClaimsIdentity identity) => + identity.FindAll(x => x.Type == Constants.Security.StartContentNodeIdClaimType) + .Select(node => int.TryParse(node.Value, out var i) ? i : default) + .Where(x => x != default).ToArray(); + + /// + /// Get the start media nodes from a ClaimsIdentity + /// + /// + /// Array of start media nodes + public static int[] GetStartMediaNodes(this ClaimsIdentity identity) => + identity.FindAll(x => x.Type == Constants.Security.StartMediaNodeIdClaimType) + .Select(node => int.TryParse(node.Value, out var i) ? i : default) + .Where(x => x != default).ToArray(); + + /// + /// Get the allowed applications from a ClaimsIdentity + /// + /// + /// + public static string[] GetAllowedApplications(this ClaimsIdentity identity) => identity + .FindAll(x => x.Type == Constants.Security.AllowedApplicationsClaimType).Select(app => app.Value).ToArray(); + + /// + /// Get the user ID from a ClaimsIdentity + /// + /// + /// User ID as integer + public static int GetId(this ClaimsIdentity identity) => int.Parse(identity.FindFirstValue(ClaimTypes.NameIdentifier)); + + /// + /// Get the real name belonging to the user from a ClaimsIdentity + /// + /// + /// Real name of the user + public static string GetRealName(this ClaimsIdentity identity) => identity.FindFirstValue(ClaimTypes.GivenName); + + /// + /// Get the username of the user from a ClaimsIdentity + /// + /// + /// Username of the user + public static string GetUsername(this ClaimsIdentity identity) => identity.FindFirstValue(ClaimTypes.Name); + + /// + /// Get the culture string from a ClaimsIdentity + /// + /// + /// Culture string + public static string GetCultureString(this ClaimsIdentity identity) => identity.FindFirstValue(ClaimTypes.Locality); + + /// + /// Get the security stamp from a ClaimsIdentity + /// + /// + /// Security stamp + public static string GetSecurityStamp(this ClaimsIdentity identity) => identity.FindFirstValue(Constants.Security.SecurityStampClaimType); + + /// + /// Get the roles assigned to a user from a ClaimsIdentity + /// + /// + /// Array of roles + public static string[] GetRoles(this ClaimsIdentity identity) => identity + .FindAll(x => x.Type == ClaimsIdentity.DefaultRoleClaimType).Select(role => role.Value).ToArray(); + + + /// + /// Adds or updates and existing claim. + /// + public static void AddOrUpdateClaim(this ClaimsIdentity identity, Claim claim) + { + if (identity == null) + { + throw new ArgumentNullException(nameof(identity)); + } + + Claim existingClaim = identity.Claims.FirstOrDefault(x => x.Type == claim.Type); + identity.TryRemoveClaim(existingClaim); + + identity.AddClaim(claim); + } } } diff --git a/src/Umbraco.Core/Security/AuthenticationExtensions.cs b/src/Umbraco.Core/Security/AuthenticationExtensions.cs index 15eba68de1..3068b1c41d 100644 --- a/src/Umbraco.Core/Security/AuthenticationExtensions.cs +++ b/src/Umbraco.Core/Security/AuthenticationExtensions.cs @@ -2,9 +2,9 @@ // See LICENSE for more details. using System.Globalization; +using System.Security.Claims; using System.Security.Principal; using System.Threading; -using Umbraco.Cms.Core.Security; namespace Umbraco.Extensions { @@ -24,9 +24,9 @@ namespace Umbraco.Extensions public static CultureInfo GetCulture(this IIdentity identity) { - if (identity is UmbracoBackOfficeIdentity umbIdentity && umbIdentity.IsAuthenticated) + if (identity is ClaimsIdentity umbIdentity && umbIdentity.VerifyBackOfficeIdentity(out _) && umbIdentity.IsAuthenticated) { - return CultureInfo.GetCultureInfo(umbIdentity.Culture); + return CultureInfo.GetCultureInfo(umbIdentity.GetCultureString()); } return null; diff --git a/src/Umbraco.Core/Security/ClaimsPrincipalExtensions.cs b/src/Umbraco.Core/Security/ClaimsPrincipalExtensions.cs index c8405e1216..ce0e0eb774 100644 --- a/src/Umbraco.Core/Security/ClaimsPrincipalExtensions.cs +++ b/src/Umbraco.Core/Security/ClaimsPrincipalExtensions.cs @@ -14,29 +14,21 @@ namespace Umbraco.Extensions public static class ClaimsPrincipalExtensions { /// - /// This will return the current back office identity if the IPrincipal is the correct type + /// This will return the current back office identity if the IPrincipal is the correct type and authenticated. /// /// /// - public static UmbracoBackOfficeIdentity GetUmbracoIdentity(this IPrincipal user) + public static ClaimsIdentity 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; - - //Check if there's more than one identity assigned and see if it's a UmbracoBackOfficeIdentity and use that - if (user is ClaimsPrincipal claimsPrincipal) - { - backOfficeIdentity = claimsPrincipal.Identities.OfType().FirstOrDefault(); - if (backOfficeIdentity != null) return backOfficeIdentity; - } - - //Otherwise convert to a UmbracoBackOfficeIdentity if it's auth'd + // Check if the identity is a ClaimsIdentity, and that's it's authenticated and has all required claims. if (user.Identity is ClaimsIdentity claimsIdentity && claimsIdentity.IsAuthenticated - && UmbracoBackOfficeIdentity.FromClaimsIdentity(claimsIdentity, out var umbracoIdentity)) + && claimsIdentity.VerifyBackOfficeIdentity(out ClaimsIdentity umbracoIdentity)) { + if (claimsIdentity.AuthenticationType == Constants.Security.BackOfficeAuthenticationType) + { + return claimsIdentity; + } return umbracoIdentity; } diff --git a/src/Umbraco.Core/Security/UmbracoBackOfficeIdentity.cs b/src/Umbraco.Core/Security/UmbracoBackOfficeIdentity.cs deleted file mode 100644 index ab959bf2e7..0000000000 --- a/src/Umbraco.Core/Security/UmbracoBackOfficeIdentity.cs +++ /dev/null @@ -1,232 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Security.Claims; -using Umbraco.Extensions; - -namespace Umbraco.Cms.Core.Security -{ - /// - /// A custom user identity for the Umbraco backoffice - /// - [Serializable] - public class UmbracoBackOfficeIdentity : ClaimsIdentity - { - // TODO: Ideally we remove this class and only deal with ClaimsIdentity as a best practice. All things relevant to our own - // identity are part of claims. This class would essentially become extension methods on a ClaimsIdentity for resolving - // values from it. - public static bool FromClaimsIdentity(ClaimsIdentity identity, out UmbracoBackOfficeIdentity backOfficeIdentity) - { - // validate that all claims exist - foreach (var t in RequiredBackOfficeIdentityClaimTypes) - { - // if the identity doesn't have the claim, or the claim value is null - if (identity.HasClaim(x => x.Type == t) == false || identity.HasClaim(x => x.Type == t && x.Value.IsNullOrWhiteSpace())) - { - backOfficeIdentity = null; - return false; - } - } - - backOfficeIdentity = new UmbracoBackOfficeIdentity(identity); - return true; - } - - /// - /// Create a back office identity based on an existing claims identity - /// - /// - private UmbracoBackOfficeIdentity(ClaimsIdentity identity) - : base(identity.Claims, Constants.Security.BackOfficeAuthenticationType) - { - } - - /// - /// Creates a new UmbracoBackOfficeIdentity - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - public UmbracoBackOfficeIdentity(string userId, string username, string realName, - IEnumerable startContentNodes, IEnumerable startMediaNodes, string culture, - string securityStamp, IEnumerable allowedApps, IEnumerable roles) - : base(Enumerable.Empty(), Constants.Security.BackOfficeAuthenticationType) //this ctor is used to ensure the IsAuthenticated property is true - { - if (allowedApps == null) - throw new ArgumentNullException(nameof(allowedApps)); - if (string.IsNullOrWhiteSpace(username)) - throw new ArgumentException("Value cannot be null or whitespace.", nameof(username)); - if (string.IsNullOrWhiteSpace(realName)) - throw new ArgumentException("Value cannot be null or whitespace.", nameof(realName)); - if (string.IsNullOrWhiteSpace(culture)) - throw new ArgumentException("Value cannot be null or whitespace.", nameof(culture)); - if (string.IsNullOrWhiteSpace(securityStamp)) - throw new ArgumentException("Value cannot be null or whitespace.", nameof(securityStamp)); - AddRequiredClaims(userId, username, realName, startContentNodes, startMediaNodes, culture, securityStamp, allowedApps, roles); - } - - /// - /// Creates a new UmbracoBackOfficeIdentity - /// - /// - /// The original identity created by the ClaimsIdentityFactory - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - public UmbracoBackOfficeIdentity(ClaimsIdentity childIdentity, - string userId, string username, string realName, - IEnumerable startContentNodes, IEnumerable startMediaNodes, string culture, - string securityStamp, IEnumerable allowedApps, IEnumerable roles) - : base(childIdentity.Claims, Constants.Security.BackOfficeAuthenticationType) - { - if (string.IsNullOrWhiteSpace(username)) - throw new ArgumentException("Value cannot be null or whitespace.", nameof(username)); - if (string.IsNullOrWhiteSpace(realName)) - throw new ArgumentException("Value cannot be null or whitespace.", nameof(realName)); - if (string.IsNullOrWhiteSpace(culture)) - throw new ArgumentException("Value cannot be null or whitespace.", nameof(culture)); - if (string.IsNullOrWhiteSpace(securityStamp)) - throw new ArgumentException("Value cannot be null or whitespace.", nameof(securityStamp)); - - AddRequiredClaims(userId, username, realName, startContentNodes, startMediaNodes, culture, securityStamp, allowedApps, roles); - } - - public const string Issuer = Constants.Security.BackOfficeAuthenticationType; - - /// - /// Returns the required claim types for a back office identity - /// - /// - /// This does not include the role claim type or allowed apps type since that is a collection and in theory could be empty - /// - public static IEnumerable RequiredBackOfficeIdentityClaimTypes => new[] - { - ClaimTypes.NameIdentifier, //id - ClaimTypes.Name, //username - ClaimTypes.GivenName, - Constants.Security.StartContentNodeIdClaimType, - Constants.Security.StartMediaNodeIdClaimType, - ClaimTypes.Locality, - Constants.Security.SecurityStampClaimType - }; - - /// - /// Adds claims based on the ctor data - /// - private void AddRequiredClaims(string userId, string username, string realName, - IEnumerable startContentNodes, IEnumerable startMediaNodes, string culture, - string securityStamp, IEnumerable allowedApps, IEnumerable roles) - { - //This is the id that 'identity' uses to check for the user id - if (HasClaim(x => x.Type == ClaimTypes.NameIdentifier) == false) - AddClaim(new Claim(ClaimTypes.NameIdentifier, userId, ClaimValueTypes.String, Issuer, Issuer, this)); - - if (HasClaim(x => x.Type == ClaimTypes.Name) == false) - AddClaim(new Claim(ClaimTypes.Name, username, ClaimValueTypes.String, Issuer, Issuer, this)); - - if (HasClaim(x => x.Type == ClaimTypes.GivenName) == false) - AddClaim(new Claim(ClaimTypes.GivenName, realName, ClaimValueTypes.String, Issuer, Issuer, this)); - - if (HasClaim(x => x.Type == Constants.Security.StartContentNodeIdClaimType) == false && startContentNodes != null) - { - foreach (var startContentNode in startContentNodes) - { - AddClaim(new Claim(Constants.Security.StartContentNodeIdClaimType, startContentNode.ToInvariantString(), ClaimValueTypes.Integer32, Issuer, Issuer, this)); - } - } - - if (HasClaim(x => x.Type == Constants.Security.StartMediaNodeIdClaimType) == false && startMediaNodes != null) - { - foreach (var startMediaNode in startMediaNodes) - { - AddClaim(new Claim(Constants.Security.StartMediaNodeIdClaimType, startMediaNode.ToInvariantString(), ClaimValueTypes.Integer32, Issuer, Issuer, this)); - } - } - - if (HasClaim(x => x.Type == ClaimTypes.Locality) == false) - AddClaim(new Claim(ClaimTypes.Locality, culture, ClaimValueTypes.String, Issuer, Issuer, this)); - - //The security stamp claim is also required - if (HasClaim(x => x.Type == Constants.Security.SecurityStampClaimType) == false) - AddClaim(new Claim(Constants.Security.SecurityStampClaimType, securityStamp, ClaimValueTypes.String, Issuer, Issuer, this)); - - //Add each app as a separate claim - if (HasClaim(x => x.Type == Constants.Security.AllowedApplicationsClaimType) == false && allowedApps != null) - { - foreach (var application in allowedApps) - { - AddClaim(new Claim(Constants.Security.AllowedApplicationsClaimType, application, ClaimValueTypes.String, Issuer, Issuer, this)); - } - } - - //Claims are added by the ClaimsIdentityFactory because our UserStore supports roles, however this identity might - // not be made with that factory if it was created with a different ticket so perform the check - if (HasClaim(x => x.Type == DefaultRoleClaimType) == false && roles != null) - { - //manually add them - foreach (var roleName in roles) - { - AddClaim(new Claim(RoleClaimType, roleName, ClaimValueTypes.String, Issuer, Issuer, this)); - } - } - - } - - /// - /// - /// Gets the type of authenticated identity. - /// - /// - /// The type of authenticated identity. This property always returns "UmbracoBackOffice". - /// - public override string AuthenticationType => Issuer; - - private int[] _startContentNodes; - public int[] StartContentNodes => _startContentNodes ?? (_startContentNodes = FindAll(x => x.Type == Constants.Security.StartContentNodeIdClaimType).Select(app => int.TryParse(app.Value, out var i) ? i : default).Where(x => x != default).ToArray()); - - private int[] _startMediaNodes; - public int[] StartMediaNodes => _startMediaNodes ?? (_startMediaNodes = FindAll(x => x.Type == Constants.Security.StartMediaNodeIdClaimType).Select(app => int.TryParse(app.Value, out var i) ? i : default).Where(x => x != default).ToArray()); - - private string[] _allowedApplications; - public string[] AllowedApplications => _allowedApplications ?? (_allowedApplications = FindAll(x => x.Type == Constants.Security.AllowedApplicationsClaimType).Select(app => app.Value).ToArray()); - - public int Id => int.Parse(this.FindFirstValue(ClaimTypes.NameIdentifier)); - - public string RealName => this.FindFirstValue(ClaimTypes.GivenName); - - public string Username => this.FindFirstValue(ClaimTypes.Name); - - public string Culture => this.FindFirstValue(ClaimTypes.Locality); - - public string SecurityStamp => this.FindFirstValue(Constants.Security.SecurityStampClaimType); - - public string[] Roles => FindAll(x => x.Type == DefaultRoleClaimType).Select(role => role.Value).ToArray(); - - /// - /// Overridden to remove any temporary claims that shouldn't be copied - /// - /// - public override ClaimsIdentity Clone() - { - var clone = base.Clone(); - - foreach (var claim in clone.FindAll(x => x.Type == Constants.Security.TicketExpiresClaimType).ToList()) - clone.RemoveClaim(claim); - - return clone; - } - } -} diff --git a/src/Umbraco.Infrastructure/Compose/AuditEventsComponent.cs b/src/Umbraco.Infrastructure/Compose/AuditEventsComponent.cs index d4e52ad4ef..8dc25d85eb 100644 --- a/src/Umbraco.Infrastructure/Compose/AuditEventsComponent.cs +++ b/src/Umbraco.Infrastructure/Compose/AuditEventsComponent.cs @@ -26,14 +26,22 @@ namespace Umbraco.Core.Compose private readonly IUserService _userService; private readonly IEntityService _entityService; private readonly IIpResolver _ipResolver; + private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; private readonly GlobalSettings _globalSettings; - public AuditEventsComponent(IAuditService auditService, IUserService userService, IEntityService entityService, IIpResolver ipResolver, IOptions globalSettings) + public AuditEventsComponent( + IAuditService auditService, + IUserService userService, + IEntityService entityService, + IIpResolver ipResolver, + IOptions globalSettings, + IBackOfficeSecurityAccessor backOfficeSecurityAccessor) { _auditService = auditService; _userService = userService; _entityService = entityService; _ipResolver = ipResolver; + _backOfficeSecurityAccessor = backOfficeSecurityAccessor; _globalSettings = globalSettings.Value; } @@ -73,7 +81,7 @@ namespace Umbraco.Core.Compose { get { - var identity = Thread.CurrentPrincipal?.GetUmbracoIdentity(); + var identity = _backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser; var user = identity == null ? null : _userService.GetUserById(Convert.ToInt32(identity.Id)); return user ?? UnknownUser(_globalSettings); } diff --git a/src/Umbraco.Infrastructure/Security/BackOfficeClaimsPrincipalFactory.cs b/src/Umbraco.Infrastructure/Security/BackOfficeClaimsPrincipalFactory.cs index f5c0e9ab7a..a57a5c1737 100644 --- a/src/Umbraco.Infrastructure/Security/BackOfficeClaimsPrincipalFactory.cs +++ b/src/Umbraco.Infrastructure/Security/BackOfficeClaimsPrincipalFactory.cs @@ -4,7 +4,7 @@ using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Options; -using Umbraco.Cms.Core.Security; +using Umbraco.Extensions; namespace Umbraco.Core.Security { @@ -26,7 +26,7 @@ namespace Umbraco.Core.Security /// /// - /// Returns a custom and allows flowing claims from the external identity + /// Returns a ClaimsIdentity that has the required claims, and allows flowing of claims from external identity /// public override async Task CreateAsync(BackOfficeIdentityUser user) { @@ -43,10 +43,7 @@ namespace Umbraco.Core.Security baseIdentity.AddClaim(new Claim(claim.ClaimType, claim.ClaimValue)); } - // TODO: We want to remove UmbracoBackOfficeIdentity and only rely on ClaimsIdentity, once - // that is done then we'll create a ClaimsIdentity with all of the requirements here instead - var umbracoIdentity = new UmbracoBackOfficeIdentity( - baseIdentity, + baseIdentity.AddRequiredClaims( user.Id, user.UserName, user.Name, @@ -57,7 +54,7 @@ namespace Umbraco.Core.Security user.AllowedSections, user.Roles.Select(x => x.RoleId).ToArray()); - return new ClaimsPrincipal(umbracoIdentity); + return new ClaimsPrincipal(baseIdentity); } /// diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/BackOffice/UmbracoBackOfficeIdentityTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/BackOffice/UmbracoBackOfficeIdentityTests.cs index 46332df4b2..881b880c80 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/BackOffice/UmbracoBackOfficeIdentityTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/BackOffice/UmbracoBackOfficeIdentityTests.cs @@ -39,24 +39,24 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.BackOffice new Claim(Constants.Security.SecurityStampClaimType, securityStamp, ClaimValueTypes.String, TestIssuer, TestIssuer), }); - if (!UmbracoBackOfficeIdentity.FromClaimsIdentity(claimsIdentity, out UmbracoBackOfficeIdentity backofficeIdentity)) + if (!claimsIdentity.VerifyBackOfficeIdentity(out ClaimsIdentity verifiedIdentity)) { Assert.Fail(); } - Assert.IsNull(backofficeIdentity.Actor); - Assert.AreEqual(1234, backofficeIdentity.Id); + Assert.IsNull(verifiedIdentity.Actor); + Assert.AreEqual(1234, verifiedIdentity.GetId()); //// Assert.AreEqual(sessionId, backofficeIdentity.SessionId); - Assert.AreEqual(securityStamp, backofficeIdentity.SecurityStamp); - Assert.AreEqual("testing", backofficeIdentity.Username); - Assert.AreEqual("hello world", backofficeIdentity.RealName); - Assert.AreEqual(1, backofficeIdentity.StartContentNodes.Length); - Assert.IsTrue(backofficeIdentity.StartMediaNodes.UnsortedSequenceEqual(new[] { 5543, 5555 })); - Assert.IsTrue(new[] { "content", "media" }.SequenceEqual(backofficeIdentity.AllowedApplications)); - Assert.AreEqual("en-us", backofficeIdentity.Culture); - Assert.IsTrue(new[] { "admin" }.SequenceEqual(backofficeIdentity.Roles)); + Assert.AreEqual(securityStamp, verifiedIdentity.GetSecurityStamp()); + Assert.AreEqual("testing", verifiedIdentity.GetUsername()); + Assert.AreEqual("hello world", verifiedIdentity.GetRealName()); + Assert.AreEqual(1, verifiedIdentity.GetStartContentNodes().Length); + Assert.IsTrue(verifiedIdentity.GetStartMediaNodes().UnsortedSequenceEqual(new[] { 5543, 5555 })); + Assert.IsTrue(new[] { "content", "media" }.SequenceEqual(verifiedIdentity.GetAllowedApplications())); + Assert.AreEqual("en-us", verifiedIdentity.GetCultureString()); + Assert.IsTrue(new[] { "admin" }.SequenceEqual(verifiedIdentity.GetRoles())); - Assert.AreEqual(11, backofficeIdentity.Claims.Count()); + Assert.AreEqual(11, verifiedIdentity.Claims.Count()); } [Test] @@ -68,7 +68,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.BackOffice new Claim(ClaimTypes.Name, "testing", ClaimValueTypes.String, TestIssuer, TestIssuer), }); - if (UmbracoBackOfficeIdentity.FromClaimsIdentity(claimsIdentity, out _)) + if (claimsIdentity.VerifyBackOfficeIdentity(out _)) { Assert.Fail(); } @@ -93,7 +93,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.BackOffice new Claim(ClaimsIdentity.DefaultRoleClaimType, "admin", ClaimValueTypes.String, TestIssuer, TestIssuer), }); - if (UmbracoBackOfficeIdentity.FromClaimsIdentity(claimsIdentity, out _)) + if (claimsIdentity.VerifyBackOfficeIdentity(out _)) { Assert.Fail(); } @@ -112,8 +112,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.BackOffice new Claim("TestClaim1", "test", ClaimValueTypes.Integer32, TestIssuer, TestIssuer) }); - var identity = new UmbracoBackOfficeIdentity( - claimsIdentity, + claimsIdentity.AddRequiredClaims( "1234", "testing", "hello world", @@ -124,25 +123,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.BackOffice new[] { "content", "media" }, new[] { "admin" }); - Assert.AreEqual(12, identity.Claims.Count()); - Assert.IsNull(identity.Actor); - } - - [Test] - public void Clone() - { - var securityStamp = Guid.NewGuid().ToString(); - - var identity = new UmbracoBackOfficeIdentity( - "1234", "testing", "hello world", new[] { 654 }, new[] { 654 }, "en-us", securityStamp, new[] { "content", "media" }, new[] { "admin" }); - - // this will be filtered out during cloning - identity.AddClaim(new Claim(Constants.Security.TicketExpiresClaimType, "test")); - - ClaimsIdentity cloned = identity.Clone(); - Assert.IsNull(cloned.Actor); - - Assert.AreEqual(10, cloned.Claims.Count()); + Assert.AreEqual(12, claimsIdentity.Claims.Count()); + Assert.IsNull(claimsIdentity.Actor); } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/ClaimsPrincipalExtensionsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/ClaimsPrincipalExtensionsTests.cs index fe688ceb76..f0ec8698ee 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/ClaimsPrincipalExtensionsTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/ClaimsPrincipalExtensionsTests.cs @@ -5,9 +5,8 @@ using System; using System.Linq; using System.Security.Claims; using NUnit.Framework; -using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core; using Umbraco.Extensions; -using Constants = Umbraco.Cms.Core.Constants; namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Extensions { @@ -17,7 +16,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Extensions [Test] public void Get_Remaining_Ticket_Seconds() { - var backOfficeIdentity = new UmbracoBackOfficeIdentity( + var backOfficeIdentity = new ClaimsIdentity(); + backOfficeIdentity.AddRequiredClaims( Constants.Security.SuperUserIdAsString, "test", "test", @@ -27,6 +27,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Extensions Guid.NewGuid().ToString(), Enumerable.Empty(), Enumerable.Empty()); + var principal = new ClaimsPrincipal(backOfficeIdentity); var expireSeconds = 99; @@ -37,16 +38,52 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Extensions var expires = now.AddSeconds(expireSeconds).ToString("o"); backOfficeIdentity.AddClaim(new Claim( - Constants.Security.TicketExpiresClaimType, - expires, - ClaimValueTypes.DateTime, - UmbracoBackOfficeIdentity.Issuer, - UmbracoBackOfficeIdentity.Issuer, - backOfficeIdentity)); + Constants.Security.TicketExpiresClaimType, + expires, + ClaimValueTypes.DateTime, + Constants.Security.BackOfficeAuthenticationType, + Constants.Security.BackOfficeAuthenticationType, + backOfficeIdentity)); var ticketRemainingSeconds = principal.GetRemainingAuthSeconds(then); Assert.AreEqual(remainingSeconds, ticketRemainingSeconds); } + + [Test] + public void AddOrUpdateClaim__Should_ensure_a_claim_is_not_added_twice() + { + var backOfficeIdentity = new ClaimsIdentity(); + backOfficeIdentity.AddRequiredClaims( + Constants.Security.SuperUserIdAsString, + "test", + "test", + Enumerable.Empty(), + Enumerable.Empty(), + "en-US", + Guid.NewGuid().ToString(), + Enumerable.Empty(), + Enumerable.Empty()); + + var expireSeconds = 99; + + DateTimeOffset now = DateTimeOffset.Now; + + var expires = now.AddSeconds(expireSeconds).ToString("o"); + + + var claim = new Claim( + Constants.Security.TicketExpiresClaimType, + expires, + ClaimValueTypes.DateTime, + Constants.Security.BackOfficeAuthenticationType, + Constants.Security.BackOfficeAuthenticationType, + backOfficeIdentity); + + backOfficeIdentity.AddOrUpdateClaim(claim); + backOfficeIdentity.AddOrUpdateClaim(claim); + + Assert.AreEqual(1, backOfficeIdentity.Claims.Count(x=>x.Type == Constants.Security.TicketExpiresClaimType)); + } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackOffice/BackOfficeClaimsPrincipalFactoryTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackOffice/BackOfficeClaimsPrincipalFactoryTests.cs index f53dc2b7d6..f893d06daa 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackOffice/BackOfficeClaimsPrincipalFactoryTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackOffice/BackOfficeClaimsPrincipalFactoryTests.cs @@ -57,13 +57,13 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.BackOffice } [Test] - public async Task CreateAsync_Should_Create_Principal_With_Umbraco_Identity() + public async Task CreateAsync_Should_Create_Principal_With_Claims_Identity() { BackOfficeClaimsPrincipalFactory sut = CreateSut(); ClaimsPrincipal claimsPrincipal = await sut.CreateAsync(_testUser); - var umbracoBackOfficeIdentity = claimsPrincipal.Identity as UmbracoBackOfficeIdentity; + var umbracoBackOfficeIdentity = claimsPrincipal.Identity as ClaimsIdentity; Assert.IsNotNull(umbracoBackOfficeIdentity); } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Security/BackOfficeAntiforgeryTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Security/BackOfficeAntiforgeryTests.cs index 9b77e6c60d..f3abcabe61 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Security/BackOfficeAntiforgeryTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Security/BackOfficeAntiforgeryTests.cs @@ -12,10 +12,8 @@ using Microsoft.Extensions.Options; using Microsoft.Net.Http.Headers; using NUnit.Framework; using Umbraco.Cms.Core; -using Umbraco.Cms.Core.Security; using Umbraco.Cms.Web.BackOffice.Security; using Umbraco.Extensions; -using Constants = Umbraco.Cms.Core.Constants; namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.BackOffice.Security { @@ -24,18 +22,21 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.BackOffice.Security { private HttpContext GetHttpContext() { + var identity = new ClaimsIdentity(); + identity.AddRequiredClaims( + Constants.Security.SuperUserIdAsString, + "test", + "test", + Enumerable.Empty(), + Enumerable.Empty(), + "en-US", + Guid.NewGuid().ToString(), + Enumerable.Empty(), + Enumerable.Empty()); + var httpContext = new DefaultHttpContext() { - User = new ClaimsPrincipal(new UmbracoBackOfficeIdentity( - Constants.Security.SuperUserIdAsString, - "test", - "test", - Enumerable.Empty(), - Enumerable.Empty(), - "en-US", - Guid.NewGuid().ToString(), - Enumerable.Empty(), - Enumerable.Empty())) + User = new ClaimsPrincipal(identity) }; httpContext.Request.IsHttps = true; return httpContext; diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs index c2761819ad..9aa9350fba 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs @@ -584,7 +584,7 @@ namespace Umbraco.Tests.PublishedContent Assert.IsNotNull(result); Assert.AreEqual(3, result.Length); - Assert.IsTrue(result.Select(x => ((dynamic)x).Id).ContainsAll(new dynamic[] { 1174, 1173, 1046 })); + Assert.IsTrue(result.Select(x => ((dynamic)x).GetId()).ContainsAll(new dynamic[] { 1174, 1173, 1046 })); } [Test] @@ -597,7 +597,7 @@ namespace Umbraco.Tests.PublishedContent Assert.IsNotNull(result); Assert.AreEqual(2, result.Length); - Assert.IsTrue(result.Select(x => ((dynamic)x).Id).ContainsAll(new dynamic[] { 1173, 1046 })); + Assert.IsTrue(result.Select(x => ((dynamic)x).GetId()).ContainsAll(new dynamic[] { 1173, 1046 })); } [Test] @@ -710,7 +710,7 @@ namespace Umbraco.Tests.PublishedContent Assert.IsNotNull(result); Assert.AreEqual(10, result.Count()); - Assert.IsTrue(result.Select(x => ((dynamic)x).Id).ContainsAll(new dynamic[] { 1046, 1173, 1174, 1176, 1175 })); + Assert.IsTrue(result.Select(x => ((dynamic)x).GetId()).ContainsAll(new dynamic[] { 1046, 1173, 1174, 1176, 1175 })); } [Test] @@ -723,7 +723,7 @@ namespace Umbraco.Tests.PublishedContent Assert.IsNotNull(result); Assert.AreEqual(9, result.Count()); - Assert.IsTrue(result.Select(x => ((dynamic)x).Id).ContainsAll(new dynamic[] { 1173, 1174, 1176, 1175, 4444 })); + Assert.IsTrue(result.Select(x => ((dynamic)x).GetId()).ContainsAll(new dynamic[] { 1173, 1174, 1176, 1175, 4444 })); } [Test] diff --git a/src/Umbraco.Tests/TestHelpers/ControllerTesting/AuthenticateEverythingMiddleware.cs b/src/Umbraco.Tests/TestHelpers/ControllerTesting/AuthenticateEverythingMiddleware.cs index 53db23ec0a..1561ad5611 100644 --- a/src/Umbraco.Tests/TestHelpers/ControllerTesting/AuthenticateEverythingMiddleware.cs +++ b/src/Umbraco.Tests/TestHelpers/ControllerTesting/AuthenticateEverythingMiddleware.cs @@ -1,12 +1,11 @@ using System; +using System.Security.Claims; using System.Threading.Tasks; using Microsoft.Owin; using Microsoft.Owin.Security; using Microsoft.Owin.Security.Infrastructure; using Owin; -using Umbraco.Cms.Core; -using Umbraco.Cms.Core.Security; -using Umbraco.Core.Security; +using Umbraco.Extensions; namespace Umbraco.Tests.TestHelpers.ControllerTesting { @@ -30,8 +29,18 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting protected override Task AuthenticateCoreAsync() { var securityStamp = Guid.NewGuid().ToString(); - var identity = new UmbracoBackOfficeIdentity( - Constants.Security.SuperUserIdAsString, "admin", "Admin", new[] { -1 }, new[] { -1 }, "en-US", securityStamp, new[] { "content", "media", "members" }, new[] { "admin" }); + + var identity = new ClaimsIdentity(); + identity.AddRequiredClaims( + Cms.Core.Constants.Security.SuperUserIdAsString, + "admin", + "Admin", + new[] { -1 }, + new[] { -1 }, + "en-US", + securityStamp, + new[] { "content", "media", "members" }, + new[] { "admin" }); return Task.FromResult(new AuthenticationTicket( identity, diff --git a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs index 517198a8f1..4b62606b5f 100644 --- a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs +++ b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Net.Http; +using System.Security.Claims; using System.Web; using System.Web.Http; using System.Web.Http.Controllers; @@ -22,6 +23,7 @@ using Umbraco.Web.Routing; using Umbraco.Web.WebApi; using Umbraco.Tests.TestHelpers.Entities; using Umbraco.Core.Security; +using Umbraco.Extensions; namespace Umbraco.Tests.TestHelpers.ControllerTesting { @@ -97,29 +99,29 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting //chuck it into the props since this is what MS does when hosted and it's needed there request.Properties["MS_HttpContext"] = httpContext; - var backofficeIdentity = (UmbracoBackOfficeIdentity) owinContext.Authentication.User.Identity; + var backofficeIdentity = (ClaimsIdentity) owinContext.Authentication.User.Identity; var backofficeSecurity = new Mock(); //mock CurrentUser var groups = new List(); - for (var index = 0; index < backofficeIdentity.Roles.Length; index++) + for (var index = 0; index < backofficeIdentity.GetRoles().Length; index++) { - var role = backofficeIdentity.Roles[index]; + var role = backofficeIdentity.GetRoles()[index]; groups.Add(new ReadOnlyUserGroup(index + 1, role, "icon-user", null, null, role, new string[0], new string[0])); } var mockUser = MockedUser.GetUserMock(); mockUser.Setup(x => x.IsApproved).Returns(true); mockUser.Setup(x => x.IsLockedOut).Returns(false); - mockUser.Setup(x => x.AllowedSections).Returns(backofficeIdentity.AllowedApplications); + mockUser.Setup(x => x.AllowedSections).Returns(backofficeIdentity.GetAllowedApplications()); mockUser.Setup(x => x.Groups).Returns(groups); mockUser.Setup(x => x.Email).Returns("admin@admin.com"); - mockUser.Setup(x => x.Id).Returns((int)backofficeIdentity.Id); + mockUser.Setup(x => x.Id).Returns((int)backofficeIdentity.GetId()); mockUser.Setup(x => x.Language).Returns("en"); - mockUser.Setup(x => x.Name).Returns(backofficeIdentity.RealName); - mockUser.Setup(x => x.StartContentIds).Returns(backofficeIdentity.StartContentNodes); - mockUser.Setup(x => x.StartMediaIds).Returns(backofficeIdentity.StartMediaNodes); - mockUser.Setup(x => x.Username).Returns(backofficeIdentity.Username); + mockUser.Setup(x => x.Name).Returns(backofficeIdentity.GetRealName()); + mockUser.Setup(x => x.StartContentIds).Returns(backofficeIdentity.GetStartContentNodes()); + mockUser.Setup(x => x.StartMediaIds).Returns(backofficeIdentity.GetStartMediaNodes()); + mockUser.Setup(x => x.Username).Returns(backofficeIdentity.GetUsername()); backofficeSecurity.Setup(x => x.CurrentUser) .Returns(mockUser.Object); diff --git a/src/Umbraco.Web.BackOffice/Filters/CheckIfUserTicketDataIsStaleAttribute.cs b/src/Umbraco.Web.BackOffice/Filters/CheckIfUserTicketDataIsStaleAttribute.cs index 1aa71f149a..b13d9a82a8 100644 --- a/src/Umbraco.Web.BackOffice/Filters/CheckIfUserTicketDataIsStaleAttribute.cs +++ b/src/Umbraco.Web.BackOffice/Filters/CheckIfUserTicketDataIsStaleAttribute.cs @@ -112,13 +112,13 @@ namespace Umbraco.Cms.Web.BackOffice.Filters return; } - var identity = actionContext.HttpContext.User.Identity as UmbracoBackOfficeIdentity; + var identity = actionContext.HttpContext.User.Identity as ClaimsIdentity; if (identity == null) { return; } - Attempt userId = identity.Id.TryConvertTo(); + Attempt userId = identity.GetId().TryConvertTo(); if (userId == false) { return; @@ -133,23 +133,23 @@ namespace Umbraco.Cms.Web.BackOffice.Filters // a list of checks to execute, if any of them pass then we resync var checks = new Func[] { - () => user.Username != identity.Username, + () => user.Username != identity.GetUsername(), () => { CultureInfo culture = user.GetUserCulture(_localizedTextService, _globalSettings.Value); - return culture != null && culture.ToString() != identity.Culture; + return culture != null && culture.ToString() != identity.GetCultureString(); }, - () => user.AllowedSections.UnsortedSequenceEqual(identity.AllowedApplications) == false, - () => user.Groups.Select(x => x.Alias).UnsortedSequenceEqual(identity.Roles) == false, + () => user.AllowedSections.UnsortedSequenceEqual(identity.GetAllowedApplications()) == false, + () => user.Groups.Select(x => x.Alias).UnsortedSequenceEqual(identity.GetRoles()) == false, () => { var startContentIds = user.CalculateContentStartNodeIds(_entityService); - return startContentIds.UnsortedSequenceEqual(identity.StartContentNodes) == false; + return startContentIds.UnsortedSequenceEqual(identity.GetStartContentNodes()) == false; }, () => { var startMediaIds = user.CalculateMediaStartNodeIds(_entityService); - return startMediaIds.UnsortedSequenceEqual(identity.StartMediaNodes) == false; + return startMediaIds.UnsortedSequenceEqual(identity.GetStartMediaNodes()) == false; } }; diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeSecureDataFormat.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeSecureDataFormat.cs index 8df128661b..c365273cbe 100644 --- a/src/Umbraco.Web.BackOffice/Security/BackOfficeSecureDataFormat.cs +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeSecureDataFormat.cs @@ -1,13 +1,13 @@ using System; using System.Security.Claims; +using Umbraco.Extensions; using Microsoft.AspNetCore.Authentication; -using Umbraco.Cms.Core.Security; namespace Umbraco.Cms.Web.BackOffice.Security { /// - /// Custom secure format that ensures the Identity in the ticket is and not just a ClaimsIdentity + /// Custom secure format that ensures the Identity in the ticket is verified /// internal class BackOfficeSecureDataFormat : ISecureDataFormat { @@ -59,11 +59,14 @@ namespace Umbraco.Cms.Web.BackOffice.Security return null; } - if (!UmbracoBackOfficeIdentity.FromClaimsIdentity((ClaimsIdentity)decrypt.Principal.Identity, out var identity)) + var identity = (ClaimsIdentity)decrypt.Principal.Identity; + if (!identity.VerifyBackOfficeIdentity(out ClaimsIdentity verifiedIdentity)) + { return null; + } //return the ticket with a UmbracoBackOfficeIdentity - var ticket = new AuthenticationTicket(new ClaimsPrincipal(identity), decrypt.Properties, decrypt.AuthenticationScheme); + var ticket = new AuthenticationTicket(new ClaimsPrincipal(verifiedIdentity), decrypt.Properties, decrypt.AuthenticationScheme); return ticket; } diff --git a/src/Umbraco.Web.BackOffice/Security/ConfigureBackOfficeCookieOptions.cs b/src/Umbraco.Web.BackOffice/Security/ConfigureBackOfficeCookieOptions.cs index 03807fd70d..9e9549134e 100644 --- a/src/Umbraco.Web.BackOffice/Security/ConfigureBackOfficeCookieOptions.cs +++ b/src/Umbraco.Web.BackOffice/Security/ConfigureBackOfficeCookieOptions.cs @@ -12,11 +12,9 @@ using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.Net; using Umbraco.Cms.Core.Routing; -using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Web; using Umbraco.Extensions; -using Constants = Umbraco.Cms.Core.Constants; namespace Umbraco.Cms.Web.BackOffice.Security { @@ -136,7 +134,7 @@ namespace Umbraco.Cms.Web.BackOffice.Security // Same goes for the signinmanager IBackOfficeSignInManager signInManager = ctx.HttpContext.RequestServices.GetRequiredService(); - UmbracoBackOfficeIdentity backOfficeIdentity = ctx.Principal.GetUmbracoIdentity(); + ClaimsIdentity backOfficeIdentity = ctx.Principal.GetUmbracoIdentity(); if (backOfficeIdentity == null) { ctx.RejectPrincipal(); @@ -150,33 +148,33 @@ namespace Umbraco.Cms.Web.BackOffice.Security await securityStampValidator.ValidateAsync(ctx); EnsureTicketRenewalIfKeepUserLoggedIn(ctx); - // add a claim to track when the cookie expires, we use this to track time remaining - backOfficeIdentity.AddClaim(new Claim( + // add or update a claim to track when the cookie expires, we use this to track time remaining + backOfficeIdentity.AddOrUpdateClaim(new Claim( Constants.Security.TicketExpiresClaimType, ctx.Properties.ExpiresUtc.Value.ToString("o"), ClaimValueTypes.DateTime, - UmbracoBackOfficeIdentity.Issuer, - UmbracoBackOfficeIdentity.Issuer, + Constants.Security.BackOfficeAuthenticationType, + Constants.Security.BackOfficeAuthenticationType, backOfficeIdentity)); }, OnSigningIn = ctx => { // occurs when sign in is successful but before the ticket is written to the outbound cookie - UmbracoBackOfficeIdentity backOfficeIdentity = ctx.Principal.GetUmbracoIdentity(); + ClaimsIdentity backOfficeIdentity = ctx.Principal.GetUmbracoIdentity(); if (backOfficeIdentity != null) { // generate a session id and assign it // create a session token - if we are configured and not in an upgrade state then use the db, otherwise just generate one Guid session = _runtimeState.Level == RuntimeLevel.Run - ? _userService.CreateLoginSession(backOfficeIdentity.Id, _ipResolver.GetCurrentRequestIpAddress()) + ? _userService.CreateLoginSession(backOfficeIdentity.GetId(), _ipResolver.GetCurrentRequestIpAddress()) : Guid.NewGuid(); // add our session claim - backOfficeIdentity.AddClaim(new Claim(Constants.Security.SessionIdClaimType, session.ToString(), ClaimValueTypes.String, UmbracoBackOfficeIdentity.Issuer, UmbracoBackOfficeIdentity.Issuer, backOfficeIdentity)); + backOfficeIdentity.AddClaim(new Claim(Constants.Security.SessionIdClaimType, session.ToString(), ClaimValueTypes.String, Constants.Security.BackOfficeAuthenticationType, Constants.Security.BackOfficeAuthenticationType, backOfficeIdentity)); // since it is a cookie-based authentication add that claim - backOfficeIdentity.AddClaim(new Claim(ClaimTypes.CookiePath, "/", ClaimValueTypes.String, UmbracoBackOfficeIdentity.Issuer, UmbracoBackOfficeIdentity.Issuer, backOfficeIdentity)); + backOfficeIdentity.AddClaim(new Claim(ClaimTypes.CookiePath, "/", ClaimValueTypes.String, Constants.Security.BackOfficeAuthenticationType, Constants.Security.BackOfficeAuthenticationType, backOfficeIdentity)); } return Task.CompletedTask; diff --git a/src/Umbraco.Web.Common/Extensions/HttpContextExtensions.cs b/src/Umbraco.Web.Common/Extensions/HttpContextExtensions.cs index 6579c69536..9513eb2ec2 100644 --- a/src/Umbraco.Web.Common/Extensions/HttpContextExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/HttpContextExtensions.cs @@ -32,7 +32,7 @@ namespace Umbraco.Extensions /// /// Returns the current back office identity if an admin is authenticated otherwise null /// - public static UmbracoBackOfficeIdentity GetCurrentIdentity(this HttpContext http) + public static ClaimsIdentity GetCurrentIdentity(this HttpContext http) { if (http == null) throw new ArgumentNullException(nameof(http)); if (http.User == null) return null; //there's no user at all so no identity diff --git a/src/Umbraco.Web.Common/Security/BackOfficeUserManager.cs b/src/Umbraco.Web.Common/Security/BackOfficeUserManager.cs index fe939c1547..18029228b4 100644 --- a/src/Umbraco.Web.Common/Security/BackOfficeUserManager.cs +++ b/src/Umbraco.Web.Common/Security/BackOfficeUserManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Security.Claims; using System.Security.Principal; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; @@ -176,8 +177,8 @@ namespace Umbraco.Cms.Web.Common.Security private string GetCurrentUserId(IPrincipal currentUser) { - UmbracoBackOfficeIdentity umbIdentity = currentUser?.GetUmbracoIdentity(); - var currentUserId = umbIdentity?.GetUserId() ?? Cms.Core.Constants.Security.SuperUserIdAsString; + ClaimsIdentity umbIdentity = currentUser?.GetUmbracoIdentity(); + var currentUserId = umbIdentity?.GetUserId() ?? Core.Constants.Security.SuperUserIdAsString; return currentUserId; } diff --git a/src/Umbraco.Web.Common/Security/BackofficeSecurity.cs b/src/Umbraco.Web.Common/Security/BackofficeSecurity.cs index 27dd822f1a..d7a0aeb043 100644 --- a/src/Umbraco.Web.Common/Security/BackofficeSecurity.cs +++ b/src/Umbraco.Web.Common/Security/BackofficeSecurity.cs @@ -55,7 +55,7 @@ namespace Umbraco.Cms.Web.Common.Security public Attempt GetUserId() { var identity = _httpContextAccessor.HttpContext?.GetCurrentIdentity(); - return identity == null ? Attempt.Fail() : Attempt.Succeed(identity.Id); + return identity == null ? Attempt.Fail() : Attempt.Succeed(identity.GetId()); } /// diff --git a/src/Umbraco.Web.Common/Security/MemberManager.cs b/src/Umbraco.Web.Common/Security/MemberManager.cs index 386b1ba231..00bdcbd436 100644 --- a/src/Umbraco.Web.Common/Security/MemberManager.cs +++ b/src/Umbraco.Web.Common/Security/MemberManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Security.Claims; using System.Security.Principal; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity; @@ -39,7 +40,7 @@ namespace Umbraco.Web.Common.Security private string GetCurrentUserId(IPrincipal currentUser) { - UmbracoBackOfficeIdentity umbIdentity = currentUser?.GetUmbracoIdentity(); + ClaimsIdentity umbIdentity = currentUser?.GetUmbracoIdentity(); var currentUserId = umbIdentity?.GetUserId() ?? Cms.Core.Constants.Security.SuperUserIdAsString; return currentUserId; }