diff --git a/AMWD.Common.AspNetCore/Attributes/BasicAuthenticationAttribute.cs b/AMWD.Common.AspNetCore/Attributes/BasicAuthenticationAttribute.cs
index 3a357d5..56a17ad 100644
--- a/AMWD.Common.AspNetCore/Attributes/BasicAuthenticationAttribute.cs
+++ b/AMWD.Common.AspNetCore/Attributes/BasicAuthenticationAttribute.cs
@@ -4,7 +4,7 @@ using System.Net.Http.Headers;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
-using AMWD.Common.AspNetCore.BasicAuthentication;
+using AMWD.Common.AspNetCore.Security.BasicAuthentication;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
diff --git a/AMWD.Common.AspNetCore/Attributes/IPWhitelistAttribute.cs b/AMWD.Common.AspNetCore/Attributes/IPAllowListAttribute.cs
similarity index 94%
rename from AMWD.Common.AspNetCore/Attributes/IPWhitelistAttribute.cs
rename to AMWD.Common.AspNetCore/Attributes/IPAllowListAttribute.cs
index ef2f9bb..4441633 100644
--- a/AMWD.Common.AspNetCore/Attributes/IPWhitelistAttribute.cs
+++ b/AMWD.Common.AspNetCore/Attributes/IPAllowListAttribute.cs
@@ -10,7 +10,7 @@ namespace Microsoft.AspNetCore.Mvc.Filters
///
/// Implements an IP filter. Only defined addresses are allowed to access.
///
- public class IPWhitelistAttribute : ActionFilterAttribute
+ public class IPAllowListAttribute : ActionFilterAttribute
{
///
/// Gets or sets a value indicating whether local (localhost) access is granted (Default: true).
diff --git a/AMWD.Common.AspNetCore/Attributes/IPBlacklistAttribute.cs b/AMWD.Common.AspNetCore/Attributes/IPBlockListAttribute.cs
similarity index 85%
rename from AMWD.Common.AspNetCore/Attributes/IPBlacklistAttribute.cs
rename to AMWD.Common.AspNetCore/Attributes/IPBlockListAttribute.cs
index 9ca9c88..36d3e33 100644
--- a/AMWD.Common.AspNetCore/Attributes/IPBlacklistAttribute.cs
+++ b/AMWD.Common.AspNetCore/Attributes/IPBlockListAttribute.cs
@@ -10,12 +10,12 @@ namespace Microsoft.AspNetCore.Mvc.Filters
///
/// Implements an IP filter. The defined addresses are blocked.
///
- public class IPBlacklistAttribute : ActionFilterAttribute
+ public class IPBlockListAttribute : ActionFilterAttribute
{
///
/// Gets or sets a value indicating whether local (localhost) access is blocked (Default: false).
///
- public bool RestrictLocalAccess { get; set; }
+ public bool BlockLocalAccess { get; set; }
///
/// Gets or sets a configuration key where the blocked IP addresses are defined.
@@ -35,7 +35,7 @@ namespace Microsoft.AspNetCore.Mvc.Filters
///
/// Gets or sets a comma separated list of blocked IP addresses.
///
- public string RestrictedIpAddresses { get; set; }
+ public string BlockedIpAddresses { get; set; }
///
public override void OnActionExecuting(ActionExecutingContext context)
@@ -43,13 +43,13 @@ namespace Microsoft.AspNetCore.Mvc.Filters
base.OnActionExecuting(context);
context.HttpContext.Items["RemoteAddress"] = context.HttpContext.GetRemoteIpAddress();
- if (!RestrictLocalAccess && context.HttpContext.IsLocalRequest())
+ if (!BlockLocalAccess && context.HttpContext.IsLocalRequest())
return;
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)
{
if (string.IsNullOrWhiteSpace(ipAddress))
diff --git a/AMWD.Common.AspNetCore/Extensions/HttpContextExtensions.cs b/AMWD.Common.AspNetCore/Extensions/HttpContextExtensions.cs
index d630d61..13f49c3 100644
--- a/AMWD.Common.AspNetCore/Extensions/HttpContextExtensions.cs
+++ b/AMWD.Common.AspNetCore/Extensions/HttpContextExtensions.cs
@@ -1,4 +1,5 @@
-using System.Net;
+using System.Linq;
+using System.Net;
using Microsoft.AspNetCore.Antiforgery;
using Microsoft.Extensions.DependencyInjection;
@@ -9,30 +10,57 @@ namespace Microsoft.AspNetCore.Http
///
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
+ };
+
///
/// Retrieves the antiforgery token.
///
/// The current .
- /// Name and value of the token.
- public static (string Name, string Value) GetAntiforgeryToken(this HttpContext httpContext)
+ /// FormName, HeaderName and Value of the antiforgery token.
+ public static (string FormName, string HeaderName, string Value) GetAntiforgeryToken(this HttpContext httpContext)
{
- var af = httpContext.RequestServices.GetService();
- var set = af?.GetAndStoreTokens(httpContext);
+ var antiforgery = httpContext.RequestServices.GetService();
+ var tokenSet = antiforgery?.GetAndStoreTokens(httpContext);
- return (Name: set?.FormFieldName, Value: set?.RequestToken);
+ return (tokenSet?.FormFieldName, tokenSet?.HeaderName, tokenSet?.RequestToken);
}
///
/// Returns the remote ip address.
///
+ ///
+ /// Searches for additional headers in the following order:
+ ///
+ /// X-Forwarded-For
+ /// X-Real-IP
+ /// CF-Connecting-IP
+ ///
+ ///
/// The current .
- /// The name of the header to resolve the when behind a proxy (Default: X-Forwarded-For).
+ /// The name of the header to resolve the when behind a proxy.
/// The ip address of the client.
- 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();
- if (!string.IsNullOrWhiteSpace(forwardedHeader) && IPAddress.TryParse(forwardedHeader, out var forwarded))
- return forwarded;
+ string forwardedForAddress = null;
+
+ 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;
}
@@ -40,12 +68,20 @@ namespace Microsoft.AspNetCore.Http
///
/// Returns whether the request was made locally.
///
+ ///
+ /// Searches for additional headers in the following order:
+ ///
+ /// X-Forwarded-For
+ /// X-Real-IP
+ /// CF-Connecting-IP
+ ///
+ ///
/// The current .
- /// The name of the header to resolve the when behind a proxy (Default: X-Forwarded-For).
+ /// The name of the header to resolve the when behind a proxy.
///
- 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);
}
diff --git a/AMWD.Common.AspNetCore/BasicAuthentication/BasicAuthenticationHandler.cs b/AMWD.Common.AspNetCore/Security/BasicAuthentication/BasicAuthenticationHandler.cs
similarity index 95%
rename from AMWD.Common.AspNetCore/BasicAuthentication/BasicAuthenticationHandler.cs
rename to AMWD.Common.AspNetCore/Security/BasicAuthentication/BasicAuthenticationHandler.cs
index 6d0f33b..8ce5bc3 100644
--- a/AMWD.Common.AspNetCore/BasicAuthentication/BasicAuthenticationHandler.cs
+++ b/AMWD.Common.AspNetCore/Security/BasicAuthentication/BasicAuthenticationHandler.cs
@@ -11,7 +11,7 @@ using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
-namespace AMWD.Common.AspNetCore.BasicAuthentication
+namespace AMWD.Common.AspNetCore.Security.BasicAuthentication
{
///
/// Implements the for Basic Authentication.
diff --git a/AMWD.Common.AspNetCore/BasicAuthentication/BasicAuthenticationMiddleware.cs b/AMWD.Common.AspNetCore/Security/BasicAuthentication/BasicAuthenticationMiddleware.cs
similarity index 95%
rename from AMWD.Common.AspNetCore/BasicAuthentication/BasicAuthenticationMiddleware.cs
rename to AMWD.Common.AspNetCore/Security/BasicAuthentication/BasicAuthenticationMiddleware.cs
index 4e14801..35268f8 100644
--- a/AMWD.Common.AspNetCore/BasicAuthentication/BasicAuthenticationMiddleware.cs
+++ b/AMWD.Common.AspNetCore/Security/BasicAuthentication/BasicAuthenticationMiddleware.cs
@@ -6,7 +6,7 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
-namespace AMWD.Common.AspNetCore.BasicAuthentication
+namespace AMWD.Common.AspNetCore.Security.BasicAuthentication
{
///
/// Implements a basic authentication.
diff --git a/AMWD.Common.AspNetCore/BasicAuthentication/IBasicAuthenticationValidator.cs b/AMWD.Common.AspNetCore/Security/BasicAuthentication/IBasicAuthenticationValidator.cs
similarity index 91%
rename from AMWD.Common.AspNetCore/BasicAuthentication/IBasicAuthenticationValidator.cs
rename to AMWD.Common.AspNetCore/Security/BasicAuthentication/IBasicAuthenticationValidator.cs
index 47fd1ef..0d440d1 100644
--- a/AMWD.Common.AspNetCore/BasicAuthentication/IBasicAuthenticationValidator.cs
+++ b/AMWD.Common.AspNetCore/Security/BasicAuthentication/IBasicAuthenticationValidator.cs
@@ -3,7 +3,7 @@ using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
-namespace AMWD.Common.AspNetCore.BasicAuthentication
+namespace AMWD.Common.AspNetCore.Security.BasicAuthentication
{
///
/// Interface representing the validation of a basic authentication.
diff --git a/AMWD.Common.AspNetCore/Security/PathProtection/ProtectedPathExtensions.cs b/AMWD.Common.AspNetCore/Security/PathProtection/ProtectedPathExtensions.cs
new file mode 100644
index 0000000..cf56de0
--- /dev/null
+++ b/AMWD.Common.AspNetCore/Security/PathProtection/ProtectedPathExtensions.cs
@@ -0,0 +1,19 @@
+using Microsoft.AspNetCore.Builder;
+
+namespace AMWD.Common.AspNetCore.Security.PathProtection
+{
+ ///
+ /// Extnsion for to enable folder protection.
+ ///
+ [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
+ public static class ProtectedPathExtensions
+ {
+ ///
+ /// Provide protected paths even for static files.
+ ///
+ /// The .
+ /// The with path and policy name.
+ public static IApplicationBuilder UseProtectedPath(this IApplicationBuilder app, ProtectedPathOptions options)
+ => app.UseMiddleware(options);
+ }
+}
diff --git a/AMWD.Common.AspNetCore/Security/PathProtection/ProtectedPathMiddleware.cs b/AMWD.Common.AspNetCore/Security/PathProtection/ProtectedPathMiddleware.cs
new file mode 100644
index 0000000..3982333
--- /dev/null
+++ b/AMWD.Common.AspNetCore/Security/PathProtection/ProtectedPathMiddleware.cs
@@ -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
+{
+ ///
+ /// Implements a check to provide protected paths.
+ ///
+ public class ProtectedPathMiddleware
+ {
+ private readonly RequestDelegate next;
+ private readonly PathString path;
+ private readonly string policyName;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The following delegate in the process chain.
+ /// The options to configure the middleware.
+ public ProtectedPathMiddleware(RequestDelegate next, ProtectedPathOptions options)
+ {
+ this.next = next;
+ path = options.Path;
+ policyName = options.PolicyName;
+ }
+
+ ///
+ /// The delegate invokation.
+ /// Performs the protection check.
+ ///
+ /// The corresponding HTTP context.
+ /// The .
+ /// An awaitable task.
+ 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);
+ }
+ }
+}
diff --git a/AMWD.Common.AspNetCore/Security/PathProtection/ProtectedPathOptions.cs b/AMWD.Common.AspNetCore/Security/PathProtection/ProtectedPathOptions.cs
new file mode 100644
index 0000000..aa46d86
--- /dev/null
+++ b/AMWD.Common.AspNetCore/Security/PathProtection/ProtectedPathOptions.cs
@@ -0,0 +1,20 @@
+using Microsoft.AspNetCore.Http;
+
+namespace AMWD.Common.AspNetCore.Security.PathProtection
+{
+ ///
+ /// Options to define which folder should be protected.
+ ///
+ public class ProtectedPathOptions
+ {
+ ///
+ /// Gets or sets the path to the protected folder.
+ ///
+ public PathString Path { get; set; }
+
+ ///
+ /// Gets or sets the policy name to use.
+ ///
+ public string PolicyName { get; set; }
+ }
+}
diff --git a/AMWD.Common/Extensions/JsonExtensions.cs b/AMWD.Common/Extensions/JsonExtensions.cs
index 95dfaa1..7d91a91 100644
--- a/AMWD.Common/Extensions/JsonExtensions.cs
+++ b/AMWD.Common/Extensions/JsonExtensions.cs
@@ -146,13 +146,26 @@ namespace Newtonsoft.Json
if (lvlObj == null)
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)
return defaultValue;
- return DeepConvert.ChangeType(lvlObj is JValue ? ((JValue)lvlObj).Value : lvlObj.Value