Skip to content

Commit

Permalink
Merged with the master branch
Browse files Browse the repository at this point in the history
  • Loading branch information
nnseva committed Oct 14, 2022
2 parents 848e88d + cf4b191 commit a55fce4
Show file tree
Hide file tree
Showing 61 changed files with 1,577 additions and 148 deletions.
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ repos:
args: ['--check-only', '--diff']

- repo: https://github.com/PyCQA/flake8
rev: 4.0.1
rev: 5.0.4
hooks:
- id: flake8
files: ^(example|tests|two_factor)/
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
# Changelog

## Unreleased

### Added
- Enforcing a redirect to setup of otp device when none available for user [#550](https://github.com/jazzband/django-two-factor-auth/pull/500)
- Confirmed Django 4.1 support
- WebAuthn support

### Removed
- Django 2.2, 3.0, and 3.1 support

### Changed

- display the TOTP secret key alongside the QR code to streamline setup for
password managers without QR support.

## 1.14.0

### Added
Expand Down
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ include *.rst LICENSE
prune two_factor/locale
recursive-include two_factor/locale *
recursive-include two_factor/templates *
recursive-include two_factor/plugins/*/static *
8 changes: 6 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
TARGET?=tests

.PHONY: docs flake8 example test coverage migrations

docs:
Expand All @@ -13,6 +11,12 @@ example:
DJANGO_SETTINGS_MODULE=example.settings PYTHONPATH=. \
django-admin runserver

example-webauthn:
DJANGO_SETTINGS_MODULE=example.settings_webauthn PYTHONPATH=. \
django-admin migrate
DJANGO_SETTINGS_MODULE=example.settings_webauthn PYTHONPATH=. \
django-admin runserver

test:
DJANGO_SETTINGS_MODULE=tests.settings PYTHONPATH=. \
django-admin test ${TARGET}
Expand Down
4 changes: 2 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ providing Django sessions with a foreign key to the user. Although the package
is optional, it improves account security control over
``django.contrib.sessions``.

Compatible with modern Django versions. At the moment of writing that's
including 2.2, 3.1, 3.2, and 4.0 on Python 3.7, 3.8, 3.9 and 3.10.
Compatible with supported Django and Python versions. At the moment of writing that
includes 3.2, 4.0 and 4.1 on Python 3.7, 3.8, 3.9 and 3.10.
Documentation is available at `readthedocs.org`_.


Expand Down
109 changes: 107 additions & 2 deletions docs/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -160,14 +160,119 @@ Fake Gateway
.. _python-qrcode: https://pypi.python.org/pypi/qrcode
.. _`the upstream ticket`: https://code.google.com/p/google-authenticator/issues/detail?id=327

WebAuthn Settings
-----------------

Start by providing a value for the following setting:

``TWO_FACTOR_WEBAUTHN_RP_NAME`` (default: ``None``)
The human-palatable identifier for the `Relying Party`_. You **MUST** name your application. Failing to do so will
raise an ``ImproperlyConfigured`` exception.

The defaults provided for all other settings should be enough to enable the use of fingerprint readers, security keys
and android phones (Chrome-based browsers only).

Tweak the following settings if you want to restrict the types of devices that can be used and the information that
will be sent to your application after the authentication takes place:

``TWO_FACTOR_WEBAUTHN_AUTHENTICATOR_ATTACHMENT`` (default: ``None``)
The preferred `Authenticator Attachment`_ modality.
Possible values: ``'platform'`` (like an embedded fingerprint reader), ``'cross-platform'`` (such as a usb security
key). The default is to accept all attachment modalities.

``TWO_FACTOR_WEBAUTHN_PREFERRED_TRANSPORTS`` (default: ``None``)
A list of preferred communication transports that will be set for all registered authenticators. **This can be
used to optimize user interaction at authentication time. Its implementation is highly browser-dependent and may
even be disregarded.**

Chrome uses this to filter out credentials that do not use any of the transports listed.
For example, if set to ``['usb', 'internal']`` Chrome will not attempt to authenticate the user with authenticators
that communicate using CaBLE (e.g., android phones).

Possible values for each element in the list are members of ``webauthn.helpers.structs.AuthenticatorTransport``. The
default is to accept all transports.

``TWO_FACTOR_WEBAUTHN_UV_REQUIREMENT`` (default: ``'discouraged'``)
The type of `User Verification`_ that is required. Verification ranges from a simple test of user presence such as
by touching a button to more thorough checks like using biometrics or requiring user PIN input.
Possible values: ``'discouraged'``, ``'preferred'``, ``'required'``.

``TWO_FACTOR_WEBAUTHN_ATTESTATION_CONVEYANCE`` (default: ``'none'``)
The type of `Attestation Conveyance`_. A `Relying Party`_ may want to verify attestations to ensure that
only authentication devices from certain approved vendors can be used. Depending on the level of conveyance, the
attestation could include potentially identifying information, resulting in an additional prompt to the users so
they can decide if they want to proceed.
Possible values: ``'none'``, ``'indirect'`` and ``'direct'``. The ``'enterprise'`` conveyance type is not supported
and will result in ``ImproperlyConfigured`` being raised.

.. warning::
Setting conveyance to other than ``'none'`` enables attestation verification against a list of root certificates.
If the list of root certificates for a particular attestation statement format is empty, **then verification will
always pass**.

``'fido-u2f'``, ``'packed'`` and ``'tpm'`` do not come pre-configured with root certificates. Download the
additional certificates that you needed for your particular device and use the
``TWO_FACTOR_WEBAUTHN_PEM_ROOT_CERTS_BYTES_BY_FMT`` setting below.

``TWO_FACTOR_WEBAUTHN_PEM_ROOT_CERTS_BYTES_BY_FMT`` (default: ``None``)
A mapping of attestation statement formats to lists of Root Certificates, provided as bytes. These will be used in
addition to those already provided by ``py_webauthn`` to verify attestation objects.

**Example:**

If you want to verify attestations made by a Yubikey, get `Yubico's root CA`_ and use it as follows:

.. code-block:: python
yubico_u2f_ca = """
-----BEGIN CERTIFICATE-----
(Yubico's root CA goes here)
-----END CERTIFICATE-----
"""
root_ca_list = [yubico_u2f_ca.encode('ascii')]
TWO_FACTOR_WEBAUTHN_PEM_ROOT_CERTS_BYTES_BY_FMT = {
AttestationFormat.PACKED: root_ca_list,
AttestationFormat.FIDO_U2F: root_ca_list,
}
The following settings control how the attributes for WebAuthn entities are built:

``TWO_FACTOR_WEBAUTHN_ENTITIES_FORM_MIXIN`` (default: ``'two_factor.webauthn.utils.WebauthnEntitiesFormMixin'``)
A mixin to provide WebAuthn entities (user and `Relying Party`_) needed during setup and authentication. Although
the default works in most cases you can provide your own methods to build the different attributes that
are required by these entities (e.g., if you use a custom `User` model).

``TWO_FACTOR_WEBAUTHN_RP_ID`` (default: ``None``)
The default form mixin uses this setting to specify the domain of the `Relying Party`_. By default, the relying
party ID is ``None`` i.e. the current domain returned by `HttpRequest.get_host()`_ will be used. You may want to set
it to a higher-level domain if your application has several sub-domains (e.g., ``www.example.com`` for web and
``m.example.com`` for the mobile version, meaning you may want to set this value to ``'example.com'`` so credentials
are valid for both versions).

WebAuthn devices support throttling too:

``TWO_FACTOR_WEBAUTHN_THROTTLE_FACTOR`` (default: ``1``)
This controls the rate of throttling. The sequence of 1, 2, 4, 8... seconds is
multiplied by this factor to define the delay imposed after 1, 2, 3, 4...
successive failures. Set to ``0`` to disable throttling completely.

.. _`Relying Party`: https://w3c.github.io/webauthn/#webauthn-relying-party
.. _`Authenticator Attachment`: https://www.w3.org/TR/webauthn/#enum-attachment
.. _`User Verification`: https://www.w3.org/TR/webauthn-2/#enum-userVerificationRequirement
.. _`Attestation Conveyance`: https://www.w3.org/TR/webauthn-2/#enum-attestation-convey
.. _`Yubico's Root CA`: https://developers.yubico.com/U2F/Attestation_and_Metadata/
.. _`HttpRequest.get_host()`: https://docs.djangoproject.com/en/4.0/ref/request-response/#django.http.HttpRequest.get_host

Remember Browser
----------------

During a successful login with a token, the user may choose to remember this browser.
If the same user logs in again on the same browser, a token will not be requested, as the browser
serves as a second factor.

The option to remember a browser is deactived by default. Set `TWO_FACTOR_REMEMBER_COOKIE_AGE` to activate.
The option to remember a browser is deactivated by default. Set `TWO_FACTOR_REMEMBER_COOKIE_AGE` to activate.

The browser will be remembered as long as:

Expand All @@ -177,7 +282,7 @@ The browser will be remembered as long as:

The browser is remembered by setting a signed 'remember cookie'.

In order to invalidate remebered browsers after password resets,
In order to invalidate remembered browsers after password resets,
the package relies on the `password` field of the `User` model.
Please consider this in case you do not use the `password` field
e.g. [django-auth-ldap](https://github.com/django-auth-ldap/django-auth-ldap)
Expand Down
46 changes: 42 additions & 4 deletions docs/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Add the following apps to the ``INSTALLED_APPS``:

.. code-block:: python
INSTALLED_APPS = (
INSTALLED_APPS = [
...
'django_otp',
'django_otp.plugins.otp_static',
Expand All @@ -38,7 +38,7 @@ Add the following apps to the ``INSTALLED_APPS``:
'two_factor.plugins.phonenumber', # <- if you want phone number capability.
'two_factor.plugins.email', # <- if you want email capability.
'two_factor.plugins.yubikey', # <- for yubikey capability.
)
]
Add the ``django-otp`` middleware to your ``MIDDLEWARE``. Make sure
it comes after ``AuthenticationMiddleware``:
Expand Down Expand Up @@ -89,10 +89,10 @@ Add the following app to the ``INSTALLED_APPS``:

.. code-block:: python
INSTALLED_APPS = (
INSTALLED_APPS = [
...
'otp_yubikey',
)
]
This plugin also requires adding a validation service, through which YubiKeys
will be verified. Normally, you'd use the YubiCloud for this. In the Django
Expand All @@ -116,5 +116,43 @@ You could also do this using Django's `manage.py shell`:
... )
<ValidationService: default>
.. _webauthn-setup:

WebAuthn Setup
--------------

In order to support WebAuthn_ devices, you have to install the py_webauthn_ package.
It's a ``django-two-factor-auth`` extra so you can select it at install time:

.. code-block:: console
$ pip install django-two-factor-auth[webauthn]
You need to include the plugin in your Django settings:

.. code-block:: python
INSTALLED_APPS = [
...
'two_factor.plugins.webauthn',
]
WebAuthn also requires your service to be reachable using HTTPS.
An exception is made if the domain is ``localhost``, which can be served using plain HTTP.

If you use a different domain, don't forget to set ``SECURE_PROXY_SSL_HEADER`` in your Django settings accordingly:

.. code-block:: python
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
You can try a WebAuthn-enabled version of the example app that is reachable at http://localhost:8000:

.. code-block:: console
$ make example-webauthn
.. _PyPI: https://pypi.python.org/pypi/django-two-factor-auth
.. _Yubikeys: https://www.yubico.com/products/yubikey-hardware/
.. _WebAuthn: https://www.w3.org/TR/webauthn/
.. _py_webauthn: https://pypi.org/project/webauthn/
3 changes: 1 addition & 2 deletions docs/requirements.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ Requirements

Django
------
Modern Django versions are supported. Currently this list includes Django 2.2,
3.0, 3.1, 3.2, and 4.0.
Supported Django versions are supported. Currently this list includes Django 3.2, and 4.0.

Python
------
Expand Down
6 changes: 4 additions & 2 deletions example/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
},
]

INSTALLED_APPS = (
INSTALLED_APPS = [
'django.contrib.auth',
'django.contrib.contenttypes',
'user_sessions',
Expand All @@ -68,7 +68,7 @@

'debug_toolbar',
'bootstrapform'
)
]


LOGOUT_REDIRECT_URL = 'home'
Expand Down Expand Up @@ -100,6 +100,8 @@

TWO_FACTOR_REMEMBER_COOKIE_AGE = 120 # Set to 2 minute for testing

TWO_FACTOR_WEBAUTHN_RP_NAME = 'Demo Application'

SESSION_ENGINE = 'user_sessions.backends.db'

EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
Expand Down
3 changes: 3 additions & 0 deletions example/settings_webauthn.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .settings import * # noqa: F403

INSTALLED_APPS.extend(['two_factor.plugins.webauthn']) # noqa: F405
4 changes: 4 additions & 0 deletions requirements_dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ django-debug-toolbar
django-bootstrap-form
django-user-sessions

# Example app (WebAuthn)

webauthn~=1.6.0

# Testing

coverage
Expand Down
3 changes: 3 additions & 0 deletions requirements_e2e.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# test with selenium
selenium~=4.4.3
webdriver-manager~=3.8.3
7 changes: 3 additions & 4 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
license='MIT',
packages=find_packages(exclude=('example', 'tests')),
install_requires=[
'Django>=2.2',
'Django>=3.2',
'django_otp>=0.8.0',
'qrcode>=4.0.0,<7.99',
'django-phonenumber-field>=1.1.0,<7',
Expand All @@ -21,6 +21,7 @@
extras_require={
'call': ['twilio>=6.0'],
'sms': ['twilio>=6.0'],
'webauthn': ['webauthn>=1.6.0,<1.99', 'pydantic>=1.9.0,<1.99'],
'yubikey': ['django-otp-yubikey'],
'phonenumbers': ['phonenumbers>=7.0.9,<8.99',],
'phonenumberslite': ['phonenumberslite>=7.0.9,<8.99',],
Expand All @@ -30,11 +31,9 @@
'Development Status :: 5 - Production/Stable',
'Environment :: Web Environment',
'Framework :: Django',
'Framework :: Django :: 2.2',
'Framework :: Django :: 3.0',
'Framework :: Django :: 3.1',
'Framework :: Django :: 3.2',
'Framework :: Django :: 4.0',
'Framework :: Django :: 4.1',
'Intended Audience :: Developers',
'License :: OSI Approved :: MIT License',
'Operating System :: OS Independent',
Expand Down
14 changes: 14 additions & 0 deletions tests/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
except ImportError:
otp_yubikey = None

try:
import webauthn
except ImportError:
webauthn = None

BASE_DIR = os.path.dirname(__file__)

SECRET_KEY = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'
Expand All @@ -28,6 +33,9 @@
if otp_yubikey:
INSTALLED_APPS.extend(['otp_yubikey', 'two_factor.plugins.yubikey'])

if webauthn:
INSTALLED_APPS.extend(['two_factor.plugins.webauthn'])

MIDDLEWARE = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
Expand All @@ -40,6 +48,8 @@

ROOT_URLCONF = 'tests.urls'

STATIC_URL = '/static/'

LOGIN_URL = 'two_factor:login'
LOGIN_REDIRECT_URL = 'two_factor:profile'

Expand Down Expand Up @@ -77,8 +87,12 @@
},
]

DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'

TWO_FACTOR_PATCH_ADMIN = False

TWO_FACTOR_WEBAUTHN_RP_NAME = 'Test Server'

AUTH_USER_MODEL = os.environ.get('AUTH_USER_MODEL', 'auth.User')
PASSWORD_HASHERS = ['django.contrib.auth.hashers.MD5PasswordHasher']

Expand Down
Loading

0 comments on commit a55fce4

Please sign in to comment.