Compare commits

...

3 Commits

6 changed files with 129 additions and 339 deletions

View File

@ -12,8 +12,6 @@ views.
HTTP Digest Authentication is specified in `RFC 2617`_.
Refer to the full `Flask-DigestAuth readthedocs documentation`_.
Why HTTP Digest Authentication?
-------------------------------
@ -52,345 +50,16 @@ You may also install the latest source from the
pip install git+https://github.com/imacat/flask-digestauth.git
Configuration
Documentation
=============
Flask-DigestAuth takes the configuration ``DIGEST_AUTH_REALM`` as the
realm. The default realm is ``Login Required``.
Refer to the `documentation on Read the Docs`_.
Setting the Password
====================
Change Log
==========
The password hash of the HTTP Digest Authentication is composed of the
realm, the username, and the password. Example for setting the
password:
::
from flask_digest_auth import make_password_hash
user.password = make_password_hash(realm, username, password)
The username is part of the hash. If the user changes their username,
you need to ask their password, to generate and store the new password
hash.
Flask-DigestAuth Alone
======================
Flask-DigestAuth can authenticate the users alone.
Simple Applications with Flask-DigestAuth Alone
-----------------------------------------------
In your ``my_app.py``:
::
from flask import Flask, request, redirect
from flask_digest_auth import DigestAuth
app: flask = Flask(__name__)
... (Configure the Flask application) ...
auth: DigestAuth = DigestAuth()
auth.init_app(app)
@auth.register_get_password
def get_password_hash(username: str) -> t.Optional[str]:
... (Load the password hash) ...
@auth.register_get_user
def get_user(username: str) -> t.Optional[t.Any]:
... (Load the user) ...
@app.get("/admin")
@auth.login_required
def admin():
return f"Hello, {g.user.username}!"
@app.post("/logout")
@auth.login_required
def logout():
auth.logout()
return redirect(request.form.get("next"))
Larger Applications with ``create_app()`` with Flask-DigestAuth Alone
---------------------------------------------------------------------
In your ``my_app/__init__.py``:
::
from flask import Flask
from flask_digest_auth import DigestAuth
auth: DigestAuth = DigestAuth()
def create_app(test_config = None) -> Flask:
app: flask = Flask(__name__)
... (Configure the Flask application) ...
auth.init_app(app)
@auth.register_get_password
def get_password_hash(username: str) -> t.Optional[str]:
... (Load the password hash) ...
@auth.register_get_user
def get_user(username: str) -> t.Optional[t.Any]:
... (Load the user) ...
return app
In your ``my_app/views.py``:
::
from my_app import auth
from flask import Flask, Blueprint, request, redirect
bp = Blueprint("admin", __name__, url_prefix="/admin")
@bp.get("/admin")
@auth.login_required
def admin():
return f"Hello, {g.user.username}!"
@app.post("/logout")
@auth.login_required
def logout():
auth.logout()
return redirect(request.form.get("next"))
def init_app(app: Flask) -> None:
app.register_blueprint(bp)
Flask-Login Integration
=======================
Flask-DigestAuth works with Flask-Login_. You can write a Flask
module that requires log in, without specifying how to log in. The
application can use either HTTP Digest Authentication, or the log in
forms, as needed.
To use Flask-Login with Flask-DigestAuth,
``login_manager.init_app(app)`` must be called before
``auth.init_app(app)``.
The currently logged-in user can be retrieved at
``flask_login.current_user``, if any.
The views only depend on Flask-Login, but not the Flask-DigestAuth.
You can change the actual authentication mechanism without changing
the views.
Simple Applications with Flask-Login Integration
------------------------------------------------
In your ``my_app.py``:
::
import flask_login
from flask import Flask, request, redirect
from flask_digest_auth import DigestAuth
app: flask = Flask(__name__)
... (Configure the Flask application) ...
login_manager: flask_login.LoginManager = flask_login.LoginManager()
login_manager.init_app(app)
@login_manager.user_loader
def load_user(user_id: str) -> t.Optional[User]:
... (Load the user with the username) ...
auth: DigestAuth = DigestAuth()
auth.init_app(app)
@auth.register_get_password
def get_password_hash(username: str) -> t.Optional[str]:
... (Load the password hash) ...
@app.get("/admin")
@flask_login.login_required
def admin():
return f"Hello, {flask_login.current_user.get_id()}!"
@app.post("/logout")
@flask_login.login_required
def logout():
auth.logout()
# Do not call flask_login.logout_user()
return redirect(request.form.get("next"))
Larger Applications with ``create_app()`` with Flask-Login Integration
----------------------------------------------------------------------
In your ``my_app/__init__.py``:
::
from flask import Flask
from flask_digest_auth import DigestAuth
from flask_login import LoginManager
auth: DigestAuth = DigestAuth()
def create_app(test_config = None) -> Flask:
app: flask = Flask(__name__)
... (Configure the Flask application) ...
login_manager: LoginManager = LoginManager()
login_manager.init_app(app)
@login_manager.user_loader
def load_user(user_id: str) -> t.Optional[User]:
... (Load the user with the username) ...
auth.init_app(app)
@auth.register_get_password
def get_password_hash(username: str) -> t.Optional[str]:
... (Load the password hash) ...
return app
In your ``my_app/views.py``:
::
import flask_login
from flask import Flask, Blueprint, request, redirect
from my_app import auth
bp = Blueprint("admin", __name__, url_prefix="/admin")
@bp.get("/admin")
@flask_login.login_required
def admin():
return f"Hello, {flask_login.current_user.get_id()}!"
@app.post("/logout")
@flask_login.login_required
def logout():
auth.logout()
# Do not call flask_login.logout_user()
return redirect(request.form.get("next"))
def init_app(app: Flask) -> None:
app.register_blueprint(bp)
The views only depend on Flask-Login, but not the actual
authentication mechanism. You can change the actual authentication
mechanism without changing the views.
Session Integration
===================
Flask-DigestAuth features session integration. The user log in
is remembered in the session. The authentication information is not
requested again. This is different to the practice of the HTTP Digest
Authentication, but is convenient for the log in accounting.
Log In Bookkeeping
==================
You can register a callback to run when the user logs in, for ex.,
logging the log in event, adding the log in counter, etc.
::
@auth.register_on_login
def on_login(user: User) -> None:
user.visits = user.visits + 1
Log Out
=======
Flask-DigestAuth supports log out. The user will be prompted for the
new username and password.
Test Client
===========
Flask-DigestAuth comes with a test client that supports HTTP digest
authentication.
A unittest Test Case
--------------------
::
from flask import Flask
from flask_digest_auth import Client
from flask_testing import TestCase
from my_app import create_app
class MyTestCase(TestCase):
def create_app(self):
app: Flask = create_app({
"TESTING": True,
"SECRET_KEY": token_urlsafe(32),
"DIGEST_AUTH_REALM": "admin",
})
app.test_client_class = Client
return app
def test_admin(self):
response = self.client.get("/admin")
self.assertEqual(response.status_code, 401)
response = self.client.get(
"/admin", digest_auth=(USERNAME, PASSWORD))
self.assertEqual(response.status_code, 200)
A pytest Test
-------------
::
import pytest
from flask import Flask
from flask_digest_auth import Client
from my_app import create_app
@pytest.fixture()
def app():
app: Flask = create_app({
"TESTING": True,
"SECRET_KEY": token_urlsafe(32),
"DIGEST_AUTH_REALM": "admin",
})
app.test_client_class = Client
yield app
@pytest.fixture()
def client(app):
return app.test_client()
def test_admin(app: Flask, client: Client):
with app.app_context():
response = client.get("/admin")
assert response.status_code == 401
response = client.get(
"/admin", digest_auth=(USERNAME, PASSWORD))
assert response.status_code == 200
Refer to the `change log`_.
Copyright
@ -422,5 +91,6 @@ Authors
.. _RFC 2617: https://www.rfc-editor.org/rfc/rfc2617
.. _Flask: https://flask.palletsprojects.com
.. _Flask-DigestAuth GitHub repository: https://github.com/imacat/flask-digestauth
.. _Flask-DigestAuth readthedocs documentation: https://flask-digestauth.readthedocs.io
.. _Flask-Login: https://flask-login.readthedocs.io
.. _documentation on Read the Docs: https://flask-digestauth.readthedocs.io
.. _change log: https://flask-digestauth.readthedocs.io/en/latest/changelog.html

