diff --git a/src/Umbraco.Cms.Api.Delivery/Handlers/InitializeMemberApplicationNotificationHandler.cs b/src/Umbraco.Cms.Api.Delivery/Handlers/InitializeMemberApplicationNotificationHandler.cs index 242fd47857..06b6472506 100644 --- a/src/Umbraco.Cms.Api.Delivery/Handlers/InitializeMemberApplicationNotificationHandler.cs +++ b/src/Umbraco.Cms.Api.Delivery/Handlers/InitializeMemberApplicationNotificationHandler.cs @@ -1,4 +1,5 @@ -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration.Models; @@ -11,20 +12,20 @@ namespace Umbraco.Cms.Api.Delivery.Handlers; internal sealed class InitializeMemberApplicationNotificationHandler : INotificationAsyncHandler { - private readonly IMemberApplicationManager _memberApplicationManager; private readonly IRuntimeState _runtimeState; private readonly ILogger _logger; private readonly DeliveryApiSettings _deliveryApiSettings; + private readonly IServiceScopeFactory _serviceScopeFactory; public InitializeMemberApplicationNotificationHandler( - IMemberApplicationManager memberApplicationManager, IRuntimeState runtimeState, IOptions deliveryApiSettings, - ILogger logger) + ILogger logger, + IServiceScopeFactory serviceScopeFactory) { - _memberApplicationManager = memberApplicationManager; _runtimeState = runtimeState; _logger = logger; + _serviceScopeFactory = serviceScopeFactory; _deliveryApiSettings = deliveryApiSettings.Value; } @@ -35,26 +36,31 @@ internal sealed class InitializeMemberApplicationNotificationHandler : INotifica return; } + // we cannot inject the IMemberApplicationManager because it ultimately takes a dependency on the DbContext ... and during + // install that is not allowed (no connection string means no DbContext) + using IServiceScope scope = _serviceScopeFactory.CreateScope(); + IMemberApplicationManager memberApplicationManager = scope.ServiceProvider.GetRequiredService(); + if (_deliveryApiSettings.MemberAuthorization?.AuthorizationCodeFlow?.Enabled is not true) { - await _memberApplicationManager.DeleteMemberApplicationAsync(cancellationToken); + await memberApplicationManager.DeleteMemberApplicationAsync(cancellationToken); return; } if (ValidateRedirectUrls(_deliveryApiSettings.MemberAuthorization.AuthorizationCodeFlow.LoginRedirectUrls) is false) { - await _memberApplicationManager.DeleteMemberApplicationAsync(cancellationToken); + await memberApplicationManager.DeleteMemberApplicationAsync(cancellationToken); return; } if (_deliveryApiSettings.MemberAuthorization.AuthorizationCodeFlow.LogoutRedirectUrls.Any() && ValidateRedirectUrls(_deliveryApiSettings.MemberAuthorization.AuthorizationCodeFlow.LogoutRedirectUrls) is false) { - await _memberApplicationManager.DeleteMemberApplicationAsync(cancellationToken); + await memberApplicationManager.DeleteMemberApplicationAsync(cancellationToken); return; } - await _memberApplicationManager.EnsureMemberApplicationAsync( + await memberApplicationManager.EnsureMemberApplicationAsync( _deliveryApiSettings.MemberAuthorization.AuthorizationCodeFlow.LoginRedirectUrls, _deliveryApiSettings.MemberAuthorization.AuthorizationCodeFlow.LogoutRedirectUrls, cancellationToken); diff --git a/src/Umbraco.Cms.Persistence.EFCore/Extensions/UmbracoEFCoreServiceCollectionExtensions.cs b/src/Umbraco.Cms.Persistence.EFCore/Extensions/UmbracoEFCoreServiceCollectionExtensions.cs index 3d7e01a0ad..7694f83dd2 100644 --- a/src/Umbraco.Cms.Persistence.EFCore/Extensions/UmbracoEFCoreServiceCollectionExtensions.cs +++ b/src/Umbraco.Cms.Persistence.EFCore/Extensions/UmbracoEFCoreServiceCollectionExtensions.cs @@ -1,14 +1,10 @@ -using System; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; using Serilog; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.DistributedLocking; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Persistence.EFCore.Factories; using Umbraco.Cms.Persistence.EFCore.Locking; using Umbraco.Cms.Persistence.EFCore.Scoping; @@ -22,13 +18,6 @@ public static class UmbracoEFCoreServiceCollectionExtensions public static IServiceCollection AddUmbracoEFCoreContext(this IServiceCollection services, DefaultEFCoreOptionsAction? defaultEFCoreOptionsAction = null) where T : DbContext { - var optionsBuilder = new DbContextOptionsBuilder(); - services.TryAddSingleton>( - sp => - { - SetupDbContext(defaultEFCoreOptionsAction, sp, optionsBuilder); - return new UmbracoPooledDbContextFactory(sp.GetRequiredService(), optionsBuilder.Options); - }); services.AddPooledDbContextFactory((provider, builder) => SetupDbContext(defaultEFCoreOptionsAction, provider, builder)); services.AddTransient(services => services.GetRequiredService>().CreateDbContext()); @@ -52,13 +41,6 @@ public static class UmbracoEFCoreServiceCollectionExtensions connectionString = connectionString.Replace(Constants.System.DataDirectoryPlaceholder, dataDirectory); } - var optionsBuilder = new DbContextOptionsBuilder(); - services.TryAddSingleton>( - sp => - { - defaultEFCoreOptionsAction?.Invoke(optionsBuilder, providerName, connectionString); - return new UmbracoPooledDbContextFactory(sp.GetRequiredService(), optionsBuilder.Options); - }); services.AddPooledDbContextFactory(options => defaultEFCoreOptionsAction?.Invoke(options, providerName, connectionString)); services.AddTransient(services => services.GetRequiredService>().CreateDbContext()); @@ -99,13 +81,6 @@ public static class UmbracoEFCoreServiceCollectionExtensions { optionsAction ??= (sp, options) => { }; - var optionsBuilder = new DbContextOptionsBuilder(); - - services.TryAddSingleton>(sp => - { - optionsAction.Invoke(sp, optionsBuilder); - return new UmbracoPooledDbContextFactory(sp.GetRequiredService(), optionsBuilder.Options); - }); services.AddPooledDbContextFactory(optionsAction); services.AddTransient(services => services.GetRequiredService>().CreateDbContext()); diff --git a/src/Umbraco.Cms.Persistence.EFCore/Factories/UmbracoPooledDbContextFactory.cs b/src/Umbraco.Cms.Persistence.EFCore/Factories/UmbracoPooledDbContextFactory.cs deleted file mode 100644 index de0f7db200..0000000000 --- a/src/Umbraco.Cms.Persistence.EFCore/Factories/UmbracoPooledDbContextFactory.cs +++ /dev/null @@ -1,47 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Umbraco.Cms.Core; -using Umbraco.Cms.Core.Services; - -namespace Umbraco.Cms.Persistence.EFCore.Factories; - -/// -internal class UmbracoPooledDbContextFactory : PooledDbContextFactory - where TContext : DbContext -{ - private readonly IRuntimeState _runtimeState; - private readonly DbContextOptions _options; - - /// - public UmbracoPooledDbContextFactory(IRuntimeState runtimeState, DbContextOptions options, int poolSize = 1024 /*DbContextPool.DefaultPoolSize*/) : base(options, poolSize) - { - _runtimeState = runtimeState; - _options = options; - } - - /// - public override TContext CreateDbContext() - { - if (_runtimeState.Level == RuntimeLevel.Run) - { - return base.CreateDbContext(); - } - else - { - return (TContext?)Activator.CreateInstance(typeof(TContext), _options) ?? throw new InvalidOperationException("Unable to create DbContext"); - } - } - - /// - public override async Task CreateDbContextAsync(CancellationToken cancellationToken = default) - { - if (_runtimeState.Level == RuntimeLevel.Run) - { - return await base.CreateDbContextAsync(cancellationToken); - } - else - { - return (TContext?)Activator.CreateInstance(typeof(TContext), _options) ?? throw new InvalidOperationException("Unable to create DbContext"); - } - } -} diff --git a/src/Umbraco.Cms.Persistence.EFCore/UmbracoDbContext.cs b/src/Umbraco.Cms.Persistence.EFCore/UmbracoDbContext.cs index a27630bd34..3df757ee15 100644 --- a/src/Umbraco.Cms.Persistence.EFCore/UmbracoDbContext.cs +++ b/src/Umbraco.Cms.Persistence.EFCore/UmbracoDbContext.cs @@ -1,3 +1,4 @@ +using System.Configuration; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.Extensions.DependencyInjection; @@ -55,6 +56,10 @@ public class UmbracoDbContext : DbContext { ILogger logger = StaticServiceProvider.Instance.GetRequiredService>(); logger.LogCritical("No connection string was found, cannot setup Umbraco EF Core context"); + + // we're throwing an exception here to make it abundantly clear that one should never utilize (or have a + // dependency on) the DbContext before the connection string has been initialized by the installer. + throw new ConfigurationErrorsException("No connection string was found, cannot setup Umbraco EF Core context"); } IEnumerable migrationProviders = StaticServiceProvider.Instance.GetServices(); diff --git a/src/Umbraco.Core/Models/DeliveryApi/ApiLink.cs b/src/Umbraco.Core/Models/DeliveryApi/ApiLink.cs index d255f6ab32..fcfe12b339 100644 --- a/src/Umbraco.Core/Models/DeliveryApi/ApiLink.cs +++ b/src/Umbraco.Core/Models/DeliveryApi/ApiLink.cs @@ -2,19 +2,32 @@ namespace Umbraco.Cms.Core.Models.DeliveryApi; public sealed class ApiLink { + [Obsolete("Please use the overload that accepts a query string. Will be removed in V14.")] public static ApiLink Content(string title, string? target, Guid destinationId, string destinationType, IApiContentRoute route) - => new(LinkType.Content, null, title, target, destinationId, destinationType, route); + => Content(title, queryString: null, target, destinationId, destinationType, route); + public static ApiLink Content(string title, string? queryString, string? target, Guid destinationId, string destinationType, IApiContentRoute route) + => new(LinkType.Content, url: null, queryString, title, target, destinationId, destinationType, route); + + [Obsolete("Please use the overload that accepts a query string. Will be removed in V14.")] public static ApiLink Media(string title, string url, string? target, Guid destinationId, string destinationType) - => new(LinkType.Media, url, title, target, destinationId, destinationType, null); + => Media(title, url, queryString: null, target, destinationId, destinationType); + public static ApiLink Media(string title, string url, string? queryString, string? target, Guid destinationId, string destinationType) + => new(LinkType.Media, url, queryString, title, target, destinationId, destinationType, route: null); + + [Obsolete("Please use the overload that accepts a query string. Will be removed in V14.")] public static ApiLink External(string? title, string url, string? target) - => new(LinkType.External, url, title, target, null, null, null); + => External(title, url, queryString: null, target); - private ApiLink(LinkType linkType, string? url, string? title, string? target, Guid? destinationId, string? destinationType, IApiContentRoute? route) + public static ApiLink External(string? title, string url, string? queryString, string? target) + => new(LinkType.External, url, queryString, title, target, null, null, null); + + private ApiLink(LinkType linkType, string? url, string? queryString, string? title, string? target, Guid? destinationId, string? destinationType, IApiContentRoute? route) { LinkType = linkType; Url = url; + QueryString = queryString; Title = title; Target = target; DestinationId = destinationId; @@ -24,6 +37,8 @@ public sealed class ApiLink public string? Url { get; } + public string? QueryString { get; } + public string? Title { get; } public string? Target { get; } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/MultiUrlPickerValueConverter.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/MultiUrlPickerValueConverter.cs index dfc64bdad5..61cc0e1c7d 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/MultiUrlPickerValueConverter.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/MultiUrlPickerValueConverter.cs @@ -185,6 +185,7 @@ public class MultiUrlPickerValueConverter : PropertyValueConverterBase, IDeliver ? null : ApiLink.Content( item.Name.IfNullOrWhiteSpace(_apiContentNameProvider.GetName(content)), + item.QueryString, item.Target, content.Key, content.ContentType.Alias, @@ -195,12 +196,13 @@ public class MultiUrlPickerValueConverter : PropertyValueConverterBase, IDeliver ? null : ApiLink.Media( item.Name.IfNullOrWhiteSpace(_apiContentNameProvider.GetName(media)), - _apiMediaUrlProvider.GetUrl(media), + $"{_apiMediaUrlProvider.GetUrl(media)}{item.QueryString}", + item.QueryString, item.Target, media.Key, media.ContentType.Alias); default: - return ApiLink.External(item.Name, $"{item.Url}{item.QueryString}", item.Target); + return ApiLink.External(item.Name, $"{item.Url}{item.QueryString}", item.QueryString, item.Target); } } diff --git a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs index 71eb240fd5..284f36318a 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs @@ -592,7 +592,7 @@ public class AuthenticationController : UmbracoApiControllerBase await _signInManager.TwoFactorSignInAsync(model.Provider, model.Code, model.IsPersistent, model.RememberClient); if (result.Succeeded) { - return GetUserDetail(_userService.GetByUsername(user.UserName)); + return Ok(GetUserDetail(_userService.GetByUsername(user.UserName))); } if (result.IsLockedOut) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/util.service.js b/src/Umbraco.Web.UI.Client/src/common/services/util.service.js index e69d49d82e..91318f5cf4 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/util.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/util.service.js @@ -108,7 +108,7 @@ function dateHelper() { const localOffset = new Date().getTimezoneOffset(); const serverTimeNeedsOffsetting = -serverOffset !== localOffset; if (serverTimeNeedsOffsetting) { - dateVal = this.convertToLocalMomentTime(date, serverOffset, format); + dateVal = this.convertToLocalMomentTime(date, serverOffset); } else { dateVal = moment(date, parsingFormat); } diff --git a/src/Umbraco.Web.UI.Client/src/views/common/login-2fa.html b/src/Umbraco.Web.UI.Client/src/views/common/login-2fa.html index be11a78d76..5d8b9cd3fd 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/login-2fa.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/login-2fa.html @@ -1,6 +1,6 @@ -