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

clear token on oauth2.0 error #315

Merged
merged 8 commits into from Jan 8, 2016

Conversation

Projects
None yet
3 participants
@nathangoulding
Contributor

nathangoulding commented Jan 6, 2016

This implements OAuth2.0 error handling as described in the RFC on refresh, and clears the token locally.

Fixes #311. BigQuery respects the OAuth2.0 RFC, so the behavior he describes is resolved without any changes to bigrquery.

Tested locally both using the local file cache and not.

@krlmlr

This comment has been minimized.

Member

krlmlr commented Jan 6, 2016

This works better. On the first try, I'm seeing:

> query_exec(sql, project = project)
Auto-refreshing stale OAuth token.
No encoding supplied: defaulting to UTF-8.
Error: Invalid Credentials
In addition: Warning message:
In req$auth_token$refresh() :
  Unable to get refreshed credentials with refresh token
9: stop(out$err$message, call. = FALSE) at request.r#67
8: process_request(req) at request.r#34
7: bq_post(url, body) at jobs.r#60
6: insert_query_job(query, project, destination_table = destination_table, 
       default_dataset = default_dataset) at query.r#32
5: query_exec(sql, project = project) at oauth.R#4
4: eval(expr, envir, enclos)
3: eval(ei, envir)
2: withVisible(eval(ei, envir))
1: source("oauth.R")

Only the second try opens a browser for re-authentication. Furthermore, from RStudio, I was able to connect even after I had revoked the access from the Google console and after restarting RStudio. Then I tried from the R console, which led to the behavior above. I'm not sure if this is due to a timeout or some magic cache.

has_oauth2.0_error <- function(response) {
if (status_code(response) %in% oauth2.0_error_codes) {
content <- content(response)
if (content["error"] %in% oauth2.0_errors) {

This comment has been minimized.

@hadley

hadley Jan 6, 2016

Member

That should be content$error or content[["error"]]

This comment has been minimized.

@hadley

hadley Jan 6, 2016

Member

But it seems like it would be safer to assume that any error implies that it failed.

@@ -17,6 +17,9 @@ refresh_oauth2.0 <- function(endpoint, app, credentials) {
)

response <- POST(refresh_url, body = body, encode = "form")
if (has_oauth2.0_error(response)) {
return(NULL)

This comment has been minimized.

@hadley

hadley Jan 6, 2016

Member

I would rather this threw an error, to be consistent with the stop_for_status() below

This comment has been minimized.

@hadley

hadley Jan 6, 2016

Member

But maybe you're trying to separate this into known errors and unknown errors? I'd like to hear your thinking about this.

This comment has been minimized.

@nathangoulding

nathangoulding Jan 6, 2016

Contributor

My thinking was that here in refresh_oauth2.0 our goal is to get new, valid credentials using our refresh token (obviously), and OAuth2 provides a defined mechanism to inform us of that this can't happen - by returning invalid_grant in the error response.

Separating this out from stop_for_status allows us to still catch any errors that occur using stop_for_status - 404s, redirects, internal server errors, etc. All of these are errors that prevent us from getting refreshed credentials, but there isn't any specific information in them that tells us what to do.

Contrast that with the OAuth2 error(s) that tell us we have an invalid token, which is why our logic is to clear the token from our cache.

Taking it a step further, the OAuth2 spec is pretty clear that invalid_grant is the error we should look for in the case of the refresh token. I've currently written this in such a way that has_oauth2.0_error will return TRUE even in the case of OAuth2 errors that aren't specific to the refresh token.

I did opt to cast a broader net, but there's an argument to be made that this function should be called has_oauth2.0_refresh_token_error(response) and only return TRUE when invalid_grant is present.

Thoughts?

@@ -211,7 +211,12 @@ Token2.0 <- R6::R6Class("Token2.0", inherit = Token, list(
refresh = function() {
self$credentials <- refresh_oauth2.0(self$endpoint, self$app,
self$credentials, self$params$user_params)
self$cache()

This comment has been minimized.

@hadley

hadley Jan 7, 2016

Member

I think the logic here could be made more clear. Maybe:

creds <- refresh_oauth2.0(self$endpoint, self$app,
  self$credentials, self$params$user_params)
if (is.null(creds) {
  remove_cached_token(self)
  warning("Unable refresh token", call. = FALSE)
} else {
  self$credentials <- creds
  self$cache()
}
self

?

This comment has been minimized.

@hadley

hadley Jan 7, 2016

Member

Or maybe the warning should occur in refresh_oauth2.0()?

This comment has been minimized.

@nathangoulding

nathangoulding Jan 8, 2016

Contributor

Yeah, I think the warning should probably occur in refresh_oauth2.0 in thinking about it.


# This implements error checking according to the OAuth2.0
# specification: https://tools.ietf.org/html/rfc6749#section-5.2
has_oauth2.0_error <- function(response) {

This comment has been minimized.

@hadley

hadley Jan 7, 2016

Member

Maybe call this known_oauth2_error()?

nathangoulding added some commits Jan 8, 2016

@nathangoulding

This comment has been minimized.

Contributor

nathangoulding commented Jan 8, 2016

PTAL

@krlmlr

This comment has been minimized.

Member

krlmlr commented Jan 8, 2016

I'm now reliably seeing the following behavior (using nathangoulding/bigrquery#71):

  • Initial state: Connection from bigrquery works, no service token set

  • Revoke access via https://myaccount.google.com/security#connectedapps

    • I can still connect from bigrquery for a minute or so
  • Eventually, I'm seeing the following as a result from query_exec():

    Auto-refreshing stale OAuth token.
    No encoding supplied: defaulting to UTF-8.
    Error: Invalid Credentials
    In addition: Warning message:
    Unable to refresh token 
    9: stop(out$err$message, call. = FALSE) at request.r#67
    8: process_request(req) at request.r#34
    7: bq_post(url, body) at jobs.r#60
    6: insert_query_job(query, project, destination_table = destination_table, 
           default_dataset = default_dataset) at query.r#32
    5: query_exec(sql, project = project) at oauth.R#5
    4: eval(expr, envir, enclos)
    3: eval(ei, envir)
    2: withVisible(eval(ei, envir))
    1: source("~/git/R/bigrquery/oauth.R", echo = TRUE)
    
  • The second call of query_exec() finally opens the authentication page in the browser

@hadley

This comment has been minimized.

Member

hadley commented Jan 8, 2016

@nathangoulding LTGM - can you please add a bullet to NEWS?

@hadley

This comment has been minimized.

Member

hadley commented Jan 8, 2016

@krlmlr I think eliminating the second refresh will require a tweak in bigrquery

@nathangoulding

This comment has been minimized.

Contributor

nathangoulding commented Jan 8, 2016

Updated NEWS.

hadley added a commit that referenced this pull request Jan 8, 2016

@hadley hadley merged commit a653c52 into r-lib:master Jan 8, 2016

1 check passed

continuous-integration/travis-ci/pr The Travis CI build passed
Details
@hadley

This comment has been minimized.

Member

hadley commented Jan 8, 2016

Thanks!

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