Skip to content
Merged
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
42 changes: 42 additions & 0 deletions integration/tests/posit/connect/oauth/test_associations.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,14 @@ def test_find_by_integration(self):
no_associations = self.another_integration.associations.find()
assert len(no_associations) == 0

def test_find_by_content(self):
association = self.content.oauth.associations.find_by(integration_type="custom")
assert association is not None
assert association["oauth_integration_guid"] == self.integration["guid"]

no_association = self.content.oauth.associations.find_by(integration_type="connect")
assert no_association is None

def test_find_update_by_content(self):
associations = self.content.oauth.associations.find()
assert len(associations) == 1
Expand All @@ -114,3 +122,37 @@ def test_find_update_by_content(self):
self.content.oauth.associations.delete()
no_associations = self.content.oauth.associations.find()
assert len(no_associations) == 0

@pytest.mark.skipif(
CONNECT_VERSION < version.parse("2025.07.0"),
reason="Multi associations not supported.",
)
def test_find_update_by_content_multiple(self):
self.content.oauth.associations.update(
[
self.integration["guid"],
self.another_integration["guid"],
]
)
updated_associations = self.content.oauth.associations.find()
assert len(updated_associations) == 2
for assoc in updated_associations:
assert assoc["app_guid"] == self.content["guid"]
assert assoc["oauth_integration_guid"] in [
self.integration["guid"],
self.another_integration["guid"],
]

associated_connect_integration = self.content.oauth.associations.find_by(
name=".*another.*"
)
assert associated_connect_integration is not None
assert (
associated_connect_integration["oauth_integration_guid"]
== self.another_integration["guid"]
)

# unset content association
self.content.oauth.associations.delete()
no_associations = self.content.oauth.associations.find()
assert len(no_associations) == 0
16 changes: 16 additions & 0 deletions integration/tests/posit/connect/oauth/test_integrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,22 @@ def test_find(self):
assert results[0] == self.integration
assert results[1] == self.another_integration

def test_find_by(self):
result = self.client.oauth.integrations.find_by(
integration_type="custom",
config={"auth_mode": "Confidential"},
name="example integration",
)
assert result is not None
assert result["guid"] == self.integration["guid"]

result = self.client.oauth.integrations.find_by(
integration_type="custom",
config={"auth_mode": "Confidential"},
name="nonexistent integration",
)
assert result is None

def test_create_update_delete(self):
# create a new integration

Expand Down
18 changes: 17 additions & 1 deletion src/posit/connect/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from .groups import Groups
from .metrics.metrics import Metrics
from .oauth.oauth import OAuth
from .oauth.types import OAuthTokenType
from .oauth.types import OAuthIntegrationType, OAuthTokenType
from .resources import _PaginatedResourceSequence, _ResourceSequence
from .sessions import Session
from .system import System
Expand Down Expand Up @@ -198,6 +198,10 @@ def with_user_session_token(
----------
token : str
The user session token.
audience : str, optional
The audience for the token exchange. This is the integration GUID of the Connect API integration
that is associate with the content. If not provided when there are multiple integrations, the
function will attempt to determine the audience from the current content associations.

Returns
-------
Expand Down Expand Up @@ -260,6 +264,18 @@ def user_profile():
if token is None or token == "":
raise ValueError("token must be set to non-empty string.")

# If the audience is not provided and there are multiple associations,
# we will try to find the Connect API integration GUID from the content resource.
current_content_associations = self.content.get().oauth.associations.find()
if audience is None and len(current_content_associations) > 1:
connect_api_integration_guids = [
a["oauth_integration_guid"]
for a in current_content_associations
if a.get("oauth_integration_template") == OAuthIntegrationType.CONNECT
]
if len(connect_api_integration_guids) == 1:
audience = connect_api_integration_guids[0]

visitor_credentials = self.oauth.get_credentials(
token,
requested_token_type=OAuthTokenType.API_KEY,
Expand Down
2 changes: 0 additions & 2 deletions src/posit/connect/oauth/associations.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

from typing_extensions import TYPE_CHECKING, List, Optional

from ..context import requires
from ..resources import BaseResource, Resources, _matches_exact, _matches_pattern

if TYPE_CHECKING:
Expand Down Expand Up @@ -67,7 +66,6 @@ def find(self) -> List[Association]:
for result in response.json()
]

@requires("2025.07.0-dev")
def find_by(
self,
integration_type: Optional[types.OAuthIntegrationType | str] = None,
Expand Down
2 changes: 0 additions & 2 deletions src/posit/connect/oauth/integrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

from typing_extensions import TYPE_CHECKING, List, Optional, overload

from ..context import requires
from ..resources import (
BaseResource,
Resources,
Expand Down Expand Up @@ -132,7 +131,6 @@ def find(self) -> List[Integration]:
for result in response.json()
]

@requires("2025.07.0-dev")
def find_by(
self,
integration_type: Optional[types.OAuthIntegrationType | str] = None,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"oauth_integration_guid": "00000000-a27b-4118-ad06-e24459b05126",
"oauth_integration_name": "another integration",
"oauth_integration_description": "another description",
"oauth_integration_template": "custom",
"oauth_integration_template": "connect",
"created_time": "2024-10-02T18:16:09Z"
}
]
4 changes: 2 additions & 2 deletions tests/posit/connect/oauth/test_associations.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,9 +220,9 @@ def test(self):
assert found["oauth_integration_name"] == "keycloak integration" # first one

# by multiple criteria
found = associations.find_by(integration_type="custom", name="another integration")
found = associations.find_by(integration_type="connect", name="another integration")
assert found is not None
assert found["oauth_integration_template"] == "custom"
assert found["oauth_integration_template"] == "connect"
assert found["oauth_integration_name"] == "another integration"

# no match
Expand Down
Loading