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.Security.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 = []; _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) { StringValues outVal = header.Value; _requestHeaderMock .Setup(h => h.ContainsKey(header.Key)) .Returns(true); _requestHeaderMock .Setup(h => h[header.Key]) .Returns(header.Value); _requestHeaderMock .Setup(h => h.TryGetValue(header.Key, out outVal)) .Returns(true); } _responseHeaderMock = new Mock(); _responseHeaderMock .SetupSet(h => h.WWWAuthenticate = It.IsAny()) .Callback((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()); } } }