Input validation framework for Python
Project description
Stoplight [![Build Status](https://api.travis-ci.org/painterjd/stoplight.png)](https://travis-ci.org/painterjd/stoplight)
=========
Stoplight -- An Input Validation Framework for Python
Why Validate User Input?
------------------------
Input validation is the most basic, first step to adding security to any application that accepts untrusted user input. Volumes have been written on the subject, but the gist is to reduce the attack surface of your application by sanitizing all user input so that it meets a very tight set of criteria needed just for the application, and nothing more. The most common type of attack prevented by input validation is [Code Injection](http://en.wikipedia.org/wiki/Code_injection).
A great number of user input vulnerabilities (i.e. [Shellshock](http://en.wikipedia.org/wiki/Shellshock_%28software_bug%29)) could be avoided almost entirely if user input were sanitized.
Example
-------
Let's say that our application is accepting a US Phone Number only. In that case, our application should only need to accept NNN-NNN-NNNN where N is a digit from 0-9. If the user passes anything else, we can throw it away.
The problem that stoplight aims to address is the intermixing of input validation logic with application logic (in particular with RESTful/REST-like API frameworks). Sometimes they are inseparable, but in almost all cases, they are not. So let's look at the above-mentioned phone number example.
Almost all of today's API frameworks work in a similar manner -- you declare a function that defines an endpoint and the framework calls the function when an HTTP request comes in from a client.
```python
def post(self, account_id, phone_number):
if not is_valid_account_id(account_id):
handle_bad_account_id()
if not is_valid_phone_number(phone_number):
handle_bad_phone_number()
model.set_phone_number(account_id, phone_number)
```
This is a simple, contrived example. Typically things start getting much more complex. For certain HTTP verbs, a user will want different responses returned. There may be other things to accomplish as well.
In Stoplight, we would validate the input like so:
```python
@validate(account_id=AccountIdRule, phone_number=PhoneNumberRule)
def post(self, account_id, phone_number):
model.set_phone_number(account_id, phone_number)
```
This allows us to effectively separate our "input validation" logic from "business logic".
Rules are fairly simple to create. For example, here is how one might declare the PhoneNumberRule
```python
PhoneNumberRule = Rule(is_validate_phone_number(), lambda: abort(404))
```
And of course, that leads us to is_valid_phone_number() declaration.
```python
@validation_function
def is_valid_phone_number(candidate):
if (phone_regex.match(candidate) is None):
msg = 'Not a valid phone number: {0}'
msg = msg.format(candidate)
raise ValidationFailed(msg)
```
This allows us to separate validation from transports (imagine an API where you must support HTTP and ZMQ, for example). It also allows us to centralize validation logic and write separate tests for the validation rules.
Other Features:
---------------
* Ensures that all parameters (positional and keyword) are all validated. If they are not validated, a ValidationProgrammingError is raised.
* Allows validation of globally-scoped values (think items in thread local storage, as is done in the Pecan framework)
Caveats (TODO):
---------------
* Overhead. Such is the nature of Python with decorators.
Documentation:
--------------
The project is being documented at readthedocs [here](http://stoplight.readthedocs.org/en/latest/). For other examples, please see the unit tests.
=========
Stoplight -- An Input Validation Framework for Python
Why Validate User Input?
------------------------
Input validation is the most basic, first step to adding security to any application that accepts untrusted user input. Volumes have been written on the subject, but the gist is to reduce the attack surface of your application by sanitizing all user input so that it meets a very tight set of criteria needed just for the application, and nothing more. The most common type of attack prevented by input validation is [Code Injection](http://en.wikipedia.org/wiki/Code_injection).
A great number of user input vulnerabilities (i.e. [Shellshock](http://en.wikipedia.org/wiki/Shellshock_%28software_bug%29)) could be avoided almost entirely if user input were sanitized.
Example
-------
Let's say that our application is accepting a US Phone Number only. In that case, our application should only need to accept NNN-NNN-NNNN where N is a digit from 0-9. If the user passes anything else, we can throw it away.
The problem that stoplight aims to address is the intermixing of input validation logic with application logic (in particular with RESTful/REST-like API frameworks). Sometimes they are inseparable, but in almost all cases, they are not. So let's look at the above-mentioned phone number example.
Almost all of today's API frameworks work in a similar manner -- you declare a function that defines an endpoint and the framework calls the function when an HTTP request comes in from a client.
```python
def post(self, account_id, phone_number):
if not is_valid_account_id(account_id):
handle_bad_account_id()
if not is_valid_phone_number(phone_number):
handle_bad_phone_number()
model.set_phone_number(account_id, phone_number)
```
This is a simple, contrived example. Typically things start getting much more complex. For certain HTTP verbs, a user will want different responses returned. There may be other things to accomplish as well.
In Stoplight, we would validate the input like so:
```python
@validate(account_id=AccountIdRule, phone_number=PhoneNumberRule)
def post(self, account_id, phone_number):
model.set_phone_number(account_id, phone_number)
```
This allows us to effectively separate our "input validation" logic from "business logic".
Rules are fairly simple to create. For example, here is how one might declare the PhoneNumberRule
```python
PhoneNumberRule = Rule(is_validate_phone_number(), lambda: abort(404))
```
And of course, that leads us to is_valid_phone_number() declaration.
```python
@validation_function
def is_valid_phone_number(candidate):
if (phone_regex.match(candidate) is None):
msg = 'Not a valid phone number: {0}'
msg = msg.format(candidate)
raise ValidationFailed(msg)
```
This allows us to separate validation from transports (imagine an API where you must support HTTP and ZMQ, for example). It also allows us to centralize validation logic and write separate tests for the validation rules.
Other Features:
---------------
* Ensures that all parameters (positional and keyword) are all validated. If they are not validated, a ValidationProgrammingError is raised.
* Allows validation of globally-scoped values (think items in thread local storage, as is done in the Pecan framework)
Caveats (TODO):
---------------
* Overhead. Such is the nature of Python with decorators.
Documentation:
--------------
The project is being documented at readthedocs [here](http://stoplight.readthedocs.org/en/latest/). For other examples, please see the unit tests.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
No source distribution files available for this release.See tutorial on generating distribution archives.
Built Distribution
stoplight-1.4.1-py2-none-any.whl
(15.7 kB
view details)
File details
Details for the file stoplight-1.4.1-py2-none-any.whl
.
File metadata
- Download URL: stoplight-1.4.1-py2-none-any.whl
- Upload date:
- Size: 15.7 kB
- Tags: Python 2
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/1.9.1 pkginfo/1.4.1 requests/2.18.4 setuptools/38.2.4 requests-toolbelt/0.8.0 tqdm/4.19.5 CPython/2.7.14
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 51797962ad4223baac78f0ce762954c9a09e29529285ee82633ce36e5875f7ae |
|
MD5 | 16801b3e60591109dd2cd0adc7e832a7 |
|
BLAKE2b-256 | 0c346480bafa08c519fb767b51ddf55f095d1ee5771e877cdebb508390627f64 |