Use HttpDate for created and expires properties of Signature

This commit is contained in:
Izalia Mae 2024-04-22 22:33:10 -04:00
parent 743fc04341
commit 8681ab88e4
4 changed files with 28 additions and 58 deletions

2
.gitignore vendored
View file

@ -116,4 +116,4 @@ dmypy.json
.pyre/
test*.py
pyvenv.json
*.pem

View file

@ -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)

View file

@ -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
}

View file

@ -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)