Compare commits

..

No commits in common. "765822a3003fdb685887c942375fd36b36b1a059" and "6c7f7e8c8e782eb5efa1e091542dd5e283ddc75a" have entirely different histories.

7 changed files with 179 additions and 306 deletions

View File

@ -12,17 +12,15 @@ views.
HTTP Digest Authentication is specified in `RFC 2617`_.
Refer to the full `Flask-Digest-Auth readthedocs documentation`_.
Why HTTP Digest Authentication?
-------------------------------
*HTTP Digest Authentication* has the advantage that it does not send
thee actual password to the server, which greatly enhances the
security. It uses the challenge-response authentication scheme. The
client returns the response calculated from the challenge and the
password, but not the original password.
HTTP Digest Authentication has the advantage that it does not send the
actual password to the server, which greatly enhances the security.
It uses the challenge-response authentication scheme. The client
returns the response calculated from the challenge and the password,
but not the original password.
Log in forms has the advantage of freedom, in the senses of both the
visual design and the actual implementation. You may implement your
@ -35,6 +33,51 @@ separated with the authentication mechanism. You can create protected
Flask modules without knowing the actual authentication mechanisms.
Features
--------
There are a couple of Flask HTTP digest authentication
implementations. Flask-Digest-Auth has the following features:
Flask-Login Integration
#######################
Flask-Digest-Auth features Flask-Login integration. The views
can be totally independent with the actual authentication mechanism.
You can write a Flask module that requires log in, without specify
the actual authentication mechanism. The application can specify
either HTTP Digest Authentication, or the log in forms, as needed.
Session Integration
###################
Flask-Digest-Auth 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 Out Support
###############
Flask-Digest-Auth supports log out. The user will be prompted for
new username and password.
Log In Bookkeeping
##################
You can register a callback to run when the user logs in.
.. _HTTP Digest Authentication: https://en.wikipedia.org/wiki/Digest_access_authentication
.. _RFC 2617: https://www.rfc-editor.org/rfc/rfc2617
.. _Flask: https://flask.palletsprojects.com
.. _Flask-Login: https://flask-login.readthedocs.io
Installation
============
@ -49,25 +92,11 @@ You may also install the latest source from the
::
pip install git+https://github.com/imacat/flask-digest-auth.git
git clone git@github.com:imacat/flask-digest-auth.git
cd flask-digest-auth
pip install .
Setting the Password
====================
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-Digest-Auth GitHub repository: https://github.com/imacat/flask-digest-auth
Flask-Digest-Auth Alone
@ -75,9 +104,11 @@ Flask-Digest-Auth Alone
Flask-Digest-Auth can authenticate the users alone.
The currently logged-in user can be retrieved at ``g.user``, if any.
Simple Applications with Flask-Digest-Auth Alone
------------------------------------------------
Example for Simple Applications with Flask-Digest-Auth Alone
------------------------------------------------------------
In your ``my_app.py``:
@ -112,8 +143,8 @@ In your ``my_app.py``:
return redirect(request.form.get("next"))
Larger Applications with ``create_app()`` with Flask-Digest-Auth Alone
----------------------------------------------------------------------
Example for Larger Applications with ``create_app()`` with Flask-Digest-Auth Alone
----------------------------------------------------------------------------------
In your ``my_app/__init__.py``:
@ -165,29 +196,23 @@ In your ``my_app/views.py``:
app.register_blueprint(bp)
Flask-Login Integration
=======================
Flask-Digest-Auth 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.
Flask-Digest-Auth can work with Flask-Login. You can write a Flask
module that requires log in, without specifying the authentication
mechanism. The Flask application can specify the actual
authentication mechanism as it sees fit.
To use Flask-Login with Flask-Digest-Auth,
``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-Digest-Auth.
You can change the actual authentication mechanism without changing
the views.
Simple Applications with Flask-Login Integration
------------------------------------------------
Example for Simple Applications with Flask-Login Integration
------------------------------------------------------------
In your ``my_app.py``:
@ -227,8 +252,8 @@ In your ``my_app.py``:
return redirect(request.form.get("next"))
Larger Applications with ``create_app()`` with Flask-Login Integration
----------------------------------------------------------------------
Example for Larger Applications with ``create_app()`` with Flask-Login Integration
----------------------------------------------------------------------------------
In your ``my_app/__init__.py``:
@ -290,13 +315,31 @@ authentication mechanism. You can change the actual authentication
mechanism without changing the views.
Session Integration
===================
Setting the Password Hash
=========================
Flask-Digest-Auth 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.
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.
Log Out
=======
Call ``auth.logout()`` when the user wants to log out.
Besides the usual log out routine, ``auth.logout()`` actually causes
the next browser automatic authentication to fail, forcing the browser
to ask the user for the username and password again.
Log In Bookkeeping
@ -312,22 +355,13 @@ logging the log in event, adding the log in counter, etc.
user.visits = user.visits + 1
Log Out
=======
Writing Tests
=============
Flask-Digest-Auth supports log out. The user will be prompted for the
new username and password.
You can write tests with our test client that handles HTTP Digest
Authentication.
Test Client
===========
Flask-Digest-Auth comes with a test client that supports HTTP digest
authentication.
A unittest Test Case
--------------------
Example for a unittest_ test case:
::
@ -354,8 +388,8 @@ A unittest Test Case
self.assertEqual(response.status_code, 200)
A pytest Test
-------------
Example for a pytest_ test:
::
@ -385,6 +419,9 @@ A pytest Test
"/admin", digest_auth=("my_name", "my_pass"))
assert response.status_code == 200
.. _unittest: https://docs.python.org/3/library/unittest.html
.. _pytest: https://pytest.org
Copyright
=========
@ -410,10 +447,3 @@ Authors
| imacat
| imacat@mail.imacat.idv.tw
| 2022/11/23
.. _HTTP Digest Authentication: https://en.wikipedia.org/wiki/Digest_access_authentication
.. _RFC 2617: https://www.rfc-editor.org/rfc/rfc2617
.. _Flask: https://flask.palletsprojects.com
.. _Flask-Digest-Auth GitHub repository: https://github.com/imacat/flask-digest-auth
.. _Flask-Digest-Auth readthedocs documentation: https://flask-digest-auth.readthedocs.io
.. _Flask-Login: https://flask-login.readthedocs.io

