Skip to content
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

Fix HTTP_Status on OAuth2 refresh token redirect #15336

Merged

Conversation

huberty89
Copy link
Contributor

@huberty89 huberty89 commented Dec 7, 2022

Description

When refresh token is retrieved for UI, currently we were sending HTTP Status 303, assuming that all the request will just repeat the call on the Location header. When this works for GET/PUT verbs, it does not for non-idempotent ones like POST, as every JS HTTP client should do a GET on LOCATION after 303 on POST. Due to that I change it to 307, that should force every client to repeat the exactly the same request, no matter the verb.

This PR replaces #15184

Release notes

( ) This is not user-visible or docs only and no release notes are required.
( ) Release notes are required, please propose a release note for me.
(x) Release notes are required, with the following suggested text:

# Security
* Fix HTTP redirect after OAuth2 token refresh. ({issue}`15336`)

@huberty89 huberty89 force-pushed the huberty89/fix-oauth-refresh-on-post-method branch 2 times, most recently from 389c3c1 to bb62f43 Compare December 8, 2022 09:22
@@ -772,6 +811,24 @@ private void assertAuth2Authentication(HttpServerInfo httpServerInfo, String acc
assertResponseCode(client, getLocation(baseUri, "/ui/unknown"), SC_NOT_FOUND);
assertResponseCode(client, getLocation(baseUri, "/ui/api/unknown"), SC_NOT_FOUND);

if (refreshTokensEnabled) {
// wait till auth token expires
Thread.sleep(Duration.between(ZonedDateTime.now(), authTokenExpiration).toMillis());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you issue an already expired token? To get rid of this the sleep.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, done

String state = newJwtBuilder()
.signWith(hmacShaKeyFor(Hashing.sha256().hashString(STATE_KEY, UTF_8).asBytes()))
.setAudience("trino_oauth_ui")
.setExpiration(Date.from(ZonedDateTime.now().plusMinutes(10).toInstant()))
.setExpiration(Date.from(authTokenExpiration.toInstant()))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change is probably not needed, state is only a part of the authorization challenge https://www.rfc-editor.org/rfc/rfc6749#section-4.1.1 so its expiration shouldn't matter.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, I mixed them up, reverted

@@ -772,6 +811,24 @@ private void assertAuth2Authentication(HttpServerInfo httpServerInfo, String acc
assertResponseCode(client, getLocation(baseUri, "/ui/unknown"), SC_NOT_FOUND);
assertResponseCode(client, getLocation(baseUri, "/ui/api/unknown"), SC_NOT_FOUND);

if (refreshTokensEnabled) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: extract to a method?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extracted because now I use this part in two places

@@ -148,6 +152,8 @@
private static final String TEST_PASSWORD2 = "test-password2";
private static final String HMAC_KEY = Resources.getResource("hmac_key.txt").getPath();
private static final PrivateKey JWK_PRIVATE_KEY;
private static final String REFRESH_TOKEN = "REFRESH_TOKEN";
private static final int REFRESH_TOKEN_TIMEOUT_MINUTES = 5;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: How about using a Duration

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replaced

.putAll(OAUTH2_PROPERTIES)
.put("http-server.authentication.oauth2.jwks-url", jwkServer.getBaseUrl().toString())
.put("http-server.authentication.oauth2.refresh-tokens", "true")
.put("http-server.authentication.oauth2.refresh-tokens.issued-token.timeout", REFRESH_TOKEN_TIMEOUT_MINUTES + "m")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a timeout for the token minted by us (i.e with access token and refresh token) - can we have a test where the minted refresh token is expired ?

Copy link
Contributor Author

@huberty89 huberty89 Dec 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added additional test to test if it really expires: testOAuth2AuthenticatorRefreshTokenExpiration

@huberty89 huberty89 force-pushed the huberty89/fix-oauth-refresh-on-post-method branch 2 times, most recently from e6fd9b0 to 523e1f3 Compare December 8, 2022 21:30
@huberty89
Copy link
Contributor Author

I pushed updated version.
@Praveen2112 @lukasz-walkiewicz @skrzypo987 please take a look again

@huberty89 huberty89 force-pushed the huberty89/fix-oauth-refresh-on-post-method branch from 523e1f3 to 9a7593c Compare December 9, 2022 07:10
Copy link
Member

@lukasz-walkiewicz lukasz-walkiewicz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGMT 👍

{
claims = new DefaultClaims(createClaims());
claims.putAll(additionalClaims);
this.accessTokenValidity = accessTokenValidity;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rnn

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added

@huberty89 huberty89 force-pushed the huberty89/fix-oauth-refresh-on-post-method branch from 9a7593c to a0241a8 Compare December 9, 2022 10:52
@huberty89
Copy link
Contributor Author

I hit flaky test #15293

private static void assertCookieWithRefreshToken(TestingTrinoServer server, HttpCookie authCookie, String accessToken)
{
TokenPairSerializer tokenPairSerializer = server.getInstance(Key.get(TokenPairSerializer.class));
TokenPairSerializer.TokenPair deserialize = tokenPairSerializer.deserialize(authCookie.getValue());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Can we import TokenPair directly ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replaced

HttpCookie cookie = getOnlyElement(cookieManager.getCookieStore().getCookies());
assertCookieWithRefreshToken(server, cookie, oauthClient.getAccessToken());

String location = getLocation(baseUri, "/ui/api/query/20221206_145344_00001_qe6gw/killed");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible for us to check with an existing API. The QueryId doesn't exists in the testing server right ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use validApiLocation here ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could replace all of this with:

assertResponseCode(client, getValidApiLocation(baseUri), SC_TEMPORARY_REDIRECT);
assertOk(client, getValidApiLocation(baseUri));

but getValidApiLocation returns an URL to GET endpoint and here I used PUT method.
Ok, I will replace that. It's much more readable.

When refresh token is retrieved for UI, currently we were sending
HTTP Status 303, assuming that all the request will just repeat the
call on the Location header. When this works for GET/PUT verbs, it does
not for non-idempotent ones like POST, as every js http client should
do a GET on LOCATION after 303 on POST. Due to that I change it to 307, that
should force every client to repeat exactly the same request,
no matter the verb.

Co-authored-by: s2lomon <[email protected]>
@huberty89 huberty89 force-pushed the huberty89/fix-oauth-refresh-on-post-method branch from a0241a8 to fe1e1c8 Compare December 12, 2022 10:53
@kokosing kokosing merged commit dfe33c7 into trinodb:master Dec 12, 2022
@kokosing
Copy link
Member

Merged, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging this pull request may close these issues.

5 participants