Merge branch 'main' into cmd
This commit is contained in:
@@ -4,7 +4,7 @@ using System.Net.Http.Headers;
|
|||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using AMWD.Common.AspNetCore.BasicAuthentication;
|
using AMWD.Common.AspNetCore.Security.BasicAuthentication;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Mvc.Filters;
|
using Microsoft.AspNetCore.Mvc.Filters;
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ namespace Microsoft.AspNetCore.Mvc.Filters
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Implements an IP filter. Only defined addresses are allowed to access.
|
/// Implements an IP filter. Only defined addresses are allowed to access.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class IPWhitelistAttribute : ActionFilterAttribute
|
public class IPAllowListAttribute : ActionFilterAttribute
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether local (localhost) access is granted (Default: true).
|
/// Gets or sets a value indicating whether local (localhost) access is granted (Default: true).
|
||||||
@@ -10,12 +10,12 @@ namespace Microsoft.AspNetCore.Mvc.Filters
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Implements an IP filter. The defined addresses are blocked.
|
/// Implements an IP filter. The defined addresses are blocked.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class IPBlacklistAttribute : ActionFilterAttribute
|
public class IPBlockListAttribute : ActionFilterAttribute
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether local (localhost) access is blocked (Default: false).
|
/// Gets or sets a value indicating whether local (localhost) access is blocked (Default: false).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool RestrictLocalAccess { get; set; }
|
public bool BlockLocalAccess { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a configuration key where the blocked IP addresses are defined.
|
/// Gets or sets a configuration key where the blocked IP addresses are defined.
|
||||||
@@ -35,7 +35,7 @@ namespace Microsoft.AspNetCore.Mvc.Filters
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a comma separated list of blocked IP addresses.
|
/// Gets or sets a comma separated list of blocked IP addresses.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string RestrictedIpAddresses { get; set; }
|
public string BlockedIpAddresses { get; set; }
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override void OnActionExecuting(ActionExecutingContext context)
|
public override void OnActionExecuting(ActionExecutingContext context)
|
||||||
@@ -43,13 +43,13 @@ namespace Microsoft.AspNetCore.Mvc.Filters
|
|||||||
base.OnActionExecuting(context);
|
base.OnActionExecuting(context);
|
||||||
context.HttpContext.Items["RemoteAddress"] = context.HttpContext.GetRemoteIpAddress();
|
context.HttpContext.Items["RemoteAddress"] = context.HttpContext.GetRemoteIpAddress();
|
||||||
|
|
||||||
if (!RestrictLocalAccess && context.HttpContext.IsLocalRequest())
|
if (!BlockLocalAccess && context.HttpContext.IsLocalRequest())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var remoteIpAddress = context.HttpContext.GetRemoteIpAddress();
|
var remoteIpAddress = context.HttpContext.GetRemoteIpAddress();
|
||||||
if (!string.IsNullOrWhiteSpace(RestrictedIpAddresses))
|
if (!string.IsNullOrWhiteSpace(BlockedIpAddresses))
|
||||||
{
|
{
|
||||||
string[] ipAddresses = RestrictedIpAddresses.Split(',');
|
string[] ipAddresses = BlockedIpAddresses.Split(',');
|
||||||
foreach (string ipAddress in ipAddresses)
|
foreach (string ipAddress in ipAddresses)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(ipAddress))
|
if (string.IsNullOrWhiteSpace(ipAddress))
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Net;
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
using Microsoft.AspNetCore.Antiforgery;
|
using Microsoft.AspNetCore.Antiforgery;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
@@ -9,30 +10,57 @@ namespace Microsoft.AspNetCore.Http
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static class HttpContextExtensions
|
public static class HttpContextExtensions
|
||||||
{
|
{
|
||||||
|
// Search these additional headers for a remote client ip address.
|
||||||
|
private static readonly string[] defaultIpHeaderNames = new[]
|
||||||
|
{
|
||||||
|
"X-Forwarded-For", // commonly used on all known proxies
|
||||||
|
"X-Real-IP", // wide-spread alternative to X-Forwarded-For
|
||||||
|
"CF-Connecting-IP" // set by Cloudflare
|
||||||
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves the antiforgery token.
|
/// Retrieves the antiforgery token.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="httpContext">The current <see cref="HttpContext"/>.</param>
|
/// <param name="httpContext">The current <see cref="HttpContext"/>.</param>
|
||||||
/// <returns>Name and value of the token.</returns>
|
/// <returns>FormName, HeaderName and Value of the antiforgery token.</returns>
|
||||||
public static (string Name, string Value) GetAntiforgeryToken(this HttpContext httpContext)
|
public static (string FormName, string HeaderName, string Value) GetAntiforgeryToken(this HttpContext httpContext)
|
||||||
{
|
{
|
||||||
var af = httpContext.RequestServices.GetService<IAntiforgery>();
|
var antiforgery = httpContext.RequestServices.GetService<IAntiforgery>();
|
||||||
var set = af?.GetAndStoreTokens(httpContext);
|
var tokenSet = antiforgery?.GetAndStoreTokens(httpContext);
|
||||||
|
|
||||||
return (Name: set?.FormFieldName, Value: set?.RequestToken);
|
return (tokenSet?.FormFieldName, tokenSet?.HeaderName, tokenSet?.RequestToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the remote ip address.
|
/// Returns the remote ip address.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Searches for additional headers in the following order:
|
||||||
|
/// <list type="number">
|
||||||
|
/// <item>X-Forwarded-For</item>
|
||||||
|
/// <item>X-Real-IP</item>
|
||||||
|
/// <item>CF-Connecting-IP</item>
|
||||||
|
/// </list>
|
||||||
|
/// </remarks>
|
||||||
/// <param name="httpContext">The current <see cref="HttpContext"/>.</param>
|
/// <param name="httpContext">The current <see cref="HttpContext"/>.</param>
|
||||||
/// <param name="headerName">The name of the header to resolve the <see cref="IPAddress"/> when behind a proxy (Default: X-Forwarded-For).</param>
|
/// <param name="ipHeaderName">The name of the header to resolve the <see cref="IPAddress"/> when behind a proxy.</param>
|
||||||
/// <returns>The ip address of the client.</returns>
|
/// <returns>The ip address of the client.</returns>
|
||||||
public static IPAddress GetRemoteIpAddress(this HttpContext httpContext, string headerName = "X-Forwarded-For")
|
public static IPAddress GetRemoteIpAddress(this HttpContext httpContext, string ipHeaderName = null)
|
||||||
{
|
{
|
||||||
string forwardedHeader = httpContext.Request.Headers[headerName].ToString();
|
string forwardedForAddress = null;
|
||||||
if (!string.IsNullOrWhiteSpace(forwardedHeader) && IPAddress.TryParse(forwardedHeader, out var forwarded))
|
|
||||||
return forwarded;
|
var headerNames = string.IsNullOrWhiteSpace(ipHeaderName) ? defaultIpHeaderNames : new[] { ipHeaderName }.Concat(defaultIpHeaderNames);
|
||||||
|
foreach (string headerName in headerNames)
|
||||||
|
{
|
||||||
|
if (!httpContext.Request.Headers.ContainsKey(headerName))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
forwardedForAddress = httpContext.Request.Headers[headerName].ToString();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(forwardedForAddress) && IPAddress.TryParse(forwardedForAddress, out var remoteAddress))
|
||||||
|
return remoteAddress;
|
||||||
|
|
||||||
return httpContext.Connection.RemoteIpAddress;
|
return httpContext.Connection.RemoteIpAddress;
|
||||||
}
|
}
|
||||||
@@ -40,12 +68,20 @@ namespace Microsoft.AspNetCore.Http
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns whether the request was made locally.
|
/// Returns whether the request was made locally.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Searches for additional headers in the following order:
|
||||||
|
/// <list type="number">
|
||||||
|
/// <item>X-Forwarded-For</item>
|
||||||
|
/// <item>X-Real-IP</item>
|
||||||
|
/// <item>CF-Connecting-IP</item>
|
||||||
|
/// </list>
|
||||||
|
/// </remarks>
|
||||||
/// <param name="httpContext">The current <see cref="HttpContext"/>.</param>
|
/// <param name="httpContext">The current <see cref="HttpContext"/>.</param>
|
||||||
/// <param name="headerName">The name of the header to resolve the <see cref="IPAddress"/> when behind a proxy (Default: X-Forwarded-For).</param>
|
/// <param name="ipHeaderName">The name of the header to resolve the <see cref="IPAddress"/> when behind a proxy.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static bool IsLocalRequest(this HttpContext httpContext, string headerName = "X-Forwarded-For")
|
public static bool IsLocalRequest(this HttpContext httpContext, string ipHeaderName = null)
|
||||||
{
|
{
|
||||||
var remoteIpAddress = httpContext.GetRemoteIpAddress(headerName);
|
var remoteIpAddress = httpContext.GetRemoteIpAddress(ipHeaderName);
|
||||||
return httpContext.Connection.LocalIpAddress.Equals(remoteIpAddress);
|
return httpContext.Connection.LocalIpAddress.Equals(remoteIpAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ using Microsoft.AspNetCore.Http;
|
|||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
namespace AMWD.Common.AspNetCore.BasicAuthentication
|
namespace AMWD.Common.AspNetCore.Security.BasicAuthentication
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Implements the <see cref="AuthenticationHandler{TOptions}"/> for Basic Authentication.
|
/// Implements the <see cref="AuthenticationHandler{TOptions}"/> for Basic Authentication.
|
||||||
@@ -6,7 +6,7 @@ using System.Threading.Tasks;
|
|||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace AMWD.Common.AspNetCore.BasicAuthentication
|
namespace AMWD.Common.AspNetCore.Security.BasicAuthentication
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Implements a basic authentication.
|
/// Implements a basic authentication.
|
||||||
@@ -3,7 +3,7 @@ using System.Security.Claims;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace AMWD.Common.AspNetCore.BasicAuthentication
|
namespace AMWD.Common.AspNetCore.Security.BasicAuthentication
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Interface representing the validation of a basic authentication.
|
/// Interface representing the validation of a basic authentication.
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
|
||||||
|
namespace AMWD.Common.AspNetCore.Security.PathProtection
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Extnsion for <see cref="IApplicationBuilder"/> to enable folder protection.
|
||||||
|
/// </summary>
|
||||||
|
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
|
||||||
|
public static class ProtectedPathExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provide protected paths even for static files.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="app">The <see cref="IApplicationBuilder"/>.</param>
|
||||||
|
/// <param name="options">The <see cref="ProtectedPathOptions"/> with path and policy name.</param>
|
||||||
|
public static IApplicationBuilder UseProtectedPath(this IApplicationBuilder app, ProtectedPathOptions options)
|
||||||
|
=> app.UseMiddleware<ProtectedPathMiddleware>(options);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Authentication;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
|
||||||
|
namespace AMWD.Common.AspNetCore.Security.PathProtection
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Implements a check to provide protected paths.
|
||||||
|
/// </summary>
|
||||||
|
public class ProtectedPathMiddleware
|
||||||
|
{
|
||||||
|
private readonly RequestDelegate next;
|
||||||
|
private readonly PathString path;
|
||||||
|
private readonly string policyName;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ProtectedPathExtensions"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="next">The following delegate in the process chain.</param>
|
||||||
|
/// <param name="options">The options to configure the middleware.</param>
|
||||||
|
public ProtectedPathMiddleware(RequestDelegate next, ProtectedPathOptions options)
|
||||||
|
{
|
||||||
|
this.next = next;
|
||||||
|
path = options.Path;
|
||||||
|
policyName = options.PolicyName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The delegate invokation.
|
||||||
|
/// Performs the protection check.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="httpContext">The corresponding HTTP context.</param>
|
||||||
|
/// <param name="authorizationService">The <see cref="IAuthorizationService"/>.</param>
|
||||||
|
/// <returns>An awaitable task.</returns>
|
||||||
|
public async Task InvokeAsync(HttpContext httpContext, IAuthorizationService authorizationService)
|
||||||
|
{
|
||||||
|
if (httpContext.Request.Path.StartsWithSegments(path))
|
||||||
|
{
|
||||||
|
var result = await authorizationService.AuthorizeAsync(httpContext.User, null, policyName);
|
||||||
|
if (!result.Succeeded)
|
||||||
|
{
|
||||||
|
await httpContext.ChallengeAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await next.Invoke(httpContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
|
||||||
|
namespace AMWD.Common.AspNetCore.Security.PathProtection
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Options to define which folder should be protected.
|
||||||
|
/// </summary>
|
||||||
|
public class ProtectedPathOptions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the path to the protected folder.
|
||||||
|
/// </summary>
|
||||||
|
public PathString Path { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the policy name to use.
|
||||||
|
/// </summary>
|
||||||
|
public string PolicyName { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -146,13 +146,26 @@ namespace Newtonsoft.Json
|
|||||||
if (lvlObj == null)
|
if (lvlObj == null)
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
|
|
||||||
lvlObj = lvlObj[level];
|
string lvl = level;
|
||||||
|
if (lvlObj.Type == JTokenType.Object)
|
||||||
|
{
|
||||||
|
foreach (var prop in ((JObject)lvlObj).Properties())
|
||||||
|
{
|
||||||
|
if (prop.Name.Equals(lvl, System.StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
lvl = prop.Name;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lvlObj = lvlObj[lvl];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lvlObj == null)
|
if (lvlObj == null)
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
|
|
||||||
return DeepConvert.ChangeType<T>(lvlObj is JValue ? ((JValue)lvlObj).Value : lvlObj.Value<object>());
|
return DeepConvert.ChangeType<T>(lvlObj is JValue lvlValue ? lvlValue.Value : lvlObj.Value<object>());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
39
CHANGELOG.md
39
CHANGELOG.md
@@ -5,7 +5,12 @@ All notable changes to this project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## [Upcoming](https://git.am-wd.de/AM.WD/common/compare/v1.11.0...main) - 0000-00-00
|
## [Upcoming](https://git.am-wd.de/AM.WD/common/compare/v2.0.0...main) - 0000-00-00
|
||||||
|
|
||||||
|
_no changes yet_
|
||||||
|
|
||||||
|
|
||||||
|
## [v2.0.0](https://git.am-wd.de/AM.WD/common/compare/v1.13.0...v2.0.0) - 2023-08-08
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
@@ -16,6 +21,38 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Support for .NET Core 3.1
|
- Support for .NET Core 3.1
|
||||||
|
|
||||||
|
|
||||||
|
## [v1.13.0](https://git.am-wd.de/AM.WD/common/compare/v1.12.0...v1.13.0) - 2023-06-27
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- `ProtectedPathMiddleware` to secure even static file paths
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Moved `BasicAuthentication`* into sub-namespace `Security`
|
||||||
|
|
||||||
|
|
||||||
|
## [v1.12.0](https://git.am-wd.de/AM.WD/common/compare/v1.11.1...v1.12.0) - 2023-06-01
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Renamed `IPBlacklistAttribute` to `IPBlockListAttribute`
|
||||||
|
- Renamed `IPWhitelistAttribute` to `IPAllowListAttribute`
|
||||||
|
- `HttpContextExtensions`
|
||||||
|
- `GetAntiforgeryToken()` now returns the header name also
|
||||||
|
- `GetRemoteIpAddress()` checks following additional headers by default:
|
||||||
|
- `X-Forwarded-For`
|
||||||
|
- `X-Real-IP`
|
||||||
|
- `CF-Connecting-IP`
|
||||||
|
|
||||||
|
|
||||||
|
## [v1.11.1](https://git.am-wd.de/AM.WD/common/compare/v1.11.0...v1.11.1) - 2023-05-11
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- `JsonExtensions.GetValue<T>()` now is case insensitive and detects the correct property name of a `JObject`.
|
||||||
|
|
||||||
|
|
||||||
## [v1.11.0](https://git.am-wd.de/AM.WD/common/compare/v1.10.0...v1.11.0) - 2023-03-29
|
## [v1.11.0](https://git.am-wd.de/AM.WD/common/compare/v1.10.0...v1.11.0) - 2023-03-29
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@@ -6,8 +6,4 @@
|
|||||||
<Copy SourceFiles="$(MSBuildProjectDirectory)\bin\$(Configuration)\$(PackageId).$(PackageVersion).nupkg" DestinationFolder="D:\NuGetLocal" />
|
<Copy SourceFiles="$(MSBuildProjectDirectory)\bin\$(Configuration)\$(PackageId).$(PackageVersion).nupkg" DestinationFolder="D:\NuGetLocal" />
|
||||||
<Copy SourceFiles="$(MSBuildProjectDirectory)\bin\$(Configuration)\$(PackageId).$(PackageVersion).snupkg" DestinationFolder="D:\NuGetLocal" />
|
<Copy SourceFiles="$(MSBuildProjectDirectory)\bin\$(Configuration)\$(PackageId).$(PackageVersion).snupkg" DestinationFolder="D:\NuGetLocal" />
|
||||||
</Target>
|
</Target>
|
||||||
|
|
||||||
<Target Condition="'$(Configuration)' == 'DebugLocal'" Name="PushToLocalFeed" AfterTargets="Pack">
|
|
||||||
<Exec Command="dotnet nuget push -s https://nuget.syshorst.de/v3/index.json $(MSBuildProjectDirectory)\bin\$(Configuration)\$(PackageId).$(PackageVersion).nupkg" />
|
|
||||||
</Target>
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ using System.Security.Claims;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using AMWD.Common.AspNetCore.BasicAuthentication;
|
using AMWD.Common.AspNetCore.Security.BasicAuthentication;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ namespace UnitTests.AspNetCore.Attributes
|
|||||||
{
|
{
|
||||||
[TestClass]
|
[TestClass]
|
||||||
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
|
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
|
||||||
public class IPWhitelistAttributeTests
|
public class IPAllowListAttributeTests
|
||||||
{
|
{
|
||||||
private Dictionary<string, string> requestHeaders;
|
private Dictionary<string, string> requestHeaders;
|
||||||
private Dictionary<object, object> itemsCallback;
|
private Dictionary<object, object> itemsCallback;
|
||||||
@@ -37,7 +37,7 @@ namespace UnitTests.AspNetCore.Attributes
|
|||||||
{
|
{
|
||||||
// arrange
|
// arrange
|
||||||
var remote = IPAddress.Parse("192.168.178.1");
|
var remote = IPAddress.Parse("192.168.178.1");
|
||||||
var attribute = new IPWhitelistAttribute();
|
var attribute = new IPAllowListAttribute();
|
||||||
var context = GetContext(remote);
|
var context = GetContext(remote);
|
||||||
|
|
||||||
// act
|
// act
|
||||||
@@ -57,7 +57,7 @@ namespace UnitTests.AspNetCore.Attributes
|
|||||||
{
|
{
|
||||||
// arrange
|
// arrange
|
||||||
var remote = IPAddress.Parse("192.168.178.1");
|
var remote = IPAddress.Parse("192.168.178.1");
|
||||||
var attribute = new IPWhitelistAttribute
|
var attribute = new IPAllowListAttribute
|
||||||
{
|
{
|
||||||
AllowedIpAddresses = "192.168.178:1"
|
AllowedIpAddresses = "192.168.178:1"
|
||||||
};
|
};
|
||||||
@@ -79,7 +79,7 @@ namespace UnitTests.AspNetCore.Attributes
|
|||||||
public void ShouldAllowLocalAccess()
|
public void ShouldAllowLocalAccess()
|
||||||
{
|
{
|
||||||
// arrange
|
// arrange
|
||||||
var attribute = new IPWhitelistAttribute();
|
var attribute = new IPAllowListAttribute();
|
||||||
var context = GetContext();
|
var context = GetContext();
|
||||||
|
|
||||||
// act
|
// act
|
||||||
@@ -95,7 +95,7 @@ namespace UnitTests.AspNetCore.Attributes
|
|||||||
public void ShouldDenyLocalAccess()
|
public void ShouldDenyLocalAccess()
|
||||||
{
|
{
|
||||||
// arrange
|
// arrange
|
||||||
var attribute = new IPWhitelistAttribute
|
var attribute = new IPAllowListAttribute
|
||||||
{
|
{
|
||||||
AllowLocalAccess = false
|
AllowLocalAccess = false
|
||||||
};
|
};
|
||||||
@@ -120,7 +120,7 @@ namespace UnitTests.AspNetCore.Attributes
|
|||||||
{
|
{
|
||||||
// arrange
|
// arrange
|
||||||
var remote = IPAddress.Parse(address);
|
var remote = IPAddress.Parse(address);
|
||||||
var attribute = new IPWhitelistAttribute
|
var attribute = new IPAllowListAttribute
|
||||||
{
|
{
|
||||||
AllowLocalAccess = false,
|
AllowLocalAccess = false,
|
||||||
AllowedIpAddresses = ",127.0.0.0/8,192.168.178.10"
|
AllowedIpAddresses = ",127.0.0.0/8,192.168.178.10"
|
||||||
@@ -154,7 +154,7 @@ namespace UnitTests.AspNetCore.Attributes
|
|||||||
configExists = true;
|
configExists = true;
|
||||||
allowedIpsConfig.Add("127.0.0.0/8");
|
allowedIpsConfig.Add("127.0.0.0/8");
|
||||||
allowedIpsConfig.Add("192.168.178.10");
|
allowedIpsConfig.Add("192.168.178.10");
|
||||||
var attribute = new IPWhitelistAttribute
|
var attribute = new IPAllowListAttribute
|
||||||
{
|
{
|
||||||
AllowLocalAccess = true,
|
AllowLocalAccess = true,
|
||||||
ConfigurationKey = configKey
|
ConfigurationKey = configKey
|
||||||
@@ -178,7 +178,7 @@ namespace UnitTests.AspNetCore.Attributes
|
|||||||
configExists = true;
|
configExists = true;
|
||||||
allowedIpsConfig.Add("");
|
allowedIpsConfig.Add("");
|
||||||
allowedIpsConfig.Add("192.168.178.10");
|
allowedIpsConfig.Add("192.168.178.10");
|
||||||
var attribute = new IPWhitelistAttribute
|
var attribute = new IPAllowListAttribute
|
||||||
{
|
{
|
||||||
AllowLocalAccess = false,
|
AllowLocalAccess = false,
|
||||||
ConfigurationKey = configKey
|
ConfigurationKey = configKey
|
||||||
@@ -206,7 +206,7 @@ namespace UnitTests.AspNetCore.Attributes
|
|||||||
configKey = "White:List";
|
configKey = "White:List";
|
||||||
configExists = true;
|
configExists = true;
|
||||||
allowedIpsConfig.Add("192.168.178.10");
|
allowedIpsConfig.Add("192.168.178.10");
|
||||||
var attribute = new IPWhitelistAttribute
|
var attribute = new IPAllowListAttribute
|
||||||
{
|
{
|
||||||
AllowLocalAccess = false,
|
AllowLocalAccess = false,
|
||||||
ConfigurationKey = configKey
|
ConfigurationKey = configKey
|
||||||
@@ -239,7 +239,7 @@ namespace UnitTests.AspNetCore.Attributes
|
|||||||
// arrange
|
// arrange
|
||||||
configKey = "White:List";
|
configKey = "White:List";
|
||||||
configExists = false;
|
configExists = false;
|
||||||
var attribute = new IPWhitelistAttribute
|
var attribute = new IPAllowListAttribute
|
||||||
{
|
{
|
||||||
AllowLocalAccess = false,
|
AllowLocalAccess = false,
|
||||||
ConfigurationKey = configKey
|
ConfigurationKey = configKey
|
||||||
@@ -14,7 +14,7 @@ namespace UnitTests.AspNetCore.Attributes
|
|||||||
{
|
{
|
||||||
[TestClass]
|
[TestClass]
|
||||||
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
|
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
|
||||||
public class IPBlacklistAttributeTests
|
public class IPBlockListAttributeTests
|
||||||
{
|
{
|
||||||
private Dictionary<string, string> requestHeaders;
|
private Dictionary<string, string> requestHeaders;
|
||||||
private Dictionary<object, object> itemsCallback;
|
private Dictionary<object, object> itemsCallback;
|
||||||
@@ -37,7 +37,7 @@ namespace UnitTests.AspNetCore.Attributes
|
|||||||
{
|
{
|
||||||
// arrange
|
// arrange
|
||||||
var remote = IPAddress.Parse("192.168.178.1");
|
var remote = IPAddress.Parse("192.168.178.1");
|
||||||
var attribute = new IPBlacklistAttribute();
|
var attribute = new IPBlockListAttribute();
|
||||||
var context = GetContext(remote);
|
var context = GetContext(remote);
|
||||||
|
|
||||||
// act
|
// act
|
||||||
@@ -54,9 +54,9 @@ namespace UnitTests.AspNetCore.Attributes
|
|||||||
{
|
{
|
||||||
// arrange
|
// arrange
|
||||||
var remote = IPAddress.Parse("192.168.178.1");
|
var remote = IPAddress.Parse("192.168.178.1");
|
||||||
var attribute = new IPBlacklistAttribute
|
var attribute = new IPBlockListAttribute
|
||||||
{
|
{
|
||||||
RestrictedIpAddresses = "192.168.178:1"
|
BlockedIpAddresses = "192.168.178:1"
|
||||||
};
|
};
|
||||||
var context = GetContext(remote);
|
var context = GetContext(remote);
|
||||||
|
|
||||||
@@ -73,10 +73,10 @@ namespace UnitTests.AspNetCore.Attributes
|
|||||||
public void ShouldAllowLocalAccess()
|
public void ShouldAllowLocalAccess()
|
||||||
{
|
{
|
||||||
// arrange
|
// arrange
|
||||||
var attribute = new IPBlacklistAttribute
|
var attribute = new IPBlockListAttribute
|
||||||
{
|
{
|
||||||
RestrictLocalAccess = false,
|
BlockLocalAccess = false,
|
||||||
RestrictedIpAddresses = "127.0.0.0/8"
|
BlockedIpAddresses = "127.0.0.0/8"
|
||||||
};
|
};
|
||||||
var context = GetContext();
|
var context = GetContext();
|
||||||
|
|
||||||
@@ -93,10 +93,10 @@ namespace UnitTests.AspNetCore.Attributes
|
|||||||
public void ShouldBlockLocalAccess()
|
public void ShouldBlockLocalAccess()
|
||||||
{
|
{
|
||||||
// arrange
|
// arrange
|
||||||
var attribute = new IPBlacklistAttribute
|
var attribute = new IPBlockListAttribute
|
||||||
{
|
{
|
||||||
RestrictLocalAccess = true,
|
BlockLocalAccess = true,
|
||||||
RestrictedIpAddresses = ",127.0.0.0/8"
|
BlockedIpAddresses = ",127.0.0.0/8"
|
||||||
};
|
};
|
||||||
var context = GetContext();
|
var context = GetContext();
|
||||||
|
|
||||||
@@ -119,10 +119,10 @@ namespace UnitTests.AspNetCore.Attributes
|
|||||||
{
|
{
|
||||||
// arrange
|
// arrange
|
||||||
var remote = IPAddress.Parse(address);
|
var remote = IPAddress.Parse(address);
|
||||||
var attribute = new IPBlacklistAttribute
|
var attribute = new IPBlockListAttribute
|
||||||
{
|
{
|
||||||
RestrictLocalAccess = true,
|
BlockLocalAccess = true,
|
||||||
RestrictedIpAddresses = "127.0.0.0/8,192.168.178.10"
|
BlockedIpAddresses = "127.0.0.0/8,192.168.178.10"
|
||||||
};
|
};
|
||||||
var context = GetContext(remote);
|
var context = GetContext(remote);
|
||||||
|
|
||||||
@@ -153,9 +153,9 @@ namespace UnitTests.AspNetCore.Attributes
|
|||||||
configExists = true;
|
configExists = true;
|
||||||
restrictedIpsConfig.Add("127.0.0.0/8");
|
restrictedIpsConfig.Add("127.0.0.0/8");
|
||||||
restrictedIpsConfig.Add("192.168.178.10");
|
restrictedIpsConfig.Add("192.168.178.10");
|
||||||
var attribute = new IPBlacklistAttribute
|
var attribute = new IPBlockListAttribute
|
||||||
{
|
{
|
||||||
RestrictLocalAccess = false,
|
BlockLocalAccess = false,
|
||||||
ConfigurationKey = configKey
|
ConfigurationKey = configKey
|
||||||
};
|
};
|
||||||
var context = GetContext();
|
var context = GetContext();
|
||||||
@@ -178,9 +178,9 @@ namespace UnitTests.AspNetCore.Attributes
|
|||||||
restrictedIpsConfig.Add("");
|
restrictedIpsConfig.Add("");
|
||||||
restrictedIpsConfig.Add("127.0.0.0/8");
|
restrictedIpsConfig.Add("127.0.0.0/8");
|
||||||
restrictedIpsConfig.Add("192.168.178.10");
|
restrictedIpsConfig.Add("192.168.178.10");
|
||||||
var attribute = new IPBlacklistAttribute
|
var attribute = new IPBlockListAttribute
|
||||||
{
|
{
|
||||||
RestrictLocalAccess = true,
|
BlockLocalAccess = true,
|
||||||
ConfigurationKey = configKey
|
ConfigurationKey = configKey
|
||||||
};
|
};
|
||||||
var context = GetContext();
|
var context = GetContext();
|
||||||
@@ -207,9 +207,9 @@ namespace UnitTests.AspNetCore.Attributes
|
|||||||
configExists = true;
|
configExists = true;
|
||||||
restrictedIpsConfig.Add("127.0.0.0/8");
|
restrictedIpsConfig.Add("127.0.0.0/8");
|
||||||
restrictedIpsConfig.Add("192.168.178.10");
|
restrictedIpsConfig.Add("192.168.178.10");
|
||||||
var attribute = new IPBlacklistAttribute
|
var attribute = new IPBlockListAttribute
|
||||||
{
|
{
|
||||||
RestrictLocalAccess = true,
|
BlockLocalAccess = true,
|
||||||
ConfigurationKey = configKey
|
ConfigurationKey = configKey
|
||||||
};
|
};
|
||||||
var remote = IPAddress.Parse(address);
|
var remote = IPAddress.Parse(address);
|
||||||
@@ -240,9 +240,9 @@ namespace UnitTests.AspNetCore.Attributes
|
|||||||
// arrange
|
// arrange
|
||||||
configKey = "Black:List";
|
configKey = "Black:List";
|
||||||
configExists = false;
|
configExists = false;
|
||||||
var attribute = new IPBlacklistAttribute
|
var attribute = new IPBlockListAttribute
|
||||||
{
|
{
|
||||||
RestrictLocalAccess = true,
|
BlockLocalAccess = true,
|
||||||
ConfigurationKey = configKey
|
ConfigurationKey = configKey
|
||||||
};
|
};
|
||||||
var context = GetContext();
|
var context = GetContext();
|
||||||
@@ -14,7 +14,8 @@ namespace UnitTests.AspNetCore.Extensions
|
|||||||
{
|
{
|
||||||
private Mock<ISession> sessionMock;
|
private Mock<ISession> sessionMock;
|
||||||
|
|
||||||
private string tokenName;
|
private string tokenFormName;
|
||||||
|
private string tokenHeaderName;
|
||||||
private string tokenValue;
|
private string tokenValue;
|
||||||
|
|
||||||
private Dictionary<string, string> requestHeaders;
|
private Dictionary<string, string> requestHeaders;
|
||||||
@@ -26,7 +27,8 @@ namespace UnitTests.AspNetCore.Extensions
|
|||||||
[TestInitialize]
|
[TestInitialize]
|
||||||
public void InitializeTests()
|
public void InitializeTests()
|
||||||
{
|
{
|
||||||
tokenName = null;
|
tokenFormName = null;
|
||||||
|
tokenHeaderName = null;
|
||||||
tokenValue = null;
|
tokenValue = null;
|
||||||
|
|
||||||
requestHeaders = new Dictionary<string, string>();
|
requestHeaders = new Dictionary<string, string>();
|
||||||
@@ -42,34 +44,38 @@ namespace UnitTests.AspNetCore.Extensions
|
|||||||
public void ShouldReturnAntiforgery()
|
public void ShouldReturnAntiforgery()
|
||||||
{
|
{
|
||||||
// arrange
|
// arrange
|
||||||
tokenName = "af-token";
|
tokenFormName = "af-token";
|
||||||
|
tokenHeaderName = "af-header";
|
||||||
tokenValue = "security_first";
|
tokenValue = "security_first";
|
||||||
|
|
||||||
var context = GetContext();
|
var context = GetContext();
|
||||||
|
|
||||||
// act
|
// act
|
||||||
var result = context.GetAntiforgeryToken();
|
var (formName, headerName, value) = context.GetAntiforgeryToken();
|
||||||
|
|
||||||
// assert
|
// assert
|
||||||
Assert.AreEqual(tokenName, result.Name);
|
Assert.AreEqual(tokenFormName, formName);
|
||||||
Assert.AreEqual(tokenValue, result.Value);
|
Assert.AreEqual(tokenHeaderName, headerName);
|
||||||
|
Assert.AreEqual(tokenValue, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void ShouldReturnAntiforgeryNullService()
|
public void ShouldReturnAntiforgeryNullService()
|
||||||
{
|
{
|
||||||
// arrange
|
// arrange
|
||||||
tokenName = "af-token";
|
tokenFormName = "af-token";
|
||||||
|
tokenHeaderName = "af-header";
|
||||||
tokenValue = "security_first";
|
tokenValue = "security_first";
|
||||||
|
|
||||||
var context = GetContext(hasAntiforgery: false);
|
var context = GetContext(hasAntiforgery: false);
|
||||||
|
|
||||||
// act
|
// act
|
||||||
var result = context.GetAntiforgeryToken();
|
var (formName, headerName, value) = context.GetAntiforgeryToken();
|
||||||
|
|
||||||
// assert
|
// assert
|
||||||
Assert.AreEqual(null, result.Name);
|
Assert.IsNull(formName);
|
||||||
Assert.AreEqual(null, result.Value);
|
Assert.IsNull(headerName);
|
||||||
|
Assert.IsNull(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
@@ -79,11 +85,12 @@ namespace UnitTests.AspNetCore.Extensions
|
|||||||
var context = GetContext();
|
var context = GetContext();
|
||||||
|
|
||||||
// act
|
// act
|
||||||
var result = context.GetAntiforgeryToken();
|
var (formName, headerName, value) = context.GetAntiforgeryToken();
|
||||||
|
|
||||||
// assert
|
// assert
|
||||||
Assert.AreEqual(null, result.Name);
|
Assert.IsNull(formName);
|
||||||
Assert.AreEqual(null, result.Value);
|
Assert.IsNull(headerName);
|
||||||
|
Assert.IsNull(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion Antiforgery
|
#endregion Antiforgery
|
||||||
@@ -105,13 +112,16 @@ namespace UnitTests.AspNetCore.Extensions
|
|||||||
Assert.AreEqual(remote, result);
|
Assert.AreEqual(remote, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[DataTestMethod]
|
||||||
public void ShouldReturnDefaultHeader()
|
[DataRow("X-Forwarded-For")]
|
||||||
|
[DataRow("X-Real-IP")]
|
||||||
|
[DataRow("CF-Connecting-IP")]
|
||||||
|
public void ShouldReturnDefaultHeader(string headerName)
|
||||||
{
|
{
|
||||||
// arrange
|
// arrange
|
||||||
remote = IPAddress.Parse("1.2.3.4");
|
remote = IPAddress.Parse("1.2.3.4");
|
||||||
var header = IPAddress.Parse("5.6.7.8");
|
var header = IPAddress.Parse("5.6.7.8");
|
||||||
requestHeaders.Add("X-Forwarded-For", header.ToString());
|
requestHeaders.Add(headerName, header.ToString());
|
||||||
|
|
||||||
var context = GetContext();
|
var context = GetContext();
|
||||||
|
|
||||||
@@ -130,12 +140,14 @@ namespace UnitTests.AspNetCore.Extensions
|
|||||||
remote = IPAddress.Parse("1.2.3.4");
|
remote = IPAddress.Parse("1.2.3.4");
|
||||||
string headerName = "FooBar";
|
string headerName = "FooBar";
|
||||||
var headerIp = IPAddress.Parse("5.6.7.8");
|
var headerIp = IPAddress.Parse("5.6.7.8");
|
||||||
|
|
||||||
requestHeaders.Add(headerName, headerIp.ToString());
|
requestHeaders.Add(headerName, headerIp.ToString());
|
||||||
|
requestHeaders.Add("X-Forwarded-For", remote.ToString());
|
||||||
|
|
||||||
var context = GetContext();
|
var context = GetContext();
|
||||||
|
|
||||||
// act
|
// act
|
||||||
var result = context.GetRemoteIpAddress(headerName: headerName);
|
var result = context.GetRemoteIpAddress(ipHeaderName: headerName);
|
||||||
|
|
||||||
// assert
|
// assert
|
||||||
Assert.AreNotEqual(remote, result);
|
Assert.AreNotEqual(remote, result);
|
||||||
@@ -221,7 +233,7 @@ namespace UnitTests.AspNetCore.Extensions
|
|||||||
var context = GetContext();
|
var context = GetContext();
|
||||||
|
|
||||||
// act
|
// act
|
||||||
bool result = context.IsLocalRequest(headerName: headerName);
|
bool result = context.IsLocalRequest(ipHeaderName: headerName);
|
||||||
|
|
||||||
// assert
|
// assert
|
||||||
Assert.IsTrue(result);
|
Assert.IsTrue(result);
|
||||||
@@ -254,7 +266,7 @@ namespace UnitTests.AspNetCore.Extensions
|
|||||||
var context = GetContext();
|
var context = GetContext();
|
||||||
|
|
||||||
// act
|
// act
|
||||||
bool result = context.IsLocalRequest(headerName: headerName);
|
bool result = context.IsLocalRequest(ipHeaderName: headerName);
|
||||||
|
|
||||||
// assert
|
// assert
|
||||||
Assert.IsFalse(result);
|
Assert.IsFalse(result);
|
||||||
@@ -385,7 +397,7 @@ namespace UnitTests.AspNetCore.Extensions
|
|||||||
var antiforgeryMock = new Mock<IAntiforgery>();
|
var antiforgeryMock = new Mock<IAntiforgery>();
|
||||||
antiforgeryMock
|
antiforgeryMock
|
||||||
.Setup(af => af.GetAndStoreTokens(It.IsAny<HttpContext>()))
|
.Setup(af => af.GetAndStoreTokens(It.IsAny<HttpContext>()))
|
||||||
.Returns(string.IsNullOrWhiteSpace(tokenName) ? null : new AntiforgeryTokenSet(tokenValue, tokenValue, tokenName, tokenName));
|
.Returns(() => string.IsNullOrWhiteSpace(tokenValue) ? null : new AntiforgeryTokenSet(tokenValue, tokenValue, tokenFormName, tokenHeaderName));
|
||||||
|
|
||||||
requestServicesMock
|
requestServicesMock
|
||||||
.Setup(rs => rs.GetService(typeof(IAntiforgery)))
|
.Setup(rs => rs.GetService(typeof(IAntiforgery)))
|
||||||
|
|||||||
@@ -6,13 +6,13 @@ using System.Security.Claims;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using AMWD.Common.AspNetCore.BasicAuthentication;
|
using AMWD.Common.AspNetCore.Security.BasicAuthentication;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.Extensions.Primitives;
|
using Microsoft.Extensions.Primitives;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
using Moq;
|
using Moq;
|
||||||
|
|
||||||
namespace UnitTests.AspNetCore.BasicAuthentication
|
namespace UnitTests.AspNetCore.Security.BasicAuthentication
|
||||||
{
|
{
|
||||||
[TestClass]
|
[TestClass]
|
||||||
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
|
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
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 Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using Moq;
|
||||||
|
|
||||||
|
namespace UnitTests.AspNetCore.Security.PathProtection
|
||||||
|
{
|
||||||
|
[TestClass]
|
||||||
|
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
|
||||||
|
public class ProtectedPathMiddlewareTests
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -260,7 +260,7 @@ namespace UnitTests.Common.Extensions
|
|||||||
// act
|
// act
|
||||||
string topLevelString = jObj.GetValue<string>("stringValue");
|
string topLevelString = jObj.GetValue<string>("stringValue");
|
||||||
decimal topLevelDecimal = jObj.GetValue<decimal>("decimalValue");
|
decimal topLevelDecimal = jObj.GetValue<decimal>("decimalValue");
|
||||||
int subLevelInteger = jObj.GetValue<int>("object:integerValue");
|
int subLevelInteger = jObj.GetValue<int>("object:IntegerValue");
|
||||||
string subLevelString = jObj.GetValue<string>("object:stringValue");
|
string subLevelString = jObj.GetValue<string>("object:stringValue");
|
||||||
|
|
||||||
string notExistingOnTopLevel = jObj.GetValue<string>("fancyValue");
|
string notExistingOnTopLevel = jObj.GetValue<string>("fancyValue");
|
||||||
@@ -288,7 +288,7 @@ namespace UnitTests.Common.Extensions
|
|||||||
// act
|
// act
|
||||||
string topLevelString = jObj.GetValue("stringValue", "Test String");
|
string topLevelString = jObj.GetValue("stringValue", "Test String");
|
||||||
decimal topLevelDecimal = jObj.GetValue("decimalValue", 13.24m);
|
decimal topLevelDecimal = jObj.GetValue("decimalValue", 13.24m);
|
||||||
int subLevelInteger = jObj.GetValue("object:integerValue", 55);
|
int subLevelInteger = jObj.GetValue("object:IntegerValue", 55);
|
||||||
string subLevelString = jObj.GetValue("object:stringValue", "Yeah!");
|
string subLevelString = jObj.GetValue("object:stringValue", "Yeah!");
|
||||||
|
|
||||||
string notExistingOnTopLevel = jObj.GetValue("fancyValue", "Party!");
|
string notExistingOnTopLevel = jObj.GetValue("fancyValue", "Party!");
|
||||||
|
|||||||
Reference in New Issue
Block a user