From 940978810239a4fff54cc38acf6e1e9d83606456 Mon Sep 17 00:00:00 2001 From: CoprDistGit Date: Fri, 5 May 2023 10:33:58 +0000 Subject: automatic import of python-starlette-wtf --- .gitignore | 1 + python-starlette-wtf.spec | 1377 +++++++++++++++++++++++++++++++++++++++++++++ sources | 1 + 3 files changed, 1379 insertions(+) create mode 100644 python-starlette-wtf.spec create mode 100644 sources diff --git a/.gitignore b/.gitignore index e69de29..b406e41 100644 --- a/.gitignore +++ b/.gitignore @@ -0,0 +1 @@ +/Starlette-WTF-0.4.3.tar.gz diff --git a/python-starlette-wtf.spec b/python-starlette-wtf.spec new file mode 100644 index 0000000..13fa11d --- /dev/null +++ b/python-starlette-wtf.spec @@ -0,0 +1,1377 @@ +%global _empty_manifest_terminate_build 0 +Name: python-Starlette-WTF +Version: 0.4.3 +Release: 1 +Summary: Simple integration of Starlette and WTForms. +License: MIT +URL: https://github.com/muicss/starlette-wtf +Source0: https://mirrors.nju.edu.cn/pypi/web/packages/ce/46/00eda53b774e9a5b2ef4edeb7e69f849d49492ee26e97504a06e782ebb0c/Starlette-WTF-0.4.3.tar.gz +BuildArch: noarch + + +%description +# Starlette-WTF + +Starlette-WTF is a simple tool for integrating [Starlette](https://www.starlette.io/) and [WTForms](https://wtforms.readthedocs.io/en/stable/). It is modeled on the excellent [Flask-WTF](https://flask-wtf.readthedocs.io) library. + +## Table of Contents + +- [Installation](#installation) +- [Quickstart](#quickstart) +- [Creating Forms](#creating-forms) + * [The StarletteForm Class](#the-starletteform-class) + * [Validation](#validation) + * [Async Custom Validators](#async-custom-validators) +- [CSRF Protection](#csrf-protection) + * [Setup](#setup) + * [Protect Views](#protect-views) + * [HTML Forms](#html-forms) + * [JavaScript Requests](#javascript-requests) + * [Disable in Unit Tests](#disable-in-unit-tests) + * [Configuration](#configuration) +- [Development](#development) + * [Get the code](#get-the-code) + * [Run unit tests](#run-unit-tests) + +## Installation + +Installing Starlette-WTF is simple with [pip](https://pip.pypa.io/en/stable/): + +```bash +$ pip install starlette-wtf +``` + +## Quickstart + +The following code implements a simple form handler with CSRF protection. The form has a required string field and validation errors are handled by the html template. Note that CSRF protection requires `SessionMiddleware`, `CSRFProtectMiddleware`, `@csrf_protect` and the `csrf_token` field to be added to the HTML form. + +First, install the dependencies for this quickstart: + +```bash +$ pip install starlette starlette-wtf jinja2 uvicorn +``` + +Next, create a Python file (app.py) with the following code: + +```python +from jinja2 import Template +from starlette.applications import Starlette +from starlette.middleware import Middleware +from starlette.middleware.sessions import SessionMiddleware +from starlette.responses import PlainTextResponse, HTMLResponse +from starlette_wtf import StarletteForm, CSRFProtectMiddleware, csrf_protect +from wtforms import StringField +from wtforms.validators import DataRequired + + +class MyForm(StarletteForm): + name = StringField('name', validators=[DataRequired()]) + + +template = Template(''' + + +
+ {{ form.csrf_token }} +
+ {{ form.name(placeholder='Name') }} + {% if form.name.errors -%} + {{ form.name.errors[0] }} + {%- endif %} +
+ +
+ + +''') + + +app = Starlette(middleware=[ + Middleware(SessionMiddleware, secret_key='***REPLACEME1***'), + Middleware(CSRFProtectMiddleware, csrf_secret='***REPLACEME2***') +]) + + +@app.route('/', methods=['GET', 'POST']) +@csrf_protect +async def index(request): + """GET|POST /: form handler + """ + form = await MyForm.from_formdata(request) + + if await form.validate_on_submit(): + return PlainTextResponse('SUCCESS') + + html = template.render(form=form) + return HTMLResponse(html) +``` + +Finally, run the app using the following command: + +```bash +$ uvicorn app:app +``` + +## Creating Forms + +### The StarletteForm Class + +Starlette-WTF provides a form class that makes it easy to add form validation and CSRF protection to Starlette apps. To make a form, subclass the `StarletteForm` class and use [WTForms](https://wtforms.readthedocs.io/) fields, validators and widgets to define the inputs. The `StarletteForm` class inherits from the WTForms `Form` class so you can use WTForms features and methods to add more advanced functionality to your app: + +```python +from starlette_wtf import StarletteForm +from wtforms import TextField, PasswordField +from wtforms.validators import DataRequired, Email, EqualTo +from wtforms.widgets import PasswordInput + + +class CreateAccountForm(StarletteForm): + email = TextField( + 'Email address', + validators=[ + DataRequired('Please enter your email address'), + Email() + ] + ) + + password = PasswordField( + 'Password', + widget=PasswordInput(hide_value=False), + validators=[ + DataRequired('Please enter your password'), + EqualTo('password_confirm', message='Passwords must match') + ] + ) + + password_confirm = PasswordField( + 'Confirm Password', + widget=PasswordInput(hide_value=False), + validators=[ + DataRequired('Please confirm your password') + ] + ) +``` + +Often you will want to initialize form objects using default values on GET requests and from submitted formdata on POST requests. To make this easier you can use the `.from_formdata()` async class method which does this for you automatically: + +```python +@app.route('/create-account', methods=['GET', 'POST']) +async def create_account(request): + """GET|POST /create-account: Create account form handler + """ + form = await CreateAccountForm.from_formdata(request) + return PlainTextResponse() +``` + +### Validation + +The `StarletteForm` class has a useful `.validate_on_submit()` method that performs input validation for POST, PUT, PATCH and DELETE requests and returns a boolean indicating whether or not there were any errors. After validation, errors are available via the `.errors` attribute attached to each input field instance. Note that validation is asynchronous to handle async field validators (see below): + +```python +from jinja2 import Template +from starlette.applications import Starlette +from starlette.responses import (PlainTextResponse, RedirectResponse, + HTMLResponse) + + +template = Template(''' + + +

