Replaced importing the "typing" module as "t" with importing the individual names in the "typing" module. We do not have as many names to import. This is also to be consistent with the practices of most major and standard packages and examples.

This commit is contained in:
依瑪貓 2023-04-26 23:15:13 +08:00
parent 264ba158ee
commit e861cae2e0
6 changed files with 50 additions and 52 deletions

View File

@ -20,8 +20,8 @@
""" """
from __future__ import annotations from __future__ import annotations
import typing as t
from hashlib import md5 from hashlib import md5
from typing import Optional, Literal
def make_password_hash(realm: str, username: str, password: str) -> str: def make_password_hash(realm: str, username: str, password: str) -> str:
@ -44,10 +44,10 @@ def make_password_hash(realm: str, username: str, password: str) -> str:
def calc_response( def calc_response(
method: str, uri: str, password_hash: str, method: str, uri: str, password_hash: str,
nonce: str, qop: t.Optional[t.Literal["auth", "auth-int"]] = None, nonce: str, qop: Optional[Literal["auth", "auth-int"]] = None,
algorithm: t.Optional[t.Literal["MD5", "MD5-sess"]] = "MD5-sess", algorithm: Optional[Literal["MD5", "MD5-sess"]] = "MD5-sess",
cnonce: t.Optional[str] = None, nc: t.Optional[str] = None, cnonce: Optional[str] = None, nc: Optional[str] = None,
body: t.Optional[bytes] = None) -> str: body: Optional[bytes] = None) -> str:
"""Calculates the response value of the HTTP digest authentication. """Calculates the response value of the HTTP digest authentication.
:param method: The request method. :param method: The request method.

View File

