Use HttpDate for created and expires properties of Signature
This commit is contained in:
parent
743fc04341
commit
8681ab88e4
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -116,4 +116,4 @@ dmypy.json
|
|||
.pyre/
|
||||
|
||||
test*.py
|
||||
pyvenv.json
|
||||
*.pem
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue