Salesforce resources owned by viewer with R
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)
<- page_sidebar(
ui 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")
)
)
)
<- function(input, output, session) {
server
# 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
<- connect()
client # read the user-session-token header
<- session$request$HTTP_POSIT_CONNECT_USER_SESSION_TOKEN
user_session_token # grab the OAuth Integration access token using the session token
<- get_oauth_credentials(client, user_session_token)
credentials <- credentials$access_token
token else {
} # grab the access token from the SALESFORCE_TOKEN env var if running locally
<- Sys.getenv("SALESFORCE_TOKEN")
token
}
observeEvent(input$resource, {
# grab the salesforce domain and form the query endpoint
<- Sys.getenv("SALESFORCE_DOMAIN")
sf_domain <- paste0(sf_domain, "/services/data/v62.0/query?q=")
query_path
# grab the user ID from user info endpoint
<- "https://login.salesforce.com/services/oauth2/userinfo"
user_url <- httr2::request(user_url) |>
resp ::req_headers("Accept" = "application/json") |>
httr2::req_auth_bearer_token(token) |>
httr2::req_perform() |>
httr2::resp_body_json()
httr2<- resp$user_id
user_id
# set URL encoded query based on provided resource
<- input$resource
resource <- switch(resource,
query 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, "'")},
) <- URLencode(query, reserved = TRUE)
url_query
# append the URL encoded query to the path
<- paste0(query_path, url_query)
path
# make the request
<- httr2::request(path) |>
resp ::req_headers("Accept" = "application/json") |>
httr2::req_auth_bearer_token(token) |>
httr2# to avoid HTTP response error codes being surfaced as R errors
::req_error(is_error = ~FALSE) |>
httr2::req_perform()
httr2
# check the HTTP response code
<- httr2::resp_status(resp)
http_code if (http_code == 200) {
# format response body now that we have the HTTP status code
<- httr2::resp_body_json(resp)
resp $results <- renderText({
outputpaste0(resp)
})else {
} # output the unexpected HTTP code
$results <- renderUI({
outputHTML("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>")
::runApp() shiny