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

use gitcreds for managing github PAT #96

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .github/workflows/R-CMD-check.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
- {os: ubuntu-latest, r: 'oldrel-1'}

env:
GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
GITHUB_PAT: ${{ secrets.GIST_TOKEN }}
R_KEEP_PKG_SOURCE: yes

steps:
Expand All @@ -44,6 +44,12 @@ jobs:
extra-packages: any::rcmdcheck
needs: check

# Added step for testing gist creation using GIST_TOKEN
# - name: Run gist creation test
# run: |
# echo "Running gist creation test..."
# Rscript -e "gistr::gist_create(file = 'tests/test-gist.md', description = 'Test gist creation')"

- uses: r-lib/actions/check-r-package@v2
with:
upload-snapshots: true
13 changes: 7 additions & 6 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ Description: Work with 'GitHub' 'gists' from 'R' (e.g.,
<https://gist.github.com/>.
Version: 0.9.0.93
Authors@R: c(
person("Scott", "Chamberlain", role = c("aut", "cre"),
email = "[email protected]",
comment = c(ORCID="0000-0003-1444-9135")),
person("Scott", "Chamberlain", , "[email protected]", role = "aut",
comment = c(ORCID = "0000-0003-1444-9135")),
person("Ramnath", "Vaidyanathan", role = "aut"),
person("Karthik", "Ram", role = "aut"),
person("Milgram", "Eric", role = "aut")
person("Milgram", "Eric", role = c("aut", "cre")),
person("Eric R.", "Scott", role = "ctb")
)
License: MIT + file LICENSE
URL: https://github.com/ropensci/gistr (devel),
Expand All @@ -35,11 +35,12 @@ Imports:
assertthat,
knitr,
rmarkdown,
dplyr
dplyr,
gitcreds
Suggests:
git2r,
testthat
RoxygenNote: 7.1.1
RoxygenNote: 7.3.2
X-schema.org-applicationCategory: Web
X-schema.org-keywords: http, https, API, web-services, GitHub, GitHub API, gist, gists, code, script, snippet
X-schema.org-isPartOf: https://ropensci.org
4 changes: 4 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
gistr in development
===============

### NEW FEATURES

* `gistr` now uses `gitcreds` to discover GitHub PATs and now recommends you store your PAT using `gitcreds::gitcreds_set()` rather than as the `GITHUB_PAT` environment variable (although this still works).

### BUG FIXES

* fix `gist_auth()`: at some point `httr::oauth2.0_token` stopped returning the token in the `headers` slot; can't figure out when this change happened; fix is to get the token from a different place in the returned object; changes to `gist_auth()` to access that new path to the token (#83)
Expand Down
72 changes: 48 additions & 24 deletions R/gist_auth.R
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
#' Authorize with GitHub.
#'
#' This function is run automatically to allow gistr to access your GitHub
#' This function is run automatically to allow gistr to access your GitHub
#' account.
#'
#' There are two ways to authorise gistr to work with your GitHub account:
#'
#' - Generate a personal access token with the gist scope selected, and set it
#' as the `GITHUB_PAT` environment variable per session using `Sys.setenv`
#' or across sessions by adding it to your `.Renviron` file or similar.
#' See
#'
#' - Generate a personal access token with the gist scope selected, and store it
#' with `gitcreds::gitcreds_set()`. Alternatively you can set it as the
#' `GITHUB_PAT` environment variable per session using `Sys.setenv()` or across
#' sessions by adding it to your `.Renviron` file or similar. See
#' https://help.github.com/articles/creating-an-access-token-for-command-line-use
#' for help
#' - Interactively login into your GitHub account and authorise with OAuth.
#' for help or use `usethis::create_github_token()`.
#' - Interactively log in into your GitHub account and authorise with OAuth.
#'
#' Using `GITHUB_PAT` is recommended.
#' Using `gitcreds::gitcreds_set()` is recommended.
#'
#' @export
#' @param app An [httr::oauth_app()] for GitHub. The default uses an
#' @param app An [httr::oauth_app()] for GitHub. The default uses an
#' application `gistr_oauth` created by Scott Chamberlain.
#' @param reauth (logical) Force re-authorization?
#' @return a named list, with a single slot for `Authorization`, with a single
Expand All @@ -27,24 +27,40 @@
#' }

gist_auth <- function(app = gistr_app, reauth = FALSE) {


#if there is a token cached, use that
if (exists("auth_config", envir = cache) && !reauth) {
return(auth_header(cache$auth_config$auth_token$credentials$access_token))
return(auth_header(cache$auth_config$auth_token))
}
pat <- Sys.getenv("GITHUB_PAT", "")
if (!identical(pat, "")) {
auth_config <- list(auth_token=list(credentials=list(access_token=pat)))
} else if (!interactive()) {
stop("In non-interactive environments, please set GITHUB_PAT env to a GitHub",
" access token (https://help.github.com/articles/creating-an-access-token-for-command-line-use)",
call. = FALSE)
} else {
endpt <- httr::oauth_endpoints("github")
token <- httr::oauth2.0_token(endpt, app, scope = "gist", cache = !reauth)
auth_config <- httr::config(token = token)
#if nothing cached, use gitcreds to retrieve PAT stored as an GITHUB_PAT
#environment variable or set with gitcreds::gitcreds_set(). gitcreds_get()
#errors when no PAT is found, but we want to try one more method, so silence
#this error and return NULL.
creds <- tryCatch(
error = function(cnd) {
return(NULL)
},
gitcreds::gitcreds_get()
)
token <- creds$password
#TODO would be great to check here that token has "gist" scope
Copy link
Member

Choose a reason for hiding this comment

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

#if no token, or invalid token and interactive, try direct oauth
if ((is.null(token) | !valid_gh_pat(token))) {
if (interactive()) {
endpt <- httr::oauth_endpoints("github")
auth <- httr::oauth2.0_token(endpt, app, scope = "gist", cache = !reauth)
token <- auth$credentials$access_token
} else {
stop("In non-interactive environments, please set GITHUB_PAT env to a GitHub",
" access token (https://help.github.com/articles/creating-an-access-token-for-command-line-use)",
Copy link
Member

Choose a reason for hiding this comment

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

this could also point to https://usethis.r-lib.org/reference/github-token.html as the functions are quite handy

call. = FALSE)
}
}

#cache auth config
auth_config <- httr::config(token = token)
cache$auth_config <- auth_config
auth_header(auth_config$auth_token$credentials$access_token)
return(auth_header(auth_config$auth_token))
}

auth_header <- function(x) list(Authorization = paste0("token ", x))
Expand All @@ -56,3 +72,11 @@ gistr_app <- httr::oauth_app(
"89ecf04527f70e0f9730",
"77b5970cdeda925513b2cdec40c309ea384b74b7"
)

# inspired by https://github.com/r-lib/gh/blob/main/R/gh_token.R
valid_gh_pat <- function(x) {
Copy link
Member

Choose a reason for hiding this comment

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

!is.null(x) & (
grepl("^(gh[pousr]_[A-Za-z0-9_]{36,251}|github_pat_[A-Za-z0-9_]{36,244})$", x) ||
grepl("^[[:xdigit:]]{40}$", x)
)
}
5 changes: 2 additions & 3 deletions R/gistr-package.R
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,12 @@
#' @importFrom jsonlite fromJSON flatten
#' @name gistr-package
#' @aliases gistr
#' @docType package
#' @title R client for GitHub gists
#' @author Scott Chamberlain \email{myrmecocystus@@gmail.com}
#' @author Ramnath Vaidyanathan \email{ramnath.vaidya@@gmail.com}
#' @author Karthik Ram \email{karthik.ram@@gmail.com}
#' @keywords package
NULL
"_PACKAGE"

#' @title Create gists
#'
Expand All @@ -43,7 +42,7 @@ NULL
#' - [gist_create_git()] - Create gists from files or code
#' blocks, using git. Because this function uses git, you have more
#' flexibility than with the above function: you can include any binary files,
#' and can easily upload all artifacts.
#' and can easily upload all artifacts.
#' - [gist_create_obj()] - Create gists from R objects: data.frame, list,
#' character string, matrix, or numeric. Uses the GitHub HTTP API.
#'
Expand Down
14 changes: 7 additions & 7 deletions man/gist_auth.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions man/gistr-package.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions tests/testthat/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.httr-oauth
21 changes: 21 additions & 0 deletions tests/testthat/test-gist_auth.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
test_that("gitcreds finds PAT", {
expect_equal(gitcreds::gitcreds_get()$password, Sys.getenv("GITHUB_PAT"))
})


test_that("gist_auth finds PAT", {
skip_on_cran()
auth <- gist_auth(reauth = TRUE)
expect_equal(
auth$Authorization,
paste0("token ", gitcreds::gitcreds_get()$password)
)
})

#can't (easily) test PAT stored with gitcreds::gitcreds_set() or interactive oauth

test_that("valid_gh_pat() works", {
expect_false(valid_gh_pat("hello"))
expect_false(valid_gh_pat(""))
expect_false(valid_gh_pat(NULL))
})
Loading