88 lines
2.9 KiB
Python
88 lines
2.9 KiB
Python
|
# The Flask HTTP Digest Authentication Project.
|
||
|
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/10/5
|
||
|
|
||
|
# Copyright (c) 2023 imacat.
|
||
|
#
|
||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
# you may not use this file except in compliance with the License.
|
||
|
# You may obtain a copy of the License at
|
||
|
#
|
||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||
|
#
|
||
|
# Unless required by applicable law or agreed to in writing, software
|
||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
# See the License for the specific language governing permissions and
|
||
|
# limitations under the License.
|
||
|
|
||
|
"""The common test libraries.
|
||
|
|
||
|
"""
|
||
|
from secrets import token_urlsafe
|
||
|
from typing import Optional, Literal, Dict
|
||
|
|
||
|
from werkzeug.datastructures import Authorization, WWWAuthenticate
|
||
|
from werkzeug.http import parse_set_header
|
||
|
|
||
|
from flask_digest_auth import calc_response, make_password_hash
|
||
|
|
||
|
REALM: str = "testrealm@host.com"
|
||
|
"""The realm."""
|
||
|
USERNAME: str = "Mufasa"
|
||
|
"""The username."""
|
||
|
PASSWORD: str = "Circle Of Life"
|
||
|
"""The password."""
|
||
|
ADMIN_1_URI: str = "/admin-1/auth"
|
||
|
"""The first administration URI."""
|
||
|
ADMIN_2_URI: str = "/admin-2/auth"
|
||
|
"""The first administration URI."""
|
||
|
LOGOUT_URI: str = "/logout"
|
||
|
"""The log out URI."""
|
||
|
|
||
|
|
||
|
def make_authorization(www_authenticate: WWWAuthenticate, uri: str,
|
||
|
username: str, password: str) -> str:
|
||
|
"""Composes and returns the request authorization.
|
||
|
|
||
|
:param www_authenticate: The ``WWW-Authenticate`` response.
|
||
|
:param uri: The request URI.
|
||
|
:param username: The username.
|
||
|
:param password: The password.
|
||
|
:return: The request authorization header.
|
||
|
"""
|
||
|
qop: Optional[Literal["auth", "auth-int"]] = None
|
||
|
if "auth" in parse_set_header(www_authenticate.get("qop")):
|
||
|
qop = "auth"
|
||
|
|
||
|
cnonce: Optional[str] = None
|
||
|
if qop is not None or www_authenticate.algorithm == "MD5-sess":
|
||
|
cnonce = token_urlsafe(8)
|
||
|
nc: Optional[str] = None
|
||
|
count: int = 1
|
||
|
if qop is not None:
|
||
|
nc: str = hex(count)[2:].zfill(8)
|
||
|
|
||
|
expected: str = calc_response(
|
||
|
method="GET", uri=uri,
|
||
|
password_hash=make_password_hash(www_authenticate.realm,
|
||
|
username, password),
|
||
|
nonce=www_authenticate.nonce, qop=qop,
|
||
|
algorithm=www_authenticate.algorithm, cnonce=cnonce, nc=nc,
|
||
|
body=None)
|
||
|
|
||
|
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:
|
||
|
data["algorithm"] = www_authenticate.algorithm
|
||
|
if cnonce is not None:
|
||
|
data["cnonce"] = cnonce
|
||
|
if www_authenticate.opaque is not None:
|
||
|
data["opaque"] = www_authenticate.opaque
|
||
|
if qop is not None:
|
||
|
data["qop"] = qop
|
||
|
if nc is not None:
|
||
|
data["nc"] = nc
|
||
|
|
||
|
return str(Authorization("digest", data=data))
|