Skip to content

Commit dcaf0f1

Browse files
author
Vineeth Chavatapalli
committed
Added new auth changes to TSC
1 parent 2886018 commit dcaf0f1

File tree

5 files changed

+282
-1
lines changed

5 files changed

+282
-1
lines changed

samples/update_connection_auth.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import argparse
2+
import logging
3+
import tableauserverclient as TSC
4+
5+
6+
def main():
7+
parser = argparse.ArgumentParser(description="Update a single connection on a datasource or workbook to embed credentials")
8+
9+
# Common options
10+
parser.add_argument("--server", "-s", help="Server address", required=True)
11+
parser.add_argument("--site", "-S", help="Site name", required=True)
12+
parser.add_argument("--token-name", "-p", help="Personal access token name", required=True)
13+
parser.add_argument("--token-value", "-v", help="Personal access token value", required=True)
14+
parser.add_argument(
15+
"--logging-level", "-l",
16+
choices=["debug", "info", "error"],
17+
default="error",
18+
help="Logging level (default: error)",
19+
)
20+
21+
# Resource and connection details
22+
parser.add_argument("resource_type", choices=["workbook", "datasource"])
23+
parser.add_argument("resource_id", help="Workbook or datasource ID")
24+
parser.add_argument("connection_id", help="Connection ID to update")
25+
parser.add_argument("datasource_username", help="Username to set for the connection")
26+
parser.add_argument("datasource_password", help="Password to set for the connection")
27+
parser.add_argument("authentication_type", help="Authentication type")
28+
29+
args = parser.parse_args()
30+
31+
# Logging setup
32+
logging_level = getattr(logging, args.logging_level.upper())
33+
logging.basicConfig(level=logging_level)
34+
35+
tableau_auth = TSC.PersonalAccessTokenAuth(args.token_name, args.token_value, site_id=args.site)
36+
server = TSC.Server(args.server, use_server_version=True)
37+
38+
with server.auth.sign_in(tableau_auth):
39+
endpoint = {
40+
"workbook": server.workbooks,
41+
"datasource": server.datasources
42+
}.get(args.resource_type)
43+
44+
update_function = endpoint.update_connection
45+
resource = endpoint.get_by_id(args.resource_id)
46+
endpoint.populate_connections(resource)
47+
48+
connections = [conn for conn in resource.connections if conn.id == args.connection_id]
49+
assert len(connections) == 1, f"Connection ID '{args.connection_id}' not found."
50+
51+
connection = connections[0]
52+
connection.username = args.datasource_username
53+
connection.password = args.datasource_password
54+
connection.authentication_type = args.authentication_type
55+
connection.embed_password = True
56+
57+
updated_connection = update_function(resource, connection)
58+
print(f"Updated connection: {updated_connection.__dict__}")
59+
60+
61+
if __name__ == "__main__":
62+
main()

samples/update_connections_auth.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import argparse
2+
import logging
3+
import tableauserverclient as TSC
4+
5+
6+
def main():
7+
parser = argparse.ArgumentParser(description="Bulk update all workbook or datasource connections")
8+
9+
# Common options
10+
parser.add_argument("--server", "-s", help="Server address", required=True)
11+
parser.add_argument("--site", "-S", help="Site name", required=True)
12+
parser.add_argument("--username", "-p", help="Personal access token name", required=True)
13+
parser.add_argument("--password", "-v", help="Personal access token value", required=True)
14+
parser.add_argument(
15+
"--logging-level",
16+
"-l",
17+
choices=["debug", "info", "error"],
18+
default="error",
19+
help="Logging level (default: error)",
20+
)
21+
22+
# Resource-specific
23+
parser.add_argument("resource_type", choices=["workbook", "datasource"])
24+
parser.add_argument("resource_id")
25+
parser.add_argument("datasource_username")
26+
parser.add_argument("authentication_type")
27+
parser.add_argument("--datasource_password", default=None, help="Datasource password (optional)")
28+
parser.add_argument("--embed_password", default="true", choices=["true", "false"], help="Embed password (default: true)")
29+
30+
args = parser.parse_args()
31+
32+
# Set logging level
33+
logging_level = getattr(logging, args.logging_level.upper())
34+
logging.basicConfig(level=logging_level)
35+
36+
tableau_auth = TSC.TableauAuth(args.username, args.password, site_id=args.site)
37+
server = TSC.Server(args.server, use_server_version=True)
38+
39+
with server.auth.sign_in(tableau_auth):
40+
endpoint = {
41+
"workbook": server.workbooks,
42+
"datasource": server.datasources
43+
}.get(args.resource_type)
44+
45+
resource = endpoint.get_by_id(args.resource_id)
46+
endpoint.populate_connections(resource)
47+
48+
connection_luids = [conn.id for conn in resource.connections]
49+
embed_password = args.embed_password.lower() == "true"
50+
51+
# Call unified update_connections method
52+
updated_ids = endpoint.update_connections(
53+
resource,
54+
connection_luids=connection_luids,
55+
authentication_type=args.authentication_type,
56+
username=args.datasource_username,
57+
password=args.datasource_password,
58+
embed_password=embed_password
59+
)
60+
61+
print(f"Updated connections on {args.resource_type} {args.resource_id}: {updated_ids}")
62+
63+
64+
if __name__ == "__main__":
65+
main()

