diff --git a/tableauserverclient/__init__.py b/tableauserverclient/__init__.py
index 21e2c476..c15e1a6e 100644
--- a/tableauserverclient/__init__.py
+++ b/tableauserverclient/__init__.py
@@ -37,6 +37,7 @@
RevisionItem,
ScheduleItem,
SiteAuthConfiguration,
+ SiteOIDCConfiguration,
SiteItem,
ServerInfoItem,
SubscriptionItem,
@@ -125,6 +126,7 @@
"ServerResponseError",
"SiteItem",
"SiteAuthConfiguration",
+ "SiteOIDCConfiguration",
"Sort",
"SubscriptionItem",
"TableauAuth",
diff --git a/tableauserverclient/models/__init__.py b/tableauserverclient/models/__init__.py
index 30cd8810..5ad7ec1c 100644
--- a/tableauserverclient/models/__init__.py
+++ b/tableauserverclient/models/__init__.py
@@ -30,6 +30,7 @@
)
from tableauserverclient.models.location_item import LocationItem
from tableauserverclient.models.metric_item import MetricItem
+from tableauserverclient.models.oidc_item import SiteOIDCConfiguration
from tableauserverclient.models.pagination_item import PaginationItem
from tableauserverclient.models.permissions_item import PermissionsRule, Permission
from tableauserverclient.models.project_item import ProjectItem
@@ -79,6 +80,7 @@
"BackgroundJobItem",
"LocationItem",
"MetricItem",
+ "SiteOIDCConfiguration",
"PaginationItem",
"Permission",
"PermissionsRule",
@@ -88,6 +90,7 @@
"ServerInfoItem",
"SiteAuthConfiguration",
"SiteItem",
+ "SiteOIDCConfiguration",
"SubscriptionItem",
"TableItem",
"TableauAuth",
diff --git a/tableauserverclient/models/oidc_item.py b/tableauserverclient/models/oidc_item.py
new file mode 100644
index 00000000..9391ff4f
--- /dev/null
+++ b/tableauserverclient/models/oidc_item.py
@@ -0,0 +1,70 @@
+from typing import Optional
+from defusedxml.ElementTree import fromstring
+
+
+class SiteOIDCConfiguration:
+ def __init__(self) -> None:
+ self.enabled: bool = False
+ self.test_login_url: Optional[str] = None
+ self.known_provider_alias: Optional[str] = None
+ self.allow_embedded_authentication: bool = False
+ self.use_full_name: bool = False
+ self.idp_configuration_name: Optional[str] = None
+ self.idp_configuration_id: Optional[str] = None
+ self.client_id: Optional[str] = None
+ self.client_secret: Optional[str] = None
+ self.authorization_endpoint: Optional[str] = None
+ self.token_endpoint: Optional[str] = None
+ self.userinfo_endpoint: Optional[str] = None
+ self.jwks_uri: Optional[str] = None
+ self.end_session_endpoint: Optional[str] = None
+ self.custom_scope: Optional[str] = None
+ self.essential_acr_values: Optional[str] = None
+ self.email_mapping: Optional[str] = None
+ self.first_name_mapping: Optional[str] = None
+ self.last_name_mapping: Optional[str] = None
+ self.full_name_mapping: Optional[str] = None
+ self.prompt: Optional[str] = None
+ self.client_authentication: Optional[str] = None
+ self.voluntary_acr_values: Optional[str] = None
+
+ @classmethod
+ def from_response(cls, raw_xml: bytes, ns) -> "SiteOIDCConfiguration":
+ """
+ Parses the raw XML bytes and returns a SiteOIDCConfiguration object.
+ """
+ root = fromstring(raw_xml)
+ elem = root.find("t:siteOIDCConfiguration", namespaces=ns)
+ if elem is None:
+ raise ValueError("No siteOIDCConfiguration element found in the XML.")
+ config = cls()
+
+ config.enabled = str_to_bool(elem.get("enabled", "false"))
+ config.test_login_url = elem.get("testLoginUrl")
+ config.known_provider_alias = elem.get("knownProviderAlias")
+ config.allow_embedded_authentication = str_to_bool(elem.get("allowEmbeddedAuthentication", "false").lower())
+ config.use_full_name = str_to_bool(elem.get("useFullName", "false").lower())
+ config.idp_configuration_name = elem.get("idpConfigurationName")
+ config.idp_configuration_id = elem.get("idpConfigurationId")
+ config.client_id = elem.get("clientId")
+ config.client_secret = elem.get("clientSecret")
+ config.authorization_endpoint = elem.get("authorizationEndpoint")
+ config.token_endpoint = elem.get("tokenEndpoint")
+ config.userinfo_endpoint = elem.get("userinfoEndpoint")
+ config.jwks_uri = elem.get("jwksUri")
+ config.end_session_endpoint = elem.get("endSessionEndpoint")
+ config.custom_scope = elem.get("customScope")
+ config.essential_acr_values = elem.get("essentialAcrValues")
+ config.email_mapping = elem.get("emailMapping")
+ config.first_name_mapping = elem.get("firstNameMapping")
+ config.last_name_mapping = elem.get("lastNameMapping")
+ config.full_name_mapping = elem.get("fullNameMapping")
+ config.prompt = elem.get("prompt")
+ config.client_authentication = elem.get("clientAuthentication")
+ config.voluntary_acr_values = elem.get("voluntaryAcrValues")
+
+ return config
+
+
+def str_to_bool(s: str) -> bool:
+ return s == "true"
diff --git a/tableauserverclient/server/endpoint/__init__.py b/tableauserverclient/server/endpoint/__init__.py
index b05b9add..3c1266f9 100644
--- a/tableauserverclient/server/endpoint/__init__.py
+++ b/tableauserverclient/server/endpoint/__init__.py
@@ -17,6 +17,7 @@
from tableauserverclient.server.endpoint.linked_tasks_endpoint import LinkedTasks
from tableauserverclient.server.endpoint.metadata_endpoint import Metadata
from tableauserverclient.server.endpoint.metrics_endpoint import Metrics
+from tableauserverclient.server.endpoint.oidc_endpoint import OIDC
from tableauserverclient.server.endpoint.projects_endpoint import Projects
from tableauserverclient.server.endpoint.schedules_endpoint import Schedules
from tableauserverclient.server.endpoint.server_info_endpoint import ServerInfo
@@ -52,6 +53,7 @@
"LinkedTasks",
"Metadata",
"Metrics",
+ "OIDC",
"Projects",
"Schedules",
"ServerInfo",
diff --git a/tableauserverclient/server/endpoint/oidc_endpoint.py b/tableauserverclient/server/endpoint/oidc_endpoint.py
new file mode 100644
index 00000000..d1600809
--- /dev/null
+++ b/tableauserverclient/server/endpoint/oidc_endpoint.py
@@ -0,0 +1,157 @@
+from typing import Protocol, Union, TYPE_CHECKING
+from tableauserverclient.models.oidc_item import SiteOIDCConfiguration
+from tableauserverclient.server.endpoint import Endpoint
+from tableauserverclient.server.request_factory import RequestFactory
+from tableauserverclient.server.endpoint.endpoint import api
+
+if TYPE_CHECKING:
+ from tableauserverclient.models.site_item import SiteAuthConfiguration
+ from tableauserverclient.server.server import Server
+
+
+class IDPAttributes(Protocol):
+ idp_configuration_id: str
+
+
+class IDPProperty(Protocol):
+ @property
+ def idp_configuration_id(self) -> str: ...
+
+
+HasIdpConfigurationID = Union[str, IDPAttributes]
+
+
+class OIDC(Endpoint):
+ def __init__(self, server: "Server") -> None:
+ self.parent_srv = server
+
+ @property
+ def baseurl(self) -> str:
+ return f"{self.parent_srv.baseurl}/sites/{self.parent_srv.site_id}/site-oidc-configuration"
+
+ @api(version="3.24")
+ def get(self) -> list["SiteAuthConfiguration"]:
+ """
+ Get all OpenID Connect (OIDC) configurations for the currently
+ authenticated Tableau Cloud site. To get all of the configuration
+ details, use the get_by_id method.
+
+ REST API: https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_identity_pools.htm#AuthnService_ListAuthConfigurations
+
+ Returns
+ -------
+ list[SiteAuthConfiguration]
+ """
+ return self.parent_srv.sites.list_auth_configurations()
+
+ @api(version="3.24")
+ def get_by_id(self, id: Union[str, HasIdpConfigurationID]) -> SiteOIDCConfiguration:
+ """
+ Get details about a specific OpenID Connect (OIDC) configuration on the
+ current Tableau Cloud site. Only retrieves configurations for the
+ currently authenticated site.
+
+ REST API: https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_openid_connect.htm#get_openid_connect_configuration
+
+ Parameters
+ ----------
+ id : Union[str, HasID]
+ The ID of the OIDC configuration to retrieve. Can be either the
+ ID string or an object with an id attribute.
+
+ Returns
+ -------
+ SiteOIDCConfiguration
+ The OIDC configuration for the specified site.
+ """
+ target = getattr(id, "idp_configuration_id", id)
+ url = f"{self.baseurl}/{target}"
+ response = self.get_request(url)
+ return SiteOIDCConfiguration.from_response(response.content, self.parent_srv.namespace)
+
+ @api(version="3.22")
+ def create(self, config_item: SiteOIDCConfiguration) -> SiteOIDCConfiguration:
+ """
+ Create the OpenID Connect (OIDC) configuration for the currently
+ authenticated Tableau Cloud site. The config_item must have the
+ following attributes set, others are optional:
+
+ idp_configuration_name
+ client_id
+ client_secret
+ authorization_endpoint
+ token_endpoint
+ userinfo_endpoint
+ enabled
+ jwks_uri
+
+ The secret in the returned config will be masked.
+
+ REST API: https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_openid_connect.htm#create_openid_connect_configuration
+
+ Parameters
+ ----------
+ config : SiteOIDCConfiguration
+ The OIDC configuration to create.
+
+ Returns
+ -------
+ SiteOIDCConfiguration
+ The created OIDC configuration.
+ """
+ url = self.baseurl
+ create_req = RequestFactory.OIDC.create_req(config_item)
+ response = self.put_request(url, create_req)
+ return SiteOIDCConfiguration.from_response(response.content, self.parent_srv.namespace)
+
+ @api(version="3.24")
+ def delete_configuration(self, config: Union[str, HasIdpConfigurationID]) -> None:
+ """
+ Delete the OpenID Connect (OIDC) configuration for the currently
+ authenticated Tableau Cloud site. The config parameter can be either
+ the ID of the configuration or the configuration object itself.
+
+ **Important**: Before removing the OIDC configuration, make sure that
+ users who are set to authenticate with OIDC are set to use a different
+ authentication type. Users who are not set with a different
+ authentication type before removing the OIDC configuration will not be
+ able to sign in to Tableau Cloud.
+
+
+ REST API: https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_openid_connect.htm#remove_openid_connect_configuration
+
+ Parameters
+ ----------
+ config : Union[str, HasID]
+ The OIDC configuration to delete. Can be either the ID of the
+ configuration or the configuration object itself.
+ """
+
+ target = getattr(config, "idp_configuration_id", config)
+
+ url = f"{self.parent_srv.baseurl}/sites/{self.parent_srv.site_id}/disable-site-oidc-configuration?idpConfigurationId={target}"
+ _ = self.put_request(url)
+ return None
+
+ @api(version="3.22")
+ def update(self, config: SiteOIDCConfiguration) -> SiteOIDCConfiguration:
+ """
+ Update the Tableau Cloud site's OpenID Connect (OIDC) configuration. The
+ secret in the returned config will be masked.
+
+ REST API: https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_openid_connect.htm#update_openid_connect_configuration
+
+ Parameters
+ ----------
+ config : SiteOIDCConfiguration
+ The OIDC configuration to update. Must have the id attribute set.
+
+ Returns
+ -------
+ SiteOIDCConfiguration
+ The updated OIDC configuration.
+ """
+ url = f"{self.baseurl}/{config.idp_configuration_id}"
+ update_req = RequestFactory.OIDC.update_req(config)
+ response = self.put_request(url, update_req)
+ return SiteOIDCConfiguration.from_response(response.content, self.parent_srv.namespace)
diff --git a/tableauserverclient/server/request_factory.py b/tableauserverclient/server/request_factory.py
index c898004f..f9c9b471 100644
--- a/tableauserverclient/server/request_factory.py
+++ b/tableauserverclient/server/request_factory.py
@@ -1446,6 +1446,122 @@ def publish(self, xml_request: ET.Element, virtual_connection: VirtualConnection
return ET.tostring(xml_request)
+class OIDCRequest:
+ @_tsrequest_wrapped
+ def create_req(self, xml_request: ET.Element, oidc_item: SiteOIDCConfiguration) -> bytes:
+ oidc_element = ET.SubElement(xml_request, "siteOIDCConfiguration")
+
+ # Check required attributes first
+
+ if oidc_item.idp_configuration_name is None:
+ raise ValueError(f"OIDC Item missing idp_configuration_name: {oidc_item}")
+ if oidc_item.client_id is None:
+ raise ValueError(f"OIDC Item missing client_id: {oidc_item}")
+ if oidc_item.client_secret is None:
+ raise ValueError(f"OIDC Item missing client_secret: {oidc_item}")
+ if oidc_item.authorization_endpoint is None:
+ raise ValueError(f"OIDC Item missing authorization_endpoint: {oidc_item}")
+ if oidc_item.token_endpoint is None:
+ raise ValueError(f"OIDC Item missing token_endpoint: {oidc_item}")
+ if oidc_item.userinfo_endpoint is None:
+ raise ValueError(f"OIDC Item missing userinfo_endpoint: {oidc_item}")
+ if not isinstance(oidc_item.enabled, bool):
+ raise ValueError(f"OIDC Item missing enabled: {oidc_item}")
+ if oidc_item.jwks_uri is None:
+ raise ValueError(f"OIDC Item missing jwks_uri: {oidc_item}")
+
+ oidc_element.attrib["name"] = oidc_item.idp_configuration_name
+ oidc_element.attrib["clientId"] = oidc_item.client_id
+ oidc_element.attrib["clientSecret"] = oidc_item.client_secret
+ oidc_element.attrib["authorizationEndpoint"] = oidc_item.authorization_endpoint
+ oidc_element.attrib["tokenEndpoint"] = oidc_item.token_endpoint
+ oidc_element.attrib["userInfoEndpoint"] = oidc_item.userinfo_endpoint
+ oidc_element.attrib["enabled"] = str(oidc_item.enabled).lower()
+ oidc_element.attrib["jwksUri"] = oidc_item.jwks_uri
+
+ if oidc_item.allow_embedded_authentication is not None:
+ oidc_element.attrib["allowEmbeddedAuthentication"] = str(oidc_item.allow_embedded_authentication).lower()
+ if oidc_item.custom_scope is not None:
+ oidc_element.attrib["customScope"] = oidc_item.custom_scope
+ if oidc_item.prompt is not None:
+ oidc_element.attrib["prompt"] = oidc_item.prompt
+ if oidc_item.client_authentication is not None:
+ oidc_element.attrib["clientAuthentication"] = oidc_item.client_authentication
+ if oidc_item.essential_acr_values is not None:
+ oidc_element.attrib["essentialAcrValues"] = oidc_item.essential_acr_values
+ if oidc_item.voluntary_acr_values is not None:
+ oidc_element.attrib["voluntaryAcrValues"] = oidc_item.voluntary_acr_values
+ if oidc_item.email_mapping is not None:
+ oidc_element.attrib["emailMapping"] = oidc_item.email_mapping
+ if oidc_item.first_name_mapping is not None:
+ oidc_element.attrib["firstNameMapping"] = oidc_item.first_name_mapping
+ if oidc_item.last_name_mapping is not None:
+ oidc_element.attrib["lastNameMapping"] = oidc_item.last_name_mapping
+ if oidc_item.full_name_mapping is not None:
+ oidc_element.attrib["fullNameMapping"] = oidc_item.full_name_mapping
+ if oidc_item.use_full_name is not None:
+ oidc_element.attrib["useFullName"] = str(oidc_item.use_full_name).lower()
+
+ return ET.tostring(xml_request)
+
+ @_tsrequest_wrapped
+ def update_req(self, xml_request: ET.Element, oidc_item: SiteOIDCConfiguration) -> bytes:
+ oidc_element = ET.SubElement(xml_request, "siteOIDCConfiguration")
+
+ # Check required attributes first
+
+ if oidc_item.idp_configuration_name is None:
+ raise ValueError(f"OIDC Item missing idp_configuration_name: {oidc_item}")
+ if oidc_item.client_id is None:
+ raise ValueError(f"OIDC Item missing client_id: {oidc_item}")
+ if oidc_item.client_secret is None:
+ raise ValueError(f"OIDC Item missing client_secret: {oidc_item}")
+ if oidc_item.authorization_endpoint is None:
+ raise ValueError(f"OIDC Item missing authorization_endpoint: {oidc_item}")
+ if oidc_item.token_endpoint is None:
+ raise ValueError(f"OIDC Item missing token_endpoint: {oidc_item}")
+ if oidc_item.userinfo_endpoint is None:
+ raise ValueError(f"OIDC Item missing userinfo_endpoint: {oidc_item}")
+ if not isinstance(oidc_item.enabled, bool):
+ raise ValueError(f"OIDC Item missing enabled: {oidc_item}")
+ if oidc_item.jwks_uri is None:
+ raise ValueError(f"OIDC Item missing jwks_uri: {oidc_item}")
+
+ oidc_element.attrib["name"] = oidc_item.idp_configuration_name
+ oidc_element.attrib["clientId"] = oidc_item.client_id
+ oidc_element.attrib["clientSecret"] = oidc_item.client_secret
+ oidc_element.attrib["authorizationEndpoint"] = oidc_item.authorization_endpoint
+ oidc_element.attrib["tokenEndpoint"] = oidc_item.token_endpoint
+ oidc_element.attrib["userInfoEndpoint"] = oidc_item.userinfo_endpoint
+ oidc_element.attrib["enabled"] = str(oidc_item.enabled).lower()
+ oidc_element.attrib["jwksUri"] = oidc_item.jwks_uri
+
+ if oidc_item.allow_embedded_authentication is not None:
+ oidc_element.attrib["allowEmbeddedAuthentication"] = str(oidc_item.allow_embedded_authentication).lower()
+ if oidc_item.custom_scope is not None:
+ oidc_element.attrib["customScope"] = oidc_item.custom_scope
+ if oidc_item.prompt is not None:
+ oidc_element.attrib["prompt"] = oidc_item.prompt
+ if oidc_item.client_authentication is not None:
+ oidc_element.attrib["clientAuthentication"] = oidc_item.client_authentication
+ if oidc_item.essential_acr_values is not None:
+ oidc_element.attrib["essentialAcrValues"] = oidc_item.essential_acr_values
+ if oidc_item.voluntary_acr_values is not None:
+ oidc_element.attrib["voluntaryAcrValues"] = oidc_item.voluntary_acr_values
+ if oidc_item.email_mapping is not None:
+ oidc_element.attrib["emailMapping"] = oidc_item.email_mapping
+ if oidc_item.first_name_mapping is not None:
+ oidc_element.attrib["firstNameMapping"] = oidc_item.first_name_mapping
+ if oidc_item.last_name_mapping is not None:
+ oidc_element.attrib["lastNameMapping"] = oidc_item.last_name_mapping
+ if oidc_item.full_name_mapping is not None:
+ oidc_element.attrib["fullNameMapping"] = oidc_item.full_name_mapping
+ if oidc_item.use_full_name is not None:
+ oidc_element.attrib["useFullName"] = str(oidc_item.use_full_name).lower()
+
+ return ET.tostring(xml_request)
+
+
class RequestFactory:
Auth = AuthRequest()
Connection = Connection()
@@ -1463,6 +1579,7 @@ class RequestFactory:
Group = GroupRequest()
GroupSet = GroupSetRequest()
Metric = MetricRequest()
+ OIDC = OIDCRequest()
Permission = PermissionRequest()
Project = ProjectRequest()
Schedule = ScheduleRequest()
diff --git a/tableauserverclient/server/server.py b/tableauserverclient/server/server.py
index d5d163db..9202e3e6 100644
--- a/tableauserverclient/server/server.py
+++ b/tableauserverclient/server/server.py
@@ -38,6 +38,7 @@
GroupSets,
Tags,
VirtualConnections,
+ OIDC,
)
from tableauserverclient.server.exceptions import (
ServerInfoEndpointNotFoundError,
@@ -183,6 +184,7 @@ def __init__(self, server_address, use_server_version=False, http_options=None,
self.group_sets = GroupSets(self)
self.tags = Tags(self)
self.virtual_connections = VirtualConnections(self)
+ self.oidc = OIDC(self)
self._session = self._session_factory()
self._http_options = dict() # must set this before making a server call
diff --git a/test/assets/oidc_create.xml b/test/assets/oidc_create.xml
new file mode 100644
index 00000000..cbe632f3
--- /dev/null
+++ b/test/assets/oidc_create.xml
@@ -0,0 +1,30 @@
+
+
+
+
diff --git a/test/assets/oidc_get.xml b/test/assets/oidc_get.xml
new file mode 100644
index 00000000..cbe632f3
--- /dev/null
+++ b/test/assets/oidc_get.xml
@@ -0,0 +1,30 @@
+
+
+
+
diff --git a/test/assets/oidc_update.xml b/test/assets/oidc_update.xml
new file mode 100644
index 00000000..cbe632f3
--- /dev/null
+++ b/test/assets/oidc_update.xml
@@ -0,0 +1,30 @@
+
+
+
+
diff --git a/test/test_oidc.py b/test/test_oidc.py
new file mode 100644
index 00000000..4c318735
--- /dev/null
+++ b/test/test_oidc.py
@@ -0,0 +1,153 @@
+import unittest
+import requests_mock
+from pathlib import Path
+
+import tableauserverclient as TSC
+
+assets = Path(__file__).parent / "assets"
+OIDC_GET = assets / "oidc_get.xml"
+OIDC_GET_BY_ID = assets / "oidc_get_by_id.xml"
+OIDC_UPDATE = assets / "oidc_update.xml"
+OIDC_CREATE = assets / "oidc_create.xml"
+
+
+class Testoidc(unittest.TestCase):
+ def setUp(self) -> None:
+ self.server = TSC.Server("http://test", False)
+
+ # Fake signin
+ self.server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67"
+ self.server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM"
+ self.server.version = "3.24"
+
+ self.baseurl = self.server.oidc.baseurl
+
+ def test_oidc_get_by_id(self) -> None:
+ luid = "6561daa3-20e8-407f-ba09-709b178c0b4a"
+ with requests_mock.mock() as m:
+ m.get(f"{self.baseurl}/{luid}", text=OIDC_GET.read_text())
+ oidc = self.server.oidc.get_by_id(luid)
+
+ assert oidc.enabled is True
+ assert (
+ oidc.test_login_url
+ == "https://sso.online.tableau.com/public/testLogin?alias=8a04d825-e5d4-408f-bbc2-1042b8bb4818&authSetting=OIDC&idpConfigurationId=78c985b4-5494-4436-bcee-f595e287ba4a"
+ )
+ assert oidc.known_provider_alias == "Google"
+ assert oidc.allow_embedded_authentication is False
+ assert oidc.use_full_name is False
+ assert oidc.idp_configuration_name == "GoogleOIDC"
+ assert oidc.idp_configuration_id == "78c985b4-5494-4436-bcee-f595e287ba4a"
+ assert oidc.client_id == "ICcGeDt3XHwzZ1D0nCZt"
+ assert oidc.client_secret == "omit"
+ assert oidc.authorization_endpoint == "https://myidp.com/oauth2/v1/authorize"
+ assert oidc.token_endpoint == "https://myidp.com/oauth2/v1/token"
+ assert oidc.userinfo_endpoint == "https://myidp.com/oauth2/v1/userinfo"
+ assert oidc.jwks_uri == "https://myidp.com/oauth2/v1/keys"
+ assert oidc.end_session_endpoint == "https://myidp.com/oauth2/v1/logout"
+ assert oidc.custom_scope == "openid, email, profile"
+ assert oidc.prompt == "login,consent"
+ assert oidc.client_authentication == "client_secret_basic"
+ assert oidc.essential_acr_values == "phr"
+ assert oidc.email_mapping == "email"
+ assert oidc.first_name_mapping == "given_name"
+ assert oidc.last_name_mapping == "family_name"
+ assert oidc.full_name_mapping == "name"
+
+ def test_oidc_delete(self) -> None:
+ luid = "6561daa3-20e8-407f-ba09-709b178c0b4a"
+ with requests_mock.mock() as m:
+ m.put(f"{self.server.baseurl}/sites/{self.server.site_id}/disable-site-oidc-configuration")
+ self.server.oidc.delete_configuration(luid)
+ history = m.request_history[0]
+
+ assert "idpconfigurationid" in history.qs
+ assert history.qs["idpconfigurationid"][0] == luid
+
+ def test_oidc_update(self) -> None:
+ luid = "6561daa3-20e8-407f-ba09-709b178c0b4a"
+ oidc = TSC.SiteOIDCConfiguration()
+ oidc.idp_configuration_id = luid
+
+ # Only include the required fields for updates
+ oidc.enabled = True
+ oidc.idp_configuration_name = "GoogleOIDC"
+ oidc.client_id = "ICcGeDt3XHwzZ1D0nCZt"
+ oidc.client_secret = "omit"
+ oidc.authorization_endpoint = "https://myidp.com/oauth2/v1/authorize"
+ oidc.token_endpoint = "https://myidp.com/oauth2/v1/token"
+ oidc.userinfo_endpoint = "https://myidp.com/oauth2/v1/userinfo"
+ oidc.jwks_uri = "https://myidp.com/oauth2/v1/keys"
+
+ with requests_mock.mock() as m:
+ m.put(f"{self.baseurl}/{luid}", text=OIDC_UPDATE.read_text())
+ oidc = self.server.oidc.update(oidc)
+
+ assert oidc.enabled is True
+ assert (
+ oidc.test_login_url
+ == "https://sso.online.tableau.com/public/testLogin?alias=8a04d825-e5d4-408f-bbc2-1042b8bb4818&authSetting=OIDC&idpConfigurationId=78c985b4-5494-4436-bcee-f595e287ba4a"
+ )
+ assert oidc.known_provider_alias == "Google"
+ assert oidc.allow_embedded_authentication is False
+ assert oidc.use_full_name is False
+ assert oidc.idp_configuration_name == "GoogleOIDC"
+ assert oidc.idp_configuration_id == "78c985b4-5494-4436-bcee-f595e287ba4a"
+ assert oidc.client_id == "ICcGeDt3XHwzZ1D0nCZt"
+ assert oidc.client_secret == "omit"
+ assert oidc.authorization_endpoint == "https://myidp.com/oauth2/v1/authorize"
+ assert oidc.token_endpoint == "https://myidp.com/oauth2/v1/token"
+ assert oidc.userinfo_endpoint == "https://myidp.com/oauth2/v1/userinfo"
+ assert oidc.jwks_uri == "https://myidp.com/oauth2/v1/keys"
+ assert oidc.end_session_endpoint == "https://myidp.com/oauth2/v1/logout"
+ assert oidc.custom_scope == "openid, email, profile"
+ assert oidc.prompt == "login,consent"
+ assert oidc.client_authentication == "client_secret_basic"
+ assert oidc.essential_acr_values == "phr"
+ assert oidc.email_mapping == "email"
+ assert oidc.first_name_mapping == "given_name"
+ assert oidc.last_name_mapping == "family_name"
+ assert oidc.full_name_mapping == "name"
+
+ def test_oidc_create(self) -> None:
+ oidc = TSC.SiteOIDCConfiguration()
+
+ # Only include the required fields for creation
+ oidc.enabled = True
+ oidc.idp_configuration_name = "GoogleOIDC"
+ oidc.client_id = "ICcGeDt3XHwzZ1D0nCZt"
+ oidc.client_secret = "omit"
+ oidc.authorization_endpoint = "https://myidp.com/oauth2/v1/authorize"
+ oidc.token_endpoint = "https://myidp.com/oauth2/v1/token"
+ oidc.userinfo_endpoint = "https://myidp.com/oauth2/v1/userinfo"
+ oidc.jwks_uri = "https://myidp.com/oauth2/v1/keys"
+
+ with requests_mock.mock() as m:
+ m.put(self.baseurl, text=OIDC_CREATE.read_text())
+ oidc = self.server.oidc.create(oidc)
+
+ assert oidc.enabled is True
+ assert (
+ oidc.test_login_url
+ == "https://sso.online.tableau.com/public/testLogin?alias=8a04d825-e5d4-408f-bbc2-1042b8bb4818&authSetting=OIDC&idpConfigurationId=78c985b4-5494-4436-bcee-f595e287ba4a"
+ )
+ assert oidc.known_provider_alias == "Google"
+ assert oidc.allow_embedded_authentication is False
+ assert oidc.use_full_name is False
+ assert oidc.idp_configuration_name == "GoogleOIDC"
+ assert oidc.idp_configuration_id == "78c985b4-5494-4436-bcee-f595e287ba4a"
+ assert oidc.client_id == "ICcGeDt3XHwzZ1D0nCZt"
+ assert oidc.client_secret == "omit"
+ assert oidc.authorization_endpoint == "https://myidp.com/oauth2/v1/authorize"
+ assert oidc.token_endpoint == "https://myidp.com/oauth2/v1/token"
+ assert oidc.userinfo_endpoint == "https://myidp.com/oauth2/v1/userinfo"
+ assert oidc.jwks_uri == "https://myidp.com/oauth2/v1/keys"
+ assert oidc.end_session_endpoint == "https://myidp.com/oauth2/v1/logout"
+ assert oidc.custom_scope == "openid, email, profile"
+ assert oidc.prompt == "login,consent"
+ assert oidc.client_authentication == "client_secret_basic"
+ assert oidc.essential_acr_values == "phr"
+ assert oidc.email_mapping == "email"
+ assert oidc.first_name_mapping == "given_name"
+ assert oidc.last_name_mapping == "family_name"
+ assert oidc.full_name_mapping == "name"