Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
31f0ad4490 | ||
|
|
b6307fb359 | ||
|
|
d37b5a2edb |
@@ -12,7 +12,6 @@ permissions:
|
||||
|
||||
jobs:
|
||||
reconcile:
|
||||
if: github.repository == 'umbraco/Umbraco-CMS'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Reconcile release/* labels → discussions
|
||||
|
||||
@@ -107,7 +107,6 @@ trace.zip
|
||||
/src/Umbraco.Web.UI/appsettings-schema.json
|
||||
/src/Umbraco.Web.UI/appsettings-schema.*.json
|
||||
/src/Umbraco.Web.UI/umbraco-package-schema.json
|
||||
/src/Umbraco.Web.UI.Client/umbraco-package-schema.json
|
||||
/tests/Umbraco.Tests.Integration/appsettings-schema.json
|
||||
/tests/Umbraco.Tests.Integration/appsettings-schema.*.json
|
||||
/tests/Umbraco.Tests.Integration/umbraco-package-schema.json
|
||||
|
||||
+23
-23
@@ -13,27 +13,27 @@
|
||||
</ItemGroup>
|
||||
<!-- Microsoft packages -->
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="10.0.2" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="10.0.1" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" />
|
||||
<PackageVersion Include="Microsoft.Data.Sqlite" Version="10.0.2" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.2" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="10.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="10.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="10.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.FileProviders.Embedded" Version="10.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.FileProviders.Physical" Version="10.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Http" Version="10.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Identity.Core" Version="10.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Identity.Stores" Version="10.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging" Version="10.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Options" Version="10.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="10.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Options.DataAnnotations" Version="10.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Hybrid" Version="10.2.0" />
|
||||
<PackageVersion Include="Microsoft.Data.Sqlite" Version="10.0.1" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.1" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="10.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="10.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="10.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.FileProviders.Embedded" Version="10.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.FileProviders.Physical" Version="10.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Http" Version="10.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Identity.Core" Version="10.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Identity.Stores" Version="10.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging" Version="10.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Options" Version="10.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="10.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Options.DataAnnotations" Version="10.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Hybrid" Version="10.1.0" />
|
||||
<PackageVersion Include="System.Linq.Async" Version="7.0.0" />
|
||||
</ItemGroup>
|
||||
<!-- Umbraco packages -->
|
||||
@@ -42,8 +42,8 @@
|
||||
</ItemGroup>
|
||||
<!-- Third-party packages -->
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="Asp.Versioning.Mvc" Version="8.1.1" />
|
||||
<PackageVersion Include="Asp.Versioning.Mvc.ApiExplorer" Version="8.1.1" />
|
||||
<PackageVersion Include="Asp.Versioning.Mvc" Version="8.1.0" />
|
||||
<PackageVersion Include="Asp.Versioning.Mvc.ApiExplorer" Version="8.1.0" />
|
||||
<PackageVersion Include="Dazinator.Extensions.FileProviders" Version="2.0.0" />
|
||||
<PackageVersion Include="Examine" Version="3.7.1" />
|
||||
<PackageVersion Include="Examine.Core" Version="3.7.1" />
|
||||
@@ -76,7 +76,7 @@
|
||||
<PackageVersion Include="Serilog.Sinks.Map" Version="2.0.0" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.12" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp.Web" Version="3.2.0" />
|
||||
<PackageVersion Include="Swashbuckle.AspNetCore" Version="10.1.0" />
|
||||
<PackageVersion Include="Swashbuckle.AspNetCore" Version="10.0.1" />
|
||||
</ItemGroup>
|
||||
<!-- Transitive pinned versions (only required because our direct dependencies have vulnerable versions of transitive dependencies) -->
|
||||
<ItemGroup>
|
||||
|
||||
+71
-77
@@ -201,56 +201,53 @@ stages:
|
||||
umbracoMajorVersion: $[ stageDependencies.Build.A.outputs['build.NBGV_VersionMajor'] ]
|
||||
jobs:
|
||||
# C# API Reference
|
||||
# This job is disabled due to failures in correct generation of the docs when running on the pipeline.
|
||||
# API docs are currently manually generated locally and uploaded to blob storage.
|
||||
# TODO: Investigate and resolve this issue such that doc generation and upload can be restored to the pipeline.
|
||||
# - job:
|
||||
# displayName: Build C# API Reference
|
||||
# pool:
|
||||
# vmImage: "windows-latest"
|
||||
# steps:
|
||||
# - task: UseDotNet@2
|
||||
# displayName: Use .NET SDK from global.json
|
||||
# inputs:
|
||||
# useGlobalJson: true
|
||||
# - task: PowerShell@2
|
||||
# displayName: Install DocFX
|
||||
# inputs:
|
||||
# targetType: inline
|
||||
# script: |
|
||||
# choco install docfx --version=2.59.4 -y
|
||||
# if ($lastexitcode -ne 0){
|
||||
# throw ("Error installing DocFX")
|
||||
# }
|
||||
# - task: PowerShell@2
|
||||
# displayName: Generate metadata
|
||||
# inputs:
|
||||
# targetType: inline
|
||||
# script: |
|
||||
# docfx metadata "$(Build.SourcesDirectory)/build/csharp-docs/docfx.json"
|
||||
# if ($lastexitcode -ne 0){
|
||||
# throw ("Error generating metadata.")
|
||||
# }
|
||||
# - task: PowerShell@2
|
||||
# displayName: Generate documentation
|
||||
# inputs:
|
||||
# targetType: inline
|
||||
# script: |
|
||||
# docfx build "$(Build.SourcesDirectory)/build/csharp-docs/docfx.json"
|
||||
# if ($lastexitcode -ne 0){
|
||||
# throw ("Error generating documentation.")
|
||||
# }
|
||||
# - task: ArchiveFiles@2
|
||||
# displayName: Archive C# Docs
|
||||
# inputs:
|
||||
# rootFolderOrFile: $(Build.SourcesDirectory)/build/csharp-docs/_site
|
||||
# includeRootFolder: false
|
||||
# archiveFile: $(Build.ArtifactStagingDirectory)/csharp-docs.zip
|
||||
# - task: PublishPipelineArtifact@1
|
||||
# displayName: Publish C# Docs
|
||||
# inputs:
|
||||
# targetPath: $(Build.ArtifactStagingDirectory)/csharp-docs.zip
|
||||
# artifact: csharp-docs
|
||||
- job:
|
||||
displayName: Build C# API Reference
|
||||
pool:
|
||||
vmImage: "windows-latest"
|
||||
steps:
|
||||
- task: UseDotNet@2
|
||||
displayName: Use .NET SDK from global.json
|
||||
inputs:
|
||||
useGlobalJson: true
|
||||
- task: PowerShell@2
|
||||
displayName: Install DocFX
|
||||
inputs:
|
||||
targetType: inline
|
||||
script: |
|
||||
dotnet tool install -g docfx
|
||||
if ($lastexitcode -ne 0){
|
||||
throw ("Error installing DocFX")
|
||||
}
|
||||
- task: PowerShell@2
|
||||
displayName: Generate metadata
|
||||
inputs:
|
||||
targetType: inline
|
||||
script: |
|
||||
docfx metadata "$(Build.SourcesDirectory)/build/csharp-docs/docfx.json"
|
||||
if ($lastexitcode -ne 0){
|
||||
throw ("Error generating metadata.")
|
||||
}
|
||||
- task: PowerShell@2
|
||||
displayName: Generate documentation
|
||||
inputs:
|
||||
targetType: inline
|
||||
script: |
|
||||
docfx build "$(Build.SourcesDirectory)/build/csharp-docs/docfx.json"
|
||||
if ($lastexitcode -ne 0){
|
||||
throw ("Error generating documentation.")
|
||||
}
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Archive C# Docs
|
||||
inputs:
|
||||
rootFolderOrFile: $(Build.SourcesDirectory)/build/csharp-docs/_site
|
||||
includeRootFolder: false
|
||||
archiveFile: $(Build.ArtifactStagingDirectory)/csharp-docs.zip
|
||||
- task: PublishPipelineArtifact@1
|
||||
displayName: Publish C# Docs
|
||||
inputs:
|
||||
targetPath: $(Build.ArtifactStagingDirectory)/csharp-docs.zip
|
||||
artifact: csharp-docs
|
||||
|
||||
# js API Reference
|
||||
- job:
|
||||
@@ -885,33 +882,30 @@ stages:
|
||||
- Deploy_NuGet
|
||||
condition: and(succeeded(), or(eq(dependencies.Build.outputs['A.build.NBGV_PublicRelease'], 'True'), ${{parameters.uploadApiDocs}}))
|
||||
jobs:
|
||||
# This job is disabled due to failures in correct generation of the docs when running on the pipeline.
|
||||
# API docs are currently manually generated locally and uploaded to blob storage.
|
||||
# TODO: Investigate and resolve this issue such that doc generation and upload can be restored to the pipeline.
|
||||
# - job:
|
||||
# displayName: Upload C# Docs
|
||||
# steps:
|
||||
# - checkout: none
|
||||
# - task: DownloadPipelineArtifact@2
|
||||
# displayName: Download artifact
|
||||
# inputs:
|
||||
# artifact: csharp-docs
|
||||
# path: $(Build.SourcesDirectory)
|
||||
# - task: ExtractFiles@1
|
||||
# inputs:
|
||||
# archiveFilePatterns: $(Build.SourcesDirectory)/csharp-docs.zip
|
||||
# destinationFolder: $(Build.ArtifactStagingDirectory)/csharp-docs
|
||||
# overwriteExistingFiles: true
|
||||
# - task: AzureFileCopy@4
|
||||
# displayName: "Copy C# Docs to blob storage"
|
||||
# inputs:
|
||||
# SourcePath: "$(Build.ArtifactStagingDirectory)/csharp-docs/*"
|
||||
# azureSubscription: umbraco-storage
|
||||
# Destination: AzureBlob
|
||||
# storage: umbracoapidocs
|
||||
# ContainerName: "$web"
|
||||
# BlobPrefix: v$(umbracoMajorVersion)/csharp
|
||||
# CleanTargetBeforeCopy: true
|
||||
- job:
|
||||
displayName: Upload C# Docs
|
||||
steps:
|
||||
- checkout: none
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: Download artifact
|
||||
inputs:
|
||||
artifact: csharp-docs
|
||||
path: $(Build.SourcesDirectory)
|
||||
- task: ExtractFiles@1
|
||||
inputs:
|
||||
archiveFilePatterns: $(Build.SourcesDirectory)/csharp-docs.zip
|
||||
destinationFolder: $(Build.ArtifactStagingDirectory)/csharp-docs
|
||||
overwriteExistingFiles: true
|
||||
- task: AzureFileCopy@4
|
||||
displayName: "Copy C# Docs to blob storage"
|
||||
inputs:
|
||||
SourcePath: "$(Build.ArtifactStagingDirectory)/csharp-docs/*"
|
||||
azureSubscription: umbraco-storage
|
||||
Destination: AzureBlob
|
||||
storage: umbracoapidocs
|
||||
ContainerName: "$web"
|
||||
BlobPrefix: v$(umbracoMajorVersion)/csharp
|
||||
CleanTargetBeforeCopy: true
|
||||
- job:
|
||||
displayName: Upload Storybook
|
||||
steps:
|
||||
|
||||
+1
-8
@@ -1,17 +1,10 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Umbraco.Cms.Core.DeliveryApi;
|
||||
|
||||
namespace Umbraco.Cms.Api.Common.Accessors;
|
||||
|
||||
/// <summary>
|
||||
/// Provides access to the <see cref="IOutputExpansionStrategy"/> for the current HTTP request context.
|
||||
/// </summary>
|
||||
public sealed class RequestContextOutputExpansionStrategyAccessor : RequestContextServiceAccessorBase<IOutputExpansionStrategy>, IOutputExpansionStrategyAccessor
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequestContextOutputExpansionStrategyAccessor"/> class.
|
||||
/// </summary>
|
||||
/// <param name="httpContextAccessor">The HTTP context accessor.</param>
|
||||
public RequestContextOutputExpansionStrategyAccessor(IHttpContextAccessor httpContextAccessor)
|
||||
: base(httpContextAccessor)
|
||||
{
|
||||
|
||||
@@ -1,30 +1,17 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Umbraco.Cms.Api.Common.Accessors;
|
||||
|
||||
/// <summary>
|
||||
/// Base class for accessing request-scoped services from the current HTTP context.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of service to access.</typeparam>
|
||||
public abstract class RequestContextServiceAccessorBase<T>
|
||||
where T : class
|
||||
{
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequestContextServiceAccessorBase{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="httpContextAccessor">The HTTP context accessor.</param>
|
||||
protected RequestContextServiceAccessorBase(IHttpContextAccessor httpContextAccessor)
|
||||
=> _httpContextAccessor = httpContextAccessor;
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to retrieve the service from the current HTTP context's request services.
|
||||
/// </summary>
|
||||
/// <param name="requestStartNodeService">When this method returns, contains the service instance if found; otherwise, <c>null</c>.</param>
|
||||
/// <returns><c>true</c> if the service was found; otherwise, <c>false</c>.</returns>
|
||||
public bool TryGetValue([NotNullWhen(true)] out T? requestStartNodeService)
|
||||
{
|
||||
requestStartNodeService = _httpContextAccessor.HttpContext?.RequestServices.GetService<T>();
|
||||
|
||||
@@ -1,19 +1,9 @@
|
||||
namespace Umbraco.Cms.Api.Common.Attributes;
|
||||
|
||||
/// <summary>
|
||||
/// Attribute used to map a class to a specific API for OpenAPI documentation generation.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
|
||||
public class MapToApiAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MapToApiAttribute"/> class.
|
||||
/// </summary>
|
||||
/// <param name="apiName">The name of the API to map to.</param>
|
||||
public MapToApiAttribute(string apiName) => ApiName = apiName;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the API this class is mapped to.
|
||||
/// </summary>
|
||||
public string ApiName { get; }
|
||||
}
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Api.Common.Builders;
|
||||
|
||||
/// <summary>
|
||||
/// A fluent builder for creating RFC 7807 <see cref="ProblemDetails"/> responses.
|
||||
/// </summary>
|
||||
public class ProblemDetailsBuilder
|
||||
{
|
||||
private string? _title;
|
||||
@@ -15,45 +12,24 @@ public class ProblemDetailsBuilder
|
||||
private string? _operationStatus;
|
||||
private IDictionary<string, object>? _extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the title of the problem details.
|
||||
/// </summary>
|
||||
/// <param name="title">A short, human-readable summary of the problem type.</param>
|
||||
/// <returns>The current builder instance for method chaining.</returns>
|
||||
public ProblemDetailsBuilder WithTitle(string title)
|
||||
{
|
||||
_title = title;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the detail of the problem details.
|
||||
/// </summary>
|
||||
/// <param name="detail">A human-readable explanation specific to this occurrence of the problem.</param>
|
||||
/// <returns>The current builder instance for method chaining.</returns>
|
||||
public ProblemDetailsBuilder WithDetail(string detail)
|
||||
{
|
||||
_detail = detail;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the type of the problem details.
|
||||
/// </summary>
|
||||
/// <param name="type">A URI reference that identifies the problem type.</param>
|
||||
/// <returns>The current builder instance for method chaining.</returns>
|
||||
public ProblemDetailsBuilder WithType(string type)
|
||||
{
|
||||
_type = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the operation status from an enum value.
|
||||
/// </summary>
|
||||
/// <typeparam name="TEnum">The enum type representing operation statuses.</typeparam>
|
||||
/// <param name="operationStatus">The operation status enum value.</param>
|
||||
/// <returns>The current builder instance for method chaining.</returns>
|
||||
public ProblemDetailsBuilder WithOperationStatus<TEnum>(TEnum operationStatus)
|
||||
where TEnum : Enum
|
||||
{
|
||||
@@ -61,20 +37,9 @@ public class ProblemDetailsBuilder
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds request model validation errors to the problem details.
|
||||
/// </summary>
|
||||
/// <param name="errors">A dictionary of field names to error messages.</param>
|
||||
/// <returns>The current builder instance for method chaining.</returns>
|
||||
public ProblemDetailsBuilder WithRequestModelErrors(IDictionary<string, string[]> errors)
|
||||
=> WithExtension(nameof(HttpValidationProblemDetails.Errors).ToFirstLowerInvariant(), errors);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a custom extension to the problem details.
|
||||
/// </summary>
|
||||
/// <param name="key">The extension key.</param>
|
||||
/// <param name="value">The extension value.</param>
|
||||
/// <returns>The current builder instance for method chaining.</returns>
|
||||
public ProblemDetailsBuilder WithExtension(string key, object value)
|
||||
{
|
||||
_extensions ??= new Dictionary<string, object>();
|
||||
@@ -82,10 +47,6 @@ public class ProblemDetailsBuilder
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds the <see cref="ProblemDetails"/> instance with all configured values.
|
||||
/// </summary>
|
||||
/// <returns>A new <see cref="ProblemDetails"/> instance.</returns>
|
||||
public ProblemDetails Build()
|
||||
{
|
||||
var problemDetails = new ProblemDetails
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Umbraco.Cms.Api.Common.Configuration;
|
||||
|
||||
/// <summary>
|
||||
/// Configures <see cref="ApiBehaviorOptions"/> for Umbraco APIs.
|
||||
/// </summary>
|
||||
public class ConfigureApiBehaviorOptions : IConfigureOptions<ApiBehaviorOptions>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public void Configure(ApiBehaviorOptions options) =>
|
||||
// disable ProblemDetails as default result type for every non-success response (i.e. 404)
|
||||
// - see https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.apibehavioroptions.suppressmapclienterrors
|
||||
|
||||
@@ -5,21 +5,12 @@ using Umbraco.Cms.Api.Common.Json;
|
||||
|
||||
namespace Umbraco.Cms.Api.Common.Configuration;
|
||||
|
||||
/// <summary>
|
||||
/// Configures <see cref="MvcOptions"/> with named JSON input and output formatters for Umbraco APIs.
|
||||
/// </summary>
|
||||
public class ConfigureMvcJsonOptions : IConfigureOptions<MvcOptions>
|
||||
{
|
||||
private readonly string _jsonOptionsName;
|
||||
private readonly IOptionsMonitor<JsonOptions> _jsonOptions;
|
||||
private readonly ILoggerFactory _loggerFactory;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConfigureMvcJsonOptions"/> class.
|
||||
/// </summary>
|
||||
/// <param name="jsonOptionsName">The name of the JSON options configuration to use.</param>
|
||||
/// <param name="jsonOptions">The JSON options monitor.</param>
|
||||
/// <param name="loggerFactory">The logger factory.</param>
|
||||
public ConfigureMvcJsonOptions(
|
||||
string jsonOptionsName,
|
||||
IOptionsMonitor<JsonOptions> jsonOptions,
|
||||
@@ -30,7 +21,6 @@ public class ConfigureMvcJsonOptions : IConfigureOptions<MvcOptions>
|
||||
_loggerFactory = loggerFactory;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Configure(MvcOptions options)
|
||||
{
|
||||
JsonOptions jsonOptions = _jsonOptions.Get(_jsonOptionsName);
|
||||
|
||||
@@ -4,24 +4,12 @@ using Umbraco.Cms.Core.Configuration.Models;
|
||||
|
||||
namespace Umbraco.Cms.Api.Common.Configuration;
|
||||
|
||||
/// <summary>
|
||||
/// Configures OpenIddict server options for Umbraco authentication.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Disables transport security requirement when HTTPS is not configured in global settings.
|
||||
/// Warning: This should only be used in development environments.
|
||||
/// </remarks>
|
||||
internal sealed class ConfigureOpenIddict : IConfigureOptions<OpenIddictServerAspNetCoreOptions>
|
||||
{
|
||||
private readonly IOptions<GlobalSettings> _globalSettings;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConfigureOpenIddict"/> class.
|
||||
/// </summary>
|
||||
/// <param name="globalSettings">The global settings options.</param>
|
||||
public ConfigureOpenIddict(IOptions<GlobalSettings> globalSettings) => _globalSettings = globalSettings;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Configure(OpenIddictServerAspNetCoreOptions options)
|
||||
=> options.DisableTransportSecurityRequirement = _globalSettings.Value.UseHttps is false;
|
||||
}
|
||||
|
||||
@@ -8,9 +8,6 @@ using Umbraco.Cms.Core.DependencyInjection;
|
||||
|
||||
namespace Umbraco.Cms.Api.Common.Configuration;
|
||||
|
||||
/// <summary>
|
||||
/// Configures Swagger/OpenAPI generation options for Umbraco APIs.
|
||||
/// </summary>
|
||||
public class ConfigureUmbracoSwaggerGenOptions : IConfigureOptions<SwaggerGenOptions>
|
||||
{
|
||||
private readonly IOperationIdSelector _operationIdSelector;
|
||||
@@ -18,13 +15,6 @@ public class ConfigureUmbracoSwaggerGenOptions : IConfigureOptions<SwaggerGenOpt
|
||||
private readonly ISubTypesSelector _subTypesSelector;
|
||||
private readonly IDocumentInclusionSelector _documentInclusionSelector;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConfigureUmbracoSwaggerGenOptions"/> class.
|
||||
/// </summary>
|
||||
/// <param name="operationIdSelector">The operation ID selector.</param>
|
||||
/// <param name="schemaIdSelector">The schema ID selector.</param>
|
||||
/// <param name="subTypesSelector">The sub-types selector for polymorphism support.</param>
|
||||
/// <param name="documentInclusionSelector">The document inclusion selector.</param>
|
||||
public ConfigureUmbracoSwaggerGenOptions(
|
||||
IOperationIdSelector operationIdSelector,
|
||||
ISchemaIdSelector schemaIdSelector,
|
||||
@@ -37,12 +27,6 @@ public class ConfigureUmbracoSwaggerGenOptions : IConfigureOptions<SwaggerGenOpt
|
||||
_documentInclusionSelector = documentInclusionSelector;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConfigureUmbracoSwaggerGenOptions"/> class.
|
||||
/// </summary>
|
||||
/// <param name="operationIdSelector">The operation ID selector.</param>
|
||||
/// <param name="schemaIdSelector">The schema ID selector.</param>
|
||||
/// <param name="subTypesSelector">The sub-types selector for polymorphism support.</param>
|
||||
[Obsolete("Please use the constructor with all parameters. Scheduled for removal in Umbraco 19.")]
|
||||
public ConfigureUmbracoSwaggerGenOptions(
|
||||
IOperationIdSelector operationIdSelector,
|
||||
@@ -56,7 +40,6 @@ public class ConfigureUmbracoSwaggerGenOptions : IConfigureOptions<SwaggerGenOpt
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Configure(SwaggerGenOptions swaggerGenOptions)
|
||||
{
|
||||
swaggerGenOptions.SwaggerDoc(
|
||||
@@ -81,14 +64,7 @@ public class ConfigureUmbracoSwaggerGenOptions : IConfigureOptions<SwaggerGenOpt
|
||||
swaggerGenOptions.SupportNonNullableReferenceTypes();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a sort key for API actions.
|
||||
/// </summary>
|
||||
/// <param name="apiDesc">The API description.</param>
|
||||
/// <returns>A string used to sort API operations in the documentation.</returns>
|
||||
/// <remarks>
|
||||
/// See https://github.com/domaindrivendev/Swashbuckle.AspNetCore#change-operation-sort-order-eg-for-ui-sorting.
|
||||
/// </remarks>
|
||||
// see https://github.com/domaindrivendev/Swashbuckle.AspNetCore#change-operation-sort-order-eg-for-ui-sorting
|
||||
private static string ActionOrderBy(ApiDescription apiDesc)
|
||||
=> $"{apiDesc.GroupName}_{apiDesc.ActionDescriptor.AttributeRouteInfo?.Template ?? apiDesc.ActionDescriptor.RouteValues["controller"]}_{(apiDesc.ActionDescriptor.RouteValues.TryGetValue("action", out var action) ? action : null)}_{apiDesc.HttpMethod}";
|
||||
}
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
namespace Umbraco.Cms.Api.Common.Configuration;
|
||||
|
||||
/// <summary>
|
||||
/// Contains default configuration values for the API.
|
||||
/// </summary>
|
||||
internal static class DefaultApiConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// The default API name used for endpoints not assigned to a specific API.
|
||||
/// </summary>
|
||||
public const string ApiName = "default";
|
||||
}
|
||||
|
||||
@@ -13,14 +13,6 @@ using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Api.Common.DependencyInjection;
|
||||
|
||||
/// <summary>
|
||||
/// Handles secure storage of back-office authentication tokens in HTTP-only cookies.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This handler intercepts OpenIddict token responses for the back-office client and stores
|
||||
/// access tokens, refresh tokens, and PKCE codes in encrypted HTTP-only cookies. The tokens
|
||||
/// are redacted from the response to prevent client-side JavaScript access.
|
||||
/// </remarks>
|
||||
internal sealed class HideBackOfficeTokensHandler
|
||||
: IOpenIddictServerHandler<OpenIddictServerEvents.ApplyTokenResponseContext>,
|
||||
IOpenIddictServerHandler<OpenIddictServerEvents.ApplyAuthorizationResponseContext>,
|
||||
@@ -42,13 +34,6 @@ internal sealed class HideBackOfficeTokensHandler
|
||||
private readonly BackOfficeTokenCookieSettings _backOfficeTokenCookieSettings;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HideBackOfficeTokensHandler"/> class.
|
||||
/// </summary>
|
||||
/// <param name="httpContextAccessor">The HTTP context accessor.</param>
|
||||
/// <param name="dataProtectionProvider">The data protection provider for encrypting cookie values.</param>
|
||||
/// <param name="backOfficeTokenCookieSettings">The back-office token cookie settings.</param>
|
||||
/// <param name="globalSettings">The global settings.</param>
|
||||
public HideBackOfficeTokensHandler(
|
||||
IHttpContextAccessor httpContextAccessor,
|
||||
IDataProtectionProvider dataProtectionProvider,
|
||||
@@ -178,7 +163,6 @@ internal sealed class HideBackOfficeTokensHandler
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Handle(UserLogoutSuccessNotification notification)
|
||||
{
|
||||
HttpContext? httpContext = _httpContextAccessor.HttpContext;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
@@ -6,18 +6,8 @@ using Umbraco.Cms.Api.Common.Configuration;
|
||||
|
||||
namespace Umbraco.Cms.Api.Common.DependencyInjection;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for <see cref="IMvcBuilder"/>.
|
||||
/// </summary>
|
||||
public static class MvcBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds named JSON serialization options to the MVC builder.
|
||||
/// </summary>
|
||||
/// <param name="builder">The MVC builder.</param>
|
||||
/// <param name="settingsName">The name for the JSON options configuration.</param>
|
||||
/// <param name="configure">The action to configure the JSON options.</param>
|
||||
/// <returns>The MVC builder for method chaining.</returns>
|
||||
public static IMvcBuilder AddJsonOptions(this IMvcBuilder builder, string settingsName, Action<JsonOptions> configure)
|
||||
{
|
||||
builder.Services.Configure(settingsName, configure);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using OpenIddict.Server;
|
||||
using OpenIddict.Validation;
|
||||
using Umbraco.Cms.Core;
|
||||
@@ -6,23 +6,12 @@ using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Api.Common.DependencyInjection;
|
||||
|
||||
/// <summary>
|
||||
/// Handles OpenIddict request processing to skip handling for non-authentication requests.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This handler prevents OpenIddict from processing every request to the server,
|
||||
/// limiting its scope to back-office and well-known OpenID Connect endpoints.
|
||||
/// </remarks>
|
||||
public class ProcessRequestContextHandler
|
||||
: IOpenIddictServerHandler<OpenIddictServerEvents.ProcessRequestContext>, IOpenIddictValidationHandler<OpenIddictValidationEvents.ProcessRequestContext>
|
||||
{
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
private readonly string[] _pathsToHandle;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ProcessRequestContextHandler"/> class.
|
||||
/// </summary>
|
||||
/// <param name="httpContextAccessor">The HTTP context accessor.</param>
|
||||
public ProcessRequestContextHandler(IHttpContextAccessor httpContextAccessor)
|
||||
{
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
@@ -32,11 +21,6 @@ public class ProcessRequestContextHandler
|
||||
_pathsToHandle = [backOfficePathSegment, "/.well-known/openid-configuration", "/.well-known/jwks"];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles the server process request context event.
|
||||
/// </summary>
|
||||
/// <param name="context">The process request context.</param>
|
||||
/// <returns>A <see cref="ValueTask"/> representing the asynchronous operation.</returns>
|
||||
public ValueTask HandleAsync(OpenIddictServerEvents.ProcessRequestContext context)
|
||||
{
|
||||
if (SkipOpenIddictHandlingForRequest())
|
||||
@@ -47,11 +31,6 @@ public class ProcessRequestContextHandler
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles the validation process request context event.
|
||||
/// </summary>
|
||||
/// <param name="context">The process request context.</param>
|
||||
/// <returns>A <see cref="ValueTask"/> representing the asynchronous operation.</returns>
|
||||
public ValueTask HandleAsync(OpenIddictValidationEvents.ProcessRequestContext context)
|
||||
{
|
||||
if (SkipOpenIddictHandlingForRequest())
|
||||
|
||||
@@ -7,16 +7,8 @@ using Umbraco.Cms.Web.Common.ApplicationBuilder;
|
||||
|
||||
namespace Umbraco.Cms.Api.Common.DependencyInjection;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for <see cref="IUmbracoBuilder"/> to configure API services.
|
||||
/// </summary>
|
||||
public static class UmbracoBuilderApiExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds Umbraco API OpenAPI/Swagger UI services to the builder.
|
||||
/// </summary>
|
||||
/// <param name="builder">The Umbraco builder.</param>
|
||||
/// <returns>The Umbraco builder for method chaining.</returns>
|
||||
public static IUmbracoBuilder AddUmbracoApiOpenApiUI(this IUmbracoBuilder builder)
|
||||
{
|
||||
if (builder.Services.Any(x => !x.IsKeyedService && x.ImplementationType == typeof(OperationIdSelector)))
|
||||
|
||||
@@ -9,26 +9,14 @@ using Umbraco.Cms.Api.Common.Security;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Notifications;
|
||||
using Umbraco.Cms.Infrastructure.BackgroundJobs;
|
||||
using Umbraco.Cms.Infrastructure.BackgroundJobs.Jobs.DistributedJobs;
|
||||
using Umbraco.Cms.Core.Notifications;
|
||||
|
||||
namespace Umbraco.Cms.Api.Common.DependencyInjection;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for <see cref="IUmbracoBuilder"/> to configure authentication services.
|
||||
/// </summary>
|
||||
public static class UmbracoBuilderAuthExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds OpenIddict authentication services for Umbraco APIs.
|
||||
/// </summary>
|
||||
/// <param name="builder">The Umbraco builder.</param>
|
||||
/// <returns>The Umbraco builder for method chaining.</returns>
|
||||
/// <remarks>
|
||||
/// Configures OpenIddict with authorization code flow (with PKCE), client credentials flow,
|
||||
/// reference tokens, and ASP.NET Core Data Protection for token encryption.
|
||||
/// </remarks>
|
||||
public static IUmbracoBuilder AddUmbracoOpenIddict(this IUmbracoBuilder builder)
|
||||
{
|
||||
if (builder.Services.Any(x => !x.IsKeyedService && x.ImplementationType == typeof(OpenIddictCleanupJob)) is false)
|
||||
|
||||
@@ -33,4 +33,4 @@ public static class ActionDescriptorApiCommonExtensions
|
||||
|
||||
return mapToApiAttributes.SingleOrDefault()?.ApiName;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,16 +5,9 @@ using Umbraco.Cms.Api.Common.Configuration;
|
||||
|
||||
namespace Umbraco.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for <see cref="MethodInfo"/> to work with API-related attributes.
|
||||
/// </summary>
|
||||
public static class MethodInfoApiCommonExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the API version values from <see cref="MapToApiVersionAttribute"/> applied to the method.
|
||||
/// </summary>
|
||||
/// <param name="methodInfo">The method info to inspect.</param>
|
||||
/// <returns>A pipe-separated string of API version values.</returns>
|
||||
|
||||
public static string GetMapToApiVersionAttributeValue(this MethodInfo methodInfo)
|
||||
{
|
||||
MapToApiVersionAttribute[] mapToApis = methodInfo.GetCustomAttributes(typeof(MapToApiVersionAttribute), inherit: true).Cast<MapToApiVersionAttribute>().ToArray();
|
||||
@@ -22,11 +15,6 @@ public static class MethodInfoApiCommonExtensions
|
||||
return string.Join("|", mapToApis.SelectMany(x => x.Versions));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the API name from <see cref="MapToApiAttribute"/> applied to the method's declaring type.
|
||||
/// </summary>
|
||||
/// <param name="methodInfo">The method info to inspect.</param>
|
||||
/// <returns>The API name if the attribute is present; otherwise, <c>null</c>.</returns>
|
||||
public static string? GetMapToApiAttributeValue(this MethodInfo methodInfo)
|
||||
{
|
||||
MapToApiAttribute[] mapToApis = (methodInfo.DeclaringType?.GetCustomAttributes(typeof(MapToApiAttribute), inherit: true) ?? Array.Empty<object>()).Cast<MapToApiAttribute>().ToArray();
|
||||
@@ -34,15 +22,6 @@ public static class MethodInfoApiCommonExtensions
|
||||
return mapToApis.SingleOrDefault()?.ApiName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the method's declaring type has a <see cref="MapToApiAttribute"/> with the specified API name.
|
||||
/// </summary>
|
||||
/// <param name="methodInfo">The method info to inspect.</param>
|
||||
/// <param name="apiName">The API name to check for.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the attribute is present and matches the specified API name,
|
||||
/// or if the attribute is not present and the API name matches the default API name; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
public static bool HasMapToApiAttribute(this MethodInfo methodInfo, string apiName)
|
||||
{
|
||||
var value = methodInfo.GetMapToApiAttributeValue();
|
||||
|
||||
@@ -1,19 +1,9 @@
|
||||
namespace Umbraco.Cms.Api.Common.Filters;
|
||||
namespace Umbraco.Cms.Api.Common.Filters;
|
||||
|
||||
/// <summary>
|
||||
/// Attribute used to specify the named JSON serialization options for a controller.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class JsonOptionsNameAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="JsonOptionsNameAttribute"/> class.
|
||||
/// </summary>
|
||||
/// <param name="jsonOptionsName">The name of the JSON options configuration to use.</param>
|
||||
public JsonOptionsNameAttribute(string jsonOptionsName) => JsonOptionsName = jsonOptionsName;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the JSON options configuration.
|
||||
/// </summary>
|
||||
public string JsonOptionsName { get; }
|
||||
}
|
||||
|
||||
@@ -1,18 +1,10 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Umbraco.Cms.Api.Common.Filters;
|
||||
|
||||
namespace Umbraco.Cms.Api.Common.Json;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for <see cref="HttpContext"/> related to JSON serialization.
|
||||
/// </summary>
|
||||
public static class HttpContextJsonExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the named JSON options configuration for the current endpoint.
|
||||
/// </summary>
|
||||
/// <param name="context">The HTTP context.</param>
|
||||
/// <returns>The JSON options name if specified via <see cref="JsonOptionsNameAttribute"/>; otherwise, <c>null</c>.</returns>
|
||||
public static string? CurrentJsonOptionsName(this HttpContext context)
|
||||
=> context.GetEndpoint()?.Metadata.GetMetadata<JsonOptionsNameAttribute>()?.JsonOptionsName;
|
||||
}
|
||||
|
||||
@@ -1,31 +1,20 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Formatters;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Umbraco.Cms.Api.Common.Json;
|
||||
|
||||
/// <summary>
|
||||
/// A JSON input formatter that only processes requests for endpoints with matching named JSON options.
|
||||
/// </summary>
|
||||
internal sealed class NamedSystemTextJsonInputFormatter : SystemTextJsonInputFormatter
|
||||
{
|
||||
private readonly string _jsonOptionsName;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NamedSystemTextJsonInputFormatter"/> class.
|
||||
/// </summary>
|
||||
/// <param name="jsonOptionsName">The name of the JSON options configuration this formatter handles.</param>
|
||||
/// <param name="options">The JSON options.</param>
|
||||
/// <param name="logger">The logger.</param>
|
||||
public NamedSystemTextJsonInputFormatter(string jsonOptionsName, JsonOptions options, ILogger<NamedSystemTextJsonInputFormatter> logger)
|
||||
: base(options, logger) =>
|
||||
_jsonOptionsName = jsonOptionsName;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool CanRead(InputFormatterContext context)
|
||||
=> context.HttpContext.CurrentJsonOptionsName() == _jsonOptionsName && base.CanRead(context);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<InputFormatterResult> ReadAsync(InputFormatterContext context)
|
||||
{
|
||||
try
|
||||
|
||||
@@ -1,26 +1,17 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json;
|
||||
using Microsoft.AspNetCore.Mvc.Formatters;
|
||||
|
||||
namespace Umbraco.Cms.Api.Common.Json;
|
||||
|
||||
/// <summary>
|
||||
/// A JSON output formatter that only processes responses for endpoints with matching named JSON options.
|
||||
/// </summary>
|
||||
internal sealed class NamedSystemTextJsonOutputFormatter : SystemTextJsonOutputFormatter
|
||||
{
|
||||
private readonly string _jsonOptionsName;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NamedSystemTextJsonOutputFormatter"/> class.
|
||||
/// </summary>
|
||||
/// <param name="jsonOptionsName">The name of the JSON options configuration this formatter handles.</param>
|
||||
/// <param name="jsonSerializerOptions">The JSON serializer options.</param>
|
||||
public NamedSystemTextJsonOutputFormatter(string jsonOptionsName, JsonSerializerOptions jsonSerializerOptions) : base(jsonSerializerOptions)
|
||||
{
|
||||
_jsonOptionsName = jsonOptionsName;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool CanWriteResult(OutputFormatterCanWriteContext context)
|
||||
=> context.HttpContext.CurrentJsonOptionsName() == _jsonOptionsName && base.CanWriteResult(context);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Routing;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
@@ -16,13 +16,6 @@ public sealed class EmptyCreatedAtActionResult : ActionResult
|
||||
private readonly object _routeValues;
|
||||
private readonly string _resourceIdentifier;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="EmptyCreatedAtActionResult"/> class.
|
||||
/// </summary>
|
||||
/// <param name="actionName">The name of the action to generate the URL for.</param>
|
||||
/// <param name="controllerName">The name of the controller to generate the URL for.</param>
|
||||
/// <param name="routeValues">The route values to use for URL generation.</param>
|
||||
/// <param name="resourceIdentifier">The identifier of the created resource.</param>
|
||||
public EmptyCreatedAtActionResult(string actionName, string controllerName, object routeValues, string resourceIdentifier)
|
||||
{
|
||||
_actionName = actionName;
|
||||
@@ -31,7 +24,6 @@ public sealed class EmptyCreatedAtActionResult : ActionResult
|
||||
_resourceIdentifier = resourceIdentifier;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void ExecuteResult(ActionContext context)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(context);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Reflection;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text.Json.Nodes;
|
||||
using Microsoft.OpenApi;
|
||||
@@ -6,16 +6,8 @@ using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
|
||||
namespace Umbraco.Cms.Api.Common.OpenApi;
|
||||
|
||||
/// <summary>
|
||||
/// A schema filter that converts enum schemas to string type with enum member names.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This filter ensures enums are represented as strings in the OpenAPI schema,
|
||||
/// using <see cref="EnumMemberAttribute"/> values when available.
|
||||
/// </remarks>
|
||||
public class EnumSchemaFilter : ISchemaFilter
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public void Apply(IOpenApiSchema model, SchemaFilterContext context)
|
||||
{
|
||||
if (model is not OpenApiSchema schema || context.Type.IsEnum is false)
|
||||
|
||||
@@ -2,22 +2,9 @@ using Microsoft.AspNetCore.Mvc.ApiExplorer;
|
||||
|
||||
namespace Umbraco.Cms.Api.Common.OpenApi;
|
||||
|
||||
/// <summary>
|
||||
/// Defines a handler for generating OpenAPI operation IDs.
|
||||
/// </summary>
|
||||
public interface IOperationIdHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines whether this handler can generate an operation ID for the specified API description.
|
||||
/// </summary>
|
||||
/// <param name="apiDescription">The API description to check.</param>
|
||||
/// <returns><c>true</c> if this handler can handle the API description; otherwise, <c>false</c>.</returns>
|
||||
bool CanHandle(ApiDescription apiDescription);
|
||||
bool CanHandle(ApiDescription apiDescription);
|
||||
|
||||
/// <summary>
|
||||
/// Generates an operation ID for the specified API description.
|
||||
/// </summary>
|
||||
/// <param name="apiDescription">The API description to generate an operation ID for.</param>
|
||||
/// <returns>The generated operation ID.</returns>
|
||||
string Handle(ApiDescription apiDescription);
|
||||
string Handle(ApiDescription apiDescription);
|
||||
}
|
||||
|
||||
@@ -3,15 +3,7 @@ using Microsoft.AspNetCore.Mvc.ApiExplorer;
|
||||
|
||||
namespace Umbraco.Cms.Api.Common.OpenApi;
|
||||
|
||||
/// <summary>
|
||||
/// Defines a selector for choosing operation IDs from registered handlers.
|
||||
/// </summary>
|
||||
public interface IOperationIdSelector
|
||||
{
|
||||
/// <summary>
|
||||
/// Selects an operation ID for the specified API description.
|
||||
/// </summary>
|
||||
/// <param name="apiDescription">The API description to generate an operation ID for.</param>
|
||||
/// <returns>The operation ID, or <c>null</c> if none could be determined.</returns>
|
||||
string? OperationId(ApiDescription apiDescription);
|
||||
}
|
||||
|
||||
@@ -1,21 +1,8 @@
|
||||
namespace Umbraco.Cms.Api.Common.OpenApi;
|
||||
|
||||
/// <summary>
|
||||
/// Defines a handler for generating OpenAPI schema IDs.
|
||||
/// </summary>
|
||||
public interface ISchemaIdHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines whether this handler can generate a schema ID for the specified type.
|
||||
/// </summary>
|
||||
/// <param name="type">The type to check.</param>
|
||||
/// <returns><c>true</c> if this handler can handle the type; otherwise, <c>false</c>.</returns>
|
||||
bool CanHandle(Type type);
|
||||
|
||||
/// <summary>
|
||||
/// Generates a schema ID for the specified type.
|
||||
/// </summary>
|
||||
/// <param name="type">The type to generate a schema ID for.</param>
|
||||
/// <returns>The generated schema ID.</returns>
|
||||
string Handle(Type type);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,6 @@
|
||||
namespace Umbraco.Cms.Api.Common.OpenApi;
|
||||
|
||||
/// <summary>
|
||||
/// Defines a selector for choosing schema IDs from registered handlers.
|
||||
/// </summary>
|
||||
public interface ISchemaIdSelector
|
||||
{
|
||||
/// <summary>
|
||||
/// Selects a schema ID for the specified type.
|
||||
/// </summary>
|
||||
/// <param name="type">The type to generate a schema ID for.</param>
|
||||
/// <returns>The schema ID.</returns>
|
||||
string SchemaId(Type type);
|
||||
}
|
||||
|
||||
@@ -1,22 +1,8 @@
|
||||
namespace Umbraco.Cms.Api.Common.OpenApi;
|
||||
|
||||
/// <summary>
|
||||
/// Defines a handler for discovering sub-types for polymorphic OpenAPI schemas.
|
||||
/// </summary>
|
||||
public interface ISubTypesHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines whether this handler can discover sub-types for the specified type and document.
|
||||
/// </summary>
|
||||
/// <param name="type">The type to check.</param>
|
||||
/// <param name="documentName">The OpenAPI document name.</param>
|
||||
/// <returns><c>true</c> if this handler can handle the type; otherwise, <c>false</c>.</returns>
|
||||
bool CanHandle(Type type, string documentName);
|
||||
|
||||
/// <summary>
|
||||
/// Discovers sub-types for the specified type.
|
||||
/// </summary>
|
||||
/// <param name="type">The type to discover sub-types for.</param>
|
||||
/// <returns>An enumerable of discovered sub-types.</returns>
|
||||
IEnumerable<Type> Handle(Type type);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,6 @@
|
||||
namespace Umbraco.Cms.Api.Common.OpenApi;
|
||||
|
||||
/// <summary>
|
||||
/// Defines a selector for choosing sub-types from registered handlers.
|
||||
/// </summary>
|
||||
public interface ISubTypesSelector
|
||||
{
|
||||
/// <summary>
|
||||
/// Selects sub-types for the specified type for polymorphic OpenAPI schema generation.
|
||||
/// </summary>
|
||||
/// <param name="type">The type to find sub-types for.</param>
|
||||
/// <returns>An enumerable of sub-types.</returns>
|
||||
IEnumerable<Type> SubTypes(Type type);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Microsoft.OpenApi;
|
||||
using Microsoft.OpenApi;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
@@ -11,13 +11,8 @@ public class MimeTypeDocumentFilter : IDocumentFilter
|
||||
{
|
||||
private readonly string _documentName;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MimeTypeDocumentFilter"/> class.
|
||||
/// </summary>
|
||||
/// <param name="documentName">The name of the OpenAPI document to filter.</param>
|
||||
public MimeTypeDocumentFilter(string documentName) => _documentName = documentName;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
|
||||
{
|
||||
if (context.DocumentName != _documentName)
|
||||
@@ -29,7 +24,7 @@ public class MimeTypeDocumentFilter : IDocumentFilter
|
||||
.SelectMany(path => path.Value.Operations?.Values ?? Enumerable.Empty<OpenApiOperation>())
|
||||
.ToArray();
|
||||
|
||||
static void RemoveUnwantedMimeTypes(IDictionary<string, OpenApiMediaType>? content)
|
||||
void RemoveUnwantedMimeTypes(IDictionary<string, OpenApiMediaType>? content)
|
||||
{
|
||||
if (content is null || content.ContainsKey("application/json") is false)
|
||||
{
|
||||
|
||||
@@ -6,24 +6,14 @@ using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Api.Common.OpenApi;
|
||||
|
||||
/// <summary>
|
||||
/// Default handler for generating OpenAPI operation IDs for Umbraco API controllers.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Left unsealed on purpose, so it is extendable by consuming APIs.
|
||||
/// </remarks>
|
||||
// NOTE: Left unsealed on purpose, so it is extendable.
|
||||
public class OperationIdHandler : IOperationIdHandler
|
||||
{
|
||||
private readonly ApiVersioningOptions _apiVersioningOptions;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OperationIdHandler"/> class.
|
||||
/// </summary>
|
||||
/// <param name="apiVersioningOptions">The API versioning options.</param>
|
||||
public OperationIdHandler(IOptions<ApiVersioningOptions> apiVersioningOptions)
|
||||
=> _apiVersioningOptions = apiVersioningOptions.Value;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool CanHandle(ApiDescription apiDescription)
|
||||
{
|
||||
if (apiDescription.ActionDescriptor is not ControllerActionDescriptor controllerActionDescriptor)
|
||||
@@ -34,16 +24,9 @@ public class OperationIdHandler : IOperationIdHandler
|
||||
return CanHandle(apiDescription, controllerActionDescriptor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this handler can process the API description based on the controller namespace.
|
||||
/// </summary>
|
||||
/// <param name="apiDescription">The API description.</param>
|
||||
/// <param name="controllerActionDescriptor">The controller action descriptor.</param>
|
||||
/// <returns><c>true</c> if the controller is in an Umbraco.Cms.Api namespace; otherwise, <c>false</c>.</returns>
|
||||
protected virtual bool CanHandle(ApiDescription apiDescription, ControllerActionDescriptor controllerActionDescriptor)
|
||||
=> controllerActionDescriptor.ControllerTypeInfo.Namespace?.StartsWith("Umbraco.Cms.Api") is true;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual string Handle(ApiDescription apiDescription)
|
||||
=> UmbracoOperationId(apiDescription);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Umbraco.Cms.Api.Common.OpenApi;
|
||||
|
||||
|
||||
@@ -3,30 +3,19 @@ using Microsoft.AspNetCore.Mvc.ApiExplorer;
|
||||
|
||||
namespace Umbraco.Cms.Api.Common.OpenApi;
|
||||
|
||||
/// <summary>
|
||||
/// Selects an operation ID for an API description using registered handlers.
|
||||
/// </summary>
|
||||
public class OperationIdSelector : IOperationIdSelector
|
||||
{
|
||||
private readonly IEnumerable<IOperationIdHandler> _operationIdHandlers;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OperationIdSelector"/> class.
|
||||
/// </summary>
|
||||
[Obsolete("Use non-obsolete constructor. This will be removed in Umbraco 15.")]
|
||||
public OperationIdSelector()
|
||||
: this(Enumerable.Empty<IOperationIdHandler>())
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OperationIdSelector"/> class.
|
||||
/// </summary>
|
||||
/// <param name="operationIdHandlers">The registered operation ID handlers.</param>
|
||||
public OperationIdSelector(IEnumerable<IOperationIdHandler> operationIdHandlers)
|
||||
=> _operationIdHandlers = operationIdHandlers;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual string? OperationId(ApiDescription apiDescription)
|
||||
{
|
||||
IOperationIdHandler? handler = _operationIdHandlers.FirstOrDefault(h => h.CanHandle(apiDescription));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Microsoft.OpenApi;
|
||||
using Microsoft.OpenApi;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
|
||||
namespace Umbraco.Cms.Api.Common.OpenApi;
|
||||
@@ -10,14 +10,9 @@ public class RemoveSecuritySchemesDocumentFilter : IDocumentFilter
|
||||
{
|
||||
private readonly string _documentName;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RemoveSecuritySchemesDocumentFilter"/> class.
|
||||
/// </summary>
|
||||
/// <param name="documentName">The name of the OpenAPI document to filter.</param>
|
||||
public RemoveSecuritySchemesDocumentFilter(string documentName)
|
||||
=> _documentName = documentName;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
|
||||
{
|
||||
if (context.DocumentName != _documentName)
|
||||
|
||||
@@ -3,20 +3,12 @@ using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Api.Common.OpenApi;
|
||||
|
||||
/// <summary>
|
||||
/// Default handler for generating OpenAPI schema IDs for Umbraco types.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Left unsealed on purpose, so it is extendable by consuming APIs.
|
||||
/// Adds "Model" suffix to avoid TypeScript name clashes and removes invalid characters.
|
||||
/// </remarks>
|
||||
// NOTE: Left unsealed on purpose, so it is extendable.
|
||||
public class SchemaIdHandler : ISchemaIdHandler
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public virtual bool CanHandle(Type type)
|
||||
=> type.Namespace?.StartsWith("Umbraco.Cms") is true;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual string Handle(Type type)
|
||||
=> UmbracoSchemaId(type);
|
||||
|
||||
|
||||
@@ -1,20 +1,12 @@
|
||||
namespace Umbraco.Cms.Api.Common.OpenApi;
|
||||
namespace Umbraco.Cms.Api.Common.OpenApi;
|
||||
|
||||
/// <summary>
|
||||
/// Selects a schema ID for a type using registered handlers.
|
||||
/// </summary>
|
||||
public class SchemaIdSelector : ISchemaIdSelector
|
||||
{
|
||||
private readonly IEnumerable<ISchemaIdHandler> _schemaIdHandlers;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SchemaIdSelector"/> class.
|
||||
/// </summary>
|
||||
/// <param name="schemaIdHandlers">The registered schema ID handlers.</param>
|
||||
public SchemaIdSelector(IEnumerable<ISchemaIdHandler> schemaIdHandlers)
|
||||
=> _schemaIdHandlers = schemaIdHandlers;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual string SchemaId(Type type)
|
||||
{
|
||||
ISchemaIdHandler? handler = _schemaIdHandlers.FirstOrDefault(h => h.CanHandle(type));
|
||||
|
||||
@@ -2,33 +2,19 @@ using Umbraco.Cms.Api.Common.Serialization;
|
||||
|
||||
namespace Umbraco.Cms.Api.Common.OpenApi;
|
||||
|
||||
/// <summary>
|
||||
/// Default handler for discovering sub-types for polymorphic OpenAPI schemas.
|
||||
/// </summary>
|
||||
public class SubTypesHandler : ISubTypesHandler
|
||||
{
|
||||
private readonly IUmbracoJsonTypeInfoResolver _umbracoJsonTypeInfoResolver;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SubTypesHandler"/> class.
|
||||
/// </summary>
|
||||
/// <param name="umbracoJsonTypeInfoResolver">The JSON type info resolver for finding sub-types.</param>
|
||||
public SubTypesHandler(IUmbracoJsonTypeInfoResolver umbracoJsonTypeInfoResolver)
|
||||
=> _umbracoJsonTypeInfoResolver = umbracoJsonTypeInfoResolver;
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this handler can process the specified type based on namespace.
|
||||
/// </summary>
|
||||
/// <param name="type">The type to check.</param>
|
||||
/// <returns><c>true</c> if the type is in an Umbraco.Cms namespace; otherwise, <c>false</c>.</returns>
|
||||
protected virtual bool CanHandle(Type type)
|
||||
=> type.Namespace?.StartsWith("Umbraco.Cms") is true;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual bool CanHandle(Type type, string documentName)
|
||||
=> CanHandle(type);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual IEnumerable<Type> Handle(Type type)
|
||||
=> _umbracoJsonTypeInfoResolver.FindSubTypes(type);
|
||||
}
|
||||
|
||||
@@ -8,9 +8,6 @@ using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Api.Common.OpenApi;
|
||||
|
||||
/// <summary>
|
||||
/// Selects sub-types for polymorphic OpenAPI schemas using registered handlers.
|
||||
/// </summary>
|
||||
public class SubTypesSelector : ISubTypesSelector
|
||||
{
|
||||
private readonly IHostingEnvironment _hostingEnvironment;
|
||||
@@ -18,13 +15,6 @@ public class SubTypesSelector : ISubTypesSelector
|
||||
private readonly IEnumerable<ISubTypesHandler> _subTypeHandlers;
|
||||
private readonly IUmbracoJsonTypeInfoResolver _umbracoJsonTypeInfoResolver;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SubTypesSelector"/> class.
|
||||
/// </summary>
|
||||
/// <param name="hostingEnvironment">The hosting environment.</param>
|
||||
/// <param name="httpContextAccessor">The HTTP context accessor.</param>
|
||||
/// <param name="subTypeHandlers">The registered sub-type handlers.</param>
|
||||
/// <param name="umbracoJsonTypeInfoResolver">The JSON type info resolver for finding sub-types.</param>
|
||||
public SubTypesSelector(
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
IHttpContextAccessor httpContextAccessor,
|
||||
@@ -37,7 +27,6 @@ public class SubTypesSelector : ISubTypesSelector
|
||||
_umbracoJsonTypeInfoResolver = umbracoJsonTypeInfoResolver;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<Type> SubTypes(Type type)
|
||||
{
|
||||
var backOfficePath = _hostingEnvironment.GetBackOfficePath();
|
||||
@@ -46,7 +35,8 @@ public class SubTypesSelector : ISubTypesSelector
|
||||
if (_httpContextAccessor.HttpContext?.Request.Path.StartsWithSegments(swaggerPath) ?? false)
|
||||
{
|
||||
// Split the path into segments
|
||||
var segments = _httpContextAccessor.HttpContext.Request.Path.Value![swaggerPath.Length..]
|
||||
var segments = _httpContextAccessor.HttpContext.Request.Path.Value!
|
||||
.Substring(swaggerPath.Length)
|
||||
.TrimStart(Constants.CharArrays.ForwardSlash)
|
||||
.Split(Constants.CharArrays.ForwardSlash);
|
||||
|
||||
|
||||
@@ -13,15 +13,8 @@ using IHostingEnvironment = Umbraco.Cms.Core.Hosting.IHostingEnvironment;
|
||||
|
||||
namespace Umbraco.Cms.Api.Common.OpenApi;
|
||||
|
||||
/// <summary>
|
||||
/// Pipeline filter that configures Swagger/OpenAPI endpoints for Umbraco APIs.
|
||||
/// </summary>
|
||||
public class SwaggerRouteTemplatePipelineFilter : UmbracoPipelineFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SwaggerRouteTemplatePipelineFilter"/> class.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the pipeline filter.</param>
|
||||
public SwaggerRouteTemplatePipelineFilter(string name)
|
||||
: base(name)
|
||||
=> PostPipeline = PostPipelineAction;
|
||||
@@ -43,36 +36,15 @@ public class SwaggerRouteTemplatePipelineFilter : UmbracoPipelineFilter
|
||||
applicationBuilder.UseSwaggerUI(swaggerUiOptions => SwaggerUiConfiguration(swaggerUiOptions, swaggerGenOptions.Value, applicationBuilder));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether Swagger is enabled for the application.
|
||||
/// </summary>
|
||||
/// <param name="applicationBuilder">The application builder.</param>
|
||||
/// <returns><c>true</c> if Swagger is enabled; otherwise, <c>false</c>.</returns>
|
||||
protected virtual bool SwaggerIsEnabled(IApplicationBuilder applicationBuilder)
|
||||
=> applicationBuilder.ApplicationServices.GetRequiredService<IWebHostEnvironment>().IsProduction() is false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the route template for Swagger JSON endpoints.
|
||||
/// </summary>
|
||||
/// <param name="applicationBuilder">The application builder.</param>
|
||||
/// <returns>The Swagger route template.</returns>
|
||||
protected virtual string SwaggerRouteTemplate(IApplicationBuilder applicationBuilder)
|
||||
=> $"{GetBackOfficePath(applicationBuilder).TrimStart(Constants.CharArrays.ForwardSlash)}/swagger/{{documentName}}/swagger.json";
|
||||
|
||||
/// <summary>
|
||||
/// Gets the route prefix for the Swagger UI.
|
||||
/// </summary>
|
||||
/// <param name="applicationBuilder">The application builder.</param>
|
||||
/// <returns>The Swagger UI route prefix.</returns>
|
||||
protected virtual string SwaggerUiRoutePrefix(IApplicationBuilder applicationBuilder)
|
||||
=> $"{GetBackOfficePath(applicationBuilder).TrimStart(Constants.CharArrays.ForwardSlash)}/swagger";
|
||||
|
||||
/// <summary>
|
||||
/// Configures the Swagger UI options.
|
||||
/// </summary>
|
||||
/// <param name="swaggerUiOptions">The Swagger UI options to configure.</param>
|
||||
/// <param name="swaggerGenOptions">The Swagger generation options.</param>
|
||||
/// <param name="applicationBuilder">The application builder.</param>
|
||||
protected virtual void SwaggerUiConfiguration(
|
||||
SwaggerUIOptions swaggerUiOptions,
|
||||
SwaggerGenOptions swaggerGenOptions,
|
||||
|
||||
@@ -4,64 +4,30 @@ using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Api.Common.Rendering;
|
||||
|
||||
/// <summary>
|
||||
/// Implements output expansion strategy for element-only rendering in the Delivery API.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This strategy handles the expansion and filtering of properties when rendering content
|
||||
/// through the Delivery API based on expand and fields query parameters.
|
||||
/// </remarks>
|
||||
public class ElementOnlyOutputExpansionStrategy : IOutputExpansionStrategy
|
||||
{
|
||||
/// <summary>
|
||||
/// The parameter value indicating all properties should be included.
|
||||
/// </summary>
|
||||
protected const string All = "$all";
|
||||
|
||||
/// <summary>
|
||||
/// The parameter value indicating no properties should be included.
|
||||
/// </summary>
|
||||
protected const string None = "";
|
||||
|
||||
/// <summary>
|
||||
/// The name of the expand query parameter.
|
||||
/// </summary>
|
||||
protected const string ExpandParameterName = "expand";
|
||||
|
||||
/// <summary>
|
||||
/// The name of the fields query parameter.
|
||||
/// </summary>
|
||||
protected const string FieldsParameterName = "fields";
|
||||
|
||||
private readonly IApiPropertyRenderer _propertyRenderer;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the stack of expand property nodes for tracking nested expansions.
|
||||
/// </summary>
|
||||
protected Stack<Node?> ExpandProperties { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the stack of include property nodes for tracking nested field selections.
|
||||
/// </summary>
|
||||
protected Stack<Node?> IncludeProperties { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ElementOnlyOutputExpansionStrategy"/> class.
|
||||
/// </summary>
|
||||
/// <param name="propertyRenderer">The property renderer for converting property values.</param>
|
||||
public ElementOnlyOutputExpansionStrategy(
|
||||
IApiPropertyRenderer propertyRenderer)
|
||||
{
|
||||
_propertyRenderer = propertyRenderer;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual IDictionary<string, object?> MapContentProperties(IPublishedContent content)
|
||||
=> content.ItemType == PublishedItemType.Content
|
||||
? MapProperties(content.Properties)
|
||||
: throw new ArgumentException($"Invalid item type. This method can only be used with item type {nameof(PublishedItemType.Content)}, got: {content.ItemType}");
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual IDictionary<string, object?> MapMediaProperties(IPublishedContent media, bool skipUmbracoProperties = true)
|
||||
{
|
||||
if (media.ItemType != PublishedItemType.Media)
|
||||
@@ -79,7 +45,6 @@ public class ElementOnlyOutputExpansionStrategy : IOutputExpansionStrategy
|
||||
: new Dictionary<string, object?>();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual IDictionary<string, object?> MapElementProperties(IPublishedElement element)
|
||||
=> MapProperties(element.Properties, true);
|
||||
|
||||
@@ -122,27 +87,12 @@ public class ElementOnlyOutputExpansionStrategy : IOutputExpansionStrategy
|
||||
private object? GetPropertyValue(IPublishedProperty property)
|
||||
=> _propertyRenderer.GetPropertyValue(property, ExpandProperties.Peek() is not null);
|
||||
|
||||
/// <summary>
|
||||
/// Represents a node in the parsed expand/fields parameter tree structure.
|
||||
/// </summary>
|
||||
protected sealed class Node
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the key of this node.
|
||||
/// </summary>
|
||||
public string Key { get; private set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the child nodes of this node.
|
||||
/// </summary>
|
||||
public List<Node> Items { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Parses an expand/fields parameter value into a node tree structure.
|
||||
/// </summary>
|
||||
/// <param name="value">The parameter value to parse.</param>
|
||||
/// <returns>The root node of the parsed tree.</returns>
|
||||
/// <exception cref="ArgumentException">Thrown when the value has invalid syntax.</exception>
|
||||
public static Node Parse(string value)
|
||||
{
|
||||
// verify that there are as many start brackets as there are end brackets
|
||||
|
||||
@@ -2,77 +2,35 @@ using Umbraco.Cms.Core;
|
||||
|
||||
namespace Umbraco.Cms.Api.Common.Security;
|
||||
|
||||
/// <summary>
|
||||
/// Contains OAuth/OpenID Connect endpoint paths for Umbraco APIs.
|
||||
/// </summary>
|
||||
public static class Paths
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains endpoint paths for back-office authentication.
|
||||
/// </summary>
|
||||
public static class BackOfficeApi
|
||||
{
|
||||
/// <summary>
|
||||
/// The base endpoint template for back-office security endpoints.
|
||||
/// </summary>
|
||||
public const string EndpointTemplate = "security/back-office";
|
||||
|
||||
/// <summary>
|
||||
/// The authorization endpoint path.
|
||||
/// </summary>
|
||||
public static readonly string AuthorizationEndpoint = EndpointPath($"{EndpointTemplate}/authorize");
|
||||
|
||||
/// <summary>
|
||||
/// The token endpoint path.
|
||||
/// </summary>
|
||||
public static readonly string TokenEndpoint = EndpointPath($"{EndpointTemplate}/token");
|
||||
|
||||
/// <summary>
|
||||
/// The logout/sign-out endpoint path.
|
||||
/// </summary>
|
||||
public static readonly string LogoutEndpoint = EndpointPath($"{EndpointTemplate}/signout");
|
||||
|
||||
/// <summary>
|
||||
/// The token revocation endpoint path.
|
||||
/// </summary>
|
||||
public static readonly string RevokeEndpoint = EndpointPath($"{EndpointTemplate}/revoke");
|
||||
|
||||
private static string EndpointPath(string relativePath) => $"/umbraco{Constants.Web.ManagementApiPath}v1/{relativePath}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains endpoint paths for member authentication.
|
||||
/// </summary>
|
||||
public static class MemberApi
|
||||
{
|
||||
/// <summary>
|
||||
/// The base endpoint template for member security endpoints.
|
||||
/// </summary>
|
||||
public const string EndpointTemplate = "security/member";
|
||||
|
||||
/// <summary>
|
||||
/// The authorization endpoint path.
|
||||
/// </summary>
|
||||
public static readonly string AuthorizationEndpoint = EndpointPath($"{EndpointTemplate}/authorize");
|
||||
|
||||
/// <summary>
|
||||
/// The token endpoint path.
|
||||
/// </summary>
|
||||
public static readonly string TokenEndpoint = EndpointPath($"{EndpointTemplate}/token");
|
||||
|
||||
/// <summary>
|
||||
/// The logout/sign-out endpoint path.
|
||||
/// </summary>
|
||||
public static readonly string LogoutEndpoint = EndpointPath($"{EndpointTemplate}/signout");
|
||||
|
||||
/// <summary>
|
||||
/// The token revocation endpoint path.
|
||||
/// </summary>
|
||||
public static readonly string RevokeEndpoint = EndpointPath($"{EndpointTemplate}/revoke");
|
||||
|
||||
/// <summary>
|
||||
/// The user info endpoint path.
|
||||
/// </summary>
|
||||
public static readonly string UserinfoEndpoint = EndpointPath($"{EndpointTemplate}/userinfo");
|
||||
|
||||
// NOTE: we're NOT using /api/v1.0/ here because it will clash with the Delivery API docs
|
||||
|
||||
@@ -2,22 +2,9 @@ using System.Text.Json.Serialization.Metadata;
|
||||
|
||||
namespace Umbraco.Cms.Api.Common.Serialization;
|
||||
|
||||
/// <summary>
|
||||
/// Extends <see cref="IJsonTypeInfoResolver"/> with Umbraco-specific type resolution for polymorphic JSON serialization.
|
||||
/// </summary>
|
||||
public interface IUmbracoJsonTypeInfoResolver : IJsonTypeInfoResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Finds all sub-types of the specified type for polymorphic serialization.
|
||||
/// </summary>
|
||||
/// <param name="type">The base type to find sub-types for.</param>
|
||||
/// <returns>An enumerable of sub-types.</returns>
|
||||
IEnumerable<Type> FindSubTypes(Type type);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type discriminator value used for polymorphic serialization.
|
||||
/// </summary>
|
||||
/// <param name="type">The type to get the discriminator value for.</param>
|
||||
/// <returns>The discriminator value, or <c>null</c> if not applicable.</returns>
|
||||
string? GetTypeDiscriminatorValue(Type type);
|
||||
}
|
||||
|
||||
@@ -8,26 +8,14 @@ using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Api.Common.Serialization;
|
||||
|
||||
/// <summary>
|
||||
/// Implements JSON type info resolution for Umbraco with support for polymorphic serialization.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This resolver discovers sub-types of interfaces for polymorphic JSON serialization,
|
||||
/// caching results for performance. It also handles type discriminator values for OpenAPI schema generation.
|
||||
/// </remarks>
|
||||
public sealed class UmbracoJsonTypeInfoResolver : DefaultJsonTypeInfoResolver, IUmbracoJsonTypeInfoResolver
|
||||
{
|
||||
private readonly ITypeFinder _typeFinder;
|
||||
private readonly ConcurrentDictionary<Type, ISet<Type>> _subTypesCache = new ConcurrentDictionary<Type, ISet<Type>>();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UmbracoJsonTypeInfoResolver"/> class.
|
||||
/// </summary>
|
||||
/// <param name="typeFinder">The type finder for discovering sub-types.</param>
|
||||
public UmbracoJsonTypeInfoResolver(ITypeFinder typeFinder)
|
||||
=> _typeFinder = typeFinder;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<Type> FindSubTypes(Type type)
|
||||
{
|
||||
JsonDerivedTypeAttribute[] explicitJsonDerivedTypes = type
|
||||
@@ -56,7 +44,6 @@ public sealed class UmbracoJsonTypeInfoResolver : DefaultJsonTypeInfoResolver, I
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string? GetTypeDiscriminatorValue(Type type)
|
||||
{
|
||||
JsonDerivedTypeAttribute? jsonDerivedTypeAttribute = type
|
||||
@@ -75,7 +62,6 @@ public sealed class UmbracoJsonTypeInfoResolver : DefaultJsonTypeInfoResolver, I
|
||||
return typeof(IOpenApiDiscriminator).IsAssignableFrom(type) ? type.Name : null;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options)
|
||||
{
|
||||
JsonTypeInfo result = base.GetTypeInfo(type, options);
|
||||
|
||||
@@ -1,28 +1,14 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Umbraco.Cms.Api.Common.ViewModels.Pagination;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a paged collection of items with total count.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items in the collection.</typeparam>
|
||||
public class PagedViewModel<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the total number of items available.
|
||||
/// </summary>
|
||||
[Required]
|
||||
public long Total { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the items in the current page.
|
||||
/// </summary>
|
||||
[Required]
|
||||
public IEnumerable<T> Items { get; set; } = Enumerable.Empty<T>();
|
||||
|
||||
/// <summary>
|
||||
/// Creates an empty paged view model.
|
||||
/// </summary>
|
||||
/// <returns>An empty <see cref="PagedViewModel{T}"/> instance.</returns>
|
||||
public static PagedViewModel<T> Empty() => new();
|
||||
}
|
||||
|
||||
@@ -2,33 +2,16 @@ using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Umbraco.Cms.Api.Common.ViewModels.Pagination;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a subset of items with counts of items before and after the subset.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items in the collection.</typeparam>
|
||||
public class SubsetViewModel<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the total number of items before this subset.
|
||||
/// </summary>
|
||||
[Required]
|
||||
public long TotalBefore { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the total number of items after this subset.
|
||||
/// </summary>
|
||||
[Required]
|
||||
public long TotalAfter { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the items in the subset.
|
||||
/// </summary>
|
||||
[Required]
|
||||
public IEnumerable<T> Items { get; set; } = Enumerable.Empty<T>();
|
||||
|
||||
/// <summary>
|
||||
/// Creates an empty subset view model.
|
||||
/// </summary>
|
||||
/// <returns>An empty <see cref="SubsetViewModel{T}"/> instance.</returns>
|
||||
public static SubsetViewModel<T> Empty() => new();
|
||||
}
|
||||
|
||||
@@ -7,6 +7,23 @@ using Umbraco.Cms.Core.Hosting;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management;
|
||||
|
||||
[BindProperties]
|
||||
public class BackOfficeLoginModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the value of the "ReturnUrl" query parameter or defaults to the configured Umbraco directory.
|
||||
/// </summary>
|
||||
[FromQuery(Name = "ReturnUrl")]
|
||||
public string? ReturnUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The configured Umbraco directory.
|
||||
/// </summary>
|
||||
public string? UmbracoUrl { get; set; }
|
||||
|
||||
public bool UserIsAlreadyLoggedIn { get; set; }
|
||||
}
|
||||
|
||||
[ApiExplorerSettings(IgnoreApi = true)]
|
||||
[Route(LoginPath)]
|
||||
public class BackOfficeLoginController : Controller
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management;
|
||||
|
||||
[BindProperties]
|
||||
public class BackOfficeLoginModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the value of the "ReturnUrl" query parameter or defaults to the configured Umbraco directory.
|
||||
/// </summary>
|
||||
[FromQuery(Name = "ReturnUrl")]
|
||||
public string? ReturnUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The configured Umbraco directory.
|
||||
/// </summary>
|
||||
public string? UmbracoUrl { get; set; }
|
||||
|
||||
public bool UserIsAlreadyLoggedIn { get; set; }
|
||||
}
|
||||
+2
-2
@@ -43,8 +43,8 @@ public class DataTypeTreeControllerBase : FolderTreeControllerBase<DataTypeTreeI
|
||||
{
|
||||
get
|
||||
{
|
||||
var ordering = Ordering.By(Infrastructure.Persistence.Dtos.NodeDto.NodeObjectTypeColumnName, Direction.Descending); // We need to override to change direction
|
||||
ordering.Next = Ordering.By(Infrastructure.Persistence.Dtos.NodeDto.TextColumnName);
|
||||
var ordering = Ordering.By(nameof(Infrastructure.Persistence.Dtos.NodeDto.NodeObjectType), Direction.Descending); // We need to override to change direction
|
||||
ordering.Next = Ordering.By(nameof(Infrastructure.Persistence.Dtos.NodeDto.Text));
|
||||
|
||||
return ordering;
|
||||
}
|
||||
|
||||
+1
-26
@@ -5,7 +5,6 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Api.Management.Factories;
|
||||
using Umbraco.Cms.Api.Management.Services.Entities;
|
||||
using Umbraco.Cms.Api.Management.Services.Flags;
|
||||
using Umbraco.Cms.Api.Management.Services.PermissionFilter;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Tree;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
@@ -36,7 +35,7 @@ public class AncestorsDocumentTreeController : DocumentTreeControllerBase
|
||||
{
|
||||
}
|
||||
|
||||
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 19.")]
|
||||
[ActivatorUtilitiesConstructor]
|
||||
public AncestorsDocumentTreeController(
|
||||
IEntityService entityService,
|
||||
FlagProviderCollection flagProviders,
|
||||
@@ -58,30 +57,6 @@ public class AncestorsDocumentTreeController : DocumentTreeControllerBase
|
||||
{
|
||||
}
|
||||
|
||||
[ActivatorUtilitiesConstructor]
|
||||
public AncestorsDocumentTreeController(
|
||||
IEntityService entityService,
|
||||
FlagProviderCollection flagProviders,
|
||||
IUserStartNodeEntitiesService userStartNodeEntitiesService,
|
||||
IDataTypeService dataTypeService,
|
||||
IPublicAccessService publicAccessService,
|
||||
AppCaches appCaches,
|
||||
IBackOfficeSecurityAccessor backofficeSecurityAccessor,
|
||||
IDocumentPresentationFactory documentPresentationFactory,
|
||||
IDocumentPermissionFilterService documentPermissionFilterService)
|
||||
: base(
|
||||
entityService,
|
||||
flagProviders,
|
||||
userStartNodeEntitiesService,
|
||||
dataTypeService,
|
||||
publicAccessService,
|
||||
appCaches,
|
||||
backofficeSecurityAccessor,
|
||||
documentPresentationFactory,
|
||||
documentPermissionFilterService)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("ancestors")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(IEnumerable<DocumentTreeItemResponseModel>), StatusCodes.Status200OK)]
|
||||
|
||||
+1
-26
@@ -6,7 +6,6 @@ using Umbraco.Cms.Api.Common.ViewModels.Pagination;
|
||||
using Umbraco.Cms.Api.Management.Factories;
|
||||
using Umbraco.Cms.Api.Management.Services.Entities;
|
||||
using Umbraco.Cms.Api.Management.Services.Flags;
|
||||
using Umbraco.Cms.Api.Management.Services.PermissionFilter;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Tree;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
@@ -37,7 +36,7 @@ public class ChildrenDocumentTreeController : DocumentTreeControllerBase
|
||||
{
|
||||
}
|
||||
|
||||
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 19.")]
|
||||
[ActivatorUtilitiesConstructor]
|
||||
public ChildrenDocumentTreeController(
|
||||
IEntityService entityService,
|
||||
FlagProviderCollection flagProviders,
|
||||
@@ -59,30 +58,6 @@ public class ChildrenDocumentTreeController : DocumentTreeControllerBase
|
||||
{
|
||||
}
|
||||
|
||||
[ActivatorUtilitiesConstructor]
|
||||
public ChildrenDocumentTreeController(
|
||||
IEntityService entityService,
|
||||
FlagProviderCollection flagProviders,
|
||||
IUserStartNodeEntitiesService userStartNodeEntitiesService,
|
||||
IDataTypeService dataTypeService,
|
||||
IPublicAccessService publicAccessService,
|
||||
AppCaches appCaches,
|
||||
IBackOfficeSecurityAccessor backofficeSecurityAccessor,
|
||||
IDocumentPresentationFactory documentPresentationFactory,
|
||||
IDocumentPermissionFilterService documentPermissionFilterService)
|
||||
: base(
|
||||
entityService,
|
||||
flagProviders,
|
||||
userStartNodeEntitiesService,
|
||||
dataTypeService,
|
||||
publicAccessService,
|
||||
appCaches,
|
||||
backofficeSecurityAccessor,
|
||||
documentPresentationFactory,
|
||||
documentPermissionFilterService)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("children")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(PagedViewModel<DocumentTreeItemResponseModel>), StatusCodes.Status200OK)]
|
||||
|
||||
+2
-39
@@ -6,7 +6,6 @@ using Umbraco.Cms.Api.Management.Factories;
|
||||
using Umbraco.Cms.Api.Management.Routing;
|
||||
using Umbraco.Cms.Api.Management.Services.Entities;
|
||||
using Umbraco.Cms.Api.Management.Services.Flags;
|
||||
using Umbraco.Cms.Api.Management.Services.PermissionFilter;
|
||||
using Umbraco.Cms.Api.Management.ViewModels;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Tree;
|
||||
using Umbraco.Cms.Core;
|
||||
@@ -29,7 +28,6 @@ public abstract class DocumentTreeControllerBase : UserStartNodeTreeControllerBa
|
||||
private readonly AppCaches _appCaches;
|
||||
private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor;
|
||||
private readonly IDocumentPresentationFactory _documentPresentationFactory;
|
||||
private readonly IDocumentPermissionFilterService _documentPermissionFilterService;
|
||||
|
||||
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")]
|
||||
protected DocumentTreeControllerBase(
|
||||
@@ -52,29 +50,6 @@ public abstract class DocumentTreeControllerBase : UserStartNodeTreeControllerBa
|
||||
{
|
||||
}
|
||||
|
||||
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 19.")]
|
||||
protected DocumentTreeControllerBase(
|
||||
IEntityService entityService,
|
||||
FlagProviderCollection flagProviders,
|
||||
IUserStartNodeEntitiesService userStartNodeEntitiesService,
|
||||
IDataTypeService dataTypeService,
|
||||
IPublicAccessService publicAccessService,
|
||||
AppCaches appCaches,
|
||||
IBackOfficeSecurityAccessor backofficeSecurityAccessor,
|
||||
IDocumentPresentationFactory documentPresentationFactory)
|
||||
: this(
|
||||
entityService,
|
||||
flagProviders,
|
||||
userStartNodeEntitiesService,
|
||||
dataTypeService,
|
||||
publicAccessService,
|
||||
appCaches,
|
||||
backofficeSecurityAccessor,
|
||||
documentPresentationFactory,
|
||||
StaticServiceProvider.Instance.GetRequiredService<IDocumentPermissionFilterService>())
|
||||
{
|
||||
}
|
||||
|
||||
[ActivatorUtilitiesConstructor]
|
||||
protected DocumentTreeControllerBase(
|
||||
IEntityService entityService,
|
||||
@@ -84,20 +59,18 @@ public abstract class DocumentTreeControllerBase : UserStartNodeTreeControllerBa
|
||||
IPublicAccessService publicAccessService,
|
||||
AppCaches appCaches,
|
||||
IBackOfficeSecurityAccessor backofficeSecurityAccessor,
|
||||
IDocumentPresentationFactory documentPresentationFactory,
|
||||
IDocumentPermissionFilterService documentPermissionFilterService)
|
||||
IDocumentPresentationFactory documentPresentationFactory)
|
||||
: base(entityService, flagProviders, userStartNodeEntitiesService, dataTypeService)
|
||||
{
|
||||
_publicAccessService = publicAccessService;
|
||||
_appCaches = appCaches;
|
||||
_backofficeSecurityAccessor = backofficeSecurityAccessor;
|
||||
_documentPresentationFactory = documentPresentationFactory;
|
||||
_documentPermissionFilterService = documentPermissionFilterService;
|
||||
}
|
||||
|
||||
protected override UmbracoObjectTypes ItemObjectType => UmbracoObjectTypes.Document;
|
||||
|
||||
protected override Ordering ItemOrdering => Ordering.By(Infrastructure.Persistence.Dtos.NodeDto.SortOrderColumnName);
|
||||
protected override Ordering ItemOrdering => Ordering.By(nameof(Infrastructure.Persistence.Dtos.NodeDto.SortOrder));
|
||||
|
||||
protected override DocumentTreeItemResponseModel MapTreeItemViewModel(Guid? parentId, IEntitySlim entity)
|
||||
{
|
||||
@@ -119,7 +92,6 @@ public abstract class DocumentTreeControllerBase : UserStartNodeTreeControllerBa
|
||||
return responseModel;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override int[] GetUserStartNodeIds()
|
||||
=> _backofficeSecurityAccessor
|
||||
.BackOfficeSecurity?
|
||||
@@ -127,19 +99,10 @@ public abstract class DocumentTreeControllerBase : UserStartNodeTreeControllerBa
|
||||
.CalculateContentStartNodeIds(EntityService, _appCaches)
|
||||
?? Array.Empty<int>();
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override string[] GetUserStartNodePaths()
|
||||
=> _backofficeSecurityAccessor
|
||||
.BackOfficeSecurity?
|
||||
.CurrentUser?
|
||||
.GetContentStartNodePaths(EntityService, _appCaches)
|
||||
?? Array.Empty<string>();
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override Task<(IEntitySlim[] Entities, long TotalItems)> FilterTreeEntities(IEntitySlim[] entities, long totalItems)
|
||||
=> _documentPermissionFilterService.FilterAsync(entities, totalItems);
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override Task<(IEntitySlim[] Entities, long TotalBefore, long TotalAfter)> FilterTreeEntities(Guid targetKey, IEntitySlim[] entities, long totalBefore, long totalAfter)
|
||||
=> _documentPermissionFilterService.FilterAsync(targetKey, entities, totalBefore, totalAfter);
|
||||
}
|
||||
|
||||
+1
-26
@@ -6,7 +6,6 @@ using Umbraco.Cms.Api.Common.ViewModels.Pagination;
|
||||
using Umbraco.Cms.Api.Management.Factories;
|
||||
using Umbraco.Cms.Api.Management.Services.Entities;
|
||||
using Umbraco.Cms.Api.Management.Services.Flags;
|
||||
using Umbraco.Cms.Api.Management.Services.PermissionFilter;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Tree;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
@@ -37,7 +36,7 @@ public class RootDocumentTreeController : DocumentTreeControllerBase
|
||||
{
|
||||
}
|
||||
|
||||
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 19.")]
|
||||
[ActivatorUtilitiesConstructor]
|
||||
public RootDocumentTreeController(
|
||||
IEntityService entityService,
|
||||
FlagProviderCollection flagProviders,
|
||||
@@ -59,30 +58,6 @@ public class RootDocumentTreeController : DocumentTreeControllerBase
|
||||
{
|
||||
}
|
||||
|
||||
[ActivatorUtilitiesConstructor]
|
||||
public RootDocumentTreeController(
|
||||
IEntityService entityService,
|
||||
FlagProviderCollection flagProviders,
|
||||
IUserStartNodeEntitiesService userStartNodeEntitiesService,
|
||||
IDataTypeService dataTypeService,
|
||||
IPublicAccessService publicAccessService,
|
||||
AppCaches appCaches,
|
||||
IBackOfficeSecurityAccessor backofficeSecurityAccessor,
|
||||
IDocumentPresentationFactory documentPresentationFactory,
|
||||
IDocumentPermissionFilterService documentPermissionFilterService)
|
||||
: base(
|
||||
entityService,
|
||||
flagProviders,
|
||||
userStartNodeEntitiesService,
|
||||
dataTypeService,
|
||||
publicAccessService,
|
||||
appCaches,
|
||||
backofficeSecurityAccessor,
|
||||
documentPresentationFactory,
|
||||
documentPermissionFilterService)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("root")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(PagedViewModel<DocumentTreeItemResponseModel>), StatusCodes.Status200OK)]
|
||||
|
||||
+1
-26
@@ -6,7 +6,6 @@ using Umbraco.Cms.Api.Common.ViewModels.Pagination;
|
||||
using Umbraco.Cms.Api.Management.Factories;
|
||||
using Umbraco.Cms.Api.Management.Services.Entities;
|
||||
using Umbraco.Cms.Api.Management.Services.Flags;
|
||||
using Umbraco.Cms.Api.Management.Services.PermissionFilter;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Tree;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
@@ -37,7 +36,7 @@ public class SiblingsDocumentTreeController : DocumentTreeControllerBase
|
||||
{
|
||||
}
|
||||
|
||||
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 19.")]
|
||||
[ActivatorUtilitiesConstructor]
|
||||
public SiblingsDocumentTreeController(
|
||||
IEntityService entityService,
|
||||
FlagProviderCollection flagProviders,
|
||||
@@ -59,30 +58,6 @@ public class SiblingsDocumentTreeController : DocumentTreeControllerBase
|
||||
{
|
||||
}
|
||||
|
||||
[ActivatorUtilitiesConstructor]
|
||||
public SiblingsDocumentTreeController(
|
||||
IEntityService entityService,
|
||||
FlagProviderCollection flagProviders,
|
||||
IUserStartNodeEntitiesService userStartNodeEntitiesService,
|
||||
IDataTypeService dataTypeService,
|
||||
IPublicAccessService publicAccessService,
|
||||
AppCaches appCaches,
|
||||
IBackOfficeSecurityAccessor backofficeSecurityAccessor,
|
||||
IDocumentPresentationFactory documentPresentationFactory,
|
||||
IDocumentPermissionFilterService documentPermissionFilterService)
|
||||
: base(
|
||||
entityService,
|
||||
flagProviders,
|
||||
userStartNodeEntitiesService,
|
||||
dataTypeService,
|
||||
publicAccessService,
|
||||
appCaches,
|
||||
backofficeSecurityAccessor,
|
||||
documentPresentationFactory,
|
||||
documentPermissionFilterService)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("siblings")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(SubsetViewModel<DocumentTreeItemResponseModel>), StatusCodes.Status200OK)]
|
||||
|
||||
+2
-2
@@ -43,8 +43,8 @@ public class DocumentBlueprintTreeControllerBase : FolderTreeControllerBase<Docu
|
||||
{
|
||||
get
|
||||
{
|
||||
var ordering = Ordering.By(Infrastructure.Persistence.Dtos.NodeDto.NodeObjectTypeColumnName, Direction.Descending); // We need to override to change direction
|
||||
ordering.Next = Ordering.By(Infrastructure.Persistence.Dtos.NodeDto.TextColumnName);
|
||||
var ordering = Ordering.By(nameof(Infrastructure.Persistence.Dtos.NodeDto.NodeObjectType), Direction.Descending); // We need to override to change direction
|
||||
ordering.Next = Ordering.By(nameof(Infrastructure.Persistence.Dtos.NodeDto.Text));
|
||||
|
||||
return ordering;
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ public abstract class ManagementApiControllerBase : Controller, IUmbracoFeature
|
||||
/// Creates a 403 Forbidden result.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Use this method instead of the controller base class's Forbid() method.
|
||||
/// Use this method instead of <see cref="ManagementApiControllerBase.Forbid()"/> on the controller base.
|
||||
/// This method ensures that a proper 403 Forbidden status code is returned to the client.
|
||||
/// </remarks>
|
||||
// Duplicate code copied between Management API and Delivery API.
|
||||
|
||||
@@ -64,7 +64,7 @@ public class MediaTreeControllerBase : UserStartNodeTreeControllerBase<MediaTree
|
||||
|
||||
protected override UmbracoObjectTypes ItemObjectType => UmbracoObjectTypes.Media;
|
||||
|
||||
protected override Ordering ItemOrdering => Ordering.By(Infrastructure.Persistence.Dtos.NodeDto.SortOrderColumnName);
|
||||
protected override Ordering ItemOrdering => Ordering.By(nameof(Infrastructure.Persistence.Dtos.NodeDto.SortOrder));
|
||||
|
||||
protected override MediaTreeItemResponseModel MapTreeItemViewModel(Guid? parentKey, IEntitySlim entity)
|
||||
{
|
||||
|
||||
@@ -168,7 +168,7 @@ public abstract class RecycleBinControllerBase<TItem> : ContentControllerBase
|
||||
after,
|
||||
out totalBefore,
|
||||
out totalAfter,
|
||||
ordering: Ordering.By(Infrastructure.Persistence.Dtos.NodeDto.TextColumnName))
|
||||
ordering: Ordering.By(nameof(Infrastructure.Persistence.Dtos.NodeDto.Text)))
|
||||
.ToArray();
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -36,14 +36,12 @@ public abstract class EntityTreeControllerBase<TItem> : ManagementApiControllerB
|
||||
|
||||
protected abstract UmbracoObjectTypes ItemObjectType { get; }
|
||||
|
||||
protected virtual Ordering ItemOrdering => Ordering.By(Infrastructure.Persistence.Dtos.NodeDto.TextColumnName);
|
||||
protected virtual Ordering ItemOrdering => Ordering.By(nameof(Infrastructure.Persistence.Dtos.NodeDto.Text));
|
||||
|
||||
protected async Task<ActionResult<PagedViewModel<TItem>>> GetRoot(int skip, int take)
|
||||
{
|
||||
IEntitySlim[] rootEntities = GetPagedRootEntities(skip, take, out var totalItems);
|
||||
|
||||
(rootEntities, totalItems) = await FilterTreeEntities(rootEntities, totalItems);
|
||||
|
||||
TItem[] treeItemViewModels = MapTreeItemViewModels(null, rootEntities);
|
||||
|
||||
await PopulateFlags(treeItemViewModels);
|
||||
@@ -57,8 +55,6 @@ public abstract class EntityTreeControllerBase<TItem> : ManagementApiControllerB
|
||||
{
|
||||
IEntitySlim[] children = GetPagedChildEntities(parentId, skip, take, out var totalItems);
|
||||
|
||||
(children, totalItems) = await FilterTreeEntities(children, totalItems);
|
||||
|
||||
TItem[] treeItemViewModels = MapTreeItemViewModels(parentId, children);
|
||||
|
||||
await PopulateFlags(treeItemViewModels);
|
||||
@@ -76,8 +72,6 @@ public abstract class EntityTreeControllerBase<TItem> : ManagementApiControllerB
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
(siblings, totalBefore, totalAfter) = await FilterTreeEntities(target, siblings, totalBefore, totalAfter);
|
||||
|
||||
IEntitySlim? entity = siblings.FirstOrDefault();
|
||||
Guid? parentKey = GetParentKey(entity);
|
||||
|
||||
@@ -90,34 +84,6 @@ public abstract class EntityTreeControllerBase<TItem> : ManagementApiControllerB
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters the specified collection of tree entities and returns the filtered results asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="entities">An array of entities to be filtered.</param>
|
||||
/// <param name="totalItems">The total number of items before filtering.</param>
|
||||
/// <returns>A task that represents the asynchronous operation. The task result contains a tuple of the filtered entities and the adjusted total items count.</returns>
|
||||
/// <remarks>
|
||||
/// Override this method to implement custom filtering logic for tree entities. The default
|
||||
/// implementation returns the input array and total items unchanged.
|
||||
/// </remarks>
|
||||
protected virtual Task<(IEntitySlim[] Entities, long TotalItems)> FilterTreeEntities(IEntitySlim[] entities, long totalItems)
|
||||
=> Task.FromResult((entities, totalItems));
|
||||
|
||||
/// <summary>
|
||||
/// Filters the specified collection of tree entities for sibling queries and returns the filtered results asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="targetKey">The key of the target entity around which siblings are being retrieved.</param>
|
||||
/// <param name="entities">An array of entities to be filtered.</param>
|
||||
/// <param name="totalBefore">The total number of siblings before the target entity.</param>
|
||||
/// <param name="totalAfter">The total number of siblings after the target entity.</param>
|
||||
/// <returns>A task that represents the asynchronous operation. The task result contains a tuple of the filtered entities and the adjusted before/after counts.</returns>
|
||||
/// <remarks>
|
||||
/// Override this method to implement custom filtering logic for sibling tree entities. The default
|
||||
/// implementation returns the input array and totals unchanged.
|
||||
/// </remarks>
|
||||
protected virtual Task<(IEntitySlim[] Entities, long TotalBefore, long TotalAfter)> FilterTreeEntities(Guid targetKey, IEntitySlim[] entities, long totalBefore, long totalAfter)
|
||||
=> Task.FromResult((entities, totalBefore, totalAfter));
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parent key for an entity, or root if null or no parent.
|
||||
/// </summary>
|
||||
|
||||
@@ -24,8 +24,8 @@ public abstract class FolderTreeControllerBase<TItem> : NamedEntityTreeControlle
|
||||
get
|
||||
{
|
||||
// Override to order by type (folder vs item) before the text
|
||||
var ordering = Ordering.By(Infrastructure.Persistence.Dtos.NodeDto.NodeObjectTypeColumnName);
|
||||
ordering.Next = Ordering.By(Infrastructure.Persistence.Dtos.NodeDto.TextColumnName);
|
||||
var ordering = Ordering.By(nameof(Infrastructure.Persistence.Dtos.NodeDto.NodeObjectType));
|
||||
ordering.Next = Ordering.By(nameof(Infrastructure.Persistence.Dtos.NodeDto.Text));
|
||||
|
||||
return ordering;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Api.Management.Factories;
|
||||
using Umbraco.Cms.Api.Management.Mapping.Document;
|
||||
using Umbraco.Cms.Api.Management.Services.PermissionFilter;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Mapping;
|
||||
|
||||
@@ -11,7 +10,6 @@ internal static class DocumentBuilderExtensions
|
||||
{
|
||||
internal static IUmbracoBuilder AddDocuments(this IUmbracoBuilder builder)
|
||||
{
|
||||
builder.Services.AddScoped<IDocumentPermissionFilterService, DocumentPermissionFilterService>();
|
||||
builder.Services.AddTransient<IDocumentPresentationFactory, DocumentPresentationFactory>();
|
||||
builder.Services.AddTransient<IDocumentNotificationPresentationFactory, DocumentNotificationPresentationFactory>();
|
||||
builder.Services.AddTransient<IDocumentUrlFactory, DocumentUrlFactory>();
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Api.Management.Factories;
|
||||
using Umbraco.Cms.Api.Management.Mapping.TemporaryFile;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
@@ -6,7 +6,7 @@ using Umbraco.Cms.Core.Mapping;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.DependencyInjection;
|
||||
|
||||
internal static class TemporaryFileBuilderExtensions
|
||||
internal static class TTemporaryFileBuilderExtensions
|
||||
{
|
||||
internal static IUmbracoBuilder AddTemporaryFiles(this IUmbracoBuilder builder)
|
||||
{
|
||||
@@ -32,15 +32,20 @@ public class MediaUrlFactory : IMediaUrlFactory
|
||||
.Select(mediaUrl => new MediaUrlInfo
|
||||
{
|
||||
Culture = null,
|
||||
Url = CreateMediaUrl(mediaUrl, media.Trashed),
|
||||
Url = CreateMediaUrl(mediaUrl),
|
||||
})
|
||||
.ToArray();
|
||||
|
||||
private string CreateMediaUrl(string mediaUrl, bool isTrashed)
|
||||
private string CreateMediaUrl(string mediaUrl)
|
||||
{
|
||||
var url = _absoluteUrlBuilder.ToAbsoluteUrl(mediaUrl).ToString();
|
||||
|
||||
return isTrashed && _contentSettings.EnableMediaRecycleBinProtection
|
||||
if (_contentSettings.EnableMediaRecycleBinProtection is false)
|
||||
{
|
||||
return url;
|
||||
}
|
||||
|
||||
return _contentSettings.EnableMediaRecycleBinProtection
|
||||
? AddProtectedSuffixToMediaUrl(url)
|
||||
: url;
|
||||
}
|
||||
|
||||
@@ -58,8 +58,7 @@ public class RelationTypePresentationFactory : IRelationTypePresentationFactory
|
||||
return Task.FromResult<IEnumerable<IReferenceResponseModel>>(result);
|
||||
}
|
||||
|
||||
private IReferenceResponseModel? MapDocumentReference(
|
||||
RelationItemModel relationItemModel,
|
||||
private IReferenceResponseModel? MapDocumentReference(RelationItemModel relationItemModel,
|
||||
List<IEntitySlim> slimEntities)
|
||||
{
|
||||
DocumentReferenceResponseModel? documentReferenceResponseModel =
|
||||
|
||||
@@ -203,7 +203,7 @@ public class UserGroupPresentationFactory : IUserGroupPresentationFactory
|
||||
current.Description = request.Description;
|
||||
current.Icon = request.Icon;
|
||||
current.HasAccessToAllLanguages = request.HasAccessToAllLanguages;
|
||||
|
||||
|
||||
current.Permissions = request.FallbackPermissions;
|
||||
current.GranularPermissions = await _permissionPresentationFactory.CreatePermissionSetsAsync(request.Permissions);
|
||||
|
||||
|
||||
+2
-2
@@ -8,11 +8,11 @@ using Umbraco.Extensions;
|
||||
namespace Umbraco.Cms.Api.Management.Middleware;
|
||||
|
||||
/// <summary>
|
||||
/// Used to handle errors registered by external login providers.
|
||||
/// Used to handle errors registered by external login providers
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// When an external login provider registers an error with
|
||||
/// <see cref="HttpContextExtensions.SetExternalLoginProviderErrors" /> during the OAuth process,
|
||||
/// <see cref="Extensions.HttpContextExtensions.SetExternalLoginProviderErrors" /> during the OAuth process,
|
||||
/// this middleware will detect that, store the errors into cookie data and redirect to the back office login so we can
|
||||
/// read the errors back out.
|
||||
/// </remarks>
|
||||
|
||||
@@ -38700,10 +38700,6 @@
|
||||
"alias": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"icon": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
@@ -46940,7 +46936,6 @@
|
||||
"required": [
|
||||
"allowLocalLogin",
|
||||
"allowPasswordReset",
|
||||
"umbracoCssPath",
|
||||
"versionCheckPeriod"
|
||||
],
|
||||
"type": "object",
|
||||
@@ -46954,9 +46949,6 @@
|
||||
},
|
||||
"allowLocalLogin": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"umbracoCssPath": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
@@ -49185,10 +49177,6 @@
|
||||
"alias": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"icon": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
@@ -49626,10 +49614,6 @@
|
||||
"alias": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"icon": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Globalization;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using OpenIddict.Abstractions;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Infrastructure.Security;
|
||||
|
||||
@@ -19,12 +14,10 @@ public class BackOfficeApplicationManager : OpenIdDictApplicationManagerBase, IB
|
||||
{
|
||||
private readonly IWebHostEnvironment _webHostEnvironment;
|
||||
private readonly IRuntimeState _runtimeState;
|
||||
private readonly ILogger<BackOfficeApplicationManager> _logger;
|
||||
private readonly Uri? _backOfficeHost;
|
||||
private readonly string _authorizeCallbackPathName;
|
||||
private readonly string _authorizeCallbackLogoutPathName;
|
||||
|
||||
[Obsolete("Use the non obsoleted constructor instead. Scheduled for removal in v19")]
|
||||
public BackOfficeApplicationManager(
|
||||
IOpenIddictApplicationManager applicationManager,
|
||||
IWebHostEnvironment webHostEnvironment,
|
||||
@@ -37,29 +30,12 @@ public class BackOfficeApplicationManager : OpenIdDictApplicationManagerBase, IB
|
||||
_backOfficeHost = securitySettings.Value.BackOfficeHost;
|
||||
_authorizeCallbackPathName = securitySettings.Value.AuthorizeCallbackPathName;
|
||||
_authorizeCallbackLogoutPathName = securitySettings.Value.AuthorizeCallbackLogoutPathName;
|
||||
_logger = StaticServiceProvider.Instance.GetRequiredService<ILogger<BackOfficeApplicationManager>>();
|
||||
}
|
||||
|
||||
public BackOfficeApplicationManager(
|
||||
IOpenIddictApplicationManager applicationManager,
|
||||
IWebHostEnvironment webHostEnvironment,
|
||||
IOptions<SecuritySettings> securitySettings,
|
||||
IRuntimeState runtimeState,
|
||||
ILogger<BackOfficeApplicationManager> logger)
|
||||
: base(applicationManager)
|
||||
{
|
||||
_webHostEnvironment = webHostEnvironment;
|
||||
_runtimeState = runtimeState;
|
||||
_logger = logger;
|
||||
_backOfficeHost = securitySettings.Value.BackOfficeHost;
|
||||
_authorizeCallbackPathName = securitySettings.Value.AuthorizeCallbackPathName;
|
||||
_authorizeCallbackLogoutPathName = securitySettings.Value.AuthorizeCallbackLogoutPathName;
|
||||
}
|
||||
|
||||
public async Task EnsureBackOfficeApplicationAsync(IEnumerable<Uri> backOfficeHosts, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Install is okay without this, because we do not need a token to install,
|
||||
// but upgrades do, so we need to execute for everything higher than or equal to upgrade.
|
||||
// but upgrades do, so we need to execute for everything higher then or equal to upgrade.
|
||||
if (_runtimeState.Level < RuntimeLevel.Upgrade)
|
||||
{
|
||||
return;
|
||||
@@ -71,18 +47,6 @@ public class BackOfficeApplicationManager : OpenIdDictApplicationManagerBase, IB
|
||||
throw new ArgumentException($"Expected absolute URLs, got: {string.Join(", ", backOfficeHostsAsArray.Select(url => url.ToString()))}", nameof(backOfficeHosts));
|
||||
}
|
||||
|
||||
// A balanced environment:
|
||||
// - has 2 or more dedicated CD (Content Delivery) servers - ServerRole.Subscriber
|
||||
// - has 1 or more dedicated CM (Content Management) servers - ServerRole.SchedulingPublisher
|
||||
// The CD and CM URLs are different for the backoffice and the individual servers do not start at the same time, for example:
|
||||
// CD www.domain.com/umbraco
|
||||
// CM cms.domain.com/umbraco
|
||||
// To access the Umbraco Backoffice, it is absolutely necessary to add the address of each server to the OpenId, as they share one database!
|
||||
// Destination table: umbracoOpenIddictApplications
|
||||
// Destination Fields: RedirectUris and PostLogoutRedirectUris
|
||||
// Read saved settings from DB and add unique additional servers.
|
||||
backOfficeHostsAsArray = await MergeWithExistingBackOfficeHostsAsync(backOfficeHostsAsArray, cancellationToken);
|
||||
|
||||
await CreateOrUpdate(
|
||||
BackofficeOpenIddictApplicationDescriptor(backOfficeHostsAsArray),
|
||||
cancellationToken);
|
||||
@@ -132,94 +96,6 @@ public class BackOfficeApplicationManager : OpenIdDictApplicationManagerBase, IB
|
||||
public async Task DeleteBackOfficeClientCredentialsApplicationAsync(string clientId, CancellationToken cancellationToken = default)
|
||||
=> await Delete(clientId, cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Merges new back-office hosts with existing hosts from the database.
|
||||
/// Uses OpenIddict API to read existing redirect URIs and extracts unique authorities (hosts).
|
||||
/// Handles invalid URIs gracefully by skipping them.
|
||||
/// </summary>
|
||||
/// <param name="newHosts">The new hosts to merge</param>
|
||||
/// <param name="cancellationToken">Cancellation token</param>
|
||||
/// <returns>Array of merged unique hosts (by authority, case-insensitive)</returns>
|
||||
private async Task<Uri[]> MergeWithExistingBackOfficeHostsAsync(Uri[] newHosts, CancellationToken cancellationToken)
|
||||
{
|
||||
// Find an existing back-office application
|
||||
var application = await ApplicationManager.FindByClientIdAsync(Constants.OAuthClientIds.BackOffice, cancellationToken);
|
||||
if (application is null)
|
||||
{
|
||||
// No existing application, return new hosts as-is
|
||||
return newHosts;
|
||||
}
|
||||
|
||||
// Get existing redirect URIs using OpenIddict API
|
||||
ImmutableArray<string> existingRedirectUris = await ApplicationManager.GetRedirectUrisAsync(application, cancellationToken);
|
||||
|
||||
// Use HashSet for O(n) performance and automatic deduplication
|
||||
// Case-insensitive comparison for authorities (host names)
|
||||
var mergedAuthorities = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
// Extract authorities from existing redirect URIs
|
||||
foreach (var existingUriString in existingRedirectUris)
|
||||
{
|
||||
if (TryGetAuthorityFromUriString(existingUriString, out var authority))
|
||||
{
|
||||
mergedAuthorities.Add(authority);
|
||||
}
|
||||
}
|
||||
|
||||
// Add new hosts' authorities
|
||||
foreach (Uri newHost in newHosts)
|
||||
{
|
||||
if (TryGetAuthorityFromUri(newHost, out var authority))
|
||||
{
|
||||
mergedAuthorities.Add(authority);
|
||||
}
|
||||
}
|
||||
|
||||
// Convert back to Uri array
|
||||
return mergedAuthorities.Select(authority => new Uri(authority)).ToArray();
|
||||
}
|
||||
|
||||
private bool TryGetAuthorityFromUri(Uri uri, [NotNullWhen(true)] out string? authority)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (uri.IsAbsoluteUri)
|
||||
{
|
||||
authority = uri.GetLeftPart(UriPartial.Authority);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
// GetLeftPart can throw InvalidOperationException for some URI types
|
||||
// Skip malformed URIs
|
||||
_logger.LogDebug("Could not extract authority from URI {Uri} as the left part could not be identified, skipping", uri);
|
||||
}
|
||||
|
||||
authority = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool TryGetAuthorityFromUriString(string uriString, [NotNullWhen(true)] out string? authority)
|
||||
{
|
||||
try
|
||||
{
|
||||
var existingUri = new Uri(uriString);
|
||||
if (TryGetAuthorityFromUri(existingUri, out authority))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (UriFormatException)
|
||||
{
|
||||
// Skip URIs with invalid format
|
||||
_logger.LogDebug("Could not extract authority from uriString {String} because of malformed uri format, skipping", uriString);
|
||||
}
|
||||
|
||||
authority = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
internal OpenIddictApplicationDescriptor BackofficeOpenIddictApplicationDescriptor(Uri backOfficeUrl)
|
||||
=> BackofficeOpenIddictApplicationDescriptor([backOfficeUrl]);
|
||||
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Models.ServerEvents;
|
||||
using Umbraco.Cms.Core.ServerEvents;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.ServerEvents;
|
||||
|
||||
@@ -14,59 +9,22 @@ internal sealed class ServerEventRouter : IServerEventRouter
|
||||
{
|
||||
private readonly IHubContext<ServerEventHub, IServerEventHub> _eventHub;
|
||||
private readonly IUserConnectionManager _connectionManager;
|
||||
private readonly IRuntimeState _runtimeState;
|
||||
private readonly ILogger<ServerEventRouter> _logger;
|
||||
|
||||
[Obsolete("Please use the constructor that takes all parameters. Scheduled for removal in Umbraco 18.")]
|
||||
public ServerEventRouter(
|
||||
IHubContext<ServerEventHub, IServerEventHub> eventHub,
|
||||
IUserConnectionManager connectionManager)
|
||||
: this(
|
||||
eventHub,
|
||||
connectionManager,
|
||||
StaticServiceProvider.Instance.GetRequiredService<IRuntimeState>(),
|
||||
StaticServiceProvider.Instance.GetRequiredService<ILogger<ServerEventRouter>>())
|
||||
{
|
||||
}
|
||||
|
||||
public ServerEventRouter(
|
||||
IHubContext<ServerEventHub, IServerEventHub> eventHub,
|
||||
IUserConnectionManager connectionManager,
|
||||
IRuntimeState runtimeState,
|
||||
ILogger<ServerEventRouter> logger)
|
||||
{
|
||||
_eventHub = eventHub;
|
||||
_connectionManager = connectionManager;
|
||||
_runtimeState = runtimeState;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task RouteEventAsync(ServerEvent serverEvent)
|
||||
{
|
||||
if (_runtimeState.Level != RuntimeLevel.Run)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await _eventHub.Clients.Group(serverEvent.EventSource).notify(serverEvent);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Failed to route server event {EventType} for {EventSource}", serverEvent.EventType, serverEvent.EventSource);
|
||||
}
|
||||
}
|
||||
public Task RouteEventAsync(ServerEvent serverEvent)
|
||||
=> _eventHub.Clients.Group(serverEvent.EventSource).notify(serverEvent);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task NotifyUserAsync(ServerEvent serverEvent, Guid userKey)
|
||||
{
|
||||
if (_runtimeState.Level != RuntimeLevel.Run)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ISet<string> userConnections = _connectionManager.GetConnections(userKey);
|
||||
|
||||
if (userConnections.Any() is false)
|
||||
@@ -74,31 +32,10 @@ internal sealed class ServerEventRouter : IServerEventRouter
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await _eventHub.Clients.Clients(userConnections).notify(serverEvent);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Failed to notify user {UserKey} of server event {EventType}", userKey, serverEvent.EventType);
|
||||
}
|
||||
await _eventHub.Clients.Clients(userConnections).notify(serverEvent);
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task BroadcastEventAsync(ServerEvent serverEvent)
|
||||
{
|
||||
if (_runtimeState.Level != RuntimeLevel.Run)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await _eventHub.Clients.All.notify(serverEvent);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Failed to broadcast server event {EventType}", serverEvent.EventType);
|
||||
}
|
||||
}
|
||||
public async Task BroadcastEventAsync(ServerEvent serverEvent) => await _eventHub.Clients.All.notify(serverEvent);
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ public interface IUserStartNodeEntitiesService
|
||||
/// <summary>
|
||||
/// Calculates the applicable child entities from a list of candidate child entities for users without root access.
|
||||
/// </summary>
|
||||
/// <param name="candidateChildren">The candidate child entities to filter (i.e. entities fetched with the EntityService's GetPagedChildren method).</param>
|
||||
/// <param name="candidateChildren">The candidate child entities to filter (i.e. entities fetched with <see cref="EntityService.GetPagedChildren"/>).</param>
|
||||
/// <param name="userStartNodePaths">The calculated start node paths for the user.</param>
|
||||
/// <returns>A list of child entities applicable entities for the user.</returns>
|
||||
/// <remarks>
|
||||
|
||||
@@ -142,8 +142,7 @@ public class UserStartNodeEntitiesService : IUserStartNodeEntitiesService
|
||||
}
|
||||
|
||||
// is ancestor of a start node?
|
||||
// Note: Add trailing comma to prevent false matches (e.g., path "-1,100" should not match "-1,1001")
|
||||
if (userStartNodePaths.Any(path => path.StartsWith($"{child.Path},")))
|
||||
if (userStartNodePaths.Any(path => path.StartsWith(child.Path)))
|
||||
{
|
||||
return new UserAccessEntity(child, false);
|
||||
}
|
||||
@@ -221,7 +220,5 @@ public class UserStartNodeEntitiesService : IUserStartNodeEntitiesService
|
||||
=> entities.Select(entity => new UserAccessEntity(entity, IsDescendantOrSelf(entity, userStartNodePaths))).ToArray();
|
||||
|
||||
private static bool IsDescendantOrSelf(IEntitySlim child, string[] userStartNodePaths)
|
||||
// Note: Add trailing commas to both paths to prevent false matches (e.g., path "-1,100" should not match "-1,1001")
|
||||
// This matches the pattern used in lines 92 and 192 of this file
|
||||
=> userStartNodePaths.Any(path => $"{child.Path},".StartsWith($"{path},"));
|
||||
=> userStartNodePaths.Any(path => child.Path.StartsWith(path));
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ internal class HasScheduleFlagProvider : IFlagProvider
|
||||
private readonly IIdKeyMap _keyMap;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HasScheduleFlagProvider"/> class.
|
||||
/// Initializes a new instance of the <see cref="HasScheduleSignProvider"/> class.
|
||||
/// </summary>
|
||||
public HasScheduleFlagProvider(IContentService contentService, IIdKeyMap keyMap)
|
||||
{
|
||||
|
||||
-91
@@ -1,91 +0,0 @@
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Actions;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Entities;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Services.PermissionFilter;
|
||||
|
||||
/// <summary>
|
||||
/// Provides functionality to filter document entities based on the current user's permissions.
|
||||
/// </summary>
|
||||
internal sealed class DocumentPermissionFilterService : IDocumentPermissionFilterService
|
||||
{
|
||||
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
|
||||
private readonly IUserService _userService;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DocumentPermissionFilterService"/> class.
|
||||
/// </summary>
|
||||
/// <param name="backOfficeSecurityAccessor">Provides access to the current backoffice user's security context.</param>
|
||||
/// <param name="userService">Service used to retrieve user and document permissions.</param>
|
||||
public DocumentPermissionFilterService(
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
|
||||
IUserService userService)
|
||||
{
|
||||
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
|
||||
_userService = userService;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<(IEntitySlim[] Entities, long TotalItems)> FilterAsync(IEntitySlim[] entities, long totalItems)
|
||||
{
|
||||
Dictionary<Guid, NodePermissions>? permissionsByNodeKey = await GetDocumentPermissionsByNodeKeyAsync(entities);
|
||||
if (permissionsByNodeKey is null)
|
||||
{
|
||||
return (entities, totalItems);
|
||||
}
|
||||
|
||||
IEntitySlim[] filteredEntities = FilterEntitiesWithBrowsePermission(entities, permissionsByNodeKey);
|
||||
var removedCount = entities.Length - filteredEntities.Length;
|
||||
|
||||
return (filteredEntities, totalItems - removedCount);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<(IEntitySlim[] Entities, long TotalBefore, long TotalAfter)> FilterAsync(Guid targetKey, IEntitySlim[] entities, long totalBefore, long totalAfter)
|
||||
{
|
||||
Dictionary<Guid, NodePermissions>? permissionsByNodeKey = await GetDocumentPermissionsByNodeKeyAsync(entities);
|
||||
if (permissionsByNodeKey is null)
|
||||
{
|
||||
return (entities, totalBefore, totalAfter);
|
||||
}
|
||||
|
||||
// Find the index of the target entity to determine before/after boundaries
|
||||
var targetIndex = Array.FindIndex(entities, e => e.Key == targetKey);
|
||||
|
||||
// Count removed entities before and after the target separately
|
||||
var removedBefore = entities.Take(targetIndex).Count(e => HasBrowsePermission(e, permissionsByNodeKey) is false);
|
||||
var removedAfter = entities.Skip(targetIndex + 1).Count(e => HasBrowsePermission(e, permissionsByNodeKey) is false);
|
||||
|
||||
IEntitySlim[] filteredEntities = FilterEntitiesWithBrowsePermission(entities, permissionsByNodeKey);
|
||||
|
||||
return (filteredEntities, totalBefore - removedBefore, totalAfter - removedAfter);
|
||||
}
|
||||
|
||||
private async Task<Dictionary<Guid, NodePermissions>?> GetDocumentPermissionsByNodeKeyAsync(IEntitySlim[] entities)
|
||||
{
|
||||
Guid userKey = CurrentUserKey();
|
||||
var entityKeys = entities.Select(e => e.Key).ToHashSet();
|
||||
|
||||
Attempt<IEnumerable<NodePermissions>, UserOperationStatus> permissionsAttempt =
|
||||
await _userService.GetDocumentPermissionsAsync(userKey, entityKeys);
|
||||
|
||||
return permissionsAttempt.Success
|
||||
? permissionsAttempt.Result.ToDictionary(p => p.NodeKey)
|
||||
: null;
|
||||
}
|
||||
|
||||
private Guid CurrentUserKey()
|
||||
=> _backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.Key
|
||||
?? throw new InvalidOperationException("No backoffice user found");
|
||||
|
||||
private static IEntitySlim[] FilterEntitiesWithBrowsePermission(IEntitySlim[] entities, Dictionary<Guid, NodePermissions> permissionsByNodeKey)
|
||||
=> entities.Where(e => HasBrowsePermission(e, permissionsByNodeKey)).ToArray();
|
||||
|
||||
private static bool HasBrowsePermission(IEntitySlim entity, Dictionary<Guid, NodePermissions> permissionsByNodeKey)
|
||||
=> permissionsByNodeKey.TryGetValue(entity.Key, out NodePermissions? nodePermissions) is false
|
||||
|| nodePermissions.Permissions.Contains(ActionBrowse.ActionLetter);
|
||||
}
|
||||
-27
@@ -1,27 +0,0 @@
|
||||
using Umbraco.Cms.Core.Models.Entities;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Services.PermissionFilter;
|
||||
|
||||
/// <summary>
|
||||
/// Service for filtering document entities based on user browse permissions.
|
||||
/// </summary>
|
||||
public interface IDocumentPermissionFilterService
|
||||
{
|
||||
/// <summary>
|
||||
/// Filters document entities based on the current user's browse permissions.
|
||||
/// </summary>
|
||||
/// <param name="entities">The entities to filter.</param>
|
||||
/// <param name="totalItems">The total number of items before filtering.</param>
|
||||
/// <returns>A tuple containing the filtered entities and the adjusted total items count.</returns>
|
||||
Task<(IEntitySlim[] Entities, long TotalItems)> FilterAsync(IEntitySlim[] entities, long totalItems);
|
||||
|
||||
/// <summary>
|
||||
/// Filters sibling document entities based on the current user's browse permissions.
|
||||
/// </summary>
|
||||
/// <param name="targetKey">The key of the target entity around which siblings are being retrieved.</param>
|
||||
/// <param name="entities">The entities to filter.</param>
|
||||
/// <param name="totalBefore">The total number of siblings before the target entity.</param>
|
||||
/// <param name="totalAfter">The total number of siblings after the target entity.</param>
|
||||
/// <returns>A tuple containing the filtered entities and the adjusted before/after counts.</returns>
|
||||
Task<(IEntitySlim[] Entities, long TotalBefore, long TotalAfter)> FilterAsync(Guid targetKey, IEntitySlim[] entities, long totalBefore, long totalAfter);
|
||||
}
|
||||
@@ -15,11 +15,12 @@
|
||||
[CS1998] remove async or make method synchronous
|
||||
[CS8524] switch statement
|
||||
[IDE0060] removed unused parameter
|
||||
[SA1649] file name match type
|
||||
[CS0419] ambiguous reference
|
||||
[CS1573] param tag for all parameters
|
||||
[CS1574] unresolveable cref
|
||||
-->
|
||||
<WarningsNotAsErrors>$(WarningsNotAsErrors),SA1117,SA1401,SA1134,CS0108,CS0618,CS9042,CS1998,CS8524,IDE0060,CS0419,CS1573,CS1574</WarningsNotAsErrors>
|
||||
<WarningsNotAsErrors>$(WarningsNotAsErrors),SA1117,SA1401,SA1134,CS0108,CS0618,CS9042,CS1998,CS8524,IDE0060,SA1649,CS0419,CS1573,CS1574</WarningsNotAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Umbraco.Cms.Api.Management.ViewModels.ContentType;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.ContentType;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.MediaType;
|
||||
|
||||
@@ -17,9 +17,4 @@ public class CreateMediaTypeRequestModel
|
||||
/// Gets or sets the compositions for this media type.
|
||||
/// </summary>
|
||||
public IEnumerable<MediaTypeComposition> Compositions { get; set; } = Enumerable.Empty<MediaTypeComposition>();
|
||||
|
||||
// TODO (V18): This is already declared on the base type, so for the next major, when we can allow a binary breaking change, we should remove it from here.
|
||||
#pragma warning disable CS0108 // Member hides inherited member; missing new keyword
|
||||
public ReferenceByIdModel? Collection { get; set; }
|
||||
#pragma warning restore CS0108 // Member hides inherited member; missing new keyword
|
||||
}
|
||||
|
||||
+5
-5
@@ -145,7 +145,7 @@ internal sealed class CollectibleRuntimeViewCompiler : IViewCompiler
|
||||
// release the lock first.
|
||||
cacheEntryOptions = new MemoryCacheEntryOptions();
|
||||
|
||||
Debug.Assert(item.ExpirationTokens != null, "Expiration tokens should not be null.");
|
||||
Debug.Assert(item.ExpirationTokens != null);
|
||||
for (var i = 0; i < item.ExpirationTokens.Count; i++)
|
||||
{
|
||||
cacheEntryOptions.ExpirationTokens.Add(item.ExpirationTokens[i]);
|
||||
@@ -159,7 +159,7 @@ internal sealed class CollectibleRuntimeViewCompiler : IViewCompiler
|
||||
else
|
||||
{
|
||||
// If we can't compile, we should have already created the descriptor
|
||||
Debug.Assert(item.Descriptor != null, "Descriptor should not be null when compilation is not supported.");
|
||||
Debug.Assert(item.Descriptor != null);
|
||||
taskSource.SetResult(item.Descriptor);
|
||||
}
|
||||
|
||||
@@ -169,13 +169,13 @@ internal sealed class CollectibleRuntimeViewCompiler : IViewCompiler
|
||||
// Now the lock has been released so we can do more expensive processing.
|
||||
if (item.SupportsCompilation)
|
||||
{
|
||||
Debug.Assert(taskSource != null, "Task source should not be null when compilation is supported.");
|
||||
Debug.Assert(taskSource != null);
|
||||
|
||||
if (item.Descriptor?.Item != null &&
|
||||
ChecksumValidator.IsItemValid(_projectEngine.FileSystem, item.Descriptor.Item))
|
||||
{
|
||||
// If the item has checksums to validate, we should also have a precompiled view.
|
||||
Debug.Assert(item.Descriptor != null, "Descriptor should not be null when checksums are valid.");
|
||||
Debug.Assert(item.Descriptor != null);
|
||||
|
||||
taskSource.SetResult(item.Descriptor);
|
||||
return taskSource.Task;
|
||||
@@ -426,7 +426,7 @@ internal sealed class CollectibleRuntimeViewCompiler : IViewCompiler
|
||||
|
||||
private string GetNormalizedPath(string relativePath)
|
||||
{
|
||||
Debug.Assert(relativePath != null, "Relative path should not be null.");
|
||||
Debug.Assert(relativePath != null);
|
||||
if (relativePath.Length == 0)
|
||||
{
|
||||
return relativePath;
|
||||
|
||||
+4
-4
@@ -1,4 +1,4 @@
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
@@ -77,9 +77,9 @@ internal class CompilationOptionsProvider
|
||||
_optionsInitialized = true;
|
||||
}
|
||||
|
||||
Debug.Assert(_parseOptions is not null, "Parse options should be initialized.");
|
||||
Debug.Assert(_compilationOptions is not null, "Compilation options should be initialized.");
|
||||
Debug.Assert(_emitOptions is not null, "Emit options should be initialized.");
|
||||
Debug.Assert(_parseOptions is not null);
|
||||
Debug.Assert(_compilationOptions is not null);
|
||||
Debug.Assert(_emitOptions is not null);
|
||||
}
|
||||
|
||||
private DependencyContextCompilationOptions GetDependencyContextCompilationOptions()
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<Description>Adds imaging support using ImageSharp/ImageSharp.Web version 2 to Umbraco CMS.</Description>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SixLabors.ImageSharp" VersionOverride="[2.1.13, 3)" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" VersionOverride="[2.1.11, 3)" />
|
||||
<PackageReference Include="SixLabors.ImageSharp.Web" VersionOverride="[2.0.2, 3)" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -5,12 +5,8 @@ using Umbraco.Cms.Persistence.EFCore.Migrations;
|
||||
|
||||
namespace Umbraco.Cms.Persistence.EFCore.SqlServer;
|
||||
|
||||
/// <summary>
|
||||
/// Composer for registering SQL Server EF Core migration services.
|
||||
/// </summary>
|
||||
public class EFCoreSqlServerComposer : IComposer
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public void Compose(IUmbracoBuilder builder)
|
||||
{
|
||||
builder.Services.AddSingleton<IMigrationProvider, SqlServerMigrationProvider>();
|
||||
|
||||
@@ -4,30 +4,20 @@ using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Persistence.EFCore.SqlServer;
|
||||
|
||||
/// <summary>
|
||||
/// Provides SQL Server-specific migration functionality for Umbraco's EF Core database context.
|
||||
/// </summary>
|
||||
public class SqlServerMigrationProvider : IMigrationProvider
|
||||
{
|
||||
private readonly IDbContextFactory<UmbracoDbContext> _dbContextFactory;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SqlServerMigrationProvider"/> class.
|
||||
/// </summary>
|
||||
/// <param name="dbContextFactory">The factory for creating database context instances.</param>
|
||||
public SqlServerMigrationProvider(IDbContextFactory<UmbracoDbContext> dbContextFactory) => _dbContextFactory = dbContextFactory;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string ProviderName => Constants.ProviderNames.SQLServer;
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task MigrateAsync(EFCoreMigration migration)
|
||||
{
|
||||
UmbracoDbContext context = await _dbContextFactory.CreateDbContextAsync();
|
||||
await context.MigrateDatabaseAsync(GetMigrationType(migration));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task MigrateAllAsync()
|
||||
{
|
||||
UmbracoDbContext context = await _dbContextFactory.CreateDbContextAsync();
|
||||
|
||||
@@ -4,15 +4,10 @@ using Umbraco.Cms.Persistence.EFCore.Migrations;
|
||||
|
||||
namespace Umbraco.Cms.Persistence.EFCore.SqlServer;
|
||||
|
||||
/// <summary>
|
||||
/// Configures the EF Core DbContext to use SQL Server as the database provider.
|
||||
/// </summary>
|
||||
public class SqlServerMigrationProviderSetup : IMigrationProviderSetup
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string ProviderName => Constants.ProviderNames.SQLServer;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Setup(DbContextOptionsBuilder builder, string? connectionString)
|
||||
{
|
||||
builder.UseSqlServer(connectionString, x => x.MigrationsAssembly(GetType().Assembly.FullName));
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user