Skip to content

Commit c19f581

Browse files
jorwoodsjacalata
authored andcommitted
feat: support OIDC endpoints (#1630)
* feat: support OIDC endpoints Add support for remaining OIDC endpoints, including getting an OIDC configuration by ID, removing the configuration, creating, and updating configurations. * feat: add str and repr to oidc item --------- Co-authored-by: Jordan Woods <13803242+jorwoods@users.noreply.github.com>
1 parent c0af92d commit c19f581

File tree

3 files changed

+141
-207
lines changed

3 files changed

+141
-207
lines changed

tableauserverclient/server/request_factory.py

Lines changed: 0 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1665,64 +1665,6 @@ def update_req(self, xml_request: ET.Element, oidc_item: SiteOIDCConfiguration)
16651665
return ET.tostring(xml_request)
16661666

16671667

1668-
class ExtensionsRequest:
1669-
@_tsrequest_wrapped
1670-
def update_server_extensions(self, xml_request: ET.Element, extensions_server: "ExtensionsServer") -> None:
1671-
extensions_element = ET.SubElement(xml_request, "extensionsServerSettings")
1672-
if not isinstance(extensions_server.enabled, bool):
1673-
raise ValueError(f"Extensions Server missing enabled: {extensions_server}")
1674-
enabled_element = ET.SubElement(extensions_element, "extensionsGloballyEnabled")
1675-
enabled_element.text = str(extensions_server.enabled).lower()
1676-
1677-
if extensions_server.block_list is None:
1678-
return
1679-
for blocked in extensions_server.block_list:
1680-
blocked_element = ET.SubElement(extensions_element, "blockList")
1681-
blocked_element.text = blocked
1682-
return
1683-
1684-
@_tsrequest_wrapped
1685-
def update_site_extensions(self, xml_request: ET.Element, extensions_site_settings: ExtensionsSiteSettings) -> None:
1686-
ext_element = ET.SubElement(xml_request, "extensionsSiteSettings")
1687-
if not isinstance(extensions_site_settings.enabled, bool):
1688-
raise ValueError(f"Extensions Site Settings missing enabled: {extensions_site_settings}")
1689-
enabled_element = ET.SubElement(ext_element, "extensionsEnabled")
1690-
enabled_element.text = str(extensions_site_settings.enabled).lower()
1691-
if not isinstance(extensions_site_settings.use_default_setting, bool):
1692-
raise ValueError(
1693-
f"Extensions Site Settings missing use_default_setting: {extensions_site_settings.use_default_setting}"
1694-
)
1695-
default_element = ET.SubElement(ext_element, "useDefaultSetting")
1696-
default_element.text = str(extensions_site_settings.use_default_setting).lower()
1697-
if extensions_site_settings.allow_trusted is not None:
1698-
allow_trusted_element = ET.SubElement(ext_element, "allowTrusted")
1699-
allow_trusted_element.text = str(extensions_site_settings.allow_trusted).lower()
1700-
if extensions_site_settings.include_sandboxed is not None:
1701-
include_sandboxed_element = ET.SubElement(ext_element, "includeSandboxed")
1702-
include_sandboxed_element.text = str(extensions_site_settings.include_sandboxed).lower()
1703-
if extensions_site_settings.include_tableau_built is not None:
1704-
include_tableau_built_element = ET.SubElement(ext_element, "includeTableauBuilt")
1705-
include_tableau_built_element.text = str(extensions_site_settings.include_tableau_built).lower()
1706-
if extensions_site_settings.include_partner_built is not None:
1707-
include_partner_built_element = ET.SubElement(ext_element, "includePartnerBuilt")
1708-
include_partner_built_element.text = str(extensions_site_settings.include_partner_built).lower()
1709-
1710-
if extensions_site_settings.safe_list is None:
1711-
return
1712-
1713-
safe_element = ET.SubElement(ext_element, "safeList")
1714-
for safe in extensions_site_settings.safe_list:
1715-
if safe.url is not None:
1716-
url_element = ET.SubElement(safe_element, "url")
1717-
url_element.text = safe.url
1718-
if safe.full_data_allowed is not None:
1719-
full_data_element = ET.SubElement(safe_element, "fullDataAllowed")
1720-
full_data_element.text = str(safe.full_data_allowed).lower()
1721-
if safe.prompt_needed is not None:
1722-
prompt_element = ET.SubElement(safe_element, "promptNeeded")
1723-
prompt_element.text = str(safe.prompt_needed).lower()
1724-
1725-
17261668
class RequestFactory:
17271669
Auth = AuthRequest()
17281670
Connection = Connection()

tableauserverclient/server/server.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@
3939
Tags,
4040
VirtualConnections,
4141
OIDC,
42-
Extensions,
4342
)
4443
from tableauserverclient.server.exceptions import (
4544
ServerInfoEndpointNotFoundError,
@@ -186,7 +185,6 @@ def __init__(self, server_address, use_server_version=False, http_options=None,
186185
self.tags = Tags(self)
187186
self.virtual_connections = VirtualConnections(self)
188187
self.oidc = OIDC(self)
189-
self.extensions = Extensions(self)
190188

191189
self._session = self._session_factory()
192190
self._http_options = dict() # must set this before making a server call

test/test_oidc.py

Lines changed: 141 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1+
import unittest
12
import requests_mock
23
from pathlib import Path
34

4-
import pytest
5-
65
import tableauserverclient as TSC
76

87
assets = Path(__file__).parent / "assets"
@@ -12,148 +11,143 @@
1211
OIDC_CREATE = assets / "oidc_create.xml"
1312

1413

15-
@pytest.fixture(scope="function")
16-
def server():
17-
"""Fixture to create a TSC.Server instance for testing."""
18-
server = TSC.Server("http://test", False)
19-
20-
# Fake signin
21-
server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67"
22-
server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM"
23-
server.version = "3.24"
24-
25-
return server
26-
27-
28-
def test_oidc_get_by_id(server: TSC.Server) -> None:
29-
luid = "6561daa3-20e8-407f-ba09-709b178c0b4a"
30-
with requests_mock.mock() as m:
31-
m.get(f"{server.oidc.baseurl}/{luid}", text=OIDC_GET.read_text())
32-
oidc = server.oidc.get_by_id(luid)
33-
34-
assert oidc.enabled is True
35-
assert (
36-
oidc.test_login_url
37-
== "https://sso.online.tableau.com/public/testLogin?alias=8a04d825-e5d4-408f-bbc2-1042b8bb4818&authSetting=OIDC&idpConfigurationId=78c985b4-5494-4436-bcee-f595e287ba4a"
38-
)
39-
assert oidc.known_provider_alias == "Google"
40-
assert oidc.allow_embedded_authentication is False
41-
assert oidc.use_full_name is False
42-
assert oidc.idp_configuration_name == "GoogleOIDC"
43-
assert oidc.idp_configuration_id == "78c985b4-5494-4436-bcee-f595e287ba4a"
44-
assert oidc.client_id == "ICcGeDt3XHwzZ1D0nCZt"
45-
assert oidc.client_secret == "omit"
46-
assert oidc.authorization_endpoint == "https://myidp.com/oauth2/v1/authorize"
47-
assert oidc.token_endpoint == "https://myidp.com/oauth2/v1/token"
48-
assert oidc.userinfo_endpoint == "https://myidp.com/oauth2/v1/userinfo"
49-
assert oidc.jwks_uri == "https://myidp.com/oauth2/v1/keys"
50-
assert oidc.end_session_endpoint == "https://myidp.com/oauth2/v1/logout"
51-
assert oidc.custom_scope == "openid, email, profile"
52-
assert oidc.prompt == "login,consent"
53-
assert oidc.client_authentication == "client_secret_basic"
54-
assert oidc.essential_acr_values == "phr"
55-
assert oidc.email_mapping == "email"
56-
assert oidc.first_name_mapping == "given_name"
57-
assert oidc.last_name_mapping == "family_name"
58-
assert oidc.full_name_mapping == "name"
59-
60-
61-
def test_oidc_delete(server: TSC.Server) -> None:
62-
luid = "6561daa3-20e8-407f-ba09-709b178c0b4a"
63-
with requests_mock.mock() as m:
64-
m.put(f"{server.baseurl}/sites/{server.site_id}/disable-site-oidc-configuration")
65-
server.oidc.delete_configuration(luid)
66-
history = m.request_history[0]
67-
68-
assert "idpconfigurationid" in history.qs
69-
assert history.qs["idpconfigurationid"][0] == luid
70-
71-
72-
def test_oidc_update(server: TSC.Server) -> None:
73-
luid = "6561daa3-20e8-407f-ba09-709b178c0b4a"
74-
oidc = TSC.SiteOIDCConfiguration()
75-
oidc.idp_configuration_id = luid
76-
77-
# Only include the required fields for updates
78-
oidc.enabled = True
79-
oidc.idp_configuration_name = "GoogleOIDC"
80-
oidc.client_id = "ICcGeDt3XHwzZ1D0nCZt"
81-
oidc.client_secret = "omit"
82-
oidc.authorization_endpoint = "https://myidp.com/oauth2/v1/authorize"
83-
oidc.token_endpoint = "https://myidp.com/oauth2/v1/token"
84-
oidc.userinfo_endpoint = "https://myidp.com/oauth2/v1/userinfo"
85-
oidc.jwks_uri = "https://myidp.com/oauth2/v1/keys"
86-
87-
with requests_mock.mock() as m:
88-
m.put(f"{server.oidc.baseurl}/{luid}", text=OIDC_UPDATE.read_text())
89-
oidc = server.oidc.update(oidc)
90-
91-
assert oidc.enabled is True
92-
assert (
93-
oidc.test_login_url
94-
== "https://sso.online.tableau.com/public/testLogin?alias=8a04d825-e5d4-408f-bbc2-1042b8bb4818&authSetting=OIDC&idpConfigurationId=78c985b4-5494-4436-bcee-f595e287ba4a"
95-
)
96-
assert oidc.known_provider_alias == "Google"
97-
assert oidc.allow_embedded_authentication is False
98-
assert oidc.use_full_name is False
99-
assert oidc.idp_configuration_name == "GoogleOIDC"
100-
assert oidc.idp_configuration_id == "78c985b4-5494-4436-bcee-f595e287ba4a"
101-
assert oidc.client_id == "ICcGeDt3XHwzZ1D0nCZt"
102-
assert oidc.client_secret == "omit"
103-
assert oidc.authorization_endpoint == "https://myidp.com/oauth2/v1/authorize"
104-
assert oidc.token_endpoint == "https://myidp.com/oauth2/v1/token"
105-
assert oidc.userinfo_endpoint == "https://myidp.com/oauth2/v1/userinfo"
106-
assert oidc.jwks_uri == "https://myidp.com/oauth2/v1/keys"
107-
assert oidc.end_session_endpoint == "https://myidp.com/oauth2/v1/logout"
108-
assert oidc.custom_scope == "openid, email, profile"
109-
assert oidc.prompt == "login,consent"
110-
assert oidc.client_authentication == "client_secret_basic"
111-
assert oidc.essential_acr_values == "phr"
112-
assert oidc.email_mapping == "email"
113-
assert oidc.first_name_mapping == "given_name"
114-
assert oidc.last_name_mapping == "family_name"
115-
assert oidc.full_name_mapping == "name"
116-
117-
118-
def test_oidc_create(server: TSC.Server) -> None:
119-
oidc = TSC.SiteOIDCConfiguration()
120-
121-
# Only include the required fields for creation
122-
oidc.enabled = True
123-
oidc.idp_configuration_name = "GoogleOIDC"
124-
oidc.client_id = "ICcGeDt3XHwzZ1D0nCZt"
125-
oidc.client_secret = "omit"
126-
oidc.authorization_endpoint = "https://myidp.com/oauth2/v1/authorize"
127-
oidc.token_endpoint = "https://myidp.com/oauth2/v1/token"
128-
oidc.userinfo_endpoint = "https://myidp.com/oauth2/v1/userinfo"
129-
oidc.jwks_uri = "https://myidp.com/oauth2/v1/keys"
130-
131-
with requests_mock.mock() as m:
132-
m.put(server.oidc.baseurl, text=OIDC_CREATE.read_text())
133-
oidc = server.oidc.create(oidc)
134-
135-
assert oidc.enabled is True
136-
assert (
137-
oidc.test_login_url
138-
== "https://sso.online.tableau.com/public/testLogin?alias=8a04d825-e5d4-408f-bbc2-1042b8bb4818&authSetting=OIDC&idpConfigurationId=78c985b4-5494-4436-bcee-f595e287ba4a"
139-
)
140-
assert oidc.known_provider_alias == "Google"
141-
assert oidc.allow_embedded_authentication is False
142-
assert oidc.use_full_name is False
143-
assert oidc.idp_configuration_name == "GoogleOIDC"
144-
assert oidc.idp_configuration_id == "78c985b4-5494-4436-bcee-f595e287ba4a"
145-
assert oidc.client_id == "ICcGeDt3XHwzZ1D0nCZt"
146-
assert oidc.client_secret == "omit"
147-
assert oidc.authorization_endpoint == "https://myidp.com/oauth2/v1/authorize"
148-
assert oidc.token_endpoint == "https://myidp.com/oauth2/v1/token"
149-
assert oidc.userinfo_endpoint == "https://myidp.com/oauth2/v1/userinfo"
150-
assert oidc.jwks_uri == "https://myidp.com/oauth2/v1/keys"
151-
assert oidc.end_session_endpoint == "https://myidp.com/oauth2/v1/logout"
152-
assert oidc.custom_scope == "openid, email, profile"
153-
assert oidc.prompt == "login,consent"
154-
assert oidc.client_authentication == "client_secret_basic"
155-
assert oidc.essential_acr_values == "phr"
156-
assert oidc.email_mapping == "email"
157-
assert oidc.first_name_mapping == "given_name"
158-
assert oidc.last_name_mapping == "family_name"
159-
assert oidc.full_name_mapping == "name"
14+
class Testoidc(unittest.TestCase):
15+
def setUp(self) -> None:
16+
self.server = TSC.Server("http://test", False)
17+
18+
# Fake signin
19+
self.server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67"
20+
self.server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM"
21+
self.server.version = "3.24"
22+
23+
self.baseurl = self.server.oidc.baseurl
24+
25+
def test_oidc_get_by_id(self) -> None:
26+
luid = "6561daa3-20e8-407f-ba09-709b178c0b4a"
27+
with requests_mock.mock() as m:
28+
m.get(f"{self.baseurl}/{luid}", text=OIDC_GET.read_text())
29+
oidc = self.server.oidc.get_by_id(luid)
30+
31+
assert oidc.enabled is True
32+
assert (
33+
oidc.test_login_url
34+
== "https://sso.online.tableau.com/public/testLogin?alias=8a04d825-e5d4-408f-bbc2-1042b8bb4818&authSetting=OIDC&idpConfigurationId=78c985b4-5494-4436-bcee-f595e287ba4a"
35+
)
36+
assert oidc.known_provider_alias == "Google"
37+
assert oidc.allow_embedded_authentication is False
38+
assert oidc.use_full_name is False
39+
assert oidc.idp_configuration_name == "GoogleOIDC"
40+
assert oidc.idp_configuration_id == "78c985b4-5494-4436-bcee-f595e287ba4a"
41+
assert oidc.client_id == "ICcGeDt3XHwzZ1D0nCZt"
42+
assert oidc.client_secret == "omit"
43+
assert oidc.authorization_endpoint == "https://myidp.com/oauth2/v1/authorize"
44+
assert oidc.token_endpoint == "https://myidp.com/oauth2/v1/token"
45+
assert oidc.userinfo_endpoint == "https://myidp.com/oauth2/v1/userinfo"
46+
assert oidc.jwks_uri == "https://myidp.com/oauth2/v1/keys"
47+
assert oidc.end_session_endpoint == "https://myidp.com/oauth2/v1/logout"
48+
assert oidc.custom_scope == "openid, email, profile"
49+
assert oidc.prompt == "login,consent"
50+
assert oidc.client_authentication == "client_secret_basic"
51+
assert oidc.essential_acr_values == "phr"
52+
assert oidc.email_mapping == "email"
53+
assert oidc.first_name_mapping == "given_name"
54+
assert oidc.last_name_mapping == "family_name"
55+
assert oidc.full_name_mapping == "name"
56+
57+
def test_oidc_delete(self) -> None:
58+
luid = "6561daa3-20e8-407f-ba09-709b178c0b4a"
59+
with requests_mock.mock() as m:
60+
m.put(f"{self.server.baseurl}/sites/{self.server.site_id}/disable-site-oidc-configuration")
61+
self.server.oidc.delete_configuration(luid)
62+
history = m.request_history[0]
63+
64+
assert "idpconfigurationid" in history.qs
65+
assert history.qs["idpconfigurationid"][0] == luid
66+
67+
def test_oidc_update(self) -> None:
68+
luid = "6561daa3-20e8-407f-ba09-709b178c0b4a"
69+
oidc = TSC.SiteOIDCConfiguration()
70+
oidc.idp_configuration_id = luid
71+
72+
# Only include the required fields for updates
73+
oidc.enabled = True
74+
oidc.idp_configuration_name = "GoogleOIDC"
75+
oidc.client_id = "ICcGeDt3XHwzZ1D0nCZt"
76+
oidc.client_secret = "omit"
77+
oidc.authorization_endpoint = "https://myidp.com/oauth2/v1/authorize"
78+
oidc.token_endpoint = "https://myidp.com/oauth2/v1/token"
79+
oidc.userinfo_endpoint = "https://myidp.com/oauth2/v1/userinfo"
80+
oidc.jwks_uri = "https://myidp.com/oauth2/v1/keys"
81+
82+
with requests_mock.mock() as m:
83+
m.put(f"{self.baseurl}/{luid}", text=OIDC_UPDATE.read_text())
84+
oidc = self.server.oidc.update(oidc)
85+
86+
assert oidc.enabled is True
87+
assert (
88+
oidc.test_login_url
89+
== "https://sso.online.tableau.com/public/testLogin?alias=8a04d825-e5d4-408f-bbc2-1042b8bb4818&authSetting=OIDC&idpConfigurationId=78c985b4-5494-4436-bcee-f595e287ba4a"
90+
)
91+
assert oidc.known_provider_alias == "Google"
92+
assert oidc.allow_embedded_authentication is False
93+
assert oidc.use_full_name is False
94+
assert oidc.idp_configuration_name == "GoogleOIDC"
95+
assert oidc.idp_configuration_id == "78c985b4-5494-4436-bcee-f595e287ba4a"
96+
assert oidc.client_id == "ICcGeDt3XHwzZ1D0nCZt"
97+
assert oidc.client_secret == "omit"
98+
assert oidc.authorization_endpoint == "https://myidp.com/oauth2/v1/authorize"
99+
assert oidc.token_endpoint == "https://myidp.com/oauth2/v1/token"
100+
assert oidc.userinfo_endpoint == "https://myidp.com/oauth2/v1/userinfo"
101+
assert oidc.jwks_uri == "https://myidp.com/oauth2/v1/keys"
102+
assert oidc.end_session_endpoint == "https://myidp.com/oauth2/v1/logout"
103+
assert oidc.custom_scope == "openid, email, profile"
104+
assert oidc.prompt == "login,consent"
105+
assert oidc.client_authentication == "client_secret_basic"
106+
assert oidc.essential_acr_values == "phr"
107+
assert oidc.email_mapping == "email"
108+
assert oidc.first_name_mapping == "given_name"
109+
assert oidc.last_name_mapping == "family_name"
110+
assert oidc.full_name_mapping == "name"
111+
112+
def test_oidc_create(self) -> None:
113+
oidc = TSC.SiteOIDCConfiguration()
114+
115+
# Only include the required fields for creation
116+
oidc.enabled = True
117+
oidc.idp_configuration_name = "GoogleOIDC"
118+
oidc.client_id = "ICcGeDt3XHwzZ1D0nCZt"
119+
oidc.client_secret = "omit"
120+
oidc.authorization_endpoint = "https://myidp.com/oauth2/v1/authorize"
121+
oidc.token_endpoint = "https://myidp.com/oauth2/v1/token"
122+
oidc.userinfo_endpoint = "https://myidp.com/oauth2/v1/userinfo"
123+
oidc.jwks_uri = "https://myidp.com/oauth2/v1/keys"
124+
125+
with requests_mock.mock() as m:
126+
m.put(self.baseurl, text=OIDC_CREATE.read_text())
127+
oidc = self.server.oidc.create(oidc)
128+
129+
assert oidc.enabled is True
130+
assert (
131+
oidc.test_login_url
132+
== "https://sso.online.tableau.com/public/testLogin?alias=8a04d825-e5d4-408f-bbc2-1042b8bb4818&authSetting=OIDC&idpConfigurationId=78c985b4-5494-4436-bcee-f595e287ba4a"
133+
)
134+
assert oidc.known_provider_alias == "Google"
135+
assert oidc.allow_embedded_authentication is False
136+
assert oidc.use_full_name is False
137+
assert oidc.idp_configuration_name == "GoogleOIDC"
138+
assert oidc.idp_configuration_id == "78c985b4-5494-4436-bcee-f595e287ba4a"
139+
assert oidc.client_id == "ICcGeDt3XHwzZ1D0nCZt"
140+
assert oidc.client_secret == "omit"
141+
assert oidc.authorization_endpoint == "https://myidp.com/oauth2/v1/authorize"
142+
assert oidc.token_endpoint == "https://myidp.com/oauth2/v1/token"
143+
assert oidc.userinfo_endpoint == "https://myidp.com/oauth2/v1/userinfo"
144+
assert oidc.jwks_uri == "https://myidp.com/oauth2/v1/keys"
145+
assert oidc.end_session_endpoint == "https://myidp.com/oauth2/v1/logout"
146+
assert oidc.custom_scope == "openid, email, profile"
147+
assert oidc.prompt == "login,consent"
148+
assert oidc.client_authentication == "client_secret_basic"
149+
assert oidc.essential_acr_values == "phr"
150+
assert oidc.email_mapping == "email"
151+
assert oidc.first_name_mapping == "given_name"
152+
assert oidc.last_name_mapping == "family_name"
153+
assert oidc.full_name_mapping == "name"

0 commit comments

Comments
 (0)