-
Notifications
You must be signed in to change notification settings - Fork 56
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-352846 OAuth Authentication: #2 OAuth Support #537
Conversation
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.
Looks good, had bunch of nit comments mostly
src/main/java/net/snowflake/ingest/connection/OAuthManager.java
Outdated
Show resolved
Hide resolved
} | ||
|
||
/** refreshToken - Get new access token using refresh_token, client_id, client_secret */ | ||
void refreshToken() { |
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.
nit: it looks like this can be private
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.
Fixed.
src/test/java/net/snowflake/ingest/streaming/internal/OAuthTest.java
Outdated
Show resolved
Hide resolved
@@ -279,6 +290,28 @@ public RequestBuilder( | |||
this.host = hostName; | |||
this.userAgentSuffix = userAgentSuffix; | |||
|
|||
// create our security/token manager | |||
switch (credential.getClass().getSimpleName()) { |
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.
Using a getClass to do the type check is probably fine here but I'd rather we use instanceof check here instead. The main reason is that, getClass().getSimpleName() will break if someone creates their own credentials class but instanceof will work for inheritance without problem.
if (credential instanceof KeyPair) {
...
} else if( credential instanceof OAuthCredential) {
...
}
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.
Fixed, thanks for the tip!
} | ||
|
||
/** Test instantiate OAuth manager with invalid params */ | ||
@Test |
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.
You can use
@test(expected = IllegalArgumentException.class)
for test cases throws the correct exception
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.
Fixed, thanks for the tip!
Codecov Report
@@ Coverage Diff @@
## master #537 +/- ##
==========================================
- Coverage 78.37% 78.30% -0.08%
==========================================
Files 73 76 +3
Lines 4597 4734 +137
Branches 408 424 +16
==========================================
+ Hits 3603 3707 +104
- Misses 821 846 +25
- Partials 173 181 +8
... and 1 file with indirect coverage changes 📣 We’re building smart automated test selection to slash your CI/CD build times. Learn more |
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.
Nice change! Left some comments, PTAL
@@ -0,0 +1,55 @@ | |||
/* | |||
* Copyright (c) 2012-2023 Snowflake Computing Inc. All rights reserved. |
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.
nit: 2023
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.
Fixed.
@Override | ||
String getToken() { | ||
if (refreshFailed.get()) { | ||
LOGGER.error("getToken request failed due to token refresh failure"); |
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.
which LOGGER are you using?
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.
And do we need to log it since an exception will be thrown?
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 LOGGER is from its super SecurityManager
. Each client has exactly one SecurityManager which is either JWTManager
or OAuthManager
.
String getToken() { | ||
if (refreshFailed.get()) { | ||
LOGGER.error("getToken request failed due to token refresh failure"); | ||
throw new SecurityException(); |
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 add a message in the exception?
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.
Moved error message into the exception.
@@ -1,5 +1,5 @@ | |||
/* | |||
* Copyright (c) 2022 Snowflake Computing Inc. All rights reserved. | |||
* Copyright (c) 2023 Snowflake Computing Inc. All rights reserved. |
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 updating the year?
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.
Updated by mistake, fixed.
...ain/java/net/snowflake/ingest/streaming/internal/SnowflakeStreamingIngestClientInternal.java
Show resolved
Hide resolved
channelStatus.getChannelName(), | ||
channelStatus.getChannelSequencer(), | ||
channelStatus.getStatusCode(), | ||
channelStatus.getMessage(), |
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 the message is removed?
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 my mistake merge, reverted.
@@ -50,6 +56,7 @@ public class Constants { | |||
public static final int MAX_STREAMING_INGEST_API_CHANNEL_RETRY = 3; | |||
public static final int STREAMING_INGEST_TELEMETRY_UPLOAD_INTERVAL_IN_SEC = 10; | |||
public static final long EP_NDV_UNKNOWN = -1L; | |||
public static final int MAX_REFRESH_TOKEN_RETRY = 3; |
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.
is this only used for OAuth? if yes, could you add it to the name to make it clear?
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, modified.
this.refreshToken = refreshToken; | ||
} | ||
|
||
public void setExpires_in(int expires_in) { |
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 should be expiresIn
.
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.
Fixed.
private final String clientSecret; | ||
private String accessToken; | ||
private String refreshToken; | ||
private int expires_in; |
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.
Nit, expiresIn
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.
Fixed.
/** | ||
* RequestBuilder - this constructor is for testing OAuth purposes only | ||
* | ||
* @param securityManager - security manager, either JWTManager or OAuthManager | ||
*/ | ||
public RequestBuilder(SecurityManager securityManager) { | ||
this.port = DEFAULT_PORT; | ||
this.scheme = DEFAULT_SCHEME; | ||
this.host = DEFAULT_HOST_SUFFIX; | ||
this.securityManager = securityManager; | ||
|
||
this.userAgentSuffix = null; | ||
this.authType = "OAuth"; | ||
this.telemetryService = 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 belongs in a test class
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.
Is it possible put a constructor of RequestBuilder
in other class?
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.
Just found out we actually don't need this constructor. Removed.
// The authorization type used in auth header | ||
public final String authType; |
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.
Could you infer this from the SecurityManager type?
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.
securityManager = | ||
new OAuthManager( | ||
accountName, userName, (OAuthCredential) credential, makeBaseURI(), telemetryService); | ||
authType = Constants.OAUTH; |
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.
If we are assigning securityManager here could we simply call something like securityManager.getAuthType()
and use when needed?
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.
It turns out that we actually don't need authType
, removed.
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
public class SnowflakeOAuthClient implements OAuthClient { |
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.
Docstring needed.
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.
Added
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.
LGTM! Left some minor comments, PTAL. Could you make sure the Code Coverage report won't regress? Looks like we're missing some test coverage
throw new IllegalArgumentException(); | ||
} | ||
|
||
// Set up the telemetry service if needed | ||
this.telemetryService = | ||
ENABLE_TELEMETRY_TO_SF | ||
ENABLE_TELEMETRY_TO_SF && credential instanceof KeyPair |
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.
Please add a TODO in the comment with JIRA mentioning that OAuth support will come later
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.
Fixed.
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) { | ||
throw new SFException(e, ErrorCode.KEYPAIR_CREATION_FAILURE); | ||
} | ||
logger.logInfo("Using JWT KeyPair for authorization"); |
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.
nit: could we combine the log line with the one below and use the authType as an input?
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.
Fixed.
MAKE_URI_FAILURE("0032"), | ||
OAUTH_REFRESH_TOKEN_ERROR("0033"), | ||
INVALID_CONFIG_PARAMETER("0034"); |
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 don't the corresponding error messages being added, did I miss anything?
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.
Used 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.
@sfc-gh-alhuang Translations for these errors are missing, see https://github.com/snowflakedb/snowflake-ingest-java/blob/master/src/main/resources/net/snowflake/ingest/ingest_error_messages.properties#L36.
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.
@sfc-gh-lsembera Thanks for the hint! I'll send a PR to add the error messages.
2f5a0fa
to
6d1e967
Compare
OAuthCredential oAuthCredential, | ||
URIBuilder baseURIBuilder, | ||
TelemetryService telemetryService) { | ||
this( |
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 tests
baseURIBuilder, | ||
DEFAULT_UPDATE_THRESHOLD_RATIO, | ||
telemetryService); | ||
} |
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 tests?
|
||
// check if update threshold is within (0, 1) | ||
if (updateThresholdRatio <= 0 || updateThresholdRatio >= 1) { | ||
throw new IllegalArgumentException("updateThresholdRatio should fall in (0, 1)"); |
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.
Missing tests
refreshToken(); | ||
} |
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 covered by tests?
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.
For this #L94-L95 and #L50-L57, those lines require actual snowflake oauth client to run. The lines required backdoor function which cannot be test in this repo, the tests are added in the dew test PR.
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.
Changes since last time look good! See my comments about some missing tests. Thanks!
This PR includes core and test code for OAuth authentication. For more details, please refer to this doc.
According to the discussion, the integration would be added in dew. All OAuth unit test would be perform by mock OAuth client. For detail of integration test, please refer to this PR.