33from __future__ import annotations
44
55import base64
6+ import json
67import time
7- from functools import cached_property
8+ import uuid
89from typing import TYPE_CHECKING , ClassVar
910
1011import palantir_oauth_client
1112import requests
1213from requests .structures import CaseInsensitiveDict
1314
1415from foundry_dev_tools .config .config_types import Host
15- from foundry_dev_tools .errors .config import TokenProviderConfigError
16+ from foundry_dev_tools .errors .config import InvalidOrExpiredJWTTokenError , TokenProviderConfigError
1617from foundry_dev_tools .errors .handling import ErrorHandlingConfig , raise_foundry_api_error
1718from foundry_dev_tools .errors .multipass import ClientAuthenticationFailedError
1819from foundry_dev_tools .utils .config import entry_point_fdt_token_provider
1920
2021if TYPE_CHECKING :
2122 from foundry_dev_tools .config .config_types import FoundryOAuthGrantType , Token
23+ from foundry_dev_tools .config .context import FoundryContext
2224
2325
2426class TokenProvider :
@@ -56,10 +58,18 @@ def requests_auth_handler(self, r: requests.PreparedRequest) -> requests.Prepare
5658 def set_requests_session (self , session : requests .Session ) -> None :
5759 """No-op by default."""
5860
61+ def set_expiration (self , ctx : FoundryContext ) -> None :
62+ """No-op by default.
63+
64+ Used for jwt expiration retrieval.
65+ """
66+
5967
6068class JWTTokenProvider (TokenProvider ):
6169 """Provides Host and Token."""
6270
71+ __expiration : int | None = None
72+
6373 def __init__ (self , host : Host | str , jwt : Token ) -> None :
6474 """Initialize the JWTTokenProvider.
6575
@@ -70,9 +80,39 @@ def __init__(self, host: Host | str, jwt: Token) -> None:
7080 super ().__init__ (host )
7181 self ._jwt = jwt
7282
73- @cached_property
83+ def _decode_token_id (self ) -> str :
84+ split_jwt = self ._jwt .split ("." )
85+ if len (split_jwt ) < 2 :
86+ raise InvalidOrExpiredJWTTokenError
87+ try :
88+ claim_part = split_jwt [1 ] + "==" # add padding, which is omitted by jwt
89+ claim_json = base64 .b64decode (claim_part )
90+ claims = json .loads (claim_json )
91+ jti = base64 .b64decode (claims ["jti" ])
92+ return str (uuid .UUID (bytes = jti ))
93+ except Exception as e :
94+ raise InvalidOrExpiredJWTTokenError from e
95+
96+ def set_expiration (self , ctx : FoundryContext ) -> None :
97+ """This method decodes the jwt token and retrieves its expiration."""
98+ if self .__expiration :
99+ return
100+ token_id = self ._decode_token_id ()
101+ try :
102+ tokens = ctx .multipass .get_tokens () # retrieve all tokens the user has generated
103+ for token in tokens :
104+ if token ["tokenId" ] == token_id : # search for this jwt token
105+ # save expiration time, which will be used in the token() property
106+ self .__expiration = time .time () + token ["expires_in" ] - 15 # subtract 15 seconds, to be safe
107+ break
108+ except : # noqa: E722
109+ raise InvalidOrExpiredJWTTokenError from None
110+
111+ @property
74112 def token (self ) -> Token :
75113 """Returns the token supplied when creating this Provider."""
114+ if self .__expiration and self .__expiration <= time .time ():
115+ raise InvalidOrExpiredJWTTokenError
76116 return self ._jwt
77117
78118
0 commit comments