Skip to content

Commit 3a400ef

Browse files
committed
feat: support clock skew leeway in hsa_created_at validation
When multiple systems are working together, clock skew can become an issue especially when validating token start times. If the issuing system clock is ahead of the validating system clock, it is possible that the token always ends up being not yet valid when validation is attempted. It is fairly common industry practice to allow some leeway for clock skew. For example JWT spec notes for the nbf (not before) claim that > Implementers MAY provide for some small leeway, usually no more than a > few minutes, to account for clock skew. This commit adds a new setting HSA_CLOCK_SKEW_LEEWAY_SECONDS defaulting to 60 seconds which allows the HSA to be created 60 seconds in the future. refs: HAUKI-694
1 parent 7942aef commit 3a400ef

5 files changed

Lines changed: 56 additions & 2 deletions

File tree

hauki/settings.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ def get_git_revision_hash() -> str:
7575
MAIL_MAILGUN_DOMAIN=(str, ""),
7676
MAIL_MAILGUN_API=(str, ""),
7777
RESOURCE_DEFAULT_TIMEZONE=(str, None),
78+
HSA_CLOCK_SKEW_LEEWAY_SECONDS=(int, 30),
7879
)
7980

8081
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
@@ -393,3 +394,5 @@ def get_git_revision_hash() -> str:
393394
for i in range(64)
394395
]
395396
)
397+
398+
HSA_CLOCK_SKEW_LEEWAY_SECONDS = env("HSA_CLOCK_SKEW_LEEWAY_SECONDS")

hauki/test_settings.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from .settings import * # noqa: F403
2+
3+
HSA_CLOCK_SKEW_LEEWAY_SECONDS = 0

hours/authentication.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import hashlib
22
import hmac
33
import urllib.parse
4+
from datetime import timedelta
45

56
from dateutil.parser import parse
7+
from django.conf import settings
68
from django.contrib.auth import get_user_model
79
from django.db.models import Q
810
from django.utils import timezone
@@ -135,7 +137,9 @@ def validate_params_and_signature(params) -> bool:
135137
try:
136138
created_at = parse(params["hsa_created_at"])
137139
try:
138-
if created_at > timezone.now():
140+
if created_at > timezone.now() + timedelta(
141+
seconds=settings.HSA_CLOCK_SKEW_LEEWAY_SECONDS
142+
):
139143
raise SignatureValidationError(_("Invalid hsa_created_at"))
140144
except TypeError:
141145
raise SignatureValidationError(_("Invalid hsa_created_at"))

hours/tests/test_hauki_signed_auth.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
import pytest
55
from django.urls import reverse
6+
from django.utils import timezone
7+
from freezegun import freeze_time
68
from pytz import UTC
79
from rest_framework.test import APIRequestFactory
810
from rest_framework.views import APIView
@@ -73,6 +75,48 @@ def test_get_auth_required_header_invalid_created_at(
7375
assert str(response.data["detail"]) == "Invalid hsa_created_at"
7476

7577

78+
@pytest.mark.django_db
79+
@pytest.mark.parametrize(
80+
"leeway,expected_response_code,expected_response_data_contains",
81+
[(0, 403, "Invalid hsa_created_at"), (1, 200, "You are authenticated")],
82+
)
83+
def test_get_auth_required_header_clock_skew_leeway(
84+
api_client,
85+
data_source,
86+
signed_auth_key_factory,
87+
leeway,
88+
expected_response_code,
89+
expected_response_data_contains,
90+
settings,
91+
):
92+
signed_auth_key = signed_auth_key_factory(data_source=data_source)
93+
url = reverse("auth_required_test-list")
94+
95+
now = timezone.now()
96+
settings.HSA_CLOCK_SKEW_LEEWAY_SECONDS = leeway
97+
98+
with freeze_time(now):
99+
data = {
100+
"hsa_source": data_source.id,
101+
"hsa_username": "test_user",
102+
"hsa_created_at": (now + datetime.timedelta(seconds=1)).isoformat(),
103+
"hsa_valid_until": (
104+
now + datetime.timedelta(hours=1, seconds=1)
105+
).isoformat(),
106+
}
107+
108+
source_string = join_params(data)
109+
signature = calculate_signature(signed_auth_key.signing_key, source_string)
110+
111+
authz_string = "haukisigned " + urllib.parse.urlencode(
112+
{**data, "hsa_signature": signature}
113+
)
114+
115+
response = api_client.get(url, HTTP_AUTHORIZATION=authz_string)
116+
assert response.status_code == expected_response_code
117+
assert expected_response_data_contains in str(response.data)
118+
119+
76120
@pytest.mark.django_db
77121
def test_get_auth_required_header_timezone_missing_created_at(
78122
api_client, data_source, signed_auth_key_factory

pytest.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
[pytest]
2-
DJANGO_SETTINGS_MODULE = hauki.settings
2+
DJANGO_SETTINGS_MODULE = hauki.test_settings
33
filterwarnings =
44
ignore::django.utils.deprecation.RemovedInDjango50Warning

0 commit comments

Comments
 (0)