Keep custom claims that are flowed from autolinking

When auto linking with callbacks such as OnExternalLogin, custom claims can be added to the user which are flowed to the auth ticket/identity.
However, when the security stamp validator executes, the identity is re-created manually without any knowledge of those custom claims so
they are lost. This ensures that those custom claims flow through to the re-generated identity during the security stamp validation phase.
This commit is contained in:
Shannon
2021-02-23 10:41:00 +11:00
parent 81f1c2e7d6
commit e3dc017d7e
4 changed files with 58 additions and 12 deletions

View File

@@ -169,11 +169,24 @@ namespace Umbraco.Web.Security
// Enables the application to validate the security stamp when the user
// logs in. This is a security feature which is used when you
// change a password or add an external login to your account.
OnValidateIdentity = SecurityStampValidator
.OnValidateIdentity<BackOfficeUserManager, BackOfficeIdentityUser, int>(
TimeSpan.FromMinutes(30),
(manager, user) => manager.GenerateUserIdentityAsync(user),
identity => identity.GetUserId<int>()),
OnValidateIdentity = context =>
{
var identity = context.Identity;
return SecurityStampValidator
.OnValidateIdentity<BackOfficeUserManager, BackOfficeIdentityUser, int>(
TimeSpan.FromMinutes(3),
async (manager, user) =>
{
var regenerated = await manager.GenerateUserIdentityAsync(user);
// Keep any custom claims from the original identity
regenerated.MergeClaimsFromBackOfficeIdentity(identity);
return regenerated;
},
identity => identity.GetUserId<int>())(context);
}
};

View File

@@ -27,12 +27,6 @@ namespace Umbraco.Web.Security
{
var baseIdentity = await base.CreateAsync(manager, user, authenticationType);
// now we can flow any custom claims that the actual user has currently assigned which could be done in the OnExternalLogin callback
foreach (var claim in user.Claims)
{
baseIdentity.AddClaim(new Claim(claim.ClaimType, claim.ClaimValue));
}
var umbracoIdentity = new UmbracoBackOfficeIdentity(baseIdentity,
user.Id,
user.UserName,
@@ -40,12 +34,16 @@ namespace Umbraco.Web.Security
user.CalculatedContentStartNodeIds,
user.CalculatedMediaStartNodeIds,
user.Culture,
//NOTE - there is no session id assigned here, this is just creating the identity, a session id will be generated when the cookie is written
// NOTE - there is no session id assigned here, this is just creating the identity, a session id will be generated when the cookie is written
Guid.NewGuid().ToString(),
user.SecurityStamp,
user.AllowedSections,
user.Roles.Select(x => x.RoleId).ToArray());
// now we can flow any custom claims that the actual user has currently
// assigned which could be done in the OnExternalLogin callback
umbracoIdentity.MergeClaimsFromBackOfficeIdentity(user);
return umbracoIdentity;
}
}

View File

@@ -0,0 +1,34 @@
using System.Linq;
using System.Security.Claims;
using Umbraco.Core.Models.Identity;
using Constants = Umbraco.Core.Constants;
namespace Umbraco.Web.Security
{
internal static class ClaimsIdentityExtensions
{
// Ignore these Claims when merging, these claims are dynamically added whenever the ticket
// is re-issued and we don't want to merge old values of these.
private static readonly string[] IgnoredClaims = new[] { ClaimTypes.CookiePath, Constants.Security.SessionIdClaimType };
internal static void MergeClaimsFromBackOfficeIdentity(this ClaimsIdentity destination, ClaimsIdentity source)
{
foreach (var claim in source.Claims
.Where(claim => !IgnoredClaims.Contains(claim.Type))
.Where(claim => !destination.HasClaim(claim.Type, claim.Value)))
{
destination.AddClaim(new Claim(claim.Type, claim.Value));
}
}
internal static void MergeClaimsFromBackOfficeIdentity(this ClaimsIdentity destination, BackOfficeIdentityUser source)
{
foreach (var claim in source.Claims
.Where(claim => !IgnoredClaims.Contains(claim.ClaimType))
.Where(claim => !destination.HasClaim(claim.ClaimType, claim.ClaimValue)))
{
destination.AddClaim(new Claim(claim.ClaimType, claim.ClaimValue));
}
}
}
}

View File

@@ -288,6 +288,7 @@
<Compile Include="Security\BackOfficeExternalLoginProviderErrorMiddlware.cs" />
<Compile Include="Security\BackOfficeExternalLoginProviderErrors.cs" />
<Compile Include="Security\BackOfficeExternalLoginProviderOptions.cs" />
<Compile Include="Security\ClaimsIdentityExtensions.cs" />
<Compile Include="Security\SignOutAuditEventArgs.cs" />
<Compile Include="Security\UserInviteEventArgs.cs" />
<Compile Include="Services\DashboardService.cs" />