View File

@ -1,23 +1,23 @@
flask\_digest\_auth package
===========================
The ``DigestAuth`` Class
------------------------
The ``DigestAuth`` class
************************
.. autoclass:: flask_digest_auth.DigestAuth
:members:
:undoc-members:
:show-inheritance:
The ``make_password_hash`` Function
-----------------------------------
***********************************
.. autofunction:: flask_digest_auth.make_password_hash
The ``calc_response`` Function
------------------------------
******************************
.. autofunction:: flask_digest_auth.calc_response
The ``Client`` Test Class
-------------------------
*************************
.. autoclass:: flask_digest_auth.Client
:members:
:undoc-members:

View File

@ -6,12 +6,6 @@
Welcome to Flask-Digest-Auth's documentation!
=============================================
*Flask-Digest-Auth* is an `HTTP Digest Authentication`_ implementation
for Flask_ applications. It authenticates the user for the protected
views.
HTTP Digest Authentication is specified in `RFC 2617`_.
.. toctree::
:maxdepth: 2
:caption: Contents:
@ -28,7 +22,3 @@ Indices and tables
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
.. _HTTP Digest Authentication: https://en.wikipedia.org/wiki/Digest_access_authentication
.. _RFC 2617: https://www.rfc-editor.org/rfc/rfc2617
.. _Flask: https://flask.palletsprojects.com

View File

