Fixes Invalid URL exception message response. (#3644)

* changed invalidurl exception status code to 400 insteadof 500

* Added Validation Filter for ResourceType and Id.
Removed Argumentnull exception block from OperationoutcomeExceptionFilterAttribute class

* Validation added to few other scenarios

* Added unit test case for Filete validation and rename filter attribute to ValidateIdSegmentAttribute

* E2E for VRead BadRequest

* reverting test

* decorated newly added test case class with Trait attribute

---------

Co-authored-by: rajithaaluri <rajithaaluri@microsoft.com>
This commit is contained in:
mahajan-xor 2024-01-10 15:05:05 +05:30 коммит произвёл GitHub
Родитель d4e58b9f41
Коммит 7e0f423774
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
5 изменённых файлов: 162 добавлений и 0 удалений

Просмотреть файл

@ -0,0 +1,108 @@
// -------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// -------------------------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using Hl7.Fhir.Model;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Routing;
using Microsoft.Health.Fhir.Api.Features.Filters;
using Microsoft.Health.Fhir.Api.Features.Routing;
using Microsoft.Health.Fhir.Core.Features.Validation;
using Microsoft.Health.Fhir.Core.Models;
using Microsoft.Health.Fhir.Tests.Common;
using Microsoft.Health.Test.Utilities;
using Xunit;
namespace Microsoft.Health.Fhir.Api.UnitTests.Features.Filters
{
[Trait(Traits.OwningTeam, OwningTeam.Fhir)]
[Trait(Traits.Category, Categories.Validate)]
[Trait(Traits.Category, Categories.Web)]
public class ValidateIdSegmentAttributeTests
{
[Theory]
[InlineData(" ")]
[InlineData(null)]
public void GivenAPatientAction_WhenPuttingAPatinetObjectWithNullResourceId_ThenAResourceNotValidExceptionShouldBeThrown(string id)
{
var filter = new ValidateIdSegmentAttribute();
var patient = new Patient
{
Id = Guid.NewGuid().ToString(),
};
var context = CreateContext(patient, id);
Assert.Throws<ResourceNotValidException>(() => filter.OnActionExecuting(context));
}
[Fact]
public void GivenAPatientAction_WhenPuttingAPatinetObjectwithValidResourceId_ThenTheResultIsSuccessful()
{
var filter = new ValidateIdSegmentAttribute();
var patient = new Patient
{
Id = Guid.NewGuid().ToString(),
};
var context = CreateContext(patient, patient.Id);
var exception = Record.Exception(() => filter.OnActionExecuting(context));
Assert.Null(exception);
}
[Theory]
[InlineData(" ")]
[InlineData(null)]
public void GivenAPatinetAction_WhenPuttingAParametersPatientObjectwithNullResourceId_ThenAResourceNotValidExceptionShouldBeThrown(string id)
{
var filter = new ValidateIdSegmentAttribute(true);
var patient = new Patient
{
Id = Guid.NewGuid().ToString(),
};
var parameters = new Parameters();
parameters.Add("resource", patient);
var context = CreateContext(parameters, id);
Assert.Throws<ResourceNotValidException>(() => filter.OnActionExecuting(context));
}
[Fact]
public void GivenAPatientAction_WhenPuttingAParametersPatientObjectwithValidResourceId_ThenTheResultIsSuccessful()
{
var filter = new ValidateResourceIdFilterAttribute(true);
var patient = new Patient
{
Id = Guid.NewGuid().ToString(),
};
var parameters = new Parameters();
parameters.Add("resource", patient);
var context = CreateContext(parameters, patient.Id);
filter.OnActionExecuting(context);
}
private static ActionExecutingContext CreateContext(Resource type, string id)
{
return new ActionExecutingContext(
new ActionContext(new DefaultHttpContext(), new RouteData { Values = { [KnownActionParameterNames.ResourceType] = "Patient", [KnownActionParameterNames.Id] = id } }, new ActionDescriptor()),
new List<IFilterMetadata>(),
new Dictionary<string, object> { { "resource", type } },
FilterTestsHelper.CreateMockFhirController());
}
}
}

Просмотреть файл

@ -40,6 +40,7 @@
<Compile Include="$(MSBuildThisFileDirectory)Features\Filters\OperationOutcomeExceptionFilterTests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Features\Filters\ValidateContentTypeFilterAttributeTests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Features\Filters\ValidateExportRequestFilterAttributeTests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Features\Filters\ValidateIdSegmentAttributeTests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Features\Filters\ValidateResourceIdFilterTests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Features\Filters\ValidateResourceTypeFilterTests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Features\Filters\SearchParameterFilterAttributeTests.cs" />

Просмотреть файл

@ -267,6 +267,7 @@ namespace Microsoft.Health.Fhir.Api.Controllers
/// <param name="typeParameter">The type.</param>
/// <param name="idParameter">The identifier.</param>
[HttpGet]
[ValidateIdSegmentAttribute]
[Route(KnownRoutes.ResourceTypeById, Name = RouteNames.ReadResource)]
[AuditEventType(AuditEventSubType.Read)]
public async Task<IActionResult> Read(string typeParameter, string idParameter)
@ -361,6 +362,7 @@ namespace Microsoft.Health.Fhir.Api.Controllers
/// <param name="idParameter">The identifier.</param>
/// <param name="vidParameter">The versionId.</param>
[HttpGet]
[ValidateIdSegmentAttribute]
[Route(KnownRoutes.ResourceTypeByIdAndVid, Name = RouteNames.ReadResourceWithVersionRoute)]
[AuditEventType(AuditEventSubType.VRead)]
public async Task<IActionResult> VRead(string typeParameter, string idParameter, string vidParameter)
@ -381,6 +383,7 @@ namespace Microsoft.Health.Fhir.Api.Controllers
/// <param name="idParameter">The identifier.</param>
/// <param name="hardDelete">A flag indicating whether to hard-delete the resource or not.</param>
[HttpDelete]
[ValidateIdSegmentAttribute]
[Route(KnownRoutes.ResourceTypeById)]
[AuditEventType(AuditEventSubType.Delete)]
public async Task<IActionResult> Delete(string typeParameter, string idParameter, [FromQuery] bool hardDelete)
@ -401,6 +404,7 @@ namespace Microsoft.Health.Fhir.Api.Controllers
/// <param name="typeParameter">The type.</param>
/// <param name="idParameter">The identifier.</param>
[HttpDelete]
[ValidateIdSegmentAttribute]
[Route(KnownRoutes.PurgeHistoryResourceTypeById)]
[AuditEventType(AuditEventSubType.PurgeHistory)]
public async Task<IActionResult> PurgeHistory(string typeParameter, string idParameter)
@ -455,6 +459,7 @@ namespace Microsoft.Health.Fhir.Api.Controllers
/// <param name="patchDocument">The JSON patch document.</param>
/// <param name="ifMatchHeader">Optional If-Match header.</param>
[HttpPatch]
[ValidateIdSegmentAttribute]
[Route(KnownRoutes.ResourceTypeById)]
[AuditEventType(AuditEventSubType.Patch)]
[Consumes("application/json-patch+json")]
@ -504,6 +509,7 @@ namespace Microsoft.Health.Fhir.Api.Controllers
/// <param name="paramsResource">The JSON FHIR Parameters Resource.</param>
/// <param name="ifMatchHeader">Optional If-Match header.</param>
[HttpPatch]
[ValidateIdSegmentAttribute]
[Route(KnownRoutes.ResourceTypeById)]
[AuditEventType(AuditEventSubType.Patch)]
[Consumes("application/fhir+json")]

Просмотреть файл

@ -0,0 +1,46 @@
// -------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// -------------------------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using EnsureThat;
using FluentValidation.Results;
using Hl7.Fhir.Model;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Health.Fhir.Api.Features.Routing;
using Microsoft.Health.Fhir.Core.Features.Validation;
namespace Microsoft.Health.Fhir.Api.Features.Filters
{
[AttributeUsage(AttributeTargets.Method)]
internal sealed class ValidateIdSegmentAttribute : ParameterCompatibleFilter
{
public ValidateIdSegmentAttribute(bool allowParametersResource = false)
: base(allowParametersResource)
{
}
public override void OnActionExecuting(ActionExecutingContext context)
{
EnsureArg.IsNotNull(context, nameof(context));
if (context.RouteData.Values.TryGetValue(KnownActionParameterNames.Id, out var resourceId))
{
ValidateId((string)resourceId);
}
}
private static void ValidateId(string resourceId)
{
if (string.IsNullOrWhiteSpace(resourceId))
{
throw new ResourceNotValidException(new List<ValidationFailure>
{
new ValidationFailure("ResourceKey.Id", string.Format(Core.Resources.IdRequirements)),
});
}
}
}
}

Просмотреть файл

@ -54,6 +54,7 @@
<Compile Include="$(MSBuildThisFileDirectory)Features\Filters\OperationOutcomeExceptionFilterAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Features\Filters\ValidateResourceIdFilterAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Features\Filters\ValidateResourceTypeFilterAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Features\Filters\ValidateIdSegmentAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Features\Filters\WeakETagBinder.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Features\Formatters\FhirJsonInputFormatter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Features\Formatters\FhirJsonOutputFormatter.cs" />