Skip to content

Commit

Permalink
Added 'PathOptions' to possbile options to allow specific paths have …
Browse files Browse the repository at this point in the history
…different blocking rules.
  • Loading branch information
msmolka committed Oct 12, 2019
1 parent 67b4b24 commit b2b6b58
Show file tree
Hide file tree
Showing 15 changed files with 1,015 additions and 555 deletions.
24 changes: 21 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ When you install the package, it should be added to your `.csproj`. Alternativel

```xml
<ItemGroup>
<PackageReference Include="ZNetCS.AspNetCore.IPFiltering" Version="2.3.0" />
<PackageReference Include="ZNetCS.AspNetCore.IPFiltering" Version="3.0.0" />
</ItemGroup>
```

Expand Down Expand Up @@ -54,6 +54,7 @@ public void ConfigureServices(IServiceCollection services)
opts.Blacklist = new List<string> { "192.168.0.100-192.168.1.200" };
opts.Whitelist = new List<string> { "192.168.0.10-192.168.10.20", "fe80::/10" };
opts.IgnoredPaths = new List<string> { "get:/ignoreget", "*:/ignore" };
opts.PathOptions = new List<PathOptions> { ... };
});
}
```
Expand All @@ -76,7 +77,23 @@ Middleware can be configured in `appsettings.json` file. By adding following sec
"HttpStatusCode": 404,
"Whitelist": [ "192.168.0.10-192.168.10.20", "fe80::/10" ],
"Blacklist": [ "192.168.0.100-192.168.1.200"],
"IgnoredPaths": [ "GET:/ignoreget", "*:/ignore" ]
"IgnoredPaths": [ "GET:/ignoreget", "*:/ignore" ],
"PathOptions": [
{
"Paths": [ "GET:/pathget", "*:/path" ],
"DefaultBlockLevel": "None",
"HttpStatusCode": 401,
"Whitelist": [ "192.168.0.100-192.168.1.200" ],
"Blacklist": [ "192.168.0.10-192.168.10.20", "fe80::/10" ]
},
{
"Paths": [ "GET:/path2get", "*:/path2" ],
"DefaultBlockLevel": "All",
"HttpStatusCode": 401,
"Whitelist": [ "192.168.0.10-192.168.10.20", "fe80::/10" ],
"Blacklist": [ "192.168.0.100-192.168.1.200" ]
}
]
}
}
```
Expand All @@ -88,7 +105,8 @@ This middleware can be configured using following configuration options:
* `HttpStatusCode` defines status code that is returned to client when IP address is forbidden. Default value is `404` (`Not Found`).
* `Whitelist` defines list of IP address ranges that are allowed for request.
* `Blacklist` defines list of IP address ranges that are forbidden for request.
* `IgnoredPaths` defines list of path with HTTP Verb to be ignored from IP filtering. `*` means all HTTP Verbs for given path will be ignored. Format `{VERB}:{PATH}` (no space after `:`). This configuration is case insensitive.
* `IgnoredPaths` defines list of paths with HTTP Verb to be ignored from IP filtering. `*` means all HTTP Verbs for given path will be ignored. Format `{VERB}:{PATH}` (no space after `:`). This configuration is case insensitive.
* `PathOptions` defines list of paths with HTTP Verb to be processed with custom rules. `*` means all HTTP Verbs for given path will be ignored. Format `{VERB}:{PATH}` (no space after `:`). This configuration is case insensitive.

