33import com .amazonaws .util .StringUtils ;
44import com .google .common .base .Strings ;
55import java .net .URI ;
6+ import java .nio .charset .StandardCharsets ;
7+ import java .util .Base64 ;
68import net .snowflake .client .jdbc .ErrorCode ;
79import net .snowflake .client .log .SFLogger ;
810import net .snowflake .client .log .SFLoggerFactory ;
@@ -124,6 +126,25 @@ static void fillCachedOAuthRefreshToken(SFLoginInput loginInput) throws SFExcept
124126 loginInput , host , loginInput .getUserName (), CachedCredentialType .OAUTH_REFRESH_TOKEN );
125127 }
126128
129+ /**
130+ * Reuse the cached OAuth access token & DPoP public key tied to it
131+ *
132+ * @param loginInput login input to attach refresh token
133+ */
134+ static void fillCachedDPoPBundledAccessToken (SFLoginInput loginInput ) throws SFException {
135+ String host = getHostForOAuthCacheKey (loginInput );
136+ logger .debug (
137+ "Looking for cached DPoP public key for user: {}, host: {}" ,
138+ loginInput .getUserName (),
139+ host );
140+ getInstance ()
141+ .fillCachedCredential (
142+ loginInput ,
143+ host ,
144+ loginInput .getUserName (),
145+ CachedCredentialType .DPOP_BUNDLED_ACCESS_TOKEN );
146+ }
147+
127148 /** Reuse the cached token stored locally */
128149 synchronized void fillCachedCredential (
129150 SFLoginInput loginInput , String host , String username , CachedCredentialType credType )
@@ -137,24 +158,34 @@ synchronized void fillCachedCredential(
137158 return ;
138159 }
139160
140- String cred ;
161+ String base64EncodedCred , cred = null ;
141162 try {
142- cred = secureStorageManager .getCredential (host , username , credType .getValue ());
163+ base64EncodedCred = secureStorageManager .getCredential (host , username , credType .getValue ());
143164 } catch (NoClassDefFoundError error ) {
144165 logMissingJnaJarForSecureLocalStorage ();
145166 return ;
146167 }
147168
148- if (cred == null ) {
169+ if (base64EncodedCred == null ) {
149170 logger .debug ("Retrieved {} is null" , credType );
150171 }
151172
152173 logger .debug (
153174 "Setting {}{} token for user: {}, host: {}" ,
154- cred == null ? "null " : "" ,
175+ base64EncodedCred == null ? "null " : "" ,
155176 credType .getValue (),
156177 username ,
157178 host );
179+
180+ if (base64EncodedCred != null && credType != CachedCredentialType .DPOP_BUNDLED_ACCESS_TOKEN ) {
181+ try {
182+ cred = new String (Base64 .getDecoder ().decode (base64EncodedCred ));
183+ } catch (Exception e ) {
184+ // handle legacy non-base64 encoded cache values (CredentialManager fails to decode)
185+ deleteTemporaryCredential (host , username , credType );
186+ return ;
187+ }
188+ }
158189 switch (credType ) {
159190 case ID_TOKEN :
160191 loginInput .setIdToken (cred );
@@ -168,12 +199,29 @@ synchronized void fillCachedCredential(
168199 case OAUTH_REFRESH_TOKEN :
169200 loginInput .setOauthRefreshToken (cred );
170201 break ;
202+ case DPOP_BUNDLED_ACCESS_TOKEN :
203+ updateInputWithTokenAndPublicKey (base64EncodedCred , loginInput );
204+ break ;
171205 default :
172206 throw new SFException (
173207 ErrorCode .INTERNAL_ERROR , "Unrecognized type {} for local cached credential" , credType );
174208 }
175209 }
176210
211+ private void updateInputWithTokenAndPublicKey (String cred , SFLoginInput loginInput )
212+ throws SFException {
213+ if (Strings .isNullOrEmpty (cred )) {
214+ String [] values = cred .split ("\\ ." );
215+ if (values .length != 2 ) {
216+ throw new SFException (
217+ ErrorCode .INTERNAL_ERROR , "Invalid DPoP bundled access token credential format" );
218+ }
219+ Base64 .Decoder decoder = Base64 .getDecoder ();
220+ loginInput .setOauthAccessToken (new String (decoder .decode (values [0 ])));
221+ loginInput .setDPoPPublicKey (new String (decoder .decode (values [1 ])));
222+ }
223+ }
224+
177225 static void writeIdToken (SFLoginInput loginInput , String idToken ) throws SFException {
178226 logger .debug (
179227 "Caching id token in a secure storage for user: {}, host: {}" ,
@@ -238,6 +286,30 @@ static void writeOAuthRefreshToken(SFLoginInput loginInput) throws SFException {
238286 CachedCredentialType .OAUTH_REFRESH_TOKEN );
239287 }
240288
289+ /**
290+ * Store OAuth DPoP Public Key With Token
291+ *
292+ * @param loginInput loginInput to denote to the cache
293+ */
294+ static void writeDPoPBundledAccessToken (SFLoginInput loginInput ) throws SFException {
295+ String host = getHostForOAuthCacheKey (loginInput );
296+ logger .debug (
297+ "Caching DPoP public key in a secure storage for user: {}, host: {}" ,
298+ loginInput .getUserName (),
299+ host );
300+ Base64 .Encoder encoder = Base64 .getEncoder ();
301+ String tokenBase64 =
302+ encoder .encodeToString (loginInput .getOauthAccessToken ().getBytes (StandardCharsets .UTF_8 ));
303+ String publicKeyBase64 =
304+ encoder .encodeToString (loginInput .getDPoPPublicKey ().getBytes (StandardCharsets .UTF_8 ));
305+ getInstance ()
306+ .writeTemporaryCredential (
307+ host ,
308+ loginInput .getUserName (),
309+ tokenBase64 + "." + publicKeyBase64 ,
310+ CachedCredentialType .DPOP_BUNDLED_ACCESS_TOKEN );
311+ }
312+
241313 /** Store the temporary credential */
242314 synchronized void writeTemporaryCredential (
243315 String host , String user , String cred , CachedCredentialType credType ) {
@@ -256,52 +328,75 @@ synchronized void writeTemporaryCredential(
256328 }
257329
258330 try {
259- secureStorageManager .setCredential (host , user , credType .getValue (), cred );
331+ if (credType == CachedCredentialType .DPOP_BUNDLED_ACCESS_TOKEN ) {
332+ // DPOP_ACCESS_TOKEN is already preformatted and Base64 encoded
333+ secureStorageManager .setCredential (host , user , credType .getValue (), cred );
334+ } else {
335+ String base64EncodedCred =
336+ Base64 .getEncoder ().encodeToString (cred .getBytes (StandardCharsets .UTF_8 ));
337+ secureStorageManager .setCredential (host , user , credType .getValue (), base64EncodedCred );
338+ }
260339 } catch (NoClassDefFoundError error ) {
261340 logMissingJnaJarForSecureLocalStorage ();
262341 }
263342 }
264343
265344 /** Delete the id token cache */
266- static void deleteIdTokenCache (String host , String user ) {
345+ static void deleteIdTokenCacheEntry (String host , String user ) {
267346 logger .debug (
268347 "Removing cached id token from a secure storage for user: {}, host: {}" , user , host );
269348 getInstance ().deleteTemporaryCredential (host , user , CachedCredentialType .ID_TOKEN );
270349 }
271350
272351 /** Delete the mfa token cache */
273- static void deleteMfaTokenCache (String host , String user ) {
352+ static void deleteMfaTokenCacheEntry (String host , String user ) {
274353 logger .debug (
275354 "Removing cached mfa token from a secure storage for user: {}, host: {}" , user , host );
276355 getInstance ().deleteTemporaryCredential (host , user , CachedCredentialType .MFA_TOKEN );
277356 }
278357
279358 /** Delete the Oauth access token cache */
280- static void deleteOAuthAccessTokenCache (String host , String user ) {
359+ static void deleteOAuthAccessTokenCacheEntry (String host , String user ) {
281360 logger .debug (
282- "Removing cached mfa token from a secure storage for user: {}, host: {}" , user , host );
361+ "Removing cached oauth access token from a secure storage for user: {}, host: {}" ,
362+ user ,
363+ host );
283364 getInstance ().deleteTemporaryCredential (host , user , CachedCredentialType .OAUTH_ACCESS_TOKEN );
284365 }
285366
367+ /** Delete the Oauth refresh token cache */
368+ static void deleteOAuthRefreshTokenCacheEntry (String host , String user ) {
369+ logger .debug (
370+ "Removing cached OAuth refresh token from a secure storage for user: {}, host: {}" ,
371+ user ,
372+ host );
373+ getInstance ().deleteTemporaryCredential (host , user , CachedCredentialType .OAUTH_REFRESH_TOKEN );
374+ }
375+
376+ /** Delete the DPoP bundled access token cache */
377+ static void deleteDPoPBundledAccessTokenCacheEntry (String host , String user ) {
378+ logger .debug (
379+ "Removing cached DPoP public key from a secure storage for user: {}, host: {}" , user , host );
380+ getInstance ()
381+ .deleteTemporaryCredential (host , user , CachedCredentialType .DPOP_BUNDLED_ACCESS_TOKEN );
382+ }
383+
286384 /** Delete the OAuth access token cache */
287- static void deleteOAuthAccessTokenCache (SFLoginInput loginInput ) throws SFException {
385+ static void deleteOAuthAccessTokenCacheEntry (SFLoginInput loginInput ) throws SFException {
288386 String host = getHostForOAuthCacheKey (loginInput );
289- deleteOAuthAccessTokenCache (host , loginInput .getUserName ());
387+ deleteOAuthAccessTokenCacheEntry (host , loginInput .getUserName ());
290388 }
291389
292390 /** Delete the OAuth refresh token cache */
293- static void deleteOAuthRefreshTokenCache (SFLoginInput loginInput ) throws SFException {
391+ static void deleteOAuthRefreshTokenCacheEntry (SFLoginInput loginInput ) throws SFException {
294392 String host = getHostForOAuthCacheKey (loginInput );
295- deleteOAuthRefreshTokenCache (host , loginInput .getUserName ());
393+ deleteOAuthRefreshTokenCacheEntry (host , loginInput .getUserName ());
296394 }
297395
298- /** Delete the Oauth refresh token cache */
299- static void deleteOAuthRefreshTokenCache (String host , String user ) {
300- logger .debug (
301- "Removing cached OAuth refresh token from a secure storage for user: {}, host: {}" ,
302- user ,
303- host );
304- getInstance ().deleteTemporaryCredential (host , user , CachedCredentialType .OAUTH_REFRESH_TOKEN );
396+ /** Delete the DPoP bundled access token cache */
397+ static void deleteDPoPBundledAccessTokenCacheEntry (SFLoginInput loginInput ) throws SFException {
398+ String host = getHostForOAuthCacheKey (loginInput );
399+ deleteDPoPBundledAccessTokenCacheEntry (host , loginInput .getUserName ());
305400 }
306401
307402 /**
0 commit comments