Merge branch 'v14/dev' into v15/dev

# Conflicts:
#	src/Umbraco.Infrastructure/Runtime/FileSystemMainDomLock.cs
#	src/Umbraco.Web.Common/Views/UmbracoViewPage.cs
This commit is contained in:
Sven Geusens
2025-02-18 11:09:52 +01:00
26 changed files with 419 additions and 71 deletions

View File

@@ -62,9 +62,16 @@ public static class HttpContextExtensions
// Update the HttpContext's user with the authenticated user's principal to ensure
// that subsequent requests within the same context will recognize the user
// as authenticated.
if (result.Succeeded)
if (result is { Succeeded: true, Principal.Identity: not null })
{
httpContext.User = result.Principal;
// We need to get existing identities that are not the backoffice kind and flow them to the new identity
// Otherwise we can't log in as both a member and a backoffice user
// For instance if you've enabled basic auth.
ClaimsPrincipal? authenticatedPrincipal = result.Principal;
IEnumerable<ClaimsIdentity> existingIdentities = httpContext.User.Identities.Where(x => x.IsAuthenticated && x.AuthenticationType != authenticatedPrincipal.Identity.AuthenticationType);
authenticatedPrincipal.AddIdentities(existingIdentities);
httpContext.User = authenticatedPrincipal;
}
return result;

View File

@@ -0,0 +1,18 @@
using System.Security.Claims;
using Microsoft.AspNetCore.Identity;
namespace Umbraco.Extensions;
public static class MemberClaimsPrincipalExtensions
{
/// <summary>
/// Tries to get specifically the member identity from the ClaimsPrincipal
/// </summary>
/// <remarks>
/// The identity returned is the one with default authentication type.
/// </remarks>
/// <param name="principal">The principal to find the identity in.</param>
/// <returns>The default authenticated authentication type identity.</returns>
public static ClaimsIdentity? GetMemberIdentity(this ClaimsPrincipal principal)
=> principal.Identities.FirstOrDefault(x => x.AuthenticationType == IdentityConstants.ApplicationScheme);
}

View File

@@ -24,7 +24,7 @@ public sealed class ConfigureMemberIdentityOptions : IConfigureOptions<IdentityO
options.SignIn.RequireConfirmedEmail = false; // not implemented
options.SignIn.RequireConfirmedPhoneNumber = false; // not implemented
options.User.RequireUniqueEmail = true;
options.User.RequireUniqueEmail = _securitySettings.MemberRequireUniqueEmail;
// Support validation of member names using Down-Level Logon Name format
options.User.AllowedUserNameCharacters = _securitySettings.AllowedUserNameCharacters;

View File

@@ -1,4 +1,5 @@
using System.Globalization;
using System.Security.Claims;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
@@ -113,8 +114,11 @@ public class MemberManager : UmbracoUserManager<MemberIdentityUser, MemberPasswo
/// <inheritdoc />
public virtual bool IsLoggedIn()
{
HttpContext? httpContext = _httpContextAccessor.HttpContext;
return httpContext?.User.Identity?.IsAuthenticated ?? false;
// We have to try and specifically find the member identity, it's entirely possible for there to be both backoffice and member.
ClaimsIdentity? memberIdentity = _httpContextAccessor.HttpContext?.User.GetMemberIdentity();
return memberIdentity is not null &&
memberIdentity.IsAuthenticated;
}
/// <inheritdoc />
@@ -170,23 +174,27 @@ public class MemberManager : UmbracoUserManager<MemberIdentityUser, MemberPasswo
/// <inheritdoc />
public virtual async Task<MemberIdentityUser?> GetCurrentMemberAsync()
{
if (_currentMember == null)
if (_currentMember is not null)
{
if (!IsLoggedIn())
{
return null;
}
_currentMember = await GetUserAsync(_httpContextAccessor.HttpContext?.User!);
return _currentMember;
}
if (IsLoggedIn() is false)
{
return null;
}
// Create a principal the represents the member security context.
var memberPrincipal = new ClaimsPrincipal(_httpContextAccessor.HttpContext?.User.GetMemberIdentity()!);
_currentMember = await GetUserAsync(memberPrincipal);
return _currentMember;
}
public virtual IPublishedContent? AsPublishedMember(MemberIdentityUser user) => _store.GetPublishedMember(user);
/// <summary>
/// This will check if the member has access to this path
/// This will check if the member has access to this path.
/// </summary>
/// <param name="path"></param>
/// <returns></returns>