Skip to content

Commit fe0dee4

Browse files
committed
feat: support OIDC endpoints
Add support for remaining OIDC endpoints, including getting an OIDC configuration by ID, removing the configuration, creating, and updating configurations.
1 parent 5e49f38 commit fe0dee4

File tree

11 files changed

+596
-0
lines changed

11 files changed

+596
-0
lines changed

tableauserverclient/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
RevisionItem,
3838
ScheduleItem,
3939
SiteAuthConfiguration,
40+
SiteOIDCConfiguration,
4041
SiteItem,
4142
ServerInfoItem,
4243
SubscriptionItem,
@@ -125,6 +126,7 @@
125126
"ServerResponseError",
126127
"SiteItem",
127128
"SiteAuthConfiguration",
129+
"SiteOIDCConfiguration",
128130
"Sort",
129131
"SubscriptionItem",
130132
"TableauAuth",

tableauserverclient/models/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
)
3131
from tableauserverclient.models.location_item import LocationItem
3232
from tableauserverclient.models.metric_item import MetricItem
33+
from tableauserverclient.models.oidc_item import SiteOIDCConfiguration
3334
from tableauserverclient.models.pagination_item import PaginationItem
3435
from tableauserverclient.models.permissions_item import PermissionsRule, Permission
3536
from tableauserverclient.models.project_item import ProjectItem
@@ -79,6 +80,7 @@
7980
"BackgroundJobItem",
8081
"LocationItem",
8182
"MetricItem",
83+
"SiteOIDCConfiguration",
8284
"PaginationItem",
8385
"Permission",
8486
"PermissionsRule",
@@ -88,6 +90,7 @@
8890
"ServerInfoItem",
8991
"SiteAuthConfiguration",
9092
"SiteItem",
93+
"SiteOIDCConfiguration",
9194
"SubscriptionItem",
9295
"TableItem",
9396
"TableauAuth",
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
from typing import Optional
2+
from defusedxml.ElementTree import fromstring
3+
4+
5+
class SiteOIDCConfiguration:
6+
def __init__(self) -> None:
7+
self.enabled: bool = False
8+
self.test_login_url: Optional[str] = None
9+
self.known_provider_alias: Optional[str] = None
10+
self.allow_embedded_authentication: bool = False
11+
self.use_full_name: bool = False
12+
self.idp_configuration_name: Optional[str] = None
13+
self.idp_configuration_id: Optional[str] = None
14+
self.client_id: Optional[str] = None
15+
self.client_secret: Optional[str] = None
16+
self.authorization_endpoint: Optional[str] = None
17+
self.token_endpoint: Optional[str] = None
18+
self.userinfo_endpoint: Optional[str] = None
19+
self.jwks_uri: Optional[str] = None
20+
self.end_session_endpoint: Optional[str] = None
21+
self.custom_scope: Optional[str] = None
22+
self.essential_acr_values: Optional[str] = None
23+
self.email_mapping: Optional[str] = None
24+
self.first_name_mapping: Optional[str] = None
25+
self.last_name_mapping: Optional[str] = None
26+
self.full_name_mapping: Optional[str] = None
27+
self.prompt: Optional[str] = None
28+
self.client_authentication: Optional[str] = None
29+
self.voluntary_acr_values: Optional[str] = None
30+
31+
@classmethod
32+
def from_response(cls, raw_xml: bytes, ns) -> "SiteOIDCConfiguration":
33+
"""
34+
Parses the raw XML bytes and returns a SiteOIDCConfiguration object.
35+
"""
36+
root = fromstring(raw_xml)
37+
elem = root.find("t:siteOIDCConfiguration", namespaces=ns)
38+
if elem is None:
39+
raise ValueError("No siteOIDCConfiguration element found in the XML.")
40+
config = cls()
41+
42+
config.enabled = str_to_bool(elem.get("enabled", "false"))
43+
config.test_login_url = elem.get("testLoginUrl")
44+
config.known_provider_alias = elem.get("knownProviderAlias")
45+
config.allow_embedded_authentication = str_to_bool(elem.get("allowEmbeddedAuthentication", "false").lower())
46+
config.use_full_name = str_to_bool(elem.get("useFullName", "false").lower())
47+
config.idp_configuration_name = elem.get("idpConfigurationName")
48+
config.idp_configuration_id = elem.get("idpConfigurationId")
49+
config.client_id = elem.get("clientId")
50+
config.client_secret = elem.get("clientSecret")
51+
config.authorization_endpoint = elem.get("authorizationEndpoint")
52+
config.token_endpoint = elem.get("tokenEndpoint")
53+
config.userinfo_endpoint = elem.get("userinfoEndpoint")
54+
config.jwks_uri = elem.get("jwksUri")
55+
config.end_session_endpoint = elem.get("endSessionEndpoint")
56+
config.custom_scope = elem.get("customScope")
57+
config.essential_acr_values = elem.get("essentialAcrValues")
58+
config.email_mapping = elem.get("emailMapping")
59+
config.first_name_mapping = elem.get("firstNameMapping")
60+
config.last_name_mapping = elem.get("lastNameMapping")
61+
config.full_name_mapping = elem.get("fullNameMapping")
62+
config.prompt = elem.get("prompt")
63+
config.client_authentication = elem.get("clientAuthentication")
64+
config.voluntary_acr_values = elem.get("voluntaryAcrValues")
65+
66+
return config
67+
68+
69+
def str_to_bool(s: str) -> bool:
70+
return s == "true"

