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
import typing as t
from hashlib import md5
from typing import Optional, Literal
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(
method: str, uri: str, password_hash: str,
nonce: str, qop: t.Optional[t.Literal["auth", "auth-int"]] = None,
algorithm: t.Optional[t.Literal["MD5", "MD5-sess"]] = "MD5-sess",
cnonce: t.Optional[str] = None, nc: t.Optional[str] = None,
body: t.Optional[bytes] = None) -> str:
nonce: str, qop: Optional[Literal["auth", "auth-int"]] = None,
algorithm: Optional[Literal["MD5", "MD5-sess"]] = "MD5-sess",
cnonce: Optional[str] = None, nc: Optional[str] = None,
body: Optional[bytes] = None) -> str:
"""Calculates the response value of the HTTP digest authentication.
: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
import sys
import typing as t
from functools import wraps
from secrets import token_urlsafe, randbits
from typing import Any, Optional, Literal, Callable, List
from flask import g, request, Response, session, abort, Flask, Request, \
current_app
@ -38,7 +38,7 @@ from flask_digest_auth.algo import calc_response
class DigestAuth:
"""The HTTP digest authentication."""
def __init__(self, realm: t.Optional[str] = None):
def __init__(self, realm: Optional[str] = None):
"""Constructs the HTTP digest authentication.
:param realm: The realm.
@ -48,16 +48,15 @@ class DigestAuth:
"""The serializer to generate and validate the nonce and opaque."""
self.realm: str = "Login Required" if realm is None else realm
"""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
None."""
self.use_opaque: bool = 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.
Default is empty."""
self.__qop: t.List[t.Literal["auth", "auth-int"]] \
= ["auth", "auth-int"]
self.__qop: List[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 \
@ -68,7 +67,7 @@ class DigestAuth:
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) -> Callable:
"""The view decorator for the HTTP digest authentication.
:Example:
@ -89,7 +88,7 @@ class DigestAuth:
class NoLogInException(Exception):
"""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.
:return: The currently logged-in user.
@ -97,13 +96,13 @@ class DigestAuth:
"""
if "user" not in session:
raise NoLogInException
user: t.Optional[t.Any] = self.__get_user(session["user"])
user: Optional[Any] = self.__get_user(session["user"])
if user is None:
del session["user"]
raise NoLogInException
return user
def auth_user(state: AuthState) -> t.Any:
def auth_user(state: AuthState) -> Any:
"""Authenticates a user.
:param state: The authentication state.
@ -121,7 +120,7 @@ class DigestAuth:
return self.__get_user(authorization.username)
@wraps(view)
def login_required_view(*args, **kwargs) -> t.Any:
def login_required_view(*args, **kwargs) -> Any:
"""The login-protected view.
:param args: The positional arguments of the view.
@ -171,7 +170,7 @@ class DigestAuth:
except BadData:
raise UnauthorizedException("Invalid opaque")
state.opaque = authorization.opaque
password_hash: t.Optional[str] \
password_hash: Optional[str] \
= self.__get_password_hash(authorization.username)
if password_hash is None:
raise UnauthorizedException(
@ -202,7 +201,7 @@ class DigestAuth:
:return: The ``WWW-Authenticate`` response header.
"""
def get_opaque() -> t.Optional[str]:
def get_opaque() -> Optional[str]:
"""Returns the opaque value.
:return: The opaque value.
@ -213,7 +212,7 @@ class DigestAuth:
return state.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(
randbits(32),
salt="nonce" if opaque is None else f"nonce-{opaque}")
@ -234,7 +233,7 @@ class DigestAuth:
header += f", qop=\"{qop_list}\""
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:
"""The decorator to register the callback to obtain the password hash.
@ -256,7 +255,7 @@ class DigestAuth:
"""The base password hash getter."""
@staticmethod
def __call__(username: str) -> t.Optional[str]:
def __call__(username: str) -> Optional[str]:
"""Returns the password hash of a user.
:param username: The username.
@ -266,8 +265,7 @@ class DigestAuth:
self.__get_password_hash = PasswordHashGetter()
def register_get_user(self, func: t.Callable[[str], t.Optional[t.Any]])\
-> None:
def register_get_user(self, func: Callable[[str], Optional[Any]]) -> None:
"""The decorator to register the callback to obtain the user.
:Example:
@ -287,7 +285,7 @@ class DigestAuth:
"""The user getter."""
@staticmethod
def __call__(username: str) -> t.Optional[t.Any]:
def __call__(username: str) -> Optional[Any]:
"""Returns a user.
:param username: The username.
@ -297,7 +295,7 @@ class DigestAuth:
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.
:Example:
@ -316,7 +314,7 @@ class DigestAuth:
"""The callback when the user logs in."""
@staticmethod
def __call__(user: t.Any) -> None:
def __call__(user: Any) -> None:
"""Runs the callback when the user logs in.
:param user: The logged-in user.
@ -366,7 +364,7 @@ class DigestAuth:
abort(response)
@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.
:param req: The request.
@ -427,9 +425,9 @@ class AuthState:
def __init__(self):
"""Constructs the authorization state."""
self.opaque: t.Optional[str] = None
self.opaque: Optional[str] = None
"""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."""
@ -446,7 +444,7 @@ class BasePasswordHashGetter:
"""
@staticmethod
def __call__(username: str) -> t.Optional[str]:
def __call__(username: str) -> Optional[str]:
"""Returns the password hash of a user.
:param username: The username.
@ -467,7 +465,7 @@ class BaseUserGetter:
"""
@staticmethod
def __call__(username: str) -> t.Optional[t.Any]:
def __call__(username: str) -> Optional[Any]:
"""Returns a user.
:param username: The username.
@ -487,7 +485,7 @@ class BaseOnLogInCallback:
"""
@staticmethod
def __call__(user: t.Any) -> None:
def __call__(user: Any) -> None:
"""Runs the callback when the user logs in.
:param user: The logged-in user.

View File

@ -18,8 +18,8 @@
"""The test client with HTTP digest authentication enabled.
"""
import typing as t
from secrets import token_urlsafe
from typing import Optional, Literal, Tuple, Dict
from flask import g
from werkzeug.datastructures import Authorization, WWWAuthenticate
@ -83,7 +83,7 @@ class Client(WerkzeugClient):
.. _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:
"""Opens a request.
@ -117,14 +117,14 @@ class Client(WerkzeugClient):
:param password: The password.
: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:
qop = "auth"
cnonce: t.Optional[str] = None
cnonce: Optional[str] = None
if qop is not None or www_authenticate.algorithm == "MD5-sess":
cnonce = token_urlsafe(8)
nc: t.Optional[str] = None
nc: Optional[str] = None
count: int = 1
if qop is not None:
nc: str = hex(count)[2:].zfill(8)
@ -137,7 +137,7 @@ class Client(WerkzeugClient):
algorithm=www_authenticate.algorithm, cnonce=cnonce, nc=nc,
body=None)
data: t.Dict[str, str] = {
data: Dict[str, str] = {
"username": username, "realm": www_authenticate.realm,
"nonce": www_authenticate.nonce, "uri": uri, "response": expected}
if www_authenticate.algorithm is not None:

View File

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

View File

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

View File

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