Delivery API: Not Found response when the start-item in "Start-Item" header is invalid (#14217)

* Update comment

* Implement ValidateStartItemAttribute

* Implement StartItemHeaderHasValue() on IRequestStartItemProvider

* Fix case-sensitivity problem on url segment

* Change StartItemHeaderHasValue() to RequestedStartItem()

* Fix variant start-item header bug

* Rearranging attributes

* Revert to not explicitly get the culture from IRequestCultureService

---------

Co-authored-by: kjac <kja@umbraco.dk>
This commit is contained in:
Elitsa Marinovska
2023-05-10 08:05:57 +02:00
committed by GitHub
parent 6f3cf6ead5
commit 7ed59f05cb
9 changed files with 71 additions and 9 deletions

View File

@@ -1,5 +1,6 @@
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Common.Builders;
using Umbraco.Cms.Api.Delivery.Filters;
using Umbraco.Cms.Api.Delivery.Routing;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.DeliveryApi;
@@ -9,6 +10,8 @@ namespace Umbraco.Cms.Api.Delivery.Controllers;
[VersionedDeliveryApiRoute("content")]
[ApiExplorerSettings(GroupName = "Content")]
[LocalizeFromAcceptLanguageHeader]
[ValidateStartItem]
public abstract class ContentApiControllerBase : DeliveryApiControllerBase
{
protected IApiPublishedContentCache ApiPublishedContentCache { get; }

View File

@@ -9,7 +9,6 @@ namespace Umbraco.Cms.Api.Delivery.Controllers;
[ApiVersion("1.0")]
[DeliveryApiAccess]
[JsonOptionsName(Constants.JsonOptionsNames.DeliveryApi)]
[LocalizeFromAcceptLanguageHeader]
public abstract class DeliveryApiControllerBase : Controller
{
}

View File

@@ -34,6 +34,7 @@ public class QueryContentApiController : ContentApiControllerBase
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(PagedViewModel<IApiContentResponse>), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> Query(
string? fetch,
[FromQuery] string[] filter,

View File

@@ -0,0 +1,41 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Umbraco.Cms.Core.DeliveryApi;
using Umbraco.Cms.Core.Models.PublishedContent;
namespace Umbraco.Cms.Api.Delivery.Filters;
internal sealed class ValidateStartItemAttribute : TypeFilterAttribute
{
public ValidateStartItemAttribute()
: base(typeof(ValidateStartItemFilter))
{
}
private class ValidateStartItemFilter : IActionFilter
{
private readonly IRequestStartItemProvider _requestStartItemProvider;
public ValidateStartItemFilter(IRequestStartItemProvider requestStartItemProvider)
=> _requestStartItemProvider = requestStartItemProvider;
public void OnActionExecuting(ActionExecutingContext context)
{
if (_requestStartItemProvider.RequestedStartItem() is null)
{
return;
}
IPublishedContent? startItem = _requestStartItemProvider.GetStartItem();
if (startItem is null)
{
context.Result = new NotFoundObjectResult("The Start-Item could not be found");
}
}
public void OnActionExecuted(ActionExecutedContext context)
{
}
}
}

View File

@@ -22,7 +22,7 @@ public sealed class ContentTypeFilter : IFilterHandler
Value = string.Empty
};
// TODO: do we support negation?
// Support negation
if (alias.StartsWith('!'))
{
filterOption.Value = alias.Substring(1);

View File

@@ -22,7 +22,7 @@ public sealed class NameFilter : IFilterHandler
Value = string.Empty
};
// TODO: do we support negation?
// Support negation
if (value.StartsWith('!'))
{
filterOption.Value = value.Substring(1);

View File

@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Http;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.DeliveryApi;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.PublishedCache;
@@ -9,15 +10,20 @@ namespace Umbraco.Cms.Api.Delivery.Services;
internal sealed class RequestStartItemProvider : RequestHeaderHandler, IRequestStartItemProvider
{
private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor;
private readonly IVariationContextAccessor _variationContextAccessor;
// this provider lifetime is Scope, so we can cache this as a field
private IPublishedContent? _requestedStartContent;
public RequestStartItemProvider(
IHttpContextAccessor httpContextAccessor,
IPublishedSnapshotAccessor publishedSnapshotAccessor)
: base(httpContextAccessor) =>
IPublishedSnapshotAccessor publishedSnapshotAccessor,
IVariationContextAccessor variationContextAccessor)
: base(httpContextAccessor)
{
_publishedSnapshotAccessor = publishedSnapshotAccessor;
_variationContextAccessor = variationContextAccessor;
}
/// <inheritdoc/>
public IPublishedContent? GetStartItem()
@@ -27,13 +33,14 @@ internal sealed class RequestStartItemProvider : RequestHeaderHandler, IRequestS
return _requestedStartContent;
}
var headerValue = GetHeaderValue("Start-Item");
var headerValue = RequestedStartItem()?.Trim(Constants.CharArrays.ForwardSlash);
if (headerValue.IsNullOrWhiteSpace())
{
return null;
}
if (_publishedSnapshotAccessor.TryGetPublishedSnapshot(out IPublishedSnapshot? publishedSnapshot) == false || publishedSnapshot?.Content == null)
if (_publishedSnapshotAccessor.TryGetPublishedSnapshot(out IPublishedSnapshot? publishedSnapshot) == false ||
publishedSnapshot?.Content == null)
{
return null;
}
@@ -42,8 +49,11 @@ internal sealed class RequestStartItemProvider : RequestHeaderHandler, IRequestS
_requestedStartContent = Guid.TryParse(headerValue, out Guid key)
? rootContent.FirstOrDefault(c => c.Key == key)
: rootContent.FirstOrDefault(c => c.UrlSegment == headerValue);
: rootContent.FirstOrDefault(c => c.UrlSegment(_variationContextAccessor).InvariantEquals(headerValue));
return _requestedStartContent;
}
/// <inheritdoc/>
public string? RequestedStartItem() => GetHeaderValue("Start-Item");
}

View File

@@ -5,7 +5,12 @@ namespace Umbraco.Cms.Core.DeliveryApi;
public interface IRequestStartItemProvider
{
/// <summary>
/// Gets the requested start item from the "Start-Item" header, if present.
/// Gets the requested start item, if present.
/// </summary>
IPublishedContent? GetStartItem();
/// <summary>
/// Gets the value of the requested start item, if present.
/// </summary>
string? RequestedStartItem();
}

View File

@@ -6,4 +6,7 @@ internal sealed class NoopRequestStartItemProvider : IRequestStartItemProvider
{
/// <inheritdoc />
public IPublishedContent? GetStartItem() => null;
/// <inheritdoc />
public string? RequestedStartItem() => null;
}