From 8681ab88e439c9bc850d857b01482a5e01660c06 Mon Sep 17 00:00:00 2001 From: Izalia Mae Date: Mon, 22 Apr 2024 22:33:10 -0400 Subject: [PATCH] Use HttpDate for created and expires properties of Signature --- .gitignore | 2 +- aputils/algorithms.py | 18 ++++-------- aputils/misc.py | 64 ++++++++++++++----------------------------- tests/signer.py | 2 +- 4 files changed, 28 insertions(+), 58 deletions(-) diff --git a/.gitignore b/.gitignore index 89e3388..8764b88 100644 --- a/.gitignore +++ b/.gitignore @@ -116,4 +116,4 @@ dmypy.json .pyre/ test*.py -pyvenv.json +*.pem diff --git a/aputils/algorithms.py b/aputils/algorithms.py index a33319f..79d6b2f 100644 --- a/aputils/algorithms.py +++ b/aputils/algorithms.py @@ -198,8 +198,8 @@ class Algorithm(ABC): ) if type(self).algo_type == AlgorithmType.HS2019: - signature.created = int(headers["(created)"]) - signature.expires = int(headers["(expires)"]) + signature.created = int(headers["(created)"]) # type: ignore[assignment] + signature.expires = int(headers["(expires)"]) # type: ignore[assignment] for key in {"(request-target)", "(created)", "(expires)", "host"}: try: @@ -306,15 +306,7 @@ class HS2019(Algorithm): headers["host"] = host raw_date: HttpDate | datetime | str = headers.get("date", HttpDate.new_utc()) - - if isinstance(raw_date, str): - date = HttpDate.parse(raw_date) - - elif isinstance(raw_date, datetime) and not isinstance(raw_date, HttpDate): - date = HttpDate.parse(raw_date) - - else: - date = raw_date + date = HttpDate.parse(raw_date) if date + timedelta(hours = 6) >= (new_date := HttpDate.new_utc()): date = new_date @@ -346,13 +338,13 @@ class HS2019(Algorithm): headers["(request-target)"] = f"{method.lower()} {path}" if signature.created is not None: - if signature.created_date > HttpDate.new_utc(): + if signature.created > HttpDate.new_utc(): raise SignatureFailureError("Signature creation date is in the future") headers["(created)"] = str(signature.created) if signature.expires is not None: - if signature.expires_date < HttpDate.new_utc(): + if signature.expires < HttpDate.new_utc(): raise SignatureFailureError("Signature has expired") headers["(expires)"] = str(signature.expires) diff --git a/aputils/misc.py b/aputils/misc.py index 93b5bde..1d7a879 100644 --- a/aputils/misc.py +++ b/aputils/misc.py @@ -139,10 +139,7 @@ class MessageDate(HttpDate): class Signature: """ - Represents a signature header with access to values as attributes - - .. note:: ``created`` and ``expires`` properties will be :class:`datetime.datetime` objects - in 0.3.0 + Represents a signature header value """ __slots__ = {"keyid", "algorithm", "headers", "signature", "created", "expires"} @@ -152,8 +149,8 @@ class Signature: keyid: str, algorithm: AlgorithmType | str, headers: Sequence[str] | str, - created: int | None = None, - expires: int | None = None) -> None: + created: HttpDate | datetime | str | int | float | None = None, + expires: HttpDate | datetime | str | int | float | None = None) -> None: """ Create a new signature object. This should not be initiated directly. @@ -171,17 +168,17 @@ class Signature: self.keyid: str = keyid "URL of the public key" - self.algorithm: AlgorithmType = algorithm # type: ignore + self.algorithm: AlgorithmType = algorithm # type: ignore[assignment] "Hashing and signing algorithms used to create the signature" self.headers: Sequence[str] = headers "Header keys used to create the signature" - self.created: int | None = created - "Unix timestamp representing the signature creation date" + self.created: HttpDate | None = created # type: ignore[assignment] + "Signature creation date" - self.expires: int | None = expires - "Unix timestamp representing the date the signature expires" + self.expires: HttpDate | None = expires # type: ignore[assignment] + "Signature expiration date" def __setattr__(self, key: str, value: Any) -> None: @@ -192,7 +189,14 @@ class Signature: value = AlgorithmType.parse(value) elif key in {"created", "expires"} and value is not None: - value = int(value) + if isinstance(value, str): + try: + value = int(value) + + except ValueError: + pass + + value = HttpDate.parse(value) object.__setattr__(self, key, value) @@ -202,8 +206,8 @@ class Signature: "keyid": repr(self.keyid), "algorithm": self.algorithm, "headers": self.headers, - "created": self.created, - "expires": self.expires + "created": self.created.timestamp() if self.created is not None else None, + "expires": self.expires.timestamp() if self.expires is not None else None } str_data = ", ".join(f"{key}={value}" for key, value in data.items()) @@ -232,7 +236,7 @@ class Signature: key, value = chunk.split("=", 1) data[key.lower()] = value.strip("\"") - return cls(**data) # type: ignore + return cls(**data) @classmethod @@ -264,32 +268,6 @@ class Signature: return self.algs[0] - @property - def created_date(self) -> HttpDate: - if not self.created: - raise AttributeError("Created timestamp not set") - - return HttpDate.parse(self.created) - - - @created_date.setter - def created_date(self, value: datetime) -> None: - self.created = int(value.timestamp()) - - - @property - def expires_date(self) -> HttpDate: - if not self.expires: - raise AttributeError("Expires timestamp not set") - - return HttpDate.parse(self.expires) - - - @expires_date.setter - def expires_date(self, value: datetime) -> None: - self.expires = int(value.timestamp()) - - def compile(self) -> str: "Generate a string for a Signature header" @@ -297,8 +275,8 @@ class Signature: "keyId": self.keyid, "algorithm": self.algorithm.value, "headers": " ".join(self.headers), - "created": self.created, - "expires": self.expires, + "created": self.created.timestamp() if self.created is not None else None, + "expires": self.expires.timestamp() if self.expires is not None else None, "signature": self.signature } diff --git a/tests/signer.py b/tests/signer.py index 6a8bde9..7e77325 100644 --- a/tests/signer.py +++ b/tests/signer.py @@ -33,7 +33,7 @@ class SignerTest(unittest.TestCase): output = signer.sign_headers('GET', url, headers={'date': date}, sign_all = True) data = { 'date': "Fri, 25 Nov 2022 06:09:42 GMT", - 'signature': "keyId=\"https://social.example.com/users/merpinator#main-key\",algorithm=\"hs2019\",headers=\"date host (request-target) (created) (expires) date\",created=\"1669356582\",expires=\"1669378182\",signature=\"EUFFBpQb1R2lIOIAJ7wADNjJr+4wWnZntULpextt1B11v0t02v3ga44GU7KHHSfFWkCmM55WZSZKMgI6Dl/yUV8oxvia6Xx6hDwFym1fQkDjN7Q4HQlp8ycQrFvlWB5EwMTf4+9VMgJJB2jWQoEqQ8jLQ7Rpu9/3k2oX0blioS4ypL7KgeqlDrEFG5+IZAHu3jMrfBN0tXK24zfqeyOqAuAPcIt03fo4iy8N2xIc2z9pmT6Z5BLZyFpWNgprM8nYhEhQGo3SRImBvIi7+ad1bliUXi3ImWZwzArl6wUaune4Zfl4iAZjFaA4KYPVIOGYzrrjLcPFBv5yBHtQqm6ifA==\"" + 'signature': "keyId=\"https://social.example.com/users/merpinator#main-key\",algorithm=\"hs2019\",headers=\"date host (request-target) (created) (expires) date\",created=\"1669338582\",expires=\"1669360182\",signature=\"EUFFBpQb1R2lIOIAJ7wADNjJr+4wWnZntULpextt1B11v0t02v3ga44GU7KHHSfFWkCmM55WZSZKMgI6Dl/yUV8oxvia6Xx6hDwFym1fQkDjN7Q4HQlp8ycQrFvlWB5EwMTf4+9VMgJJB2jWQoEqQ8jLQ7Rpu9/3k2oX0blioS4ypL7KgeqlDrEFG5+IZAHu3jMrfBN0tXK24zfqeyOqAuAPcIt03fo4iy8N2xIc2z9pmT6Z5BLZyFpWNgprM8nYhEhQGo3SRImBvIi7+ad1bliUXi3ImWZwzArl6wUaune4Zfl4iAZjFaA4KYPVIOGYzrrjLcPFBv5yBHtQqm6ifA==\"" } self.assertEqual(output, data)