Merge branch 'v13/dev' into v14/dev

# Conflicts:
#	Directory.Build.props
#	build/azure-pipelines.yml
#	src/Umbraco.Cms.Api.Common/DependencyInjection/UmbracoBuilderAuthExtensions.cs
#	src/Umbraco.Cms.Api.Common/OpenApi/SwaggerRouteTemplatePipelineFilter.cs
#	src/Umbraco.Cms.Api.Common/Security/Paths.cs
#	src/Umbraco.Cms.Api.Delivery/Controllers/Security/MemberController.cs
#	src/Umbraco.Cms.Api.Delivery/DependencyInjection/UmbracoBuilderExtensions.cs
#	src/Umbraco.Cms.Api.Delivery/Handlers/InitializeMemberApplicationNotificationHandler.cs
#	src/Umbraco.Cms.Api.Delivery/Handlers/RevokeMemberAuthenticationTokensNotificationHandler.cs
#	src/Umbraco.Cms.Api.Delivery/Security/MemberApplicationManager.cs
#	src/Umbraco.Cms.Api.Delivery/Services/RequestMemberAccessService.cs
#	src/Umbraco.Core/Constants-OAuthClaims.cs
#	src/Umbraco.Core/Constants-OAuthClientIds.cs
#	src/Umbraco.Core/DeliveryApi/IApiContentQueryProvider.cs
#	src/Umbraco.Core/DeliveryApi/IApiContentQueryService.cs
#	src/Umbraco.Core/DeliveryApi/IRequestMemberAccessService.cs
#	src/Umbraco.Core/DeliveryApi/NoopRequestMemberAccessService.cs
#	src/Umbraco.Core/Models/DeliveryApi/ProtectedAccess.cs
#	src/Umbraco.Core/Services/ITagService.cs
#	src/Umbraco.Core/Services/UserService.cs
#	src/Umbraco.Infrastructure/Security/IMemberApplicationManager.cs
#	src/Umbraco.Infrastructure/Security/OpenIdDictApplicationManagerBase.cs
#	src/Umbraco.Web.BackOffice/Controllers/MediaController.cs
#	tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/HelpPanel/systemInformation.spec.ts
#	version.json
This commit is contained in:
kjac
2023-10-23 19:03:13 +02:00
200 changed files with 3248 additions and 2475 deletions

View File

@@ -9,7 +9,7 @@ public static class FormCollectionExtensions
{
/// <summary>
/// Converts a dictionary object to a query string representation such as:
/// firstname=shannon&lastname=deminick
/// firstname=shannon&amp;lastname=deminick
/// </summary>
/// <param name="items"></param>
/// <param name="keysToIgnore">Any keys found in this collection will be removed from the output</param>

View File

@@ -3,6 +3,7 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Web.Common.Security;
namespace Umbraco.Extensions;
@@ -39,6 +40,17 @@ public static partial class UmbracoApplicationBuilderExtensions
return builder;
}
public static IUmbracoBuilder SetMemberSignInManager<TSignInManager>(this IUmbracoBuilder builder)
where TSignInManager : SignInManager<MemberIdentityUser>, IMemberSignInManager
{
Type customType = typeof(TSignInManager);
Type signInManagerType = typeof(SignInManager<MemberIdentityUser>);
builder.Services.Replace(ServiceDescriptor.Scoped(typeof(IMemberSignInManager), customType));
builder.Services.AddScoped(customType, services => services.GetRequiredService(signInManagerType));
builder.Services.Replace(ServiceDescriptor.Scoped(signInManagerType, customType));
return builder;
}
public static IUmbracoBuilder SetMemberUserStore<TUserStore>(this IUmbracoBuilder builder)
where TUserStore : MemberUserStore
{

View File

@@ -18,7 +18,7 @@ public class WebRootFileProviderFactory : ILegacyPackageManifestFileProviderFact
}
/// <summary>
/// Creates a new <see cref="IFileProvider" /> instance, pointing at <see cref="WebRootPath"/>.
/// Creates a new <see cref="IFileProvider" /> instance, pointing at <see cref="IWebHostEnvironment.WebRootPath"/>.
/// </summary>
/// <returns>
/// The newly created <see cref="IFileProvider" /> instance.

View File

@@ -13,8 +13,8 @@ namespace Umbraco.Cms.Web.Common.Filters;
/// Attribute used to check that the request contains a valid Umbraco form request string.
/// </summary>
/// <remarks>
/// Applying this attribute/filter to a <see cref="SurfaceController"/> or SurfaceController Action will ensure that the Action can only be executed
/// when it is routed to from within Umbraco, typically when rendering a form with BeginUmbracoForm. It will mean that the natural MVC route for this Action
/// Applying this attribute/filter to a <see cref="T:Umbraco.Cms.Web.Website.Controllers.SurfaceContorller"/> or SurfaceController Action will ensure that the Action can only be executed
/// when it is routed to from within Umbraco, typically when rendering a form with BeginUmbracoForm. It will mean that the natural MVC route for this Action
/// will fail with a <see cref="HttpUmbracoFormRouteStringException" />.
/// </remarks>
public class ValidateUmbracoFormRouteStringAttribute : TypeFilterAttribute

