Compare commits
240
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
da502e06ae | ||
|
|
2b7efbe861 | ||
|
|
9beed532a9 | ||
|
|
215c4dc540 | ||
|
|
f19aaaee52 | ||
|
|
dc50a6e8c3 | ||
|
|
1e64c8d5af | ||
|
|
745d74104e | ||
|
|
cc8d90be91 | ||
|
|
95c9d48b6e | ||
|
|
57c66faf40 | ||
|
|
08990b9147 | ||
|
|
23fc355e61 | ||
|
|
1fedaa3d8f | ||
|
|
ba7d550a74 | ||
|
|
0600df4211 | ||
|
|
06c566c074 | ||
|
|
2a609e1eca | ||
|
|
342cb171eb | ||
|
|
e850c131e5 | ||
|
|
c48103cd30 | ||
|
|
20180c3180 | ||
|
|
89d487e449 | ||
|
|
640f9f2615 | ||
|
|
14f4c54312 | ||
|
|
cbdb1d567f | ||
|
|
31072bab51 | ||
|
|
d687c4365a | ||
|
|
43688148ae | ||
|
|
e39b05f44d | ||
|
|
2067db1c3c | ||
|
|
6f2b7ab1a0 | ||
|
|
ea142d51b7 | ||
|
|
e3c394b109 | ||
|
|
2e85f1bbf8 | ||
|
|
b22d3ddc00 | ||
|
|
b68a6a9502 | ||
|
|
a4d893a7b4 | ||
|
|
313b60aca5 | ||
|
|
4ee1d7b13e | ||
|
|
95cc6cc67b | ||
|
|
417335a5c6 | ||
|
|
bd94522237 | ||
|
|
aadf5e7643 | ||
|
|
e315ae3ae1 | ||
|
|
393179d8a1 | ||
|
|
b7105c88d8 | ||
|
|
f1b7a33e86 | ||
|
|
a61bb8dbef | ||
|
|
da0bdbf8c7 | ||
|
|
f726a7d7c5 | ||
|
|
f6b566c65f | ||
|
|
a3d6b4f844 | ||
|
|
1615740bdf | ||
|
|
d3b9256b4e | ||
|
|
399431b02d | ||
|
|
33c2838d20 | ||
|
|
981c9a7db2 | ||
|
|
de9cbf9ef5 | ||
|
|
bd6ab83712 | ||
|
|
4252cbc64b | ||
|
|
b3145a01eb | ||
|
|
aeb1437f0e | ||
|
|
04df2652f5 | ||
|
|
1bf53f3554 | ||
|
|
a5adcbdeef | ||
|
|
286e727de8 | ||
|
|
201906f662 | ||
|
|
a4c0da3465 | ||
|
|
28c01d2819 | ||
|
|
2248eeb03e | ||
|
|
f6df0c1828 | ||
|
|
63ae29a1aa | ||
|
|
6effe32136 | ||
|
|
f0ac7b4a50 | ||
|
|
5f0122e18b | ||
|
|
de5a9ca5af | ||
|
|
58068d1aa7 | ||
|
|
5f1c65e7ea | ||
|
|
20eedda4c7 | ||
|
|
1099332edb | ||
|
|
3a83e4a494 | ||
|
|
af3b17243b | ||
|
|
d05add4e1f | ||
|
|
68d1b9481a | ||
|
|
e6f48799a1 | ||
|
|
1efdde2473 | ||
|
|
c666c00ac5 | ||
|
|
94692cccb7 | ||
|
|
ab4be79da0 | ||
|
|
a808e3ab46 | ||
|
|
92dbaf7162 | ||
|
|
72d44decd4 | ||
|
|
7e7d9da144 | ||
|
|
f82c0a90e7 | ||
|
|
65798b5722 | ||
|
|
7d0adc4755 | ||
|
|
11bf60a67c | ||
|
|
8beb7f2acc | ||
|
|
498754e170 | ||
|
|
3830d75413 | ||
|
|
f1f2f702c0 | ||
|
|
2503cabbce | ||
|
|
7e8a7f0b40 | ||
|
|
126c8a56d5 | ||
|
|
99ea8b9087 | ||
|
|
86dda9f9b3 | ||
|
|
9c082c613e | ||
|
|
5af9df1871 | ||
|
|
12297ea617 | ||
|
|
3892670d92 | ||
|
|
d9de69c40f | ||
|
|
49f3fc9993 | ||
|
|
7f1cdf8ef5 | ||
|
|
37b239b8ca | ||
|
|
ea44850804 | ||
|
|
8be2d77006 | ||
|
|
a6eb2a1527 | ||
|
|
11fcfba091 | ||
|
|
d6b9eb61b2 | ||
|
|
0367e56db6 | ||
|
|
2ce6c8f8c8 | ||
|
|
fd1c9f1673 | ||
|
|
c68a26cb5f | ||
|
|
c037323910 | ||
|
|
f60436270f | ||
|
|
9b0b60e6cb | ||
|
|
f539a27a19 | ||
|
|
832bd7eb61 | ||
|
|
fb91ade164 | ||
|
|
38a9a79c75 | ||
|
|
924b1446e3 | ||
|
|
2e0d006a6c | ||
|
|
d9f8faf509 | ||
|
|
5eaf7fdb90 | ||
|
|
0dcb10ad41 | ||
|
|
15089b28bf | ||
|
|
ebe056df05 | ||
|
|
54407a0687 | ||
|
|
787a5ccc2d | ||
|
|
17a5477242 | ||
|
|
74328e9496 | ||
|
|
cd9dda2aad | ||
|
|
62efc2631e | ||
|
|
20de48a496 | ||
|
|
f0cf4703fa | ||
|
|
0cf8279e65 | ||
|
|
2f712cab08 | ||
|
|
034cf2894c | ||
|
|
1498f3bdc6 | ||
|
|
324e5bcb0a | ||
|
|
ba4a874fb4 | ||
|
|
1f351244d4 | ||
|
|
e3d2001477 | ||
|
|
7c0b5b7dd4 | ||
|
|
7bcef5fde5 | ||
|
|
173887d0a1 | ||
|
|
c67478874e | ||
|
|
0daf5ea506 | ||
|
|
29bdd3cac0 | ||
|
|
c5b4571cd5 | ||
|
|
d45a8f2049 | ||
|
|
0ded9a74fb | ||
|
|
99c51a54f1 | ||
|
|
3da8f41292 | ||
|
|
f14c4d5968 | ||
|
|
5d986e44f6 | ||
|
|
51575e5e36 | ||
|
|
07203b7270 | ||
|
|
cdb2be64d4 | ||
|
|
ac6b205e7f | ||
|
|
81f29be53b | ||
|
|
ac23ff5f38 | ||
|
|
099e7d7d3a | ||
|
|
b1956dea3e | ||
|
|
8d5117fec1 | ||
|
|
d8e8e32f75 | ||
|
|
765e5b0ba9 | ||
|
|
099fd2aa7c | ||
|
|
cea2184d47 | ||
|
|
f5cfbb110e | ||
|
|
aa2a85328e | ||
|
|
7db7346d4a | ||
|
|
859505e751 | ||
|
|
5d17920a73 | ||
|
|
815f28e3b9 | ||
|
|
a7bb1fc20d | ||
|
|
dca70a0bd0 | ||
|
|
07641981a4 | ||
|
|
b593d9dadf | ||
|
|
dd01a56d2a | ||
|
|
8a342be4f4 | ||
|
|
eea970a648 | ||
|
|
20a2f90441 | ||
|
|
3d7ac5776c | ||
|
|
c3eabda10e | ||
|
|
d3e6508952 | ||
|
|
6dc9191ad3 | ||
|
|
f4c0264e61 | ||
|
|
60a03c75e4 | ||
|
|
f050488b92 | ||
|
|
5a0a2bf77e | ||
|
|
da8c036719 | ||
|
|
20c29f8091 | ||
|
|
b8ca11edd6 | ||
|
|
2a6bb64c78 | ||
|
|
156bcdc98a | ||
|
|
07abec239d | ||
|
|
50ae48b0a2 | ||
|
|
0b2a247614 | ||
|
|
f2b5061414 | ||
|
|
d623476902 | ||
|
|
b427a8cbdd | ||
|
|
bd8ad5f6f9 | ||
|
|
596389d961 | ||
|
|
cd00dfaa34 | ||
|
|
6d5271fb80 | ||
|
|
f8ec2dadfe | ||
|
|
e57f66537c | ||
|
|
ab0af2a291 | ||
|
|
5fc12ec36e | ||
|
|
73d2f207d0 | ||
|
|
c3e93f143c | ||
|
|
c6bc4ef49a | ||
|
|
c1ac80653b | ||
|
|
a50ad893a8 | ||
|
|
a6c311977c | ||
|
|
04ac8d3ea9 | ||
|
|
3fcc09169a | ||
|
|
2ce647738f | ||
|
|
4a438ad6e9 | ||
|
|
0b1bd16b9d | ||
|
|
447cb881bd | ||
|
|
ceb745a7bd | ||
|
|
7fc2bc84de | ||
|
|
321087c96e | ||
|
|
f0e37e04fa | ||
|
|
135f0f63de | ||
|
|
2dced37117 | ||
|
|
44d9f5ac2e |
+2
-3
@@ -9,7 +9,7 @@ In order to use Umbraco as a CMS and build your website with it, you should not
|
||||
- Are you about to [create a pull request for Umbraco][contribution guidelines]?
|
||||
- Are you trying to get to the bottom of a problem in your existing Umbraco installation?
|
||||
|
||||
If the answer is yes, please read on. Otherwise, make sure to head on over [to the download page](https://our.umbraco.com/download) and start using Umbraco CMS as intended.
|
||||
If the answer is yes, please read on. Otherwise, make sure to head on over [to the releases page](https://releases.umbraco.com) and start using Umbraco CMS as intended.
|
||||
|
||||
## Table of contents
|
||||
|
||||
@@ -79,13 +79,12 @@ Conversely, if you are working on front-end only, you want to build the back-end
|
||||
"AuthorizeCallbackLogoutPathName": "/logout",
|
||||
"AuthorizeCallbackErrorPathName": "/error",
|
||||
"BackOfficeTokenCookie": {
|
||||
"Enabled": true,
|
||||
"SameSite": "None"
|
||||
}
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> If you get stuck in a login loop, try clearing your browser cookies for localhost, and make sure that the `BackOfficeTokenCookie` settings are correct. Namely, that `SameSite` should be set to `None` when running the front-end server separately.
|
||||
> If you get stuck in a login loop, try clearing your browser cookies for localhost, and make sure that the `Umbraco:Cms:Security:BackOfficeTokenCookie:SameSite` setting is set to `None`.
|
||||
|
||||
Then run Umbraco from the command line.
|
||||
|
||||
|
||||
@@ -38,14 +38,6 @@ Some important documentation links to get you started:
|
||||
- [Getting to know Umbraco](https://docs.umbraco.com/umbraco-cms/fundamentals/get-to-know-umbraco)
|
||||
- [Tutorials for creating a basic website and customizing the editing experience](https://docs.umbraco.com/umbraco-cms/tutorials/overview)
|
||||
|
||||
## Backoffice Preview
|
||||
|
||||
Want to see the latest backoffice UI in action? Check out our live preview:
|
||||
|
||||
**[backofficepreview.umbraco.com](https://backofficepreview.umbraco.com/)**
|
||||
|
||||
This preview is automatically deployed from the main branch and showcases the latest backoffice features and improvements. It runs from mock data and persistent edits are not supported.
|
||||
|
||||
## Get help
|
||||
|
||||
If you need a bit of feedback while building your Umbraco projects, we are [chatty on Discord](https://discord.umbraco.com). Our Discord server serves as a social space for all Umbracians. If you have any questions or need some help with a problem, head over to our [dedicated forum](https://forum.umbraco.com/) where the Umbraco Community will be happy to help.
|
||||
|
||||
@@ -79,7 +79,7 @@ You can get in touch with [the core contributors team][core collabs] in multiple
|
||||
|
||||
- If there's an existing issue on the issue tracker then that's a good place to leave questions and discuss how to start or move forward.
|
||||
- If you want to ask questions on some code you've already written you can create a draft pull request, [detailed in a GitHub blog post][draft prs].
|
||||
- Unsure where to start? Did something not work as expected? Try leaving a note in the ["Contributing to Umbraco"][contrib forum] forum. The team monitors that one closely, so one of us will be on hand and ready to point you in the right direction.
|
||||
- Unsure where to start? Did something not work as expected? Try leaving a note in the [forum][forum]. The team monitors that one closely, so one of us will be on hand and ready to point you in the right direction.
|
||||
|
||||
|
||||
<!-- Local -->
|
||||
@@ -90,7 +90,7 @@ You can get in touch with [the core contributors team][core collabs] in multiple
|
||||
|
||||
[sync fork ext]: http://robots.thoughtbot.com/post/5133345960/keeping-a-git-fork-updated "Details on keeping a git fork updated"
|
||||
[draft prs]: https://github.blog/2019-02-14-introducing-draft-pull-requests/ "Github's blog post providing details on draft pull requests"
|
||||
[contrib forum]: https://our.umbraco.com/forum/contributing-to-umbraco-cms/
|
||||
[forum]: https://forum.umbraco.com/
|
||||
[Umbraco CMS repo]: https://github.com/umbraco/Umbraco-CMS
|
||||
[up for grabs issues]: https://github.com/umbraco/Umbraco-CMS/issues?q=is%3Aissue+is%3Aopen+label%3Acommunity%2Fup-for-grabs
|
||||
[issue tracker]: https://github.com/umbraco/Umbraco-CMS/issues
|
||||
@@ -31,6 +31,8 @@ Bootstrap, build, and test the repository:
|
||||
- `cd src/Umbraco.Web.UI`
|
||||
- `dotnet run --no-build` -- Application runs on https://localhost:44339 and http://localhost:11000
|
||||
|
||||
Check out [BUILD.md](./BUILD.md) for more detailed instructions.
|
||||
|
||||
## Validation
|
||||
|
||||
- ALWAYS run through at least one complete end-to-end scenario after making changes.
|
||||
@@ -94,34 +96,22 @@ The solution contains 30 C# projects organized as follows:
|
||||
|
||||
## Common Tasks
|
||||
|
||||
### Running Umbraco in Different Modes
|
||||
|
||||
**Production Mode (Standard Development)**
|
||||
Use this for backend development, testing full builds, or when you don't need hot reloading:
|
||||
1. Build frontend assets: `cd src/Umbraco.Web.UI.Client && npm run build:for:cms`
|
||||
2. Run backend: `cd src/Umbraco.Web.UI && dotnet run --no-build`
|
||||
3. Access backoffice: `https://localhost:44339/umbraco`
|
||||
4. Application uses compiled frontend from `wwwroot/umbraco/backoffice/`
|
||||
|
||||
**Vite Dev Server Mode (Frontend Development with Hot Reload)**
|
||||
Use this for frontend-only development with hot module reloading:
|
||||
1. Configure backend for frontend development - Add to `src/Umbraco.Web.UI/appsettings.json` under `Umbraco:CMS:Security`:
|
||||
### Frontend Development
|
||||
For frontend-only changes:
|
||||
1. Configure backend for frontend development:
|
||||
```json
|
||||
<!-- Add to src/Umbraco.Web.UI/appsettings.json under Umbraco:Cms:Security: -->
|
||||
```json
|
||||
"BackOfficeHost": "http://localhost:5173",
|
||||
"AuthorizeCallbackPathName": "/oauth_complete",
|
||||
"AuthorizeCallbackLogoutPathName": "/logout",
|
||||
"AuthorizeCallbackErrorPathName": "/error",
|
||||
"BackOfficeTokenCookie": {
|
||||
"Enabled": true,
|
||||
"SameSite": "None"
|
||||
}
|
||||
```
|
||||
2. Run backend: `cd src/Umbraco.Web.UI && dotnet run --no-build`
|
||||
3. Run frontend dev server: `cd src/Umbraco.Web.UI.Client && npm run dev:server`
|
||||
4. Access backoffice: `http://localhost:5173/` (no `/umbraco` prefix)
|
||||
5. Changes to TypeScript/Lit files hot reload automatically
|
||||
|
||||
**Important:** Remove the `BackOfficeHost` configuration before committing or switching back to production mode.
|
||||
|
||||
### Backend-Only Development
|
||||
For backend-only changes, disable frontend builds:
|
||||
|
||||
@@ -52,7 +52,7 @@ jobs:
|
||||
for (const item of items) {
|
||||
const releaseLabels = (item.labels || [])
|
||||
.map(l => (typeof l === "string" ? l : l.name)) // always get the name
|
||||
.filter(n => typeof n === "string" && n.startsWith("release/") && n !== "release/no-notes");
|
||||
.filter(n => typeof n === "string" && n.startsWith("release/"));
|
||||
if (releaseLabels.length === 0) continue;
|
||||
|
||||
core.info(`#${item.number}: ${releaseLabels.join(", ")}`);
|
||||
|
||||
Vendored
-2
@@ -101,13 +101,11 @@
|
||||
"env": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development",
|
||||
"ASPNETCORE_URLS": "https://localhost:44339",
|
||||
"UMBRACO__CMS__WEBROUTING__UMBRACOAPPLICATIONURL": "https://localhost:44339",
|
||||
"UMBRACO__CMS__SECURITY__BACKOFFICEHOST": "http://localhost:5173",
|
||||
"UMBRACO__CMS__SECURITY__AUTHORIZECALLBACKPATHNAME": "/oauth_complete",
|
||||
"UMBRACO__CMS__SECURITY__AUTHORIZECALLBACKLOGOUTPATHNAME": "/logout",
|
||||
"UMBRACO__CMS__SECURITY__AUTHORIZECALLBACKERRORPATHNAME": "/error",
|
||||
"UMBRACO__CMS__SECURITY__KEEPUSERLOGGEDIN": "true",
|
||||
"UMBRACO__CMS__SECURITY__BACKOFFICETOKENCOOKIE__ENABLED": "true",
|
||||
"UMBRACO__CMS__SECURITY__BACKOFFICETOKENCOOKIE__SAMESITE": "None"
|
||||
},
|
||||
"sourceFileMap": {
|
||||
|
||||
Vendored
+3
@@ -1,5 +1,8 @@
|
||||
{
|
||||
"cSpell.words": [
|
||||
"backoffice",
|
||||
"pickable",
|
||||
"Pickable",
|
||||
"unprovide",
|
||||
"Unproviding"
|
||||
],
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Company>Umbraco HQ</Company>
|
||||
<Authors>Umbraco</Authors>
|
||||
<Copyright>Copyright © Umbraco $([System.DateTime]::Today.ToString('yyyy'))</Copyright>
|
||||
@@ -40,7 +40,7 @@
|
||||
<!-- Package Validation -->
|
||||
<PropertyGroup>
|
||||
<GenerateCompatibilitySuppressionFile>false</GenerateCompatibilitySuppressionFile>
|
||||
<EnablePackageValidation>true</EnablePackageValidation>
|
||||
<EnablePackageValidation>false</EnablePackageValidation> <!-- TODO (V17): Set to true with version 17.0.0 once this version is released. -->
|
||||
<PackageValidationBaselineVersion>16.0.0</PackageValidationBaselineVersion>
|
||||
<EnableStrictModeForCompatibleFrameworksInPackage>true</EnableStrictModeForCompatibleFrameworksInPackage>
|
||||
<EnableStrictModeForCompatibleTfms>true</EnableStrictModeForCompatibleTfms>
|
||||
|
||||
+37
-58
@@ -5,36 +5,35 @@
|
||||
</PropertyGroup>
|
||||
<!-- Global packages (private, build-time packages for all projects) -->
|
||||
<ItemGroup>
|
||||
<GlobalPackageReference Include="Nerdbank.GitVersioning" Version="3.7.115" />
|
||||
<GlobalPackageReference Include="Nerdbank.GitVersioning" Version="3.8.118" />
|
||||
<GlobalPackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556" />
|
||||
<GlobalPackageReference Include="Umbraco.Code" Version="2.3.0" />
|
||||
<GlobalPackageReference Include="Umbraco.Code" Version="2.4.0" />
|
||||
<GlobalPackageReference Include="Umbraco.GitVersioning.Extensions" Version="0.2.0" />
|
||||
</ItemGroup>
|
||||
<!-- Microsoft packages -->
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="9.0.4" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.13.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.13.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.Workspaces.MSBuild" Version="4.13.0" />
|
||||
<PackageVersion Include="Microsoft.Data.Sqlite" Version="9.0.4" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.4" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.4" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="9.0.4" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="9.0.4" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.4" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="9.0.4" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.4" />
|
||||
<PackageVersion Include="Microsoft.Extensions.FileProviders.Embedded" Version="9.0.4" />
|
||||
<PackageVersion Include="Microsoft.Extensions.FileProviders.Physical" Version="9.0.4" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.4" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Http" Version="9.0.4" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Identity.Core" Version="9.0.4" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Identity.Stores" Version="9.0.4" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.4" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Options" Version="9.0.4" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="9.0.4" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Options.DataAnnotations" Version="9.0.4" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Hybrid" Version="9.4.0" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="10.0.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" />
|
||||
<PackageVersion Include="Microsoft.Data.Sqlite" Version="10.0.0" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.0" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="10.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="10.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="10.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.FileProviders.Embedded" Version="10.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.FileProviders.Physical" Version="10.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Http" Version="10.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Identity.Core" Version="10.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Identity.Stores" Version="10.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging" Version="10.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Options" Version="10.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="10.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Options.DataAnnotations" Version="10.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Hybrid" Version="10.0.0" />
|
||||
<PackageVersion Include="System.Linq.Async" Version="7.0.0" />
|
||||
</ItemGroup>
|
||||
<!-- Umbraco packages -->
|
||||
<ItemGroup>
|
||||
@@ -47,21 +46,21 @@
|
||||
<PackageVersion Include="Dazinator.Extensions.FileProviders" Version="2.0.0" />
|
||||
<PackageVersion Include="Examine" Version="3.7.1" />
|
||||
<PackageVersion Include="Examine.Core" Version="3.7.1" />
|
||||
<PackageVersion Include="HtmlAgilityPack" Version="1.12.1" />
|
||||
<PackageVersion Include="HtmlAgilityPack" Version="1.12.4" />
|
||||
<PackageVersion Include="JsonPatch.Net" Version="3.3.0" />
|
||||
<PackageVersion Include="K4os.Compression.LZ4" Version="1.3.8" />
|
||||
<PackageVersion Include="MailKit" Version="4.11.0" />
|
||||
<PackageVersion Include="MailKit" Version="4.14.0" />
|
||||
<PackageVersion Include="Markdown" Version="2.2.1" />
|
||||
<PackageVersion Include="MessagePack" Version="3.1.3" />
|
||||
<PackageVersion Include="MessagePack" Version="3.1.4" />
|
||||
<PackageVersion Include="MiniProfiler.AspNetCore.Mvc" Version="4.5.4" />
|
||||
<PackageVersion Include="MiniProfiler.Shared" Version="4.5.4" />
|
||||
<PackageVersion Include="ncrontab" Version="3.3.3" />
|
||||
<PackageVersion Include="NPoco" Version="5.7.1" />
|
||||
<PackageVersion Include="NPoco.SqlServer" Version="5.7.1" />
|
||||
<PackageVersion Include="OpenIddict.Abstractions" Version="6.2.1" />
|
||||
<PackageVersion Include="OpenIddict.AspNetCore" Version="6.2.1" />
|
||||
<PackageVersion Include="OpenIddict.EntityFrameworkCore" Version="6.2.1" />
|
||||
<PackageVersion Include="Serilog" Version="4.2.0" />
|
||||
<PackageVersion Include="ncrontab" Version="3.4.0" />
|
||||
<PackageVersion Include="NPoco" Version="6.1.0" />
|
||||
<PackageVersion Include="NPoco.SqlServer" Version="6.1.0" />
|
||||
<PackageVersion Include="OpenIddict.Abstractions" Version="7.1.0" />
|
||||
<PackageVersion Include="OpenIddict.AspNetCore" Version="7.1.0" />
|
||||
<PackageVersion Include="OpenIddict.EntityFrameworkCore" Version="7.1.0" />
|
||||
<PackageVersion Include="Serilog" Version="4.3.0" />
|
||||
<PackageVersion Include="Serilog.AspNetCore" Version="9.0.0" />
|
||||
<PackageVersion Include="Serilog.Enrichers.Process" Version="3.0.0" />
|
||||
<PackageVersion Include="Serilog.Enrichers.Thread" Version="4.0.0" />
|
||||
@@ -71,33 +70,13 @@
|
||||
<PackageVersion Include="Serilog.Formatting.Compact.Reader" Version="4.0.0" />
|
||||
<PackageVersion Include="Serilog.Settings.Configuration" Version="9.0.0" />
|
||||
<PackageVersion Include="Serilog.Sinks.Async" Version="2.1.0" />
|
||||
<PackageVersion Include="Serilog.Sinks.File" Version="6.0.0" />
|
||||
<PackageVersion Include="Serilog.Sinks.File" Version="7.0.0" />
|
||||
<PackageVersion Include="Serilog.Sinks.Map" Version="2.0.0" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.11" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp.Web" Version="3.1.5" />
|
||||
<PackageVersion Include="Swashbuckle.AspNetCore" Version="8.1.1" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp.Web" Version="3.2.0" />
|
||||
<PackageVersion Include="Swashbuckle.AspNetCore" Version="10.0.1" />
|
||||
</ItemGroup>
|
||||
<!-- Transitive pinned versions (only required because our direct dependencies have vulnerable versions of transitive dependencies) -->
|
||||
<ItemGroup>
|
||||
<!-- Microsoft.EntityFrameworkCore.SqlServer and NPoco.SqlServer brings in a vulnerable version of Azure.Identity -->
|
||||
<!-- Take top-level depedendency on Azure.Identity, because Microsoft.EntityFrameworkCore.SqlServer depends on a vulnerable version -->
|
||||
<PackageVersion Include="Azure.Identity" Version="1.13.2" />
|
||||
<!-- Microsoft.EntityFrameworkCore.SqlServer brings in a vulnerable version of System.Runtime.Caching -->
|
||||
<PackageVersion Include="System.Runtime.Caching" Version="9.0.4" />
|
||||
<!-- Dazinator.Extensions.FileProviders brings in a vulnerable version of System.Net.Http -->
|
||||
<PackageVersion Include="System.Net.Http" Version="4.3.4" />
|
||||
<!-- Examine brings in a vulnerable version of System.Security.Cryptography.Xml -->
|
||||
<PackageVersion Include="System.Security.Cryptography.Xml" Version="9.0.4" />
|
||||
<!-- Dazinator.Extensions.FileProviders and MiniProfiler.AspNetCore.Mvc brings in a vulnerable version of System.Text.RegularExpressions -->
|
||||
<PackageVersion Include="System.Text.RegularExpressions" Version="4.3.1" />
|
||||
<!-- OpenIddict.AspNetCore, Npoco.SqlServer and Microsoft.EntityFrameworkCore.SqlServer brings in a vulnerable version of Microsoft.IdentityModel.JsonWebTokens -->
|
||||
<!-- Take top-level depedendency on Microsoft.IdentityModel.JsonWebTokens, because OpenIddict.AspNetCore, Npoco.SqlServer and Microsoft.EntityFrameworkCore.SqlServer depends on a vulnerable version -->
|
||||
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.8.0" />
|
||||
<!-- Azure.Identity, Microsoft.EntityFrameworkCore.SqlServer and Dazinator.Extensions.FileProviders brings in a legacy version of System.Text.Encodings.Web -->
|
||||
<PackageVersion Include="System.Text.Encodings.Web" Version="9.0.4" />
|
||||
<!-- NPoco.SqlServer brings in a vulnerable version of Microsoft.Data.SqlClient -->
|
||||
<PackageVersion Include="Microsoft.Data.SqlClient" Version="6.0.1" />
|
||||
<!-- Examine.Lucene brings in a vulnerable version of Lucene.Net.Replicator -->
|
||||
<PackageVersion Include="Lucene.Net.Replicator" Version="4.8.0-beta00017" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -365,8 +365,8 @@ stages:
|
||||
testFilter: "(FullyQualifiedName~Umbraco.Infrastructure.Service)"
|
||||
LinuxPart3Of3:
|
||||
vmImage: "ubuntu-latest"
|
||||
# Filter tests that are not part of the Umbraco.Infrastructure namespace. So this will run all tests that are not part of the Umbraco.Infrastructure namespace
|
||||
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure)"
|
||||
# Filter tests that are not part of the Umbraco.Infrastructure and ManagementApi namespace. So this will run all tests that are not part of the Umbraco.Infrastructure and ManagementApi namespace
|
||||
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure) & (FullyQualifiedName!~ManagementApi)"
|
||||
macOSPart1Of3:
|
||||
vmImage: "macOS-latest"
|
||||
# Filter tests that are part of the Umbraco.Infrastructure namespace but not part of the Umbraco.Infrastructure.Service namespace
|
||||
@@ -377,8 +377,8 @@ stages:
|
||||
testFilter: "(FullyQualifiedName~Umbraco.Infrastructure.Service)"
|
||||
macOSPart3Of3:
|
||||
vmImage: "macOS-latest"
|
||||
# Filter tests that are not part of the Umbraco.Infrastructure namespace.
|
||||
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure)"
|
||||
# Filter tests that are not part of the Umbraco.Infrastructure and ManagementApi namespace. So this will run all tests that are not part of the Umbraco.Infrastructure and the ManagementApi namespace
|
||||
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure) & (FullyQualifiedName!~ManagementApi)"
|
||||
pool:
|
||||
vmImage: $(vmImage)
|
||||
variables:
|
||||
@@ -441,8 +441,8 @@ stages:
|
||||
vmImage: "windows-latest"
|
||||
Tests__Database__DatabaseType: LocalDb
|
||||
Tests__Database__SQLServerMasterConnectionString: N/A
|
||||
# Filter tests that are not part of the Umbraco.Infrastructure namespace. So this will run all tests that are not part of the Umbraco.Infrastructure namespace
|
||||
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure)"
|
||||
# Filter tests that are not part of the Umbraco.Infrastructure and ManagementApi namespace. So this will run all tests that are not part of the Umbraco.Infrastructure and ManagementApi namespace
|
||||
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure) & (FullyQualifiedName!~ManagementApi)"
|
||||
LinuxPart1Of3:
|
||||
vmImage: "ubuntu-latest"
|
||||
SA_PASSWORD: UmbracoIntegration123!
|
||||
@@ -462,8 +462,8 @@ stages:
|
||||
SA_PASSWORD: UmbracoIntegration123!
|
||||
Tests__Database__DatabaseType: SqlServer
|
||||
Tests__Database__SQLServerMasterConnectionString: "Server=(local);User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=True"
|
||||
# Filter tests that are not part of the Umbraco.Infrastructure namespace. So this will run all tests that are not part of the Umbraco.Infrastructure namespace
|
||||
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure)"
|
||||
# Filter tests that are not part of the Umbraco.Infrastructure and ManagementApi namespace. So this will run all tests that are not part of the Umbraco.Infrastructure and ManagementApi namespace
|
||||
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure) & (FullyQualifiedName!~ManagementApi)"
|
||||
pool:
|
||||
vmImage: $(vmImage)
|
||||
steps:
|
||||
|
||||
@@ -4,11 +4,13 @@ pr: none
|
||||
trigger: none
|
||||
|
||||
schedules:
|
||||
- cron: '0 6 * * *'
|
||||
displayName: Daily 6 AM build (v16/dev)
|
||||
- cron: '0 0 * * *'
|
||||
displayName: Daily midnight build
|
||||
branches:
|
||||
include:
|
||||
- v16/dev
|
||||
- v15/dev
|
||||
- main
|
||||
- v17/dev
|
||||
|
||||
parameters:
|
||||
- name: skipIntegrationTests
|
||||
@@ -19,7 +21,7 @@ parameters:
|
||||
- name: differentAppSettingsAcceptanceTests
|
||||
displayName: Run acceptance tests with different app settings
|
||||
type: boolean
|
||||
default: false
|
||||
default: true
|
||||
|
||||
- name: skipDefaultConfigAcceptanceTests
|
||||
displayName: Skip tests with DefaultConfig
|
||||
@@ -121,31 +123,39 @@ stages:
|
||||
matrix:
|
||||
# Windows:
|
||||
# vmImage: 'windows-latest'
|
||||
# We split the tests into 3 parts for each OS to reduce the time it takes to run them on the pipeline
|
||||
LinuxPart1Of3:
|
||||
# We split the tests into 4 parts for each OS to reduce the time it takes to run them on the pipeline
|
||||
LinuxPart1Of4:
|
||||
vmImage: "ubuntu-latest"
|
||||
# Filter tests that are part of the Umbraco.Infrastructure namespace but not part of the Umbraco.Infrastructure.Service namespace
|
||||
testFilter: "(FullyQualifiedName~Umbraco.Infrastructure) & (FullyQualifiedName!~Umbraco.Infrastructure.Service)"
|
||||
LinuxPart2Of3:
|
||||
LinuxPart2Of4:
|
||||
vmImage: "ubuntu-latest"
|
||||
# Filter tests that are part of the Umbraco.Infrastructure.Service namespace
|
||||
testFilter: "(FullyQualifiedName~Umbraco.Infrastructure.Service)"
|
||||
LinuxPart3Of3:
|
||||
LinuxPart3Of4:
|
||||
vmImage: "ubuntu-latest"
|
||||
# Filter tests that are not part of the Umbraco.Infrastructure namespace. So this will run all tests that are not part of the Umbraco.Infrastructure namespace
|
||||
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure)"
|
||||
macOSPart1Of3:
|
||||
# Filter tests that are not part of the Umbraco.Infrastructure and ManagementApi namespace. So this will run all tests that are not part of the Umbraco.Infrastructure namespace
|
||||
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure) & (FullyQualifiedName!~ManagementApi)"
|
||||
LinuxPart4Of4:
|
||||
vmImage: "ubuntu-latest"
|
||||
# Filter tests that are part of the ManagementApi namespace
|
||||
testFilter: "(FullyQualifiedName~ManagementApi)"
|
||||
macOSPart1Of4:
|
||||
vmImage: "macOS-latest"
|
||||
# Filter tests that are part of the Umbraco.Infrastructure namespace but not part of the Umbraco.Infrastructure.Service namespace
|
||||
testFilter: "(FullyQualifiedName~Umbraco.Infrastructure) & (FullyQualifiedName!~Umbraco.Infrastructure.Service)"
|
||||
macOSPart2Of3:
|
||||
macOSPart2Of4:
|
||||
vmImage: "macOS-latest"
|
||||
# Filter tests that are part of the Umbraco.Infrastructure.Service namespace
|
||||
testFilter: "(FullyQualifiedName~Umbraco.Infrastructure.Service)"
|
||||
macOSPart3Of3:
|
||||
macOSPart3Of4:
|
||||
vmImage: "macOS-latest"
|
||||
# Filter tests that are not part of the Umbraco.Infrastructure namespace.
|
||||
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure)"
|
||||
# Filter tests that are not part of the Umbraco.Infrastructure and ManagementApi namespace.
|
||||
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure) & (FullyQualifiedName!~ManagementApi)"
|
||||
macOSPart4Of4:
|
||||
vmImage: "macOS-latest"
|
||||
# Filter tests that are part of the ManagementApi namespace.
|
||||
testFilter: "(FullyQualifiedName~ManagementApi)"
|
||||
pool:
|
||||
vmImage: $(vmImage)
|
||||
variables:
|
||||
@@ -185,43 +195,55 @@ stages:
|
||||
SA_PASSWORD: UmbracoAcceptance123!
|
||||
strategy:
|
||||
matrix:
|
||||
# We split the tests into 3 parts for each OS to reduce the time it takes to run them on the pipeline
|
||||
WindowsPart1Of3:
|
||||
# We split the tests into 4 parts for each OS to reduce the time it takes to run them on the pipeline
|
||||
WindowsPart1Of4:
|
||||
vmImage: "windows-latest"
|
||||
Tests__Database__DatabaseType: LocalDb
|
||||
Tests__Database__SQLServerMasterConnectionString: N/A
|
||||
# Filter tests that are part of the Umbraco.Infrastructure namespace but not part of the Umbraco.Infrastructure.Service namespace
|
||||
testFilter: "(FullyQualifiedName~Umbraco.Infrastructure) & (FullyQualifiedName!~Umbraco.Infrastructure.Service)"
|
||||
WindowsPart2Of3:
|
||||
WindowsPart2Of4:
|
||||
vmImage: "windows-latest"
|
||||
Tests__Database__DatabaseType: LocalDb
|
||||
Tests__Database__SQLServerMasterConnectionString: N/A
|
||||
# Filter tests that are part of the Umbraco.Infrastructure.Service namespace
|
||||
testFilter: "(FullyQualifiedName~Umbraco.Infrastructure.Service)"
|
||||
WindowsPart3Of3:
|
||||
WindowsPart3Of4:
|
||||
vmImage: "windows-latest"
|
||||
Tests__Database__DatabaseType: LocalDb
|
||||
Tests__Database__SQLServerMasterConnectionString: N/A
|
||||
# Filter tests that are not part of the Umbraco.Infrastructure namespace. So this will run all tests that are not part of the Umbraco.Infrastructure namespace
|
||||
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure)"
|
||||
LinuxPart1Of3:
|
||||
# Filter tests that are not part of the Umbraco.Infrastructure and ManagementApi namespace.
|
||||
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure) & (FullyQualifiedName!~ManagementApi)"
|
||||
WindowsPart4Of4:
|
||||
vmImage: "windows-latest"
|
||||
Tests__Database__DatabaseType: LocalDb
|
||||
Tests__Database__SQLServerMasterConnectionString: N/A
|
||||
# Filter tests that are part of the ManagementApi namespace.
|
||||
testFilter: "(FullyQualifiedName~ManagementApi)"
|
||||
LinuxPart1Of4:
|
||||
vmImage: "ubuntu-latest"
|
||||
Tests__Database__DatabaseType: SqlServer
|
||||
Tests__Database__SQLServerMasterConnectionString: "Server=(local);User Id=sa;Password=$(SA_PASSWORD);Encrypt=True;TrustServerCertificate=True"
|
||||
# Filter tests that are part of the Umbraco.Infrastructure namespace but not part of the Umbraco.Infrastructure.Service namespace
|
||||
testFilter: "(FullyQualifiedName~Umbraco.Infrastructure) & (FullyQualifiedName!~Umbraco.Infrastructure.Service)"
|
||||
LinuxPart2Of3:
|
||||
LinuxPart2Of4:
|
||||
vmImage: "ubuntu-latest"
|
||||
Tests__Database__DatabaseType: SqlServer
|
||||
Tests__Database__SQLServerMasterConnectionString: "Server=(local);User Id=sa;Password=$(SA_PASSWORD);Encrypt=True;TrustServerCertificate=True"
|
||||
# Filter tests that are part of the Umbraco.Infrastructure.Service namespace
|
||||
testFilter: "(FullyQualifiedName~Umbraco.Infrastructure.Service)"
|
||||
LinuxPart3Of3:
|
||||
LinuxPart3Of4:
|
||||
vmImage: "ubuntu-latest"
|
||||
Tests__Database__DatabaseType: SqlServer
|
||||
Tests__Database__SQLServerMasterConnectionString: "Server=(local);User Id=sa;Password=$(SA_PASSWORD);Encrypt=True;TrustServerCertificate=True"
|
||||
# Filter tests that are not part of the Umbraco.Infrastructure namespace. So this will run all tests that are not part of the Umbraco.Infrastructure namespace
|
||||
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure)"
|
||||
# Filter tests that are not part of the Umbraco.Infrastructure and ManagementApi namespace.
|
||||
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure) & (FullyQualifiedName!~ManagementApi)"
|
||||
LinuxPart4Of4:
|
||||
vmImage: "ubuntu-latest"
|
||||
Tests__Database__DatabaseType: SqlServer
|
||||
Tests__Database__SQLServerMasterConnectionString: "Server=(local);User Id=sa;Password=$(SA_PASSWORD);Encrypt=True;TrustServerCertificate=True"
|
||||
# Filter tests that are part of the ManagementApi namespace.
|
||||
testFilter: "(FullyQualifiedName~ManagementApi)"
|
||||
pool:
|
||||
vmImage: $(vmImage)
|
||||
steps:
|
||||
@@ -293,8 +315,7 @@ stages:
|
||||
|
||||
- stage: DefaultConfigE2E
|
||||
displayName: Default Config E2E Tests
|
||||
dependsOn: Integration
|
||||
condition: always()
|
||||
dependsOn: Build
|
||||
variables:
|
||||
npm_config_cache: $(Pipeline.Workspace)/.npm_e2e
|
||||
# Enable console logging in Release mode
|
||||
@@ -475,8 +496,7 @@ stages:
|
||||
|
||||
- stage: AdditionalConfigE2E
|
||||
displayName: Additional Config E2E Tests
|
||||
dependsOn: DefaultConfigE2E
|
||||
condition: always()
|
||||
dependsOn: Build
|
||||
variables:
|
||||
npm_config_cache: $(Pipeline.Workspace)/.npm_e2e
|
||||
ASPNETCORE_URLS: https://localhost:44331
|
||||
@@ -485,7 +505,7 @@ stages:
|
||||
jobs:
|
||||
- job:
|
||||
displayName: E2E Tests with Different App settings (SQL Server)
|
||||
condition: ${{ or(eq(parameters.differentAppSettingsAcceptanceTests, true), eq(parameters.skipDefaultConfigAcceptanceTests, true)) }}
|
||||
condition: ${{ eq(parameters.differentAppSettingsAcceptanceTests, true) }}
|
||||
timeoutInMinutes: 180
|
||||
variables:
|
||||
SA_PASSWORD: UmbracoAcceptance123!
|
||||
@@ -675,4 +695,4 @@ stages:
|
||||
--data "$PAYLOAD" \
|
||||
"$SLACK_WEBHOOK_URL"
|
||||
env:
|
||||
SLACK_WEBHOOK_URL: $(E2ESLACKWEBHOOKURL)
|
||||
SLACK_WEBHOOK_URL: $(E2ESLACKWEBHOOKURL)
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"sdk": {
|
||||
"version": "9.0.306",
|
||||
"version": "10.0.100",
|
||||
"rollForward": "latestFeature",
|
||||
"allowPrerelease": false
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ using Microsoft.AspNetCore.Mvc.ApiExplorer;
|
||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Microsoft.OpenApi;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
using Umbraco.Cms.Api.Common.OpenApi;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
@@ -9,9 +9,9 @@ using Umbraco.Cms.Api.Common.Security;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Infrastructure.BackgroundJobs;
|
||||
using Umbraco.Cms.Infrastructure.BackgroundJobs.Jobs.DistributedJobs;
|
||||
using Umbraco.Cms.Core.Notifications;
|
||||
using Umbraco.Cms.Infrastructure.BackgroundJobs.Jobs;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Api.Common.DependencyInjection;
|
||||
|
||||
@@ -29,11 +29,6 @@ public static class UmbracoBuilderAuthExtensions
|
||||
|
||||
private static void ConfigureOpenIddict(IUmbracoBuilder builder)
|
||||
{
|
||||
// Optionally hide tokens from the back-office.
|
||||
var hideBackOfficeTokens = (builder.Config
|
||||
.GetSection(Constants.Configuration.ConfigBackOfficeTokenCookie)
|
||||
.Get<BackOfficeTokenCookieSettings>() ?? new BackOfficeTokenCookieSettings()).Enabled;
|
||||
|
||||
builder.Services.AddOpenIddict()
|
||||
// Register the OpenIddict server components.
|
||||
.AddServer(options =>
|
||||
@@ -120,27 +115,24 @@ public static class UmbracoBuilderAuthExtensions
|
||||
configuration.UseSingletonHandler<ProcessRequestContextHandler>().SetOrder(OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreHandlers.ResolveRequestUri.Descriptor.Order - 1);
|
||||
});
|
||||
|
||||
if (hideBackOfficeTokens)
|
||||
options.AddEventHandler<OpenIddictServerEvents.ApplyTokenResponseContext>(configuration =>
|
||||
{
|
||||
options.AddEventHandler<OpenIddictServerEvents.ApplyTokenResponseContext>(configuration =>
|
||||
{
|
||||
configuration
|
||||
.UseSingletonHandler<HideBackOfficeTokensHandler>()
|
||||
.SetOrder(OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreHandlers.ProcessJsonResponse<OpenIddictServerEvents.ApplyTokenResponseContext>.Descriptor.Order - 1);
|
||||
});
|
||||
options.AddEventHandler<OpenIddictServerEvents.ApplyAuthorizationResponseContext>(configuration =>
|
||||
{
|
||||
configuration
|
||||
.UseSingletonHandler<HideBackOfficeTokensHandler>()
|
||||
.SetOrder(OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreHandlers.Authentication.ProcessQueryResponse.Descriptor.Order - 1);
|
||||
});
|
||||
options.AddEventHandler<OpenIddictServerEvents.ExtractTokenRequestContext>(configuration =>
|
||||
{
|
||||
configuration
|
||||
.UseSingletonHandler<HideBackOfficeTokensHandler>()
|
||||
.SetOrder(OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreHandlers.ExtractPostRequest<OpenIddictServerEvents.ExtractTokenRequestContext>.Descriptor.Order + 1);
|
||||
});
|
||||
}
|
||||
configuration
|
||||
.UseSingletonHandler<HideBackOfficeTokensHandler>()
|
||||
.SetOrder(OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreHandlers.ProcessJsonResponse<OpenIddictServerEvents.ApplyTokenResponseContext>.Descriptor.Order - 1);
|
||||
});
|
||||
options.AddEventHandler<OpenIddictServerEvents.ApplyAuthorizationResponseContext>(configuration =>
|
||||
{
|
||||
configuration
|
||||
.UseSingletonHandler<HideBackOfficeTokensHandler>()
|
||||
.SetOrder(OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreHandlers.Authentication.ProcessQueryResponse.Descriptor.Order - 1);
|
||||
});
|
||||
options.AddEventHandler<OpenIddictServerEvents.ExtractTokenRequestContext>(configuration =>
|
||||
{
|
||||
configuration
|
||||
.UseSingletonHandler<HideBackOfficeTokensHandler>()
|
||||
.SetOrder(OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreHandlers.ExtractPostRequest<OpenIddictServerEvents.ExtractTokenRequestContext>.Descriptor.Order + 1);
|
||||
});
|
||||
})
|
||||
|
||||
// Register the OpenIddict validation components.
|
||||
@@ -166,24 +158,18 @@ public static class UmbracoBuilderAuthExtensions
|
||||
configuration.UseSingletonHandler<ProcessRequestContextHandler>().SetOrder(OpenIddict.Validation.AspNetCore.OpenIddictValidationAspNetCoreHandlers.ResolveRequestUri.Descriptor.Order - 1);
|
||||
});
|
||||
|
||||
if (hideBackOfficeTokens)
|
||||
options.AddEventHandler<OpenIddictValidationEvents.ProcessAuthenticationContext>(configuration =>
|
||||
{
|
||||
options.AddEventHandler<OpenIddictValidationEvents.ProcessAuthenticationContext>(configuration =>
|
||||
{
|
||||
configuration
|
||||
.UseSingletonHandler<HideBackOfficeTokensHandler>()
|
||||
// IMPORTANT: the handler must be AFTER the built-in query string handler, because the client-side SignalR library sometimes appends access tokens to the query string.
|
||||
.SetOrder(OpenIddict.Validation.AspNetCore.OpenIddictValidationAspNetCoreHandlers.ExtractAccessTokenFromQueryString.Descriptor.Order + 1);
|
||||
});
|
||||
}
|
||||
configuration
|
||||
.UseSingletonHandler<HideBackOfficeTokensHandler>()
|
||||
// IMPORTANT: the handler must be AFTER the built-in query string handler, because the client-side SignalR library sometimes appends access tokens to the query string.
|
||||
.SetOrder(OpenIddict.Validation.AspNetCore.OpenIddictValidationAspNetCoreHandlers.ExtractAccessTokenFromQueryString.Descriptor.Order + 1);
|
||||
});
|
||||
});
|
||||
|
||||
builder.Services.AddRecurringBackgroundJob<OpenIddictCleanupJob>();
|
||||
builder.Services.AddSingleton<IDistributedBackgroundJob, OpenIddictCleanupJob>();
|
||||
builder.Services.ConfigureOptions<ConfigureOpenIddict>();
|
||||
|
||||
if (hideBackOfficeTokens)
|
||||
{
|
||||
builder.AddNotificationHandler<UserLogoutSuccessNotification, HideBackOfficeTokensHandler>();
|
||||
}
|
||||
builder.AddNotificationHandler<UserLogoutSuccessNotification, HideBackOfficeTokensHandler>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,11 +8,11 @@ namespace Umbraco.Extensions;
|
||||
public static class MethodInfoApiCommonExtensions
|
||||
{
|
||||
|
||||
public static string? GetMapToApiVersionAttributeValue(this MethodInfo methodInfo)
|
||||
public static string GetMapToApiVersionAttributeValue(this MethodInfo methodInfo)
|
||||
{
|
||||
MapToApiVersionAttribute[] mapToApis = methodInfo.GetCustomAttributes(typeof(MapToApiVersionAttribute), inherit: true).Cast<MapToApiVersionAttribute>().ToArray();
|
||||
|
||||
return string.Join("|", mapToApis.SelectMany(x=>x.Versions));
|
||||
return string.Join("|", mapToApis.SelectMany(x => x.Versions));
|
||||
}
|
||||
|
||||
public static string? GetMapToApiAttributeValue(this MethodInfo methodInfo)
|
||||
|
||||
@@ -1,25 +1,27 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.Serialization;
|
||||
using Microsoft.OpenApi.Any;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using System.Text.Json.Nodes;
|
||||
using Microsoft.OpenApi;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
|
||||
namespace Umbraco.Cms.Api.Common.OpenApi;
|
||||
|
||||
public class EnumSchemaFilter : ISchemaFilter
|
||||
{
|
||||
public void Apply(OpenApiSchema model, SchemaFilterContext context)
|
||||
public void Apply(IOpenApiSchema model, SchemaFilterContext context)
|
||||
{
|
||||
if (context.Type.IsEnum)
|
||||
if (model is not OpenApiSchema schema || context.Type.IsEnum is false)
|
||||
{
|
||||
model.Type = "string";
|
||||
model.Format = null;
|
||||
model.Enum.Clear();
|
||||
foreach (var name in Enum.GetNames(context.Type))
|
||||
{
|
||||
var actualName = context.Type.GetField(name)?.GetCustomAttribute<EnumMemberAttribute>()?.Value ?? name;
|
||||
model.Enum.Add(new OpenApiString(actualName));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
schema.Type = JsonSchemaType.String;
|
||||
schema.Format = null;
|
||||
schema.Enum = new List<JsonNode>();
|
||||
foreach (var name in Enum.GetNames(context.Type))
|
||||
{
|
||||
var actualName = context.Type.GetField(name)?.GetCustomAttribute<EnumMemberAttribute>()?.Value ?? name;
|
||||
schema.Enum.Add(actualName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Microsoft.OpenApi;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
@@ -21,25 +21,32 @@ public class MimeTypeDocumentFilter : IDocumentFilter
|
||||
}
|
||||
|
||||
OpenApiOperation[] operations = swaggerDoc.Paths
|
||||
.SelectMany(path => path.Value.Operations.Values)
|
||||
.SelectMany(path => path.Value.Operations?.Values ?? Enumerable.Empty<OpenApiOperation>())
|
||||
.ToArray();
|
||||
|
||||
void RemoveUnwantedMimeTypes(IDictionary<string, OpenApiMediaType> content)
|
||||
void RemoveUnwantedMimeTypes(IDictionary<string, OpenApiMediaType>? content)
|
||||
{
|
||||
if (content.ContainsKey("application/json"))
|
||||
if (content is null || content.ContainsKey("application/json") is false)
|
||||
{
|
||||
content.RemoveAll(r => r.Key != "application/json");
|
||||
return;
|
||||
}
|
||||
|
||||
content.RemoveAll(r => r.Key != "application/json");
|
||||
}
|
||||
|
||||
OpenApiRequestBody[] requestBodies = operations.Select(operation => operation.RequestBody).WhereNotNull().ToArray();
|
||||
OpenApiRequestBody[] requestBodies = operations
|
||||
.Select(operation => operation.RequestBody)
|
||||
.OfType<OpenApiRequestBody>()
|
||||
.ToArray();
|
||||
foreach (OpenApiRequestBody requestBody in requestBodies)
|
||||
{
|
||||
RemoveUnwantedMimeTypes(requestBody.Content);
|
||||
}
|
||||
|
||||
OpenApiResponse[] responses = operations.SelectMany(operation => operation.Responses.Values).WhereNotNull().ToArray();
|
||||
OpenApiResponse[] responses = operations
|
||||
.SelectMany(operation => operation.Responses?.Values ?? Enumerable.Empty<IOpenApiResponse>())
|
||||
.OfType<OpenApiResponse>()
|
||||
.ToArray();
|
||||
foreach (OpenApiResponse response in responses)
|
||||
{
|
||||
RemoveUnwantedMimeTypes(response.Content);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Microsoft.OpenApi;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
|
||||
namespace Umbraco.Cms.Api.Common.OpenApi;
|
||||
@@ -20,6 +20,6 @@ public class RemoveSecuritySchemesDocumentFilter : IDocumentFilter
|
||||
return;
|
||||
}
|
||||
|
||||
swaggerDoc.Components.SecuritySchemes.Clear();
|
||||
swaggerDoc.Components?.SecuritySchemes?.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,20 +15,6 @@ public class SubTypesSelector : ISubTypesSelector
|
||||
private readonly IEnumerable<ISubTypesHandler> _subTypeHandlers;
|
||||
private readonly IUmbracoJsonTypeInfoResolver _umbracoJsonTypeInfoResolver;
|
||||
|
||||
[Obsolete("The settings parameter is not required anymore, use the other constructor instead. Scheduled for removal in Umbraco 17.")]
|
||||
public SubTypesSelector(
|
||||
IOptions<GlobalSettings> settings,
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
IHttpContextAccessor httpContextAccessor,
|
||||
IEnumerable<ISubTypesHandler> subTypeHandlers,
|
||||
IUmbracoJsonTypeInfoResolver umbracoJsonTypeInfoResolver)
|
||||
{
|
||||
_hostingEnvironment = hostingEnvironment;
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
_subTypeHandlers = subTypeHandlers;
|
||||
_umbracoJsonTypeInfoResolver = umbracoJsonTypeInfoResolver;
|
||||
}
|
||||
|
||||
public SubTypesSelector(
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
IHttpContextAccessor httpContextAccessor,
|
||||
|
||||
@@ -3,13 +3,12 @@ using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Microsoft.OpenApi;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
using Swashbuckle.AspNetCore.SwaggerUI;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Hosting;
|
||||
using Umbraco.Cms.Web.Common.ApplicationBuilder;
|
||||
using Umbraco.Extensions;
|
||||
using IHostingEnvironment = Umbraco.Cms.Core.Hosting.IHostingEnvironment;
|
||||
|
||||
namespace Umbraco.Cms.Api.Common.OpenApi;
|
||||
|
||||
@@ -16,11 +16,6 @@
|
||||
<PackageReference Include="OpenIddict.AspNetCore" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Take top-level depedendency on OpenIddict.AspNetCore depends on a vulnerable version -->
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Umbraco.Core\Umbraco.Core.csproj" />
|
||||
<ProjectReference Include="..\Umbraco.Web.Common\Umbraco.Web.Common.csproj" />
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Microsoft.OpenApi;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
using Umbraco.Cms.Api.Common.OpenApi;
|
||||
using Umbraco.Cms.Api.Delivery.Filters;
|
||||
|
||||
+9
-21
@@ -1,6 +1,6 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Microsoft.OpenApi;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
using Umbraco.Cms.Api.Common.Security;
|
||||
using Umbraco.Cms.Api.Delivery.Controllers.Content;
|
||||
@@ -35,23 +35,9 @@ public class ConfigureUmbracoMemberAuthenticationDeliveryApiSwaggerGenOptions :
|
||||
return;
|
||||
}
|
||||
|
||||
operation.Security = new List<OpenApiSecurityRequirement>
|
||||
{
|
||||
new OpenApiSecurityRequirement
|
||||
{
|
||||
{
|
||||
new OpenApiSecurityScheme
|
||||
{
|
||||
Reference = new OpenApiReference
|
||||
{
|
||||
Type = ReferenceType.SecurityScheme,
|
||||
Id = AuthSchemeName,
|
||||
}
|
||||
},
|
||||
[]
|
||||
}
|
||||
}
|
||||
};
|
||||
var schemaRef = new OpenApiSecuritySchemeReference(AuthSchemeName, context.Document);
|
||||
operation.Security ??= new List<OpenApiSecurityRequirement>();
|
||||
operation.Security.Add(new OpenApiSecurityRequirement { [schemaRef] = [] });
|
||||
}
|
||||
|
||||
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
|
||||
@@ -61,6 +47,8 @@ public class ConfigureUmbracoMemberAuthenticationDeliveryApiSwaggerGenOptions :
|
||||
return;
|
||||
}
|
||||
|
||||
swaggerDoc.Components ??= new OpenApiComponents();
|
||||
swaggerDoc.Components.SecuritySchemes ??= new Dictionary<string, IOpenApiSecurityScheme>();
|
||||
swaggerDoc.Components.SecuritySchemes.Add(
|
||||
AuthSchemeName,
|
||||
new OpenApiSecurityScheme
|
||||
@@ -74,9 +62,9 @@ public class ConfigureUmbracoMemberAuthenticationDeliveryApiSwaggerGenOptions :
|
||||
AuthorizationCode = new OpenApiOAuthFlow
|
||||
{
|
||||
AuthorizationUrl = new Uri(Paths.MemberApi.AuthorizationEndpoint, UriKind.Relative),
|
||||
TokenUrl = new Uri(Paths.MemberApi.TokenEndpoint, UriKind.Relative)
|
||||
}
|
||||
}
|
||||
TokenUrl = new Uri(Paths.MemberApi.TokenEndpoint, UriKind.Relative),
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using Microsoft.OpenApi.Any;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using System.Text.Json.Nodes;
|
||||
using Microsoft.OpenApi;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
using Umbraco.Cms.Api.Delivery.Configuration;
|
||||
using Umbraco.Cms.Api.Delivery.Controllers;
|
||||
using Umbraco.Cms.Api.Delivery.Controllers.Content;
|
||||
using Umbraco.Cms.Core;
|
||||
|
||||
namespace Umbraco.Cms.Api.Delivery.Filters;
|
||||
|
||||
@@ -13,7 +13,7 @@ internal sealed class SwaggerContentDocumentationFilter : SwaggerDocumentationFi
|
||||
|
||||
protected override void ApplyOperation(OpenApiOperation operation, OperationFilterContext context)
|
||||
{
|
||||
operation.Parameters ??= new List<OpenApiParameter>();
|
||||
operation.Parameters ??= new List<IOpenApiParameter>();
|
||||
|
||||
AddExpand(operation, context);
|
||||
|
||||
@@ -21,50 +21,50 @@ internal sealed class SwaggerContentDocumentationFilter : SwaggerDocumentationFi
|
||||
|
||||
operation.Parameters.Add(new OpenApiParameter
|
||||
{
|
||||
Name = Core.Constants.DeliveryApi.HeaderNames.AcceptLanguage,
|
||||
Name = Constants.DeliveryApi.HeaderNames.AcceptLanguage,
|
||||
In = ParameterLocation.Header,
|
||||
Required = false,
|
||||
Description = "Defines the language to return. Use this when querying language variant content items.",
|
||||
Schema = new OpenApiSchema { Type = "string" },
|
||||
Examples = new Dictionary<string, OpenApiExample>
|
||||
Schema = new OpenApiSchema { Type = JsonSchemaType.String },
|
||||
Examples = new Dictionary<string, IOpenApiExample>
|
||||
{
|
||||
{ "Default", new OpenApiExample { Value = new OpenApiString(string.Empty) } },
|
||||
{ "English culture", new OpenApiExample { Value = new OpenApiString("en-us") } }
|
||||
}
|
||||
{ "Default", new OpenApiExample { Value = string.Empty } },
|
||||
{ "English culture", new OpenApiExample { Value = "en-us" } },
|
||||
},
|
||||
});
|
||||
|
||||
operation.Parameters.Add(new OpenApiParameter
|
||||
{
|
||||
Name = Core.Constants.DeliveryApi.HeaderNames.AcceptSegment,
|
||||
Name = Constants.DeliveryApi.HeaderNames.AcceptSegment,
|
||||
In = ParameterLocation.Header,
|
||||
Required = false,
|
||||
Description = "Defines the segment to return. Use this when querying segment variant content items.",
|
||||
Schema = new OpenApiSchema { Type = "string" },
|
||||
Examples = new Dictionary<string, OpenApiExample>
|
||||
Schema = new OpenApiSchema { Type = JsonSchemaType.String },
|
||||
Examples = new Dictionary<string, IOpenApiExample>
|
||||
{
|
||||
{ "Default", new OpenApiExample { Value = new OpenApiString(string.Empty) } },
|
||||
{ "Segment One", new OpenApiExample { Value = new OpenApiString("segment-one") } }
|
||||
}
|
||||
{ "Default", new OpenApiExample { Value = string.Empty } },
|
||||
{ "Segment One", new OpenApiExample { Value = "segment-one" } },
|
||||
},
|
||||
});
|
||||
|
||||
AddApiKey(operation);
|
||||
|
||||
operation.Parameters.Add(new OpenApiParameter
|
||||
{
|
||||
Name = Core.Constants.DeliveryApi.HeaderNames.Preview,
|
||||
Name = Constants.DeliveryApi.HeaderNames.Preview,
|
||||
In = ParameterLocation.Header,
|
||||
Required = false,
|
||||
Description = "Whether to request draft content.",
|
||||
Schema = new OpenApiSchema { Type = "boolean" }
|
||||
Schema = new OpenApiSchema { Type = JsonSchemaType.Boolean },
|
||||
});
|
||||
|
||||
operation.Parameters.Add(new OpenApiParameter
|
||||
{
|
||||
Name = Core.Constants.DeliveryApi.HeaderNames.StartItem,
|
||||
Name = Constants.DeliveryApi.HeaderNames.StartItem,
|
||||
In = ParameterLocation.Header,
|
||||
Required = false,
|
||||
Description = "URL segment or GUID of a root content item.",
|
||||
Schema = new OpenApiSchema { Type = "string" }
|
||||
Schema = new OpenApiSchema { Type = JsonSchemaType.String },
|
||||
});
|
||||
}
|
||||
|
||||
@@ -92,105 +92,36 @@ internal sealed class SwaggerContentDocumentationFilter : SwaggerDocumentationFi
|
||||
}
|
||||
}
|
||||
|
||||
private Dictionary<string, OpenApiExample> FetchQueryParameterExamples() =>
|
||||
private Dictionary<string, IOpenApiExample> FetchQueryParameterExamples() =>
|
||||
new()
|
||||
{
|
||||
{ "Select all", new OpenApiExample { Value = new OpenApiString(string.Empty) } },
|
||||
{
|
||||
"Select all ancestors of a node by id",
|
||||
new OpenApiExample { Value = new OpenApiString("ancestors:id") }
|
||||
},
|
||||
{
|
||||
"Select all ancestors of a node by path",
|
||||
new OpenApiExample { Value = new OpenApiString("ancestors:path") }
|
||||
},
|
||||
{
|
||||
"Select all children of a node by id",
|
||||
new OpenApiExample { Value = new OpenApiString("children:id") }
|
||||
},
|
||||
{
|
||||
"Select all children of a node by path",
|
||||
new OpenApiExample { Value = new OpenApiString("children:path") }
|
||||
},
|
||||
{
|
||||
"Select all descendants of a node by id",
|
||||
new OpenApiExample { Value = new OpenApiString("descendants:id") }
|
||||
},
|
||||
{
|
||||
"Select all descendants of a node by path",
|
||||
new OpenApiExample { Value = new OpenApiString("descendants:path") }
|
||||
}
|
||||
{ "Select all", new OpenApiExample { Value = string.Empty } },
|
||||
{ "Select all ancestors of a node by id", new OpenApiExample { Value = "ancestors:id" } },
|
||||
{ "Select all ancestors of a node by path", new OpenApiExample { Value = "ancestors:path" } },
|
||||
{ "Select all children of a node by id", new OpenApiExample { Value = "children:id" } },
|
||||
{ "Select all children of a node by path", new OpenApiExample { Value = "children:path" } },
|
||||
{ "Select all descendants of a node by id", new OpenApiExample { Value = "descendants:id" } },
|
||||
{ "Select all descendants of a node by path", new OpenApiExample { Value = "descendants:path" } },
|
||||
};
|
||||
|
||||
private Dictionary<string, OpenApiExample> FilterQueryParameterExamples() =>
|
||||
private Dictionary<string, IOpenApiExample> FilterQueryParameterExamples() =>
|
||||
new()
|
||||
{
|
||||
{ "Default filter", new OpenApiExample { Value = new OpenApiString(string.Empty) } },
|
||||
{
|
||||
"Filter by content type (equals)",
|
||||
new OpenApiExample { Value = new OpenApiArray { new OpenApiString("contentType:alias1") } }
|
||||
},
|
||||
{
|
||||
"Filter by name (contains)",
|
||||
new OpenApiExample { Value = new OpenApiArray { new OpenApiString("name:nodeName") } }
|
||||
},
|
||||
{
|
||||
"Filter by creation date (less than)",
|
||||
new OpenApiExample { Value = new OpenApiArray { new OpenApiString("createDate<2024-01-01") } }
|
||||
},
|
||||
{
|
||||
"Filter by update date (greater than or equal)",
|
||||
new OpenApiExample { Value = new OpenApiArray { new OpenApiString("updateDate>:2023-01-01") } }
|
||||
}
|
||||
{ "Default filter", new OpenApiExample { Value = string.Empty } },
|
||||
{ "Filter by content type (equals)", new OpenApiExample { Value = new JsonArray { "contentType:alias1" } } },
|
||||
{ "Filter by name (contains)", new OpenApiExample { Value = new JsonArray { "name:nodeName" } } },
|
||||
{ "Filter by creation date (less than)", new OpenApiExample { Value = new JsonArray { "createDate<2024-01-01" } } },
|
||||
{ "Filter by update date (greater than or equal)", new OpenApiExample { Value = new JsonArray { "updateDate>:2023-01-01" } } },
|
||||
};
|
||||
|
||||
private Dictionary<string, OpenApiExample> SortQueryParameterExamples() =>
|
||||
private Dictionary<string, IOpenApiExample> SortQueryParameterExamples() =>
|
||||
new()
|
||||
{
|
||||
{ "Default sort", new OpenApiExample { Value = new OpenApiString(string.Empty) } },
|
||||
{
|
||||
"Sort by create date",
|
||||
new OpenApiExample
|
||||
{
|
||||
Value = new OpenApiArray
|
||||
{
|
||||
new OpenApiString("createDate:asc"), new OpenApiString("createDate:desc")
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Sort by level",
|
||||
new OpenApiExample
|
||||
{
|
||||
Value = new OpenApiArray { new OpenApiString("level:asc"), new OpenApiString("level:desc") }
|
||||
}
|
||||
},
|
||||
{
|
||||
"Sort by name",
|
||||
new OpenApiExample
|
||||
{
|
||||
Value = new OpenApiArray { new OpenApiString("name:asc"), new OpenApiString("name:desc") }
|
||||
}
|
||||
},
|
||||
{
|
||||
"Sort by sort order",
|
||||
new OpenApiExample
|
||||
{
|
||||
Value = new OpenApiArray
|
||||
{
|
||||
new OpenApiString("sortOrder:asc"), new OpenApiString("sortOrder:desc")
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Sort by update date",
|
||||
new OpenApiExample
|
||||
{
|
||||
Value = new OpenApiArray
|
||||
{
|
||||
new OpenApiString("updateDate:asc"), new OpenApiString("updateDate:desc")
|
||||
}
|
||||
}
|
||||
}
|
||||
{ "Default sort", new OpenApiExample { Value = string.Empty } },
|
||||
{ "Sort by create date", new OpenApiExample { Value = new JsonArray { "createDate:asc", "createDate:desc" } } },
|
||||
{ "Sort by level", new OpenApiExample { Value = new JsonArray { "level:asc", "level:desc" } } },
|
||||
{ "Sort by name", new OpenApiExample { Value = new JsonArray { "name:asc", "name:desc" } } },
|
||||
{ "Sort by sort order", new OpenApiExample { Value = new JsonArray { "sortOrder:asc", "sortOrder:desc" } } },
|
||||
{ "Sort by update date", new OpenApiExample { Value = new JsonArray { "updateDate:asc", "updateDate:desc" } } },
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.OpenApi.Any;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Microsoft.OpenApi;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
using Umbraco.Cms.Core;
|
||||
|
||||
namespace Umbraco.Cms.Api.Delivery.Filters;
|
||||
|
||||
@@ -9,6 +9,8 @@ internal abstract class SwaggerDocumentationFilterBase<TBaseController>
|
||||
: SwaggerFilterBase<TBaseController>, IOperationFilter, IParameterFilter
|
||||
where TBaseController : Controller
|
||||
{
|
||||
protected abstract string DocumentationLink { get; }
|
||||
|
||||
public void Apply(OpenApiOperation operation, OperationFilterContext context)
|
||||
{
|
||||
if (CanApply(context))
|
||||
@@ -17,21 +19,19 @@ internal abstract class SwaggerDocumentationFilterBase<TBaseController>
|
||||
}
|
||||
}
|
||||
|
||||
public void Apply(OpenApiParameter parameter, ParameterFilterContext context)
|
||||
public void Apply(IOpenApiParameter parameter, ParameterFilterContext context)
|
||||
{
|
||||
if (CanApply(context))
|
||||
if (CanApply(context) && parameter is OpenApiParameter openApiParameter)
|
||||
{
|
||||
ApplyParameter(parameter, context);
|
||||
ApplyParameter(openApiParameter, context);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract string DocumentationLink { get; }
|
||||
|
||||
protected abstract void ApplyOperation(OpenApiOperation operation, OperationFilterContext context);
|
||||
|
||||
protected abstract void ApplyParameter(OpenApiParameter parameter, ParameterFilterContext context);
|
||||
|
||||
protected void AddQueryParameterDocumentation(OpenApiParameter parameter, Dictionary<string, OpenApiExample> examples, string description)
|
||||
protected void AddQueryParameterDocumentation(OpenApiParameter parameter, Dictionary<string, IOpenApiExample> examples, string description)
|
||||
{
|
||||
parameter.Description = QueryParameterDescription(description);
|
||||
parameter.Examples = examples;
|
||||
@@ -60,15 +60,19 @@ internal abstract class SwaggerDocumentationFilterBase<TBaseController>
|
||||
AddFields(operation);
|
||||
}
|
||||
|
||||
protected void AddApiKey(OpenApiOperation operation) =>
|
||||
operation.Parameters.Add(new OpenApiParameter
|
||||
{
|
||||
Name = Core.Constants.DeliveryApi.HeaderNames.ApiKey,
|
||||
In = ParameterLocation.Header,
|
||||
Required = false,
|
||||
Description = "API key specified through configuration to authorize access to the API.",
|
||||
Schema = new OpenApiSchema { Type = "string" }
|
||||
});
|
||||
protected void AddApiKey(OpenApiOperation operation)
|
||||
{
|
||||
operation.Parameters ??= new List<IOpenApiParameter>();
|
||||
operation.Parameters.Add(
|
||||
new OpenApiParameter
|
||||
{
|
||||
Name = Constants.DeliveryApi.HeaderNames.ApiKey,
|
||||
In = ParameterLocation.Header,
|
||||
Required = false,
|
||||
Description = "API key specified through configuration to authorize access to the API.",
|
||||
Schema = new OpenApiSchema { Type = JsonSchemaType.String },
|
||||
});
|
||||
}
|
||||
|
||||
protected string PaginationDescription(bool skip, string itemType)
|
||||
=> $"Specifies the number of found {itemType} items to {(skip ? "skip" : "take")}. Use this to control pagination of the response.";
|
||||
@@ -82,78 +86,70 @@ internal abstract class SwaggerDocumentationFilterBase<TBaseController>
|
||||
|
||||
// FIXME: remove this when Delivery API V1 has been removed (expectedly in V15)
|
||||
private void AddExpandV1(OpenApiOperation operation)
|
||||
=> operation.Parameters.Add(new OpenApiParameter
|
||||
{
|
||||
Name = "expand",
|
||||
In = ParameterLocation.Query,
|
||||
Required = false,
|
||||
Description = QueryParameterDescription("Defines the properties that should be expanded in the response"),
|
||||
Schema = new OpenApiSchema { Type = "string" },
|
||||
Examples = new Dictionary<string, OpenApiExample>
|
||||
{
|
||||
operation.Parameters ??= new List<IOpenApiParameter>();
|
||||
operation.Parameters.Add(
|
||||
new OpenApiParameter
|
||||
{
|
||||
{ "Expand none", new OpenApiExample { Value = new OpenApiString(string.Empty) } },
|
||||
{ "Expand all", new OpenApiExample { Value = new OpenApiString("all") } },
|
||||
Name = "expand",
|
||||
In = ParameterLocation.Query,
|
||||
Required = false,
|
||||
Description =
|
||||
QueryParameterDescription("Defines the properties that should be expanded in the response"),
|
||||
Schema = new OpenApiSchema { Type = JsonSchemaType.String },
|
||||
Examples = new Dictionary<string, IOpenApiExample>
|
||||
{
|
||||
"Expand specific property",
|
||||
new OpenApiExample { Value = new OpenApiString("property:alias1") }
|
||||
{ "Expand none", new OpenApiExample { Value = string.Empty } },
|
||||
{ "Expand all", new OpenApiExample { Value = "all" } },
|
||||
{ "Expand specific property", new OpenApiExample { Value = "property:alias1" } },
|
||||
{ "Expand specific properties", new OpenApiExample { Value = "property:alias1,alias2" } },
|
||||
},
|
||||
{
|
||||
"Expand specific properties",
|
||||
new OpenApiExample { Value = new OpenApiString("property:alias1,alias2") }
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void AddExpand(OpenApiOperation operation)
|
||||
=> operation.Parameters.Add(new OpenApiParameter
|
||||
{
|
||||
Name = "expand",
|
||||
In = ParameterLocation.Query,
|
||||
Required = false,
|
||||
Description = QueryParameterDescription("Defines the properties that should be expanded in the response"),
|
||||
Schema = new OpenApiSchema { Type = "string" },
|
||||
Examples = new Dictionary<string, OpenApiExample>
|
||||
{
|
||||
operation.Parameters ??= new List<IOpenApiParameter>();
|
||||
operation.Parameters.Add(
|
||||
new OpenApiParameter
|
||||
{
|
||||
{ "Expand none", new OpenApiExample { Value = new OpenApiString(string.Empty) } },
|
||||
{ "Expand all properties", new OpenApiExample { Value = new OpenApiString("properties[$all]") } },
|
||||
Name = "expand",
|
||||
In = ParameterLocation.Query,
|
||||
Required = false,
|
||||
Description =
|
||||
QueryParameterDescription("Defines the properties that should be expanded in the response"),
|
||||
Schema = new OpenApiSchema { Type = JsonSchemaType.String },
|
||||
Examples = new Dictionary<string, IOpenApiExample>
|
||||
{
|
||||
"Expand specific property",
|
||||
new OpenApiExample { Value = new OpenApiString("properties[alias1]") }
|
||||
{ "Expand none", new OpenApiExample { Value = string.Empty } },
|
||||
{ "Expand all properties", new OpenApiExample { Value = "properties[$all]" } },
|
||||
{ "Expand specific property", new OpenApiExample { Value = "properties[alias1]" } },
|
||||
{ "Expand specific properties", new OpenApiExample { Value = "properties[alias1,alias2]" } },
|
||||
{ "Expand nested properties", new OpenApiExample { Value = "properties[alias1[properties[nestedAlias1,nestedAlias2]]]" } },
|
||||
},
|
||||
{
|
||||
"Expand specific properties",
|
||||
new OpenApiExample { Value = new OpenApiString("properties[alias1,alias2]") }
|
||||
},
|
||||
{
|
||||
"Expand nested properties",
|
||||
new OpenApiExample { Value = new OpenApiString("properties[alias1[properties[nestedAlias1,nestedAlias2]]]") }
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void AddFields(OpenApiOperation operation)
|
||||
=> operation.Parameters.Add(new OpenApiParameter
|
||||
{
|
||||
Name = "fields",
|
||||
In = ParameterLocation.Query,
|
||||
Required = false,
|
||||
Description = QueryParameterDescription("Explicitly defines which properties should be included in the response (by default all properties are included)"),
|
||||
Schema = new OpenApiSchema { Type = "string" },
|
||||
Examples = new Dictionary<string, OpenApiExample>
|
||||
{
|
||||
operation.Parameters ??= new List<IOpenApiParameter>();
|
||||
operation.Parameters.Add(
|
||||
new OpenApiParameter
|
||||
{
|
||||
{ "Include all properties", new OpenApiExample { Value = new OpenApiString("properties[$all]") } },
|
||||
Name = "fields",
|
||||
In = ParameterLocation.Query,
|
||||
Required = false,
|
||||
Description =
|
||||
QueryParameterDescription(
|
||||
"Explicitly defines which properties should be included in the response (by default all properties are included)"),
|
||||
Schema = new OpenApiSchema { Type = JsonSchemaType.String },
|
||||
Examples = new Dictionary<string, IOpenApiExample>
|
||||
{
|
||||
"Include only specific property",
|
||||
new OpenApiExample { Value = new OpenApiString("properties[alias1]") }
|
||||
{ "Include all properties", new OpenApiExample { Value = "properties[$all]" } },
|
||||
{ "Include only specific property", new OpenApiExample { Value = "properties[alias1]" } },
|
||||
{ "Include only specific properties", new OpenApiExample { Value = "properties[alias1,alias2]" } },
|
||||
{ "Include only specific nested properties", new OpenApiExample { Value = "properties[alias1[properties[nestedAlias1,nestedAlias2]]]" } },
|
||||
},
|
||||
{
|
||||
"Include only specific properties",
|
||||
new OpenApiExample { Value = new OpenApiString("properties[alias1,alias2]") }
|
||||
},
|
||||
{
|
||||
"Include only specific nested properties",
|
||||
new OpenApiExample { Value = new OpenApiString("properties[alias1[properties[nestedAlias1,nestedAlias2]]]") }
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
using Microsoft.OpenApi.Any;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using System.Text.Json.Nodes;
|
||||
using Microsoft.OpenApi;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
using Umbraco.Cms.Api.Delivery.Configuration;
|
||||
using Umbraco.Cms.Api.Delivery.Controllers;
|
||||
using Umbraco.Cms.Api.Delivery.Controllers.Media;
|
||||
|
||||
namespace Umbraco.Cms.Api.Delivery.Filters;
|
||||
@@ -13,7 +12,7 @@ internal sealed class SwaggerMediaDocumentationFilter : SwaggerDocumentationFilt
|
||||
|
||||
protected override void ApplyOperation(OpenApiOperation operation, OperationFilterContext context)
|
||||
{
|
||||
operation.Parameters ??= new List<OpenApiParameter>();
|
||||
operation.Parameters ??= new List<IOpenApiParameter>();
|
||||
|
||||
AddExpand(operation, context);
|
||||
|
||||
@@ -46,77 +45,29 @@ internal sealed class SwaggerMediaDocumentationFilter : SwaggerDocumentationFilt
|
||||
}
|
||||
}
|
||||
|
||||
private Dictionary<string, OpenApiExample> FetchQueryParameterExamples() =>
|
||||
private Dictionary<string, IOpenApiExample> FetchQueryParameterExamples() =>
|
||||
new()
|
||||
{
|
||||
{
|
||||
"Select all children at root level",
|
||||
new OpenApiExample { Value = new OpenApiString("children:/") }
|
||||
},
|
||||
{
|
||||
"Select all children of a media item by id",
|
||||
new OpenApiExample { Value = new OpenApiString("children:id") }
|
||||
},
|
||||
{
|
||||
"Select all children of a media item by path",
|
||||
new OpenApiExample { Value = new OpenApiString("children:path") }
|
||||
}
|
||||
{ "Select all children at root level", new OpenApiExample { Value = "children:/" } },
|
||||
{ "Select all children of a media item by id", new OpenApiExample { Value = "children:id" } },
|
||||
{ "Select all children of a media item by path", new OpenApiExample { Value = "children:path" } },
|
||||
};
|
||||
|
||||
private Dictionary<string, OpenApiExample> FilterQueryParameterExamples() =>
|
||||
private Dictionary<string, IOpenApiExample> FilterQueryParameterExamples() =>
|
||||
new()
|
||||
{
|
||||
{ "Default filter", new OpenApiExample { Value = new OpenApiString(string.Empty) } },
|
||||
{
|
||||
"Filter by media type",
|
||||
new OpenApiExample { Value = new OpenApiArray { new OpenApiString("mediaType:alias1") } }
|
||||
},
|
||||
{
|
||||
"Filter by name",
|
||||
new OpenApiExample { Value = new OpenApiArray { new OpenApiString("name:nodeName") } }
|
||||
}
|
||||
{ "Default filter", new OpenApiExample { Value = string.Empty } },
|
||||
{ "Filter by media type", new OpenApiExample { Value = new JsonArray { "mediaType:alias1" } } },
|
||||
{ "Filter by name", new OpenApiExample { Value = new JsonArray { "name:nodeName" } } },
|
||||
};
|
||||
|
||||
private Dictionary<string, OpenApiExample> SortQueryParameterExamples() =>
|
||||
private Dictionary<string, IOpenApiExample> SortQueryParameterExamples() =>
|
||||
new()
|
||||
{
|
||||
{ "Default sort", new OpenApiExample { Value = new OpenApiString(string.Empty) } },
|
||||
{
|
||||
"Sort by create date",
|
||||
new OpenApiExample
|
||||
{
|
||||
Value = new OpenApiArray
|
||||
{
|
||||
new OpenApiString("createDate:asc"), new OpenApiString("createDate:desc")
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Sort by name",
|
||||
new OpenApiExample
|
||||
{
|
||||
Value = new OpenApiArray { new OpenApiString("name:asc"), new OpenApiString("name:desc") }
|
||||
}
|
||||
},
|
||||
{
|
||||
"Sort by sort order",
|
||||
new OpenApiExample
|
||||
{
|
||||
Value = new OpenApiArray
|
||||
{
|
||||
new OpenApiString("sortOrder:asc"), new OpenApiString("sortOrder:desc")
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Sort by update date",
|
||||
new OpenApiExample
|
||||
{
|
||||
Value = new OpenApiArray
|
||||
{
|
||||
new OpenApiString("updateDate:asc"), new OpenApiString("updateDate:desc")
|
||||
}
|
||||
}
|
||||
}
|
||||
{ "Default sort", new OpenApiExample { Value = string.Empty } },
|
||||
{ "Sort by create date", new OpenApiExample { Value = new JsonArray { "createDate:asc", "createDate:desc" } } },
|
||||
{ "Sort by name", new OpenApiExample { Value = new JsonArray { "name:asc", "name:desc" } } },
|
||||
{ "Sort by sort order", new OpenApiExample { Value = new JsonArray { "sortOrder:asc", "sortOrder:desc" } } },
|
||||
{ "Sort by update date", new OpenApiExample { Value = new JsonArray { "updateDate:asc", "updateDate:desc" } } },
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Core.DeliveryApi;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.PublishedCache;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Api.Delivery.Querying;
|
||||
@@ -14,30 +11,6 @@ public abstract class QueryOptionBase
|
||||
private readonly IApiDocumentUrlService _apiDocumentUrlService;
|
||||
private readonly IVariationContextAccessor _variationContextAccessor;
|
||||
|
||||
[Obsolete("Please use the non-obsolete constructor. Will be removed in V17.")]
|
||||
public QueryOptionBase(
|
||||
IPublishedContentCache publishedContentCache,
|
||||
IRequestRoutingService requestRoutingService)
|
||||
: this(
|
||||
requestRoutingService,
|
||||
StaticServiceProvider.Instance.GetRequiredService<IRequestPreviewService>(),
|
||||
StaticServiceProvider.Instance.GetRequiredService<IApiDocumentUrlService>(),
|
||||
StaticServiceProvider.Instance.GetRequiredService<IVariationContextAccessor>())
|
||||
{
|
||||
}
|
||||
|
||||
[Obsolete("Please use the non-obsolete constructor. Will be removed in V17.")]
|
||||
public QueryOptionBase(
|
||||
IPublishedContentCache publishedContentCache,
|
||||
IRequestRoutingService requestRoutingService,
|
||||
IRequestPreviewService requestPreviewService,
|
||||
IRequestCultureService requestCultureService,
|
||||
IApiDocumentUrlService apiDocumentUrlService,
|
||||
IVariationContextAccessor variationContextAccessor)
|
||||
: this(requestRoutingService, requestPreviewService, apiDocumentUrlService, variationContextAccessor)
|
||||
{
|
||||
}
|
||||
|
||||
public QueryOptionBase(
|
||||
IRequestRoutingService requestRoutingService,
|
||||
IRequestPreviewService requestPreviewService,
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
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.Cms.Core.Services.Navigation;
|
||||
|
||||
namespace Umbraco.Cms.Api.Delivery.Querying.Selectors;
|
||||
@@ -13,46 +10,6 @@ public sealed class AncestorsSelector : QueryOptionBase, ISelectorHandler
|
||||
private readonly IDocumentNavigationQueryService _navigationQueryService;
|
||||
private const string AncestorsSpecifier = "ancestors:";
|
||||
|
||||
[Obsolete("Please use the non-obsolete constructor. Will be removed in V17.")]
|
||||
public AncestorsSelector(
|
||||
IPublishedContentCache publishedContentCache,
|
||||
IRequestRoutingService requestRoutingService,
|
||||
IDocumentNavigationQueryService navigationQueryService,
|
||||
IRequestPreviewService requestPreviewService)
|
||||
: this(
|
||||
requestRoutingService,
|
||||
requestPreviewService,
|
||||
StaticServiceProvider.Instance.GetRequiredService<IApiDocumentUrlService>(),
|
||||
StaticServiceProvider.Instance.GetRequiredService<IVariationContextAccessor>(),
|
||||
navigationQueryService)
|
||||
{
|
||||
}
|
||||
|
||||
[Obsolete("Please use the non-obsolete constructor. Will be removed in V17.")]
|
||||
public AncestorsSelector(
|
||||
IPublishedContentCache publishedContentCache,
|
||||
IRequestRoutingService requestRoutingService,
|
||||
IDocumentNavigationQueryService navigationQueryService)
|
||||
: this(
|
||||
requestRoutingService,
|
||||
StaticServiceProvider.Instance.GetRequiredService<IRequestPreviewService>(),
|
||||
StaticServiceProvider.Instance.GetRequiredService<IApiDocumentUrlService>(),
|
||||
StaticServiceProvider.Instance.GetRequiredService<IVariationContextAccessor>(),
|
||||
navigationQueryService)
|
||||
{
|
||||
}
|
||||
|
||||
[Obsolete("Use the constructor that takes all parameters. Scheduled for removal in V17.")]
|
||||
public AncestorsSelector(IPublishedContentCache publishedContentCache, IRequestRoutingService requestRoutingService)
|
||||
: this(
|
||||
requestRoutingService,
|
||||
StaticServiceProvider.Instance.GetRequiredService<IRequestPreviewService>(),
|
||||
StaticServiceProvider.Instance.GetRequiredService<IApiDocumentUrlService>(),
|
||||
StaticServiceProvider.Instance.GetRequiredService<IVariationContextAccessor>(),
|
||||
StaticServiceProvider.Instance.GetRequiredService<IDocumentNavigationQueryService>())
|
||||
{
|
||||
}
|
||||
|
||||
public AncestorsSelector(
|
||||
IRequestRoutingService requestRoutingService,
|
||||
IRequestPreviewService requestPreviewService,
|
||||
@@ -62,18 +19,6 @@ public sealed class AncestorsSelector : QueryOptionBase, ISelectorHandler
|
||||
: base(requestRoutingService, requestPreviewService, apiDocumentUrlService, variationContextAccessor)
|
||||
=> _navigationQueryService = navigationQueryService;
|
||||
|
||||
[Obsolete("Use the constructor that takes all parameters. Scheduled for removal in V17.")]
|
||||
public AncestorsSelector(
|
||||
IRequestRoutingService requestRoutingService,
|
||||
IPublishedContentCache publishedContentCache,
|
||||
IRequestPreviewService requestPreviewService,
|
||||
IApiDocumentUrlService apiDocumentUrlService,
|
||||
IVariationContextAccessor variationContextAccessor,
|
||||
IDocumentNavigationQueryService navigationQueryService)
|
||||
: this(requestRoutingService, requestPreviewService, apiDocumentUrlService, variationContextAccessor, navigationQueryService)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool CanHandle(string query)
|
||||
=> query.StartsWith(AncestorsSpecifier, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
@@ -12,27 +12,6 @@ public sealed class ChildrenSelector : QueryOptionBase, ISelectorHandler
|
||||
{
|
||||
private const string ChildrenSpecifier = "children:";
|
||||
|
||||
[Obsolete("Please use the non-obsolete constructor. Will be removed in V17.")]
|
||||
public ChildrenSelector(IPublishedContentCache publishedContentCache, IRequestRoutingService requestRoutingService)
|
||||
: this(
|
||||
requestRoutingService,
|
||||
StaticServiceProvider.Instance.GetRequiredService<IRequestPreviewService>(),
|
||||
StaticServiceProvider.Instance.GetRequiredService<IApiDocumentUrlService>(),
|
||||
StaticServiceProvider.Instance.GetRequiredService<IVariationContextAccessor>())
|
||||
{
|
||||
}
|
||||
|
||||
[Obsolete("Please use the non-obsolete constructor. Will be removed in V17.")]
|
||||
public ChildrenSelector(
|
||||
IPublishedContentCache publishedContentCache,
|
||||
IRequestRoutingService requestRoutingService,
|
||||
IRequestPreviewService requestPreviewService,
|
||||
IApiDocumentUrlService apiDocumentUrlService,
|
||||
IVariationContextAccessor variationContextAccessor)
|
||||
: this(requestRoutingService, requestPreviewService, apiDocumentUrlService, variationContextAccessor)
|
||||
{
|
||||
}
|
||||
|
||||
public ChildrenSelector(
|
||||
IRequestRoutingService requestRoutingService,
|
||||
IRequestPreviewService requestPreviewService,
|
||||
|
||||
@@ -12,27 +12,6 @@ public sealed class DescendantsSelector : QueryOptionBase, ISelectorHandler
|
||||
{
|
||||
private const string DescendantsSpecifier = "descendants:";
|
||||
|
||||
[Obsolete("Please use the non-obsolete constructor. Will be removed in V17.")]
|
||||
public DescendantsSelector(IPublishedContentCache publishedContentCache, IRequestRoutingService requestRoutingService)
|
||||
: this(
|
||||
requestRoutingService,
|
||||
StaticServiceProvider.Instance.GetRequiredService<IRequestPreviewService>(),
|
||||
StaticServiceProvider.Instance.GetRequiredService<IApiDocumentUrlService>(),
|
||||
StaticServiceProvider.Instance.GetRequiredService<IVariationContextAccessor>())
|
||||
{
|
||||
}
|
||||
|
||||
[Obsolete("Please use the non-obsolete constructor. Will be removed in V17.")]
|
||||
public DescendantsSelector(
|
||||
IPublishedContentCache publishedContentCache,
|
||||
IRequestRoutingService requestRoutingService,
|
||||
IRequestPreviewService requestPreviewService,
|
||||
IApiDocumentUrlService apiDocumentUrlService,
|
||||
IVariationContextAccessor variationContextAccessor)
|
||||
: this(requestRoutingService, requestPreviewService, apiDocumentUrlService, variationContextAccessor)
|
||||
{
|
||||
}
|
||||
|
||||
public DescendantsSelector(
|
||||
IRequestRoutingService requestRoutingService,
|
||||
IRequestPreviewService requestPreviewService,
|
||||
|
||||
@@ -66,9 +66,14 @@ internal sealed class DeliveryApiItemsEndpointsMatcherPolicy : MatcherPolicy, IE
|
||||
{
|
||||
ApiVersion[]? supportedApiVersions = endpoint.Metadata.GetMetadata<MapToApiVersionAttribute>()?.Versions.ToArray();
|
||||
|
||||
// if the endpoint is versioned, the requested API version must be among the API versions supported by the endpoint.
|
||||
// if the endpoint is NOT versioned, it cannot be used with a requested API version
|
||||
return supportedApiVersions?.Contains(requestedApiVersion) ?? requestedApiVersion is null;
|
||||
// If the endpoint is versioned, the requested API version must be among the API versions supported by the endpoint.
|
||||
// If the endpoint is NOT versioned, it cannot be used with a requested API version.
|
||||
if (supportedApiVersions is not null && requestedApiVersion is not null)
|
||||
{
|
||||
return supportedApiVersions.Contains(requestedApiVersion);
|
||||
}
|
||||
|
||||
return requestedApiVersion is null;
|
||||
}
|
||||
|
||||
private static bool IsByIdsController(ControllerActionDescriptor? controllerActionDescriptor)
|
||||
|
||||
@@ -66,7 +66,7 @@ internal sealed class ApiMediaQueryService : IApiMediaQueryService
|
||||
private IPublishedContent? TryGetByPath(string path, IPublishedMediaCache mediaCache)
|
||||
{
|
||||
var segments = path.Split(Constants.CharArrays.ForwardSlash, StringSplitOptions.RemoveEmptyEntries);
|
||||
IEnumerable<IPublishedContent> currentChildren = mediaCache.GetAtRoot();
|
||||
IEnumerable<IPublishedContent> currentChildren = GetRootContent(mediaCache);
|
||||
IPublishedContent? resolvedMedia = null;
|
||||
|
||||
foreach (var segment in segments)
|
||||
@@ -103,7 +103,7 @@ internal sealed class ApiMediaQueryService : IApiMediaQueryService
|
||||
IPublishedMediaCache mediaCache = GetRequiredPublishedMediaCache();
|
||||
if (childrenOf.Trim(Constants.CharArrays.ForwardSlash).Length == 0)
|
||||
{
|
||||
return mediaCache.GetAtRoot();
|
||||
return GetRootContent(mediaCache);
|
||||
}
|
||||
|
||||
IPublishedContent? parent = Guid.TryParse(childrenOf, out Guid parentKey)
|
||||
@@ -196,4 +196,8 @@ internal sealed class ApiMediaQueryService : IApiMediaQueryService
|
||||
|
||||
return Attempt.SucceedWithStatus(ApiMediaQueryOperationStatus.Success, result);
|
||||
}
|
||||
|
||||
private IEnumerable<IPublishedContent> GetRootContent(IPublishedMediaCache mediaCache)
|
||||
=> _mediaNavigationQueryService.TryGetRootKeys(out IEnumerable<Guid> rootKeys) is false ? []
|
||||
: rootKeys.Select(x => mediaCache.GetById(false, x)).WhereNotNull();
|
||||
}
|
||||
|
||||
@@ -19,12 +19,6 @@ internal sealed partial class RequestCultureService : RequestHeaderHandler, IReq
|
||||
return ValidLanguageHeaderRegex().IsMatch(acceptLanguage) ? acceptLanguage : null;
|
||||
}
|
||||
|
||||
[Obsolete("Use IVariationContextAccessor to manipulate the variation context. Scheduled for removal in V17.")]
|
||||
public void SetRequestCulture(string culture)
|
||||
{
|
||||
// no-op
|
||||
}
|
||||
|
||||
// at the time of writing we're introducing this to get rid of accept-language header values like "en-GB,en-US;q=0.9,en;q=0.8",
|
||||
// so we don't want to be too restrictive in this regex - keep it simple for now.
|
||||
[GeneratedRegex(@"^[\w-]*$")]
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<Title>Umbraco CMS - Delivery API</Title>
|
||||
<Description>Contains the presentation layer for the Umbraco CMS Delivery API.</Description>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<!--
|
||||
TODO: Fix and remove overrides:
|
||||
@@ -11,11 +12,18 @@
|
||||
-->
|
||||
<WarningsNotAsErrors>$(WarningsNotAsErrors),ASP0019,CS0618,CS0612</WarningsNotAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Umbraco.Cms.Api.Common\Umbraco.Cms.Api.Common.csproj" />
|
||||
<ProjectReference Include="..\Umbraco.Web.Common\Umbraco.Web.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Linq.Async">
|
||||
<ExcludeAssets>compile</ExcludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
|
||||
<_Parameter1>Umbraco.Tests.UnitTests</_Parameter1>
|
||||
|
||||
@@ -5,6 +5,7 @@ using Microsoft.AspNetCore.DataProtection;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using Umbraco.Cms.Api.Management.Security;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
@@ -219,10 +220,44 @@ public class ConfigureBackOfficeCookieOptions : IConfigureNamedOptions<CookieAut
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
},
|
||||
// FIXME: We want to change this over to using an attribute on the backoffice controllers
|
||||
// See this for more: https://github.com/dotnet/aspnetcore/issues/63093#issuecomment-3201530217
|
||||
OnRedirectToLogin = context =>
|
||||
{
|
||||
if (IsXhr(context.Request))
|
||||
{
|
||||
context.Response.Headers.Location = context.RedirectUri;
|
||||
context.Response.StatusCode = 401;
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Response.Redirect(context.RedirectUri);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
},
|
||||
OnRedirectToAccessDenied = context =>
|
||||
{
|
||||
if (IsXhr(context.Request))
|
||||
{
|
||||
context.Response.Headers.Location = context.RedirectUri;
|
||||
context.Response.StatusCode = 403;
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Response.Redirect(context.RedirectUri);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private bool IsXhr(HttpRequest request) =>
|
||||
string.Equals(request.Query[HeaderNames.XRequestedWith], "XMLHttpRequest", StringComparison.Ordinal) ||
|
||||
string.Equals(request.Headers.XRequestedWith, "XMLHttpRequest", StringComparison.Ordinal);
|
||||
|
||||
/// <summary>
|
||||
/// Ensures the ticket is renewed if the <see cref="SecuritySettings.KeepUserLoggedIn" /> is set to true
|
||||
/// and the current request is for the get user seconds endpoint
|
||||
|
||||
+7
-8
@@ -1,8 +1,8 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Microsoft.OpenApi;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
using Umbraco.Cms.Api.Common.OpenApi;
|
||||
using Umbraco.Cms.Api.Common.Security;
|
||||
using Umbraco.Cms.Api.Common.Serialization;
|
||||
using Umbraco.Cms.Api.Management.DependencyInjection;
|
||||
using Umbraco.Cms.Api.Management.OpenApi;
|
||||
@@ -11,7 +11,7 @@ namespace Umbraco.Cms.Api.Management.Configuration;
|
||||
|
||||
public class ConfigureUmbracoManagementApiSwaggerGenOptions : IConfigureOptions<SwaggerGenOptions>
|
||||
{
|
||||
private IUmbracoJsonTypeInfoResolver _umbracoJsonTypeInfoResolver;
|
||||
private readonly IUmbracoJsonTypeInfoResolver _umbracoJsonTypeInfoResolver;
|
||||
|
||||
public ConfigureUmbracoManagementApiSwaggerGenOptions(IUmbracoJsonTypeInfoResolver umbracoJsonTypeInfoResolver)
|
||||
{
|
||||
@@ -20,7 +20,6 @@ public class ConfigureUmbracoManagementApiSwaggerGenOptions : IConfigureOptions<
|
||||
|
||||
public void Configure(SwaggerGenOptions swaggerGenOptions)
|
||||
{
|
||||
|
||||
swaggerGenOptions.SwaggerDoc(
|
||||
ManagementApiConfiguration.ApiName,
|
||||
new OpenApiInfo
|
||||
@@ -51,10 +50,10 @@ public class ConfigureUmbracoManagementApiSwaggerGenOptions : IConfigureOptions<
|
||||
AuthorizationCode = new OpenApiOAuthFlow
|
||||
{
|
||||
AuthorizationUrl =
|
||||
new Uri(Common.Security.Paths.BackOfficeApi.AuthorizationEndpoint, UriKind.Relative),
|
||||
TokenUrl = new Uri(Common.Security.Paths.BackOfficeApi.TokenEndpoint, UriKind.Relative)
|
||||
}
|
||||
}
|
||||
new Uri(Paths.BackOfficeApi.AuthorizationEndpoint, UriKind.Relative),
|
||||
TokenUrl = new Uri(Paths.BackOfficeApi.TokenEndpoint, UriKind.Relative),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Sets Security requirement on backoffice apis
|
||||
|
||||
@@ -29,39 +29,6 @@ public abstract class ContentCollectionControllerBase<TContent, TCollectionRespo
|
||||
_flagProviders = flagProvider;
|
||||
}
|
||||
|
||||
[Obsolete("Use the constructer with all parameters. To be removed in Umbraco 18")]
|
||||
protected ContentCollectionControllerBase(IUmbracoMapper mapper)
|
||||
: this(mapper, StaticServiceProvider.Instance.GetRequiredService<FlagProviderCollection>())
|
||||
{
|
||||
}
|
||||
|
||||
[Obsolete("This method is no longer used and will be removed in Umbraco 17.")]
|
||||
protected IActionResult CollectionResult(ListViewPagedModel<TContent> result)
|
||||
{
|
||||
PagedModel<TContent> collectionItemsResult = result.Items;
|
||||
ListViewConfiguration collectionConfiguration = result.ListViewConfiguration;
|
||||
|
||||
var collectionPropertyAliases = collectionConfiguration
|
||||
.IncludeProperties
|
||||
.Select(p => p.Alias)
|
||||
.WhereNotNull()
|
||||
.ToArray();
|
||||
|
||||
List<TCollectionResponseModel> collectionResponseModels =
|
||||
_mapper.MapEnumerable<TContent, TCollectionResponseModel>(collectionItemsResult.Items, context =>
|
||||
{
|
||||
context.SetIncludedProperties(collectionPropertyAliases);
|
||||
});
|
||||
|
||||
var pageViewModel = new PagedViewModel<TCollectionResponseModel>
|
||||
{
|
||||
Items = collectionResponseModels,
|
||||
Total = collectionItemsResult.Total,
|
||||
};
|
||||
|
||||
return Ok(pageViewModel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a collection result from the provided collection response models and total number of items.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.Factories;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.DataType;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.DataType;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
[Obsolete("Please use ReferencedByDataTypeController and the referenced-by endpoint. Scheduled for removal in Umbraco 17.")]
|
||||
public class ReferencesDataTypeController : DataTypeControllerBase
|
||||
{
|
||||
private readonly IDataTypeService _dataTypeService;
|
||||
private readonly IDataTypeReferencePresentationFactory _dataTypeReferencePresentationFactory;
|
||||
|
||||
public ReferencesDataTypeController(IDataTypeService dataTypeService, IDataTypeReferencePresentationFactory dataTypeReferencePresentationFactory)
|
||||
{
|
||||
_dataTypeService = dataTypeService;
|
||||
_dataTypeReferencePresentationFactory = dataTypeReferencePresentationFactory;
|
||||
}
|
||||
|
||||
[HttpGet("{id:guid}/references")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(DataTypeReferenceResponseModel[]), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> References(CancellationToken cancellationToken, Guid id)
|
||||
{
|
||||
Attempt<IReadOnlyDictionary<Udi, IEnumerable<string>>, DataTypeOperationStatus> result = await _dataTypeService.GetReferencesAsync(id);
|
||||
if (result.Success == false)
|
||||
{
|
||||
return DataTypeOperationStatusResult(result.Status);
|
||||
}
|
||||
|
||||
DataTypeReferenceResponseModel[] viewModels = _dataTypeReferencePresentationFactory.CreateDataTypeReferenceViewModels(result.Result).ToArray();
|
||||
return Ok(viewModels);
|
||||
}
|
||||
}
|
||||
@@ -2,13 +2,10 @@ using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Api.Management.Factories;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Document;
|
||||
using Umbraco.Cms.Core.Actions;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Security.Authorization;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.Querying;
|
||||
using Umbraco.Cms.Web.Common.Authorization;
|
||||
using Umbraco.Extensions;
|
||||
@@ -22,31 +19,6 @@ public class ByKeyDocumentController : DocumentControllerBase
|
||||
private readonly IDocumentPresentationFactory _documentPresentationFactory;
|
||||
private readonly IContentQueryService _contentQueryService;
|
||||
|
||||
[Obsolete("Scheduled for removal in v17")]
|
||||
public ByKeyDocumentController(
|
||||
IAuthorizationService authorizationService,
|
||||
IContentEditingService contentEditingService,
|
||||
IDocumentPresentationFactory documentPresentationFactory)
|
||||
{
|
||||
_authorizationService = authorizationService;
|
||||
_documentPresentationFactory = documentPresentationFactory;
|
||||
_contentQueryService = StaticServiceProvider.Instance.GetRequiredService<IContentQueryService>();
|
||||
}
|
||||
|
||||
// needed for greedy selection until other constructor remains in v17
|
||||
[Obsolete("Scheduled for removal in v17")]
|
||||
public ByKeyDocumentController(
|
||||
IAuthorizationService authorizationService,
|
||||
IContentEditingService contentEditingService,
|
||||
IDocumentPresentationFactory documentPresentationFactory,
|
||||
IContentQueryService contentQueryService)
|
||||
{
|
||||
_authorizationService = authorizationService;
|
||||
_documentPresentationFactory = documentPresentationFactory;
|
||||
_contentQueryService = contentQueryService;
|
||||
}
|
||||
|
||||
[ActivatorUtilitiesConstructor]
|
||||
public ByKeyDocumentController(
|
||||
IAuthorizationService authorizationService,
|
||||
IDocumentPresentationFactory documentPresentationFactory,
|
||||
|
||||
-6
@@ -23,12 +23,6 @@ public abstract class DocumentCollectionControllerBase : ContentCollectionContro
|
||||
{
|
||||
}
|
||||
|
||||
[Obsolete("Please use the constructor with all parameters. Scheduled to be removed in V18")]
|
||||
protected DocumentCollectionControllerBase(IUmbracoMapper mapper)
|
||||
: base(mapper)
|
||||
{
|
||||
}
|
||||
|
||||
protected IActionResult CollectionOperationStatusResult(ContentCollectionOperationStatus status)
|
||||
=> ContentCollectionOperationStatusResult(status, "document");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Common.Builders;
|
||||
using Umbraco.Cms.Api.Management.Factories;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Document;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Document;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class DocumentPreviewUrlController : DocumentControllerBase
|
||||
{
|
||||
private readonly IContentService _contentService;
|
||||
private readonly IDocumentUrlFactory _documentUrlFactory;
|
||||
|
||||
public DocumentPreviewUrlController(
|
||||
IContentService contentService,
|
||||
IDocumentUrlFactory documentUrlFactory)
|
||||
{
|
||||
_contentService = contentService;
|
||||
_documentUrlFactory = documentUrlFactory;
|
||||
}
|
||||
|
||||
[MapToApiVersion("1.0")]
|
||||
[HttpGet("{id:guid}/preview-url")]
|
||||
[ProducesResponseType(typeof(DocumentUrlInfo), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> GetPreviewUrl(Guid id, string providerAlias, string? culture, string? segment)
|
||||
{
|
||||
IContent? content = _contentService.GetById(id);
|
||||
if (content is null)
|
||||
{
|
||||
return NotFound(new ProblemDetailsBuilder()
|
||||
.WithTitle("Document not found")
|
||||
.WithDetail("The requested document did not exist.")
|
||||
.Build());
|
||||
}
|
||||
|
||||
DocumentUrlInfo? previewUrlInfo = await _documentUrlFactory.GetPreviewUrlAsync(content, providerAlias, culture, segment);
|
||||
if (previewUrlInfo is null)
|
||||
{
|
||||
return BadRequest(new ProblemDetailsBuilder()
|
||||
.WithTitle("No preview URL for document")
|
||||
.WithDetail("Failed to produce a preview URL for the requested document.")
|
||||
.Build());
|
||||
}
|
||||
|
||||
return Ok(previewUrlInfo);
|
||||
}
|
||||
}
|
||||
@@ -1,60 +1,31 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Document;
|
||||
using Umbraco.Cms.Core.Actions;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Mapping;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Security.Authorization;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Web.Common.Authorization;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Document;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class DomainsController : DocumentControllerBase
|
||||
{
|
||||
private readonly IAuthorizationService _authorizationService;
|
||||
private readonly IDomainService _domainService;
|
||||
private readonly IUmbracoMapper _umbracoMapper;
|
||||
|
||||
[ActivatorUtilitiesConstructor]
|
||||
public DomainsController(IAuthorizationService authorizationService, IDomainService domainService, IUmbracoMapper umbracoMapper)
|
||||
public DomainsController(IDomainService domainService, IUmbracoMapper umbracoMapper)
|
||||
{
|
||||
_authorizationService = authorizationService;
|
||||
_domainService = domainService;
|
||||
_umbracoMapper = umbracoMapper;
|
||||
}
|
||||
|
||||
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")]
|
||||
public DomainsController(IDomainService domainService, IUmbracoMapper umbracoMapper)
|
||||
: this(
|
||||
StaticServiceProvider.Instance.GetRequiredService<IAuthorizationService>(),
|
||||
domainService,
|
||||
umbracoMapper)
|
||||
{
|
||||
}
|
||||
|
||||
[MapToApiVersion("1.0")]
|
||||
[HttpGet("{id:guid}/domains")]
|
||||
[ProducesResponseType(typeof(DomainsResponseModel), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> Domains(CancellationToken cancellationToken, Guid id)
|
||||
{
|
||||
AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync(
|
||||
User,
|
||||
ContentPermissionResource.WithKeys(ActionBrowse.ActionLetter, id),
|
||||
AuthorizationPolicies.ContentPermissionByResource);
|
||||
|
||||
if (!authorizationResult.Succeeded)
|
||||
{
|
||||
return Forbidden();
|
||||
}
|
||||
|
||||
IDomain[] assignedDomains = (await _domainService.GetAssignedDomainsAsync(id, true))
|
||||
.OrderBy(d => d.SortOrder)
|
||||
.ToArray();
|
||||
|
||||
@@ -1,19 +1,13 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Api.Management.Factories;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Document;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Actions;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Mapping;
|
||||
using Umbraco.Cms.Core.Models.ContentEditing;
|
||||
using Umbraco.Cms.Core.Security.Authorization;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
using Umbraco.Cms.Web.Common.Authorization;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Document;
|
||||
@@ -21,30 +15,17 @@ namespace Umbraco.Cms.Api.Management.Controllers.Document;
|
||||
[ApiVersion("1.0")]
|
||||
public class UpdateDomainsController : DocumentControllerBase
|
||||
{
|
||||
private readonly IAuthorizationService _authorizationService;
|
||||
private readonly IDomainService _domainService;
|
||||
private readonly IUmbracoMapper _umbracoMapper;
|
||||
private readonly IDomainPresentationFactory _domainPresentationFactory;
|
||||
|
||||
[ActivatorUtilitiesConstructor]
|
||||
public UpdateDomainsController(IAuthorizationService authorizationService, IDomainService domainService, IUmbracoMapper umbracoMapper, IDomainPresentationFactory domainPresentationFactory)
|
||||
public UpdateDomainsController(IDomainService domainService, IUmbracoMapper umbracoMapper, IDomainPresentationFactory domainPresentationFactory)
|
||||
{
|
||||
_authorizationService = authorizationService;
|
||||
_domainService = domainService;
|
||||
_umbracoMapper = umbracoMapper;
|
||||
_domainPresentationFactory = domainPresentationFactory;
|
||||
}
|
||||
|
||||
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")]
|
||||
public UpdateDomainsController(IDomainService domainService, IUmbracoMapper umbracoMapper, IDomainPresentationFactory domainPresentationFactory)
|
||||
: this(
|
||||
StaticServiceProvider.Instance.GetRequiredService<IAuthorizationService>(),
|
||||
domainService,
|
||||
umbracoMapper,
|
||||
domainPresentationFactory)
|
||||
{
|
||||
}
|
||||
|
||||
[MapToApiVersion("1.0")]
|
||||
[HttpPut("{id:guid}/domains")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
@@ -56,16 +37,6 @@ public class UpdateDomainsController : DocumentControllerBase
|
||||
Guid id,
|
||||
UpdateDomainsRequestModel updateModel)
|
||||
{
|
||||
AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync(
|
||||
User,
|
||||
ContentPermissionResource.WithKeys(ActionAssignDomain.ActionLetter, id),
|
||||
AuthorizationPolicies.ContentPermissionByResource);
|
||||
|
||||
if (!authorizationResult.Succeeded)
|
||||
{
|
||||
return Forbidden();
|
||||
}
|
||||
|
||||
DomainsUpdateModel domainsUpdateModel = _umbracoMapper.Map<DomainsUpdateModel>(updateModel)!;
|
||||
|
||||
Attempt<DomainUpdateResult, DomainOperationStatus> result = await _domainService.UpdateDomainsAsync(id, domainsUpdateModel);
|
||||
|
||||
+1
-31
@@ -1,63 +1,33 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Document;
|
||||
using Umbraco.Cms.Core.Actions;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Security.Authorization;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Web.Common.Authorization;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Document;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class UpdateNotificationsController : DocumentControllerBase
|
||||
{
|
||||
private readonly IAuthorizationService _authorizationService;
|
||||
private readonly IContentEditingService _contentEditingService;
|
||||
private readonly INotificationService _notificationService;
|
||||
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
|
||||
|
||||
[ActivatorUtilitiesConstructor]
|
||||
public UpdateNotificationsController(IAuthorizationService authorizationService, IContentEditingService contentEditingService, INotificationService notificationService, IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
|
||||
public UpdateNotificationsController(IContentEditingService contentEditingService, INotificationService notificationService, IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
|
||||
{
|
||||
_authorizationService = authorizationService;
|
||||
_contentEditingService = contentEditingService;
|
||||
_notificationService = notificationService;
|
||||
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
|
||||
}
|
||||
|
||||
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")]
|
||||
public UpdateNotificationsController(IContentEditingService contentEditingService, INotificationService notificationService, IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
|
||||
: this(
|
||||
StaticServiceProvider.Instance.GetRequiredService<IAuthorizationService>(),
|
||||
contentEditingService,
|
||||
notificationService,
|
||||
backOfficeSecurityAccessor)
|
||||
{
|
||||
}
|
||||
|
||||
[MapToApiVersion("1.0")]
|
||||
[HttpPut("{id:guid}/notifications")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> UpdateNotifications(CancellationToken cancellationToken, Guid id, UpdateDocumentNotificationsRequestModel updateModel)
|
||||
{
|
||||
AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync(
|
||||
User,
|
||||
ContentPermissionResource.WithKeys(ActionBrowse.ActionLetter, id),
|
||||
AuthorizationPolicies.ContentPermissionByResource);
|
||||
|
||||
if (!authorizationResult.Succeeded)
|
||||
{
|
||||
return Forbidden();
|
||||
}
|
||||
|
||||
IContent? content = await _contentEditingService.GetAsync(id);
|
||||
if (content == null)
|
||||
{
|
||||
|
||||
-14
@@ -21,20 +21,6 @@ public class ValidateCreateDocumentController : CreateDocumentControllerBase
|
||||
private readonly IContentEditingService _contentEditingService;
|
||||
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
|
||||
|
||||
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 17.")]
|
||||
public ValidateCreateDocumentController(
|
||||
IAuthorizationService authorizationService,
|
||||
IDocumentEditingPresentationFactory documentEditingPresentationFactory,
|
||||
IContentEditingService contentEditingService)
|
||||
: this(
|
||||
authorizationService,
|
||||
documentEditingPresentationFactory,
|
||||
contentEditingService,
|
||||
StaticServiceProvider.Instance.GetRequiredService<IBackOfficeSecurityAccessor>())
|
||||
{
|
||||
}
|
||||
|
||||
[ActivatorUtilitiesConstructor]
|
||||
public ValidateCreateDocumentController(
|
||||
IAuthorizationService authorizationService,
|
||||
IDocumentEditingPresentationFactory documentEditingPresentationFactory,
|
||||
|
||||
-14
@@ -21,20 +21,6 @@ public class ValidateUpdateDocumentController : UpdateDocumentControllerBase
|
||||
private readonly IDocumentEditingPresentationFactory _documentEditingPresentationFactory;
|
||||
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
|
||||
|
||||
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 17.")]
|
||||
public ValidateUpdateDocumentController(
|
||||
IAuthorizationService authorizationService,
|
||||
IContentEditingService contentEditingService,
|
||||
IDocumentEditingPresentationFactory documentEditingPresentationFactory)
|
||||
: this(
|
||||
authorizationService,
|
||||
contentEditingService,
|
||||
documentEditingPresentationFactory,
|
||||
StaticServiceProvider.Instance.GetRequiredService<IBackOfficeSecurityAccessor>())
|
||||
{
|
||||
}
|
||||
|
||||
[ActivatorUtilitiesConstructor]
|
||||
public ValidateUpdateDocumentController(
|
||||
IAuthorizationService authorizationService,
|
||||
IContentEditingService contentEditingService,
|
||||
|
||||
-23
@@ -16,35 +16,12 @@ public class SearchDocumentTypeItemController : DocumentTypeItemControllerBase
|
||||
private readonly IUmbracoMapper _mapper;
|
||||
private readonly IContentTypeSearchService _contentTypeSearchService;
|
||||
|
||||
[Obsolete("Please use ctor that only accepts IUmbracoMapper & IContentTypeSearchService, scheduled for removal in v17")]
|
||||
public SearchDocumentTypeItemController(IEntitySearchService entitySearchService, IContentTypeService contentTypeService, IUmbracoMapper mapper)
|
||||
: this(mapper, StaticServiceProvider.Instance.GetRequiredService<IContentTypeSearchService>())
|
||||
{
|
||||
}
|
||||
|
||||
[Obsolete("Please use ctor that only accepts IUmbracoMapper & IContentTypeSearchService, scheduled for removal in v17")]
|
||||
// We need to have this constructor, or else we get ambiguous constructor error
|
||||
public SearchDocumentTypeItemController(
|
||||
IEntitySearchService entitySearchService,
|
||||
IContentTypeService contentTypeService,
|
||||
IUmbracoMapper mapper,
|
||||
IContentTypeSearchService contentTypeSearchService)
|
||||
: this(mapper, contentTypeSearchService)
|
||||
{
|
||||
}
|
||||
|
||||
[ActivatorUtilitiesConstructor]
|
||||
public SearchDocumentTypeItemController(IUmbracoMapper mapper, IContentTypeSearchService contentTypeSearchService)
|
||||
{
|
||||
_mapper = mapper;
|
||||
_contentTypeSearchService = contentTypeSearchService;
|
||||
}
|
||||
|
||||
[NonAction]
|
||||
[Obsolete("Scheduled to be removed in v16, use the non obsoleted method instead")]
|
||||
public async Task<IActionResult> Search(CancellationToken cancellationToken, string query, int skip = 0, int take = 100)
|
||||
=> await SearchDocumentType(cancellationToken, query, null, skip, take);
|
||||
|
||||
[HttpGet("search")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(PagedModel<DocumentTypeItemResponseModel>), StatusCodes.Status200OK)]
|
||||
|
||||
@@ -11,12 +11,13 @@ using Umbraco.Cms.Api.Common.ViewModels.Pagination;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Help;
|
||||
|
||||
[Obsolete("This is no longer used and will be removed in v19")]
|
||||
[ApiVersion("1.0")]
|
||||
public class GetHelpController : HelpControllerBase
|
||||
{
|
||||
private readonly ILogger<GetHelpController> _logger;
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
private HelpPageSettings _helpPageSettings;
|
||||
private readonly HelpPageSettings _helpPageSettings;
|
||||
|
||||
public GetHelpController(
|
||||
IOptionsMonitor<HelpPageSettings> helpPageSettings,
|
||||
@@ -26,11 +27,8 @@ public class GetHelpController : HelpControllerBase
|
||||
_logger = logger;
|
||||
_jsonSerializer = jsonSerializer;
|
||||
_helpPageSettings = helpPageSettings.CurrentValue;
|
||||
helpPageSettings.OnChange(UpdateHelpPageSettings);
|
||||
}
|
||||
|
||||
private void UpdateHelpPageSettings(HelpPageSettings settings) => _helpPageSettings = settings;
|
||||
|
||||
[HttpGet]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
|
||||
@@ -3,6 +3,7 @@ using Umbraco.Cms.Api.Management.Routing;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Help;
|
||||
|
||||
[Obsolete("This is no longer used and will be removed in v19")]
|
||||
[VersionedApiBackOfficeRoute("help")]
|
||||
[ApiExplorerSettings(GroupName = "Help")]
|
||||
public abstract class HelpControllerBase : ManagementApiControllerBase
|
||||
|
||||
@@ -26,9 +26,9 @@ public class RebuildIndexerController : IndexerControllerBase
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rebuilds the index
|
||||
/// Rebuilds the index.
|
||||
/// </summary>
|
||||
/// <param name="indexName"></param>
|
||||
/// <param name="indexName">The name of the index to rebuild.</param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("{indexName}/rebuild")]
|
||||
[MapToApiVersion("1.0")]
|
||||
@@ -36,7 +36,7 @@ public class RebuildIndexerController : IndexerControllerBase
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status409Conflict)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public Task<IActionResult> Rebuild(CancellationToken cancellationToken, string indexName)
|
||||
public async Task<IActionResult> Rebuild(CancellationToken cancellationToken, string indexName)
|
||||
{
|
||||
if (!_examineManager.TryGetIndex(indexName, out IIndex? index))
|
||||
{
|
||||
@@ -48,7 +48,7 @@ public class RebuildIndexerController : IndexerControllerBase
|
||||
Type = "Error",
|
||||
};
|
||||
|
||||
return Task.FromResult<IActionResult>(NotFound(invalidModelProblem));
|
||||
return NotFound(invalidModelProblem);
|
||||
}
|
||||
|
||||
if (!_indexingRebuilderService.CanRebuild(index.Name))
|
||||
@@ -57,19 +57,19 @@ public class RebuildIndexerController : IndexerControllerBase
|
||||
{
|
||||
Title = "Could not validate the populator",
|
||||
Detail =
|
||||
$"The index {index?.Name} could not be rebuilt because we could not validate its associated {typeof(IIndexPopulator)}",
|
||||
$"The index {index.Name} could not be rebuilt because we could not validate its associated {typeof(IIndexPopulator)}",
|
||||
Status = StatusCodes.Status400BadRequest,
|
||||
Type = "Error",
|
||||
};
|
||||
|
||||
return Task.FromResult<IActionResult>(BadRequest(invalidModelProblem));
|
||||
return BadRequest(invalidModelProblem);
|
||||
}
|
||||
|
||||
_logger.LogInformation("Rebuilding index '{IndexName}'", indexName);
|
||||
|
||||
if (_indexingRebuilderService.TryRebuild(index, indexName))
|
||||
if (await _indexingRebuilderService.TryRebuildAsync(index, indexName))
|
||||
{
|
||||
return Task.FromResult<IActionResult>(Ok());
|
||||
return Ok();
|
||||
}
|
||||
|
||||
var problemDetails = new ProblemDetails
|
||||
@@ -80,6 +80,6 @@ public class RebuildIndexerController : IndexerControllerBase
|
||||
Type = "Error",
|
||||
};
|
||||
|
||||
return Task.FromResult<IActionResult>(Conflict(problemDetails));
|
||||
return Conflict(problemDetails);
|
||||
}
|
||||
}
|
||||
|
||||
-6
@@ -26,12 +26,6 @@ public abstract class MediaCollectionControllerBase : ContentCollectionControlle
|
||||
{
|
||||
}
|
||||
|
||||
[Obsolete("Please use the constructor with all parameters. Scheduled to be removed in Umbraco 18")]
|
||||
protected MediaCollectionControllerBase(IUmbracoMapper mapper)
|
||||
: base(mapper)
|
||||
{
|
||||
}
|
||||
|
||||
protected IActionResult CollectionOperationStatusResult(ContentCollectionOperationStatus status)
|
||||
=> ContentCollectionOperationStatusResult(status, "media");
|
||||
}
|
||||
|
||||
+4
-4
@@ -24,15 +24,15 @@ public class SearchMemberItemController : MemberItemControllerBase
|
||||
[HttpGet("search")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(PagedModel<MemberItemResponseModel>), StatusCodes.Status200OK)]
|
||||
public Task<IActionResult> SearchWithAllowedTypes(CancellationToken cancellationToken, string query, int skip = 0, int take = 100, [FromQuery]IEnumerable<Guid>? allowedMemberTypes = null)
|
||||
public async Task<IActionResult> SearchWithAllowedTypes(CancellationToken cancellationToken, string query, int skip = 0, int take = 100, [FromQuery]IEnumerable<Guid>? allowedMemberTypes = null)
|
||||
{
|
||||
PagedModel<IEntitySlim> searchResult = _indexedEntitySearchService.Search(UmbracoObjectTypes.Member, query, null, allowedMemberTypes, skip, take);
|
||||
PagedModel<IEntitySlim> searchResult = await _indexedEntitySearchService.SearchAsync(UmbracoObjectTypes.Member, query, null, allowedMemberTypes, false, "*", skip, take);
|
||||
var result = new PagedModel<MemberItemResponseModel>
|
||||
{
|
||||
Items = searchResult.Items.OfType<IMemberEntitySlim>().Select(_memberPresentationFactory.CreateItemResponseModel),
|
||||
Total = searchResult.Total
|
||||
Total = searchResult.Total,
|
||||
};
|
||||
|
||||
return Task.FromResult<IActionResult>(Ok(result));
|
||||
return await Task.FromResult<IActionResult>(Ok(result));
|
||||
}
|
||||
}
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Folder;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.MemberType.Folder;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class ByKeyMemberTypeFolderController : MemberTypeFolderControllerBase
|
||||
{
|
||||
public ByKeyMemberTypeFolderController(
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
|
||||
IMemberTypeContainerService memberTypeContainerService)
|
||||
: base(backOfficeSecurityAccessor, memberTypeContainerService)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("{id:guid}")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(FolderResponseModel), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> ByKey(CancellationToken cancellationToken, Guid id) => await GetFolderAsync(id);
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Folder;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.MemberType.Folder;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class CreateMemberTypeFolderController : MemberTypeFolderControllerBase
|
||||
{
|
||||
public CreateMemberTypeFolderController(
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
|
||||
IMemberTypeContainerService memberTypeContainerService)
|
||||
: base(backOfficeSecurityAccessor, memberTypeContainerService)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(StatusCodes.Status201Created)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> Create(
|
||||
CancellationToken cancellationToken,
|
||||
CreateFolderRequestModel createFolderRequestModel)
|
||||
=> await CreateFolderAsync<ByKeyMemberTypeFolderController>(
|
||||
createFolderRequestModel,
|
||||
controller => nameof(controller.ByKey)).ConfigureAwait(false);
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.MemberType.Folder;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class DeleteMemberTypeFolderController : MemberTypeFolderControllerBase
|
||||
{
|
||||
public DeleteMemberTypeFolderController(
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
|
||||
IMemberTypeContainerService memberTypeContainerService)
|
||||
: base(backOfficeSecurityAccessor, memberTypeContainerService)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpDelete("{id:guid}")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> Delete(CancellationToken cancellationToken, Guid id) => await DeleteFolderAsync(id);
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.Routing;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Web.Common.Authorization;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.MemberType.Folder;
|
||||
|
||||
[VersionedApiBackOfficeRoute($"{Constants.UdiEntityType.MemberType}/folder")]
|
||||
[ApiExplorerSettings(GroupName = "Member Type")]
|
||||
[Authorize(Policy = AuthorizationPolicies.TreeAccessMemberTypes)]
|
||||
public abstract class MemberTypeFolderControllerBase : FolderManagementControllerBase<IMemberType>
|
||||
{
|
||||
protected MemberTypeFolderControllerBase(
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
|
||||
IMemberTypeContainerService memberTypeContainerService)
|
||||
: base(backOfficeSecurityAccessor, memberTypeContainerService)
|
||||
{
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Folder;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.MemberType.Folder;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class UpdateMemberTypeFolderController : MemberTypeFolderControllerBase
|
||||
{
|
||||
public UpdateMemberTypeFolderController(
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
|
||||
IMemberTypeContainerService memberTypeContainerService)
|
||||
: base(backOfficeSecurityAccessor, memberTypeContainerService)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> Update(
|
||||
CancellationToken cancellationToken,
|
||||
Guid id,
|
||||
UpdateFolderResponseModel updateFolderResponseModel)
|
||||
=> await UpdateFolderAsync(id, updateFolderResponseModel);
|
||||
}
|
||||
+2
-1
@@ -30,6 +30,7 @@ public class RootMemberTypeTreeController : MemberTypeTreeControllerBase
|
||||
public async Task<ActionResult<PagedViewModel<MemberTypeTreeItemResponseModel>>> Root(
|
||||
CancellationToken cancellationToken,
|
||||
int skip = 0,
|
||||
int take = 100)
|
||||
int take = 100,
|
||||
bool foldersOnly = false)
|
||||
=> await GetRoot(skip, take);
|
||||
}
|
||||
|
||||
+3
-2
@@ -1,4 +1,4 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
|
||||
using Umbraco.Cms.Api.Management.Services.Flags;
|
||||
@@ -23,6 +23,7 @@ public class SiblingMemberTypeTreeController : MemberTypeTreeControllerBase
|
||||
CancellationToken cancellationToken,
|
||||
Guid target,
|
||||
int before,
|
||||
int after)
|
||||
int after,
|
||||
bool foldersOnly = false)
|
||||
=> await GetSiblings(target, before, after);
|
||||
}
|
||||
|
||||
+4
-8
@@ -1,21 +1,18 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Infrastructure.ModelsBuilder;
|
||||
using Umbraco.Cms.Infrastructure.ModelsBuilder.Building;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.ModelsBuilder;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class BuildModelsBuilderController : ModelsBuilderControllerBase
|
||||
{
|
||||
private ModelsBuilderSettings _modelsBuilderSettings;
|
||||
private readonly ModelsBuilderSettings _modelsBuilderSettings;
|
||||
private readonly ModelsGenerationError _mbErrors;
|
||||
private readonly IModelsGenerator _modelGenerator;
|
||||
|
||||
@@ -27,8 +24,6 @@ public class BuildModelsBuilderController : ModelsBuilderControllerBase
|
||||
_mbErrors = mbErrors;
|
||||
_modelGenerator = modelGenerator;
|
||||
_modelsBuilderSettings = modelsBuilderSettings.CurrentValue;
|
||||
|
||||
modelsBuilderSettings.OnChange(x => _modelsBuilderSettings = x);
|
||||
}
|
||||
|
||||
[HttpPost("build")]
|
||||
@@ -39,7 +34,8 @@ public class BuildModelsBuilderController : ModelsBuilderControllerBase
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_modelsBuilderSettings.ModelsMode.SupportsExplicitGeneration())
|
||||
if (_modelsBuilderSettings.ModelsMode != Constants.ModelsBuilder.ModelsModes.SourceCodeManual
|
||||
&& _modelsBuilderSettings.ModelsMode != Constants.ModelsBuilder.ModelsModes.SourceCodeAuto)
|
||||
{
|
||||
var problemDetailsModel = new ProblemDetails
|
||||
{
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.Services.NewsDashboard;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.NewsDashboard;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.NewsDashboard;
|
||||
|
||||
public class NewsDashboardController : NewsDashboardControllerBase
|
||||
{
|
||||
private readonly INewsDashboardService _newsDashboardService;
|
||||
|
||||
public NewsDashboardController(INewsDashboardService newsDashboardService) => _newsDashboardService = newsDashboardService;
|
||||
|
||||
[HttpGet]
|
||||
[ProducesResponseType(typeof(NewsDashboardResponseModel), StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> GetDashboard()
|
||||
{
|
||||
NewsDashboardResponseModel content = await _newsDashboardService.GetItemsAsync();
|
||||
|
||||
return Ok(content);
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.Routing;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.NewsDashboard;
|
||||
|
||||
[VersionedApiBackOfficeRoute("news-dashboard")]
|
||||
[ApiExplorerSettings(GroupName = "News Dashboard")]
|
||||
public abstract class NewsDashboardControllerBase : ManagementApiControllerBase
|
||||
{
|
||||
}
|
||||
@@ -7,6 +7,7 @@ using Umbraco.Cms.Core.Services;
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Preview;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
[Obsolete("Do not use this. Preview state is initiated implicitly by the preview URL generation. Scheduled for removal in V18.")]
|
||||
public class EnterPreviewController : PreviewControllerBase
|
||||
{
|
||||
private readonly IPreviewService _previewService;
|
||||
|
||||
@@ -254,7 +254,7 @@ public class BackOfficeController : SecurityControllerBase
|
||||
if (associatedUser is not null)
|
||||
{
|
||||
// log current datetime as last login (this also ensures that the user is not flagged as inactive)
|
||||
associatedUser.LastLoginDateUtc = DateTime.UtcNow;
|
||||
associatedUser.LastLoginDate = DateTime.UtcNow;
|
||||
await _backOfficeUserManager.UpdateAsync(associatedUser);
|
||||
|
||||
return await SignInBackOfficeUser(associatedUser, request);
|
||||
|
||||
@@ -17,12 +17,6 @@ public class BackOfficeDefaultController : Controller
|
||||
public BackOfficeDefaultController(IRuntime umbracoRuntime)
|
||||
=> _umbracoRuntime = umbracoRuntime;
|
||||
|
||||
[Obsolete("Use the non obsoleted constructor instead. Scheduled to be removed in v17")]
|
||||
public BackOfficeDefaultController()
|
||||
: this(StaticServiceProvider.Instance.GetRequiredService<IRuntime>())
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> Index(CancellationToken cancellationToken)
|
||||
|
||||
-35
@@ -43,41 +43,6 @@ public class ExecuteTemplateQueryController : TemplateQueryControllerBase
|
||||
_publishedContentStatusFilteringService = publishedContentStatusFilteringService;
|
||||
}
|
||||
|
||||
[Obsolete("Please use the non-obsolete constructor. Will be removed in V17.")]
|
||||
public ExecuteTemplateQueryController(
|
||||
IPublishedContentQuery publishedContentQuery,
|
||||
IVariationContextAccessor variationContextAccessor,
|
||||
IPublishedValueFallback publishedValueFallback,
|
||||
IContentTypeService contentTypeService,
|
||||
IPublishedContentCache contentCache,
|
||||
IDocumentNavigationQueryService documentNavigationQueryService,
|
||||
IPublishedContentStatusFilteringService publishedContentStatusFilteringService)
|
||||
: this(
|
||||
publishedContentQuery,
|
||||
publishedValueFallback,
|
||||
contentTypeService,
|
||||
documentNavigationQueryService,
|
||||
publishedContentStatusFilteringService)
|
||||
{
|
||||
}
|
||||
|
||||
[Obsolete("Please use the non-obsolete constructor. Will be removed in V17.")]
|
||||
public ExecuteTemplateQueryController(
|
||||
IPublishedContentQuery publishedContentQuery,
|
||||
IVariationContextAccessor variationContextAccessor,
|
||||
IPublishedValueFallback publishedValueFallback,
|
||||
IContentTypeService contentTypeService,
|
||||
IPublishedContentCache contentCache,
|
||||
IDocumentNavigationQueryService documentNavigationQueryService)
|
||||
: this(
|
||||
publishedContentQuery,
|
||||
publishedValueFallback,
|
||||
contentTypeService,
|
||||
documentNavigationQueryService,
|
||||
StaticServiceProvider.Instance.GetRequiredService<IPublishedContentStatusFilteringService>())
|
||||
{
|
||||
}
|
||||
|
||||
[HttpPost("execute")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(TemplateQueryResultResponseModel), StatusCodes.Status200OK)]
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
using Asp.Versioning;
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Api.Management.Controllers.UserGroup;
|
||||
using Umbraco.Cms.Api.Management.Routing;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.User;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Security.Authorization;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
@@ -28,26 +25,11 @@ public class UpdateUserGroupsUserController : UserGroupControllerBase
|
||||
{
|
||||
private readonly IAuthorizationService _authorizationService;
|
||||
private readonly IUserGroupService _userGroupService;
|
||||
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
|
||||
|
||||
[ActivatorUtilitiesConstructor]
|
||||
public UpdateUserGroupsUserController(
|
||||
IAuthorizationService authorizationService,
|
||||
IUserGroupService userGroupService,
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
|
||||
public UpdateUserGroupsUserController(IAuthorizationService authorizationService, IUserGroupService userGroupService)
|
||||
{
|
||||
_authorizationService = authorizationService;
|
||||
_userGroupService = userGroupService;
|
||||
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
|
||||
}
|
||||
|
||||
[Obsolete("Please use the constructor accepting all parameters. Scheduled for removal in Umbraco 19.")]
|
||||
public UpdateUserGroupsUserController(IAuthorizationService authorizationService, IUserGroupService userGroupService)
|
||||
: this(
|
||||
authorizationService,
|
||||
userGroupService,
|
||||
StaticServiceProvider.Instance.GetRequiredService<IBackOfficeSecurityAccessor>())
|
||||
{
|
||||
}
|
||||
|
||||
[HttpPost("set-user-groups")]
|
||||
@@ -69,8 +51,7 @@ public class UpdateUserGroupsUserController : UserGroupControllerBase
|
||||
|
||||
Attempt<UserGroupOperationStatus> result = await _userGroupService.UpdateUserGroupsOnUsersAsync(
|
||||
requestModel.UserGroupIds.Select(x => x.Id).ToHashSet(),
|
||||
requestModel.UserIds.Select(x => x.Id).ToHashSet(),
|
||||
CurrentUserKey(_backOfficeSecurityAccessor));
|
||||
requestModel.UserIds.Select(x => x.Id).ToHashSet());
|
||||
|
||||
return result.Success
|
||||
? Ok()
|
||||
|
||||
@@ -11,13 +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>();
|
||||
builder.AddNotificationAsyncHandler<UserLoginSuccessNotification, BackOfficeUserManagerAuditer>();
|
||||
builder.AddNotificationAsyncHandler<UserLogoutSuccessNotification, BackOfficeUserManagerAuditer>();
|
||||
builder.AddNotificationAsyncHandler<UserLoginFailedNotification, BackOfficeUserManagerAuditer>();
|
||||
builder.AddNotificationAsyncHandler<UserForgotPasswordRequestedNotification, BackOfficeUserManagerAuditer>();
|
||||
builder.AddNotificationAsyncHandler<UserForgotPasswordChangedNotification, BackOfficeUserManagerAuditer>();
|
||||
builder.AddNotificationAsyncHandler<UserPasswordChangedNotification, BackOfficeUserManagerAuditer>();
|
||||
builder.AddNotificationAsyncHandler<UserPasswordResetNotification, BackOfficeUserManagerAuditer>();
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Api.Management.Services.NewsDashboard;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.DependencyInjection;
|
||||
|
||||
internal static class NewsDashboardBuilderExtensions
|
||||
{
|
||||
internal static IUmbracoBuilder AddNewsDashboard(this IUmbracoBuilder builder)
|
||||
{
|
||||
builder.Services.AddSingleton<INewsDashboardService, NewsDashboardService>();
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,7 @@ public static class InstallerBuilderExtensions
|
||||
|
||||
builder.AddInstallSteps();
|
||||
services.AddTransient<IInstallService, InstallService>();
|
||||
builder.AddNotificationAsyncHandler<UnattendedInstallNotification, CreateUnattendedUserNotificationHandler>();
|
||||
builder.AddNotificationAsyncHandler<UnattendedInstallNotification, PostUnattendedInstallNotificationHandler>();
|
||||
builder.WithCollectionBuilder<MapDefinitionCollectionBuilder>().Add<InstallerViewModelsMapDefinition>();
|
||||
|
||||
return builder;
|
||||
|
||||
@@ -30,7 +30,7 @@ public static partial class UmbracoBuilderExtensions
|
||||
.AddMembersIdentity()
|
||||
.AddUmbracoProfiler()
|
||||
.AddMvcAndRazor(configureMvc)
|
||||
.AddRecurringBackgroundJobs()
|
||||
.AddBackgroundJobs()
|
||||
.AddUmbracoHybridCache()
|
||||
.AddDistributedCache()
|
||||
.AddCoreNotifications();
|
||||
|
||||
@@ -10,18 +10,26 @@ namespace Umbraco.Extensions
|
||||
{
|
||||
internal static void AddCollectionBuilders(this IUmbracoBuilder builder)
|
||||
{
|
||||
builder.SignProviders()
|
||||
builder.FlagProviders()
|
||||
.Append<HasScheduleFlagProvider>()
|
||||
.Append<IsProtectedFlagProvider>()
|
||||
.Append<HasPendingChangesFlagProvider>()
|
||||
.Append<HasCollectionFlagProvider>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the flag providers collection builder.
|
||||
/// </summary>
|
||||
/// <param name="builder">The builder.</param>
|
||||
public static FlagProviderCollectionBuilder FlagProviders(this IUmbracoBuilder builder)
|
||||
=> builder.WithCollectionBuilder<FlagProviderCollectionBuilder>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sign providers collection builder.
|
||||
/// </summary>
|
||||
/// <param name="builder">The builder.</param>
|
||||
[Obsolete("Please use the correctly named FlagProviders. Scheduled for removal in Umbraco 19.")]
|
||||
public static FlagProviderCollectionBuilder SignProviders(this IUmbracoBuilder builder)
|
||||
=> builder.WithCollectionBuilder<FlagProviderCollectionBuilder>();
|
||||
=> builder.FlagProviders();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,8 @@ public static partial class UmbracoBuilderExtensions
|
||||
.AddUserData()
|
||||
.AddSegment()
|
||||
.AddExport()
|
||||
.AddImport();
|
||||
.AddImport()
|
||||
.AddNewsDashboard();
|
||||
|
||||
services
|
||||
.ConfigureOptions<ConfigureApiBehaviorOptions>()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Api.Common.Accessors;
|
||||
using Umbraco.Cms.Api.Common.Rendering;
|
||||
using Umbraco.Cms.Api.Management.Factories;
|
||||
@@ -16,10 +16,9 @@ internal static class WebhooksBuilderExtensions
|
||||
builder.Services.AddUnique<IWebhookPresentationFactory, WebhookPresentationFactory>();
|
||||
builder.AddMapDefinition<WebhookEventMapDefinition>();
|
||||
|
||||
// We have to use TryAdd here, as if they are registered by the delivery API, we don't want to register them
|
||||
// Delivery API will also overwrite these IF it is enabled.
|
||||
builder.Services.TryAddScoped<IOutputExpansionStrategy, ElementOnlyOutputExpansionStrategy>();
|
||||
builder.Services.TryAddSingleton<IOutputExpansionStrategyAccessor, RequestContextOutputExpansionStrategyAccessor>();
|
||||
// deliveryApi will overwrite these more basic ones.
|
||||
builder.Services.AddScoped<IOutputExpansionStrategy, ElementOnlyOutputExpansionStrategy>();
|
||||
builder.Services.AddSingleton<IOutputExpansionStrategyAccessor, RequestContextOutputExpansionStrategyAccessor>();
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
@@ -14,15 +14,6 @@ public class DocumentCollectionPresentationFactory : ContentCollectionPresentati
|
||||
private readonly IPublicAccessService _publicAccessService;
|
||||
private readonly IEntityService _entityService;
|
||||
|
||||
[Obsolete("Please use the non-obsolete constructor. Scheduled for removal in V17.")]
|
||||
public DocumentCollectionPresentationFactory(IUmbracoMapper mapper, IPublicAccessService publicAccessService)
|
||||
: this(
|
||||
mapper,
|
||||
publicAccessService,
|
||||
StaticServiceProvider.Instance.GetRequiredService<IEntityService>())
|
||||
{
|
||||
}
|
||||
|
||||
[ActivatorUtilitiesConstructor]
|
||||
public DocumentCollectionPresentationFactory(IUmbracoMapper mapper, IPublicAccessService publicAccessService, IEntityService entityService)
|
||||
: base(mapper)
|
||||
|
||||
@@ -65,22 +65,6 @@ internal sealed class DocumentPresentationFactory : IDocumentPresentationFactory
|
||||
_flagProviderCollection = flagProviderCollection;
|
||||
}
|
||||
|
||||
[Obsolete("Schedule for removal in v17")]
|
||||
public async Task<DocumentResponseModel> CreateResponseModelAsync(IContent content)
|
||||
{
|
||||
DocumentResponseModel responseModel = _umbracoMapper.Map<DocumentResponseModel>(content)!;
|
||||
|
||||
Guid? templateKey = content.TemplateId.HasValue
|
||||
? _templateService.GetAsync(content.TemplateId.Value).Result?.Key
|
||||
: null;
|
||||
|
||||
responseModel.Template = templateKey.HasValue
|
||||
? new ReferenceByIdModel { Id = templateKey.Value }
|
||||
: null;
|
||||
|
||||
return responseModel;
|
||||
}
|
||||
|
||||
public async Task<PublishedDocumentResponseModel> CreatePublishedResponseModelAsync(IContent content)
|
||||
{
|
||||
PublishedDocumentResponseModel responseModel = _umbracoMapper.Map<PublishedDocumentResponseModel>(content)!;
|
||||
@@ -181,68 +165,6 @@ internal sealed class DocumentPresentationFactory : IDocumentPresentationFactory
|
||||
public DocumentTypeReferenceResponseModel CreateDocumentTypeReferenceResponseModel(IDocumentEntitySlim entity)
|
||||
=> _umbracoMapper.Map<DocumentTypeReferenceResponseModel>(entity)!;
|
||||
|
||||
[Obsolete("Use CreateCulturePublishScheduleModels instead. Scheduled for removal in v17")]
|
||||
public Attempt<CultureAndScheduleModel, ContentPublishingOperationStatus> CreateCultureAndScheduleModel(PublishDocumentRequestModel requestModel)
|
||||
{
|
||||
var contentScheduleCollection = new ContentScheduleCollection();
|
||||
var culturesToPublishImmediately = new HashSet<string>();
|
||||
foreach (CultureAndScheduleRequestModel cultureAndScheduleRequestModel in requestModel.PublishSchedules)
|
||||
{
|
||||
if (cultureAndScheduleRequestModel.Schedule is null || (cultureAndScheduleRequestModel.Schedule.PublishTime is null && cultureAndScheduleRequestModel.Schedule.UnpublishTime is null))
|
||||
{
|
||||
culturesToPublishImmediately.Add(cultureAndScheduleRequestModel.Culture ?? Constants.System.InvariantCulture); // API have `null` for invariant, but service layer has "*".
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cultureAndScheduleRequestModel.Schedule.PublishTime is not null)
|
||||
{
|
||||
if (cultureAndScheduleRequestModel.Schedule.PublishTime <= _timeProvider.GetUtcNow())
|
||||
{
|
||||
return Attempt.FailWithStatus(ContentPublishingOperationStatus.PublishTimeNeedsToBeInFuture, new CultureAndScheduleModel()
|
||||
{
|
||||
Schedules = contentScheduleCollection,
|
||||
CulturesToPublishImmediately = culturesToPublishImmediately,
|
||||
});
|
||||
}
|
||||
|
||||
contentScheduleCollection.Add(new ContentSchedule(
|
||||
cultureAndScheduleRequestModel.Culture ?? Constants.System.InvariantCulture,
|
||||
cultureAndScheduleRequestModel.Schedule.PublishTime.Value.UtcDateTime,
|
||||
ContentScheduleAction.Release));
|
||||
}
|
||||
if (cultureAndScheduleRequestModel.Schedule.UnpublishTime is not null)
|
||||
{
|
||||
if (cultureAndScheduleRequestModel.Schedule.UnpublishTime <= cultureAndScheduleRequestModel.Schedule.PublishTime)
|
||||
{
|
||||
return Attempt.FailWithStatus(ContentPublishingOperationStatus.UnpublishTimeNeedsToBeAfterPublishTime, new CultureAndScheduleModel()
|
||||
{
|
||||
Schedules = contentScheduleCollection,
|
||||
CulturesToPublishImmediately = culturesToPublishImmediately,
|
||||
});
|
||||
}
|
||||
|
||||
if (cultureAndScheduleRequestModel.Schedule.UnpublishTime <= _timeProvider.GetUtcNow())
|
||||
{
|
||||
return Attempt.FailWithStatus(ContentPublishingOperationStatus.UpublishTimeNeedsToBeInFuture, new CultureAndScheduleModel()
|
||||
{
|
||||
Schedules = contentScheduleCollection,
|
||||
CulturesToPublishImmediately = culturesToPublishImmediately,
|
||||
});
|
||||
}
|
||||
|
||||
contentScheduleCollection.Add(new ContentSchedule(
|
||||
cultureAndScheduleRequestModel.Culture ?? Constants.System.InvariantCulture,
|
||||
cultureAndScheduleRequestModel.Schedule.UnpublishTime.Value.UtcDateTime,
|
||||
ContentScheduleAction.Expire));
|
||||
}
|
||||
}
|
||||
return Attempt.SucceedWithStatus(ContentPublishingOperationStatus.Success, new CultureAndScheduleModel()
|
||||
{
|
||||
Schedules = contentScheduleCollection,
|
||||
CulturesToPublishImmediately = culturesToPublishImmediately,
|
||||
});
|
||||
}
|
||||
|
||||
public Attempt<List<CulturePublishScheduleModel>, ContentPublishingOperationStatus> CreateCulturePublishScheduleModels(PublishDocumentRequestModel requestModel)
|
||||
{
|
||||
var model = new List<CulturePublishScheduleModel>();
|
||||
|
||||
@@ -1,24 +1,42 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Umbraco.Cms.Api.Management.Routing;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Document;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Membership;
|
||||
using Umbraco.Cms.Core.Routing;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Factories;
|
||||
|
||||
public class DocumentUrlFactory : IDocumentUrlFactory
|
||||
{
|
||||
private readonly IPublishedUrlInfoProvider _publishedUrlInfoProvider;
|
||||
private readonly UrlProviderCollection _urlProviders;
|
||||
private readonly IPreviewService _previewService;
|
||||
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
|
||||
private readonly ILogger<DocumentUrlFactory> _logger;
|
||||
|
||||
|
||||
public DocumentUrlFactory(IPublishedUrlInfoProvider publishedUrlInfoProvider)
|
||||
=> _publishedUrlInfoProvider = publishedUrlInfoProvider;
|
||||
public DocumentUrlFactory(
|
||||
IPublishedUrlInfoProvider publishedUrlInfoProvider,
|
||||
UrlProviderCollection urlProviders,
|
||||
IPreviewService previewService,
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
|
||||
ILogger<DocumentUrlFactory> logger)
|
||||
{
|
||||
_publishedUrlInfoProvider = publishedUrlInfoProvider;
|
||||
_urlProviders = urlProviders;
|
||||
_previewService = previewService;
|
||||
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<DocumentUrlInfo>> CreateUrlsAsync(IContent content)
|
||||
{
|
||||
ISet<UrlInfo> urlInfos = await _publishedUrlInfoProvider.GetAllAsync(content);
|
||||
|
||||
return urlInfos
|
||||
.Where(urlInfo => urlInfo.IsUrl)
|
||||
.Select(urlInfo => new DocumentUrlInfo { Culture = urlInfo.Culture, Url = urlInfo.Text })
|
||||
.Select(CreateDocumentUrlInfo)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
@@ -34,4 +52,52 @@ public class DocumentUrlFactory : IDocumentUrlFactory
|
||||
|
||||
return documentUrlInfoResourceSets;
|
||||
}
|
||||
|
||||
public async Task<DocumentUrlInfo?> GetPreviewUrlAsync(IContent content, string providerAlias, string? culture, string? segment)
|
||||
{
|
||||
IUrlProvider? provider = _urlProviders.FirstOrDefault(provider => provider.Alias.InvariantEquals(providerAlias));
|
||||
if (provider is null)
|
||||
{
|
||||
_logger.LogError("Could not resolve a URL provider requested for preview - it was not registered in the URL providers collection.");
|
||||
return null;
|
||||
}
|
||||
|
||||
UrlInfo? previewUrlInfo = await provider.GetPreviewUrlAsync(content, culture, segment);
|
||||
if (previewUrlInfo is null)
|
||||
{
|
||||
_logger.LogError("The URL provider could not generate a preview URL for content with key: {contentKey}", content.Key);
|
||||
return null;
|
||||
}
|
||||
|
||||
// must initiate preview state for internal preview URLs
|
||||
if (previewUrlInfo.Url is not null && previewUrlInfo.IsExternal is false)
|
||||
{
|
||||
IUser? currentUser = _backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser;
|
||||
if (currentUser is null)
|
||||
{
|
||||
_logger.LogError("Could not access the current backoffice user while attempting to authenticate for preview.");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (await _previewService.TryEnterPreviewAsync(currentUser) is false)
|
||||
{
|
||||
_logger.LogError("A server error occured, could not initiate an authenticated preview state for the current user.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return CreateDocumentUrlInfo(previewUrlInfo);
|
||||
}
|
||||
|
||||
private DocumentUrlInfo CreateDocumentUrlInfo(UrlInfo urlInfo)
|
||||
{
|
||||
var url = urlInfo.Url?.ToString();
|
||||
return new DocumentUrlInfo
|
||||
{
|
||||
Culture = urlInfo.Culture,
|
||||
Url = url,
|
||||
Message = urlInfo.Message,
|
||||
Provider = urlInfo.Provider,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,14 +41,6 @@ public class HealthCheckGroupPresentationFactory : IHealthCheckGroupPresentation
|
||||
return groups;
|
||||
}
|
||||
|
||||
[Obsolete("Use CreateHealthCheckGroupWithResultViewModelAsync instead. Will be removed in v17.")]
|
||||
public HealthCheckGroupWithResultResponseModel CreateHealthCheckGroupWithResultViewModel(IGrouping<string?, HealthCheck> healthCheckGroup)
|
||||
=> CreateHealthCheckGroupWithResultViewModelAsync(healthCheckGroup).GetAwaiter().GetResult();
|
||||
|
||||
[Obsolete("Use CreateHealthCheckGroupWithResultViewModelAsync instead. Will be removed in v17.")]
|
||||
public HealthCheckWithResultPresentationModel CreateHealthCheckWithResultViewModel(HealthCheck healthCheck) =>
|
||||
CreateHealthCheckWithResultViewModelAsync(healthCheck).GetAwaiter().GetResult();
|
||||
|
||||
public async Task<HealthCheckGroupWithResultResponseModel> CreateHealthCheckGroupWithResultViewModelAsync(IGrouping<string?, HealthCheck> healthCheckGroup)
|
||||
{
|
||||
var healthChecks = new List<HealthCheckWithResultPresentationModel>();
|
||||
|
||||
@@ -14,17 +14,9 @@ namespace Umbraco.Cms.Api.Management.Factories;
|
||||
|
||||
public interface IDocumentPresentationFactory
|
||||
{
|
||||
[Obsolete("Schedule for removal in v17")]
|
||||
Task<DocumentResponseModel> CreateResponseModelAsync(IContent content);
|
||||
|
||||
Task<PublishedDocumentResponseModel> CreatePublishedResponseModelAsync(IContent content);
|
||||
|
||||
Task<DocumentResponseModel> CreateResponseModelAsync(IContent content, ContentScheduleCollection schedule)
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
// Remove when obsolete CreateResponseModelAsync is removed
|
||||
=> CreateResponseModelAsync(content);
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
|
||||
Task<DocumentResponseModel> CreateResponseModelAsync(IContent content, ContentScheduleCollection schedule);
|
||||
DocumentItemResponseModel CreateItemResponseModel(IDocumentEntitySlim entity);
|
||||
|
||||
DocumentBlueprintItemResponseModel CreateBlueprintItemResponseModel(IDocumentEntitySlim entity);
|
||||
@@ -33,9 +25,6 @@ public interface IDocumentPresentationFactory
|
||||
|
||||
DocumentTypeReferenceResponseModel CreateDocumentTypeReferenceResponseModel(IDocumentEntitySlim entity);
|
||||
|
||||
[Obsolete("Use CreateCulturePublishScheduleModels instead. Scheduled for removal in v17")]
|
||||
Attempt<CultureAndScheduleModel, ContentPublishingOperationStatus> CreateCultureAndScheduleModel(PublishDocumentRequestModel requestModel);
|
||||
|
||||
Attempt<List<CulturePublishScheduleModel>, ContentPublishingOperationStatus> CreateCulturePublishScheduleModels(
|
||||
PublishDocumentRequestModel requestModel)
|
||||
{
|
||||
|
||||
@@ -8,4 +8,6 @@ public interface IDocumentUrlFactory
|
||||
Task<IEnumerable<DocumentUrlInfo>> CreateUrlsAsync(IContent content);
|
||||
|
||||
Task<IEnumerable<DocumentUrlInfoResponseModel>> CreateUrlSetsAsync(IEnumerable<IContent> contentItems);
|
||||
|
||||
Task<DocumentUrlInfo?> GetPreviewUrlAsync(IContent content, string providerAlias, string? culture, string? segment);
|
||||
}
|
||||
|
||||
@@ -7,19 +7,8 @@ public interface IHealthCheckGroupPresentationFactory
|
||||
{
|
||||
IEnumerable<IGrouping<string?, HealthCheck>> CreateGroupingFromHealthCheckCollection();
|
||||
|
||||
[Obsolete("Use CreateHealthCheckGroupWithResultViewModelAsync instead. Will be removed in v17.")]
|
||||
HealthCheckGroupWithResultResponseModel CreateHealthCheckGroupWithResultViewModel(IGrouping<string?, HealthCheck> healthCheckGroup);
|
||||
Task<HealthCheckGroupWithResultResponseModel> CreateHealthCheckGroupWithResultViewModelAsync(
|
||||
IGrouping<string?, HealthCheck> healthCheckGroup);
|
||||
|
||||
[Obsolete("Use CreateHealthCheckGroupWithResultViewModelAsync instead. Will be removed in v17.")]
|
||||
HealthCheckWithResultPresentationModel CreateHealthCheckWithResultViewModel(HealthCheck healthCheck);
|
||||
|
||||
Task<HealthCheckGroupWithResultResponseModel> CreateHealthCheckGroupWithResultViewModelAsync(IGrouping<string?, HealthCheck> healthCheckGroup)
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
=> Task.FromResult(CreateHealthCheckGroupWithResultViewModel(healthCheckGroup));
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
|
||||
Task<HealthCheckWithResultPresentationModel> CreateHealthCheckWithResultViewModelAsync(HealthCheck healthCheck)
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
=> Task.FromResult(CreateHealthCheckWithResultViewModel(healthCheck));
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
Task<HealthCheckWithResultPresentationModel> CreateHealthCheckWithResultViewModelAsync(HealthCheck healthCheck);
|
||||
}
|
||||
|
||||
@@ -5,5 +5,8 @@ namespace Umbraco.Cms.Api.Management.Factories;
|
||||
|
||||
public interface IIndexPresentationFactory
|
||||
{
|
||||
[Obsolete("Use CreateAsync() instead. Scheduled for removal in v19.")]
|
||||
IndexResponseModel Create(IIndex index);
|
||||
|
||||
Task<IndexResponseModel> CreateAsync(IIndex index) => Task.FromResult(Create(index));
|
||||
}
|
||||
|
||||
@@ -28,21 +28,17 @@ public class IndexPresentationFactory : IIndexPresentationFactory
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
[Obsolete("Use the non obsolete method instead. Scheduled for removal in v17")]
|
||||
public IndexPresentationFactory(IIndexDiagnosticsFactory indexDiagnosticsFactory, IIndexRebuilder indexRebuilder, IIndexingRebuilderService indexingRebuilderService)
|
||||
:this(
|
||||
indexDiagnosticsFactory,
|
||||
indexRebuilder,
|
||||
indexingRebuilderService,
|
||||
StaticServiceProvider.Instance.GetRequiredService<ILogger<IndexPresentationFactory>>())
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[Obsolete("Use CreateAsync() instead. Scheduled for removal in v19.")]
|
||||
public IndexResponseModel Create(IIndex index)
|
||||
=> CreateAsync(index).GetAwaiter().GetResult();
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<IndexResponseModel> CreateAsync(IIndex index)
|
||||
{
|
||||
var isCorrupt = !TryGetSearcherName(index, out var searcherName);
|
||||
|
||||
if (_indexingRebuilderService.IsRebuilding(index.Name))
|
||||
if (await _indexingRebuilderService.IsRebuildingAsync(index.Name))
|
||||
{
|
||||
return new IndexResponseModel
|
||||
{
|
||||
@@ -63,7 +59,7 @@ public class IndexPresentationFactory : IIndexPresentationFactory
|
||||
|
||||
var properties = new Dictionary<string, object?>();
|
||||
|
||||
foreach (var property in indexDiag.Metadata)
|
||||
foreach (KeyValuePair<string, object?> property in indexDiag.Metadata)
|
||||
{
|
||||
if (property.Value is null)
|
||||
{
|
||||
@@ -71,7 +67,7 @@ public class IndexPresentationFactory : IIndexPresentationFactory
|
||||
}
|
||||
else
|
||||
{
|
||||
var propertyType = property.Value.GetType();
|
||||
Type propertyType = property.Value.GetType();
|
||||
properties[property.Key] = propertyType.IsClass && !propertyType.IsArray ? property.Value?.ToString() : property.Value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +1,29 @@
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.ModelsBuilderDashboard;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Configuration;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Infrastructure.ModelsBuilder;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.ModelsBuilderDashboard;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Factories;
|
||||
|
||||
public class ModelsBuilderPresentationFactory : IModelsBuilderPresentationFactory
|
||||
{
|
||||
private ModelsBuilderSettings _modelsBuilderSettings;
|
||||
private readonly ModelsGenerationError _mbErrors;
|
||||
private readonly OutOfDateModelsStatus _outOfDateModels;
|
||||
private readonly ModelsBuilderSettings _modelsBuilderSettings;
|
||||
|
||||
public ModelsBuilderPresentationFactory(IOptionsMonitor<ModelsBuilderSettings> modelsBuilderSettings, ModelsGenerationError mbErrors, OutOfDateModelsStatus outOfDateModels)
|
||||
{
|
||||
_mbErrors = mbErrors;
|
||||
_outOfDateModels = outOfDateModels;
|
||||
_modelsBuilderSettings = modelsBuilderSettings.CurrentValue;
|
||||
|
||||
modelsBuilderSettings.OnChange(x => _modelsBuilderSettings = x);
|
||||
}
|
||||
|
||||
|
||||
public ModelsBuilderResponseModel Create() =>
|
||||
new()
|
||||
{
|
||||
Mode = _modelsBuilderSettings.ModelsMode,
|
||||
CanGenerate = _modelsBuilderSettings.ModelsMode.SupportsExplicitGeneration(),
|
||||
CanGenerate = _modelsBuilderSettings.ModelsMode is Constants.ModelsBuilder.ModelsModes.SourceCodeManual or Constants.ModelsBuilder.ModelsModes.SourceCodeAuto,
|
||||
OutOfDateModels = _outOfDateModels.IsOutOfDate,
|
||||
LastError = _mbErrors.GetLastError(),
|
||||
Version = ApiVersion.Current.Version.ToString(),
|
||||
|
||||
@@ -18,16 +18,6 @@ public class RelationTypePresentationFactory : IRelationTypePresentationFactory
|
||||
private readonly IDocumentPresentationFactory _documentPresentationFactory;
|
||||
private readonly IScopeProvider _scopeProvider;
|
||||
|
||||
[Obsolete("Please use the non obsoleted constructor. Scheduled for removal in v17")]
|
||||
public RelationTypePresentationFactory(IUmbracoMapper umbracoMapper)
|
||||
: this(
|
||||
umbracoMapper,
|
||||
StaticServiceProvider.Instance.GetRequiredService<IEntityRepository>(),
|
||||
StaticServiceProvider.Instance.GetRequiredService<IDocumentPresentationFactory>(),
|
||||
StaticServiceProvider.Instance.GetRequiredService<IScopeProvider>())
|
||||
{
|
||||
}
|
||||
|
||||
public RelationTypePresentationFactory(
|
||||
IUmbracoMapper umbracoMapper,
|
||||
IEntityRepository entityRepository,
|
||||
|
||||
@@ -43,39 +43,6 @@ public class UserPresentationFactory : IUserPresentationFactory
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UserPresentationFactory"/> class.
|
||||
/// </summary>
|
||||
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 17.")]
|
||||
public UserPresentationFactory(
|
||||
IEntityService entityService,
|
||||
AppCaches appCaches,
|
||||
MediaFileManager mediaFileManager,
|
||||
IImageUrlGenerator imageUrlGenerator,
|
||||
IUserGroupPresentationFactory userGroupPresentationFactory,
|
||||
IAbsoluteUrlBuilder absoluteUrlBuilder,
|
||||
IEmailSender emailSender,
|
||||
IPasswordConfigurationPresentationFactory passwordConfigurationPresentationFactory,
|
||||
IOptionsSnapshot<SecuritySettings> securitySettings,
|
||||
IBackOfficeExternalLoginProviders externalLoginProviders)
|
||||
: this(
|
||||
entityService,
|
||||
appCaches,
|
||||
mediaFileManager,
|
||||
imageUrlGenerator,
|
||||
userGroupPresentationFactory,
|
||||
absoluteUrlBuilder,
|
||||
emailSender,
|
||||
passwordConfigurationPresentationFactory,
|
||||
securitySettings,
|
||||
externalLoginProviders,
|
||||
StaticServiceProvider.Instance.GetRequiredService<IUserService>(),
|
||||
StaticServiceProvider.Instance.GetRequiredService<IContentService>(),
|
||||
StaticServiceProvider.Instance.GetRequiredService<IEnumerable<IPermissionPresentationMapper>>())
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UserPresentationFactory"/> class.
|
||||
/// </summary>
|
||||
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 17.")]
|
||||
public UserPresentationFactory(
|
||||
IEntityService entityService,
|
||||
AppCaches appCaches,
|
||||
@@ -87,45 +54,6 @@ public class UserPresentationFactory : IUserPresentationFactory
|
||||
IPasswordConfigurationPresentationFactory passwordConfigurationPresentationFactory,
|
||||
IOptionsSnapshot<SecuritySettings> securitySettings,
|
||||
IBackOfficeExternalLoginProviders externalLoginProviders,
|
||||
IUserService userService,
|
||||
IContentService contentService)
|
||||
: this(
|
||||
entityService,
|
||||
appCaches,
|
||||
mediaFileManager,
|
||||
imageUrlGenerator,
|
||||
userGroupPresentationFactory,
|
||||
absoluteUrlBuilder,
|
||||
emailSender,
|
||||
passwordConfigurationPresentationFactory,
|
||||
securitySettings,
|
||||
externalLoginProviders,
|
||||
userService,
|
||||
contentService,
|
||||
StaticServiceProvider.Instance.GetRequiredService<IEnumerable<IPermissionPresentationMapper>>())
|
||||
{
|
||||
}
|
||||
|
||||
// TODO (V17): Remove the unused userService and contentService parameters from this constructor.
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UserPresentationFactory"/> class.
|
||||
/// </summary>
|
||||
public UserPresentationFactory(
|
||||
IEntityService entityService,
|
||||
AppCaches appCaches,
|
||||
MediaFileManager mediaFileManager,
|
||||
IImageUrlGenerator imageUrlGenerator,
|
||||
IUserGroupPresentationFactory userGroupPresentationFactory,
|
||||
IAbsoluteUrlBuilder absoluteUrlBuilder,
|
||||
IEmailSender emailSender,
|
||||
IPasswordConfigurationPresentationFactory passwordConfigurationPresentationFactory,
|
||||
IOptionsSnapshot<SecuritySettings> securitySettings,
|
||||
IBackOfficeExternalLoginProviders externalLoginProviders,
|
||||
#pragma warning disable IDE0060 // Remove unused parameter - need to keep these until the next major to avoid breaking changes and/or ambiguous constructor errors
|
||||
IUserService userService,
|
||||
IContentService contentService,
|
||||
#pragma warning restore IDE0060 // Remove unused parameter
|
||||
IEnumerable<IPermissionPresentationMapper> permissionPresentationMappers)
|
||||
{
|
||||
_entityService = entityService;
|
||||
|
||||
+10
-4
@@ -12,27 +12,31 @@ using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Install;
|
||||
|
||||
public class CreateUnattendedUserNotificationHandler : INotificationAsyncHandler<UnattendedInstallNotification>
|
||||
public class PostUnattendedInstallNotificationHandler : INotificationAsyncHandler<UnattendedInstallNotification>
|
||||
{
|
||||
private readonly IServiceScopeFactory _serviceScopeFactory;
|
||||
private readonly IOptions<UnattendedSettings> _unattendedSettings;
|
||||
private readonly IUserService _userService;
|
||||
private readonly IMetricsConsentService _metricsConsentService;
|
||||
|
||||
public CreateUnattendedUserNotificationHandler(IOptions<UnattendedSettings> unattendedSettings, IUserService userService, IServiceScopeFactory serviceScopeFactory)
|
||||
public PostUnattendedInstallNotificationHandler(IOptions<UnattendedSettings> unattendedSettings, IUserService userService, IServiceScopeFactory serviceScopeFactory, IMetricsConsentService metricsConsentService)
|
||||
{
|
||||
_unattendedSettings = unattendedSettings;
|
||||
_userService = userService;
|
||||
_serviceScopeFactory = serviceScopeFactory;
|
||||
_metricsConsentService = metricsConsentService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Listening for when the UnattendedInstallNotification fired after a sucessfulk
|
||||
/// Listening for when the UnattendedInstallNotification fired after a successful unattended install
|
||||
/// This creates the user and sets the telemetry level based on the 'Unattended' settings.
|
||||
/// </summary>
|
||||
/// <param name="notification"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
public async Task HandleAsync(UnattendedInstallNotification notification, CancellationToken cancellationToken)
|
||||
{
|
||||
UnattendedSettings? unattendedSettings = _unattendedSettings.Value;
|
||||
|
||||
// Ensure we have the setting enabled (Sanity check)
|
||||
// In theory this should always be true as the event only fired when a sucessfull
|
||||
if (_unattendedSettings.Value.InstallUnattended == false)
|
||||
@@ -83,7 +87,7 @@ public class CreateUnattendedUserNotificationHandler : INotificationAsyncHandler
|
||||
$"No user found in membership provider with id of {Constants.Security.SuperUserIdAsString}.");
|
||||
}
|
||||
|
||||
//To change the password here we actually need to reset it since we don't have an old one to use to change
|
||||
// To change the password here we actually need to reset it since we don't have an old one to use to change
|
||||
var resetToken = await backOfficeUserManager.GeneratePasswordResetTokenAsync(membershipUser);
|
||||
if (string.IsNullOrWhiteSpace(resetToken))
|
||||
{
|
||||
@@ -98,5 +102,7 @@ public class CreateUnattendedUserNotificationHandler : INotificationAsyncHandler
|
||||
throw new InvalidOperationException("Could not reset password: " +
|
||||
string.Join(", ", resetResult.Errors.ToErrorMessage()));
|
||||
}
|
||||
|
||||
await _metricsConsentService.SetConsentLevelAsync(unattendedSettings.UnattendedTelemetryLevel);
|
||||
}
|
||||
}
|
||||
@@ -22,17 +22,6 @@ public class DocumentPermissionMapper : IPermissionPresentationMapper, IPermissi
|
||||
private readonly Lazy<IEntityService> _entityService;
|
||||
private readonly Lazy<IUserService> _userService;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DocumentPermissionMapper"/> class.
|
||||
/// </summary>
|
||||
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 17.")]
|
||||
public DocumentPermissionMapper()
|
||||
: this(
|
||||
StaticServiceProvider.Instance.GetRequiredService<Lazy<IEntityService>>(),
|
||||
StaticServiceProvider.Instance.GetRequiredService<Lazy<IUserService>>())
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DocumentPermissionMapper"/> class.
|
||||
/// </summary>
|
||||
|
||||
-12
@@ -21,18 +21,6 @@ public class BackOfficeAuthorizationInitializationMiddleware : IMiddleware
|
||||
private readonly IRuntimeState _runtimeState;
|
||||
private readonly WebRoutingSettings _webRoutingSettings;
|
||||
|
||||
[Obsolete("Use the non-obsolete constructor. This will be removed in Umbraco 17.")]
|
||||
public BackOfficeAuthorizationInitializationMiddleware(
|
||||
UmbracoRequestPaths umbracoRequestPaths,
|
||||
IServiceProvider serviceProvider,
|
||||
IRuntimeState runtimeState,
|
||||
IOptions<GlobalSettings> globalSettings,
|
||||
IOptions<WebRoutingSettings> webRoutingSettings,
|
||||
IHostingEnvironment hostingEnvironment)
|
||||
: this(umbracoRequestPaths, serviceProvider, runtimeState, webRoutingSettings)
|
||||
{
|
||||
}
|
||||
|
||||
public BackOfficeAuthorizationInitializationMiddleware(
|
||||
UmbracoRequestPaths umbracoRequestPaths,
|
||||
IServiceProvider serviceProvider,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+18
-25
@@ -1,6 +1,6 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Microsoft.OpenApi;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
using Umbraco.Cms.Api.Management.DependencyInjection;
|
||||
using Umbraco.Extensions;
|
||||
@@ -21,27 +21,17 @@ public abstract class BackOfficeSecurityRequirementsOperationFilterBase : IOpera
|
||||
if (!context.MethodInfo.GetCustomAttributes(true).Any(x => x is AllowAnonymousAttribute) &&
|
||||
!(context.MethodInfo.DeclaringType?.GetCustomAttributes(true).Any(x => x is AllowAnonymousAttribute) ?? false))
|
||||
{
|
||||
operation.Responses.Add(StatusCodes.Status401Unauthorized.ToString(), new OpenApiResponse
|
||||
{
|
||||
Description = "The resource is protected and requires an authentication token"
|
||||
});
|
||||
|
||||
operation.Security = new List<OpenApiSecurityRequirement>
|
||||
{
|
||||
new OpenApiSecurityRequirement
|
||||
operation.Responses ??= new OpenApiResponses();
|
||||
operation.Responses.Add(
|
||||
StatusCodes.Status401Unauthorized.ToString(),
|
||||
new OpenApiResponse
|
||||
{
|
||||
{
|
||||
new OpenApiSecurityScheme
|
||||
{
|
||||
Reference = new OpenApiReference
|
||||
{
|
||||
Type = ReferenceType.SecurityScheme,
|
||||
Id = ManagementApiConfiguration.ApiSecurityName
|
||||
}
|
||||
}, []
|
||||
}
|
||||
}
|
||||
};
|
||||
Description = "The resource is protected and requires an authentication token",
|
||||
});
|
||||
|
||||
var schemaRef = new OpenApiSecuritySchemeReference(ManagementApiConfiguration.ApiSecurityName, context.Document);
|
||||
operation.Security ??= new List<OpenApiSecurityRequirement>();
|
||||
operation.Security.Add(new OpenApiSecurityRequirement { [schemaRef] = [] });
|
||||
}
|
||||
|
||||
// Assuming if and endpoint have more then one AuthorizeAttribute, there is a risk the user do not have access while still being authorized.
|
||||
@@ -57,10 +47,13 @@ public abstract class BackOfficeSecurityRequirementsOperationFilterBase : IOpera
|
||||
|
||||
if (numberOfAuthorizeAttributes > 2 || hasConstructorInjectingIAuthorizationService)
|
||||
{
|
||||
operation.Responses.Add(StatusCodes.Status403Forbidden.ToString(), new OpenApiResponse()
|
||||
{
|
||||
Description = "The authenticated user does not have access to this resource"
|
||||
});
|
||||
operation.Responses ??= new OpenApiResponses();
|
||||
operation.Responses.Add(
|
||||
StatusCodes.Status403Forbidden.ToString(),
|
||||
new OpenApiResponse
|
||||
{
|
||||
Description = "The authenticated user does not have access to this resource",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Microsoft.OpenApi;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
using Umbraco.Cms.Api.Management.ViewModels;
|
||||
using Umbraco.Cms.Core;
|
||||
@@ -24,28 +24,25 @@ internal sealed class NotificationHeaderFilter : IOperationFilter
|
||||
|
||||
// filter out irrelevant responses (401 will never produce notifications)
|
||||
IEnumerable<OpenApiResponse> relevantResponses = operation
|
||||
.Responses
|
||||
.Responses?
|
||||
.Where(pair => pair.Key != StatusCodes.Status401Unauthorized.ToString())
|
||||
.Select(pair => pair.Value);
|
||||
.Select(pair => pair.Value)
|
||||
.OfType<OpenApiResponse>()
|
||||
?? Enumerable.Empty<OpenApiResponse>();
|
||||
foreach (OpenApiResponse response in relevantResponses)
|
||||
{
|
||||
response.Headers.TryAdd(Constants.Headers.Notifications, new OpenApiHeader
|
||||
{
|
||||
Description = "The list of notifications produced during the request.",
|
||||
Schema = new OpenApiSchema
|
||||
response.Headers ??= new Dictionary<string, IOpenApiHeader>();
|
||||
response.Headers.TryAdd(
|
||||
Constants.Headers.Notifications,
|
||||
new OpenApiHeader
|
||||
{
|
||||
Type = "array",
|
||||
Nullable = true,
|
||||
Items = new OpenApiSchema()
|
||||
Description = "The list of notifications produced during the request.",
|
||||
Schema = new OpenApiSchema
|
||||
{
|
||||
Reference = new OpenApiReference()
|
||||
{
|
||||
Type = ReferenceType.Schema,
|
||||
Id = notificationModelType.Name
|
||||
},
|
||||
}
|
||||
}
|
||||
});
|
||||
Type = JsonSchemaType.Array | JsonSchemaType.Null,
|
||||
Items = new OpenApiSchemaReference(notificationModelType.Name),
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Microsoft.OpenApi;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
using Umbraco.Cms.Api.Management.DependencyInjection;
|
||||
using Umbraco.Cms.Core;
|
||||
@@ -11,36 +11,36 @@ internal sealed class ResponseHeaderOperationFilter : IOperationFilter
|
||||
{
|
||||
public void Apply(OpenApiOperation operation, OperationFilterContext context)
|
||||
{
|
||||
if (context.MethodInfo.HasMapToApiAttribute(ManagementApiConfiguration.ApiName) == false)
|
||||
if (context.MethodInfo.HasMapToApiAttribute(ManagementApiConfiguration.ApiName) is false || operation.Responses is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ((var key, OpenApiResponse? value) in operation.Responses)
|
||||
foreach ((var key, IOpenApiResponse value) in operation.Responses)
|
||||
{
|
||||
if (value is not OpenApiResponse openApiResponse)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (int.Parse(key))
|
||||
{
|
||||
case StatusCodes.Status201Created:
|
||||
// NOTE: The header order matters to the back-office client. Do not change.
|
||||
SetHeader(value, Constants.Headers.GeneratedResource, "Identifier of the newly created resource", "string");
|
||||
SetHeader(value, Constants.Headers.Location, "Location of the newly created resource", "string", "uri");
|
||||
SetHeader(openApiResponse, Constants.Headers.GeneratedResource, "Identifier of the newly created resource", JsonSchemaType.String);
|
||||
SetHeader(openApiResponse, Constants.Headers.Location, "Location of the newly created resource", JsonSchemaType.String, "uri");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetHeader(OpenApiResponse value, string headerName, string description, string type, string? format = null)
|
||||
private static void SetHeader(OpenApiResponse value, string headerName, string description, JsonSchemaType type, string? format = null)
|
||||
{
|
||||
|
||||
if (value.Headers is null)
|
||||
{
|
||||
value.Headers = new Dictionary<string, OpenApiHeader>();
|
||||
}
|
||||
|
||||
value.Headers[headerName] = new OpenApiHeader()
|
||||
value.Headers ??= new Dictionary<string, IOpenApiHeader>();
|
||||
value.Headers[headerName] = new OpenApiHeader
|
||||
{
|
||||
Description = description,
|
||||
Schema = new OpenApiSchema { Description = description, Type = type, Format = format }
|
||||
Schema = new OpenApiSchema { Description = description, Type = type, Format = format },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Microsoft.OpenApi;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.OpenApi;
|
||||
@@ -8,14 +8,21 @@ public class RequireNonNullablePropertiesSchemaFilter : ISchemaFilter
|
||||
/// <summary>
|
||||
/// Add to model.Required all properties where Nullable is false.
|
||||
/// </summary>
|
||||
public void Apply(OpenApiSchema model, SchemaFilterContext context)
|
||||
public void Apply(IOpenApiSchema model, SchemaFilterContext context)
|
||||
{
|
||||
var additionalRequiredProps = model.Properties
|
||||
.Where(x => !x.Value.Nullable && !model.Required.Contains(x.Key))
|
||||
.Select(x => x.Key);
|
||||
if (model is not OpenApiSchema schema)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IEnumerable<string> additionalRequiredProps = schema.Properties
|
||||
?.Where(x => x.Value.Type?.HasFlag(JsonSchemaType.Null) is not true && model.Required?.Contains(x.Key) is not true)
|
||||
.Select(x => x.Key)
|
||||
?? [];
|
||||
schema.Required ??= new SortedSet<string>();
|
||||
foreach (var propKey in additionalRequiredProps)
|
||||
{
|
||||
model.Required.Add(propKey);
|
||||
schema.Required.Add(propKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,10 +32,6 @@ public class BackOfficeApplicationManager : OpenIdDictApplicationManagerBase, IB
|
||||
_authorizeCallbackLogoutPathName = securitySettings.Value.AuthorizeCallbackLogoutPathName;
|
||||
}
|
||||
|
||||
[Obsolete("Please use the overload that allows for multiple back-office hosts. Will be removed in V17.")]
|
||||
public async Task EnsureBackOfficeApplicationAsync(Uri backOfficeUrl, CancellationToken cancellationToken = default)
|
||||
=> await EnsureBackOfficeApplicationAsync([backOfficeUrl], cancellationToken);
|
||||
|
||||
public async Task EnsureBackOfficeApplicationAsync(IEnumerable<Uri> backOfficeHosts, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Install is okay without this, because we do not need a token to install,
|
||||
@@ -100,8 +96,7 @@ public class BackOfficeApplicationManager : OpenIdDictApplicationManagerBase, IB
|
||||
public async Task DeleteBackOfficeClientCredentialsApplicationAsync(string clientId, CancellationToken cancellationToken = default)
|
||||
=> await Delete(clientId, cancellationToken);
|
||||
|
||||
[Obsolete("Do not use - for internal usage only. Will be made internal in V17.")]
|
||||
public OpenIddictApplicationDescriptor BackofficeOpenIddictApplicationDescriptor(Uri backOfficeUrl)
|
||||
internal OpenIddictApplicationDescriptor BackofficeOpenIddictApplicationDescriptor(Uri backOfficeUrl)
|
||||
=> BackofficeOpenIddictApplicationDescriptor([backOfficeUrl]);
|
||||
|
||||
internal OpenIddictApplicationDescriptor BackofficeOpenIddictApplicationDescriptor(Uri[] backOfficeHosts)
|
||||
|
||||
@@ -18,19 +18,6 @@ public class BackOfficeExternalLoginProviders : IBackOfficeExternalLoginProvider
|
||||
|
||||
private const string ExternalLoginProvidersKey = "Umbraco.Cms.Web.BackOffice.Security.BackOfficeExternalLoginProviders";
|
||||
|
||||
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 17.")]
|
||||
public BackOfficeExternalLoginProviders(
|
||||
IEnumerable<BackOfficeExternalLoginProvider> externalLogins,
|
||||
IAuthenticationSchemeProvider authenticationSchemeProvider)
|
||||
: this(
|
||||
externalLogins,
|
||||
authenticationSchemeProvider,
|
||||
StaticServiceProvider.Instance.GetRequiredService<IKeyValueService>(),
|
||||
StaticServiceProvider.Instance.GetRequiredService<IExternalLoginWithKeyService>(),
|
||||
StaticServiceProvider.Instance.GetRequiredService<ILogger<BackOfficeExternalLoginProviders>>())
|
||||
{
|
||||
}
|
||||
|
||||
public BackOfficeExternalLoginProviders(
|
||||
IEnumerable<BackOfficeExternalLoginProvider> externalLogins,
|
||||
IAuthenticationSchemeProvider authenticationSchemeProvider,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Globalization;
|
||||
using System.Globalization;
|
||||
using Umbraco.Cms.Core.Events;
|
||||
using Umbraco.Cms.Core.Models.Membership;
|
||||
using Umbraco.Cms.Core.Notifications;
|
||||
@@ -12,24 +12,32 @@ namespace Umbraco.Cms.Api.Management.Security;
|
||||
/// 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>
|
||||
INotificationAsyncHandler<UserLoginSuccessNotification>,
|
||||
INotificationAsyncHandler<UserLogoutSuccessNotification>,
|
||||
INotificationAsyncHandler<UserLoginFailedNotification>,
|
||||
INotificationAsyncHandler<UserForgotPasswordRequestedNotification>,
|
||||
INotificationAsyncHandler<UserForgotPasswordChangedNotification>,
|
||||
INotificationAsyncHandler<UserPasswordChangedNotification>,
|
||||
INotificationAsyncHandler<UserPasswordResetNotification>
|
||||
{
|
||||
private readonly IAuditService _auditService;
|
||||
private readonly IAuditEntryService _auditEntryService;
|
||||
private readonly IUserService _userService;
|
||||
|
||||
public BackOfficeUserManagerAuditer(IAuditService auditService, IUserService userService)
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BackOfficeUserManagerAuditer"/> class.
|
||||
/// </summary>
|
||||
/// <param name="auditEntryService">The audit entry service.</param>
|
||||
/// <param name="userService">The user service.</param>
|
||||
public BackOfficeUserManagerAuditer(
|
||||
IAuditEntryService auditEntryService,
|
||||
IUserService userService)
|
||||
{
|
||||
_auditService = auditService;
|
||||
_auditEntryService = auditEntryService;
|
||||
_userService = userService;
|
||||
}
|
||||
|
||||
public void Handle(UserForgotPasswordChangedNotification notification) =>
|
||||
/// <inheritdoc />
|
||||
public Task HandleAsync(UserForgotPasswordChangedNotification notification, CancellationToken cancellationToken) =>
|
||||
WriteAudit(
|
||||
notification.PerformingUserId,
|
||||
notification.AffectedUserId,
|
||||
@@ -37,7 +45,8 @@ internal sealed class BackOfficeUserManagerAuditer :
|
||||
"umbraco/user/password/forgot/change",
|
||||
"password forgot/change");
|
||||
|
||||
public void Handle(UserForgotPasswordRequestedNotification notification) =>
|
||||
/// <inheritdoc />
|
||||
public Task HandleAsync(UserForgotPasswordRequestedNotification notification, CancellationToken cancellationToken) =>
|
||||
WriteAudit(
|
||||
notification.PerformingUserId,
|
||||
notification.AffectedUserId,
|
||||
@@ -45,7 +54,8 @@ internal sealed class BackOfficeUserManagerAuditer :
|
||||
"umbraco/user/password/forgot/request",
|
||||
"password forgot/request");
|
||||
|
||||
public void Handle(UserLoginFailedNotification notification) =>
|
||||
/// <inheritdoc />
|
||||
public Task HandleAsync(UserLoginFailedNotification notification, CancellationToken cancellationToken) =>
|
||||
WriteAudit(
|
||||
notification.PerformingUserId,
|
||||
null,
|
||||
@@ -53,7 +63,8 @@ internal sealed class BackOfficeUserManagerAuditer :
|
||||
"umbraco/user/sign-in/failed",
|
||||
"login failed");
|
||||
|
||||
public void Handle(UserLoginSuccessNotification notification)
|
||||
/// <inheritdoc />
|
||||
public Task HandleAsync(UserLoginSuccessNotification notification, CancellationToken cancellationToken)
|
||||
=> WriteAudit(
|
||||
notification.PerformingUserId,
|
||||
notification.AffectedUserId,
|
||||
@@ -61,7 +72,8 @@ internal sealed class BackOfficeUserManagerAuditer :
|
||||
"umbraco/user/sign-in/login",
|
||||
"login success");
|
||||
|
||||
public void Handle(UserLogoutSuccessNotification notification)
|
||||
/// <inheritdoc />
|
||||
public Task HandleAsync(UserLogoutSuccessNotification notification, CancellationToken cancellationToken)
|
||||
=> WriteAudit(
|
||||
notification.PerformingUserId,
|
||||
notification.AffectedUserId,
|
||||
@@ -69,7 +81,8 @@ internal sealed class BackOfficeUserManagerAuditer :
|
||||
"umbraco/user/sign-in/logout",
|
||||
"logout success");
|
||||
|
||||
public void Handle(UserPasswordChangedNotification notification) =>
|
||||
/// <inheritdoc />
|
||||
public Task HandleAsync(UserPasswordChangedNotification notification, CancellationToken cancellationToken) =>
|
||||
WriteAudit(
|
||||
notification.PerformingUserId,
|
||||
notification.AffectedUserId,
|
||||
@@ -77,7 +90,8 @@ internal sealed class BackOfficeUserManagerAuditer :
|
||||
"umbraco/user/password/change",
|
||||
"password change");
|
||||
|
||||
public void Handle(UserPasswordResetNotification notification) =>
|
||||
/// <inheritdoc />
|
||||
public Task HandleAsync(UserPasswordResetNotification notification, CancellationToken cancellationToken) =>
|
||||
WriteAudit(
|
||||
notification.PerformingUserId,
|
||||
notification.AffectedUserId,
|
||||
@@ -85,58 +99,52 @@ internal sealed class BackOfficeUserManagerAuditer :
|
||||
"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(
|
||||
private async Task WriteAudit(
|
||||
string performingId,
|
||||
string? affectedId,
|
||||
string ipAddress,
|
||||
string eventType,
|
||||
string eventDetails)
|
||||
{
|
||||
int? performingIdAsInt = ParseUserId(performingId);
|
||||
int? affectedIdAsInt = ParseUserId(affectedId);
|
||||
var performingIdAsInt = ParseUserId(performingId);
|
||||
var affectedIdAsInt = ParseUserId(affectedId);
|
||||
|
||||
WriteAudit(performingIdAsInt, affectedIdAsInt, ipAddress, eventType, eventDetails);
|
||||
await 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(
|
||||
private async Task 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)}";
|
||||
}
|
||||
IUser? performingUser = performingId is not null ? _userService.GetUserById(performingId.Value) : null;
|
||||
IUser? affectedUser = affectedId is not null ? _userService.GetUserById(affectedId.Value) : null;
|
||||
|
||||
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,
|
||||
await _auditEntryService.WriteAsync(
|
||||
performingUser?.Key,
|
||||
FormatDetails(performingId, performingUser),
|
||||
ipAddress,
|
||||
DateTime.UtcNow,
|
||||
affectedId ?? 0,
|
||||
affectedDetails,
|
||||
affectedUser?.Key,
|
||||
FormatDetails(affectedId, affectedUser),
|
||||
eventType,
|
||||
eventDetails);
|
||||
}
|
||||
|
||||
private static string FormatDetails(int? id, IUser? user)
|
||||
{
|
||||
if (user == null)
|
||||
{
|
||||
return $"User UNKNOWN:{id ?? 0}";
|
||||
}
|
||||
|
||||
return user.Email.IsNullOrWhiteSpace()
|
||||
? $"User \"{user.Name}\""
|
||||
: $"User \"{user.Name}\" <{user.Email}>";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,15 +17,6 @@ public class UserStartNodeEntitiesService : IUserStartNodeEntitiesService
|
||||
private readonly ICoreScopeProvider _scopeProvider;
|
||||
private readonly IIdKeyMap _idKeyMap;
|
||||
|
||||
[Obsolete("Please use the non-obsolete constructor. Scheduled for removal in V17.")]
|
||||
public UserStartNodeEntitiesService(IEntityService entityService)
|
||||
: this(
|
||||
entityService,
|
||||
StaticServiceProvider.Instance.GetRequiredService<ICoreScopeProvider>(),
|
||||
StaticServiceProvider.Instance.GetRequiredService<IIdKeyMap>())
|
||||
{
|
||||
}
|
||||
|
||||
public UserStartNodeEntitiesService(IEntityService entityService, ICoreScopeProvider scopeProvider, IIdKeyMap idKeyMap)
|
||||
{
|
||||
_entityService = entityService;
|
||||
@@ -96,10 +87,12 @@ public class UserStartNodeEntitiesService : IUserStartNodeEntitiesService
|
||||
return ChildUserAccessEntities(children, userStartNodePaths);
|
||||
}
|
||||
|
||||
int[] allowedChildIds = GetAllowedIds(userStartNodePaths, parentId);
|
||||
// Need to use a List here because the expression tree cannot convert an array when used in Contains.
|
||||
// See ExpressionTests.Sql_In().
|
||||
List<int> allowedChildIds = GetAllowedIds(userStartNodePaths, parentId);
|
||||
|
||||
totalItems = allowedChildIds.Length;
|
||||
if (allowedChildIds.Length == 0)
|
||||
totalItems = allowedChildIds.Count;
|
||||
if (allowedChildIds.Count == 0)
|
||||
{
|
||||
// The requested parent is outside the scope of any user start nodes.
|
||||
return [];
|
||||
@@ -111,7 +104,7 @@ public class UserStartNodeEntitiesService : IUserStartNodeEntitiesService
|
||||
return ChildUserAccessEntities(children, userStartNodePaths);
|
||||
}
|
||||
|
||||
private static int[] GetAllowedIds(string[] userStartNodePaths, int parentId)
|
||||
private static List<int> GetAllowedIds(string[] userStartNodePaths, int parentId)
|
||||
{
|
||||
// If one or more of the user start nodes are descendants of the requested parent, find the "next child IDs" in those user start node paths
|
||||
// that are the final entries in the path.
|
||||
@@ -121,7 +114,7 @@ public class UserStartNodeEntitiesService : IUserStartNodeEntitiesService
|
||||
.Where(ids => ids.Contains(parentId))
|
||||
.Select(ids => ids[ids.IndexOf(parentId) + 1]) // Given the previous checks, the parent ID can never be the last in the user start node path, so this is safe
|
||||
.Distinct()
|
||||
.ToArray();
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -194,9 +187,9 @@ public class UserStartNodeEntitiesService : IUserStartNodeEntitiesService
|
||||
return ChildUserAccessEntities(siblings, userStartNodePaths);
|
||||
}
|
||||
|
||||
int[] allowedSiblingIds = GetAllowedIds(userStartNodePaths, targetParent.Id);
|
||||
List<int> allowedSiblingIds = GetAllowedIds(userStartNodePaths, targetParent.Id);
|
||||
|
||||
if (allowedSiblingIds.Length == 0)
|
||||
if (allowedSiblingIds.Count == 0)
|
||||
{
|
||||
// The requested target is outside the scope of any user start nodes.
|
||||
totalBefore = 0;
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
using Umbraco.Cms.Api.Management.ViewModels.NewsDashboard;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Services.NewsDashboard;
|
||||
|
||||
/// <summary>
|
||||
/// Defines a service for retrieving news dashboard items.
|
||||
/// </summary>
|
||||
public interface INewsDashboardService
|
||||
{
|
||||
/// <summary>
|
||||
/// Asynchronously retrieves the collection of news dashboard items.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A task that represents the asynchronous operation.
|
||||
/// The task result contains a <see cref="NewsDashboardResponseModel"/> with the retrieved news dashboard items.
|
||||
/// </returns>
|
||||
Task<NewsDashboardResponseModel> GetItemsAsync();
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.NewsDashboard;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.Configuration;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Telemetry;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Services.NewsDashboard;
|
||||
|
||||
/// <inheritdoc />
|
||||
public class NewsDashboardService : INewsDashboardService
|
||||
{
|
||||
private readonly AppCaches _appCaches;
|
||||
private readonly IUmbracoVersion _umbracoVersion;
|
||||
private readonly ISiteIdentifierService _siteIdentifierService;
|
||||
private readonly ILogger<NewsDashboardService> _logger;
|
||||
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
|
||||
private static readonly HttpClient _httpClient = new();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NewsDashboardService"/> class.
|
||||
/// </summary>
|
||||
public NewsDashboardService(
|
||||
AppCaches appCaches,
|
||||
IUmbracoVersion umbracoVersion,
|
||||
ISiteIdentifierService siteIdentifierService,
|
||||
ILogger<NewsDashboardService> logger,
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
|
||||
IOptions<GlobalSettings> globalSettings)
|
||||
{
|
||||
_appCaches = appCaches;
|
||||
_umbracoVersion = umbracoVersion;
|
||||
_siteIdentifierService = siteIdentifierService;
|
||||
_logger = logger;
|
||||
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
|
||||
_globalSettings = globalSettings.Value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<NewsDashboardResponseModel> GetItemsAsync()
|
||||
{
|
||||
const string BaseUrl = "https://news-dashboard.umbraco.com";
|
||||
const string Path = "/api/News";
|
||||
|
||||
var version = _umbracoVersion.SemanticVersion.ToSemanticStringWithoutBuild();
|
||||
_siteIdentifierService.TryGetOrCreateSiteIdentifier(out Guid siteIdentifier);
|
||||
|
||||
var language = _backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.Language ?? _globalSettings.DefaultUILanguage;
|
||||
|
||||
var url = $"{BaseUrl}/{Path}?version={version}&siteId={siteIdentifier}&language={language}";
|
||||
|
||||
const string CacheKey = "umbraco-dashboard-news";
|
||||
NewsDashboardResponseModel? content = _appCaches.RuntimeCache.GetCacheItem<NewsDashboardResponseModel>(CacheKey);
|
||||
if (content is not null)
|
||||
{
|
||||
return content;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var json = await _httpClient.GetStringAsync(url);
|
||||
|
||||
if (TryMapModel(json, out NewsDashboardResponseModel? model))
|
||||
{
|
||||
_appCaches.RuntimeCache.InsertCacheItem(CacheKey, () => model, new TimeSpan(0, 30, 0));
|
||||
content = model;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.InnerException ?? ex, "Error getting dashboard content from {Url}", url);
|
||||
}
|
||||
|
||||
return content ?? new NewsDashboardResponseModel { Items = [] };
|
||||
}
|
||||
|
||||
private bool TryMapModel(string json, [MaybeNullWhen(false)] out NewsDashboardResponseModel newsDashboardResponseModel)
|
||||
{
|
||||
try
|
||||
{
|
||||
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true, };
|
||||
|
||||
List<NewsDashboardItemResponseModel>? items = JsonSerializer.Deserialize<List<NewsDashboardItemResponseModel>>(json, options);
|
||||
newsDashboardResponseModel = new NewsDashboardResponseModel { Items = items ?? [] };
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (JsonException ex)
|
||||
{
|
||||
_logger.LogError(ex.InnerException ?? ex, "Error deserializing dashboard news items");
|
||||
newsDashboardResponseModel = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user