R/credentials_service_account.R

Defines functions check_is_service_account credentials_service_account

Documented in check_is_service_account credentials_service_account

#' Load a service account token
#'
#' @inheritParams token_fetch
#' @param path JSON identifying the service account, in one of the forms
#'   supported for the `txt` argument of [jsonlite::fromJSON()] (typically, a
#'   file path or JSON string).
#' @param subject An optional subject claim. Specify this if you wish to use the
#'   service account represented by `path` to impersonate the `subject`, who is
#'   a normal user. Before this can work, an administrator must grant the service
#'   account domain-wide authority. Identify the user to impersonate via their
#'   email, e.g. `subject = "user@example.com"`. Note that gargle automatically
#'   adds the non-sensitive `"https://www.googleapis.com/auth/userinfo.email"`
#'   scope, so this scope must be enabled for the service account, along with
#'   any other `scopes` being requested.
#'
#' @details Note that fetching a token for a service account requires a
#'   reasonably accurate system clock. For more information, see the
#'   `vignette("how-gargle-gets-tokens")`.
#' @seealso Additional reading on delegation of domain-wide authority:
#' * <https://developers.google.com/identity/protocols/oauth2/service-account#delegatingauthority>
#'
#' @return An [`httr::TokenServiceAccount`][httr::Token-class] or `NULL`.
#' @family credential functions
#' @export
#' @examples
#' \dontrun{
#' token <- credentials_service_account(
#'   scopes = "https://www.googleapis.com/auth/userinfo.email",
#'   path = "/path/to/your/service-account.json"
#' )
#' }
credentials_service_account <- function(scopes = NULL,
                                        path = "",
                                        ...,
                                        subject = NULL) {
  gargle_debug("trying {.fun credentials_service_account}")
  info <- jsonlite::fromJSON(path, simplifyVector = FALSE)
  if (!identical(info[["type"]], "service_account")) {
    gargle_debug(c(
      "JSON does not appear to represent a service account",
      "Did you provide the JSON for an OAuth client instead of for a \\
       service account?"
    ))
    return()
  }

  # I add email scope explicitly, whereas I don't need to do so in
  # credentials_user_oauth2(), because it's done in Gargle2.0$new().
  scopes <- normalize_scopes(add_email_scope(scopes))
  token <- httr::oauth_service_token(
    ## FIXME: not sure endpoint is truly necessary, but httr thinks it is.
    ## https://github.com/r-lib/httr/issues/576
    endpoint = gargle_oauth_endpoint(),
    secrets = info,
    scope = scopes,
    sub = subject
  )
  if (is.null(token$credentials$access_token) ||
    !nzchar(token$credentials$access_token)) {
    NULL
  } else {
    gargle_debug("service account email: {.email {token_email(token)}}")
    token
  }
}

#' Check for a service account
#'
#' This pre-checks information provided to a high-level, user-facing auth
#' function, such as `googledrive::drive_auth()`, before passing the user's
#' input along to [token_fetch()], which is designed to silently swallow errors.
#' Some users are confused about the difference between an OAuth client and a
#' service account and they provide the (path to the) JSON for one, when the
#' other is what's actually expected.
#'
#' @inheritParams credentials_service_account
#' @param hint The relevant function to call for configuring an OAuth client.
#' @inheritParams rlang::abort
#'
#' @return Nothing. Exists purely to throw an error.
#' @export
#' @keywords internal
check_is_service_account <- function(path, hint, call = caller_env()) {
  if (is.null(path)) {
    return(invisible())
  }

  info <- tryCatch(
    jsonlite::fromJSON(path, simplifyVector = FALSE),
    error = function(e) NULL
  )

  if (is.null(info) || !identical(info[["type"]], "service_account")) {
    cli::cli_abort(c(
      "{.arg path} does not represent a service account.",
      "Did you provide the JSON for an OAuth client instead of for a \\
         service account?",
      "Use {.fun {hint}} to configure the OAuth client."
      ),
      call = call
    )
  }
}

Try the gargle package in your browser

Any scripts or data that you put into this service are public.

gargle documentation built on July 26, 2023, 5:29 p.m.