Added logging out.

This commit is contained in:
依瑪貓 2022-11-25 08:40:27 +11:00
parent dda8472a76
commit cb5cfaf7d4
4 changed files with 162 additions and 8 deletions

View File

@ -68,7 +68,7 @@ In your ``my_app.py``:
::
from flask import Flask
from flask import Flask, request, redirect
from flask_digest_auth import DigestAuth
app: flask = Flask(__name__)
@ -89,6 +89,12 @@ In your ``my_app.py``:
def admin():
... (Process the view) ...
@app.post("/logout")
@auth.login_required
def logout():
auth.logout()
return redirect(request.form.get("next"))
Example for Larger Applications with ``create_app()`` with Flask-Digest-Auth Alone
----------------------------------------------------------------------------------
@ -123,7 +129,7 @@ In your ``my_app/views.py``:
::
from my_app import auth
from flask import Flask, Blueprint
from flask import Flask, Blueprint, request, redirect
bp = Blueprint("admin", __name__, url_prefix="/admin")
@ -132,6 +138,12 @@ In your ``my_app/views.py``:
def admin():
... (Process the view) ...
@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)
@ -152,7 +164,7 @@ In your ``my_app.py``:
::
from flask import Flask
from flask import Flask, request, redirect
from flask_digest_auth import DigestAuth
from flask_login import LoginManager
@ -178,6 +190,13 @@ In your ``my_app.py``:
def admin():
... (Process the view) ...
@app.post("/logout")
@flask_login.login_required
def logout():
auth.logout()
# Do not call flask_login.logout_user()
return redirect(request.form.get("next"))
Example for Larger Applications with ``create_app()`` with Flask-Login Integration
----------------------------------------------------------------------------------
@ -190,6 +209,8 @@ In your ``my_app/__init__.py``:
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) ...
@ -201,7 +222,7 @@ In your ``my_app/__init__.py``:
def load_user(user_id: str) -> t.Optional[User]:
... (Load the user with the username) ...
auth: DigestAuth = DigestAuth(realm=app.config["REALM"])
auth.realm = app.config["REALM"]
auth.init_app(app)
@auth.register_get_password
@ -215,7 +236,8 @@ In your ``my_app/views.py``:
::
import flask_login
from flask import Flask, Blueprint
from flask import Flask, Blueprint, request, redirect
from my_app import auth
bp = Blueprint("admin", __name__, url_prefix="/admin")
@ -224,6 +246,13 @@ In your ``my_app/views.py``:
def admin():
... (Process the view) ...
@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)
@ -250,6 +279,15 @@ 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.
Writing Tests
=============

View File

