Compare commits
280
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 | ||
|
|
009394982c | ||
|
|
a43db4ff05 | ||
|
|
8c4780380d | ||
|
|
d3a67fe4e0 | ||
|
|
910d70302e | ||
|
|
63a487bc0b | ||
|
|
a40eadcfce | ||
|
|
0c1daa290b | ||
|
|
8f26263178 | ||
|
|
635d9b83f9 | ||
|
|
14a0e62278 | ||
|
|
a76af1de9d | ||
|
|
609b5f76d4 | ||
|
|
9b19d63a6a | ||
|
|
e4dacf5c8c | ||
|
|
99125ef5cc | ||
|
|
264fbb6987 | ||
|
|
ac575662ac | ||
|
|
6939472f37 | ||
|
|
0387307301 | ||
|
|
e92b4f3172 | ||
|
|
c70c8d86f9 | ||
|
|
e733ec1717 | ||
|
|
fa7b81474c | ||
|
|
c5243e562e | ||
|
|
5c857aa97d | ||
|
|
25a5dde8e9 | ||
|
|
2e068bb73e | ||
|
|
842cacde19 | ||
|
|
6a453a0910 | ||
|
|
ff9903b14d | ||
|
|
d64bf5de22 | ||
|
|
9300d867fb | ||
|
|
eb0f8b5c24 | ||
|
|
d58768f26d | ||
|
|
3667217053 | ||
|
|
3e6116fcba | ||
|
|
bff293213d | ||
|
|
a47a1775f2 | ||
|
|
b2a2df7a8a |
+27
-25
@@ -12,25 +12,25 @@
|
||||
</ItemGroup>
|
||||
<!-- Microsoft packages -->
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.8" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="8.0.8" />
|
||||
<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="8.0.8" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.8" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.8" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.8" />
|
||||
<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.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.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.FileProviders.Embedded" Version="8.0.8" />
|
||||
<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.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Identity.Core" Version="8.0.8" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Identity.Stores" Version="8.0.8" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging" 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" />
|
||||
@@ -45,14 +45,14 @@
|
||||
<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.3.0" />
|
||||
<PackageVersion Include="Examine.Core" Version="3.3.0" />
|
||||
<PackageVersion Include="HtmlAgilityPack" Version="1.11.65" />
|
||||
<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" />
|
||||
<PackageVersion Include="MailKit" Version="4.7.1.1" />
|
||||
<PackageVersion Include="MailKit" Version="4.8.0" />
|
||||
<PackageVersion Include="Markdown" Version="2.2.1" />
|
||||
<PackageVersion Include="MessagePack" Version="2.5.172" />
|
||||
<PackageVersion Include="MessagePack" Version="2.5.192" />
|
||||
<PackageVersion Include="MiniProfiler.AspNetCore.Mvc" Version="4.3.8" />
|
||||
<PackageVersion Include="MiniProfiler.Shared" Version="4.3.8" />
|
||||
<PackageVersion Include="ncrontab" Version="3.3.3" />
|
||||
@@ -62,32 +62,34 @@
|
||||
<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.2" />
|
||||
<PackageVersion Include="Serilog.AspNetCore" Version="8.0.3" />
|
||||
<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="2.0.0" />
|
||||
<PackageVersion Include="Serilog.Formatting.Compact.Reader" Version="3.0.0" />
|
||||
<PackageVersion Include="Serilog.Settings.Configuration" Version="8.0.2" />
|
||||
<PackageVersion Include="Serilog.Settings.Configuration" Version="8.0.4" />
|
||||
<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.5" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.7" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp.Web" Version="3.1.3" />
|
||||
<PackageVersion Include="Swashbuckle.AspNetCore" Version="6.7.3" />
|
||||
<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.12.0" />
|
||||
<PackageVersion Include="Azure.Identity" Version="1.13.1" />
|
||||
<!-- 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="8.0.1" />
|
||||
<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="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>
|
||||
|
||||
+16
-12
@@ -5,8 +5,8 @@ parameters:
|
||||
displayName: Run SQL Server Integration Tests
|
||||
type: boolean
|
||||
default: false
|
||||
- name: sqlServerAcceptanceTests
|
||||
displayName: Run SQL Server Acceptance Tests
|
||||
- name: sqlServerLinuxAcceptanceTests
|
||||
displayName: Run SQL Server Linux Acceptance Tests
|
||||
type: boolean
|
||||
default: false
|
||||
- name: myGetDeploy
|
||||
@@ -75,7 +75,7 @@ stages:
|
||||
- job: A
|
||||
displayName: Build Umbraco CMS
|
||||
pool:
|
||||
vmImage: 'ubuntu-latest'
|
||||
vmImage: 'windows-latest'
|
||||
steps:
|
||||
- checkout: self
|
||||
submodules: true
|
||||
@@ -506,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
|
||||
@@ -517,7 +521,7 @@ stages:
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
|
||||
# Test
|
||||
- pwsh: npm run smokeTest --ignore-certificate-errors
|
||||
- pwsh: npm run smokeTestSqlite --ignore-certificate-errors
|
||||
displayName: Run Playwright tests
|
||||
continueOnError: true
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
@@ -553,18 +557,17 @@ stages:
|
||||
|
||||
- job:
|
||||
displayName: E2E Tests (SQL Server)
|
||||
# condition: or(eq(stageDependencies.Build.A.outputs['build.NBGV_PublicRelease'], 'True'), ${{parameters.sqlServerAcceptanceTests}}) # Outcommented due to timeouts
|
||||
condition: eq(${{parameters.sqlServerAcceptanceTests}}, True)
|
||||
variables:
|
||||
# Connection string
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN: Data Source=(localdb)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\Umbraco.mdf;Integrated Security=True
|
||||
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:
|
||||
@@ -590,7 +593,8 @@ stages:
|
||||
- pwsh: |
|
||||
"UMBRACO_USER_LOGIN=$(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSEREMAIL)
|
||||
UMBRACO_USER_PASSWORD=$(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
|
||||
URL=$(ASPNETCORE_URLS)" | Out-File .env
|
||||
URL=$(ASPNETCORE_URLS)
|
||||
STORAGE_STAGE_PATH=$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/playwright/.auth/user.json" | Out-File .env
|
||||
displayName: Generate .env
|
||||
workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest
|
||||
|
||||
@@ -708,7 +712,7 @@ stages:
|
||||
dependsOn:
|
||||
- Unit
|
||||
- Integration
|
||||
# - E2E
|
||||
# - E2E
|
||||
condition: and(succeeded(), or(eq(dependencies.Build.outputs['A.build.NBGV_PublicRelease'], 'True'), ${{parameters.myGetDeploy}}))
|
||||
jobs:
|
||||
- job:
|
||||
|
||||
@@ -3,14 +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
|
||||
## Uncomment after merged to v15/dev
|
||||
## - v15/dev
|
||||
# schedules:
|
||||
# - cron: '0 0 * * *'
|
||||
# displayName: Daily midnight build
|
||||
# branches:
|
||||
# include:
|
||||
# - v14/dev
|
||||
|
||||
variables:
|
||||
nodeVersion: 20
|
||||
@@ -25,8 +23,8 @@ variables:
|
||||
NODE_OPTIONS: --max_old_space_size=16384
|
||||
|
||||
parameters:
|
||||
- name: runSqlServerE2ETests
|
||||
displayName: Run the SQL Server E2E Tests
|
||||
- name: runSmokeTests
|
||||
displayName: Run the smoke tests
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
@@ -109,7 +107,7 @@ stages:
|
||||
# E2E Tests
|
||||
- job:
|
||||
displayName: E2E Tests (SQLite)
|
||||
timeoutInMinutes: 120
|
||||
timeoutInMinutes: 180
|
||||
variables:
|
||||
# Connection string
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN: Data Source=Umbraco;Mode=Memory;Cache=Shared;Foreign Keys=True;Pooling=True
|
||||
@@ -207,7 +205,10 @@ stages:
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
|
||||
# Test
|
||||
- pwsh: npm run test --ignore-certificate-errors
|
||||
- ${{ if eq(parameters.runSmokeTests, true) }}:
|
||||
pwsh: npm run smokeTestSqlite --ignore-certificate-errors
|
||||
${{ else }}:
|
||||
pwsh: npm run testSqlite --ignore-certificate-errors
|
||||
displayName: Run Playwright tests
|
||||
continueOnError: true
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
@@ -243,8 +244,7 @@ stages:
|
||||
|
||||
- job:
|
||||
displayName: E2E Tests (SQL Server)
|
||||
condition: and(succeeded(), ${{ eq(parameters.runSqlServerE2ETests, true) }})
|
||||
timeoutInMinutes: 120
|
||||
timeoutInMinutes: 180
|
||||
variables:
|
||||
# Connection string
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN: Data Source=(localdb)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\Umbraco.mdf;Integrated Security=True
|
||||
@@ -280,7 +280,8 @@ stages:
|
||||
- pwsh: |
|
||||
"UMBRACO_USER_LOGIN=$(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSEREMAIL)
|
||||
UMBRACO_USER_PASSWORD=$(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
|
||||
URL=$(ASPNETCORE_URLS)" | Out-File .env
|
||||
URL=$(ASPNETCORE_URLS)
|
||||
STORAGE_STAGE_PATH=$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/playwright/.auth/user.json" | Out-File .env
|
||||
displayName: Generate .env
|
||||
workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest
|
||||
|
||||
@@ -351,7 +352,10 @@ stages:
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
|
||||
# Test
|
||||
- pwsh: npm run test --ignore-certificate-errors
|
||||
- ${{ if eq(parameters.runSmokeTests, true) }}:
|
||||
pwsh: npm run smokeTest --ignore-certificate-errors
|
||||
${{ else }}:
|
||||
pwsh: npm run test --ignore-certificate-errors
|
||||
displayName: Run Playwright tests
|
||||
continueOnError: true
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
|
||||
@@ -26,7 +26,7 @@ steps:
|
||||
useSameBranch: true
|
||||
waitForQueuedBuildsToFinish: false
|
||||
storeInEnvironmentVariable: false
|
||||
templateParameters: 'sqlServerIntegrationTests: true, sqlServerAcceptanceTests: true, forceReleaseTestFilter: true, myGetDeploy: true, isNightly: true'
|
||||
templateParameters: 'sqlServerIntegrationTests: true, forceReleaseTestFilter: true, myGetDeploy: true, isNightly: true'
|
||||
authenticationMethod: 'OAuth Token'
|
||||
enableBuildInQueueCondition: false
|
||||
dependentOnSuccessfulBuildCondition: false
|
||||
|
||||
@@ -46,7 +46,9 @@ public static class UmbracoBuilderAuthExtensions
|
||||
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
|
||||
@@ -58,7 +60,8 @@ public static class UmbracoBuilderAuthExtensions
|
||||
options
|
||||
.UseAspNetCore()
|
||||
.EnableAuthorizationEndpointPassthrough()
|
||||
.EnableLogoutEndpointPassthrough();
|
||||
.EnableLogoutEndpointPassthrough()
|
||||
.EnableUserinfoEndpointPassthrough();
|
||||
|
||||
// Enable reference tokens
|
||||
// - see https://documentation.openiddict.com/configuration/token-storage.html
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -60,6 +60,7 @@ public static class UmbracoBuilderExtensions
|
||||
builder.Services.AddSingleton<IApiMediaQueryService, ApiMediaQueryService>();
|
||||
builder.Services.AddTransient<IMemberApplicationManager, MemberApplicationManager>();
|
||||
builder.Services.AddTransient<IRequestMemberAccessService, RequestMemberAccessService>();
|
||||
builder.Services.AddTransient<ICurrentMemberClaimsProvider, CurrentMemberClaimsProvider>();
|
||||
|
||||
builder.Services.ConfigureOptions<ConfigureUmbracoDeliveryApiSwaggerGenOptions>();
|
||||
builder.AddUmbracoApiOpenApiUI();
|
||||
|
||||
@@ -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,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
|
||||
};
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Api.Delivery.Indexing.Selectors;
|
||||
using Umbraco.Cms.Core.DeliveryApi;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.PublishedCache;
|
||||
using Umbraco.Extensions;
|
||||
@@ -10,10 +12,22 @@ public sealed class AncestorsSelector : QueryOptionBase, ISelectorHandler
|
||||
{
|
||||
private const string AncestorsSpecifier = "ancestors:";
|
||||
private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor;
|
||||
private readonly IRequestPreviewService _requestPreviewService;
|
||||
|
||||
public AncestorsSelector(IPublishedSnapshotAccessor publishedSnapshotAccessor, IRequestRoutingService requestRoutingService)
|
||||
: base(publishedSnapshotAccessor, requestRoutingService) =>
|
||||
[Obsolete("Please use the non-obsolete constructor. Will be removed in V17.")]
|
||||
public AncestorsSelector(
|
||||
IPublishedSnapshotAccessor publishedSnapshotAccessor,
|
||||
IRequestRoutingService requestRoutingService)
|
||||
: this(publishedSnapshotAccessor, requestRoutingService, StaticServiceProvider.Instance.GetRequiredService<IRequestPreviewService>())
|
||||
{
|
||||
}
|
||||
|
||||
public AncestorsSelector(IPublishedSnapshotAccessor publishedSnapshotAccessor, IRequestRoutingService requestRoutingService, IRequestPreviewService requestPreviewService)
|
||||
: base(publishedSnapshotAccessor, requestRoutingService)
|
||||
{
|
||||
_publishedSnapshotAccessor = publishedSnapshotAccessor;
|
||||
_requestPreviewService = requestPreviewService;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool CanHandle(string query)
|
||||
@@ -37,10 +51,20 @@ public sealed class AncestorsSelector : QueryOptionBase, ISelectorHandler
|
||||
};
|
||||
}
|
||||
|
||||
IPublishedSnapshot publishedSnapshot = _publishedSnapshotAccessor.GetRequiredPublishedSnapshot();
|
||||
IPublishedContentCache contentCache = _publishedSnapshotAccessor.GetRequiredPublishedSnapshot()?.Content
|
||||
?? throw new InvalidOperationException("Could not obtain the content cache");
|
||||
|
||||
IPublishedContent contentItem = publishedSnapshot.Content?.GetById((Guid)id)
|
||||
?? throw new InvalidOperationException("Could not obtain the content cache");
|
||||
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();
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
+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;
|
||||
|
||||
@@ -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,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;
|
||||
|
||||
+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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
+1
-1
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ public class ByPathPartialViewController : PartialViewControllerBase
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
[HttpGet("{path}")]
|
||||
[HttpGet("{*path}")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(PartialViewResponseModel), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ public class DeletePartialViewController : PartialViewControllerBase
|
||||
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
|
||||
}
|
||||
|
||||
[HttpDelete("{path}")]
|
||||
[HttpDelete("{*path}")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ public class ByPathPartialViewFolderController : PartialViewFolderControllerBase
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
[HttpGet("{path}")]
|
||||
[HttpGet("{*path}")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(PartialViewFolderResponseModel), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ public class DeletePartialViewFolderController : PartialViewFolderControllerBase
|
||||
public DeletePartialViewFolderController(IPartialViewFolderService partialViewFolderService)
|
||||
=> _partialViewFolderService = partialViewFolderService;
|
||||
|
||||
[HttpDelete("{path}")]
|
||||
[HttpDelete("{*path}")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@ public class UpdatePartialViewController : PartialViewControllerBase
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
[HttpPut("{path}")]
|
||||
[HttpPut("{*path}")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
|
||||
@@ -23,7 +23,7 @@ public class ByPathScriptController : ScriptControllerBase
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
[HttpGet("{path}")]
|
||||
[HttpGet("{*path}")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(ScriptResponseModel), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
|
||||
@@ -22,7 +22,7 @@ public class DeleteScriptController : ScriptControllerBase
|
||||
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
|
||||
}
|
||||
|
||||
[HttpDelete("{path}")]
|
||||
[HttpDelete("{*path}")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ public class ByPathScriptFolderController : ScriptFolderControllerBase
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
[HttpGet("{path}")]
|
||||
[HttpGet("{*path}")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(ScriptFolderResponseModel), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ public class DeleteScriptFolderController : ScriptFolderControllerBase
|
||||
public DeleteScriptFolderController(IScriptFolderService scriptFolderService)
|
||||
=> _scriptFolderService = scriptFolderService;
|
||||
|
||||
[HttpDelete("{path}")]
|
||||
[HttpDelete("{*path}")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
|
||||
@@ -29,7 +29,7 @@ public class UpdateScriptController : ScriptControllerBase
|
||||
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
|
||||
}
|
||||
|
||||
[HttpPut("{path}")]
|
||||
[HttpPut("{*path}")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
|
||||
@@ -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;
|
||||
@@ -72,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]
|
||||
@@ -185,12 +207,17 @@ public class BackOfficeController : SecurityControllerBase
|
||||
[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.
|
||||
|
||||
@@ -23,7 +23,7 @@ public class ByPathStylesheetController : StylesheetControllerBase
|
||||
_umbracoMapper = umbracoMapper;
|
||||
}
|
||||
|
||||
[HttpGet("{path}")]
|
||||
[HttpGet("{*path}")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(StylesheetResponseModel), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
|
||||
@@ -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;
|
||||
@@ -22,7 +25,7 @@ public class DeleteStylesheetController : StylesheetControllerBase
|
||||
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
|
||||
}
|
||||
|
||||
[HttpDelete("{path}")]
|
||||
[HttpDelete("{*path}")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@ public class ByPathStylesheetFolderController : StylesheetFolderControllerBase
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
[HttpGet("{path}")]
|
||||
[HttpGet("{*path}")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(StylesheetFolderResponseModel), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ public class DeleteStylesheetFolderController : StylesheetFolderControllerBase
|
||||
public DeleteStylesheetFolderController(IStylesheetFolderService stylesheetFolderService)
|
||||
=> _stylesheetFolderService = stylesheetFolderService;
|
||||
|
||||
[HttpDelete("{path}")]
|
||||
[HttpDelete("{*path}")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
|
||||
@@ -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;
|
||||
@@ -29,7 +32,7 @@ public class UpdateStylesheetController : StylesheetControllerBase
|
||||
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
|
||||
}
|
||||
|
||||
[HttpPut("{path}")]
|
||||
[HttpPut("{*path}")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
|
||||
+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.")
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.Factories;
|
||||
@@ -8,12 +7,10 @@ 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.Webhook;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
[Authorize(Policy = AuthorizationPolicies.TreeAccessWebhooks)]
|
||||
public class CreateWebhookController : WebhookControllerBase
|
||||
{
|
||||
private readonly IWebhookService _webhookService;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core;
|
||||
@@ -7,12 +6,10 @@ 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.Webhook;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
[Authorize(Policy = AuthorizationPolicies.TreeAccessWebhooks)]
|
||||
public class DeleteWebhookController : WebhookControllerBase
|
||||
{
|
||||
private readonly IWebhookService _webhookService;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.Factories;
|
||||
@@ -8,12 +7,10 @@ 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.Webhook;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
[Authorize(Policy = AuthorizationPolicies.TreeAccessWebhooks)]
|
||||
public class UpdateWebhookController : WebhookControllerBase
|
||||
{
|
||||
private readonly IWebhookService _webhookService;
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Common.Builders;
|
||||
using Umbraco.Cms.Api.Management.Routing;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
using Umbraco.Cms.Web.Common.Authorization;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Webhook;
|
||||
|
||||
[VersionedApiBackOfficeRoute("webhook")]
|
||||
[ApiExplorerSettings(GroupName = "Webhook")]
|
||||
[Authorize(Policy = AuthorizationPolicies.TreeAccessWebhooks)]
|
||||
public abstract class WebhookControllerBase : ManagementApiControllerBase
|
||||
{
|
||||
protected IActionResult WebhookOperationStatusResult(WebhookOperationStatus status) =>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Api.Management.Factories;
|
||||
using Umbraco.Cms.Api.Management.Security;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Notifications;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.DependencyInjection;
|
||||
|
||||
@@ -9,6 +11,13 @@ internal static class AuditLogBuilderExtensions
|
||||
internal static IUmbracoBuilder AddAuditLogs(this IUmbracoBuilder builder)
|
||||
{
|
||||
builder.Services.AddTransient<IAuditLogPresentationFactory, AuditLogPresentationFactory>();
|
||||
builder.AddNotificationHandler<UserLoginSuccessNotification, BackOfficeUserManagerAuditer>();
|
||||
builder.AddNotificationHandler<UserLogoutSuccessNotification, BackOfficeUserManagerAuditer>();
|
||||
builder.AddNotificationHandler<UserLoginFailedNotification, BackOfficeUserManagerAuditer>();
|
||||
builder.AddNotificationHandler<UserForgotPasswordRequestedNotification, BackOfficeUserManagerAuditer>();
|
||||
builder.AddNotificationHandler<UserForgotPasswordChangedNotification, BackOfficeUserManagerAuditer>();
|
||||
builder.AddNotificationHandler<UserPasswordChangedNotification, BackOfficeUserManagerAuditer>();
|
||||
builder.AddNotificationHandler<UserPasswordResetNotification, BackOfficeUserManagerAuditer>();
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
+9
-4
@@ -58,13 +58,16 @@ internal static class BackOfficeAuthPolicyBuilderExtensions
|
||||
|
||||
AddAllowedApplicationsPolicy(AuthorizationPolicies.SectionAccessContent, Constants.Applications.Content);
|
||||
AddAllowedApplicationsPolicy(AuthorizationPolicies.SectionAccessContentOrMedia, Constants.Applications.Content, Constants.Applications.Media);
|
||||
AddAllowedApplicationsPolicy(AuthorizationPolicies.SectionAccessForContentTree,
|
||||
AddAllowedApplicationsPolicy(
|
||||
AuthorizationPolicies.SectionAccessForContentTree,
|
||||
Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Users,
|
||||
Constants.Applications.Settings, Constants.Applications.Packages, Constants.Applications.Members);
|
||||
AddAllowedApplicationsPolicy(AuthorizationPolicies.SectionAccessForMediaTree,
|
||||
AddAllowedApplicationsPolicy(
|
||||
AuthorizationPolicies.SectionAccessForMediaTree,
|
||||
Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Users,
|
||||
Constants.Applications.Settings, Constants.Applications.Packages, Constants.Applications.Members);
|
||||
AddAllowedApplicationsPolicy(AuthorizationPolicies.SectionAccessForMemberTree,
|
||||
AddAllowedApplicationsPolicy(
|
||||
AuthorizationPolicies.SectionAccessForMemberTree,
|
||||
Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members);
|
||||
AddAllowedApplicationsPolicy(AuthorizationPolicies.SectionAccessMedia, Constants.Applications.Media);
|
||||
AddAllowedApplicationsPolicy(AuthorizationPolicies.SectionAccessMembers, Constants.Applications.Members);
|
||||
@@ -84,11 +87,13 @@ internal static class BackOfficeAuthPolicyBuilderExtensions
|
||||
AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessMediaOrMediaTypes, Constants.Applications.Media, Constants.Applications.Settings);
|
||||
AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessMemberGroups, Constants.Applications.Members);
|
||||
AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessMemberTypes, Constants.Applications.Settings);
|
||||
AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessMembersOrMemberTypes, Constants.Applications.Settings, Constants.Applications.Members);
|
||||
AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessPartialViews, Constants.Applications.Settings);
|
||||
AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessRelationTypes, Constants.Applications.Settings);
|
||||
AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessScripts, Constants.Applications.Settings);
|
||||
AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessStylesheets, Constants.Applications.Settings);
|
||||
AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessTemplates, Constants.Applications.Settings);
|
||||
AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessStylesheetsOrDocumentOrMediaOrMember, Constants.Applications.Settings, Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members);
|
||||
AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessTemplates, Constants.Applications.Settings, Constants.Applications.Content);
|
||||
AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessWebhooks, Constants.Applications.Settings);
|
||||
|
||||
// Contextual permissions
|
||||
|
||||
@@ -52,8 +52,7 @@ public static partial class UmbracoBuilderExtensions
|
||||
hostingEnvironment,
|
||||
factory.GetRequiredService<ILogger<PhysicalFileSystem>>(),
|
||||
hostingEnvironment.MapPathContentRoot(path),
|
||||
hostingEnvironment.ToAbsolute(path)
|
||||
);
|
||||
hostingEnvironment.ToAbsolute(path));
|
||||
});
|
||||
|
||||
return builder;
|
||||
|
||||
+5
-4
@@ -52,8 +52,7 @@ public static partial class UmbracoBuilderExtensions
|
||||
factory.GetRequiredService<IUserRepository>(),
|
||||
factory.GetRequiredService<IRuntimeState>(),
|
||||
factory.GetRequiredService<IEventMessagesFactory>(),
|
||||
factory.GetRequiredService<ILogger<BackOfficeUserStore>>()
|
||||
))
|
||||
factory.GetRequiredService<ILogger<BackOfficeUserStore>>()))
|
||||
.AddUserManager<IBackOfficeUserManager, BackOfficeUserManager>()
|
||||
.AddSignInManager<IBackOfficeSignInManager, BackOfficeSignInManager>()
|
||||
.AddClaimsPrincipalFactory<BackOfficeClaimsPrincipalFactory>()
|
||||
@@ -94,14 +93,16 @@ public static partial class UmbracoBuilderExtensions
|
||||
/// <summary>
|
||||
/// Adds support for external login providers in Umbraco
|
||||
/// </summary>
|
||||
public static IUmbracoBuilder AddBackOfficeExternalLogins(this IUmbracoBuilder umbracoBuilder,
|
||||
public static IUmbracoBuilder AddBackOfficeExternalLogins(
|
||||
this IUmbracoBuilder umbracoBuilder,
|
||||
Action<BackOfficeExternalLoginsBuilder> builder)
|
||||
{
|
||||
builder(new BackOfficeExternalLoginsBuilder(umbracoBuilder.Services));
|
||||
return umbracoBuilder;
|
||||
}
|
||||
|
||||
public static BackOfficeIdentityBuilder AddTwoFactorProvider<T>(this BackOfficeIdentityBuilder identityBuilder,
|
||||
public static BackOfficeIdentityBuilder AddTwoFactorProvider<T>(
|
||||
this BackOfficeIdentityBuilder identityBuilder,
|
||||
string providerName) where T : class, ITwoFactorProvider
|
||||
{
|
||||
identityBuilder.Services.AddSingleton<ITwoFactorProvider, T>();
|
||||
|
||||
@@ -32906,6 +32906,9 @@
|
||||
},
|
||||
"401": {
|
||||
"description": "The resource is protected and requires an authentication token"
|
||||
},
|
||||
"403": {
|
||||
"description": "The authenticated user do not have access to this resource"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
@@ -33107,6 +33110,9 @@
|
||||
},
|
||||
"401": {
|
||||
"description": "The resource is protected and requires an authentication token"
|
||||
},
|
||||
"403": {
|
||||
"description": "The authenticated user do not have access to this resource"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
@@ -33408,6 +33414,9 @@
|
||||
},
|
||||
"401": {
|
||||
"description": "The resource is protected and requires an authentication token"
|
||||
},
|
||||
"403": {
|
||||
"description": "The authenticated user do not have access to this resource"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
@@ -45357,4 +45366,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
using Umbraco.Cms.Api.Management.ViewModels;
|
||||
@@ -32,7 +32,12 @@ internal class NotificationHeaderFilter : IOperationFilter
|
||||
response.Headers.TryAdd(Constants.Headers.Notifications, new OpenApiHeader
|
||||
{
|
||||
Description = "The list of notifications produced during the request.",
|
||||
Schema = new OpenApiSchema { Type = "array" , Nullable = true, Items = new OpenApiSchema(){
|
||||
Schema = new OpenApiSchema
|
||||
{
|
||||
Type = "array",
|
||||
Nullable = true,
|
||||
Items = new OpenApiSchema()
|
||||
{
|
||||
Reference = new OpenApiReference()
|
||||
{
|
||||
Type = ReferenceType.Schema,
|
||||
|
||||
@@ -32,8 +32,10 @@ public class BackOfficeAuthenticationBuilder : AuthenticationBuilder
|
||||
/// <param name="displayName"></param>
|
||||
/// <param name="configureOptions"></param>
|
||||
/// <returns></returns>
|
||||
public override AuthenticationBuilder AddRemoteScheme<TOptions, THandler>(string authenticationScheme,
|
||||
string? displayName, Action<TOptions>? configureOptions)
|
||||
public override AuthenticationBuilder AddRemoteScheme<TOptions, THandler>(
|
||||
string authenticationScheme,
|
||||
string? displayName,
|
||||
Action<TOptions>? configureOptions)
|
||||
{
|
||||
// Validate that the prefix is set
|
||||
if (!authenticationScheme.StartsWith(Constants.Security.BackOfficeExternalAuthenticationTypePrefix))
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
using System.Globalization;
|
||||
using Umbraco.Cms.Core.Events;
|
||||
using Umbraco.Cms.Core.Models.Membership;
|
||||
using Umbraco.Cms.Core.Notifications;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Web.Common.Security;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Security;
|
||||
|
||||
/// <summary>
|
||||
/// Binds to notifications to write audit logs for the <see cref="BackOfficeUserManager" />
|
||||
/// </summary>
|
||||
internal sealed class BackOfficeUserManagerAuditer :
|
||||
INotificationHandler<UserLoginSuccessNotification>,
|
||||
INotificationHandler<UserLogoutSuccessNotification>,
|
||||
INotificationHandler<UserLoginFailedNotification>,
|
||||
INotificationHandler<UserForgotPasswordRequestedNotification>,
|
||||
INotificationHandler<UserForgotPasswordChangedNotification>,
|
||||
INotificationHandler<UserPasswordChangedNotification>,
|
||||
INotificationHandler<UserPasswordResetNotification>
|
||||
{
|
||||
private readonly IAuditService _auditService;
|
||||
private readonly IUserService _userService;
|
||||
|
||||
public BackOfficeUserManagerAuditer(IAuditService auditService, IUserService userService)
|
||||
{
|
||||
_auditService = auditService;
|
||||
_userService = userService;
|
||||
}
|
||||
|
||||
public void Handle(UserForgotPasswordChangedNotification notification) =>
|
||||
WriteAudit(
|
||||
notification.PerformingUserId,
|
||||
notification.AffectedUserId,
|
||||
notification.IpAddress,
|
||||
"umbraco/user/password/forgot/change",
|
||||
"password forgot/change");
|
||||
|
||||
public void Handle(UserForgotPasswordRequestedNotification notification) =>
|
||||
WriteAudit(
|
||||
notification.PerformingUserId,
|
||||
notification.AffectedUserId,
|
||||
notification.IpAddress,
|
||||
"umbraco/user/password/forgot/request",
|
||||
"password forgot/request");
|
||||
|
||||
public void Handle(UserLoginFailedNotification notification) =>
|
||||
WriteAudit(
|
||||
notification.PerformingUserId,
|
||||
null,
|
||||
notification.IpAddress,
|
||||
"umbraco/user/sign-in/failed",
|
||||
"login failed");
|
||||
|
||||
public void Handle(UserLoginSuccessNotification notification)
|
||||
=> WriteAudit(
|
||||
notification.PerformingUserId,
|
||||
notification.AffectedUserId,
|
||||
notification.IpAddress,
|
||||
"umbraco/user/sign-in/login",
|
||||
"login success");
|
||||
|
||||
public void Handle(UserLogoutSuccessNotification notification)
|
||||
=> WriteAudit(
|
||||
notification.PerformingUserId,
|
||||
notification.AffectedUserId,
|
||||
notification.IpAddress,
|
||||
"umbraco/user/sign-in/logout",
|
||||
"logout success");
|
||||
|
||||
public void Handle(UserPasswordChangedNotification notification) =>
|
||||
WriteAudit(
|
||||
notification.PerformingUserId,
|
||||
notification.AffectedUserId,
|
||||
notification.IpAddress,
|
||||
"umbraco/user/password/change",
|
||||
"password change");
|
||||
|
||||
public void Handle(UserPasswordResetNotification notification) =>
|
||||
WriteAudit(
|
||||
notification.PerformingUserId,
|
||||
notification.AffectedUserId,
|
||||
notification.IpAddress,
|
||||
"umbraco/user/password/reset",
|
||||
"password reset");
|
||||
|
||||
private static string FormatEmail(IMembershipUser? user) =>
|
||||
user is null ? string.Empty : user.Email.IsNullOrWhiteSpace() ? string.Empty : $"<{user.Email}>";
|
||||
|
||||
private void WriteAudit(
|
||||
string performingId,
|
||||
string? affectedId,
|
||||
string ipAddress,
|
||||
string eventType,
|
||||
string eventDetails)
|
||||
{
|
||||
int? performingIdAsInt = ParseUserId(performingId);
|
||||
int? affectedIdAsInt = ParseUserId(affectedId);
|
||||
|
||||
WriteAudit(performingIdAsInt, affectedIdAsInt, ipAddress, eventType, eventDetails);
|
||||
}
|
||||
|
||||
private static int? ParseUserId(string? id)
|
||||
=> int.TryParse(id, NumberStyles.Integer, CultureInfo.InvariantCulture, out var isAsInt) ? isAsInt : null;
|
||||
|
||||
private void WriteAudit(
|
||||
int? performingId,
|
||||
int? affectedId,
|
||||
string ipAddress,
|
||||
string eventType,
|
||||
string eventDetails)
|
||||
{
|
||||
var performingDetails = "User UNKNOWN:0";
|
||||
if (performingId.HasValue)
|
||||
{
|
||||
IUser? performingUser = _userService.GetUserById(performingId.Value);
|
||||
performingDetails = performingUser is null
|
||||
? $"User UNKNOWN:{performingId.Value}"
|
||||
: $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}";
|
||||
}
|
||||
|
||||
var affectedDetails = "User UNKNOWN:0";
|
||||
if (affectedId.HasValue)
|
||||
{
|
||||
IUser? affectedUser = _userService.GetUserById(affectedId.Value);
|
||||
affectedDetails = affectedUser is null
|
||||
? $"User UNKNOWN:{affectedId.Value}"
|
||||
: $"User \"{affectedUser.Name}\" {FormatEmail(affectedUser)}";
|
||||
}
|
||||
|
||||
_auditService.Write(
|
||||
performingId ?? 0,
|
||||
performingDetails,
|
||||
ipAddress,
|
||||
DateTime.UtcNow,
|
||||
affectedId ?? 0,
|
||||
affectedDetails,
|
||||
eventType,
|
||||
eventDetails);
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,8 @@ public interface IBackOfficeExternalLoginService
|
||||
|
||||
Task<Attempt<IEnumerable<IdentityError>, ExternalLoginOperationStatus>> HandleLoginCallbackAsync(HttpContext httpContext);
|
||||
|
||||
Task<Attempt<Guid?, ExternalLoginOperationStatus>> GenerateLoginProviderSecretAsync(ClaimsPrincipal claimsPrincipal,
|
||||
Task<Attempt<Guid?, ExternalLoginOperationStatus>> GenerateLoginProviderSecretAsync(
|
||||
ClaimsPrincipal claimsPrincipal,
|
||||
string loginProvider);
|
||||
|
||||
Task<Attempt<ClaimsPrincipal?, ExternalLoginOperationStatus>> ClaimsPrincipleFromLoginProviderLinkKeyAsync(
|
||||
|
||||
@@ -8,8 +8,15 @@
|
||||
<RootNamespace>Umbraco.Cms.Api.Management</RootNamespace>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<!-- TODO: Fix all warnings and remove this override -->
|
||||
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||
<!-- TODO: Fix [SA1117] params all on same line, [SA1401] make fields private,
|
||||
[SA1134] own line attributes, [CS0108] hidden inherited member, [CS0618]/[CS9042] update
|
||||
obsolete references, [CS1998] remove async or make method synchronous, [CS8524] switch statement,
|
||||
[IDE0060] removed unused parameter, [SA1649] file name match type, [CS0419] ambiguous reference,
|
||||
[CS1573] param tag for all parameters, [CS1574] unresolveable cref, and remove overrides -->
|
||||
<WarningsNotAsErrors>
|
||||
SA1117,SA1401,SA1134,CS0108,CS0618,CS1998,CS8524,CS9042,IDE0060,SA1649,CS0419,
|
||||
CS1573,CS1574
|
||||
</WarningsNotAsErrors>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="JsonPatch.Net" />
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Globalization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Headers;
|
||||
using Microsoft.Extensions.Options;
|
||||
@@ -48,16 +49,32 @@ public sealed class ConfigureImageSharpMiddlewareOptions : IConfigureOptions<Ima
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
int width = context.Parser.ParseValue<int>(context.Commands.GetValueOrDefault(ResizeWebProcessor.Width), context.Culture);
|
||||
if (width <= 0 || width > _imagingSettings.Resize.MaxWidth)
|
||||
if (context.Commands.Contains(ResizeWebProcessor.Width))
|
||||
{
|
||||
context.Commands.Remove(ResizeWebProcessor.Width);
|
||||
if (!int.TryParse(
|
||||
context.Commands.GetValueOrDefault(ResizeWebProcessor.Width),
|
||||
NumberStyles.Integer,
|
||||
CultureInfo.InvariantCulture,
|
||||
out var width)
|
||||
|| width < 0
|
||||
|| width >= _imagingSettings.Resize.MaxWidth)
|
||||
{
|
||||
context.Commands.Remove(ResizeWebProcessor.Width);
|
||||
}
|
||||
}
|
||||
|
||||
int height = context.Parser.ParseValue<int>(context.Commands.GetValueOrDefault(ResizeWebProcessor.Height), context.Culture);
|
||||
if (height <= 0 || height > _imagingSettings.Resize.MaxHeight)
|
||||
if (context.Commands.Contains(ResizeWebProcessor.Height))
|
||||
{
|
||||
context.Commands.Remove(ResizeWebProcessor.Height);
|
||||
if (!int.TryParse(
|
||||
context.Commands.GetValueOrDefault(ResizeWebProcessor.Height),
|
||||
NumberStyles.Integer,
|
||||
CultureInfo.InvariantCulture,
|
||||
out var height)
|
||||
|| height < 0
|
||||
|| height >= _imagingSettings.Resize.MaxHeight)
|
||||
{
|
||||
context.Commands.Remove(ResizeWebProcessor.Height);
|
||||
}
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Globalization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Headers;
|
||||
using Microsoft.Extensions.Options;
|
||||
@@ -47,20 +48,32 @@ public sealed class ConfigureImageSharpMiddlewareOptions : IConfigureOptions<Ima
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
var width = context.Parser.ParseValue<int>(
|
||||
context.Commands.GetValueOrDefault(ResizeWebProcessor.Width),
|
||||
context.Culture);
|
||||
if (width <= 0 || width > _imagingSettings.Resize.MaxWidth)
|
||||
if (context.Commands.Contains(ResizeWebProcessor.Width))
|
||||
{
|
||||
context.Commands.Remove(ResizeWebProcessor.Width);
|
||||
if (!int.TryParse(
|
||||
context.Commands.GetValueOrDefault(ResizeWebProcessor.Width),
|
||||
NumberStyles.Integer,
|
||||
CultureInfo.InvariantCulture,
|
||||
out var width)
|
||||
|| width < 0
|
||||
|| width >= _imagingSettings.Resize.MaxWidth)
|
||||
{
|
||||
context.Commands.Remove(ResizeWebProcessor.Width);
|
||||
}
|
||||
}
|
||||
|
||||
var height = context.Parser.ParseValue<int>(
|
||||
context.Commands.GetValueOrDefault(ResizeWebProcessor.Height),
|
||||
context.Culture);
|
||||
if (height <= 0 || height > _imagingSettings.Resize.MaxHeight)
|
||||
if (context.Commands.Contains(ResizeWebProcessor.Height))
|
||||
{
|
||||
context.Commands.Remove(ResizeWebProcessor.Height);
|
||||
if (!int.TryParse(
|
||||
context.Commands.GetValueOrDefault(ResizeWebProcessor.Height),
|
||||
NumberStyles.Integer,
|
||||
CultureInfo.InvariantCulture,
|
||||
out var height)
|
||||
|| height < 0
|
||||
|| height >= _imagingSettings.Resize.MaxHeight)
|
||||
{
|
||||
context.Commands.Remove(ResizeWebProcessor.Height);
|
||||
}
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<Description>Adds imaging support using ImageSharp/ImageSharp.Web version 2 to Umbraco CMS.</Description>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SixLabors.ImageSharp" VersionOverride="[2.1.9, 3)" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" VersionOverride="[2.1.10, 3)" />
|
||||
<PackageReference Include="SixLabors.ImageSharp.Web" VersionOverride="[2.0.2, 3)" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
+3
-1
@@ -170,7 +170,9 @@ internal class SqlServerEFCoreDistributedLockingMechanism<T> : IDistributedLocki
|
||||
"A transaction with minimum ReadCommitted isolation level is required.");
|
||||
}
|
||||
|
||||
var rowsAffected = await dbContext.Database.ExecuteSqlAsync(@$"SET LOCK_TIMEOUT {(int)_timeout.TotalMilliseconds};UPDATE umbracoLock WITH (REPEATABLEREAD) SET value = (CASE WHEN (value=1) THEN -1 ELSE 1 END) WHERE id={LockId}");
|
||||
#pragma warning disable EF1002
|
||||
var rowsAffected = await dbContext.Database.ExecuteSqlRawAsync(@$"SET LOCK_TIMEOUT {(int)_timeout.TotalMilliseconds};UPDATE umbracoLock WITH (REPEATABLEREAD) SET value = (CASE WHEN (value=1) THEN -1 ELSE 1 END) WHERE id={LockId}");
|
||||
#pragma warning restore EF1002
|
||||
|
||||
if (rowsAffected == 0)
|
||||
{
|
||||
|
||||
@@ -4,13 +4,17 @@
|
||||
<Description>Adds support for Entity Framework Core to Umbraco CMS.</Description>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<!-- TODO: [IDE0270] Simplify null checks, [CS0108] resolve hiding inherited members, [CS1998] remove async or make method synchronous,
|
||||
and remove this override -->
|
||||
<!-- TODO: [IDE0270] Simplify null checks, [CS0108] resolve hiding inherited members, [CS1998]
|
||||
remove async or make method synchronous, and remove these overrides -->
|
||||
<WarningsNotAsErrors>IDE0270,CS0108,CS1998</WarningsNotAsErrors>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ItemGroup>
|
||||
<!-- Take top-level depedendency on Azure.Identity, because Microsoft.EntityFrameworkCore.SqlServer depends on a vulnerable version -->
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
|
||||
<!-- Take top-level depedendency on Microsoft.Extensions.Caching.Memory, because Microsoft.EntityFrameworkCore.* depends on a vulnerable version -->
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" />
|
||||
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" />
|
||||
<PackageReference Include="OpenIddict.EntityFrameworkCore" />
|
||||
|
||||
@@ -4,10 +4,15 @@
|
||||
<Description>Adds support for SQL Server to Umbraco CMS.</Description>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<!-- TODO: [SA1405] Simplify null checks, [SA1121] resolve hiding inherited members, [SA1117] remove async or make method synchronous,
|
||||
[IDE1006] fix naming rule violation, [CS0618] handle member obsolete appropriately, [IDE0270] simplify null check, [IDE0057] simplify substring,
|
||||
[IDE0054] use compound assignment, [CSO618] use NVARCARMAX, [IDE0048] add parenthesis for clarity, [CS1574] resolve ML comment cref attribute and remove this override -->
|
||||
<WarningsNotAsErrors>SA1405,SA1121,SA1117,SA1116,IDE1006,CS0618,IDE0270,IDE0057,IDE0054,CSO618,IDE0048,CS1574</WarningsNotAsErrors>
|
||||
<!-- TODO: [SA1405] Debug assret message text, [SA1121] resolve hiding inherited members, [SA1117] remove
|
||||
async or make method synchronous, [IDE1006] fix naming rule violation, [CS0618] handle member
|
||||
obsolete appropriately, [IDE0270] simplify null check, [IDE0057] simplify substring, [IDE0054]
|
||||
use compound assignment, [CSO618] use NVARCARMAX, [IDE0048] add parenthesis for clarity,
|
||||
[CS1574] resolve ML comment cref attribute, and remove these overrides -->
|
||||
<WarningsNotAsErrors>
|
||||
SA1405,SA1121,SA1117,SA1116,IDE1006,CS0618,IDE0270,IDE0057,IDE0054,CSO618,IDE0048,
|
||||
CS1574
|
||||
</WarningsNotAsErrors>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<!-- Take top-level depedendency on Azure.Identity, because NPoco.SqlServer depends on a vulnerable version -->
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- TODO: [NU5123] Rename files so path is shorter -->
|
||||
<!-- TODO: [NU5123] Rename files so path is shorter and remove this override -->
|
||||
<WarningsNotAsErrors>NU5123</WarningsNotAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
<IncludeSymbols>false</IncludeSymbols>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<!-- TODO: [IDE0270] Simplify null checks, [CS0108] resolve hiding inherited members, [CS1998] remove async or make method synchronous,
|
||||
and remove this override -->
|
||||
<!-- TODO: [IDE0270] Simplify null checks, [CS0108] resolve hiding inherited members, [CS1998] remove
|
||||
async or make method synchronous, and remove these overrides -->
|
||||
<WarningsNotAsErrors>IDE0270,CS0108,CS1998</WarningsNotAsErrors>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
|
||||
@@ -140,7 +140,9 @@ public static class DistributedCacheExtensions
|
||||
Id = x.Item.Id,
|
||||
Key = x.Item.Key,
|
||||
ChangeTypes = x.ChangeTypes,
|
||||
Blueprint = x.Item.Blueprint
|
||||
Blueprint = x.Item.Blueprint,
|
||||
PublishedCultures = x.PublishedCultures?.ToArray(),
|
||||
UnpublishedCultures = x.UnpublishedCultures?.ToArray()
|
||||
});
|
||||
|
||||
dc.RefreshByPayload(ContentCacheRefresher.UniqueId, payloads);
|
||||
|
||||
@@ -182,6 +182,10 @@ public sealed class ContentCacheRefresher : PayloadCacheRefresherBase<ContentCac
|
||||
public TreeChangeTypes ChangeTypes { get; init; }
|
||||
|
||||
public bool Blueprint { get; init; }
|
||||
|
||||
public string[]? PublishedCultures { get; init; }
|
||||
|
||||
public string[]? UnpublishedCultures { get; init; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -11,6 +11,7 @@ public class RepositoryCachePolicyOptions
|
||||
public RepositoryCachePolicyOptions(Func<int> performCount)
|
||||
{
|
||||
PerformCount = performCount;
|
||||
CacheNullValues = false;
|
||||
GetAllCacheValidateCount = true;
|
||||
GetAllCacheAllowZeroCount = false;
|
||||
}
|
||||
@@ -21,6 +22,7 @@ public class RepositoryCachePolicyOptions
|
||||
public RepositoryCachePolicyOptions()
|
||||
{
|
||||
PerformCount = null;
|
||||
CacheNullValues = false;
|
||||
GetAllCacheValidateCount = false;
|
||||
GetAllCacheAllowZeroCount = false;
|
||||
}
|
||||
@@ -30,6 +32,11 @@ public class RepositoryCachePolicyOptions
|
||||
/// </summary>
|
||||
public Func<int>? PerformCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// True if the Get method will cache null results so that the db is not hit for repeated lookups
|
||||
/// </summary>
|
||||
public bool CacheNullValues { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// True/false as to validate the total item count when all items are returned from cache, the default is true but this
|
||||
/// means that a db lookup will occur - though that lookup will probably be significantly less expensive than the
|
||||
|
||||
@@ -34,7 +34,7 @@ public class TypeFinder : ITypeFinder
|
||||
"ServiceStack.", "SqlCE4Umbraco,", "Superpower,", // used by Serilog
|
||||
"System.", "TidyNet,", "TidyNet.", "WebDriver,", "itextsharp,", "mscorlib,", "NUnit,", "NUnit.", "NUnit3.",
|
||||
"Selenium.", "ImageProcessor", "MiniProfiler.", "Owin,", "SQLite",
|
||||
"ReSharperTestRunner", "ReSharperTestRunner32", "ReSharperTestRunner64", // These are used by the Jetbrains Rider IDE and Visual Studio ReSharper Extension
|
||||
"ReSharperTestRunner", "ReSharperTestRunner32", "ReSharperTestRunner64", "ReSharperTestRunnerArm32", "ReSharperTestRunnerArm64", // These are used by the Jetbrains Rider IDE and Visual Studio ReSharper Extension
|
||||
};
|
||||
|
||||
private static readonly ConcurrentDictionary<string, Type?> TypeNamesCache = new();
|
||||
|
||||
@@ -127,6 +127,7 @@ public class GlobalSettings
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to install the database when it is missing.
|
||||
/// </summary>
|
||||
[Obsolete("This option will be removed in V16.")]
|
||||
[DefaultValue(StaticInstallMissingDatabase)]
|
||||
public bool InstallMissingDatabase { get; set; } = StaticInstallMissingDatabase;
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ public class ModelsBuilderSettings
|
||||
internal const string StaticModelsDirectory = "~/umbraco/models";
|
||||
internal const bool StaticAcceptUnsafeModelsDirectory = false;
|
||||
internal const int StaticDebugLevel = 0;
|
||||
internal const bool StaticIncludeVersionNumberInGeneratedModels = true;
|
||||
private bool _flagOutOfDateModels = true;
|
||||
|
||||
/// <summary>
|
||||
@@ -78,4 +79,16 @@ public class ModelsBuilderSettings
|
||||
/// <remarks>0 means minimal (safe on live site), anything else means more and more details (maybe not safe).</remarks>
|
||||
[DefaultValue(StaticDebugLevel)]
|
||||
public int DebugLevel { get; set; } = StaticDebugLevel;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the version number should be included in generated models.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// By default this is written to the <see cref="System.CodeDom.Compiler.GeneratedCodeAttribute"/> output in
|
||||
/// generated code for each property of the model. This can be useful for debugging purposes but isn't essential,
|
||||
/// and it has the causes the generated code to change every time Umbraco is upgraded. In turn, this leads
|
||||
/// to unnecessary code file changes that need to be checked into source control. Default is <c>true</c>.
|
||||
/// </remarks>
|
||||
[DefaultValue(StaticIncludeVersionNumberInGeneratedModels)]
|
||||
public bool IncludeVersionNumberInGeneratedModels { get; set; } = StaticIncludeVersionNumberInGeneratedModels;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ public class RequestHandlerSettings
|
||||
{
|
||||
internal const bool StaticAddTrailingSlash = true;
|
||||
internal const string StaticConvertUrlsToAscii = "try";
|
||||
internal const string StaticConvertFileNamesToAscii = "false";
|
||||
internal const bool StaticEnableDefaultCharReplacements = true;
|
||||
|
||||
internal static readonly CharItem[] DefaultCharCollection =
|
||||
@@ -73,6 +74,22 @@ public class RequestHandlerSettings
|
||||
/// </summary>
|
||||
public bool ShouldTryConvertUrlsToAscii => ConvertUrlsToAscii.InvariantEquals("try");
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to convert file names to ASCII (valid values: "true", "try" or "false").
|
||||
/// </summary>
|
||||
[DefaultValue(StaticConvertFileNamesToAscii)]
|
||||
public string ConvertFileNamesToAscii { get; set; } = StaticConvertFileNamesToAscii;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether URLs should be converted to ASCII.
|
||||
/// </summary>
|
||||
public bool ShouldConvertFileNamesToAscii => ConvertFileNamesToAscii.InvariantEquals("true");
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether URLs should be tried to be converted to ASCII.
|
||||
/// </summary>
|
||||
public bool ShouldTryConvertFileNamesToAscii => ConvertFileNamesToAscii.InvariantEquals("try");
|
||||
|
||||
/// <summary>
|
||||
/// Disable all default character replacements
|
||||
/// </summary>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// See LICENSE for more details.
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Umbraco.Cms.Core.Configuration.Models;
|
||||
|
||||
@@ -19,12 +20,16 @@ public class SecuritySettings
|
||||
internal const bool StaticAllowEditInvariantFromNonDefault = false;
|
||||
internal const bool StaticAllowConcurrentLogins = false;
|
||||
internal const string StaticAuthCookieName = "UMB_UCONTEXT";
|
||||
internal const bool StaticUsernameIsEmail = true;
|
||||
internal const bool StaticMemberRequireUniqueEmail = true;
|
||||
|
||||
internal const string StaticAllowedUserNameCharacters =
|
||||
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+\\";
|
||||
|
||||
internal const int StaticMemberDefaultLockoutTimeInMinutes = 30 * 24 * 60;
|
||||
internal const int StaticUserDefaultLockoutTimeInMinutes = 30 * 24 * 60;
|
||||
private const long StaticUserDefaultFailedLoginDurationInMilliseconds = 1000;
|
||||
private const long StaticUserMinimumFailedLoginDurationInMilliseconds = 250;
|
||||
internal const string StaticAuthorizeCallbackPathName = "/umbraco/oauth_complete";
|
||||
internal const string StaticAuthorizeCallbackLogoutPathName = "/umbraco/logout";
|
||||
internal const string StaticAuthorizeCallbackErrorPathName = "/umbraco/error";
|
||||
@@ -61,7 +66,14 @@ public class SecuritySettings
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the user's email address is to be considered as their username.
|
||||
/// </summary>
|
||||
public bool UsernameIsEmail { get; set; } = true;
|
||||
[DefaultValue(StaticUsernameIsEmail)]
|
||||
public bool UsernameIsEmail { get; set; } = StaticUsernameIsEmail;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the member's email address must be unique.
|
||||
/// </summary>
|
||||
[DefaultValue(StaticMemberRequireUniqueEmail)]
|
||||
public bool MemberRequireUniqueEmail { get; set; } = StaticMemberRequireUniqueEmail;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the set of allowed characters for a username
|
||||
@@ -108,6 +120,30 @@ public class SecuritySettings
|
||||
[DefaultValue(StaticAllowConcurrentLogins)]
|
||||
public bool AllowConcurrentLogins { get; set; } = StaticAllowConcurrentLogins;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the default duration (in milliseconds) of failed login attempts.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The default duration (in milliseconds) of failed login attempts.
|
||||
/// </value>
|
||||
/// <remarks>
|
||||
/// The user login endpoint ensures that failed login attempts take at least as long as the average successful login.
|
||||
/// However, if no successful logins have occurred, this value is used as the default duration.
|
||||
/// </remarks>
|
||||
[Range(0, long.MaxValue)]
|
||||
[DefaultValue(StaticUserDefaultFailedLoginDurationInMilliseconds)]
|
||||
public long UserDefaultFailedLoginDurationInMilliseconds { get; set; } = StaticUserDefaultFailedLoginDurationInMilliseconds;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the minimum duration (in milliseconds) of failed login attempts.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The minimum duration (in milliseconds) of failed login attempts.
|
||||
/// </value>
|
||||
[Range(0, long.MaxValue)]
|
||||
[DefaultValue(StaticUserMinimumFailedLoginDurationInMilliseconds)]
|
||||
public long UserMinimumFailedLoginDurationInMilliseconds { get; set; } = StaticUserMinimumFailedLoginDurationInMilliseconds;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value of the back-office host URI. Use this when running the back-office client and the Management API on different hosts. Leave empty when running both on the same host.
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace Umbraco.Cms.Core.DeliveryApi;
|
||||
|
||||
public interface ICurrentMemberClaimsProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Retrieves the claims for the currently logged in member.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is used by the OIDC user info endpoint to supply "current user" info.
|
||||
/// </remarks>
|
||||
Task<Dictionary<string, object>> GetClaimsAsync();
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
namespace Umbraco.Cms.Core.DeliveryApi;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class IncludeInApiVersionAttribute : Attribute
|
||||
{
|
||||
public int? MinVersion { get; }
|
||||
|
||||
public int? MaxVersion { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="IncludeInApiVersionAttribute"/> class.
|
||||
/// Specifies that the property should be included in the API response if the API version falls within the specified bounds.
|
||||
/// </summary>
|
||||
/// <param name="minVersion">The minimum API version (inclusive) for which the property should be included.</param>
|
||||
/// <param name="maxVersion">The maximum API version (inclusive) for which the property should be included.</param>
|
||||
public IncludeInApiVersionAttribute(int minVersion = -1, int maxVersion = -1)
|
||||
{
|
||||
MinVersion = minVersion >= 0 ? minVersion : null;
|
||||
MaxVersion = maxVersion >= 0 ? maxVersion : null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Umbraco.Cms.Core.DeliveryApi;
|
||||
|
||||
public class NoopCurrentMemberClaimsProvider : ICurrentMemberClaimsProvider
|
||||
{
|
||||
public Task<Dictionary<string, object>> GetClaimsAsync() => Task.FromResult(new Dictionary<string, object>());
|
||||
}
|
||||
@@ -26,8 +26,6 @@ using Umbraco.Cms.Core.Notifications;
|
||||
using Umbraco.Cms.Core.Packaging;
|
||||
using Umbraco.Cms.Core.Persistence.Repositories;
|
||||
using Umbraco.Cms.Core.PropertyEditors;
|
||||
using Umbraco.Cms.Core.PublishedCache;
|
||||
using Umbraco.Cms.Core.PublishedCache.Internal;
|
||||
using Umbraco.Cms.Core.Routing;
|
||||
using Umbraco.Cms.Core.Runtime;
|
||||
using Umbraco.Cms.Core.Scoping;
|
||||
@@ -36,7 +34,10 @@ using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.ContentTypeEditing;
|
||||
using Umbraco.Cms.Core.DynamicRoot;
|
||||
using Umbraco.Cms.Core.Preview;
|
||||
using Umbraco.Cms.Core.PublishedCache;
|
||||
using Umbraco.Cms.Core.PublishedCache.Internal;
|
||||
using Umbraco.Cms.Core.Security.Authorization;
|
||||
using Umbraco.Cms.Core.Serialization;
|
||||
using Umbraco.Cms.Core.Services.FileSystem;
|
||||
using Umbraco.Cms.Core.Services.ImportExport;
|
||||
using Umbraco.Cms.Core.Services.Querying.RecycleBin;
|
||||
@@ -373,7 +374,10 @@ namespace Umbraco.Cms.Core.DependencyInjection
|
||||
Services.AddUnique<IWebhookService, WebhookService>();
|
||||
Services.AddUnique<IWebhookLogService, WebhookLogService>();
|
||||
Services.AddUnique<IWebhookLogFactory, WebhookLogFactory>();
|
||||
Services.AddUnique<IWebhookRequestService, WebhookRequestService>();
|
||||
Services.AddUnique<IWebhookRequestService>(factory => new WebhookRequestService(
|
||||
factory.GetRequiredService<ICoreScopeProvider>(),
|
||||
factory.GetRequiredService<IWebhookRequestRepository>(),
|
||||
factory.GetRequiredService<IWebhookJsonSerializer>()));
|
||||
|
||||
// Data type configuration cache
|
||||
Services.AddUnique<IDataTypeConfigurationCache, DataTypeConfigurationCache>();
|
||||
|
||||
@@ -27,7 +27,9 @@ public class RootDynamicRootOriginFinder : IDynamicRootOriginFinder
|
||||
return null;
|
||||
}
|
||||
|
||||
var entity = _entityService.Get(query.Context.ParentKey);
|
||||
// when creating new content, CurrentKey will be null - fallback to using ParentKey
|
||||
Guid entityKey = query.Context.CurrentKey ?? query.Context.ParentKey;
|
||||
var entity = _entityService.Get(entityKey);
|
||||
|
||||
if (entity is null || _allowedObjectTypes.Contains(entity.NodeObjectType) is false)
|
||||
{
|
||||
|
||||
@@ -20,12 +20,14 @@ public class SiteDynamicRootOriginFinder : RootDynamicRootOriginFinder
|
||||
|
||||
public override Guid? FindOriginKey(DynamicRootNodeQuery query)
|
||||
{
|
||||
if (query.OriginAlias != SupportedOriginType || query.Context.CurrentKey.HasValue is false)
|
||||
if (query.OriginAlias != SupportedOriginType)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
IEntitySlim? entity = _entityService.Get(query.Context.CurrentKey.Value);
|
||||
// when creating new content, CurrentKey will be null - fallback to using ParentKey
|
||||
Guid entityKey = query.Context.CurrentKey ?? query.Context.ParentKey;
|
||||
IEntitySlim? entity = _entityService.Get(entityKey);
|
||||
if (entity is null || entity.NodeObjectType != Constants.ObjectTypes.Document)
|
||||
{
|
||||
return null;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user