### IP Address Ranges
`Whitelist` and `Blacklist` can be defined as single IP address or IP address range. For parsing middleware is using extenal
Expand Down
39 changes: 23 additions & 16 deletions src/ZNetCS.AspNetCore.IPFiltering/IIPAddressChecker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ namespace ZNetCS.AspNetCore.IPFiltering
{
#region Usings

using System.Collections.Generic;
using System.Net;

#endregion
Expand All @@ -23,35 +24,41 @@ public interface IIPAddressChecker
#region Public Methods

/// <summary>
/// Checks if IP address is allowed.
/// Check if path is on given list.
/// </summary>
/// <param name="ipAddress">
/// The IP address.
/// <param name="verb">
/// The HTTP verb.
/// </param>
/// <param name="options">
/// The IP filtering options.
/// <param name="path">
/// The request path.
/// </param>
/// <param name="paths">
/// The paths to check.
/// </param>
/// <returns>
/// Returns <see langword="true"/> if IP address is allowed, otherwise <see langword="false"/>.
/// Returns <see langword="true"/> if request path is on give list, otherwise <see langword="false"/>.
/// </returns>
bool IsAllowed(IPAddress ipAddress, IPFilteringOptions options);
bool CheckPaths(string verb, string path, ICollection<string> paths);

/// <summary>
/// Check if path should be ignored.
/// Checks if IP address is allowed.
/// </summary>
/// <param name="verb">
/// The HTTP verb.
/// <param name="ipAddress">
/// The IP address.
/// </param>
/// <param name="path">
/// The request path.
/// <param name="optWhitelist">
/// The option whitelist.
/// </param>
/// <param name="options">
/// The IP filtering options.
/// <param name="optBlacklist">
/// The option blacklist.
/// </param>
/// <param name="blockLevel">
/// The default block level.
/// </param>
/// <returns>
/// Returns <see langword="true"/> if request path should be ignored, otherwise <see langword="false"/>.
/// Returns <see langword="true"/> if IP address is allowed, otherwise <see langword="false"/>.
/// </returns>
bool IsIgnored(string verb, string path, IPFilteringOptions options);
bool IsAllowed(IPAddress ipAddress, ICollection<string> optWhitelist, ICollection<string> optBlacklist, DefaultBlockLevel blockLevel);

#endregion
}
Expand Down
52 changes: 44 additions & 8 deletions src/ZNetCS.AspNetCore.IPFiltering/IPFilteringMiddleware.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ public IPFilteringMiddleware(RequestDelegate next, ILoggerFactory loggerFactory,
this.options.Blacklist = opts.Blacklist;
this.options.Whitelist = opts.Whitelist;
this.options.IgnoredPaths = opts.IgnoredPaths;
this.options.PathOptions = opts.PathOptions;
});
}

Expand All @@ -108,21 +109,56 @@ public async Task Invoke(HttpContext context)
var finder = context.RequestServices.GetRequiredService<IIPAddressFinder>();
var checker = context.RequestServices.GetRequiredService<IIPAddressChecker>();

var path = context.Request.Path.HasValue ? context.Request.Path.Value : null;
var verb = context.Request.Method;
string path = context.Request.Path.HasValue ? context.Request.Path.Value : null;
string verb = context.Request.Method;

PathOption foundPath = null;

if (this.options.PathOptions != null)
{
this.logger.LogDebug("Checking if path: {path} with method {verb} is on any specific path option.", path, verb);

foreach (PathOption pathOption in this.options.PathOptions)
{
if (checker.CheckPaths(verb, path, pathOption.Paths))
{
foundPath = pathOption;
break;
}
}
}

IPAddress ipAddress = finder.Find(context);

