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/
|
.pyre/
|
||||||
|
|
||||||
test*.py
|
test*.py
|
||||||
pyvenv.json
|
*.pem
|
||||||
|
|
|
@ -198,8 +198,8 @@ class Algorithm(ABC):
|
||||||
)
|
)
|
||||||
|
|
||||||
if type(self).algo_type == AlgorithmType.HS2019:
|
if type(self).algo_type == AlgorithmType.HS2019:
|
||||||
signature.created = int(headers["(created)"])
|
signature.created = int(headers["(created)"]) # type: ignore[assignment]
|
||||||
signature.expires = int(headers["(expires)"])
|
signature.expires = int(headers["(expires)"]) # type: ignore[assignment]
|
||||||
|
|
||||||
for key in {"(request-target)", "(created)", "(expires)", "host"}:
|
for key in {"(request-target)", "(created)", "(expires)", "host"}:
|
||||||
try:
|
try:
|
||||||
|
@ -306,15 +306,7 @@ class HS2019(Algorithm):
|
||||||
headers["host"] = host
|
headers["host"] = host
|
||||||
|
|
||||||
raw_date: HttpDate | datetime | str = headers.get("date", HttpDate.new_utc())
|
raw_date: HttpDate | datetime | str = headers.get("date", HttpDate.new_utc())
|
||||||
|
date = HttpDate.parse(raw_date)
|
||||||
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
|
|
||||||
|
|
||||||
if date + timedelta(hours = 6) >= (new_date := HttpDate.new_utc()):
|
if date + timedelta(hours = 6) >= (new_date := HttpDate.new_utc()):
|
||||||
date = new_date
|
date = new_date
|
||||||
|
@ -346,13 +338,13 @@ class HS2019(Algorithm):
|
||||||
headers["(request-target)"] = f"{method.lower()} {path}"
|
headers["(request-target)"] = f"{method.lower()} {path}"
|
||||||
|
|
||||||
if signature.created is not None:
|
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")
|
raise SignatureFailureError("Signature creation date is in the future")
|
||||||
|
|
||||||
headers["(created)"] = str(signature.created)
|
headers["(created)"] = str(signature.created)
|
||||||
|
|
||||||
if signature.expires is not None:
|
if signature.expires is not None:
|
||||||
if signature.expires_date < HttpDate.new_utc():
|
if signature.expires < HttpDate.new_utc():
|
||||||
raise SignatureFailureError("Signature has expired")
|
raise SignatureFailureError("Signature has expired")
|
||||||
|
|
||||||
headers["(expires)"] = str(signature.expires)
|
headers["(expires)"] = str(signature.expires)
|
||||||
|
|
|
@ -139,10 +139,7 @@ class MessageDate(HttpDate):
|
||||||
|
|
||||||
class Signature:
|
class Signature:
|
||||||
"""
|
"""
|
||||||
Represents a signature header with access to values as attributes
|
Represents a signature header value
|
||||||
|
|
||||||
.. note:: ``created`` and ``expires`` properties will be :class:`datetime.datetime` objects
|
|
||||||
in 0.3.0
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = {"keyid", "algorithm", "headers", "signature", "created", "expires"}
|
__slots__ = {"keyid", "algorithm", "headers", "signature", "created", "expires"}
|
||||||
|
@ -152,8 +149,8 @@ class Signature:
|
||||||
keyid: str,
|
keyid: str,
|
||||||
algorithm: AlgorithmType | str,
|
algorithm: AlgorithmType | str,
|
||||||
headers: Sequence[str] | str,
|
headers: Sequence[str] | str,
|
||||||
created: int | None = None,
|
created: HttpDate | datetime | str | int | float | None = None,
|
||||||
expires: int | None = None) -> None:
|
expires: HttpDate | datetime | str | int | float | None = None) -> None:
|
||||||
"""
|
"""
|
||||||
Create a new signature object. This should not be initiated directly.
|
Create a new signature object. This should not be initiated directly.
|
||||||
|
|
||||||
|
@ -171,17 +168,17 @@ class Signature:
|
||||||
self.keyid: str = keyid
|
self.keyid: str = keyid
|
||||||
"URL of the public key"
|
"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"
|
"Hashing and signing algorithms used to create the signature"
|
||||||
|
|
||||||
self.headers: Sequence[str] = headers
|
self.headers: Sequence[str] = headers
|
||||||
"Header keys used to create the signature"
|
"Header keys used to create the signature"
|
||||||
|
|
||||||
self.created: int | None = created
|
self.created: HttpDate | None = created # type: ignore[assignment]
|
||||||
"Unix timestamp representing the signature creation date"
|
"Signature creation date"
|
||||||
|
|
||||||
self.expires: int | None = expires
|
self.expires: HttpDate | None = expires # type: ignore[assignment]
|
||||||
"Unix timestamp representing the date the signature expires"
|
"Signature expiration date"
|
||||||
|
|
||||||
|
|
||||||
def __setattr__(self, key: str, value: Any) -> None:
|
def __setattr__(self, key: str, value: Any) -> None:
|
||||||
|
@ -192,7 +189,14 @@ class Signature:
|
||||||
value = AlgorithmType.parse(value)
|
value = AlgorithmType.parse(value)
|
||||||
|
|
||||||
elif key in {"created", "expires"} and value is not None:
|
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)
|
object.__setattr__(self, key, value)
|
||||||
|
|
||||||
|
@ -202,8 +206,8 @@ class Signature:
|
||||||
"keyid": repr(self.keyid),
|
"keyid": repr(self.keyid),
|
||||||
"algorithm": self.algorithm,
|
"algorithm": self.algorithm,
|
||||||
"headers": self.headers,
|
"headers": self.headers,
|
||||||
"created": self.created,
|
"created": self.created.timestamp() if self.created is not None else None,
|
||||||
"expires": self.expires
|
"expires": self.expires.timestamp() if self.expires is not None else None
|
||||||
}
|
}
|
||||||
|
|
||||||
str_data = ", ".join(f"{key}={value}" for key, value in data.items())
|
str_data = ", ".join(f"{key}={value}" for key, value in data.items())
|
||||||
|
@ -232,7 +236,7 @@ class Signature:
|
||||||
key, value = chunk.split("=", 1)
|
key, value = chunk.split("=", 1)
|
||||||
data[key.lower()] = value.strip("\"")
|
data[key.lower()] = value.strip("\"")
|
||||||
|
|
||||||
return cls(**data) # type: ignore
|
return cls(**data)
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -264,32 +268,6 @@ class Signature:
|
||||||
return self.algs[0]
|
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:
|
def compile(self) -> str:
|
||||||
"Generate a string for a Signature header"
|
"Generate a string for a Signature header"
|
||||||
|
|
||||||
|
@ -297,8 +275,8 @@ class Signature:
|
||||||
"keyId": self.keyid,
|
"keyId": self.keyid,
|
||||||
"algorithm": self.algorithm.value,
|
"algorithm": self.algorithm.value,
|
||||||
"headers": " ".join(self.headers),
|
"headers": " ".join(self.headers),
|
||||||
"created": self.created,
|
"created": self.created.timestamp() if self.created is not None else None,
|
||||||
"expires": self.expires,
|
"expires": self.expires.timestamp() if self.expires is not None else None,
|
||||||
"signature": self.signature
|
"signature": self.signature
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,7 @@ class SignerTest(unittest.TestCase):
|
||||||
output = signer.sign_headers('GET', url, headers={'date': date}, sign_all = True)
|
output = signer.sign_headers('GET', url, headers={'date': date}, sign_all = True)
|
||||||
data = {
|
data = {
|
||||||
'date': "Fri, 25 Nov 2022 06:09:42 GMT",
|
'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)
|
self.assertEqual(output, data)
|
||||||
|
|
Loading…
Reference in a new issue