tableauserverclient/models/connection_item.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ class ConnectionItem:
4141
server_port: str
4242
The port used for the connection.
4343
44+
auth_type: str
45+
Specifies the type of authentication used by the connection.
46+
4447
connection_credentials: ConnectionCredentials
4548
The Connection Credentials object containing authentication details for
4649
the connection. Replaces username/password/embed_password when
@@ -59,6 +62,7 @@ def __init__(self):
5962
self.username: Optional[str] = None
6063
self.connection_credentials: Optional[ConnectionCredentials] = None
6164
self._query_tagging: Optional[bool] = None
65+
self._auth_type: Optional[str] = None
6266

6367
@property
6468
def datasource_id(self) -> Optional[str]:
@@ -80,6 +84,10 @@ def connection_type(self) -> Optional[str]:
8084
def query_tagging(self) -> Optional[bool]:
8185
return self._query_tagging
8286

87+
@property
88+
def auth_type(self) -> Optional[str]:
89+
return self._auth_type
90+
8391
@query_tagging.setter
8492
@property_is_boolean
8593
def query_tagging(self, value: Optional[bool]):
@@ -92,7 +100,7 @@ def query_tagging(self, value: Optional[bool]):
92100
self._query_tagging = value
93101

94102
def __repr__(self):
95-
return "<ConnectionItem#{_id} embed={embed_password} type={_connection_type} username={username}>".format(
103+
return "<ConnectionItem#{_id} embed={embed_password} type={_connection_type} auth={_auth_type} username={username}>".format(
96104
**self.__dict__
97105
)
98106

@@ -112,6 +120,7 @@ def from_response(cls, resp, ns) -> list["ConnectionItem"]:
112120
connection_item._query_tagging = (
113121
string_to_bool(s) if (s := connection_xml.get("queryTagging", None)) else None
114122
)
123+
connection_item._auth_type = connection_xml.get("authenticationType", None)
115124
datasource_elem = connection_xml.find(".//t:datasource", namespaces=ns)
116125
if datasource_elem is not None:
117126
connection_item._datasource_id = datasource_elem.get("id", None)
@@ -139,6 +148,7 @@ def from_xml_element(cls, parsed_response, ns) -> list["ConnectionItem"]:
139148

140149
connection_item.server_address = connection_xml.get("serverAddress", None)
141150
connection_item.server_port = connection_xml.get("serverPort", None)
151+
connection_item._auth_type = connection_xml.get("authenticationType", None)
142152

143153
connection_credentials = connection_xml.find(".//t:connectionCredentials", namespaces=ns)
144154

tableauserverclient/server/endpoint/datasources_endpoint.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,77 @@ def update_connection(
319319
logger.info(f"Updated datasource item (ID: {datasource_item.id} & connection item {connection_item.id}")
320320
return connection
321321

322+
@api(version="3.26")
323+
def update_connections(
324+
self,
325+
datasource_item: DatasourceItem,
326+
connection_luids: list[str],
327+
authentication_type: str,
328+
username: Optional[str] = None,
329+
password: Optional[str] = None,
330+
embed_password: Optional[bool] = None
331+
) -> list[str]:
332+
"""
333+
Bulk updates one or more datasource connections by LUID.
334+
335+
Parameters
336+
----------
337+
datasource_item : DatasourceItem
338+
The datasource item containing the connections.
339+
340+
connection_luids : list of str
341+
The connection LUIDs to update.
342+
343+
authentication_type : str
344+
The authentication type to use (e.g., 'auth-keypair').
345+
346+
username : str, optional
347+
The username to set.
348+
349+
password : str, optional
350+
The password or secret to set.
351+
352+
embed_password : bool, optional
353+
Whether to embed the password.
354+
355+
Returns
356+
-------
357+
list of str
358+
The connection LUIDs that were updated.
359+
"""
360+
from xml.etree.ElementTree import Element, SubElement, tostring
361+
362+
url = f"{self.baseurl}/{datasource_item.id}/connections"
363+
364+
ts_request = Element("tsRequest")
365+
366+
# <connectionLuids>
367+
conn_luids_elem = SubElement(ts_request, "connectionLuids")
368+
for luid in connection_luids:
369+
SubElement(conn_luids_elem, "connectionLuid").text = luid
370+
371+
# <connection>
372+
connection_elem = SubElement(ts_request, "connection")
373+
connection_elem.set("authenticationType", authentication_type)
374+
375+
if username:
376+
connection_elem.set("userName", username)
377+
378+
if password:
379+
connection_elem.set("password", password)
380+
381+
if embed_password is not None:
382+
connection_elem.set("embedPassword", str(embed_password).lower())
383+
384+
request_body = tostring(ts_request)
385+
386+
response = self.put_request(url, request_body)
387+
388+
logger.info(
389+
f"Updated connections for datasource {datasource_item.id}: {', '.join(connection_luids)}"
390+
)
391+
return connection_luids
392+
322393
@api(version="2.8")
323394
def refresh(self, datasource_item: DatasourceItem, incremental: bool = False) -> JobItem:
324395
"""

tableauserverclient/server/endpoint/workbooks_endpoint.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,79 @@ def update_connection(self, workbook_item: WorkbookItem, connection_item: Connec
325325
logger.info(f"Updated workbook item (ID: {workbook_item.id} & connection item {connection_item.id})")
326326
return connection
327327

328+
# Update workbook_connections
329+
@api(version="3.26")
330+
def update_connections(
331+
self,
332+
workbook_item: WorkbookItem,
333+
connection_luids: list[str],
334+
authentication_type: str,
335+
username: Optional[str] = None,
336+
password: Optional[str] = None,
337+
embed_password: Optional[bool] = None
338+
) -> list[str]:
339+
"""
340+
Bulk updates one or more workbook connections by LUID, including authenticationType, username, password, and embedPassword.
341+
342+
Parameters
343+
----------
344+
workbook_item : WorkbookItem
345+
The workbook item containing the connections.
346+
347+
connection_luids : list of str
348+
The connection LUIDs to update.
349+
350+
authentication_type : str
351+
The authentication type to use (e.g., 'AD Service Principal').
352+
353+
username : str, optional
354+
The username to set (e.g., client ID for keypair auth).
355+
356+
password : str, optional
357+
The password or secret to set.
358+
359+
embed_password : bool, optional
360+
Whether to embed the password.
361+
362+
Returns
363+
-------
364+
list of str
365+
The connection LUIDs that were updated.
366+
"""
367+
from xml.etree.ElementTree import Element, SubElement, tostring
368+
369+
url = f"{self.baseurl}/{workbook_item.id}/connections"
370+
371+
ts_request = Element("tsRequest")
372+
373+
# <connectionLuids>
374+
conn_luids_elem = SubElement(ts_request, "connectionLuids")
375+
for luid in connection_luids:
376+
SubElement(conn_luids_elem, "connectionLuid").text = luid
377+
378+
# <connection>
379+
connection_elem = SubElement(ts_request, "connection")
380+
connection_elem.set("authenticationType", authentication_type)
381+
382+
if username:
383+
connection_elem.set("userName", username)
384+
385+
if password:
386+
connection_elem.set("password", password)
387+
388+
if embed_password is not None:
389+
connection_elem.set("embedPassword", str(embed_password).lower())
390+
391+
request_body = tostring(ts_request)
392+
393+
# Send request
394+
response = self.put_request(url, request_body)
395+
396+
logger.info(
397+
f"Updated connections for workbook {workbook_item.id}: {', '.join(connection_luids)}"
398+
)
399+
return connection_luids
400+
328401
# Download workbook contents with option of passing in filepath
329402
@api(version="2.0")
330403
@parameter_added_in(no_extract="2.5")

0 commit comments

Comments
 (0)