-
Notifications
You must be signed in to change notification settings - Fork 52
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 a whoami function (user, token, scopes); closes #39 #51
Conversation
Current coverage is 67.87% (diff: 96.00%)@@ master #51 diff @@
==========================================
Files 7 8 +1
Lines 228 249 +21
Methods 0 0
Messages 0 0
Branches 0 0
==========================================
+ Hits 148 169 +21
Misses 80 80
Partials 0 0
|
Hmmm. I can see that this is very useful, so it is tempting. I am also not sure if TBH, I still haven't given up the hope of having a proper higher level GH API package, and I think |
Short-term, this is much closer to going to CRAN! Even long-term, I will finish off the PR today and you can render your final judgment 🙏. |
I see But it will be fine here, for now. (Which, as we know, likely means forever. :) |
I also just realized this
|
My roxygen seems different from your roxygen. Testing: shall I assume nothing, i.e. no token necessarily available? Or take advantage of whatever you've rigged up with httrmock (which I have not yet digested)? |
BTW I have used successfully with GHE. |
From another conversation:
I basically see how this works. But yes I need that token. I played around with my own to get this far. I had some very puzzling times until I discovered I obviously won't leave the test like this, with my own info in there and skipping on travis and appveyor. |
Yes, sorry, I realized that github_pubkey <- function(user){
url <- sprintf("https://api.github.com/users/%s/keys", user)
keys <- jsonlite::fromJSON(url)
lapply(keys$key, openssl::read_pubkey)
}
jenny <- github_pubkey('jennybc')
pubkey <- jenny[[1]] # in case there are multiple keys
token <- readLines("~/works/r-pkgs/gh/tests/testthat/github-token.txt")
buf <- openssl::rsa_encrypt(charToRaw(token), pubkey)
cat(openssl::base64_encode(buf)) And here is the encrypted token:
You can just decrypt it with your private key. |
CC-ing @jimhester, who might be interested in doing this kind of mocking for As for the if (file.exists("github-token.txt")) {
Sys.setenv(GITHUB_TOKEN = readLines("github-token.txt", n = 1))
Sys.setenv(DEBUGME = "httrmock")
httrmock::start_recording()
httrmock::start_replaying()
} else {
httrmock::stop_recording()
httrmock::start_replaying()
} which basically means, reading from the back, that if you don't have a token, then you just want to replay the recorded responses. This is how the tests run on CRAN. Everything that was recorded before is just replayed ( If you do have a token, then, 1) everything that was recorded before is replayed ( The idea is that most of the time you don't want to perform all GH requests while developing Does it make sense? I realize that it is a bit messy, and it would be great to improve it. E.g. we could have some interactive mode chooser, that you could use to select the desired behavior for the current session. I would even show it in my R prompt. |
I think one additional thing we need to make this nice is the ability to run a teardown file(s) in testthat / devtools. Because ideally you want recording and replaying on when you are running the tests, but off when you are developing. Also r-lib/devtools#1202, r-lib/devtools#1169 would be useful for the same reasons. Also in lookup I had planned on writing a function with instructions on setting up a Github Token, as that package won't work well without one. I briefly looked into using https://developer.github.com/v3/oauth_authorizations/#create-a-new-authorization to request a token from the user automatically based on basic authentication, but I am not quite clear how the flow works with two factor authentication. Something like that might be useful for GitHug or here as well. |
Exactly. The code I cited is in "setup", i.e.
Maybe in |
Success with the token! Thanks.
Right now
Yes I would also like to help people store a PAT somehow, from one of these packages. However, given the way the Authorizations API works, it almost feels like we could create as much friction as we remove by using it. In Happy Git, I have instructions to obtain PAT in the browser and offer this code snippet to help store it: cat("GITHUB_PAT=8c70...adf2\n",
file = file.path(normalizePath("~/"), ".Renviron"), append = TRUE) I know that's very low tech 😔. |
Agreed. How about controlling the behavior via environment variables? Then we could have a default behavior, for the user, and developers could change it via a simple command that would just get/set an environment variable? |
}) | ||
|
||
test_that("whoami works in absence of PAT", { | ||
expect_message(res <- gh_whoami(.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.
@jimhester Re: #50. I know it's regarded as good practice to include a specific message inside expect_error()
and friends. Do you think the HTTP version of this mindset is that one should write expectations in this context for a specific HTTP status? I'm trying to understand if your motivation for #50 is something you want to do in lookup or for testing or ....
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 think would be great if expect_error
retained the error object, and we could test the error class. See r-lib/testthat#530
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.
Well I was using it in lookup https://github.com/jimhester/lookup/blob/e841d72819e39242e0987aa6f23b240c9d47d60c/R/rcpp.R#L3.
But I think if you are expecting a response to have a specific error class you should catch just that specific class and let any other error be signaled normally.
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 am now catching and expecting a specific error class and HTTP error.
}) | ||
|
||
test_that("whoami errors with bad PAT", { | ||
expect_error(res <- gh_whoami(.token = NA), "Requires authentication") |
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.
@jimhester More examples of "should I expect a certain status?" instead of doing this.
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 here I would definitely check the error class rather than the message, the message could potentially be changed by GitHub any time, the HTTP error code should be more stable.
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 am now catching and expecting a specific error class and HTTP error.
That sounds great! Does the environment variable |
Yes, R_TESTS could indeed help, but I am a bit reluctant to use it, as it causes trouble for many packages, and some people (including myself) often unset it. |
Re: environment variables @lionel- has a PR that should help some r-lib/devtools#1391, but it is not yet merged. |
This allows you to catch a github_error or the exact status code directly.
Note: there's only one new recording because the second test matches against first recording. It just happens to pass in a happy coincidence.
Btw. if you |
I have skipped the one that I know must fail for now. The first one should not. Seeing if setting GITHUB_TOKEN to something other than |
VICTORY. I had to set |
Yeah, I think that is fine. |
if (token != "") auth <- c("Authorization" = paste("token", token)) | ||
if (isTRUE(token != "")) { | ||
auth <- c("Authorization" = paste("token", 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.
Sorry, can you write this with if () { ... } else { ... }
? I think it shows the intent better.
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.
Done.
@@ -21,7 +21,7 @@ gh_process_response <- function(response) { | |||
headers = heads, | |||
message = paste0("GitHub API error (", status_code(response), "): ", | |||
heads$`status`, "\n ", res$message, "\n") | |||
), class = c("condition", "error")) | |||
), class = c("github_error", paste0("http_error_", status_code(response)), "error", "condition")) |
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 was already added by Jim's PR, no? Can you please rebase, if it is easy? If it is not easy, then don't worry about it, it is not worth getting into git trouble for this.....
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 merged master into my branch in the middle of this adventure (my branch started before I pulled the commit from master with that PR). This will work out when you squash, yes?
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, right, should be fine I think.
#' your planned tasks. The \code{repo} scope, for example, is one many are | ||
#' likely to need. The token itself is a string of 40 letters and digits. You | ||
#' can store it any way you like and provide explicitly via the \code{.token} | ||
#' argument to \code{\link{gh}()}. |
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.
markdown roxygen parsing is on for this project, so feel free to write markdown if you want to. You would need the dev version of roxygen2, though, so if you don't want to deal with that, that's fine, 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.
I haven't done this here. Maybe after the merge I could do it for all exported functions at once?
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.
You don't have to do it, just mentioned it for next time. :) I don't think rewriting it it worth the effort.
#' | ||
#' Reports wallet name, GitHub login, and GitHub URL for the current | ||
#' authenticated user, the first few and last characters of the token, and the | ||
#' associated scopes. |
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.
Hmmm. I am not a security expert, but I would not print out anything from the token. I know that the user has access to it, but I just don't want it to appear in printouts, Rmds, etc.
Let me think about this a bit. I see that it can be useful for beginners, but I am still concerned.
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 thought showing first 4 and last 4 characters was OK. I do the same over in googlesheets. It is helpful when you have more than one token in your life and there's no obvious way to give them names. But we can kill it or reveal even less of 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.
I don't know, TBH. Showing 8 characters makes the token 2^(8*4) times less secure. We would show 32 bits of the 160.
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.
On the other hand, this is not sg you can "crack", an attacker would need to try each remaining valid key in an API request....
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.
How about we only show the first two letters now? Is that still useful for the user?
I'll also ask my security consultant, @jeroenooms. :)
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 2 characters it is.
#' Put a line break at the end! If you’re using an editor that shows line | ||
#' numbers, there should be (at least) two lines, where the second one is empty. | ||
#' Restart R for this to take effect. Call \code{gh_whoami()} to confirm | ||
#' success. |
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.
Wow! You really have a lot of empathy for beginners. :)
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 may have answered this question a few dozen times.
"For more on what to do with the PAT, see ?gh_whoami.") | ||
return(invisible(NULL)) | ||
} | ||
req <- gh_build_request(endpoint = "/user", token = .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 guess if we do not show the token, then you don't need to build a gh_request
manually, right?
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.
You're right. I can avoid all the manual stuff and simply use gh()
. Have simplified that.
Thanks much, looks awesome! I added some small comments. Most important is about showing part of the token on the screen. I am not sure if that's a good idea to be honest, but I might be paranoid. |
OK, I will merge this now, and maybe I'll change the token printout slightly. |
Thanks much! |
@jennybc FYI, I added a simple version of |
Great! I will pursue that. I might be able to resume the |
Great! I'll still add better request matching. E.g. for POST we should at least use the data.... |
I will start with GETs anyway, as I'm sure that will be highly educational. |
This PR was a good warm-up because I understand the basic mojo of httrmock now but did not before. |
Are you willing to have such a function here? I think it would be handy for new users and troubleshooting. But I could put it in githug instead, if you'd prefer not.
If you are receptive, I will flesh out the "HERE'S HOW TO GET A TOKEN AND WHERE TO STICK IT" message and add a test. And do anything else you suggest.