Open yxl1108 opened 3 years ago
I could see in the documentation that TSC.ConnectionCredentials class is used for workbook and data source publish requests. In that case, the parameter set for new_conn_cred wouldn't work, right? Correct me if I am wrong. I am looking for a solution to switch from user name & password to an OAuth connection.
Unfortunately this change (to/from oAuth) isn't currently supported through the API. You might be able to do it using the document api to edit the xml of the downloaded file, if you're willing to try that.
I am experiencing the same problem simply replacing the password of a Snowflake user that already had a password set. Trying to build out a password rotation automater.
Steps:
I update the password of the Snowflake user in the console to a new known value.
Then run pretty similar code to above which is referenced in the sample code: https://github.com/tableau/server-client-python/blob/777db4ef914c00d955ec215d869f8c0f845d58d7/samples/update_connection.py#L40-L54
The TSC
info logs return:
INFO:TSC:Updated datasource item (ID: <datasource GUID redacted> & connection item <connection item GUID redacted>
Check in Tableau Cloud Online editor if triggering a refresh on the datasource uses the correct credentials.
It has not updated the credentials and fails to make a connection.
Perform this enough times and I can lock the Snowflake user for bad password attemps.
When looking at the Chrome Dev Tools > Network tab and manually changed the password on the connection in the console I saw it made calls to undocuments API calls generatePublicKey
and within updateConnections
there is undocumented parameters encryptedPassword
and encryptedPasswordKeyId
.
{
"method":"updateConnections",
"params":{
"ids":["<redacted>"],
"server":"<accountid>.<region>.snowflakecomputing.com",
"username":"<redacted>",
"usingRemoteQueryAgent":false,
"embedPassword":true,
"encryptedPassword":"<redacted>",
"encryptedPasswordKeyId":"<redacted>"
}
}
In theory it is possible. Just the existing examples are not working as documented and the parts that are working are not yet documented.
I managed to get publishing updated credentials working via this method:
server.datasources.download()
server.datasources.publish()
It even overwrote OAuth connection detail credentials with the fixed username/password I was applying to all datasources selected.
Realisation: I think once I realised why I keep getting pushed towards the Tableau Document API I kinda realised this whole thing is built on a very legacy concept from the Tableau Desktop that you have to download the XML as a file and publish new versions (revisions) of the XML.
Editorialising: The API is trying it's best to be modern but still anchored to this concept which is more painful than a SOAP API but I can't fault the dev team since they have been keeping a very solid product running as a best in market solution for a very long time so hat tip 🎩 where it is due here.
# Standard Library
import logging
import os
from pathlib import Path
from typing import TYPE_CHECKING
# Third Party
import tableauserverclient as TSC
from tableauserverclient.models import DatasourceItem
from tableauserverclient.server import Server
from dotenv import load_dotenv
if TYPE_CHECKING:
from tableauserverclient.models.tableau_auth import Credentials
load_dotenv() # take environment variables from .env.
logging.basicConfig(level=logging.INFO)
tableau_auth: Credentials = TSC.PersonalAccessTokenAuth(
os.environ.get("TABLEAU_CLOUD_TOKEN_NAME"),
os.environ.get("TABLEAU_CLOUD_TOKEN_VALUE"),
os.environ.get("TABLEAU_CLOUD_SITENAME"),
)
server: Server = TSC.Server(os.environ.get("TABLEAU_CLOUD_HOST"), use_server_version=True)
# Establish server connection
with server.auth.sign_in(tableau_auth):
# Known issues with the datasources.get() method not really returning everything.
# https://community.tableau.com/s/question/0D54T00000C5OkPSAV/rest-api-recently-published-datasource-missing
all_datasources = server.datasources.all()
# Extract shortlist of Datasources by prefix.
datasources: list[DatasourceItem] = list(
[datasource for datasource in all_datasources if datasource.name.upper().startswith(os.environ.get("DATASOURCE_PREFIX"))]
)
for datasource in datasources:
print(datasource.name)
# Retrieve connection details.
server.datasources.populate_connections(datasource)
# Downloaded Datasource Document (.tdsx file)
downloaded_datasource = server.datasources.download(datasource.id, include_extract=False)
print(downloaded_datasource)
# Publish an updated version and Overwrite with new credentials
updated_datasource = server.datasources.publish(
datasource_item=datasource,
file=Path(downloaded_datasource),
mode=TSC.Server.PublishMode.Overwrite,
connection_credentials=TSC.ConnectionCredentials(
os.environ.get("CREDENTIAL_USERNAME"),
os.environ.get("NEW_PASSWORD"),
embed=True,
oauth=False
),
)
Describe the bug have published datasource (PDS) using embedded oAuth authentication to database. Want to use TSC to switch those to use user name / password
the above code snippet ran fine, but extract refresh failed with "Authentication failed." error.
Downloaded the tds xml file and found <connection authentication='oauth' which should be <connection authentication='Username Password'
Versions Details of your environment, including:
To Reproduce See description
Results Authentication failed.
NOTE: Be careful not to post user names, passwords, auth tokens or any other private or sensitive information.