@ -23,9 +23,9 @@ See `RFC 2617`_ HTTP Authentication: Basic and Digest Access Authentication
from __future__ import annotations from __future__ import annotations
import sys import sys
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 typing import Any, Optional, Literal, Callable, List
from flask import g, request, Response, session, abort, Flask, Request, \ from flask import g, request, Response, session, abort, Flask, Request, \
current_app current_app
@ -38,7 +38,7 @@ from flask_digest_auth.algo import calc_response
class DigestAuth: class DigestAuth:
"""The HTTP digest authentication.""" """The HTTP digest authentication."""
def __init__(self, realm: t.Optional[str] = None): def __init__(self, realm: Optional[str] = None):
"""Constructs the HTTP digest authentication. """Constructs the HTTP digest authentication.
:param realm: The realm. :param realm: The realm.
@ -48,16 +48,15 @@ class DigestAuth:
"""The serializer to generate and validate the nonce and opaque.""" """The serializer to generate and validate the nonce and opaque."""
self.realm: str = "Login Required" if realm is None else realm self.realm: str = "Login Required" if realm is None else realm
"""The realm. Default is "Login Required".""" """The realm. Default is "Login Required"."""
self.algorithm: t.Optional[t.Literal["MD5", "MD5-sess"]] = None self.algorithm: Optional[Literal["MD5", "MD5-sess"]] = None
"""The algorithm, either None, ``MD5``, or ``MD5-sess``. Default is """The algorithm, either None, ``MD5``, or ``MD5-sess``. Default is
None.""" None."""
self.use_opaque: bool = True self.use_opaque: bool = True
"""Whether to use an opaque. Default is True.""" """Whether to use an opaque. Default is True."""
self.__domain: t.List[str] = [] self.__domain: List[str] = []
"""A list of directories that this username and password applies to. """A list of directories that this username and password applies to.
Default is empty.""" Default is empty."""
self.__qop: t.List[t.Literal["auth", "auth-int"]] \ self.__qop: List[Literal["auth", "auth-int"]] = ["auth", "auth-int"]
= ["auth", "auth-int"]
"""A list of supported quality of protection supported, either """A list of supported quality of protection supported, either
``qop``, ``auth-int``, both, or empty. Default is both.""" ``qop``, ``auth-int``, both, or empty. Default is both."""
self.__get_password_hash: BasePasswordHashGetter \ self.__get_password_hash: BasePasswordHashGetter \
@ -68,7 +67,7 @@ class DigestAuth:
self.__on_login: BaseOnLogInCallback = BaseOnLogInCallback() self.__on_login: BaseOnLogInCallback = BaseOnLogInCallback()
"""The callback to run when the user logs in.""" """The callback to run when the user logs in."""
def login_required(self, view) -> t.Callable: def login_required(self, view) -> Callable:
"""The view decorator for the HTTP digest authentication. """The view decorator for the HTTP digest authentication.
:Example: :Example:
@ -89,7 +88,7 @@ class DigestAuth:
class NoLogInException(Exception): class NoLogInException(Exception):
"""The exception thrown when the user is not authorized.""" """The exception thrown when the user is not authorized."""
def get_logged_in_user() -> t.Any: def get_logged_in_user() -> Any:
"""Returns the currently logged-in user. """Returns the currently logged-in user.
:return: The currently logged-in user. :return: The currently logged-in user.
@ -97,13 +96,13 @@ class DigestAuth:
""" """
if "user" not in session: if "user" not in session:
raise NoLogInException raise NoLogInException
user: t.Optional[t.Any] = self.__get_user(session["user"]) user: Optional[Any] = self.__get_user(session["user"])
if user is None: if user is None:
del session["user"] del session["user"]
raise NoLogInException raise NoLogInException
return user return user
def auth_user(state: AuthState) -> t.Any: def auth_user(state: AuthState) -> Any:
"""Authenticates a user. """Authenticates a user.
:param state: The authentication state. :param state: The authentication state.
@ -121,7 +120,7 @@ class DigestAuth:
return self.__get_user(authorization.username) return self.__get_user(authorization.username)
@wraps(view) @wraps(view)
def login_required_view(*args, **kwargs) -> t.Any: def login_required_view(*args, **kwargs) -> Any:
"""The login-protected view. """The login-protected view.
:param args: The positional arguments of the view. :param args: The positional arguments of the view.
@ -171,7 +170,7 @@ class DigestAuth:
except BadData: except BadData:
raise UnauthorizedException("Invalid opaque") raise UnauthorizedException("Invalid opaque")
state.opaque = authorization.opaque state.opaque = authorization.opaque
password_hash: t.Optional[str] \ password_hash: Optional[str] \
= self.__get_password_hash(authorization.username) = self.__get_password_hash(authorization.username)
if password_hash is None: if password_hash is None:
raise UnauthorizedException( raise UnauthorizedException(
@ -202,7 +201,7 @@ class DigestAuth:
:return: The ``WWW-Authenticate`` response header. :return: The ``WWW-Authenticate`` response header.
""" """
def get_opaque() -> t.Optional[str]: def get_opaque() -> Optional[str]:
"""Returns the opaque value. """Returns the opaque value.
:return: The opaque value. :return: The opaque value.
@ -213,7 +212,7 @@ class DigestAuth:
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: 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}")
@ -234,7 +233,7 @@ class DigestAuth:
header += f", qop=\"{qop_list}\"" header += f", qop=\"{qop_list}\""
return header return header
def register_get_password(self, func: t.Callable[[str], t.Optional[str]])\ def register_get_password(self, func: Callable[[str], Optional[str]]) \
-> None: -> None:
"""The decorator to register the callback to obtain the password hash. """The decorator to register the callback to obtain the password hash.
@ -256,7 +255,7 @@ class DigestAuth:
"""The base password hash getter.""" """The base password hash getter."""
@staticmethod @staticmethod
def __call__(username: str) -> t.Optional[str]: def __call__(username: str) -> Optional[str]:
"""Returns the password hash of a user. """Returns the password hash of a user.
:param username: The username. :param username: The username.
@ -266,8 +265,7 @@ class DigestAuth:
self.__get_password_hash = PasswordHashGetter() self.__get_password_hash = PasswordHashGetter()
def register_get_user(self, func: t.Callable[[str], t.Optional[t.Any]])\ def register_get_user(self, func: Callable[[str], Optional[Any]]) -> None:
-> None:
"""The decorator to register the callback to obtain the user. """The decorator to register the callback to obtain the user.
:Example: :Example:
@ -287,7 +285,7 @@ class DigestAuth:
"""The user getter.""" """The user getter."""
@staticmethod @staticmethod
def __call__(username: str) -> t.Optional[t.Any]: def __call__(username: str) -> Optional[Any]:
"""Returns a user. """Returns a user.
:param username: The username. :param username: The username.
@ -297,7 +295,7 @@ class DigestAuth:
self.__get_user = UserGetter() self.__get_user = UserGetter()
def register_on_login(self, func: t.Callable[[t.Any], None]) -> None: def register_on_login(self, func: Callable[[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.
:Example: :Example:
@ -316,7 +314,7 @@ class DigestAuth:
"""The callback when the user logs in.""" """The callback when the user logs in."""
@staticmethod @staticmethod
def __call__(user: t.Any) -> None: def __call__(user: Any) -> None:
"""Runs the callback when the user logs in. """Runs the callback when the user logs in.
:param user: The logged-in user. :param user: The logged-in user.
@ -366,7 +364,7 @@ class DigestAuth:
abort(response) abort(response)
@login_manager.request_loader @login_manager.request_loader
def load_user_from_request(req: Request) -> t.Optional[t.Any]: def load_user_from_request(req: Request) -> Optional[Any]:
"""Loads the user from the request header. """Loads the user from the request header.
:param req: The request. :param req: The request.
@ -427,9 +425,9 @@ class AuthState:
def __init__(self): def __init__(self):
"""Constructs the authorization state.""" """Constructs the authorization state."""
self.opaque: t.Optional[str] = None self.opaque: Optional[str] = None
"""The opaque value specified by the client, if valid.""" """The opaque value specified by the client, if valid."""
self.stale: t.Optional[bool] = None self.stale: Optional[bool] = None
"""The stale value, if there is a previous log in attempt.""" """The stale value, if there is a previous log in attempt."""
@ -446,7 +444,7 @@ class BasePasswordHashGetter:
""" """
@staticmethod @staticmethod
def __call__(username: str) -> t.Optional[str]: def __call__(username: str) -> Optional[str]:
"""Returns the password hash of a user. """Returns the password hash of a user.
:param username: The username. :param username: The username.
@ -467,7 +465,7 @@ class BaseUserGetter:
""" """
@staticmethod @staticmethod
def __call__(username: str) -> t.Optional[t.Any]: def __call__(username: str) -> Optional[Any]:
"""Returns a user. """Returns a user.
:param username: The username. :param username: The username.
@ -487,7 +485,7 @@ class BaseOnLogInCallback:
""" """
@staticmethod @staticmethod
def __call__(user: t.Any) -> None: def __call__(user: Any) -> None:
"""Runs the callback when the user logs in. """Runs the callback when the user logs in.
:param user: The logged-in user. :param user: The logged-in user.

View File

@ -18,8 +18,8 @@
"""The test client with HTTP digest authentication enabled. """The test client with HTTP digest authentication enabled.
""" """
import typing as t
from secrets import token_urlsafe from secrets import token_urlsafe
from typing import Optional, Literal, Tuple, Dict
from flask import g from flask import g
from werkzeug.datastructures import Authorization, WWWAuthenticate from werkzeug.datastructures import Authorization, WWWAuthenticate
@ -83,7 +83,7 @@ class Client(WerkzeugClient):
.. _pytest: https://pytest.org .. _pytest: https://pytest.org
""" """
def open(self, *args, digest_auth: t.Optional[t.Tuple[str, str]] = None, def open(self, *args, digest_auth: Optional[Tuple[str, str]] = None,
**kwargs) -> TestResponse: **kwargs) -> TestResponse:
"""Opens a request. """Opens a request.
@ -117,14 +117,14 @@ class Client(WerkzeugClient):
:param password: The password. :param password: The password.
:return: The request authorization. :return: The request authorization.
""" """
qop: t.Optional[t.Literal["auth", "auth-int"]] = None qop: Optional[Literal["auth", "auth-int"]] = None
if www_authenticate.qop is not None and "auth" in www_authenticate.qop: if www_authenticate.qop is not None and "auth" in www_authenticate.qop:
qop = "auth" qop = "auth"
cnonce: t.Optional[str] = None cnonce: Optional[str] = None
if qop is not None or www_authenticate.algorithm == "MD5-sess": if qop is not None or www_authenticate.algorithm == "MD5-sess":
cnonce = token_urlsafe(8) cnonce = token_urlsafe(8)
nc: t.Optional[str] = None nc: Optional[str] = None
count: int = 1 count: int = 1
if qop is not None: if qop is not None:
nc: str = hex(count)[2:].zfill(8) nc: str = hex(count)[2:].zfill(8)
@ -137,7 +137,7 @@ class Client(WerkzeugClient):
algorithm=www_authenticate.algorithm, cnonce=cnonce, nc=nc, algorithm=www_authenticate.algorithm, cnonce=cnonce, nc=nc,
body=None) body=None)
data: t.Dict[str, str] = { data: Dict[str, str] = {
"username": username, "realm": www_authenticate.realm, "username": username, "realm": www_authenticate.realm,
"nonce": www_authenticate.nonce, "uri": uri, "response": expected} "nonce": www_authenticate.nonce, "uri": uri, "response": expected}
if www_authenticate.algorithm is not None: if www_authenticate.algorithm is not None:

View File

@ -18,8 +18,8 @@
"""The test case for the HTTP digest authentication algorithm. """The test case for the HTTP digest authentication algorithm.
""" """
import typing as t
import unittest import unittest
from typing import Optional, Literal
from flask_digest_auth import make_password_hash, calc_response from flask_digest_auth import make_password_hash, calc_response
@ -39,11 +39,11 @@ class AlgorithmTestCase(unittest.TestCase):
method: str = "GET" method: str = "GET"
uri: str = "/dir/index.html" uri: str = "/dir/index.html"
nonce: str = "dcd98b7102dd2f0e8b11d0f600bfb0c093" nonce: str = "dcd98b7102dd2f0e8b11d0f600bfb0c093"
qop: t.Optional[t.Literal["auth", "auth-int"]] = "auth" qop: Optional[Literal["auth", "auth-int"]] = "auth"
algorithm: t.Optional[t.Literal["MD5", "MD5-sess"]] = None algorithm: Optional[Literal["MD5", "MD5-sess"]] = None
cnonce: t.Optional[str] = "0a4f113b" cnonce: Optional[str] = "0a4f113b"
nc: t.Optional[str] = "00000001" nc: Optional[str] = "00000001"
body: t.Optional[bytes] = None body: Optional[bytes] = None
password_hash: str = make_password_hash(realm, username, password) password_hash: str = make_password_hash(realm, username, password)
response: str = calc_response(method, uri, password_hash, nonce, qop, response: str = calc_response(method, uri, password_hash, nonce, qop,

View File

@ -18,8 +18,8 @@
"""The test case for the HTTP digest authentication. """The test case for the HTTP digest authentication.
""" """
import typing as t
from secrets import token_urlsafe from secrets import token_urlsafe
from typing import Any, Optional, Dict
from flask import Response, Flask, g, redirect, request from flask import Response, Flask, g, redirect, request
from flask_testing import TestCase from flask_testing import TestCase
@ -66,10 +66,10 @@ class AuthenticationTestCase(TestCase):
auth: DigestAuth = DigestAuth() auth: DigestAuth = DigestAuth()
auth.init_app(app) auth.init_app(app)
self.user: User = User(_USERNAME, _PASSWORD) self.user: User = User(_USERNAME, _PASSWORD)
user_db: t.Dict[str, User] = {_USERNAME: self.user} user_db: Dict[str, User] = {_USERNAME: self.user}
@auth.register_get_password @auth.register_get_password
def get_password_hash(username: str) -> t.Optional[str]: def get_password_hash(username: str) -> Optional[str]:
"""Returns the password hash of a user. """Returns the password hash of a user.
:param username: The username. :param username: The username.
@ -79,7 +79,7 @@ class AuthenticationTestCase(TestCase):
else None else None
@auth.register_get_user @auth.register_get_user
def get_user(username: str) -> t.Optional[t.Any]: def get_user(username: str) -> Optional[Any]:
"""Returns a user. """Returns a user.
:param username: The username. :param username: The username.

View File

@ -18,8 +18,8 @@
"""The test case for the Flask-Login integration. """The test case for the Flask-Login integration.
""" """
import typing as t
from secrets import token_urlsafe from secrets import token_urlsafe
from typing import Optional, Dict
from flask import Response, Flask, g, redirect, request from flask import Response, Flask, g, redirect, request
from flask_testing import TestCase from flask_testing import TestCase
@ -97,10 +97,10 @@ class FlaskLoginTestCase(TestCase):
auth.init_app(app) auth.init_app(app)
self.user: User = User(_USERNAME, _PASSWORD) self.user: User = User(_USERNAME, _PASSWORD)
user_db: t.Dict[str, User] = {_USERNAME: self.user} user_db: Dict[str, User] = {_USERNAME: self.user}
@auth.register_get_password @auth.register_get_password
def get_password_hash(username: str) -> t.Optional[str]: def get_password_hash(username: str) -> Optional[str]:
"""Returns the password hash of a user. """Returns the password hash of a user.
:param username: The username. :param username: The username.
@ -119,7 +119,7 @@ class FlaskLoginTestCase(TestCase):
user.visits = user.visits + 1 user.visits = user.visits + 1
@login_manager.user_loader @login_manager.user_loader
def load_user(user_id: str) -> t.Optional[User]: def load_user(user_id: str) -> Optional[User]:
"""Loads a user. """Loads a user.
:param user_id: The username. :param user_id: The username.