diff --git a/src/Microsoft.Health.Api.UnitTests/Features/Audit/AuditEventTypeMappingTests.cs b/src/Microsoft.Health.Api.UnitTests/Features/Audit/AuditEventTypeMappingTests.cs new file mode 100644 index 00000000..4e60f431 --- /dev/null +++ b/src/Microsoft.Health.Api.UnitTests/Features/Audit/AuditEventTypeMappingTests.cs @@ -0,0 +1,97 @@ +// ------------------------------------------------------------------------------------------------- +// 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 Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.Health.Api.Features.Audit; +using Microsoft.Health.Extensions.DependencyInjection; +using NSubstitute; +using Xunit; + +namespace Microsoft.Health.Api.UnitTests.Features.Audit +{ + public class AuditEventTypeMappingTests + { + private const string ControllerName = nameof(MockController); + private const string AnonymousMethodName = nameof(MockController.Anonymous); + private const string AudittedMethodName = nameof(MockController.Auditted); + private const string NoAttributeMethodName = nameof(MockController.NoAttribute); + private const string AuditEventType = "audit"; + + private readonly IActionDescriptorCollectionProvider _actionDescriptorCollectionProvider = Substitute.For(); + private readonly AuditEventTypeMapping _auditEventTypeMapping; + + public AuditEventTypeMappingTests() + { + Type mockControllerType = typeof(MockController); + + var actionDescriptors = new List() + { + new ControllerActionDescriptor() + { + ControllerName = ControllerName, + ActionName = AnonymousMethodName, + MethodInfo = mockControllerType.GetMethod(AnonymousMethodName), + }, + new ControllerActionDescriptor() + { + ControllerName = ControllerName, + ActionName = AudittedMethodName, + MethodInfo = mockControllerType.GetMethod(AudittedMethodName), + }, + new ControllerActionDescriptor() + { + ControllerName = ControllerName, + ActionName = NoAttributeMethodName, + MethodInfo = mockControllerType.GetMethod(NoAttributeMethodName), + }, + new PageActionDescriptor() + { + }, + }; + + var actionDescriptorCollection = new ActionDescriptorCollection(actionDescriptors, 1); + + _actionDescriptorCollectionProvider.ActionDescriptors.Returns(actionDescriptorCollection); + + _auditEventTypeMapping = new AuditEventTypeMapping(_actionDescriptorCollectionProvider); + + ((IStartable)_auditEventTypeMapping).Start(); + } + + [Theory] + [InlineData(ControllerName, AnonymousMethodName, null)] + [InlineData(ControllerName, AudittedMethodName, AuditEventType)] + public void GivenControllerNameAndActionName_WhenGetAuditEventTypeIsCalled_ThenAuditEventTypeShouldBeReturned(string controllerName, string actionName, string expectedAuditEventType) + { + string actualAuditEventType = _auditEventTypeMapping.GetAuditEventType(controllerName, actionName); + + Assert.Equal(expectedAuditEventType, actualAuditEventType); + } + + [Fact] + public void GivenUnknownControllerNameAndActionName_WhenGetAuditEventTypeIsCalled_ThenAuditExceptionShouldBeThrown() + { + Assert.Throws(() => _auditEventTypeMapping.GetAuditEventType("test", "action")); + } + + private class MockController : Controller + { + [AllowAnonymous] + public IActionResult Anonymous() => new OkResult(); + + [AuditEventType(AuditEventType)] + public IActionResult Auditted() => new OkResult(); + + public IActionResult NoAttribute() => new OkResult(); + } + } +} diff --git a/src/Microsoft.Health.Api.UnitTests/Features/Audit/AuditMiddlewareTests.cs b/src/Microsoft.Health.Api.UnitTests/Features/Audit/AuditMiddlewareTests.cs new file mode 100644 index 00000000..5ba8724e --- /dev/null +++ b/src/Microsoft.Health.Api.UnitTests/Features/Audit/AuditMiddlewareTests.cs @@ -0,0 +1,56 @@ +// ------------------------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------------------------------------------- + +using System.Net; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.Health.Api.Features.Audit; +using Microsoft.Health.Core.Features.Security; +using NSubstitute; +using Xunit; + +namespace Microsoft.Health.Api.UnitTests.Features.Audit +{ + public class AuditMiddlewareTests + { + private readonly IClaimsExtractor _claimsExtractor = Substitute.For(); + private readonly IAuditHelper _auditHelper = Substitute.For(); + + private readonly AuditMiddleware _auditMiddleware; + + private readonly HttpContext _httpContext = new DefaultHttpContext(); + + public AuditMiddlewareTests() + { + _auditMiddleware = new AuditMiddleware( + httpContext => Task.CompletedTask, + _claimsExtractor, + _auditHelper); + } + + [Fact] + public async Task GivenNotAuthXFailure_WhenInvoked_ThenAuditLogShouldNotBeLogged() + { + _httpContext.Response.StatusCode = (int)HttpStatusCode.OK; + + await _auditMiddleware.Invoke(_httpContext); + + _auditHelper.DidNotReceiveWithAnyArgs().LogExecuted( + httpContext: default, + claimsExtractor: default); + } + + [Theory] + [InlineData(HttpStatusCode.Unauthorized)] + public async Task GivenAuthXFailed_WhenInvoked_ThenAuditLogShouldBeLogged(HttpStatusCode statusCode) + { + _httpContext.Response.StatusCode = (int)statusCode; + + await _auditMiddleware.Invoke(_httpContext); + + _auditHelper.Received(1).LogExecuted(_httpContext, _claimsExtractor); + } + } +} diff --git a/src/Microsoft.Health.Api.UnitTests/Microsoft.Health.Api.UnitTests.csproj b/src/Microsoft.Health.Api.UnitTests/Microsoft.Health.Api.UnitTests.csproj index cd23cadb..cdfa5942 100644 --- a/src/Microsoft.Health.Api.UnitTests/Microsoft.Health.Api.UnitTests.csproj +++ b/src/Microsoft.Health.Api.UnitTests/Microsoft.Health.Api.UnitTests.csproj @@ -1,4 +1,4 @@ - + netcoreapp3.1 diff --git a/src/Microsoft.Health.Api/Extensions/MethodInfoExtensions.cs b/src/Microsoft.Health.Api/Extensions/MethodInfoExtensions.cs new file mode 100644 index 00000000..59c1e146 --- /dev/null +++ b/src/Microsoft.Health.Api/Extensions/MethodInfoExtensions.cs @@ -0,0 +1,24 @@ +// ------------------------------------------------------------------------------------------------- +// 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 System.Reflection; +using EnsureThat; + +namespace Microsoft.Health.Api.Extensions +{ + public static class MethodInfoExtensions + { + public static IEnumerable GetCustomAttributes(this MethodInfo methodInfo, bool inherit = false) + where T : Attribute + { + EnsureArg.IsNotNull(methodInfo, nameof(methodInfo)); + + return methodInfo.GetCustomAttributes(typeof(T), inherit)?.Cast(); + } + } +} diff --git a/src/Microsoft.Health.Api/Features/Audit/AuditAction.cs b/src/Microsoft.Health.Api/Features/Audit/AuditAction.cs new file mode 100644 index 00000000..073f562e --- /dev/null +++ b/src/Microsoft.Health.Api/Features/Audit/AuditAction.cs @@ -0,0 +1,20 @@ +// ------------------------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------------------------------------------- + +namespace Microsoft.Health.Api.Features.Audit +{ + public enum AuditAction + { + /// + /// Executing + /// + Executing, + + /// + /// Executed + /// + Executed, + } +} diff --git a/src/Microsoft.Health.Api/Features/Audit/AuditConstants.cs b/src/Microsoft.Health.Api/Features/Audit/AuditConstants.cs new file mode 100644 index 00000000..b25ac02d --- /dev/null +++ b/src/Microsoft.Health.Api/Features/Audit/AuditConstants.cs @@ -0,0 +1,16 @@ +// ------------------------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------------------------------------------- + +namespace Microsoft.Health.Api.Features.Audit +{ + public static class AuditConstants + { + public const string CustomAuditHeaderKeyValue = "CustomAuditHeaderCollectionKeyValue"; + + public const int MaximumNumberOfCustomHeaders = 10; + + public const int MaximumLengthOfCustomHeader = 2048; + } +} diff --git a/src/Microsoft.Health.Api/Features/Audit/AuditEventTypeAttribute.cs b/src/Microsoft.Health.Api/Features/Audit/AuditEventTypeAttribute.cs new file mode 100644 index 00000000..a589b67c --- /dev/null +++ b/src/Microsoft.Health.Api/Features/Audit/AuditEventTypeAttribute.cs @@ -0,0 +1,22 @@ +// ------------------------------------------------------------------------------------------------- +// 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 EnsureThat; + +namespace Microsoft.Health.Api.Features.Audit +{ + [AttributeUsage(AttributeTargets.Method)] + public class AuditEventTypeAttribute : Attribute + { + public AuditEventTypeAttribute(string auditEventType) + { + EnsureArg.IsNotNull(auditEventType, nameof(auditEventType)); + AuditEventType = auditEventType; + } + + public string AuditEventType { get; } + } +} diff --git a/src/Microsoft.Health.Api/Features/Audit/AuditEventTypeMapping.cs b/src/Microsoft.Health.Api/Features/Audit/AuditEventTypeMapping.cs new file mode 100644 index 00000000..124dca09 --- /dev/null +++ b/src/Microsoft.Health.Api/Features/Audit/AuditEventTypeMapping.cs @@ -0,0 +1,80 @@ +// ------------------------------------------------------------------------------------------------- +// 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 EnsureThat; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.Health.Api.Extensions; +using Microsoft.Health.Extensions.DependencyInjection; + +namespace Microsoft.Health.Api.Features.Audit +{ + /// + /// Provides the ability to lookup audit event type. + /// + /// + /// + /// Normally, the MVC middleware handles the routing which maps the controller name and action name to a method within the controller. + /// + /// + /// The that contains the audit event type information is defined on the method so we need the method + /// in order to be able to retrieve the attribute. However, since authentication middleware runs before the MVC middleware, if the authentication + /// rejects the call for whatever reason, we will not be able to retrieve the attribute and therefore, will not be able to get the corresponding audit event type. + /// + /// + /// This class builds the mapping ahead of time so that we can lookup the audit event type any time during the pipeline given the controller name and action name. + /// + /// + public class AuditEventTypeMapping : IAuditEventTypeMapping, IStartable + { + private readonly IActionDescriptorCollectionProvider _actionDescriptorCollectionProvider; + + private IReadOnlyDictionary<(string ControllerName, string ActionName), Attribute> _attributeDictionary; + + public AuditEventTypeMapping(IActionDescriptorCollectionProvider actionDescriptorCollectionProvider) + { + EnsureArg.IsNotNull(actionDescriptorCollectionProvider, nameof(actionDescriptorCollectionProvider)); + + _actionDescriptorCollectionProvider = actionDescriptorCollectionProvider; + } + + /// + public string GetAuditEventType(string controllerName, string actionName) + { + if (!_attributeDictionary.TryGetValue((controllerName, actionName), out Attribute attribute)) + { + throw new MissingAuditEventTypeMappingException(controllerName, actionName); + } + + if (attribute is AuditEventTypeAttribute auditEventTypeAttribute) + { + return auditEventTypeAttribute.AuditEventType; + } + + return null; + } + + void IStartable.Start() + { + _attributeDictionary = _actionDescriptorCollectionProvider.ActionDescriptors.Items + .OfType() + .Select(ad => + { + Attribute attribute = ad.MethodInfo?.GetCustomAttributes().FirstOrDefault() ?? + (Attribute)ad.MethodInfo?.GetCustomAttributes().FirstOrDefault(); + + return (ad.ControllerName, ad.ActionName, Attribute: attribute); + }) + .Where(item => item.Attribute != null) + .ToDictionary( + item => (item.ControllerName, item.ActionName), + item => item.Attribute); + } + } +} diff --git a/src/Microsoft.Health.Api/Features/Audit/AuditLoggingFilterAttribute.cs b/src/Microsoft.Health.Api/Features/Audit/AuditLoggingFilterAttribute.cs new file mode 100644 index 00000000..dd47438c --- /dev/null +++ b/src/Microsoft.Health.Api/Features/Audit/AuditLoggingFilterAttribute.cs @@ -0,0 +1,48 @@ +// ------------------------------------------------------------------------------------------------- +// 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 EnsureThat; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Health.Core.Features.Security; + +namespace Microsoft.Health.Api.Features.Audit +{ + [AttributeUsage(AttributeTargets.Class)] + public class AuditLoggingFilterAttribute : ActionFilterAttribute + { + private readonly IClaimsExtractor _claimsExtractor; + private readonly IAuditHelper _auditHelper; + + public AuditLoggingFilterAttribute( + IClaimsExtractor claimsExtractor, + IAuditHelper auditHelper) + { + EnsureArg.IsNotNull(claimsExtractor, nameof(claimsExtractor)); + EnsureArg.IsNotNull(auditHelper, nameof(auditHelper)); + + _claimsExtractor = claimsExtractor; + _auditHelper = auditHelper; + } + + public override void OnActionExecuting(ActionExecutingContext context) + { + EnsureArg.IsNotNull(context, nameof(context)); + + _auditHelper.LogExecuting(context.HttpContext, _claimsExtractor); + + base.OnActionExecuting(context); + } + + public override void OnResultExecuted(ResultExecutedContext context) + { + EnsureArg.IsNotNull(context, nameof(context)); + + _auditHelper.LogExecuted(context.HttpContext, _claimsExtractor); + + base.OnResultExecuted(context); + } + } +} diff --git a/src/Microsoft.Health.Api/Features/Audit/AuditMiddleware.cs b/src/Microsoft.Health.Api/Features/Audit/AuditMiddleware.cs new file mode 100644 index 00000000..44c9a213 --- /dev/null +++ b/src/Microsoft.Health.Api/Features/Audit/AuditMiddleware.cs @@ -0,0 +1,57 @@ +// ------------------------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------------------------------------------- + +using System.Net; +using System.Threading.Tasks; +using EnsureThat; +using Microsoft.AspNetCore.Http; +using Microsoft.Health.Core.Features.Security; + +namespace Microsoft.Health.Api.Features.Audit +{ + /// + /// A middleware that logs executed audit events. + /// + public class AuditMiddleware + { + private readonly RequestDelegate _next; + private readonly IClaimsExtractor _claimsExtractor; + private readonly IAuditHelper _auditHelper; + + public AuditMiddleware( + RequestDelegate next, + IClaimsExtractor claimsExtractor, + IAuditHelper auditHelper) + { + EnsureArg.IsNotNull(next, nameof(next)); + EnsureArg.IsNotNull(claimsExtractor, nameof(claimsExtractor)); + EnsureArg.IsNotNull(auditHelper, nameof(auditHelper)); + + _next = next; + _claimsExtractor = claimsExtractor; + _auditHelper = auditHelper; + } + + public async Task Invoke(HttpContext context) + { + try + { + await _next(context); + } + finally + { + var statusCode = (HttpStatusCode)context.Response.StatusCode; + + // Since authorization filters runs first before any other filters, if the authorization fails, + // the AuditLoggingFilterAttribute, which is where the audit logging would normally happen, will not be executed. + // This middleware will log any Unauthorized request if it hasn't been logged yet. + if (statusCode == HttpStatusCode.Unauthorized) + { + _auditHelper.LogExecuted(context, _claimsExtractor); + } + } + } + } +} diff --git a/src/Microsoft.Health.Api/Features/Audit/AuditMiddlewareExtensions.cs b/src/Microsoft.Health.Api/Features/Audit/AuditMiddlewareExtensions.cs new file mode 100644 index 00000000..0140208f --- /dev/null +++ b/src/Microsoft.Health.Api/Features/Audit/AuditMiddlewareExtensions.cs @@ -0,0 +1,21 @@ +// ------------------------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------------------------------------------- + +using EnsureThat; +using Microsoft.AspNetCore.Builder; + +namespace Microsoft.Health.Api.Features.Audit +{ + public static class AuditMiddlewareExtensions + { + public static IApplicationBuilder UseAudit( + this IApplicationBuilder builder) + { + EnsureArg.IsNotNull(builder, nameof(builder)); + + return builder.UseMiddleware(); + } + } +} diff --git a/src/Microsoft.Health.Api/Features/Audit/IAuditEventTypeMapping.cs b/src/Microsoft.Health.Api/Features/Audit/IAuditEventTypeMapping.cs new file mode 100644 index 00000000..b122c6b9 --- /dev/null +++ b/src/Microsoft.Health.Api/Features/Audit/IAuditEventTypeMapping.cs @@ -0,0 +1,22 @@ +// ------------------------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------------------------------------------- + +namespace Microsoft.Health.Api.Features.Audit +{ + /// + /// Provides the ability to lookup audit event type. + /// + public interface IAuditEventTypeMapping + { + /// + /// Gets the audit event type from the and . + /// + /// The controller name. + /// The action name. + /// The audit event type if exists; null if anonymous access is allowed. + /// Thrown when there is no audit event type associated with the and . + string GetAuditEventType(string controllerName, string actionName); + } +} diff --git a/src/Microsoft.Health.Api/Features/Audit/IAuditHeaderReader.cs b/src/Microsoft.Health.Api/Features/Audit/IAuditHeaderReader.cs new file mode 100644 index 00000000..b3db574d --- /dev/null +++ b/src/Microsoft.Health.Api/Features/Audit/IAuditHeaderReader.cs @@ -0,0 +1,15 @@ +// ------------------------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------------------------------------------- + +using System.Collections.Generic; +using Microsoft.AspNetCore.Http; + +namespace Microsoft.Health.Api.Features.Audit +{ + public interface IAuditHeaderReader + { + IReadOnlyDictionary Read(HttpContext httpContext); + } +} diff --git a/src/Microsoft.Health.Api/Features/Audit/IAuditHelper.cs b/src/Microsoft.Health.Api/Features/Audit/IAuditHelper.cs new file mode 100644 index 00000000..0bc49d2b --- /dev/null +++ b/src/Microsoft.Health.Api/Features/Audit/IAuditHelper.cs @@ -0,0 +1,30 @@ +// ------------------------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------------------------------------------- + +using Microsoft.AspNetCore.Http; +using Microsoft.Health.Core.Features.Security; + +namespace Microsoft.Health.Api.Features.Audit +{ + /// + /// Provides helper methods for auditing. + /// + public interface IAuditHelper + { + /// + /// Logs an executing audit entry for the current operation. + /// + /// The HTTP context. + /// The extractor used to extract claims. + void LogExecuting(HttpContext httpContext, IClaimsExtractor claimsExtractor); + + /// + /// Logs an executed audit entry for the current operation. + /// + /// The HTTP context. + /// The extractor used to extract claims. + void LogExecuted(HttpContext httpContext, IClaimsExtractor claimsExtractor); + } +} diff --git a/src/Microsoft.Health.Api/Features/Audit/MissingAuditEventTypeMappingException.cs b/src/Microsoft.Health.Api/Features/Audit/MissingAuditEventTypeMappingException.cs new file mode 100644 index 00000000..2a5777aa --- /dev/null +++ b/src/Microsoft.Health.Api/Features/Audit/MissingAuditEventTypeMappingException.cs @@ -0,0 +1,17 @@ +// ------------------------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------------------------------------------- + +using System; + +namespace Microsoft.Health.Api.Features.Audit +{ + public class MissingAuditEventTypeMappingException : Exception + { + public MissingAuditEventTypeMappingException(string controllerName, string actionName) + : base(string.Format(Resources.MissingAuditInformation, controllerName, actionName)) + { + } + } +} diff --git a/src/Microsoft.Health.Api/Microsoft.Health.Api.csproj b/src/Microsoft.Health.Api/Microsoft.Health.Api.csproj index efe6b478..5126f79a 100644 --- a/src/Microsoft.Health.Api/Microsoft.Health.Api.csproj +++ b/src/Microsoft.Health.Api/Microsoft.Health.Api.csproj @@ -9,7 +9,7 @@ - + diff --git a/src/Microsoft.Health.Api/Resources.Designer.cs b/src/Microsoft.Health.Api/Resources.Designer.cs index a86f0d53..6b33e136 100644 --- a/src/Microsoft.Health.Api/Resources.Designer.cs +++ b/src/Microsoft.Health.Api/Resources.Designer.cs @@ -60,6 +60,15 @@ internal Resources() { } } + /// + /// Looks up a localized string similar to The maximum length of a custom audit header value is {0}. The supplied custom audit header '{1}' has length of {2}.. + /// + internal static string CustomAuditHeaderTooLarge { + get { + return ResourceManager.GetString("CustomAuditHeaderTooLarge", resourceCulture); + } + } + /// /// Looks up a localized string similar to Failed while running health check.. /// @@ -68,5 +77,23 @@ internal static string FailedHealthCheckMessage { return ResourceManager.GetString("FailedHealthCheckMessage", resourceCulture); } } + + /// + /// Looks up a localized string similar to The audit information is missing for Controller: {0} and Action: {1}. This usually means the action is not marked with appropriate attribute.. + /// + internal static string MissingAuditInformation { + get { + return ResourceManager.GetString("MissingAuditInformation", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The maximum number of custom audit headers allowed is {0}. The number of custom audit headers supplied is {1}.. + /// + internal static string TooManyCustomAuditHeaders { + get { + return ResourceManager.GetString("TooManyCustomAuditHeaders", resourceCulture); + } + } } } diff --git a/src/Microsoft.Health.Api/Resources.resx b/src/Microsoft.Health.Api/Resources.resx index d5fb2c4f..43d307c9 100644 --- a/src/Microsoft.Health.Api/Resources.resx +++ b/src/Microsoft.Health.Api/Resources.resx @@ -117,7 +117,19 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + The maximum length of a custom audit header value is {0}. The supplied custom audit header '{1}' has length of {2}. + PH0 is a constant defining the max length of a header. PH1 is the name of the header sent in the request. PH2 is the length of the incoming header value. Example: The maximum length of a custom audit header value is 2048. The supplied custom audit header 'X-MS-AZUREHEALTH-AUDIT-SITE' has length of 3072. + Failed while running health check. + + The audit information is missing for Controller: {0} and Action: {1}. This usually means the action is not marked with appropriate attribute. + {0} is the controller name and {1} is the action name. + + + The maximum number of custom audit headers allowed is {0}. The number of custom audit headers supplied is {1}. + PH0 is a constant defining the max number of custom headers. PH1 is the count of custom headers sent in the request. Example: The maximum number of custom audit headers allowed is 10. The number of custom audit headers supplied is 12. + \ No newline at end of file diff --git a/src/Microsoft.Health.Blob/Microsoft.Health.Blob.csproj b/src/Microsoft.Health.Blob/Microsoft.Health.Blob.csproj index c5da7e6a..9fb0e3a6 100644 --- a/src/Microsoft.Health.Blob/Microsoft.Health.Blob.csproj +++ b/src/Microsoft.Health.Blob/Microsoft.Health.Blob.csproj @@ -9,7 +9,7 @@ - + diff --git a/src/Microsoft.Health.Core.UnitTests/Microsoft.Health.Core.UnitTests.csproj b/src/Microsoft.Health.Core.UnitTests/Microsoft.Health.Core.UnitTests.csproj index d61ff353..7a21516f 100644 --- a/src/Microsoft.Health.Core.UnitTests/Microsoft.Health.Core.UnitTests.csproj +++ b/src/Microsoft.Health.Core.UnitTests/Microsoft.Health.Core.UnitTests.csproj @@ -1,4 +1,4 @@ - + netcoreapp3.1 diff --git a/src/Microsoft.Health.Core/AssemblyInfo.cs b/src/Microsoft.Health.Core/AssemblyInfo.cs new file mode 100644 index 00000000..c23af353 --- /dev/null +++ b/src/Microsoft.Health.Core/AssemblyInfo.cs @@ -0,0 +1,10 @@ +// ------------------------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------------------------------------------- + +using System.Resources; +using System.Runtime.CompilerServices; + +[assembly: NeutralResourcesLanguage("en-us")] +[assembly: InternalsVisibleTo("Microsoft.Health.Api.UnitTests")] \ No newline at end of file diff --git a/src/Microsoft.Health.Core/Features/Context/IRequestContext.cs b/src/Microsoft.Health.Core/Features/Context/IRequestContext.cs new file mode 100644 index 00000000..a6956606 --- /dev/null +++ b/src/Microsoft.Health.Core/Features/Context/IRequestContext.cs @@ -0,0 +1,33 @@ +// ------------------------------------------------------------------------------------------------- +// 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.Security.Claims; +using Microsoft.Extensions.Primitives; + +namespace Microsoft.Health.Core.Features.Context +{ + public interface IRequestContext + { + string Method { get; } + + Uri BaseUri { get; } + + Uri Uri { get; } + + string CorrelationId { get; } + + string RouteName { get; set; } + + string AuditEventType { get; set; } + + ClaimsPrincipal Principal { get; set; } + + IDictionary RequestHeaders { get; } + + IDictionary ResponseHeaders { get; } + } +} diff --git a/src/Microsoft.Health.Core/Features/Security/IClaimsExtractor.cs b/src/Microsoft.Health.Core/Features/Security/IClaimsExtractor.cs new file mode 100644 index 00000000..57ae597d --- /dev/null +++ b/src/Microsoft.Health.Core/Features/Security/IClaimsExtractor.cs @@ -0,0 +1,14 @@ +// ------------------------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------------------------------------------- + +using System.Collections.Generic; + +namespace Microsoft.Health.Core.Features.Security +{ + public interface IClaimsExtractor + { + IReadOnlyCollection> Extract(); + } +} diff --git a/src/Microsoft.Health.Core/Microsoft.Health.Core.csproj b/src/Microsoft.Health.Core/Microsoft.Health.Core.csproj index 14a1f0fc..82198bae 100644 --- a/src/Microsoft.Health.Core/Microsoft.Health.Core.csproj +++ b/src/Microsoft.Health.Core/Microsoft.Health.Core.csproj @@ -1,11 +1,33 @@ - + netcoreapp3.1 - + + + + + + + + + + + + + True + True + Resources.resx + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + diff --git a/src/Microsoft.Health.Core/Resources.Designer.cs b/src/Microsoft.Health.Core/Resources.Designer.cs new file mode 100644 index 00000000..ae45639e --- /dev/null +++ b/src/Microsoft.Health.Core/Resources.Designer.cs @@ -0,0 +1,91 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Microsoft.Health.Core { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.Health.Core.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to The prefix used to identify custom audit headers cannot be empty.. + /// + internal static string CustomHeaderPrefixCannotBeEmpty { + get { + return ResourceManager.GetString("CustomHeaderPrefixCannotBeEmpty", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to There are {0} roles with the name '{1}'. + /// + internal static string DuplicateRoleNames { + get { + return ResourceManager.GetString("DuplicateRoleNames", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Error validating roles: + ///{0}. + /// + internal static string ErrorValidatingRoles { + get { + return ResourceManager.GetString("ErrorValidatingRoles", resourceCulture); + } + } + } +} diff --git a/src/Microsoft.Health.Core/Resources.resx b/src/Microsoft.Health.Core/Resources.resx new file mode 100644 index 00000000..7ee0cb9e --- /dev/null +++ b/src/Microsoft.Health.Core/Resources.resx @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The prefix used to identify custom audit headers cannot be empty. + + + There are {0} roles with the name '{1}' + + + Error validating roles: +{0} + + \ No newline at end of file diff --git a/src/Microsoft.Health.SqlServer.Api.UnitTests/Microsoft.Health.SqlServer.Api.UnitTests.csproj b/src/Microsoft.Health.SqlServer.Api.UnitTests/Microsoft.Health.SqlServer.Api.UnitTests.csproj index ed1a371a..a627400d 100644 --- a/src/Microsoft.Health.SqlServer.Api.UnitTests/Microsoft.Health.SqlServer.Api.UnitTests.csproj +++ b/src/Microsoft.Health.SqlServer.Api.UnitTests/Microsoft.Health.SqlServer.Api.UnitTests.csproj @@ -5,7 +5,7 @@ - + diff --git a/src/Microsoft.Health.SqlServer.Api/Microsoft.Health.SqlServer.Api.csproj b/src/Microsoft.Health.SqlServer.Api/Microsoft.Health.SqlServer.Api.csproj index 95bd5849..9ba1f3a8 100644 --- a/src/Microsoft.Health.SqlServer.Api/Microsoft.Health.SqlServer.Api.csproj +++ b/src/Microsoft.Health.SqlServer.Api/Microsoft.Health.SqlServer.Api.csproj @@ -9,7 +9,7 @@ - +