111
docs/source/changelog.rst Normal file
View File

@ -0,0 +1,111 @@
Change Log
==========
Version 0.5.0
-------------
Released 2023/1/6
* Added the ``DIGEST_AUTH_REALM`` configuration variable as the
recommended way to set the authentication realm.
* Changed the default realm from an empty string to
``Login Required``.
Version 0.4.0
-------------
Released 2023/1/4
* Changed the package name from ``flask-digest-auth`` to
``Flask-DigestAuth``, according to the Flask recommended extension
guidelines
https://flask.palletsprojects.com/en/latest/extensiondev/ .
* Replaced ``app.digest_auth`` with ``app.extensions["digest-auth"]``
to store the ``DigestAuth`` instance.
* Replaced ``auth.app`` with ``current_app``, to prevent circular
imports.
Version 0.3.1
-------------
Released 2022/12/29
Fixed the missing authentication state with disabled users.
Version 0.3.0
-------------
Released 2022/12/7
Changed the visibility of several methods and properties of the
DigestAuth class that should be private to private.
Version 0.2.4
-------------
Released 2022/12/6
Fixed the pytest example in the documentation.
Version 0.2.3
-------------
Released 2022/12/6
Fixed the dependencies for the documentation hosted on Read the Docs.
Version 0.2.2
-------------
Released 2022/12/6
Added the Sphinx documentation, and hosted the documentation on
Read the Docs.
Version 0.2.1
-------------
Released 2022/12/6
Various fixes, with the help from SonarQube.
Version 0.2.0
-------------
Released 2022/11/27
* Added log out support. User can log out.
* Added on-login event handler. You can do some accounting when the
user logs in.
This release is written in Sydney and on the international flight,
and released in Taipei.
Version 0.1.1
-------------
Released 2022/11/24
Changed the minimal Python version to 3.7.
Released at Sydney, Australia on vacation.
Version 0.1.0
-------------
Released 2022/11/24
The initial release.
Released at Sydney, Australia on vacation.

