Compare commits
262
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9ab0abca99 | ||
|
|
009a999e22 | ||
|
|
d3c1443b14 | ||
|
|
8a9db11422 | ||
|
|
d9fb6df16e | ||
|
|
14ed3348bf | ||
|
|
9e7a36865a | ||
|
|
db1d999721 | ||
|
|
18047a7cfb | ||
|
|
def7ebd48c | ||
|
|
8c2b1ebdc5 | ||
|
|
9227517a50 | ||
|
|
5322d0f7b5 | ||
|
|
a282cc5691 | ||
|
|
048f8bcdf9 | ||
|
|
7bcbc748d4 | ||
|
|
f30e6cfe7f | ||
|
|
026e80e026 | ||
|
|
a3b77cff63 | ||
|
|
4ca68d6995 | ||
|
|
280cb7f2b1 | ||
|
|
095a73132c | ||
|
|
cfb0fc23ac | ||
|
|
6620aca9fe | ||
|
|
2422a02b23 | ||
|
|
b4a9dc0770 | ||
|
|
e7411244fd | ||
|
|
9f357173c3 | ||
|
|
7dc6e3ed5b | ||
|
|
5dfff212d0 | ||
|
|
17615f966b | ||
|
|
7a2d6b6c63 | ||
|
|
343867f6ee | ||
|
|
b9837ac77c | ||
|
|
bb73ec6c68 | ||
|
|
ee2d7bbb1b | ||
|
|
5d48bc7371 | ||
|
|
59a4649528 | ||
|
|
f54b6033ab | ||
|
|
bf340cd7d4 | ||
|
|
313417cb91 | ||
|
|
7850078623 | ||
|
|
44bf3b77b3 | ||
|
|
8485458896 | ||
|
|
3d253f5f06 | ||
|
|
64287d7e1a | ||
|
|
edc78a5a4c | ||
|
|
8056a59a10 | ||
|
|
1a18d6c035 | ||
|
|
3e877182b5 | ||
|
|
0e4f883bc1 | ||
|
|
265cc62ab8 | ||
|
|
800c16970e | ||
|
|
6bd11bf233 | ||
|
|
a2fd82a3f3 | ||
|
|
56c0aebd76 | ||
|
|
7567990da1 | ||
|
|
c7d157bbfd | ||
|
|
4b9771c0eb | ||
|
|
abc312c9b4 | ||
|
|
2161edb871 | ||
|
|
e77e9c5691 | ||
|
|
e934a943b5 | ||
|
|
65bb2801b0 | ||
|
|
a0a4af6a0c | ||
|
|
e8d6cded2b | ||
|
|
c65204a146 | ||
|
|
95eb58587b | ||
|
|
86f3033334 | ||
|
|
25628a8b76 | ||
|
|
7552e315fb | ||
|
|
d15b5b221e | ||
|
|
60e3fa97a9 | ||
|
|
fd1bd51aff | ||
|
|
b12620af9c | ||
|
|
d1af59fdf0 | ||
|
|
1b050ebc47 | ||
|
|
ff3e457c58 | ||
|
|
2ffaede07b | ||
|
|
2a83f57262 | ||
|
|
5be9193978 | ||
|
|
d3f264fc54 | ||
|
|
04ea002bf3 | ||
|
|
8f42a3a0ea | ||
|
|
4dd8fbe25d | ||
|
|
ee727c43e1 | ||
|
|
395213946b | ||
|
|
ed0b236208 | ||
|
|
8d56e75257 | ||
|
|
b3c27415d1 | ||
|
|
9839b0ee66 | ||
|
|
f3f57f9748 | ||
|
|
85d16a4354 | ||
|
|
c0517c3562 | ||
|
|
92e535a1c9 | ||
|
|
7411c2552b | ||
|
|
f2410f376e | ||
|
|
a9313c1367 | ||
|
|
58d6404ec8 | ||
|
|
53a5813189 | ||
|
|
5b3b9d770a | ||
|
|
4a54215fa1 | ||
|
|
52fbb34ddd | ||
|
|
c96dc7eaa1 | ||
|
|
0166727eee | ||
|
|
f19409ba5b | ||
|
|
679c42ef90 | ||
|
|
77b6e6e6d9 | ||
|
|
32d0cb477e | ||
|
|
d451390d78 | ||
|
|
3304c6f472 | ||
|
|
34f09fa260 | ||
|
|
fd4efd38cd | ||
|
|
e4e9bfc3e9 | ||
|
|
3774dbe38c | ||
|
|
4ef50642ad | ||
|
|
cb88cbba43 | ||
|
|
f95fb78022 | ||
|
|
0f9a8bde61 | ||
|
|
129d369c1a | ||
|
|
dd2c591a5b | ||
|
|
e9758321ce | ||
|
|
82e58eae3e | ||
|
|
de78560343 | ||
|
|
f569553d79 | ||
|
|
16749a724d | ||
|
|
a627930b58 | ||
|
|
404a62aa0b | ||
|
|
2d9cfc880b | ||
|
|
ee8bdfc4ac | ||
|
|
f048cfe62d | ||
|
|
9cab0e8bb1 | ||
|
|
b55d484863 | ||
|
|
ed0058a503 | ||
|
|
21e3518a69 | ||
|
|
b0aed393d6 | ||
|
|
28756d449b | ||
|
|
38a96db369 | ||
|
|
a1ea203281 | ||
|
|
4590739fa5 | ||
|
|
7c617f2976 | ||
|
|
9141f61708 | ||
|
|
ba4120050f | ||
|
|
772c523a23 | ||
|
|
cbd4dc6e0d | ||
|
|
c7014e159b | ||
|
|
9febbc7db1 | ||
|
|
ec8e10f406 | ||
|
|
3233b24a4e | ||
|
|
5775e9cb18 | ||
|
|
570005f5e1 | ||
|
|
1e9182cfa4 | ||
|
|
fbaa3440f2 | ||
|
|
e662468ecc | ||
|
|
33eb4dd5a4 | ||
|
|
c8efc30654 | ||
|
|
d7b98a2f00 | ||
|
|
9edd21a3b0 | ||
|
|
7010ff1d26 | ||
|
|
6acdf21eab | ||
|
|
a5479bc96e | ||
|
|
79d117026f | ||
|
|
2709054bdb | ||
|
|
0acdd26850 | ||
|
|
2d4230c001 | ||
|
|
2d027ce9a1 | ||
|
|
b1d9085c83 | ||
|
|
41da2e2cf3 | ||
|
|
76fcf19b15 | ||
|
|
11ccafeb97 | ||
|
|
aa9f194d76 | ||
|
|
73d70ba9fc | ||
|
|
6435ff0bb3 | ||
|
|
f7729508cf | ||
|
|
31e1acce67 | ||
|
|
aa1f3df76b | ||
|
|
728dc89909 | ||
|
|
e36dc1f554 | ||
|
|
3431f76320 | ||
|
|
1bc5466a8d | ||
|
|
d83daf44d3 | ||
|
|
2ad5ecd5d5 | ||
|
|
35c51a029a | ||
|
|
8c1128c85b | ||
|
|
c9021ab2d2 | ||
|
|
67a71f8f82 | ||
|
|
6caf53ed2e | ||
|
|
97d74f39e7 | ||
|
|
edd0a4a4a9 | ||
|
|
6399f235f2 | ||
|
|
8a22672c7f | ||
|
|
11270eaaf5 | ||
|
|
1e32d59ecb | ||
|
|
4be183a20d | ||
|
|
eab27123dd | ||
|
|
a6253957c8 | ||
|
|
a8f5631144 | ||
|
|
ba1080541b | ||
|
|
1581eb61d3 | ||
|
|
6d98162e19 | ||
|
|
ee37ad0f4b | ||
|
|
2b3a91757d | ||
|
|
00563013b6 | ||
|
|
378d4ecfef | ||
|
|
a497f3f4dd | ||
|
|
2d71b5a63b | ||
|
|
f4f83bccbe | ||
|
|
5183391a86 | ||
|
|
0551976126 | ||
|
|
da2a4d1713 | ||
|
|
9f5867bdf8 | ||
|
|
30b114d538 | ||
|
|
c3db3457e7 | ||
|
|
7787af2df1 | ||
|
|
3730109cce | ||
|
|
b814608c06 | ||
|
|
ed63b51e46 | ||
|
|
ea608cc630 | ||
|
|
4cfe498628 | ||
|
|
42912dd5c9 | ||
|
|
9bab74d30e | ||
|
|
865787db1d | ||
|
|
c58a7b7b23 | ||
|
|
32c00f9a09 | ||
|
|
348f1f2bae | ||
|
|
73c8d8e0f9 | ||
|
|
2d7c00f27f | ||
|
|
4fae91d55c | ||
|
|
6c08f69801 | ||
|
|
910fb4062b | ||
|
|
f2e4e44db1 | ||
|
|
d57d12d54d | ||
|
|
46604909be | ||
|
|
9a12eea495 | ||
|
|
762d72b018 | ||
|
|
45f43a6b7a | ||
|
|
c9c9374de1 | ||
|
|
6bd558c016 | ||
|
|
f1cddd91c6 | ||
|
|
fdb9cfa3e7 | ||
|
|
63a487bc0b | ||
|
|
a40eadcfce | ||
|
|
8f26263178 | ||
|
|
9b19d63a6a | ||
|
|
99125ef5cc | ||
|
|
ac575662ac | ||
|
|
6939472f37 | ||
|
|
0387307301 | ||
|
|
25a5dde8e9 | ||
|
|
2e068bb73e | ||
|
|
842cacde19 | ||
|
|
6a453a0910 | ||
|
|
ff9903b14d | ||
|
|
d64bf5de22 | ||
|
|
9300d867fb | ||
|
|
eb0f8b5c24 | ||
|
|
d58768f26d | ||
|
|
3667217053 | ||
|
|
3e6116fcba | ||
|
|
bff293213d | ||
|
|
a47a1775f2 | ||
|
|
b2a2df7a8a |
@@ -0,0 +1,3 @@
|
||||
[submodule "src/Umbraco.Web.UI.Client"]
|
||||
path = src/Umbraco.Web.UI.Client
|
||||
url = https://github.com/umbraco/Umbraco.CMS.Backoffice.git
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Company>Umbraco HQ</Company>
|
||||
<Authors>Umbraco</Authors>
|
||||
<Copyright>Copyright © Umbraco $([System.DateTime]::Today.ToString('yyyy'))</Copyright>
|
||||
@@ -15,9 +15,6 @@
|
||||
<Nullable>enable</Nullable>
|
||||
<WarningsAsErrors>nullable</WarningsAsErrors>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<!-- TODO: [NU5104] Warning As Error: A stable release of a package should not have a prerelease dependency. Either modify the version spec of dependency -->
|
||||
<NoWarn>$(NoWarn);NU5104</NoWarn>
|
||||
<WarningsNotAsErrors>$(WarningsNotAsErrors);NU5104</WarningsNotAsErrors>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<WarnOnPackingNonPackableProject>false</WarnOnPackingNonPackableProject>
|
||||
@@ -33,7 +30,7 @@
|
||||
<!-- Package Validation -->
|
||||
<PropertyGroup>
|
||||
<GenerateCompatibilitySuppressionFile>false</GenerateCompatibilitySuppressionFile>
|
||||
<EnablePackageValidation>false</EnablePackageValidation>
|
||||
<EnablePackageValidation>true</EnablePackageValidation>
|
||||
<PackageValidationBaselineVersion>14.0.0</PackageValidationBaselineVersion>
|
||||
<EnableStrictModeForCompatibleFrameworksInPackage>true</EnableStrictModeForCompatibleFrameworksInPackage>
|
||||
<EnableStrictModeForCompatibleTfms>true</EnableStrictModeForCompatibleTfms>
|
||||
|
||||
+41
-47
@@ -5,35 +5,35 @@
|
||||
</PropertyGroup>
|
||||
<!-- Global packages (private, build-time packages for all projects) -->
|
||||
<ItemGroup>
|
||||
<GlobalPackageReference Include="Nerdbank.GitVersioning" Version="3.6.146" />
|
||||
<GlobalPackageReference Include="Nerdbank.GitVersioning" Version="3.6.139" />
|
||||
<GlobalPackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556" />
|
||||
<GlobalPackageReference Include="Umbraco.Code" Version="2.2.0" />
|
||||
<GlobalPackageReference Include="Umbraco.GitVersioning.Extensions" Version="0.2.0" />
|
||||
</ItemGroup>
|
||||
<!-- Microsoft packages -->
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="9.0.0" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.11" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="8.0.11" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.10.0" />
|
||||
<PackageVersion Include="Microsoft.Data.Sqlite" Version="9.0.0" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.0" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.0" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.10" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="9.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="9.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="9.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.FileProviders.Embedded" Version="9.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.FileProviders.Physical" Version="9.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Http" Version="9.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Identity.Core" Version="9.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Identity.Stores" Version="9.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Options" Version="9.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="9.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Options.DataAnnotations" Version="9.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Hybrid" Version="9.0.0-preview.9.24556.5" />
|
||||
<PackageVersion Include="Microsoft.Data.Sqlite" Version="8.0.11" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.11" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.11" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.11" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="8.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.FileProviders.Embedded" Version="8.0.11" />
|
||||
<PackageVersion Include="Microsoft.Extensions.FileProviders.Physical" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Http" Version="8.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Identity.Core" Version="8.0.11" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Identity.Stores" Version="8.0.11" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging" Version="8.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Options" Version="8.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Options.DataAnnotations" Version="8.0.0" />
|
||||
</ItemGroup>
|
||||
<!-- Umbraco packages -->
|
||||
<ItemGroup>
|
||||
@@ -45,8 +45,8 @@
|
||||
<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.4.0" />
|
||||
<PackageVersion Include="Examine.Core" Version="3.4.0" />
|
||||
<PackageVersion Include="Examine" Version="3.5.0" />
|
||||
<PackageVersion Include="Examine.Core" Version="3.5.0" />
|
||||
<PackageVersion Include="HtmlAgilityPack" Version="1.11.71" />
|
||||
<PackageVersion Include="JsonPatch.Net" Version="3.1.1" />
|
||||
<PackageVersion Include="K4os.Compression.LZ4" Version="1.3.8" />
|
||||
@@ -58,44 +58,38 @@
|
||||
<PackageVersion Include="ncrontab" Version="3.3.3" />
|
||||
<PackageVersion Include="NPoco" Version="5.7.1" />
|
||||
<PackageVersion Include="NPoco.SqlServer" Version="5.7.1" />
|
||||
<PackageVersion Include="OpenIddict.Abstractions" Version="6.0.0-preview3.24551.41" />
|
||||
<PackageVersion Include="OpenIddict.AspNetCore" Version="6.0.0-preview3.24551.41" />
|
||||
<PackageVersion Include="OpenIddict.EntityFrameworkCore" Version="6.0.0-preview3.24551.41" />
|
||||
<PackageVersion Include="Serilog" Version="4.1.0" />
|
||||
<PackageVersion Include="OpenIddict.Abstractions" Version="5.7.0" />
|
||||
<PackageVersion Include="OpenIddict.AspNetCore" Version="5.7.0" />
|
||||
<PackageVersion Include="OpenIddict.EntityFrameworkCore" Version="5.7.0" />
|
||||
<PackageVersion Include="Serilog" Version="3.1.1" />
|
||||
<PackageVersion Include="Serilog.AspNetCore" Version="8.0.3" />
|
||||
<PackageVersion Include="Serilog.Enrichers.Process" Version="3.0.0" />
|
||||
<PackageVersion Include="Serilog.Enrichers.Thread" Version="4.0.0" />
|
||||
<PackageVersion Include="Serilog.Expressions" Version="5.0.0" />
|
||||
<PackageVersion Include="Serilog.Enrichers.Process" Version="2.0.2" />
|
||||
<PackageVersion Include="Serilog.Enrichers.Thread" Version="3.1.0" />
|
||||
<PackageVersion Include="Serilog.Expressions" Version="4.0.0" />
|
||||
<PackageVersion Include="Serilog.Extensions.Hosting" Version="8.0.0" />
|
||||
<PackageVersion Include="Serilog.Formatting.Compact" Version="3.0.0" />
|
||||
<PackageVersion Include="Serilog.Formatting.Compact.Reader" Version="4.0.0" />
|
||||
<PackageVersion Include="Serilog.Formatting.Compact" Version="2.0.0" />
|
||||
<PackageVersion Include="Serilog.Formatting.Compact.Reader" Version="3.0.0" />
|
||||
<PackageVersion Include="Serilog.Settings.Configuration" Version="8.0.4" />
|
||||
<PackageVersion Include="Serilog.Sinks.Async" Version="2.1.0" />
|
||||
<PackageVersion Include="Serilog.Sinks.File" Version="6.0.0" />
|
||||
<PackageVersion Include="Serilog.Sinks.Map" Version="2.0.0" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.5" />
|
||||
<PackageVersion Include="Serilog.Sinks.Async" Version="1.5.0" />
|
||||
<PackageVersion Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||
<PackageVersion Include="Serilog.Sinks.Map" Version="1.0.2" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.7" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp.Web" Version="3.1.3" />
|
||||
<PackageVersion Include="Swashbuckle.AspNetCore" Version="7.0.0" />
|
||||
<PackageVersion Include="Swashbuckle.AspNetCore" Version="6.9.0" />
|
||||
</ItemGroup>
|
||||
<!-- Transitive pinned versions (only required because our direct dependencies have vulnerable versions of transitive dependencies) -->
|
||||
<ItemGroup>
|
||||
<!-- Both Microsoft.EntityFrameworkCore.SqlServer and NPoco.SqlServer bring in a vulnerable version of Azure.Identity -->
|
||||
<PackageVersion Include="Azure.Identity" Version="1.13.1" />
|
||||
<!-- Both Microsoft.EntityFrameworkCore.SqlServer and NPoco.SqlServer bring in a vulnerable version of Azure.Identity -->
|
||||
<PackageVersion Include="System.Runtime.Caching" Version="9.0.0" />
|
||||
<!-- Dazinator.Extensions.FileProviders brings in a vulnerable version of System.Net.Http -->
|
||||
<PackageVersion Include="System.Net.Http" Version="4.3.4" />
|
||||
<!-- Examine brings in a vulnerable version of System.Security.Cryptography.Xml -->
|
||||
<PackageVersion Include="System.Security.Cryptography.Xml" Version="9.0.0" />
|
||||
<PackageVersion Include="System.Security.Cryptography.Xml" Version="8.0.2" />
|
||||
<!-- Both Dazinator.Extensions.FileProviders and MiniProfiler.AspNetCore.Mvc bring in a vulnerable version of System.Text.RegularExpressions -->
|
||||
<PackageVersion Include="System.Text.RegularExpressions" Version="4.3.1" />
|
||||
<!-- Both OpenIddict.AspNetCore, Npoco.SqlServer and Microsoft.EntityFrameworkCore.SqlServer bring in a vulnerable version of Microsoft.IdentityModel.JsonWebTokens -->
|
||||
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.2.0" />
|
||||
<!-- Both Azure.Identity, Microsoft.EntityFrameworkCore.SqlServer, Dazinator.Extensions.FileProviders bring in legacy versions of System.Text.Encodings.Web -->
|
||||
<PackageVersion Include="System.Text.Encodings.Web" Version="9.0.0" />
|
||||
<!-- NPoco.SqlServer bring in vulnerable version of Microsoft.Data.SqlClient -->
|
||||
<PackageVersion Include="Microsoft.Data.SqlClient" Version="5.2.2" />
|
||||
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="7.7.1" />
|
||||
<!-- Examine.Lucene bring in a vulnerable version of Lucene.Net.Replicator -->
|
||||
<PackageVersion Include="Lucene.Net.Replicator" Version="4.8.0-beta00017" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
@@ -5,6 +5,10 @@ parameters:
|
||||
displayName: Run SQL Server Integration Tests
|
||||
type: boolean
|
||||
default: false
|
||||
- name: sqlServerLinuxAcceptanceTests
|
||||
displayName: Run SQL Server Linux Acceptance Tests
|
||||
type: boolean
|
||||
default: false
|
||||
- name: myGetDeploy
|
||||
displayName: Deploy to MyGet
|
||||
type: boolean
|
||||
@@ -284,12 +288,11 @@ stages:
|
||||
jobs:
|
||||
# Integration Tests (SQLite)
|
||||
- job:
|
||||
timeoutInMinutes: 180
|
||||
displayName: Integration Tests (SQLite)
|
||||
strategy:
|
||||
matrix:
|
||||
# Windows:
|
||||
# vmImage: 'windows-latest'
|
||||
Windows:
|
||||
vmImage: 'windows-latest'
|
||||
Linux:
|
||||
vmImage: 'ubuntu-latest'
|
||||
macOS:
|
||||
@@ -503,6 +506,10 @@ stages:
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
|
||||
workingDirectory: $(Agent.BuildDirectory)/app
|
||||
|
||||
# Ensures we have the package wait-on installed
|
||||
- pwsh: npm install wait-on
|
||||
displayName: Install wait-on package
|
||||
|
||||
# Wait for application to start responding to requests
|
||||
- pwsh: npx wait-on -v --interval 1000 --timeout 120000 $(ASPNETCORE_URLS)
|
||||
displayName: Wait for application
|
||||
@@ -556,10 +563,11 @@ stages:
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN_PROVIDERNAME: Microsoft.Data.SqlClient
|
||||
strategy:
|
||||
matrix:
|
||||
Linux:
|
||||
vmImage: 'ubuntu-latest'
|
||||
SA_PASSWORD: $(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN: 'Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=True'
|
||||
${{ if eq(parameters.sqlServerLinuxAcceptanceTests, True) }} :
|
||||
Linux:
|
||||
vmImage: 'ubuntu-latest'
|
||||
SA_PASSWORD: $(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN: 'Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=True'
|
||||
Windows:
|
||||
vmImage: 'windows-latest'
|
||||
pool:
|
||||
|
||||
@@ -3,13 +3,12 @@ name: Nightly_E2E_Test_$(TeamProject)_$(Build.DefinitionName)_$(SourceBranchName
|
||||
pr: none
|
||||
trigger: none
|
||||
|
||||
schedules:
|
||||
- cron: '0 0 * * *'
|
||||
displayName: Daily midnight build
|
||||
branches:
|
||||
include:
|
||||
- v14/dev
|
||||
- v15/dev
|
||||
# schedules:
|
||||
# - cron: '0 0 * * *'
|
||||
# displayName: Daily midnight build
|
||||
# branches:
|
||||
# include:
|
||||
# - v14/dev
|
||||
|
||||
variables:
|
||||
nodeVersion: 20
|
||||
|
||||
+2
-3
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"sdk": {
|
||||
"version": "9.0.100",
|
||||
"rollForward": "latestFeature",
|
||||
"allowPrerelease": false
|
||||
"version": "8.0.300",
|
||||
"rollForward": "latestFeature"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ using Microsoft.Extensions.Options;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
using Umbraco.Cms.Api.Common.OpenApi;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Api.Common.Configuration;
|
||||
@@ -15,23 +14,22 @@ public class ConfigureUmbracoSwaggerGenOptions : IConfigureOptions<SwaggerGenOpt
|
||||
{
|
||||
private readonly IOperationIdSelector _operationIdSelector;
|
||||
private readonly ISchemaIdSelector _schemaIdSelector;
|
||||
private readonly ISubTypesSelector _subTypesSelector;
|
||||
|
||||
[Obsolete("Use non-obsolete constructor. This will be removed in Umbraco 16.")]
|
||||
[Obsolete("Use non-obsolete constructor. This will be removed in Umbraco 15.")]
|
||||
public ConfigureUmbracoSwaggerGenOptions(
|
||||
IOptions<ApiVersioningOptions> apiVersioningOptions,
|
||||
IOperationIdSelector operationIdSelector,
|
||||
ISchemaIdSelector schemaIdSelector)
|
||||
: this(operationIdSelector, schemaIdSelector)
|
||||
{
|
||||
}
|
||||
|
||||
public ConfigureUmbracoSwaggerGenOptions(
|
||||
IOperationIdSelector operationIdSelector,
|
||||
ISchemaIdSelector schemaIdSelector)
|
||||
: this(operationIdSelector, schemaIdSelector, StaticServiceProvider.Instance.GetRequiredService<ISubTypesSelector>())
|
||||
{ }
|
||||
|
||||
public ConfigureUmbracoSwaggerGenOptions(
|
||||
IOperationIdSelector operationIdSelector,
|
||||
ISchemaIdSelector schemaIdSelector,
|
||||
ISubTypesSelector subTypesSelector)
|
||||
{
|
||||
_operationIdSelector = operationIdSelector;
|
||||
_schemaIdSelector = schemaIdSelector;
|
||||
_subTypesSelector = subTypesSelector;
|
||||
}
|
||||
|
||||
public void Configure(SwaggerGenOptions swaggerGenOptions)
|
||||
@@ -64,7 +62,6 @@ public class ConfigureUmbracoSwaggerGenOptions : IConfigureOptions<SwaggerGenOpt
|
||||
swaggerGenOptions.OrderActionsBy(ActionOrderBy);
|
||||
swaggerGenOptions.SchemaFilter<EnumSchemaFilter>();
|
||||
swaggerGenOptions.CustomSchemaIds(_schemaIdSelector.SchemaId);
|
||||
swaggerGenOptions.SelectSubTypesUsing(_subTypesSelector.SubTypes);
|
||||
swaggerGenOptions.SupportNonNullableReferenceTypes();
|
||||
}
|
||||
|
||||
|
||||
@@ -23,8 +23,6 @@ public static class UmbracoBuilderApiExtensions
|
||||
builder.Services.AddSingleton<IOperationIdHandler, OperationIdHandler>();
|
||||
builder.Services.AddSingleton<ISchemaIdSelector, SchemaIdSelector>();
|
||||
builder.Services.AddSingleton<ISchemaIdHandler, SchemaIdHandler>();
|
||||
builder.Services.AddSingleton<ISubTypesSelector, SubTypesSelector>();
|
||||
builder.Services.AddSingleton<ISubTypesHandler, SubTypesHandler>();
|
||||
builder.Services.Configure<UmbracoPipelineOptions>(options => options.AddFilter(new SwaggerRouteTemplatePipelineFilter("UmbracoApiCommon")));
|
||||
|
||||
return builder;
|
||||
|
||||
@@ -41,12 +41,14 @@ public static class UmbracoBuilderAuthExtensions
|
||||
.SetTokenEndpointUris(
|
||||
Paths.MemberApi.TokenEndpoint.TrimStart(Constants.CharArrays.ForwardSlash),
|
||||
Paths.BackOfficeApi.TokenEndpoint.TrimStart(Constants.CharArrays.ForwardSlash))
|
||||
.SetEndSessionEndpointUris(
|
||||
.SetLogoutEndpointUris(
|
||||
Paths.MemberApi.LogoutEndpoint.TrimStart(Constants.CharArrays.ForwardSlash),
|
||||
Paths.BackOfficeApi.LogoutEndpoint.TrimStart(Constants.CharArrays.ForwardSlash))
|
||||
.SetRevocationEndpointUris(
|
||||
Paths.MemberApi.RevokeEndpoint.TrimStart(Constants.CharArrays.ForwardSlash),
|
||||
Paths.BackOfficeApi.RevokeEndpoint.TrimStart(Constants.CharArrays.ForwardSlash));
|
||||
Paths.BackOfficeApi.RevokeEndpoint.TrimStart(Constants.CharArrays.ForwardSlash))
|
||||
.SetUserinfoEndpointUris(
|
||||
Paths.MemberApi.UserinfoEndpoint.TrimStart(Constants.CharArrays.ForwardSlash));
|
||||
|
||||
// Enable authorization code flow with PKCE
|
||||
options
|
||||
@@ -54,15 +56,12 @@ public static class UmbracoBuilderAuthExtensions
|
||||
.RequireProofKeyForCodeExchange()
|
||||
.AllowRefreshTokenFlow();
|
||||
|
||||
// Enable the client credentials flow.
|
||||
options.AllowClientCredentialsFlow();
|
||||
|
||||
// Register the ASP.NET Core host and configure for custom authentication endpoint.
|
||||
options
|
||||
.UseAspNetCore()
|
||||
.EnableAuthorizationEndpointPassthrough()
|
||||
.EnableTokenEndpointPassthrough()
|
||||
.EnableEndSessionEndpointPassthrough();
|
||||
.EnableLogoutEndpointPassthrough()
|
||||
.EnableUserinfoEndpointPassthrough();
|
||||
|
||||
// Enable reference tokens
|
||||
// - see https://documentation.openiddict.com/configuration/token-storage.html
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
namespace Umbraco.Cms.Api.Common.OpenApi;
|
||||
|
||||
public interface ISubTypesHandler
|
||||
{
|
||||
bool CanHandle(Type type, string documentName);
|
||||
|
||||
IEnumerable<Type> Handle(Type type);
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
namespace Umbraco.Cms.Api.Common.OpenApi;
|
||||
|
||||
public interface ISubTypesSelector
|
||||
{
|
||||
IEnumerable<Type> SubTypes(Type type);
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
|
||||
namespace Umbraco.Cms.Api.Common.OpenApi;
|
||||
|
||||
/// <summary>
|
||||
/// This filter explicitly removes all security schemes from a named OpenAPI document.
|
||||
/// </summary>
|
||||
public class RemoveSecuritySchemesDocumentFilter : IDocumentFilter
|
||||
{
|
||||
private readonly string _documentName;
|
||||
|
||||
public RemoveSecuritySchemesDocumentFilter(string documentName)
|
||||
=> _documentName = documentName;
|
||||
|
||||
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
|
||||
{
|
||||
if (context.DocumentName != _documentName)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
swaggerDoc.Components.SecuritySchemes.Clear();
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
using Umbraco.Cms.Api.Common.Serialization;
|
||||
|
||||
namespace Umbraco.Cms.Api.Common.OpenApi;
|
||||
|
||||
public class SubTypesHandler : ISubTypesHandler
|
||||
{
|
||||
private readonly IUmbracoJsonTypeInfoResolver _umbracoJsonTypeInfoResolver;
|
||||
|
||||
public SubTypesHandler(IUmbracoJsonTypeInfoResolver umbracoJsonTypeInfoResolver)
|
||||
=> _umbracoJsonTypeInfoResolver = umbracoJsonTypeInfoResolver;
|
||||
|
||||
protected virtual bool CanHandle(Type type)
|
||||
=> type.Namespace?.StartsWith("Umbraco.Cms") is true;
|
||||
|
||||
public virtual bool CanHandle(Type type, string documentName)
|
||||
=> CanHandle(type);
|
||||
|
||||
public virtual IEnumerable<Type> Handle(Type type)
|
||||
=> _umbracoJsonTypeInfoResolver.FindSubTypes(type);
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Cms.Api.Common.Serialization;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Hosting;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Api.Common.OpenApi;
|
||||
|
||||
public class SubTypesSelector : ISubTypesSelector
|
||||
{
|
||||
private readonly IOptions<GlobalSettings> _settings;
|
||||
private readonly IHostingEnvironment _hostingEnvironment;
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
private readonly IEnumerable<ISubTypesHandler> _subTypeHandlers;
|
||||
private readonly IUmbracoJsonTypeInfoResolver _umbracoJsonTypeInfoResolver;
|
||||
|
||||
public SubTypesSelector(
|
||||
IOptions<GlobalSettings> settings,
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
IHttpContextAccessor httpContextAccessor,
|
||||
IEnumerable<ISubTypesHandler> subTypeHandlers,
|
||||
IUmbracoJsonTypeInfoResolver umbracoJsonTypeInfoResolver)
|
||||
{
|
||||
_settings = settings;
|
||||
_hostingEnvironment = hostingEnvironment;
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
_subTypeHandlers = subTypeHandlers;
|
||||
_umbracoJsonTypeInfoResolver = umbracoJsonTypeInfoResolver;
|
||||
}
|
||||
|
||||
public IEnumerable<Type> SubTypes(Type type)
|
||||
{
|
||||
var backOfficePath = _settings.Value.GetBackOfficePath(_hostingEnvironment);
|
||||
var swaggerPath = $"{backOfficePath}/swagger";
|
||||
|
||||
if (_httpContextAccessor.HttpContext?.Request.Path.StartsWithSegments(swaggerPath) ?? false)
|
||||
{
|
||||
// Split the path into segments
|
||||
var segments = _httpContextAccessor.HttpContext.Request.Path.Value!
|
||||
.Substring(swaggerPath.Length)
|
||||
.TrimStart(Constants.CharArrays.ForwardSlash)
|
||||
.Split(Constants.CharArrays.ForwardSlash);
|
||||
|
||||
// Extract the document name from the path
|
||||
var documentName = segments[0];
|
||||
|
||||
// Find the first handler that can handle the type / document name combination
|
||||
ISubTypesHandler? handler = _subTypeHandlers.FirstOrDefault(h => h.CanHandle(type, documentName));
|
||||
if (handler != null)
|
||||
{
|
||||
return handler.Handle(type);
|
||||
}
|
||||
}
|
||||
|
||||
// Default implementation to maintain backwards compatibility
|
||||
return _umbracoJsonTypeInfoResolver.FindSubTypes(type);
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,8 @@ public static class Paths
|
||||
|
||||
public static readonly string RevokeEndpoint = EndpointPath($"{EndpointTemplate}/revoke");
|
||||
|
||||
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
|
||||
private static string EndpointPath(string relativePath) => $"/umbraco/delivery/api/v1/{relativePath}";
|
||||
}
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
|
||||
<!-- Both OpenIddict.AspNetCore, Npoco.SqlServer and Microsoft.EntityFrameworkCore.SqlServer bring in a vulnerable version of Microsoft.IdentityModel.JsonWebTokens -->
|
||||
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens"/>
|
||||
|
||||
<!-- Take top-level depedendency on OpenIddict.AspNetCore depends on a vulnerable version -->
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
using Microsoft.AspNetCore.OutputCaching;
|
||||
|
||||
namespace Umbraco.Cms.Api.Delivery.Caching;
|
||||
|
||||
internal sealed class NoOutputCachePolicy : IOutputCachePolicy
|
||||
{
|
||||
ValueTask IOutputCachePolicy.CacheRequestAsync(OutputCacheContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
context.EnableOutputCaching = false;
|
||||
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
|
||||
ValueTask IOutputCachePolicy.ServeFromCacheAsync(OutputCacheContext context, CancellationToken cancellationToken)
|
||||
=> ValueTask.CompletedTask;
|
||||
|
||||
ValueTask IOutputCachePolicy.ServeResponseAsync(OutputCacheContext context, CancellationToken cancellationToken)
|
||||
=> ValueTask.CompletedTask;
|
||||
}
|
||||
-1
@@ -21,7 +21,6 @@ public class ConfigureUmbracoDeliveryApiSwaggerGenOptions: IConfigureOptions<Swa
|
||||
});
|
||||
|
||||
swaggerGenOptions.DocumentFilter<MimeTypeDocumentFilter>(DeliveryApiConfiguration.ApiName);
|
||||
swaggerGenOptions.DocumentFilter<RemoveSecuritySchemesDocumentFilter>(DeliveryApiConfiguration.ApiName);
|
||||
|
||||
swaggerGenOptions.OperationFilter<SwaggerContentDocumentationFilter>();
|
||||
swaggerGenOptions.OperationFilter<SwaggerMediaDocumentationFilter>();
|
||||
|
||||
+20
-29
@@ -17,16 +17,33 @@ namespace Umbraco.Cms.Api.Delivery.Configuration;
|
||||
/// </remarks>
|
||||
public class ConfigureUmbracoMemberAuthenticationDeliveryApiSwaggerGenOptions : IConfigureOptions<SwaggerGenOptions>
|
||||
{
|
||||
private const string AuthSchemeName = "UmbracoMember";
|
||||
private const string AuthSchemeName = "Umbraco Member";
|
||||
|
||||
public void Configure(SwaggerGenOptions options)
|
||||
{
|
||||
options.AddSecurityDefinition(
|
||||
AuthSchemeName,
|
||||
new OpenApiSecurityScheme
|
||||
{
|
||||
In = ParameterLocation.Header,
|
||||
Name = AuthSchemeName,
|
||||
Type = SecuritySchemeType.OAuth2,
|
||||
Description = "Umbraco Member Authentication",
|
||||
Flows = new OpenApiOAuthFlows
|
||||
{
|
||||
AuthorizationCode = new OpenApiOAuthFlow
|
||||
{
|
||||
AuthorizationUrl = new Uri(Paths.MemberApi.AuthorizationEndpoint, UriKind.Relative),
|
||||
TokenUrl = new Uri(Paths.MemberApi.TokenEndpoint, UriKind.Relative)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// add security requirements for content API operations
|
||||
options.DocumentFilter<DeliveryApiSecurityFilter>();
|
||||
options.OperationFilter<DeliveryApiSecurityFilter>();
|
||||
}
|
||||
|
||||
private class DeliveryApiSecurityFilter : SwaggerFilterBase<ContentApiControllerBase>, IOperationFilter, IDocumentFilter
|
||||
private class DeliveryApiSecurityFilter : SwaggerFilterBase<ContentApiControllerBase>, IOperationFilter
|
||||
{
|
||||
public void Apply(OpenApiOperation operation, OperationFilterContext context)
|
||||
{
|
||||
@@ -53,31 +70,5 @@ public class ConfigureUmbracoMemberAuthenticationDeliveryApiSwaggerGenOptions :
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
|
||||
{
|
||||
if (context.DocumentName != DeliveryApiConfiguration.ApiName)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
swaggerDoc.Components.SecuritySchemes.Add(
|
||||
AuthSchemeName,
|
||||
new OpenApiSecurityScheme
|
||||
{
|
||||
In = ParameterLocation.Header,
|
||||
Name = AuthSchemeName,
|
||||
Type = SecuritySchemeType.OAuth2,
|
||||
Description = "Umbraco Member Authentication",
|
||||
Flows = new OpenApiOAuthFlows
|
||||
{
|
||||
AuthorizationCode = new OpenApiOAuthFlow
|
||||
{
|
||||
AuthorizationUrl = new Uri(Paths.MemberApi.AuthorizationEndpoint, UriKind.Relative),
|
||||
TokenUrl = new Uri(Paths.MemberApi.TokenEndpoint, UriKind.Relative)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System.Diagnostics;
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
@@ -47,17 +46,19 @@ public class ByIdContentApiController : ContentApiItemControllerBase
|
||||
|
||||
private async Task<IActionResult> HandleRequest(Guid id)
|
||||
{
|
||||
IPublishedContent? contentItem = await ApiPublishedContentCache.GetByIdAsync(id).ConfigureAwait(false);
|
||||
IPublishedContent? contentItem = ApiPublishedContentCache.GetById(id);
|
||||
|
||||
if (contentItem is null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
IActionResult? deniedAccessResult = await HandleMemberAccessAsync(contentItem, _requestMemberAccessService).ConfigureAwait(false);
|
||||
|
||||
IActionResult? deniedAccessResult = await HandleMemberAccessAsync(contentItem, _requestMemberAccessService);
|
||||
if (deniedAccessResult is not null)
|
||||
{
|
||||
return deniedAccessResult;
|
||||
}
|
||||
|
||||
IApiContentResponse? apiContentResponse = ApiContentResponseBuilder.Build(contentItem);
|
||||
if (apiContentResponse is null)
|
||||
{
|
||||
|
||||
@@ -45,7 +45,7 @@ public class ByIdsContentApiController : ContentApiItemControllerBase
|
||||
|
||||
private async Task<IActionResult> HandleRequest(HashSet<Guid> ids)
|
||||
{
|
||||
IPublishedContent[] contentItems = (await ApiPublishedContentCache.GetByIdsAsync(ids).ConfigureAwait(false)).ToArray();
|
||||
IPublishedContent[] contentItems = ApiPublishedContentCache.GetByIds(ids).ToArray();
|
||||
|
||||
IActionResult? deniedAccessResult = await HandleMemberAccessAsync(contentItems, _requestMemberAccessService);
|
||||
if (deniedAccessResult is not null)
|
||||
|
||||
@@ -15,6 +15,7 @@ namespace Umbraco.Cms.Api.Delivery.Controllers.Content;
|
||||
[ApiExplorerSettings(GroupName = "Content")]
|
||||
[LocalizeFromAcceptLanguageHeader]
|
||||
[ValidateStartItem]
|
||||
[AddVaryHeader]
|
||||
[OutputCache(PolicyName = Constants.DeliveryApi.OutputCache.ContentCachePolicy)]
|
||||
public abstract class ContentApiControllerBase : DeliveryApiControllerBase
|
||||
{
|
||||
|
||||
@@ -12,10 +12,8 @@ namespace Umbraco.Cms.Api.Delivery.Controllers.Media;
|
||||
[ApiVersion("2.0")]
|
||||
public class ByIdMediaApiController : MediaApiControllerBase
|
||||
{
|
||||
public ByIdMediaApiController(
|
||||
IPublishedMediaCache publishedMediaCache,
|
||||
IApiMediaWithCropsResponseBuilder apiMediaWithCropsResponseBuilder)
|
||||
: base(publishedMediaCache, apiMediaWithCropsResponseBuilder)
|
||||
public ByIdMediaApiController(IPublishedSnapshotAccessor publishedSnapshotAccessor, IApiMediaWithCropsResponseBuilder apiMediaWithCropsResponseBuilder)
|
||||
: base(publishedSnapshotAccessor, apiMediaWithCropsResponseBuilder)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -13,8 +13,8 @@ namespace Umbraco.Cms.Api.Delivery.Controllers.Media;
|
||||
[ApiVersion("2.0")]
|
||||
public class ByIdsMediaApiController : MediaApiControllerBase
|
||||
{
|
||||
public ByIdsMediaApiController(IPublishedMediaCache publishedMediaCache, IApiMediaWithCropsResponseBuilder apiMediaWithCropsResponseBuilder)
|
||||
: base(publishedMediaCache, apiMediaWithCropsResponseBuilder)
|
||||
public ByIdsMediaApiController(IPublishedSnapshotAccessor publishedSnapshotAccessor, IApiMediaWithCropsResponseBuilder apiMediaWithCropsResponseBuilder)
|
||||
: base(publishedSnapshotAccessor, apiMediaWithCropsResponseBuilder)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -16,10 +16,10 @@ public class ByPathMediaApiController : MediaApiControllerBase
|
||||
private readonly IApiMediaQueryService _apiMediaQueryService;
|
||||
|
||||
public ByPathMediaApiController(
|
||||
IPublishedMediaCache publishedMediaCache,
|
||||
IPublishedSnapshotAccessor publishedSnapshotAccessor,
|
||||
IApiMediaWithCropsResponseBuilder apiMediaWithCropsResponseBuilder,
|
||||
IApiMediaQueryService apiMediaQueryService)
|
||||
: base(publishedMediaCache, apiMediaWithCropsResponseBuilder)
|
||||
: base(publishedSnapshotAccessor, apiMediaWithCropsResponseBuilder)
|
||||
=> _apiMediaQueryService = apiMediaQueryService;
|
||||
|
||||
[HttpGet("item/{*path}")]
|
||||
|
||||
@@ -20,15 +20,18 @@ namespace Umbraco.Cms.Api.Delivery.Controllers.Media;
|
||||
public abstract class MediaApiControllerBase : DeliveryApiControllerBase
|
||||
{
|
||||
private readonly IApiMediaWithCropsResponseBuilder _apiMediaWithCropsResponseBuilder;
|
||||
private IPublishedMediaCache _publishedMediaCache;
|
||||
private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor;
|
||||
private IPublishedMediaCache? _publishedMediaCache;
|
||||
|
||||
protected MediaApiControllerBase(IPublishedMediaCache publishedMediaCache, IApiMediaWithCropsResponseBuilder apiMediaWithCropsResponseBuilder)
|
||||
protected MediaApiControllerBase(IPublishedSnapshotAccessor publishedSnapshotAccessor, IApiMediaWithCropsResponseBuilder apiMediaWithCropsResponseBuilder)
|
||||
{
|
||||
_publishedMediaCache = publishedMediaCache;
|
||||
_publishedSnapshotAccessor = publishedSnapshotAccessor;
|
||||
_apiMediaWithCropsResponseBuilder = apiMediaWithCropsResponseBuilder;
|
||||
}
|
||||
|
||||
protected IPublishedMediaCache PublishedMediaCache => _publishedMediaCache;
|
||||
protected IPublishedMediaCache PublishedMediaCache => _publishedMediaCache
|
||||
??= _publishedSnapshotAccessor.GetRequiredPublishedSnapshot().Media
|
||||
?? throw new InvalidOperationException("Could not obtain the published media cache");
|
||||
|
||||
protected IApiMediaWithCropsResponse BuildApiMediaWithCrops(IPublishedContent media)
|
||||
=> _apiMediaWithCropsResponseBuilder.Build(media);
|
||||
|
||||
@@ -21,10 +21,10 @@ public class QueryMediaApiController : MediaApiControllerBase
|
||||
private readonly IApiMediaQueryService _apiMediaQueryService;
|
||||
|
||||
public QueryMediaApiController(
|
||||
IPublishedMediaCache publishedMediaCache,
|
||||
IPublishedSnapshotAccessor publishedSnapshotAccessor,
|
||||
IApiMediaWithCropsResponseBuilder apiMediaWithCropsResponseBuilder,
|
||||
IApiMediaQueryService apiMediaQueryService)
|
||||
: base(publishedMediaCache, apiMediaWithCropsResponseBuilder)
|
||||
: base(publishedSnapshotAccessor, apiMediaWithCropsResponseBuilder)
|
||||
=> _apiMediaQueryService = apiMediaQueryService;
|
||||
|
||||
[HttpGet]
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using OpenIddict.Server.AspNetCore;
|
||||
using Umbraco.Cms.Api.Delivery.Routing;
|
||||
using Umbraco.Cms.Core.DeliveryApi;
|
||||
|
||||
namespace Umbraco.Cms.Api.Delivery.Controllers.Security;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
[ApiController]
|
||||
[VersionedDeliveryApiRoute(Common.Security.Paths.MemberApi.EndpointTemplate)]
|
||||
[ApiExplorerSettings(IgnoreApi = true)]
|
||||
[Authorize(AuthenticationSchemes = OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)]
|
||||
public class CurrentMemberController : DeliveryApiControllerBase
|
||||
{
|
||||
private readonly ICurrentMemberClaimsProvider _currentMemberClaimsProvider;
|
||||
|
||||
public CurrentMemberController(ICurrentMemberClaimsProvider currentMemberClaimsProvider)
|
||||
=> _currentMemberClaimsProvider = currentMemberClaimsProvider;
|
||||
|
||||
[HttpGet("userinfo")]
|
||||
public async Task<IActionResult> Userinfo()
|
||||
{
|
||||
Dictionary<string, object> claims = await _currentMemberClaimsProvider.GetClaimsAsync();
|
||||
return Ok(claims);
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@ using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using OpenIddict.Abstractions;
|
||||
@@ -13,7 +12,6 @@ using OpenIddict.Server.AspNetCore;
|
||||
using Umbraco.Cms.Api.Delivery.Routing;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Web.Common.Security;
|
||||
using Umbraco.Extensions;
|
||||
@@ -27,47 +25,22 @@ namespace Umbraco.Cms.Api.Delivery.Controllers.Security;
|
||||
[ApiExplorerSettings(IgnoreApi = true)]
|
||||
public class MemberController : DeliveryApiControllerBase
|
||||
{
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
private readonly IMemberSignInManager _memberSignInManager;
|
||||
private readonly IMemberManager _memberManager;
|
||||
private readonly IMemberClientCredentialsManager _memberClientCredentialsManager;
|
||||
private readonly DeliveryApiSettings _deliveryApiSettings;
|
||||
private readonly ILogger<MemberController> _logger;
|
||||
|
||||
|
||||
[Obsolete("Please use the non-obsolete constructor. Will be removed in V16.")]
|
||||
public MemberController(
|
||||
IHttpContextAccessor httpContextAccessor,
|
||||
IMemberSignInManager memberSignInManager,
|
||||
IMemberManager memberManager,
|
||||
IOptions<DeliveryApiSettings> deliveryApiSettings,
|
||||
ILogger<MemberController> logger)
|
||||
: this(memberSignInManager, memberManager, StaticServiceProvider.Instance.GetRequiredService<IMemberClientCredentialsManager>(), deliveryApiSettings, logger)
|
||||
{
|
||||
}
|
||||
|
||||
[Obsolete("Please use the non-obsolete constructor. Will be removed in V16.")]
|
||||
public MemberController(
|
||||
IHttpContextAccessor httpContextAccessor,
|
||||
IMemberSignInManager memberSignInManager,
|
||||
IMemberManager memberManager,
|
||||
IMemberClientCredentialsManager memberClientCredentialsManager,
|
||||
IOptions<DeliveryApiSettings> deliveryApiSettings,
|
||||
ILogger<MemberController> logger)
|
||||
: this(memberSignInManager, memberManager, memberClientCredentialsManager, deliveryApiSettings, logger)
|
||||
{
|
||||
}
|
||||
|
||||
[ActivatorUtilitiesConstructor]
|
||||
public MemberController(
|
||||
IMemberSignInManager memberSignInManager,
|
||||
IMemberManager memberManager,
|
||||
IMemberClientCredentialsManager memberClientCredentialsManager,
|
||||
IOptions<DeliveryApiSettings> deliveryApiSettings,
|
||||
ILogger<MemberController> logger)
|
||||
{
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
_memberSignInManager = memberSignInManager;
|
||||
_memberManager = memberManager;
|
||||
_memberClientCredentialsManager = memberClientCredentialsManager;
|
||||
_logger = logger;
|
||||
_deliveryApiSettings = deliveryApiSettings.Value;
|
||||
}
|
||||
@@ -76,31 +49,25 @@ public class MemberController : DeliveryApiControllerBase
|
||||
[MapToApiVersion("1.0")]
|
||||
public async Task<IActionResult> Authorize()
|
||||
{
|
||||
// the Authorize endpoint is not allowed unless authorization code flow is enabled.
|
||||
if (_deliveryApiSettings.MemberAuthorization?.AuthorizationCodeFlow?.Enabled is not true)
|
||||
// in principle this is not necessary for now, since the member application has been removed, thus making
|
||||
// the member client ID invalid for the authentication code flow. However, if we ever add additional flows
|
||||
// to the API, we should perform this check, so we might as well include it upfront.
|
||||
if (_deliveryApiSettings.MemberAuthorizationIsEnabled() is false)
|
||||
{
|
||||
return BadRequest(new OpenIddictResponse
|
||||
{
|
||||
Error = "Not allowed", ErrorDescription = "Member authorization is not allowed."
|
||||
});
|
||||
return BadRequest("Member authorization is not allowed.");
|
||||
}
|
||||
|
||||
OpenIddictRequest? request = HttpContext.GetOpenIddictServerRequest();
|
||||
HttpContext context = _httpContextAccessor.GetRequiredHttpContext();
|
||||
OpenIddictRequest? request = context.GetOpenIddictServerRequest();
|
||||
if (request is null)
|
||||
{
|
||||
return BadRequest(new OpenIddictResponse
|
||||
{
|
||||
Error = "No context found", ErrorDescription = "Unable to obtain context from the current request."
|
||||
});
|
||||
return BadRequest("Unable to obtain OpenID data from the current request.");
|
||||
}
|
||||
|
||||
// make sure this endpoint ONLY handles member authentication
|
||||
if (request.ClientId is not Constants.OAuthClientIds.Member)
|
||||
{
|
||||
return BadRequest(new OpenIddictResponse
|
||||
{
|
||||
Error = "Invalid 'client ID'", ErrorDescription = "The specified 'client_id' is not valid."
|
||||
});
|
||||
return BadRequest("The specified client ID cannot be used here.");
|
||||
}
|
||||
|
||||
return request.IdentityProvider.IsNullOrWhiteSpace()
|
||||
@@ -108,50 +75,6 @@ public class MemberController : DeliveryApiControllerBase
|
||||
: await AuthorizeExternal(request);
|
||||
}
|
||||
|
||||
[HttpPost("token")]
|
||||
[MapToApiVersion("1.0")]
|
||||
public async Task<IActionResult> Token()
|
||||
{
|
||||
OpenIddictRequest? request = HttpContext.GetOpenIddictServerRequest();
|
||||
if (request is null)
|
||||
{
|
||||
return BadRequest(new OpenIddictResponse
|
||||
{
|
||||
Error = "No context found", ErrorDescription = "Unable to obtain context from the current request."
|
||||
});
|
||||
}
|
||||
|
||||
// authorization code flow or refresh token flow?
|
||||
if ((request.IsAuthorizationCodeGrantType() || request.IsRefreshTokenGrantType()) && _deliveryApiSettings.MemberAuthorization?.AuthorizationCodeFlow?.Enabled is true)
|
||||
{
|
||||
// attempt to authorize against the supplied the authorization code
|
||||
AuthenticateResult authenticateResult = await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
|
||||
|
||||
return authenticateResult is { Succeeded: true, Principal: not null }
|
||||
? new SignInResult(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, authenticateResult.Principal)
|
||||
: BadRequest(new OpenIddictResponse
|
||||
{
|
||||
Error = "Authorization failed", ErrorDescription = "The supplied authorization could not be verified."
|
||||
});
|
||||
}
|
||||
|
||||
// client credentials flow?
|
||||
if (request.IsClientCredentialsGrantType() && _deliveryApiSettings.MemberAuthorization?.ClientCredentialsFlow?.Enabled is true)
|
||||
{
|
||||
// if we get here, the client ID and secret are valid (verified by OpenIddict)
|
||||
|
||||
MemberIdentityUser? member = await _memberClientCredentialsManager.FindMemberAsync(request.ClientId!);
|
||||
return member is not null
|
||||
? await SignInMember(member, request)
|
||||
: BadRequest(new OpenIddictResponse
|
||||
{
|
||||
Error = "Authorization failed", ErrorDescription = "Invalid 'client_id' or client configuration."
|
||||
});
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("The requested grant type is not supported.");
|
||||
}
|
||||
|
||||
[HttpGet("signout")]
|
||||
[MapToApiVersion("1.0")]
|
||||
public async Task<IActionResult> Signout()
|
||||
@@ -177,10 +100,7 @@ public class MemberController : DeliveryApiControllerBase
|
||||
if (member is null)
|
||||
{
|
||||
_logger.LogError("The member with username {userName} was successfully authorized, but could not be retrieved by the member manager", userName);
|
||||
return BadRequest(new OpenIddictResponse
|
||||
{
|
||||
Error = "Authorization failed", ErrorDescription = "The member associated with the supplied 'client_id' could not be found."
|
||||
});
|
||||
return BadRequest("The member could not be found.");
|
||||
}
|
||||
|
||||
return await SignInMember(member, request);
|
||||
@@ -208,10 +128,7 @@ public class MemberController : DeliveryApiControllerBase
|
||||
if (member is null)
|
||||
{
|
||||
_logger.LogError("A member was successfully authorized using external authentication, but could not be retrieved by the member manager");
|
||||
return BadRequest(new OpenIddictResponse
|
||||
{
|
||||
Error = "Authorization failed", ErrorDescription = "The member associated with the supplied 'client_id' could not be found."
|
||||
});
|
||||
return BadRequest("The member could not be found.");
|
||||
}
|
||||
|
||||
// update member authentication tokens if succeeded
|
||||
|
||||
@@ -21,7 +21,6 @@ using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.DeliveryApi;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Notifications;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Infrastructure.Security;
|
||||
using Umbraco.Cms.Web.Common.ApplicationBuilder;
|
||||
|
||||
@@ -61,7 +60,7 @@ public static class UmbracoBuilderExtensions
|
||||
builder.Services.AddSingleton<IApiMediaQueryService, ApiMediaQueryService>();
|
||||
builder.Services.AddTransient<IMemberApplicationManager, MemberApplicationManager>();
|
||||
builder.Services.AddTransient<IRequestMemberAccessService, RequestMemberAccessService>();
|
||||
builder.Services.AddScoped<IMemberClientCredentialsManager, MemberClientCredentialsManager>();
|
||||
builder.Services.AddTransient<ICurrentMemberClaimsProvider, CurrentMemberClaimsProvider>();
|
||||
|
||||
builder.Services.ConfigureOptions<ConfigureUmbracoDeliveryApiSwaggerGenOptions>();
|
||||
builder.AddUmbracoApiOpenApiUI();
|
||||
@@ -105,7 +104,7 @@ public static class UmbracoBuilderExtensions
|
||||
|
||||
builder.Services.AddOutputCache(options =>
|
||||
{
|
||||
options.AddBasePolicy(build => build.AddPolicy<NoOutputCachePolicy>());
|
||||
options.AddBasePolicy(_ => { });
|
||||
|
||||
if (outputCacheSettings.ContentDuration.TotalSeconds > 0)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
|
||||
namespace Umbraco.Cms.Api.Delivery.Filters;
|
||||
|
||||
public sealed class AddVaryHeaderAttribute : ActionFilterAttribute
|
||||
{
|
||||
private const string Vary = "Accept-Language, Preview, Start-Item";
|
||||
|
||||
public override void OnResultExecuting(ResultExecutingContext context)
|
||||
=> context.HttpContext.Response.Headers.Vary = context.HttpContext.Response.Headers.Vary.Count > 0
|
||||
? $"{context.HttpContext.Response.Headers.Vary}, {Vary}"
|
||||
: Vary;
|
||||
}
|
||||
+1
-26
@@ -5,7 +5,6 @@ using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Events;
|
||||
using Umbraco.Cms.Core.Notifications;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Infrastructure.Security;
|
||||
|
||||
@@ -17,19 +16,16 @@ internal sealed class InitializeMemberApplicationNotificationHandler : INotifica
|
||||
private readonly ILogger<InitializeMemberApplicationNotificationHandler> _logger;
|
||||
private readonly DeliveryApiSettings _deliveryApiSettings;
|
||||
private readonly IServiceScopeFactory _serviceScopeFactory;
|
||||
private readonly IMemberClientCredentialsManager _memberClientCredentialsManager;
|
||||
|
||||
public InitializeMemberApplicationNotificationHandler(
|
||||
IRuntimeState runtimeState,
|
||||
IOptions<DeliveryApiSettings> deliveryApiSettings,
|
||||
ILogger<InitializeMemberApplicationNotificationHandler> logger,
|
||||
IServiceScopeFactory serviceScopeFactory,
|
||||
IMemberClientCredentialsManager memberClientCredentialsManager)
|
||||
IServiceScopeFactory serviceScopeFactory)
|
||||
{
|
||||
_runtimeState = runtimeState;
|
||||
_logger = logger;
|
||||
_serviceScopeFactory = serviceScopeFactory;
|
||||
_memberClientCredentialsManager = memberClientCredentialsManager;
|
||||
_deliveryApiSettings = deliveryApiSettings.Value;
|
||||
}
|
||||
|
||||
@@ -45,12 +41,6 @@ internal sealed class InitializeMemberApplicationNotificationHandler : INotifica
|
||||
using IServiceScope scope = _serviceScopeFactory.CreateScope();
|
||||
IMemberApplicationManager memberApplicationManager = scope.ServiceProvider.GetRequiredService<IMemberApplicationManager>();
|
||||
|
||||
await HandleMemberApplication(memberApplicationManager, cancellationToken);
|
||||
await HandleMemberClientCredentialsApplication(memberApplicationManager, cancellationToken);
|
||||
}
|
||||
|
||||
private async Task HandleMemberApplication(IMemberApplicationManager memberApplicationManager, CancellationToken cancellationToken)
|
||||
{
|
||||
if (_deliveryApiSettings.MemberAuthorization?.AuthorizationCodeFlow?.Enabled is not true)
|
||||
{
|
||||
await memberApplicationManager.DeleteMemberApplicationAsync(cancellationToken);
|
||||
@@ -76,21 +66,6 @@ internal sealed class InitializeMemberApplicationNotificationHandler : INotifica
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
private async Task HandleMemberClientCredentialsApplication(IMemberApplicationManager memberApplicationManager, CancellationToken cancellationToken)
|
||||
{
|
||||
if (_deliveryApiSettings.MemberAuthorization?.ClientCredentialsFlow?.Enabled is not true)
|
||||
{
|
||||
// disabled
|
||||
return;
|
||||
}
|
||||
|
||||
IEnumerable<MemberClientCredentials> memberClientCredentials = await _memberClientCredentialsManager.GetAllAsync();
|
||||
foreach (MemberClientCredentials memberClientCredential in memberClientCredentials)
|
||||
{
|
||||
await memberApplicationManager.EnsureMemberClientCredentialsApplicationAsync(memberClientCredential.ClientId, memberClientCredential.ClientSecret, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
private bool ValidateRedirectUrls(Uri[] redirectUrls)
|
||||
{
|
||||
if (redirectUrls.Any() is false)
|
||||
|
||||
@@ -1,56 +1,7 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.Json.Serialization.Metadata;
|
||||
using Umbraco.Cms.Core.Models.DeliveryApi;
|
||||
using Umbraco.Cms.Infrastructure.Serialization;
|
||||
|
||||
namespace Umbraco.Cms.Api.Delivery.Json;
|
||||
|
||||
// see https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/polymorphism?pivots=dotnet-7-0
|
||||
public class DeliveryApiJsonTypeResolver : DefaultJsonTypeInfoResolver
|
||||
{
|
||||
public override JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options)
|
||||
{
|
||||
JsonTypeInfo jsonTypeInfo = base.GetTypeInfo(type, options);
|
||||
|
||||
Type[] derivedTypes = GetDerivedTypes(jsonTypeInfo);
|
||||
if (derivedTypes.Length > 0)
|
||||
{
|
||||
ConfigureJsonPolymorphismOptions(jsonTypeInfo, derivedTypes);
|
||||
}
|
||||
|
||||
return jsonTypeInfo;
|
||||
}
|
||||
|
||||
protected virtual Type[] GetDerivedTypes(JsonTypeInfo jsonTypeInfo)
|
||||
{
|
||||
if (jsonTypeInfo.Type == typeof(IApiContent))
|
||||
{
|
||||
return new[] { typeof(ApiContent) };
|
||||
}
|
||||
|
||||
if (jsonTypeInfo.Type == typeof(IApiContentResponse))
|
||||
{
|
||||
return new[] { typeof(ApiContentResponse) };
|
||||
}
|
||||
|
||||
if (jsonTypeInfo.Type == typeof(IRichTextElement))
|
||||
{
|
||||
return new[] { typeof(RichTextRootElement), typeof(RichTextGenericElement), typeof(RichTextTextElement) };
|
||||
}
|
||||
|
||||
return Array.Empty<Type>();
|
||||
}
|
||||
|
||||
protected void ConfigureJsonPolymorphismOptions(JsonTypeInfo jsonTypeInfo, params Type[] derivedTypes)
|
||||
{
|
||||
jsonTypeInfo.PolymorphismOptions = new JsonPolymorphismOptions
|
||||
{
|
||||
UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FailSerialization,
|
||||
};
|
||||
|
||||
foreach (Type derivedType in derivedTypes)
|
||||
{
|
||||
jsonTypeInfo.PolymorphismOptions.DerivedTypes.Add(new JsonDerivedType(derivedType));
|
||||
}
|
||||
}
|
||||
}
|
||||
public class DeliveryApiJsonTypeResolver : ContentJsonTypeResolverBase
|
||||
{ }
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
using System.Reflection;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Umbraco.Cms.Core.DeliveryApi;
|
||||
|
||||
namespace Umbraco.Cms.Api.Delivery.Json;
|
||||
|
||||
public abstract class DeliveryApiVersionAwareJsonConverterBase<T> : JsonConverter<T>
|
||||
{
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
private readonly JsonConverter<T> _defaultConverter = (JsonConverter<T>)JsonSerializerOptions.Default.GetConverter(typeof(T));
|
||||
|
||||
public DeliveryApiVersionAwareJsonConverterBase(IHttpContextAccessor httpContextAccessor)
|
||||
=> _httpContextAccessor = httpContextAccessor;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
=> _defaultConverter.Read(ref reader, typeToConvert, options);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
|
||||
{
|
||||
Type type = typeof(T);
|
||||
var apiVersion = GetApiVersion();
|
||||
|
||||
// Get the properties in the specified order
|
||||
PropertyInfo[] properties = type.GetProperties().OrderBy(GetPropertyOrder).ToArray();
|
||||
|
||||
writer.WriteStartObject();
|
||||
|
||||
foreach (PropertyInfo property in properties)
|
||||
{
|
||||
// Filter out properties based on the API version
|
||||
var include = apiVersion is null || ShouldIncludeProperty(property, apiVersion.Value);
|
||||
|
||||
if (include is false)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var propertyName = property.Name;
|
||||
writer.WritePropertyName(options.PropertyNamingPolicy?.ConvertName(propertyName) ?? propertyName);
|
||||
JsonSerializer.Serialize(writer, property.GetValue(value), options);
|
||||
}
|
||||
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
|
||||
private int? GetApiVersion()
|
||||
{
|
||||
HttpContext? httpContext = _httpContextAccessor.HttpContext;
|
||||
ApiVersion? apiVersion = httpContext?.GetRequestedApiVersion();
|
||||
|
||||
return apiVersion?.MajorVersion;
|
||||
}
|
||||
|
||||
private int GetPropertyOrder(PropertyInfo prop)
|
||||
{
|
||||
var attribute = prop.GetCustomAttribute<JsonPropertyOrderAttribute>();
|
||||
return attribute?.Order ?? 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a property should be included based on version bounds.
|
||||
/// </summary>
|
||||
/// <param name="propertyInfo">The property info.</param>
|
||||
/// <param name="version">An integer representing an API version.</param>
|
||||
/// <returns><c>true</c> if the property should be included; otherwise, <c>false</c>.</returns>
|
||||
private bool ShouldIncludeProperty(PropertyInfo propertyInfo, int version)
|
||||
{
|
||||
var attribute = propertyInfo
|
||||
.GetCustomAttributes(typeof(IncludeInApiVersionAttribute), false)
|
||||
.FirstOrDefault();
|
||||
|
||||
if (attribute is not IncludeInApiVersionAttribute apiVersionAttribute)
|
||||
{
|
||||
return true; // No attribute means include the property
|
||||
}
|
||||
|
||||
// Check if the version is within the specified bounds
|
||||
var isWithinMinVersion = apiVersionAttribute.MinVersion.HasValue is false || version >= apiVersionAttribute.MinVersion.Value;
|
||||
var isWithinMaxVersion = apiVersionAttribute.MaxVersion.HasValue is false || version <= apiVersionAttribute.MaxVersion.Value;
|
||||
|
||||
return isWithinMinVersion && isWithinMaxVersion;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
using Umbraco.Cms.Api.Delivery.Indexing.Filters;
|
||||
using Umbraco.Cms.Core.DeliveryApi;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Api.Delivery.Querying.Filters;
|
||||
|
||||
@@ -15,15 +14,15 @@ public sealed class ContentTypeFilter : IFilterHandler
|
||||
/// <inheritdoc/>
|
||||
public FilterOption BuildFilterOption(string filter)
|
||||
{
|
||||
var alias = filter.Substring(ContentTypeSpecifier.Length);
|
||||
var filterValue = filter.Substring(ContentTypeSpecifier.Length);
|
||||
var negate = filterValue.StartsWith('!');
|
||||
var aliases = filterValue.TrimStart('!').Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
return new FilterOption
|
||||
{
|
||||
FieldName = ContentTypeFilterIndexer.FieldName,
|
||||
Values = alias.IsNullOrWhiteSpace() == false
|
||||
? new[] { alias.TrimStart('!') }
|
||||
: Array.Empty<string>(),
|
||||
Operator = alias.StartsWith('!')
|
||||
Values = aliases,
|
||||
Operator = negate
|
||||
? FilterOperation.IsNot
|
||||
: FilterOperation.Is
|
||||
};
|
||||
|
||||
@@ -7,15 +7,14 @@ namespace Umbraco.Cms.Api.Delivery.Querying;
|
||||
|
||||
public abstract class QueryOptionBase
|
||||
{
|
||||
private readonly IPublishedContentCache _publishedContentCache;
|
||||
private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor;
|
||||
private readonly IRequestRoutingService _requestRoutingService;
|
||||
|
||||
|
||||
public QueryOptionBase(
|
||||
IPublishedContentCache publishedContentCache,
|
||||
IPublishedSnapshotAccessor publishedSnapshotAccessor,
|
||||
IRequestRoutingService requestRoutingService)
|
||||
{
|
||||
_publishedContentCache = publishedContentCache;
|
||||
_publishedSnapshotAccessor = publishedSnapshotAccessor;
|
||||
_requestRoutingService = requestRoutingService;
|
||||
}
|
||||
|
||||
@@ -31,9 +30,11 @@ public abstract class QueryOptionBase
|
||||
return id;
|
||||
}
|
||||
|
||||
IPublishedSnapshot publishedSnapshot = _publishedSnapshotAccessor.GetRequiredPublishedSnapshot();
|
||||
|
||||
// Check if the passed value is a path of a content item
|
||||
var contentRoute = _requestRoutingService.GetContentRoute(queryStringValue);
|
||||
IPublishedContent? contentItem = _publishedContentCache.GetByRoute(contentRoute);
|
||||
IPublishedContent? contentItem = publishedSnapshot.Content?.GetByRoute(contentRoute);
|
||||
|
||||
return contentItem?.Key;
|
||||
}
|
||||
|
||||
@@ -4,31 +4,29 @@ using Umbraco.Cms.Core.DeliveryApi;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.PublishedCache;
|
||||
using Umbraco.Cms.Core.Services.Navigation;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Api.Delivery.Querying.Selectors;
|
||||
|
||||
public sealed class AncestorsSelector : QueryOptionBase, ISelectorHandler
|
||||
{
|
||||
private readonly IPublishedContentCache _publishedContentCache;
|
||||
private readonly IDocumentNavigationQueryService _navigationQueryService;
|
||||
private const string AncestorsSpecifier = "ancestors:";
|
||||
private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor;
|
||||
private readonly IRequestPreviewService _requestPreviewService;
|
||||
|
||||
[Obsolete("Please use the non-obsolete constructor. Will be removed in V17.")]
|
||||
public AncestorsSelector(
|
||||
IPublishedContentCache publishedContentCache,
|
||||
IRequestRoutingService requestRoutingService,
|
||||
IDocumentNavigationQueryService navigationQueryService)
|
||||
: base(publishedContentCache, requestRoutingService)
|
||||
IPublishedSnapshotAccessor publishedSnapshotAccessor,
|
||||
IRequestRoutingService requestRoutingService)
|
||||
: this(publishedSnapshotAccessor, requestRoutingService, StaticServiceProvider.Instance.GetRequiredService<IRequestPreviewService>())
|
||||
{
|
||||
_publishedContentCache = publishedContentCache;
|
||||
_navigationQueryService = navigationQueryService;
|
||||
}
|
||||
|
||||
[Obsolete("Use the constructor that takes all parameters. Scheduled for removal in V17.")]
|
||||
public AncestorsSelector(IPublishedContentCache publishedContentCache, IRequestRoutingService requestRoutingService)
|
||||
: this(publishedContentCache, requestRoutingService, StaticServiceProvider.Instance.GetRequiredService<IDocumentNavigationQueryService>())
|
||||
public AncestorsSelector(IPublishedSnapshotAccessor publishedSnapshotAccessor, IRequestRoutingService requestRoutingService, IRequestPreviewService requestPreviewService)
|
||||
: base(publishedSnapshotAccessor, requestRoutingService)
|
||||
{
|
||||
_publishedSnapshotAccessor = publishedSnapshotAccessor;
|
||||
_requestPreviewService = requestPreviewService;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -53,10 +51,22 @@ public sealed class AncestorsSelector : QueryOptionBase, ISelectorHandler
|
||||
};
|
||||
}
|
||||
|
||||
IPublishedContent contentItem = _publishedContentCache.GetById((Guid)id)
|
||||
?? throw new InvalidOperationException("Could not obtain the content cache");
|
||||
IPublishedContentCache contentCache = _publishedSnapshotAccessor.GetRequiredPublishedSnapshot()?.Content
|
||||
?? throw new InvalidOperationException("Could not obtain the content cache");
|
||||
|
||||
var ancestorKeys = contentItem.Ancestors(_publishedContentCache, _navigationQueryService).Select(a => a.Key.ToString("D")).ToArray();
|
||||
IPublishedContent? contentItem = contentCache.GetById(_requestPreviewService.IsPreview(), id.Value);
|
||||
|
||||
if (contentItem is null)
|
||||
{
|
||||
// no such content item, make sure the selector does not yield any results
|
||||
return new SelectorOption
|
||||
{
|
||||
FieldName = AncestorsSelectorIndexer.FieldName,
|
||||
Values = Array.Empty<string>()
|
||||
};
|
||||
}
|
||||
|
||||
var ancestorKeys = contentItem.Ancestors().Select(a => a.Key.ToString("D")).ToArray();
|
||||
|
||||
return new SelectorOption
|
||||
{
|
||||
|
||||
@@ -9,8 +9,8 @@ public sealed class ChildrenSelector : QueryOptionBase, ISelectorHandler
|
||||
{
|
||||
private const string ChildrenSpecifier = "children:";
|
||||
|
||||
public ChildrenSelector(IPublishedContentCache publishedContentCache, IRequestRoutingService requestRoutingService)
|
||||
: base(publishedContentCache, requestRoutingService)
|
||||
public ChildrenSelector(IPublishedSnapshotAccessor publishedSnapshotAccessor, IRequestRoutingService requestRoutingService)
|
||||
: base(publishedSnapshotAccessor, requestRoutingService)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -9,8 +9,8 @@ public sealed class DescendantsSelector : QueryOptionBase, ISelectorHandler
|
||||
{
|
||||
private const string DescendantsSpecifier = "descendants:";
|
||||
|
||||
public DescendantsSelector(IPublishedContentCache publishedContentCache, IRequestRoutingService requestRoutingService)
|
||||
: base(publishedContentCache, requestRoutingService)
|
||||
public DescendantsSelector(IPublishedSnapshotAccessor publishedSnapshotAccessor, IRequestRoutingService requestRoutingService)
|
||||
: base(publishedSnapshotAccessor, requestRoutingService)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ public class MemberApplicationManager : OpenIdDictApplicationManagerBase, IMembe
|
||||
{
|
||||
OpenIddictConstants.Permissions.Endpoints.Authorization,
|
||||
OpenIddictConstants.Permissions.Endpoints.Token,
|
||||
OpenIddictConstants.Permissions.Endpoints.EndSession,
|
||||
OpenIddictConstants.Permissions.Endpoints.Logout,
|
||||
OpenIddictConstants.Permissions.Endpoints.Revocation,
|
||||
OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode,
|
||||
OpenIddictConstants.Permissions.GrantTypes.RefreshToken,
|
||||
@@ -64,26 +64,4 @@ public class MemberApplicationManager : OpenIdDictApplicationManagerBase, IMembe
|
||||
|
||||
public async Task DeleteMemberApplicationAsync(CancellationToken cancellationToken = default)
|
||||
=> await Delete(Constants.OAuthClientIds.Member, cancellationToken);
|
||||
|
||||
public async Task EnsureMemberClientCredentialsApplicationAsync(string clientId, string clientSecret, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var applicationDescriptor = new OpenIddictApplicationDescriptor
|
||||
{
|
||||
DisplayName = $"Umbraco client credentials member access: {clientId}",
|
||||
ClientId = clientId,
|
||||
ClientSecret = clientSecret,
|
||||
ClientType = OpenIddictConstants.ClientTypes.Confidential,
|
||||
Permissions =
|
||||
{
|
||||
OpenIddictConstants.Permissions.Endpoints.Token,
|
||||
OpenIddictConstants.Permissions.Endpoints.Revocation,
|
||||
OpenIddictConstants.Permissions.GrantTypes.ClientCredentials
|
||||
}
|
||||
};
|
||||
|
||||
await CreateOrUpdate(applicationDescriptor, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task DeleteMemberClientCredentialsApplicationAsync(string clientId, CancellationToken cancellationToken = default)
|
||||
=> await Delete(clientId, cancellationToken);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ using Umbraco.Cms.Core.DeliveryApi;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.PublishedCache;
|
||||
using Umbraco.Cms.Core.Services.Navigation;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
@@ -13,15 +12,13 @@ namespace Umbraco.Cms.Api.Delivery.Services;
|
||||
/// <inheritdoc />
|
||||
internal sealed class ApiMediaQueryService : IApiMediaQueryService
|
||||
{
|
||||
private readonly IPublishedMediaCache _publishedMediaCache;
|
||||
private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor;
|
||||
private readonly ILogger<ApiMediaQueryService> _logger;
|
||||
private readonly IMediaNavigationQueryService _mediaNavigationQueryService;
|
||||
|
||||
public ApiMediaQueryService(IPublishedMediaCache publishedMediaCache, ILogger<ApiMediaQueryService> logger, IMediaNavigationQueryService mediaNavigationQueryService)
|
||||
public ApiMediaQueryService(IPublishedSnapshotAccessor publishedSnapshotAccessor, ILogger<ApiMediaQueryService> logger)
|
||||
{
|
||||
_publishedMediaCache = publishedMediaCache;
|
||||
_publishedSnapshotAccessor = publishedSnapshotAccessor;
|
||||
_logger = logger;
|
||||
_mediaNavigationQueryService = mediaNavigationQueryService;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -55,7 +52,8 @@ internal sealed class ApiMediaQueryService : IApiMediaQueryService
|
||||
=> TryGetByPath(path, GetRequiredPublishedMediaCache());
|
||||
|
||||
private IPublishedMediaCache GetRequiredPublishedMediaCache()
|
||||
=> _publishedMediaCache;
|
||||
=> _publishedSnapshotAccessor.GetRequiredPublishedSnapshot().Media
|
||||
?? throw new InvalidOperationException("Could not obtain the published media cache");
|
||||
|
||||
private IPublishedContent? TryGetByPath(string path, IPublishedMediaCache mediaCache)
|
||||
{
|
||||
@@ -71,7 +69,7 @@ internal sealed class ApiMediaQueryService : IApiMediaQueryService
|
||||
break;
|
||||
}
|
||||
|
||||
currentChildren = resolvedMedia.Children(null, _publishedMediaCache, _mediaNavigationQueryService);
|
||||
currentChildren = resolvedMedia.Children;
|
||||
}
|
||||
|
||||
return resolvedMedia;
|
||||
@@ -104,7 +102,7 @@ internal sealed class ApiMediaQueryService : IApiMediaQueryService
|
||||
? mediaCache.GetById(parentKey)
|
||||
: TryGetByPath(childrenOf, mediaCache);
|
||||
|
||||
return parent?.Children(null, _publishedMediaCache, _mediaNavigationQueryService) ?? Array.Empty<IPublishedContent>();
|
||||
return parent?.Children ?? Array.Empty<IPublishedContent>();
|
||||
}
|
||||
|
||||
private IEnumerable<IPublishedContent>? ApplyFilters(IEnumerable<IPublishedContent> source, IEnumerable<string> filters)
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
using OpenIddict.Abstractions;
|
||||
using Umbraco.Cms.Core.DeliveryApi;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
|
||||
namespace Umbraco.Cms.Api.Delivery.Services;
|
||||
|
||||
// NOTE: this is public and unsealed to allow overriding the default claims with minimal effort.
|
||||
public class CurrentMemberClaimsProvider : ICurrentMemberClaimsProvider
|
||||
{
|
||||
private readonly IMemberManager _memberManager;
|
||||
|
||||
public CurrentMemberClaimsProvider(IMemberManager memberManager)
|
||||
=> _memberManager = memberManager;
|
||||
|
||||
public virtual async Task<Dictionary<string, object>> GetClaimsAsync()
|
||||
{
|
||||
MemberIdentityUser? memberIdentityUser = await _memberManager.GetCurrentMemberAsync();
|
||||
return memberIdentityUser is not null
|
||||
? await GetClaimsForMemberIdentityAsync(memberIdentityUser)
|
||||
: throw new InvalidOperationException("Could not retrieve the current member. This method should only ever be invoked when a member has been authorized.");
|
||||
}
|
||||
|
||||
protected virtual async Task<Dictionary<string, object>> GetClaimsForMemberIdentityAsync(MemberIdentityUser memberIdentityUser)
|
||||
{
|
||||
var claims = new Dictionary<string, object>
|
||||
{
|
||||
[OpenIddictConstants.Claims.Subject] = memberIdentityUser.Key
|
||||
};
|
||||
|
||||
if (memberIdentityUser.Name is not null)
|
||||
{
|
||||
claims[OpenIddictConstants.Claims.Name] = memberIdentityUser.Name;
|
||||
}
|
||||
|
||||
if (memberIdentityUser.Email is not null)
|
||||
{
|
||||
claims[OpenIddictConstants.Claims.Email] = memberIdentityUser.Email;
|
||||
}
|
||||
|
||||
claims[OpenIddictConstants.Claims.Role] = await _memberManager.GetRolesAsync(memberIdentityUser);
|
||||
|
||||
return claims;
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ internal sealed class RequestRedirectService : RoutingServiceBase, IRequestRedir
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
|
||||
public RequestRedirectService(
|
||||
IDomainCache domainCache,
|
||||
IPublishedSnapshotAccessor publishedSnapshotAccessor,
|
||||
IHttpContextAccessor httpContextAccessor,
|
||||
IRequestStartItemProviderAccessor requestStartItemProviderAccessor,
|
||||
IRequestCultureService requestCultureService,
|
||||
@@ -29,7 +29,7 @@ internal sealed class RequestRedirectService : RoutingServiceBase, IRequestRedir
|
||||
IApiPublishedContentCache apiPublishedContentCache,
|
||||
IApiContentRouteBuilder apiContentRouteBuilder,
|
||||
IOptions<GlobalSettings> globalSettings)
|
||||
: base(domainCache, httpContextAccessor, requestStartItemProviderAccessor)
|
||||
: base(publishedSnapshotAccessor, httpContextAccessor, requestStartItemProviderAccessor)
|
||||
{
|
||||
_requestCultureService = requestCultureService;
|
||||
_redirectUrlService = redirectUrlService;
|
||||
@@ -42,31 +42,42 @@ internal sealed class RequestRedirectService : RoutingServiceBase, IRequestRedir
|
||||
{
|
||||
requestedPath = requestedPath.EnsureStartsWith("/");
|
||||
|
||||
IPublishedContent? startItem = GetStartItem();
|
||||
|
||||
// must append the root content url segment if it is not hidden by config, because
|
||||
// the URL tracking is based on the actual URL, including the root content url segment
|
||||
if (_globalSettings.HideTopLevelNodeFromPath == false)
|
||||
if (_globalSettings.HideTopLevelNodeFromPath == false && startItem?.UrlSegment != null)
|
||||
{
|
||||
IPublishedContent? startItem = GetStartItem();
|
||||
if (startItem?.UrlSegment != null)
|
||||
{
|
||||
requestedPath = $"{startItem.UrlSegment.EnsureStartsWith("/")}{requestedPath}";
|
||||
}
|
||||
requestedPath = $"{startItem.UrlSegment.EnsureStartsWith("/")}{requestedPath}";
|
||||
}
|
||||
|
||||
var culture = _requestCultureService.GetRequestedCulture();
|
||||
|
||||
// append the configured domain content ID to the path if we have a domain bound request,
|
||||
// because URL tracking registers the tracked url like "{domain content ID}/{content path}"
|
||||
Uri contentRoute = GetDefaultRequestUri(requestedPath);
|
||||
DomainAndUri? domainAndUri = GetDomainAndUriForRoute(contentRoute);
|
||||
if (domainAndUri != null)
|
||||
// important: redirect URLs are always tracked without trailing slashes
|
||||
requestedPath = requestedPath.TrimEnd("/");
|
||||
IRedirectUrl? redirectUrl = _redirectUrlService.GetMostRecentRedirectUrl(requestedPath, culture);
|
||||
|
||||
// if a redirect URL was not found, try by appending the start item ID because URL tracking might have tracked
|
||||
// a redirect with "{root content ID}/{content path}"
|
||||
if (redirectUrl is null && startItem is not null)
|
||||
{
|
||||
requestedPath = GetContentRoute(domainAndUri, contentRoute);
|
||||
culture ??= domainAndUri.Culture;
|
||||
redirectUrl = _redirectUrlService.GetMostRecentRedirectUrl($"{startItem.Id}{requestedPath}", culture);
|
||||
}
|
||||
|
||||
// still no redirect URL found - try looking for a configured domain if we have a domain bound request,
|
||||
// because URL tracking might have tracked a redirect with "{domain content ID}/{content path}"
|
||||
if (redirectUrl is null)
|
||||
{
|
||||
Uri contentRoute = GetDefaultRequestUri(requestedPath);
|
||||
DomainAndUri? domainAndUri = GetDomainAndUriForRoute(contentRoute);
|
||||
if (domainAndUri is not null)
|
||||
{
|
||||
requestedPath = GetContentRoute(domainAndUri, contentRoute);
|
||||
culture ??= domainAndUri.Culture;
|
||||
redirectUrl = _redirectUrlService.GetMostRecentRedirectUrl(requestedPath, culture);
|
||||
}
|
||||
}
|
||||
|
||||
// important: redirect URLs are always tracked without trailing slashes
|
||||
IRedirectUrl? redirectUrl = _redirectUrlService.GetMostRecentRedirectUrl(requestedPath.TrimEnd("/"), culture);
|
||||
IPublishedContent? content = redirectUrl != null
|
||||
? _apiPublishedContentCache.GetById(redirectUrl.ContentKey)
|
||||
: null;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Umbraco.Cms.Core.DeliveryApi;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.PublishedCache;
|
||||
using Umbraco.Cms.Core.Routing;
|
||||
@@ -13,11 +12,11 @@ internal sealed class RequestRoutingService : RoutingServiceBase, IRequestRoutin
|
||||
private readonly IRequestCultureService _requestCultureService;
|
||||
|
||||
public RequestRoutingService(
|
||||
IDomainCache domainCache,
|
||||
IPublishedSnapshotAccessor publishedSnapshotAccessor,
|
||||
IHttpContextAccessor httpContextAccessor,
|
||||
IRequestStartItemProviderAccessor requestStartItemProviderAccessor,
|
||||
IRequestCultureService requestCultureService)
|
||||
: base(domainCache, httpContextAccessor, requestStartItemProviderAccessor) =>
|
||||
: base(publishedSnapshotAccessor, httpContextAccessor, requestStartItemProviderAccessor) =>
|
||||
_requestCultureService = requestCultureService;
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -3,34 +3,29 @@ using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.DeliveryApi;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.PublishedCache;
|
||||
using Umbraco.Cms.Core.Services.Navigation;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Api.Delivery.Services;
|
||||
|
||||
internal sealed class RequestStartItemProvider : RequestHeaderHandler, IRequestStartItemProvider
|
||||
{
|
||||
private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor;
|
||||
private readonly IVariationContextAccessor _variationContextAccessor;
|
||||
private readonly IRequestPreviewService _requestPreviewService;
|
||||
private readonly IDocumentNavigationQueryService _documentNavigationQueryService;
|
||||
private readonly IPublishedContentCache _publishedContentCache;
|
||||
|
||||
// this provider lifetime is Scope, so we can cache this as a field
|
||||
private IPublishedContent? _requestedStartContent;
|
||||
|
||||
public RequestStartItemProvider(
|
||||
IHttpContextAccessor httpContextAccessor,
|
||||
IPublishedSnapshotAccessor publishedSnapshotAccessor,
|
||||
IVariationContextAccessor variationContextAccessor,
|
||||
IRequestPreviewService requestPreviewService,
|
||||
IDocumentNavigationQueryService documentNavigationQueryService,
|
||||
IPublishedContentCache publishedContentCache)
|
||||
IRequestPreviewService requestPreviewService)
|
||||
: base(httpContextAccessor)
|
||||
{
|
||||
|
||||
_publishedSnapshotAccessor = publishedSnapshotAccessor;
|
||||
_variationContextAccessor = variationContextAccessor;
|
||||
_requestPreviewService = requestPreviewService;
|
||||
_documentNavigationQueryService = documentNavigationQueryService;
|
||||
_publishedContentCache = publishedContentCache;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -47,11 +42,13 @@ internal sealed class RequestStartItemProvider : RequestHeaderHandler, IRequestS
|
||||
return null;
|
||||
}
|
||||
|
||||
_documentNavigationQueryService.TryGetRootKeys(out IEnumerable<Guid> rootKeys);
|
||||
IEnumerable<IPublishedContent> rootContent = rootKeys
|
||||
.Select(_publishedContentCache.GetById)
|
||||
.WhereNotNull()
|
||||
.Where(x => x.IsPublished() != _requestPreviewService.IsPreview());
|
||||
if (_publishedSnapshotAccessor.TryGetPublishedSnapshot(out IPublishedSnapshot? publishedSnapshot) == false ||
|
||||
publishedSnapshot?.Content == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
IEnumerable<IPublishedContent> rootContent = publishedSnapshot.Content.GetAtRoot(_requestPreviewService.IsPreview());
|
||||
|
||||
_requestedStartContent = Guid.TryParse(headerValue, out Guid key)
|
||||
? rootContent.FirstOrDefault(c => c.Key == key)
|
||||
|
||||
@@ -9,16 +9,16 @@ namespace Umbraco.Cms.Api.Delivery.Services;
|
||||
|
||||
internal abstract class RoutingServiceBase
|
||||
{
|
||||
private readonly IDomainCache _domainCache;
|
||||
private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor;
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
private readonly IRequestStartItemProviderAccessor _requestStartItemProviderAccessor;
|
||||
|
||||
protected RoutingServiceBase(
|
||||
IDomainCache domainCache,
|
||||
IPublishedSnapshotAccessor publishedSnapshotAccessor,
|
||||
IHttpContextAccessor httpContextAccessor,
|
||||
IRequestStartItemProviderAccessor requestStartItemProviderAccessor)
|
||||
{
|
||||
_domainCache = domainCache;
|
||||
_publishedSnapshotAccessor = publishedSnapshotAccessor;
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
_requestStartItemProviderAccessor = requestStartItemProviderAccessor;
|
||||
}
|
||||
@@ -40,9 +40,15 @@ internal abstract class RoutingServiceBase
|
||||
|
||||
protected DomainAndUri? GetDomainAndUriForRoute(Uri contentUrl)
|
||||
{
|
||||
IEnumerable<Domain> domains = _domainCache.GetAll(false);
|
||||
IDomainCache? domainCache = _publishedSnapshotAccessor.GetRequiredPublishedSnapshot().Domains;
|
||||
if (domainCache == null)
|
||||
{
|
||||
throw new InvalidOperationException("Could not obtain the domain cache in the current context");
|
||||
}
|
||||
|
||||
return DomainUtilities.SelectDomain(domains, contentUrl, defaultCulture: _domainCache.DefaultCulture);
|
||||
IEnumerable<Domain> domains = domainCache.GetAll(false);
|
||||
|
||||
return DomainUtilities.SelectDomain(domains, contentUrl, defaultCulture: domainCache.DefaultCulture);
|
||||
}
|
||||
|
||||
protected IPublishedContent? GetStartItem()
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
<Description>Contains the presentation layer for the Umbraco CMS Delivery API.</Description>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<!-- TODO: [ASP0019] use IHeaderDictionary.Append or the indexer to append or set headers and remove this override -->
|
||||
<!-- TODO: [ASP0019] use IHeaderDictionary.Append or the indexer to append or set headers,
|
||||
and remove this override -->
|
||||
<WarningsNotAsErrors>ASP0019</WarningsNotAsErrors>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
@@ -12,10 +13,6 @@
|
||||
<ProjectReference Include="..\Umbraco.Web.Common\Umbraco.Web.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- TODO: Fix all warnings and remove this override -->
|
||||
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
|
||||
<_Parameter1>Umbraco.Tests.UnitTests</_Parameter1>
|
||||
|
||||
+1
@@ -31,6 +31,7 @@ public class ConfigureUmbracoManagementApiSwaggerGenOptions : IConfigureOptions<
|
||||
});
|
||||
|
||||
swaggerGenOptions.OperationFilter<ResponseHeaderOperationFilter>();
|
||||
swaggerGenOptions.SelectSubTypesUsing(_umbracoJsonTypeInfoResolver.FindSubTypes);
|
||||
swaggerGenOptions.UseOneOfForPolymorphism();
|
||||
|
||||
// Ensure all types that implements the IOpenApiDiscriminator have a $type property in the OpenApi schema with the default value (The class name) that is expected by the server
|
||||
|
||||
@@ -51,7 +51,7 @@ public class BackOfficeLoginController : Controller
|
||||
|
||||
if (string.IsNullOrEmpty(model.UmbracoUrl))
|
||||
{
|
||||
model.UmbracoUrl = _hostingEnvironment.ToAbsolute(Constants.System.DefaultUmbracoPath);
|
||||
model.UmbracoUrl = _hostingEnvironment.ToAbsolute(_globalSettings.UmbracoPath);
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(model.ReturnUrl))
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ namespace Umbraco.Cms.Api.Management.Controllers.Content;
|
||||
public abstract class ContentCollectionControllerBase<TContent, TCollectionResponseModel, TValueResponseModelBase, TVariantResponseModel> : ManagementApiControllerBase
|
||||
where TContent : class, IContentBase
|
||||
where TCollectionResponseModel : ContentResponseModelBase<TValueResponseModelBase, TVariantResponseModel>
|
||||
where TValueResponseModelBase : ValueResponseModelBase
|
||||
where TValueResponseModelBase : ValueModelBase
|
||||
where TVariantResponseModel : VariantResponseModelBase
|
||||
{
|
||||
private readonly IUmbracoMapper _mapper;
|
||||
|
||||
@@ -4,7 +4,6 @@ using Umbraco.Cms.Api.Management.ViewModels.Content;
|
||||
using Umbraco.Cms.Core.Mapping;
|
||||
using Umbraco.Cms.Core.Models.ContentEditing;
|
||||
using Umbraco.Cms.Core.Models.ContentEditing.Validation;
|
||||
using Umbraco.Cms.Core.PropertyEditors.Validation;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
@@ -101,7 +100,7 @@ public abstract class ContentControllerBase : ManagementApiControllerBase
|
||||
|
||||
var errors = new SortedDictionary<string, string[]>();
|
||||
|
||||
var validationErrorExpressionRoot = $"$.{nameof(ContentModelBase<TValueModel, TVariantModel>.Values).ToFirstLowerInvariant()}";
|
||||
var missingPropertyModels = new List<PropertyValidationResponseModel>();
|
||||
foreach (PropertyValidationError validationError in validationResult.ValidationErrors)
|
||||
{
|
||||
TValueModel? requestValue = requestModel.Values.FirstOrDefault(value =>
|
||||
@@ -110,16 +109,13 @@ public abstract class ContentControllerBase : ManagementApiControllerBase
|
||||
&& value.Segment == validationError.Segment);
|
||||
if (requestValue is null)
|
||||
{
|
||||
errors.Add(
|
||||
$"{validationErrorExpressionRoot}[{JsonPathExpression.MissingPropertyValue(validationError.Alias, validationError.Culture, validationError.Segment)}].{nameof(ValueModelBase.Value)}",
|
||||
validationError.ErrorMessages);
|
||||
missingPropertyModels.Add(MapMissingProperty(validationError));
|
||||
continue;
|
||||
}
|
||||
|
||||
var index = requestModel.Values.IndexOf(requestValue);
|
||||
errors.Add(
|
||||
$"$.{nameof(ContentModelBase<TValueModel, TVariantModel>.Values).ToFirstLowerInvariant()}[{index}].{nameof(ValueModelBase.Value).ToFirstLowerInvariant()}{validationError.JsonPath}",
|
||||
validationError.ErrorMessages);
|
||||
var key = $"$.{nameof(ContentModelBase<TValueModel, TVariantModel>.Values).ToFirstLowerInvariant()}[{index}].{nameof(ValueModelBase.Value).ToFirstLowerInvariant()}{validationError.JsonPath}";
|
||||
errors.Add(key, validationError.ErrorMessages);
|
||||
}
|
||||
|
||||
return OperationStatusResult(status, problemDetailsBuilder
|
||||
@@ -127,6 +123,16 @@ public abstract class ContentControllerBase : ManagementApiControllerBase
|
||||
.WithTitle("Validation failed")
|
||||
.WithDetail("One or more properties did not pass validation")
|
||||
.WithRequestModelErrors(errors)
|
||||
.WithExtension("missingValues", missingPropertyModels.ToArray())
|
||||
.Build()));
|
||||
}
|
||||
|
||||
private PropertyValidationResponseModel MapMissingProperty(PropertyValidationError source) =>
|
||||
new()
|
||||
{
|
||||
Alias = source.Alias,
|
||||
Segment = source.Segment,
|
||||
Culture = source.Culture,
|
||||
Messages = source.ErrorMessages,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.DataType;
|
||||
@@ -7,10 +8,12 @@ using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
using Umbraco.Cms.Web.Common.Authorization;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.DataType;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
[Authorize(Policy = AuthorizationPolicies.TreeAccessDataTypes)]
|
||||
public class CopyDataTypeController : DataTypeControllerBase
|
||||
{
|
||||
private readonly IDataTypeService _dataTypeService;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Asp.Versioning;
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.Factories;
|
||||
@@ -8,10 +9,12 @@ using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
using Umbraco.Cms.Web.Common.Authorization;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.DataType;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
[Authorize(Policy = AuthorizationPolicies.TreeAccessDataTypes)]
|
||||
public class CreateDataTypeController : DataTypeControllerBase
|
||||
{
|
||||
private readonly IDataTypeService _dataTypeService;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Asp.Versioning;
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core;
|
||||
@@ -6,10 +7,12 @@ using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
using Umbraco.Cms.Web.Common.Authorization;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.DataType;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
[Authorize(Policy = AuthorizationPolicies.TreeAccessDataTypes)]
|
||||
public class DeleteDataTypeController : DataTypeControllerBase
|
||||
{
|
||||
private readonly IDataTypeService _dataTypeService;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.DataType;
|
||||
@@ -7,10 +8,12 @@ using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
using Umbraco.Cms.Web.Common.Authorization;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.DataType;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
[Authorize(Policy = AuthorizationPolicies.TreeAccessDataTypes)]
|
||||
public class MoveDataTypeController : DataTypeControllerBase
|
||||
{
|
||||
private readonly IDataTypeService _dataTypeService;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Asp.Versioning;
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.Factories;
|
||||
@@ -8,10 +9,12 @@ using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
using Umbraco.Cms.Web.Common.Authorization;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.DataType;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
[Authorize(Policy = AuthorizationPolicies.TreeAccessDataTypes)]
|
||||
public class UpdateDataTypeController : DataTypeControllerBase
|
||||
{
|
||||
private readonly IDataTypeService _dataTypeService;
|
||||
|
||||
-58
@@ -1,58 +0,0 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.Factories;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Document;
|
||||
using Umbraco.Cms.Core.Actions;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Security.Authorization;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Web.Common.Authorization;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Document;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class ByKeyPublishedDocumentController : DocumentControllerBase
|
||||
{
|
||||
private readonly IAuthorizationService _authorizationService;
|
||||
private readonly IContentEditingService _contentEditingService;
|
||||
private readonly IDocumentPresentationFactory _documentPresentationFactory;
|
||||
|
||||
public ByKeyPublishedDocumentController(
|
||||
IAuthorizationService authorizationService,
|
||||
IContentEditingService contentEditingService,
|
||||
IDocumentPresentationFactory documentPresentationFactory)
|
||||
{
|
||||
_authorizationService = authorizationService;
|
||||
_contentEditingService = contentEditingService;
|
||||
_documentPresentationFactory = documentPresentationFactory;
|
||||
}
|
||||
|
||||
[HttpGet("{id:guid}/published")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(PublishedDocumentResponseModel), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> ByKeyPublished(CancellationToken cancellationToken, Guid id)
|
||||
{
|
||||
AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync(
|
||||
User,
|
||||
ContentPermissionResource.WithKeys(ActionBrowse.ActionLetter, id),
|
||||
AuthorizationPolicies.ContentPermissionByResource);
|
||||
|
||||
if (!authorizationResult.Succeeded)
|
||||
{
|
||||
return Forbidden();
|
||||
}
|
||||
|
||||
IContent? content = await _contentEditingService.GetAsync(id);
|
||||
if (content == null || content.Published is false)
|
||||
{
|
||||
return DocumentNotFound();
|
||||
}
|
||||
|
||||
PublishedDocumentResponseModel model = await _documentPresentationFactory.CreatePublishedResponseModelAsync(content);
|
||||
return Ok(model);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -15,7 +15,7 @@ namespace Umbraco.Cms.Api.Management.Controllers.Document.Collection;
|
||||
[VersionedApiBackOfficeRoute($"{Constants.Web.RoutePath.Collection}/{Constants.UdiEntityType.Document}")]
|
||||
[ApiExplorerSettings(GroupName = nameof(Constants.UdiEntityType.Document))]
|
||||
[Authorize(Policy = AuthorizationPolicies.TreeAccessDocuments)]
|
||||
public abstract class DocumentCollectionControllerBase : ContentCollectionControllerBase<IContent, DocumentCollectionResponseModel, DocumentValueResponseModel, DocumentVariantResponseModel>
|
||||
public abstract class DocumentCollectionControllerBase : ContentCollectionControllerBase<IContent, DocumentCollectionResponseModel, DocumentValueModel, DocumentVariantResponseModel>
|
||||
{
|
||||
protected DocumentCollectionControllerBase(IUmbracoMapper mapper)
|
||||
: base(mapper)
|
||||
|
||||
+1
-27
@@ -12,7 +12,6 @@ using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Document;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
[ApiVersion("1.1")]
|
||||
public class ValidateUpdateDocumentController : UpdateDocumentControllerBase
|
||||
{
|
||||
private readonly IContentEditingService _contentEditingService;
|
||||
@@ -33,35 +32,10 @@ public class ValidateUpdateDocumentController : UpdateDocumentControllerBase
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
[Obsolete("Please use version 1.1 of this API. Will be removed in V16.")]
|
||||
public async Task<IActionResult> Validate(CancellationToken cancellationToken, Guid id, UpdateDocumentRequestModel requestModel)
|
||||
=> await HandleRequest(id, requestModel, async () =>
|
||||
{
|
||||
var validateUpdateDocumentRequestModel = new ValidateUpdateDocumentRequestModel
|
||||
{
|
||||
Values = requestModel.Values,
|
||||
Variants = requestModel.Variants,
|
||||
Template = requestModel.Template,
|
||||
Cultures = null
|
||||
};
|
||||
|
||||
ValidateContentUpdateModel model = _documentEditingPresentationFactory.MapValidateUpdateModel(validateUpdateDocumentRequestModel);
|
||||
Attempt<ContentValidationResult, ContentEditingOperationStatus> result = await _contentEditingService.ValidateUpdateAsync(id, model);
|
||||
|
||||
return result.Success
|
||||
? Ok()
|
||||
: DocumentEditingOperationStatusResult(result.Status, requestModel, result.Result);
|
||||
});
|
||||
|
||||
[HttpPut("{id:guid}/validate")]
|
||||
[MapToApiVersion("1.1")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> ValidateV1_1(CancellationToken cancellationToken, Guid id, ValidateUpdateDocumentRequestModel requestModel)
|
||||
=> await HandleRequest(id, requestModel, async () =>
|
||||
{
|
||||
ValidateContentUpdateModel model = _documentEditingPresentationFactory.MapValidateUpdateModel(requestModel);
|
||||
ContentUpdateModel model = _documentEditingPresentationFactory.MapUpdateModel(requestModel);
|
||||
Attempt<ContentValidationResult, ContentEditingOperationStatus> result = await _contentEditingService.ValidateUpdateAsync(id, model);
|
||||
|
||||
return result.Success
|
||||
|
||||
+3
@@ -1,14 +1,17 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.Factories;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
using Umbraco.Cms.Web.Common.Authorization;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.DocumentType;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
[Authorize(Policy = AuthorizationPolicies.TreeAccessDocumentTypes)]
|
||||
public class ExportDocumentTypeController : DocumentTypeControllerBase
|
||||
{
|
||||
private readonly IContentTypeService _contentTypeService;
|
||||
|
||||
+3
-1
@@ -1,17 +1,19 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.DocumentType;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Membership;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services.ImportExport;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
using Umbraco.Cms.Web.Common.Authorization;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.DocumentType;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
[Authorize(Policy = AuthorizationPolicies.TreeAccessDocumentTypes)]
|
||||
public class ImportExistingDocumentTypeController : DocumentTypeControllerBase
|
||||
{
|
||||
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
|
||||
|
||||
+3
-1
@@ -1,17 +1,19 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.DocumentType;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Membership;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services.ImportExport;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
using Umbraco.Cms.Web.Common.Authorization;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.DocumentType;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
[Authorize(Policy = AuthorizationPolicies.TreeAccessDocumentTypes)]
|
||||
public class ImportNewDocumentTypeController : DocumentTypeControllerBase
|
||||
{
|
||||
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
|
||||
|
||||
+1
-1
@@ -32,7 +32,7 @@ public class ItemDocumentTypeItemController : DocumentTypeItemControllerBase
|
||||
return Ok(Enumerable.Empty<DocumentTypeItemResponseModel>());
|
||||
}
|
||||
|
||||
IEnumerable<IContentType> contentTypes = _contentTypeService.GetMany(ids);
|
||||
IEnumerable<IContentType> contentTypes = _contentTypeService.GetAll(ids);
|
||||
List<DocumentTypeItemResponseModel> responseModels = _mapper.MapEnumerable<IContentType, DocumentTypeItemResponseModel>(contentTypes);
|
||||
return await Task.FromResult(Ok(responseModels));
|
||||
}
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ public class SearchDocumentTypeItemController : DocumentTypeItemControllerBase
|
||||
return await Task.FromResult(Ok(new PagedModel<DocumentTypeItemResponseModel> { Total = searchResult.Total }));
|
||||
}
|
||||
|
||||
IEnumerable<IContentType> contentTypes = _contentTypeService.GetMany(searchResult.Items.Select(item => item.Key).ToArray().EmptyNull());
|
||||
IEnumerable<IContentType> contentTypes = _contentTypeService.GetAll(searchResult.Items.Select(item => item.Key).ToArray().EmptyNull());
|
||||
var result = new PagedModel<DocumentTypeItemResponseModel>
|
||||
{
|
||||
Items = _mapper.MapEnumerable<IContentType, DocumentTypeItemResponseModel>(contentTypes),
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@ public class DocumentTypeTreeControllerBase : FolderTreeControllerBase<DocumentT
|
||||
protected override DocumentTypeTreeItemResponseModel[] MapTreeItemViewModels(Guid? parentKey, IEntitySlim[] entities)
|
||||
{
|
||||
var contentTypes = _contentTypeService
|
||||
.GetMany(entities.Select(entity => entity.Id).ToArray())
|
||||
.GetAll(entities.Select(entity => entity.Id).ToArray())
|
||||
.ToDictionary(contentType => contentType.Id);
|
||||
|
||||
return entities.Select(entity =>
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ namespace Umbraco.Cms.Api.Management.Controllers.Media.Collection;
|
||||
[VersionedApiBackOfficeRoute($"{Constants.Web.RoutePath.Collection}/{Constants.UdiEntityType.Media}")]
|
||||
[ApiExplorerSettings(GroupName = nameof(Constants.UdiEntityType.Media))]
|
||||
[Authorize(Policy = AuthorizationPolicies.SectionAccessMedia)]
|
||||
public abstract class MediaCollectionControllerBase : ContentCollectionControllerBase<IMedia, MediaCollectionResponseModel, MediaValueResponseModel, MediaVariantResponseModel>
|
||||
public abstract class MediaCollectionControllerBase : ContentCollectionControllerBase<IMedia, MediaCollectionResponseModel, MediaValueModel, MediaVariantResponseModel>
|
||||
{
|
||||
protected MediaCollectionControllerBase(IUmbracoMapper mapper)
|
||||
: base(mapper)
|
||||
|
||||
@@ -46,6 +46,6 @@ public class CreateMediaController : CreateMediaControllerBase
|
||||
|
||||
return result.Success
|
||||
? CreatedAtId<ByKeyMediaController>(controller => nameof(controller.ByKey), result.Result.Content!.Key)
|
||||
: ContentEditingOperationStatusResult(result.Status);
|
||||
: MediaEditingOperationStatusResult(result.Status, requestModel, result.Result.ValidationResult);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.Factories;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
using Umbraco.Cms.Web.Common.Authorization;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.MediaType;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
[Authorize(Policy = AuthorizationPolicies.TreeAccessMediaTypes)]
|
||||
public class ExportMediaTypeController : MediaTypeControllerBase
|
||||
{
|
||||
private readonly IMediaTypeService _mediaTypeService;
|
||||
|
||||
+3
-1
@@ -1,17 +1,19 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.MediaType;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Membership;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services.ImportExport;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
using Umbraco.Cms.Web.Common.Authorization;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.MediaType;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
[Authorize(Policy = AuthorizationPolicies.TreeAccessMediaTypes)]
|
||||
public class ImportExistingMediaTypeController : MediaTypeControllerBase
|
||||
{
|
||||
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
|
||||
|
||||
+3
-2
@@ -1,18 +1,19 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.Controllers.DocumentType;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.MediaType;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Membership;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services.ImportExport;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
using Umbraco.Cms.Web.Common.Authorization;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.MediaType;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
[Authorize(Policy = AuthorizationPolicies.TreeAccessMediaTypes)]
|
||||
public class ImportNewMediaTypeController : MediaTypeControllerBase
|
||||
{
|
||||
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
|
||||
|
||||
+1
-1
@@ -32,7 +32,7 @@ public class ItemMediaTypeItemController : MediaTypeItemControllerBase
|
||||
return Ok(Enumerable.Empty<MediaTypeItemResponseModel>());
|
||||
}
|
||||
|
||||
IEnumerable<IMediaType> mediaTypes = _mediaTypeService.GetMany(ids);
|
||||
IEnumerable<IMediaType> mediaTypes = _mediaTypeService.GetAll(ids);
|
||||
List<MediaTypeItemResponseModel> responseModels = _mapper.MapEnumerable<IMediaType, MediaTypeItemResponseModel>(mediaTypes);
|
||||
return await Task.FromResult(Ok(responseModels));
|
||||
}
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ public class SearchMediaTypeItemController : MediaTypeItemControllerBase
|
||||
return await Task.FromResult(Ok(new PagedModel<MediaTypeItemResponseModel> { Total = searchResult.Total }));
|
||||
}
|
||||
|
||||
IEnumerable<IMediaType> mediaTypes = _mediaTypeService.GetMany(searchResult.Items.Select(item => item.Key).ToArray().EmptyNull());
|
||||
IEnumerable<IMediaType> mediaTypes = _mediaTypeService.GetAll(searchResult.Items.Select(item => item.Key).ToArray().EmptyNull());
|
||||
var result = new PagedModel<MediaTypeItemResponseModel>
|
||||
{
|
||||
Items = _mapper.MapEnumerable<IMediaType, MediaTypeItemResponseModel>(mediaTypes),
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.Controllers.DocumentType;
|
||||
using Umbraco.Cms.Api.Management.Routing;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.MediaType;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
using Umbraco.Cms.Web.Common.Authorization;
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ public class MediaTypeTreeControllerBase : FolderTreeControllerBase<MediaTypeTre
|
||||
protected override MediaTypeTreeItemResponseModel[] MapTreeItemViewModels(Guid? parentKey, IEntitySlim[] entities)
|
||||
{
|
||||
var mediaTypes = _mediaTypeService
|
||||
.GetMany(entities.Select(entity => entity.Id).ToArray())
|
||||
.GetAll(entities.Select(entity => entity.Id).ToArray())
|
||||
.ToDictionary(contentType => contentType.Id);
|
||||
|
||||
return entities.Select(entity =>
|
||||
|
||||
+3
@@ -1,14 +1,17 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.Factories;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.MemberType;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Services.ContentTypeEditing;
|
||||
using Umbraco.Cms.Web.Common.Authorization;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.MemberType;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
[Authorize(Policy = AuthorizationPolicies.TreeAccessMemberTypes)]
|
||||
public class AvailableCompositionMemberTypeController : MemberTypeControllerBase
|
||||
{
|
||||
private readonly IMemberTypeEditingService _memberTypeEditingService;
|
||||
|
||||
+3
@@ -1,4 +1,5 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.MemberType;
|
||||
@@ -6,10 +7,12 @@ using Umbraco.Cms.Core.Mapping;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
using Umbraco.Cms.Web.Common.Authorization;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.MemberType;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
[Authorize(Policy = AuthorizationPolicies.TreeAccessMemberTypes)]
|
||||
public class CompositionReferenceMemberTypeController : MemberTypeControllerBase
|
||||
{
|
||||
private readonly IMemberTypeService _memberTypeService;
|
||||
|
||||
+1
@@ -9,6 +9,7 @@ using Umbraco.Cms.Web.Common.Authorization;
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.MemberType;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
[Authorize(Policy = AuthorizationPolicies.TreeAccessMemberTypes)]
|
||||
public class ConfigurationMemberTypeController : MemberTypeControllerBase
|
||||
{
|
||||
private readonly IConfigurationPresentationFactory _configurationPresentationFactory;
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
using Umbraco.Cms.Web.Common.Authorization;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.MemberType;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
[Authorize(Policy = AuthorizationPolicies.TreeAccessMemberTypes)]
|
||||
public class CopyMemberTypeController : MemberTypeControllerBase
|
||||
{
|
||||
private readonly IMemberTypeService _memberTypeService;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.Factories;
|
||||
@@ -9,10 +10,12 @@ using Umbraco.Cms.Core.Models.ContentTypeEditing;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services.ContentTypeEditing;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
using Umbraco.Cms.Web.Common.Authorization;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.MemberType;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
[Authorize(Policy = AuthorizationPolicies.TreeAccessMemberTypes)]
|
||||
public class CreateMemberTypeController : MemberTypeControllerBase
|
||||
{
|
||||
private readonly IMemberTypeEditingPresentationFactory _memberTypeEditingPresentationFactory;
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
using Umbraco.Cms.Web.Common.Authorization;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.MemberType;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
[Authorize(Policy = AuthorizationPolicies.TreeAccessMemberTypes)]
|
||||
public class DeleteMemberTypeController : MemberTypeControllerBase
|
||||
{
|
||||
private readonly IMemberTypeService _memberTypeService;
|
||||
|
||||
+1
-1
@@ -32,7 +32,7 @@ public class ItemMemberTypeItemController : MemberTypeItemControllerBase
|
||||
return Ok(Enumerable.Empty<MemberTypeItemResponseModel>());
|
||||
}
|
||||
|
||||
IEnumerable<IMemberType> memberTypes = _memberTypeService.GetMany(ids);
|
||||
IEnumerable<IMemberType> memberTypes = _memberTypeService.GetAll(ids);
|
||||
List<MemberTypeItemResponseModel> responseModels = _mapper.MapEnumerable<IMemberType, MemberTypeItemResponseModel>(memberTypes);
|
||||
return Ok(responseModels);
|
||||
}
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ public class SearchMemberTypeItemController : MemberTypeItemControllerBase
|
||||
return await Task.FromResult(Ok(new PagedModel<MemberTypeItemResponseModel> { Total = searchResult.Total }));
|
||||
}
|
||||
|
||||
IEnumerable<IMemberType> memberTypes = _memberTypeService.GetMany(searchResult.Items.Select(item => item.Key).ToArray());
|
||||
IEnumerable<IMemberType> memberTypes = _memberTypeService.GetAll(searchResult.Items.Select(item => item.Key).ToArray());
|
||||
var result = new PagedModel<MemberTypeItemResponseModel>
|
||||
{
|
||||
Items = _mapper.MapEnumerable<IMemberType, MemberTypeItemResponseModel>(memberTypes),
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Umbraco.Cms.Api.Management.Controllers.MemberType;
|
||||
|
||||
[VersionedApiBackOfficeRoute(Constants.UdiEntityType.MemberType)]
|
||||
[ApiExplorerSettings(GroupName = "Member Type")]
|
||||
[Authorize(Policy = AuthorizationPolicies.TreeAccessMemberTypes)]
|
||||
[Authorize(Policy = AuthorizationPolicies.TreeAccessMembersOrMemberTypes)]
|
||||
public abstract class MemberTypeControllerBase : ManagementApiControllerBase
|
||||
{
|
||||
protected IActionResult OperationStatusResult(ContentTypeOperationStatus status)
|
||||
|
||||
+2
-2
@@ -13,7 +13,7 @@ namespace Umbraco.Cms.Api.Management.Controllers.MemberType.Tree;
|
||||
|
||||
[VersionedApiBackOfficeRoute($"{Constants.Web.RoutePath.Tree}/{Constants.UdiEntityType.MemberType}")]
|
||||
[ApiExplorerSettings(GroupName = "Member Type")]
|
||||
[Authorize(Policy = AuthorizationPolicies.TreeAccessMemberTypes)]
|
||||
[Authorize(Policy = AuthorizationPolicies.TreeAccessMembersOrMemberTypes)]
|
||||
public class MemberTypeTreeControllerBase : NamedEntityTreeControllerBase<MemberTypeTreeItemResponseModel>
|
||||
{
|
||||
private readonly IMemberTypeService _memberTypeService;
|
||||
@@ -27,7 +27,7 @@ public class MemberTypeTreeControllerBase : NamedEntityTreeControllerBase<Member
|
||||
protected override MemberTypeTreeItemResponseModel[] MapTreeItemViewModels(Guid? parentKey, IEntitySlim[] entities)
|
||||
{
|
||||
var memberTypes = _memberTypeService
|
||||
.GetMany(entities.Select(entity => entity.Id).ToArray())
|
||||
.GetAll(entities.Select(entity => entity.Id).ToArray())
|
||||
.ToDictionary(contentType => contentType.Id);
|
||||
|
||||
return entities.Select(entity =>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.Factories;
|
||||
@@ -10,10 +11,12 @@ using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.ContentTypeEditing;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
using Umbraco.Cms.Web.Common.Authorization;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.MemberType;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
[Authorize(Policy = AuthorizationPolicies.TreeAccessMemberTypes)]
|
||||
public class UpdateMemberTypeController : MemberTypeControllerBase
|
||||
{
|
||||
private readonly IMemberTypeEditingPresentationFactory _memberTypeEditingPresentationFactory;
|
||||
|
||||
+10
-3
@@ -1,18 +1,25 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core.PublishedCache;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.PublishedCache;
|
||||
|
||||
[Obsolete("This controller no longer serves a purpose")]
|
||||
[ApiVersion("1.0")]
|
||||
public class CollectPublishedCacheController : PublishedCacheControllerBase
|
||||
{
|
||||
private readonly IPublishedSnapshotService _publishedSnapshotService;
|
||||
|
||||
public CollectPublishedCacheController(IPublishedSnapshotService publishedSnapshotService)
|
||||
=> _publishedSnapshotService = publishedSnapshotService;
|
||||
|
||||
[HttpPost("collect")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(StatusCodes.Status501NotImplemented)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> Collect(CancellationToken cancellationToken)
|
||||
{
|
||||
return StatusCode(StatusCodes.Status501NotImplemented);
|
||||
GC.Collect();
|
||||
await _publishedSnapshotService.CollectAsync();
|
||||
return Ok();
|
||||
}
|
||||
}
|
||||
|
||||
+4
-3
@@ -8,16 +8,17 @@ namespace Umbraco.Cms.Api.Management.Controllers.PublishedCache;
|
||||
[ApiVersion("1.0")]
|
||||
public class RebuildPublishedCacheController : PublishedCacheControllerBase
|
||||
{
|
||||
private readonly IDatabaseCacheRebuilder _databaseCacheRebuilder;
|
||||
private readonly IPublishedSnapshotService _publishedSnapshotService;
|
||||
|
||||
public RebuildPublishedCacheController(IDatabaseCacheRebuilder databaseCacheRebuilder) => _databaseCacheRebuilder = databaseCacheRebuilder;
|
||||
public RebuildPublishedCacheController(IPublishedSnapshotService publishedSnapshotService)
|
||||
=> _publishedSnapshotService = publishedSnapshotService;
|
||||
|
||||
[HttpPost("rebuild")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> Rebuild(CancellationToken cancellationToken)
|
||||
{
|
||||
_databaseCacheRebuilder.Rebuild();
|
||||
_publishedSnapshotService.Rebuild();
|
||||
return await Task.FromResult(Ok());
|
||||
}
|
||||
}
|
||||
|
||||
+8
-5
@@ -1,18 +1,21 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core.PublishedCache;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.PublishedCache;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
[Obsolete("This no longer relevant since snapshots are no longer used")]
|
||||
public class StatusPublishedCacheController : PublishedCacheControllerBase
|
||||
{
|
||||
private readonly IPublishedSnapshotStatus _publishedSnapshotStatus;
|
||||
|
||||
public StatusPublishedCacheController(IPublishedSnapshotStatus publishedSnapshotStatus)
|
||||
=> _publishedSnapshotStatus = publishedSnapshotStatus;
|
||||
|
||||
[HttpGet("status")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(StatusCodes.Status501NotImplemented)]
|
||||
[ProducesResponseType(typeof(string), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<string>> Status(CancellationToken cancellationToken)
|
||||
{
|
||||
return StatusCode(StatusCodes.Status501NotImplemented);
|
||||
}
|
||||
=> await Task.FromResult(Ok(_publishedSnapshotStatus.GetStatus()));
|
||||
}
|
||||
|
||||
@@ -34,6 +34,8 @@ namespace Umbraco.Cms.Api.Management.Controllers.Security;
|
||||
[ApiExplorerSettings(IgnoreApi = true)]
|
||||
public class BackOfficeController : SecurityControllerBase
|
||||
{
|
||||
private static long? _loginDurationAverage;
|
||||
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
private readonly IBackOfficeSignInManager _backOfficeSignInManager;
|
||||
private readonly IBackOfficeUserManager _backOfficeUserManager;
|
||||
@@ -42,7 +44,6 @@ public class BackOfficeController : SecurityControllerBase
|
||||
private readonly IBackOfficeTwoFactorOptions _backOfficeTwoFactorOptions;
|
||||
private readonly IUserTwoFactorLoginService _userTwoFactorLoginService;
|
||||
private readonly IBackOfficeExternalLoginService _externalLoginService;
|
||||
private readonly IBackOfficeUserClientCredentialsManager _backOfficeUserClientCredentialsManager;
|
||||
|
||||
private const string RedirectFlowParameter = "flow";
|
||||
private const string RedirectStatusParameter = "status";
|
||||
@@ -56,8 +57,7 @@ public class BackOfficeController : SecurityControllerBase
|
||||
ILogger<BackOfficeController> logger,
|
||||
IBackOfficeTwoFactorOptions backOfficeTwoFactorOptions,
|
||||
IUserTwoFactorLoginService userTwoFactorLoginService,
|
||||
IBackOfficeExternalLoginService externalLoginService,
|
||||
IBackOfficeUserClientCredentialsManager backOfficeUserClientCredentialsManager)
|
||||
IBackOfficeExternalLoginService externalLoginService)
|
||||
{
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
_backOfficeSignInManager = backOfficeSignInManager;
|
||||
@@ -67,7 +67,6 @@ public class BackOfficeController : SecurityControllerBase
|
||||
_backOfficeTwoFactorOptions = backOfficeTwoFactorOptions;
|
||||
_userTwoFactorLoginService = userTwoFactorLoginService;
|
||||
_externalLoginService = externalLoginService;
|
||||
_backOfficeUserClientCredentialsManager = backOfficeUserClientCredentialsManager;
|
||||
}
|
||||
|
||||
[HttpPost("login")]
|
||||
@@ -75,45 +74,65 @@ public class BackOfficeController : SecurityControllerBase
|
||||
[Authorize(Policy = AuthorizationPolicies.DenyLocalLoginIfConfigured)]
|
||||
public async Task<IActionResult> Login(CancellationToken cancellationToken, LoginRequestModel model)
|
||||
{
|
||||
IdentitySignInResult result = await _backOfficeSignInManager.PasswordSignInAsync(
|
||||
model.Username, model.Password, true, true);
|
||||
// Start a timed scope to ensure failed responses return is a consistent time
|
||||
var loginDuration = Math.Max(_loginDurationAverage ?? _securitySettings.Value.UserDefaultFailedLoginDurationInMilliseconds, _securitySettings.Value.UserMinimumFailedLoginDurationInMilliseconds);
|
||||
await using var timedScope = new TimedScope(loginDuration, cancellationToken);
|
||||
|
||||
if (result.IsNotAllowed)
|
||||
IdentitySignInResult result = await _backOfficeSignInManager.PasswordSignInAsync(model.Username, model.Password, true, true);
|
||||
if (result.Succeeded is false)
|
||||
{
|
||||
return StatusCode(StatusCodes.Status403Forbidden, new ProblemDetailsBuilder()
|
||||
.WithTitle("User is not allowed")
|
||||
.WithDetail("The operation is not allowed on the user")
|
||||
.Build());
|
||||
}
|
||||
|
||||
if (result.IsLockedOut)
|
||||
{
|
||||
return StatusCode(StatusCodes.Status403Forbidden, new ProblemDetailsBuilder()
|
||||
.WithTitle("User is locked")
|
||||
.WithDetail("The user is locked, and need to be unlocked before more login attempts can be executed.")
|
||||
.Build());
|
||||
}
|
||||
|
||||
if(result.RequiresTwoFactor)
|
||||
{
|
||||
string? twofactorView = _backOfficeTwoFactorOptions.GetTwoFactorView(model.Username);
|
||||
BackOfficeIdentityUser? attemptingUser = await _backOfficeUserManager.FindByNameAsync(model.Username);
|
||||
IEnumerable<string> enabledProviders = (await _userTwoFactorLoginService.GetProviderNamesAsync(attemptingUser!.Key)).Result.Where(x=>x.IsEnabledOnUser).Select(x=>x.ProviderName);
|
||||
return StatusCode(StatusCodes.Status402PaymentRequired, new RequiresTwoFactorResponseModel()
|
||||
// TODO: The result should include the user and whether the credentials were valid to avoid these additional checks
|
||||
BackOfficeIdentityUser? user = await _backOfficeUserManager.FindByNameAsync(model.Username.Trim()); // Align with UmbracoSignInManager and trim username!
|
||||
if (user is not null &&
|
||||
await _backOfficeUserManager.CheckPasswordAsync(user, model.Password))
|
||||
{
|
||||
TwoFactorLoginView = twofactorView,
|
||||
EnabledTwoFactorProviderNames = enabledProviders
|
||||
});
|
||||
// The credentials were correct, so cancel timed scope and provide a more detailed failure response
|
||||
await timedScope.CancelAsync();
|
||||
|
||||
if (result.IsNotAllowed)
|
||||
{
|
||||
return StatusCode(StatusCodes.Status403Forbidden, new ProblemDetailsBuilder()
|
||||
.WithTitle("User is not allowed")
|
||||
.WithDetail("The operation is not allowed on the user")
|
||||
.Build());
|
||||
}
|
||||
|
||||
if (result.IsLockedOut)
|
||||
{
|
||||
return StatusCode(StatusCodes.Status403Forbidden, new ProblemDetailsBuilder()
|
||||
.WithTitle("User is locked")
|
||||
.WithDetail("The user is locked, and need to be unlocked before more login attempts can be executed.")
|
||||
.Build());
|
||||
}
|
||||
|
||||
if (result.RequiresTwoFactor)
|
||||
{
|
||||
string? twofactorView = _backOfficeTwoFactorOptions.GetTwoFactorView(model.Username);
|
||||
IEnumerable<string> enabledProviders = (await _userTwoFactorLoginService.GetProviderNamesAsync(user.Key)).Result.Where(x => x.IsEnabledOnUser).Select(x => x.ProviderName);
|
||||
|
||||
return StatusCode(StatusCodes.Status402PaymentRequired, new RequiresTwoFactorResponseModel()
|
||||
{
|
||||
TwoFactorLoginView = twofactorView,
|
||||
EnabledTwoFactorProviderNames = enabledProviders
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return StatusCode(StatusCodes.Status401Unauthorized, new ProblemDetailsBuilder()
|
||||
.WithTitle("Invalid credentials")
|
||||
.WithDetail("The provided credentials are invalid. User has not been signed in.")
|
||||
.Build());
|
||||
}
|
||||
|
||||
if (result.Succeeded)
|
||||
{
|
||||
return Ok();
|
||||
}
|
||||
return StatusCode(StatusCodes.Status401Unauthorized, new ProblemDetailsBuilder()
|
||||
.WithTitle("Invalid credentials")
|
||||
.WithDetail("The provided credentials are invalid. User has not been signed in.")
|
||||
.Build());
|
||||
// Set initial or update average (successful) login duration
|
||||
_loginDurationAverage = _loginDurationAverage is long average
|
||||
? (average + (long)timedScope.Elapsed.TotalMilliseconds) / 2
|
||||
: (long)timedScope.Elapsed.TotalMilliseconds;
|
||||
|
||||
// Cancel the timed scope (we don't want to unnecessarily wait on a successful response)
|
||||
await timedScope.CancelAsync();
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
@@ -169,19 +188,13 @@ public class BackOfficeController : SecurityControllerBase
|
||||
OpenIddictRequest? request = context.GetOpenIddictServerRequest();
|
||||
if (request == null)
|
||||
{
|
||||
return BadRequest(new OpenIddictResponse
|
||||
{
|
||||
Error = "No context found", ErrorDescription = "Unable to obtain context from the current request."
|
||||
});
|
||||
return BadRequest("Unable to obtain OpenID data from the current request");
|
||||
}
|
||||
|
||||
// make sure we keep member authentication away from this endpoint
|
||||
if (request.ClientId is Constants.OAuthClientIds.Member)
|
||||
{
|
||||
return BadRequest(new OpenIddictResponse
|
||||
{
|
||||
Error = "Invalid 'client ID'", ErrorDescription = "The specified 'client_id' is not valid."
|
||||
});
|
||||
return BadRequest("The specified client ID cannot be used here.");
|
||||
}
|
||||
|
||||
return request.IdentityProvider.IsNullOrWhiteSpace()
|
||||
@@ -189,72 +202,22 @@ public class BackOfficeController : SecurityControllerBase
|
||||
: await AuthorizeExternal(request);
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[HttpPost("token")]
|
||||
[MapToApiVersion("1.0")]
|
||||
public async Task<IActionResult> Token()
|
||||
{
|
||||
HttpContext context = _httpContextAccessor.GetRequiredHttpContext();
|
||||
OpenIddictRequest? request = context.GetOpenIddictServerRequest();
|
||||
if (request == null)
|
||||
{
|
||||
return BadRequest(new OpenIddictResponse
|
||||
{
|
||||
Error = "No context found", ErrorDescription = "Unable to obtain context from the current request."
|
||||
});
|
||||
}
|
||||
|
||||
if (request.IsAuthorizationCodeGrantType() || request.IsRefreshTokenGrantType())
|
||||
{
|
||||
// attempt to authorize against the supplied the authorization code
|
||||
AuthenticateResult authenticateResult = await context.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
|
||||
|
||||
return authenticateResult is { Succeeded: true, Principal: not null }
|
||||
? new SignInResult(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, authenticateResult.Principal)
|
||||
: BadRequest(new OpenIddictResponse
|
||||
{
|
||||
Error = "Authorization failed", ErrorDescription = "The supplied authorization could not be verified."
|
||||
});
|
||||
}
|
||||
|
||||
if (request.IsClientCredentialsGrantType())
|
||||
{
|
||||
// if we get here, the client ID and secret are valid (verified by OpenIddict)
|
||||
|
||||
// grab the user associated with the client ID
|
||||
BackOfficeIdentityUser? associatedUser = await _backOfficeUserClientCredentialsManager.FindUserAsync(request.ClientId!);
|
||||
|
||||
if (associatedUser is not null)
|
||||
{
|
||||
// log current datetime as last login (this also ensures that the user is not flagged as inactive)
|
||||
associatedUser.LastLoginDateUtc = DateTime.UtcNow;
|
||||
await _backOfficeUserManager.UpdateAsync(associatedUser);
|
||||
|
||||
return await SignInBackOfficeUser(associatedUser, request);
|
||||
}
|
||||
|
||||
// if this happens, the OpenIddict applications have somehow gone out of sync with the Umbraco users
|
||||
_logger.LogError("The user associated with the client ID ({clientId}) could not be found", request.ClientId);
|
||||
return BadRequest(new OpenIddictResponse
|
||||
{
|
||||
Error = "Authorization failed", ErrorDescription = "The user associated with the supplied 'client_id' could not be found."
|
||||
});
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("The requested grant type is not supported.");
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[HttpGet("signout")]
|
||||
[MapToApiVersion("1.0")]
|
||||
public async Task<IActionResult> Signout(CancellationToken cancellationToken)
|
||||
{
|
||||
var userName = await GetUserNameFromAuthCookie();
|
||||
AuthenticateResult cookieAuthResult = await HttpContext.AuthenticateAsync(Constants.Security.BackOfficeAuthenticationType);
|
||||
var userName = cookieAuthResult.Principal?.Identity?.Name;
|
||||
var userId = cookieAuthResult.Principal?.Identity?.GetUserId();
|
||||
|
||||
await _backOfficeSignInManager.SignOutAsync();
|
||||
_backOfficeUserManager.NotifyLogoutSuccess(cookieAuthResult.Principal ?? User, userId);
|
||||
|
||||
_logger.LogInformation("User {UserName} from IP address {RemoteIpAddress} has logged out",
|
||||
userName ?? "UNKNOWN", HttpContext.Connection.RemoteIpAddress);
|
||||
_logger.LogInformation(
|
||||
"User {UserName} from IP address {RemoteIpAddress} has logged out",
|
||||
userName ?? "UNKNOWN",
|
||||
HttpContext.Connection.RemoteIpAddress);
|
||||
|
||||
// Returning a SignOutResult will ask OpenIddict to redirect the user agent
|
||||
// to the post_logout_redirect_uri specified by the client application.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.Extensions;
|
||||
@@ -9,10 +10,12 @@ using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
using Umbraco.Cms.Web.Common.Authorization;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Stylesheet;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
[Authorize(Policy = AuthorizationPolicies.TreeAccessStylesheets)]
|
||||
public class CreateStylesheetController : StylesheetControllerBase
|
||||
{
|
||||
private readonly IStylesheetService _stylesheetService;
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.Extensions;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
using Umbraco.Cms.Web.Common.Authorization;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Stylesheet;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
[Authorize(Policy = AuthorizationPolicies.TreeAccessStylesheets)]
|
||||
public class DeleteStylesheetController : StylesheetControllerBase
|
||||
{
|
||||
private readonly IStylesheetService _stylesheetService;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.Extensions;
|
||||
@@ -9,10 +10,12 @@ using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
using Umbraco.Cms.Web.Common.Authorization;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Stylesheet;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
[Authorize(Policy = AuthorizationPolicies.TreeAccessStylesheets)]
|
||||
public class RenameStylesheetController : StylesheetControllerBase
|
||||
{
|
||||
private readonly IStylesheetService _stylesheetService;
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Umbraco.Cms.Api.Management.Controllers.Stylesheet;
|
||||
|
||||
[VersionedApiBackOfficeRoute($"{Constants.UdiEntityType.Stylesheet}")]
|
||||
[ApiExplorerSettings(GroupName = "Stylesheet")]
|
||||
[Authorize(Policy = AuthorizationPolicies.TreeAccessStylesheets)]
|
||||
[Authorize(Policy = AuthorizationPolicies.TreeAccessStylesheetsOrDocumentOrMediaOrMember)]
|
||||
public class StylesheetControllerBase : FileSystemManagementControllerBase
|
||||
{
|
||||
protected IActionResult StylesheetOperationStatusResult(StylesheetOperationStatus status) =>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.Extensions;
|
||||
@@ -9,10 +10,12 @@ using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
using Umbraco.Cms.Web.Common.Authorization;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Stylesheet;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
[Authorize(Policy = AuthorizationPolicies.TreeAccessStylesheets)]
|
||||
public class UpdateStylesheetController : StylesheetControllerBase
|
||||
{
|
||||
private readonly IStylesheetService _stylesheetService;
|
||||
|
||||
+4
-13
@@ -1,6 +1,5 @@
|
||||
using System.Diagnostics;
|
||||
using System.Linq.Expressions;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Text;
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
@@ -9,9 +8,7 @@ using Umbraco.Cms.Api.Management.ViewModels.Template.Query;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.Models.TemplateQuery;
|
||||
using Umbraco.Cms.Core.PublishedCache;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.Navigation;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Template.Query;
|
||||
@@ -23,8 +20,6 @@ public class ExecuteTemplateQueryController : TemplateQueryControllerBase
|
||||
private readonly IVariationContextAccessor _variationContextAccessor;
|
||||
private readonly IPublishedValueFallback _publishedValueFallback;
|
||||
private readonly IContentTypeService _contentTypeService;
|
||||
private readonly IPublishedContentCache _contentCache;
|
||||
private readonly IDocumentNavigationQueryService _documentNavigationQueryService;
|
||||
|
||||
private static readonly string _indent = $"{Environment.NewLine} ";
|
||||
|
||||
@@ -32,16 +27,12 @@ public class ExecuteTemplateQueryController : TemplateQueryControllerBase
|
||||
IPublishedContentQuery publishedContentQuery,
|
||||
IVariationContextAccessor variationContextAccessor,
|
||||
IPublishedValueFallback publishedValueFallback,
|
||||
IContentTypeService contentTypeService,
|
||||
IPublishedContentCache contentCache,
|
||||
IDocumentNavigationQueryService documentNavigationQueryService)
|
||||
IContentTypeService contentTypeService)
|
||||
{
|
||||
_publishedContentQuery = publishedContentQuery;
|
||||
_variationContextAccessor = variationContextAccessor;
|
||||
_publishedValueFallback = publishedValueFallback;
|
||||
_contentTypeService = contentTypeService;
|
||||
_contentCache = contentCache;
|
||||
_documentNavigationQueryService = documentNavigationQueryService;
|
||||
}
|
||||
|
||||
[HttpPost("execute")]
|
||||
@@ -60,7 +51,7 @@ public class ExecuteTemplateQueryController : TemplateQueryControllerBase
|
||||
timer.Stop();
|
||||
|
||||
var contentTypeIconsByKey = _contentTypeService
|
||||
.GetMany(results.Select(content => content.ContentType.Key).Distinct())
|
||||
.GetAll(results.Select(content => content.ContentType.Key).Distinct())
|
||||
.ToDictionary(contentType => contentType.Key, contentType => contentType.Icon);
|
||||
|
||||
return await Task.FromResult(Ok(new TemplateQueryResultResponseModel
|
||||
@@ -118,13 +109,13 @@ public class ExecuteTemplateQueryController : TemplateQueryControllerBase
|
||||
queryExpression.Append($".ChildrenOfType(\"{model.DocumentTypeAlias}\")");
|
||||
return rootContent == null
|
||||
? Enumerable.Empty<IPublishedContent>()
|
||||
: rootContent.ChildrenOfType(_variationContextAccessor, _contentCache, _documentNavigationQueryService, model.DocumentTypeAlias);
|
||||
: rootContent.ChildrenOfType(_variationContextAccessor, model.DocumentTypeAlias);
|
||||
}
|
||||
|
||||
queryExpression.Append(".Children()");
|
||||
return rootContent == null
|
||||
? Enumerable.Empty<IPublishedContent>()
|
||||
: rootContent.Children(_variationContextAccessor, _contentCache, _documentNavigationQueryService);
|
||||
: rootContent.Children(_variationContextAccessor);
|
||||
}
|
||||
|
||||
private IEnumerable<IPublishedContent> ApplyFiltering(IEnumerable<TemplateQueryExecuteFilterPresentationModel>? filters, IEnumerable<IPublishedContent> contentQuery, StringBuilder queryExpression)
|
||||
|
||||
+4
-1
@@ -1,4 +1,4 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Common.Builders;
|
||||
using Umbraco.Cms.Api.Management.Routing;
|
||||
@@ -17,6 +17,9 @@ public abstract class TemporaryFileControllerBase : ManagementApiControllerBase
|
||||
.WithTitle("File extension not allowed")
|
||||
.WithDetail("The file extension is not allowed.")
|
||||
.Build()),
|
||||
TemporaryFileOperationStatus.InvalidFileName => BadRequest(problemDetailsBuilder
|
||||
.WithTitle("The provided file name is not valid")
|
||||
.Build()),
|
||||
TemporaryFileOperationStatus.KeyAlreadyUsed => BadRequest(problemDetailsBuilder
|
||||
.WithTitle("Key already used")
|
||||
.WithDetail("The specified key is already used.")
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.Filters;
|
||||
@@ -21,7 +21,8 @@ public abstract class UpgradeControllerBase : ManagementApiControllerBase
|
||||
? Ok()
|
||||
: OperationStatusResult(status, problemDetailsBuilder => status switch
|
||||
{
|
||||
UpgradeOperationStatus.UpgradeFailed => StatusCode(StatusCodes.Status500InternalServerError,
|
||||
UpgradeOperationStatus.UpgradeFailed => StatusCode(
|
||||
StatusCodes.Status500InternalServerError,
|
||||
problemDetailsBuilder
|
||||
.WithTitle("Upgrade failed")
|
||||
.WithDetail(result?.ErrorMessage ?? "An unknown error occurred.")
|
||||
|
||||
-29
@@ -1,29 +0,0 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core.Security.OperationStatus;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.User.ClientCredentials;
|
||||
|
||||
[ApiExplorerSettings(GroupName = "User")]
|
||||
public abstract class ClientCredentialsUserControllerBase : UserControllerBase
|
||||
{
|
||||
protected IActionResult BackOfficeUserClientCredentialsOperationStatusResult(BackOfficeUserClientCredentialsOperationStatus status) =>
|
||||
OperationStatusResult(status, problemDetailsBuilder => status switch
|
||||
{
|
||||
BackOfficeUserClientCredentialsOperationStatus.InvalidUser => BadRequest(problemDetailsBuilder
|
||||
.WithTitle("Invalid user")
|
||||
.WithDetail("The specified user does not support this operation. Possibly caused by a mismatched client ID or an inapplicable user type.")
|
||||
.Build()),
|
||||
BackOfficeUserClientCredentialsOperationStatus.DuplicateClientId => BadRequest(problemDetailsBuilder
|
||||
.WithTitle("Duplicate client ID")
|
||||
.WithDetail("The specified client ID is already in use. Choose another client ID.")
|
||||
.Build()),
|
||||
BackOfficeUserClientCredentialsOperationStatus.InvalidClientId => BadRequest(problemDetailsBuilder
|
||||
.WithTitle("Invalid client ID")
|
||||
.WithDetail("The specified client ID is invalid. A valid client ID can only contain [a-z], [A-Z], [0-9], and [-._~].")
|
||||
.Build()),
|
||||
_ => StatusCode(StatusCodes.Status500InternalServerError, problemDetailsBuilder
|
||||
.WithTitle("Unknown client credentials operation status.")
|
||||
.Build()),
|
||||
});
|
||||
}
|
||||
-49
@@ -1,49 +0,0 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.User.ClientCredentials;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Security.Authorization;
|
||||
using Umbraco.Cms.Core.Security.OperationStatus;
|
||||
using Umbraco.Cms.Web.Common.Authorization;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.User.ClientCredentials;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class CreateClientCredentialsUserController : ClientCredentialsUserControllerBase
|
||||
{
|
||||
private readonly IBackOfficeUserClientCredentialsManager _backOfficeUserClientCredentialsManager;
|
||||
private readonly IAuthorizationService _authorizationService;
|
||||
|
||||
public CreateClientCredentialsUserController(
|
||||
IBackOfficeUserClientCredentialsManager backOfficeUserClientCredentialsManager,
|
||||
IAuthorizationService authorizationService)
|
||||
{
|
||||
_backOfficeUserClientCredentialsManager = backOfficeUserClientCredentialsManager;
|
||||
_authorizationService = authorizationService;
|
||||
}
|
||||
|
||||
[HttpPost("{id:guid}/client-credentials")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
public async Task<IActionResult> Create(CancellationToken cancellationToken, Guid id, CreateUserClientCredentialsRequestModel model)
|
||||
{
|
||||
AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync(
|
||||
User,
|
||||
UserPermissionResource.WithKeys(id),
|
||||
AuthorizationPolicies.UserPermissionByResource);
|
||||
|
||||
if (!authorizationResult.Succeeded)
|
||||
{
|
||||
return Forbidden();
|
||||
}
|
||||
|
||||
Attempt<BackOfficeUserClientCredentialsOperationStatus> result = await _backOfficeUserClientCredentialsManager.SaveAsync(id, model.ClientId, model.ClientSecret);
|
||||
return result.Success
|
||||
? Ok()
|
||||
: BackOfficeUserClientCredentialsOperationStatusResult(result.Result);
|
||||
}
|
||||
}
|
||||
-48
@@ -1,48 +0,0 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Security.Authorization;
|
||||
using Umbraco.Cms.Core.Security.OperationStatus;
|
||||
using Umbraco.Cms.Web.Common.Authorization;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.User.ClientCredentials;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class DeleteClientCredentialsUserController : ClientCredentialsUserControllerBase
|
||||
{
|
||||
private readonly IBackOfficeUserClientCredentialsManager _backOfficeUserClientCredentialsManager;
|
||||
private readonly IAuthorizationService _authorizationService;
|
||||
|
||||
public DeleteClientCredentialsUserController(
|
||||
IBackOfficeUserClientCredentialsManager backOfficeUserClientCredentialsManager,
|
||||
IAuthorizationService authorizationService)
|
||||
{
|
||||
_backOfficeUserClientCredentialsManager = backOfficeUserClientCredentialsManager;
|
||||
_authorizationService = authorizationService;
|
||||
}
|
||||
|
||||
[HttpDelete("{id:guid}/client-credentials/{clientId}")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
public async Task<IActionResult> Delete(CancellationToken cancellationToken, Guid id, string clientId)
|
||||
{
|
||||
AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync(
|
||||
User,
|
||||
UserPermissionResource.WithKeys(id),
|
||||
AuthorizationPolicies.UserPermissionByResource);
|
||||
|
||||
if (!authorizationResult.Succeeded)
|
||||
{
|
||||
return Forbidden();
|
||||
}
|
||||
|
||||
Attempt<BackOfficeUserClientCredentialsOperationStatus> result = await _backOfficeUserClientCredentialsManager.DeleteAsync(id, clientId);
|
||||
return result.Success
|
||||
? Ok()
|
||||
: BackOfficeUserClientCredentialsOperationStatusResult(result.Result);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user