Skip to content

Commit 7c020af

Browse files
committed
Hardened the test suite to ensure that all code paths are covered
1 parent 316f0a8 commit 7c020af

File tree

4 files changed

+33
-27
lines changed

4 files changed

+33
-27
lines changed

test/auth/test_client_credentials_authenticator.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ def test_refresh_token(self):
1515
time.sleep(20)
1616

1717
authenticator = ClientCredentialsAuthenticator.builder(self.oauth_host, "dummy-client", "dummy-secret") \
18-
.scopes({"openid", "foo"}) \
18+
.scopes("openid", "foo") \
1919
.build()
2020

2121
self.assertTrue(authenticator.get_auth_token(), "Access token should not be empty")

zitadel_client/auth/authenticator.py

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,30 @@ def get_host(self) -> str:
4343

4444
class Token:
4545
def __init__(self, access_token: str, expires_at: datetime):
46+
"""
47+
Initializes a new Token instance.
48+
49+
Parameters:
50+
- access_token (str): The JWT or OAuth token.
51+
- expires_at (datetime): The expiration time of the token. It should be timezone-aware.
52+
If a naive datetime is provided, it will be converted to an aware datetime in UTC.
53+
"""
4654
self.access_token = access_token
47-
self.expires_at = expires_at
55+
56+
# Ensure expires_at is timezone-aware. If naive, assume UTC.
57+
if expires_at.tzinfo is None:
58+
self.expires_at = expires_at.replace(tzinfo=timezone.utc)
59+
else:
60+
self.expires_at = expires_at
4861

4962
def is_expired(self) -> bool:
63+
"""
64+
Checks if the token is expired by comparing the current UTC time
65+
with the token's expiration time.
66+
67+
Returns:
68+
- bool: True if expired, False otherwise.
69+
"""
5070
return datetime.now(timezone.utc) >= self.expires_at
5171

5272

@@ -64,14 +84,14 @@ def __init__(self, host: str):
6484
:param host: The base URL for the OAuth provider.
6585
"""
6686
self.open_id = OpenId(host)
67-
self.auth_scopes = "openid urn:zitadel:iam:org:project:id:zitadel:aud"
87+
self.auth_scopes = {"openid", "urn:zitadel:iam:org:project:id:zitadel:aud"}
6888

69-
def scopes(self, auth_scopes: set) -> "OAuthAuthenticatorBuilder":
89+
def scopes(self, *auth_scopes: str) -> "OAuthAuthenticatorBuilder":
7090
"""
7191
Sets the authentication scopes for the OAuth authenticator.
7292
73-
:param auth_scopes: A set of scope strings.
93+
:param auth_scopes: A variable number of scope strings.
7494
:return: The builder instance to allow for method chaining.
7595
"""
76-
self.auth_scopes = " ".join(auth_scopes)
96+
self.auth_scopes = set(auth_scopes)
7797
return self

zitadel_client/auth/client_credentials_authenticator.py

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import override
1+
from typing import override, Set
22

33
from authlib.integrations.requests_client import OAuth2Session
44

@@ -14,7 +14,7 @@ class ClientCredentialsAuthenticator(OAuthAuthenticator):
1414
Uses client_id and client_secret to obtain an access token from the OAuth2 token endpoint.
1515
"""
1616

17-
def __init__(self, open_id: OpenId, client_id: str, client_secret: str, auth_scopes: str):
17+
def __init__(self, open_id: OpenId, client_id: str, client_secret: str, auth_scopes: Set[str]):
1818
"""
1919
Constructs a ClientCredentialsAuthenticator.
2020
@@ -23,7 +23,7 @@ def __init__(self, open_id: OpenId, client_id: str, client_secret: str, auth_sco
2323
:param client_secret: The OAuth client secret.
2424
:param auth_scopes: The scope(s) for the token request.
2525
"""
26-
super().__init__(open_id, OAuth2Session(client_id=client_id, client_secret=client_secret, scope=auth_scopes))
26+
super().__init__(open_id, OAuth2Session(client_id=client_id, client_secret=client_secret, scope=" ".join(auth_scopes)))
2727

2828
@override
2929
def get_grant(self) -> dict:
@@ -73,9 +73,4 @@ def build(self) -> ClientCredentialsAuthenticator:
7373
7474
:return: A configured ClientCredentialsAuthenticator.
7575
"""
76-
return ClientCredentialsAuthenticator(
77-
self.open_id, # OpenId instance with endpoint info (constructed from host)
78-
self.client_id, # OAuth client identifier
79-
self.client_secret, # OAuth client secret
80-
self.auth_scopes # Authentication scopes configured in the builder
81-
)
76+
return ClientCredentialsAuthenticator(self.open_id, self.client_id, self.client_secret, self.auth_scopes)

zitadel_client/auth/web_token_authenticator.py

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from datetime import datetime, timedelta, timezone
2+
from typing import Set
23

34
from authlib.integrations.requests_client import OAuth2Session
45
from authlib.jose import jwt, JoseError
@@ -15,7 +16,7 @@ class WebTokenAuthenticator(OAuthAuthenticator):
1516
This implementation builds a JWT assertion dynamically in get_grant().
1617
"""
1718

18-
def __init__(self, open_id: OpenId, auth_scopes: str,
19+
def __init__(self, open_id: OpenId, auth_scopes: Set[str],
1920
jwt_issuer: str, jwt_subject: str, jwt_audience: str,
2021
private_key: str, jwt_lifetime: timedelta = timedelta(hours=1), jwt_algorithm: str = "RS256"):
2122
"""
@@ -30,7 +31,7 @@ def __init__(self, open_id: OpenId, auth_scopes: str,
3031
:param jwt_lifetime: Lifetime of the JWT in seconds.
3132
:param jwt_algorithm: The JWT signing algorithm (default "RS256").
3233
"""
33-
super().__init__(open_id, OAuth2Session(scope=auth_scopes))
34+
super().__init__(open_id, OAuth2Session(scope=" ".join(auth_scopes)))
3435
self.jwt_issuer = jwt_issuer
3536
self.jwt_subject = jwt_subject
3637
self.jwt_audience = jwt_audience
@@ -109,16 +110,6 @@ def token_lifetime_seconds(self, seconds: int) -> "JWTAuthenticatorBuilder":
109110
self.jwt_lifetime = timedelta(seconds=seconds)
110111
return self
111112

112-
def scopes(self, scopes: set) -> "JWTAuthenticatorBuilder":
113-
"""
114-
Overrides the default scopes for the JWTAuthenticator.
115-
116-
:param scopes: A set of scope strings.
117-
:return: The builder instance.
118-
"""
119-
self.auth_scopes = " ".join(scopes)
120-
return self
121-
122113
def build(self) -> WebTokenAuthenticator:
123114
"""
124115
Builds and returns a new JWTAuthenticator instance using the configured parameters.

0 commit comments

Comments
 (0)