Compare commits
	
		
			32 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 4f30756dc5 | |||
| cdc057f851 | |||
| 574ecade05 | |||
| 84b9c5f62e | |||
| 4990de085c | |||
| 51e51ae4e2 | |||
| 2de770aed0 | |||
| 9ab413d583 | |||
| aeb93a60e5 | |||
| a07118ef9c | |||
| 514e9255aa | |||
| 79abdc9cde | |||
| 038e7a8352 | |||
| 0387abb4f6 | |||
| 10e8add9e6 | |||
| c004e28c37 | |||
| 46f05a1022 | |||
| b9384150b7 | |||
| 4296756ae7 | |||
| 83cf83a67c | |||
| 7aaa7b9abe | |||
| 14b90de059 | |||
| e9013017fb | |||
| 6e6ac8bbe7 | |||
| 7ec56ee52d | |||
| b348c872dc | |||
| d78093ab53 | |||
| c6b8569543 | |||
| a1c48beb32 | |||
| a88fdf81fa | |||
| 4993bfdd9b | |||
| 696f350a44 | 
							
								
								
									
										54
									
								
								README.rst
									
									
									
									
									
								
							
							
						
						
									
										54
									
								
								README.rst
									
									
									
									
									
								
							| @@ -6,13 +6,13 @@ Flask HTTP Digest Authentication | |||||||
