diff --git a/src/Umbraco.Core/Configuration/Models/DeliveryApiSettings.cs b/src/Umbraco.Core/Configuration/Models/DeliveryApiSettings.cs index 797a055912a1..80353edcdd2b 100644 --- a/src/Umbraco.Core/Configuration/Models/DeliveryApiSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/DeliveryApiSettings.cs @@ -42,8 +42,25 @@ public class DeliveryApiSettings /// /// The content type aliases that are not to be exposed. /// + /// + /// If is configured (non-empty), this setting is ignored. + /// public ISet DisallowedContentTypeAliases { get; set; } = new HashSet(); + /// + /// Gets or sets the aliases of the content types that are exclusively allowed to be exposed through the Delivery API. + /// When configured, only content of these types will be returned from Delivery API endpoints and added to the query index. + /// + /// + /// The content type aliases that are allowed to be exposed. + /// + /// + /// When this setting is configured (non-empty), it takes precedence over . + /// If a content type alias appears in both lists, the allow list wins and the content type will be exposed. + /// If this setting is empty, all content types are allowed except those in . + /// + public ISet AllowedContentTypeAliases { get; set; } = new HashSet(); + /// /// Gets or sets a value indicating whether the Delivery API should output rich text values as JSON instead of HTML. /// diff --git a/src/Umbraco.Core/Configuration/Models/Validation/DeliveryApiSettingsValidator.cs b/src/Umbraco.Core/Configuration/Models/Validation/DeliveryApiSettingsValidator.cs new file mode 100644 index 000000000000..05074690c006 --- /dev/null +++ b/src/Umbraco.Core/Configuration/Models/Validation/DeliveryApiSettingsValidator.cs @@ -0,0 +1,47 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Core.Configuration.Models.Validation; + +/// +/// Validator for configuration represented as . +/// +public class DeliveryApiSettingsValidator : IValidateOptions +{ + private readonly ILogger _logger; + + public DeliveryApiSettingsValidator(ILogger logger) + => _logger = logger; + + /// + public ValidateOptionsResult Validate(string? name, DeliveryApiSettings options) + { + ValidateContentTypeAliasOverlap(options); + + return ValidateOptionsResult.Success; + } + + private void ValidateContentTypeAliasOverlap(DeliveryApiSettings options) + { + if (options.AllowedContentTypeAliases.Count == 0 || options.DisallowedContentTypeAliases.Count == 0) + { + return; + } + + var overlappingAliases = options.AllowedContentTypeAliases + .Where(alias => options.DisallowedContentTypeAliases.InvariantContains(alias)) + .ToArray(); + + if (overlappingAliases.Length > 0) + { + _logger.LogWarning( + "Delivery API configuration contains content type aliases that appear in both AllowedContentTypeAliases and DisallowedContentTypeAliases: {Aliases}. " + + "The allow list takes precedence, so these content types will be allowed. Consider removing them from the disallow list to avoid confusion.", + string.Join(", ", overlappingAliases)); + } + } +} diff --git a/src/Umbraco.Core/DeliveryApi/ApiPublishedContentCache.cs b/src/Umbraco.Core/DeliveryApi/ApiPublishedContentCache.cs index dd91b4ced111..0e48262e733e 100644 --- a/src/Umbraco.Core/DeliveryApi/ApiPublishedContentCache.cs +++ b/src/Umbraco.Core/DeliveryApi/ApiPublishedContentCache.cs @@ -129,5 +129,5 @@ public IEnumerable GetByIds(IEnumerable contentIds) : null; private bool IsAllowedContentType(IPublishedContent content) - => _deliveryApiSettings.IsAllowedContentType(content); + => _deliveryApiSettings.IsAllowedContentType(content.ContentType.Alias); } diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs index e56a4cef2780..db436b867064 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs @@ -41,6 +41,7 @@ public static IUmbracoBuilder AddConfiguration(this IUmbracoBuilder builder) { // Register configuration validators. builder.Services.AddSingleton, ContentSettingsValidator>(); + builder.Services.AddSingleton, DeliveryApiSettingsValidator>(); builder.Services.AddSingleton, GlobalSettingsValidator>(); builder.Services.AddSingleton, HealthChecksSettingsValidator>(); builder.Services.AddSingleton, LoggingSettingsValidator>(); diff --git a/src/Umbraco.Core/Extensions/DeliveryApiSettingsExtensions.cs b/src/Umbraco.Core/Extensions/DeliveryApiSettingsExtensions.cs index f0e35f0d6e9e..2f49086cea57 100644 --- a/src/Umbraco.Core/Extensions/DeliveryApiSettingsExtensions.cs +++ b/src/Umbraco.Core/Extensions/DeliveryApiSettingsExtensions.cs @@ -1,19 +1,48 @@ -using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Models.PublishedContent; namespace Umbraco.Extensions; +/// +/// Provides extension methods for determining whether content types or content items are allowed to be exposed through +/// the Delivery API based on the configured allow and disallow lists. +/// public static class DeliveryApiSettingsExtensions { + [Obsolete("Please use the overload of IsAllowedContentType taking a content type alias. Scheduled for removal in Umbraco 19.")] public static bool IsAllowedContentType(this DeliveryApiSettings settings, IPublishedContent content) => settings.IsAllowedContentType(content.ContentType.Alias); + [Obsolete("Please use IsAllowedContentType and negate the result instead. Scheduled for removal in Umbraco 19.")] public static bool IsDisallowedContentType(this DeliveryApiSettings settings, IPublishedContent content) => settings.IsDisallowedContentType(content.ContentType.Alias); + /// + /// Determines whether a content type alias is allowed to be exposed through the Delivery API. + /// + /// The Delivery API settings. + /// The content type alias to check. + /// + /// true if the content type is allowed; otherwise, false. + /// + /// + /// If the allow list is configured (non-empty), only content types in the allow list are permitted. + /// The allow list takes precedence - if a content type is in both allow and disallow lists, it is allowed. + /// If the allow list is empty, all content types are allowed except those in the disallow list. + /// public static bool IsAllowedContentType(this DeliveryApiSettings settings, string contentTypeAlias) - => settings.IsDisallowedContentType(contentTypeAlias) is false; + { + // If allow list is configured, it takes precedence. + if (settings.AllowedContentTypeAliases.Count > 0) + { + return settings.AllowedContentTypeAliases.InvariantContains(contentTypeAlias); + } + // Otherwise the content type is allowed if it's not in the disallow list. + return settings.DisallowedContentTypeAliases.InvariantContains(contentTypeAlias) is false; + } + + [Obsolete("Please use IsAllowedContentType and negate the result instead. Scheduled for removal in Umbraco 19.")] public static bool IsDisallowedContentType(this DeliveryApiSettings settings, string contentTypeAlias) - => settings.DisallowedContentTypeAliases.InvariantContains(contentTypeAlias); + => settings.IsAllowedContentType(contentTypeAlias) is false; } diff --git a/src/Umbraco.Infrastructure/Examine/DeliveryApiContentIndexValueSetBuilder.cs b/src/Umbraco.Infrastructure/Examine/DeliveryApiContentIndexValueSetBuilder.cs index 7c76eaddd3b1..0da2e86ee7f1 100644 --- a/src/Umbraco.Infrastructure/Examine/DeliveryApiContentIndexValueSetBuilder.cs +++ b/src/Umbraco.Infrastructure/Examine/DeliveryApiContentIndexValueSetBuilder.cs @@ -204,7 +204,7 @@ private bool CanIndex(IContent content) } // is the content type allowed in the index? - if (_deliveryApiSettings.IsDisallowedContentType(content.ContentType.Alias)) + if (_deliveryApiSettings.IsAllowedContentType(content.ContentType.Alias) is false) { return false; } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/Validation/DeliveryApiSettingsValidatorTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/Validation/DeliveryApiSettingsValidatorTests.cs new file mode 100644 index 000000000000..5cd3ab6ef532 --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/Validation/DeliveryApiSettingsValidatorTests.cs @@ -0,0 +1,101 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Configuration.Models.Validation; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Configuration.Models.Validation; + +[TestFixture] +public class DeliveryApiSettingsValidatorTests +{ + private Mock> _loggerMock; + + [SetUp] + public void SetUp() => _loggerMock = new Mock>(); + + [Test] + public void Returns_Success_For_Configuration_With_Only_AllowList() + { + var validator = new DeliveryApiSettingsValidator(_loggerMock.Object); + var options = new DeliveryApiSettings + { + AllowedContentTypeAliases = new HashSet { "content1", "content2" }, + }; + + ValidateOptionsResult result = validator.Validate("settings", options); + + Assert.IsTrue(result.Succeeded); + VerifyNoWarningLogged(); + } + + [Test] + public void Returns_Success_For_Configuration_With_Only_DisallowList() + { + var validator = new DeliveryApiSettingsValidator(_loggerMock.Object); + var options = new DeliveryApiSettings + { + DisallowedContentTypeAliases = new HashSet { "content1", "content2" }, + }; + + ValidateOptionsResult result = validator.Validate("settings", options); + + Assert.IsTrue(result.Succeeded); + VerifyNoWarningLogged(); + } + + [Test] + public void Returns_Success_For_Configuration_With_No_Overlapping_Lists() + { + var validator = new DeliveryApiSettingsValidator(_loggerMock.Object); + var options = new DeliveryApiSettings + { + AllowedContentTypeAliases = new HashSet { "content1", "content2" }, + DisallowedContentTypeAliases = new HashSet { "content3", "content4" }, + }; + + ValidateOptionsResult result = validator.Validate("settings", options); + + Assert.IsTrue(result.Succeeded); + VerifyNoWarningLogged(); + } + + [Test] + public void Returns_Success_But_Logs_Warning_For_Overlapping_Lists() + { + var validator = new DeliveryApiSettingsValidator(_loggerMock.Object); + var options = new DeliveryApiSettings + { + AllowedContentTypeAliases = new HashSet { "content1", "content2" }, + DisallowedContentTypeAliases = new HashSet { "content1", "content3" }, + }; + + ValidateOptionsResult result = validator.Validate("settings", options); + + Assert.IsTrue(result.Succeeded); + + VerifyWarningLogged(); + } + + private void VerifyWarningLogged() + { + var warningCount = GetWarningLogCount(); + Assert.AreEqual(1, warningCount); + } + + private void VerifyNoWarningLogged() + { + var warningCount = GetWarningLogCount(); + Assert.AreEqual(0, warningCount); + } + + private int GetWarningLogCount() => + _loggerMock.Invocations + .Count(invocation => + invocation.Method.Name == nameof(ILogger.Log) && + invocation.Arguments.OfType().Any(level => level == LogLevel.Warning)); +} diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/PublishedContentCacheTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/PublishedContentCacheTests.cs index 405ff1416211..f4f176755d1b 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/PublishedContentCacheTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/PublishedContentCacheTests.cs @@ -24,7 +24,7 @@ public class PublishedContentCacheTests : DeliveryApiTests private IDocumentUrlService _documentUrlService; [SetUp] - public void Setup() + public override void Setup() { var contentTypeOneMock = new Mock(); contentTypeOneMock.SetupGet(m => m.Alias).Returns("theContentType"); @@ -81,7 +81,7 @@ public void Setup() [Test] public void PublishedContentCache_CanGetById() { - var publishedContentCache = new ApiPublishedContentCache(CreateRequestPreviewService(), CreateDeliveryApiSettings(), CreateApiDocumentUrlService(), _contentCache, CreateVariationContextAccessor()); + var publishedContentCache = CreateApiPublishedContentCache(CreateDeliveryApiSettings()); var content = publishedContentCache.GetById(_contentOneId); Assert.IsNotNull(content); Assert.AreEqual(_contentOneId, content.Key); @@ -92,7 +92,7 @@ public void PublishedContentCache_CanGetById() [Test] public void PublishedContentCache_CanGetByRoute() { - var publishedContentCache = new ApiPublishedContentCache(CreateRequestPreviewService(), CreateDeliveryApiSettings(), CreateApiDocumentUrlService(), _contentCache, CreateVariationContextAccessor()); + var publishedContentCache = CreateApiPublishedContentCache(CreateDeliveryApiSettings()); var content = publishedContentCache.GetByRoute("/content-two"); Assert.IsNotNull(content); Assert.AreEqual(_contentTwoId, content.Key); @@ -103,7 +103,7 @@ public void PublishedContentCache_CanGetByRoute() [Test] public void PublishedContentCache_CanGetByRoute_WithStartNodeIdPrefix() { - var publishedContentCache = new ApiPublishedContentCache(CreateRequestPreviewService(), CreateDeliveryApiSettings(), CreateApiDocumentUrlService(), _contentCache, CreateVariationContextAccessor()); + var publishedContentCache = CreateApiPublishedContentCache(CreateDeliveryApiSettings()); var content = publishedContentCache.GetByRoute("1234/content-three"); Assert.IsNotNull(content); Assert.AreEqual(_contentThreeId, content.Key); @@ -114,7 +114,7 @@ public void PublishedContentCache_CanGetByRoute_WithStartNodeIdPrefix() [Test] public void PublishedContentCache_CanGetByIds() { - var publishedContentCache = new ApiPublishedContentCache(CreateRequestPreviewService(), CreateDeliveryApiSettings(), CreateApiDocumentUrlService(), _contentCache, CreateVariationContextAccessor()); + var publishedContentCache = CreateApiPublishedContentCache(CreateDeliveryApiSettings()); var content = publishedContentCache.GetByIds(new[] { _contentOneId, _contentTwoId }).ToArray(); Assert.AreEqual(2, content.Length); Assert.AreEqual(_contentOneId, content.First().Key); @@ -123,10 +123,10 @@ public void PublishedContentCache_CanGetByIds() [TestCase(true)] [TestCase(false)] - public void PublishedContentCache_GetById_SupportsDenyList(bool denied) + public void PublishedContentCache_GetById_SupportsDisallowList(bool denied) { var denyList = denied ? new[] { "theOtherContentType" } : null; - var publishedContentCache = new ApiPublishedContentCache(CreateRequestPreviewService(), CreateDeliveryApiSettings(denyList), CreateApiDocumentUrlService(), _contentCache, CreateVariationContextAccessor()); + var publishedContentCache = CreateApiPublishedContentCache(CreateDeliveryApiSettings(denyList)); var content = publishedContentCache.GetById(_contentTwoId); if (denied) @@ -141,10 +141,10 @@ public void PublishedContentCache_GetById_SupportsDenyList(bool denied) [TestCase(true)] [TestCase(false)] - public void PublishedContentCache_GetByRoute_SupportsDenyList(bool denied) + public void PublishedContentCache_GetByRoute_SupportsDisallowList(bool denied) { var denyList = denied ? new[] { "theContentType" } : null; - var publishedContentCache = new ApiPublishedContentCache(CreateRequestPreviewService(), CreateDeliveryApiSettings(denyList), CreateApiDocumentUrlService(), _contentCache, CreateVariationContextAccessor()); + var publishedContentCache = CreateApiPublishedContentCache(CreateDeliveryApiSettings(denyList)); var content = publishedContentCache.GetByRoute("/content-one"); if (denied) @@ -159,10 +159,10 @@ public void PublishedContentCache_GetByRoute_SupportsDenyList(bool denied) [TestCase("theContentType")] [TestCase("theOtherContentType")] - public void PublishedContentCache_GetByIds_SupportsDenyList(string deniedContentType) + public void PublishedContentCache_GetByIds_SupportsDisallowList(string deniedContentType) { var denyList = new[] { deniedContentType }; - var publishedContentCache = new ApiPublishedContentCache(CreateRequestPreviewService(), CreateDeliveryApiSettings(denyList), CreateApiDocumentUrlService(), _contentCache, CreateVariationContextAccessor()); + var publishedContentCache = CreateApiPublishedContentCache(CreateDeliveryApiSettings(denyList)); var content = publishedContentCache.GetByIds(new[] { _contentOneId, _contentTwoId }).ToArray(); Assert.AreEqual(1, content.Length); @@ -177,10 +177,10 @@ public void PublishedContentCache_GetByIds_SupportsDenyList(string deniedContent } [Test] - public void PublishedContentCache_GetById_CanRetrieveContentTypesOutsideTheDenyList() + public void PublishedContentCache_GetById_CanRetrieveContentTypesOutsideTheDisallowList() { var denyList = new[] { "theContentType" }; - var publishedContentCache = new ApiPublishedContentCache(CreateRequestPreviewService(), CreateDeliveryApiSettings(denyList), CreateApiDocumentUrlService(), _contentCache, CreateVariationContextAccessor()); + var publishedContentCache = CreateApiPublishedContentCache(CreateDeliveryApiSettings(denyList)); var content = publishedContentCache.GetById(_contentTwoId); Assert.IsNotNull(content); Assert.AreEqual(_contentTwoId, content.Key); @@ -189,10 +189,10 @@ public void PublishedContentCache_GetById_CanRetrieveContentTypesOutsideTheDenyL } [Test] - public void PublishedContentCache_GetByRoute_CanRetrieveContentTypesOutsideTheDenyList() + public void PublishedContentCache_GetByRoute_CanRetrieveContentTypesOutsideTheDisallowList() { var denyList = new[] { "theOtherContentType" }; - var publishedContentCache = new ApiPublishedContentCache(CreateRequestPreviewService(), CreateDeliveryApiSettings(denyList), CreateApiDocumentUrlService(), _contentCache, CreateVariationContextAccessor()); + var publishedContentCache = CreateApiPublishedContentCache(CreateDeliveryApiSettings(denyList)); var content = publishedContentCache.GetByRoute("/content-one"); Assert.IsNotNull(content); Assert.AreEqual(_contentOneId, content.Key); @@ -204,7 +204,7 @@ public void PublishedContentCache_GetByRoute_CanRetrieveContentTypesOutsideTheDe [TestCase("da-DK")] public void PublishedContentCache_GetByRoute_CanRetrieveForCulture(string culture) { - var publishedContentCache = new ApiPublishedContentCache(CreateRequestPreviewService(), CreateDeliveryApiSettings(), CreateApiDocumentUrlService(), _contentCache, CreateVariationContextAccessor(culture)); + var publishedContentCache = CreateApiPublishedContentCache(CreateDeliveryApiSettings(), culture); var content = publishedContentCache.GetByRoute("/content-four"); Assert.IsNotNull(content); Assert.AreEqual(_contentFourId, content.Key); @@ -216,7 +216,7 @@ public void PublishedContentCache_GetByRoute_CanRetrieveForCulture(string cultur [TestCase(null)] public void PublishedContentCache_GetByRoute_CannotRetrieveForMissingOrUnknownCulture(string? culture) { - var publishedContentCache = new ApiPublishedContentCache(CreateRequestPreviewService(), CreateDeliveryApiSettings(), CreateApiDocumentUrlService(), _contentCache, CreateVariationContextAccessor(culture)); + var publishedContentCache = CreateApiPublishedContentCache(CreateDeliveryApiSettings(), culture); var content = publishedContentCache.GetByRoute("/content-four"); Assert.IsNull(content); } @@ -225,20 +225,131 @@ public void PublishedContentCache_GetByRoute_CannotRetrieveForMissingOrUnknownCu public void PublishedContentCache_GetByIds_CanDenyAllRequestedContent() { var denyList = new[] { "theContentType", "theOtherContentType" }; - var publishedContentCache = new ApiPublishedContentCache(CreateRequestPreviewService(), CreateDeliveryApiSettings(denyList), CreateApiDocumentUrlService(), _contentCache, CreateVariationContextAccessor()); + var publishedContentCache = CreateApiPublishedContentCache(CreateDeliveryApiSettings(denyList)); var content = publishedContentCache.GetByIds(new[] { _contentOneId, _contentTwoId }).ToArray(); Assert.IsEmpty(content); } [Test] - public void PublishedContentCache_DenyListIsCaseInsensitive() + public void PublishedContentCache_DisallowListIsCaseInsensitive() { var denyList = new[] { "THEcontentTYPE" }; - var publishedContentCache = new ApiPublishedContentCache(CreateRequestPreviewService(), CreateDeliveryApiSettings(denyList), CreateApiDocumentUrlService(), _contentCache, CreateVariationContextAccessor()); + var publishedContentCache = CreateApiPublishedContentCache(CreateDeliveryApiSettings(denyList)); var content = publishedContentCache.GetByRoute("/content-one"); Assert.IsNull(content); } + [TestCase(true)] + [TestCase(false)] + public void PublishedContentCache_GetById_SupportsAllowList(bool allowed) + { + var allowList = allowed ? new[] { "theOtherContentType" } : new[] { "someOtherType" }; + var publishedContentCache = CreateApiPublishedContentCache(CreateDeliveryApiSettings(allowedContentTypeAliases: allowList)); + var content = publishedContentCache.GetById(_contentTwoId); + + if (allowed) + { + Assert.IsNotNull(content); + } + else + { + Assert.IsNull(content); + } + } + + [TestCase(true)] + [TestCase(false)] + public void PublishedContentCache_GetByRoute_SupportsAllowList(bool allowed) + { + var allowList = allowed ? new[] { "theContentType" } : new[] { "someOtherType" }; + var publishedContentCache = CreateApiPublishedContentCache(CreateDeliveryApiSettings(allowedContentTypeAliases: allowList)); + var content = publishedContentCache.GetByRoute("/content-one"); + + if (allowed) + { + Assert.IsNotNull(content); + } + else + { + Assert.IsNull(content); + } + } + + [TestCase("theContentType")] + [TestCase("theOtherContentType")] + public void PublishedContentCache_GetByIds_SupportsAllowList(string allowedContentType) + { + var allowList = new[] { allowedContentType }; + var publishedContentCache = CreateApiPublishedContentCache(CreateDeliveryApiSettings(allowedContentTypeAliases: allowList)); + var content = publishedContentCache.GetByIds(new[] { _contentOneId, _contentTwoId }).ToArray(); + + Assert.AreEqual(1, content.Length); + if (allowedContentType == "theContentType") + { + Assert.AreEqual(_contentOneId, content.First().Key); + } + else + { + Assert.AreEqual(_contentTwoId, content.First().Key); + } + } + + [Test] + public void PublishedContentCache_GetByIds_AllowListCanAllowMultipleContentTypes() + { + var allowList = new[] { "theContentType", "theOtherContentType" }; + var publishedContentCache = CreateApiPublishedContentCache(CreateDeliveryApiSettings(allowedContentTypeAliases: allowList)); + var content = publishedContentCache.GetByIds(new[] { _contentOneId, _contentTwoId }).ToArray(); + Assert.AreEqual(2, content.Length); + } + + [Test] + public void PublishedContentCache_AllowListIsCaseInsensitive() + { + var allowList = new[] { "THEcontentTYPE" }; + var publishedContentCache = CreateApiPublishedContentCache(CreateDeliveryApiSettings(allowedContentTypeAliases: allowList)); + var content = publishedContentCache.GetByRoute("/content-one"); + Assert.IsNotNull(content); + } + + [Test] + public void PublishedContentCache_AllowListTakesPrecedenceOverDisallowList() + { + var denyList = new[] { "theContentType" }; + var allowList = new[] { "theContentType" }; + var publishedContentCache = CreateApiPublishedContentCache(CreateDeliveryApiSettings(denyList, allowList)); + var content = publishedContentCache.GetByRoute("/content-one"); + Assert.IsNotNull(content); + } + + [Test] + public void PublishedContentCache_AllowListIgnoresDisallowListCompletely() + { + var denyList = new[] { "theOtherContentType" }; + var allowList = new[] { "theContentType" }; + var publishedContentCache = CreateApiPublishedContentCache(CreateDeliveryApiSettings(denyList, allowList)); + + var contentOne = publishedContentCache.GetByRoute("/content-one"); + Assert.IsNotNull(contentOne); + + var contentTwo = publishedContentCache.GetById(_contentTwoId); + Assert.IsNull(contentTwo); + } + + [Test] + public void PublishedContentCache_EmptyAllowListFallsBackToDisallowList() + { + var denyList = new[] { "theContentType" }; + string[] allowList = Array.Empty(); + var publishedContentCache = CreateApiPublishedContentCache(CreateDeliveryApiSettings(denyList, allowList)); + + var contentOne = publishedContentCache.GetByRoute("/content-one"); + Assert.IsNull(contentOne); + + var contentTwo = publishedContentCache.GetById(_contentTwoId); + Assert.IsNotNull(contentTwo); + } + private IVariationContextAccessor CreateVariationContextAccessor(string? culture = null) { var mock = new Mock(); @@ -253,16 +364,20 @@ private IRequestPreviewService CreateRequestPreviewService(bool isPreview = fals return previewServiceMock.Object; } - private IOptionsMonitor CreateDeliveryApiSettings(string[]? disallowedContentTypeAliases = null) + private IOptionsMonitor CreateDeliveryApiSettings(string[]? disallowedContentTypeAliases = null, string[]? allowedContentTypeAliases = null) { var deliveryApiSettings = new DeliveryApiSettings { - DisallowedContentTypeAliases = new HashSet(disallowedContentTypeAliases ?? Array.Empty()) + DisallowedContentTypeAliases = new HashSet(disallowedContentTypeAliases ?? Array.Empty()), + AllowedContentTypeAliases = new HashSet(allowedContentTypeAliases ?? Array.Empty()), }; var deliveryApiOptionsMonitorMock = new Mock>(); deliveryApiOptionsMonitorMock.SetupGet(s => s.CurrentValue).Returns(deliveryApiSettings); return deliveryApiOptionsMonitorMock.Object; } + private ApiPublishedContentCache CreateApiPublishedContentCache(IOptionsMonitor deliveryApiSettings, string? culture = null) + => new(CreateRequestPreviewService(), deliveryApiSettings, CreateApiDocumentUrlService(), _contentCache, CreateVariationContextAccessor(culture)); + private IApiDocumentUrlService CreateApiDocumentUrlService() => new ApiDocumentUrlService(_documentUrlService); }