using System; using System.Collections.Generic; using System.Net; using System.Security.Claims; using System.Text; using System.Threading; using System.Threading.Tasks; using AMWD.Common.AspNetCore.BasicAuthentication; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Primitives; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; namespace UnitTests.AspNetCore.Attributes { [TestClass] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public class BasicAuthenticationAttributeTests { private Mock requestHeaderMock; private Mock responseHeaderMock; private Mock requestMock; private Mock responseMock; private Mock contextMock; private Dictionary requestHeaders; private string validatorRealm; private ClaimsPrincipal validatorResult; private string responseHeaderAuthCallback; [TestInitialize] public void InitializeTest() { requestHeaders = new Dictionary(); validatorRealm = null; validatorResult = null; responseHeaderAuthCallback = null; } [TestMethod] public async Task ShouldValidateViaUsernamePassword() { // arrange var attribute = new BasicAuthenticationAttribute { Username = "user", Password = "password" }; requestHeaders.Add("Authorization", $"Basic {Convert.ToBase64String(Encoding.UTF8.GetBytes($"{attribute.Username}:{attribute.Password}"))}"); var context = GetContext(); // act await attribute.OnAuthorizationAsync(context); // assert Assert.IsNull(context.Result); Assert.IsTrue(string.IsNullOrWhiteSpace(responseHeaderAuthCallback)); } [TestMethod] public async Task ShouldValidateViaValidator() { // arrange var attribute = new BasicAuthenticationAttribute(); requestHeaders.Add("Authorization", $"Basic {Convert.ToBase64String(Encoding.UTF8.GetBytes($"{attribute.Username}:{attribute.Password}"))}"); validatorResult = new ClaimsPrincipal(); var context = GetContext(hasValidator: true); // act await attribute.OnAuthorizationAsync(context); // assert Assert.IsNull(context.Result); Assert.IsTrue(string.IsNullOrWhiteSpace(responseHeaderAuthCallback)); } [TestMethod] public async Task ShouldAllowAnonymous() { // arrange var attribute = new BasicAuthenticationAttribute { Username = "user", Password = "password" }; var context = GetContext(isAnonymousAllowed: true); // act await attribute.OnAuthorizationAsync(context); // assert Assert.IsNull(context.Result); Assert.IsTrue(string.IsNullOrWhiteSpace(responseHeaderAuthCallback)); } [TestMethod] public async Task ShouldAskOnUsernamePasswordWithoutRealm() { // arrange var attribute = new BasicAuthenticationAttribute { Username = "user", Password = "password" }; var context = GetContext(); // act await attribute.OnAuthorizationAsync(context); // assert Assert.IsNotNull(context.Result); Assert.IsTrue(context.Result is StatusCodeResult); Assert.AreEqual(401, ((StatusCodeResult)context.Result).StatusCode); Assert.IsFalse(string.IsNullOrWhiteSpace(responseHeaderAuthCallback)); Assert.AreEqual("Basic", responseHeaderAuthCallback); } [TestMethod] public async Task ShouldAskOnUsernamePasswordWithRealm() { // arrange var attribute = new BasicAuthenticationAttribute { Username = "user", Password = "password", Realm = "re:al\"m" }; var context = GetContext(); // act await attribute.OnAuthorizationAsync(context); // assert Assert.IsNotNull(context.Result); Assert.IsTrue(context.Result is StatusCodeResult); Assert.AreEqual(401, ((StatusCodeResult)context.Result).StatusCode); Assert.IsFalse(string.IsNullOrWhiteSpace(responseHeaderAuthCallback)); Assert.AreEqual("Basic realm=\"re:alm\"", responseHeaderAuthCallback); } [TestMethod] public async Task ShouldAskOnUsernamePasswordWrongUser() { // arrange var attribute = new BasicAuthenticationAttribute { Username = "user", Password = "password" }; requestHeaders.Add("Authorization", $"Basic {Convert.ToBase64String(Encoding.UTF8.GetBytes($"{attribute.Username}a:{attribute.Password}"))}"); var context = GetContext(); // act await attribute.OnAuthorizationAsync(context); // assert Assert.IsNotNull(context.Result); Assert.IsTrue(context.Result is StatusCodeResult); Assert.AreEqual(401, ((StatusCodeResult)context.Result).StatusCode); Assert.IsFalse(string.IsNullOrWhiteSpace(responseHeaderAuthCallback)); Assert.AreEqual("Basic", responseHeaderAuthCallback); } [TestMethod] public async Task ShouldAskOnUsernamePasswordWrongPassword() { // arrange var attribute = new BasicAuthenticationAttribute { Username = "user", Password = "password" }; requestHeaders.Add("Authorization", $"Basic {Convert.ToBase64String(Encoding.UTF8.GetBytes($"{attribute.Username}:{attribute.Password}a"))}"); var context = GetContext(); // act await attribute.OnAuthorizationAsync(context); // assert Assert.IsNotNull(context.Result); Assert.IsTrue(context.Result is StatusCodeResult); Assert.AreEqual(401, ((StatusCodeResult)context.Result).StatusCode); Assert.IsFalse(string.IsNullOrWhiteSpace(responseHeaderAuthCallback)); Assert.AreEqual("Basic", responseHeaderAuthCallback); } [TestMethod] public async Task ShouldAskOnValidatorWithRealmOnAttribute() { // arrange var attribute = new BasicAuthenticationAttribute { Realm = "attribute" }; requestHeaders.Add("Authorization", $"Basic {Convert.ToBase64String(Encoding.UTF8.GetBytes($"{attribute.Username}:{attribute.Password}"))}"); var context = GetContext(hasValidator: true); // act await attribute.OnAuthorizationAsync(context); // assert Assert.IsNotNull(context.Result); Assert.IsTrue(context.Result is StatusCodeResult); Assert.AreEqual(401, ((StatusCodeResult)context.Result).StatusCode); Assert.IsFalse(string.IsNullOrWhiteSpace(responseHeaderAuthCallback)); Assert.AreEqual("Basic realm=\"attribute\"", responseHeaderAuthCallback); } [TestMethod] public async Task ShouldAskOnValidatorWithRealmOnValidator() { // arrange validatorRealm = "validator"; var attribute = new BasicAuthenticationAttribute(); requestHeaders.Add("Authorization", $"Basic {Convert.ToBase64String(Encoding.UTF8.GetBytes($"{attribute.Username}:{attribute.Password}"))}"); var context = GetContext(hasValidator: true); // act await attribute.OnAuthorizationAsync(context); // assert Assert.IsNotNull(context.Result); Assert.IsTrue(context.Result is StatusCodeResult); Assert.AreEqual(401, ((StatusCodeResult)context.Result).StatusCode); Assert.IsFalse(string.IsNullOrWhiteSpace(responseHeaderAuthCallback)); Assert.AreEqual("Basic realm=\"validator\"", responseHeaderAuthCallback); } [TestMethod] public async Task ShouldReturnInternalError() { // arrange var attribute = new BasicAuthenticationAttribute(); requestHeaders.Add("Authorization", $"Basic {Convert.ToBase64String(Encoding.UTF8.GetBytes($"{attribute.Username}"))}"); var context = GetContext(); // act await attribute.OnAuthorizationAsync(context); // assert Assert.IsNotNull(context.Result); Assert.IsTrue(context.Result is StatusCodeResult); Assert.AreEqual(500, ((StatusCodeResult)context.Result).StatusCode); } private AuthorizationFilterContext GetContext(bool isAnonymousAllowed = false, bool hasValidator = false) { requestHeaderMock = new Mock(); foreach (var header in requestHeaders) { requestHeaderMock .Setup(h => h.ContainsKey(header.Key)) .Returns(true); requestHeaderMock .Setup(h => h[header.Key]) .Returns(header.Value); } responseHeaderMock = new Mock(); responseHeaderMock .SetupSet(h => h["WWW-Authenticate"] = It.IsAny()) .Callback((key, value) => { responseHeaderAuthCallback = value; }); requestMock = new Mock(); requestMock .Setup(r => r.Headers) .Returns(requestHeaderMock.Object); responseMock = new Mock(); responseMock .Setup(r => r.Headers) .Returns(responseHeaderMock.Object); var requestServicesMock = new Mock(); if (hasValidator) { var validatorMock = new Mock(); validatorMock .Setup(v => v.Realm) .Returns(validatorRealm); validatorMock .Setup(v => v.ValidateAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(validatorResult); requestServicesMock .Setup(rs => rs.GetService(typeof(IBasicAuthenticationValidator))) .Returns(validatorMock.Object); } var connectionInfoMock = new Mock(); connectionInfoMock .Setup(ci => ci.RemoteIpAddress) .Returns(IPAddress.Loopback); contextMock = new Mock(); contextMock .Setup(c => c.Request) .Returns(requestMock.Object); contextMock .Setup(c => c.Response) .Returns(responseMock.Object); contextMock .Setup(c => c.RequestServices) .Returns(requestServicesMock.Object); contextMock .Setup(c => c.Connection) .Returns(connectionInfoMock.Object); contextMock .Setup(c => c.RequestAborted) .Returns(CancellationToken.None); var routeDataMock = new Mock(); var actionDescriptor = new ActionDescriptor { EndpointMetadata = new List() }; if (isAnonymousAllowed) actionDescriptor.EndpointMetadata.Add(new AllowAnonymousAttribute()); return new AuthorizationFilterContext(new ActionContext { HttpContext = contextMock.Object, RouteData = routeDataMock.Object, ActionDescriptor = actionDescriptor, }, new List()); } } }