Create Account

+
+
+ {{ form.email(placeholder='Email address', + autofocus='true', + type='email', + spellcheck='false') }} + {% if form.email.errors -%} + {{ form.email.errors[0] }} + {%- endif %} +
+
+ {{ form.password(placeholder="Password") }} + {% if form.password.errors -%} + {{ form.password.errors[0] }} + {%- endif %} +
+
+ {{ form.password_confirm(placeholder="Confirm password") }} + {% if form.password_confirm.errors -%} + {{ form.password_confirm.errors[0] }} + {%- endif %} +
+ +
+ + +''') + + +app = Starlette() + + +@app.route('/', methods=['GET']) +async def index(request): + """GET /: Return home page + """ + return PlainTextResponse() + + +@app.route('/create-account', methods=['GET', 'POST']) +async def create_account(request): + """GET|POST /create-account: Create account form handler + """ + # initialize form + form = await CreateAccountForm.from_formdata(request) + + # validate form + if await form.validate_on_submit(): + # TODO: Save account credentials before returning redirect response + return RedirectResponse(url='/', status_code=303) + + # generate html + html = template.render(form=form) + + # return response + status_code = 422 if form.errors else 200 + return HTMLResponse(html, status_code=status_code) +``` + +### Async Custom Validators + +The `StarletteForm` class allows you to implement asynchronous [WTForms-like custom validators](https://wtforms.readthedocs.io/en/stable/validators/#custom-validators) by adding `async_validate_{fieldname}` methods to your form classes: + +```python +from starlette_wtf import StarletteForm +from wtforms import TextField, PasswordField, ValidationError +from wtforms.validators import DataRequired, Email, EqualTo + + +class CreateAccountForm(StarletteForm): + email = TextField( + 'Email address', + validators=[ + DataRequired('Please enter your email address'), + Email() + ] + ) + + password = PasswordField( + 'Password', + widget=PasswordInput(hide_value=False), + validators=[ + DataRequired('Please enter your password'), + EqualTo('password_confirm', message='Passwords must match') + ] + ) + + password_confirm = PasswordField( + 'Confirm Password', + widget=PasswordInput(hide_value=False), + validators=[ + DataRequired('Please confirm your password') + ] + ) + + async def async_validate_email(self, field): + """Asynchronous validator to check if email is already in-use + """ + # replace this with your own code + if await make_database_request_here(): + raise ValidationError('Email is already in use') +``` + +## CSRF Protection + +In order to add CSRF protection to your app, first you must ensure that Starlette's `SessionMiddleware` is enabled, second you must configure Starlette-WTF using `CSRFProtectMiddleware`, third you must use the `@csrf_protect` decorator to protect individual endpoints, and fourth you must add the CSRF token to your HTML forms or JavaScript requests. + +### Setup + +To enable CSRF protection for your app, first you must ensure that Starlette's `SessionMiddleware` is enabled, and second you must configure Starlette-WTF using `CSRFProtectMiddleware`. + +```python +from starlette.applications import Starlette +from starlette.middleware import Middleware +from starlette.middleware.sessions import SessionMiddleware +from starlette_wtf import CSRFProtectMiddleware + + +app = Starlette(middleware=[ + Middleware(SessionMiddleware, secret_key='***REPLACEME1***'), + Middleware(CSRFProtectMiddleware, csrf_secret='***REPLACEME2***') +]) +``` + +### Protect Views + +Once Starlette-WTF has been configured using `CSRFProtectMiddleware` you can enable CSRF protection for individual endpoints using the `@csrf_protect` decorator. The `@csrf_protect` decorator will automatically look for `csrf_token` in the form data or in the request headers (`X-CSRFToken`) and it will raise an `HTTPException` if the token is missing or invalid. CSRF token validation will only be performed on submission requests (POST, PUT, PATCH, DELETE). Note that the `@csrf_protect` must run after `@app.route()`: + +```python +from starlette.responses import PlainTextResponse +from starlette_wtf import csrf_protect + + +@app.route('/form-handler', methods=['GET', 'POST']) +@csrf_protect +async def form_handler(request): + """GET|POST /form-handler: Form handler + """ + # this code won't run unless the CSRF token has been validated + return PlainTextResponse() +``` + +The `@csrf_protect` decorator can also be used with class-based views (e.g. [HTTPEndpoint](https://www.starlette.io/endpoints/)): +```python +from starlette.endpoints import HTTPEndpoint +from starlette.responses import PlainTextResponse +from starlette_wtf import csrf_protect + + +@csrf_protect +class Endpoint(HTTPEndpoint): + async def get(self, request): + # this code will run without a CSRF check + return PlainTextResponse() + + async def post(self, request): + # this code won't run unless the CSRF token has been validated + return PlainTextResponse() +``` + +The `@csrf_protect` decorator can also be used with bound methods attached to class-based views: +```python +from starlette.endpoints import HTTPEndpoint +from starlette.responses import PlainTextResponse +from starlette_wtf import csrf_protect + + +class Endpoint(HTTPEndpoint): + async def get(self, request): + # this code will run without a CSRF check + return PlainTextResponse() + + @csrf_protect + async def post(self, request): + # this code won't run unless the CSRF token has been validated + return PlainTextResponse() +``` + +### HTML Forms + +When using `StarletteForm` you can render the form's CSRF token field like this: + +```html +
+ {{ form.csrf_token }} +
+``` + +### JavaScript Requests + +When sending an AJAX request, add the `X-CSRFToken` header to allow Starlette-WTF to perform CSRF validation. For example, in jQuery you can configure all requests to send the token: + +```html + +``` + +### Disable in Unit Tests + +To disable CSRF protection in unit tests you can toggle the `enabled` attribute in `CSRFProtectionMiddleware`: + +```python +from starlette.applications import Starlette +from starlette.config import environ +from starlette.middleware import Middleware +from starlette.middleware.sessions import SessionMiddleware +from starlette_wtf import CSRFProtectMiddleware + + +app = Starlette(middleware=[ + Middleware(SessionMiddleware, secret_key='***REPLACEME1***'), + Middleware(CSRFProtectMiddleware, + enable=!environ.get('TESTING', False), + csrf_secret='***REPLACEME2***') +]) +``` + +### Configuration + +`CSRFProtectMiddleware` accepts the following options: + +| Argument | Description +| ----------------- | ----------- +| enabled | If true, enables CSRF protection. Default to True. +| csrf_secret | The CSRF token signing key. +| csrf_field_name | The CSRF token's field name in the session. Defaults to "csrf_token" +| csrf_time_limit | The time limit for each signed token in seconds. Defaults to 3600. +| csrf_headers | List of CSRF HTTP header field names. Defaults to ["X-CSRFToken", "X-CSRF-Token"] +| csrf_ssl_strict | If enabled, ensures same origin policy on https requests. Defaults to True. + +## Development + +### Get the code + +Starlette-WTF is actively developed on GitHub. You can clone the repository using git: + +```bash +$ git clone git@github.com:muicss/starlette-wtf.git +``` + +Once you have a copy of the source, you can install it into your site-packages in development mode so you can modify and execute the code: + +```bash +$ python setup.py develop +``` + +### Run unit tests + +To install unit test dependencies: + +```bash +$ pip install -e .[test] +``` + +To run unit tests: + +```bash +$ pytest +``` + +%package -n python3-Starlette-WTF +Summary: Simple integration of Starlette and WTForms. +Provides: python-Starlette-WTF +BuildRequires: python3-devel +BuildRequires: python3-setuptools +BuildRequires: python3-pip +%description -n python3-Starlette-WTF +# Starlette-WTF + +Starlette-WTF is a simple tool for integrating [Starlette](https://www.starlette.io/) and [WTForms](https://wtforms.readthedocs.io/en/stable/). It is modeled on the excellent [Flask-WTF](https://flask-wtf.readthedocs.io) library. + +## Table of Contents + +- [Installation](#installation) +- [Quickstart](#quickstart) +- [Creating Forms](#creating-forms) + * [The StarletteForm Class](#the-starletteform-class) + * [Validation](#validation) + * [Async Custom Validators](#async-custom-validators) +- [CSRF Protection](#csrf-protection) + * [Setup](#setup) + * [Protect Views](#protect-views) + * [HTML Forms](#html-forms) + * [JavaScript Requests](#javascript-requests) + * [Disable in Unit Tests](#disable-in-unit-tests) + * [Configuration](#configuration) +- [Development](#development) + * [Get the code](#get-the-code) + * [Run unit tests](#run-unit-tests) + +## Installation + +Installing Starlette-WTF is simple with [pip](https://pip.pypa.io/en/stable/): + +```bash +$ pip install starlette-wtf +``` + +## Quickstart + +The following code implements a simple form handler with CSRF protection. The form has a required string field and validation errors are handled by the html template. Note that CSRF protection requires `SessionMiddleware`, `CSRFProtectMiddleware`, `@csrf_protect` and the `csrf_token` field to be added to the HTML form. + +First, install the dependencies for this quickstart: + +```bash +$ pip install starlette starlette-wtf jinja2 uvicorn +``` + +Next, create a Python file (app.py) with the following code: + +```python +from jinja2 import Template +from starlette.applications import Starlette +from starlette.middleware import Middleware +from starlette.middleware.sessions import SessionMiddleware +from starlette.responses import PlainTextResponse, HTMLResponse +from starlette_wtf import StarletteForm, CSRFProtectMiddleware, csrf_protect +from wtforms import StringField +from wtforms.validators import DataRequired + + +class MyForm(StarletteForm): + name = StringField('name', validators=[DataRequired()]) + + +template = Template(''' + + +
+ {{ form.csrf_token }} +
+ {{ form.name(placeholder='Name') }} + {% if form.name.errors -%} + {{ form.name.errors[0] }} + {%- endif %} +
+ +
+ + +''') + + +app = Starlette(middleware=[ + Middleware(SessionMiddleware, secret_key='***REPLACEME1***'), + Middleware(CSRFProtectMiddleware, csrf_secret='***REPLACEME2***') +]) + + +@app.route('/', methods=['GET', 'POST']) +@csrf_protect +async def index(request): + """GET|POST /: form handler + """ + form = await MyForm.from_formdata(request) + + if await form.validate_on_submit(): + return PlainTextResponse('SUCCESS') + + html = template.render(form=form) + return HTMLResponse(html) +``` + +Finally, run the app using the following command: + +```bash +$ uvicorn app:app +``` + +## Creating Forms + +### The StarletteForm Class + +Starlette-WTF provides a form class that makes it easy to add form validation and CSRF protection to Starlette apps. To make a form, subclass the `StarletteForm` class and use [WTForms](https://wtforms.readthedocs.io/) fields, validators and widgets to define the inputs. The `StarletteForm` class inherits from the WTForms `Form` class so you can use WTForms features and methods to add more advanced functionality to your app: + +```python +from starlette_wtf import StarletteForm +from wtforms import TextField, PasswordField +from wtforms.validators import DataRequired, Email, EqualTo +from wtforms.widgets import PasswordInput + + +class CreateAccountForm(StarletteForm): + email = TextField( + 'Email address', + validators=[ + DataRequired('Please enter your email address'), + Email() + ] + ) + + password = PasswordField( + 'Password', + widget=PasswordInput(hide_value=False), + validators=[ + DataRequired('Please enter your password'), + EqualTo('password_confirm', message='Passwords must match') + ] + ) + + password_confirm = PasswordField( + 'Confirm Password', + widget=PasswordInput(hide_value=False), + validators=[ + DataRequired('Please confirm your password') + ] + ) +``` + +Often you will want to initialize form objects using default values on GET requests and from submitted formdata on POST requests. To make this easier you can use the `.from_formdata()` async class method which does this for you automatically: + +```python +@app.route('/create-account', methods=['GET', 'POST']) +async def create_account(request): + """GET|POST /create-account: Create account form handler + """ + form = await CreateAccountForm.from_formdata(request) + return PlainTextResponse() +``` + +### Validation + +The `StarletteForm` class has a useful `.validate_on_submit()` method that performs input validation for POST, PUT, PATCH and DELETE requests and returns a boolean indicating whether or not there were any errors. After validation, errors are available via the `.errors` attribute attached to each input field instance. Note that validation is asynchronous to handle async field validators (see below): + +```python +from jinja2 import Template +from starlette.applications import Starlette +from starlette.responses import (PlainTextResponse, RedirectResponse, + HTMLResponse) + + +template = Template(''' + + +

