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
feat: Added HMAC-SHA256 signature method in OAuthHmacSha256Signer #703
Conversation
@@ -32,14 +34,34 @@ | |||
@Beta | |||
public final class OAuthHmacSigner implements OAuthSigner { | |||
|
|||
private final Map<String, String> signatureMethodMap = ImmutableMap.of( | |||
"HMAC-SHA1", "HmacSHA1", |
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 understand why you need both the keys and the values 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.
Good question. The signature method that need to be used for the OAuth 1.0 header has to be of the format: "HMAC-SHA1" or "HMAC-SHA2", while the actual Algorithm strings that Java requires have to be of the format "HmacSHA1" or "HmacSHA256" as described here: https://docs.oracle.com/javase/9/docs/specs/security/standard-names.html#mac-algorithms.
My intent was to use a Map to associate the signature method as the key with the corresponding algorithm as the value, so there would not be an accidental mismatch.
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 has been reverted back in favor of a new class
|
||
/** Set the signature method. Valid signature methods are "HMAC-SHA1" and "HMAC-SHA256" */ | ||
public void setSignatureMethod(String signatureMethod) throws IllegalArgumentException { | ||
if (signatureMethodMap.containsKey(signatureMethod)) { |
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.
Maybe use an enum instead?
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 has been reverted back in favor of a new class
@@ -32,4 +35,39 @@ public void testComputeSignature() throws GeneralSecurityException { | |||
signer.tokenSharedSecret = "def"; | |||
assertEquals(EXPECTED_SIGNATURE, signer.computeSignature("foo")); | |||
} | |||
|
|||
public void testComputeSignatureHmacSha256() throws GeneralSecurityException { | |||
final OAuthHmacSigner signer = new OAuthHmacSigner(); |
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.
Google style specifies no final on local variables except where required
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 has been reverted back in favor of a new class
|
||
public void testSetSignatureMethodHmacMD5() { | ||
assertThrows( | ||
"Signature method HMAC-MD5 not available", |
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.
four space indents for continuation lines, per Google style guide
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 has been reverted back in favor of a new 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.
perhaps HMAC-SHA256 should be a separate class? I'm not sure.
@@ -32,14 +34,34 @@ | |||
@Beta | |||
public final class OAuthHmacSigner implements OAuthSigner { |
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.
class doc comment needs to be updated
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 has been reverted back in favor of a new class
I think you may be right about having a separate class. I was trying not to repeat code in a class that would be similar in every way except for three lines, but having a separate class makes a lot of sense, particularly since this is likely to be a special use case signing class, only needed for a few services. I am going to refactor this as a separate class and see if that is simpler. |
@@ -0,0 +1,61 @@ | |||
/* | |||
* Copyright (c) 2010 Google Inc. |
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.
Needs current copyright date, Inc. --> LLC
and never, ever use (c) in anything, here or elsewhere. It is not recognized by the law, no matter how many places you find it.
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 has been updated
* {@link Beta} <br> | ||
* OAuth {@code "HMAC-SHA256"} signature method. | ||
*/ | ||
@Beta |
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.
auth leader (not me) needs to figure out if we want this and if it should be beta
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.
Since the interface the OAuthSigner
interface itself is Beta I took that as a signal that any classes implementing it should also be Beta.
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.
Either way annotation might be enough. Is there a reason to include this in doc comment too?
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 this from the doc comment
public final class OAuthHmacSha256Signer implements OAuthSigner { | ||
|
||
/** Client-shared secret or {@code null} for none. */ | ||
public String clientSharedSecret; |
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.
private on fields please
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 can make these fields private, but then will also need to add accessors/mutators. I noticed in the original class (OAuthHmacSigner) these fields were public and had no methods to get/set the values. Do we want to have this signing class implemented differently from the other on that it is based on? Existing code using OAuthHmacSigner that needs to migrate to this class would have fewer code changes keeping it the same, but then the API was labelled Beta.
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.
mutable public fields are a hard no. Don't try to be consistent with bad code. You can probably make these final with no mutators.
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 client secret and the token secret probably shouldn't be final, as there is often a multi-step flow to obtain the values in separate steps, so they need to be set after the signer instance is created in most instances.
Also, in looking at the other code required for making a call, the OAuthParameters
class will still have mutable public fields (similar to OAuthHmacSigner
), and it is necessary to use that class together with the signer, to make a complete OAuth 1.0 signed request:
Current code needed for HMAC-SHA1:
OAuthHmacSigner hmacSha1Signer = new OAuthHmacSigner();
hmacSha1Signer.clientSharedSecret = CLIENT_SECRET;
hmacSha1Signer.tokenSharedSecret = TOKEN_SECRET;
OAuthParameters oauthParameters = new OAuthParameters();
oauthParameters.signer = hmacSha1Signer;
oauthParameters.consumerKey = CONSUMER_KEY;
oauthParameters.token = ACCESS_TOKEN;
HttpRequestFactory requestFactory = new NetHttpTransport().createRequestFactory(oauthParameters);
Making the fields on the new class private with setters would make the code look like this:
New code needed for HMAC-SHA256:
OAuthHmacSha256Signer hmacSha256Signer = new OAuthHmacSha256Signer();
hmacSha256Signer.setClientSecret(CLIENT_SECRET);
hmacSha256Signer.setTokenSecret(TOKEN_SECRET);
OAuthParameters oauthParameters = new OAuthParameters();
oauthParameters.signer = hmacSha256Signer;
oauthParameters.consumerKey = CONSUMER_KEY;
oauthParameters.token = ACCESS_TOKEN;
HttpRequestFactory requestFactory = new NetHttpTransport().createRequestFactory(oauthParameters);
It feels a little bit inconsistent this way, even though this is arguably the better way. Should anything be done about OAuthParameters
? I'd normally not change something if it isn't necessary.
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 made the fields private, with setter methods.
public String clientSharedSecret; | ||
|
||
/** Token-shared secret or {@code null} for none. */ | ||
public String tokenSharedSecret; |
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.
ditto, also make final
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 made the field private, but not final, due to OAuth 1.0 frequently setting these values in multiple steps of the flow, so they cannot always be set during construction.
|
||
public String computeSignature(String signatureBaseString) throws GeneralSecurityException { | ||
// compute key | ||
StringBuilder keyBuf = new StringBuilder(); |
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.
keyBuffer
Google style never abbreviates
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 has been updated
@@ -0,0 +1,33 @@ | |||
/* | |||
* Copyright (c) 2012 Google Inc. |
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.
fix copyright
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 has been updated
package com.google.api.client.auth.oauth; | ||
|
||
import java.security.GeneralSecurityException; | ||
import junit.framework.TestCase; |
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.
org.junit preferred
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 POM file only has the current junit dependency (junit:junit:4.13.2). I can switch to a different junit, but that would require adding a new dependency. Which group/artifact/version org.junit package is the preferred one to add and use?
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.
junit.* is the old legacy classes. org.junit are the new ones from JUnit 4.
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.
Thank you, I misunderstood. I updated the test to use org.junit / JUnit4
public final class OAuthHmacSha256Signer implements OAuthSigner { | ||
|
||
/** Client-shared secret or {@code null} for none. */ | ||
public String clientSharedSecret; |
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.
mutable public fields are a hard no. Don't try to be consistent with bad code. You can probably make these final with no mutators.
package com.google.api.client.auth.oauth; | ||
|
||
import java.security.GeneralSecurityException; | ||
import junit.framework.TestCase; |
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.
junit.* is the old legacy classes. org.junit are the new ones from JUnit 4.
* {@link Beta} <br> | ||
* OAuth {@code "HMAC-SHA256"} signature method. | ||
*/ | ||
@Beta |
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.
Either way annotation might be enough. Is there a reason to include this in doc comment too?
tokenSharedSecret = tokenSecret; | ||
} | ||
|
||
public String getSignatureMethod() { |
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.
@Override
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 has been updated. Strangely, the annotation did not exist in the original implementing class.
return "HMAC-SHA256"; | ||
} | ||
|
||
public String computeSignature(String signatureBaseString) throws GeneralSecurityException { |
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.
@Override
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 has been updated. Strangely, the annotation did not exist in the original implementing class.
/** Token secret */ | ||
private String tokenSharedSecret; | ||
|
||
public void setClientSecret(String clientSecret) { |
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 are these mutable? It makes the class not thread-safe.
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 client secret can be final, so I did change it, however the token secret usually is not known until after the first request of the three-legged OAuth 1.0 authorization flow. Because of that, it needs to be able to be set later. This is common for OAuth 1.0, as it is a multi-step process where not all of the values are known prior to the first step.
The token secret will usually be set to the final value only once during the authorization flow, before the second step, and never changed again.
private static final String EXPECTED_SIGNATURE = "xDJIQbKJTwGumZFvSG1V3ctym2tz6kD8fKGWPr5ImPU="; | ||
|
||
@Test | ||
public void testComputeSignature() throws GeneralSecurityException { |
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 mutability is deliberate it should be tested.
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 tests have been added to cover the null cases
} | ||
keyBuffer.append('&'); | ||
String tokenSecret = tokenSharedSecret; | ||
if (tokenSecret != 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 branch is not tested when tokenSecret is 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.
New tests have been added to cover the cases when tokenSecret is null
// compute key | ||
StringBuilder keyBuffer = new StringBuilder(); | ||
String clientSecret = clientSharedSecret; | ||
if (clientSecret != 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 branch is not tested when clientSecret is 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.
New tests have been added to cover the cases when clientSecret is null
public String computeSignature(String signatureBaseString) throws GeneralSecurityException { | ||
// compute key | ||
StringBuilder keyBuffer = new StringBuilder(); | ||
String clientSecret = clientSharedSecret; |
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 see why you copy into a local variable 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.
No need, it was simply in the original class that way. I have eliminated the local variable.
keyBuffer.append(OAuthParameters.escape(clientSecret)); | ||
} | ||
keyBuffer.append('&'); | ||
String tokenSecret = tokenSharedSecret; |
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 see why you copy into a local variable 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.
No need, it was simply in the original class that way. I have eliminated the local variable.
private String tokenSharedSecret; | ||
|
||
public void setTokenSecret(String tokenSecret) { | ||
tokenSharedSecret = tokenSecret; |
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 probably fail if the existing field is not null. Or does it need to be reset? What do you think?
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.
There is a legitimate case for swapping a temporary token for a permanent token. That is a common scenario in OAuth 1.0
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.
OK. Can you add a test for this?
} | ||
|
||
public OAuthHmacSha256Signer(String clientSecret) { | ||
this.clientSharedSecret = clientSecret; |
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.
does it make sense for this field to be 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.
In reality, no, this should never be null. I have worked with other OAuth libraries, though, and none of them actually validate that this is non-null. However, if any code sets this to be null, then the OAuth calls will be completely non-functional, failing to authenticate. Setting this to a valid string is critical to OAuth working at all. None of the other signer classes do any validation of these (in fact, they are just public variables), and we could be more restrictive here, but in practice this will have to be set to a vlid value or the authentication simply won't work.
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 check for null then? It dies not break anything and adds value of failing early.
keyBuffer.append(OAuthParameters.escape(clientSharedSecret)); | ||
} | ||
keyBuffer.append('&'); | ||
if (tokenSharedSecret != 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.
It's OK for this to be omitted?
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, the token secret can be null, a blank string, or even a temporary token for the first call of the authorization flow. It is intended to be replaced/updated with a permanent token after the initial call.
@svonduhn Please sign the cla, it is required for all the contributors. Here is the instruction: https://opensource.google/docs/cla/#external-contributors |
@TimurSadykov I signed the CLA here: https://cla.developers.google.com/clas/edit?id=111692684224848719787&kind=KIND_INDIVIDUAL&domain=DOMAIN_GOOGLE I also added the Google CLA GitHub App to my GitHub account, but I am not sure what else needs to be done to get the pull request to recognize the CLA. Is there something additional needed? |
@googlebot rescan |
We found a Contributor License Agreement for you (the sender of this pull request), but were unable to find agreements for all the commit author(s) or Co-authors. If you authored these, maybe you used a different email address in the git commits than was used to sign the CLA (login here to double check)? If these were authored by someone else, then they will need to sign a CLA as well, and confirm that they're okay with these being contributed to Google. ℹ️ Googlers: Go here for more info. |
@svonduhn Hey, sorry for delay. I see what is happening. I can see that your GitHub login has CLA signed. However, it seems like you made a commit using different email, that is not associated with your account: svonduhn@commercehub.com Please try to add that email to you GitHub account and then rescan. |
We found a Contributor License Agreement for you (the sender of this pull request), but were unable to find agreements for all the commit author(s) or Co-authors. If you authored these, maybe you used a different email address in the git commits than was used to sign the CLA (login here to double check)? If these were authored by someone else, then they will need to sign a CLA as well, and confirm that they're okay with these being contributed to Google. ℹ️ Googlers: Go here for more info. |
@TimurSadykov you are right, the commits were made with the wrong email address. The only problem I have now is that when I try to add that email address to my account, it says that it is already associated with a different Google account (which it should not be, but that is the error I am getting). Because of that, it seems that I can't actually fix the issue. Would it be reasonable (acceptable) to simply create a new pull request with these changes using the correct email address, and close this one? My apologies for the trouble. I have updated my .gitconfig to prevent this from happening in the future. Let me know how best to proceed. Thank you. |
@svonduhn There are a couple of options:
|
We found a Contributor License Agreement for you (the sender of this pull request), but were unable to find agreements for all the commit author(s) or Co-authors. If you authored these, maybe you used a different email address in the git commits than was used to sign the CLA (login here to double check)? If these were authored by someone else, then they will need to sign a CLA as well, and confirm that they're okay with these being contributed to Google. ℹ️ Googlers: Go here for more info. |
@elharo @TimurSadykov This pull request should be closed and replaced by #711. There were issues with my original commit email, and I was unable to resolve them via an interactive rebase (a few of the commits did not get rebased correctly). I do apologize for the trouble. The new pull request does appear to be passing the CLA check now. Thank you for your time! |
closing, as this was included in PR #711, which has been merged. |
I have added OAuthHmacSha256Signer to enable using the signature method to HMAC-SHA256.
Unit test/code coverage has been added in OAuthHmacSha256SignerTest.
This change is necessary for establishing connections to NetSuite as they will no longer accept HMAC-SHA1 as a signature method.
Fixes #702 ☕️
This pull request should be close and replaced by #711. There were issues with the original commit email, and I was unable to resolve them by rebasing (a few of the commits did not get rebased correctly).