@ -8,15 +8,17 @@ views.
HTTP Digest Authentication is specified in `RFC 2617`_.
See :ref:`example-alone-simple` and :ref:`example-alone-large`.
Why HTTP Digest Authentication?
-------------------------------
*HTTP Digest Authentication* has the advantage that it does not send
the actual password to the server, which greatly enhances the
security. It uses the challenge-response authentication scheme. The
client returns the response calculated from the challenge and the
password, but not the original password.
HTTP Digest Authentication has the advantage that it does not send the
actual password to the server, which greatly enhances the security.
It uses the challenge-response authentication scheme. The client
returns the response calculated from the challenge and the password,
but not the original password.
Log in forms has the advantage of freedom, in the senses of both the
visual design and the actual implementation. You may implement your
@ -29,6 +31,60 @@ separated with the authentication mechanism. You can create protected
Flask modules without knowing the actual authentication mechanisms.
Features
--------
There are a couple of Flask HTTP digest authentication
implementations. Flask-Digest-Auth has the following features:
Flask-Login Integration
#######################
Flask-Digest-Auth features Flask-Login integration. The views
can be totally independent with the actual authentication mechanism.
You can write a Flask module that requires log in, without specify
the actual authentication mechanism. The application can specify
either HTTP Digest Authentication, or the log in forms, as needed.
See :ref:`example-flask-login-simple` and
:ref:`example-flask-login-large`.
Session Integration
###################
Flask-Digest-Auth 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.
See :meth:`flask_digest_auth.DigestAuth.register_on_login`.
Log Out
#######
Flask-Digest-Auth supports log out. The user will be prompted for the
new username and password.
See :meth:`flask_digest_auth.DigestAuth.logout`.
Test Client
###########
Flask-Digest-Auth comes with a test client that supports HTTP digest
authentication.
See :class:`flask_digest_auth.Client`.
Also see :ref:`example-unittest` and :ref:`example-pytest`.
Installation
------------
@ -43,102 +99,7 @@ You may also install the latest source from the
::
pip install git+https://github.com/imacat/flask-digest-auth.git
Setting the Password
--------------------
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.
See :meth:`flask_digest_auth.make_password_hash`.
Flask-Digest-Auth Alone
-----------------------
Flask-Digest-Auth can authenticate the users alone.
See :ref:`example-alone-simple` and :ref:`example-alone-large`.
Flask-Login Integration
-----------------------
Flask-Digest-Auth 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-Digest-Auth,
``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.
See :ref:`example-flask-login-simple` and
:ref:`example-flask-login-large`.
The views only depend on Flask-Login, but not the Flask-Digest-Auth.
You can change the actual authentication mechanism without changing
the views.
Session Integration
-------------------
Flask-Digest-Auth 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
See :meth:`flask_digest_auth.DigestAuth.register_on_login`.
Log Out
-------
Flask-Digest-Auth supports log out. The user will be prompted for the
new username and password.
See :meth:`flask_digest_auth.DigestAuth.logout`.
Test Client
-----------
Flask-Digest-Auth comes with a test client that supports HTTP digest
authentication.
See :class:`flask_digest_auth.Client`.
Also see :ref:`example-unittest` and :ref:`example-pytest`.
pip install git+https://github.com/imacat/flask-digest-auth
.. _HTTP Digest Authentication: https://en.wikipedia.org/wiki/Digest_access_authentication

View File