View File

@ -6,6 +6,7 @@ import os
import sys
sys.path.insert(0, os.path.abspath('../../src/'))
import flask_digest_auth
# -- Project information -----------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
@ -13,7 +14,7 @@ sys.path.insert(0, os.path.abspath('../../src/'))
project = 'Flask-DigestAuth'
copyright = '2022-2023, imacat'
author = 'imacat'
release = '0.5.0'
release = flask_digest_auth.VERSION
# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration

View File

@ -19,6 +19,7 @@ HTTP Digest Authentication is specified in `RFC 2617`_.
intro
flask_digest_auth
examples
changelog

View File

@ -17,7 +17,7 @@
[project]
name = "Flask-DigestAuth"
version = "0.5.0"
dynamic = ["version"]
description = "The Flask HTTP Digest Authentication project."
readme = "README.rst"
requires-python = ">=3.7"
@ -45,9 +45,13 @@ test = [
[project.urls]
"Documentation" = "https://flask-digestauth.readthedocs.io"
"Change Log" = "https://mia-accounting.readthedocs.io/en/latest/changelog.html"
"Repository" = "https://github.com/imacat/flask-digestauth"
"Bug Tracker" = "https://github.com/imacat/flask-digestauth/issues"
[build-system]
requires = ["setuptools>=42"]
build-backend = "setuptools.build_meta"
[tool.setuptools.dynamic]
version = {attr = "flask_digest_auth.VERSION"}

View File

@ -21,3 +21,6 @@
from flask_digest_auth.algo import make_password_hash, calc_response
from flask_digest_auth.auth import DigestAuth
from flask_digest_auth.test import Client
VERSION: str = "0.5.0"
"""The package version."""