View File

@@ -114,8 +114,6 @@ public static class UmbracoBuilderDependencyInjectionExtensions
builder.AddInMemoryModelsRazorEngine();
builder.AddNotificationHandler<ModelBindingErrorNotification, ModelsBuilderNotificationHandler>();
builder.AddNotificationHandler<ContentTypeCacheRefresherNotification, OutOfDateModelsStatus>();
builder.AddNotificationHandler<DataTypeCacheRefresherNotification, OutOfDateModelsStatus>();
}
if (builder.Config.GetRuntimeMode() != RuntimeMode.Production)
@@ -128,12 +126,16 @@ public static class UmbracoBuilderDependencyInjectionExtensions
builder.AddNotificationHandler<UmbracoRequestEndNotification, AutoModelsNotificationHandler>();
builder.AddNotificationHandler<ContentTypeCacheRefresherNotification, AutoModelsNotificationHandler>();
builder.AddNotificationHandler<DataTypeCacheRefresherNotification, AutoModelsNotificationHandler>();
builder.AddNotificationHandler<ContentTypeCacheRefresherNotification, OutOfDateModelsStatus>();
builder.AddNotificationHandler<DataTypeCacheRefresherNotification, OutOfDateModelsStatus>();
}
builder.Services.TryAddSingleton<IModelsBuilderDashboardProvider, NoopModelsBuilderDashboardProvider>();
// Register required services for ModelsBuilderDashboardController
builder.Services.AddSingleton<IModelsGenerator, ModelsGenerator>();
// TODO: Remove in v13 - this is only here in case someone is already using this generator directly
builder.Services.AddSingleton<ModelsGenerator>();
builder.Services.AddSingleton<OutOfDateModelsStatus>();
@@ -163,6 +165,7 @@ public static class UmbracoBuilderDependencyInjectionExtensions
// This is what the community MB would replace, all of the above services are fine to be registered
builder.Services.AddSingleton<IPublishedModelFactory>(factory => factory.CreateDefaultPublishedModelFactory());
return builder;
}
}

View File

@@ -1,20 +1,44 @@
using System.Security.Claims;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Extensions;
namespace Umbraco.Cms.Web.Common.Security;
public class ConfigureSecurityStampOptions : IConfigureOptions<SecurityStampValidatorOptions>
{
private readonly SecuritySettings _securitySettings;
public ConfigureSecurityStampOptions()
: this(StaticServiceProvider.Instance.GetRequiredService<IOptions<SecuritySettings>>())
{
}
public ConfigureSecurityStampOptions(IOptions<SecuritySettings> securitySettings)
=> _securitySettings = securitySettings.Value;
[Obsolete("Use the overload accepting SecuritySettings instead. Scheduled for removal in v14.")]
public static void ConfigureOptions(SecurityStampValidatorOptions options)
=> ConfigureOptions(options, StaticServiceProvider.Instance.GetRequiredService<SecuritySettings>());
/// <summary>
/// Configures security stamp options and ensures any custom claims
/// set on the identity are persisted to the new identity when it's refreshed.
/// </summary>
/// <param name="options"></param>
public static void ConfigureOptions(SecurityStampValidatorOptions options)
/// <param name="options">Options for <see cref="ISecurityStampValidator"/>.</param>
/// <param name="securitySettings">The <see cref="SecuritySettings" /> options.</param>
public static void ConfigureOptions(SecurityStampValidatorOptions options, SecuritySettings securitySettings)
{
options.ValidationInterval = TimeSpan.FromMinutes(30);
// Adjust the security stamp validation interval to a shorter duration
// when concurrent logins are not allowed and the duration has the default interval value
// (currently defaults to 30 minutes), ensuring quicker re-validation.
if (securitySettings.AllowConcurrentLogins is false && options.ValidationInterval == TimeSpan.FromMinutes(30))
{
options.ValidationInterval = TimeSpan.FromSeconds(30);
}
// When refreshing the principal, ensure custom claims that
// might have been set with an external identity continue
@@ -34,6 +58,7 @@ public class ConfigureSecurityStampOptions : IConfigureOptions<SecurityStampVali
};
}
/// <inheritdoc />
public void Configure(SecurityStampValidatorOptions options)
=> ConfigureOptions(options);
=> ConfigureOptions(options, _securitySettings);
}

View File

