-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Decode URL's user info before setting auth header #760
base: master
Are you sure you want to change the base?
Conversation
It looks like @i7c hasn't signed our Contributor License Agreement, yet.
You can read and sign our full Contributor License Agreement here. Once you've signed reply with Appreciation of efforts, clabot |
Please note, that if merged, you also need to percent-encode credentials set via 'schema.registry.basic.auth.user.info'. If this is not wanted, we should maybe consider using completely different means to set the credentials (independetly of any "user info" form). |
[clabot:check] |
@confluentinc It looks like @i7c just signed our Contributor License Agreement. 👍 Always at your service, clabot |
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 work! thanks for the PR.
just a couple small things...
String authHeader = DatatypeConverter.printBase64Binary(userInfoBytes); | ||
connection.setRequestProperty("Authorization", "Basic " + authHeader); | ||
} catch (UnsupportedEncodingException e) { | ||
return; |
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.
We shouldn't swallow this exception. Looks like we could wrap it in an IllegalArgumentException so it gets passed back to the user (like in parseBaseUrl).
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.
Yeah, I wouldn't wrap the lines here in a try-catch block. We want to indicate to the calling methods that an error happened.
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.
Uhm, I’ll gladly change it, but the UnsupportedEncodingException is thrown, if "UTF-8" is not supported by the stdlib/jvm as possible encoding, so IllegalArgumentException seems like an odd choice to me. I think usually one would throw an AssertionError in this case. Whether the encoding is supported or not depends on the environment rather than what the user provides as data.
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 point, I'm less opinionated on the exception thrown or whether we log something. Just know it'll be a pain for people to troubleshoot if we silently swallow it. @wicknicks ?
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 @codyaray, definitely agree that we shouldn't simply swallow the exception here. Good point, @i7c! Also, since this method is called setBasicAuthRequestHeader
, I am less inclined to do the decoding here. The more appropriate place would be just before calling this method on line 157 in this class, and have the setter only set stuff, something like this:
if (basicAuthCredentialProvider != null) {
userInfo = basicAuthCredentialProvider.getUserInfo(connection.getURL()))
userInfoBytes = URLDecoder.decode(userInfo, "UTF-8").getBytes(StandardCharsets.UTF_8);
setBasicAuthRequestHeader(userInfoBytes, connection);
}
This would also allow easier handling of the IOException. The other alternative is to have setBasicAuthRequestHeader
throw an IOException, which is a bit odd as well.
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.
First of all thanks for the helpful feedback. I get the points and I agree with what you said.
However, trying out your proposal bumped up cyclomatic and NPath complexity of sendHttpRequest() above the allowed limits, which made me think: Why should this method (or the setter) have to deal with decoding user input anyways?
Wouldn't it make more sense to have the CredentialProviders deal with decoding? This would have some nice properties:
- we can decode only once (when BasicAuthCredentialProvider.config() is called or in a "caching manner" in case of the UrlBasicAuthCredentialProvider), so we reduce execution time of sendHttpRequest()
- we can deal with errors where they occur conceptually, in "providing credentials"
- we can offer a nicer way for the user to specify credentials (with a new Provider implementation), namely with two properties "username" and "password" where no URL encoding is necessary. I actually don't like the idea that the user has to put something like johndoe:my%20secret in there anyways. It also might cause some hassle when using configuration management. Think of a centralised store for secrets, so configuration management would need to URL-encode the provided secret first ...)
Please let me know what you think about it and if this is getting too big for a simple feature. ;)
I’ll gladly provide a patch, should you like the idea. 😄
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 like the direction you're thinking in. We actually already do support passing user and password separately. Seems like it may just be undocumented/poorly documented. So I'm not sure how far down this road with code refactoring versus docs improvements.
Here's a gist that does SR auth using system properties.
https://gist.github.com/mageshn/c623f2d43b66b1153f5da32662ca5e94
(Sorry if this possibility was already obvious to you. This isn't a code base I normally touch.)
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.
@i7c: thanks a lot for your thoughts on this. Would love that patch. Thank you!
Some thoughts:
I like the new contract being established in your second suggestion.
Also, maybe we can create an abstract class which extends BasicAuthCredentialProvider
to house your suggestions. The other Provider implementations would just extend this new class. Once we officially move to Java 8, this could be replaced with a default method in the interface.
connection.setRequestProperty("Authorization", "Basic " + authHeader); | ||
try { | ||
byte[] userInfoBytes = | ||
URLDecoder.decode(userInfo, "UTF-8").getBytes(StandardCharsets.UTF_8); |
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.
We should add some test cases to make our intention clear here.
Might be easier to make this method @VisibleForTesting than try to implicitly test it like the above does though.
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.
Thanks for the changes. Much appreciated!
String authHeader = DatatypeConverter.printBase64Binary(userInfoBytes); | ||
connection.setRequestProperty("Authorization", "Basic " + authHeader); | ||
} catch (UnsupportedEncodingException e) { | ||
return; |
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.
Yeah, I wouldn't wrap the lines here in a try-catch block. We want to indicate to the calling methods that an error happened.
I can hopefully wrap this up tomorrow and we can do another round of review. :) |
This commit introduces a common superclass AbstractBasicAuthCredentialProvider, that may house useful methods for the inner workings of the concrete implementations of BasicAuthCredentialProvider. decodeUserInfo() can be used to decode percent-encoded credentials: https://tools.ietf.org/html/rfc3986#section-2.1 UrlBasicAuthCredentialProvider and UserInfoCredentialProvider both use decodeUserInfo() to handle encoded credentials with special characters correctly.
This commit adds a convenience method to BasicAuthCredentialProvider's interface: getAuthHeader() returns the base64-encoded auth header derived from the provided credentials, so the calling site does not have to bother about encoding of credentials.
We introduce a new credential provider that is selected by using the credential source "USERNAME_PASSWORD". The user must supply two options, namely schema.registry.basic.auth.username and schema.registry.basic.auth.password. The motivation for this additional way is, that the user can provide the credentials in plain text, which may be more convenient than percent-encoded credentials as part of the target URL.
Hi again, my reworked pull request is out for discussion. I split it in three commits, because I’m doing three steps which you may individually decide if you like them or not.
I have not yet tested the code against an actual schema registry. I will do so if you think the patch series is going in an acceptable direction. Looking forward to getting feedback! :) |
@i7c |
|
Commit 789621d added possibility to set http basic auth header in the
schema registry client by putting them on the URL (as in
https://user:password@host:port). However, if the credentials contain
special characters, they must be percent-encoded:
https://tools.ietf.org/html/rfc3986#section-2.1
This patch makes schema registry client respect percent encoded
credentials by decoding them before calculating the base64
representation. Please note, that the decoding step happens
independently of the specific credentials provider, so other ways to
supply credentials might be affected as well.
Currently, all ways to set credentials use the user info form
"user:pass", so this should not be harmful.