-
Notifications
You must be signed in to change notification settings - Fork 171
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
SNOW-1831103/SNOW-1853435: Refresh token & OAuth tokens caching support #2009
Conversation
logger.debug("Unrecognized type {} for local cached credential", credType); | ||
switch (credType) { | ||
case ID_TOKEN: | ||
logger.debug( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the messages are different only in the part after {}
- could we make the login before and once?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
@@ -98,6 +104,14 @@ String getMfaToken() { | |||
return mfaToken; | |||
} | |||
|
|||
public String getOauthAccessToken() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
new public methods may be internal?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed public modifier, it was actually unnecessary
preNewSession(loginInput); | ||
readCachedTokens(loginInput); | ||
|
||
if (AccessTokenProviderFactory.isEligible(getAuthenticator(loginInput))) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we extract methods from newly added parts in openSession?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
|
||
if (AccessTokenProviderFactory.isEligible(getAuthenticator(loginInput))) { | ||
if (loginInput.getOauthAccessToken() != null) { // Access Token cached | ||
loginInput.setAuthenticator(AuthenticatorType.OAUTH.name()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will the new authenticators work with session renewal also?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, but I'm still thinking how to write automated test for it.
TokenResponseDTO tokenResponse = accessTokenProvider.getAccessToken(loginInput); | ||
loginInput.setAuthenticator(AuthenticatorType.OAUTH.name()); | ||
loginInput.setToken(tokenResponse.getAccessToken()); | ||
loginInput.setOauthAccessToken(tokenResponse.getAccessToken()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why do we pass access token twice?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Token
field is generic token field which is related to /login-request body and oauthToken
on the other hand is a field that is being used to store only oauth access token and to interact with token cache
@@ -998,6 +1064,14 @@ public static void deleteMfaTokenCache(String host, String user) { | |||
CredentialManager.getInstance().deleteMfaTokenCache(host, user); | |||
} | |||
|
|||
private static void deleteOAuthAccessTokenCache(String host, String user) { | |||
CredentialManager.getInstance().deleteOAuthAccessTokenCache(host, user); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cannot we expose static methods directly from CredentialManager? under the hood it's instance could be used and we don't need to provide such helper methods?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
private static final SFLogger logger = | ||
SFLoggerFactory.getLogger(OAuthClientCredentialsAccessTokenProvider.class); | ||
|
||
private final ObjectMapper objectMapper = new ObjectMapper(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
don't we want to use ObjectMapperFactory?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No really since it's configuration causes problems with deserialization via constructor
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ObjectMapper
is thread safe, maybe it could be a static field?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed to static field.
objectMapper.readValue(tokenResponse, TokenResponseDTO.class); | ||
logger.debug( | ||
"Received new OAuth access token from: {}", | ||
requestUri.getAuthority() + requestUri.getPath()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let's use separate parameters without concatenation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done.
try { | ||
cred = | ||
secureStorageManager.getCredential( | ||
loginInput.getHostFromServerUrl(), loginInput.getUserName(), credType); | ||
loginInput.getHostFromServerUrl(), loginInput.getUserName(), credType.getValue()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if it is correct for oauth. Guessing from the code above getHostFromServerUrl
returns snowflake host, right? For OAuth it should be IDP address. Otherwise we could accidentally leak tokens between IDPs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Refactored according to your suggestion.
} | ||
|
||
/** Delete the OAuth access token cache */ | ||
static void deleteOAuthAccessTokenCache(String host, String user) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
...FromCache
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd rather leave it as is since another functions are named in similar convention
* @return true if authenticator type is EXTERNALBROWSER | ||
*/ | ||
boolean isExternalbrowserAuthenticator() { | ||
boolean isExternalbrowserOrOAuthFullFlowAuthenticator() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Full flow is a new name? WDYT about isBrowserBasedAuthenticator
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CLIENT_CREDENTIALS flow is not browser based.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Full flow as in opposed to OAUTH authenticator :)
if (tokenResponse.getRefreshToken() != null) { | ||
loginInput.setOauthRefreshToken(tokenResponse.getRefreshToken()); | ||
} | ||
} catch (Throwable e) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do not catch throwable. What if OOM or stack overflow is thrown here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True, fixed.
&& asBoolean(loginInput.getSessionParameters().get(CLIENT_STORE_TEMPORARY_CREDENTIAL))) { | ||
CredentialManager.getInstance().writeIdToken(loginInput, ret); | ||
if (asBoolean(loginInput.getSessionParameters().get(CLIENT_STORE_TEMPORARY_CREDENTIAL))) { | ||
if (consentCacheIdToken) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What the consent is here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure, this code was already there. I prefer not to touch it :).
if (consentCacheIdToken) { | ||
CredentialManager.writeIdToken(loginInput, ret); | ||
} | ||
if (loginInput.getOauthAccessToken() != null) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a good assertion, but maybe it would be better placed in CredentialManager
? It would make the code more robust.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd prefer to leave it here. For me it's counterintuitive - it would suggests that we always save this token
private static final SFLogger logger = | ||
SFLoggerFactory.getLogger(OAuthClientCredentialsAccessTokenProvider.class); | ||
|
||
private final ObjectMapper objectMapper = new ObjectMapper(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ObjectMapper
is thread safe, maybe it could be a static field?
requestUri.getAuthority(), | ||
requestUri.getPath()); | ||
String tokenResponse = | ||
HttpUtil.executeGeneralRequest( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we use this utility for non-snowflake requests? It contains some of snowflake-specific behaviour, like telemetry, adding request id etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is also used in case of external browser saml request. It supports configured retry stategy, timeouts and proxy.
@@ -18,10 +18,10 @@ | |||
import net.snowflake.client.log.SFLoggerFactory; | |||
|
|||
@SnowflakeJdbcInternalApi | |||
public class AccessTokenProviderFactory { | |||
public class OAuthAccessTokenProviderFactory { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even more precise - OAuth21
, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is too detailed. Documentation can point this out but that's about it IMHO.
Overview
SNOW-1831103/SNOW-1853435: Refresh token & OAuth tokens caching support
Pre-review self checklist
master
branchmvn -P check-style validate
)mvn verify
and inspecttarget/japicmp/japicmp.html
)SNOW-XXXX:
External contributors - please answer these questions before submitting a pull request. Thanks!
What GitHub issue is this PR addressing? Make sure that there is an accompanying issue to your PR.
Issue: #NNNN
Fill out the following pre-review checklist:
@SnowflakeJdbcInternalApi
(note that public/protected methods/fields in classes marked with this annotation are already internal)Please describe how your code solves the related issue.
Please write a short description of how your code change solves the related issue.