@@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Notifications;
@@ -30,14 +31,40 @@ public class MemberSignInManager : UmbracoSignInManager<MemberIdentityUser>, IMe
IAuthenticationSchemeProvider schemes,
IUserConfirmation<MemberIdentityUser> confirmation,
IMemberExternalLoginProviders memberExternalLoginProviders,
IEventAggregator eventAggregator)
: base(memberManager, contextAccessor, claimsFactory, optionsAccessor, logger, schemes, confirmation)
IEventAggregator eventAggregator,
IOptions<SecuritySettings> securitySettings)
: base(memberManager, contextAccessor, claimsFactory, optionsAccessor, logger, schemes, confirmation, securitySettings)
{
_memberExternalLoginProviders = memberExternalLoginProviders;
_eventAggregator = eventAggregator;
}
[Obsolete("Use ctor with all params")]
[Obsolete("Use non-obsolete constructor. This is scheduled for removal in V14.")]
public MemberSignInManager(
UserManager<MemberIdentityUser> memberManager,
IHttpContextAccessor contextAccessor,
IUserClaimsPrincipalFactory<MemberIdentityUser> claimsFactory,
IOptions<IdentityOptions> optionsAccessor,
ILogger<SignInManager<MemberIdentityUser>> logger,
IAuthenticationSchemeProvider schemes,
IUserConfirmation<MemberIdentityUser> confirmation,
IMemberExternalLoginProviders memberExternalLoginProviders,
IEventAggregator eventAggregator)
: this(
memberManager,
contextAccessor,
claimsFactory,
optionsAccessor,
logger,
schemes,
confirmation,
StaticServiceProvider.Instance.GetRequiredService<IMemberExternalLoginProviders>(),
StaticServiceProvider.Instance.GetRequiredService<IEventAggregator>(),
StaticServiceProvider.Instance.GetRequiredService<IOptions<SecuritySettings>>())
{
}
[Obsolete("Use non-obsolete constructor. This is scheduled for removal in V14.")]
public MemberSignInManager(
UserManager<MemberIdentityUser> memberManager,
IHttpContextAccessor contextAccessor,

View File

@@ -2,26 +2,32 @@ using System.Security.Claims;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Security;
using Umbraco.Extensions;
namespace Umbraco.Cms.Web.Common.Security;
/// <summary>
/// Abstract sign in manager implementation allowing modifying all defeault authentication schemes
/// Abstract sign in manager implementation allowing modifying all default authentication schemes.
/// </summary>
/// <typeparam name="TUser"></typeparam>
public abstract class UmbracoSignInManager<TUser> : SignInManager<TUser>
where TUser : UmbracoIdentityUser
{
private SecuritySettings _securitySettings;
// borrowed from https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs
protected const string UmbracoSignInMgrLoginProviderKey = "LoginProvider";
// borrowed from https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs
protected const string UmbracoSignInMgrXsrfKey = "XsrfId";
[Obsolete("Use non-obsolete constructor. This is scheduled for removal in V14.")]
public UmbracoSignInManager(
UserManager<TUser> userManager,
IHttpContextAccessor contextAccessor,
@@ -30,8 +36,30 @@ public abstract class UmbracoSignInManager<TUser> : SignInManager<TUser>
ILogger<SignInManager<TUser>> logger,
IAuthenticationSchemeProvider schemes,
IUserConfirmation<TUser> confirmation)
: this(
userManager,
contextAccessor,
claimsFactory,
optionsAccessor,
logger,
schemes,
confirmation,
StaticServiceProvider.Instance.GetRequiredService<IOptions<SecuritySettings>>())
{
}
public UmbracoSignInManager(
UserManager<TUser> userManager,
IHttpContextAccessor contextAccessor,
IUserClaimsPrincipalFactory<TUser> claimsFactory,
IOptions<IdentityOptions> optionsAccessor,
ILogger<SignInManager<TUser>> logger,
IAuthenticationSchemeProvider schemes,
IUserConfirmation<TUser> confirmation,
IOptions<SecuritySettings> securitySettingsOptions)
: base(userManager, contextAccessor, claimsFactory, optionsAccessor, logger, schemes, confirmation)
{
_securitySettings = securitySettingsOptions.Value;
}
protected abstract string AuthenticationType { get; }
@@ -47,7 +75,7 @@ public abstract class UmbracoSignInManager<TUser> : SignInManager<TUser>
{
// override to handle logging/events
SignInResult result = await base.PasswordSignInAsync(user, password, isPersistent, lockoutOnFailure);
return await HandleSignIn(user, user.UserName, result);
return result;
}
/// <inheritdoc />
@@ -340,6 +368,11 @@ public abstract class UmbracoSignInManager<TUser> : SignInManager<TUser>
await UserManager.UpdateAsync(user);
if (_securitySettings.AllowConcurrentLogins is false)
{
await UserManager.UpdateSecurityStampAsync(user);
}
Logger.LogInformation("User: {UserName} logged in from IP address {IpAddress}", username, Context.Connection.RemoteIpAddress);
}
else if (result.IsLockedOut)