| Description | Description | ||||||
| =========== | =========== | ||||||
|  |  | ||||||
| *Flask-Digest-Auth* is an `HTTP Digest Authentication`_ implementation | *Flask-DigestAuth* is an `HTTP Digest Authentication`_ implementation | ||||||
| for Flask_ applications.  It authenticates the user for the protected | for Flask_ applications.  It authenticates the user for the protected | ||||||
| views. | views. | ||||||
|  |  | ||||||
| HTTP Digest Authentication is specified in `RFC 2617`_. | HTTP Digest Authentication is specified in `RFC 2617`_. | ||||||
|  |  | ||||||
| Refer to the full `Flask-Digest-Auth readthedocs documentation`_. | Refer to the full `Flask-DigestAuth readthedocs documentation`_. | ||||||
|  |  | ||||||
|  |  | ||||||
| Why HTTP Digest Authentication? | Why HTTP Digest Authentication? | ||||||
| @@ -30,7 +30,7 @@ own challenge-response log in form, but then you are reinventing the | |||||||
| wheels.  If a pretty log in form is not critical to your project, HTTP | wheels.  If a pretty log in form is not critical to your project, HTTP | ||||||
| Digest Authentication should be a good choice. | Digest Authentication should be a good choice. | ||||||
|  |  | ||||||
| Flask-Digest-Auth works with Flask-Login_.  Log in protection can be | Flask-DigestAuth works with Flask-Login_.  Log in protection can be | ||||||
| separated with the authentication mechanism.  You can create protected | separated with the authentication mechanism.  You can create protected | ||||||
| Flask modules without knowing the actual authentication mechanisms. | Flask modules without knowing the actual authentication mechanisms. | ||||||
|  |  | ||||||
| @@ -38,18 +38,18 @@ Flask modules without knowing the actual authentication mechanisms. | |||||||
| Installation | Installation | ||||||
| ============ | ============ | ||||||
|  |  | ||||||
| You can install Flask-Digest-Auth with ``pip``: | You can install Flask-DigestAuth with ``pip``: | ||||||
|  |  | ||||||
| :: | :: | ||||||
|  |  | ||||||
|     pip install Flask-Digest-Auth |     pip install Flask-DigestAuth | ||||||
|  |  | ||||||
| You may also install the latest source from the | You may also install the latest source from the | ||||||
| `Flask-Digest-Auth GitHub repository`_. | `Flask-DigestAuth GitHub repository`_. | ||||||
|  |  | ||||||
| :: | :: | ||||||
|  |  | ||||||
|     pip install git+https://github.com/imacat/flask-digest-auth.git |     pip install git+https://github.com/imacat/flask-digestauth.git | ||||||
|  |  | ||||||
|  |  | ||||||
| Setting the Password | Setting the Password | ||||||
| @@ -70,14 +70,14 @@ you need to ask their password, to generate and store the new password | |||||||
| hash. | hash. | ||||||
|  |  | ||||||
|  |  | ||||||
| Flask-Digest-Auth Alone | Flask-DigestAuth Alone | ||||||
| ======================= | ====================== | ||||||
|  |  | ||||||
| Flask-Digest-Auth can authenticate the users alone. | Flask-DigestAuth can authenticate the users alone. | ||||||
|  |  | ||||||
|  |  | ||||||
| Simple Applications with Flask-Digest-Auth Alone | Simple Applications with Flask-DigestAuth Alone | ||||||
| ------------------------------------------------ | ----------------------------------------------- | ||||||
|  |  | ||||||
| In your ``my_app.py``: | In your ``my_app.py``: | ||||||
|  |  | ||||||
| @@ -112,8 +112,8 @@ In your ``my_app.py``: | |||||||
|         return redirect(request.form.get("next")) |         return redirect(request.form.get("next")) | ||||||
|  |  | ||||||
|  |  | ||||||
| Larger Applications with ``create_app()`` with Flask-Digest-Auth Alone | Larger Applications with ``create_app()`` with Flask-DigestAuth Alone | ||||||
| ---------------------------------------------------------------------- | --------------------------------------------------------------------- | ||||||
|  |  | ||||||
| In your ``my_app/__init__.py``: | In your ``my_app/__init__.py``: | ||||||
|  |  | ||||||
| @@ -169,19 +169,19 @@ In your ``my_app/views.py``: | |||||||
| Flask-Login Integration | Flask-Login Integration | ||||||
| ======================= | ======================= | ||||||
|  |  | ||||||
| Flask-Digest-Auth works with Flask-Login_.  You can write a Flask | Flask-DigestAuth works with Flask-Login_.  You can write a Flask | ||||||
| module that requires log in, without specifying how to log in.  The | module that requires log in, without specifying how to log in.  The | ||||||
| application can use either HTTP Digest Authentication, or the log in | application can use either HTTP Digest Authentication, or the log in | ||||||
| forms, as needed. | forms, as needed. | ||||||
|  |  | ||||||
| To use Flask-Login with Flask-Digest-Auth, | To use Flask-Login with Flask-DigestAuth, | ||||||
| ``login_manager.init_app(app)`` must be called before | ``login_manager.init_app(app)`` must be called before | ||||||
| ``auth.init_app(app)``. | ``auth.init_app(app)``. | ||||||
|  |  | ||||||
| The currently logged-in user can be retrieved at | The currently logged-in user can be retrieved at | ||||||
| ``flask_login.current_user``, if any. | ``flask_login.current_user``, if any. | ||||||
|  |  | ||||||
| The views only depend on Flask-Login, but not the Flask-Digest-Auth. | The views only depend on Flask-Login, but not the Flask-DigestAuth. | ||||||
| You can change the actual authentication mechanism without changing | You can change the actual authentication mechanism without changing | ||||||
| the views. | the views. | ||||||
|  |  | ||||||
| @@ -293,7 +293,7 @@ mechanism without changing the views. | |||||||
| Session Integration | Session Integration | ||||||
| =================== | =================== | ||||||
|  |  | ||||||
| Flask-Digest-Auth features session integration.  The user log in | Flask-DigestAuth features session integration.  The user log in | ||||||
| is remembered in the session.  The authentication information is not | is remembered in the session.  The authentication information is not | ||||||
| requested again.  This is different to the practice of the HTTP Digest | requested again.  This is different to the practice of the HTTP Digest | ||||||
| Authentication, but is convenient for the log in accounting. | Authentication, but is convenient for the log in accounting. | ||||||
| @@ -315,14 +315,14 @@ logging the log in event, adding the log in counter, etc. | |||||||
| Log Out | Log Out | ||||||
| ======= | ======= | ||||||
|  |  | ||||||
| Flask-Digest-Auth supports log out.  The user will be prompted for the | Flask-DigestAuth supports log out.  The user will be prompted for the | ||||||
| new username and password. | new username and password. | ||||||
|  |  | ||||||
|  |  | ||||||
| Test Client | Test Client | ||||||
| =========== | =========== | ||||||
|  |  | ||||||
| Flask-Digest-Auth comes with a test client that supports HTTP digest | Flask-DigestAuth comes with a test client that supports HTTP digest | ||||||
| authentication. | authentication. | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -350,7 +350,7 @@ A unittest Test Case | |||||||
|             response = self.client.get("/admin") |             response = self.client.get("/admin") | ||||||
|             self.assertEqual(response.status_code, 401) |             self.assertEqual(response.status_code, 401) | ||||||
|             response = self.client.get( |             response = self.client.get( | ||||||
|                 "/admin", digest_auth=("my_name", "my_pass")) |                 "/admin", digest_auth=(USERNAME, PASSWORD)) | ||||||
|             self.assertEqual(response.status_code, 200) |             self.assertEqual(response.status_code, 200) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -379,17 +379,17 @@ A pytest Test | |||||||
|  |  | ||||||
|     def test_admin(app: Flask, client: Client): |     def test_admin(app: Flask, client: Client): | ||||||
|         with app.app_context(): |         with app.app_context(): | ||||||
|             response = self.client.get("/admin") |             response = client.get("/admin") | ||||||
|             assert response.status_code == 401 |             assert response.status_code == 401 | ||||||
|             response = self.client.get( |             response = client.get( | ||||||
|                 "/admin", digest_auth=("my_name", "my_pass")) |                 "/admin", digest_auth=(USERNAME, PASSWORD)) | ||||||
|             assert response.status_code == 200 |             assert response.status_code == 200 | ||||||
|  |  | ||||||
|  |  | ||||||
| Copyright | Copyright | ||||||
| ========= | ========= | ||||||
|  |  | ||||||
|  Copyright (c) 2022 imacat. |  Copyright (c) 2022-2023 imacat. | ||||||
|  |  | ||||||
|  Licensed under the Apache License, Version 2.0 (the "License"); |  Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  you may not use this file except in compliance with the License. |  you may not use this file except in compliance with the License. | ||||||
| @@ -414,6 +414,6 @@ Authors | |||||||
| .. _HTTP Digest Authentication: https://en.wikipedia.org/wiki/Digest_access_authentication | .. _HTTP Digest Authentication: https://en.wikipedia.org/wiki/Digest_access_authentication | ||||||
| .. _RFC 2617: https://www.rfc-editor.org/rfc/rfc2617 | .. _RFC 2617: https://www.rfc-editor.org/rfc/rfc2617 | ||||||
| .. _Flask: https://flask.palletsprojects.com | .. _Flask: https://flask.palletsprojects.com | ||||||
| .. _Flask-Digest-Auth GitHub repository: https://github.com/imacat/flask-digest-auth | .. _Flask-DigestAuth GitHub repository: https://github.com/imacat/flask-digestauth | ||||||
| .. _Flask-Digest-Auth readthedocs documentation: https://flask-digest-auth.readthedocs.io | .. _Flask-DigestAuth readthedocs documentation: https://flask-digestauth.readthedocs.io | ||||||
| .. _Flask-Login: https://flask-login.readthedocs.io | .. _Flask-Login: https://flask-login.readthedocs.io | ||||||
|   | |||||||
| @@ -10,8 +10,6 @@ if "%SPHINXBUILD%" == "" ( | |||||||
| set SOURCEDIR=source | set SOURCEDIR=source | ||||||
| set BUILDDIR=build | set BUILDDIR=build | ||||||
|  |  | ||||||
| if "%1" == "" goto help |  | ||||||
|  |  | ||||||
| %SPHINXBUILD% >NUL 2>NUL | %SPHINXBUILD% >NUL 2>NUL | ||||||
| if errorlevel 9009 ( | if errorlevel 9009 ( | ||||||
| 	echo. | 	echo. | ||||||
| @@ -21,10 +19,12 @@ if errorlevel 9009 ( | |||||||
| 	echo.may add the Sphinx directory to PATH. | 	echo.may add the Sphinx directory to PATH. | ||||||
| 	echo. | 	echo. | ||||||
| 	echo.If you don't have Sphinx installed, grab it from | 	echo.If you don't have Sphinx installed, grab it from | ||||||
| 	echo.http://sphinx-doc.org/ | 	echo.https://www.sphinx-doc.org/ | ||||||
| 	exit /b 1 | 	exit /b 1 | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | if "%1" == "" goto help | ||||||
|  |  | ||||||
| %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% | ||||||
| goto end | goto end | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								docs/requirements.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								docs/requirements.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | flask | ||||||
| @@ -1,59 +1,32 @@ | |||||||
| # Configuration file for the Sphinx documentation builder. | # Configuration file for the Sphinx documentation builder. | ||||||
| # | # | ||||||
| # This file only contains a selection of the most common options. For a full | # For the full list of built-in configuration values, see the documentation: | ||||||
| # list see the documentation: |  | ||||||
| # https://www.sphinx-doc.org/en/master/usage/configuration.html | # https://www.sphinx-doc.org/en/master/usage/configuration.html | ||||||
| import os | import os | ||||||
| # -- Path setup -------------------------------------------------------------- |  | ||||||
|  |  | ||||||
| # If extensions (or modules to document with autodoc) are in another directory, |  | ||||||
| # add these directories to sys.path here. If the directory is relative to the |  | ||||||
| # documentation root, use os.path.abspath to make it absolute, like shown here. |  | ||||||
| # |  | ||||||
| import sys | import sys | ||||||
|  |  | ||||||
| sys.path.insert(0, os.path.abspath('../../src/')) | sys.path.insert(0, os.path.abspath('../../src/')) | ||||||
|  |  | ||||||
|  |  | ||||||
| # -- Project information ----------------------------------------------------- | # -- Project information ----------------------------------------------------- | ||||||
|  | # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information | ||||||
|  |  | ||||||
| project = 'Flask-Digest-Auth' | project = 'Flask-DigestAuth' | ||||||
| copyright = '2022, imacat' | copyright = '2022-2023, imacat' | ||||||
| author = 'imacat' | author = 'imacat' | ||||||
|  | release = '0.4.0' | ||||||
| # The full version, including alpha/beta/rc tags |  | ||||||
| release = '0.2.1' |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # -- General configuration --------------------------------------------------- | # -- General configuration --------------------------------------------------- | ||||||
|  | # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration | ||||||
|  |  | ||||||
| # Add any Sphinx extension module names here, as strings. They can be | extensions = ["sphinx.ext.autodoc"] | ||||||
| # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom |  | ||||||
| # ones. |  | ||||||
| extensions = [ |  | ||||||
|     "sphinx.ext.autodoc" |  | ||||||
| ] |  | ||||||
|  |  | ||||||
| # Add any paths that contain templates here, relative to this directory. |  | ||||||
| templates_path = ['_templates'] | templates_path = ['_templates'] | ||||||
|  |  | ||||||
| # List of patterns, relative to source directory, that match files and |  | ||||||
| # directories to ignore when looking for source files. |  | ||||||
| # This pattern also affects html_static_path and html_extra_path. |  | ||||||
| exclude_patterns = [] | exclude_patterns = [] | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # -- Options for HTML output ------------------------------------------------- | # -- Options for HTML output ------------------------------------------------- | ||||||
|  | # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output | ||||||
|  |  | ||||||
| # The theme to use for HTML and HTML Help pages.  See the documentation for |  | ||||||
| # a list of builtin themes. |  | ||||||
| # |  | ||||||
| html_theme = 'sphinx_rtd_theme' | html_theme = 'sphinx_rtd_theme' | ||||||
|  |  | ||||||
| # Add any paths that contain custom static files (such as style sheets) here, |  | ||||||
| # relative to this directory. They are copied after the builtin static files, |  | ||||||
| # so a file named "default.css" will overwrite the builtin "default.css". |  | ||||||
| html_static_path = ['_static'] | html_static_path = ['_static'] | ||||||
|  |  | ||||||
| # For readthedocs.io to work properly. |  | ||||||
| master_doc = 'index' |  | ||||||
|   | |||||||
| @@ -4,8 +4,8 @@ Examples | |||||||
|  |  | ||||||
| .. _example-alone-simple: | .. _example-alone-simple: | ||||||
|  |  | ||||||
| Simple Applications with Flask-Digest-Auth Alone | Simple Applications with Flask-DigestAuth Alone | ||||||
| ------------------------------------------------ | ----------------------------------------------- | ||||||
|  |  | ||||||
| In your ``my_app.py``: | In your ``my_app.py``: | ||||||
|  |  | ||||||
| @@ -42,8 +42,8 @@ In your ``my_app.py``: | |||||||
|  |  | ||||||
| .. _example-alone-large: | .. _example-alone-large: | ||||||
|  |  | ||||||
| Larger Applications with ``create_app()`` with Flask-Digest-Auth Alone | Larger Applications with ``create_app()`` with Flask-DigestAuth Alone | ||||||
| ---------------------------------------------------------------------- | --------------------------------------------------------------------- | ||||||
|  |  | ||||||
| In your ``my_app/__init__.py``: | In your ``my_app/__init__.py``: | ||||||
|  |  | ||||||
| @@ -229,7 +229,7 @@ A unittest Test Case | |||||||
|             response = self.client.get("/admin") |             response = self.client.get("/admin") | ||||||
|             self.assertEqual(response.status_code, 401) |             self.assertEqual(response.status_code, 401) | ||||||
|             response = self.client.get( |             response = self.client.get( | ||||||
|                 "/admin", digest_auth=("my_name", "my_pass")) |                 "/admin", digest_auth=(USERNAME, PASSWORD)) | ||||||
|             self.assertEqual(response.status_code, 200) |             self.assertEqual(response.status_code, 200) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -261,8 +261,8 @@ A pytest Test | |||||||
|  |  | ||||||
|     def test_admin(app: Flask, client: Client): |     def test_admin(app: Flask, client: Client): | ||||||
|         with app.app_context(): |         with app.app_context(): | ||||||
|             response = self.client.get("/admin") |             response = client.get("/admin") | ||||||
|             assert response.status_code == 401 |             assert response.status_code == 401 | ||||||
|             response = self.client.get( |             response = client.get( | ||||||
|                 "/admin", digest_auth=("my_name", "my_pass")) |                 "/admin", digest_auth=(USERNAME, PASSWORD)) | ||||||
|             assert response.status_code == 200 |             assert response.status_code == 200 | ||||||
|   | |||||||
| @@ -1,24 +1,37 @@ | |||||||
| flask\_digest\_auth package | flask\_digest\_auth package | ||||||
| =========================== | =========================== | ||||||
|  |  | ||||||
| The ``DigestAuth`` Class | Submodules | ||||||
| ------------------------ | ---------- | ||||||
| .. autoclass:: flask_digest_auth.DigestAuth |  | ||||||
|     :members: |  | ||||||
|     :undoc-members: |  | ||||||
|     :show-inheritance: |  | ||||||
|  |  | ||||||
| The ``make_password_hash`` Function | flask\_digest\_auth.algo module | ||||||
| ----------------------------------- | ------------------------------- | ||||||
| .. autofunction:: flask_digest_auth.make_password_hash |  | ||||||
|  |  | ||||||
| The ``calc_response`` Function | .. automodule:: flask_digest_auth.algo | ||||||
| ------------------------------ |    :members: | ||||||
| .. autofunction:: flask_digest_auth.calc_response |    :undoc-members: | ||||||
|  |    :show-inheritance: | ||||||
|  |  | ||||||
| The ``Client`` Test Class | flask\_digest\_auth.auth module | ||||||
| ------------------------- | ------------------------------- | ||||||
| .. autoclass:: flask_digest_auth.Client |  | ||||||
|     :members: | .. automodule:: flask_digest_auth.auth | ||||||
|     :undoc-members: |    :members: | ||||||
|     :show-inheritance: |    :undoc-members: | ||||||
|  |    :show-inheritance: | ||||||
|  |  | ||||||
|  | flask\_digest\_auth.test module | ||||||
|  | ------------------------------- | ||||||
|  |  | ||||||
|  | .. automodule:: flask_digest_auth.test | ||||||
|  |    :members: | ||||||
|  |    :undoc-members: | ||||||
|  |    :show-inheritance: | ||||||
|  |  | ||||||
|  | Module contents | ||||||
|  | --------------- | ||||||
|  |  | ||||||
|  | .. automodule:: flask_digest_auth | ||||||
|  |    :members: | ||||||
|  |    :undoc-members: | ||||||
|  |    :show-inheritance: | ||||||
|   | |||||||
| @@ -1,12 +1,12 @@ | |||||||
| .. flask-digest-auth documentation master file, created by | .. Flask-DigestAuth documentation master file, created by | ||||||
|    sphinx-quickstart on Tue Dec  6 15:15:08 2022. |    sphinx-quickstart on Wed Dec  7 09:40:48 2022. | ||||||
|    You can adapt this file completely to your liking, but it should at least |    You can adapt this file completely to your liking, but it should at least | ||||||
|    contain the root `toctree` directive. |    contain the root `toctree` directive. | ||||||
|  |  | ||||||
| Welcome to Flask-Digest-Auth's documentation! | Welcome to Flask-DigestAuth's documentation! | ||||||
| ============================================= | ============================================ | ||||||
|  |  | ||||||
| *Flask-Digest-Auth* is an `HTTP Digest Authentication`_ implementation | *Flask-DigestAuth* is an `HTTP Digest Authentication`_ implementation | ||||||
| for Flask_ applications.  It authenticates the user for the protected | for Flask_ applications.  It authenticates the user for the protected | ||||||
| views. | views. | ||||||
|  |  | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ Introduction | |||||||
| ============ | ============ | ||||||
|  |  | ||||||
|  |  | ||||||
| *Flask-Digest-Auth* is an `HTTP Digest Authentication`_ implementation | *Flask-DigestAuth* is an `HTTP Digest Authentication`_ implementation | ||||||
| for Flask_ applications.  It authenticates the user for the protected | for Flask_ applications.  It authenticates the user for the protected | ||||||
| views. | views. | ||||||
|  |  | ||||||
| @@ -24,7 +24,7 @@ own challenge-response log in form, but then you are reinventing the | |||||||
| wheels.  If a pretty log in form is not critical to your project, HTTP | wheels.  If a pretty log in form is not critical to your project, HTTP | ||||||
| Digest Authentication should be a good choice. | Digest Authentication should be a good choice. | ||||||
|  |  | ||||||
| Flask-Digest-Auth works with Flask-Login_.  Log in protection can be | Flask-DigestAuth works with Flask-Login_.  Log in protection can be | ||||||
| separated with the authentication mechanism.  You can create protected | separated with the authentication mechanism.  You can create protected | ||||||
| Flask modules without knowing the actual authentication mechanisms. | Flask modules without knowing the actual authentication mechanisms. | ||||||
|  |  | ||||||
| @@ -32,18 +32,18 @@ Flask modules without knowing the actual authentication mechanisms. | |||||||
| Installation | Installation | ||||||
| ------------ | ------------ | ||||||
|  |  | ||||||
| You can install Flask-Digest-Auth with ``pip``: | You can install Flask-DigestAuth with ``pip``: | ||||||
|  |  | ||||||
| :: | :: | ||||||
|  |  | ||||||
|     pip install Flask-Digest-Auth |     pip install Flask-DigestAuth | ||||||
|  |  | ||||||
| You may also install the latest source from the | You may also install the latest source from the | ||||||
| `Flask-Digest-Auth GitHub repository`_. | `Flask-DigestAuth GitHub repository`_. | ||||||
|  |  | ||||||
| :: | :: | ||||||
|  |  | ||||||
|     pip install git+https://github.com/imacat/flask-digest-auth.git |     pip install git+https://github.com/imacat/flask-digestauth.git | ||||||
|  |  | ||||||
|  |  | ||||||
| Setting the Password | Setting the Password | ||||||
| @@ -63,13 +63,13 @@ 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 | you need to ask their password, to generate and store the new password | ||||||
| hash. | hash. | ||||||
|  |  | ||||||
| See :meth:`flask_digest_auth.make_password_hash`. | See :func:`flask_digest_auth.algo.make_password_hash`. | ||||||
|  |  | ||||||
|  |  | ||||||
| Flask-Digest-Auth Alone | Flask-DigestAuth Alone | ||||||
| ----------------------- | ---------------------- | ||||||
|  |  | ||||||
| Flask-Digest-Auth can authenticate the users alone. | Flask-DigestAuth can authenticate the users alone. | ||||||
|  |  | ||||||
| See :ref:`example-alone-simple` and :ref:`example-alone-large`. | See :ref:`example-alone-simple` and :ref:`example-alone-large`. | ||||||
|  |  | ||||||
| @@ -77,12 +77,12 @@ See :ref:`example-alone-simple` and :ref:`example-alone-large`. | |||||||
| Flask-Login Integration | Flask-Login Integration | ||||||
| ----------------------- | ----------------------- | ||||||
|  |  | ||||||
| Flask-Digest-Auth works with Flask-Login_.  You can write a Flask | Flask-DigestAuth works with Flask-Login_.  You can write a Flask | ||||||
| module that requires log in, without specifying how to log in.  The | module that requires log in, without specifying how to log in.  The | ||||||
| application can use either HTTP Digest Authentication, or the log in | application can use either HTTP Digest Authentication, or the log in | ||||||
| forms, as needed. | forms, as needed. | ||||||
|  |  | ||||||
| To use Flask-Login with Flask-Digest-Auth, | To use Flask-Login with Flask-DigestAuth, | ||||||
| ``login_manager.init_app(app)`` must be called before | ``login_manager.init_app(app)`` must be called before | ||||||
| ``auth.init_app(app)``. | ``auth.init_app(app)``. | ||||||
|  |  | ||||||
| @@ -92,7 +92,7 @@ The currently logged-in user can be retrieved at | |||||||
| See :ref:`example-flask-login-simple` and | See :ref:`example-flask-login-simple` and | ||||||
| :ref:`example-flask-login-large`. | :ref:`example-flask-login-large`. | ||||||
|  |  | ||||||
| The views only depend on Flask-Login, but not the Flask-Digest-Auth. | The views only depend on Flask-Login, but not the Flask-DigestAuth. | ||||||
| You can change the actual authentication mechanism without changing | You can change the actual authentication mechanism without changing | ||||||
| the views. | the views. | ||||||
|  |  | ||||||
| @@ -100,7 +100,7 @@ the views. | |||||||
| Session Integration | Session Integration | ||||||
| ------------------- | ------------------- | ||||||
|  |  | ||||||
| Flask-Digest-Auth features session integration.  The user log in | Flask-DigestAuth features session integration.  The user log in | ||||||
| is remembered in the session.  The authentication information is not | is remembered in the session.  The authentication information is not | ||||||
| requested again.  This is different to the practice of the HTTP Digest | requested again.  This is different to the practice of the HTTP Digest | ||||||
| Authentication, but is convenient for the log in accounting. | Authentication, but is convenient for the log in accounting. | ||||||
| @@ -118,25 +118,25 @@ logging the log in event, adding the log in counter, etc. | |||||||
|     def on_login(user: User) -> None: |     def on_login(user: User) -> None: | ||||||
|         user.visits = user.visits + 1 |         user.visits = user.visits + 1 | ||||||
|  |  | ||||||
| See :meth:`flask_digest_auth.DigestAuth.register_on_login`. | See :meth:`flask_digest_auth.auth.DigestAuth.register_on_login`. | ||||||
|  |  | ||||||
|  |  | ||||||
| Log Out | Log Out | ||||||
| ------- | ------- | ||||||
|  |  | ||||||
| Flask-Digest-Auth supports log out.  The user will be prompted for the | Flask-DigestAuth supports log out.  The user will be prompted for the | ||||||
| new username and password. | new username and password. | ||||||
|  |  | ||||||
| See :meth:`flask_digest_auth.DigestAuth.logout`. | See :meth:`flask_digest_auth.auth.DigestAuth.logout`. | ||||||
|  |  | ||||||
|  |  | ||||||
| Test Client | Test Client | ||||||
| ----------- | ----------- | ||||||
|  |  | ||||||
| Flask-Digest-Auth comes with a test client that supports HTTP digest | Flask-DigestAuth comes with a test client that supports HTTP digest | ||||||
| authentication. | authentication. | ||||||
|  |  | ||||||
| See :class:`flask_digest_auth.Client`. | See :class:`flask_digest_auth.test.Client`. | ||||||
|  |  | ||||||
| Also see :ref:`example-unittest` and :ref:`example-pytest`. | Also see :ref:`example-unittest` and :ref:`example-pytest`. | ||||||
|  |  | ||||||
| @@ -145,4 +145,4 @@ Also see :ref:`example-unittest` and :ref:`example-pytest`. | |||||||
| .. _RFC 2617: https://www.rfc-editor.org/rfc/rfc2617 | .. _RFC 2617: https://www.rfc-editor.org/rfc/rfc2617 | ||||||
| .. _Flask: https://flask.palletsprojects.com | .. _Flask: https://flask.palletsprojects.com | ||||||
| .. _Flask-Login: https://flask-login.readthedocs.io | .. _Flask-Login: https://flask-login.readthedocs.io | ||||||
| .. _Flask-Digest-Auth GitHub repository: https://github.com/imacat/flask-digest-auth | .. _Flask-DigestAuth GitHub repository: https://github.com/imacat/flask-digestauth | ||||||
|   | |||||||
							
								
								
									
										7
									
								
								docs/source/modules.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								docs/source/modules.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | |||||||
|  | src | ||||||
|  | === | ||||||
|  |  | ||||||
|  | .. toctree:: | ||||||
|  |    :maxdepth: 4 | ||||||
|  |  | ||||||
|  |    flask_digest_auth | ||||||
							
								
								
									
										10
									
								
								setup.cfg
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								setup.cfg
									
									
									
									
									
								
							| @@ -1,7 +1,7 @@ | |||||||
| # The Flask HTTP Digest Authentication Project. | # The Flask HTTP Digest Authentication Project. | ||||||
| # Author: imacat@mail.imacat.idv.tw (imacat), 2022/11/23 | # Author: imacat@mail.imacat.idv.tw (imacat), 2022/11/23 | ||||||
|  |  | ||||||
| #  Copyright (c) 2022 imacat. | #  Copyright (c) 2022-2023 imacat. | ||||||
| # | # | ||||||
| #  Licensed under the Apache License, Version 2.0 (the "License"); | #  Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
| #  you may not use this file except in compliance with the License. | #  you may not use this file except in compliance with the License. | ||||||
| @@ -16,16 +16,16 @@ | |||||||
| #  limitations under the License. | #  limitations under the License. | ||||||
|  |  | ||||||
| [metadata] | [metadata] | ||||||
| name = flask-digest-auth | name = Flask-DigestAuth | ||||||
| version = 0.2.2 | version = 0.4.0 | ||||||
| author = imacat | author = imacat | ||||||
| author_email = imacat@mail.imacat.idv.tw | author_email = imacat@mail.imacat.idv.tw | ||||||
| description = The Flask HTTP Digest Authentication project. | description = The Flask HTTP Digest Authentication project. | ||||||
| long_description = file: README.rst | long_description = file: README.rst | ||||||
| long_description_content_type = text/x-rst | long_description_content_type = text/x-rst | ||||||
| url = https://github.com/imacat/flask-digest-auth | url = https://github.com/imacat/flask-digestauth | ||||||
| project_urls = | project_urls = | ||||||
|     Bug Tracker = https://github.com/imacat/flask-digest-auth/issues |     Bug Tracker = https://github.com/imacat/flask-digestauth/issues | ||||||
| classifiers = | classifiers = | ||||||
|     Programming Language :: Python :: 3 |     Programming Language :: Python :: 3 | ||||||
|     License :: OSI Approved :: Apache Software License |     License :: OSI Approved :: Apache Software License | ||||||
|   | |||||||
| @@ -28,7 +28,7 @@ def make_password_hash(realm: str, username: str, password: str) -> str: | |||||||
|     """Calculates the password hash for the HTTP digest authentication. |     """Calculates the password hash for the HTTP digest authentication. | ||||||
|     Use this function to set the password for the user. |     Use this function to set the password for the user. | ||||||
|  |  | ||||||
|     For example: |     :Example: | ||||||
|  |  | ||||||
|     :: |     :: | ||||||
|  |  | ||||||
| @@ -54,7 +54,7 @@ def calc_response( | |||||||
|     :param uri: The request URI. |     :param uri: The request URI. | ||||||
|     :param password_hash: The password hash for the HTTP digest authentication. |     :param password_hash: The password hash for the HTTP digest authentication. | ||||||
|     :param nonce: The nonce. |     :param nonce: The nonce. | ||||||
|     :param qop: the quality of protection, either ``auth`` or ``auth-int``. |     :param qop: The quality of protection, either ``auth`` or ``auth-int``. | ||||||
|     :param algorithm: The algorithm, either ``MD5`` or ``MD5-sess``. |     :param algorithm: The algorithm, either ``MD5`` or ``MD5-sess``. | ||||||
|     :param cnonce: The client nonce, which must exists when qop exists or |     :param cnonce: The client nonce, which must exists when qop exists or | ||||||
|         algorithm is ``MD5-sess``. |         algorithm is ``MD5-sess``. | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| # The Flask HTTP Digest Authentication Project. | # The Flask HTTP Digest Authentication Project. | ||||||
| # Author: imacat@mail.imacat.idv.tw (imacat), 2022/10/22 | # Author: imacat@mail.imacat.idv.tw (imacat), 2022/10/22 | ||||||
|  |  | ||||||
| #  Copyright (c) 2022 imacat. | #  Copyright (c) 2022-2023 imacat. | ||||||
| # | # | ||||||
| #  Licensed under the Apache License, Version 2.0 (the "License"); | #  Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
| #  you may not use this file except in compliance with the License. | #  you may not use this file except in compliance with the License. | ||||||
| @@ -16,8 +16,9 @@ | |||||||
| #  limitations under the License. | #  limitations under the License. | ||||||
|  |  | ||||||
| """The HTTP Digest Authentication. | """The HTTP Digest Authentication. | ||||||
| See RFC 2617 HTTP Authentication: Basic and Digest Access Authentication | See `RFC 2617`_ HTTP Authentication: Basic and Digest Access Authentication | ||||||
|  |  | ||||||
|  | .. _RFC 2617: https://www.rfc-editor.org/rfc/rfc2617 | ||||||
| """ | """ | ||||||
| from __future__ import annotations | from __future__ import annotations | ||||||
|  |  | ||||||
| @@ -26,7 +27,8 @@ import typing as t | |||||||
| from functools import wraps | from functools import wraps | ||||||
| from secrets import token_urlsafe, randbits | from secrets import token_urlsafe, randbits | ||||||
|  |  | ||||||
| from flask import g, request, Response, session, abort, Flask, Request | from flask import g, request, Response, session, abort, Flask, Request, \ | ||||||
|  |     current_app | ||||||
| from itsdangerous import URLSafeTimedSerializer, BadData | from itsdangerous import URLSafeTimedSerializer, BadData | ||||||
| from werkzeug.datastructures import Authorization | from werkzeug.datastructures import Authorization | ||||||
|  |  | ||||||
| @@ -41,27 +43,39 @@ class DigestAuth: | |||||||
|  |  | ||||||
|         :param realm: The realm. |         :param realm: The realm. | ||||||
|         """ |         """ | ||||||
|         self.secret_key: str = token_urlsafe(32) |         self.__serializer: URLSafeTimedSerializer \ | ||||||
|         self.serializer: URLSafeTimedSerializer \ |             = URLSafeTimedSerializer(token_urlsafe(32)) | ||||||
|             = URLSafeTimedSerializer(self.secret_key) |         """The serializer to generate and validate the nonce and opaque.""" | ||||||
|         self.realm: str = "" if realm is None else realm |         self.realm: str = "" if realm is None else realm | ||||||
|         self.algorithm: t.Optional[str] = None |         """The realm.  Default is an empty string.""" | ||||||
|  |         self.algorithm: t.Optional[t.Literal["MD5", "MD5-sess"]] = None | ||||||
|  |         """The algorithm, either None, ``MD5``, or ``MD5-sess``.  Default is | ||||||
|  |         None.""" | ||||||
|         self.use_opaque: bool = True |         self.use_opaque: bool = True | ||||||
|         self.domain: t.List[str] = [] |         """Whether to use an opaque.  Default is True.""" | ||||||
|         self.qop: t.List[str] = ["auth", "auth-int"] |         self.__domain: t.List[str] = [] | ||||||
|         self.app: t.Optional[Flask] = None |         """A list of directories that this username and password applies to. | ||||||
|  |         Default is empty.""" | ||||||
|  |         self.__qop: t.List[t.Literal["auth", "auth-int"]] \ | ||||||
|  |             = ["auth", "auth-int"] | ||||||
|  |         """A list of supported quality of protection supported, either | ||||||
|  |         ``qop``, ``auth-int``, both, or empty.  Default is both.""" | ||||||
|         self.__get_password_hash: BasePasswordHashGetter \ |         self.__get_password_hash: BasePasswordHashGetter \ | ||||||
|             = BasePasswordHashGetter() |             = BasePasswordHashGetter() | ||||||
|  |         """The callback to return the password hash.""" | ||||||
|         self.__get_user: BaseUserGetter = BaseUserGetter() |         self.__get_user: BaseUserGetter = BaseUserGetter() | ||||||
|  |         """The callback to return the user.""" | ||||||
|         self.__on_login: BaseOnLogInCallback = BaseOnLogInCallback() |         self.__on_login: BaseOnLogInCallback = BaseOnLogInCallback() | ||||||
|  |         """The callback to run when the user logs in.""" | ||||||
|  |  | ||||||
|     def login_required(self, view) -> t.Callable: |     def login_required(self, view) -> t.Callable: | ||||||
|         """The view decorator for HTTP digest authentication. |         """The view decorator for the HTTP digest authentication. | ||||||
|  |  | ||||||
|         For example: |         :Example: | ||||||
|  |  | ||||||
|         :: |         :: | ||||||
|  |  | ||||||
|  |             @app.get("/admin") | ||||||
|             @auth.login_required |             @auth.login_required | ||||||
|             def admin(): |             def admin(): | ||||||
|                 return f"Hello, {g.user.username}!" |                 return f"Hello, {g.user.username}!" | ||||||
| @@ -152,7 +166,7 @@ class DigestAuth: | |||||||
|                 raise UnauthorizedException( |                 raise UnauthorizedException( | ||||||
|                     "Missing \"opaque\" in the Authorization header") |                     "Missing \"opaque\" in the Authorization header") | ||||||
|             try: |             try: | ||||||
|                 self.serializer.loads( |                 self.__serializer.loads( | ||||||
|                     authorization.opaque, salt="opaque", max_age=1800) |                     authorization.opaque, salt="opaque", max_age=1800) | ||||||
|             except BadData: |             except BadData: | ||||||
|                 raise UnauthorizedException("Invalid opaque") |                 raise UnauthorizedException("Invalid opaque") | ||||||
| @@ -173,7 +187,7 @@ class DigestAuth: | |||||||
|             state.stale = False |             state.stale = False | ||||||
|             raise UnauthorizedException("Incorrect response value") |             raise UnauthorizedException("Incorrect response value") | ||||||
|         try: |         try: | ||||||
|             self.serializer.loads( |             self.__serializer.loads( | ||||||
|                 authorization.nonce, |                 authorization.nonce, | ||||||
|                 salt="nonce" if authorization.opaque is None |                 salt="nonce" if authorization.opaque is None | ||||||
|                 else f"nonce-{authorization.opaque}") |                 else f"nonce-{authorization.opaque}") | ||||||
| @@ -197,16 +211,16 @@ class DigestAuth: | |||||||
|                 return None |                 return None | ||||||
|             if state.opaque is not None: |             if state.opaque is not None: | ||||||
|                 return state.opaque |                 return state.opaque | ||||||
|             return self.serializer.dumps(randbits(32), salt="opaque") |             return self.__serializer.dumps(randbits(32), salt="opaque") | ||||||
|  |  | ||||||
|         opaque: t.Optional[str] = get_opaque() |         opaque: t.Optional[str] = get_opaque() | ||||||
|         nonce: str = self.serializer.dumps( |         nonce: str = self.__serializer.dumps( | ||||||
|             randbits(32), |             randbits(32), | ||||||
|             salt="nonce" if opaque is None else f"nonce-{opaque}") |             salt="nonce" if opaque is None else f"nonce-{opaque}") | ||||||
|  |  | ||||||
|         header: str = f"Digest realm=\"{self.realm}\"" |         header: str = f"Digest realm=\"{self.realm}\"" | ||||||
|         if len(self.domain) > 0: |         if len(self.__domain) > 0: | ||||||
|             domain_list: str = ",".join(self.domain) |             domain_list: str = ",".join(self.__domain) | ||||||
|             header += f", domain=\"{domain_list}\"" |             header += f", domain=\"{domain_list}\"" | ||||||
|         header += f", nonce=\"{nonce}\"" |         header += f", nonce=\"{nonce}\"" | ||||||
|         if opaque is not None: |         if opaque is not None: | ||||||
| @@ -215,8 +229,8 @@ class DigestAuth: | |||||||
|             header += f", stale=TRUE" if state.stale else f", stale=FALSE" |             header += f", stale=TRUE" if state.stale else f", stale=FALSE" | ||||||
|         if self.algorithm is not None: |         if self.algorithm is not None: | ||||||
|             header += f", algorithm=\"{self.algorithm}\"" |             header += f", algorithm=\"{self.algorithm}\"" | ||||||
|         if len(self.qop) > 0: |         if len(self.__qop) > 0: | ||||||
|             qop_list: str = ",".join(self.qop) |             qop_list: str = ",".join(self.__qop) | ||||||
|             header += f", qop=\"{qop_list}\"" |             header += f", qop=\"{qop_list}\"" | ||||||
|         return header |         return header | ||||||
|  |  | ||||||
| @@ -224,7 +238,7 @@ class DigestAuth: | |||||||
|             -> None: |             -> None: | ||||||
|         """The decorator to register the callback to obtain the password hash. |         """The decorator to register the callback to obtain the password hash. | ||||||
|  |  | ||||||
|         For example: |         :Example: | ||||||
|  |  | ||||||
|         :: |         :: | ||||||
|  |  | ||||||
| @@ -256,7 +270,7 @@ class DigestAuth: | |||||||
|             -> None: |             -> None: | ||||||
|         """The decorator to register the callback to obtain the user. |         """The decorator to register the callback to obtain the user. | ||||||
|  |  | ||||||
|         For example: |         :Example: | ||||||
|  |  | ||||||
|         :: |         :: | ||||||
|  |  | ||||||
| @@ -286,7 +300,7 @@ class DigestAuth: | |||||||
|     def register_on_login(self, func: t.Callable[[t.Any], None]) -> None: |     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. |         """The decorator to register the callback to run when the user logs in. | ||||||
|  |  | ||||||
|         For example: |         :Example: | ||||||
|  |  | ||||||
|         :: |         :: | ||||||
|  |  | ||||||
| @@ -313,9 +327,10 @@ class DigestAuth: | |||||||
|         self.__on_login = OnLogInCallback() |         self.__on_login = OnLogInCallback() | ||||||
|  |  | ||||||
|     def init_app(self, app: Flask) -> None: |     def init_app(self, app: Flask) -> None: | ||||||
|         """Initializes the Flask application. |         """Initializes the Flask application.  The DigestAuth instance will | ||||||
|  |         be stored in ``app.extensions["digest_auth"]``. | ||||||
|  |  | ||||||
|         For example: |         :Example: | ||||||
|  |  | ||||||
|         :: |         :: | ||||||
|  |  | ||||||
| @@ -327,8 +342,7 @@ class DigestAuth: | |||||||
|         :param app: The Flask application. |         :param app: The Flask application. | ||||||
|         :return: None. |         :return: None. | ||||||
|         """ |         """ | ||||||
|         app.digest_auth = self |         app.extensions["digest_auth"] = self | ||||||
|         self.app = app |  | ||||||
|  |  | ||||||
|         if hasattr(app, "login_manager"): |         if hasattr(app, "login_manager"): | ||||||
|             from flask_login import LoginManager, login_user |             from flask_login import LoginManager, login_user | ||||||
| @@ -341,10 +355,13 @@ class DigestAuth: | |||||||
|  |  | ||||||
|                 :return: None. |                 :return: None. | ||||||
|                 """ |                 """ | ||||||
|  |                 state: AuthState = getattr(request, "_digest_auth_state") \ | ||||||
|  |                     if hasattr(request, "_digest_auth_state") \ | ||||||
|  |                     else AuthState() | ||||||
|                 response: Response = Response() |                 response: Response = Response() | ||||||
|                 response.status = 401 |                 response.status = 401 | ||||||
|                 response.headers["WWW-Authenticate"] \ |                 response.headers["WWW-Authenticate"] \ | ||||||
|                     = self.__make_response_header(g.digest_auth_state) |                     = self.__make_response_header(state) | ||||||
|                 abort(response) |                 abort(response) | ||||||
|  |  | ||||||
|             @login_manager.request_loader |             @login_manager.request_loader | ||||||
| @@ -355,7 +372,7 @@ class DigestAuth: | |||||||
|                 :return: The authenticated user, or None if the |                 :return: The authenticated user, or None if the | ||||||
|                     authentication fails |                     authentication fails | ||||||
|                 """ |                 """ | ||||||
|                 g.digest_auth_state = AuthState() |                 request._digest_auth_state = AuthState() | ||||||
|                 authorization: Authorization = req.authorization |                 authorization: Authorization = req.authorization | ||||||
|                 try: |                 try: | ||||||
|                     if authorization is None: |                     if authorization is None: | ||||||
| @@ -363,7 +380,7 @@ class DigestAuth: | |||||||
|                     if authorization.type != "digest": |                     if authorization.type != "digest": | ||||||
|                         raise UnauthorizedException( |                         raise UnauthorizedException( | ||||||
|                             "Not an HTTP digest authorization") |                             "Not an HTTP digest authorization") | ||||||
|                     self.__authenticate(g.digest_auth_state) |                     self.__authenticate(request._digest_auth_state) | ||||||
|                     user = login_manager.user_callback( |                     user = login_manager.user_callback( | ||||||
|                         authorization.username) |                         authorization.username) | ||||||
|                     login_user(user) |                     login_user(user) | ||||||
| @@ -379,7 +396,7 @@ class DigestAuth: | |||||||
|         This actually causes the next authentication to fail, which forces |         This actually causes the next authentication to fail, which forces | ||||||
|         the browser to ask the user for the username and password again. |         the browser to ask the user for the username and password again. | ||||||
|  |  | ||||||
|         For example: |         :Example: | ||||||
|  |  | ||||||
|         :: |         :: | ||||||
|  |  | ||||||
| @@ -394,7 +411,7 @@ class DigestAuth: | |||||||
|         if "user" in session: |         if "user" in session: | ||||||
|             del session["user"] |             del session["user"] | ||||||
|         try: |         try: | ||||||
|             if hasattr(self.app, "login_manager"): |             if hasattr(current_app, "login_manager"): | ||||||
|                 from flask_login import logout_user |                 from flask_login import logout_user | ||||||
|                 logout_user() |                 logout_user() | ||||||
|         except ModuleNotFoundError: |         except ModuleNotFoundError: | ||||||
| @@ -403,21 +420,30 @@ class DigestAuth: | |||||||
|  |  | ||||||
|  |  | ||||||
| class AuthState: | class AuthState: | ||||||
|     """The authorization state.""" |     """The authentication state.  It keeps the status in the earlier | ||||||
|  |     authentication stage, so that the latter response stage knows how to | ||||||
|  |     response. | ||||||
|  |     """ | ||||||
|  |  | ||||||
|     def __init__(self): |     def __init__(self): | ||||||
|         """Constructs the authorization state.""" |         """Constructs the authorization state.""" | ||||||
|         self.opaque: t.Optional[str] = None |         self.opaque: t.Optional[str] = None | ||||||
|  |         """The opaque value specified by the client, if valid.""" | ||||||
|         self.stale: t.Optional[bool] = None |         self.stale: t.Optional[bool] = None | ||||||
|  |         """The stale value, if there is a previous log in attempt.""" | ||||||
|  |  | ||||||
|  |  | ||||||
| class UnauthorizedException(Exception): | class UnauthorizedException(Exception): | ||||||
|     """The exception thrown when the authentication is failed.""" |     """The exception thrown when the authentication fails.""" | ||||||
|     pass |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class BasePasswordHashGetter: | class BasePasswordHashGetter: | ||||||
|     """The base password hash getter.""" |     """The base callback that given the username, returns the password hash, | ||||||
|  |     or None if the user does not exist.  The default is to raise an | ||||||
|  |     :class:`UnboundLocalError` if the callback is not registered yet. | ||||||
|  |  | ||||||
|  |     See :meth:`flask_digest_auth.auth.DigestAuth.register_get_password` | ||||||
|  |     """ | ||||||
|  |  | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     def __call__(username: str) -> t.Optional[str]: |     def __call__(username: str) -> t.Optional[str]: | ||||||
| @@ -433,7 +459,12 @@ class BasePasswordHashGetter: | |||||||
|  |  | ||||||
|  |  | ||||||
| class BaseUserGetter: | class BaseUserGetter: | ||||||
|     """The base user getter.""" |     """The base callback that given the username, returns the user, or None if | ||||||
|  |     the user does not exist.  The default is to raise an | ||||||
|  |     :class:`UnboundLocalError` if the callback is not registered yet. | ||||||
|  |  | ||||||
|  |     See :meth:`flask_digest_auth.auth.DigestAuth.register_get_user` | ||||||
|  |     """ | ||||||
|  |  | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     def __call__(username: str) -> t.Optional[t.Any]: |     def __call__(username: str) -> t.Optional[t.Any]: | ||||||
| @@ -449,7 +480,11 @@ class BaseUserGetter: | |||||||
|  |  | ||||||
|  |  | ||||||
| class BaseOnLogInCallback: | class BaseOnLogInCallback: | ||||||
|     """The base callback when the user logs in.""" |     """The base callback to run when the user logs in, given the logged-in | ||||||
|  |     user.  The default does nothing. | ||||||
|  |  | ||||||
|  |     See :meth:`flask_digest_auth.auth.DigestAuth.register_on_login` | ||||||
|  |     """ | ||||||
|  |  | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     def __call__(user: t.Any) -> None: |     def __call__(user: t.Any) -> None: | ||||||
|   | |||||||
| @@ -31,7 +31,9 @@ from flask_digest_auth.algo import calc_response, make_password_hash | |||||||
| class Client(WerkzeugClient): | class Client(WerkzeugClient): | ||||||
|     """The test client with HTTP digest authentication enabled. |     """The test client with HTTP digest authentication enabled. | ||||||
|  |  | ||||||
|     For unittest example: |     :Example: | ||||||
|  |  | ||||||
|  |     For unittest_: | ||||||
|  |  | ||||||
|     :: |     :: | ||||||
|  |  | ||||||
| @@ -49,10 +51,10 @@ class Client(WerkzeugClient): | |||||||
|                 response = self.client.get("/admin") |                 response = self.client.get("/admin") | ||||||
|                 self.assertEqual(response.status_code, 401) |                 self.assertEqual(response.status_code, 401) | ||||||
|                 response = self.client.get( |                 response = self.client.get( | ||||||
|                     "/admin", digest_auth=("my_name", "my_pass")) |                     "/admin", digest_auth=(USERNAME, PASSWORD)) | ||||||
|                 self.assertEqual(response.status_code, 200) |                 self.assertEqual(response.status_code, 200) | ||||||
|  |  | ||||||
|     For pytest example: |     For pytest_: | ||||||
|  |  | ||||||
|     :: |     :: | ||||||
|  |  | ||||||
| @@ -71,11 +73,14 @@ class Client(WerkzeugClient): | |||||||
|  |  | ||||||
|         def test_admin(app: Flask, client: Client): |         def test_admin(app: Flask, client: Client): | ||||||
|             with app.app_context(): |             with app.app_context(): | ||||||
|                 response = self.client.get("/admin") |                 response = client.get("/admin") | ||||||
|                 assert response.status_code == 401 |                 assert response.status_code == 401 | ||||||
|                 response = self.client.get( |                 response = client.get( | ||||||
|                     "/admin", digest_auth=("my_name", "my_pass")) |                     "/admin", digest_auth=(USERNAME, PASSWORD)) | ||||||
|                 assert response.status_code == 200 |                 assert response.status_code == 200 | ||||||
|  |  | ||||||
|  |     .. _unittest: https://docs.python.org/3/library/unittest.html | ||||||
|  |     .. _pytest: https://pytest.org | ||||||
|     """ |     """ | ||||||
|  |  | ||||||
|     def open(self, *args, digest_auth: t.Optional[t.Tuple[str, str]] = None, |     def open(self, *args, digest_auth: t.Optional[t.Tuple[str, str]] = None, | ||||||
| @@ -83,7 +88,7 @@ class Client(WerkzeugClient): | |||||||
|         """Opens a request. |         """Opens a request. | ||||||
|  |  | ||||||
|         :param args: The arguments. |         :param args: The arguments. | ||||||
|         :param digest_auth: A tuple of the username and password for the HTTP |         :param digest_auth: The (*username*, *password*) tuple for the HTTP | ||||||
|             digest authentication. |             digest authentication. | ||||||
|         :param kwargs: The keyword arguments. |         :param kwargs: The keyword arguments. | ||||||
|         :return: The response. |         :return: The response. | ||||||
|   | |||||||
| @@ -45,7 +45,6 @@ class User: | |||||||
|         self.password_hash: str = make_password_hash( |         self.password_hash: str = make_password_hash( | ||||||
|             _REALM, username, password) |             _REALM, username, password) | ||||||
|         self.visits: int = 0 |         self.visits: int = 0 | ||||||
|         self.is_authenticated: bool = True |  | ||||||
|         self.is_active: bool = True |         self.is_active: bool = True | ||||||
|         self.is_anonymous: bool = False |         self.is_anonymous: bool = False | ||||||
|  |  | ||||||
| @@ -57,6 +56,16 @@ class User: | |||||||
|         """ |         """ | ||||||
|         return self.username |         return self.username | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def is_authenticated(self) -> bool: | ||||||
|  |         """Returns whether the user is authenticated. | ||||||
|  |         This is required by Flask-Login. | ||||||
|  |         This should return self.is_active. | ||||||
|  |  | ||||||
|  |         :return: True if the user is active, or False otherwise. | ||||||
|  |         """ | ||||||
|  |         return self.is_active | ||||||
|  |  | ||||||
|  |  | ||||||
| class FlaskLoginTestCase(TestCase): | class FlaskLoginTestCase(TestCase): | ||||||
|     """The test case with the Flask-Login integration.""" |     """The test case with the Flask-Login integration.""" | ||||||
| @@ -256,3 +265,34 @@ class FlaskLoginTestCase(TestCase): | |||||||
|         response = self.client.get(admin_uri) |         response = self.client.get(admin_uri) | ||||||
|         self.assertEqual(response.status_code, 200) |         self.assertEqual(response.status_code, 200) | ||||||
|         self.assertEqual(self.user.visits, 2) |         self.assertEqual(self.user.visits, 2) | ||||||
|  |  | ||||||
|  |     def test_disabled(self) -> None: | ||||||
|  |         """Tests the disabled user. | ||||||
|  |  | ||||||
|  |         :return: None. | ||||||
|  |         """ | ||||||
|  |         if not self.has_flask_login: | ||||||
|  |             self.skipTest("Skipped without Flask-Login.") | ||||||
|  |  | ||||||
|  |         response: Response | ||||||
|  |  | ||||||
|  |         self.user.is_active = False | ||||||
|  |         response = self.client.get(self.app.url_for("admin-1")) | ||||||
|  |         self.assertEqual(response.status_code, 401) | ||||||
|  |         response = self.client.get(self.app.url_for("admin-1"), | ||||||
|  |                                    digest_auth=(_USERNAME, _PASSWORD)) | ||||||
|  |         self.assertEqual(response.status_code, 401) | ||||||
|  |  | ||||||
|  |         self.user.is_active = True | ||||||
|  |         response = self.client.get(self.app.url_for("admin-1"), | ||||||
|  |                                    digest_auth=(_USERNAME, _PASSWORD)) | ||||||
|  |         self.assertEqual(response.status_code, 200) | ||||||
|  |         response = self.client.get(self.app.url_for("admin-1")) | ||||||
|  |         self.assertEqual(response.status_code, 200) | ||||||
|  |  | ||||||
|  |         self.user.is_active = False | ||||||
|  |         response = self.client.get(self.app.url_for("admin-1")) | ||||||
|  |         self.assertEqual(response.status_code, 401) | ||||||
|  |         response = self.client.get(self.app.url_for("admin-1"), | ||||||
|  |                                    digest_auth=(_USERNAME, _PASSWORD)) | ||||||
|  |         self.assertEqual(response.status_code, 401) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user