Solution restructured to use multiple test projects
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\AMWD.Common.AspNetCore\AMWD.Common.AspNetCore.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,352 @@
|
||||
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 Moq;
|
||||
|
||||
namespace AMWD.Common.AspNetCore.Tests.Attributes
|
||||
{
|
||||
[TestClass]
|
||||
public class BasicAuthenticationAttributeTest
|
||||
{
|
||||
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 = [];
|
||||
_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)
|
||||
{
|
||||
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<IHeaderDictionary>();
|
||||
_responseHeaderMock
|
||||
.SetupSet(h => h.WWWAuthenticate = It.IsAny<StringValues>())
|
||||
.Callback<StringValues>((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>());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,339 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
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.Configuration;
|
||||
using Moq;
|
||||
|
||||
namespace AMWD.Common.AspNetCore.Tests.Attributes
|
||||
{
|
||||
[TestClass]
|
||||
public class IPAllowListAttributeTest
|
||||
{
|
||||
private Dictionary<string, string> _requestHeaders;
|
||||
private Dictionary<object, object> _itemsCallback;
|
||||
private string _configKey;
|
||||
private bool _configExists;
|
||||
private List<string> _allowedIpsConfig;
|
||||
|
||||
[TestInitialize]
|
||||
public void InitializeTest()
|
||||
{
|
||||
_requestHeaders = [];
|
||||
_itemsCallback = [];
|
||||
_configKey = null;
|
||||
_configExists = false;
|
||||
_allowedIpsConfig = [];
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldDenyOnNoConfiguration()
|
||||
{
|
||||
// arrange
|
||||
var remote = IPAddress.Parse("192.168.178.1");
|
||||
var attribute = new IPAllowListAttribute();
|
||||
var context = GetContext(remote);
|
||||
|
||||
// act
|
||||
attribute.OnActionExecuting(context);
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(context.Result);
|
||||
Assert.IsTrue(context.Result is StatusCodeResult);
|
||||
Assert.AreEqual(403, ((StatusCodeResult)context.Result).StatusCode);
|
||||
|
||||
Assert.AreEqual(1, _itemsCallback.Count);
|
||||
Assert.AreEqual(remote, _itemsCallback["RemoteAddress"]);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldDenyOnWrongConfiguration()
|
||||
{
|
||||
// arrange
|
||||
var remote = IPAddress.Parse("192.168.178.1");
|
||||
var attribute = new IPAllowListAttribute
|
||||
{
|
||||
AllowedIpAddresses = "192.168.178:1"
|
||||
};
|
||||
var context = GetContext(remote);
|
||||
|
||||
// act
|
||||
attribute.OnActionExecuting(context);
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(context.Result);
|
||||
Assert.IsTrue(context.Result is StatusCodeResult);
|
||||
Assert.AreEqual(403, ((StatusCodeResult)context.Result).StatusCode);
|
||||
|
||||
Assert.AreEqual(1, _itemsCallback.Count);
|
||||
Assert.AreEqual(remote, _itemsCallback["RemoteAddress"]);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldAllowLocalAccess()
|
||||
{
|
||||
// arrange
|
||||
var attribute = new IPAllowListAttribute();
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
attribute.OnActionExecuting(context);
|
||||
|
||||
// assert
|
||||
Assert.IsNull(context.Result);
|
||||
Assert.AreEqual(1, _itemsCallback.Count);
|
||||
Assert.AreEqual(IPAddress.Loopback, _itemsCallback["RemoteAddress"]);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldDenyLocalAccess()
|
||||
{
|
||||
// arrange
|
||||
var attribute = new IPAllowListAttribute
|
||||
{
|
||||
AllowLocalAccess = false
|
||||
};
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
attribute.OnActionExecuting(context);
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(context.Result);
|
||||
Assert.IsTrue(context.Result is StatusCodeResult);
|
||||
Assert.AreEqual(403, ((StatusCodeResult)context.Result).StatusCode);
|
||||
|
||||
Assert.AreEqual(1, _itemsCallback.Count);
|
||||
Assert.AreEqual(IPAddress.Loopback, _itemsCallback["RemoteAddress"]);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow("192.168.178.10")]
|
||||
[DataRow("192.168.178.20")]
|
||||
public void ShouldAllowSpecificAddress(string address)
|
||||
{
|
||||
// arrange
|
||||
var remote = IPAddress.Parse(address);
|
||||
var attribute = new IPAllowListAttribute
|
||||
{
|
||||
AllowLocalAccess = false,
|
||||
AllowedIpAddresses = ",127.0.0.0/8,192.168.178.10"
|
||||
};
|
||||
var context = GetContext(remote);
|
||||
|
||||
// act
|
||||
attribute.OnActionExecuting(context);
|
||||
|
||||
// assert
|
||||
if (address == "192.168.178.10")
|
||||
{
|
||||
Assert.IsNull(context.Result);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.IsNotNull(context.Result);
|
||||
Assert.IsTrue(context.Result is StatusCodeResult);
|
||||
Assert.AreEqual(403, ((StatusCodeResult)context.Result).StatusCode);
|
||||
}
|
||||
|
||||
Assert.AreEqual(1, _itemsCallback.Count);
|
||||
Assert.AreEqual(remote, _itemsCallback["RemoteAddress"]);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldAllowLocalAccessConfig()
|
||||
{
|
||||
// arrange
|
||||
_configKey = "White:List";
|
||||
_configExists = true;
|
||||
_allowedIpsConfig.Add("127.0.0.0/8");
|
||||
_allowedIpsConfig.Add("192.168.178.10");
|
||||
var attribute = new IPAllowListAttribute
|
||||
{
|
||||
AllowLocalAccess = true,
|
||||
ConfigurationKey = _configKey
|
||||
};
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
attribute.OnActionExecuting(context);
|
||||
|
||||
// assert
|
||||
Assert.IsNull(context.Result);
|
||||
Assert.AreEqual(1, _itemsCallback.Count);
|
||||
Assert.AreEqual(IPAddress.Loopback, _itemsCallback["RemoteAddress"]);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldDenyLocalAccessConfig()
|
||||
{
|
||||
// arrange
|
||||
_configKey = "White:List";
|
||||
_configExists = true;
|
||||
_allowedIpsConfig.Add("");
|
||||
_allowedIpsConfig.Add("192.168.178.10");
|
||||
var attribute = new IPAllowListAttribute
|
||||
{
|
||||
AllowLocalAccess = false,
|
||||
ConfigurationKey = _configKey
|
||||
};
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
attribute.OnActionExecuting(context);
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(context.Result);
|
||||
Assert.IsTrue(context.Result is StatusCodeResult);
|
||||
Assert.AreEqual(403, ((StatusCodeResult)context.Result).StatusCode);
|
||||
|
||||
Assert.AreEqual(1, _itemsCallback.Count);
|
||||
Assert.AreEqual(IPAddress.Loopback, _itemsCallback["RemoteAddress"]);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow("192.168.178.10")]
|
||||
[DataRow("192.168.178.20")]
|
||||
public void ShouldAllowSpecificAddressConfig(string address)
|
||||
{
|
||||
// arrange
|
||||
_configKey = "White:List";
|
||||
_configExists = true;
|
||||
_allowedIpsConfig.Add("192.168.178.10");
|
||||
var attribute = new IPAllowListAttribute
|
||||
{
|
||||
AllowLocalAccess = false,
|
||||
ConfigurationKey = _configKey
|
||||
};
|
||||
var remote = IPAddress.Parse(address);
|
||||
var context = GetContext(remote);
|
||||
|
||||
// act
|
||||
attribute.OnActionExecuting(context);
|
||||
|
||||
// assert
|
||||
if (address == "192.168.178.10")
|
||||
{
|
||||
Assert.IsNull(context.Result);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.IsNotNull(context.Result);
|
||||
Assert.IsTrue(context.Result is StatusCodeResult);
|
||||
Assert.AreEqual(403, ((StatusCodeResult)context.Result).StatusCode);
|
||||
}
|
||||
|
||||
Assert.AreEqual(1, _itemsCallback.Count);
|
||||
Assert.AreEqual(remote, _itemsCallback["RemoteAddress"]);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldDenyOnMissingConfiguration()
|
||||
{
|
||||
// arrange
|
||||
_configKey = "White:List";
|
||||
_configExists = false;
|
||||
var attribute = new IPAllowListAttribute
|
||||
{
|
||||
AllowLocalAccess = false,
|
||||
ConfigurationKey = _configKey
|
||||
};
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
attribute.OnActionExecuting(context);
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(context.Result);
|
||||
Assert.IsTrue(context.Result is StatusCodeResult);
|
||||
Assert.AreEqual(403, ((StatusCodeResult)context.Result).StatusCode);
|
||||
|
||||
Assert.AreEqual(1, _itemsCallback.Count);
|
||||
Assert.AreEqual(IPAddress.Loopback, _itemsCallback["RemoteAddress"]);
|
||||
}
|
||||
|
||||
private ActionExecutingContext GetContext(IPAddress remote = null)
|
||||
{
|
||||
var 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);
|
||||
}
|
||||
|
||||
var requestMock = new Mock<HttpRequest>();
|
||||
requestMock
|
||||
.Setup(r => r.Headers)
|
||||
.Returns(requestHeaderMock.Object);
|
||||
|
||||
var connectionInfoMock = new Mock<ConnectionInfo>();
|
||||
connectionInfoMock
|
||||
.Setup(ci => ci.LocalIpAddress)
|
||||
.Returns(IPAddress.Loopback);
|
||||
connectionInfoMock
|
||||
.Setup(ci => ci.RemoteIpAddress)
|
||||
.Returns(remote ?? IPAddress.Loopback);
|
||||
|
||||
var itemsMock = new Mock<IDictionary<object, object>>();
|
||||
itemsMock
|
||||
.SetupSet(i => i[It.IsAny<object>()] = It.IsAny<object>())
|
||||
.Callback<object, object>((key, val) => _itemsCallback.Add(key, val));
|
||||
|
||||
var configurationMock = new Mock<IConfiguration>();
|
||||
var children = new List<IConfigurationSection>();
|
||||
foreach (string ipAddress in _allowedIpsConfig)
|
||||
{
|
||||
var csm = new Mock<IConfigurationSection>();
|
||||
csm.Setup(cs => cs.Value).Returns(ipAddress);
|
||||
|
||||
children.Add(csm.Object);
|
||||
}
|
||||
|
||||
var configSectionMock = new Mock<IConfigurationSection>();
|
||||
configSectionMock
|
||||
.Setup(cs => cs.GetChildren())
|
||||
.Returns(children);
|
||||
|
||||
configurationMock
|
||||
.Setup(c => c.GetSection(_configKey))
|
||||
.Returns(_configExists ? configSectionMock.Object : null);
|
||||
|
||||
var requestServicesMock = new Mock<IServiceProvider>();
|
||||
requestServicesMock
|
||||
.Setup(s => s.GetService(typeof(IConfiguration)))
|
||||
.Returns(configurationMock.Object);
|
||||
|
||||
var contextMock = new Mock<HttpContext>();
|
||||
contextMock
|
||||
.Setup(c => c.Request)
|
||||
.Returns(requestMock.Object);
|
||||
contextMock
|
||||
.Setup(c => c.RequestServices)
|
||||
.Returns(requestServicesMock.Object);
|
||||
contextMock
|
||||
.Setup(c => c.Items)
|
||||
.Returns(itemsMock.Object);
|
||||
contextMock
|
||||
.Setup(c => c.Connection)
|
||||
.Returns(connectionInfoMock.Object);
|
||||
|
||||
var routeDataMock = new Mock<RouteData>();
|
||||
var actionDescriptorMock = new Mock<ActionDescriptor>();
|
||||
|
||||
return new ActionExecutingContext(new ActionContext
|
||||
{
|
||||
HttpContext = contextMock.Object,
|
||||
RouteData = routeDataMock.Object,
|
||||
ActionDescriptor = actionDescriptorMock.Object,
|
||||
}, new List<IFilterMetadata>(), new Dictionary<string, object>(), null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,338 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
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.Configuration;
|
||||
using Moq;
|
||||
|
||||
namespace AMWD.Common.AspNetCore.Tests.Attributes
|
||||
{
|
||||
[TestClass]
|
||||
public class IPBlockListAttributeTest
|
||||
{
|
||||
private Dictionary<string, string> _requestHeaders;
|
||||
private Dictionary<object, object> _itemsCallback;
|
||||
private string _configKey;
|
||||
private bool _configExists;
|
||||
private List<string> _restrictedIpsConfig;
|
||||
|
||||
[TestInitialize]
|
||||
public void InitializeTest()
|
||||
{
|
||||
_requestHeaders = [];
|
||||
_itemsCallback = [];
|
||||
_configKey = null;
|
||||
_configExists = false;
|
||||
_restrictedIpsConfig = [];
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldAllowOnNoConfiguration()
|
||||
{
|
||||
// arrange
|
||||
var remote = IPAddress.Parse("192.168.178.1");
|
||||
var attribute = new IPBlockListAttribute();
|
||||
var context = GetContext(remote);
|
||||
|
||||
// act
|
||||
attribute.OnActionExecuting(context);
|
||||
|
||||
// assert
|
||||
Assert.IsNull(context.Result);
|
||||
Assert.AreEqual(1, _itemsCallback.Count);
|
||||
Assert.AreEqual(remote, _itemsCallback["RemoteAddress"]);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldAllowOnWrongConfiguration()
|
||||
{
|
||||
// arrange
|
||||
var remote = IPAddress.Parse("192.168.178.1");
|
||||
var attribute = new IPBlockListAttribute
|
||||
{
|
||||
BlockedIpAddresses = "192.168.178:1"
|
||||
};
|
||||
var context = GetContext(remote);
|
||||
|
||||
// act
|
||||
attribute.OnActionExecuting(context);
|
||||
|
||||
// assert
|
||||
Assert.IsNull(context.Result);
|
||||
Assert.AreEqual(1, _itemsCallback.Count);
|
||||
Assert.AreEqual(remote, _itemsCallback["RemoteAddress"]);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldAllowLocalAccess()
|
||||
{
|
||||
// arrange
|
||||
var attribute = new IPBlockListAttribute
|
||||
{
|
||||
BlockLocalAccess = false,
|
||||
BlockedIpAddresses = "127.0.0.0/8"
|
||||
};
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
attribute.OnActionExecuting(context);
|
||||
|
||||
// assert
|
||||
Assert.IsNull(context.Result);
|
||||
Assert.AreEqual(1, _itemsCallback.Count);
|
||||
Assert.AreEqual(IPAddress.Loopback, _itemsCallback["RemoteAddress"]);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldBlockLocalAccess()
|
||||
{
|
||||
// arrange
|
||||
var attribute = new IPBlockListAttribute
|
||||
{
|
||||
BlockLocalAccess = true,
|
||||
BlockedIpAddresses = ",127.0.0.0/8"
|
||||
};
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
attribute.OnActionExecuting(context);
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(context.Result);
|
||||
Assert.IsTrue(context.Result is StatusCodeResult);
|
||||
Assert.AreEqual(403, ((StatusCodeResult)context.Result).StatusCode);
|
||||
|
||||
Assert.AreEqual(1, _itemsCallback.Count);
|
||||
Assert.AreEqual(IPAddress.Loopback, _itemsCallback["RemoteAddress"]);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow("192.168.178.10")]
|
||||
[DataRow("192.168.178.20")]
|
||||
public void ShouldBlockSpecificAddress(string address)
|
||||
{
|
||||
// arrange
|
||||
var remote = IPAddress.Parse(address);
|
||||
var attribute = new IPBlockListAttribute
|
||||
{
|
||||
BlockLocalAccess = true,
|
||||
BlockedIpAddresses = "127.0.0.0/8,192.168.178.10"
|
||||
};
|
||||
var context = GetContext(remote);
|
||||
|
||||
// act
|
||||
attribute.OnActionExecuting(context);
|
||||
|
||||
// assert
|
||||
if (address == "192.168.178.10")
|
||||
{
|
||||
Assert.IsNotNull(context.Result);
|
||||
Assert.IsTrue(context.Result is StatusCodeResult);
|
||||
Assert.AreEqual(403, ((StatusCodeResult)context.Result).StatusCode);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.IsNull(context.Result);
|
||||
}
|
||||
|
||||
Assert.AreEqual(1, _itemsCallback.Count);
|
||||
Assert.AreEqual(remote, _itemsCallback["RemoteAddress"]);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldAllowLocalAccessConfig()
|
||||
{
|
||||
// arrange
|
||||
_configKey = "Black:List";
|
||||
_configExists = true;
|
||||
_restrictedIpsConfig.Add("127.0.0.0/8");
|
||||
_restrictedIpsConfig.Add("192.168.178.10");
|
||||
var attribute = new IPBlockListAttribute
|
||||
{
|
||||
BlockLocalAccess = false,
|
||||
ConfigurationKey = _configKey
|
||||
};
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
attribute.OnActionExecuting(context);
|
||||
|
||||
// assert
|
||||
Assert.IsNull(context.Result);
|
||||
Assert.AreEqual(1, _itemsCallback.Count);
|
||||
Assert.AreEqual(IPAddress.Loopback, _itemsCallback["RemoteAddress"]);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldBlockLocalAccessConfig()
|
||||
{
|
||||
// arrange
|
||||
_configKey = "Black:List";
|
||||
_configExists = true;
|
||||
_restrictedIpsConfig.Add("");
|
||||
_restrictedIpsConfig.Add("127.0.0.0/8");
|
||||
_restrictedIpsConfig.Add("192.168.178.10");
|
||||
var attribute = new IPBlockListAttribute
|
||||
{
|
||||
BlockLocalAccess = true,
|
||||
ConfigurationKey = _configKey
|
||||
};
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
attribute.OnActionExecuting(context);
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(context.Result);
|
||||
Assert.IsTrue(context.Result is StatusCodeResult);
|
||||
Assert.AreEqual(403, ((StatusCodeResult)context.Result).StatusCode);
|
||||
|
||||
Assert.AreEqual(1, _itemsCallback.Count);
|
||||
Assert.AreEqual(IPAddress.Loopback, _itemsCallback["RemoteAddress"]);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow("192.168.178.10")]
|
||||
[DataRow("192.168.178.20")]
|
||||
public void ShouldBlockSpecificAddressConfig(string address)
|
||||
{
|
||||
// arrange
|
||||
_configKey = "Black:List";
|
||||
_configExists = true;
|
||||
_restrictedIpsConfig.Add("127.0.0.0/8");
|
||||
_restrictedIpsConfig.Add("192.168.178.10");
|
||||
var attribute = new IPBlockListAttribute
|
||||
{
|
||||
BlockLocalAccess = true,
|
||||
ConfigurationKey = _configKey
|
||||
};
|
||||
var remote = IPAddress.Parse(address);
|
||||
var context = GetContext(remote);
|
||||
|
||||
// act
|
||||
attribute.OnActionExecuting(context);
|
||||
|
||||
// assert
|
||||
if (address == "192.168.178.10")
|
||||
{
|
||||
Assert.IsNotNull(context.Result);
|
||||
Assert.IsTrue(context.Result is StatusCodeResult);
|
||||
Assert.AreEqual(403, ((StatusCodeResult)context.Result).StatusCode);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.IsNull(context.Result);
|
||||
}
|
||||
|
||||
Assert.AreEqual(1, _itemsCallback.Count);
|
||||
Assert.AreEqual(remote, _itemsCallback["RemoteAddress"]);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldAllowOnMissingConfiguration()
|
||||
{
|
||||
// arrange
|
||||
_configKey = "Black:List";
|
||||
_configExists = false;
|
||||
var attribute = new IPBlockListAttribute
|
||||
{
|
||||
BlockLocalAccess = true,
|
||||
ConfigurationKey = _configKey
|
||||
};
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
attribute.OnActionExecuting(context);
|
||||
|
||||
// assert
|
||||
Assert.IsNull(context.Result);
|
||||
|
||||
Assert.AreEqual(1, _itemsCallback.Count);
|
||||
Assert.AreEqual(IPAddress.Loopback, _itemsCallback["RemoteAddress"]);
|
||||
}
|
||||
|
||||
private ActionExecutingContext GetContext(IPAddress remote = null)
|
||||
{
|
||||
var 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);
|
||||
}
|
||||
|
||||
var requestMock = new Mock<HttpRequest>();
|
||||
requestMock
|
||||
.Setup(r => r.Headers)
|
||||
.Returns(requestHeaderMock.Object);
|
||||
|
||||
var connectionInfoMock = new Mock<ConnectionInfo>();
|
||||
connectionInfoMock
|
||||
.Setup(ci => ci.LocalIpAddress)
|
||||
.Returns(IPAddress.Loopback);
|
||||
connectionInfoMock
|
||||
.Setup(ci => ci.RemoteIpAddress)
|
||||
.Returns(remote ?? IPAddress.Loopback);
|
||||
|
||||
var itemsMock = new Mock<IDictionary<object, object>>();
|
||||
itemsMock
|
||||
.SetupSet(i => i[It.IsAny<object>()] = It.IsAny<object>())
|
||||
.Callback<object, object>((key, val) => _itemsCallback.Add(key, val));
|
||||
|
||||
var configurationMock = new Mock<IConfiguration>();
|
||||
var children = new List<IConfigurationSection>();
|
||||
foreach (string ipAddress in _restrictedIpsConfig)
|
||||
{
|
||||
var csm = new Mock<IConfigurationSection>();
|
||||
csm.Setup(cs => cs.Value).Returns(ipAddress);
|
||||
|
||||
children.Add(csm.Object);
|
||||
}
|
||||
|
||||
var configSectionMock = new Mock<IConfigurationSection>();
|
||||
configSectionMock
|
||||
.Setup(cs => cs.GetChildren())
|
||||
.Returns(children);
|
||||
|
||||
configurationMock
|
||||
.Setup(c => c.GetSection(_configKey))
|
||||
.Returns(_configExists ? configSectionMock.Object : null);
|
||||
|
||||
var requestServicesMock = new Mock<IServiceProvider>();
|
||||
requestServicesMock
|
||||
.Setup(s => s.GetService(typeof(IConfiguration)))
|
||||
.Returns(configurationMock.Object);
|
||||
|
||||
var contextMock = new Mock<HttpContext>();
|
||||
contextMock
|
||||
.Setup(c => c.Request)
|
||||
.Returns(requestMock.Object);
|
||||
contextMock
|
||||
.Setup(c => c.RequestServices)
|
||||
.Returns(requestServicesMock.Object);
|
||||
contextMock
|
||||
.Setup(c => c.Items)
|
||||
.Returns(itemsMock.Object);
|
||||
contextMock
|
||||
.Setup(c => c.Connection)
|
||||
.Returns(connectionInfoMock.Object);
|
||||
|
||||
var routeDataMock = new Mock<RouteData>();
|
||||
var actionDescriptorMock = new Mock<ActionDescriptor>();
|
||||
|
||||
return new ActionExecutingContext(new ActionContext
|
||||
{
|
||||
HttpContext = contextMock.Object,
|
||||
RouteData = routeDataMock.Object,
|
||||
ActionDescriptor = actionDescriptorMock.Object,
|
||||
}, new List<IFilterMetadata>(), new Dictionary<string, object>(), null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,477 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using Microsoft.AspNetCore.Antiforgery;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Moq;
|
||||
|
||||
namespace AMWD.Common.AspNetCore.Tests.Extensions
|
||||
{
|
||||
[TestClass]
|
||||
public class HttpContextExtensionsTest
|
||||
{
|
||||
private Mock<ISession> _sessionMock;
|
||||
|
||||
private string _tokenFormName;
|
||||
private string _tokenHeaderName;
|
||||
private string _tokenValue;
|
||||
|
||||
private Dictionary<string, string> _requestHeaders;
|
||||
private Dictionary<string, string> _requestQueries;
|
||||
private Dictionary<object, object> _items;
|
||||
|
||||
private IPAddress _remote;
|
||||
|
||||
[TestInitialize]
|
||||
public void InitializeTests()
|
||||
{
|
||||
_tokenFormName = null;
|
||||
_tokenHeaderName = null;
|
||||
_tokenValue = null;
|
||||
|
||||
_requestHeaders = [];
|
||||
_requestQueries = [];
|
||||
_items = [];
|
||||
|
||||
_remote = IPAddress.Loopback;
|
||||
}
|
||||
|
||||
#region Antiforgery
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnAntiforgery()
|
||||
{
|
||||
// arrange
|
||||
_tokenFormName = "af-token";
|
||||
_tokenHeaderName = "af-header";
|
||||
_tokenValue = "security_first";
|
||||
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
var (formName, headerName, value) = context.GetAntiforgeryToken();
|
||||
|
||||
// assert
|
||||
Assert.AreEqual(_tokenFormName, formName);
|
||||
Assert.AreEqual(_tokenHeaderName, headerName);
|
||||
Assert.AreEqual(_tokenValue, value);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnAntiforgeryNullService()
|
||||
{
|
||||
// arrange
|
||||
_tokenFormName = "af-token";
|
||||
_tokenHeaderName = "af-header";
|
||||
_tokenValue = "security_first";
|
||||
|
||||
var context = GetContext(hasAntiforgery: false);
|
||||
|
||||
// act
|
||||
var (formName, headerName, value) = context.GetAntiforgeryToken();
|
||||
|
||||
// assert
|
||||
Assert.IsNull(formName);
|
||||
Assert.IsNull(headerName);
|
||||
Assert.IsNull(value);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnAntiforgeryNullToken()
|
||||
{
|
||||
// arrange
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
var (formName, headerName, value) = context.GetAntiforgeryToken();
|
||||
|
||||
// assert
|
||||
Assert.IsNull(formName);
|
||||
Assert.IsNull(headerName);
|
||||
Assert.IsNull(value);
|
||||
}
|
||||
|
||||
#endregion Antiforgery
|
||||
|
||||
#region RemoteAddres
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnRemoteAddress()
|
||||
{
|
||||
// arrange
|
||||
_remote = IPAddress.Parse("1.2.3.4");
|
||||
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
var result = context.GetRemoteIpAddress();
|
||||
|
||||
// assert
|
||||
Assert.AreEqual(_remote, result);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow("Cf-Connecting-Ip")]
|
||||
[DataRow("X-Real-IP")]
|
||||
[DataRow("X-Forwarded-For")]
|
||||
public void ShouldReturnDefaultHeader(string headerName)
|
||||
{
|
||||
// arrange
|
||||
_remote = IPAddress.Parse("1.2.3.4");
|
||||
var header = IPAddress.Parse("5.6.7.8");
|
||||
_requestHeaders.Add(headerName, header.ToString());
|
||||
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
var result = context.GetRemoteIpAddress();
|
||||
|
||||
// assert
|
||||
Assert.AreNotEqual(_remote, result);
|
||||
Assert.AreEqual(header, result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnCustomHeader()
|
||||
{
|
||||
// arrange
|
||||
_remote = IPAddress.Parse("1.2.3.4");
|
||||
string headerName = "FooBar";
|
||||
var headerIp = IPAddress.Parse("5.6.7.8");
|
||||
|
||||
_requestHeaders.Add(headerName, headerIp.ToString());
|
||||
_requestHeaders.Add("X-Forwarded-For", _remote.ToString());
|
||||
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
var result = context.GetRemoteIpAddress(ipHeaderName: headerName);
|
||||
|
||||
// assert
|
||||
Assert.AreNotEqual(_remote, result);
|
||||
Assert.AreEqual(headerIp, result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnAddressInvalidHeader()
|
||||
{
|
||||
// arrange
|
||||
_remote = IPAddress.Parse("1.2.3.4");
|
||||
_requestHeaders.Add("X-Forwarded-For", "1.2.3:4");
|
||||
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
var result = context.GetRemoteIpAddress();
|
||||
|
||||
// assert
|
||||
Assert.AreEqual(_remote, result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnFirstAddressOnMultipleProxies()
|
||||
{
|
||||
// arrange
|
||||
_remote = IPAddress.Parse("1.2.3.4");
|
||||
var header = IPAddress.Parse("5.6.7.8");
|
||||
_requestHeaders.Add("X-Forwarded-For", $"{header}, 111.222.111.222");
|
||||
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
var result = context.GetRemoteIpAddress();
|
||||
|
||||
// assert
|
||||
Assert.AreNotEqual(_remote, result);
|
||||
Assert.AreEqual(header, result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnV4AddressOnMapped()
|
||||
{
|
||||
// arrange
|
||||
_remote = IPAddress.Parse("1.2.3.4");
|
||||
var header = IPAddress.Parse("::ffff:127.0.0.1");
|
||||
_requestHeaders.Add("X-Forwarded-For", "::ffff:127.0.0.1");
|
||||
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
var result = context.GetRemoteIpAddress();
|
||||
|
||||
// assert
|
||||
Assert.AreNotEqual(_remote, result);
|
||||
Assert.AreNotEqual(header, result);
|
||||
Assert.AreEqual(header.MapToIPv4(), result);
|
||||
}
|
||||
|
||||
#endregion RemoteAddres
|
||||
|
||||
#region Local Request
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnTrueOnLocal()
|
||||
{
|
||||
// arrange
|
||||
_remote = IPAddress.Loopback;
|
||||
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
bool result = context.IsLocalRequest();
|
||||
|
||||
// assert
|
||||
Assert.IsTrue(result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnFalseOnRemote()
|
||||
{
|
||||
// arrange
|
||||
_remote = IPAddress.Parse("1.2.3.4");
|
||||
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
bool result = context.IsLocalRequest();
|
||||
|
||||
// assert
|
||||
Assert.IsFalse(result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnTrueOnDefaultHeader()
|
||||
{
|
||||
// arrange
|
||||
_remote = IPAddress.Parse("1.2.3.4");
|
||||
var headerIp = IPAddress.Loopback;
|
||||
_requestHeaders.Add("X-Forwarded-For", headerIp.ToString());
|
||||
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
bool result = context.IsLocalRequest();
|
||||
|
||||
// assert
|
||||
Assert.IsTrue(result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnTrueOnCustomHeader()
|
||||
{
|
||||
// arrange
|
||||
_remote = IPAddress.Parse("1.2.3.4");
|
||||
string headerName = "FooBar";
|
||||
var headerIp = IPAddress.Loopback;
|
||||
_requestHeaders.Add(headerName, headerIp.ToString());
|
||||
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
bool result = context.IsLocalRequest(ipHeaderName: headerName);
|
||||
|
||||
// assert
|
||||
Assert.IsTrue(result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnFalseOnDefaultHeader()
|
||||
{
|
||||
// arrange
|
||||
var headerIp = IPAddress.Parse("1.2.3.4");
|
||||
_requestHeaders.Add("X-Forwarded-For", headerIp.ToString());
|
||||
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
bool result = context.IsLocalRequest();
|
||||
|
||||
// assert
|
||||
Assert.IsFalse(result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnFalseOnCustomHeader()
|
||||
{
|
||||
// arrange
|
||||
string headerName = "FooBar";
|
||||
var headerIp = IPAddress.Parse("1.2.3.4");
|
||||
_requestHeaders.Add(headerName, headerIp.ToString());
|
||||
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
bool result = context.IsLocalRequest(ipHeaderName: headerName);
|
||||
|
||||
// assert
|
||||
Assert.IsFalse(result);
|
||||
}
|
||||
|
||||
#endregion Local Request
|
||||
|
||||
#region ReturnUrl
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnNull()
|
||||
{
|
||||
// arrange
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
string result = context.GetReturnUrl();
|
||||
|
||||
// assert
|
||||
Assert.IsNull(result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnOriginalRequest()
|
||||
{
|
||||
// arrange
|
||||
string request = "abc";
|
||||
string query = "def";
|
||||
|
||||
_items.Add("OriginalRequest", request);
|
||||
_requestQueries.Add("ReturnUrl", query);
|
||||
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
string result = context.GetReturnUrl();
|
||||
|
||||
// assert
|
||||
Assert.AreEqual(request, result);
|
||||
Assert.AreNotEqual(query, result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnUrl()
|
||||
{
|
||||
// arrange
|
||||
string query = "def";
|
||||
_requestQueries.Add("ReturnUrl", query);
|
||||
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
string result = context.GetReturnUrl();
|
||||
|
||||
// assert
|
||||
Assert.AreEqual(query, result);
|
||||
}
|
||||
|
||||
#endregion ReturnUrl
|
||||
|
||||
#region Session
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldClearSession()
|
||||
{
|
||||
// arrange
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
context.ClearSession();
|
||||
|
||||
// assert
|
||||
_sessionMock.Verify(s => s.Clear(), Times.Once);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldSkipWhenNoSession()
|
||||
{
|
||||
// arrange
|
||||
var context = GetContext(hasSession: false);
|
||||
|
||||
// act
|
||||
context.ClearSession();
|
||||
|
||||
// assert
|
||||
_sessionMock.Verify(s => s.Clear(), Times.Never);
|
||||
}
|
||||
|
||||
#endregion Session
|
||||
|
||||
private HttpContext GetContext(bool hasAntiforgery = true, bool hasSession = true)
|
||||
{
|
||||
// Request
|
||||
var 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);
|
||||
}
|
||||
|
||||
var requestQueryMock = new Mock<IQueryCollection>();
|
||||
foreach (var query in _requestQueries)
|
||||
{
|
||||
requestQueryMock
|
||||
.Setup(h => h.ContainsKey(query.Key))
|
||||
.Returns(true);
|
||||
requestQueryMock
|
||||
.Setup(h => h[query.Key])
|
||||
.Returns(query.Value);
|
||||
}
|
||||
|
||||
var requestMock = new Mock<HttpRequest>();
|
||||
requestMock
|
||||
.Setup(r => r.Headers)
|
||||
.Returns(requestHeaderMock.Object);
|
||||
requestMock
|
||||
.Setup(r => r.Query)
|
||||
.Returns(requestQueryMock.Object);
|
||||
|
||||
// Request Services
|
||||
var requestServicesMock = new Mock<IServiceProvider>();
|
||||
if (hasAntiforgery)
|
||||
{
|
||||
var antiforgeryMock = new Mock<IAntiforgery>();
|
||||
antiforgeryMock
|
||||
.Setup(af => af.GetAndStoreTokens(It.IsAny<HttpContext>()))
|
||||
.Returns(() => string.IsNullOrWhiteSpace(_tokenValue) ? null : new AntiforgeryTokenSet(_tokenValue, _tokenValue, _tokenFormName, _tokenHeaderName));
|
||||
|
||||
requestServicesMock
|
||||
.Setup(rs => rs.GetService(typeof(IAntiforgery)))
|
||||
.Returns(antiforgeryMock.Object);
|
||||
}
|
||||
|
||||
// Connection
|
||||
var connectionInfoMock = new Mock<ConnectionInfo>();
|
||||
connectionInfoMock
|
||||
.Setup(ci => ci.LocalIpAddress)
|
||||
.Returns(IPAddress.Loopback);
|
||||
connectionInfoMock
|
||||
.Setup(ci => ci.RemoteIpAddress)
|
||||
.Returns(_remote);
|
||||
|
||||
// Session
|
||||
_sessionMock = new Mock<ISession>();
|
||||
|
||||
var contextMock = new Mock<HttpContext>();
|
||||
contextMock
|
||||
.Setup(c => c.Request)
|
||||
.Returns(requestMock.Object);
|
||||
contextMock
|
||||
.Setup(c => c.RequestServices)
|
||||
.Returns(requestServicesMock.Object);
|
||||
contextMock
|
||||
.Setup(c => c.Connection)
|
||||
.Returns(connectionInfoMock.Object);
|
||||
contextMock
|
||||
.Setup(c => c.Items)
|
||||
.Returns(_items);
|
||||
if (hasSession)
|
||||
{
|
||||
contextMock
|
||||
.Setup(c => c.Session)
|
||||
.Returns(_sessionMock.Object);
|
||||
}
|
||||
|
||||
return contextMock.Object;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
|
||||
namespace AMWD.Common.AspNetCore.Tests.Extensions
|
||||
{
|
||||
[TestClass]
|
||||
public class ModelStateDictionaryExtensionsTest
|
||||
{
|
||||
private TestModel _testModel;
|
||||
|
||||
[TestInitialize]
|
||||
public void InitializeTests()
|
||||
{
|
||||
_testModel = new TestModel
|
||||
{
|
||||
ValueA = "A",
|
||||
ValueB = "B",
|
||||
SubModel = new TestSubModel
|
||||
{
|
||||
SubValueA = "a",
|
||||
SubValueB = "b"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldAddNormalModelError()
|
||||
{
|
||||
// arrange
|
||||
var modelState = new ModelStateDictionary();
|
||||
|
||||
// act
|
||||
modelState.AddModelError(_testModel, m => m.ValueA, "ShitHappens");
|
||||
|
||||
// assert
|
||||
Assert.AreEqual(1, modelState.Count);
|
||||
Assert.AreEqual("ValueA", modelState.Keys.First());
|
||||
Assert.AreEqual("ShitHappens", modelState.Values.First().Errors.First().ErrorMessage);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldAddExtendedModelError()
|
||||
{
|
||||
// arrange
|
||||
var modelState = new ModelStateDictionary();
|
||||
|
||||
// act
|
||||
modelState.AddModelError(_testModel, m => m.SubModel.SubValueB, "ShitHappens");
|
||||
|
||||
// assert
|
||||
Assert.AreEqual(1, modelState.Count);
|
||||
Assert.AreEqual("SubModel.SubValueB", modelState.Keys.First());
|
||||
Assert.AreEqual("ShitHappens", modelState.Values.First().Errors.First().ErrorMessage);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentNullException))]
|
||||
public void ShouldThrowArgumentNull()
|
||||
{
|
||||
// arrange
|
||||
ModelStateDictionary modelState = null;
|
||||
|
||||
// act
|
||||
modelState.AddModelError(_testModel, m => m.SubModel.SubValueB, "ShitHappens");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(InvalidOperationException))]
|
||||
public void ShouldThrowInvalidOperation()
|
||||
{
|
||||
// arrange
|
||||
var modelState = new ModelStateDictionary();
|
||||
|
||||
// act
|
||||
modelState.AddModelError(_testModel, m => m, "ShitHappens");
|
||||
}
|
||||
|
||||
internal class TestModel
|
||||
{
|
||||
public string ValueA { get; set; }
|
||||
|
||||
public string ValueB { get; set; }
|
||||
|
||||
public TestSubModel SubModel { get; set; }
|
||||
}
|
||||
|
||||
internal class TestSubModel
|
||||
{
|
||||
public string SubValueA { get; set; }
|
||||
|
||||
public string SubValueB { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Moq;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace AMWD.Common.AspNetCore.Tests.Extensions
|
||||
{
|
||||
[TestClass]
|
||||
public class SessionExtensionsTest
|
||||
{
|
||||
private Mock<ISession> _sessionMock;
|
||||
|
||||
private string _sessionKey;
|
||||
private byte[] _sessionValue;
|
||||
|
||||
private TestModel _model;
|
||||
|
||||
internal class TestModel
|
||||
{
|
||||
public string ValueA { get; set; }
|
||||
|
||||
public string ValueB { get; set; }
|
||||
}
|
||||
|
||||
[TestInitialize]
|
||||
public void InitializeTests()
|
||||
{
|
||||
_sessionKey = null;
|
||||
_sessionValue = null;
|
||||
|
||||
_model = new TestModel
|
||||
{
|
||||
ValueA = "A",
|
||||
ValueB = "B"
|
||||
};
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldCheckKeyExists()
|
||||
{
|
||||
// arrange
|
||||
_sessionKey = "exists";
|
||||
var session = GetSession();
|
||||
|
||||
// act
|
||||
bool result1 = session.HasKey("exists");
|
||||
bool result2 = session.HasKey("somewhere");
|
||||
|
||||
// assert
|
||||
Assert.IsTrue(result1);
|
||||
Assert.IsFalse(result2);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldGetValue()
|
||||
{
|
||||
// arrange
|
||||
_sessionKey = "test";
|
||||
_sessionValue = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(_model));
|
||||
var session = GetSession();
|
||||
|
||||
// act
|
||||
var result = session.GetValue<TestModel>(_sessionKey);
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(result);
|
||||
Assert.AreEqual(_model.ValueA, result.ValueA);
|
||||
Assert.AreEqual(_model.ValueB, result.ValueB);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldGetNull()
|
||||
{
|
||||
// arrange
|
||||
_sessionKey = "foo";
|
||||
_sessionValue = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(_model));
|
||||
var session = GetSession();
|
||||
|
||||
// act
|
||||
var result = session.GetValue<TestModel>("bar");
|
||||
|
||||
// assert
|
||||
Assert.IsNull(result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldGetValueWithFallback()
|
||||
{
|
||||
// arrange
|
||||
_sessionKey = "test";
|
||||
_sessionValue = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(_model));
|
||||
var session = GetSession();
|
||||
|
||||
// act
|
||||
var result = session.GetValue(_sessionKey, new TestModel());
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(result);
|
||||
Assert.AreEqual(_model.ValueA, result.ValueA);
|
||||
Assert.AreEqual(_model.ValueB, result.ValueB);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldGetFallback()
|
||||
{
|
||||
// arrange
|
||||
_sessionKey = "foo";
|
||||
_sessionValue = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(_model));
|
||||
var session = GetSession();
|
||||
|
||||
// act
|
||||
var result = session.GetValue("bar", new TestModel());
|
||||
|
||||
// assert
|
||||
Assert.IsNotNull(result);
|
||||
Assert.AreEqual(null, result.ValueA);
|
||||
Assert.AreEqual(null, result.ValueB);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldSetValue()
|
||||
{
|
||||
// arrange
|
||||
string key = "foobar";
|
||||
var session = GetSession();
|
||||
|
||||
// act
|
||||
session.SetValue(key, _model);
|
||||
|
||||
// arrange
|
||||
Assert.AreEqual(key, _sessionKey);
|
||||
Assert.AreEqual(JsonConvert.SerializeObject(_model), Encoding.UTF8.GetString(_sessionValue));
|
||||
}
|
||||
|
||||
private ISession GetSession()
|
||||
{
|
||||
string[] keys = [_sessionKey];
|
||||
|
||||
_sessionMock = new Mock<ISession>();
|
||||
_sessionMock
|
||||
.Setup(s => s.TryGetValue(It.IsAny<string>(), out _sessionValue))
|
||||
.Returns<string, byte[]>((key, value) => _sessionKey == key);
|
||||
_sessionMock
|
||||
.Setup(s => s.Set(It.IsAny<string>(), It.IsAny<byte[]>()))
|
||||
.Callback<string, byte[]>((key, value) =>
|
||||
{
|
||||
_sessionKey = key;
|
||||
_sessionValue = value;
|
||||
});
|
||||
_sessionMock
|
||||
.Setup(s => s.Keys)
|
||||
.Returns(keys);
|
||||
|
||||
return _sessionMock.Object;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
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.Http;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using Moq;
|
||||
|
||||
namespace AMWD.Common.AspNetCore.Tests.Security.BasicAuthentication
|
||||
{
|
||||
[TestClass]
|
||||
public class BasicAuthenticationMiddlewareTest
|
||||
{
|
||||
private Dictionary<string, string> _requestHeaders;
|
||||
|
||||
private Dictionary<string, string> _responseHeadersCallback;
|
||||
private int _responseStatusCodeCallback;
|
||||
|
||||
private string _validatorRealm;
|
||||
private ClaimsPrincipal _validatorResponse;
|
||||
private List<(string username, string password, IPAddress ipAddr)> _validatorCallback;
|
||||
|
||||
[TestInitialize]
|
||||
public void InitializeTests()
|
||||
{
|
||||
_requestHeaders = [];
|
||||
|
||||
_responseHeadersCallback = [];
|
||||
_responseStatusCodeCallback = 0;
|
||||
|
||||
_validatorRealm = null;
|
||||
_validatorResponse = null;
|
||||
_validatorCallback = [];
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldAllowAccess()
|
||||
{
|
||||
// arrange
|
||||
string username = "user";
|
||||
string password = "pass:word";
|
||||
|
||||
_requestHeaders.Add("Authorization", $"Basic {Convert.ToBase64String(Encoding.UTF8.GetBytes($"{username}:{password}"))}");
|
||||
_validatorResponse = new ClaimsPrincipal();
|
||||
|
||||
var middleware = GetMiddleware();
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
await middleware.InvokeAsync(context);
|
||||
|
||||
// assert
|
||||
Assert.AreEqual(0, _responseStatusCodeCallback); // not triggered
|
||||
Assert.AreEqual(0, _responseHeadersCallback.Count);
|
||||
Assert.AreEqual(1, _validatorCallback.Count);
|
||||
|
||||
Assert.AreEqual(username, _validatorCallback.First().username);
|
||||
Assert.AreEqual(password, _validatorCallback.First().password);
|
||||
Assert.AreEqual(IPAddress.Loopback, _validatorCallback.First().ipAddr);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldDenyMissingHeader()
|
||||
{
|
||||
// arrange
|
||||
var middleware = GetMiddleware();
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
await middleware.InvokeAsync(context);
|
||||
|
||||
// assert
|
||||
Assert.AreEqual(401, _responseStatusCodeCallback);
|
||||
|
||||
Assert.AreEqual(0, _validatorCallback.Count);
|
||||
|
||||
Assert.AreEqual(1, _responseHeadersCallback.Count);
|
||||
Assert.AreEqual("WWW-Authenticate", _responseHeadersCallback.Keys.First());
|
||||
Assert.AreEqual("Basic", _responseHeadersCallback.Values.First());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldDenyNoResult()
|
||||
{
|
||||
// arrange
|
||||
string username = "user";
|
||||
string password = "pw";
|
||||
|
||||
_validatorRealm = "TEST";
|
||||
var remote = IPAddress.Parse("1.2.3.4");
|
||||
|
||||
_requestHeaders.Add("Authorization", $"Basic {Convert.ToBase64String(Encoding.UTF8.GetBytes($"{username}:{password}"))}");
|
||||
|
||||
var middleware = GetMiddleware();
|
||||
var context = GetContext(remote);
|
||||
|
||||
// act
|
||||
await middleware.InvokeAsync(context);
|
||||
|
||||
// assert
|
||||
Assert.AreEqual(401, _responseStatusCodeCallback);
|
||||
|
||||
Assert.AreEqual(1, _responseHeadersCallback.Count);
|
||||
Assert.AreEqual("WWW-Authenticate", _responseHeadersCallback.Keys.First());
|
||||
Assert.AreEqual($"Basic realm=\"{_validatorRealm}\"", _responseHeadersCallback.Values.First());
|
||||
|
||||
Assert.AreEqual(1, _validatorCallback.Count);
|
||||
Assert.AreEqual(username, _validatorCallback.First().username);
|
||||
Assert.AreEqual(password, _validatorCallback.First().password);
|
||||
Assert.AreEqual(remote, _validatorCallback.First().ipAddr);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldBreakOnException()
|
||||
{
|
||||
// arrange
|
||||
string username = "user";
|
||||
|
||||
_requestHeaders.Add("Authorization", $"Basic {Convert.ToBase64String(Encoding.UTF8.GetBytes($"{username}"))}");
|
||||
|
||||
var middleware = GetMiddleware();
|
||||
var context = GetContext();
|
||||
|
||||
// act
|
||||
await middleware.InvokeAsync(context);
|
||||
|
||||
// assert
|
||||
Assert.AreEqual(500, _responseStatusCodeCallback);
|
||||
}
|
||||
|
||||
private BasicAuthenticationMiddleware GetMiddleware()
|
||||
{
|
||||
var nextMock = new Mock<RequestDelegate>();
|
||||
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>()))
|
||||
.Callback<string, string, IPAddress, CancellationToken>((username, password, ipAddress, _) => _validatorCallback.Add((username, password, ipAddress)))
|
||||
.ReturnsAsync(_validatorResponse);
|
||||
|
||||
return new BasicAuthenticationMiddleware(nextMock.Object, validatorMock.Object);
|
||||
}
|
||||
|
||||
private HttpContext GetContext(IPAddress remote = null)
|
||||
{
|
||||
// Request
|
||||
var requestHeaderMock = new Mock<IHeaderDictionary>();
|
||||
foreach (var header in _requestHeaders)
|
||||
{
|
||||
var strVal = new StringValues(header.Value);
|
||||
requestHeaderMock
|
||||
.Setup(h => h.ContainsKey(header.Key))
|
||||
.Returns(true);
|
||||
requestHeaderMock
|
||||
.Setup(h => h[header.Key])
|
||||
.Returns(strVal);
|
||||
requestHeaderMock
|
||||
.Setup(h => h.TryGetValue(header.Key, out strVal))
|
||||
.Returns(true);
|
||||
}
|
||||
|
||||
var requestMock = new Mock<HttpRequest>();
|
||||
requestMock
|
||||
.Setup(r => r.Headers)
|
||||
.Returns(requestHeaderMock.Object);
|
||||
|
||||
// Response
|
||||
var responseHeaderMock = new Mock<IHeaderDictionary>();
|
||||
responseHeaderMock
|
||||
.SetupSet(h => h[It.IsAny<string>()] = It.IsAny<StringValues>())
|
||||
.Callback<string, StringValues>((key, value) => _responseHeadersCallback[key] = value);
|
||||
#pragma warning disable CS0618
|
||||
responseHeaderMock
|
||||
.SetupSet(h => h.WWWAuthenticate)
|
||||
.Callback((value) => _responseHeadersCallback[HeaderNames.WWWAuthenticate] = value);
|
||||
#pragma warning restore CS0618
|
||||
|
||||
var responseMock = new Mock<HttpResponse>();
|
||||
responseMock
|
||||
.Setup(r => r.Headers)
|
||||
.Returns(responseHeaderMock.Object);
|
||||
responseMock
|
||||
.SetupSet(r => r.StatusCode = It.IsAny<int>())
|
||||
.Callback<int>((code) => _responseStatusCodeCallback = code);
|
||||
|
||||
// Connection
|
||||
var connectionInfoMock = new Mock<ConnectionInfo>();
|
||||
connectionInfoMock
|
||||
.Setup(ci => ci.LocalIpAddress)
|
||||
.Returns(IPAddress.Loopback);
|
||||
connectionInfoMock
|
||||
.Setup(ci => ci.RemoteIpAddress)
|
||||
.Returns(remote ?? IPAddress.Loopback);
|
||||
|
||||
// Request Services
|
||||
var requestServicesMock = new Mock<IServiceProvider>();
|
||||
|
||||
var contextMock = new Mock<HttpContext>();
|
||||
contextMock
|
||||
.Setup(c => c.Request)
|
||||
.Returns(requestMock.Object);
|
||||
contextMock
|
||||
.Setup(c => c.Response)
|
||||
.Returns(responseMock.Object);
|
||||
contextMock
|
||||
.Setup(c => c.Connection)
|
||||
.Returns(connectionInfoMock.Object);
|
||||
contextMock
|
||||
.Setup(c => c.RequestServices)
|
||||
.Returns(requestServicesMock.Object);
|
||||
contextMock
|
||||
.Setup(c => c.RequestAborted)
|
||||
.Returns(CancellationToken.None);
|
||||
|
||||
return contextMock.Object;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
using System;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using AMWD.Common.AspNetCore.Security.PathProtection;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Moq;
|
||||
|
||||
namespace AMWD.Common.AspNetCore.Tests.Security.PathProtection
|
||||
{
|
||||
[TestClass]
|
||||
public class ProtectedPathMiddlewareTest
|
||||
{
|
||||
private Mock<RequestDelegate> _nextMock;
|
||||
private Mock<HttpContext> _httpContextMock;
|
||||
private Mock<IAuthorizationService> _authorizationServiceMock;
|
||||
private Mock<IAuthenticationService> _authenticationServiceMock;
|
||||
|
||||
private ProtectedPathOptions _options;
|
||||
|
||||
[TestInitialize]
|
||||
public void InitializeTest()
|
||||
{
|
||||
_options = new ProtectedPathOptions
|
||||
{
|
||||
Path = "/secure/protected",
|
||||
PolicyName = "Protection"
|
||||
};
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldValidateAccessSuccessful()
|
||||
{
|
||||
// arrange
|
||||
var middleware = GetMiddleware();
|
||||
var context = GetHttpContext(_options.Path);
|
||||
var auth = GetAuthService(true);
|
||||
|
||||
// act
|
||||
await middleware.InvokeAsync(context, auth);
|
||||
|
||||
// assert
|
||||
_authorizationServiceMock.Verify(s => s.AuthorizeAsync(It.IsAny<ClaimsPrincipal>(), It.IsAny<object>(), _options.PolicyName), Times.Once);
|
||||
_authorizationServiceMock.VerifyNoOtherCalls();
|
||||
|
||||
_authenticationServiceMock.Verify(s => s.ChallengeAsync(It.IsAny<HttpContext>(), It.IsAny<string>(), It.IsAny<AuthenticationProperties>()), Times.Never);
|
||||
_authenticationServiceMock.VerifyNoOtherCalls();
|
||||
|
||||
_nextMock.Verify(n => n.Invoke(It.IsAny<HttpContext>()), Times.Once);
|
||||
_nextMock.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldNotValidate()
|
||||
{
|
||||
// arrange
|
||||
var middleware = GetMiddleware();
|
||||
var context = GetHttpContext("/some/path");
|
||||
var auth = GetAuthService(true);
|
||||
|
||||
// act
|
||||
await middleware.InvokeAsync(context, auth);
|
||||
|
||||
// assert
|
||||
_authorizationServiceMock.Verify(s => s.AuthorizeAsync(It.IsAny<ClaimsPrincipal>(), It.IsAny<object>(), _options.PolicyName), Times.Never);
|
||||
_authorizationServiceMock.VerifyNoOtherCalls();
|
||||
|
||||
_authenticationServiceMock.Verify(s => s.ChallengeAsync(It.IsAny<HttpContext>(), It.IsAny<string>(), It.IsAny<AuthenticationProperties>()), Times.Never);
|
||||
_authenticationServiceMock.VerifyNoOtherCalls();
|
||||
|
||||
_nextMock.Verify(n => n.Invoke(It.IsAny<HttpContext>()), Times.Once);
|
||||
_nextMock.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldValidateAccessFailure()
|
||||
{
|
||||
// arrange
|
||||
var middleware = GetMiddleware();
|
||||
var context = GetHttpContext(_options.Path);
|
||||
var auth = GetAuthService(false);
|
||||
|
||||
// act
|
||||
await middleware.InvokeAsync(context, auth);
|
||||
|
||||
// assert
|
||||
_authorizationServiceMock.Verify(s => s.AuthorizeAsync(It.IsAny<ClaimsPrincipal>(), It.IsAny<object>(), _options.PolicyName), Times.Once);
|
||||
_authorizationServiceMock.VerifyNoOtherCalls();
|
||||
|
||||
_authenticationServiceMock.Verify(s => s.ChallengeAsync(It.IsAny<HttpContext>(), It.IsAny<string>(), It.IsAny<AuthenticationProperties>()), Times.Once);
|
||||
_authenticationServiceMock.VerifyNoOtherCalls();
|
||||
|
||||
_nextMock.Verify(n => n.Invoke(It.IsAny<HttpContext>()), Times.Never);
|
||||
_nextMock.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
private ProtectedPathMiddleware GetMiddleware()
|
||||
{
|
||||
_nextMock = new Mock<RequestDelegate>();
|
||||
return new ProtectedPathMiddleware(_nextMock.Object, _options);
|
||||
}
|
||||
|
||||
private HttpContext GetHttpContext(string requestPath)
|
||||
{
|
||||
var requestMock = new Mock<HttpRequest>();
|
||||
requestMock
|
||||
.Setup(r => r.Path)
|
||||
.Returns(new PathString(requestPath));
|
||||
|
||||
_authenticationServiceMock = new Mock<IAuthenticationService>();
|
||||
|
||||
var requestServicesMock = new Mock<IServiceProvider>();
|
||||
requestServicesMock
|
||||
.Setup(s => s.GetService(typeof(IAuthenticationService)))
|
||||
.Returns(_authenticationServiceMock.Object);
|
||||
|
||||
_httpContextMock = new Mock<HttpContext>();
|
||||
_httpContextMock
|
||||
.Setup(c => c.Request)
|
||||
.Returns(requestMock.Object);
|
||||
_httpContextMock
|
||||
.Setup(c => c.RequestServices)
|
||||
.Returns(requestServicesMock.Object);
|
||||
|
||||
return _httpContextMock.Object;
|
||||
}
|
||||
|
||||
private IAuthorizationService GetAuthService(bool success)
|
||||
{
|
||||
_authorizationServiceMock = new Mock<IAuthorizationService>();
|
||||
_authorizationServiceMock
|
||||
.Setup(service => service.AuthorizeAsync(It.IsAny<ClaimsPrincipal>(), It.IsAny<object>(), It.IsAny<string>()))
|
||||
.ReturnsAsync(() => success ? AuthorizationResult.Success() : AuthorizationResult.Failed());
|
||||
|
||||
return _authorizationServiceMock.Object;
|
||||
}
|
||||
}
|
||||
}
|
||||
153
test/AMWD.Common.AspNetCore.Tests/Utilities/HtmlHelperTest.cs
Normal file
153
test/AMWD.Common.AspNetCore.Tests/Utilities/HtmlHelperTest.cs
Normal file
@@ -0,0 +1,153 @@
|
||||
using System;
|
||||
using AMWD.Common.AspNetCore.Utilities;
|
||||
|
||||
namespace AMWD.Common.AspNetCore.Tests.Utilities
|
||||
{
|
||||
[TestClass]
|
||||
public class HtmlHelperTest
|
||||
{
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentNullException))]
|
||||
public void ShouldThrowErrorOnEmptyColor()
|
||||
{
|
||||
// arrange
|
||||
|
||||
// act
|
||||
HtmlHelper.IsDarkColor("");
|
||||
|
||||
// assert
|
||||
// exception thrown
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(NotSupportedException))]
|
||||
public void ShouldThrowErrorOnUnsupportedColor()
|
||||
{
|
||||
// arrange
|
||||
|
||||
// act
|
||||
HtmlHelper.IsDarkColor("hsv(1, 2, 3)");
|
||||
|
||||
// assert
|
||||
// exception thrown
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow("rgb(255, 255, 255)")]
|
||||
[DataRow("rgba(255, 255, 255, .5)")]
|
||||
public void ShouldReturnLightColorForWhiteRgb(string white)
|
||||
{
|
||||
// arrange
|
||||
|
||||
// act
|
||||
bool isDark = HtmlHelper.IsDarkColor(white);
|
||||
|
||||
// assert
|
||||
Assert.IsFalse(isDark);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow("#ffFFff")]
|
||||
[DataRow("FFffFF")]
|
||||
public void ShouldReturnLightColorForWhiteFullHex(string white)
|
||||
{
|
||||
// arrange
|
||||
|
||||
// act
|
||||
bool isDark = HtmlHelper.IsDarkColor(white);
|
||||
|
||||
// assert
|
||||
Assert.IsFalse(isDark);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow("#fFf")]
|
||||
[DataRow("FfF")]
|
||||
public void ShouldReturnLightColorForWhiteShortHex(string white)
|
||||
{
|
||||
// arrange
|
||||
|
||||
// act
|
||||
bool isDark = HtmlHelper.IsDarkColor(white);
|
||||
|
||||
// assert
|
||||
Assert.IsFalse(isDark);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow("rgb(0, 0, 0)")]
|
||||
[DataRow("rgba(0, 0, 0, .5)")]
|
||||
public void ShouldReturnDarkColorForBlackRgb(string black)
|
||||
{
|
||||
// arrange
|
||||
|
||||
// act
|
||||
bool isDark = HtmlHelper.IsDarkColor(black);
|
||||
|
||||
// assert
|
||||
Assert.IsTrue(isDark);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow("#000000")]
|
||||
[DataRow("000000")]
|
||||
public void ShouldReturnDarkColorForBlackFullHex(string black)
|
||||
{
|
||||
// arrange
|
||||
|
||||
// act
|
||||
bool isDark = HtmlHelper.IsDarkColor(black);
|
||||
|
||||
// assert
|
||||
Assert.IsTrue(isDark);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow("#000")]
|
||||
[DataRow("000")]
|
||||
public void ShouldReturnDarkColorForBlackShortHex(string black)
|
||||
{
|
||||
// arrange
|
||||
|
||||
// act
|
||||
bool isDark = HtmlHelper.IsDarkColor(black);
|
||||
|
||||
// assert
|
||||
Assert.IsTrue(isDark);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow("rgb(255, 88, 0)")]
|
||||
[DataRow("rgb(0, 218, 0)")]
|
||||
[DataRow("rgb(0, 168, 255)")]
|
||||
[DataRow("rgb(255, 38, 255)")]
|
||||
[DataRow("rgb(128, 128, 128)")]
|
||||
public void ShouldReturnLightColorForBorderColors(string color)
|
||||
{
|
||||
// arrange
|
||||
|
||||
// act
|
||||
bool isDark = HtmlHelper.IsDarkColor(color);
|
||||
|
||||
// assert
|
||||
Assert.IsFalse(isDark);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow("rgb(253, 88, 0)")]
|
||||
[DataRow("rgb(0, 217, 0)")]
|
||||
[DataRow("rgb(0, 168, 253)")]
|
||||
[DataRow("rgb(254, 38, 254)")]
|
||||
[DataRow("rgb(127, 127, 127)")]
|
||||
public void ShouldReturnDarkColorForBorderColors(string color)
|
||||
{
|
||||
// arrange
|
||||
|
||||
// act
|
||||
bool isDark = HtmlHelper.IsDarkColor(color);
|
||||
|
||||
// assert
|
||||
Assert.IsTrue(isDark);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace AMWD.Common.AspNetCore.Tests.Utilities
|
||||
{
|
||||
[TestClass]
|
||||
public class PasswordHelperTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void ShouldReturnNullHashWhenNullProvided()
|
||||
{
|
||||
// arrange
|
||||
string password = null;
|
||||
|
||||
// act
|
||||
string hash = PasswordHelper.HashPassword(password);
|
||||
|
||||
// assert
|
||||
Assert.IsNull(hash);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnEmptyHashWhenSpacesProvided()
|
||||
{
|
||||
// arrange
|
||||
string password = " ";
|
||||
|
||||
// act
|
||||
string hash = PasswordHelper.HashPassword(password);
|
||||
|
||||
// assert
|
||||
Assert.AreEqual("", hash);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnHashWhenTextProvided()
|
||||
{
|
||||
// arrange
|
||||
string password = "password";
|
||||
|
||||
// act
|
||||
string hash = PasswordHelper.HashPassword(password);
|
||||
|
||||
// assert
|
||||
Assert.IsTrue(!string.IsNullOrWhiteSpace(hash));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnFalseOnNullPassword()
|
||||
{
|
||||
// arrange
|
||||
string password = null;
|
||||
string hash = PasswordHelper.HashPassword(password);
|
||||
|
||||
// act
|
||||
bool isValid = PasswordHelper.VerifyPassword(password, hash, out bool rehashNeeded);
|
||||
|
||||
// assert
|
||||
Assert.IsFalse(isValid);
|
||||
Assert.IsFalse(rehashNeeded);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnFalseOnEmptyPassword()
|
||||
{
|
||||
// arrange
|
||||
string password = " ";
|
||||
string hash = PasswordHelper.HashPassword(password);
|
||||
|
||||
// act
|
||||
bool isValid = PasswordHelper.VerifyPassword(password, hash, out bool rehashNeeded);
|
||||
|
||||
// assert
|
||||
Assert.IsFalse(isValid);
|
||||
Assert.IsFalse(rehashNeeded);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnFalseOnNullHash()
|
||||
{
|
||||
// arrange
|
||||
string password = "password";
|
||||
string hash = null;
|
||||
|
||||
// act
|
||||
bool isValid = PasswordHelper.VerifyPassword(password, hash, out bool rehashNeeded);
|
||||
|
||||
// assert
|
||||
Assert.IsFalse(isValid);
|
||||
Assert.IsFalse(rehashNeeded);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnFalseOnEmptyHash()
|
||||
{
|
||||
// arrange
|
||||
string password = "password";
|
||||
string hash = "";
|
||||
|
||||
// act
|
||||
bool isValid = PasswordHelper.VerifyPassword(password, hash, out bool rehashNeeded);
|
||||
|
||||
// assert
|
||||
Assert.IsFalse(isValid);
|
||||
Assert.IsFalse(rehashNeeded);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnTrueOnSuccess()
|
||||
{
|
||||
// arrange
|
||||
string password = "password";
|
||||
string hash = PasswordHelper.HashPassword(password);
|
||||
|
||||
// act
|
||||
bool isValid = PasswordHelper.VerifyPassword(password, hash, out bool rehashNeeded);
|
||||
|
||||
// assert
|
||||
Assert.IsTrue(isValid);
|
||||
Assert.IsFalse(rehashNeeded);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnFalseOnError()
|
||||
{
|
||||
// arrange
|
||||
string password = "pass";
|
||||
string hash = PasswordHelper.HashPassword(password + "word");
|
||||
|
||||
// act
|
||||
bool isValid = PasswordHelper.VerifyPassword(password, hash, out bool rehashNeeded);
|
||||
|
||||
// assert
|
||||
Assert.IsFalse(isValid);
|
||||
Assert.IsFalse(rehashNeeded);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnTrueWithRehash()
|
||||
{
|
||||
// arrange
|
||||
var devHasher = new PasswordHasher<object>();
|
||||
var field = devHasher.GetType().GetField("_compatibilityMode", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
field.SetValue(devHasher, PasswordHasherCompatibilityMode.IdentityV2);
|
||||
|
||||
string password = "password";
|
||||
string hash = devHasher.HashPassword(null, password);
|
||||
|
||||
// act
|
||||
bool isValid = PasswordHelper.VerifyPassword(password, hash, out bool rehashNeeded);
|
||||
|
||||
// assert
|
||||
Assert.IsTrue(isValid);
|
||||
Assert.IsTrue(rehashNeeded);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user