11from __future__ import annotations
22
33import os
4+ import re
45from enum import Enum
56
67from typing_extensions import TYPE_CHECKING , Optional , TypedDict
78
9+ from posit .connect .oauth .associations import ContentItemAssociations
10+
811from ..resources import Resources
912from .integrations import Integrations
1013from .sessions import Sessions
1114
1215if TYPE_CHECKING :
16+ from posit .connect .external import aws
17+
1318 from ..context import Context
1419
1520GRANT_TYPE = "urn:ietf:params:oauth:grant-type:token-exchange"
@@ -23,6 +28,25 @@ class OAuthTokenType(str, Enum):
2328 USER_SESSION_TOKEN = "urn:posit:connect:user-session-token"
2429
2530
31+ class OAuthIntegrationAuthType (str , Enum ):
32+ """OAuth integration authentication type."""
33+
34+ VIEWER = "Viewer"
35+ SERVICE_ACCOUNT = "Service Account"
36+ VISITOR_API_KEY = "Visitor API Key"
37+
38+
39+ class OAuthIntegrationType (str , Enum ):
40+ """OAuth integration type."""
41+
42+ AWS = "aws"
43+ AZURE = "azure"
44+ CONNECT = "connect"
45+ SNOWFLAKE = "snowflake"
46+ CUSTOM = "custom"
47+ # TODO add the rest
48+
49+
2650def _get_content_session_token () -> str :
2751 """Return the content session token.
2852
@@ -99,6 +123,74 @@ def get_content_credentials(
99123 response = self ._ctx .client .post (self ._path , data = data )
100124 return Credentials (** response .json ())
101125
126+ def get_credentials_by (
127+ self ,
128+ user_session_token : str ,
129+ content_session_token : Optional [str ] = None ,
130+ integration_type : Optional [OAuthIntegrationType ] = None ,
131+ auth_type : Optional [OAuthIntegrationAuthType ] = None ,
132+ name : Optional [str | re .Pattern ] = None ,
133+ guid : Optional [str ] = None ,
134+ ) -> Credentials | aws .Credentials :
135+ """Perform an oauth credential exchange for all integrations associated with the current content item."""
136+ content_guid = os .getenv ("CONNECT_CONTENT_GUID" )
137+ if not content_guid :
138+ raise ValueError ("CONNECT_CONTENT_GUID environment variable is required." )
139+ content_associations = ContentItemAssociations (self ._ctx , content_guid = content_guid ).find ()
140+ # associations format: [{
141+ # content_guid: uuid
142+ # app_guid: uuid
143+ # oauth_integration_guid: uuid
144+ # oauth_integration_name: string
145+ # oauth_integration_description: string┃null
146+ # oauth_integration_template: string
147+ # oauth_integration_auth_type: string
148+ # created_time: date-time
149+ # }]
150+ for association in content_associations :
151+ match = True
152+
153+ if (
154+ integration_type is not None
155+ and association .get ("oauth_integration_template" ) != integration_type
156+ ):
157+ match = False
158+
159+ if (
160+ auth_type is not None
161+ and association .get ("oauth_integration_auth_type" ) != auth_type
162+ ):
163+ match = False
164+
165+ if name is not None :
166+ integration_name = association .get ("oauth_integration_name" , "" )
167+ if isinstance (name , re .Pattern ):
168+ if not name .search (integration_name ):
169+ match = False
170+ else :
171+ if integration_name != name :
172+ match = False
173+
174+ if guid is not None and association .get ("oauth_integration_guid" ) != guid :
175+ match = False
176+
177+ if match :
178+ # Use the first matching association to get credentials
179+ if association .get ("oauth_integration_auth_type" ) in [
180+ OAuthIntegrationAuthType .VIEWER ,
181+ OAuthIntegrationAuthType .VISITOR_API_KEY ,
182+ ]:
183+ return self .get_credentials (
184+ user_session_token = user_session_token ,
185+ audience = association .get ("oauth_integration_guid" ),
186+ )
187+ return self .get_content_credentials (
188+ content_session_token = content_session_token ,
189+ audience = association .get ("oauth_integration_guid" ),
190+ )
191+
192+ raise ValueError ("No matching OAuth integration found for the specified criteria." )
193+
102194
103195class Credentials (TypedDict , total = False ):
104196 access_token : str
0 commit comments