this.logger.LogDebug("Checking if path: {path} with method {verb} should be ignored or IP: {ipAddress} address is allowed .", path, verb, ipAddress);
if (checker.IsIgnored(verb, path, this.options) || checker.IsAllowed(ipAddress, this.options))
if (foundPath != null)
{
this.logger.LogDebug("IP is allowed for further process.");
await this.next.Invoke(context);
this.logger.LogDebug("Checking if IP: {ipAddress} address is allowed .", ipAddress);
if (checker.IsAllowed(ipAddress, foundPath.Whitelist, foundPath.Blacklist, foundPath.DefaultBlockLevel))
{
this.logger.LogDebug("IP is allowed for further process.");
await this.next.Invoke(context);
}
else
{
this.logger.LogInformation(1, "The IP Address: {ipAddress} was blocked.", ipAddress);
context.Response.StatusCode = (int)foundPath.HttpStatusCode;
}
}
else
{
this.logger.LogInformation(1, "The IP Address: {ipAddress} was blocked.", ipAddress);
context.Response.StatusCode = (int)this.options.HttpStatusCode;
this.logger.LogDebug("Checking if path: {path} with method {verb} should be ignored or IP: {ipAddress} address is allowed .", path, verb, ipAddress);

if (checker.CheckPaths(verb, path, this.options.IgnoredPaths)
|| checker.IsAllowed(ipAddress, this.options.Whitelist, this.options.Blacklist, this.options.DefaultBlockLevel))
{
this.logger.LogDebug("IP is allowed for further process.");
await this.next.Invoke(context);
}
else
{
this.logger.LogInformation(1, "The IP Address: {ipAddress} was blocked.", ipAddress);
context.Response.StatusCode = (int)this.options.HttpStatusCode;
}
}
}

Expand Down
32 changes: 8 additions & 24 deletions src/ZNetCS.AspNetCore.IPFiltering/IPFilteringOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,44 +12,28 @@ namespace ZNetCS.AspNetCore.IPFiltering
#region Usings

using System.Collections.Generic;
using System.Net;
using System.Diagnostics.CodeAnalysis;

#endregion

/// <summary>
/// The IP filtering options.
/// </summary>
public class IPFilteringOptions
public class IPFilteringOptions : OptionBase
{
#region Public Properties

/// <summary>
/// Gets or sets the blacklist.
/// Gets or sets the paths to be ignored from filtering.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2227:Collection properties should be read only", Justification = "Options serialization.")]
public ICollection<string> Blacklist { get; set; } = new List<string>();

/// <summary>
/// Gets or sets the default block level.
/// </summary>
public DefaultBlockLevel DefaultBlockLevel { get; set; } = DefaultBlockLevel.All;

/// <summary>
/// Gets or sets the http status code.
/// </summary>
public HttpStatusCode HttpStatusCode { get; set; } = HttpStatusCode.NotFound;

/// <summary>
/// Gets or sets the whitelist.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2227:Collection properties should be read only", Justification = "Options serialization.")]
public ICollection<string> Whitelist { get; set; } = new List<string>();
[SuppressMessage("Usage", "CA2227:Collection properties should be read only", Justification = "Options serialization.")]
public ICollection<string> IgnoredPaths { get; set; } = new List<string>();

/// <summary>
/// Gets or sets the paths to be ignored for compression.
/// Gets or sets the paths specific filtering.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2227:Collection properties should be read only", Justification = "Options serialization.")]
public ICollection<string> IgnoredPaths { get; set; } = new List<string>();
[SuppressMessage("Usage", "CA2227:Collection properties should be read only", Justification = "Options serialization.")]
public ICollection<PathOption> PathOptions { get; set; } = new List<PathOption>();

#endregion
}
Expand Down
59 changes: 29 additions & 30 deletions src/ZNetCS.AspNetCore.IPFiltering/Internal/IPAddressChecker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ namespace ZNetCS.AspNetCore.IPFiltering.Internal
#region Usings

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;

Expand All @@ -29,56 +30,54 @@ public class IPAddressChecker : IIPAddressChecker
#region IIPAddressChecker

