351 lines
10 KiB
C#
351 lines
10 KiB
C#
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<IHeaderDictionary> requestHeaderMock;
|
|
private Mock<IHeaderDictionary> responseHeaderMock;
|
|
|
|
private Mock<HttpRequest> requestMock;
|
|
private Mock<HttpResponse> responseMock;
|
|
|
|
private Mock<HttpContext> contextMock;
|
|
|
|
private Dictionary<string, string> requestHeaders;
|
|
private string validatorRealm;
|
|
private ClaimsPrincipal validatorResult;
|
|
|
|
private string responseHeaderAuthCallback;
|
|
|
|
[TestInitialize]
|
|
public void InitializeTest()
|
|
{
|
|
requestHeaders = new Dictionary<string, string>();
|
|
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<IHeaderDictionary>();
|
|
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<IHeaderDictionary>();
|
|
responseHeaderMock
|
|
.SetupSet(h => h["WWW-Authenticate"] = It.IsAny<StringValues>())
|
|
.Callback<string, StringValues>((key, value) =>
|
|
{
|
|
responseHeaderAuthCallback = value;
|
|
});
|
|
|
|
requestMock = new Mock<HttpRequest>();
|
|
requestMock
|
|
.Setup(r => r.Headers)
|
|
.Returns(requestHeaderMock.Object);
|
|
|
|
responseMock = new Mock<HttpResponse>();
|
|
responseMock
|
|
.Setup(r => r.Headers)
|
|
.Returns(responseHeaderMock.Object);
|
|
|
|
var requestServicesMock = new Mock<IServiceProvider>();
|
|
|
|
if (hasValidator)
|
|
{
|
|
var validatorMock = new Mock<IBasicAuthenticationValidator>();
|
|
validatorMock
|
|
.Setup(v => v.Realm)
|
|
.Returns(validatorRealm);
|
|
validatorMock
|
|
.Setup(v => v.ValidateAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<IPAddress>(), It.IsAny<CancellationToken>()))
|
|
.ReturnsAsync(validatorResult);
|
|
|
|
requestServicesMock
|
|
.Setup(rs => rs.GetService(typeof(IBasicAuthenticationValidator)))
|
|
.Returns(validatorMock.Object);
|
|
}
|
|
|
|
var connectionInfoMock = new Mock<ConnectionInfo>();
|
|
connectionInfoMock
|
|
.Setup(ci => ci.RemoteIpAddress)
|
|
.Returns(IPAddress.Loopback);
|
|
|
|
contextMock = new Mock<HttpContext>();
|
|
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<RouteData>();
|
|
|
|
var actionDescriptor = new ActionDescriptor
|
|
{
|
|
EndpointMetadata = new List<object>()
|
|
};
|
|
if (isAnonymousAllowed)
|
|
actionDescriptor.EndpointMetadata.Add(new AllowAnonymousAttribute());
|
|
|
|
return new AuthorizationFilterContext(new ActionContext
|
|
{
|
|
HttpContext = contextMock.Object,
|
|
RouteData = routeDataMock.Object,
|
|
ActionDescriptor = actionDescriptor,
|
|
}, new List<IFilterMetadata>());
|
|
}
|
|
}
|
|
}
|