-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
The IPFilter class now contains logic for interacting with Flask. The Whitelist class contains logic for permitting hosts and networks and checking whether an IP address should be allowed. This separation will allow other types of rules to be created in the future. This is actually a complete rewrite using a TDD approach.
- Loading branch information
Showing
15 changed files
with
361 additions
and
105 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,4 +10,5 @@ | |
load balancer. | ||
""" | ||
|
||
from flask_ipfilter.ipfilter import IPFilter | ||
from flask_ipfilter.whitelist import Whitelist |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
"""Restrict access to Flask applications by requestor IP address.""" | ||
|
||
from flask import request | ||
from werkzeug.exceptions import Forbidden | ||
|
||
|
||
class IPFilter: | ||
"""An IP address filter for Flask applications.""" | ||
|
||
def __init__(self, flask_app=None, ruleset=None): | ||
""" | ||
Initialize the IP filter. | ||
:parameter app: The Flask application may or may not exist when | ||
__init__ is called. If app is passed, the filter will | ||
be applied to the Flask application immediately. The | ||
filter can be applied to a Flask application later by | ||
calling init_app with the app object as the parameter. | ||
""" | ||
self.app = flask_app | ||
self.ruleset = ruleset | ||
if flask_app: | ||
self.init_app(flask_app) | ||
|
||
def init_app(self, flask_app): | ||
""" | ||
Connect the whitelist filter to a Flask application. | ||
This is called in the :func:`__init__` function if the app | ||
parameter is passed at that time, but can be called later in order to | ||
allow a whitelist to be set up before the Flask app is created. | ||
:parameter app: Required reference to the Flask application to which we | ||
will apply the filter. | ||
""" | ||
flask_app.before_request(self) | ||
|
||
def __call__(self): | ||
""" | ||
Validate an IP before processing a request. | ||
When :func:`init_app` is called, it will be registered to run before | ||
each request. This depends on the Flask request object. | ||
:returns: Nothing, but raises an exception for Flask to catch if the | ||
ruleset determines that the IP address should be blocked. | ||
""" | ||
x_forwarded_for = request.headers.get('X-Forwarded-For') | ||
if x_forwarded_for: | ||
ip_address = x_forwarded_for.split(',')[-1].strip() | ||
else: | ||
ip_address = request.remote_addr | ||
|
||
if not self.ruleset.evaluate(ip_address): | ||
raise Forbidden() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,84 +1,54 @@ | ||
"""Code for filtering Flask requests based on an IP address whitelist.""" | ||
"""IPFilter Whitelist.""" | ||
|
||
import ipaddress | ||
from flask import request | ||
from werkzeug.exceptions import Forbidden | ||
|
||
|
||
class Whitelist: | ||
"""An IP address whitelist filter for Flask.""" | ||
""" | ||
A ruleset that denies requests by default. | ||
def __init__(self, app=None): | ||
"""Initialize the whitelist filter.""" | ||
self.addresses = set() | ||
self.networks = set() | ||
if app: | ||
self.init_app(app) | ||
Hosts and networks will be allowed only if they have been explicitly | ||
permitted with the :func:`permit` function. | ||
""" | ||
|
||
def init_app(self, app): | ||
""" | ||
Connect the whitelist filter to a Flask application. | ||
This is called in the :func:`__init__` function if the app | ||
parameter is passed at that time, but can be called later in order to | ||
allow a whitelist to be set up before the Flask app is created. | ||
""" | ||
self.app = app | ||
self.app.before_request(self.validate_ip) | ||
|
||
def whitelist(self, address): | ||
""" | ||
Add a new host or network to the whitelist. | ||
:parameter address: The IP address to allow. The address parameter | ||
accepts anything that the :mod:`ipaddress` module | ||
can take as a parameter to :class:`ip_address` or | ||
:class:`ip_network`. | ||
""" | ||
try: | ||
host = ipaddress.ip_address(address) | ||
self.addresses.add(host) | ||
except ValueError: | ||
net = ipaddress.ip_network(address) | ||
self.networks.add(net) | ||
def __init__(self): | ||
"""Initialize the ruleset.""" | ||
self.permitted_hosts = set() | ||
self.permitted_networks = set() | ||
|
||
def check(self, address): | ||
def evaluate(self, ip_address): | ||
""" | ||
Check an IP address against the whitelist. | ||
Determine whether an IP address is allowed. | ||
:parameter address: The IP address to check. | ||
:parameter ip_address: The IP address to check. This must be something | ||
that :mod:`ipaddress` can convert into an | ||
:class:`ip_address`. | ||
:returns: True if access should be allowed and False otherwise. | ||
""" | ||
ip_addr = ipaddress.ip_address(address) | ||
ip_addr = ipaddress.ip_address(ip_address) | ||
|
||
if ip_addr in self.addresses: | ||
if ip_addr in self.permitted_hosts: | ||
return True | ||
|
||
for net in self.networks: | ||
for net in self.permitted_networks: | ||
if ip_addr in net: | ||
return True | ||
|
||
return False | ||
|
||
def validate_ip(self): | ||
def permit(self, ip_address): | ||
""" | ||
Validate an IP before processing a request. | ||
When :func:`init_app` is called, it will be registered to run before | ||
each request. | ||
This depends on the Flask request object. | ||
Add a new host or network to the whitelist. | ||
:returns: A denied message with HTTP status 403 if the IP address | ||
associated with the request object is not allowed. | ||
Otherwise, nothing. | ||
:parameter ip_address: The IP address to allow. The address parameter | ||
accepts anything that the :mod:`ipaddress` module | ||
can take as a parameter to :class:`ip_address` or | ||
:class:`ip_network`. | ||
""" | ||
x_forwarded_for = request.headers.get('X-Forwarded-For') | ||
if x_forwarded_for: | ||
ip_addr = x_forwarded_for.split(',')[-1].strip() | ||
else: | ||
ip_addr = request.remote_addr | ||
|
||
if not self.check(ip_addr): | ||
raise Forbidden() | ||
try: | ||
host = ipaddress.ip_address(ip_address) | ||
self.permitted_hosts.add(host) | ||
except ValueError: | ||
net = ipaddress.ip_network(ip_address) | ||
self.permitted_networks.add(net) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.