Skip to content

Commit

Permalink
Audit the authentication/authorization failures (#310)
Browse files Browse the repository at this point in the history
Audit the authentication/authorization failures
  • Loading branch information
jackliums authored Jan 8, 2019
1 parent e723842 commit ad4ffe7
Show file tree
Hide file tree
Showing 28 changed files with 814 additions and 375 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
// -------------------------------------------------------------------------------------------------
// 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.Net;
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.Extensions.Logging.Abstractions;
using Microsoft.Health.Extensions.DependencyInjection;
using Microsoft.Health.Fhir.Api.Features.Audit;
using Microsoft.Health.Fhir.Core.Features.Context;
using Microsoft.Health.Fhir.Core.Features.Persistence;
using NSubstitute;
using Xunit;

namespace Microsoft.Health.Fhir.Api.UnitTests.Features.Audit
{
public class AuditHelperTests
{
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 const string CorrelationId = "correlation";
private static readonly Uri Uri = new Uri("http://localhost/123");
private static readonly IReadOnlyCollection<KeyValuePair<string, string>> Claims = new List<KeyValuePair<string, string>>();

private readonly IActionDescriptorCollectionProvider _actionDescriptorCollectionProvider = Substitute.For<IActionDescriptorCollectionProvider>();
private readonly IFhirRequestContextAccessor _fhirRequestContextAccessor = Substitute.For<IFhirRequestContextAccessor>();
private readonly IClaimsIndexer _claimsIndexer = Substitute.For<IClaimsIndexer>();
private readonly IAuditLogger _auditLogger = Substitute.For<IAuditLogger>();

private readonly IFhirRequestContext _fhirRequestContext = Substitute.For<IFhirRequestContext>();

private readonly IAuditHelper _auditHelper;

public AuditHelperTests()
{
Type mockControllerType = typeof(MockController);

var actionDescriptors = new List<ActionDescriptor>()
{
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);

_fhirRequestContext.Uri.Returns(Uri);
_fhirRequestContext.CorrelationId.Returns(CorrelationId);

_fhirRequestContextAccessor.FhirRequestContext = _fhirRequestContext;

_claimsIndexer.Extract().Returns(Claims);

_auditHelper = new AuditHelper(_actionDescriptorCollectionProvider, _fhirRequestContextAccessor, _claimsIndexer, _auditLogger, NullLogger<AuditHelper>.Instance);

((IStartable)_auditHelper).Start();
}

[Theory]
[InlineData(ControllerName, AnonymousMethodName, null)]
[InlineData(ControllerName, AudittedMethodName, AuditEventType)]
public void GivenControllerNameAndActionName_WhenGetAuditEventTypeIsCalled_ThenAuditEventTypeShouldBeReturned(string controllerName, string actionName, string expectedAuditEventType)
{
string actualAuditEventType = _auditHelper.GetAuditEventType(controllerName, actionName);

Assert.Equal(expectedAuditEventType, actualAuditEventType);
}

[Fact]
public void GivenUnknownControllerNameAndActionName_WhenGetAuditEventTypeIsCalled_ThenAuditExceptionShouldBeThrown()
{
Assert.Throws<AuditException>(() => _auditHelper.GetAuditEventType("test", "action"));
}

[Fact]
public void GivenAnActionHasAllowAnonymousAttribute_WhenLogExecutingIsCalled_ThenAuditLogShouldNotBeLogged()
{
_auditHelper.LogExecuting(ControllerName, AnonymousMethodName);

_auditLogger.DidNotReceiveWithAnyArgs().LogAudit(AuditAction.Executing, null, null, null, null, null, null);
}

[Fact]
public void GivenAnActionHasAuditEventTypeAttribute_WhenLogExecutingIsCalled_ThenAuditLogShouldBeLogged()
{
_auditHelper.LogExecuting(ControllerName, AudittedMethodName);

_auditLogger.Received(1).LogAudit(
AuditAction.Executing,
AuditEventType,
resourceType: null,
requestUri: Uri,
statusCode: null,
correlationId: CorrelationId,
claims: Claims);
}

[Fact]
public void GivenUnknownActon_WhenLogExecutingIsCalled_ThenAuditExceptionShouldBeThrown()
{
Assert.Throws<AuditException>(() => _auditHelper.LogExecuting("test", "action"));
}

[Fact]
public void GivenAnActionHasAllowAnonymousAttribute_WhenLogExecutedIsCalled_ThenAuditLogShouldNotBeLogged()
{
_auditHelper.LogExecuted(ControllerName, AnonymousMethodName, HttpStatusCode.OK, "Patient");

_auditLogger.DidNotReceiveWithAnyArgs().LogAudit(AuditAction.Executed, null, null, null, null, null, null);
}

[Fact]
public void GivenAnActionHasAuditEventTypeAttribute_WhenLogExecutedIsCalled_ThenAuditLogShouldBeLogged()
{
const HttpStatusCode expectedStatusCode = HttpStatusCode.Created;
const string expectedResourceType = "Patient";

_auditHelper.LogExecuted(ControllerName, AudittedMethodName, expectedStatusCode, expectedResourceType);

_auditLogger.Received(1).LogAudit(
AuditAction.Executed,
AuditEventType,
expectedResourceType,
Uri,
expectedStatusCode,
CorrelationId,
Claims);
}

[Fact]
public void GivenUnknownActon_WhenLogExecutedIsCalled_ThenAuditExceptionShouldBeThrown()
{
Assert.Throws<AuditException>(() => _auditHelper.LogExecuted("test", "action", HttpStatusCode.Created, "Patient"));
}

private class MockController : Controller
{
[AllowAnonymous]
public IActionResult Anonymous() => new OkResult();

[AuditEventType(AuditEventType)]
public IActionResult Auditted() => new OkResult();

public IActionResult NoAttribute() => new OkResult();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// -------------------------------------------------------------------------------------------------
// 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 System.Net;
using Hl7.Fhir.Model;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Routing;
using Microsoft.Health.Fhir.Api.Features.ActionResults;
using Microsoft.Health.Fhir.Api.Features.Audit;
using NSubstitute;
using Xunit;

namespace Microsoft.Health.Fhir.Api.UnitTests.Features.Audit
{
public class AuditLoggingFilterAttributeTests
{
private const string ControllerName = "controller";
private const string ActionName = "action";

private readonly IAuditHelper _auditHelper = Substitute.For<IAuditHelper>();

private readonly AuditLoggingFilterAttribute _filter;

public AuditLoggingFilterAttributeTests()
{
_filter = new AuditLoggingFilterAttribute(_auditHelper);
}

[Fact]
public void GivenAController_WhenExecutingAction_ThenAuditLogShouldBeLogged()
{
var actionExecutingContext = new ActionExecutingContext(
new ActionContext(new DefaultHttpContext(), new RouteData(), new ControllerActionDescriptor() { DisplayName = "Executing Context Test Descriptor" }),
new List<IFilterMetadata>(),
new Dictionary<string, object>(),
new MockController());

actionExecutingContext.ActionDescriptor = new ControllerActionDescriptor()
{
ControllerName = ControllerName,
ActionName = ActionName,
};

_filter.OnActionExecuting(actionExecutingContext);

_auditHelper.Received(1).LogExecuting(ControllerName, ActionName);
}

[Fact]
public void GivenANonFhirResult_WhenExecutedAction_ThenAuditLogShouldBeLogged()
{
const HttpStatusCode expectedStatusCode = HttpStatusCode.InternalServerError;

SetupExecutedAction(expectedStatusCode, new OkResult());

_auditHelper.Received(1).LogExecuted(ControllerName, ActionName, expectedStatusCode, null);
}

[Fact]
public void GivenAFhirResultWithNullResource_WhenExecutedAction_ThenAuditLogShouldBeLogged()
{
const HttpStatusCode expectedStatusCode = HttpStatusCode.OK;

SetupExecutedAction(expectedStatusCode, new FhirResult());

_auditHelper.Received(1).LogExecuted(ControllerName, ActionName, expectedStatusCode, null);
}

[Fact]
public void GivenAController_WhenExecutedAction_ThenAuditLogShouldBeLogged()
{
const HttpStatusCode expectedStatusCode = HttpStatusCode.Created;

var fhirResult = new FhirResult(new Patient() { Name = { new HumanName() { Text = "TestPatient" } } });

SetupExecutedAction(expectedStatusCode, fhirResult);

_auditHelper.Received(1).LogExecuted(ControllerName, ActionName, expectedStatusCode, "Patient");
}

private void SetupExecutedAction(HttpStatusCode expectedStatusCode, IActionResult result)
{
var resultExecutedContext = new ResultExecutedContext(
new ActionContext(new DefaultHttpContext(), new RouteData(), new ControllerActionDescriptor() { DisplayName = "Executed Context Test Descriptor" }),
new List<IFilterMetadata>(),
result,
new MockController());

resultExecutedContext.HttpContext.Response.StatusCode = (int)expectedStatusCode;

resultExecutedContext.ActionDescriptor = new ControllerActionDescriptor()
{
ControllerName = ControllerName,
ActionName = ActionName,
};

_filter.OnResultExecuted(resultExecutedContext);
}

private class MockController : Controller
{
}
}
}
Loading

0 comments on commit ad4ffe7

Please sign in to comment.