Salesforce resources owned by viewer with R
All of the content listed below operates under the assumption that all of the necessary setup in the Salesforce section of the OAuth Integrations Admin Guide has already been completed.
Problem
Your organization’s Sales team contains a diverse group of people with varying roles in Salesforce. Tasked to help generate sales reports per-user, you are not sure what resources each person can access. While researching the roles and their associated permissions you wonder if each user could query Salesforce resources as themselves instead of using a Salesforce Developer API key.
Solution
You create a simple Shiny application that uses the Salesforce Viewer OAuth integration set up by your Posit Connect administrator. Members of the Sales team are able to query various resources assigned to them in Salesforce after logging in to the integration. Folks who do not have access to the resource do not see any results, and only resources that the user is the owner of are shown.
app.R
library(shiny)
library(httr2)
library(bslib)
library(connectapi)
ui <- page_sidebar(
title = "Salesforce Resource by Owner",
sidebar = sidebar(
title = "Salesforce",
selectInput("resource",
"Resource to query",
list("Tasks" = "tasks", "Accounts" = "accounts", "Leads" = "leads")),
),
layout_columns(
card(
card_header("Results"),
verbatimTextOutput("results")
)
)
)
server <- function(input, output, session) {
# check if running on Posit Connect
# note: use RSTUDIO_PRODUCT (deprecated) for Connect versions < 2025.02.0
if (Sys.getenv("POSIT_PRODUCT") == "CONNECT") {
# initialize Connect API client
client <- connect()
# read the user-session-token header
user_session_token <- session$request$HTTP_POSIT_CONNECT_USER_SESSION_TOKEN
# grab the OAuth Integration access token using the session token
credentials <- get_oauth_credentials(client, user_session_token)
token <- credentials$access_token
} else {
# grab the access token from the SALESFORCE_TOKEN env var if running locally
token <- Sys.getenv("SALESFORCE_TOKEN")
}
observeEvent(input$resource, {
# grab the salesforce domain and form the query endpoint
sf_domain <- Sys.getenv("SALESFORCE_DOMAIN")
query_path <- paste0(sf_domain, "/services/data/v62.0/query?q=")
# grab the user ID from user info endpoint
user_url <- "https://login.salesforce.com/services/oauth2/userinfo"
resp <- httr2::request(user_url) |>
httr2::req_headers("Accept" = "application/json") |>
httr2::req_auth_bearer_token(token) |>
httr2::req_perform() |>
httr2::resp_body_json()
user_id <- resp$user_id
# set URL encoded query based on provided resource
resource <- input$resource
query <- switch(resource,
tasks = {paste0("SELECT Subject, Status, Priority FROM Task WHERE OwnerId = '", user_id, "'")},
accounts = {paste0("SELECT Id, Name, Industry, BillingCity FROM Account WHERE OwnerId = '", user_id, "'")},
leads = {paste0("SELECT Id, Name, Company, Status FROM Lead WHERE OwnerId = '", user_id, "'")},
)
url_query <- URLencode(query, reserved = TRUE)
# append the URL encoded query to the path
path <- paste0(query_path, url_query)
# make the request
resp <- httr2::request(path) |>
httr2::req_headers("Accept" = "application/json") |>
httr2::req_auth_bearer_token(token) |>
# to avoid HTTP response error codes being surfaced as R errors
httr2::req_error(is_error = ~FALSE) |>
httr2::req_perform()
# check the HTTP response code
http_code <- httr2::resp_status(resp)
if (http_code == 200) {
# format response body now that we have the HTTP status code
resp <- httr2::resp_body_json(resp)
output$results <- renderText({
paste0(resp)
})
} else {
# output the unexpected HTTP code
output$results <- renderUI({
HTML("Received non-200 HTTP response code: ", http_code)
})
}
})
}
shinyApp(ui, server)Running the app locally
Terminal
Sys.setenv(CONNECT_SERVER = "<connect-host>")
Sys.setenv(CONNECT_API_KEY = "<connect-api-key>")
# SALESFORCE_TOKEN is only required when running the example locally
Sys.setenv(SALESFORCE_TOKEN = "<salesforce-token>")
# SALESFORCE_DOMAIN must not contain a trailing slash
Sys.setenv(SALESFORCE_DOMAIN = "<salesforce-domain>")
shiny::runApp()