Skip to content

feat: added support for optional audience param #423

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

Open
wants to merge 7 commits into
base: main
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
5 changes: 4 additions & 1 deletion R/connect.R
Original file line number Diff line number Diff line change
Expand Up @@ -962,6 +962,7 @@ Connect <- R6::R6Class(
#' key here you can test a visitor client with differently-scoped
#' permissions.
#' @param prefix The prefix used to determine environment variables
#' @param audience Optional. The GUID of a Connect API integration associated with this piece of content.
#' @param ... Additional arguments. Not used at present
#' @param .check_is_fatal Whether to fail if "check" requests fail. Useful in
#' rare cases where more http request customization is needed for requests to
Expand Down Expand Up @@ -998,6 +999,7 @@ connect <- function(
token,
token_local_testing_key = api_key,
prefix = "CONNECT",
audience = NULL,
...,
.check_is_fatal = TRUE
) {
Expand All @@ -1017,7 +1019,8 @@ connect <- function(
visitor_creds <- get_oauth_credentials(
con,
user_session_token = token,
requested_token_type = "urn:posit:connect:api-key"
requested_token_type = "urn:posit:connect:api-key",
audience = audience
)
con <- connect(server = server, api_key = visitor_creds$access_token)
} else {
Expand Down
30 changes: 22 additions & 8 deletions R/get.R
Original file line number Diff line number Diff line change
Expand Up @@ -643,6 +643,8 @@ get_procs <- function(src) {
#' default to `urn:ietf:params:oauth:token-type:access_token`. Otherwise, this can
#' be set to `urn:ietf:params:aws:token-type:credentials` for AWS integrations or
#' `urn:posit:connect:api-key` for Connect API Key integrations.
#' @param audience Optional. The GUID of an OAuth integration associated with
#' this piece of content.
#'
#' @examples
#' \dontrun{
Expand Down Expand Up @@ -671,15 +673,17 @@ get_procs <- function(src) {
get_oauth_credentials <- function(
connect,
user_session_token,
requested_token_type = NULL
requested_token_type = NULL,
audience = NULL
) {
validate_R6_class(connect, "Connect")
url <- v1_url("oauth", "integrations", "credentials")
body <- list(
grant_type = "urn:ietf:params:oauth:grant-type:token-exchange",
subject_token_type = "urn:posit:connect:user-session-token",
subject_token = user_session_token,
requested_token_type = requested_token_type
requested_token_type = requested_token_type,
audience = audience
)
connect$POST(
url,
Expand All @@ -701,6 +705,8 @@ get_oauth_credentials <- function(
#' will default to `urn:ietf:params:oauth:token-type:access_token`. Otherwise,
#' this can be set to `urn:ietf:params:aws:token-type:credentials` for AWS
#' integrations or `urn:posit:connect:api-key` for Connect API Key integrations.
#' @param audience Optional. The GUID of an OAuth integration associated with
#' this piece of content.
#'
#' @examples
#' \dontrun{
Expand Down Expand Up @@ -728,7 +734,8 @@ get_oauth_credentials <- function(
get_oauth_content_credentials <- function(
connect,
content_session_token = NULL,
requested_token_type = NULL
requested_token_type = NULL,
audience = NULL
) {
validate_R6_class(connect, "Connect")
error_if_less_than(connect$version, "2024.12.0")
Expand All @@ -745,7 +752,8 @@ get_oauth_content_credentials <- function(
grant_type = "urn:ietf:params:oauth:grant-type:token-exchange",
subject_token_type = "urn:posit:connect:content-session-token",
subject_token = content_session_token,
requested_token_type = requested_token_type
requested_token_type = requested_token_type,
audience = audience
)
connect$POST(
Copy link
Collaborator

Choose a reason for hiding this comment

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

Two questions:

  • Should we check that the connect server version is new enough if !is.null(audience)? I'm guessing that if you want to specify audience and you're on Connect older than the upcoming release, you won't get back what you're asking for.
  • Does the POST handler on the server ignore unknown body fields? Like, if you were to include audience in the payload for an older Connect.

Assuming that the answers are "yes" and "no", respectively, I think we handle this like this here, instead of adding it in the body definition above:

if (!is.null(audience)) {
  if (connect$version < something) {
    stop(helpful message that this is not supported)
  }
  body$audience <- audience
}

Note that the tests in tests/testthat/ don't test against real Connect servers, so they won't give us any assurance about how older versions of Connect respond if you include this parameter. There is an integration test suite that runs against a range of Connect versions but IDK how feasible it would be to set them up with integrations to test this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do we do this version checking elsewhere?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Would it be sufficient to add a comment to the docstring that 2025.07.0 is required to respect that parameter instead of doing this check? Not sure how often this will come up, but it is just ignored if they are on an older version since the API has no knowledge of it. If they provide a value on an older version that is okay but on older versions there can only ever be one associated integration so the parameter is meaningless

Copy link
Collaborator

@nealrichardson nealrichardson Jul 22, 2025

Choose a reason for hiding this comment

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

Actually there is a helper function for this, so it would be:

if (!is.null(audience)) {
  error_if_less_than(connect$version, "2025.07.0")
  body$audience <- audience
}

url,
Expand All @@ -761,6 +769,8 @@ get_oauth_content_credentials <- function(
#' can only be obtained when the content is running on a Connect server. The token
#' identifies the user who is viewing the content interactively on the Connect server.
#' Read this value from the HTTP header: `Posit-Connect-User-Session-Token`
#' @param audience Optional. The GUID of an OAuth integration associated with
#' this piece of content.
#'
#' @return The AWS credentials as a list with fields named `access_key_id`,
#' `secret_access_key`, `session_token`, and `expiration`.
Expand Down Expand Up @@ -806,12 +816,13 @@ get_oauth_content_credentials <- function(
#' }
#'
#' @export
get_aws_credentials <- function(connect, user_session_token) {
get_aws_credentials <- function(connect, user_session_token, audience = NULL) {
error_if_less_than(connect$version, "2025.03.0")
response <- get_oauth_credentials(
connect,
user_session_token,
requested_token_type = "urn:ietf:params:aws:token-type:credentials"
requested_token_type = "urn:ietf:params:aws:token-type:credentials",
audience = audience
)

# Extract access token and decode it
Expand All @@ -834,6 +845,8 @@ get_aws_credentials <- function(connect, user_session_token) {
#' token identifies the service account integration previously configured by
#' the publisher on the Connect server. Defaults to the value from the
#' environment variable: `CONNECT_CONTENT_SESSION_TOKEN`
#' @param audience Optional. The GUID of an OAuth integration associated with
#' this piece of content.
#'
#' @return The AWS credentials as a list with fields named `access_key_id`,
#' `secret_access_key`, `session_token`, and `expiration`.
Expand Down Expand Up @@ -873,12 +886,13 @@ get_aws_credentials <- function(connect, user_session_token) {
#' }
#'
#' @export
get_aws_content_credentials <- function(connect, content_session_token = NULL) {
get_aws_content_credentials <- function(connect, content_session_token = NULL, audience = NULL) {
error_if_less_than(connect$version, "2025.03.0")
response <- get_oauth_content_credentials(
connect,
content_session_token,
requested_token_type = "urn:ietf:params:aws:token-type:credentials"
requested_token_type = "urn:ietf:params:aws:token-type:credentials",
audience = audience
)

# Extract access token and decode it
Expand Down
3 changes: 3 additions & 0 deletions man/connect.Rd

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

9 changes: 8 additions & 1 deletion man/get_aws_content_credentials.Rd

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

5 changes: 4 additions & 1 deletion man/get_aws_credentials.Rd

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

6 changes: 5 additions & 1 deletion man/get_oauth_content_credentials.Rd

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

10 changes: 9 additions & 1 deletion man/get_oauth_credentials.Rd

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

25 changes: 25 additions & 0 deletions tests/testthat/test-connect.R
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,31 @@ test_that("Visitor client can successfully be created running on Connect", {
})
})

test_that("Visitor client can successfully be created running on Connect with audience", {
with_mock_api({
withr::local_options(list(rlib_warning_verbosity = "verbose"))
withr::local_envvar(
CONNECT_SERVER = "https://connect.example",
CONNECT_API_KEY = "fake",
RSTUDIO_PRODUCT = "CONNECT"
)

expect_warning(
client <- connect(token = "my-token", audience = "audience"),
"This feature requires Posit Connect version"
)

expect_equal(
client$server,
"https://connect.example"
)
expect_equal(
client$api_key,
"visitor-api-key"
)
})
})

test_that("Visitor client uses fallback api key when running locally", {
with_mock_api({
withr::local_options(list(rlib_warning_verbosity = "verbose"))
Expand Down
Loading
Loading