diff --git a/backend/extensions/Squidex.Extensions/Samples/AssetStore/MemoryAssetStorePlugin.cs b/backend/extensions/Squidex.Extensions/Samples/AssetStore/MemoryAssetStorePlugin.cs index 1d1a0fef5..95f167944 100644 --- a/backend/extensions/Squidex.Extensions/Samples/AssetStore/MemoryAssetStorePlugin.cs +++ b/backend/extensions/Squidex.Extensions/Samples/AssetStore/MemoryAssetStorePlugin.cs @@ -50,8 +50,7 @@ namespace Squidex.Extensions.Samples.AssetStore { services.AddSingleton(this); - services.AddSingletonAs() - .As(); + services.AddSingleton(); } } } diff --git a/backend/extensions/Squidex.Extensions/Squidex.Extensions.csproj b/backend/extensions/Squidex.Extensions/Squidex.Extensions.csproj index 4520b40ea..c3ff3c4c7 100644 --- a/backend/extensions/Squidex.Extensions/Squidex.Extensions.csproj +++ b/backend/extensions/Squidex.Extensions/Squidex.Extensions.csproj @@ -13,11 +13,11 @@ - + - + diff --git a/backend/extensions/Squidex.Extensions/Validation/CompositeUniqueValidatorPlugin.cs b/backend/extensions/Squidex.Extensions/Validation/CompositeUniqueValidatorPlugin.cs index 86af7cd17..b5b3762e5 100644 --- a/backend/extensions/Squidex.Extensions/Validation/CompositeUniqueValidatorPlugin.cs +++ b/backend/extensions/Squidex.Extensions/Validation/CompositeUniqueValidatorPlugin.cs @@ -16,8 +16,7 @@ namespace Squidex.Extensions.Validation { public void ConfigureServices(IServiceCollection services, IConfiguration config) { - services.AddSingletonAs() - .As(); + services.AddSingleton(); } } } diff --git a/backend/src/Migrations/MigrationPath.cs b/backend/src/Migrations/MigrationPath.cs index dccfbe849..4711f8e1d 100644 --- a/backend/src/Migrations/MigrationPath.cs +++ b/backend/src/Migrations/MigrationPath.cs @@ -18,7 +18,7 @@ namespace Migrations { public sealed class MigrationPath : IMigrationPath { - private const int CurrentVersion = 23; + private const int CurrentVersion = 24; private readonly IServiceProvider serviceProvider; public MigrationPath(IServiceProvider serviceProvider) @@ -74,7 +74,8 @@ namespace Migrations } // Version 12: Introduce roles. - if (version < 12) + // Version 24: Improve a naming in the languages config. + if (version < 24) { yield return serviceProvider.GetRequiredService(); } diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Apps/AppClients.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Apps/AppClients.cs index 846bf9693..2ba208f40 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Apps/AppClients.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Apps/AppClients.cs @@ -42,7 +42,7 @@ namespace Squidex.Domain.Apps.Core.Apps if (ContainsKey(id)) { - throw new ArgumentException("Id already exists.", nameof(id)); + return this; } var newClient = new AppClient(id, secret) diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/DependencyInjectionExtensions.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/DependencyInjectionExtensions.cs index 1113bcfbb..72f39cf6d 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/DependencyInjectionExtensions.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/DependencyInjectionExtensions.cs @@ -14,9 +14,7 @@ namespace Microsoft.Extensions.DependencyInjection { public static IServiceCollection AddRuleAction(this IServiceCollection services) where THandler : class, IRuleActionHandler where TAction : RuleAction { - services.AddSingletonAs() - .As(); - + services.AddSingleton(); services.AddSingleton(new RuleActionRegistration(typeof(TAction))); return services; diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj b/backend/src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj index b76420490..1021e1b9b 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj @@ -20,8 +20,8 @@ - - + + diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs index 47eb09222..913a1e66d 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs +++ b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs @@ -18,6 +18,7 @@ using Squidex.Domain.Apps.Entities.Contents.Repositories; using Squidex.Domain.Apps.Entities.Contents.Text; using Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations; using Squidex.Domain.Apps.Entities.Schemas; +using Squidex.Hosting; using Squidex.Infrastructure; using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Queries; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetStats.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetStats.cs index 72ce523e6..8966d427d 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetStats.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetStats.cs @@ -7,22 +7,14 @@ using System; +#pragma warning disable SA1313 // Parameter names should begin with lower-case letter + namespace Squidex.Domain.Apps.Entities.Assets { - public sealed class AssetStats + public sealed record AssetStats( + DateTime Date, + long TotalCount, + long TotalSize) { - public DateTime Date { get; } - - public long TotalCount { get; } - - public long TotalSize { get; } - - public AssetStats(DateTime date, long totalCount, long totalSize) - { - Date = date; - - TotalCount = totalCount; - TotalSize = totalSize; - } } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/Elastic/ElasticSearchTextIndex.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/Elastic/ElasticSearchTextIndex.cs index c78bb28e1..9cbea39e1 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/Elastic/ElasticSearchTextIndex.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/Elastic/ElasticSearchTextIndex.cs @@ -13,6 +13,7 @@ using System.Threading; using System.Threading.Tasks; using Elasticsearch.Net; using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Hosting; using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities.Contents.Text.Elastic diff --git a/backend/src/Squidex.Domain.Apps.Entities/History/NotifoService.cs b/backend/src/Squidex.Domain.Apps.Entities/History/NotifoService.cs index 1901fe301..0de802c41 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/History/NotifoService.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/History/NotifoService.cs @@ -7,7 +7,6 @@ using System.Collections.Generic; using System.Linq; -using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Options; using NodaTime; @@ -26,14 +25,14 @@ using Squidex.Shared.Users; namespace Squidex.Domain.Apps.Entities.History { - public class NotifoService : IInitializable, IUserEventHandler + public class NotifoService : IUserEventHandler { private static readonly Duration MaxAge = Duration.FromHours(12); private readonly NotifoOptions options; private readonly IUrlGenerator urlGenerator; private readonly IUserResolver userResolver; private readonly IClock clock; - private INotifoClient? client; + private readonly INotifoClient? client; public NotifoService(IOptions options, IUrlGenerator urlGenerator, IUserResolver userResolver, IClock clock) { @@ -48,25 +47,20 @@ namespace Squidex.Domain.Apps.Entities.History this.userResolver = userResolver; this.clock = clock; - } - public Task InitializeAsync(CancellationToken ct = default) - { - if (options.IsConfigured()) + if (options.Value.IsConfigured()) { var builder = NotifoClientBuilder.Create() - .SetApiKey(options.ApiKey); + .SetApiKey(options.Value.ApiKey); - if (!string.IsNullOrWhiteSpace(options.ApiUrl)) + if (!string.IsNullOrWhiteSpace(options.Value.ApiUrl)) { - builder = builder.SetApiUrl(options.ApiUrl); + builder = builder.SetApiUrl(options.Value.ApiUrl); } client = builder.Build(); } - - return Task.CompletedTask; } public void OnUserUpdated(IUser user) diff --git a/backend/src/Squidex.Domain.Apps.Entities/Rules/Runner/RuleRunnerGrain.cs b/backend/src/Squidex.Domain.Apps.Entities/Rules/Runner/RuleRunnerGrain.cs index 4ff82584f..e6af8bbf5 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Rules/Runner/RuleRunnerGrain.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Rules/Runner/RuleRunnerGrain.cs @@ -17,9 +17,11 @@ using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Orleans; using Squidex.Infrastructure.States; -using Squidex.Infrastructure.Tasks; using Squidex.Infrastructure.Translations; using Squidex.Log; +using TaskExtensions = Squidex.Infrastructure.Tasks.TaskExtensions; + +#pragma warning disable RECS0015 // If an extension method is called as static method convert it to method syntax namespace Squidex.Domain.Apps.Entities.Rules.Runner { @@ -151,7 +153,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Runner private void Process(State job, CancellationToken ct) { - ProcessAsync(job, ct).Forget(); + TaskExtensions.Forget(ProcessAsync(job, ct)); } private async Task ProcessAsync(State currentState, CancellationToken ct) diff --git a/backend/src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj b/backend/src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj index fc69a86ae..ab0ec9cf1 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj +++ b/backend/src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj @@ -17,7 +17,7 @@ - + diff --git a/backend/src/Squidex.Infrastructure.Azure/EventSourcing/CosmosDbEventStore.cs b/backend/src/Squidex.Infrastructure.Azure/EventSourcing/CosmosDbEventStore.cs index 933e60819..e1c85a3d1 100644 --- a/backend/src/Squidex.Infrastructure.Azure/EventSourcing/CosmosDbEventStore.cs +++ b/backend/src/Squidex.Infrastructure.Azure/EventSourcing/CosmosDbEventStore.cs @@ -12,6 +12,7 @@ using System.Threading.Tasks; using Microsoft.Azure.Documents; using Microsoft.Azure.Documents.Client; using Newtonsoft.Json; +using Squidex.Hosting; using Index = Microsoft.Azure.Documents.Index; namespace Squidex.Infrastructure.EventSourcing diff --git a/backend/src/Squidex.Infrastructure.Azure/EventSourcing/CosmosDbEventStore_Reader.cs b/backend/src/Squidex.Infrastructure.Azure/EventSourcing/CosmosDbEventStore_Reader.cs index 642927913..f12fc0d9f 100644 --- a/backend/src/Squidex.Infrastructure.Azure/EventSourcing/CosmosDbEventStore_Reader.cs +++ b/backend/src/Squidex.Infrastructure.Azure/EventSourcing/CosmosDbEventStore_Reader.cs @@ -10,6 +10,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Squidex.Hosting; using Squidex.Log; namespace Squidex.Infrastructure.EventSourcing diff --git a/backend/src/Squidex.Infrastructure.Azure/Squidex.Infrastructure.Azure.csproj b/backend/src/Squidex.Infrastructure.Azure/Squidex.Infrastructure.Azure.csproj index 4d243c5e9..1ae4731e7 100644 --- a/backend/src/Squidex.Infrastructure.Azure/Squidex.Infrastructure.Azure.csproj +++ b/backend/src/Squidex.Infrastructure.Azure/Squidex.Infrastructure.Azure.csproj @@ -7,7 +7,7 @@ - + diff --git a/backend/src/Squidex.Infrastructure.GetEventStore/EventSourcing/GetEventStore.cs b/backend/src/Squidex.Infrastructure.GetEventStore/EventSourcing/GetEventStore.cs index 8fb8baff5..4f2e89988 100644 --- a/backend/src/Squidex.Infrastructure.GetEventStore/EventSourcing/GetEventStore.cs +++ b/backend/src/Squidex.Infrastructure.GetEventStore/EventSourcing/GetEventStore.cs @@ -12,6 +12,8 @@ using System.Threading; using System.Threading.Tasks; using EventStore.ClientAPI; using EventStore.ClientAPI.Exceptions; +using Squidex.Hosting; +using Squidex.Hosting.Configuration; using Squidex.Infrastructure.Json; using Squidex.Log; @@ -48,7 +50,9 @@ namespace Squidex.Infrastructure.EventSourcing } catch (Exception ex) { - throw new ConfigurationException("Cannot connect to event store.", ex); + var error = new ConfigurationError("GetEventStore cannot connect to event store."); + + throw new ConfigurationException(error, ex); } await projectionClient.ConnectAsync(); diff --git a/backend/src/Squidex.Infrastructure.GetEventStore/EventSourcing/ProjectionClient.cs b/backend/src/Squidex.Infrastructure.GetEventStore/EventSourcing/ProjectionClient.cs index c640303e4..934ac42b5 100644 --- a/backend/src/Squidex.Infrastructure.GetEventStore/EventSourcing/ProjectionClient.cs +++ b/backend/src/Squidex.Infrastructure.GetEventStore/EventSourcing/ProjectionClient.cs @@ -14,6 +14,7 @@ using System.Threading.Tasks; using EventStore.ClientAPI; using EventStore.ClientAPI.Exceptions; using EventStore.ClientAPI.Projections; +using Squidex.Hosting.Configuration; using Squidex.Text; namespace Squidex.Infrastructure.EventSourcing @@ -102,7 +103,9 @@ namespace Squidex.Infrastructure.EventSourcing } catch (Exception ex) { - throw new ConfigurationException($"Cannot connect to event store projections: {projectionHost}.", ex); + var error = new ConfigurationError($"GetEventStore cannot connect to event store projections: {projectionHost}."); + + throw new ConfigurationException(error, ex); } } diff --git a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoExtensions.cs b/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoExtensions.cs index 1059c8d7e..4c829a29f 100644 --- a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoExtensions.cs +++ b/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoExtensions.cs @@ -11,7 +11,6 @@ using System.Threading; using System.Threading.Tasks; using System.Threading.Tasks.Dataflow; using MongoDB.Bson; -using MongoDB.Bson.Serialization; using MongoDB.Driver; using Squidex.Infrastructure.States; diff --git a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoRepositoryBase.cs b/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoRepositoryBase.cs index edb6ec628..6ad0063f4 100644 --- a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoRepositoryBase.cs +++ b/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoRepositoryBase.cs @@ -10,6 +10,8 @@ using System.Globalization; using System.Threading; using System.Threading.Tasks; using MongoDB.Driver; +using Squidex.Hosting; +using Squidex.Hosting.Configuration; #pragma warning disable RECS0108 // Warns about static fields in generic types @@ -112,7 +114,9 @@ namespace Squidex.Infrastructure.MongoDb } catch (Exception ex) { - throw new ConfigurationException($"MongoDb connection failed to connect to database {Database.DatabaseNamespace.DatabaseName}", ex); + var error = new ConfigurationError($"MongoDb connection failed to connect to database {Database.DatabaseNamespace.DatabaseName}."); + + throw new ConfigurationException(error, ex); } } diff --git a/backend/src/Squidex.Infrastructure.RabbitMq/CQRS/Events/RabbitMqEventConsumer.cs b/backend/src/Squidex.Infrastructure.RabbitMq/CQRS/Events/RabbitMqEventConsumer.cs index afb56c463..c10061707 100644 --- a/backend/src/Squidex.Infrastructure.RabbitMq/CQRS/Events/RabbitMqEventConsumer.cs +++ b/backend/src/Squidex.Infrastructure.RabbitMq/CQRS/Events/RabbitMqEventConsumer.cs @@ -10,6 +10,8 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using RabbitMQ.Client; +using Squidex.Hosting; +using Squidex.Hosting.Configuration; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Json; @@ -69,14 +71,18 @@ namespace Squidex.Infrastructure.CQRS.Events if (!currentConnection.IsOpen) { - throw new ConfigurationException($"RabbitMq event bus failed to connect to {connectionFactory.Endpoint}"); + var error = new ConfigurationError($"RabbitMq event bus failed to connect to {connectionFactory.Endpoint}."); + + throw new ConfigurationException(error); } return Task.CompletedTask; } - catch (Exception e) + catch (Exception ex) { - throw new ConfigurationException($"RabbitMq event bus failed to connect to {connectionFactory.Endpoint}", e); + var error = new ConfigurationError($"RabbitMq event bus failed to connect to {connectionFactory.Endpoint}."); + + throw new ConfigurationException(error, ex); } } diff --git a/backend/src/Squidex.Infrastructure/Configuration/Alternatives.cs b/backend/src/Squidex.Infrastructure/Configuration/Alternatives.cs deleted file mode 100644 index 77d70602f..000000000 --- a/backend/src/Squidex.Infrastructure/Configuration/Alternatives.cs +++ /dev/null @@ -1,20 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System; -using System.Collections.Generic; - -namespace Microsoft.Extensions.Configuration -{ - public sealed class Alternatives : Dictionary - { - public Alternatives() - : base(StringComparer.OrdinalIgnoreCase) - { - } - } -} diff --git a/backend/src/Squidex.Infrastructure/Configuration/ConfigurationExtensions.cs b/backend/src/Squidex.Infrastructure/Configuration/ConfigurationExtensions.cs deleted file mode 100644 index efc33c2d8..000000000 --- a/backend/src/Squidex.Infrastructure/Configuration/ConfigurationExtensions.cs +++ /dev/null @@ -1,70 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.Globalization; -using System.Linq; -using Squidex.Infrastructure; -using Squidex.Text; - -namespace Microsoft.Extensions.Configuration -{ - public static class ConfigurationExtensions - { - public static T GetOptionalValue(this IConfiguration config, string path, T defaultValue = default) - { - var value = config.GetValue(path, defaultValue!); - - return value; - } - - public static int GetOptionalValue(this IConfiguration config, string path, int defaultValue) - { - var value = config.GetValue(path); - - if (string.IsNullOrWhiteSpace(value) || !int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) - { - result = defaultValue; - } - - return result; - } - - public static string GetRequiredValue(this IConfiguration config, string path) - { - var value = config.GetValue(path); - - if (string.IsNullOrWhiteSpace(value)) - { - var name = string.Join(" ", path.Split(':').Select(x => x.ToPascalCase())); - - throw new ConfigurationException($"Configure the {name} with '{path}'."); - } - - return value; - } - - public static string ConfigureByOption(this IConfiguration config, string path, Alternatives options) - { - var value = config.GetRequiredValue(path); - - if (options.TryGetValue(value, out var action)) - { - action(); - } - else if (options.TryGetValue("default", out action)) - { - action(); - } - else - { - throw new ConfigurationException($"Unsupported value '{value}' for '{path}', supported: {string.Join(" ", options.Keys)}."); - } - - return value; - } - } -} diff --git a/backend/src/Squidex.Infrastructure/ConfigurationException.cs b/backend/src/Squidex.Infrastructure/ConfigurationException.cs deleted file mode 100644 index 14e6d5eb5..000000000 --- a/backend/src/Squidex.Infrastructure/ConfigurationException.cs +++ /dev/null @@ -1,26 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschränkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System; -using System.Runtime.Serialization; - -namespace Squidex.Infrastructure -{ - [Serializable] - public class ConfigurationException : Exception - { - public ConfigurationException(string message, Exception? inner = null) - : base(message, inner) - { - } - - protected ConfigurationException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } - } -} diff --git a/backend/src/Squidex.Infrastructure/DelegateInitializer.cs b/backend/src/Squidex.Infrastructure/DelegateInitializer.cs deleted file mode 100644 index 38da741be..000000000 --- a/backend/src/Squidex.Infrastructure/DelegateInitializer.cs +++ /dev/null @@ -1,38 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace Squidex.Infrastructure -{ - public sealed class DelegateInitializer : IInitializable - { - private readonly string name; - private readonly Func action; - - public DelegateInitializer(string name, Func action) - { - Guard.NotNull(action, nameof(action)); - - this.name = name; - - this.action = action; - } - - public override string ToString() - { - return name; - } - - public Task InitializeAsync(CancellationToken ct = default) - { - return action(ct); - } - } -} diff --git a/backend/src/Squidex.Infrastructure/DependencyInjection/DependencyInjectionExtensions.cs b/backend/src/Squidex.Infrastructure/DependencyInjection/DependencyInjectionExtensions.cs deleted file mode 100644 index 204b91b45..000000000 --- a/backend/src/Squidex.Infrastructure/DependencyInjection/DependencyInjectionExtensions.cs +++ /dev/null @@ -1,110 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System; -using System.Linq; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Squidex.Infrastructure; - -namespace Microsoft.Extensions.DependencyInjection -{ - public static class DependencyInjectionExtensions - { - public delegate void Registrator(Type serviceType, Func implementationFactory); - - public sealed class InterfaceRegistrator where T : notnull - { - private readonly Registrator register; - private readonly Registrator registerOptional; - - public InterfaceRegistrator(Registrator register, Registrator registerOptional) - { - this.register = register; - this.registerOptional = registerOptional; - - var interfaces = typeof(T).GetInterfaces(); - - if (interfaces.Contains(typeof(IInitializable))) - { - register(typeof(IInitializable), c => c.GetRequiredService()); - } - - if (interfaces.Contains(typeof(IBackgroundProcess))) - { - register(typeof(IBackgroundProcess), c => c.GetRequiredService()); - } - } - - public InterfaceRegistrator AsSelf() - { - return this; - } - - public InterfaceRegistrator AsOptional() - { - if (typeof(TInterface) != typeof(T)) - { - registerOptional(typeof(TInterface), c => c.GetRequiredService()); - } - - return this; - } - - public InterfaceRegistrator As() - { - if (typeof(TInterface) != typeof(T)) - { - register(typeof(TInterface), c => c.GetRequiredService()); - } - - return this; - } - } - - public static InterfaceRegistrator AddTransientAs(this IServiceCollection services, Func factory) where T : class - { - services.AddTransient(typeof(T), factory); - - return new InterfaceRegistrator((t, f) => services.AddTransient(t, f), services.TryAddTransient); - } - - public static InterfaceRegistrator AddTransientAs(this IServiceCollection services) where T : class - { - services.AddTransient(); - - return new InterfaceRegistrator((t, f) => services.AddTransient(t, f), services.TryAddTransient); - } - - public static InterfaceRegistrator AddSingletonAs(this IServiceCollection services, Func factory) where T : class - { - services.AddSingleton(typeof(T), factory); - - return new InterfaceRegistrator((t, f) => services.AddSingleton(t, f), services.TryAddSingleton); - } - - public static InterfaceRegistrator AddSingletonAs(this IServiceCollection services) where T : class - { - services.AddSingleton(); - - return new InterfaceRegistrator((t, f) => services.AddSingleton(t, f), services.TryAddSingleton); - } - - public static InterfaceRegistrator AddScopedAs(this IServiceCollection services, Func factory) where T : class - { - services.AddScoped(typeof(T), factory); - - return new InterfaceRegistrator((t, f) => services.AddScoped(t, f), services.TryAddScoped); - } - - public static InterfaceRegistrator AddScopedAs(this IServiceCollection services) where T : class - { - services.AddScoped(); - - return new InterfaceRegistrator((t, f) => services.AddScoped(t, f), services.TryAddScoped); - } - } -} diff --git a/backend/src/Squidex.Infrastructure/IBackgroundProcess.cs b/backend/src/Squidex.Infrastructure/IBackgroundProcess.cs deleted file mode 100644 index 38003910c..000000000 --- a/backend/src/Squidex.Infrastructure/IBackgroundProcess.cs +++ /dev/null @@ -1,17 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.Threading; -using System.Threading.Tasks; - -namespace Squidex.Infrastructure -{ - public interface IBackgroundProcess - { - Task StartAsync(CancellationToken ct); - } -} diff --git a/backend/src/Squidex.Infrastructure/IInitializable.cs b/backend/src/Squidex.Infrastructure/IInitializable.cs deleted file mode 100644 index 621076be5..000000000 --- a/backend/src/Squidex.Infrastructure/IInitializable.cs +++ /dev/null @@ -1,19 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschränkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.Threading; -using System.Threading.Tasks; - -namespace Squidex.Infrastructure -{ - public interface IInitializable - { - int Order => 0; - - Task InitializeAsync(CancellationToken ct = default); - } -} diff --git a/backend/src/Squidex.Infrastructure/LanguagesInitializer.cs b/backend/src/Squidex.Infrastructure/LanguagesInitializer.cs index 3e5440b8d..3f8669af8 100644 --- a/backend/src/Squidex.Infrastructure/LanguagesInitializer.cs +++ b/backend/src/Squidex.Infrastructure/LanguagesInitializer.cs @@ -8,6 +8,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Options; +using Squidex.Hosting; namespace Squidex.Infrastructure { diff --git a/backend/src/Squidex.Infrastructure/Orleans/GrainBootstrap.cs b/backend/src/Squidex.Infrastructure/Orleans/GrainBootstrap.cs index cebe9623f..1ef4f7d75 100644 --- a/backend/src/Squidex.Infrastructure/Orleans/GrainBootstrap.cs +++ b/backend/src/Squidex.Infrastructure/Orleans/GrainBootstrap.cs @@ -9,6 +9,7 @@ using System.Threading; using System.Threading.Tasks; using Orleans; using Orleans.Runtime; +using Squidex.Hosting; namespace Squidex.Infrastructure.Orleans { @@ -17,6 +18,8 @@ namespace Squidex.Infrastructure.Orleans private const int NumTries = 10; private readonly IGrainFactory grainFactory; + public string Name => typeof(T).Name; + public GrainBootstrap(IGrainFactory grainFactory) { Guard.NotNull(grainFactory, nameof(grainFactory)); @@ -46,10 +49,5 @@ namespace Squidex.Infrastructure.Orleans } } } - - public override string ToString() - { - return typeof(T).ToString(); - } } } diff --git a/backend/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj b/backend/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj index e5f41c674..1c1cbf7d0 100644 --- a/backend/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj +++ b/backend/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj @@ -18,7 +18,7 @@ - + all runtime; build; native; contentfiles; analyzers @@ -26,10 +26,11 @@ - + - + + diff --git a/backend/src/Squidex.Infrastructure/UsageTracking/ApiStats.cs b/backend/src/Squidex.Infrastructure/UsageTracking/ApiStats.cs index 52f3c3b3e..eeb8777ff 100644 --- a/backend/src/Squidex.Infrastructure/UsageTracking/ApiStats.cs +++ b/backend/src/Squidex.Infrastructure/UsageTracking/ApiStats.cs @@ -7,26 +7,14 @@ using System; +#pragma warning disable SA1313 // Parameter names should begin with lower-case letter + namespace Squidex.Infrastructure.UsageTracking { - public sealed class ApiStats + public sealed record ApiStats( + DateTime Date, + long TotalCalls, double AverageElapsedMs, + long TotalBytes) { - public DateTime Date { get; } - - public long TotalCalls { get; } - - public long TotalBytes { get; } - - public double AverageElapsedMs { get; } - - public ApiStats(DateTime date, long totalCalls, double averageElapsedMs, long totalBytes) - { - Date = date; - - TotalCalls = totalCalls; - TotalBytes = totalBytes; - - AverageElapsedMs = averageElapsedMs; - } } } diff --git a/backend/src/Squidex.Infrastructure/UsageTracking/ApiStatsSummary.cs b/backend/src/Squidex.Infrastructure/UsageTracking/ApiStatsSummary.cs index 525ad4bf6..d08d1e7d1 100644 --- a/backend/src/Squidex.Infrastructure/UsageTracking/ApiStatsSummary.cs +++ b/backend/src/Squidex.Infrastructure/UsageTracking/ApiStatsSummary.cs @@ -5,29 +5,16 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +#pragma warning disable SA1313 // Parameter names should begin with lower-case letter + namespace Squidex.Infrastructure.UsageTracking { - public sealed class ApiStatsSummary + public sealed record ApiStatsSummary( + double AverageElapsedMs, + long TotalCalls, + long TotalBytes, + long MonthCalls, + long MonthBytes) { - public long TotalCalls { get; } - - public long TotalBytes { get; } - - public long MonthCalls { get; } - - public long MonthBytes { get; } - - public double AverageElapsedMs { get; } - - public ApiStatsSummary(double averageElapsedMs, long totalCalls, long totalBytes, long monthCalls, long monthBytes) - { - TotalCalls = totalCalls; - TotalBytes = totalBytes; - - MonthCalls = monthCalls; - MonthBytes = monthBytes; - - AverageElapsedMs = averageElapsedMs; - } } } diff --git a/backend/src/Squidex.Infrastructure/UsageTracking/StoredUsage.cs b/backend/src/Squidex.Infrastructure/UsageTracking/StoredUsage.cs index d6b3beaa2..f3f4d0b3d 100644 --- a/backend/src/Squidex.Infrastructure/UsageTracking/StoredUsage.cs +++ b/backend/src/Squidex.Infrastructure/UsageTracking/StoredUsage.cs @@ -7,24 +7,11 @@ using System; +#pragma warning disable SA1313 // Parameter names should begin with lower-case letter + namespace Squidex.Infrastructure.UsageTracking { - public sealed class StoredUsage + public sealed record StoredUsage(string? Category, DateTime Date, Counters Counters) { - public string? Category { get; } - - public DateTime Date { get; } - - public Counters Counters { get; } - - public StoredUsage(string? category, DateTime date, Counters counters) - { - Guard.NotNull(counters, nameof(counters)); - - Category = category; - Counters = counters; - - Date = date; - } } } diff --git a/backend/src/Squidex.Web/Pipeline/CleanupHostMiddleware.cs b/backend/src/Squidex.Web/Pipeline/CleanupHostMiddleware.cs deleted file mode 100644 index 82b1fbcd8..000000000 --- a/backend/src/Squidex.Web/Pipeline/CleanupHostMiddleware.cs +++ /dev/null @@ -1,44 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; - -namespace Squidex.Web.Pipeline -{ - public class CleanupHostMiddleware - { - private readonly RequestDelegate next; - - public CleanupHostMiddleware(RequestDelegate next) - { - this.next = next; - } - - public Task InvokeAsync(HttpContext context) - { - var request = context.Request; - - if (request.Host.HasValue && (HasHttpsPort(request) || HasHttpPort(request))) - { - request.Host = new HostString(request.Host.Host); - } - - return next(context); - } - - private static bool HasHttpPort(HttpRequest request) - { - return request.Scheme == "http" && request.Host.Port == 80; - } - - private static bool HasHttpsPort(HttpRequest request) - { - return request.Scheme == "https" && request.Host.Port == 443; - } - } -} \ No newline at end of file diff --git a/backend/src/Squidex.Web/Services/UrlGenerator.cs b/backend/src/Squidex.Web/Services/UrlGenerator.cs index bb85f42fa..e8d7ace91 100644 --- a/backend/src/Squidex.Web/Services/UrlGenerator.cs +++ b/backend/src/Squidex.Web/Services/UrlGenerator.cs @@ -5,29 +5,29 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Microsoft.Extensions.Options; using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Core.Assets; using Squidex.Domain.Apps.Entities.Assets; using Squidex.Infrastructure; +using IGenericUrlGenerator = Squidex.Hosting.IUrlGenerator; namespace Squidex.Web.Services { public sealed class UrlGenerator : IUrlGenerator { private readonly IAssetFileStore assetFileStore; - private readonly UrlsOptions urlsOptions; + private readonly IGenericUrlGenerator urlGenerator; public bool CanGenerateAssetSourceUrl { get; } - public UrlGenerator(IOptions urlsOptions, IAssetFileStore assetFileStore, bool allowAssetSourceUrl) + public UrlGenerator(IGenericUrlGenerator urlGenerator, IAssetFileStore assetFileStore, bool allowAssetSourceUrl) { Guard.NotNull(assetFileStore, nameof(assetFileStore)); - Guard.NotNull(urlsOptions, nameof(urlsOptions)); + Guard.NotNull(urlGenerator, nameof(urlGenerator)); this.assetFileStore = assetFileStore; - this.urlsOptions = urlsOptions.Value; + this.urlGenerator = urlGenerator; CanGenerateAssetSourceUrl = allowAssetSourceUrl; } @@ -39,32 +39,32 @@ namespace Squidex.Web.Services return null; } - return urlsOptions.BuildUrl($"api/assets/{appId.Name}/{idOrSlug}?width=100&mode=Max"); + return urlGenerator.BuildUrl($"api/assets/{appId.Name}/{idOrSlug}?width=100&mode=Max"); } public string AppSettingsUI(NamedId appId) { - return urlsOptions.BuildUrl($"app/{appId.Name}/settings", false); + return urlGenerator.BuildUrl($"app/{appId.Name}/settings", false); } public string AssetContentBase() { - return urlsOptions.BuildUrl("api/assets/"); + return urlGenerator.BuildUrl("api/assets/"); } public string AssetContentBase(string appName) { - return urlsOptions.BuildUrl($"api/assets/{appName}/"); + return urlGenerator.BuildUrl($"api/assets/{appName}/"); } public string AssetContent(NamedId appId, DomainId assetId) { - return urlsOptions.BuildUrl($"api/assets/{appId.Name}/{assetId}"); + return urlGenerator.BuildUrl($"api/assets/{appId.Name}/{assetId}"); } public string AssetContent(NamedId appId, string idOrSlug) { - return urlsOptions.BuildUrl($"api/assets/{appId.Name}/{idOrSlug}"); + return urlGenerator.BuildUrl($"api/assets/{appId.Name}/{idOrSlug}"); } public string? AssetSource(NamedId appId, DomainId assetId, long fileVersion) @@ -74,92 +74,92 @@ namespace Squidex.Web.Services public string AssetsUI(NamedId appId, string? query = null) { - return urlsOptions.BuildUrl($"app/{appId.Name}/assets", false) + query != null ? $"?query={query}" : string.Empty; + return urlGenerator.BuildUrl($"app/{appId.Name}/assets", false) + query != null ? $"?query={query}" : string.Empty; } public string AssetsUI(NamedId appId, string? query = null) { - return urlsOptions.BuildUrl($"app/{appId.Name}/assets?query={query}", false); + return urlGenerator.BuildUrl($"app/{appId.Name}/assets?query={query}", false); } public string BackupsUI(NamedId appId) { - return urlsOptions.BuildUrl($"app/{appId.Name}/settings/backups", false); + return urlGenerator.BuildUrl($"app/{appId.Name}/settings/backups", false); } public string ClientsUI(NamedId appId) { - return urlsOptions.BuildUrl($"app/{appId.Name}/settings/clients", false); + return urlGenerator.BuildUrl($"app/{appId.Name}/settings/clients", false); } public string ContentsUI(NamedId appId) { - return urlsOptions.BuildUrl($"app/{appId.Name}/content", false); + return urlGenerator.BuildUrl($"app/{appId.Name}/content", false); } public string ContentsUI(NamedId appId, NamedId schemaId) { - return urlsOptions.BuildUrl($"app/{appId.Name}/content/{schemaId.Name}", false); + return urlGenerator.BuildUrl($"app/{appId.Name}/content/{schemaId.Name}", false); } public string ContentUI(NamedId appId, NamedId schemaId, DomainId contentId) { - return urlsOptions.BuildUrl($"app/{appId.Name}/content/{schemaId.Name}/{contentId}/history", false); + return urlGenerator.BuildUrl($"app/{appId.Name}/content/{schemaId.Name}/{contentId}/history", false); } public string ContributorsUI(NamedId appId) { - return urlsOptions.BuildUrl($"app/{appId.Name}/settings/contributors", false); + return urlGenerator.BuildUrl($"app/{appId.Name}/settings/contributors", false); } public string DashboardUI(NamedId appId) { - return urlsOptions.BuildUrl($"app/{appId.Name}", false); + return urlGenerator.BuildUrl($"app/{appId.Name}", false); } public string LanguagesUI(NamedId appId) { - return urlsOptions.BuildUrl($"app/{appId.Name}/settings/languages", false); + return urlGenerator.BuildUrl($"app/{appId.Name}/settings/languages", false); } public string PatternsUI(NamedId appId) { - return urlsOptions.BuildUrl($"app/{appId.Name}/settings/patterns", false); + return urlGenerator.BuildUrl($"app/{appId.Name}/settings/patterns", false); } public string PlansUI(NamedId appId) { - return urlsOptions.BuildUrl($"app/{appId.Name}/settings/plans", false); + return urlGenerator.BuildUrl($"app/{appId.Name}/settings/plans", false); } public string RolesUI(NamedId appId) { - return urlsOptions.BuildUrl($"app/{appId.Name}/settings/roles", false); + return urlGenerator.BuildUrl($"app/{appId.Name}/settings/roles", false); } public string RulesUI(NamedId appId) { - return urlsOptions.BuildUrl($"app/{appId.Name}/rules", false); + return urlGenerator.BuildUrl($"app/{appId.Name}/rules", false); } public string SchemasUI(NamedId appId) { - return urlsOptions.BuildUrl($"app/{appId.Name}/schemas", false); + return urlGenerator.BuildUrl($"app/{appId.Name}/schemas", false); } public string SchemaUI(NamedId appId, NamedId schemaId) { - return urlsOptions.BuildUrl($"app/{appId.Name}/schemas/{schemaId.Name}", false); + return urlGenerator.BuildUrl($"app/{appId.Name}/schemas/{schemaId.Name}", false); } public string WorkflowsUI(NamedId appId) { - return urlsOptions.BuildUrl($"app/{appId.Name}/settings/workflows", false); + return urlGenerator.BuildUrl($"app/{appId.Name}/settings/workflows", false); } public string UI() { - return urlsOptions.BuildUrl("app", false); + return urlGenerator.BuildUrl("app", false); } } } diff --git a/backend/src/Squidex.Web/UrlsOptions.cs b/backend/src/Squidex.Web/UrlsOptions.cs deleted file mode 100644 index 36ad4832c..000000000 --- a/backend/src/Squidex.Web/UrlsOptions.cs +++ /dev/null @@ -1,159 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschränkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System; -using System.Collections.Generic; -using Microsoft.AspNetCore.Http; -using Squidex.Infrastructure; - -namespace Squidex.Web -{ - public sealed class UrlsOptions - { - private readonly HashSet allTrustedHosts = new HashSet(); - private string baseUrl; - private string[]? trustedHosts; - - public string[]? KnownProxies { get; set; } - - public bool EnableForwardHeaders { get; set; } = true; - - public bool EnforceHTTPS { get; set; } = false; - - public bool EnforceHost { get; set; } = false; - - public int? HttpsPort { get; set; } = 443; - - public string BaseUrl - { - get - { - return baseUrl; - } - set - { - if (TryBuildHost(value, out var host)) - { - allTrustedHosts.Add(host); - } - - baseUrl = value; - } - } - - public string[]? TrustedHosts - { - get - { - return trustedHosts; - } - set - { - if (trustedHosts != null) - { - foreach (var canidate in trustedHosts) - { - if (TryBuildHost(canidate, out var host)) - { - allTrustedHosts.Add(host); - } - } - } - - trustedHosts = value; - } - } - - public bool IsAllowedHost(string? url) - { - if (!Uri.TryCreate(url, UriKind.RelativeOrAbsolute, out var uri)) - { - return false; - } - - return IsAllowedHost(uri); - } - - public bool IsAllowedHost(Uri uri) - { - if (!uri.IsAbsoluteUri) - { - return true; - } - - return allTrustedHosts.Contains(BuildHost(uri)); - } - - public string BuildUrl(string path, bool trailingSlash = true) - { - if (string.IsNullOrWhiteSpace(BaseUrl)) - { - throw new ConfigurationException("Configure BaseUrl with 'urls:baseUrl'."); - } - - return BaseUrl.BuildFullUrl(path, trailingSlash); - } - - public HostString BuildHost() - { - if (string.IsNullOrWhiteSpace(BaseUrl)) - { - throw new ConfigurationException("Configure BaseUrl with 'urls:baseUrl'."); - } - - if (!TryBuildHost(BaseUrl, out var host)) - { - throw new ConfigurationException("Configure BaseUrl with 'urls:baseUrl' host name."); - } - - return host; - } - - private static bool TryBuildHost(string urlOrHost, out HostString host) - { - host = default; - - if (string.IsNullOrWhiteSpace(urlOrHost)) - { - return false; - } - - if (Uri.TryCreate(urlOrHost, UriKind.Absolute, out var uri1)) - { - host = BuildHost(uri1); - - return true; - } - - if (Uri.TryCreate($"http://{urlOrHost}", UriKind.Absolute, out var uri2)) - { - host = BuildHost(uri2); - - return true; - } - - return false; - } - - private static HostString BuildHost(Uri uri) - { - return BuildHost(uri.Host, uri.Port); - } - - private static HostString BuildHost(string host, int port) - { - if (port == 443 || port == 80) - { - return new HostString(host.ToLowerInvariant()); - } - else - { - return new HostString(host.ToLowerInvariant(), port); - } - } - } -} diff --git a/backend/src/Squidex/Areas/Api/Config/OpenApi/CommonProcessor.cs b/backend/src/Squidex/Areas/Api/Config/OpenApi/CommonProcessor.cs index 20a01d6b7..a9c3f384b 100644 --- a/backend/src/Squidex/Areas/Api/Config/OpenApi/CommonProcessor.cs +++ b/backend/src/Squidex/Areas/Api/Config/OpenApi/CommonProcessor.cs @@ -6,10 +6,10 @@ // ========================================================================== using System.Collections.Generic; -using Microsoft.Extensions.Options; using NSwag; using NSwag.Generation.Processors; using NSwag.Generation.Processors.Contexts; +using Squidex.Hosting; using Squidex.Web; namespace Squidex.Areas.Api.Config.OpenApi @@ -24,9 +24,9 @@ namespace Squidex.Areas.Api.Config.OpenApi Url = "https://docs.squidex.io" }; - public CommonProcessor(ExposedValues exposedValues, IOptions urlOptions) + public CommonProcessor(ExposedValues exposedValues, IUrlGenerator urlGenerator) { - logoUrl = urlOptions.Value.BuildUrl("images/logo-white.png", false); + logoUrl = urlGenerator.BuildUrl("images/logo-white.png", false); if (!exposedValues.TryGetValue("version", out version!) || version == null) { diff --git a/backend/src/Squidex/Areas/Api/Config/OpenApi/SecurityProcessor.cs b/backend/src/Squidex/Areas/Api/Config/OpenApi/SecurityProcessor.cs index c0c4bd636..3d5538083 100644 --- a/backend/src/Squidex/Areas/Api/Config/OpenApi/SecurityProcessor.cs +++ b/backend/src/Squidex/Areas/Api/Config/OpenApi/SecurityProcessor.cs @@ -7,9 +7,9 @@ using System.Collections.Generic; using System.Linq; -using Microsoft.Extensions.Options; using NSwag; using NSwag.Generation.Processors.Security; +using Squidex.Hosting; using Squidex.Pipeline.OpenApi; using Squidex.Web; @@ -17,19 +17,19 @@ namespace Squidex.Areas.Api.Config.OpenApi { public sealed class SecurityProcessor : SecurityDefinitionAppender { - public SecurityProcessor(IOptions urlOptions) - : base(Constants.SecurityDefinition, Enumerable.Empty(), CreateOAuthSchema(urlOptions.Value)) + public SecurityProcessor(IUrlGenerator urlGenerator) + : base(Constants.SecurityDefinition, Enumerable.Empty(), CreateOAuthSchema(urlGenerator)) { } - private static OpenApiSecurityScheme CreateOAuthSchema(UrlsOptions urlOptions) + private static OpenApiSecurityScheme CreateOAuthSchema(IUrlGenerator urlGenerator) { var security = new OpenApiSecurityScheme { Type = OpenApiSecuritySchemeType.OAuth2 }; - var tokenUrl = urlOptions.BuildUrl($"{Constants.IdentityServerPrefix}/connect/token", false); + var tokenUrl = urlGenerator.BuildUrl($"{Constants.IdentityServerPrefix}/connect/token", false); security.TokenUrl = tokenUrl; diff --git a/backend/src/Squidex/Areas/Api/Controllers/Statistics/UsagesController.cs b/backend/src/Squidex/Areas/Api/Controllers/Statistics/UsagesController.cs index 0aad7d835..5a6e51163 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Statistics/UsagesController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Statistics/UsagesController.cs @@ -10,11 +10,11 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Options; using Squidex.Areas.Api.Controllers.Statistics.Models; using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps.Plans; using Squidex.Domain.Apps.Entities.Assets; +using Squidex.Hosting; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.UsageTracking; @@ -34,16 +34,16 @@ namespace Squidex.Areas.Api.Controllers.Statistics private readonly IAppPlansProvider appPlansProvider; private readonly IAssetUsageTracker assetStatsRepository; private readonly IDataProtector dataProtector; - private readonly UrlsOptions urlsOptions; + private readonly IUrlGenerator urlGenerator; public UsagesController( ICommandBus commandBus, + IDataProtectionProvider dataProtection, IApiUsageTracker usageTracker, IAppLogStore appLogStore, IAppPlansProvider appPlansProvider, IAssetUsageTracker assetStatsRepository, - IDataProtectionProvider dataProtection, - IOptions urlsOptions) + IUrlGenerator urlGenerator) : base(commandBus) { this.usageTracker = usageTracker; @@ -51,7 +51,7 @@ namespace Squidex.Areas.Api.Controllers.Statistics this.appLogStore = appLogStore; this.appPlansProvider = appPlansProvider; this.assetStatsRepository = assetStatsRepository; - this.urlsOptions = urlsOptions.Value; + this.urlGenerator = urlGenerator; dataProtector = dataProtection.CreateProtector("LogToken"); } @@ -73,7 +73,7 @@ namespace Squidex.Areas.Api.Controllers.Statistics { var token = dataProtector.Protect(App.Id.ToString()); - var url = urlsOptions.BuildUrl($"/api/apps/log/{token}/"); + var url = urlGenerator.BuildUrl($"/api/apps/log/{token}/"); var response = new LogDownloadDto { DownloadUrl = url }; diff --git a/backend/src/Squidex/Areas/Api/Controllers/Translations/Models/TranslationDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Translations/Models/TranslationDto.cs index 8cf314fe0..6eb301ff9 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Translations/Models/TranslationDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Translations/Models/TranslationDto.cs @@ -6,7 +6,6 @@ // ========================================================================== using Squidex.Infrastructure.Reflection; -using Squidex.Infrastructure.Translations; using Squidex.Text.Translations; namespace Squidex.Areas.Api.Controllers.Translations.Models diff --git a/backend/src/Squidex/Areas/Api/Controllers/Translations/TranslationsController.cs b/backend/src/Squidex/Areas/Api/Controllers/Translations/TranslationsController.cs index 068f3e0ea..ea9b74897 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Translations/TranslationsController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Translations/TranslationsController.cs @@ -9,7 +9,6 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Squidex.Areas.Api.Controllers.Translations.Models; using Squidex.Infrastructure.Commands; -using Squidex.Infrastructure.Translations; using Squidex.Shared; using Squidex.Text.Translations; using Squidex.Web; diff --git a/backend/src/Squidex/Areas/IdentityServer/Config/CreateAdminHost.cs b/backend/src/Squidex/Areas/IdentityServer/Config/CreateAdminInitializer.cs similarity index 91% rename from backend/src/Squidex/Areas/IdentityServer/Config/CreateAdminHost.cs rename to backend/src/Squidex/Areas/IdentityServer/Config/CreateAdminInitializer.cs index 228bbaf49..728470dc7 100644 --- a/backend/src/Squidex/Areas/IdentityServer/Config/CreateAdminHost.cs +++ b/backend/src/Squidex/Areas/IdentityServer/Config/CreateAdminInitializer.cs @@ -14,8 +14,8 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Logging; using Squidex.Config; -using Squidex.Config.Startup; using Squidex.Domain.Users; +using Squidex.Hosting; using Squidex.Infrastructure; using Squidex.Infrastructure.Security; using Squidex.Log; @@ -24,20 +24,21 @@ using Squidex.Shared.Users; namespace Squidex.Areas.IdentityServer.Config { - public sealed class CreateAdminHost : SafeHostedService + public sealed class CreateAdminInitializer : IInitializable { private readonly IServiceProvider serviceProvider; private readonly MyIdentityOptions identityOptions; - public CreateAdminHost(ISemanticLog log, IServiceProvider serviceProvider, IOptions identityOptions) - : base(log) + public int Order => int.MaxValue; + + public CreateAdminInitializer(IServiceProvider serviceProvider, IOptions identityOptions) { this.serviceProvider = serviceProvider; this.identityOptions = identityOptions.Value; } - protected override async Task StartAsync(ISemanticLog log, CancellationToken ct) + public async Task InitializeAsync(CancellationToken ct) { IdentityModelEventSource.ShowPII = identityOptions.ShowPII; @@ -91,6 +92,8 @@ namespace Squidex.Areas.IdentityServer.Config } catch (Exception ex) { + var log = serviceProvider.GetRequiredService(); + log.LogError(ex, w => w .WriteProperty("action", "createAdmin") .WriteProperty("status", "failed")); diff --git a/backend/src/Squidex/Areas/IdentityServer/Config/IdentityServerServices.cs b/backend/src/Squidex/Areas/IdentityServer/Config/IdentityServerServices.cs index 8e7f82f7e..ec060c9c2 100644 --- a/backend/src/Squidex/Areas/IdentityServer/Config/IdentityServerServices.cs +++ b/backend/src/Squidex/Areas/IdentityServer/Config/IdentityServerServices.cs @@ -15,7 +15,6 @@ using Microsoft.AspNetCore.DataProtection.KeyManagement; using Microsoft.AspNetCore.DataProtection.Repositories; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; using Squidex.Domain.Users; using Squidex.Shared.Identity; using Squidex.Web; @@ -27,15 +26,13 @@ namespace Squidex.Areas.IdentityServer.Config { public static void AddSquidexIdentityServer(this IServiceCollection services) { - services.AddSingletonAs>(s => + services.Configure((c, options) => { - return new ConfigureOptions(options => - { - options.XmlRepository = s.GetRequiredService(); - }); + options.XmlRepository = c.GetRequiredService(); }); - services.AddDataProtection().SetApplicationName("Squidex"); + services.AddDataProtection() + .SetApplicationName("Squidex"); services.AddIdentity() .AddDefaultTokenProviders(); @@ -61,6 +58,9 @@ namespace Squidex.Areas.IdentityServer.Config services.AddSingletonAs() .As(); + services.AddSingletonAs() + .AsSelf(); + services.AddIdentityServer(options => { options.UserInteraction.ErrorUrl = "/error/"; diff --git a/backend/src/Squidex/Areas/IdentityServer/Config/LazyClientStore.cs b/backend/src/Squidex/Areas/IdentityServer/Config/LazyClientStore.cs index e4ce295b5..4e3ae48a1 100644 --- a/backend/src/Squidex/Areas/IdentityServer/Config/LazyClientStore.cs +++ b/backend/src/Squidex/Areas/IdentityServer/Config/LazyClientStore.cs @@ -18,6 +18,7 @@ using Squidex.Config; using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Entities; using Squidex.Domain.Users; +using Squidex.Hosting; using Squidex.Infrastructure; using Squidex.Infrastructure.Security; using Squidex.Shared; @@ -30,25 +31,15 @@ namespace Squidex.Areas.IdentityServer.Config public class LazyClientStore : IClientStore { private readonly IServiceProvider serviceProvider; - private readonly IAppProvider appProvider; private readonly Dictionary staticClients = new Dictionary(StringComparer.OrdinalIgnoreCase); - public LazyClientStore( - IServiceProvider serviceProvider, - IOptions urlsOptions, - IOptions identityOptions, - IAppProvider appProvider) + public LazyClientStore(IServiceProvider serviceProvider) { - Guard.NotNull(appProvider, nameof(appProvider)); - Guard.NotNull(identityOptions, nameof(identityOptions)); Guard.NotNull(serviceProvider, nameof(serviceProvider)); - Guard.NotNull(urlsOptions, nameof(urlsOptions)); this.serviceProvider = serviceProvider; - this.appProvider = appProvider; - - CreateStaticClients(urlsOptions, identityOptions); + CreateStaticClients(); } public async Task FindClientByIdAsync(string clientId) @@ -62,6 +53,8 @@ namespace Squidex.Areas.IdentityServer.Config var (appName, appClientId) = clientId.GetClientParts(); + var appProvider = serviceProvider.GetRequiredService(); + if (!string.IsNullOrWhiteSpace(appName) && !string.IsNullOrWhiteSpace(appClientId)) { var app = await appProvider.GetAppAsync(appName, true); @@ -136,15 +129,19 @@ namespace Squidex.Areas.IdentityServer.Config }; } - private void CreateStaticClients(IOptions urlsOptions, IOptions identityOptions) + private void CreateStaticClients() { - foreach (var client in CreateStaticClients(urlsOptions.Value, identityOptions.Value)) + var identityOptions = serviceProvider.GetRequiredService>().Value; + + var urlGenerator = serviceProvider.GetRequiredService(); + + foreach (var client in CreateStaticClients(urlGenerator, identityOptions)) { staticClients[client.ClientId] = client; } } - private static IEnumerable CreateStaticClients(UrlsOptions urlsOptions, MyIdentityOptions identityOptions) + private static IEnumerable CreateStaticClients(IUrlGenerator urlGenerator, MyIdentityOptions identityOptions) { var frontendId = Constants.FrontendClient; @@ -154,13 +151,13 @@ namespace Squidex.Areas.IdentityServer.Config ClientName = frontendId, RedirectUris = new List { - urlsOptions.BuildUrl("login;"), - urlsOptions.BuildUrl("client-callback-silent", false), - urlsOptions.BuildUrl("client-callback-popup", false) + urlGenerator.BuildUrl("login;"), + urlGenerator.BuildUrl("client-callback-silent", false), + urlGenerator.BuildUrl("client-callback-popup", false) }, PostLogoutRedirectUris = new List { - urlsOptions.BuildUrl("logout", false) + urlGenerator.BuildUrl("logout", false) }, AllowAccessTokensViaBrowser = true, AllowedGrantTypes = GrantTypes.Implicit, @@ -189,8 +186,8 @@ namespace Squidex.Areas.IdentityServer.Config }, RedirectUris = new List { - urlsOptions.BuildUrl($"{Constants.PortalPrefix}/signin-internal", false), - urlsOptions.BuildUrl($"{Constants.OrleansPrefix}/signin-internal", false) + urlGenerator.BuildUrl($"{Constants.PortalPrefix}/signin-internal", false), + urlGenerator.BuildUrl($"{Constants.OrleansPrefix}/signin-internal", false) }, AccessTokenLifetime = (int)TimeSpan.FromDays(30).TotalSeconds, AllowedGrantTypes = GrantTypes.ImplicitAndClientCredentials, diff --git a/backend/src/Squidex/Areas/IdentityServer/Controllers/Account/AccountController.cs b/backend/src/Squidex/Areas/IdentityServer/Controllers/Account/AccountController.cs index 696a9b252..f7fb7c1ad 100644 --- a/backend/src/Squidex/Areas/IdentityServer/Controllers/Account/AccountController.cs +++ b/backend/src/Squidex/Areas/IdentityServer/Controllers/Account/AccountController.cs @@ -22,6 +22,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using Squidex.Config; using Squidex.Domain.Users; +using Squidex.Hosting; using Squidex.Infrastructure; using Squidex.Infrastructure.Security; using Squidex.Infrastructure.Translations; @@ -41,7 +42,7 @@ namespace Squidex.Areas.IdentityServer.Controllers.Account private readonly UserManager userManager; private readonly IUserFactory userFactory; private readonly IUserEvents userEvents; - private readonly UrlsOptions urlsOptions; + private readonly IUrlGenerator urlGenerator; private readonly MyIdentityOptions identityOptions; private readonly ISemanticLog log; private readonly IIdentityServerInteractionService interactions; @@ -51,7 +52,7 @@ namespace Squidex.Areas.IdentityServer.Controllers.Account UserManager userManager, IUserFactory userFactory, IUserEvents userEvents, - IOptions urlsOptions, + IUrlGenerator urlGenerator, IOptions identityOptions, ISemanticLog log, IIdentityServerInteractionService interactions) @@ -59,7 +60,7 @@ namespace Squidex.Areas.IdentityServer.Controllers.Account this.identityOptions = identityOptions.Value; this.interactions = interactions; this.signInManager = signInManager; - this.urlsOptions = urlsOptions.Value; + this.urlGenerator = urlGenerator; this.userEvents = userEvents; this.userFactory = userFactory; this.userManager = userManager; @@ -406,7 +407,7 @@ namespace Squidex.Areas.IdentityServer.Controllers.Account private IActionResult RedirectToReturnUrl(string? returnUrl) { - if (urlsOptions.IsAllowedHost(returnUrl) || interactions.IsValidReturnUrl(returnUrl)) + if (urlGenerator.IsAllowedHost(returnUrl) || interactions.IsValidReturnUrl(returnUrl)) { return Redirect(returnUrl); } diff --git a/backend/src/Squidex/Config/Authentication/IdentityServerServices.cs b/backend/src/Squidex/Config/Authentication/IdentityServerServices.cs index aa3284347..59370a1c7 100644 --- a/backend/src/Squidex/Config/Authentication/IdentityServerServices.cs +++ b/backend/src/Squidex/Config/Authentication/IdentityServerServices.cs @@ -5,13 +5,17 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System; using IdentityServer4; using IdentityServer4.AccessTokenValidation; +using IdentityServer4.Hosting.LocalApiAuthentication; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Authentication.OpenIdConnect; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Squidex.Hosting; using Squidex.Web; namespace Squidex.Config.Authentication @@ -20,25 +24,14 @@ namespace Squidex.Config.Authentication { public static AuthenticationBuilder AddSquidexIdentityServerAuthentication(this AuthenticationBuilder authBuilder, MyIdentityOptions identityOptions, IConfiguration config) { - var apiAuthorityUrl = identityOptions.AuthorityUrl; - - var useCustomAuthorityUrl = !string.IsNullOrWhiteSpace(apiAuthorityUrl); - - if (!useCustomAuthorityUrl) - { - var urlsOptions = config.GetSection("urls").Get(); - - apiAuthorityUrl = urlsOptions.BuildUrl(Constants.IdentityServerPrefix); - } - - var apiScope = Constants.ApiScope; + var useCustomAuthorityUrl = !string.IsNullOrWhiteSpace(identityOptions.AuthorityUrl); if (useCustomAuthorityUrl) { authBuilder.AddIdentityServerAuthentication(options => { - options.Authority = apiAuthorityUrl; - options.ApiName = apiScope; + options.Authority = identityOptions.AuthorityUrl; + options.ApiName = Constants.ApiScope; options.ApiSecret = null; options.RequireHttpsMetadata = identityOptions.RequiresHttps; options.SupportedTokens = SupportedTokens.Jwt; @@ -46,19 +39,27 @@ namespace Squidex.Config.Authentication } else { - var urlsOptions = config.GetSection("urls").Get(); + authBuilder.AddLocalApi(); - authBuilder.AddLocalApi(options => + authBuilder.Services.Configure((c, options) => { - options.ClaimsIssuer = urlsOptions.BuildUrl("/identity-server", false); + options.ClaimsIssuer = GetAuthorityUrl(c); - options.ExpectedScope = apiScope; + options.ExpectedScope = Constants.ApiScope; }); } - authBuilder.AddOpenIdConnect(options => + authBuilder.Services.Configure((c, options) => { - options.Authority = apiAuthorityUrl; + if (!string.IsNullOrWhiteSpace(identityOptions.AuthorityUrl)) + { + options.Authority = identityOptions.AuthorityUrl; + } + else + { + options.Authority = GetAuthorityUrl(c); + } + options.ClientId = Constants.InternalClientId; options.ClientSecret = Constants.InternalClientSecret; options.CallbackPath = "/signin-internal"; @@ -85,5 +86,12 @@ namespace Squidex.Config.Authentication return authBuilder; } + + private static string GetAuthorityUrl(IServiceProvider services) + { + var urlGenerator = services.GetRequiredService(); + + return urlGenerator.BuildUrl(Constants.IdentityServerPrefix, false); + } } } diff --git a/backend/src/Squidex/Config/Authentication/IdentityServices.cs b/backend/src/Squidex/Config/Authentication/IdentityServices.cs index 1b57970f4..d4d4c0f3e 100644 --- a/backend/src/Squidex/Config/Authentication/IdentityServices.cs +++ b/backend/src/Squidex/Config/Authentication/IdentityServices.cs @@ -16,8 +16,7 @@ namespace Squidex.Config.Authentication { public static void AddSquidexIdentity(this IServiceCollection services, IConfiguration config) { - services.Configure( - config.GetSection("identity")); + services.Configure(config, "identity"); services.AddSingletonAs() .AsOptional(); diff --git a/backend/src/Squidex/Config/Domain/AssetServices.cs b/backend/src/Squidex/Config/Domain/AssetServices.cs index 8da5af9bc..f1f35f3cf 100644 --- a/backend/src/Squidex/Config/Domain/AssetServices.cs +++ b/backend/src/Squidex/Config/Domain/AssetServices.cs @@ -19,6 +19,7 @@ using Squidex.Domain.Apps.Entities.Assets.DomainObject; using Squidex.Domain.Apps.Entities.Assets.Queries; using Squidex.Domain.Apps.Entities.History; using Squidex.Domain.Apps.Entities.Search; +using Squidex.Hosting; using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; @@ -28,8 +29,7 @@ namespace Squidex.Config.Domain { public static void AddSquidexAssets(this IServiceCollection services, IConfiguration config) { - services.Configure( - config.GetSection("assets")); + services.Configure(config, "assets"); if (config.GetValue("assets:deleteRecursive")) { @@ -162,7 +162,7 @@ namespace Squidex.Config.Domain .As(); services.AddSingletonAs(c => new DelegateInitializer( - c.GetRequiredService().GetType().FullName!, + c.GetRequiredService().GetType().Name, c.GetRequiredService().InitializeAsync)) .As(); } diff --git a/backend/src/Squidex/Config/Domain/CommandsServices.cs b/backend/src/Squidex/Config/Domain/CommandsServices.cs index 84cec98f4..f614b721c 100644 --- a/backend/src/Squidex/Config/Domain/CommandsServices.cs +++ b/backend/src/Squidex/Config/Domain/CommandsServices.cs @@ -32,8 +32,7 @@ namespace Squidex.Config.Domain { public static void AddSquidexCommands(this IServiceCollection services, IConfiguration config) { - services.Configure( - config.GetSection("mode")); + services.Configure(config, "mode"); services.AddSingletonAs() .As(); diff --git a/backend/src/Squidex/Config/Domain/ContentsServices.cs b/backend/src/Squidex/Config/Domain/ContentsServices.cs index 7ae9d556c..e8ef3582d 100644 --- a/backend/src/Squidex/Config/Domain/ContentsServices.cs +++ b/backend/src/Squidex/Config/Domain/ContentsServices.cs @@ -27,8 +27,7 @@ namespace Squidex.Config.Domain { public static void AddSquidexContents(this IServiceCollection services, IConfiguration config) { - services.Configure( - config.GetSection("contents")); + services.Configure(config, "contents"); services.AddSingletonAs(c => new Lazy(c.GetRequiredService)) .AsSelf(); diff --git a/backend/src/Squidex/Config/Domain/EventPublishersServices.cs b/backend/src/Squidex/Config/Domain/EventPublishersServices.cs index d1dfbd662..3f0a799ab 100644 --- a/backend/src/Squidex/Config/Domain/EventPublishersServices.cs +++ b/backend/src/Squidex/Config/Domain/EventPublishersServices.cs @@ -8,7 +8,7 @@ using System; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Squidex.Infrastructure; +using Squidex.Hosting.Configuration; using Squidex.Infrastructure.CQRS.Events; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Json; @@ -27,7 +27,9 @@ namespace Squidex.Config.Domain if (string.IsNullOrWhiteSpace(eventPublisherType)) { - throw new ConfigurationException($"Configure EventPublisher type with 'eventPublishers:{child.Key}:type'."); + var error = new ConfigurationError("Value is required.", "eventPublishers:{child.Key}:type"); + + throw new ConfigurationException(error); } var eventsFilter = child.GetValue("eventsFilter"); @@ -40,14 +42,18 @@ namespace Squidex.Config.Domain if (string.IsNullOrWhiteSpace(publisherConfig)) { - throw new ConfigurationException($"Configure EventPublisher RabbitMq configuration with 'eventPublishers:{child.Key}:configuration'."); + var error = new ConfigurationError("Value is required.", "eventPublishers:{child.Key}:configuration"); + + throw new ConfigurationException(error); } var exchange = child.GetValue("exchange"); if (string.IsNullOrWhiteSpace(exchange)) { - throw new ConfigurationException($"Configure EventPublisher RabbitMq exchange with 'eventPublishers:{child.Key}:configuration'."); + var error = new ConfigurationError("Value is required.", "eventPublishers:{child.Key}:exchange"); + + throw new ConfigurationException(error); } var name = $"EventPublishers_{child.Key}"; @@ -60,7 +66,9 @@ namespace Squidex.Config.Domain } else { - throw new ConfigurationException($"Unsupported value '{child.Key}' for 'eventPublishers:{child.Key}:type', supported: RabbitMq."); + var error = new ConfigurationError($"Unsupported value '{child.Key}", "eventPublishers:{child.Key}:type."); + + throw new ConfigurationException(error); } } } diff --git a/backend/src/Squidex/Config/Domain/HealthCheckServices.cs b/backend/src/Squidex/Config/Domain/HealthCheckServices.cs index a337b8568..64452862c 100644 --- a/backend/src/Squidex/Config/Domain/HealthCheckServices.cs +++ b/backend/src/Squidex/Config/Domain/HealthCheckServices.cs @@ -17,8 +17,7 @@ namespace Squidex.Config.Domain { public static void AddSquidexHealthChecks(this IServiceCollection services, IConfiguration config) { - services.Configure( - config.GetSection("healthz:gc")); + services.Configure(config, "healthz:gc"); services.AddHealthChecks() .AddCheck("GC", tags: new[] { "node" }) diff --git a/backend/src/Squidex/Config/Domain/InfrastructureServices.cs b/backend/src/Squidex/Config/Domain/InfrastructureServices.cs index efce25d96..f76db8ee7 100644 --- a/backend/src/Squidex/Config/Domain/InfrastructureServices.cs +++ b/backend/src/Squidex/Config/Domain/InfrastructureServices.cs @@ -29,7 +29,6 @@ using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.EventSourcing.Grains; using Squidex.Infrastructure.Orleans; -using Squidex.Infrastructure.Translations; using Squidex.Infrastructure.UsageTracking; using Squidex.Pipeline.Robots; using Squidex.Text.Translations; @@ -43,12 +42,9 @@ namespace Squidex.Config.Domain { public static void AddSquidexInfrastructure(this IServiceCollection services, IConfiguration config) { - services.Configure( - config.GetSection("urls")); - services.Configure( - config.GetSection("exposedConfiguration")); - services.Configure( - config.GetSection("caching:replicated")); + services.Configure(config, "exposedConfiguration"); + + services.Configure(config, "caching:replicated"); services.AddReplicatedCache(); services.AddAsyncLocalCache(); @@ -103,8 +99,7 @@ namespace Squidex.Config.Domain public static void AddSquidexUsageTracking(this IServiceCollection services, IConfiguration config) { - services.Configure( - config.GetSection("usage")); + services.Configure(config, "usage"); services.AddSingletonAs(c => new CachingUsageTracker( c.GetRequiredService(), @@ -123,12 +118,11 @@ namespace Squidex.Config.Domain public static void AddSquidexTranslation(this IServiceCollection services, IConfiguration config) { - services.Configure( - config.GetSection("translations:deepL")); - services.Configure( - config.GetSection("translations:googleCloud")); - services.Configure( - config.GetSection("languages")); + services.Configure(config, "translations:googleCloud"); + + services.Configure(config, "translations:deepL"); + + services.Configure(config, "languages"); services.AddSingletonAs() .AsSelf(); @@ -145,14 +139,13 @@ namespace Squidex.Config.Domain public static void AddSquidexControllerServices(this IServiceCollection services, IConfiguration config) { - services.Configure( - config.GetSection("robots")); - services.Configure( - config.GetSection("caching")); - services.Configure( - config.GetSection("ui")); - services.Configure( - config.GetSection("news")); + services.Configure(config, "robots"); + + services.Configure(config, "caching"); + + services.Configure(config, "ui"); + + services.Configure(config, "news"); services.AddSingletonAs() .AsSelf(); diff --git a/backend/src/Squidex/Config/Domain/LoggingServices.cs b/backend/src/Squidex/Config/Domain/LoggingServices.cs index 666d00106..d2d5fb3f7 100644 --- a/backend/src/Squidex/Config/Domain/LoggingServices.cs +++ b/backend/src/Squidex/Config/Domain/LoggingServices.cs @@ -23,6 +23,7 @@ namespace Squidex.Config.Domain public static void ConfigureForSquidex(this ILoggingBuilder builder, IConfiguration config) { builder.ClearProviders(); + builder.ConfigureSemanticLog(config); builder.AddConfiguration(config.GetSection("logging")); @@ -34,38 +35,9 @@ namespace Squidex.Config.Domain private static void AddServices(this IServiceCollection services, IConfiguration config) { - services.Configure( - config.GetSection("logging")); + services.Configure(config, "logging"); - services.Configure( - config.GetSection("logging")); - - services.Configure( - config.GetSection("logging")); - - if (config.GetValue("logging:human")) - { - services.AddSingletonAs(_ => JsonLogWriterFactory.Readable()) - .As(); - } - else - { - services.AddSingletonAs(_ => JsonLogWriterFactory.Default()) - .As(); - } - - var loggingFile = config.GetValue("logging:file"); - - if (!string.IsNullOrWhiteSpace(loggingFile)) - { - services.AddSingletonAs(_ => new FileChannel(loggingFile)) - .As(); - } - - var useColors = config.GetValue("logging:colors"); - - services.AddSingletonAs(_ => new ConsoleLogChannel(useColors)) - .As(); + services.Configure(config, "logging"); services.AddSingletonAs(_ => new ApplicationInfoLogAppender(typeof(LoggingServices).Assembly, Guid.NewGuid())) .As(); @@ -73,15 +45,6 @@ namespace Squidex.Config.Domain services.AddSingletonAs() .As(); - services.AddSingletonAs() - .As(); - - services.AddSingletonAs() - .As(); - - services.AddSingletonAs() - .As(); - services.AddSingletonAs() .As(); diff --git a/backend/src/Squidex/Config/Domain/MigrationServices.cs b/backend/src/Squidex/Config/Domain/MigrationServices.cs index ea10f6a3e..8bf642238 100644 --- a/backend/src/Squidex/Config/Domain/MigrationServices.cs +++ b/backend/src/Squidex/Config/Domain/MigrationServices.cs @@ -17,8 +17,7 @@ namespace Squidex.Config.Domain { public static void AddSquidexMigration(this IServiceCollection services, IConfiguration config) { - services.Configure( - config.GetSection("rebuild")); + services.Configure(config, "rebuild"); services.AddSingletonAs() .AsSelf(); diff --git a/backend/src/Squidex/Config/Domain/NotificationsServices.cs b/backend/src/Squidex/Config/Domain/NotificationsServices.cs index d85ca41c8..8af672216 100644 --- a/backend/src/Squidex/Config/Domain/NotificationsServices.cs +++ b/backend/src/Squidex/Config/Domain/NotificationsServices.cs @@ -25,8 +25,7 @@ namespace Squidex.Config.Domain { services.AddSingleton(Options.Create(emailOptions)); - services.Configure( - config.GetSection("email:notifications")); + services.Configure(config, "email:notifications"); services.AddSingletonAs() .As(); diff --git a/backend/src/Squidex/Config/Domain/QueryServices.cs b/backend/src/Squidex/Config/Domain/QueryServices.cs index 183513e18..4732f9646 100644 --- a/backend/src/Squidex/Config/Domain/QueryServices.cs +++ b/backend/src/Squidex/Config/Domain/QueryServices.cs @@ -8,11 +8,8 @@ using GraphQL.DataLoader; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; using Squidex.Domain.Apps.Core; -using Squidex.Domain.Apps.Entities.Assets; using Squidex.Domain.Apps.Entities.Contents.GraphQL; -using Squidex.Web; using Squidex.Web.Services; namespace Squidex.Config.Domain @@ -23,10 +20,7 @@ namespace Squidex.Config.Domain { var exposeSourceUrl = config.GetOptionalValue("assetStore:exposeSourceUrl", true); - services.AddSingletonAs(c => new UrlGenerator( - c.GetRequiredService>(), - c.GetRequiredService(), - exposeSourceUrl)) + services.AddSingletonAs(c => ActivatorUtilities.CreateInstance(c, exposeSourceUrl)) .As(); services.AddSingletonAs() diff --git a/backend/src/Squidex/Config/Domain/RuleServices.cs b/backend/src/Squidex/Config/Domain/RuleServices.cs index b8b79d60b..5b92340ff 100644 --- a/backend/src/Squidex/Config/Domain/RuleServices.cs +++ b/backend/src/Squidex/Config/Domain/RuleServices.cs @@ -30,8 +30,7 @@ namespace Squidex.Config.Domain { public static void AddSquidexRules(this IServiceCollection services, IConfiguration config) { - services.Configure( - config.GetSection("rules")); + services.Configure(config, "rules"); services.AddTransientAs() .AsSelf(); diff --git a/backend/src/Squidex/Config/Domain/SerializationInitializer.cs b/backend/src/Squidex/Config/Domain/SerializationInitializer.cs index 2aac58757..282bd7b7f 100644 --- a/backend/src/Squidex/Config/Domain/SerializationInitializer.cs +++ b/backend/src/Squidex/Config/Domain/SerializationInitializer.cs @@ -11,6 +11,7 @@ using System.Threading.Tasks; using Newtonsoft.Json; using Squidex.Areas.Api.Controllers.Rules.Models; using Squidex.Domain.Apps.Core.HandleRules; +using Squidex.Hosting; using Squidex.Infrastructure; using Squidex.Infrastructure.Json; using Squidex.Infrastructure.MongoDb; @@ -24,6 +25,8 @@ namespace Squidex.Config.Domain private readonly IJsonSerializer jsonSerializer; private readonly RuleRegistry ruleRegistry; + public int Order => -1; + public SerializationInitializer(JsonSerializer jsonNetSerializer, IJsonSerializer jsonSerializer, RuleRegistry ruleRegistry) { this.jsonNetSerializer = jsonNetSerializer; diff --git a/backend/src/Squidex/Config/Orleans/Helper.cs b/backend/src/Squidex/Config/Orleans/Helper.cs index 16cb90d0b..9ba161811 100644 --- a/backend/src/Squidex/Config/Orleans/Helper.cs +++ b/backend/src/Squidex/Config/Orleans/Helper.cs @@ -10,7 +10,7 @@ using System.Linq; using System.Net; using System.Net.Sockets; using System.Threading.Tasks; -using Squidex.Infrastructure; +using Squidex.Hosting.Configuration; using Squidex.Infrastructure.Net; namespace Squidex.Config.Orleans @@ -37,7 +37,9 @@ namespace Squidex.Config.Orleans if (chosen == null) { - throw new ConfigurationException($"Hostname {addressOrHost} with family {family} is not a valid IP address or DNS name"); + var error = new ConfigurationError($"Hostname {addressOrHost} with family {family} is not a valid IP address or DNS name"); + + throw new ConfigurationException(error); } return chosen; diff --git a/backend/src/Squidex/Config/Startup/BackgroundHost.cs b/backend/src/Squidex/Config/Startup/BackgroundHost.cs deleted file mode 100644 index 1044b5232..000000000 --- a/backend/src/Squidex/Config/Startup/BackgroundHost.cs +++ /dev/null @@ -1,38 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. - -// ========================================================================== - -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Squidex.Infrastructure; -using Squidex.Log; - -namespace Squidex.Config.Startup -{ - public sealed class BackgroundHost : SafeHostedService - { - private readonly IEnumerable targets; - - public BackgroundHost(IEnumerable targets, ISemanticLog log) - : base(log) - { - this.targets = targets; - } - - protected override async Task StartAsync(ISemanticLog log, CancellationToken ct) - { - foreach (var target in targets.Distinct()) - { - await target.StartAsync(ct); - - log.LogInformation(w => w.WriteProperty("backgroundSystem", target.ToString())); - } - } - } -} diff --git a/backend/src/Squidex/Config/Startup/InitializerHost.cs b/backend/src/Squidex/Config/Startup/InitializerHost.cs deleted file mode 100644 index 24d657750..000000000 --- a/backend/src/Squidex/Config/Startup/InitializerHost.cs +++ /dev/null @@ -1,37 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Squidex.Infrastructure; -using Squidex.Log; - -namespace Squidex.Config.Startup -{ - public sealed class InitializerHost : SafeHostedService - { - private readonly IEnumerable targets; - - public InitializerHost(IEnumerable targets, ISemanticLog log) - : base(log) - { - this.targets = targets; - } - - protected override async Task StartAsync(ISemanticLog log, CancellationToken ct) - { - foreach (var target in targets.Distinct().OrderBy(x => x.Order)) - { - await target.InitializeAsync(ct); - - log.LogInformation(w => w.WriteProperty("initializedSystem", target.ToString())); - } - } - } -} diff --git a/backend/src/Squidex/Config/Startup/LogConfigurationHost.cs b/backend/src/Squidex/Config/Startup/LogConfigurationHost.cs index 2c70838b4..980efc0a9 100644 --- a/backend/src/Squidex/Config/Startup/LogConfigurationHost.cs +++ b/backend/src/Squidex/Config/Startup/LogConfigurationHost.cs @@ -11,21 +11,24 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; using Squidex.Log; namespace Squidex.Config.Startup { - public sealed class LogConfigurationHost : SafeHostedService + public sealed class LogConfigurationHost : IHostedService { private readonly IConfiguration configuration; + private readonly ISemanticLog log; - public LogConfigurationHost(ISemanticLog log, IConfiguration configuration) - : base(log) + public LogConfigurationHost(IConfiguration configuration, ISemanticLog log) { this.configuration = configuration; + + this.log = log; } - protected override Task StartAsync(ISemanticLog log, CancellationToken ct) + public Task StartAsync(CancellationToken cancellationToken) { log.LogInformation(w => w .WriteProperty("message", "Application started") @@ -46,5 +49,10 @@ namespace Squidex.Config.Startup return Task.CompletedTask; } + + public Task StopAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } } } diff --git a/backend/src/Squidex/Config/Startup/MigrationRebuilderHost.cs b/backend/src/Squidex/Config/Startup/MigrationRebuilderHost.cs index 9cd3bbb5f..c23fb052f 100644 --- a/backend/src/Squidex/Config/Startup/MigrationRebuilderHost.cs +++ b/backend/src/Squidex/Config/Startup/MigrationRebuilderHost.cs @@ -7,24 +7,28 @@ using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.Hosting; using Migrations; -using Squidex.Log; namespace Squidex.Config.Startup { - public sealed class MigrationRebuilderHost : SafeHostedService + public sealed class MigrationRebuilderHost : IHostedService { private readonly RebuildRunner rebuildRunner; - public MigrationRebuilderHost(RebuildRunner rebuildRunner, ISemanticLog log) - : base(log) + public MigrationRebuilderHost(RebuildRunner rebuildRunner) { this.rebuildRunner = rebuildRunner; } - protected override Task StartAsync(ISemanticLog log, CancellationToken ct) + public Task StartAsync(CancellationToken cancellationToken) { - return rebuildRunner.RunAsync(ct); + return rebuildRunner.RunAsync(cancellationToken); + } + + public Task StopAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; } } } diff --git a/backend/src/Squidex/Config/Startup/MigratorHost.cs b/backend/src/Squidex/Config/Startup/MigratorHost.cs index b50d3bb3d..8cd31f20f 100644 --- a/backend/src/Squidex/Config/Startup/MigratorHost.cs +++ b/backend/src/Squidex/Config/Startup/MigratorHost.cs @@ -7,24 +7,28 @@ using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.Hosting; using Squidex.Infrastructure.Migrations; -using Squidex.Log; namespace Squidex.Config.Startup { - public sealed class MigratorHost : SafeHostedService + public sealed class MigratorHost : IHostedService { private readonly Migrator migrator; - public MigratorHost(Migrator migrator, ISemanticLog log) - : base(log) + public MigratorHost(Migrator migrator) { this.migrator = migrator; } - protected override Task StartAsync(ISemanticLog log, CancellationToken ct) + public Task StartAsync(CancellationToken cancellationToken) { - return migrator.MigrateAsync(ct); + return migrator.MigrateAsync(cancellationToken); + } + + public Task StopAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; } } } diff --git a/backend/src/Squidex/Config/Startup/SafeHostedService.cs b/backend/src/Squidex/Config/Startup/SafeHostedService.cs deleted file mode 100644 index 8e9140a6c..000000000 --- a/backend/src/Squidex/Config/Startup/SafeHostedService.cs +++ /dev/null @@ -1,47 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Hosting; -using Squidex.Log; - -namespace Squidex.Config.Startup -{ - public abstract class SafeHostedService : IHostedService - { - private readonly ISemanticLog log; - private bool isStarted; - - protected SafeHostedService(ISemanticLog log) - { - this.log = log; - } - - public async Task StartAsync(CancellationToken cancellationToken) - { - await StartAsync(log, cancellationToken); - - isStarted = true; - } - - public async Task StopAsync(CancellationToken cancellationToken) - { - if (isStarted) - { - await StopAsync(log, cancellationToken); - } - } - - protected abstract Task StartAsync(ISemanticLog log, CancellationToken ct); - - protected virtual Task StopAsync(ISemanticLog log, CancellationToken ct) - { - return Task.CompletedTask; - } - } -} diff --git a/backend/src/Squidex/Config/Web/WebExtensions.cs b/backend/src/Squidex/Config/Web/WebExtensions.cs index d924e6db8..a064b9e34 100644 --- a/backend/src/Squidex/Config/Web/WebExtensions.cs +++ b/backend/src/Squidex/Config/Web/WebExtensions.cs @@ -8,20 +8,15 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Net; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Diagnostics.HealthChecks; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.HttpOverrides; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Diagnostics.HealthChecks; -using Microsoft.Extensions.Options; using Microsoft.Net.Http.Headers; using Squidex.Infrastructure.Json; using Squidex.Pipeline.Robots; -using Squidex.Web; using Squidex.Web.Pipeline; namespace Squidex.Config.Web @@ -130,52 +125,5 @@ namespace Squidex.Config.Web .AllowAnyMethod() .AllowAnyHeader()); } - - public static void UseSquidexForwardingRules(this IApplicationBuilder app, IConfiguration config) - { - var urlsOptions = app.ApplicationServices.GetRequiredService>().Value; - - if (urlsOptions.EnableForwardHeaders) - { - var options = new ForwardedHeadersOptions - { - AllowedHosts = new List - { - new Uri(urlsOptions.BaseUrl).Host - }, - ForwardedHeaders = ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedHost, - ForwardLimit = null, - RequireHeaderSymmetry = false - }; - - options.KnownNetworks.Clear(); - options.KnownProxies.Clear(); - - if (urlsOptions.KnownProxies != null) - { - foreach (var proxy in urlsOptions.KnownProxies) - { - if (IPAddress.TryParse(proxy, out var address)) - { - options.KnownProxies.Add(address); - } - } - } - - app.UseForwardedHeaders(options); - } - - app.UseMiddleware(); - - if (urlsOptions.EnforceHost) - { - app.UseHostFiltering(); - } - - if (urlsOptions.EnforceHTTPS) - { - app.UseHttpsRedirection(); - } - } } } diff --git a/backend/src/Squidex/Config/Web/WebServices.cs b/backend/src/Squidex/Config/Web/WebServices.cs index 3502c48a2..825ee67b6 100644 --- a/backend/src/Squidex/Config/Web/WebServices.cs +++ b/backend/src/Squidex/Config/Web/WebServices.cs @@ -5,8 +5,6 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System; -using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Infrastructure; @@ -35,6 +33,9 @@ namespace Squidex.Config.Web T.Setup(translator); + services.AddDefaultWebServices(config); + services.AddDefaultForwardRules(); + services.AddSingletonAs(c => new ExposedValues(c.GetRequiredService>().Value, config, typeof(WebServices).Assembly)) .AsSelf(); @@ -102,29 +103,6 @@ namespace Squidex.Config.Web .AddRazorRuntimeCompilation() .AddSquidexPlugins(config) .AddSquidexSerializers(); - - var urlsOptions = config.GetSection("urls").Get(); - - var host = urlsOptions.BuildHost(); - - if (urlsOptions.EnforceHost) - { - services.AddHostFiltering(options => - { - options.AllowEmptyHosts = true; - options.AllowedHosts.Add(host.Host); - - options.IncludeFailureMessage = false; - }); - } - - if (urlsOptions.EnforceHTTPS && !string.Equals(host.Host, "localhost", StringComparison.OrdinalIgnoreCase)) - { - services.AddHttpsRedirection(options => - { - options.HttpsPort = urlsOptions.HttpsPort; - }); - } } } } diff --git a/backend/src/Squidex/Program.cs b/backend/src/Squidex/Program.cs index a1632056c..f7953bb16 100644 --- a/backend/src/Squidex/Program.cs +++ b/backend/src/Squidex/Program.cs @@ -9,7 +9,6 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using Squidex.Areas.IdentityServer.Config; using Squidex.Config.Domain; using Squidex.Config.Orleans; using Squidex.Config.Startup; @@ -39,10 +38,7 @@ namespace Squidex services.AddHostedService(); // Step 1: Initialize all services. - services.AddHostedService(); - - // Step 2: Create admin user. - services.AddHostedService(); + services.AddInitializer(); }) .UseOrleans((context, builder) => { @@ -58,7 +54,7 @@ namespace Squidex services.AddHostedService(); // Step 6: Start background processes. - services.AddHostedService(); + services.AddBackgroundProcesses(); }) .ConfigureWebHostDefaults(builder => { diff --git a/backend/src/Squidex/Squidex.csproj b/backend/src/Squidex/Squidex.csproj index f518ae485..caa02321f 100644 --- a/backend/src/Squidex/Squidex.csproj +++ b/backend/src/Squidex/Squidex.csproj @@ -44,27 +44,28 @@ - + - + - + - - + + + diff --git a/backend/src/Squidex/Startup.cs b/backend/src/Squidex/Startup.cs index e9f0210d7..585ecee2e 100644 --- a/backend/src/Squidex/Startup.cs +++ b/backend/src/Squidex/Startup.cs @@ -74,7 +74,8 @@ namespace Squidex { app.UseCookiePolicy(); - app.UseSquidexForwardingRules(config); + app.UseDefaultForwardRules(); + app.UseSquidexHealthCheck(); app.UseSquidexRobotsTxt(); app.UseSquidexTracking(); diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppClientsTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppClientsTests.cs index cc3ba649a..fd8df1fa4 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppClientsTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppClientsTests.cs @@ -35,11 +35,11 @@ namespace Squidex.Domain.Apps.Core.Model.Apps } [Fact] - public void Should_throw_exception_if_assigning_client_with_same_id() + public void Should_do_nothing_if_assigning_client_with_same_id() { var clients_1 = clients_0.Add("2", "my-secret"); - Assert.Throws(() => clients_1.Add("2", "my-secret")); + Assert.Same(clients_0, clients_1); } [Fact] diff --git a/backend/tests/Squidex.Web.Tests/Pipeline/CleanupHostMiddlewareTests.cs b/backend/tests/Squidex.Web.Tests/Pipeline/CleanupHostMiddlewareTests.cs deleted file mode 100644 index 8bb6f7005..000000000 --- a/backend/tests/Squidex.Web.Tests/Pipeline/CleanupHostMiddlewareTests.cs +++ /dev/null @@ -1,75 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Xunit; - -#pragma warning disable RECS0092 // Convert field to readonly - -namespace Squidex.Web.Pipeline -{ - public class CleanupHostMiddlewareTests - { - private readonly CleanupHostMiddleware sut; - private bool isNextCalled; - - public CleanupHostMiddlewareTests() - { - Task Next(HttpContext context) - { - isNextCalled = true; - - return Task.CompletedTask; - } - - sut = new CleanupHostMiddleware(Next); - } - - [Fact] - public async Task Should_cleanup_host_if_https_schema_contains_default_port() - { - var httpContext = new DefaultHttpContext(); - - httpContext.Request.Scheme = "https"; - httpContext.Request.Host = new HostString("host", 443); - - await sut.InvokeAsync(httpContext); - - Assert.Null(httpContext.Request.Host.Port); - Assert.True(isNextCalled); - } - - [Fact] - public async Task Should_cleanup_host_if_http_schema_contains_default_port() - { - var httpContext = new DefaultHttpContext(); - - httpContext.Request.Scheme = "http"; - httpContext.Request.Host = new HostString("host", 80); - - await sut.InvokeAsync(httpContext); - - Assert.Null(httpContext.Request.Host.Port); - Assert.True(isNextCalled); - } - - [Fact] - public async Task Should_not_cleanup_host_if_http_schema_contains_other_port() - { - var httpContext = new DefaultHttpContext(); - - httpContext.Request.Scheme = "http"; - httpContext.Request.Host = new HostString("host", 8080); - - await sut.InvokeAsync(httpContext); - - Assert.Equal(8080, httpContext.Request.Host.Port); - Assert.True(isNextCalled); - } - } -} \ No newline at end of file diff --git a/backend/tests/Squidex.Web.Tests/UrlsOptionsTests.cs b/backend/tests/Squidex.Web.Tests/UrlsOptionsTests.cs deleted file mode 100644 index 8051c202b..000000000 --- a/backend/tests/Squidex.Web.Tests/UrlsOptionsTests.cs +++ /dev/null @@ -1,65 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using Xunit; - -namespace Squidex.Web -{ - public sealed class UrlsOptionsTests - { - private readonly UrlsOptions sut = new UrlsOptions - { - BaseUrl = "http://localhost" - }; - - [Theory] - [InlineData("/url")] - [InlineData("/url/")] - [InlineData("url")] - public void Should_build_url_with_leading_slash(string path) - { - var url = sut.BuildUrl(path); - - Assert.Equal("http://localhost/url/", url); - } - - [Theory] - [InlineData("/url")] - [InlineData("/url/")] - [InlineData("url")] - public void Should_build_url_without_leading_slash(string path) - { - var url = sut.BuildUrl(path, false); - - Assert.Equal("http://localhost/url", url); - } - - [Fact] - public void Should_allow_same_host() - { - Assert.True(sut.IsAllowedHost("http://localhost")); - } - - [Fact] - public void Should_allow_https_port() - { - Assert.True(sut.IsAllowedHost("https://localhost")); - } - - [Fact] - public void Should_not_allow_other_host() - { - Assert.False(sut.IsAllowedHost("https://other:5000")); - } - - [Fact] - public void Should_not_allow_same_host_with_other_port() - { - Assert.False(sut.IsAllowedHost("https://localhost:3000")); - } - } -}