Create Account

+
+
+ {{ form.email(placeholder='Email address', + autofocus='true', + type='email', + spellcheck='false') }} + {% if form.email.errors -%} + {{ form.email.errors[0] }} + {%- endif %} +
+
+ {{ form.password(placeholder="Password") }} + {% if form.password.errors -%} + {{ form.password.errors[0] }} + {%- endif %} +
+
+ {{ form.password_confirm(placeholder="Confirm password") }} + {% if form.password_confirm.errors -%} + {{ form.password_confirm.errors[0] }} + {%- endif %} +
+ +
+ + +''') + + +app = Starlette() + + +@app.route('/', methods=['GET']) +async def index(request): + """GET /: Return home page + """ + return PlainTextResponse() + + +@app.route('/create-account', methods=['GET', 'POST']) +async def create_account(request): + """GET|POST /create-account: Create account form handler + """ + # initialize form + form = await CreateAccountForm.from_formdata(request) + + # validate form + if await form.validate_on_submit(): + # TODO: Save account credentials before returning redirect response + return RedirectResponse(url='/', status_code=303) + + # generate html + html = template.render(form=form) + + # return response + status_code = 422 if form.errors else 200 + return HTMLResponse(html, status_code=status_code) +``` + +### Async Custom Validators + +The `StarletteForm` class allows you to implement asynchronous [WTForms-like custom validators](https://wtforms.readthedocs.io/en/stable/validators/#custom-validators) by adding `async_validate_{fieldname}` methods to your form classes: + +```python +from starlette_wtf import StarletteForm +from wtforms import TextField, PasswordField, ValidationError +from wtforms.validators import DataRequired, Email, EqualTo + + +class CreateAccountForm(StarletteForm): + email = TextField( + 'Email address', + validators=[ + DataRequired('Please enter your email address'), + Email() + ] + ) + + password = PasswordField( + 'Password', + widget=PasswordInput(hide_value=False), + validators=[ + DataRequired('Please enter your password'), + EqualTo('password_confirm', message='Passwords must match') + ] + ) + + password_confirm = PasswordField( + 'Confirm Password', + widget=PasswordInput(hide_value=False), + validators=[ + DataRequired('Please confirm your password') + ] + ) + + async def async_validate_email(self, field): + """Asynchronous validator to check if email is already in-use + """ + # replace this with your own code + if await make_database_request_here(): + raise ValidationError('Email is already in use') +``` + +## CSRF Protection + +In order to add CSRF protection to your app, first you must ensure that Starlette's `SessionMiddleware` is enabled, second you must configure Starlette-WTF using `CSRFProtectMiddleware`, third you must use the `@csrf_protect` decorator to protect individual endpoints, and fourth you must add the CSRF token to your HTML forms or JavaScript requests. + +### Setup + +To enable CSRF protection for your app, first you must ensure that Starlette's `SessionMiddleware` is enabled, and second you must configure Starlette-WTF using `CSRFProtectMiddleware`. + +```python +from starlette.applications import Starlette +from starlette.middleware import Middleware +from starlette.middleware.sessions import SessionMiddleware +from starlette_wtf import CSRFProtectMiddleware + + +app = Starlette(middleware=[ + Middleware(SessionMiddleware, secret_key='***REPLACEME1***'), + Middleware(CSRFProtectMiddleware, csrf_secret='***REPLACEME2***') +]) +``` + +### Protect Views + +Once Starlette-WTF has been configured using `CSRFProtectMiddleware` you can enable CSRF protection for individual endpoints using the `@csrf_protect` decorator. The `@csrf_protect` decorator will automatically look for `csrf_token` in the form data or in the request headers (`X-CSRFToken`) and it will raise an `HTTPException` if the token is missing or invalid. CSRF token validation will only be performed on submission requests (POST, PUT, PATCH, DELETE). Note that the `@csrf_protect` must run after `@app.route()`: + +```python +from starlette.responses import PlainTextResponse +from starlette_wtf import csrf_protect + + +@app.route('/form-handler', methods=['GET', 'POST']) +@csrf_protect +async def form_handler(request): + """GET|POST /form-handler: Form handler + """ + # this code won't run unless the CSRF token has been validated + return PlainTextResponse() +``` + +The `@csrf_protect` decorator can also be used with class-based views (e.g. [HTTPEndpoint](https://www.starlette.io/endpoints/)): +```python +from starlette.endpoints import HTTPEndpoint +from starlette.responses import PlainTextResponse +from starlette_wtf import csrf_protect + + +@csrf_protect +class Endpoint(HTTPEndpoint): + async def get(self, request): + # this code will run without a CSRF check + return PlainTextResponse() + + async def post(self, request): + # this code won't run unless the CSRF token has been validated + return PlainTextResponse() +``` + +The `@csrf_protect` decorator can also be used with bound methods attached to class-based views: +```python +from starlette.endpoints import HTTPEndpoint +from starlette.responses import PlainTextResponse +from starlette_wtf import csrf_protect + + +class Endpoint(HTTPEndpoint): + async def get(self, request): + # this code will run without a CSRF check + return PlainTextResponse() + + @csrf_protect + async def post(self, request): + # this code won't run unless the CSRF token has been validated + return PlainTextResponse() +``` + +### HTML Forms + +When using `StarletteForm` you can render the form's CSRF token field like this: + +```html +
+ {{ form.csrf_token }} +
+``` + +### JavaScript Requests + +When sending an AJAX request, add the `X-CSRFToken` header to allow Starlette-WTF to perform CSRF validation. For example, in jQuery you can configure all requests to send the token: + +```html + +``` + +### Disable in Unit Tests + +To disable CSRF protection in unit tests you can toggle the `enabled` attribute in `CSRFProtectionMiddleware`: + +```python +from starlette.applications import Starlette +from starlette.config import environ +from starlette.middleware import Middleware +from starlette.middleware.sessions import SessionMiddleware +from starlette_wtf import CSRFProtectMiddleware + + +app = Starlette(middleware=[ + Middleware(SessionMiddleware, secret_key='***REPLACEME1***'), + Middleware(CSRFProtectMiddleware, + enable=!environ.get('TESTING', False), + csrf_secret='***REPLACEME2***') +]) +``` + +### Configuration + +`CSRFProtectMiddleware` accepts the following options: + +| Argument | Description +| ----------------- | ----------- +| enabled | If true, enables CSRF protection. Default to True. +| csrf_secret | The CSRF token signing key. +| csrf_field_name | The CSRF token's field name in the session. Defaults to "csrf_token" +| csrf_time_limit | The time limit for each signed token in seconds. Defaults to 3600. +| csrf_headers | List of CSRF HTTP header field names. Defaults to ["X-CSRFToken", "X-CSRF-Token"] +| csrf_ssl_strict | If enabled, ensures same origin policy on https requests. Defaults to True. + +## Development + +### Get the code + +Starlette-WTF is actively developed on GitHub. You can clone the repository using git: + +```bash +$ git clone git@github.com:muicss/starlette-wtf.git +``` + +Once you have a copy of the source, you can install it into your site-packages in development mode so you can modify and execute the code: + +```bash +$ python setup.py develop +``` + +### Run unit tests + +To install unit test dependencies: + +```bash +$ pip install -e .[test] +``` + +To run unit tests: + +```bash +$ pytest +``` + +%package help +Summary: Development documents and examples for Starlette-WTF +Provides: python3-Starlette-WTF-doc +%description help +# Starlette-WTF + +Starlette-WTF is a simple tool for integrating [Starlette](https://www.starlette.io/) and [WTForms](https://wtforms.readthedocs.io/en/stable/). It is modeled on the excellent [Flask-WTF](https://flask-wtf.readthedocs.io) library. + +## Table of Contents + +- [Installation](#installation) +- [Quickstart](#quickstart) +- [Creating Forms](#creating-forms) + * [The StarletteForm Class](#the-starletteform-class) + * [Validation](#validation) + * [Async Custom Validators](#async-custom-validators) +- [CSRF Protection](#csrf-protection) + * [Setup](#setup) + * [Protect Views](#protect-views) + * [HTML Forms](#html-forms) + * [JavaScript Requests](#javascript-requests) + * [Disable in Unit Tests](#disable-in-unit-tests) + * [Configuration](#configuration) +- [Development](#development) + * [Get the code](#get-the-code) + * [Run unit tests](#run-unit-tests) + +## Installation + +Installing Starlette-WTF is simple with [pip](https://pip.pypa.io/en/stable/): + +```bash +$ pip install starlette-wtf +``` + +## Quickstart + +The following code implements a simple form handler with CSRF protection. The form has a required string field and validation errors are handled by the html template. Note that CSRF protection requires `SessionMiddleware`, `CSRFProtectMiddleware`, `@csrf_protect` and the `csrf_token` field to be added to the HTML form. + +First, install the dependencies for this quickstart: + +```bash +$ pip install starlette starlette-wtf jinja2 uvicorn +``` + +Next, create a Python file (app.py) with the following code: + +```python +from jinja2 import Template +from starlette.applications import Starlette +from starlette.middleware import Middleware +from starlette.middleware.sessions import SessionMiddleware +from starlette.responses import PlainTextResponse, HTMLResponse +from starlette_wtf import StarletteForm, CSRFProtectMiddleware, csrf_protect +from wtforms import StringField +from wtforms.validators import DataRequired + + +class MyForm(StarletteForm): + name = StringField('name', validators=[DataRequired()]) + + +template = Template(''' + + +
+ {{ form.csrf_token }} +
+ {{ form.name(placeholder='Name') }} + {% if form.name.errors -%} + {{ form.name.errors[0] }} + {%- endif %} +
+ +
+ + +''') + + +app = Starlette(middleware=[ + Middleware(SessionMiddleware, secret_key='***REPLACEME1***'), + Middleware(CSRFProtectMiddleware, csrf_secret='***REPLACEME2***') +]) + + +@app.route('/', methods=['GET', 'POST']) +@csrf_protect +async def index(request): + """GET|POST /: form handler + """ + form = await MyForm.from_formdata(request) + + if await form.validate_on_submit(): + return PlainTextResponse('SUCCESS') + + html = template.render(form=form) + return HTMLResponse(html) +``` + +Finally, run the app using the following command: + +```bash +$ uvicorn app:app +``` + +## Creating Forms + +### The StarletteForm Class + +Starlette-WTF provides a form class that makes it easy to add form validation and CSRF protection to Starlette apps. To make a form, subclass the `StarletteForm` class and use [WTForms](https://wtforms.readthedocs.io/) fields, validators and widgets to define the inputs. The `StarletteForm` class inherits from the WTForms `Form` class so you can use WTForms features and methods to add more advanced functionality to your app: + +```python +from starlette_wtf import StarletteForm +from wtforms import TextField, PasswordField +from wtforms.validators import DataRequired, Email, EqualTo +from wtforms.widgets import PasswordInput + + +class CreateAccountForm(StarletteForm): + email = TextField( + 'Email address', + validators=[ + DataRequired('Please enter your email address'), + Email() + ] + ) + + password = PasswordField( + 'Password', + widget=PasswordInput(hide_value=False), + validators=[ + DataRequired('Please enter your password'), + EqualTo('password_confirm', message='Passwords must match') + ] + ) + + password_confirm = PasswordField( + 'Confirm Password', + widget=PasswordInput(hide_value=False), + validators=[ + DataRequired('Please confirm your password') + ] + ) +``` + +Often you will want to initialize form objects using default values on GET requests and from submitted formdata on POST requests. To make this easier you can use the `.from_formdata()` async class method which does this for you automatically: + +```python +@app.route('/create-account', methods=['GET', 'POST']) +async def create_account(request): + """GET|POST /create-account: Create account form handler + """ + form = await CreateAccountForm.from_formdata(request) + return PlainTextResponse() +``` + +### Validation + +The `StarletteForm` class has a useful `.validate_on_submit()` method that performs input validation for POST, PUT, PATCH and DELETE requests and returns a boolean indicating whether or not there were any errors. After validation, errors are available via the `.errors` attribute attached to each input field instance. Note that validation is asynchronous to handle async field validators (see below): + +```python +from jinja2 import Template +from starlette.applications import Starlette +from starlette.responses import (PlainTextResponse, RedirectResponse, + HTMLResponse) + + +template = Template(''' + + +