tableauserverclient/server/endpoint/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from tableauserverclient.server.endpoint.linked_tasks_endpoint import LinkedTasks
1818
from tableauserverclient.server.endpoint.metadata_endpoint import Metadata
1919
from tableauserverclient.server.endpoint.metrics_endpoint import Metrics
20+
from tableauserverclient.server.endpoint.oidc_endpoint import OIDC
2021
from tableauserverclient.server.endpoint.projects_endpoint import Projects
2122
from tableauserverclient.server.endpoint.schedules_endpoint import Schedules
2223
from tableauserverclient.server.endpoint.server_info_endpoint import ServerInfo
@@ -52,6 +53,7 @@
5253
"LinkedTasks",
5354
"Metadata",
5455
"Metrics",
56+
"OIDC",
5557
"Projects",
5658
"Schedules",
5759
"ServerInfo",
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
from typing import Protocol, Union, TYPE_CHECKING
2+
from tableauserverclient.models.oidc_item import SiteOIDCConfiguration
3+
from tableauserverclient.server.endpoint import Endpoint
4+
from tableauserverclient.server.request_factory import RequestFactory
5+
from tableauserverclient.server.endpoint.endpoint import api
6+
7+
if TYPE_CHECKING:
8+
from tableauserverclient.models.site_item import SiteAuthConfiguration
9+
from tableauserverclient.server.server import Server
10+
11+
12+
class IDPAttributes(Protocol):
13+
idp_configuration_id: str
14+
15+
16+
class IDPProperty(Protocol):
17+
@property
18+
def idp_configuration_id(self) -> str: ...
19+
20+
21+
HasIdpConfigurationID = Union[str, IDPAttributes]
22+
23+
24+
class OIDC(Endpoint):
25+
def __init__(self, server: "Server") -> None:
26+
self.parent_srv = server
27+
28+
@property
29+
def baseurl(self) -> str:
30+
return f"{self.parent_srv.baseurl}/sites/{self.parent_srv.site_id}/site-oidc-configuration"
31+
32+
@api(version="3.24")
33+
def get(self) -> list["SiteAuthConfiguration"]:
34+
"""
35+
Get all OpenID Connect (OIDC) configurations for the currently
36+
authenticated Tableau Cloud site. To get all of the configuration
37+
details, use the get_by_id method.
38+
39+
REST API: https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_identity_pools.htm#AuthnService_ListAuthConfigurations
40+
41+
Returns
42+
-------
43+
list[SiteAuthConfiguration]
44+
"""
45+
return self.parent_srv.sites.list_auth_configurations()
46+
47+
@api(version="3.24")
48+
def get_by_id(self, id: Union[str, HasIdpConfigurationID]) -> SiteOIDCConfiguration:
49+
"""
50+
Get details about a specific OpenID Connect (OIDC) configuration on the
51+
current Tableau Cloud site. Only retrieves configurations for the
52+
currently authenticated site.
53+
54+
REST API: https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_openid_connect.htm#get_openid_connect_configuration
55+
56+
Parameters
57+
----------
58+
id : Union[str, HasID]
59+
The ID of the OIDC configuration to retrieve. Can be either the
60+
ID string or an object with an id attribute.
61+
62+
Returns
63+
-------
64+
SiteOIDCConfiguration
65+
The OIDC configuration for the specified site.
66+
"""
67+
target = getattr(id, "idp_configuration_id", id)
68+
url = f"{self.baseurl}/{target}"
69+
response = self.get_request(url)
70+
return SiteOIDCConfiguration.from_response(response.content, self.parent_srv.namespace)
71+
72+
@api(version="3.22")
73+
def create(self, config_item: SiteOIDCConfiguration) -> SiteOIDCConfiguration:
74+
"""
75+
Create the OpenID Connect (OIDC) configuration for the currently
76+
authenticated Tableau Cloud site. The config_item must have the
77+
following attributes set, others are optional:
78+
79+
idp_configuration_name
80+
client_id
81+
client_secret
82+
authorization_endpoint
83+
token_endpoint
84+
userinfo_endpoint
85+
enabled
86+
jwks_uri
87+
88+
The secret in the returned config will be masked.
89+
90+
REST API: https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_openid_connect.htm#create_openid_connect_configuration
91+
92+
Parameters
93+
----------
94+
config : SiteOIDCConfiguration
95+
The OIDC configuration to create.
96+
97+
Returns
98+
-------
99+
SiteOIDCConfiguration
100+
The created OIDC configuration.
101+
"""
102+
url = self.baseurl
103+
create_req = RequestFactory.OIDC.create_req(config_item)
104+
response = self.put_request(url, create_req)
105+
return SiteOIDCConfiguration.from_response(response.content, self.parent_srv.namespace)
106+
107+
@api(version="3.24")
108+
def delete_configuration(self, config: Union[str, HasIdpConfigurationID]) -> None:
109+
"""
110+
Delete the OpenID Connect (OIDC) configuration for the currently
111+
authenticated Tableau Cloud site. The config parameter can be either
112+
the ID of the configuration or the configuration object itself.
113+
114+
**Important**: Before removing the OIDC configuration, make sure that
115+
users who are set to authenticate with OIDC are set to use a different
116+
authentication type. Users who are not set with a different
117+
authentication type before removing the OIDC configuration will not be
118+
able to sign in to Tableau Cloud.
119+
120+
121+
REST API: https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_openid_connect.htm#remove_openid_connect_configuration
122+
123+
Parameters
124+
----------
125+
config : Union[str, HasID]
126+
The OIDC configuration to delete. Can be either the ID of the
127+
configuration or the configuration object itself.
128+
"""
129+
130+
target = getattr(config, "idp_configuration_id", config)
131+
132+
url = f"{self.parent_srv.baseurl}/sites/{self.parent_srv.site_id}/disable-site-oidc-configuration?idpConfigurationId={target}"
133+
_ = self.put_request(url)
134+
return None
135+
136+
@api(version="3.22")
137+
def update(self, config: SiteOIDCConfiguration) -> SiteOIDCConfiguration:
138+
"""
139+
Update the Tableau Cloud site's OpenID Connect (OIDC) configuration. The
140+
secret in the returned config will be masked.
141+
142+
REST API: https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_openid_connect.htm#update_openid_connect_configuration
143+
144+
Parameters
145+
----------
146+
config : SiteOIDCConfiguration
147+
The OIDC configuration to update. Must have the id attribute set.
148+
149+
Returns
150+
-------
151+
SiteOIDCConfiguration
152+
The updated OIDC configuration.
153+
"""
154+
url = f"{self.baseurl}/{config.idp_configuration_id}"
155+
update_req = RequestFactory.OIDC.update_req(config)
156+
response = self.put_request(url, update_req)
157+
return SiteOIDCConfiguration.from_response(response.content, self.parent_srv.namespace)

0 commit comments

Comments
 (0)