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
Add param converters for authentication credentials #1374
Conversation
Generate changelog in
|
@Override | ||
public AuthHeader fromString(final String value) { | ||
if (value == null) { | ||
throw new UnauthorizedException(MISSING_CREDENTIAL_ERROR_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.
This needs to be a WebApplicationException
. Otherwise it will get wrapped in a ExtractorException
which will get mapped to a 400:
https://github.com/jersey/jersey/blob/2.25.1/core-server/src/main/java/org/glassfish/jersey/server/internal/inject/AbstractParamValueExtractor.java#L137
The jaxrs NotAuthorizedException
doesn't provide constructors that can be used in this scenario so we need to create our own. I chose the name UnauthorizedException
to disambiguate.
import org.glassfish.jersey.internal.inject.Providers; | ||
import org.glassfish.jersey.internal.util.ReflectionHelper; | ||
import org.glassfish.jersey.internal.util.collection.ClassTypePair; | ||
|
||
@Custom |
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 annotation ensures that our custom param converters are considered first.
Otherwise the order in which the param converter providers are considered is arbitrary:
https://github.com/jersey/jersey/blob/2.25.1/core-server/src/main/java/org/glassfish/jersey/server/internal/inject/ParamConverterFactory.java#L58-L101
For example, here is an order that was used when I was debugging tests locally:
This is especially important for the AuthHeaderParamConverterProvider
and BearerTokenParamConverterProvider
to ensure that we don't fallback to the built-in AggregatedProvider
which contains the TypeValueOf
param converter (which is used currently for AuthHeader
and BearerToken
).
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.
Something along these lines would make an excellent comment in the code :-)
8fea8cd
to
17ea493
Compare
|
||
@Custom | ||
@Provider | ||
public final class BearerTokenParamConverterProvider implements ParamConverterProvider { |
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 confident this is correct, while cookie auth uses the BearerToken type, we can define additional parameters of type BearerToken that aren't the conjure-defined auth token.
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 mention this in the PR description:
These param converters will apply to all
AuthHeader
andBearerToken
parameters. However it is very rare for these types to be used outside of the endpoint auth. And most of those cases are to perform some sort of delegate authentication, so a 401 is not inappropriate.
I don't think I've ever seen a BearerToken
used as an argument outside of cookie auth. And the only times I've seen a AuthHeader
used as argument outside of header auth is for delegating authorization, in which case you could argue that a 401 is not inappropriate.
I don't think there is another way to achieve the desired behavior. If you have other suggestions, I'm happy to hear them. But I think the benefit of responding with 401 for invalid auth credentials outweighs the minor downside of this applying to other parameters of the same 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.
I've updated this PR to only apply to Conjure auth parameters. This means:
AuthHeader
parameter must be annotated with@HeaderParam
and have a value ofAuthorization
BearerToken
parameter must be annotated with@CookieParam
and have any value
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.
That sounds reasonable, I don't think we need the additional specificity for AuthHeader because conjure only uses that type for auth components, bearer tokens use the BearerToken
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.
I would prefer to not respond with 401 for parameters we know aren't the Conjure auth parameter. It's possible to have other AuthHeader
header params by using external imports. We actually do this for a number of endpoints and use a second AuthHeader
header param to represent a delegating user. It makes more sense to return 400 for these parameters since they are effectively arguments that just happen to be passed as headers.
Because Conjure doesn't allow arbitrary cookie parameters, this implementation will exactly match all auth parameters and nothing else.
817a86b
to
fec09ff
Compare
fec09ff
to
1040dde
Compare
9124eae
to
b52caa3
Compare
.../src/main/java/com/palantir/conjure/java/server/jersey/AuthHeaderParamConverterProvider.java
Show resolved
Hide resolved
This PR has been automatically marked as stale because it has not been touched in the last 14 days. If you'd like to keep it open, please leave a comment or add the 'long-lived' label, otherwise it'll be closed in 7 days. |
@carterkozak are there any other changes we should make in this PR to try to gracefully handle applications that rely on tokens being nullable? |
Let's add test coverage to make sure this change doesn't modify null token behavior, this way we can decouple the risky piece from the type of exception thrown from our infrastructure. We can hold off on null handling until the office opens up and we can discuss more broadly :-) |
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.
Generally looks good to me, thanks! Just a couple small nits
|
||
private static boolean hasAuthAnnotation(Annotation[] annotations) { | ||
for (Annotation annotation : annotations) { | ||
if (annotation.annotationType() == CookieParam.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.
nit:
if (annotation.annotationType() == CookieParam.class) { | |
if (CookieParam.class.equals(annotation.annotationType())) { |
|
||
private static boolean hasAuthAnnotation(Annotation[] annotations) { | ||
for (Annotation annotation : annotations) { | ||
if (annotation.annotationType() == HeaderParam.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.
nit:
if (annotation.annotationType() == HeaderParam.class) { | |
if (HeaderParam.class.equals(annotation.annotationType())) { |
for (Annotation annotation : annotations) { | ||
if (annotation.annotationType() == HeaderParam.class) { | ||
String value = ((HeaderParam) annotation).value(); | ||
if (value.equals(HttpHeaders.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, safer around nulls:
if (value.equals(HttpHeaders.AUTHORIZATION)) { | |
if (HttpHeaders.AUTHORIZATION.equals(value)) { |
import org.glassfish.jersey.internal.inject.Providers; | ||
import org.glassfish.jersey.internal.util.ReflectionHelper; | ||
import org.glassfish.jersey.internal.util.collection.ClassTypePair; | ||
|
||
@Custom |
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.
Something along these lines would make an excellent comment in the code :-)
Thanks! 👍 |
Released 4.47.0 |
Before this PR
Malformed authentication credentials will result in a 400. This is unfortunate because it will propagate as a 500.
After this PR
Malformed authentication credentials will result in a 401.
This change is the equivalent to part of palantir/conjure-java#656 for Jersey servers.