@ -26,13 +26,6 @@ from hashlib import md5
def make_password_hash(realm: str, username: str, password: str) -> str:
"""Calculates the password hash for the HTTP digest authentication.
Use this function to set the password for the user.
For example:
::
user.password = make_password_hash(realm, username, password)
:param realm: The realm.
:param username: The username.

View File

@ -58,16 +58,6 @@ class DigestAuth:
def login_required(self, view) -> t.Callable:
"""The view decorator for HTTP digest authentication.
For example:
::
@auth.login_required
def admin():
return f"Hello, {g.user.username}!"
The logged-in user can be retrieved at ``g.user``.
:param view: The view.
:return: The login-protected view.
"""
@ -102,7 +92,7 @@ class DigestAuth:
if authorization.type != "digest":
raise UnauthorizedException(
"Not an HTTP digest authorization")
self.__authenticate(state)
self.authenticate(state)
session["user"] = authorization.username
return self.__get_user(authorization.username)
@ -131,12 +121,12 @@ class DigestAuth:
response: Response = Response()
response.status = 401
response.headers["WWW-Authenticate"] \
= self.__make_response_header(state)
= self.make_response_header(state)
abort(response)
return login_required_view
def __authenticate(self, state: AuthState) -> None:
def authenticate(self, state: AuthState) -> None:
"""Authenticate a user.
:param state: The authorization state.
@ -181,7 +171,7 @@ class DigestAuth:
state.stale = True
raise UnauthorizedException("Invalid nonce")
def __make_response_header(self, state: AuthState) -> str:
def make_response_header(self, state: AuthState) -> str:
"""Composes and returns the ``WWW-Authenticate`` response header.
:param state: The authorization state.
@ -222,16 +212,7 @@ class DigestAuth:
def register_get_password(self, func: t.Callable[[str], t.Optional[str]])\
-> None:
"""The decorator to register the callback to obtain the password hash.
For example:
::
@auth.register_get_password
def get_password_hash(username: str) -> Optional[str]:
user = User.query.filter(User.username == username).first()
return None if user is None else user.password
"""Registers the callback to obtain the password hash.
:param func: The callback that given the username, returns the password
hash, or None if the user does not exist.
@ -254,15 +235,7 @@ class DigestAuth:
def register_get_user(self, func: t.Callable[[str], t.Optional[t.Any]])\
-> None:
"""The decorator to register the callback to obtain the user.
For example:
::
@auth.register_get_user
def get_user(username: str) -> Optional[User]:
return User.query.filter(User.username == username).first()
"""Registers the callback to obtain the user.
:param func: The callback that given the username, returns the user,
or None if the user does not exist.
@ -284,15 +257,7 @@ class DigestAuth:
self.__get_user = UserGetter()
def register_on_login(self, func: t.Callable[[t.Any], None]) -> None:
"""The decorator to register the callback to run when the user logs in.
For example:
::
@auth.register_on_login
def on_login(user: User) -> None:
user.visits = user.visits + 1
"""Registers the callback when the user logs in.
:param func: The callback given the logged-in user.
:return: None.
@ -315,15 +280,6 @@ class DigestAuth:
def init_app(self, app: Flask) -> None:
"""Initializes the Flask application.
For example:
::
app: flask = Flask(__name__)
auth: DigestAuth = DigestAuth()
auth.realm = "My Admin"
auth.init_app(app)
:param app: The Flask application.
:return: None.
"""
@ -344,7 +300,7 @@ class DigestAuth:
response: Response = Response()
response.status = 401
response.headers["WWW-Authenticate"] \
= self.__make_response_header(g.digest_auth_state)
= self.make_response_header(g.digest_auth_state)
abort(response)
@login_manager.request_loader
@ -363,7 +319,7 @@ class DigestAuth:
if authorization.type != "digest":
raise UnauthorizedException(
"Not an HTTP digest authorization")
self.__authenticate(g.digest_auth_state)
self.authenticate(g.digest_auth_state)
user = login_manager.user_callback(
authorization.username)
login_user(user)
@ -379,16 +335,6 @@ class DigestAuth:
This actually causes the next authentication to fail, which forces
the browser to ask the user for the username and password again.
For example:
::
@app.post("/logout")
@auth.login_required
def logout():
auth.logout()
return redirect(request.form.get("next"))
:return: None.
"""
if "user" in session:

View File

@ -29,54 +29,7 @@ from flask_digest_auth.algo import calc_response, make_password_hash
class Client(WerkzeugClient):
"""The test client with HTTP digest authentication enabled.
For unittest example:
::
class MyTestCase(flask_testing.TestCase):
def create_app(self):
app: Flask = create_app({
"SECRET_KEY": token_urlsafe(32),
"TESTING": True
})
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=("my_name", "my_pass"))
self.assertEqual(response.status_code, 200)
For pytest example:
::
@pytest.fixture()
def app():
app: Flask = create_app({
"SECRET_KEY": token_urlsafe(32),
"TESTING": True
})
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 = self.client.get("/admin")
assert response.status_code == 401
response = self.client.get(
"/admin", digest_auth=("my_name", "my_pass"))
assert response.status_code == 200
"""
"""The test client with HTTP digest authentication enabled."""
def open(self, *args, digest_auth: t.Optional[t.Tuple[str, str]] = None,
**kwargs) -> TestResponse: