Skip to content

Commit

Permalink
Use typing library to be python 3.8 compatible; do more thorough typi…
Browse files Browse the repository at this point in the history
…ng (#25)
  • Loading branch information
AaronAtDuo authored Jun 13, 2024
1 parent a9ec696 commit c24a53c
Show file tree
Hide file tree
Showing 5 changed files with 30 additions and 21 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/python-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:

strategy:
matrix:
python-version: ["3.9", "3.10", "3.11"]
python-version: ["3.8", "3.9", "3.10", "3.11"]

steps:
- name: Checkout
Expand Down
14 changes: 8 additions & 6 deletions duo_hmac/duo_canonicalize.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,17 @@
import hashlib
import urllib.parse

from typing import Dict, List, Optional


def generate_canonical_string(
date_string: str,
http_method: str,
api_host: str,
api_path: str,
qs_parameters: dict[bytes, list[bytes]],
body: str,
duo_headers: dict[str, str],
qs_parameters: Optional[Dict[bytes, List[bytes]]],
body: Optional[str],
duo_headers: Optional[Dict[str, str]],
) -> str:
"""
Create the "canonical string" of the request:
Expand All @@ -36,7 +38,7 @@ def generate_canonical_string(
return "\n".join(canon_parts)


def canonicalize_parameters(parameters: dict[bytes, list[bytes]]) -> str:
def canonicalize_parameters(parameters: Optional[Dict[bytes, List[bytes]]]) -> str:
"""Canonicalize the parameters by sorting and formatting them"""
if parameters is None:
return ""
Expand All @@ -52,14 +54,14 @@ def canonicalize_parameters(parameters: dict[bytes, list[bytes]]) -> str:
return "&".join(args)


def canonicalize_body(body: str) -> str:
def canonicalize_body(body: Optional[str]) -> str:
"""Canonicalize the body by encoding and hashing it"""
if body is None:
body = ""
return hashlib.sha512(body.encode("utf-8")).hexdigest()


def canonicalize_x_duo_headers(duo_headers: dict[str, str]) -> str:
def canonicalize_x_duo_headers(duo_headers: Optional[Dict[str, str]]) -> str:
"""Canonicalize the x-duo headers by joining everything together and hashing it"""
if duo_headers is None:
duo_headers = {}
Expand Down
16 changes: 9 additions & 7 deletions duo_hmac/duo_hmac.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import hmac
import urllib.parse

from typing import Any, Dict, List, Optional, Tuple


from . import duo_canonicalize, duo_hmac_utils, duo_hmac_validation

Expand All @@ -16,7 +18,7 @@ def __init__(
ikey: str,
skey: str,
api_host: str,
date_string_provider: duo_hmac_utils.DateStringProvider = None,
date_string_provider: Optional[duo_hmac_utils.DateStringProvider] = None,
):
self.ikey = ikey
self.skey = skey
Expand All @@ -30,9 +32,9 @@ def get_authentication_components(
self,
http_method: str,
api_path: str,
parameters: dict = None,
in_headers: dict[str, str] = None,
) -> tuple[str, str, dict[str, str]]:
parameters: Optional[Dict[str, Any]] = None,
in_headers: Optional[Dict[str, str]] = None,
) -> Tuple[str, str, Dict[str, str]]:
"""
Use the provided request components and calculate
- The final url (host + path + query string)
Expand Down Expand Up @@ -91,9 +93,9 @@ def _generate_authentication_header(
date_string: str,
http_method: str,
api_path: str,
qs_parameters: dict[bytes, list[bytes]],
body: str,
x_duo_headers: dict[str, str],
qs_parameters: Optional[Dict[bytes, List[bytes]]],
body: Optional[str],
x_duo_headers: Optional[Dict[str, str]],
) -> str:
"""
Calculate the authentication header from the request components
Expand Down
15 changes: 9 additions & 6 deletions duo_hmac/duo_hmac_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@
import email.utils
import json

from typing import Protocol
from typing import Any, Dict, List, Optional, Protocol, Tuple


# These type parameters are not correct, but the actual permissible
# types are far too complicated. This will be cleaned up later.
def prepare_parameters(
parameters: dict, params_go_in_body: bool
) -> tuple[dict[bytes, list[bytes]], str]:
parameters: Optional[Dict[str, Any]], params_go_in_body: bool
) -> Tuple[Dict[bytes, List[bytes]], str]:
"""
Prepare the parameters: JSONize them if they'll go in the body,
or normalize them if they'll go in the query string.
Expand All @@ -26,7 +28,7 @@ def prepare_parameters(
return (qs_parameters, body)


def jsonize_parameters(parameters: dict) -> str:
def jsonize_parameters(parameters: Optional[Dict[str, Any]]) -> str:
"""Turn a parameter dictionary into a JSON string"""
if parameters is None:
# Is this the best choice? Should we return None instead (or allow
Expand All @@ -36,7 +38,8 @@ def jsonize_parameters(parameters: dict) -> str:
return json.dumps(parameters, sort_keys=True, separators=(",", ":"))


def normalize_parameters(parameters: dict) -> dict[bytes, list[bytes]]:
def normalize_parameters(
parameters: Optional[Dict[str, Any]]) -> Dict[bytes, List[bytes]]:
"""
Return copy of params with everything stringified and listified
"""
Expand Down Expand Up @@ -68,7 +71,7 @@ def to_list(value):
)


def extract_x_duo_headers(in_headers: dict[str, str]) -> dict[str, str]:
def extract_x_duo_headers(in_headers: Optional[Dict[str, str]]) -> Dict[str, str]:
"""Extract all headers that start with 'x-duo' from the provided input headers"""
if in_headers is None:
return {}
Expand Down
4 changes: 3 additions & 1 deletion duo_hmac/duo_hmac_validation.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
# SPDX-FileCopyrightText: 2024 Cisco Systems, Inc. and/or its affiliates
# SPDX-License-Identifier: MIT

from typing import Dict, Optional

def validate_headers(headers: dict[str, str]):

def validate_headers(headers: Optional[Dict[str, str]]) -> None:
if headers is None:
headers = {}

Expand Down

0 comments on commit c24a53c

Please sign in to comment.