@ -27,7 +27,8 @@ from functools import wraps
from random import random
from secrets import token_urlsafe
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 werkzeug.datastructures import Authorization
@ -112,6 +113,9 @@ class DigestAuth:
:return: None.
:raise UnauthorizedException: When the authentication failed.
"""
if "digest_auth_logout" in session:
del session["digest_auth_logout"]
raise UnauthorizedException("Logging out")
authorization: Authorization = request.authorization
if self.use_opaque:
if authorization.opaque is None:
@ -252,6 +256,24 @@ class DigestAuth:
raise ModuleNotFoundError(
"init_app() is only for Flask-Login integration")
@staticmethod
def logout() -> None:
"""Logs out the user.
This actually causes the next authentication to fail, which forces
the browser to ask the user for the username and password again.
:return: None.
"""
if "user" in session:
del session["user"]
try:
if hasattr(current_app, "login_manager"):
from flask_login import logout_user
logout_user()
except ModuleNotFoundError:
pass
session["digest_auth_logout"] = True
class AuthState:
"""The authorization state."""

View File

@ -22,7 +22,7 @@ import typing as t
from secrets import token_urlsafe
from types import SimpleNamespace
from flask import Response, Flask, g
from flask import Response, Flask, g, redirect, request
from flask_testing import TestCase
from werkzeug.datastructures import WWWAuthenticate, Authorization
@ -89,6 +89,16 @@ class AuthenticationTestCase(TestCase):
"""
return f"Hello, {g.user.username}! #2"
@app.post("/logout", endpoint="logout")
@auth.login_required
def logout() -> redirect:
"""Logs out the user.
:return: The response.
"""
auth.logout()
return redirect(request.form.get("next"))
return app
def test_auth(self) -> None:
@ -146,3 +156,40 @@ class AuthenticationTestCase(TestCase):
www_authenticate, admin_uri, _USERNAME, _PASSWORD)
response = super(Client, self.client).get(admin_uri, auth=auth_data)
self.assertEqual(response.status_code, 200)
def test_logout(self) -> None:
"""Tests the logging out.
:return: None.
"""
admin_uri: str = self.app.url_for("admin-1")
logout_uri: str = self.app.url_for("logout")
response: Response
response = self.client.get(admin_uri)
self.assertEqual(response.status_code, 401)
response = self.client.get(admin_uri,
digest_auth=(_USERNAME, _PASSWORD))
self.assertEqual(response.status_code, 200)
response = self.client.get(admin_uri)
self.assertEqual(response.status_code, 200)
response = self.client.post(logout_uri, data={"next": admin_uri})
self.assertEqual(response.status_code, 302)
self.assertEqual(response.location, admin_uri)
response = self.client.get(admin_uri)
self.assertEqual(response.status_code, 401)
response = self.client.get(admin_uri,
digest_auth=(_USERNAME, _PASSWORD))
self.assertEqual(response.status_code, 401)
response = self.client.get(admin_uri,
digest_auth=(_USERNAME, _PASSWORD))
self.assertEqual(response.status_code, 200)
response = self.client.get(admin_uri)
self.assertEqual(response.status_code, 200)

View File

@ -21,7 +21,7 @@
import typing as t
from secrets import token_urlsafe
from flask import Response, Flask, g
from flask import Response, Flask, g, redirect, request
from flask_testing import TestCase
from werkzeug.datastructures import WWWAuthenticate, Authorization
@ -121,6 +121,16 @@ class FlaskLoginTestCase(TestCase):
"""
return f"Hello, {flask_login.current_user.username}! #2"
@app.post("/logout", endpoint="logout")
@flask_login.login_required
def logout() -> redirect:
"""Logs out the user.
:return: The response.
"""
auth.logout()
return redirect(request.form.get("next"))
return app
def test_auth(self) -> None:
@ -190,3 +200,40 @@ class FlaskLoginTestCase(TestCase):
www_authenticate, admin_uri, _USERNAME, _PASSWORD)
response = super(Client, self.client).get(admin_uri, auth=auth_data)
self.assertEqual(response.status_code, 200)
def test_logout(self) -> None:
"""Tests the logging out.
:return: None.
"""
admin_uri: str = self.app.url_for("admin-1")
logout_uri: str = self.app.url_for("logout")
response: Response
response = self.client.get(admin_uri)
self.assertEqual(response.status_code, 401)
response = self.client.get(admin_uri,
digest_auth=(_USERNAME, _PASSWORD))
self.assertEqual(response.status_code, 200)
response = self.client.get(admin_uri)
self.assertEqual(response.status_code, 200)
response = self.client.post(logout_uri, data={"next": admin_uri})
self.assertEqual(response.status_code, 302)
self.assertEqual(response.location, admin_uri)
response = self.client.get(admin_uri)
self.assertEqual(response.status_code, 401)
response = self.client.get(admin_uri,
digest_auth=(_USERNAME, _PASSWORD))
self.assertEqual(response.status_code, 401)
response = self.client.get(admin_uri,
digest_auth=(_USERNAME, _PASSWORD))
self.assertEqual(response.status_code, 200)
response = self.client.get(admin_uri)
self.assertEqual(response.status_code, 200)