Merge remote-tracking branch 'origin/release/12.3.4' into release/13.0
# Conflicts: # version.json
This commit is contained in:
@@ -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<UmbracoApplicationStartingNotification>
|
||||
{
|
||||
private readonly IMemberApplicationManager _memberApplicationManager;
|
||||
private readonly IRuntimeState _runtimeState;
|
||||
private readonly ILogger<InitializeMemberApplicationNotificationHandler> _logger;
|
||||
private readonly DeliveryApiSettings _deliveryApiSettings;
|
||||
private readonly IServiceScopeFactory _serviceScopeFactory;
|
||||
|
||||
public InitializeMemberApplicationNotificationHandler(
|
||||
IMemberApplicationManager memberApplicationManager,
|
||||
IRuntimeState runtimeState,
|
||||
IOptions<DeliveryApiSettings> deliveryApiSettings,
|
||||
ILogger<InitializeMemberApplicationNotificationHandler> logger)
|
||||
ILogger<InitializeMemberApplicationNotificationHandler> 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<IMemberApplicationManager>();
|
||||
|
||||
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);
|
||||
|
||||
@@ -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<T>(this IServiceCollection services, DefaultEFCoreOptionsAction? defaultEFCoreOptionsAction = null)
|
||||
where T : DbContext
|
||||
{
|
||||
var optionsBuilder = new DbContextOptionsBuilder<T>();
|
||||
services.TryAddSingleton<IDbContextFactory<T>>(
|
||||
sp =>
|
||||
{
|
||||
SetupDbContext(defaultEFCoreOptionsAction, sp, optionsBuilder);
|
||||
return new UmbracoPooledDbContextFactory<T>(sp.GetRequiredService<IRuntimeState>(), optionsBuilder.Options);
|
||||
});
|
||||
services.AddPooledDbContextFactory<T>((provider, builder) => SetupDbContext(defaultEFCoreOptionsAction, provider, builder));
|
||||
services.AddTransient(services => services.GetRequiredService<IDbContextFactory<T>>().CreateDbContext());
|
||||
|
||||
@@ -52,13 +41,6 @@ public static class UmbracoEFCoreServiceCollectionExtensions
|
||||
connectionString = connectionString.Replace(Constants.System.DataDirectoryPlaceholder, dataDirectory);
|
||||
}
|
||||
|
||||
var optionsBuilder = new DbContextOptionsBuilder<T>();
|
||||
services.TryAddSingleton<IDbContextFactory<T>>(
|
||||
sp =>
|
||||
{
|
||||
defaultEFCoreOptionsAction?.Invoke(optionsBuilder, providerName, connectionString);
|
||||
return new UmbracoPooledDbContextFactory<T>(sp.GetRequiredService<IRuntimeState>(), optionsBuilder.Options);
|
||||
});
|
||||
services.AddPooledDbContextFactory<T>(options => defaultEFCoreOptionsAction?.Invoke(options, providerName, connectionString));
|
||||
services.AddTransient(services => services.GetRequiredService<IDbContextFactory<T>>().CreateDbContext());
|
||||
|
||||
@@ -99,13 +81,6 @@ public static class UmbracoEFCoreServiceCollectionExtensions
|
||||
{
|
||||
optionsAction ??= (sp, options) => { };
|
||||
|
||||
var optionsBuilder = new DbContextOptionsBuilder<T>();
|
||||
|
||||
services.TryAddSingleton<IDbContextFactory<T>>(sp =>
|
||||
{
|
||||
optionsAction.Invoke(sp, optionsBuilder);
|
||||
return new UmbracoPooledDbContextFactory<T>(sp.GetRequiredService<IRuntimeState>(), optionsBuilder.Options);
|
||||
});
|
||||
services.AddPooledDbContextFactory<T>(optionsAction);
|
||||
services.AddTransient(services => services.GetRequiredService<IDbContextFactory<T>>().CreateDbContext());
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
/// <inheritdoc/>
|
||||
internal class UmbracoPooledDbContextFactory<TContext> : PooledDbContextFactory<TContext>
|
||||
where TContext : DbContext
|
||||
{
|
||||
private readonly IRuntimeState _runtimeState;
|
||||
private readonly DbContextOptions<TContext> _options;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public UmbracoPooledDbContextFactory(IRuntimeState runtimeState, DbContextOptions<TContext> options, int poolSize = 1024 /*DbContextPool<DbContext>.DefaultPoolSize*/) : base(options, poolSize)
|
||||
{
|
||||
_runtimeState = runtimeState;
|
||||
_options = options;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<TContext> 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<UmbracoDbContext> logger = StaticServiceProvider.Instance.GetRequiredService<ILogger<UmbracoDbContext>>();
|
||||
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<IMigrationProviderSetup> migrationProviders = StaticServiceProvider.Instance.GetServices<IMigrationProviderSetup>();
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<div ng-controller="Umbraco.Login2faController as vm" class="umb-login-container">
|
||||
<div ng-controller="Umbraco.Login2faController as cvm" class="umb-login-container">
|
||||
<div id="twoFactorlogin" ng-cloak="">
|
||||
<form name="vm.authForm" method="POST" ng-submit="vm.validate()">
|
||||
<form name="cvm.authForm" method="POST" ng-submit="cvm.validate()">
|
||||
<header class="h4">
|
||||
<localize key="login_2fatitle">One last step</localize>
|
||||
</header>
|
||||
@@ -12,19 +12,19 @@
|
||||
<br>
|
||||
|
||||
<!-- if there's only one provider active, it will skip this step! -->
|
||||
<umb-control-group ng-if="vm.providers.length > 1" label="@login_2faMultipleText" label-for="provider" alias="2faprovider">
|
||||
<select id="2faprovider" name="provider" ng-model="vm.provider">
|
||||
<option ng-repeat="provider in vm.providers" ng-value="provider">{{provider}}</option>
|
||||
<umb-control-group ng-if="cvm.providers.length > 1" label="@login_2faMultipleText" label-for="provider" alias="2faprovider">
|
||||
<select id="2faprovider" name="provider" ng-model="cvm.provider">
|
||||
<option ng-repeat="provider in cvm.providers" ng-value="provider">{{provider}}</option>
|
||||
</select>
|
||||
</umb-control-group>
|
||||
|
||||
<umb-control-group label-for="token" alias="2facode" label="@login_2faCodeInput" description="@user_2faDisableText" required="true">
|
||||
|
||||
<input type="text" id="2facode" class="-full-width-input input-xlarge" name="token"
|
||||
inputmode="numeric" autocomplete="one-time-code" ng-model="vm.code" localize="placeholder"
|
||||
inputmode="numeric" autocomplete="one-time-code" ng-model="cvm.code" localize="placeholder"
|
||||
placeholder="@login_2faCodeInputHelp" aria-required="true" required umb-auto-focus no-dirty-check />
|
||||
|
||||
<div ng-messages="vm.authForm.token.$error" role="alert">
|
||||
<div ng-messages="cvm.authForm.token.$error" role="alert">
|
||||
<span class="umb-validation-label" ng-message="token">
|
||||
<localize key="login_2faInvalidCode">Invalid code entered</localize>
|
||||
</span>
|
||||
@@ -37,14 +37,14 @@
|
||||
button-style="success"
|
||||
size="m"
|
||||
label-key="general_validate"
|
||||
state="vm.stateValidateButton"
|
||||
disabled="vm.code.length === 0">
|
||||
state="cvm.stateValidateButton"
|
||||
disabled="cvm.code.length === 0">
|
||||
</umb-button>
|
||||
<umb-button
|
||||
type="button"
|
||||
size="m"
|
||||
label-key="general_back"
|
||||
action="vm.goBack()">
|
||||
action="cvm.goBack()">
|
||||
</umb-button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -157,11 +157,45 @@ public class MultiUrlPickerValueConverterTests : PropertyValueConverterTests
|
||||
var link = result.First();
|
||||
Assert.AreEqual("The link", link.Title);
|
||||
Assert.AreEqual("https://umbraco.com/?something=true", link.Url);
|
||||
Assert.AreEqual("?something=true", link.QueryString);
|
||||
Assert.AreEqual(LinkType.External, link.LinkType);
|
||||
Assert.AreEqual("_blank", link.Target);
|
||||
Assert.Null(link.Route);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MultiUrlPickerValueConverter_AppliesExplicitConfigurationToMediaLink()
|
||||
{
|
||||
var publishedDataType = new PublishedDataType(123, "test", new Lazy<object>(() => new MultiUrlPickerConfiguration { MaxNumber = 1 }));
|
||||
var publishedPropertyType = new Mock<IPublishedPropertyType>();
|
||||
publishedPropertyType.SetupGet(p => p.DataType).Returns(publishedDataType);
|
||||
|
||||
var valueConverter = MultiUrlPickerValueConverter();
|
||||
|
||||
var inter = Serializer().Serialize(new[]
|
||||
{
|
||||
new MultiUrlPickerValueEditor.LinkDto
|
||||
{
|
||||
Udi = new GuidUdi(Constants.UdiEntityType.Media, PublishedMedia.Key),
|
||||
Name = "Custom link name",
|
||||
QueryString = "?something=true",
|
||||
Target = "_blank"
|
||||
}
|
||||
});
|
||||
var result = valueConverter.ConvertIntermediateToDeliveryApiObject(Mock.Of<IPublishedElement>(), publishedPropertyType.Object, PropertyCacheLevel.Element, inter, false, false) as IEnumerable<ApiLink>;
|
||||
Assert.NotNull(result);
|
||||
Assert.AreEqual(1, result.Count());
|
||||
var link = result.First();
|
||||
Assert.AreEqual("Custom link name", link.Title);
|
||||
Assert.AreEqual(PublishedMedia.Key, link.DestinationId);
|
||||
Assert.AreEqual("TheMediaType", link.DestinationType);
|
||||
Assert.AreEqual("the-media-url?something=true", link.Url);
|
||||
Assert.AreEqual(LinkType.Media, link.LinkType);
|
||||
Assert.AreEqual("_blank", link.Target);
|
||||
Assert.AreEqual("?something=true", link.QueryString);
|
||||
Assert.AreEqual(null, link.Route);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MultiUrlPickerValueConverter_AppliesExplicitConfigurationToContentLink()
|
||||
{
|
||||
@@ -190,6 +224,7 @@ public class MultiUrlPickerValueConverterTests : PropertyValueConverterTests
|
||||
Assert.AreEqual("/the-page-url", link.Route!.Path);
|
||||
Assert.AreEqual(LinkType.Content, link.LinkType);
|
||||
Assert.AreEqual("_blank", link.Target);
|
||||
Assert.AreEqual("?something=true", link.QueryString);
|
||||
Assert.Null(link.Url);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user