/// <inheritdoc/>
public virtual bool IsAllowed(IPAddress ipAddress, IPFilteringOptions options)
public virtual bool CheckPaths(string verb, string path, ICollection<string> paths)
{
if (options == null)
if (paths != null)
{
throw new ArgumentNullException(nameof(options));
}

if (ipAddress != null)
{
var whitelist = options.Whitelist.Select(IPAddressRange.Parse).ToList();
var blacklist = options.Blacklist.Select(IPAddressRange.Parse).ToList();
string fullCheck = $"{verb}:{path}";
string check = $"*:{path}";

switch (options.DefaultBlockLevel)
if (paths.Any(x => Contains(fullCheck, x)) || paths.Any(x => Contains(check, x)))
{
case DefaultBlockLevel.All:
return whitelist.Any(r => r.Contains(ipAddress)) && !blacklist.Any(r => r.Contains(ipAddress));

case DefaultBlockLevel.None:
return !blacklist.Any(r => r.Contains(ipAddress)) || whitelist.Any(r => r.Contains(ipAddress));
return true;
}
}

return options.DefaultBlockLevel == DefaultBlockLevel.None;
return false;

static bool Contains(string source, string value)
=> (source != null) && (value != null) && source.StartsWith(value, StringComparison.InvariantCultureIgnoreCase);
}

/// <inheritdoc/>
public bool IsIgnored(string verb, string path, IPFilteringOptions options)
public virtual bool IsAllowed(IPAddress ipAddress, ICollection<string> optWhitelist, ICollection<string> optBlacklist, DefaultBlockLevel blockLevel)
{
if (options == null)
if (optWhitelist == null)
{
throw new ArgumentNullException(nameof(options));
throw new ArgumentNullException(nameof(optWhitelist));
}

if (options.IgnoredPaths != null)
if (optBlacklist == null)
{
string fullCheck = $"{verb}:{path}";
string check = $"*:{path}";
throw new ArgumentNullException(nameof(optBlacklist));
}

if (ipAddress != null)
{
var whitelist = optWhitelist.Select(IPAddressRange.Parse).ToList();
var blacklist = optBlacklist.Select(IPAddressRange.Parse).ToList();

if (options.IgnoredPaths.Any(x => Contains(fullCheck, x)) || options.IgnoredPaths.Any(x => Contains(check, x)))
switch (blockLevel)
{
return true;
case DefaultBlockLevel.All:
return whitelist.Any(r => r.Contains(ipAddress)) && !blacklist.Any(r => r.Contains(ipAddress));

case DefaultBlockLevel.None:
return !blacklist.Any(r => r.Contains(ipAddress)) || whitelist.Any(r => r.Contains(ipAddress));
}
}

return false;

bool Contains(string source, string value)
{
return (source != null) && (value != null) && source.StartsWith(value, StringComparison.OrdinalIgnoreCase);
}
return blockLevel == DefaultBlockLevel.None;
}

#endregion
Expand Down
51 changes: 51 additions & 0 deletions src/ZNetCS.AspNetCore.IPFiltering/OptionBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="OptionBase.cs" company="Marcin Smółka zNET Computer Solutions">
// Copyright (c) Marcin Smółka zNET Computer Solutions. All rights reserved.
// </copyright>
// <summary>
// The option base.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace ZNetCS.AspNetCore.IPFiltering
{
#region Usings

using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Net;

#endregion

/// <summary>
/// The option base.
/// </summary>
public abstract class OptionBase
{
#region Public Properties

/// <summary>
/// Gets or sets the blacklist.
/// </summary>
[SuppressMessage("Usage", "CA2227:Collection properties should be read only", Justification = "Options serialization.")]
public ICollection<string> Blacklist { get; set; } = new List<string>();

/// <summary>
/// Gets or sets the default block level.
/// </summary>
public DefaultBlockLevel DefaultBlockLevel { get; set; } = DefaultBlockLevel.All;

/// <summary>
/// Gets or sets the http status code.
/// </summary>
public HttpStatusCode HttpStatusCode { get; set; } = HttpStatusCode.NotFound;

/// <summary>
/// Gets or sets the whitelist.
/// </summary>
[SuppressMessage("Usage", "CA2227:Collection properties should be read only", Justification = "Options serialization.")]
public ICollection<string> Whitelist { get; set; } = new List<string>();

#endregion
}
}
Loading

0 comments on commit b2b6b58

Please sign in to comment.