Compare commits

..
256 Commits
Author SHA1 Message Date
Andreas ZerbstandGitHub 9ab0abca99 E2E: Stop running version 14 on nightly test pipeline (#20558)
Stops 14 from running on our nightly test pipeline
2025-10-20 12:05:42 +02:00
Andy Butland 009a999e22 Merge branch 'release/14.3.4' into v14/dev 2025-04-08 07:15:06 +02:00
Andy ButlandandGitHub d3c1443b14 Merge commit from fork
* Prevent path traveral vulnerability with upload of temporary files.

* Used BadRequest instead of NotFound for invalid file name response.
2025-04-08 05:03:40 +02:00
Andy Butland 8a9db11422 Merge branch 'release/14.3.3' into v14/dev
# Conflicts:
#	Directory.Packages.props
#	version.json
2025-03-11 09:28:52 +01:00
Andy ButlandandGitHub d9fb6df16e Merge commit from fork
* Bumped version to 15.2.1.

# Conflicts:
#	version.json

* Tighten management API endpoint access rules.
2025-03-11 05:11:35 +01:00
Andy Butland 14ed3348bf bumped imagesharp to prevent CVE-2025-27598 (#18602)
# Conflicts:
#	Directory.Packages.props
2025-03-09 08:54:03 +01:00
Sven Geusens 9e7a36865a Merge branch 'v13/dev' into v14/dev
Revert #18249 as it is reimplemented for v15
Revert #18320 as the new architecture explictly throws an error

# Conflicts:
#	build/azure-pipelines.yml
#	src/Umbraco.Core/EmbeddedResources/Lang/en.xml
#	src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml
#	src/Umbraco.Core/Models/ContentEditing/ContentSaveAction.cs
#	src/Umbraco.Core/Services/ContentService.cs
#	src/Umbraco.Core/Services/IContentService.cs
#	src/Umbraco.Core/Services/MemberService.cs
#	src/Umbraco.Infrastructure/PropertyEditors/RichTextEditorPastedImages.cs
#	src/Umbraco.Infrastructure/Security/BackOfficeUserStore.cs
#	src/Umbraco.Infrastructure/Security/MemberUserStore.cs
#	src/Umbraco.Web.BackOffice/Controllers/ContentController.cs
#	src/Umbraco.Web.BackOffice/Controllers/EntityController.cs
#	src/Umbraco.Web.BackOffice/Controllers/MediaController.cs
#	src/Umbraco.Web.BackOffice/Controllers/MemberController.cs
#	src/Umbraco.Web.BackOffice/Controllers/PreviewController.cs
#	src/Umbraco.Web.BackOffice/Controllers/UsersController.cs
#	src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilder.BackOfficeAuth.cs
#	src/Umbraco.Web.BackOffice/Filters/ContentSaveValidationAttribute.cs
#	src/Umbraco.Web.BackOffice/Filters/MemberSaveModelValidator.cs
#	src/Umbraco.Web.BackOffice/Filters/MemberSaveValidationAttribute.cs
#	src/Umbraco.Web.BackOffice/Trees/ContentTreeController.cs
#	src/Umbraco.Web.Common/RuntimeMinification/SmidgeOptionsSetup.cs
#	src/Umbraco.Web.Common/RuntimeMinification/SmidgeRuntimeMinifier.cs
#	src/Umbraco.Web.Common/Views/UmbracoViewPage.cs
#	src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbbuttongroup.directive.js
#	src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js
#	src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbtabbedcontent.directive.js
#	src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js
#	src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js
#	src/Umbraco.Web.UI.Client/src/common/resources/publicaccess.resource.js
#	src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js
#	src/Umbraco.Web.UI.Client/src/common/services/assets.service.js
#	src/Umbraco.Web.UI.Client/src/views/common/drawers/help/help.controller.js
#	src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js
#	src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediaentryeditor/mediaentryeditor.controller.js
#	src/Umbraco.Web.UI.Client/src/views/components/content/umb-tabbed-content.html
#	src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html
#	src/Umbraco.Web.UI.Client/src/views/content/content.create.controller.js
#	src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.controller.js
#	src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.html
#	src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js
#	src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.component.js
#	src/Umbraco.Web.UI.Client/src/views/users/views/user/details.html
#	src/Umbraco.Web.UI.Client/src/views/webhooks/edit.controller.js
#	src/Umbraco.Web.UI.Client/src/views/webhooks/edit.html
#	src/Umbraco.Web.UI.Client/test/unit/app/content/create-content-controller.spec.js
#	src/Umbraco.Web.UI.Client~HEAD
#	src/Umbraco.Web.UI.Login/src/auth.element.ts
#	tests/Umbraco.TestData/UmbracoTestDataController.cs
#	tests/Umbraco.Tests.Integration/Umbraco.Core/Services/ContentServiceTests.cs
#	tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentEventsTests.cs
#	tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServicePublishBranchTests.cs
#	tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceTagsTests.cs
#	tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberUserStoreTests.cs
#	tests/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/MemberControllerUnitTests.cs
#	version.json
2025-02-17 19:25:45 +01:00
Andy ButlandandGitHub db1d999721 Avoid exception when attempting to find member by Id when Id is not an expected Guid or integer, as can be the case with external member providers. (#18320) 2025-02-17 12:47:52 +01:00
Andy ButlandandGitHub 18047a7cfb Only filter post retrieval of entities for start nodes if working with entities that support start nodes. (#18287) 2025-02-17 12:45:51 +01:00
Andy ButlandandGitHub def7ebd48c Html encodes the user's name in the invite email. (#18343) 2025-02-17 12:25:12 +01:00
8c2b1ebdc5 V13: Introduce publishNotifications method on IMembershipMemberService (#18207)
* Introduce publishNotifications method on IMembershipMemberService.cs

* Fix test

* Add PublishNotificationSaveOptions

* Fix up according to comments

* Use numeric values for flag enum

* Update src/Umbraco.Core/Services/MemberService.cs

Co-authored-by: Andy Butland <abutland73@gmail.com>

* Update src/Umbraco.Core/Services/MemberService.cs

Co-authored-by: Andy Butland <abutland73@gmail.com>

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2025-02-12 13:30:41 +01:00
Andy ButlandandGitHub 9227517a50 Split force for publish descendants into separate options for publish unpublish and re-publish unedited (13) (#18249)
* Split force for publish descendents into separate options for publish unpublish and re-publish unedited.

* Added integration task verifying updated behaviour.

* Variant integration test.

* Update test data controller.

* Remove usued function parameters.

* Refactor to enum.

* Fixed flags enum.

* Variable name refactor.

* Applied changes from code review.

* Refactored method name.

* Aligned js boolean checks.
2025-02-12 11:30:27 +00:00
Andy Butland 5322d0f7b5 Bumped version to 13.8.0-rc. 2025-02-12 08:13:58 +01:00
Andy ButlandandGitHub a282cc5691 Backport use of thread delay over sleep and handle dispose in FileSystemMainDomLock (#18151)
* Backport use of thread delay over sleep and handle dispose in FileSystemMainDomLock (from PRs #18119 and #18147)

* Applied suggestion from code review.
2025-02-12 08:06:50 +01:00
Andy ButlandandGitHub 048f8bcdf9 Fixed userResource request to get all users. (#18105) 2025-02-11 17:06:18 +01:00
Andy ButlandandGitHub 7bcbc748d4 URL encodes member user names when passing information for public access setting such that those with user names as emails containing a plus will be included in the rule. (#18142) 2025-02-11 13:36:19 +01:00
Andy ButlandandGitHub f30e6cfe7f Prevents folder selection in media picker when used from the multi URL picker. (#18288) 2025-02-11 09:18:01 +01:00
Andy ButlandandGitHub 026e80e026 Avoid an exception on sign out when the principal is populated from an incomplete external login (#18078)
* Avoid an exception on signout when the principal is populated from an incomplete external login.

* Tidied up comment.
2025-02-10 12:51:35 +01:00
Andy ButlandandGitHub a3b77cff63 Add validation to prevent update of a user or member to an invalid username (13) (#18261)
* Add validation to prevent update of a user or member to an invalid username.

* Avoid password manager updates of user name field on user details screen.
2025-02-10 10:40:53 +01:00
Niels LyngsøandGitHub 4ca68d6995 Add client-side validation for RTE (#18257) 2025-02-07 11:43:04 +00:00
Sven GeusensandGitHub 280cb7f2b1 Fix issues in newly added buttongroup localization (#18254)
* Fix #18253 nullref exception

* Fix #18239 by listening to broader scope changes
2025-02-07 07:00:34 +01:00
095a73132c Review: Allow Duplicate Email for Members (#16202)
* init

* Aligned default values on security settings.

* Added validator for security settings.

* Provide default implementation for get members by email.

* Refactored constructor of MemberController.

* Validate on unique member email only when configured to do so.

* Further code tidy and use of DI in constructor.

* Used new constructor in tests.

* Add unit test for modified behaviour.

* Removed validator for security settings (it's not necessary, I got confused with users and members).

* Spelling.

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2025-02-05 12:38:40 +01:00
Andy Butland cfb0fc23ac Merge branch 'v13/dev' into v13/contrib 2025-02-04 12:35:49 +01:00
6620aca9fe Cache null dictionary values by key (#15576)
* Add CacheNullValues option to RepositoryCachePolicy

* Cache null values in DictionaryByKeyRepository

* Fixed issue with nullable reference.

* Updated logic for caching of null values.

* Update src/Umbraco.Infrastructure/Cache/DefaultRepositoryCachePolicy.cs

Co-authored-by: Sven Geusens <geusens@gmail.com>

* Made the NullValueRepresentation overwritable in a generic manner

* Improve generic NullValueCachePolicyResolver

* Revert Commits and clarify logic with comment

This reverts commit 8befb43792 "Improve generic NullValueCachePolicyResolver"
Also reverts 8adf0a2 - Made the NullValueRepresentation overwritable in a generic manner
And 8adf0a2 - Made the NullValueRepresentation overwritable in a generic manner

* Update src/Umbraco.Infrastructure/Cache/DefaultRepositoryCachePolicy.cs

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
Co-authored-by: Sven Geusens <geusens@gmail.com>
Co-authored-by: Sven Geusens <sge@umbraco.dk>
2025-02-04 12:29:21 +01:00
2422a02b23 Set Smidge cachebuster type (#18198)
* Set Smidge cachebuster type

* Amend exception and fix typo

* Minor tweak to comment and exception message.

---------

Co-authored-by: Simon Hartfield <simon.hartfield@googlemail.com>
Co-authored-by: Andy Butland <abutland73@gmail.com>
2025-02-04 11:00:44 +00:00
b4a9dc0770 V13: Fix members while using basic auth. (#18206)
* Flow additional identities to new principal

* Add extension to more easily get member identity

* Ensure the member is used instead of the backoffice user in `MemberManager`

* Update snippet

* Fix the comment that I broke

* Update src/Umbraco.Web.Common/Extensions/MemberClaimsPrincipalExtensions.cs

Co-authored-by: Andy Butland <abutland73@gmail.com>

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2025-02-03 19:48:08 +01:00
Andy ButlandandGitHub e7411244fd Show notifications menu only to users with permission for the feature. (#18184) 2025-02-03 13:24:58 +01:00
Kenn JacobsenandGitHub 9f357173c3 Enforce user start nodes for media uploads through the RTE (#18204) 2025-02-03 12:50:23 +01:00
Andy Butland 7dc6e3ed5b Merge branch 'v13/dev' into v13/contrib 2025-01-31 16:16:22 +01:00
Gareth WrightandGitHub 5dfff212d0 Update auth.element.ts (#18192)
auth_username => general_username
2025-01-31 14:59:25 +01:00
17615f966b Fix out of memory of 2gb+ (max 4gb) error introduced by #14657 SVG xss security fix (#17421)
Co-authored-by: Andy Butland <abutland73@gmail.com>
2025-01-31 13:46:56 +00:00
Kenn JacobsenandGitHub 7a2d6b6c63 More robust resolving of Delivery API redirects (#18160) 2025-01-31 14:26:07 +01:00
Sven Geusens 343867f6ee Merge branch 'release/13.6' into v13/dev and version bump
# Conflicts:
#	src/Umbraco.Web.BackOffice/Controllers/PreviewController.cs
#	version.json
2025-01-30 16:27:49 +01:00
Bjarke BergandSven Geusens b9837ac77c Optimize Azure pipeline (#17674)
* Only fetch single commit

* Hopefully fixes Nerdbank.GitVersioning.GitException: Shallow clone lacks the objects required to calculate version height. Use full clones or clones with a history at least as deep as the last version height resetting change.

* Do not checkout again

* More test pipeline

* Another attempt

* yet another attempt

* more attempts

* Revert "more attempts"

This reverts commit 5694d97ba6.

* Test without building backoffice and login explicitly

* Fix mem leak in integration tests

* Fixes sqlserver lock test
# Conflicts:
#	build/azure-pipelines.yml
2025-01-30 14:10:05 +01:00
Sven Geusens bb73ec6c68 Set release version 2025-01-30 09:52:50 +01:00
Andy ButlandandGitHub ee2d7bbb1b Excluded tags from trashed content (#18164) 2025-01-29 21:10:11 +01:00
Andy ButlandandGitHub 5d48bc7371 Provides an option to remove the inessential version number from the generated models (#18081)
* Provides an option to remove the inessential version number from the generated models.

* Clarified comment.
2025-01-29 13:33:49 +01:00
Kenn JacobsenandGitHub 59a4649528 Do not allow editing read-only properties by clicking their labels (#18152)
* Do not allow editing read-only properties by clicking their labels

* Simplify the fix :)

* Fix linting issue
2025-01-29 10:29:58 +01:00
Andy ButlandandGitHub f54b6033ab Added Resharper test assemblies to exclude list on TypeFinder. (#18145) 2025-01-29 10:27:17 +01:00
Jacob OvergaardandGitHub bf340cd7d4 fix: remove unused parameters and documentation (#18095)
this fixes an issue where unused parameters were published as supported, but they were in fact never supported.
2025-01-28 10:53:00 +00:00
Andy Butland 313417cb91 Tidied up XML header comment in ITagQuery. 2025-01-28 10:52:21 +01:00
7850078623 Redirect to the published URL when exiting preview (#18114)
Co-authored-by: Andy Butland <abutland73@gmail.com>
2025-01-24 18:46:28 +00:00
44bf3b77b3 Make it possible to reset media picker crops (#18110)
Co-authored-by: Andy Butland <abutland73@gmail.com>
2025-01-24 13:41:30 +00:00
Kenn JacobsenandGitHub 8485458896 Add clientside validation to webhook events (#18089) 2025-01-23 17:01:55 +01:00
Nikolaj Geisle 3d253f5f06 bump version 2025-01-23 11:15:26 +01:00
Nikolaj Geisle 64287d7e1a Merge branch 'release/13.5.3' into release/13.6
# Conflicts:
#	version.json
2025-01-23 11:14:47 +01:00
Andy ButlandandGitHub edc78a5a4c Handles migration case where an expected constraint is renamed but the constraint does not exist. (#18063) 2025-01-22 12:26:06 +01:00
Andy Butland 8056a59a10 Fix schema defined in JSON schema tool definition after merge from 13. 2025-01-21 15:52:31 +01:00
1a18d6c035 Replaced deprecated navigator.platform with navigator.userAgent for platform detection. (#17373)
Co-authored-by: Niels Lyngsø <nsl@umbraco.dk>
2025-01-21 13:56:40 +01:00
Andy Butland 3e877182b5 Merge branch 'v13/contrib' into v13/dev 2025-01-21 09:47:20 +01:00
Martin VennevoldandGitHub 0e4f883bc1 Fix create child issue in list view with infinite editor (#13355). (#17637) 2025-01-21 09:44:22 +01:00
Andy Butland 265cc62ab8 Merge branch 'release/13.6' into v13/dev 2025-01-21 09:21:36 +01:00
Andy Butland 800c16970e Merge branch 'release/13.6' of https://github.com/umbraco/Umbraco-CMS into release/13.6 2025-01-21 09:20:11 +01:00
Andy Butland 6bd11bf233 Fixes failing front-end unit test to align with new behaviour from PR #17818 (
Allow skipSelect blueprints only when one blueprint exists).
2025-01-21 09:19:31 +01:00
Kenn Jacobsen a2fd82a3f3 Add NoopCurrentMemberClaimsProvider so Umbraco can boot without the Delivery API enabled (#18049) 2025-01-21 09:13:31 +01:00
Kenn JacobsenandSven Geusens 56c0aebd76 Add NoopCurrentMemberClaimsProvider so Umbraco can boot without the Delivery API enabled (#18049) 2025-01-21 09:12:20 +01:00
Kenn JacobsenandGitHub 7567990da1 Add NoopCurrentMemberClaimsProvider so Umbraco can boot without the Delivery API enabled (#18049) 2025-01-21 09:10:43 +01:00
Zeegaan c7d157bbfd build on ubuntu 2025-01-21 09:02:00 +01:00
Sven Geusens 4b9771c0eb Merge branch 'v13/dev' into v14/dev
# Conflicts:
#	Directory.Packages.props
#	build/azure-pipelines.yml
#	src/Umbraco.Cms.Api.Common/DependencyInjection/UmbracoBuilderAuthExtensions.cs
#	src/Umbraco.Cms.Persistence.EFCore/Locking/SqlServerEFCoreDistributedLockingMechanism.cs
#	src/Umbraco.Core/Configuration/Models/RichTextEditorSettings.cs
#	src/Umbraco.Core/EmbeddedResources/Lang/da.xml
#	src/Umbraco.Core/EmbeddedResources/Lang/en.xml
#	src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml
#	src/Umbraco.Core/Services/ContentService.cs
#	src/Umbraco.Web.BackOffice/Authorization/ContentPermissionsQueryStringHandler.cs
#	src/Umbraco.Web.BackOffice/Authorization/ContentPermissionsResourceHandler.cs
#	src/Umbraco.Web.BackOffice/Controllers/ContentController.cs
#	src/Umbraco.Web.BackOffice/Controllers/ExamineManagementController.cs
#	src/Umbraco.Web.BackOffice/Controllers/MediaController.cs
#	src/Umbraco.Web.BackOffice/Trees/StaticFilesTreeController.cs
#	src/Umbraco.Web.UI.Client/package-lock.json
#	src/Umbraco.Web.UI.Client/package.json
#	src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbbuttongroup.directive.js
#	src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js
#	src/Umbraco.Web.UI.Client/src/common/filters/simpleMarkdown.filter.js
#	src/Umbraco.Web.UI.Client/src/common/filters/simpleMarkdown.filter.js.js
#	src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js
#	src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less
#	src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediaentryeditor/mediaentryeditor.less
#	src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html
#	src/Umbraco.Web.UI.Client/src/views/common/overlays/ysod/ysod.controller.js
#	src/Umbraco.Web.UI.Client/src/views/common/overlays/ysod/ysod.html
#	src/Umbraco.Web.UI.Client/src/views/components/buttons/umb-button-group.html
#	src/Umbraco.Web.UI.Client/src/views/content/overlays/sendtopublish.controller.js
#	src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.overlay.controller.js
#	src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.overlay.html
#	src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.overlay.controller.js
#	src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.overlay.html
#	src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.component.js
#	src/Umbraco.Web.UI.Client~HEAD
#	src/Umbraco.Web.UI.Login/package-lock.json
#	src/Umbraco.Web.UI.Login/package.json
#	tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorContent.spec.ts
#	tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceNotificationTests.cs
#	tests/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs
#	tools/Umbraco.JsonSchema/UmbracoCmsSchema.cs
#	version.json
2025-01-21 08:47:38 +01:00
Jacob Overgaard abc312c9b4 update backoffice submodule to 14.3.2 2025-01-21 08:44:10 +01:00
Nikolaj Geisle 2161edb871 Bump version 2025-01-21 07:41:06 +01:00
Nikolaj Geisle e77e9c5691 Format sql statement (#17354)
(cherry picked from commit aa9f194d76)
2025-01-20 18:27:54 +01:00
Nikolaj Geisle e934a943b5 build on windows 2025-01-20 17:37:04 +01:00
Ronald BarendseandNikolaj Geisle 65bb2801b0 Merge commit from fork
* Add TimedScope

* Use TimedScope in login endpoint

* Use seperate default duration and only calculate average of actual successful responses

* Only return detailed error responses if credentials are valid

* Cancel timed scope when credentials are valid

* Add UserDefaultFailedLoginDuration and UserMinimumFailedLoginDuration settings
2025-01-20 16:44:28 +01:00
Andy ButlandandGitHub a0a4af6a0c Merge commit from fork 2025-01-20 14:54:14 +01:00
Jacob Overgaard e8d6cded2b update backoffice submodule to 14.3.2 2025-01-20 14:42:17 +01:00
Bjarke BergandJacob Overgaard c65204a146 update backoffice submodule to 14.3.2 2025-01-20 14:34:03 +01:00
Andy ButlandandGitHub 95eb58587b Merge commit from fork 2025-01-20 14:14:28 +01:00
Andy ButlandandGitHub 86f3033334 Merge commit from fork 2025-01-20 14:14:28 +01:00
25628a8b76 Lucene Package Update to Address CVE-2024-43383 (#17942)
* Update Lucene Package to 4.8.0-beta00017

* Add Package Reference

---------

Co-authored-by: Sebastiaan Janssen <sebastiaan@umbraco.com>
2025-01-20 11:45:35 +01:00
Callum WhyteandAndy Butland 7552e315fb Allow skipSelect blueprints only when one blueprint exists (#17818) 2025-01-20 11:45:26 +01:00
Callum WhyteandGitHub d15b5b221e Allow skipSelect blueprints only when one blueprint exists (#17818) 2025-01-20 10:27:54 +01:00
60e3fa97a9 Lucene Package Update to Address CVE-2024-43383 (#17942)
* Update Lucene Package to 4.8.0-beta00017

* Add Package Reference

---------

Co-authored-by: Sebastiaan Janssen <sebastiaan@umbraco.com>
2025-01-20 10:04:02 +01:00
Sven Geusens fd1bd51aff bump version 2025-01-16 10:10:04 +01:00
Sven Geusens b12620af9c Merge branch 'release/13.6' into v13/dev 2025-01-16 10:08:43 +01:00
Sven Geusens d1af59fdf0 Merge branch 'v13/dev' into release/13.6 2025-01-15 22:13:42 +01:00
Andreas ZerbstandGitHub 1b050ebc47 V13 QA added parallelization for E2E tests (#17995)
* Added script for running tests

* Added parallelization for our E2E tests

* Removed line

* Removed condition
2025-01-15 22:12:45 +01:00
Sven Geusens ff3e457c58 Merge branch 'v13/dev' into release/13.6 2025-01-15 20:55:41 +01:00
Andy ButlandandGitHub 2ffaede07b Added a maxlength setting to the regular expression pattern field. (#17982) 2025-01-15 20:54:54 +01:00
Sven GeusensandGitHub 2a83f57262 Do not re-map schedule modal on error as it leads to broken bindings (#17990) 2025-01-15 17:35:23 +01:00
Kenn JacobsenandGitHub 5be9193978 Do not show "Save and preview" by default (#17986) 2025-01-15 15:42:37 +01:00
Niels LyngsøandSven Geusens d3f264fc54 Fix: only fire event when instance is available (issue 17847) (#17987)
* only fire event when instance is available

* avoid js issue when no markup
2025-01-15 14:01:33 +01:00
Niels LyngsøandGitHub 04ea002bf3 Fix: only fire event when instance is available (issue 17847) (#17987)
* only fire event when instance is available

* avoid js issue when no markup
2025-01-15 13:56:53 +01:00
Kenn JacobsenandGitHub 8f42a3a0ea Make the RTE handle inline elements gracefully for JSON (#17983) 2025-01-15 11:26:03 +01:00
Kenn JacobsenandGitHub 4dd8fbe25d Enforce the "Save and preview" button after when switching content app (#17985) 2025-01-15 09:55:00 +01:00
Sven GeusensandGitHub ee727c43e1 Added localizations for composite key of missing New (media) Audit item (#17981) 2025-01-14 21:08:47 +01:00
Jacob OvergaardandGitHub 395213946b build(deps): update @microsoft/signalr from 7.0.12 to 8.0.7 (#17973) 2025-01-14 15:44:52 +00:00
Andy ButlandandGitHub ed0b236208 Resets send to approval dialog on close such that when re-opening the selection and operation can still be triggered. (#17956) 2025-01-14 16:25:26 +01:00
Kenn JacobsenandGitHub 8d56e75257 Add Vary header to all content API operations (#17962)
* Add Vary header to all content API operations

* Make attribute sealed
2025-01-14 15:54:19 +01:00
Jacob OvergaardandGitHub b3c27415d1 build(deps): bump @umbraco-ui/uui from 1.11.0 to 1.12.2 (#17972) 2025-01-14 15:48:34 +01:00
Justin NevilleandNikolaj Geisle 9839b0ee66 Fixes issue with the IVirtualPageController not setting PublishedRequest content via FindContent (#15121)
* Fixes issue https://github.com/umbraco/Umbraco-CMS/issues/12834

* Re-worked based on feedback

* Use dependency injection

* Only call new constructor

---------

Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com>
(cherry picked from commit fd9c1a0e9c)
2025-01-14 13:57:00 +01:00
f3f57f9748 Improve YSOD rendering in the client (#17940)
* Conditionally render error details and make sure something is rendered

* V13 QA updated locator for failing E2E test (#17825)

* Added timeouts

* Nullcheck user in Content permission handlers (#17846)

---------

Co-authored-by: Sven Geusens <sge@umbraco.dk>
Co-authored-by: Niels Lyngsø <nsl@umbraco.dk>

---------

Co-authored-by: Andreas Zerbst <73799582+andr317c@users.noreply.github.com>
Co-authored-by: Niels Lyngsø <nsl@umbraco.dk>
2025-01-14 13:38:07 +01:00
Nikolaj GeisleandGitHub 85d16a4354 V13: Show validation error, when saving invalid media (#17961)
* Don't save invalid media

* Revert "Don't save invalid media"

This reverts commit c4b6acf542.

* Still allow save, but show validation error, not popup error

* Add clarifying comment
2025-01-14 11:18:36 +01:00
Kenn JacobsenandGitHub c0517c3562 Allow filtering for multiple content types (#17960) 2025-01-14 10:40:45 +01:00
92e535a1c9 V13 QA updated locator for failing E2E test (#17825)
* Added timeouts

* Nullcheck user in Content permission handlers (#17846)

---------

Co-authored-by: Sven Geusens <sge@umbraco.dk>
Co-authored-by: Niels Lyngsø <nsl@umbraco.dk>
2025-01-14 09:12:41 +01:00
Kenn JacobsenandGitHub 7411c2552b Added integration test to verify the Delivery API contract (#17949) 2025-01-13 15:15:37 +01:00
Andy ButlandandGitHub f2410f376e Corrected property names and structure used in JSON schema generation for configuration of default data to install. (#17946) 2025-01-13 11:05:51 +01:00
Kenn JacobsenandGitHub a9313c1367 Make the Delivery API "ancestors" selector work with preview (#17938) 2025-01-13 07:47:14 +01:00
Nikolaj GeisleandGitHub 58d6404ec8 V14: Reintroduce BackOfficeUserManagerAuditer (#17349)
* Reintroduce BackOfficeUserManagerAuditer

* Cleanup legacy code

* Notify user logout on logout
2025-01-09 21:14:24 +01:00
Nikolaj GeisleandJacob Overgaard 53a5813189 V15: Dont create invalid media (#17534)
* Don't allow create when there is validation errors

* Fix tests

* Add tests

* Fix last test

* Fix more tests

---------

Co-authored-by: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com>
2025-01-09 12:21:54 +01:00
Nikolaj Geisleandkjac 5b3b9d770a V13: Add rcl files to static files tree (#17671)
* Refactor to use IWebhostEnvironment for wwwroot files.

* Obsolete ctor

* Add ActivatorUtiiliesConstructor

* Refactor logic to include folders

* Fix so we can see files in wwwroot/App_Plugins

* Cosmetic: Use invariant comparison for the "umbraco" folder".

---------

Co-authored-by: kjac <kja@umbraco.dk>
2025-01-09 10:02:34 +01:00
4a54215fa1 V13: Add rcl files to static files tree (#17671)
* Refactor to use IWebhostEnvironment for wwwroot files.

* Obsolete ctor

* Add ActivatorUtiiliesConstructor

* Refactor logic to include folders

* Fix so we can see files in wwwroot/App_Plugins

* Cosmetic: Use invariant comparison for the "umbraco" folder".

---------

Co-authored-by: kjac <kja@umbraco.dk>
2025-01-09 09:36:32 +01:00
Sven Geusensandnikolajlauridsen 52fbb34ddd Nullcheck user in Content permission handlers (#17846) 2025-01-07 15:07:42 +01:00
Sven GeusensandGitHub c96dc7eaa1 Nullcheck user in Content permission handlers (#17846) 2025-01-07 14:52:40 +01:00
Andy Butland 0166727eee Bumped version to 14.3.2. 2025-01-07 10:13:02 +01:00
Jacob Overgaard f19409ba5b Revert "bump version"
This reverts commit 679c42ef90.
2025-01-06 12:51:10 +01:00
Jacob Overgaard 679c42ef90 bump version 2025-01-06 12:46:03 +01:00
Nikolaj GeisleandGitHub 77b6e6e6d9 override PerformGetAll (#17822) 2024-12-16 13:35:02 +01:00
Elitsa MarinovskaandGitHub 32d0cb477e V14: Adding the ability to conditionally serialize version bound properties for the Delivery API (#16731)
* Property level versioning for the Delivery API using a custom System.Text.Json resolver

* Adding a converter base class that custom converters can implement

* Revert resolver

* Use IHttpContextAccessor for the API version

* Fix attribute and checks in ShouldIncludeProperty

* Fix enumeration

* Fix comment

* Unit tests

* Refactoring

* Remove Assert.Multiple where no needed
2024-12-16 11:34:55 +01:00
Nikolaj GeisleandElitsa d451390d78 V13: Clear username cache (#17815)
* Clear member username cache on delete

* Also refresh cache on update
2024-12-16 09:56:54 +02:00
Elitsa MarinovskaandGitHub 3304c6f472 Enable anonymous calls to HandleLogout action (#17807) 2024-12-16 07:26:12 +01:00
Nikolaj GeisleandGitHub 34f09fa260 V13: Clear username cache (#17815)
* Clear member username cache on delete

* Also refresh cache on update
2024-12-16 08:18:30 +02:00
Jacob Overgaard fd4efd38cd Merge branch 'v13/dev' into release/13.6 2024-12-13 10:07:46 +01:00
Jacob Overgaard e4e9bfc3e9 bump version to 13.6.0-rc2 2024-12-13 09:58:13 +01:00
Jacob Overgaard 3774dbe38c Merge remote-tracking branch 'origin/v13/contrib' into v13/dev 2024-12-13 09:56:46 +01:00
Dan ListerandJacob Overgaard 4ef50642ad Fixed an issue where bold markdown would not work inside a property description 2024-12-13 09:53:36 +01:00
Bjarke BergandGitHub cb88cbba43 Merge pull request #17761 from umbraco/v14/task/media-picker-min-max-validation
V14 min/max validator for mediapicker
2024-12-13 08:28:13 +01:00
Bjarke BergandGitHub f95fb78022 Merge pull request #17800 from umbraco/v13/bugfix/corrupt-examine-dashboard
Make sure Examine dashboard still functions when an index is corrupt
2024-12-12 12:07:06 +01:00
Sven Geusens 0f9a8bde61 Broadend the error catching when building the examine dashboard 2024-12-12 09:38:35 +01:00
Sven Geusens 129d369c1a Version bump to release 2024-12-11 13:46:38 +01:00
Sven Geusens dd2c591a5b Merge branch 'release/13.6' into v13/dev 2024-12-11 13:45:35 +01:00
Nhu DinhandGitHub e9758321ce V14 Added Content tests with Approved Color (#16725)
* Added Content tests with Approved Color

* Bumped version of test helper

* Make Content tests run in the pipeline

* Using custom datatype builder instead of changing the default approved color data type

* Updated variables

* Fixed comment

* Make all Content tests run in the pipeline

* Reverted
2024-12-10 14:52:46 +07:00
Nikolaj GeisleandGitHub 82e58eae3e V14: cherry pick #17656 (#17763)
* V15: Fix webhook RTE serialization (#17656)

* Make base type resolver class

* Add new webhook serializer

* fix comment

* Update src/Umbraco.Infrastructure/Serialization/ContentJsonTypeResolverBase.cs

Co-authored-by: Elitsa Marinovska <21998037+elit0451@users.noreply.github.com>

* Update src/Umbraco.Core/Services/WebhookRequestService.cs

---------

Co-authored-by: Elitsa <elm@umbraco.dk>
Co-authored-by: Elitsa Marinovska <21998037+elit0451@users.noreply.github.com>
(cherry picked from commit 6f081785e6)

* Add back missing usings
2024-12-09 16:09:54 +01:00
Sven Geusens de78560343 Fix unit test mocks 2024-12-09 15:08:22 +01:00
Sven Geusens f569553d79 Added min/max validator for mediapicker3 2024-12-09 14:26:14 +01:00
Kenn JacobsenandNikolaj Geisle 16749a724d Add (un)publishing details to TreeChange notifications (#17757)
(cherry picked from commit 404a62aa0b)
2024-12-09 11:42:53 +01:00
Kenn JacobsenandNikolaj Geisle a627930b58 Warn about un-routable content at publish time (#17705)
(cherry picked from commit 2d9cfc880b)
2024-12-09 11:42:46 +01:00
Kenn JacobsenandGitHub 404a62aa0b Add (un)publishing details to TreeChange notifications (#17757) 2024-12-09 11:36:48 +01:00
Kenn JacobsenandGitHub 2d9cfc880b Warn about un-routable content at publish time (#17705) 2024-12-09 11:07:32 +01:00
Kenn JacobsenandGitHub ee8bdfc4ac Add "user info" endpoint for the Delivery API (#17719)
* Add "user info" endpoint for the Delivery API

* Add comment about public and unsealed class
2024-12-09 09:57:21 +01:00
f048cfe62d v14: DecimalConverter - add check for integer (#17679)
* Add check for integer

* Fix typo

---------

Co-authored-by: Elitsa <elm@umbraco.dk>
2024-12-04 15:18:12 +01:00
Nikolaj GeisleandElitsa 9cab0e8bb1 Add content to TreeAccessTemplate (#17694) 2024-12-04 11:08:57 +01:00
Bjarke BergandGitHub b55d484863 Updated NuGet Packages for v13 (#17704)
* Update nuget packages

* Cherry pick fixes from v15

* Fixed: Projects that use central package version management should not define the version on the PackageReference items but on the PackageVersion items

* rollback update in tool
2024-12-03 08:51:23 +01:00
Ronald BarendseandGitHub ed0058a503 Fix PanicException: failed to get child with id= after updating content types (#17702)
* Remove tree node from hierarchy after clearing branch

* Refactor ClearBranchLocked to split clearing child and siblings
2024-12-03 08:00:18 +01:00
21e3518a69 Fix copy document with descendants onto self (#17661)
* Filter self out of descedants for self onto self copy

* Optimized code

---------

Co-authored-by: Bjarke Berg <mail@bergmania.dk>
2024-12-01 13:02:47 +01:00
Jacob OvergaardandGitHub b0aed393d6 fix: keep the lead char when editing an existing link (#17667) 2024-11-28 14:04:59 +00:00
Sven Geusens 28756d449b Rectify v13 in v14 merge 2024-11-27 14:35:22 +01:00
Sven Geusens 38a96db369 Merge branch 'v13/dev' into v14/dev
# Conflicts:
#	src/Umbraco.Cms.Imaging.ImageSharp/ConfigureImageSharpMiddlewareOptions.cs
#	src/Umbraco.Core/Models/AuditItem.cs
#	src/Umbraco.Core/Routing/UmbracoRequestPaths.cs
#	src/Umbraco.Core/Services/ContentService.cs
#	src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditRepository.cs
#	src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaRepository.cs
#	src/Umbraco.Web.BackOffice/Controllers/ContentController.cs
#	src/Umbraco.Web.UI.Client/package-lock.json
#	src/Umbraco.Web.UI.Client/package.json
#	src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js
#	src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbtabbedcontent.directive.js
#	src/Umbraco.Web.UI.Client/src/common/directives/components/media/umbmedianodeinfo.directive.js
#	src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfiledropzone.directive.js
#	src/Umbraco.Web.UI.Client/src/common/services/mediahelper.service.js
#	src/Umbraco.Web.UI.Client/src/common/services/rte-blockeditor-clipboard.service.js
#	src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js
#	src/Umbraco.Web.UI.Client/src/common/services/user.service.js
#	src/Umbraco.Web.UI.Client/src/less/components/umb-media-grid.less
#	src/Umbraco.Web.UI.Client/src/views/components/media/umbimagepreview/umb-image-preview.html
#	src/Umbraco.Web.UI.Client/src/views/components/media/umbimagepreview/umbimagepreview.controller.js
#	src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js
#	src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.component.js
#	src/Umbraco.Web.UI.Client/src/views/templates/edit.html
#	src/Umbraco.Web.UI.Client~HEAD
#	src/Umbraco.Web.UI.Login/package-lock.json
#	src/Umbraco.Web.UI.Login/package.json
#	src/Umbraco.Web.UI.Login/src/auth.element.ts
#	src/Umbraco.Web.UI.Login/src/components/pages/login.page.element.ts
#	src/Umbraco.Web.UI.Login/src/index.ts
#	tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Login/login.spec.ts
#	tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UmbracoRequestPathsTests.cs
2024-11-27 14:04:41 +01:00
Sven Geusens a1ea203281 Merge branch 'v10/dev' into v13/dev
# Conflicts:
#	src/Directory.Packages.props
#	src/Umbraco.Infrastructure/Manifest/ManifestParser.cs
2024-11-26 21:08:22 +01:00
Kenn JacobsenandGitHub 4590739fa5 Add ASCII file name conversion (#17580) 2024-11-26 11:26:44 +01:00
7c617f2976 Revert #14234 add update error message for DB connection failures (#17612)
* Revert #14234

* Make the boot failure message more descriptive when unable to connect to DB

* Update src/Umbraco.Infrastructure/Runtime/RuntimeState.cs

Co-authored-by: Ronald Barendse <ronald@barend.se>

* Revert changes

* Obsolete InstallMissingDatabase from V16

---------

Co-authored-by: Ronald Barendse <ronald@barend.se>
2024-11-26 11:06:40 +01:00
Lee KelleherandGitHub 9141f61708 V13: Dropzone, upload complete callback with processed file array (#17631)
* Dropzone, upload complete callback with processed file array

* Media card: cosmetic fix for image border-radius

The image's square corners were poking out.
2024-11-25 15:51:16 +01:00
ba4120050f Add cache key to GetByUserName (#17350)
* Add cache key to GetByUserName

* Remove constants

* create new cache policy for member repository

---------

Co-authored-by: Elitsa <elm@umbraco.dk>
2024-11-25 15:27:22 +01:00
Nikolaj Geisle 772c523a23 Validate email for member models (#17532)
* Validate email for member models

* Add null check or more test cases

* return invalid when not a valid email

* Cleanup

* remove private method in favor of extension

* Remove non used, using statement

---------

Co-authored-by: Elitsa <elm@umbraco.dk>
(cherry picked from commit 6b0f8e7b7c)
2024-11-25 13:56:45 +01:00
Kenn JacobsenandGitHub cbd4dc6e0d Handle "all slashes" routes (#17596) 2024-11-22 09:06:10 +01:00
c7014e159b Sort manifest file paths alphabetically (#14466)
* Sort manifest file paths alphabetically

* Update src/Umbraco.Infrastructure/Manifest/ManifestParser.cs

Co-authored-by: Ronald Barendse <ronald@barend.se>

---------

Co-authored-by: Ronald Barendse <ronald@barend.se>
2024-11-21 16:19:48 +01:00
Sven Geusensandnikolajlauridsen 9febbc7db1 Distinguish between default zero and intentional zero sort order for new documents (#17517)
* Distinguish between default value and initial zero

* Update special value comment documentation

* Redid solution with dirty/new entity tracking

* rework copy branch sortorder fix

* Change == false to is false

---------

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>
2024-11-21 09:09:25 +01:00
Kenn JacobsenandGitHub ec8e10f406 Fix RTE console error when blocks are not available (#17582) 2024-11-21 08:53:59 +01:00
EalseandJacob Overgaard 3233b24a4e fix: infinite editor stuck in create action (#15945) 2024-11-20 10:00:50 +01:00
Anders BjernerandJacob Overgaard 5775e9cb18 Added fix for <umb-button-group /> label issues 2024-11-20 09:59:27 +01:00
MoleandGitHub 570005f5e1 Run both cms and package migrations in upgrader (#17575)
* Run both cms and package migrations in upgrader

* Use correct setting
2024-11-20 08:38:33 +01:00
1e9182cfa4 V14: Use decimal in slider property editor (#17568)
* Allow SliderPropertyEditor to use decimals

* Add tests

* Boyscout unittest update

---------

Co-authored-by: Sven Geusens <sge@umbraco.dk>
2024-11-19 11:14:53 +01:00
Bjarne FyrstenborgandLaura Neto fbaa3440f2 Don't shrink crops container on right side 2024-11-18 20:11:48 +01:00
Bjarne FyrstenborgandJacob Overgaard e662468ecc Set TinyMCE to readonly 2024-11-18 08:51:04 +01:00
Andreas ZerbstandGitHub 33eb4dd5a4 V14 QA updated playwright config (#17544)
* Only run windows SQL Server

* Updated timeout and retry count

* Updated condition

* Fixed indentation

* Skipped
2024-11-18 08:30:50 +01:00
Bjarne FyrstenborgandMatthew-Wise c8efc30654 Fallback value when setttings element type doesn't exists as it can have reference, but no available element type 2024-11-17 17:05:28 +00:00
Bjarke Berg d7b98a2f00 Use catch all for urls what ends with a path (#17468)
(cherry picked from commit 3c0f7a03ba)
2024-11-14 12:29:40 +01:00
Sven Geusensandnikolajlauridsen 9edd21a3b0 Made some membertype endpoints available for member related actions (#17440)
Co-authored-by: nikolajlauridsen <nikolajlauridsen@protonmail.ch>
2024-11-13 13:47:37 +01:00
Andreas ZerbstandGitHub 7010ff1d26 V14 QA added rich text editor with stylesheet test (#17449)
* Rte tests

* Cleaned up

* Bumped version

* Added ensure not exists
2024-11-11 10:47:41 +01:00
Andreas ZerbstandGitHub 6acdf21eab V14 QA user groups permissions tests (#17429)
* Added tests for userGroup

* Clean up

* Updated userGroup tests

* Updated tests

* Updated tests

* Cleane up

* Cleaned up

* Bumped versions

* Run user tests

* Cleaned up

* Added permission tests

* Added tests, not done

* Updated tests

* Fixed tests

* Cleaned up

* Bumped version

* More cleanup

* Run user tests

* Added wait

* Added tests and cleaned up naming

* Bumped versions

* Reverted smokeTest command
2024-11-11 09:45:22 +01:00
Andreas ZerbstandGitHub a5479bc96e V14 QA members section user tests (#17448)
* Added tests for the members

* Cleaned up tests

* Bumped version

* Removed skip
2024-11-11 09:43:57 +01:00
marwaldvandJacob Overgaard 79d117026f Add swiss languages as backoffice languages
Add missing backoffice languages as properties etc. are translated via translation section (e.g. with "#Backoffice Settings"). At the moment the swiss languages always need to be added via the umbraco/Config folder.
2024-11-09 12:29:44 +01:00
Casper LundgreenandGitHub 2709054bdb Fix NameIdentifier is not an integer. (TryParse implemented) (#17414)
Issue: 17383
2024-11-08 13:27:53 +01:00
Sven Geusensandnikolajlauridsen 0acdd26850 Made some stylesheet endpoints available for document/media/member related actions (#17442) 2024-11-08 12:19:10 +01:00
MoleandGitHub 2d4230c001 Include create date in audit item (#17447) 2024-11-08 08:58:38 +01:00
Nhu DinhandGitHub 2d027ce9a1 V14 QA Added acceptance tests for rendering content with content picker (#17378)
* Added tests for rendering content with content picker

* Bumped version

* Make all the tests for rendering content run in the pipeline

* Reverted
2024-11-06 16:09:37 +07:00
Andreas ZerbstandGitHub b1d9085c83 V14 QA Added user groups acceptance tests (#17344)
* Added tests for userGroup

* Clean up

* Updated userGroup tests

* Updated tests

* Updated tests

* Cleane up

* Cleaned up

* Bumped versions

* Run user tests

* Cleaned up

* Added method for checking if the document tree is empty

* Bumped version

* Reverted
2024-10-28 11:11:28 +01:00
Nhu DinhandGitHub 41da2e2cf3 V14 Added acceptance tests for the List View Media and custom data type in Content section (#17025)
* Added Content tests with custom data type

* Added tests for List View Media data type in Media section

* Updated method name due to api helper changes

* Updated the assertion of Content tests with custom data type

* Bumped version of test helper

* Make all Content tests run in the pipeline

* Skipped test for code editor as it is removed

* Fixed comment

* Make Media tests running in the pipeline

* Bumped version

* Updated code due to ui helper changes

* Bumped version of test helper

* Updated tests for bulk trash in the media section

* Fixed notification message

* Make Content tests and Media tests run in the pipeline

* Added more waits

* Reverted
2024-10-28 10:53:33 +07:00
Nhu DinhandGitHub 76fcf19b15 V14 QA Added acceptance tests for rendering content with Dropdown, Radiobutton and ImageCropper (#17357)
* Added tests for rendering content with numeric

* Added tests for rendering content with textarea

* Added tests for rendering content with approved color

* Added tests for rendering content with numeric

* Added tests for rendering content with tags

* Added tests for rendering content with textarea

* Updated tests for rendering content with textstring due to test helper changes

* Added tests for rendering content with truefalse

* Bumped version of test helper

* Make all tests for rendering content run in the pipeline

* Fixed comments

* Removed blank lines

* Fixed name

* Make all smoke tests run in the pipeline

* Added tests for rendering content with dropdown

* Added tests for rendering content with Image Cropper - not done

* Updated tests for rendering content due to ui helper changes

* Updated tests for rendering content with image cropper

* Updated tests due to the api helper changes

* Bumped version of test helper

* Make all the tests for rendering content run in the pipeline

* Removed blank lines

* Format code

* Fixed test name

* Reverted
2024-10-25 18:19:53 +07:00
Nhu DinhandGitHub 11ccafeb97 V14 QA Added tests for rendering content with checkboxlist and date picker (#17332)
* Added tests for rendering content with numeric

* Added tests for rendering content with textarea

* Added tests for rendering content with approved color

* Added tests for rendering content with numeric

* Added tests for rendering content with tags

* Added tests for rendering content with textarea

* Updated tests for rendering content with textstring due to test helper changes

* Added tests for rendering content with truefalse

* Added tests for rendering content with checkbox list

* Added tests for rendering content with date picker - not done

* Updated tests for rendering content with date picker

* Updated tests for rendering content due to ui helper changes

* Bumped version

* Removed blank lines

* Make Rendering Content tests run in the pipeline

* Changed method name due to test helper changes

* Reverted
2024-10-25 15:16:28 +07:00
Nikolaj GeisleandGitHub aa9f194d76 Format sql statement (#17354) 2024-10-24 14:45:23 +02:00
Nikolaj GeisleandZeegaan 73d70ba9fc Swap to windows vm for build (#17348)
(cherry picked from commit 199a2f4619)
2024-10-24 09:47:59 +02:00
Zeegaan 6435ff0bb3 Merge remote-tracking branch 'origin/release/14.3' into v14/dev
# Conflicts:
#	version.json
2024-10-23 09:03:05 +02:00
Zeegaan f7729508cf Merge branch 'release/13.5' into v13/dev
# Conflicts:
#	version.json
2024-10-23 08:57:30 +02:00
Zeegaan 31e1acce67 Merge remote-tracking branch 'origin/release/10.8.7' into v10/dev
# Conflicts:
#	version.json
2024-10-23 08:48:08 +02:00
Andreas ZerbstandGitHub aa1f3df76b Updated to match locator (#17334) 2024-10-23 08:37:24 +02:00
Andreas ZerbstandGitHub 728dc89909 V14 QA Skip Users tests on Sqlite (#17330)
* Split sqlite test because we run into db locks

* Uses the new command
2024-10-22 13:12:42 +02:00
Nhu DinhandGitHub e36dc1f554 V14 QA Added the acceptance tests for rendering content with different value (#17293)
* Added tests for rendering content with numeric

* Added tests for rendering content with textarea

* Added tests for rendering content with approved color

* Added tests for rendering content with numeric

* Added tests for rendering content with tags

* Added tests for rendering content with textarea

* Updated tests for rendering content with textstring due to test helper changes

* Added tests for rendering content with truefalse

* Bumped version of test helper

* Make all tests for rendering content run in the pipeline

* Fixed comments

* Removed blank lines

* Fixed name

* Make all smoke tests run in the pipeline
2024-10-22 10:26:59 +07:00
Sven GeusensandNikolaj Geisle 3431f76320 Prevents XSS when viewing an uploaded SVG
from the media-info and image-preview components.
2024-10-21 12:50:12 +02:00
ElitsaandNikolaj Geisle 1bc5466a8d Update the HttpContext's user with the authenticated user's principal 2024-10-21 12:45:38 +02:00
ElitsaandNikolaj Geisle d83daf44d3 Reduce the time when getRemainingTimeoutSeconds request is made from 30s to 20s, so fewer calls are made 2024-10-21 12:44:40 +02:00
ElitsaandNikolaj Geisle 2ad5ecd5d5 Make sure that the client shows the login screen as close to the server's timout time as possible 2024-10-21 12:44:39 +02:00
Sven GeusensandNikolaj Geisle 35c51a029a Prevents XSS when viewing an uploaded SVG
from the media-info and image-preview components.
2024-10-21 12:42:06 +02:00
ElitsaandNikolaj Geisle 8c1128c85b Update the HttpContext's user with the authenticated user's principal 2024-10-21 12:41:42 +02:00
ElitsaandNikolaj Geisle c9021ab2d2 Reduce the time when getRemainingTimeoutSeconds request is made from 30s to 20s, so fewer calls are made 2024-10-21 12:41:18 +02:00
ElitsaandNikolaj Geisle 67a71f8f82 Make sure that the client shows the login screen as close to the server's timout time as possible 2024-10-21 12:41:17 +02:00
Andreas ZerbstandAndreas Zerbst 6caf53ed2e Updated version of messagepack (#17321)
(cherry picked from commit 8a22672c7f)
(cherry picked from commit 6399f235f2)
2024-10-21 11:54:26 +02:00
Andreas ZerbstandAndreas Zerbst 97d74f39e7 Updated version of messagepack (#17321)
(cherry picked from commit 8a22672c7f)
2024-10-21 11:49:13 +02:00
Andreas ZerbstandAndreas Zerbst edd0a4a4a9 Updated message pack (#17320)
(cherry picked from commit 11270eaaf5)
2024-10-21 11:46:43 +02:00
Andreas ZerbstandAndreas Zerbst 6399f235f2 Updated version of messagepack (#17321)
(cherry picked from commit 8a22672c7f)
2024-10-21 11:17:50 +02:00
Andreas ZerbstandGitHub 8a22672c7f Updated version of messagepack (#17321) 2024-10-21 11:15:59 +02:00
Andreas ZerbstandGitHub 11270eaaf5 Updated message pack (#17320) 2024-10-21 11:00:47 +02:00
Justin NevilleandGitHub 1e32d59ecb Query for media using the full file path as well as the original file path for files that contain the sizes in the file name (i.e. image_200x200.jpg) (#17314) 2024-10-21 09:53:17 +02:00
Bjarke BergandGitHub 4be183a20d Do not rely on NuCache to do key/id lookups (#17291)
* Fixes #17182

* Ensure wait-on is installed
2024-10-21 09:49:34 +02:00
Kenn JacobsenandGitHub eab27123dd MNTP: Re-initialize contextual dialog options upon content creation (#17301) 2024-10-18 09:39:29 +02:00
Kenn JacobsenandGitHub a6253957c8 MNTP: Improve site and root context for dynamic root (#17303) 2024-10-18 09:17:17 +02:00
Jacob OvergaardandGitHub a8f5631144 V13: Update @umbraco-ui/uui to 1.11.0 (#17281)
* build(deps): update @umbraco-ui/uui from 1.7.1 to 1.11.0

* fix: umb-login-element no attributes

Cherry-picked a fix from V14 where the custom login input component was no longer needed, which was fixed because it errors out. This simplifies the login form.

* cherry-pick code to handle 'enter' click from v14
2024-10-17 10:25:02 +01:00
Andreas ZerbstandGitHub ba1080541b Updated to string.empty (#17294) 2024-10-17 09:59:41 +02:00
Jacob Overgaard 1581eb61d3 V15: Rich Text Editor links do not work with query strings and anchors (#17288)
* fix: anchors and query strings do not work

Since the change from UDIs to localLinks in href, the pattern matched a little too much in the href section completely ignoring any "extras" such as querystrings and anchors after the locallink, which meant that the locallink did not get replaced at all if they were present. This is fixed by limiting the regexp a bit.

* fix: legacy links do not follow the same regexp as new links

Because we are no longer matching the whole `href` attribute but only some of its contents, we need to fix up the old pattern. It has been extended with matching groups that follow the same pattern as the new links.

* feat: allow a-tags to be multiline

example:

```html
<a
  type="document"
  href="/{localLink:<GUID>}">
Test
</a>
```

* fix: split regex into two parts: first a tokenizer for a-tags and then a type-finder

* fix: ensure only "document" and "media" are matching to speed up the pattern

* feat: allow a-tags to be multiline

(cherry picked from commit 35e8f2e460)
2024-10-17 09:06:48 +02:00
Jason ElkinandNikolaj Geisle 6d98162e19 Unlock form after unsuccessful save and publish. (#17285)
(cherry picked from commit ee37ad0f4b)
2024-10-16 15:09:31 +02:00
Jason ElkinandGitHub ee37ad0f4b Unlock form after unsuccessful save and publish. (#17285) 2024-10-16 15:08:37 +02:00
Andreas ZerbstandGitHub 2b3a91757d V14 QA added user permission tests (#17226)
* Added test

* Small changes

* Added content start node tests

* Added media start node tests

* Cleaned up

* More updates

* Cleaned up

* Added wait

* Cleaned up

* Bumped helpers

* Updated to run user tests

* Fixed user tests

* Bumped helpers

* Added missing semicolon

* Fixes based on comments

* Run smoke tests
2024-10-16 13:26:36 +02:00
Andreas ZerbstandGitHub 00563013b6 V14 QA Updated package E2E tests (#17236)
* Updated tests

* Cleaned package tests

* Updated package test file

* Bumped version

* Added missing semicolons

* Run all smoke tests

* Run smoke tests
2024-10-16 12:46:24 +02:00
Ronald BarendseandBjarke Berg 378d4ecfef Fix ContentStore locking exceptions in async code (#17246)
* Add ContentCache test

* Use SemaphoreSlim as write lock

* Apply lock imrpovements to SnapDictionary

* Obsolete unused MonitorLock

(cherry picked from commit c3db3457e7)
2024-10-16 12:34:44 +02:00
Andreas ZerbstandBjarke Berg a497f3f4dd Updated nuget package (#17286)
(cherry picked from commit 5183391a86)
2024-10-16 12:24:31 +02:00
Bjarke BergandGitHub 2d71b5a63b Updated image sharp to a non vulnerable version (#17290) 2024-10-16 12:16:38 +02:00
Bjarke BergandGitHub f4f83bccbe Added an explicit dependency to Microsoft.Extensions.Caching.Memory to force it to use a non-vulnerable version (#17287) 2024-10-16 10:25:17 +02:00
Andreas ZerbstandGitHub 5183391a86 Updated nuget package (#17286) 2024-10-16 10:07:25 +02:00
Nhu DinhandGitHub 0551976126 V14 QA Added acceptance tests for notification message (#17195)
* Updated step to verify the notification message

* Added tests for notification message

* Bumped version

* Updated expected notification message

* Bumped version
2024-10-14 13:26:55 +07:00
Nhu DinhandGitHub da2a4d1713 V14 QA Added the acceptance tests for rendering content with textstring (#17247)
* Added tests for textstring in the rendered content

* Updated tests for rendering content with textstring

* Added tests for rendering content with numeric

* Added tests for rendering content with textarea

* Removed tests

* Bumped version

* Make all tests for rendering content run in the pipeline

* Make all smoke tests run in the pipeline
2024-10-14 11:57:52 +07:00
Nhu DinhandGitHub 9f5867bdf8 V14 QA Fixing the failing acceptance tests in the pipeline v14 (#17214)
* Added more waits

* Updated assert steps

* Fixed api method name

* Added more waits to avoid the failing test in window

* Updated tests due to business changes

* Added more waits to avoid the failing tests in window

* Updated test due to Ui changes

* Bumped version

* Bumped version of test helper

* Bumped version
2024-10-11 12:15:49 +02:00
Ronald BarendseandBjarke Berg 30b114d538 Fix ContentStore locking exceptions in async code (#17246)
* Add ContentCache test

* Use SemaphoreSlim as write lock

* Apply lock imrpovements to SnapDictionary

* Obsolete unused MonitorLock

(cherry picked from commit c3db3457e7)
2024-10-11 09:47:31 +02:00
Ronald BarendseandGitHub c3db3457e7 Fix ContentStore locking exceptions in async code (#17246)
* Add ContentCache test

* Use SemaphoreSlim as write lock

* Apply lock imrpovements to SnapDictionary

* Obsolete unused MonitorLock
2024-10-11 09:45:01 +02:00
Sven GeusensandGitHub 7787af2df1 Fix install url detection (#17241) 2024-10-10 18:09:11 +02:00
Wojciech WróbelandJacob Overgaard 3730109cce Fixed issue #17225. The 'code' html element added as valid to RTE configuration 2024-10-09 14:19:38 +02:00
Nikolaj Geisle b814608c06 Bump version 2024-10-08 12:34:23 +02:00
Jacob Overgaard ed63b51e46 update backoffice submodule 2024-10-07 15:46:21 +02:00
Jacob Overgaard ea608cc630 update backoffice submodule 2024-10-07 15:46:06 +02:00
Jacob Overgaard 4cfe498628 update backoffice submodule 2024-10-07 13:45:04 +02:00
Jacob Overgaard 42912dd5c9 update backoffice submodule 2024-10-07 13:44:18 +02:00
Nikolaj Geisle 9bab74d30e Bump version.json 2024-10-07 11:55:34 +02:00
Nikolaj Geisle 865787db1d Bump version.json 2024-10-07 11:48:41 +02:00
Bjarke Berg c58a7b7b23 Merge remote-tracking branch 'origin/release/14.3' into v14/dev 2024-10-03 07:50:18 +02:00
Bjarke Berg 32c00f9a09 Merge remote-tracking branch 'origin/release/14.3' into release/14.3 2024-10-01 17:40:35 +02:00
Bjarke Berg 348f1f2bae Change webhook permissions to require webhook tree access for all endpoints 2024-10-01 07:58:41 +02:00
Jacob Overgaard 73c8d8e0f9 Merge remote-tracking branch 'origin/release/14.3' into release/14.3 2024-09-30 10:24:17 +02:00
Jacob Overgaard 2d7c00f27f update backoffice submodule 2024-09-30 10:24:03 +02:00
Andreas ZerbstandGitHub 4fae91d55c V14 QA added content tests with list view content (#17115)
* Added tests for List view

* More tests

* Added rest of test

* Bumped version

* Fixed failing tests

* Added tests and fixed comments

* Cleaned up tests

* Bumped testhelpers

* Bumped again

* Set condition to only run sql server when enabled

* Run all content test

* Reverted changes
2024-09-27 17:32:44 +02:00
Sebastiaan Janssen 6c08f69801 Merge branch 'contrib' into v14/dev 2024-09-27 09:04:32 +02:00
Bjarke Berg f2e4e44db1 Merge remote-tracking branch 'origin/release/14.3' into v14/dev 2024-09-27 07:47:59 +02:00
Bjarke Berg d57d12d54d Fixed imagesharp 2 also 2024-09-27 07:43:12 +02:00
NguyenThuyLanandBjarke Berg 46604909be Fix error format code (#17146)
* update ImageSharpMiddlewareOption for fixing invalid width and height (#17126)

Co-authored-by: Lan Nguyen Thuy <lnt@umbraco.dk>

* Fix issue format parameters

---------

Co-authored-by: Lan Nguyen Thuy <lnt@umbraco.dk>

(cherry picked from commit 9a12eea495)
2024-09-27 07:42:30 +02:00
9a12eea495 Fix error format code (#17146)
* update ImageSharpMiddlewareOption for fixing invalid width and height (#17126)

Co-authored-by: Lan Nguyen Thuy <lnt@umbraco.dk>

* Fix issue format parameters

---------

Co-authored-by: Lan Nguyen Thuy <lnt@umbraco.dk>
2024-09-27 07:41:55 +02:00
NguyenThuyLanandBjarke Berg 762d72b018 update ImageSharpMiddlewareOption for fixing invalid width and height (#17126)
Co-authored-by: Lan Nguyen Thuy <lnt@umbraco.dk>
(cherry picked from commit 9b19d63a6a)
2024-09-27 07:40:42 +02:00
Nhu DinhandGitHub 45f43a6b7a V14 Added Content tests with different document types properties (#17131)
* Added tests for Allow At Root property

* Added Content tests for Allowed Child Nodes property

* Added Content tests for the Allow at root property

* Added Content tests for the Allowed child node property

* Added Content tests for the Collection property

* Added Content tests with allow vary by culture

* Added more waits

* Updated tests due to api helper changes

* Added Content tests with allowed templates

* Bumped version of test helper

* Updated code due to api helper changes

* Fixed naming
2024-09-27 10:36:15 +07:00
Jacob Overgaard c9c9374de1 update backoffice submodule 2024-09-26 14:39:12 +02:00
Bjarke Berg f1cddd91c6 Missing context complete
(cherry picked from commit fdb9cfa3e7)
2024-09-26 09:55:19 +02:00
Bjarke Berg 63a487bc0b Merge remote-tracking branch 'origin/v13/dev' into v13/dev 2024-09-26 07:54:43 +02:00
Ronald BarendseandBjarke Berg a40eadcfce Add RemoveDefault() extension method to fluent API for CMS webhook events (#15424)
* Add RemoveDefault extension method

* Move default webhook event types to single list

(cherry picked from commit 8f26263178)
2024-09-26 07:54:26 +02:00
9b19d63a6a update ImageSharpMiddlewareOption for fixing invalid width and height (#17126)
Co-authored-by: Lan Nguyen Thuy <lnt@umbraco.dk>
2024-09-25 13:19:09 +02:00
Terence BurridgeandSebastiaan Janssen 6939472f37 Update valid reasons not to have a template on a content node to include having a redirect field
Cherry-picked from: 385a5345b1
2024-09-23 14:30:13 +02:00
Sebastiaan Janssen 0387307301 Merge branch 'v13/dev' into v13/contrib 2024-09-23 14:27:48 +02:00
Bjarke Berg 25a5dde8e9 Merge remote-tracking branch 'origin/release/13.5' into v13/dev
# Conflicts:
#	version.json
2024-09-18 12:55:35 +02:00
Bjarke Berg 2e068bb73e Merge remote-tracking branch 'origin/release/13.5' into release/13.5 2024-09-17 14:51:05 +02:00
Bjarke Berg 842cacde19 Bump version 2024-09-17 14:50:57 +02:00
Dennis ÖhmanandSebastiaan Janssen 6a453a0910 v13.5 - New Swedish translation crashes Umbraco, removed duplicate areas. (#17059)
Cherry-picked from d64bf5de22
2024-09-17 14:04:22 +02:00
Sebastiaan Janssen ff9903b14d Merge branch 'v13/contrib' of https://github.com/umbraco/Umbraco-CMS into v13/contrib 2024-09-17 13:58:37 +02:00
Dennis ÖhmanandGitHub d64bf5de22 v13.5 - New Swedish translation crashes Umbraco, removed duplicate areas. (#17059) 2024-09-17 13:56:58 +02:00
Sebastiaan Janssen 9300d867fb Merge branch 'v13/dev' into v13/contrib 2024-09-17 13:12:04 +02:00
Niels LyngsøandGitHub eb0f8b5c24 fire change event (#17078) 2024-09-17 10:06:16 +02:00
Niels LyngsøandGitHub d58768f26d utilize lock unlock events for readonly mode while saving (#17077) 2024-09-17 09:52:00 +02:00
Niels LyngsøandGitHub 3667217053 only resolve if value is present (#17070) 2024-09-17 09:51:47 +02:00
3e6116fcba No longer shows success message if content moving is cancelled (#15051)
* Fix for issue https://github.com/umbraco/Umbraco-CMS/issues/13923
 - Added AttemptMove method to the ContentService
 - Updated ContentController PostMove method to return ValidationProblem whenever the node is not moved

* Align changes with V14 solution. Make it non breaking.

---------

Co-authored-by: Laura Neto <12862535+lauraneto@users.noreply.github.com>
2024-09-05 14:08:48 +02:00
Sebastiaan Janssen bff293213d Merge branch 'v13/dev' into v13/contrib 2024-09-03 11:01:39 +02:00
a47a1775f2 Prevent templates being editable when using Production runtime mode (#16923)
Co-authored-by: Nick Hoang <nick@codeberwery.com>
Co-authored-by: Jason Elkin <jason@bumpdigital.co.uk>
2024-09-03 00:54:32 +01:00
Jason Elkin b2a2df7a8a Ignore Visual Studio's generated launchSettings file. 2024-08-28 14:08:52 +01:00
7473 changed files with 38142 additions and 379356 deletions
-1
View File
@@ -46,7 +46,6 @@
*.xml text=auto
*.resx text=auto
*.yml text eol=lf core.whitespace whitespace=tab-in-indent,trailing-space,tabwidth=2
*.sh eol=lf
*.csproj text=auto merge=union
*.vbproj text=auto merge=union
+62 -34
View File
@@ -4,8 +4,8 @@
In order to use Umbraco as a CMS and build your website with it, you should not build it yourself. If you're reading this then you're trying to contribute to Umbraco or you're debugging a complex issue.
- 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?
- 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.
@@ -13,6 +13,17 @@ If the answer is yes, please read on. Otherwise, make sure to head on over [to t
↖️ You can jump to any section by using the "table of contents" button ( ![Table of contents icon](img/tableofcontentsicon.svg) ) above.
## Getting Started:
To run umbraco, we first need to initialize the client git submodule:
* Execute `git submodule update --init` to get the files into Umbraco.Web.UI.Client project
* If you are going to work on the Backoffice, you can either go to the Umbraco.Web.UI.Client folder and check out a new branch or set it up in your IDE, which will allow you to commit to each repository simultaneously:
* **Rider**: Preferences -> Version Control -> Directory Mappings -> Click the '+' sign
* If you get a white page delete Umbraco.Cms.StaticAssets\wwwroot\umbraco folder and run `npm ci && npm run build:for:cms` inside Umbraco.Web.UI.Client folder to clear out any leftover files from older versions.
### Latest version
* If you want to get the latest changes from the client repository, run `git submodule update` again which will pull the latest main branch.
## Debugging source locally
Did you read ["Are you sure"](#are-you-sure)?
@@ -21,34 +32,29 @@ Did you read ["Are you sure"](#are-you-sure)?
If you want to run a build without debugging, see [Building from source](#building-from-source) below. This runs the build in the same way it is run on our build servers.
> [!NOTE]
> The caching for the back office has been described as 'aggressive' so we often find it's best when making back office changes to [disable caching in the browser (check "Disable cache" on the "Network" tab of developer tools)][disable browser caching] to help you to see the changes you're making.
#### Debugging with VS Code
In order to build the Umbraco source code locally with Visual Studio Code, first make sure you have the following installed.
- [Visual Studio Code](https://code.visualstudio.com/)
- [dotnet SDK v9+](https://dotnet.microsoft.com/en-us/download)
- [Node.js v20+](https://nodejs.org/en/download/)
- npm v10+ (installed with Node.js)
- [Git command line](https://git-scm.com/download/)
* [Visual Studio Code](https://code.visualstudio.com/)
* [dotnet SDK v7+](https://dotnet.microsoft.com/en-us/download)
* [Node.js v14+](https://nodejs.org/en/download/)
* npm v7+ (installed with Node.js)
* [Git command line](https://git-scm.com/download/)
Open the root folder of the repository in Visual Studio Code.
To build the front end you'll need to open the command pallet (<kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>P</kbd>) and run `>Tasks: Run Task` followed by `Client Build`.
To build the front end you'll need to open the command pallet (<kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>P</kbd>) and run `>Tasks: Run Task` followed by `Client Watch` and then run the `Client Build` task in the same way.
You can also run the tasks manually on the command line:
```
cd src\Umbraco.Web.UI.Client
npm i
npm run build:for:cms
npm run dev
```
If you just want to watch the UI Client to `Umbraco.Web.UI` then instead of running `build:for:cms`, you can do: `npm run dev:mock`. This will launch the Vite dev server on http://localhost:5173 and watch for changes with mocked API responses.
You can also run `npm run dev:server` to run the Vite server against a local Umbraco instance. In this case, you need to have the .NET project running and accept connections from the Vite server. Please see the Umbraco.Web.UI.Client README.md [Run against a local Umbraco instance](../src/Umbraco.Web.UI.Client/.github/README.md#run-against-a-local-umbraco-instance) for more information.
If you just want to build the UI Client to `Umbraco.Web.UI` then instead of running `dev`, you can do: `npm run build`.
The login screen is a different frontend build, for that one you can run it as follows:
@@ -60,6 +66,12 @@ npm run dev
If you just want to build the Login screen to `Umbraco.Web.UI` then instead of running `dev`, you can do: `npm run build`.
**The initial Gulp build might take a long time - don't worry, this will be faster on subsequent runs.**
You might run into [Gulp quirks](#gulp-quirks).
The caching for the back office has been described as 'aggressive' so we often find it's best when making back office changes to [disable caching in the browser (check "Disable cache" on the "Network" tab of developer tools)][disable browser caching] to help you to see the changes you're making.
To run the C# portion of the project, either hit <kbd>F5</kbd> to begin debugging, or manually using the command line:
```
@@ -74,18 +86,24 @@ When the page eventually loads in your web browser, you can follow the installer
In order to build the Umbraco source code locally with Visual Studio, first make sure you have the following installed.
- [Visual Studio 2022 v17+ with .NET 7+](https://visualstudio.microsoft.com/vs/) ([the community edition is free](https://www.visualstudio.com/thank-you-downloading-visual-studio/?sku=Community&rel=15) for you to use to contribute to Open Source projects)
- [Node.js v20+](https://nodejs.org/en/download/)
- npm v10+ (installed with Node.js)
- [Git command line](https://git-scm.com/download/)
* [Visual Studio 2022 v17+ with .NET 7+](https://visualstudio.microsoft.com/vs/) ([the community edition is free](https://www.visualstudio.com/thank-you-downloading-visual-studio/?sku=Community&rel=15) for you to use to contribute to Open Source projects)
* [Node.js v14+](https://nodejs.org/en/download/)
* npm v7+ (installed with Node.js)
* [Git command line](https://git-scm.com/download/)
The easiest way to get started is to open `umbraco.sln` in Visual Studio.
Umbraco is a C# based codebase, which is mostly ASP.NET Core MVC based. You can make changes, build them in Visual Studio, and hit <kbd>F5</kbd> to see the result. There are two web projects in the solution, `Umbraco.Web.UI.Client` and `Umbraco.Web.UI.Login`. They each have their own build process and can be run separately. The `Umbraco.Web.UI` project is the main project that hosts the back office and login screen. This is the project you will want to run to see your changes. It will automatically restore and build the client projects when you run it.
To build the front end, you'll first need to run `cd src\Umbraco.Web.UI.Client && npm install` in the command line (or `cd src\Umbraco.Web.UI.Client; npm install` in PowerShell). Then find the Task Runner Explorer (View → Other Windows → Task Runner Explorer) and run the `build` task under `Gulpfile.js`. You may need to refresh the Task Runner Explorer before the tasks load.
If you want to watch the UI Client to `Umbraco.Web.UI` then you can open a terminal in `src/Umbraco.Web.UI.Client` where you can run `npm run dev:mock` manually. This will launch the Vite dev server on http://localhost:5173 and watch for changes with mocked API responses.
If you're working on the backoffice, you may wish to run the `dev` command instead while you're working with it, so changes are copied over to the appropriate directories and you can refresh your browser to view the results of your changes.
You can also run `npm run dev:server` to run the Vite server against a local Umbraco instance. In this case, you need to have the .NET project running and accept connections from the Vite server. Please see the Umbraco.Web.UI.Client README.md [Run against a local Umbraco instance](../src/Umbraco.Web.UI.Client/.github/README.md#run-against-a-local-umbraco-instance) for more information.
**The initial Gulp build might take a long time - don't worry, this will be faster on subsequent runs.**
You might run into [Gulp quirks](#gulp-quirks).
The caching for the back office has been described as 'aggressive' so we often find it's best when making back office changes to [disable caching in the browser (check "Disable cache" on the "Network" tab of developer tools)][disable browser caching] to help you to see the changes you're making.
"The rest" is a C# based codebase, which is mostly ASP.NET Core MVC based. You can make changes, build them in Visual Studio, and hit <kbd>F5</kbd> to see the result.
**The initial C# build might take a _really_ long time (seriously, go and make a cup of coffee!) - but don't worry, this will be faster on subsequent runs.**
@@ -97,7 +115,7 @@ Did you read ["Are you sure"](#are-you-sure)?
Do note that this is only required if you want to test out your custom changes in a separate site (not the one in the Umbraco.Web.UI), if you just want to test your changes you can run the included test site using: `dotnet run` from `src/Umbraco.Web.UI/`
You may want to build a set of NuGet packages with your changes, this can be done using the dotnet pack command.
You may want to build a set of NuGet packages with your changes, this can be done using the dotnet pack command.
First enter the root of the project in a command line environment, and then use the following command to build the NuGet packages:
@@ -109,18 +127,17 @@ You can then add these as a local NuGet feed using the following command:
`dotnet nuget add source <Path to Build.Out folder> -n MyLocalFeed`
This will add a local nuget feed with the name "MyLocalFeed" and you'll now be able to use your custom built NuGet packages.
This will add a local nuget feed with the name "MyLocalFeed" and you'll now be able to use your custom built NuGet packages.
### Cleaning up
Once the solution has been used to run a site, one may want to "reset" the solution in order to run a fresh new site again.
The easiest way to do this by deleting the following files and folders:
* src/Umbraco.Web.UI/appsettings.json
* src/Umbraco.Web.UI/umbraco/Data
- src/Umbraco.Web.UI/appsettings.json
- src/Umbraco.Web.UI/umbraco/Data
You only have to remove the connection strings from the appsettings, but removing the data folder ensures that the sqlite database gets deleted too.
You only have to remove the connection strings from the appsettings, but removing the data folder ensures that the sqlite database gets deleted too.
Next time you run a build the `appsettings.json` file will be re-created in its default state.
@@ -128,14 +145,13 @@ This will leave media files and views around, but in most cases, it will be enou
To perform a more complete clear, you will want to also delete the content of the media, views, scripts... directories.
The following command will force remove all untracked files and directories, whether they are ignored by Git or not. Combined with `git reset` it can recreate a pristine working directory.
The following command will force remove all untracked files and directories, whether they are ignored by Git or not. Combined with `git reset` it can recreate a pristine working directory.
git clean -xdf .
For git documentation see:
- git [clean](https://git-scm.com/docs/git-clean)
- git [reset](https://git-scm.com/docs/git-reset)
* git [clean](<https://git-scm.com/docs/git-clean>)
* git [reset](<https://git-scm.com/docs/git-reset>)
## Azure DevOps
@@ -149,6 +165,18 @@ The produced artifacts are published in a container that can be downloaded from
Git might have issues dealing with long file paths during build. You may want/need to enable `core.longpaths` support (see [this page](https://github.com/msysgit/msysgit/wiki/Git-cannot-create-a-file-or-directory-with-a-long-path) for details).
[ contribution guidelines]: CONTRIBUTING.md "Read the guide to contributing for more details on contributing to Umbraco"
[ starter kits ]: https://our.umbraco.com/packages/?category=Starter%20Kits&version=9 "Browse starter kits available for v9 on Our "
### Gulp Quirks
You may need to run the following commands to set up gulp properly:
```
npm cache clean --force
npm ci
npm run build
```
[ contribution guidelines]: CONTRIBUTING.md "Read the guide to contributing for more details on contributing to Umbraco"
[ starter kits ]: https://our.umbraco.com/packages/?category=Starter%20Kits&version=9 "Browse starter kits available for v9 on Our "
[ disable browser caching ]: https://techwiser.com/disable-cache-google-chrome-firefox "Instructions on how to disable browser caching in Chrome and Firefox"
-30
View File
@@ -1,30 +0,0 @@
# Bellissima release instructions
## Build
> _See internal documentation on the build/release workflow._
## GitHub Release Notes
To generate release notes on GitHub.
- Go to the [**Releases** area](https://github.com/umbraco/Umbraco.CMS.Backoffice/releases)
- Press the [**"Draft a new release"** button](https://github.com/umbraco/Umbraco.CMS.Backoffice/releases/new)
- In the combobox for "Choose a tag", expand then select or enter the next version number, e.g. `release-14.2.0`
- If the tag does not already exist, an option labelled "Create new tag: release-14.2.0 on publish" will appear, select that option
- In the combobox for "Target: main", expand then select the release branch for the next version, e.g. `release/14.2`
- In the combobox for "Previous tag: auto":
- If the next release is an RC, then you can leave as `auto`
- Otherwise, select the previous stable version, e.g. `release-14.1.1`
- Press the **"Generate release notes"** button, this will populate the main textarea
- Change the title to match the version, e.g. `14.2.0`
- Check the details, view in the "Preview" tab
- What type of release is this?
- If it's an RC, then check "Set as a pre-release"
- If it's stable, then check "Set as the latest release"
- Once you're happy with the contents and ready to save...
- If you need more time to review, press the **"Save draft"** button and you can come back to it later
- If you are ready to make the release notes public, then press **"Publish release"** button! :tada:
> If you're curious about how the content is generated, take a look at the `release.yml` configuration:
> https://github.com/umbraco/Umbraco.CMS.Backoffice/blob/main/.github/release.yml
-230
View File
@@ -1,230 +0,0 @@
# Contribution Guidelines
## Thoughts, links, and questions
In the high probability that you are porting something from angular JS then here are a few helpful tips for using Lit:
Here is the LIT documentation and playground: [https://lit.dev](https://lit.dev)
### What is the process of contribution?
- Read the [README](README.md) to learn how to get the project up and running
- Find an issue marked as [community/up-for-grabs](https://github.com/umbraco/Umbraco.CMS.Backoffice/issues?q=is%3Aissue+is%3Aopen+label%3Acommunity%2Fup-for-grabs) - note that some are also marked [good first issue](https://github.com/umbraco/Umbraco.CMS.Backoffice/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) which indicates they are simple to get started on
- Umbraco HQ owns the Management API on the backend, so features can be worked on in the frontend only when there is an API, or otherwise if no API is required
- A contribution should be made in a fork of the repository
- Once a contribution is ready, a pull request should be made to this repository and HQ will assign a reviewer
- A pull request should always indicate what part of a feature it tries to solve, i.e. does it close the targeted issue (if any) or does the developer expect Umbraco HQ to take over
## Contributing in general terms
A lot of the UI has already been migrated to the new backoffice. Generally speaking, one would find a feature on the projects board, locate the UI in the old backoffice (v11 is fine), convert it to Lit components using the UI library, put the business logic into a store/service, write tests, and make a pull request.
We are also very keen to receive contributions towards **documentation, unit testing, package development, accessibility, and just general testing of the UI.**
## The Management API
The management API is the colloquial term used to describe the new backoffice API. It is built as a .NET Web API, has a Swagger endpoint (/umbraco/swagger), and outputs an OpenAPI v3 schema, that the frontend consumes.
The frontend has an API formatter that takes the OpenAPI schema file and converts it into a set of TypeScript classes and interfaces.
**Current schema for API:**
[https://raw.githubusercontent.com/umbraco/Umbraco-CMS/v13/dev/src/Umbraco.Cms.Api.Management/OpenApi.json](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/v15/dev/src/Umbraco.Cms.Api.Management/OpenApi.json)
**How to convert it:**
- Run `npm run generate:server-api`
## A contribution example
### Example: Published Cache Status Dashboard
![Published Status Dashboard](/.github/img/contributing/published-cache-status-dashboard.png)
### Boilerplate (example using Lit)
Links for Lit examples and documentation:
- [https://lit.dev](https://lit.dev)
- [https://lit.dev/docs/](https://lit.dev/docs/)
- [https://lit.dev/playground/](https://lit.dev/playground/)
### Functionality
**HTML**
The simplest approach is to copy over the HTML from the old backoffice into a new Lit element (check existing elements in the repository, e.g. if you are working with a dashboard, then check other dashboards, etc.). Once the HTML is inside the `render` method, it is often enough to simply replace `<umb-***>` elements with `<uui-***>` and replace a few of the attributes. In general, we try to build as much UI with Umbraco UI Library as possible.
**Controller**
The old AngularJS controllers will have to be converted into modern TypeScript and will have to use our new services and stores. We try to abstract as much away as possible, and mostly you will have to make API calls and let the rest of the system handle things like error handling and so on. In the case of this dashboard, we only have a few GET and POST requests. Looking at the new Management API, we find the PublishedCacheService, which is the new API controller to serve data to the dashboard.
To make the first button work, which simply just requests a new status from the server, we must make a call to `PublishedCacheService.getPublishedCacheStatus()`. An additional thing here is to wrap that in a friendly function called `tryExecuteAndNotify`, which is something we make available to developers to automatically handle the responses coming from the server and additionally use the Notifications to notify of any errors:
```typescript
import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources';
import { PublishedCacheService } from '@umbraco-cms/backoffice/external/backend-api';
private _getStatus() {
const { data: status } = await tryExecuteAndNotify(this, PublishedCacheService.getPublishedCacheStatus());
if (status) {
// we now have the status
console.log(status);
}
}
```
### State (buttons, etc)
It is a good idea to make buttons indicate a loading state when awaiting an API call. All `<uui-button>` support the `.state` property, which you can set around API calls:
```typescript
@state()
private _buttonState: UUIButtonState = undefined;
private _getStatus() {
this._buttonState = 'waiting';
[...await...]
this._buttonState = 'success';
}
```
## Making the dashboard visible
### Add to internal manifests
All items are declared in a `manifests.ts` file, which is located in each section directory.
To declare the Published Cache Status Dashboard as a new manifest, we need to add the section as a new json object that would look like this:
```typescript
{
type: 'dashboard',
alias: 'Umb.Dashboard.PublishedStatus',
name: 'Published Status Dashboard',
elementName: 'umb-dashboard-published-status',
element: () => import('./published-status/dashboard-published-status.element.js'),
weight: 200,
meta: {
label: 'Published Status',
pathname: 'published-status',
},
conditions: [
{
alias: 'Umb.Condition.SectionAlias',
match: 'Umb.Section.Settings',
},
],
},
```
Lets go through each of these properties…
- Type: can be one of the following:
- section - examples include: `Content`, `Media`
- dashboard - a view within a section. Examples include: the welcome dashboard
- propertyEditorUi
- editorView
- propertyAction
- tree
- editor
- treeItemAction
- Alias: is the unique key used to identify this item.
- Name: is the human-readable name for this item.
- ElementName: this is the customElementName declared on the element at the top of the file i.e
```typescript
@customElement('umb-dashboard-published-status')
```
- Js: references a function call to import the file that the element is declared within
- Weight: allows us to specify the order in which the dashboard will be displayed within the tabs bar
- Meta: allows us to reference additional data - in our case, we can specify the label that is shown in the tabs bar and the pathname that will be displayed in the URL
- Conditions: allows us to specify the conditions that must be met for the dashboard to be displayed. In our case, we are specifying that the dashboard will only be displayed within the Settings section
## API mock handlers
Running the app with `npm run dev`, you will quickly notice the API requests turn into 404 errors. To hit the API, we need to add a mock handler to define the endpoints that our dashboard will call. In the case of the Published Cache Status section, we have several calls to work through. Lets start by looking at the call to retrieve the current status of the cache:
![Published Status Dashboard](/.github/img/contributing/status-of-cache.png)
From the existing functionality, we can see that this is a string message that is received as part of a `GET` request from the server.
So to define this, we must first add a handler for the Published Status called `published-status.handlers.ts` within the mocks/domains folder. In this file we will have code that looks like the following:
```typescript
const { rest } = window.MockServiceWorker;
import { umbracoPath } from "@umbraco-cms/backoffice/utils";
export const handlers = [
rest.get(umbracoPath("/published-cache/status"), (_req, res, ctx) => {
return res(
// Respond with a 200 status code
ctx.status(200),
ctx.json<string>(
"Database cache is ok. ContentStore contains 1 item and has 1 generation and 0 snapshot. MediaStore contains 5 items and has 1 generation and 0 snapshot."
)
);
}),
];
```
This is defining the `GET` path that we will call through the resource: `/published-cache/status`
It returns a `200 OK` response and a string value with the current “status” of the published cache for us to use within the element
An example `POST` is similar. Lets take the “Refresh status” button as an example:
![Published Status Dashboard](/.github/img/contributing/refresh-status.png)
From our existing functionality, we can see that this makes a `POST` call to the server to prompt a reload of the published cache. So we would add a new endpoint to the mock handler that would look like:
```typescript
rest.post(umbracoPath('/published-cache/reload'), async (_req, res, ctx) => {
return res(
// Simulate a 1 second delay for the benefit of the UI
ctx.delay(1000)
// Respond with a 201 status code
ctx.status(201)
);
})
```
Which is defining a new `POST` endpoint that we can add to the core API fetcher using the path `/published-cache/reload`.
This call returns a simple `OK` status code and no other object.
## Storybook stories
We try to make good Storybook stories for new components, which is a nice way to work with a component in an isolated state. Imagine you are working with a dialog on page 3 and have to navigate back to that every time you make a change - this is now eliminated with Storybook as you can just make a story that displays that step. Storybook can only show one component at a time, so it also helps us to isolate view logic into more and smaller components, which in turn are more testable.
In-depth: [https://storybook.js.org/docs/web-components/get-started/introduction](https://storybook.js.org/docs/web-components/get-started/introduction)
Reference: [https://ambitious-stone-0033b3603.1.azurestaticapps.net/](https://ambitious-stone-0033b3603.1.azurestaticapps.net/)
- Locally: `npm run storybook`
For Umbraco UI stories, please navigate to [https://uui.umbraco.com/](https://uui.umbraco.com/)
## Testing
There are two testing tools on the backoffice: unit testing and end-to-end testing.
### Unit testing
We are using a tool called Web Test Runner which spins up a bunch of browsers using Playwright with the well-known jasmine/chai syntax. It is expected that any new component/element has a test file named “&lt;component>.test.ts”. It will automatically be picked up and there are a set of standard tests we apply to all components, which checks that they are registered correctly and they pass accessibility testing through Axe.
Working with playwright: [https://playwright.dev/docs/intro](https://playwright.dev/docs/intro)
## Putting it all together
When we are finished with the dashboard we will hopefully have something akin to this [real-world example of the actual dashboard that was migrated](https://github.com/umbraco/Umbraco.CMS.Backoffice/tree/main/src/backoffice/settings/dashboards/published-status).
Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

+6 -6
View File
@@ -13,9 +13,13 @@ changelog:
- title: 💥 Breaking Changes
labels:
- category/breaking
- title: 🐛 Bug Fixes
labels:
- type/bug
- category/bug
- type/improvement
- title: 📄 Documentation
labels:
- documentation
- category/documentation
- title: 🏠 Internal
labels:
@@ -25,14 +29,10 @@ changelog:
- dependencies
- title: 🌈 A11Y
labels:
- accessibility
- category/accessibility
- title: 🚀 New Features
labels:
- type/feature
- category/feature
- type/enhancement
- category/enhancement
- title: 🐛 Bug Fixes
- title: Other Changes
labels:
- '*'
+29 -38
View File
@@ -3,26 +3,20 @@ name: "Code scanning - action"
on:
push:
branches:
- "*/dev"
- "*/contrib"
- "contrib"
- "release/*"
- '*/dev'
- '*/contrib'
pull_request:
# The branches below must be a subset of the branches above
branches:
- "*/dev"
- "*/contrib"
- "contrib"
- "release/*"
schedule:
- cron: "33 2 * * 1"
- '*/dev'
- '*/contrib'
permissions:
contents: read
env:
dotnetVersion: 9.x
dotnetIncludePreviewVersions: "preview"
dotnetVersion: 8.x
dotnetIncludePreviewVersions: true
solution: umbraco.sln
buildConfiguration: SkipTests
DOTNET_NOLOGO: true
@@ -33,39 +27,36 @@ env:
jobs:
CodeQL-Build:
name: Analyze (${{ matrix.language }})
permissions:
actions: read # for github/codeql-action/init to get workflow details
contents: read # for actions/checkout to fetch code
security-events: write # for github/codeql-action/analyze to upload SARIF results
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- language: csharp
build-mode: none
- language: javascript-typescript
build-mode: none
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
submodules: true
# We use the setup-dotnet action to set up .NET Core, otherwise the CodeQL CLI will not work with preview versions.
- name: Setup .NET from global.json
uses: actions/setup-dotnet@v4
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
config-file: ./.github/config/codeql-config.yml
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
config-file: ./.github/config/codeql-config.yml
- name: Use .NET ${{ env.dotnetVersion }}
uses: actions/setup-dotnet@v2
with:
dotnet-version: ${{ env.dotnetVersion }}
include-prerelease: ${{ env.dotnetIncludePreviewVersions }}
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
category: "/language:${{matrix.language}}"
- name: Run dotnet restore
run: dotnet restore ${{ env.solution }}
- name: Run dotnet build
run: dotnet build ${{ env.solution }} --configuration ${{ env.buildConfiguration }} --no-restore -p:ContinuousIntegrationBuild=true
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
-63
View File
@@ -1,63 +0,0 @@
name: Test Backoffice
on:
push:
branches:
- contrib
- release/*
- v*/dev
paths:
- src/Umbraco.Web.UI.Client/**
pull_request:
branches:
- contrib
- release/*
- v*/dev
paths:
- src/Umbraco.Web.UI.Client/**
# Allows GitHub to use this workflow to validate the merge queue
merge_group:
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
env:
NODE_OPTIONS: --max_old_space_size=16384
jobs:
build:
runs-on: ubuntu-latest
defaults:
run:
working-directory: src/Umbraco.Web.UI.Client
steps:
- uses: actions/checkout@v4
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version-file: src/Umbraco.Web.UI.Client/.nvmrc
cache: npm
cache-dependency-path: ./src/Umbraco.Web.UI.Client/package-lock.json
- run: npm ci --no-audit --no-fund --prefer-offline
- run: npm run lint:errors
- run: npm run build:for:cms
- run: npm run check:paths
- run: npm run generate:jsonschema:dist
test:
runs-on: ubuntu-latest
defaults:
run:
working-directory: src/Umbraco.Web.UI.Client
steps:
- uses: actions/checkout@v4
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version-file: src/Umbraco.Web.UI.Client/.nvmrc
cache: npm
cache-dependency-path: ./src/Umbraco.Web.UI.Client/package-lock.json
- run: npm ci --no-audit --no-fund --prefer-offline
- run: npx playwright install --with-deps
- run: npm test
+7
View File
@@ -47,6 +47,9 @@ NDependOut/
QueryResult.htm
tools/docfx/
# Ignore rule for clearing out Belle (avoid rebuilding all the time)
preserve.belle
# csharp-docs
/build/csharp-docs/api/
/build/csharp-docs/_site/
@@ -70,6 +73,10 @@ tools/docfx/
/src/Umbraco.Cms.StaticAssets/wwwroot/umbraco/login
# Environment specific data
/src/Umbraco.Web.UI.Client/[Bb]uild/
/src/Umbraco.Web.UI.Client/[Bb]uild/[Bb]elle/
/src/Umbraco.Web.UI.Client/src/[Ll]ess/*.css
/src/Umbraco.Web.UI.Client/TESTS-*.xml
/src/Umbraco.Web.UI/wwwroot/[Mm]edia/
/src/Umbraco.Web.UI/App_Code/
/src/Umbraco.Web.UI/App_Plugins/
+3
View File
@@ -0,0 +1,3 @@
[submodule "src/Umbraco.Web.UI.Client"]
path = src/Umbraco.Web.UI.Client
url = https://github.com/umbraco/Umbraco.CMS.Backoffice.git
+7 -3
View File
@@ -27,9 +27,11 @@
"promptOnClose": true,
"group": "build",
"type": "npm",
"script": "build:for:cms",
"script": "build",
"path": "src/Umbraco.Web.UI.Client/",
"problemMatcher": []
"problemMatcher": [
"$gulp-tsc"
]
},
{
"label": "Client Watch",
@@ -39,7 +41,9 @@
"type": "npm",
"script": "dev",
"path": "src/Umbraco.Web.UI.Client/",
"problemMatcher": []
"problemMatcher": [
"$gulp-tsc"
]
},
{
"label": "Dotnet build",
+2 -11
View File
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Project>
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<Company>Umbraco HQ</Company>
<Authors>Umbraco</Authors>
<Copyright>Copyright © Umbraco $([System.DateTime]::Today.ToString('yyyy'))</Copyright>
@@ -20,15 +20,6 @@
<WarnOnPackingNonPackableProject>false</WarnOnPackingNonPackableProject>
</PropertyGroup>
<PropertyGroup>
<!--
TODO: Fix and remove overrides:
[NU5104] Warning As Error: A stable release of a package should not have a prerelease dependency. Either modify the version spec of dependency
-->
<NoWarn>$(NoWarn),NU5104</NoWarn>
<WarningsNotAsErrors>$(WarningsNotAsErrors),NU5104</WarningsNotAsErrors>
</PropertyGroup>
<!-- SourceLink -->
<PropertyGroup>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
@@ -40,7 +31,7 @@
<PropertyGroup>
<GenerateCompatibilitySuppressionFile>false</GenerateCompatibilitySuppressionFile>
<EnablePackageValidation>true</EnablePackageValidation>
<PackageValidationBaselineVersion>15.0.0</PackageValidationBaselineVersion>
<PackageValidationBaselineVersion>14.0.0</PackageValidationBaselineVersion>
<EnableStrictModeForCompatibleFrameworksInPackage>true</EnableStrictModeForCompatibleFrameworksInPackage>
<EnableStrictModeForCompatibleTfms>true</EnableStrictModeForCompatibleTfms>
</PropertyGroup>
+43 -49
View File
@@ -5,35 +5,35 @@
</PropertyGroup>
<!-- Global packages (private, build-time packages for all projects) -->
<ItemGroup>
<GlobalPackageReference Include="Nerdbank.GitVersioning" Version="3.6.146" />
<GlobalPackageReference Include="Nerdbank.GitVersioning" Version="3.6.139" />
<GlobalPackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556" />
<GlobalPackageReference Include="Umbraco.Code" Version="2.2.0" />
<GlobalPackageReference Include="Umbraco.GitVersioning.Extensions" Version="0.2.0" />
</ItemGroup>
<!-- Microsoft packages -->
<ItemGroup>
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="9.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.11" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="8.0.11" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.10.0" />
<PackageVersion Include="Microsoft.Data.Sqlite" Version="9.0.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.10" />
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.FileProviders.Embedded" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.FileProviders.Physical" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.Identity.Core" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.Identity.Stores" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.Options" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.Options.DataAnnotations" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.Caching.Hybrid" Version="9.0.0-preview.9.24556.5" />
<PackageVersion Include="Microsoft.Data.Sqlite" Version="8.0.11" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.11" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.11" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.11" />
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="8.0.1" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
<PackageVersion Include="Microsoft.Extensions.FileProviders.Embedded" Version="8.0.11" />
<PackageVersion Include="Microsoft.Extensions.FileProviders.Physical" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="8.0.1" />
<PackageVersion Include="Microsoft.Extensions.Identity.Core" Version="8.0.11" />
<PackageVersion Include="Microsoft.Extensions.Identity.Stores" Version="8.0.11" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="8.0.1" />
<PackageVersion Include="Microsoft.Extensions.Options" Version="8.0.2" />
<PackageVersion Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Options.DataAnnotations" Version="8.0.0" />
</ItemGroup>
<!-- Umbraco packages -->
<ItemGroup>
@@ -58,44 +58,38 @@
<PackageVersion Include="ncrontab" Version="3.3.3" />
<PackageVersion Include="NPoco" Version="5.7.1" />
<PackageVersion Include="NPoco.SqlServer" Version="5.7.1" />
<PackageVersion Include="OpenIddict.Abstractions" Version="6.0.0-preview3.24551.41" />
<PackageVersion Include="OpenIddict.AspNetCore" Version="6.0.0-preview3.24551.41" />
<PackageVersion Include="OpenIddict.EntityFrameworkCore" Version="6.0.0-preview3.24551.41" />
<PackageVersion Include="Serilog" Version="4.1.0" />
<PackageVersion Include="OpenIddict.Abstractions" Version="5.7.0" />
<PackageVersion Include="OpenIddict.AspNetCore" Version="5.7.0" />
<PackageVersion Include="OpenIddict.EntityFrameworkCore" Version="5.7.0" />
<PackageVersion Include="Serilog" Version="3.1.1" />
<PackageVersion Include="Serilog.AspNetCore" Version="8.0.3" />
<PackageVersion Include="Serilog.Enrichers.Process" Version="3.0.0" />
<PackageVersion Include="Serilog.Enrichers.Thread" Version="4.0.0" />
<PackageVersion Include="Serilog.Expressions" Version="5.0.0" />
<PackageVersion Include="Serilog.Enrichers.Process" Version="2.0.2" />
<PackageVersion Include="Serilog.Enrichers.Thread" Version="3.1.0" />
<PackageVersion Include="Serilog.Expressions" Version="4.0.0" />
<PackageVersion Include="Serilog.Extensions.Hosting" Version="8.0.0" />
<PackageVersion Include="Serilog.Formatting.Compact" Version="3.0.0" />
<PackageVersion Include="Serilog.Formatting.Compact.Reader" Version="4.0.0" />
<PackageVersion Include="Serilog.Formatting.Compact" Version="2.0.0" />
<PackageVersion Include="Serilog.Formatting.Compact.Reader" Version="3.0.0" />
<PackageVersion Include="Serilog.Settings.Configuration" Version="8.0.4" />
<PackageVersion Include="Serilog.Sinks.Async" Version="2.1.0" />
<PackageVersion Include="Serilog.Sinks.File" Version="6.0.0" />
<PackageVersion Include="Serilog.Sinks.Map" Version="2.0.0" />
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.6" />
<PackageVersion Include="Serilog.Sinks.Async" Version="1.5.0" />
<PackageVersion Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageVersion Include="Serilog.Sinks.Map" Version="1.0.2" />
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.7" />
<PackageVersion Include="SixLabors.ImageSharp.Web" Version="3.1.3" />
<PackageVersion Include="Swashbuckle.AspNetCore" Version="7.1.0" />
<PackageVersion Include="Swashbuckle.AspNetCore" Version="6.9.0" />
</ItemGroup>
<!-- Transitive pinned versions (only required because our direct dependencies have vulnerable versions of transitive dependencies) -->
<ItemGroup>
<!-- Microsoft.EntityFrameworkCore.SqlServer and NPoco.SqlServer brings in a vulnerable version of Azure.Identity -->
<!-- Both Microsoft.EntityFrameworkCore.SqlServer and NPoco.SqlServer bring in a vulnerable version of Azure.Identity -->
<PackageVersion Include="Azure.Identity" Version="1.13.1" />
<!-- Microsoft.EntityFrameworkCore.SqlServer brings in a vulnerable version of System.Runtime.Caching -->
<PackageVersion Include="System.Runtime.Caching" Version="9.0.0" />
<!-- Dazinator.Extensions.FileProviders brings in a vulnerable version of System.Net.Http -->
<PackageVersion Include="System.Net.Http" Version="4.3.4" />
<!-- Examine brings in a vulnerable version of System.Security.Cryptography.Xml -->
<PackageVersion Include="System.Security.Cryptography.Xml" Version="9.0.0" />
<!-- Dazinator.Extensions.FileProviders and MiniProfiler.AspNetCore.Mvc brings in a vulnerable version of System.Text.RegularExpressions -->
<PackageVersion Include="System.Security.Cryptography.Xml" Version="8.0.2" />
<!-- Both Dazinator.Extensions.FileProviders and MiniProfiler.AspNetCore.Mvc bring in a vulnerable version of System.Text.RegularExpressions -->
<PackageVersion Include="System.Text.RegularExpressions" Version="4.3.1" />
<!-- OpenIddict.AspNetCore, Npoco.SqlServer and Microsoft.EntityFrameworkCore.SqlServer brings in a vulnerable version of Microsoft.IdentityModel.JsonWebTokens -->
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.2.1" />
<!-- 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.0" />
<!-- NPoco.SqlServer brings in a vulnerable version of Microsoft.Data.SqlClient -->
<PackageVersion Include="Microsoft.Data.SqlClient" Version="5.2.2" />
<!-- Examine.Lucene brings in a vulnerable version of Lucene.Net.Replicator -->
<!-- Both OpenIddict.AspNetCore, Npoco.SqlServer and Microsoft.EntityFrameworkCore.SqlServer bring in a vulnerable version of Microsoft.IdentityModel.JsonWebTokens -->
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="7.7.1" />
<!-- Examine.Lucene bring in a vulnerable version of Lucene.Net.Replicator -->
<PackageVersion Include="Lucene.Net.Replicator" Version="4.8.0-beta00017" />
</ItemGroup>
</Project>
</Project>
+89 -180
View File
@@ -36,21 +36,21 @@ parameters:
- name: integrationNonReleaseTestFilter
displayName: TestFilter used for non-release type builds
type: string
default: "--filter TestCategory!=LongRunning&TestCategory!=NonCritical"
default: '--filter TestCategory!=LongRunning&TestCategory!=NonCritical'
- name: integrationReleaseTestFilter
displayName: TestFilter used for release type builds
type: string
default: " "
default: ' '
- name: nonWindowsIntegrationNonReleaseTestFilter
displayName: TestFilter used for non-release type builds on non Windows agents
type: string
default: "--filter TestCategory!=LongRunning&TestCategory!=NonCritical"
default: '--filter TestCategory!=LongRunning&TestCategory!=NonCritical'
- name: nonWindowsIntegrationReleaseTestFilter
displayName: TestFilter used for release type builds on non Windows agents
type: string
default: " "
default: ' '
- name: isNightly
displayName: "Is nightly build (used for MyGet feed)"
displayName: 'Is nightly build (used for MyGet feed)'
type: boolean
default: false
@@ -75,17 +75,24 @@ stages:
- job: A
displayName: Build Umbraco CMS
pool:
vmImage: "windows-latest"
vmImage: 'windows-latest'
steps:
- checkout: self
submodules: false
lfs: false,
fetchDepth: 500
- template: templates/backoffice-install.yml
submodules: true
- task: UseDotNet@2
displayName: Use .NET SDK from global.json
inputs:
useGlobalJson: true
- template: templates/backoffice-install.yml
- script: npm run build:for:cms
displayName: Run build (Bellissima)
workingDirectory: src/Umbraco.Web.UI.Client
- script: npm ci --no-fund --no-audit --prefer-offline
displayName: Run npm ci (Login)
workingDirectory: src/Umbraco.Web.UI.Login
- script: npm run build
displayName: Run npm build (Login)
workingDirectory: src/Umbraco.Web.UI.Login
- task: DotNetCoreCLI@2
displayName: Run dotnet restore
inputs:
@@ -97,7 +104,7 @@ stages:
inputs:
command: build
projects: $(solution)
arguments: "--configuration $(buildConfiguration) --no-restore --property:ContinuousIntegrationBuild=true --property:GeneratePackageOnBuild=true --property:PackageOutputPath=$(Build.ArtifactStagingDirectory)/nupkg"
arguments: '--configuration $(buildConfiguration) --no-restore --property:ContinuousIntegrationBuild=true --property:GeneratePackageOnBuild=true --property:PackageOutputPath=$(Build.ArtifactStagingDirectory)/nupkg'
- task: PublishPipelineArtifact@1
displayName: Publish nupkg
inputs:
@@ -112,12 +119,10 @@ stages:
- job: B
displayName: Build Bellissima Package
pool:
vmImage: "ubuntu-latest"
vmImage: 'ubuntu-latest'
steps:
- checkout: self
submodules: false
lfs: false,
fetchDepth: 500
submodules: true
- template: templates/backoffice-install.yml
- script: npm run build:for:npm
displayName: Run build:for:npm
@@ -147,7 +152,7 @@ stages:
- job:
displayName: Build C# API Reference
pool:
vmImage: "windows-latest"
vmImage: 'windows-latest'
steps:
- task: UseDotNet@2
displayName: Use .NET SDK from global.json
@@ -196,15 +201,12 @@ stages:
- job:
displayName: Build js API Reference
pool:
vmImage: "ubuntu-latest"
vmImage: 'ubuntu-latest'
variables:
BASE_PATH: /v$(umbracoMajorVersion)/ui
steps:
- checkout: self
submodules: false
lfs: false,
fetchDepth: 1
fetchFilter: tree:0
submodules: true
- template: templates/backoffice-install.yml
- script: npm run storybook:build
displayName: Build Storybook
@@ -253,19 +255,14 @@ stages:
strategy:
matrix:
Windows:
vmImage: "windows-latest"
vmImage: 'windows-latest'
Linux:
vmImage: "ubuntu-latest"
vmImage: 'ubuntu-latest'
macOS:
vmImage: "macOS-latest"
vmImage: 'macOS-latest'
pool:
vmImage: $(vmImage)
steps:
- checkout: self
submodules: false
lfs: false,
fetchDepth: 1
fetchFilter: tree:0
- task: DownloadPipelineArtifact@2
displayName: Download build artifacts
inputs:
@@ -279,8 +276,8 @@ stages:
displayName: Run dotnet test
inputs:
command: test
projects: "tests/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj"
arguments: "--configuration $(buildConfiguration) --no-build"
projects: 'tests/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj'
arguments: '--configuration $(buildConfiguration) --no-build'
testRunTitle: Unit Tests - $(Agent.OS)
- stage: Integration
@@ -291,47 +288,20 @@ stages:
jobs:
# Integration Tests (SQLite)
- job:
timeoutInMinutes: 180
displayName: Integration Tests (SQLite)
strategy:
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:
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:
vmImage: "ubuntu-latest"
# Filter tests that are part of the Umbraco.Infrastructure.Service namespace
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)"
macOSPart1Of3:
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:
vmImage: "macOS-latest"
# Filter tests that are part of the Umbraco.Infrastructure.Service namespace
testFilter: "(FullyQualifiedName~Umbraco.Infrastructure.Service)"
macOSPart3Of3:
vmImage: "macOS-latest"
# Filter tests that are not part of the Umbraco.Infrastructure namespace.
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure)"
Windows:
vmImage: 'windows-latest'
Linux:
vmImage: 'ubuntu-latest'
macOS:
vmImage: 'macOS-latest'
pool:
vmImage: $(vmImage)
variables:
Tests__Database__DatabaseType: "Sqlite"
Tests__Database__DatabaseType: 'Sqlite'
steps:
- checkout: self
submodules: false
lfs: false,
fetchDepth: 1
fetchFilter: tree:0
# Setup test environment
- task: DownloadPipelineArtifact@2
displayName: Download build artifacts
@@ -349,16 +319,17 @@ stages:
displayName: Run dotnet test
inputs:
command: test
projects: "tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj"
projects: 'tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj'
testRunTitle: Integration Tests SQLite - $(Agent.OS)
${{ if and(eq(variables['Agent.OS'],'Windows_NT'), or(variables.releaseTestFilter, parameters.forceReleaseTestFilter)) }}:
arguments: '--filter "$(testFilter)" --configuration $(buildConfiguration) --no-build ${{parameters.integrationReleaseTestFilter}}'
arguments: '--configuration $(buildConfiguration) --no-build ${{parameters.integrationReleaseTestFilter}}'
${{ elseif eq(variables['Agent.OS'],'Windows_NT') }}:
arguments: '--filter "$(testFilter)" --configuration $(buildConfiguration) --no-build ${{parameters.integrationNonReleaseTestFilter}}'
arguments: '--configuration $(buildConfiguration) --no-build ${{parameters.integrationNonReleaseTestFilter}}'
${{ elseif or(variables.releaseTestFilter, parameters.forceReleaseTestFilter) }}:
arguments: '--filter "$(testFilter)" --configuration $(buildConfiguration) --no-build ${{parameters.nonWindowsIntegrationReleaseTestFilter}}'
arguments: '--configuration $(buildConfiguration) --no-build ${{parameters.nonWindowsIntegrationReleaseTestFilter}}'
${{ else }}:
arguments: '--filter "$(testFilter)" --configuration $(buildConfiguration) --no-build ${{parameters.nonWindowsIntegrationNonReleaseTestFilter}}'
arguments: '--configuration $(buildConfiguration) --no-build ${{parameters.nonWindowsIntegrationNonReleaseTestFilter}}'
# Integration Tests (SQL Server)
- job:
timeoutInMinutes: 180
@@ -367,46 +338,15 @@ stages:
displayName: Integration Tests (SQL Server)
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:
vmImage: "windows-latest"
Windows:
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:
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:
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:
vmImage: "ubuntu-latest"
Linux:
vmImage: 'ubuntu-latest'
SA_PASSWORD: UmbracoIntegration123!
Tests__Database__DatabaseType: SqlServer
Tests__Database__SQLServerMasterConnectionString: "Server=(local);User Id=sa;Password=$(SA_PASSWORD);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:
vmImage: "ubuntu-latest"
SA_PASSWORD: UmbracoIntegration123!
Tests__Database__DatabaseType: SqlServer
Tests__Database__SQLServerMasterConnectionString: "Server=(local);User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=True"
# Filter tests that are part of the Umbraco.Infrastructure.Service namespace
testFilter: "(FullyQualifiedName~Umbraco.Infrastructure.Service)"
LinuxPart3Of3:
vmImage: "ubuntu-latest"
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)"
Tests__Database__SQLServerMasterConnectionString: 'Server=(local);User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=True'
pool:
vmImage: $(vmImage)
steps:
@@ -436,16 +376,16 @@ stages:
displayName: Run dotnet test
inputs:
command: test
projects: "tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj"
projects: 'tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj'
testRunTitle: Integration Tests SQL Server - $(Agent.OS)
${{ if and(eq(variables['Agent.OS'],'Windows_NT'), or(variables.releaseTestFilter, parameters.forceReleaseTestFilter)) }}:
arguments: '--filter "$(testFilter)" --configuration $(buildConfiguration) --no-build ${{parameters.integrationReleaseTestFilter}}'
arguments: '--configuration $(buildConfiguration) --no-build ${{parameters.integrationReleaseTestFilter}}'
${{ elseif eq(variables['Agent.OS'],'Windows_NT') }}:
arguments: '--filter "$(testFilter)" --configuration $(buildConfiguration) --no-build ${{parameters.integrationNonReleaseTestFilter}}'
arguments: '--configuration $(buildConfiguration) --no-build ${{parameters.integrationNonReleaseTestFilter}}'
${{ elseif or(variables.releaseTestFilter, parameters.forceReleaseTestFilter) }}:
arguments: '--filter "$(testFilter)" --configuration $(buildConfiguration) --no-build ${{parameters.nonWindowsIntegrationReleaseTestFilter}}'
arguments: '--configuration $(buildConfiguration) --no-build ${{parameters.nonWindowsIntegrationReleaseTestFilter}}'
${{ else }}:
arguments: '--filter "$(testFilter)" --configuration $(buildConfiguration) --no-build ${{parameters.nonWindowsIntegrationNonReleaseTestFilter}}'
arguments: '--configuration $(buildConfiguration) --no-build ${{parameters.nonWindowsIntegrationNonReleaseTestFilter}}'
# Stop SQL Server
- pwsh: docker stop mssql
@@ -490,24 +430,10 @@ stages:
CONNECTIONSTRINGS__UMBRACODBDSN_PROVIDERNAME: Microsoft.Data.Sqlite
strategy:
matrix:
LinuxPart1Of3:
vmImage: "ubuntu-latest"
testCommand: 'npx playwright test DefaultConfig --grep "@smoke"--shard=1/3'
LinuxPart2Of3:
vmImage: "ubuntu-latest"
testCommand: 'npx playwright test DefaultConfig --grep "@smoke" --shard=2/3'
LinuxPart3Of3:
vmImage: "ubuntu-latest"
testCommand: 'npx playwright test DefaultConfig --grep "@smoke" --shard=3/3'
WindowsPart1Of3:
vmImage: "windows-latest"
testCommand: 'npx playwright test DefaultConfig --grep "@smoke" --shard=1/3'
WindowsPart2Of3:
vmImage: "windows-latest"
testCommand: 'npx playwright test DefaultConfig --grep "@smoke" --shard=2/3'
WindowsPart3Of3:
vmImage: "windows-latest"
testCommand: 'npx playwright test DefaultConfig --grep "@smoke" --shard=3/3'
Linux:
vmImage: 'ubuntu-latest'
Windows:
vmImage: 'windows-latest'
pool:
vmImage: $(vmImage)
steps:
@@ -580,6 +506,10 @@ stages:
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
workingDirectory: $(Agent.BuildDirectory)/app
# Ensures we have the package wait-on installed
- pwsh: npm install wait-on
displayName: Install wait-on package
# Wait for application to start responding to requests
- pwsh: npx wait-on -v --interval 1000 --timeout 120000 $(ASPNETCORE_URLS)
displayName: Wait for application
@@ -591,7 +521,7 @@ stages:
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
# Test
- pwsh: $(testCommand)
- pwsh: npm run smokeTestSqlite --ignore-certificate-errors
displayName: Run Playwright tests
continueOnError: true
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
@@ -623,7 +553,7 @@ stages:
condition: succeededOrFailed()
inputs:
targetPath: $(Build.ArtifactStagingDirectory)
artifact: "Acceptance Tests - $(Agent.JobName) - Attempt #$(System.JobAttempt)"
artifact: 'Acceptance Tests - $(Agent.JobName) - Attempt #$(System.JobAttempt)'
- job:
displayName: E2E Tests (SQL Server)
@@ -633,31 +563,13 @@ stages:
CONNECTIONSTRINGS__UMBRACODBDSN_PROVIDERNAME: Microsoft.Data.SqlClient
strategy:
matrix:
${{ if eq(parameters.sqlServerLinuxAcceptanceTests, True) }}:
LinuxPart1Of3:
testCommand: 'npx playwright test DefaultConfig --grep "@smoke" --grep-invert "Users" --shard=1/3'
vmImage: "ubuntu-latest"
${{ if eq(parameters.sqlServerLinuxAcceptanceTests, True) }} :
Linux:
vmImage: 'ubuntu-latest'
SA_PASSWORD: $(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=True"
LinuxPart2Of3:
testCommand: 'npx playwright test DefaultConfig --grep "@smoke" --grep-invert "Users" --shard=2/3'
vmImage: "ubuntu-latest"
SA_PASSWORD: $(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=True"
LinuxPart3Of3:
testCommand: 'npx playwright test DefaultConfig --grep "@smoke" --grep-invert "Users" --shard=3/3'
vmImage: "ubuntu-latest"
SA_PASSWORD: $(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=True"
WindowsPart1Of3:
vmImage: "windows-latest"
testCommand: 'npx playwright test DefaultConfig --grep "@smoke" --grep-invert "Users" --shard=1/3'
WindowsPart2Of3:
vmImage: "windows-latest"
testCommand: 'npx playwright test DefaultConfig --grep "@smoke" --grep-invert "Users" --shard=2/3'
WindowsPart3Of3:
vmImage: "windows-latest"
testCommand: 'npx playwright test DefaultConfig --grep "@smoke" --grep-invert "Users" --shard=3/3'
CONNECTIONSTRINGS__UMBRACODBDSN: 'Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=True'
Windows:
vmImage: 'windows-latest'
pool:
vmImage: $(vmImage)
steps:
@@ -749,7 +661,7 @@ stages:
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
# Test
- pwsh: $(testCommand)
- pwsh: npm run smokeTest --ignore-certificate-errors
displayName: Run Playwright tests
continueOnError: true
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
@@ -790,7 +702,7 @@ stages:
condition: succeededOrFailed()
inputs:
targetPath: $(Build.ArtifactStagingDirectory)
artifact: "Acceptance Tests - $(Agent.JobName) - Attempt #$(System.JobAttempt)"
artifact: 'Acceptance Tests - $(Agent.JobName) - Attempt #$(System.JobAttempt)'
###############################################
## Release
@@ -815,13 +727,13 @@ stages:
- task: NuGetCommand@2
displayName: NuGet push
inputs:
command: "push"
command: 'push'
packagesToPush: $(Build.ArtifactStagingDirectory)/**/*.nupkg
nuGetFeedType: "external"
nuGetFeedType: 'external'
${{ if eq(parameters.isNightly, true) }}:
publishFeedCredentials: "MyGet - Umbraco Nightly"
publishFeedCredentials: 'MyGet - Umbraco Nightly'
${{ else }}:
publishFeedCredentials: "MyGet - Pre-releases"
publishFeedCredentials: 'MyGet - Pre-releases'
- job:
displayName: Push to pre-release feed (npm)
steps:
@@ -845,8 +757,8 @@ stages:
- task: npmAuthenticate@0
displayName: Authenticate with npm (MyGet)
inputs:
workingFile: "$(Pipeline.Workspace)/npm/.npmrc"
customEndpoint: "MyGet (npm) - Umbracoprereleases, MyGet (npm) - Umbraconightly"
workingFile: '$(Pipeline.Workspace)/npm/.npmrc'
customEndpoint: 'MyGet (npm) - Umbracoprereleases, MyGet (npm) - Umbraconightly'
- bash: |
# Setup temp npm project to load in defaults from the local .npmrc
npm init -y
@@ -876,10 +788,10 @@ stages:
- task: NuGetCommand@2
displayName: NuGet push
inputs:
command: "push"
command: 'push'
packagesToPush: $(Build.ArtifactStagingDirectory)/**/*.nupkg
nuGetFeedType: "external"
publishFeedCredentials: "NuGet - Umbraco.*"
nuGetFeedType: 'external'
publishFeedCredentials: 'NuGet - Umbraco.*'
- stage: Deploy_Npm
displayName: Npm release
@@ -900,7 +812,7 @@ stages:
displayName: Authenticate with npm
inputs:
workingFile: $(Pipeline.Workspace)/npm/.npmrc
customEndpoint: "NPM - Umbraco Backoffice"
customEndpoint: 'NPM - Umbraco Backoffice'
- script: |
# Setup temp npm project to load in defaults from the local .npmrc
npm init -y
@@ -913,7 +825,7 @@ stages:
- stage: Upload_API_Docs
pool:
vmImage: "windows-latest" # Apparently AzureFileCopy is windows only :(
vmImage: 'windows-latest' # Apparently AzureFileCopy is windows only :(
variables:
umbracoMajorVersion: $[ stageDependencies.Build.A.outputs['build.NBGV_VersionMajor'] ]
displayName: Upload API Documentation
@@ -935,15 +847,14 @@ stages:
inputs:
archiveFilePatterns: $(Build.SourcesDirectory)/csharp-docs.zip
destinationFolder: $(Build.ArtifactStagingDirectory)/csharp-docs
overwriteExistingFiles: true
- task: AzureFileCopy@4
displayName: "Copy C# Docs to blob storage"
displayName: 'Copy C# Docs to blob storage'
inputs:
SourcePath: "$(Build.ArtifactStagingDirectory)/csharp-docs/*"
SourcePath: '$(Build.ArtifactStagingDirectory)/csharp-docs/*'
azureSubscription: umbraco-storage
Destination: AzureBlob
storage: umbracoapidocs
ContainerName: "$web"
ContainerName: '$web'
BlobPrefix: v$(umbracoMajorVersion)/csharp
CleanTargetBeforeCopy: true
- job:
@@ -959,15 +870,14 @@ stages:
inputs:
archiveFilePatterns: $(Build.SourcesDirectory)/ui-docs.zip
destinationFolder: $(Build.ArtifactStagingDirectory)/ui-docs
overwriteExistingFiles: true
- task: AzureFileCopy@4
displayName: "Copy Storybook to blob storage"
displayName: 'Copy Storybook to blob storage'
inputs:
SourcePath: "$(Build.ArtifactStagingDirectory)/ui-docs/*"
SourcePath: '$(Build.ArtifactStagingDirectory)/ui-docs/*'
azureSubscription: umbraco-storage
Destination: AzureBlob
storage: umbracoapidocs
ContainerName: "$web"
ContainerName: '$web'
BlobPrefix: v$(umbracoMajorVersion)/ui
CleanTargetBeforeCopy: true
- job:
@@ -983,14 +893,13 @@ stages:
inputs:
archiveFilePatterns: $(Build.SourcesDirectory)/ui-api-docs.zip
destinationFolder: $(Build.ArtifactStagingDirectory)/ui-api-docs
overwriteExistingFiles: true
- task: AzureFileCopy@4
displayName: "Copy UI API Docs to blob storage"
displayName: 'Copy UI API Docs to blob storage'
inputs:
SourcePath: "$(Build.ArtifactStagingDirectory)/ui-api-docs/*"
SourcePath: '$(Build.ArtifactStagingDirectory)/ui-api-docs/*'
azureSubscription: umbraco-storage
Destination: AzureBlob
storage: umbracoapidocs
ContainerName: "$web"
ContainerName: '$web'
BlobPrefix: v$(umbracoMajorVersion)/ui-api
CleanTargetBeforeCopy: true
+29 -50
View File
@@ -3,13 +3,12 @@ name: Nightly_E2E_Test_$(TeamProject)_$(Build.DefinitionName)_$(SourceBranchName
pr: none
trigger: none
schedules:
- cron: '0 0 * * *'
displayName: Daily midnight build
branches:
include:
- v14/dev
- v15/dev
# schedules:
# - cron: '0 0 * * *'
# displayName: Daily midnight build
# branches:
# include:
# - v14/dev
variables:
nodeVersion: 20
@@ -23,6 +22,12 @@ variables:
npm_config_cache: $(Pipeline.Workspace)/.npm_client
NODE_OPTIONS: --max_old_space_size=16384
parameters:
- name: runSmokeTests
displayName: Run the smoke tests
type: boolean
default: false
stages:
###############################################
## Build
@@ -109,24 +114,10 @@ stages:
CONNECTIONSTRINGS__UMBRACODBDSN_PROVIDERNAME: Microsoft.Data.Sqlite
strategy:
matrix:
LinuxPart1Of3:
vmImage: "ubuntu-latest"
testCommand: "npx playwright test DefaultConfig --shard=1/3"
LinuxPart2Of3:
vmImage: "ubuntu-latest"
testCommand: "npx playwright test DefaultConfig --shard=2/3"
LinuxPart3Of3:
vmImage: "ubuntu-latest"
testCommand: "npx playwright test DefaultConfig --shard=3/3"
WindowsPart1Of3:
vmImage: "windows-latest"
testCommand: "npx playwright test DefaultConfig --shard=1/3"
WindowsPart2Of3:
vmImage: "windows-latest"
testCommand: "npx playwright test DefaultConfig --shard=2/3"
WindowsPart3Of3:
vmImage: "windows-latest"
testCommand: "npx playwright test DefaultConfig --shard=3/3"
Linux:
vmImage: 'ubuntu-latest'
Windows:
vmImage: 'windows-latest'
pool:
vmImage: $(vmImage)
steps:
@@ -214,7 +205,10 @@ stages:
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
# Test
- pwsh: $(testCommand)
- ${{ if eq(parameters.runSmokeTests, true) }}:
pwsh: npm run smokeTestSqlite --ignore-certificate-errors
${{ else }}:
pwsh: npm run testSqlite --ignore-certificate-errors
displayName: Run Playwright tests
continueOnError: true
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
@@ -257,30 +251,12 @@ stages:
CONNECTIONSTRINGS__UMBRACODBDSN_PROVIDERNAME: Microsoft.Data.SqlClient
strategy:
matrix:
LinuxPart1Of3:
testCommand: "npx playwright test DefaultConfig --grep-invert \"Users\" --shard=1/3"
vmImage: "ubuntu-latest"
Linux:
vmImage: 'ubuntu-latest'
SA_PASSWORD: $(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=True"
LinuxPart2Of3:
testCommand: "npx playwright test DefaultConfig --grep-invert \"Users\" --shard=2/3"
vmImage: "ubuntu-latest"
SA_PASSWORD: $(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=True"
LinuxPart3Of3:
testCommand: "npx playwright test DefaultConfig --grep-invert \"Users\" --shard=3/3"
vmImage: "ubuntu-latest"
SA_PASSWORD: $(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=True"
WindowsPart1Of3:
vmImage: "windows-latest"
testCommand: "npx playwright test DefaultConfig --grep-invert \"Users\" --shard=1/3"
WindowsPart2Of3:
vmImage: "windows-latest"
testCommand: "npx playwright test DefaultConfig --grep-invert \"Users\" --shard=2/3"
WindowsPart3Of3:
vmImage: "windows-latest"
testCommand: "npx playwright test DefaultConfig --grep-invert \"Users\" --shard=3/3"
CONNECTIONSTRINGS__UMBRACODBDSN: 'Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=True'
Windows:
vmImage: 'windows-latest'
pool:
vmImage: $(vmImage)
steps:
@@ -376,7 +352,10 @@ stages:
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
# Test
- pwsh: $(testCommand)
- ${{ if eq(parameters.runSmokeTests, true) }}:
pwsh: npm run smokeTest --ignore-certificate-errors
${{ else }}:
pwsh: npm run test --ignore-certificate-errors
displayName: Run Playwright tests
continueOnError: true
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
+2 -3
View File
@@ -1,7 +1,6 @@
{
"sdk": {
"version": "9.0.100",
"rollForward": "latestFeature",
"allowPrerelease": false
"version": "8.0.300",
"rollForward": "latestFeature"
}
}
@@ -6,7 +6,6 @@ using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using Umbraco.Cms.Api.Common.OpenApi;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Common.Configuration;
@@ -15,23 +14,22 @@ public class ConfigureUmbracoSwaggerGenOptions : IConfigureOptions<SwaggerGenOpt
{
private readonly IOperationIdSelector _operationIdSelector;
private readonly ISchemaIdSelector _schemaIdSelector;
private readonly ISubTypesSelector _subTypesSelector;
[Obsolete("Use non-obsolete constructor. This will be removed in Umbraco 16.")]
[Obsolete("Use non-obsolete constructor. This will be removed in Umbraco 15.")]
public ConfigureUmbracoSwaggerGenOptions(
IOptions<ApiVersioningOptions> apiVersioningOptions,
IOperationIdSelector operationIdSelector,
ISchemaIdSelector schemaIdSelector)
: this(operationIdSelector, schemaIdSelector)
{
}
public ConfigureUmbracoSwaggerGenOptions(
IOperationIdSelector operationIdSelector,
ISchemaIdSelector schemaIdSelector)
: this(operationIdSelector, schemaIdSelector, StaticServiceProvider.Instance.GetRequiredService<ISubTypesSelector>())
{ }
public ConfigureUmbracoSwaggerGenOptions(
IOperationIdSelector operationIdSelector,
ISchemaIdSelector schemaIdSelector,
ISubTypesSelector subTypesSelector)
{
_operationIdSelector = operationIdSelector;
_schemaIdSelector = schemaIdSelector;
_subTypesSelector = subTypesSelector;
}
public void Configure(SwaggerGenOptions swaggerGenOptions)
@@ -64,7 +62,6 @@ public class ConfigureUmbracoSwaggerGenOptions : IConfigureOptions<SwaggerGenOpt
swaggerGenOptions.OrderActionsBy(ActionOrderBy);
swaggerGenOptions.SchemaFilter<EnumSchemaFilter>();
swaggerGenOptions.CustomSchemaIds(_schemaIdSelector.SchemaId);
swaggerGenOptions.SelectSubTypesUsing(_subTypesSelector.SubTypes);
swaggerGenOptions.SupportNonNullableReferenceTypes();
}
@@ -23,8 +23,6 @@ public static class UmbracoBuilderApiExtensions
builder.Services.AddSingleton<IOperationIdHandler, OperationIdHandler>();
builder.Services.AddSingleton<ISchemaIdSelector, SchemaIdSelector>();
builder.Services.AddSingleton<ISchemaIdHandler, SchemaIdHandler>();
builder.Services.AddSingleton<ISubTypesSelector, SubTypesSelector>();
builder.Services.AddSingleton<ISubTypesHandler, SubTypesHandler>();
builder.Services.Configure<UmbracoPipelineOptions>(options => options.AddFilter(new SwaggerRouteTemplatePipelineFilter("UmbracoApiCommon")));
return builder;
@@ -41,12 +41,14 @@ public static class UmbracoBuilderAuthExtensions
.SetTokenEndpointUris(
Paths.MemberApi.TokenEndpoint.TrimStart(Constants.CharArrays.ForwardSlash),
Paths.BackOfficeApi.TokenEndpoint.TrimStart(Constants.CharArrays.ForwardSlash))
.SetEndSessionEndpointUris(
.SetLogoutEndpointUris(
Paths.MemberApi.LogoutEndpoint.TrimStart(Constants.CharArrays.ForwardSlash),
Paths.BackOfficeApi.LogoutEndpoint.TrimStart(Constants.CharArrays.ForwardSlash))
.SetRevocationEndpointUris(
Paths.MemberApi.RevokeEndpoint.TrimStart(Constants.CharArrays.ForwardSlash),
Paths.BackOfficeApi.RevokeEndpoint.TrimStart(Constants.CharArrays.ForwardSlash));
Paths.BackOfficeApi.RevokeEndpoint.TrimStart(Constants.CharArrays.ForwardSlash))
.SetUserinfoEndpointUris(
Paths.MemberApi.UserinfoEndpoint.TrimStart(Constants.CharArrays.ForwardSlash));
// Enable authorization code flow with PKCE
options
@@ -54,15 +56,12 @@ public static class UmbracoBuilderAuthExtensions
.RequireProofKeyForCodeExchange()
.AllowRefreshTokenFlow();
// Enable the client credentials flow.
options.AllowClientCredentialsFlow();
// Register the ASP.NET Core host and configure for custom authentication endpoint.
options
.UseAspNetCore()
.EnableAuthorizationEndpointPassthrough()
.EnableTokenEndpointPassthrough()
.EnableEndSessionEndpointPassthrough();
.EnableLogoutEndpointPassthrough()
.EnableUserinfoEndpointPassthrough();
// Enable reference tokens
// - see https://documentation.openiddict.com/configuration/token-storage.html
@@ -1,8 +0,0 @@
namespace Umbraco.Cms.Api.Common.OpenApi;
public interface ISubTypesHandler
{
bool CanHandle(Type type, string documentName);
IEnumerable<Type> Handle(Type type);
}
@@ -1,6 +0,0 @@
namespace Umbraco.Cms.Api.Common.OpenApi;
public interface ISubTypesSelector
{
IEnumerable<Type> SubTypes(Type type);
}
@@ -1,25 +0,0 @@
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace Umbraco.Cms.Api.Common.OpenApi;
/// <summary>
/// This filter explicitly removes all security schemes from a named OpenAPI document.
/// </summary>
public class RemoveSecuritySchemesDocumentFilter : IDocumentFilter
{
private readonly string _documentName;
public RemoveSecuritySchemesDocumentFilter(string documentName)
=> _documentName = documentName;
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
if (context.DocumentName != _documentName)
{
return;
}
swaggerDoc.Components.SecuritySchemes.Clear();
}
}
@@ -1,20 +0,0 @@
using Umbraco.Cms.Api.Common.Serialization;
namespace Umbraco.Cms.Api.Common.OpenApi;
public class SubTypesHandler : ISubTypesHandler
{
private readonly IUmbracoJsonTypeInfoResolver _umbracoJsonTypeInfoResolver;
public SubTypesHandler(IUmbracoJsonTypeInfoResolver umbracoJsonTypeInfoResolver)
=> _umbracoJsonTypeInfoResolver = umbracoJsonTypeInfoResolver;
protected virtual bool CanHandle(Type type)
=> type.Namespace?.StartsWith("Umbraco.Cms") is true;
public virtual bool CanHandle(Type type, string documentName)
=> CanHandle(type);
public virtual IEnumerable<Type> Handle(Type type)
=> _umbracoJsonTypeInfoResolver.FindSubTypes(type);
}
@@ -1,60 +0,0 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Api.Common.Serialization;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Hosting;
using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Common.OpenApi;
public class SubTypesSelector : ISubTypesSelector
{
private readonly IOptions<GlobalSettings> _settings;
private readonly IHostingEnvironment _hostingEnvironment;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IEnumerable<ISubTypesHandler> _subTypeHandlers;
private readonly IUmbracoJsonTypeInfoResolver _umbracoJsonTypeInfoResolver;
public SubTypesSelector(
IOptions<GlobalSettings> settings,
IHostingEnvironment hostingEnvironment,
IHttpContextAccessor httpContextAccessor,
IEnumerable<ISubTypesHandler> subTypeHandlers,
IUmbracoJsonTypeInfoResolver umbracoJsonTypeInfoResolver)
{
_settings = settings;
_hostingEnvironment = hostingEnvironment;
_httpContextAccessor = httpContextAccessor;
_subTypeHandlers = subTypeHandlers;
_umbracoJsonTypeInfoResolver = umbracoJsonTypeInfoResolver;
}
public IEnumerable<Type> SubTypes(Type type)
{
var backOfficePath = _settings.Value.GetBackOfficePath(_hostingEnvironment);
var swaggerPath = $"{backOfficePath}/swagger";
if (_httpContextAccessor.HttpContext?.Request.Path.StartsWithSegments(swaggerPath) ?? false)
{
// Split the path into segments
var segments = _httpContextAccessor.HttpContext.Request.Path.Value!
.Substring(swaggerPath.Length)
.TrimStart(Constants.CharArrays.ForwardSlash)
.Split(Constants.CharArrays.ForwardSlash);
// Extract the document name from the path
var documentName = segments[0];
// Find the first handler that can handle the type / document name combination
ISubTypesHandler? handler = _subTypeHandlers.FirstOrDefault(h => h.CanHandle(type, documentName));
if (handler != null)
{
return handler.Handle(type);
}
}
// Default implementation to maintain backwards compatibility
return _umbracoJsonTypeInfoResolver.FindSubTypes(type);
}
}
@@ -31,6 +31,8 @@ public static class Paths
public static readonly string RevokeEndpoint = EndpointPath($"{EndpointTemplate}/revoke");
public static readonly string UserinfoEndpoint = EndpointPath($"{EndpointTemplate}/userinfo");
// NOTE: we're NOT using /api/v1.0/ here because it will clash with the Delivery API docs
private static string EndpointPath(string relativePath) => $"/umbraco/delivery/api/v1/{relativePath}";
}
@@ -3,7 +3,6 @@
<Title>Umbraco CMS - API Common</Title>
<Description>Contains the bits and pieces that are shared between the Umbraco CMS APIs.</Description>
</PropertyGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
@@ -14,15 +13,17 @@
<PackageReference Include="Swashbuckle.AspNetCore" />
<PackageReference Include="OpenIddict.Abstractions" />
<PackageReference Include="OpenIddict.AspNetCore" />
</ItemGroup>
<ItemGroup>
<!-- Take top-level depedendency on Microsoft.IdentityModel.JsonWebTokens, because OpenIddict.AspNetCore depends on a vulnerable version -->
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" />
<!-- Both OpenIddict.AspNetCore, Npoco.SqlServer and Microsoft.EntityFrameworkCore.SqlServer bring in a vulnerable version of Microsoft.IdentityModel.JsonWebTokens -->
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens"/>
<!-- Take top-level depedendency on OpenIddict.AspNetCore depends on a vulnerable version -->
<PackageReference Include="Microsoft.Extensions.Caching.Memory" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Umbraco.Core\Umbraco.Core.csproj" />
<ProjectReference Include="..\Umbraco.Web.Common\Umbraco.Web.Common.csproj" />
</ItemGroup>
</Project>
@@ -1,19 +0,0 @@
using Microsoft.AspNetCore.OutputCaching;
namespace Umbraco.Cms.Api.Delivery.Caching;
internal sealed class NoOutputCachePolicy : IOutputCachePolicy
{
ValueTask IOutputCachePolicy.CacheRequestAsync(OutputCacheContext context, CancellationToken cancellationToken)
{
context.EnableOutputCaching = false;
return ValueTask.CompletedTask;
}
ValueTask IOutputCachePolicy.ServeFromCacheAsync(OutputCacheContext context, CancellationToken cancellationToken)
=> ValueTask.CompletedTask;
ValueTask IOutputCachePolicy.ServeResponseAsync(OutputCacheContext context, CancellationToken cancellationToken)
=> ValueTask.CompletedTask;
}
@@ -21,7 +21,6 @@ public class ConfigureUmbracoDeliveryApiSwaggerGenOptions: IConfigureOptions<Swa
});
swaggerGenOptions.DocumentFilter<MimeTypeDocumentFilter>(DeliveryApiConfiguration.ApiName);
swaggerGenOptions.DocumentFilter<RemoveSecuritySchemesDocumentFilter>(DeliveryApiConfiguration.ApiName);
swaggerGenOptions.OperationFilter<SwaggerContentDocumentationFilter>();
swaggerGenOptions.OperationFilter<SwaggerMediaDocumentationFilter>();
@@ -17,16 +17,33 @@ namespace Umbraco.Cms.Api.Delivery.Configuration;
/// </remarks>
public class ConfigureUmbracoMemberAuthenticationDeliveryApiSwaggerGenOptions : IConfigureOptions<SwaggerGenOptions>
{
private const string AuthSchemeName = "UmbracoMember";
private const string AuthSchemeName = "Umbraco Member";
public void Configure(SwaggerGenOptions options)
{
options.AddSecurityDefinition(
AuthSchemeName,
new OpenApiSecurityScheme
{
In = ParameterLocation.Header,
Name = AuthSchemeName,
Type = SecuritySchemeType.OAuth2,
Description = "Umbraco Member Authentication",
Flows = new OpenApiOAuthFlows
{
AuthorizationCode = new OpenApiOAuthFlow
{
AuthorizationUrl = new Uri(Paths.MemberApi.AuthorizationEndpoint, UriKind.Relative),
TokenUrl = new Uri(Paths.MemberApi.TokenEndpoint, UriKind.Relative)
}
}
});
// add security requirements for content API operations
options.DocumentFilter<DeliveryApiSecurityFilter>();
options.OperationFilter<DeliveryApiSecurityFilter>();
}
private class DeliveryApiSecurityFilter : SwaggerFilterBase<ContentApiControllerBase>, IOperationFilter, IDocumentFilter
private class DeliveryApiSecurityFilter : SwaggerFilterBase<ContentApiControllerBase>, IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
@@ -53,31 +70,5 @@ public class ConfigureUmbracoMemberAuthenticationDeliveryApiSwaggerGenOptions :
}
};
}
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
if (context.DocumentName != DeliveryApiConfiguration.ApiName)
{
return;
}
swaggerDoc.Components.SecuritySchemes.Add(
AuthSchemeName,
new OpenApiSecurityScheme
{
In = ParameterLocation.Header,
Name = AuthSchemeName,
Type = SecuritySchemeType.OAuth2,
Description = "Umbraco Member Authentication",
Flows = new OpenApiOAuthFlows
{
AuthorizationCode = new OpenApiOAuthFlow
{
AuthorizationUrl = new Uri(Paths.MemberApi.AuthorizationEndpoint, UriKind.Relative),
TokenUrl = new Uri(Paths.MemberApi.TokenEndpoint, UriKind.Relative)
}
}
});
}
}
}
@@ -1,4 +1,3 @@
using System.Diagnostics;
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
@@ -47,17 +46,19 @@ public class ByIdContentApiController : ContentApiItemControllerBase
private async Task<IActionResult> HandleRequest(Guid id)
{
IPublishedContent? contentItem = await ApiPublishedContentCache.GetByIdAsync(id).ConfigureAwait(false);
IPublishedContent? contentItem = ApiPublishedContentCache.GetById(id);
if (contentItem is null)
{
return NotFound();
}
IActionResult? deniedAccessResult = await HandleMemberAccessAsync(contentItem, _requestMemberAccessService).ConfigureAwait(false);
IActionResult? deniedAccessResult = await HandleMemberAccessAsync(contentItem, _requestMemberAccessService);
if (deniedAccessResult is not null)
{
return deniedAccessResult;
}
IApiContentResponse? apiContentResponse = ApiContentResponseBuilder.Build(contentItem);
if (apiContentResponse is null)
{
@@ -45,7 +45,7 @@ public class ByIdsContentApiController : ContentApiItemControllerBase
private async Task<IActionResult> HandleRequest(HashSet<Guid> ids)
{
IPublishedContent[] contentItems = (await ApiPublishedContentCache.GetByIdsAsync(ids).ConfigureAwait(false)).ToArray();
IPublishedContent[] contentItems = ApiPublishedContentCache.GetByIds(ids).ToArray();
IActionResult? deniedAccessResult = await HandleMemberAccessAsync(contentItems, _requestMemberAccessService);
if (deniedAccessResult is not null)
@@ -15,6 +15,7 @@ namespace Umbraco.Cms.Api.Delivery.Controllers.Content;
[ApiExplorerSettings(GroupName = "Content")]
[LocalizeFromAcceptLanguageHeader]
[ValidateStartItem]
[AddVaryHeader]
[OutputCache(PolicyName = Constants.DeliveryApi.OutputCache.ContentCachePolicy)]
public abstract class ContentApiControllerBase : DeliveryApiControllerBase
{
@@ -12,10 +12,8 @@ namespace Umbraco.Cms.Api.Delivery.Controllers.Media;
[ApiVersion("2.0")]
public class ByIdMediaApiController : MediaApiControllerBase
{
public ByIdMediaApiController(
IPublishedMediaCache publishedMediaCache,
IApiMediaWithCropsResponseBuilder apiMediaWithCropsResponseBuilder)
: base(publishedMediaCache, apiMediaWithCropsResponseBuilder)
public ByIdMediaApiController(IPublishedSnapshotAccessor publishedSnapshotAccessor, IApiMediaWithCropsResponseBuilder apiMediaWithCropsResponseBuilder)
: base(publishedSnapshotAccessor, apiMediaWithCropsResponseBuilder)
{
}
@@ -13,8 +13,8 @@ namespace Umbraco.Cms.Api.Delivery.Controllers.Media;
[ApiVersion("2.0")]
public class ByIdsMediaApiController : MediaApiControllerBase
{
public ByIdsMediaApiController(IPublishedMediaCache publishedMediaCache, IApiMediaWithCropsResponseBuilder apiMediaWithCropsResponseBuilder)
: base(publishedMediaCache, apiMediaWithCropsResponseBuilder)
public ByIdsMediaApiController(IPublishedSnapshotAccessor publishedSnapshotAccessor, IApiMediaWithCropsResponseBuilder apiMediaWithCropsResponseBuilder)
: base(publishedSnapshotAccessor, apiMediaWithCropsResponseBuilder)
{
}
@@ -16,10 +16,10 @@ public class ByPathMediaApiController : MediaApiControllerBase
private readonly IApiMediaQueryService _apiMediaQueryService;
public ByPathMediaApiController(
IPublishedMediaCache publishedMediaCache,
IPublishedSnapshotAccessor publishedSnapshotAccessor,
IApiMediaWithCropsResponseBuilder apiMediaWithCropsResponseBuilder,
IApiMediaQueryService apiMediaQueryService)
: base(publishedMediaCache, apiMediaWithCropsResponseBuilder)
: base(publishedSnapshotAccessor, apiMediaWithCropsResponseBuilder)
=> _apiMediaQueryService = apiMediaQueryService;
[HttpGet("item/{*path}")]
@@ -20,15 +20,18 @@ namespace Umbraco.Cms.Api.Delivery.Controllers.Media;
public abstract class MediaApiControllerBase : DeliveryApiControllerBase
{
private readonly IApiMediaWithCropsResponseBuilder _apiMediaWithCropsResponseBuilder;
private IPublishedMediaCache _publishedMediaCache;
private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor;
private IPublishedMediaCache? _publishedMediaCache;
protected MediaApiControllerBase(IPublishedMediaCache publishedMediaCache, IApiMediaWithCropsResponseBuilder apiMediaWithCropsResponseBuilder)
protected MediaApiControllerBase(IPublishedSnapshotAccessor publishedSnapshotAccessor, IApiMediaWithCropsResponseBuilder apiMediaWithCropsResponseBuilder)
{
_publishedMediaCache = publishedMediaCache;
_publishedSnapshotAccessor = publishedSnapshotAccessor;
_apiMediaWithCropsResponseBuilder = apiMediaWithCropsResponseBuilder;
}
protected IPublishedMediaCache PublishedMediaCache => _publishedMediaCache;
protected IPublishedMediaCache PublishedMediaCache => _publishedMediaCache
??= _publishedSnapshotAccessor.GetRequiredPublishedSnapshot().Media
?? throw new InvalidOperationException("Could not obtain the published media cache");
protected IApiMediaWithCropsResponse BuildApiMediaWithCrops(IPublishedContent media)
=> _apiMediaWithCropsResponseBuilder.Build(media);
@@ -21,10 +21,10 @@ public class QueryMediaApiController : MediaApiControllerBase
private readonly IApiMediaQueryService _apiMediaQueryService;
public QueryMediaApiController(
IPublishedMediaCache publishedMediaCache,
IPublishedSnapshotAccessor publishedSnapshotAccessor,
IApiMediaWithCropsResponseBuilder apiMediaWithCropsResponseBuilder,
IApiMediaQueryService apiMediaQueryService)
: base(publishedMediaCache, apiMediaWithCropsResponseBuilder)
: base(publishedSnapshotAccessor, apiMediaWithCropsResponseBuilder)
=> _apiMediaQueryService = apiMediaQueryService;
[HttpGet]
@@ -0,0 +1,28 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using OpenIddict.Server.AspNetCore;
using Umbraco.Cms.Api.Delivery.Routing;
using Umbraco.Cms.Core.DeliveryApi;
namespace Umbraco.Cms.Api.Delivery.Controllers.Security;
[ApiVersion("1.0")]
[ApiController]
[VersionedDeliveryApiRoute(Common.Security.Paths.MemberApi.EndpointTemplate)]
[ApiExplorerSettings(IgnoreApi = true)]
[Authorize(AuthenticationSchemes = OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)]
public class CurrentMemberController : DeliveryApiControllerBase
{
private readonly ICurrentMemberClaimsProvider _currentMemberClaimsProvider;
public CurrentMemberController(ICurrentMemberClaimsProvider currentMemberClaimsProvider)
=> _currentMemberClaimsProvider = currentMemberClaimsProvider;
[HttpGet("userinfo")]
public async Task<IActionResult> Userinfo()
{
Dictionary<string, object> claims = await _currentMemberClaimsProvider.GetClaimsAsync();
return Ok(claims);
}
}
@@ -5,7 +5,6 @@ using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using OpenIddict.Abstractions;
@@ -13,7 +12,6 @@ using OpenIddict.Server.AspNetCore;
using Umbraco.Cms.Api.Delivery.Routing;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Web.Common.Security;
using Umbraco.Extensions;
@@ -27,47 +25,22 @@ namespace Umbraco.Cms.Api.Delivery.Controllers.Security;
[ApiExplorerSettings(IgnoreApi = true)]
public class MemberController : DeliveryApiControllerBase
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IMemberSignInManager _memberSignInManager;
private readonly IMemberManager _memberManager;
private readonly IMemberClientCredentialsManager _memberClientCredentialsManager;
private readonly DeliveryApiSettings _deliveryApiSettings;
private readonly ILogger<MemberController> _logger;
[Obsolete("Please use the non-obsolete constructor. Will be removed in V16.")]
public MemberController(
IHttpContextAccessor httpContextAccessor,
IMemberSignInManager memberSignInManager,
IMemberManager memberManager,
IOptions<DeliveryApiSettings> deliveryApiSettings,
ILogger<MemberController> logger)
: this(memberSignInManager, memberManager, StaticServiceProvider.Instance.GetRequiredService<IMemberClientCredentialsManager>(), deliveryApiSettings, logger)
{
}
[Obsolete("Please use the non-obsolete constructor. Will be removed in V16.")]
public MemberController(
IHttpContextAccessor httpContextAccessor,
IMemberSignInManager memberSignInManager,
IMemberManager memberManager,
IMemberClientCredentialsManager memberClientCredentialsManager,
IOptions<DeliveryApiSettings> deliveryApiSettings,
ILogger<MemberController> logger)
: this(memberSignInManager, memberManager, memberClientCredentialsManager, deliveryApiSettings, logger)
{
}
[ActivatorUtilitiesConstructor]
public MemberController(
IMemberSignInManager memberSignInManager,
IMemberManager memberManager,
IMemberClientCredentialsManager memberClientCredentialsManager,
IOptions<DeliveryApiSettings> deliveryApiSettings,
ILogger<MemberController> logger)
{
_httpContextAccessor = httpContextAccessor;
_memberSignInManager = memberSignInManager;
_memberManager = memberManager;
_memberClientCredentialsManager = memberClientCredentialsManager;
_logger = logger;
_deliveryApiSettings = deliveryApiSettings.Value;
}
@@ -76,31 +49,25 @@ public class MemberController : DeliveryApiControllerBase
[MapToApiVersion("1.0")]
public async Task<IActionResult> Authorize()
{
// the Authorize endpoint is not allowed unless authorization code flow is enabled.
if (_deliveryApiSettings.MemberAuthorization?.AuthorizationCodeFlow?.Enabled is not true)
// in principle this is not necessary for now, since the member application has been removed, thus making
// the member client ID invalid for the authentication code flow. However, if we ever add additional flows
// to the API, we should perform this check, so we might as well include it upfront.
if (_deliveryApiSettings.MemberAuthorizationIsEnabled() is false)
{
return BadRequest(new OpenIddictResponse
{
Error = "Not allowed", ErrorDescription = "Member authorization is not allowed."
});
return BadRequest("Member authorization is not allowed.");
}
OpenIddictRequest? request = HttpContext.GetOpenIddictServerRequest();
HttpContext context = _httpContextAccessor.GetRequiredHttpContext();
OpenIddictRequest? request = context.GetOpenIddictServerRequest();
if (request is null)
{
return BadRequest(new OpenIddictResponse
{
Error = "No context found", ErrorDescription = "Unable to obtain context from the current request."
});
return BadRequest("Unable to obtain OpenID data from the current request.");
}
// make sure this endpoint ONLY handles member authentication
if (request.ClientId is not Constants.OAuthClientIds.Member)
{
return BadRequest(new OpenIddictResponse
{
Error = "Invalid 'client ID'", ErrorDescription = "The specified 'client_id' is not valid."
});
return BadRequest("The specified client ID cannot be used here.");
}
return request.IdentityProvider.IsNullOrWhiteSpace()
@@ -108,50 +75,6 @@ public class MemberController : DeliveryApiControllerBase
: await AuthorizeExternal(request);
}
[HttpPost("token")]
[MapToApiVersion("1.0")]
public async Task<IActionResult> Token()
{
OpenIddictRequest? request = HttpContext.GetOpenIddictServerRequest();
if (request is null)
{
return BadRequest(new OpenIddictResponse
{
Error = "No context found", ErrorDescription = "Unable to obtain context from the current request."
});
}
// authorization code flow or refresh token flow?
if ((request.IsAuthorizationCodeGrantType() || request.IsRefreshTokenGrantType()) && _deliveryApiSettings.MemberAuthorization?.AuthorizationCodeFlow?.Enabled is true)
{
// attempt to authorize against the supplied the authorization code
AuthenticateResult authenticateResult = await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
return authenticateResult is { Succeeded: true, Principal: not null }
? new SignInResult(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, authenticateResult.Principal)
: BadRequest(new OpenIddictResponse
{
Error = "Authorization failed", ErrorDescription = "The supplied authorization could not be verified."
});
}
// client credentials flow?
if (request.IsClientCredentialsGrantType() && _deliveryApiSettings.MemberAuthorization?.ClientCredentialsFlow?.Enabled is true)
{
// if we get here, the client ID and secret are valid (verified by OpenIddict)
MemberIdentityUser? member = await _memberClientCredentialsManager.FindMemberAsync(request.ClientId!);
return member is not null
? await SignInMember(member, request)
: BadRequest(new OpenIddictResponse
{
Error = "Authorization failed", ErrorDescription = "Invalid 'client_id' or client configuration."
});
}
throw new InvalidOperationException("The requested grant type is not supported.");
}
[HttpGet("signout")]
[MapToApiVersion("1.0")]
public async Task<IActionResult> Signout()
@@ -177,10 +100,7 @@ public class MemberController : DeliveryApiControllerBase
if (member is null)
{
_logger.LogError("The member with username {userName} was successfully authorized, but could not be retrieved by the member manager", userName);
return BadRequest(new OpenIddictResponse
{
Error = "Authorization failed", ErrorDescription = "The member associated with the supplied 'client_id' could not be found."
});
return BadRequest("The member could not be found.");
}
return await SignInMember(member, request);
@@ -208,10 +128,7 @@ public class MemberController : DeliveryApiControllerBase
if (member is null)
{
_logger.LogError("A member was successfully authorized using external authentication, but could not be retrieved by the member manager");
return BadRequest(new OpenIddictResponse
{
Error = "Authorization failed", ErrorDescription = "The member associated with the supplied 'client_id' could not be found."
});
return BadRequest("The member could not be found.");
}
// update member authentication tokens if succeeded
@@ -21,7 +21,6 @@ using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.DeliveryApi;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Notifications;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Infrastructure.Security;
using Umbraco.Cms.Web.Common.ApplicationBuilder;
@@ -61,7 +60,7 @@ public static class UmbracoBuilderExtensions
builder.Services.AddSingleton<IApiMediaQueryService, ApiMediaQueryService>();
builder.Services.AddTransient<IMemberApplicationManager, MemberApplicationManager>();
builder.Services.AddTransient<IRequestMemberAccessService, RequestMemberAccessService>();
builder.Services.AddScoped<IMemberClientCredentialsManager, MemberClientCredentialsManager>();
builder.Services.AddTransient<ICurrentMemberClaimsProvider, CurrentMemberClaimsProvider>();
builder.Services.ConfigureOptions<ConfigureUmbracoDeliveryApiSwaggerGenOptions>();
builder.AddUmbracoApiOpenApiUI();
@@ -105,7 +104,7 @@ public static class UmbracoBuilderExtensions
builder.Services.AddOutputCache(options =>
{
options.AddBasePolicy(build => build.AddPolicy<NoOutputCachePolicy>());
options.AddBasePolicy(_ => { });
if (outputCacheSettings.ContentDuration.TotalSeconds > 0)
{
@@ -0,0 +1,13 @@
using Microsoft.AspNetCore.Mvc.Filters;
namespace Umbraco.Cms.Api.Delivery.Filters;
public sealed class AddVaryHeaderAttribute : ActionFilterAttribute
{
private const string Vary = "Accept-Language, Preview, Start-Item";
public override void OnResultExecuting(ResultExecutingContext context)
=> context.HttpContext.Response.Headers.Vary = context.HttpContext.Response.Headers.Vary.Count > 0
? $"{context.HttpContext.Response.Headers.Vary}, {Vary}"
: Vary;
}
@@ -5,7 +5,6 @@ using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Notifications;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Infrastructure.Security;
@@ -17,19 +16,16 @@ internal sealed class InitializeMemberApplicationNotificationHandler : INotifica
private readonly ILogger<InitializeMemberApplicationNotificationHandler> _logger;
private readonly DeliveryApiSettings _deliveryApiSettings;
private readonly IServiceScopeFactory _serviceScopeFactory;
private readonly IMemberClientCredentialsManager _memberClientCredentialsManager;
public InitializeMemberApplicationNotificationHandler(
IRuntimeState runtimeState,
IOptions<DeliveryApiSettings> deliveryApiSettings,
ILogger<InitializeMemberApplicationNotificationHandler> logger,
IServiceScopeFactory serviceScopeFactory,
IMemberClientCredentialsManager memberClientCredentialsManager)
IServiceScopeFactory serviceScopeFactory)
{
_runtimeState = runtimeState;
_logger = logger;
_serviceScopeFactory = serviceScopeFactory;
_memberClientCredentialsManager = memberClientCredentialsManager;
_deliveryApiSettings = deliveryApiSettings.Value;
}
@@ -45,12 +41,6 @@ internal sealed class InitializeMemberApplicationNotificationHandler : INotifica
using IServiceScope scope = _serviceScopeFactory.CreateScope();
IMemberApplicationManager memberApplicationManager = scope.ServiceProvider.GetRequiredService<IMemberApplicationManager>();
await HandleMemberApplication(memberApplicationManager, cancellationToken);
await HandleMemberClientCredentialsApplication(memberApplicationManager, cancellationToken);
}
private async Task HandleMemberApplication(IMemberApplicationManager memberApplicationManager, CancellationToken cancellationToken)
{
if (_deliveryApiSettings.MemberAuthorization?.AuthorizationCodeFlow?.Enabled is not true)
{
await memberApplicationManager.DeleteMemberApplicationAsync(cancellationToken);
@@ -76,21 +66,6 @@ internal sealed class InitializeMemberApplicationNotificationHandler : INotifica
cancellationToken);
}
private async Task HandleMemberClientCredentialsApplication(IMemberApplicationManager memberApplicationManager, CancellationToken cancellationToken)
{
if (_deliveryApiSettings.MemberAuthorization?.ClientCredentialsFlow?.Enabled is not true)
{
// disabled
return;
}
IEnumerable<MemberClientCredentials> memberClientCredentials = await _memberClientCredentialsManager.GetAllAsync();
foreach (MemberClientCredentials memberClientCredential in memberClientCredentials)
{
await memberApplicationManager.EnsureMemberClientCredentialsApplicationAsync(memberClientCredential.ClientId, memberClientCredential.ClientSecret, cancellationToken);
}
}
private bool ValidateRedirectUrls(Uri[] redirectUrls)
{
if (redirectUrls.Any() is false)
@@ -1,56 +1,7 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using Umbraco.Cms.Core.Models.DeliveryApi;
using Umbraco.Cms.Infrastructure.Serialization;
namespace Umbraco.Cms.Api.Delivery.Json;
// see https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/polymorphism?pivots=dotnet-7-0
public class DeliveryApiJsonTypeResolver : DefaultJsonTypeInfoResolver
{
public override JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options)
{
JsonTypeInfo jsonTypeInfo = base.GetTypeInfo(type, options);
Type[] derivedTypes = GetDerivedTypes(jsonTypeInfo);
if (derivedTypes.Length > 0)
{
ConfigureJsonPolymorphismOptions(jsonTypeInfo, derivedTypes);
}
return jsonTypeInfo;
}
protected virtual Type[] GetDerivedTypes(JsonTypeInfo jsonTypeInfo)
{
if (jsonTypeInfo.Type == typeof(IApiContent))
{
return new[] { typeof(ApiContent) };
}
if (jsonTypeInfo.Type == typeof(IApiContentResponse))
{
return new[] { typeof(ApiContentResponse) };
}
if (jsonTypeInfo.Type == typeof(IRichTextElement))
{
return new[] { typeof(RichTextRootElement), typeof(RichTextGenericElement), typeof(RichTextTextElement) };
}
return Array.Empty<Type>();
}
protected void ConfigureJsonPolymorphismOptions(JsonTypeInfo jsonTypeInfo, params Type[] derivedTypes)
{
jsonTypeInfo.PolymorphismOptions = new JsonPolymorphismOptions
{
UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FailSerialization,
};
foreach (Type derivedType in derivedTypes)
{
jsonTypeInfo.PolymorphismOptions.DerivedTypes.Add(new JsonDerivedType(derivedType));
}
}
}
public class DeliveryApiJsonTypeResolver : ContentJsonTypeResolverBase
{ }
@@ -0,0 +1,88 @@
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Umbraco.Cms.Core.DeliveryApi;
namespace Umbraco.Cms.Api.Delivery.Json;
public abstract class DeliveryApiVersionAwareJsonConverterBase<T> : JsonConverter<T>
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly JsonConverter<T> _defaultConverter = (JsonConverter<T>)JsonSerializerOptions.Default.GetConverter(typeof(T));
public DeliveryApiVersionAwareJsonConverterBase(IHttpContextAccessor httpContextAccessor)
=> _httpContextAccessor = httpContextAccessor;
/// <inheritdoc />
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
=> _defaultConverter.Read(ref reader, typeToConvert, options);
/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
Type type = typeof(T);
var apiVersion = GetApiVersion();
// Get the properties in the specified order
PropertyInfo[] properties = type.GetProperties().OrderBy(GetPropertyOrder).ToArray();
writer.WriteStartObject();
foreach (PropertyInfo property in properties)
{
// Filter out properties based on the API version
var include = apiVersion is null || ShouldIncludeProperty(property, apiVersion.Value);
if (include is false)
{
continue;
}
var propertyName = property.Name;
writer.WritePropertyName(options.PropertyNamingPolicy?.ConvertName(propertyName) ?? propertyName);
JsonSerializer.Serialize(writer, property.GetValue(value), options);
}
writer.WriteEndObject();
}
private int? GetApiVersion()
{
HttpContext? httpContext = _httpContextAccessor.HttpContext;
ApiVersion? apiVersion = httpContext?.GetRequestedApiVersion();
return apiVersion?.MajorVersion;
}
private int GetPropertyOrder(PropertyInfo prop)
{
var attribute = prop.GetCustomAttribute<JsonPropertyOrderAttribute>();
return attribute?.Order ?? 0;
}
/// <summary>
/// Determines whether a property should be included based on version bounds.
/// </summary>
/// <param name="propertyInfo">The property info.</param>
/// <param name="version">An integer representing an API version.</param>
/// <returns><c>true</c> if the property should be included; otherwise, <c>false</c>.</returns>
private bool ShouldIncludeProperty(PropertyInfo propertyInfo, int version)
{
var attribute = propertyInfo
.GetCustomAttributes(typeof(IncludeInApiVersionAttribute), false)
.FirstOrDefault();
if (attribute is not IncludeInApiVersionAttribute apiVersionAttribute)
{
return true; // No attribute means include the property
}
// Check if the version is within the specified bounds
var isWithinMinVersion = apiVersionAttribute.MinVersion.HasValue is false || version >= apiVersionAttribute.MinVersion.Value;
var isWithinMaxVersion = apiVersionAttribute.MaxVersion.HasValue is false || version <= apiVersionAttribute.MaxVersion.Value;
return isWithinMinVersion && isWithinMaxVersion;
}
}
@@ -1,6 +1,5 @@
using Umbraco.Cms.Api.Delivery.Indexing.Filters;
using Umbraco.Cms.Core.DeliveryApi;
using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Delivery.Querying.Filters;
@@ -15,15 +14,15 @@ public sealed class ContentTypeFilter : IFilterHandler
/// <inheritdoc/>
public FilterOption BuildFilterOption(string filter)
{
var alias = filter.Substring(ContentTypeSpecifier.Length);
var filterValue = filter.Substring(ContentTypeSpecifier.Length);
var negate = filterValue.StartsWith('!');
var aliases = filterValue.TrimStart('!').Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
return new FilterOption
{
FieldName = ContentTypeFilterIndexer.FieldName,
Values = alias.IsNullOrWhiteSpace() == false
? new[] { alias.TrimStart('!') }
: Array.Empty<string>(),
Operator = alias.StartsWith('!')
Values = aliases,
Operator = negate
? FilterOperation.IsNot
: FilterOperation.Is
};
@@ -7,15 +7,14 @@ namespace Umbraco.Cms.Api.Delivery.Querying;
public abstract class QueryOptionBase
{
private readonly IPublishedContentCache _publishedContentCache;
private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor;
private readonly IRequestRoutingService _requestRoutingService;
public QueryOptionBase(
IPublishedContentCache publishedContentCache,
IPublishedSnapshotAccessor publishedSnapshotAccessor,
IRequestRoutingService requestRoutingService)
{
_publishedContentCache = publishedContentCache;
_publishedSnapshotAccessor = publishedSnapshotAccessor;
_requestRoutingService = requestRoutingService;
}
@@ -31,9 +30,11 @@ public abstract class QueryOptionBase
return id;
}
IPublishedSnapshot publishedSnapshot = _publishedSnapshotAccessor.GetRequiredPublishedSnapshot();
// Check if the passed value is a path of a content item
var contentRoute = _requestRoutingService.GetContentRoute(queryStringValue);
IPublishedContent? contentItem = _publishedContentCache.GetByRoute(contentRoute);
IPublishedContent? contentItem = publishedSnapshot.Content?.GetByRoute(contentRoute);
return contentItem?.Key;
}
@@ -4,31 +4,29 @@ using Umbraco.Cms.Core.DeliveryApi;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.PublishedCache;
using Umbraco.Cms.Core.Services.Navigation;
using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Delivery.Querying.Selectors;
public sealed class AncestorsSelector : QueryOptionBase, ISelectorHandler
{
private readonly IPublishedContentCache _publishedContentCache;
private readonly IDocumentNavigationQueryService _navigationQueryService;
private const string AncestorsSpecifier = "ancestors:";
private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor;
private readonly IRequestPreviewService _requestPreviewService;
[Obsolete("Please use the non-obsolete constructor. Will be removed in V17.")]
public AncestorsSelector(
IPublishedContentCache publishedContentCache,
IRequestRoutingService requestRoutingService,
IDocumentNavigationQueryService navigationQueryService)
: base(publishedContentCache, requestRoutingService)
IPublishedSnapshotAccessor publishedSnapshotAccessor,
IRequestRoutingService requestRoutingService)
: this(publishedSnapshotAccessor, requestRoutingService, StaticServiceProvider.Instance.GetRequiredService<IRequestPreviewService>())
{
_publishedContentCache = publishedContentCache;
_navigationQueryService = navigationQueryService;
}
[Obsolete("Use the constructor that takes all parameters. Scheduled for removal in V17.")]
public AncestorsSelector(IPublishedContentCache publishedContentCache, IRequestRoutingService requestRoutingService)
: this(publishedContentCache, requestRoutingService, StaticServiceProvider.Instance.GetRequiredService<IDocumentNavigationQueryService>())
public AncestorsSelector(IPublishedSnapshotAccessor publishedSnapshotAccessor, IRequestRoutingService requestRoutingService, IRequestPreviewService requestPreviewService)
: base(publishedSnapshotAccessor, requestRoutingService)
{
_publishedSnapshotAccessor = publishedSnapshotAccessor;
_requestPreviewService = requestPreviewService;
}
/// <inheritdoc />
@@ -53,10 +51,22 @@ public sealed class AncestorsSelector : QueryOptionBase, ISelectorHandler
};
}
IPublishedContent contentItem = _publishedContentCache.GetById((Guid)id)
?? throw new InvalidOperationException("Could not obtain the content cache");
IPublishedContentCache contentCache = _publishedSnapshotAccessor.GetRequiredPublishedSnapshot()?.Content
?? throw new InvalidOperationException("Could not obtain the content cache");
var ancestorKeys = contentItem.Ancestors(_publishedContentCache, _navigationQueryService).Select(a => a.Key.ToString("D")).ToArray();
IPublishedContent? contentItem = contentCache.GetById(_requestPreviewService.IsPreview(), id.Value);
if (contentItem is null)
{
// no such content item, make sure the selector does not yield any results
return new SelectorOption
{
FieldName = AncestorsSelectorIndexer.FieldName,
Values = Array.Empty<string>()
};
}
var ancestorKeys = contentItem.Ancestors().Select(a => a.Key.ToString("D")).ToArray();
return new SelectorOption
{
@@ -9,8 +9,8 @@ public sealed class ChildrenSelector : QueryOptionBase, ISelectorHandler
{
private const string ChildrenSpecifier = "children:";
public ChildrenSelector(IPublishedContentCache publishedContentCache, IRequestRoutingService requestRoutingService)
: base(publishedContentCache, requestRoutingService)
public ChildrenSelector(IPublishedSnapshotAccessor publishedSnapshotAccessor, IRequestRoutingService requestRoutingService)
: base(publishedSnapshotAccessor, requestRoutingService)
{
}
@@ -9,8 +9,8 @@ public sealed class DescendantsSelector : QueryOptionBase, ISelectorHandler
{
private const string DescendantsSpecifier = "descendants:";
public DescendantsSelector(IPublishedContentCache publishedContentCache, IRequestRoutingService requestRoutingService)
: base(publishedContentCache, requestRoutingService)
public DescendantsSelector(IPublishedSnapshotAccessor publishedSnapshotAccessor, IRequestRoutingService requestRoutingService)
: base(publishedSnapshotAccessor, requestRoutingService)
{
}
@@ -41,7 +41,7 @@ public class MemberApplicationManager : OpenIdDictApplicationManagerBase, IMembe
{
OpenIddictConstants.Permissions.Endpoints.Authorization,
OpenIddictConstants.Permissions.Endpoints.Token,
OpenIddictConstants.Permissions.Endpoints.EndSession,
OpenIddictConstants.Permissions.Endpoints.Logout,
OpenIddictConstants.Permissions.Endpoints.Revocation,
OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode,
OpenIddictConstants.Permissions.GrantTypes.RefreshToken,
@@ -64,26 +64,4 @@ public class MemberApplicationManager : OpenIdDictApplicationManagerBase, IMembe
public async Task DeleteMemberApplicationAsync(CancellationToken cancellationToken = default)
=> await Delete(Constants.OAuthClientIds.Member, cancellationToken);
public async Task EnsureMemberClientCredentialsApplicationAsync(string clientId, string clientSecret, CancellationToken cancellationToken = default)
{
var applicationDescriptor = new OpenIddictApplicationDescriptor
{
DisplayName = $"Umbraco client credentials member access: {clientId}",
ClientId = clientId,
ClientSecret = clientSecret,
ClientType = OpenIddictConstants.ClientTypes.Confidential,
Permissions =
{
OpenIddictConstants.Permissions.Endpoints.Token,
OpenIddictConstants.Permissions.Endpoints.Revocation,
OpenIddictConstants.Permissions.GrantTypes.ClientCredentials
}
};
await CreateOrUpdate(applicationDescriptor, cancellationToken);
}
public async Task DeleteMemberClientCredentialsApplicationAsync(string clientId, CancellationToken cancellationToken = default)
=> await Delete(clientId, cancellationToken);
}
@@ -4,7 +4,6 @@ using Umbraco.Cms.Core.DeliveryApi;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.PublishedCache;
using Umbraco.Cms.Core.Services.Navigation;
using Umbraco.Cms.Core.Services.OperationStatus;
using Umbraco.Extensions;
@@ -13,15 +12,13 @@ namespace Umbraco.Cms.Api.Delivery.Services;
/// <inheritdoc />
internal sealed class ApiMediaQueryService : IApiMediaQueryService
{
private readonly IPublishedMediaCache _publishedMediaCache;
private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor;
private readonly ILogger<ApiMediaQueryService> _logger;
private readonly IMediaNavigationQueryService _mediaNavigationQueryService;
public ApiMediaQueryService(IPublishedMediaCache publishedMediaCache, ILogger<ApiMediaQueryService> logger, IMediaNavigationQueryService mediaNavigationQueryService)
public ApiMediaQueryService(IPublishedSnapshotAccessor publishedSnapshotAccessor, ILogger<ApiMediaQueryService> logger)
{
_publishedMediaCache = publishedMediaCache;
_publishedSnapshotAccessor = publishedSnapshotAccessor;
_logger = logger;
_mediaNavigationQueryService = mediaNavigationQueryService;
}
/// <inheritdoc/>
@@ -55,7 +52,8 @@ internal sealed class ApiMediaQueryService : IApiMediaQueryService
=> TryGetByPath(path, GetRequiredPublishedMediaCache());
private IPublishedMediaCache GetRequiredPublishedMediaCache()
=> _publishedMediaCache;
=> _publishedSnapshotAccessor.GetRequiredPublishedSnapshot().Media
?? throw new InvalidOperationException("Could not obtain the published media cache");
private IPublishedContent? TryGetByPath(string path, IPublishedMediaCache mediaCache)
{
@@ -71,7 +69,7 @@ internal sealed class ApiMediaQueryService : IApiMediaQueryService
break;
}
currentChildren = resolvedMedia.Children(null, _publishedMediaCache, _mediaNavigationQueryService);
currentChildren = resolvedMedia.Children;
}
return resolvedMedia;
@@ -104,7 +102,7 @@ internal sealed class ApiMediaQueryService : IApiMediaQueryService
? mediaCache.GetById(parentKey)
: TryGetByPath(childrenOf, mediaCache);
return parent?.Children(null, _publishedMediaCache, _mediaNavigationQueryService) ?? Array.Empty<IPublishedContent>();
return parent?.Children ?? Array.Empty<IPublishedContent>();
}
private IEnumerable<IPublishedContent>? ApplyFilters(IEnumerable<IPublishedContent> source, IEnumerable<string> filters)
@@ -0,0 +1,44 @@
using OpenIddict.Abstractions;
using Umbraco.Cms.Core.DeliveryApi;
using Umbraco.Cms.Core.Security;
namespace Umbraco.Cms.Api.Delivery.Services;
// NOTE: this is public and unsealed to allow overriding the default claims with minimal effort.
public class CurrentMemberClaimsProvider : ICurrentMemberClaimsProvider
{
private readonly IMemberManager _memberManager;
public CurrentMemberClaimsProvider(IMemberManager memberManager)
=> _memberManager = memberManager;
public virtual async Task<Dictionary<string, object>> GetClaimsAsync()
{
MemberIdentityUser? memberIdentityUser = await _memberManager.GetCurrentMemberAsync();
return memberIdentityUser is not null
? await GetClaimsForMemberIdentityAsync(memberIdentityUser)
: throw new InvalidOperationException("Could not retrieve the current member. This method should only ever be invoked when a member has been authorized.");
}
protected virtual async Task<Dictionary<string, object>> GetClaimsForMemberIdentityAsync(MemberIdentityUser memberIdentityUser)
{
var claims = new Dictionary<string, object>
{
[OpenIddictConstants.Claims.Subject] = memberIdentityUser.Key
};
if (memberIdentityUser.Name is not null)
{
claims[OpenIddictConstants.Claims.Name] = memberIdentityUser.Name;
}
if (memberIdentityUser.Email is not null)
{
claims[OpenIddictConstants.Claims.Email] = memberIdentityUser.Email;
}
claims[OpenIddictConstants.Claims.Role] = await _memberManager.GetRolesAsync(memberIdentityUser);
return claims;
}
}
@@ -21,7 +21,7 @@ internal sealed class RequestRedirectService : RoutingServiceBase, IRequestRedir
private readonly GlobalSettings _globalSettings;
public RequestRedirectService(
IDomainCache domainCache,
IPublishedSnapshotAccessor publishedSnapshotAccessor,
IHttpContextAccessor httpContextAccessor,
IRequestStartItemProviderAccessor requestStartItemProviderAccessor,
IRequestCultureService requestCultureService,
@@ -29,7 +29,7 @@ internal sealed class RequestRedirectService : RoutingServiceBase, IRequestRedir
IApiPublishedContentCache apiPublishedContentCache,
IApiContentRouteBuilder apiContentRouteBuilder,
IOptions<GlobalSettings> globalSettings)
: base(domainCache, httpContextAccessor, requestStartItemProviderAccessor)
: base(publishedSnapshotAccessor, httpContextAccessor, requestStartItemProviderAccessor)
{
_requestCultureService = requestCultureService;
_redirectUrlService = redirectUrlService;
@@ -42,31 +42,42 @@ internal sealed class RequestRedirectService : RoutingServiceBase, IRequestRedir
{
requestedPath = requestedPath.EnsureStartsWith("/");
IPublishedContent? startItem = GetStartItem();
// must append the root content url segment if it is not hidden by config, because
// the URL tracking is based on the actual URL, including the root content url segment
if (_globalSettings.HideTopLevelNodeFromPath == false)
if (_globalSettings.HideTopLevelNodeFromPath == false && startItem?.UrlSegment != null)
{
IPublishedContent? startItem = GetStartItem();
if (startItem?.UrlSegment != null)
{
requestedPath = $"{startItem.UrlSegment.EnsureStartsWith("/")}{requestedPath}";
}
requestedPath = $"{startItem.UrlSegment.EnsureStartsWith("/")}{requestedPath}";
}
var culture = _requestCultureService.GetRequestedCulture();
// append the configured domain content ID to the path if we have a domain bound request,
// because URL tracking registers the tracked url like "{domain content ID}/{content path}"
Uri contentRoute = GetDefaultRequestUri(requestedPath);
DomainAndUri? domainAndUri = GetDomainAndUriForRoute(contentRoute);
if (domainAndUri != null)
// important: redirect URLs are always tracked without trailing slashes
requestedPath = requestedPath.TrimEnd("/");
IRedirectUrl? redirectUrl = _redirectUrlService.GetMostRecentRedirectUrl(requestedPath, culture);
// if a redirect URL was not found, try by appending the start item ID because URL tracking might have tracked
// a redirect with "{root content ID}/{content path}"
if (redirectUrl is null && startItem is not null)
{
requestedPath = GetContentRoute(domainAndUri, contentRoute);
culture ??= domainAndUri.Culture;
redirectUrl = _redirectUrlService.GetMostRecentRedirectUrl($"{startItem.Id}{requestedPath}", culture);
}
// still no redirect URL found - try looking for a configured domain if we have a domain bound request,
// because URL tracking might have tracked a redirect with "{domain content ID}/{content path}"
if (redirectUrl is null)
{
Uri contentRoute = GetDefaultRequestUri(requestedPath);
DomainAndUri? domainAndUri = GetDomainAndUriForRoute(contentRoute);
if (domainAndUri is not null)
{
requestedPath = GetContentRoute(domainAndUri, contentRoute);
culture ??= domainAndUri.Culture;
redirectUrl = _redirectUrlService.GetMostRecentRedirectUrl(requestedPath, culture);
}
}
// important: redirect URLs are always tracked without trailing slashes
IRedirectUrl? redirectUrl = _redirectUrlService.GetMostRecentRedirectUrl(requestedPath.TrimEnd("/"), culture);
IPublishedContent? content = redirectUrl != null
? _apiPublishedContentCache.GetById(redirectUrl.ContentKey)
: null;
@@ -1,6 +1,5 @@
using Microsoft.AspNetCore.Http;
using Umbraco.Cms.Core.DeliveryApi;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.PublishedCache;
using Umbraco.Cms.Core.Routing;
@@ -13,11 +12,11 @@ internal sealed class RequestRoutingService : RoutingServiceBase, IRequestRoutin
private readonly IRequestCultureService _requestCultureService;
public RequestRoutingService(
IDomainCache domainCache,
IPublishedSnapshotAccessor publishedSnapshotAccessor,
IHttpContextAccessor httpContextAccessor,
IRequestStartItemProviderAccessor requestStartItemProviderAccessor,
IRequestCultureService requestCultureService)
: base(domainCache, httpContextAccessor, requestStartItemProviderAccessor) =>
: base(publishedSnapshotAccessor, httpContextAccessor, requestStartItemProviderAccessor) =>
_requestCultureService = requestCultureService;
/// <inheritdoc />
@@ -3,34 +3,29 @@ using Umbraco.Cms.Core;
using Umbraco.Cms.Core.DeliveryApi;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.PublishedCache;
using Umbraco.Cms.Core.Services.Navigation;
using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Delivery.Services;
internal sealed class RequestStartItemProvider : RequestHeaderHandler, IRequestStartItemProvider
{
private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor;
private readonly IVariationContextAccessor _variationContextAccessor;
private readonly IRequestPreviewService _requestPreviewService;
private readonly IDocumentNavigationQueryService _documentNavigationQueryService;
private readonly IPublishedContentCache _publishedContentCache;
// this provider lifetime is Scope, so we can cache this as a field
private IPublishedContent? _requestedStartContent;
public RequestStartItemProvider(
IHttpContextAccessor httpContextAccessor,
IPublishedSnapshotAccessor publishedSnapshotAccessor,
IVariationContextAccessor variationContextAccessor,
IRequestPreviewService requestPreviewService,
IDocumentNavigationQueryService documentNavigationQueryService,
IPublishedContentCache publishedContentCache)
IRequestPreviewService requestPreviewService)
: base(httpContextAccessor)
{
_publishedSnapshotAccessor = publishedSnapshotAccessor;
_variationContextAccessor = variationContextAccessor;
_requestPreviewService = requestPreviewService;
_documentNavigationQueryService = documentNavigationQueryService;
_publishedContentCache = publishedContentCache;
}
/// <inheritdoc/>
@@ -47,11 +42,13 @@ internal sealed class RequestStartItemProvider : RequestHeaderHandler, IRequestS
return null;
}
_documentNavigationQueryService.TryGetRootKeys(out IEnumerable<Guid> rootKeys);
IEnumerable<IPublishedContent> rootContent = rootKeys
.Select(_publishedContentCache.GetById)
.WhereNotNull()
.Where(x => x.IsPublished() != _requestPreviewService.IsPreview());
if (_publishedSnapshotAccessor.TryGetPublishedSnapshot(out IPublishedSnapshot? publishedSnapshot) == false ||
publishedSnapshot?.Content == null)
{
return null;
}
IEnumerable<IPublishedContent> rootContent = publishedSnapshot.Content.GetAtRoot(_requestPreviewService.IsPreview());
_requestedStartContent = Guid.TryParse(headerValue, out Guid key)
? rootContent.FirstOrDefault(c => c.Key == key)
@@ -9,16 +9,16 @@ namespace Umbraco.Cms.Api.Delivery.Services;
internal abstract class RoutingServiceBase
{
private readonly IDomainCache _domainCache;
private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IRequestStartItemProviderAccessor _requestStartItemProviderAccessor;
protected RoutingServiceBase(
IDomainCache domainCache,
IPublishedSnapshotAccessor publishedSnapshotAccessor,
IHttpContextAccessor httpContextAccessor,
IRequestStartItemProviderAccessor requestStartItemProviderAccessor)
{
_domainCache = domainCache;
_publishedSnapshotAccessor = publishedSnapshotAccessor;
_httpContextAccessor = httpContextAccessor;
_requestStartItemProviderAccessor = requestStartItemProviderAccessor;
}
@@ -40,9 +40,15 @@ internal abstract class RoutingServiceBase
protected DomainAndUri? GetDomainAndUriForRoute(Uri contentUrl)
{
IEnumerable<Domain> domains = _domainCache.GetAll(false);
IDomainCache? domainCache = _publishedSnapshotAccessor.GetRequiredPublishedSnapshot().Domains;
if (domainCache == null)
{
throw new InvalidOperationException("Could not obtain the domain cache in the current context");
}
return DomainUtilities.SelectDomain(domains, contentUrl, defaultCulture: _domainCache.DefaultCulture);
IEnumerable<Domain> domains = domainCache.GetAll(false);
return DomainUtilities.SelectDomain(domains, contentUrl, defaultCulture: domainCache.DefaultCulture);
}
protected IPublishedContent? GetStartItem()
@@ -3,16 +3,11 @@
<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:
[ASP0019] use IHeaderDictionary.Append or the indexer to append or set headers
[CS0618/CS0612] update obsolete references
-->
<WarningsNotAsErrors>$(WarningsNotAsErrors),ASP0019,CS0618,CS0612</WarningsNotAsErrors>
<!-- TODO: [ASP0019] use IHeaderDictionary.Append or the indexer to append or set headers,
and remove this override -->
<WarningsNotAsErrors>ASP0019</WarningsNotAsErrors>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Umbraco.Cms.Api.Common\Umbraco.Cms.Api.Common.csproj" />
<ProjectReference Include="..\Umbraco.Web.Common\Umbraco.Web.Common.csproj" />
@@ -31,6 +31,7 @@ public class ConfigureUmbracoManagementApiSwaggerGenOptions : IConfigureOptions<
});
swaggerGenOptions.OperationFilter<ResponseHeaderOperationFilter>();
swaggerGenOptions.SelectSubTypesUsing(_umbracoJsonTypeInfoResolver.FindSubTypes);
swaggerGenOptions.UseOneOfForPolymorphism();
// Ensure all types that implements the IOpenApiDiscriminator have a $type property in the OpenApi schema with the default value (The class name) that is expected by the server
@@ -51,7 +51,7 @@ public class BackOfficeLoginController : Controller
if (string.IsNullOrEmpty(model.UmbracoUrl))
{
model.UmbracoUrl = _hostingEnvironment.ToAbsolute(Constants.System.DefaultUmbracoPath);
model.UmbracoUrl = _hostingEnvironment.ToAbsolute(_globalSettings.UmbracoPath);
}
if (string.IsNullOrEmpty(model.ReturnUrl))
@@ -14,7 +14,7 @@ namespace Umbraco.Cms.Api.Management.Controllers.Content;
public abstract class ContentCollectionControllerBase<TContent, TCollectionResponseModel, TValueResponseModelBase, TVariantResponseModel> : ManagementApiControllerBase
where TContent : class, IContentBase
where TCollectionResponseModel : ContentResponseModelBase<TValueResponseModelBase, TVariantResponseModel>
where TValueResponseModelBase : ValueResponseModelBase
where TValueResponseModelBase : ValueModelBase
where TVariantResponseModel : VariantResponseModelBase
{
private readonly IUmbracoMapper _mapper;
@@ -4,7 +4,6 @@ using Umbraco.Cms.Api.Management.ViewModels.Content;
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Core.Models.ContentEditing;
using Umbraco.Cms.Core.Models.ContentEditing.Validation;
using Umbraco.Cms.Core.PropertyEditors.Validation;
using Umbraco.Cms.Core.Services.OperationStatus;
using Umbraco.Extensions;
@@ -101,7 +100,7 @@ public abstract class ContentControllerBase : ManagementApiControllerBase
var errors = new SortedDictionary<string, string[]>();
var validationErrorExpressionRoot = $"$.{nameof(ContentModelBase<TValueModel, TVariantModel>.Values).ToFirstLowerInvariant()}";
var missingPropertyModels = new List<PropertyValidationResponseModel>();
foreach (PropertyValidationError validationError in validationResult.ValidationErrors)
{
TValueModel? requestValue = requestModel.Values.FirstOrDefault(value =>
@@ -110,16 +109,13 @@ public abstract class ContentControllerBase : ManagementApiControllerBase
&& value.Segment == validationError.Segment);
if (requestValue is null)
{
errors.Add(
$"{validationErrorExpressionRoot}[{JsonPathExpression.MissingPropertyValue(validationError.Alias, validationError.Culture, validationError.Segment)}].{nameof(ValueModelBase.Value)}",
validationError.ErrorMessages);
missingPropertyModels.Add(MapMissingProperty(validationError));
continue;
}
var index = requestModel.Values.IndexOf(requestValue);
errors.Add(
$"$.{nameof(ContentModelBase<TValueModel, TVariantModel>.Values).ToFirstLowerInvariant()}[{index}].{nameof(ValueModelBase.Value).ToFirstLowerInvariant()}{validationError.JsonPath}",
validationError.ErrorMessages);
var key = $"$.{nameof(ContentModelBase<TValueModel, TVariantModel>.Values).ToFirstLowerInvariant()}[{index}].{nameof(ValueModelBase.Value).ToFirstLowerInvariant()}{validationError.JsonPath}";
errors.Add(key, validationError.ErrorMessages);
}
return OperationStatusResult(status, problemDetailsBuilder
@@ -127,6 +123,16 @@ public abstract class ContentControllerBase : ManagementApiControllerBase
.WithTitle("Validation failed")
.WithDetail("One or more properties did not pass validation")
.WithRequestModelErrors(errors)
.WithExtension("missingValues", missingPropertyModels.ToArray())
.Build()));
}
private PropertyValidationResponseModel MapMissingProperty(PropertyValidationError source) =>
new()
{
Alias = source.Alias,
Segment = source.Segment,
Culture = source.Culture,
Messages = source.ErrorMessages,
};
}
@@ -1,4 +1,5 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.ViewModels.DataType;
@@ -7,10 +8,12 @@ using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
using Umbraco.Cms.Web.Common.Authorization;
namespace Umbraco.Cms.Api.Management.Controllers.DataType;
[ApiVersion("1.0")]
[Authorize(Policy = AuthorizationPolicies.TreeAccessDataTypes)]
public class CopyDataTypeController : DataTypeControllerBase
{
private readonly IDataTypeService _dataTypeService;
@@ -1,4 +1,5 @@
using Asp.Versioning;
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Factories;
@@ -8,10 +9,12 @@ using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
using Umbraco.Cms.Web.Common.Authorization;
namespace Umbraco.Cms.Api.Management.Controllers.DataType;
[ApiVersion("1.0")]
[Authorize(Policy = AuthorizationPolicies.TreeAccessDataTypes)]
public class CreateDataTypeController : DataTypeControllerBase
{
private readonly IDataTypeService _dataTypeService;
@@ -11,7 +11,7 @@ namespace Umbraco.Cms.Api.Management.Controllers.DataType;
[VersionedApiBackOfficeRoute(Constants.UdiEntityType.DataType)]
[ApiExplorerSettings(GroupName = "Data Type")]
[Authorize(Policy = AuthorizationPolicies.TreeAccessDocumentsOrMediaOrMembersOrContentTypes)]
[Authorize(Policy = AuthorizationPolicies.TreeAccessDocumentOrMediaOrContentTypes)]
public abstract class DataTypeControllerBase : ManagementApiControllerBase
{
protected IActionResult DataTypeOperationStatusResult(DataTypeOperationStatus status) =>
@@ -1,4 +1,5 @@
using Asp.Versioning;
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Core;
@@ -6,10 +7,12 @@ using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
using Umbraco.Cms.Web.Common.Authorization;
namespace Umbraco.Cms.Api.Management.Controllers.DataType;
[ApiVersion("1.0")]
[Authorize(Policy = AuthorizationPolicies.TreeAccessDataTypes)]
public class DeleteDataTypeController : DataTypeControllerBase
{
private readonly IDataTypeService _dataTypeService;
@@ -1,4 +1,5 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.ViewModels.DataType;
@@ -7,10 +8,12 @@ using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
using Umbraco.Cms.Web.Common.Authorization;
namespace Umbraco.Cms.Api.Management.Controllers.DataType;
[ApiVersion("1.0")]
[Authorize(Policy = AuthorizationPolicies.TreeAccessDataTypes)]
public class MoveDataTypeController : DataTypeControllerBase
{
private readonly IDataTypeService _dataTypeService;
@@ -1,4 +1,5 @@
using Asp.Versioning;
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Factories;
@@ -8,10 +9,12 @@ using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
using Umbraco.Cms.Web.Common.Authorization;
namespace Umbraco.Cms.Api.Management.Controllers.DataType;
[ApiVersion("1.0")]
[Authorize(Policy = AuthorizationPolicies.TreeAccessDataTypes)]
public class UpdateDataTypeController : DataTypeControllerBase
{
private readonly IDataTypeService _dataTypeService;
@@ -1,58 +0,0 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Api.Management.ViewModels.Document;
using Umbraco.Cms.Core.Actions;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Security.Authorization;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Web.Common.Authorization;
using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Controllers.Document;
[ApiVersion("1.0")]
public class ByKeyPublishedDocumentController : DocumentControllerBase
{
private readonly IAuthorizationService _authorizationService;
private readonly IContentEditingService _contentEditingService;
private readonly IDocumentPresentationFactory _documentPresentationFactory;
public ByKeyPublishedDocumentController(
IAuthorizationService authorizationService,
IContentEditingService contentEditingService,
IDocumentPresentationFactory documentPresentationFactory)
{
_authorizationService = authorizationService;
_contentEditingService = contentEditingService;
_documentPresentationFactory = documentPresentationFactory;
}
[HttpGet("{id:guid}/published")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(PublishedDocumentResponseModel), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
public async Task<IActionResult> ByKeyPublished(CancellationToken cancellationToken, Guid id)
{
AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync(
User,
ContentPermissionResource.WithKeys(ActionBrowse.ActionLetter, id),
AuthorizationPolicies.ContentPermissionByResource);
if (!authorizationResult.Succeeded)
{
return Forbidden();
}
IContent? content = await _contentEditingService.GetAsync(id);
if (content == null || content.Published is false)
{
return DocumentNotFound();
}
PublishedDocumentResponseModel model = await _documentPresentationFactory.CreatePublishedResponseModelAsync(content);
return Ok(model);
}
}
@@ -15,7 +15,7 @@ namespace Umbraco.Cms.Api.Management.Controllers.Document.Collection;
[VersionedApiBackOfficeRoute($"{Constants.Web.RoutePath.Collection}/{Constants.UdiEntityType.Document}")]
[ApiExplorerSettings(GroupName = nameof(Constants.UdiEntityType.Document))]
[Authorize(Policy = AuthorizationPolicies.TreeAccessDocuments)]
public abstract class DocumentCollectionControllerBase : ContentCollectionControllerBase<IContent, DocumentCollectionResponseModel, DocumentValueResponseModel, DocumentVariantResponseModel>
public abstract class DocumentCollectionControllerBase : ContentCollectionControllerBase<IContent, DocumentCollectionResponseModel, DocumentValueModel, DocumentVariantResponseModel>
{
protected DocumentCollectionControllerBase(IUmbracoMapper mapper)
: base(mapper)
@@ -49,7 +49,7 @@ public class GetPublicAccessDocumentController : DocumentControllerBase
}
Attempt<PublicAccessEntry?, PublicAccessOperationStatus> accessAttempt =
await _publicAccessService.GetEntryByContentKeyWithoutAncestorsAsync(id);
await _publicAccessService.GetEntryByContentKeyAsync(id);
if (accessAttempt.Success is false || accessAttempt.Result is null)
{
@@ -3,7 +3,6 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
using Umbraco.Cms.Api.Management.ViewModels;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
@@ -23,7 +22,7 @@ public class AreReferencedDocumentController : DocumentControllerBase
}
/// <summary>
/// Gets a paged list of the items used in any kind of relation from selected keys.
/// Gets a page list of the items used in any kind of relation from selected keys.
/// </summary>
/// <remarks>
/// Used when bulk deleting content/media and bulk unpublishing content (delete and unpublish on List view).
@@ -38,11 +37,11 @@ public class AreReferencedDocumentController : DocumentControllerBase
int skip = 0,
int take = 20)
{
PagedModel<Guid> distinctByKeyItemsWithReferencedRelations = await _trackedReferencesSkipTakeService.GetPagedKeysWithDependentReferencesAsync(ids, Constants.ObjectTypes.Document, skip, take);
PagedModel<RelationItemModel> distinctByKeyItemsWithReferencedRelations = await _trackedReferencesSkipTakeService.GetPagedItemsWithRelationsAsync(ids, skip, take, true);
var pagedViewModel = new PagedViewModel<ReferenceByIdModel>
{
Total = distinctByKeyItemsWithReferencedRelations.Total,
Items = _umbracoMapper.MapEnumerable<Guid, ReferenceByIdModel>(distinctByKeyItemsWithReferencedRelations.Items),
Items = _umbracoMapper.MapEnumerable<RelationItemModel, ReferenceByIdModel>(distinctByKeyItemsWithReferencedRelations.Items),
};
return await Task.FromResult(pagedViewModel);
@@ -22,7 +22,7 @@ public class ReferencedByDocumentController : DocumentControllerBase
}
/// <summary>
/// Gets a paged list of tracked references for the current item, so you can see where an item is being used.
/// Gets a page list of tracked references for the current item, so you can see where an item is being used.
/// </summary>
/// <remarks>
/// Used by info tabs on content, media etc. and for the delete and unpublish of single items.
@@ -22,7 +22,7 @@ public class ReferencedDescendantsDocumentController : DocumentControllerBase
}
/// <summary>
/// Gets a paged list of the descendant nodes of the current item used in any kind of relation.
/// Gets a page list of the child nodes of the current item used in any kind of relation.
/// </summary>
/// <remarks>
/// Used when deleting and unpublishing a single item to check if this item has any descending items that are in any
@@ -12,7 +12,6 @@ using Umbraco.Cms.Core.Services.OperationStatus;
namespace Umbraco.Cms.Api.Management.Controllers.Document;
[ApiVersion("1.0")]
[ApiVersion("1.1")]
public class ValidateUpdateDocumentController : UpdateDocumentControllerBase
{
private readonly IContentEditingService _contentEditingService;
@@ -33,35 +32,10 @@ public class ValidateUpdateDocumentController : UpdateDocumentControllerBase
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
[Obsolete("Please use version 1.1 of this API. Will be removed in V16.")]
public async Task<IActionResult> Validate(CancellationToken cancellationToken, Guid id, UpdateDocumentRequestModel requestModel)
=> await HandleRequest(id, requestModel, async () =>
{
var validateUpdateDocumentRequestModel = new ValidateUpdateDocumentRequestModel
{
Values = requestModel.Values,
Variants = requestModel.Variants,
Template = requestModel.Template,
Cultures = null
};
ValidateContentUpdateModel model = _documentEditingPresentationFactory.MapValidateUpdateModel(validateUpdateDocumentRequestModel);
Attempt<ContentValidationResult, ContentEditingOperationStatus> result = await _contentEditingService.ValidateUpdateAsync(id, model);
return result.Success
? Ok()
: DocumentEditingOperationStatusResult(result.Status, requestModel, result.Result);
});
[HttpPut("{id:guid}/validate")]
[MapToApiVersion("1.1")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
public async Task<IActionResult> ValidateV1_1(CancellationToken cancellationToken, Guid id, ValidateUpdateDocumentRequestModel requestModel)
=> await HandleRequest(id, requestModel, async () =>
{
ValidateContentUpdateModel model = _documentEditingPresentationFactory.MapValidateUpdateModel(requestModel);
ContentUpdateModel model = _documentEditingPresentationFactory.MapUpdateModel(requestModel);
Attempt<ContentValidationResult, ContentEditingOperationStatus> result = await _contentEditingService.ValidateUpdateAsync(id, model);
return result.Success
@@ -1,14 +1,17 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
using Umbraco.Cms.Web.Common.Authorization;
namespace Umbraco.Cms.Api.Management.Controllers.DocumentType;
[ApiVersion("1.0")]
[Authorize(Policy = AuthorizationPolicies.TreeAccessDocumentTypes)]
public class ExportDocumentTypeController : DocumentTypeControllerBase
{
private readonly IContentTypeService _contentTypeService;
@@ -1,17 +1,19 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.ViewModels.DocumentType;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Membership;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services.ImportExport;
using Umbraco.Cms.Core.Services.OperationStatus;
using Umbraco.Cms.Web.Common.Authorization;
namespace Umbraco.Cms.Api.Management.Controllers.DocumentType;
[ApiVersion("1.0")]
[Authorize(Policy = AuthorizationPolicies.TreeAccessDocumentTypes)]
public class ImportExistingDocumentTypeController : DocumentTypeControllerBase
{
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
@@ -1,17 +1,19 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.ViewModels.DocumentType;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Membership;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services.ImportExport;
using Umbraco.Cms.Core.Services.OperationStatus;
using Umbraco.Cms.Web.Common.Authorization;
namespace Umbraco.Cms.Api.Management.Controllers.DocumentType;
[ApiVersion("1.0")]
[Authorize(Policy = AuthorizationPolicies.TreeAccessDocumentTypes)]
public class ImportNewDocumentTypeController : DocumentTypeControllerBase
{
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
@@ -32,7 +32,7 @@ public class ItemDocumentTypeItemController : DocumentTypeItemControllerBase
return Ok(Enumerable.Empty<DocumentTypeItemResponseModel>());
}
IEnumerable<IContentType> contentTypes = _contentTypeService.GetMany(ids);
IEnumerable<IContentType> contentTypes = _contentTypeService.GetAll(ids);
List<DocumentTypeItemResponseModel> responseModels = _mapper.MapEnumerable<IContentType, DocumentTypeItemResponseModel>(contentTypes);
return await Task.FromResult(Ok(responseModels));
}
@@ -35,7 +35,7 @@ public class SearchDocumentTypeItemController : DocumentTypeItemControllerBase
return await Task.FromResult(Ok(new PagedModel<DocumentTypeItemResponseModel> { Total = searchResult.Total }));
}
IEnumerable<IContentType> contentTypes = _contentTypeService.GetMany(searchResult.Items.Select(item => item.Key).ToArray().EmptyNull());
IEnumerable<IContentType> contentTypes = _contentTypeService.GetAll(searchResult.Items.Select(item => item.Key).ToArray().EmptyNull());
var result = new PagedModel<DocumentTypeItemResponseModel>
{
Items = _mapper.MapEnumerable<IContentType, DocumentTypeItemResponseModel>(contentTypes),
@@ -29,7 +29,7 @@ public class DocumentTypeTreeControllerBase : FolderTreeControllerBase<DocumentT
protected override DocumentTypeTreeItemResponseModel[] MapTreeItemViewModels(Guid? parentKey, IEntitySlim[] entities)
{
var contentTypes = _contentTypeService
.GetMany(entities.Select(entity => entity.Id).ToArray())
.GetAll(entities.Select(entity => entity.Id).ToArray())
.ToDictionary(contentType => contentType.Id);
return entities.Select(entity =>
@@ -15,7 +15,7 @@ namespace Umbraco.Cms.Api.Management.Controllers.Media.Collection;
[VersionedApiBackOfficeRoute($"{Constants.Web.RoutePath.Collection}/{Constants.UdiEntityType.Media}")]
[ApiExplorerSettings(GroupName = nameof(Constants.UdiEntityType.Media))]
[Authorize(Policy = AuthorizationPolicies.SectionAccessMedia)]
public abstract class MediaCollectionControllerBase : ContentCollectionControllerBase<IMedia, MediaCollectionResponseModel, MediaValueResponseModel, MediaVariantResponseModel>
public abstract class MediaCollectionControllerBase : ContentCollectionControllerBase<IMedia, MediaCollectionResponseModel, MediaValueModel, MediaVariantResponseModel>
{
protected MediaCollectionControllerBase(IUmbracoMapper mapper)
: base(mapper)
@@ -46,6 +46,6 @@ public class CreateMediaController : CreateMediaControllerBase
return result.Success
? CreatedAtId<ByKeyMediaController>(controller => nameof(controller.ByKey), result.Result.Content!.Key)
: ContentEditingOperationStatusResult(result.Status);
: MediaEditingOperationStatusResult(result.Status, requestModel, result.Result.ValidationResult);
});
}
@@ -3,7 +3,6 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
using Umbraco.Cms.Api.Management.ViewModels;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
@@ -39,11 +38,11 @@ public class AreReferencedMediaController : MediaControllerBase
int skip = 0,
int take = 20)
{
PagedModel<Guid> distinctByKeyItemsWithReferencedRelations = await _trackedReferencesSkipTakeService.GetPagedKeysWithDependentReferencesAsync(ids, Constants.ObjectTypes.Media, skip, take);
PagedModel<RelationItemModel> distinctByKeyItemsWithReferencedRelations = await _trackedReferencesSkipTakeService.GetPagedItemsWithRelationsAsync(ids, skip, take, true);
var pagedViewModel = new PagedViewModel<ReferenceByIdModel>
{
Total = distinctByKeyItemsWithReferencedRelations.Total,
Items = _umbracoMapper.MapEnumerable<Guid, ReferenceByIdModel>(distinctByKeyItemsWithReferencedRelations.Items),
Items = _umbracoMapper.MapEnumerable<RelationItemModel, ReferenceByIdModel>(distinctByKeyItemsWithReferencedRelations.Items),
};
return await Task.FromResult(pagedViewModel);
@@ -1,14 +1,17 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
using Umbraco.Cms.Web.Common.Authorization;
namespace Umbraco.Cms.Api.Management.Controllers.MediaType;
[ApiVersion("1.0")]
[Authorize(Policy = AuthorizationPolicies.TreeAccessMediaTypes)]
public class ExportMediaTypeController : MediaTypeControllerBase
{
private readonly IMediaTypeService _mediaTypeService;
@@ -1,17 +1,19 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.ViewModels.MediaType;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Membership;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services.ImportExport;
using Umbraco.Cms.Core.Services.OperationStatus;
using Umbraco.Cms.Web.Common.Authorization;
namespace Umbraco.Cms.Api.Management.Controllers.MediaType;
[ApiVersion("1.0")]
[Authorize(Policy = AuthorizationPolicies.TreeAccessMediaTypes)]
public class ImportExistingMediaTypeController : MediaTypeControllerBase
{
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
@@ -1,18 +1,19 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Controllers.DocumentType;
using Umbraco.Cms.Api.Management.ViewModels.MediaType;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Membership;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services.ImportExport;
using Umbraco.Cms.Core.Services.OperationStatus;
using Umbraco.Cms.Web.Common.Authorization;
namespace Umbraco.Cms.Api.Management.Controllers.MediaType;
[ApiVersion("1.0")]
[Authorize(Policy = AuthorizationPolicies.TreeAccessMediaTypes)]
public class ImportNewMediaTypeController : MediaTypeControllerBase
{
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
@@ -32,7 +32,7 @@ public class ItemMediaTypeItemController : MediaTypeItemControllerBase
return Ok(Enumerable.Empty<MediaTypeItemResponseModel>());
}
IEnumerable<IMediaType> mediaTypes = _mediaTypeService.GetMany(ids);
IEnumerable<IMediaType> mediaTypes = _mediaTypeService.GetAll(ids);
List<MediaTypeItemResponseModel> responseModels = _mapper.MapEnumerable<IMediaType, MediaTypeItemResponseModel>(mediaTypes);
return await Task.FromResult(Ok(responseModels));
}
@@ -35,7 +35,7 @@ public class SearchMediaTypeItemController : MediaTypeItemControllerBase
return await Task.FromResult(Ok(new PagedModel<MediaTypeItemResponseModel> { Total = searchResult.Total }));
}
IEnumerable<IMediaType> mediaTypes = _mediaTypeService.GetMany(searchResult.Items.Select(item => item.Key).ToArray().EmptyNull());
IEnumerable<IMediaType> mediaTypes = _mediaTypeService.GetAll(searchResult.Items.Select(item => item.Key).ToArray().EmptyNull());
var result = new PagedModel<MediaTypeItemResponseModel>
{
Items = _mapper.MapEnumerable<IMediaType, MediaTypeItemResponseModel>(mediaTypes),
@@ -1,9 +1,8 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Controllers.DocumentType;
using Umbraco.Cms.Api.Management.Routing;
using Umbraco.Cms.Api.Management.ViewModels.MediaType;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Services.OperationStatus;
using Umbraco.Cms.Web.Common.Authorization;
@@ -30,7 +30,7 @@ public class MediaTypeTreeControllerBase : FolderTreeControllerBase<MediaTypeTre
protected override MediaTypeTreeItemResponseModel[] MapTreeItemViewModels(Guid? parentKey, IEntitySlim[] entities)
{
var mediaTypes = _mediaTypeService
.GetMany(entities.Select(entity => entity.Id).ToArray())
.GetAll(entities.Select(entity => entity.Id).ToArray())
.ToDictionary(contentType => contentType.Id);
return entities.Select(entity =>
@@ -32,7 +32,7 @@ public class ItemMemberTypeItemController : MemberTypeItemControllerBase
return Ok(Enumerable.Empty<MemberTypeItemResponseModel>());
}
IEnumerable<IMemberType> memberTypes = _memberTypeService.GetMany(ids);
IEnumerable<IMemberType> memberTypes = _memberTypeService.GetAll(ids);
List<MemberTypeItemResponseModel> responseModels = _mapper.MapEnumerable<IMemberType, MemberTypeItemResponseModel>(memberTypes);
return Ok(responseModels);
}
@@ -34,7 +34,7 @@ public class SearchMemberTypeItemController : MemberTypeItemControllerBase
return await Task.FromResult(Ok(new PagedModel<MemberTypeItemResponseModel> { Total = searchResult.Total }));
}
IEnumerable<IMemberType> memberTypes = _memberTypeService.GetMany(searchResult.Items.Select(item => item.Key).ToArray());
IEnumerable<IMemberType> memberTypes = _memberTypeService.GetAll(searchResult.Items.Select(item => item.Key).ToArray());
var result = new PagedModel<MemberTypeItemResponseModel>
{
Items = _mapper.MapEnumerable<IMemberType, MemberTypeItemResponseModel>(memberTypes),
@@ -27,7 +27,7 @@ public class MemberTypeTreeControllerBase : NamedEntityTreeControllerBase<Member
protected override MemberTypeTreeItemResponseModel[] MapTreeItemViewModels(Guid? parentKey, IEntitySlim[] entities)
{
var memberTypes = _memberTypeService
.GetMany(entities.Select(entity => entity.Id).ToArray())
.GetAll(entities.Select(entity => entity.Id).ToArray())
.ToDictionary(contentType => contentType.Id);
return entities.Select(entity =>
@@ -1,18 +1,25 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Core.PublishedCache;
namespace Umbraco.Cms.Api.Management.Controllers.PublishedCache;
[Obsolete("This controller no longer serves a purpose")]
[ApiVersion("1.0")]
public class CollectPublishedCacheController : PublishedCacheControllerBase
{
private readonly IPublishedSnapshotService _publishedSnapshotService;
public CollectPublishedCacheController(IPublishedSnapshotService publishedSnapshotService)
=> _publishedSnapshotService = publishedSnapshotService;
[HttpPost("collect")]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status501NotImplemented)]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<IActionResult> Collect(CancellationToken cancellationToken)
{
return StatusCode(StatusCodes.Status501NotImplemented);
GC.Collect();
await _publishedSnapshotService.CollectAsync();
return Ok();
}
}
@@ -8,16 +8,17 @@ namespace Umbraco.Cms.Api.Management.Controllers.PublishedCache;
[ApiVersion("1.0")]
public class RebuildPublishedCacheController : PublishedCacheControllerBase
{
private readonly IDatabaseCacheRebuilder _databaseCacheRebuilder;
private readonly IPublishedSnapshotService _publishedSnapshotService;
public RebuildPublishedCacheController(IDatabaseCacheRebuilder databaseCacheRebuilder) => _databaseCacheRebuilder = databaseCacheRebuilder;
public RebuildPublishedCacheController(IPublishedSnapshotService publishedSnapshotService)
=> _publishedSnapshotService = publishedSnapshotService;
[HttpPost("rebuild")]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<IActionResult> Rebuild(CancellationToken cancellationToken)
{
_databaseCacheRebuilder.Rebuild();
_publishedSnapshotService.Rebuild();
return await Task.FromResult(Ok());
}
}
@@ -1,18 +1,21 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Core.PublishedCache;
namespace Umbraco.Cms.Api.Management.Controllers.PublishedCache;
[ApiVersion("1.0")]
[Obsolete("This no longer relevant since snapshots are no longer used")]
public class StatusPublishedCacheController : PublishedCacheControllerBase
{
private readonly IPublishedSnapshotStatus _publishedSnapshotStatus;
public StatusPublishedCacheController(IPublishedSnapshotStatus publishedSnapshotStatus)
=> _publishedSnapshotStatus = publishedSnapshotStatus;
[HttpGet("status")]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status501NotImplemented)]
[ProducesResponseType(typeof(string), StatusCodes.Status200OK)]
public async Task<ActionResult<string>> Status(CancellationToken cancellationToken)
{
return StatusCode(StatusCodes.Status501NotImplemented);
}
=> await Task.FromResult(Ok(_publishedSnapshotStatus.GetStatus()));
}
@@ -34,6 +34,8 @@ namespace Umbraco.Cms.Api.Management.Controllers.Security;
[ApiExplorerSettings(IgnoreApi = true)]
public class BackOfficeController : SecurityControllerBase
{
private static long? _loginDurationAverage;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IBackOfficeSignInManager _backOfficeSignInManager;
private readonly IBackOfficeUserManager _backOfficeUserManager;
@@ -42,7 +44,6 @@ public class BackOfficeController : SecurityControllerBase
private readonly IBackOfficeTwoFactorOptions _backOfficeTwoFactorOptions;
private readonly IUserTwoFactorLoginService _userTwoFactorLoginService;
private readonly IBackOfficeExternalLoginService _externalLoginService;
private readonly IBackOfficeUserClientCredentialsManager _backOfficeUserClientCredentialsManager;
private const string RedirectFlowParameter = "flow";
private const string RedirectStatusParameter = "status";
@@ -56,8 +57,7 @@ public class BackOfficeController : SecurityControllerBase
ILogger<BackOfficeController> logger,
IBackOfficeTwoFactorOptions backOfficeTwoFactorOptions,
IUserTwoFactorLoginService userTwoFactorLoginService,
IBackOfficeExternalLoginService externalLoginService,
IBackOfficeUserClientCredentialsManager backOfficeUserClientCredentialsManager)
IBackOfficeExternalLoginService externalLoginService)
{
_httpContextAccessor = httpContextAccessor;
_backOfficeSignInManager = backOfficeSignInManager;
@@ -67,7 +67,6 @@ public class BackOfficeController : SecurityControllerBase
_backOfficeTwoFactorOptions = backOfficeTwoFactorOptions;
_userTwoFactorLoginService = userTwoFactorLoginService;
_externalLoginService = externalLoginService;
_backOfficeUserClientCredentialsManager = backOfficeUserClientCredentialsManager;
}
[HttpPost("login")]
@@ -75,45 +74,65 @@ public class BackOfficeController : SecurityControllerBase
[Authorize(Policy = AuthorizationPolicies.DenyLocalLoginIfConfigured)]
public async Task<IActionResult> Login(CancellationToken cancellationToken, LoginRequestModel model)
{
IdentitySignInResult result = await _backOfficeSignInManager.PasswordSignInAsync(
model.Username, model.Password, true, true);
// Start a timed scope to ensure failed responses return is a consistent time
var loginDuration = Math.Max(_loginDurationAverage ?? _securitySettings.Value.UserDefaultFailedLoginDurationInMilliseconds, _securitySettings.Value.UserMinimumFailedLoginDurationInMilliseconds);
await using var timedScope = new TimedScope(loginDuration, cancellationToken);
if (result.IsNotAllowed)
IdentitySignInResult result = await _backOfficeSignInManager.PasswordSignInAsync(model.Username, model.Password, true, true);
if (result.Succeeded is false)
{
return StatusCode(StatusCodes.Status403Forbidden, new ProblemDetailsBuilder()
.WithTitle("User is not allowed")
.WithDetail("The operation is not allowed on the user")
.Build());
}
if (result.IsLockedOut)
{
return StatusCode(StatusCodes.Status403Forbidden, new ProblemDetailsBuilder()
.WithTitle("User is locked")
.WithDetail("The user is locked, and need to be unlocked before more login attempts can be executed.")
.Build());
}
if(result.RequiresTwoFactor)
{
string? twofactorView = _backOfficeTwoFactorOptions.GetTwoFactorView(model.Username);
BackOfficeIdentityUser? attemptingUser = await _backOfficeUserManager.FindByNameAsync(model.Username);
IEnumerable<string> enabledProviders = (await _userTwoFactorLoginService.GetProviderNamesAsync(attemptingUser!.Key)).Result.Where(x=>x.IsEnabledOnUser).Select(x=>x.ProviderName);
return StatusCode(StatusCodes.Status402PaymentRequired, new RequiresTwoFactorResponseModel()
// TODO: The result should include the user and whether the credentials were valid to avoid these additional checks
BackOfficeIdentityUser? user = await _backOfficeUserManager.FindByNameAsync(model.Username.Trim()); // Align with UmbracoSignInManager and trim username!
if (user is not null &&
await _backOfficeUserManager.CheckPasswordAsync(user, model.Password))
{
TwoFactorLoginView = twofactorView,
EnabledTwoFactorProviderNames = enabledProviders
});
// The credentials were correct, so cancel timed scope and provide a more detailed failure response
await timedScope.CancelAsync();
if (result.IsNotAllowed)
{
return StatusCode(StatusCodes.Status403Forbidden, new ProblemDetailsBuilder()
.WithTitle("User is not allowed")
.WithDetail("The operation is not allowed on the user")
.Build());
}
if (result.IsLockedOut)
{
return StatusCode(StatusCodes.Status403Forbidden, new ProblemDetailsBuilder()
.WithTitle("User is locked")
.WithDetail("The user is locked, and need to be unlocked before more login attempts can be executed.")
.Build());
}
if (result.RequiresTwoFactor)
{
string? twofactorView = _backOfficeTwoFactorOptions.GetTwoFactorView(model.Username);
IEnumerable<string> enabledProviders = (await _userTwoFactorLoginService.GetProviderNamesAsync(user.Key)).Result.Where(x => x.IsEnabledOnUser).Select(x => x.ProviderName);
return StatusCode(StatusCodes.Status402PaymentRequired, new RequiresTwoFactorResponseModel()
{
TwoFactorLoginView = twofactorView,
EnabledTwoFactorProviderNames = enabledProviders
});
}
}
return StatusCode(StatusCodes.Status401Unauthorized, new ProblemDetailsBuilder()
.WithTitle("Invalid credentials")
.WithDetail("The provided credentials are invalid. User has not been signed in.")
.Build());
}
if (result.Succeeded)
{
return Ok();
}
return StatusCode(StatusCodes.Status401Unauthorized, new ProblemDetailsBuilder()
.WithTitle("Invalid credentials")
.WithDetail("The provided credentials are invalid. User has not been signed in.")
.Build());
// Set initial or update average (successful) login duration
_loginDurationAverage = _loginDurationAverage is long average
? (average + (long)timedScope.Elapsed.TotalMilliseconds) / 2
: (long)timedScope.Elapsed.TotalMilliseconds;
// Cancel the timed scope (we don't want to unnecessarily wait on a successful response)
await timedScope.CancelAsync();
return Ok();
}
[AllowAnonymous]
@@ -169,19 +188,13 @@ public class BackOfficeController : SecurityControllerBase
OpenIddictRequest? request = context.GetOpenIddictServerRequest();
if (request == null)
{
return BadRequest(new OpenIddictResponse
{
Error = "No context found", ErrorDescription = "Unable to obtain context from the current request."
});
return BadRequest("Unable to obtain OpenID data from the current request");
}
// make sure we keep member authentication away from this endpoint
if (request.ClientId is Constants.OAuthClientIds.Member)
{
return BadRequest(new OpenIddictResponse
{
Error = "Invalid 'client ID'", ErrorDescription = "The specified 'client_id' is not valid."
});
return BadRequest("The specified client ID cannot be used here.");
}
return request.IdentityProvider.IsNullOrWhiteSpace()
@@ -189,69 +202,17 @@ public class BackOfficeController : SecurityControllerBase
: await AuthorizeExternal(request);
}
[AllowAnonymous]
[HttpPost("token")]
[MapToApiVersion("1.0")]
public async Task<IActionResult> Token()
{
HttpContext context = _httpContextAccessor.GetRequiredHttpContext();
OpenIddictRequest? request = context.GetOpenIddictServerRequest();
if (request == null)
{
return BadRequest(new OpenIddictResponse
{
Error = "No context found", ErrorDescription = "Unable to obtain context from the current request."
});
}
if (request.IsAuthorizationCodeGrantType() || request.IsRefreshTokenGrantType())
{
// attempt to authorize against the supplied the authorization code
AuthenticateResult authenticateResult = await context.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
return authenticateResult is { Succeeded: true, Principal: not null }
? new SignInResult(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, authenticateResult.Principal)
: BadRequest(new OpenIddictResponse
{
Error = "Authorization failed", ErrorDescription = "The supplied authorization could not be verified."
});
}
if (request.IsClientCredentialsGrantType())
{
// if we get here, the client ID and secret are valid (verified by OpenIddict)
// grab the user associated with the client ID
BackOfficeIdentityUser? associatedUser = await _backOfficeUserClientCredentialsManager.FindUserAsync(request.ClientId!);
if (associatedUser is not null)
{
// log current datetime as last login (this also ensures that the user is not flagged as inactive)
associatedUser.LastLoginDateUtc = DateTime.UtcNow;
await _backOfficeUserManager.UpdateAsync(associatedUser);
return await SignInBackOfficeUser(associatedUser, request);
}
// if this happens, the OpenIddict applications have somehow gone out of sync with the Umbraco users
_logger.LogError("The user associated with the client ID ({clientId}) could not be found", request.ClientId);
return BadRequest(new OpenIddictResponse
{
Error = "Authorization failed", ErrorDescription = "The user associated with the supplied 'client_id' could not be found."
});
}
throw new InvalidOperationException("The requested grant type is not supported.");
}
[AllowAnonymous]
[HttpGet("signout")]
[MapToApiVersion("1.0")]
public async Task<IActionResult> Signout(CancellationToken cancellationToken)
{
var userName = await GetUserNameFromAuthCookie();
AuthenticateResult cookieAuthResult = await HttpContext.AuthenticateAsync(Constants.Security.BackOfficeAuthenticationType);
var userName = cookieAuthResult.Principal?.Identity?.Name;
var userId = cookieAuthResult.Principal?.Identity?.GetUserId();
await _backOfficeSignInManager.SignOutAsync();
_backOfficeUserManager.NotifyLogoutSuccess(cookieAuthResult.Principal ?? User, userId);
_logger.LogInformation(
"User {UserName} from IP address {RemoteIpAddress} has logged out",
@@ -1,6 +1,5 @@
using System.Diagnostics;
using System.Linq.Expressions;
using System.Runtime.Versioning;
using System.Text;
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
@@ -9,9 +8,7 @@ using Umbraco.Cms.Api.Management.ViewModels.Template.Query;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.Models.TemplateQuery;
using Umbraco.Cms.Core.PublishedCache;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.Navigation;
using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Controllers.Template.Query;
@@ -23,8 +20,6 @@ public class ExecuteTemplateQueryController : TemplateQueryControllerBase
private readonly IVariationContextAccessor _variationContextAccessor;
private readonly IPublishedValueFallback _publishedValueFallback;
private readonly IContentTypeService _contentTypeService;
private readonly IPublishedContentCache _contentCache;
private readonly IDocumentNavigationQueryService _documentNavigationQueryService;
private static readonly string _indent = $"{Environment.NewLine} ";
@@ -32,16 +27,12 @@ public class ExecuteTemplateQueryController : TemplateQueryControllerBase
IPublishedContentQuery publishedContentQuery,
IVariationContextAccessor variationContextAccessor,
IPublishedValueFallback publishedValueFallback,
IContentTypeService contentTypeService,
IPublishedContentCache contentCache,
IDocumentNavigationQueryService documentNavigationQueryService)
IContentTypeService contentTypeService)
{
_publishedContentQuery = publishedContentQuery;
_variationContextAccessor = variationContextAccessor;
_publishedValueFallback = publishedValueFallback;
_contentTypeService = contentTypeService;
_contentCache = contentCache;
_documentNavigationQueryService = documentNavigationQueryService;
}
[HttpPost("execute")]
@@ -60,7 +51,7 @@ public class ExecuteTemplateQueryController : TemplateQueryControllerBase
timer.Stop();
var contentTypeIconsByKey = _contentTypeService
.GetMany(results.Select(content => content.ContentType.Key).Distinct())
.GetAll(results.Select(content => content.ContentType.Key).Distinct())
.ToDictionary(contentType => contentType.Key, contentType => contentType.Icon);
return await Task.FromResult(Ok(new TemplateQueryResultResponseModel
@@ -118,13 +109,13 @@ public class ExecuteTemplateQueryController : TemplateQueryControllerBase
queryExpression.Append($".ChildrenOfType(\"{model.DocumentTypeAlias}\")");
return rootContent == null
? Enumerable.Empty<IPublishedContent>()
: rootContent.ChildrenOfType(_variationContextAccessor, _contentCache, _documentNavigationQueryService, model.DocumentTypeAlias);
: rootContent.ChildrenOfType(_variationContextAccessor, model.DocumentTypeAlias);
}
queryExpression.Append(".Children()");
return rootContent == null
? Enumerable.Empty<IPublishedContent>()
: rootContent.Children(_variationContextAccessor, _contentCache, _documentNavigationQueryService);
: rootContent.Children(_variationContextAccessor);
}
private IEnumerable<IPublishedContent> ApplyFiltering(IEnumerable<TemplateQueryExecuteFilterPresentationModel>? filters, IEnumerable<IPublishedContent> contentQuery, StringBuilder queryExpression)

Some files were not shown because too many files have changed in this diff Show More