Skip to content

Commit 72aa2c6

Browse files
committed
Merge branch '292-float-iat' into 'main'
Support for sub-second precision in JWT iat claim Closes #292 See merge request yaal/canaille!286
2 parents fad738b + 4e8be54 commit 72aa2c6

4 files changed

Lines changed: 17 additions & 11 deletions

File tree

CHANGES.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ Fixed
66
- JWK algorithm detection with OKP keys.
77
- OIDC Client ``acess_token`` and ``refresh_token`` are unique in the SQL backend.
88

9+
Added
10+
^^^^^
11+
- OIDC ``iat`` claims are now float, with milli/micro-second precision. :issue:`292`
12+
- LDAP dates are stored with milliseconds precision and timezones.
13+
914
[0.0.80] - 2025-08-22
1015
---------------------
1116

canaille/backends/ldap/utils.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@ def ldap_to_python(value, syntax):
4343
return datetime.datetime.strptime(value, format_string).replace(
4444
tzinfo=datetime.timezone.utc
4545
)
46-
return datetime.datetime.strptime(value, "%Y%m%d%H%M%S%z")
46+
format_string = "%Y%m%d%H%M%S.%f%z" if "." in value else "%Y%m%d%H%M%S%z"
47+
return datetime.datetime.strptime(value, format_string)
4748

4849
if syntax == Syntax.INTEGER:
4950
return int(value.decode("utf-8"))
@@ -66,9 +67,9 @@ def python_to_ldap(value, syntax, encode=True):
6667
if value == datetime.datetime.min:
6768
value = LDAP_NULL_DATE
6869
elif not value.tzinfo or value.tzinfo == datetime.timezone.utc:
69-
value = value.strftime("%Y%m%d%H%M%SZ")
70+
value = value.strftime("%Y%m%d%H%M%S.%fZ")
7071
else:
71-
value = value.strftime("%Y%m%d%H%M%S%z")
72+
value = value.strftime("%Y%m%d%H%M%S.%f%z")
7273

7374
if syntax == Syntax.INTEGER and isinstance(value, int):
7475
value = str(value)

canaille/oidc/models.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ def is_expired(self):
160160
seconds=int(self.lifetime)
161161
) < datetime.datetime.now(datetime.timezone.utc)
162162

163-
def get_auth_time(self) -> int | None:
163+
def get_auth_time(self) -> float | None:
164164
return self.auth_time.timestamp() if self.auth_time else None
165165

166166
def get_acr(self):
@@ -173,7 +173,7 @@ def get_amr(self):
173173
class Token(BaseToken, TokenMixin):
174174
@property
175175
def expire_date(self):
176-
return self.issue_date + datetime.timedelta(seconds=int(self.lifetime))
176+
return self.issue_date + datetime.timedelta(seconds=self.lifetime)
177177

178178
@property
179179
def revoked(self):
@@ -182,11 +182,11 @@ def revoked(self):
182182
def get_scope(self):
183183
return " ".join(self.scope)
184184

185-
def get_issued_at(self) -> int:
186-
return int(self.issue_date.timestamp())
185+
def get_issued_at(self) -> float:
186+
return self.issue_date.timestamp()
187187

188-
def get_expires_at(self) -> int:
189-
return self.get_issued_at() + int(self.lifetime)
188+
def get_expires_at(self) -> float:
189+
return self.get_issued_at() + self.lifetime
190190

191191
def is_refresh_token_active(self):
192192
if self.revokation_date:

tests/backends/ldap/test_utils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ def test_ldap_to_python():
104104
datetime.datetime(2000, 1, 2, 3, 4, 5, tzinfo=datetime.timezone.utc),
105105
Syntax.GENERALIZED_TIME,
106106
)
107-
== b"20000102030405Z"
107+
== b"20000102030405.000000Z"
108108
)
109109
assert (
110110
python_to_ldap(
@@ -119,7 +119,7 @@ def test_ldap_to_python():
119119
),
120120
Syntax.GENERALIZED_TIME,
121121
)
122-
== b"20000102030405-0200"
122+
== b"20000102030405.000000-0200"
123123
)
124124
assert (
125125
python_to_ldap(datetime.datetime.min, Syntax.GENERALIZED_TIME)

0 commit comments

Comments
 (0)