From e750d29d14761e2971a95cec1b72f6b7773f9b0e Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Thu, 21 Mar 2024 09:55:30 +0100 Subject: [PATCH] Ensure correct access to all manifests + introduce "public" package manifests (#15921) * Introduce "public" package manifests * Make sure "all manifests" are available to anyone with backoffice access * review comments --- .../Package/AllPackageManifestController.cs | 5 ++- .../PublicPackageManifestController.cs | 33 +++++++++++++++++ .../Manifest/IPackageManifestService.cs | 4 +- src/Umbraco.Core/Manifest/PackageManifest.cs | 2 + .../Manifest/PackageManifestService.cs | 7 +++- .../Manifest/PackageManifestServiceTests.cs | 37 ++++++++++++------- 6 files changed, 71 insertions(+), 17 deletions(-) create mode 100644 src/Umbraco.Cms.Api.Management/Controllers/Package/PublicPackageManifestController.cs diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Package/AllPackageManifestController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Package/AllPackageManifestController.cs index dcdd94bfac5a..f8eb6b3539cd 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Package/AllPackageManifestController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Package/AllPackageManifestController.cs @@ -1,13 +1,16 @@ using Asp.Versioning; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Management.ViewModels.Package; using Umbraco.Cms.Core.Manifest; using Umbraco.Cms.Core.Mapping; +using Umbraco.Cms.Web.Common.Authorization; namespace Umbraco.Cms.Api.Management.Controllers.Package; [ApiVersion("1.0")] +[Authorize(Policy = AuthorizationPolicies.BackOfficeAccess)] public class AllPackageManifestController : PackageControllerBase { private readonly IPackageManifestService _packageManifestService; @@ -25,7 +28,7 @@ public AllPackageManifestController(IPackageManifestService packageManifestServi [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] public async Task AllPackageManifests() { - PackageManifest[] packageManifests = (await _packageManifestService.GetPackageManifestsAsync()).ToArray(); + PackageManifest[] packageManifests = (await _packageManifestService.GetAllPackageManifestsAsync()).ToArray(); return Ok(_umbracoMapper.MapEnumerable(packageManifests)); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Package/PublicPackageManifestController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Package/PublicPackageManifestController.cs new file mode 100644 index 000000000000..e1204091fd01 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/Package/PublicPackageManifestController.cs @@ -0,0 +1,33 @@ +using Asp.Versioning; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.ViewModels.Package; +using Umbraco.Cms.Core.Manifest; +using Umbraco.Cms.Core.Mapping; + +namespace Umbraco.Cms.Api.Management.Controllers.Package; + +[ApiVersion("1.0")] +[AllowAnonymous] +public class PublicPackageManifestController : PackageControllerBase +{ + private readonly IPackageManifestService _packageManifestService; + private readonly IUmbracoMapper _umbracoMapper; + + public PublicPackageManifestController(IPackageManifestService packageManifestService, IUmbracoMapper umbracoMapper) + { + _packageManifestService = packageManifestService; + _umbracoMapper = umbracoMapper; + } + + // NOTE: this endpoint is deliberately created as non-paginated to ensure the fastest possible client initialization + [HttpGet("manifest/public")] + [MapToApiVersion("1.0")] + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + public async Task PublicPackageManifests() + { + PackageManifest[] packageManifests = (await _packageManifestService.GetPublicPackageManifestsAsync()).ToArray(); + return Ok(_umbracoMapper.MapEnumerable(packageManifests)); + } +} diff --git a/src/Umbraco.Core/Manifest/IPackageManifestService.cs b/src/Umbraco.Core/Manifest/IPackageManifestService.cs index 50c6c84a9242..5b6d7181c3b1 100644 --- a/src/Umbraco.Core/Manifest/IPackageManifestService.cs +++ b/src/Umbraco.Core/Manifest/IPackageManifestService.cs @@ -2,7 +2,9 @@ public interface IPackageManifestService { - Task> GetPackageManifestsAsync(); + Task> GetAllPackageManifestsAsync(); + + Task> GetPublicPackageManifestsAsync(); Task GetPackageManifestImportmapAsync(); } diff --git a/src/Umbraco.Core/Manifest/PackageManifest.cs b/src/Umbraco.Core/Manifest/PackageManifest.cs index 85f7ac104fd0..a38bd8329045 100644 --- a/src/Umbraco.Core/Manifest/PackageManifest.cs +++ b/src/Umbraco.Core/Manifest/PackageManifest.cs @@ -6,6 +6,8 @@ public class PackageManifest public string? Version { get; set; } + public bool AllowPublicAccess { get; set; } + public bool AllowTelemetry { get; set; } = true; public required object[] Extensions { get; set; } diff --git a/src/Umbraco.Infrastructure/Manifest/PackageManifestService.cs b/src/Umbraco.Infrastructure/Manifest/PackageManifestService.cs index 974a90700116..7bb8a52c3b58 100644 --- a/src/Umbraco.Infrastructure/Manifest/PackageManifestService.cs +++ b/src/Umbraco.Infrastructure/Manifest/PackageManifestService.cs @@ -22,7 +22,7 @@ internal sealed class PackageManifestService : IPackageManifestService _cache = appCaches.RuntimeCache; } - public async Task> GetPackageManifestsAsync() + public async Task> GetAllPackageManifestsAsync() => await _cache.GetCacheItemAsync( $"{nameof(PackageManifestService)}-PackageManifests", async () => @@ -37,9 +37,12 @@ public async Task> GetPackageManifestsAsync() _packageManifestSettings.CacheTimeout) ?? Array.Empty(); + public async Task> GetPublicPackageManifestsAsync() + => (await GetAllPackageManifestsAsync()).Where(manifest => manifest.AllowPublicAccess).ToArray(); + public async Task GetPackageManifestImportmapAsync() { - IEnumerable packageManifests = await GetPackageManifestsAsync(); + IEnumerable packageManifests = await GetAllPackageManifestsAsync(); var manifests = packageManifests.Select(x => x.Importmap).WhereNotNull().ToList(); var importDict = manifests diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Manifest/PackageManifestServiceTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Manifest/PackageManifestServiceTests.cs index 419e1e10dd17..a92b0af5c884 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Manifest/PackageManifestServiceTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Manifest/PackageManifestServiceTests.cs @@ -22,7 +22,8 @@ public void SetUp() _readerMock.Setup(r => r.ReadPackageManifestsAsync()).ReturnsAsync( new[] { - new PackageManifest { Name = "Test", Extensions = Array.Empty() } + new PackageManifest { Name = "Test", Extensions = Array.Empty(), AllowPublicAccess = false }, + new PackageManifest { Name = "Test Public", Extensions = Array.Empty(), AllowPublicAccess = true} }); _runtimeCache = new ObjectCacheAppCache(); @@ -37,14 +38,14 @@ public void SetUp() [Test] public async Task Caches_PackageManifests() { - var result = await _service.GetPackageManifestsAsync(); - Assert.AreEqual(1, result.Count()); + var result = await _service.GetAllPackageManifestsAsync(); + Assert.AreEqual(2, result.Count()); - var result2 = await _service.GetPackageManifestsAsync(); - Assert.AreEqual(1, result2.Count()); + var result2 = await _service.GetAllPackageManifestsAsync(); + Assert.AreEqual(2, result2.Count()); - var result3 = await _service.GetPackageManifestsAsync(); - Assert.AreEqual(1, result3.Count()); + var result3 = await _service.GetAllPackageManifestsAsync(); + Assert.AreEqual(2, result3.Count()); _readerMock.Verify(r => r.ReadPackageManifestsAsync(), Times.Exactly(1)); } @@ -52,18 +53,28 @@ public async Task Caches_PackageManifests() [Test] public async Task Reloads_PackageManifest_After_Cache_Clear() { - var result = await _service.GetPackageManifestsAsync(); - Assert.AreEqual(1, result.Count()); + var result = await _service.GetAllPackageManifestsAsync(); + Assert.AreEqual(2, result.Count()); _runtimeCache.Clear(); - var result2 = await _service.GetPackageManifestsAsync(); - Assert.AreEqual(1, result2.Count()); + var result2 = await _service.GetAllPackageManifestsAsync(); + Assert.AreEqual(2, result2.Count()); _runtimeCache.Clear(); - var result3 = await _service.GetPackageManifestsAsync(); - Assert.AreEqual(1, result3.Count()); + var result3 = await _service.GetAllPackageManifestsAsync(); + Assert.AreEqual(2, result3.Count()); _runtimeCache.Clear(); _readerMock.Verify(r => r.ReadPackageManifestsAsync(), Times.Exactly(3)); } + + [Test] + public async Task Supports_Public_PackageManifests() + { + var result = await _service.GetPublicPackageManifestsAsync(); + Assert.AreEqual(1, result.Count()); + + result = await _service.GetAllPackageManifestsAsync(); + Assert.AreEqual(2, result.Count()); + } }