Create Account

+
+
+ {{ form.email(placeholder='Email address', + autofocus='true', + type='email', + spellcheck='false') }} + {% if form.email.errors -%} + {{ form.email.errors[0] }} + {%- endif %} +
+
+ {{ form.password(placeholder="Password") }} + {% if form.password.errors -%} + {{ form.password.errors[0] }} + {%- endif %} +
+
+ {{ form.password_confirm(placeholder="Confirm password") }} + {% if form.password_confirm.errors -%} + {{ form.password_confirm.errors[0] }} + {%- endif %} +
+ +
+ + +''') + + +app = Starlette() + + +@app.route('/', methods=['GET']) +async def index(request): + """GET /: Return home page + """ + return PlainTextResponse() + + +@app.route('/create-account', methods=['GET', 'POST']) +async def create_account(request): + """GET|POST /create-account: Create account form handler + """ + # initialize form + form = await CreateAccountForm.from_formdata(request) + + # validate form + if await form.validate_on_submit(): + # TODO: Save account credentials before returning redirect response + return RedirectResponse(url='/', status_code=303) + + # generate html + html = template.render(form=form) + + # return response + status_code = 422 if form.errors else 200 + return HTMLResponse(html, status_code=status_code) +``` + +### Async Custom Validators + +The `StarletteForm` class allows you to implement asynchronous [WTForms-like custom validators](https://wtforms.readthedocs.io/en/stable/validators/#custom-validators) by adding `async_validate_{fieldname}` methods to your form classes: + +```python +from starlette_wtf import StarletteForm +from wtforms import TextField, PasswordField, ValidationError +from wtforms.validators import DataRequired, Email, EqualTo + + +class CreateAccountForm(StarletteForm): + email = TextField( + 'Email address', + validators=[ + DataRequired('Please enter your email address'), + Email() + ] + ) + + password = PasswordField( + 'Password', + widget=PasswordInput(hide_value=False), + validators=[ + DataRequired('Please enter your password'), + EqualTo('password_confirm', message='Passwords must match') + ] + ) + + password_confirm = PasswordField( + 'Confirm Password', + widget=PasswordInput(hide_value=False), + validators=[ + DataRequired('Please confirm your password') + ] + ) + + async def async_validate_email(self, field): + """Asynchronous validator to check if email is already in-use + """ + # replace this with your own code + if await make_database_request_here(): + raise ValidationError('Email is already in use') +``` + +## CSRF Protection + +In order to add CSRF protection to your app, first you must ensure that Starlette's `SessionMiddleware` is enabled, second you must configure Starlette-WTF using `CSRFProtectMiddleware`, third you must use the `@csrf_protect` decorator to protect individual endpoints, and fourth you must add the CSRF token to your HTML forms or JavaScript requests. + +### Setup + +To enable CSRF protection for your app, first you must ensure that Starlette's `SessionMiddleware` is enabled, and second you must configure Starlette-WTF using `CSRFProtectMiddleware`. + +```python +from starlette.applications import Starlette +from starlette.middleware import Middleware +from starlette.middleware.sessions import SessionMiddleware +from starlette_wtf import CSRFProtectMiddleware + + +app = Starlette(middleware=[ + Middleware(SessionMiddleware, secret_key='***REPLACEME1***'), + Middleware(CSRFProtectMiddleware, csrf_secret='***REPLACEME2***') +]) +``` + +### Protect Views + +Once Starlette-WTF has been configured using `CSRFProtectMiddleware` you can enable CSRF protection for individual endpoints using the `@csrf_protect` decorator. The `@csrf_protect` decorator will automatically look for `csrf_token` in the form data or in the request headers (`X-CSRFToken`) and it will raise an `HTTPException` if the token is missing or invalid. CSRF token validation will only be performed on submission requests (POST, PUT, PATCH, DELETE). Note that the `@csrf_protect` must run after `@app.route()`: + +```python +from starlette.responses import PlainTextResponse +from starlette_wtf import csrf_protect + + +@app.route('/form-handler', methods=['GET', 'POST']) +@csrf_protect +async def form_handler(request): + """GET|POST /form-handler: Form handler + """ + # this code won't run unless the CSRF token has been validated + return PlainTextResponse() +``` + +The `@csrf_protect` decorator can also be used with class-based views (e.g. [HTTPEndpoint](https://www.starlette.io/endpoints/)): +```python +from starlette.endpoints import HTTPEndpoint +from starlette.responses import PlainTextResponse +from starlette_wtf import csrf_protect + + +@csrf_protect +class Endpoint(HTTPEndpoint): + async def get(self, request): + # this code will run without a CSRF check + return PlainTextResponse() + + async def post(self, request): + # this code won't run unless the CSRF token has been validated + return PlainTextResponse() +``` + +The `@csrf_protect` decorator can also be used with bound methods attached to class-based views: +```python +from starlette.endpoints import HTTPEndpoint +from starlette.responses import PlainTextResponse +from starlette_wtf import csrf_protect + + +class Endpoint(HTTPEndpoint): + async def get(self, request): + # this code will run without a CSRF check + return PlainTextResponse() + + @csrf_protect + async def post(self, request): + # this code won't run unless the CSRF token has been validated + return PlainTextResponse() +``` + +### HTML Forms + +When using `StarletteForm` you can render the form's CSRF token field like this: + +```html +
+ {{ form.csrf_token }} +
+``` + +### JavaScript Requests + +When sending an AJAX request, add the `X-CSRFToken` header to allow Starlette-WTF to perform CSRF validation. For example, in jQuery you can configure all requests to send the token: + +```html + +``` + +### Disable in Unit Tests + +To disable CSRF protection in unit tests you can toggle the `enabled` attribute in `CSRFProtectionMiddleware`: + +```python +from starlette.applications import Starlette +from starlette.config import environ +from starlette.middleware import Middleware +from starlette.middleware.sessions import SessionMiddleware +from starlette_wtf import CSRFProtectMiddleware + + +app = Starlette(middleware=[ + Middleware(SessionMiddleware, secret_key='***REPLACEME1***'), + Middleware(CSRFProtectMiddleware, + enable=!environ.get('TESTING', False), + csrf_secret='***REPLACEME2***') +]) +``` + +### Configuration + +`CSRFProtectMiddleware` accepts the following options: + +| Argument | Description +| ----------------- | ----------- +| enabled | If true, enables CSRF protection. Default to True. +| csrf_secret | The CSRF token signing key. +| csrf_field_name | The CSRF token's field name in the session. Defaults to "csrf_token" +| csrf_time_limit | The time limit for each signed token in seconds. Defaults to 3600. +| csrf_headers | List of CSRF HTTP header field names. Defaults to ["X-CSRFToken", "X-CSRF-Token"] +| csrf_ssl_strict | If enabled, ensures same origin policy on https requests. Defaults to True. + +## Development + +### Get the code + +Starlette-WTF is actively developed on GitHub. You can clone the repository using git: + +```bash +$ git clone git@github.com:muicss/starlette-wtf.git +``` + +Once you have a copy of the source, you can install it into your site-packages in development mode so you can modify and execute the code: + +```bash +$ python setup.py develop +``` + +### Run unit tests + +To install unit test dependencies: + +```bash +$ pip install -e .[test] +``` + +To run unit tests: + +```bash +$ pytest +``` + +%prep +%autosetup -n Starlette-WTF-0.4.3 + +%build +%py3_build + +%install +%py3_install +install -d -m755 %{buildroot}/%{_pkgdocdir} +if [ -d doc ]; then cp -arf doc %{buildroot}/%{_pkgdocdir}; fi +if [ -d docs ]; then cp -arf docs %{buildroot}/%{_pkgdocdir}; fi +if [ -d example ]; then cp -arf example %{buildroot}/%{_pkgdocdir}; fi +if [ -d examples ]; then cp -arf examples %{buildroot}/%{_pkgdocdir}; fi +pushd %{buildroot} +if [ -d usr/lib ]; then + find usr/lib -type f -printf "/%h/%f\n" >> filelist.lst +fi +if [ -d usr/lib64 ]; then + find usr/lib64 -type f -printf "/%h/%f\n" >> filelist.lst +fi +if [ -d usr/bin ]; then + find usr/bin -type f -printf "/%h/%f\n" >> filelist.lst +fi +if [ -d usr/sbin ]; then + find usr/sbin -type f -printf "/%h/%f\n" >> filelist.lst +fi +touch doclist.lst +if [ -d usr/share/man ]; then + find usr/share/man -type f -printf "/%h/%f.gz\n" >> doclist.lst +fi +popd +mv %{buildroot}/filelist.lst . +mv %{buildroot}/doclist.lst . + +%files -n python3-Starlette-WTF -f filelist.lst +%dir %{python3_sitelib}/* + +%files help -f doclist.lst +%{_docdir}/* + +%changelog +* Fri May 05 2023 Python_Bot - 0.4.3-1 +- Package Spec generated diff --git a/sources b/sources new file mode 100644 index 0000000..07c6b7e --- /dev/null +++ b/sources @@ -0,0 +1 @@ +c18cbb872c2e9ebbfd0225f50f67abe2 Starlette-WTF-0.4.3.tar.gz -- cgit v1.2.3