From b46b584fc9fbf2fd6fd562c21f8a760b490ebb5f Mon Sep 17 00:00:00 2001 From: Alexandre Bourret Date: Mon, 14 Nov 2022 16:05:53 +0100 Subject: [PATCH 1/6] Add secure preset and preset type selector --- .../secure-connection/parameter-set.json | 51 +++++++++++++++++++ .../tableau-hyper_upload/exporter.json | 43 +++++++++++++--- .../tableau-hyper_upload/exporter.py | 40 ++------------- python-lib/tableau_server_utils.py | 40 +++++++++++++-- 4 files changed, 126 insertions(+), 48 deletions(-) create mode 100644 parameter-sets/secure-connection/parameter-set.json diff --git a/parameter-sets/secure-connection/parameter-set.json b/parameter-sets/secure-connection/parameter-set.json new file mode 100644 index 0000000..d0952bf --- /dev/null +++ b/parameter-sets/secure-connection/parameter-set.json @@ -0,0 +1,51 @@ +{ + "meta" : { + "label": "Tableau Server Secure Preset", + "description": "Tableau Server Secure Preset", + "icon": "icon-tableau" + }, + "defaultDefinableInline": false, + "defaultDefinableAtProjectLevel": false, + + "pluginParams": [], + + "params": [ + { + "name": "server_url", + "label":"URL", + "type": "STRING", + "mandatory": true, + "description": "URL of your Tableau server without subpaths. \nFor local Tableau servers, an example would be: https://www.MY_SERVER.com. For Tableau Online, \nan example would be: https://10ax.online.tableau.com/." + }, + { + "name": "site_id", + "label":"Site ID", + "type": "STRING", + "description":"The site_id is the subpath of your full site URL." + }, + { + "name": "ignore_ssl", + "label" : "Ignore SSL", + "type": "BOOLEAN", + "mandatory": false, + "description": "(not recommended)", + "defaultValue": false + }, + { + "name": "ssl_cert_path", + "label": "SSL certificate", + "type": "STRING", + "description": "(optional) Full path to your tableau SSL certificate", + "mandatory": false, + "visibilityCondition": "model.ignore_ssl == false" + }, + { + "name": "tableau_secure_basic", + "type": "CREDENTIAL_REQUEST", + "label": "Tableau basic login", + "credentialRequestSettings": { + "type": "BASIC" + } + } + ] +} diff --git a/python-exporters/tableau-hyper_upload/exporter.json b/python-exporters/tableau-hyper_upload/exporter.json index 7fdc072..031ce84 100644 --- a/python-exporters/tableau-hyper_upload/exporter.json +++ b/python-exporters/tableau-hyper_upload/exporter.json @@ -17,40 +17,67 @@ "label":"Tableau Server", "type": "SEPARATOR" }, + { + "name": "auth_type", + "label": "Authentication type", + "type": "SELECT", + "selectChoices": [ + { + "value": "basic-oauth", + "label": "Secure preset" + }, + { + "value": "legacy-preset", + "label": "Preset (legacy)" + }, + { + "value": "legacy-login", + "label": "User name / password (legacy)" + } + ] + }, { "name": "usePreset", "label" : "Use preset", "type": "BOOLEAN", "mandatory": false, - "description":"Get the Tableau Server connection parameters from a shared configuration in DSS" + "description":"Get the Tableau Server connection parameters from a shared configuration in DSS. Legacy: we just keep it to let existing flow working.", + "visibilityCondition": false + }, + { + "name": "tableau_server_secure_connection", + "label": "Tableau Server Secure Preset", + "type": "PRESET", + "parameterSetId": "secure-connection", + "visibilityCondition": "model.auth_type == 'basic-oauth'" }, { "name": "tableau_server_connection", "label": "Tableau Server Preset", "type": "PRESET", "parameterSetId": "tableau-server-connection", - "visibilityCondition": "model.usePreset == true" + "visibilityCondition": "model.auth_type == 'legacy-preset'" }, { "name": "server_url", "label":"URL", "type": "STRING", "mandatory": false, - "visibilityCondition": "model.usePreset == false || model.tableau_server_connection.mode == 'NONE'" + "visibilityCondition": "model.auth_type == 'legacy-login'" }, { "name": "username", "label":"Username", "type": "STRING", "mandatory": false, - "visibilityCondition": "model.usePreset == false || model.tableau_server_connection.mode == 'NONE'" + "visibilityCondition": "model.auth_type == 'legacy-login'" }, { "name": "password", "label":"Password", "type": "PASSWORD", "mandatory": false, - "visibilityCondition": "model.usePreset == false || model.tableau_server_connection.mode == 'NONE'" + "visibilityCondition": "model.auth_type == 'legacy-login'" }, { "name": "ignore_ssl", @@ -59,7 +86,7 @@ "mandatory": false, "description": "Ignore SSL", "defaultValue": false, - "visibilityCondition": "model.usePreset == false || model.tableau_server_connection.mode == 'NONE'" + "visibilityCondition": "model.auth_type == 'legacy-login'" }, { "name": "ssl_cert_path", @@ -67,7 +94,7 @@ "type": "STRING", "description": "(optional) Full path to your tableau SSL certificate", "mandatory": false, - "visibilityCondition": "(model.usePreset == false || model.tableau_server_connection.mode == 'NONE') && model.ignore_ssl == false" + "visibilityCondition": "(model.auth_type == 'legacy-login') && model.ignore_ssl == false" }, { "label":"Destination", @@ -111,7 +138,7 @@ "type": "STRING", "description": "The site_id is the subpath of your full site URL. (See the README.md for details)", "mandatory": false, - "visibilityCondition": "model.usePreset == false || model.tableau_server_connection.mode == 'NONE'" + "visibilityCondition": "model.auth_type == 'legacy-login'" } ] } \ No newline at end of file diff --git a/python-exporters/tableau-hyper_upload/exporter.py b/python-exporters/tableau-hyper_upload/exporter.py index 383ef86..3ae10f4 100644 --- a/python-exporters/tableau-hyper_upload/exporter.py +++ b/python-exporters/tableau-hyper_upload/exporter.py @@ -8,8 +8,7 @@ from cache_utils import get_cache_location_from_user_config from dataiku.exporter import Exporter from tableau_table_writer import TableauTableWriter -from tableau_server_utils import get_project_from_name -from tableau_server_utils import get_full_list_of_projects +from tableau_server_utils import get_project_from_name, get_full_list_of_projects, get_tableau_server_connection import tempfile from custom_exceptions import InvalidPluginParameter @@ -60,46 +59,13 @@ def __init__(self, config, plugin_config): self.output_file = None self.tmp_output_dir = None - # Extract preset configuration from general configuration - preset_config = config.pop('tableau_server_connection') - - # Sanitize the two configurations - logger.info("Processing user interface input parameters...") - remove_empty_keys(preset_config) - remove_empty_keys(config) - - # Preset configuration will overwrite the manual configuration - config = {**config, **preset_config} - self.config = config # final config - - # Retrieve credentials parameters - username = config.get('username', None) - check_null_values(username, 'username') - password = config.get('password', None) - check_null_values(password, 'password') - server_name = config.get('server_url', None) - check_null_values(server_name, 'server_url') - # The site name is optional in Tableau Server, default value should not be None but empty String - site_name = config.get('site_id', '') - + server_name, username, password, site_name, self.ignore_ssl = get_tableau_server_connection(config) + logger.info("Detected following user input configuration:\n" " username: {},\n" " server_url: {},\n" " site_name: {}".format(username, server_name, site_name)) - # Handle ssl certificates - self.ssl_cert_path = config.get('ssl_cert_path', None) - self.ignore_ssl = config.get('ignore_ssl', False) - - if not self.ignore_ssl: - if self.ssl_cert_path: - if not os.path.isfile(self.ssl_cert_path): - raise ValueError('SSL certificate file %s does not exist' % self.ssl_cert_path) - else: - # default variables handled by python requests to validate cert (used by underlying tableauserverclient) - os.environ['REQUESTS_CA_BUNDLE'] = self.ssl_cert_path - os.environ['CURL_CA_BUNDLE'] = self.ssl_cert_path - # Retrieve Tableau Hyper and Server/Online locations and table configurations self.output_file_name = config.get('output_table', 'my_dss_table') self.project_id = None diff --git a/python-lib/tableau_server_utils.py b/python-lib/tableau_server_utils.py index 86bad37..c2c2c26 100644 --- a/python-lib/tableau_server_utils.py +++ b/python-lib/tableau_server_utils.py @@ -96,7 +96,7 @@ def get_project_full_path(project, all_projects): return root + " / " + project.get("name") -def get_tableau_server_connection(config): +def get_legacy_tableau_server_connection(config): server_url = username = password = site_id = None use_preset = config.get('usePreset', False) if use_preset: @@ -111,6 +111,42 @@ def get_tableau_server_connection(config): ssl_cert_path = configuration.get('ssl_cert_path', None) ignore_ssl = configuration.get('ignore_ssl', False) + setup_ssl(ignore_ssl, ssl_cert_path) + + return server_url, username, password, site_id, ignore_ssl + + +def get_tableau_server_connection(config): + server_url = username = password = site_id = None + auth_type = config.get("auth_type", None) + + if auth_type == "legacy-login": + configuration = config + elif auth_type == "legacy-preset": + configuration = config.get('tableau_server_connection', {}) + elif auth_type == "basic-oauth": + configuration = config.get('tableau_server_secure_connection', {}) + tableau_secure_basic = configuration.get("tableau_secure_basic", {}) + username = tableau_secure_basic.get("user") + password = tableau_secure_basic.get("password") + else: # the auth_type selector was never used, so old conf file, use legacy mode + server_url, username, password, site_id, ignore_ssl = get_legacy_tableau_server_connection(config) + return server_url, username, password, site_id, ignore_ssl + + server_url = configuration.get('server_url', None) + username = configuration.get('username', username) + password = configuration.get('password', password) + site_id = configuration.get('site_id', '') + + ssl_cert_path = configuration.get('ssl_cert_path', None) + ignore_ssl = configuration.get('ignore_ssl', False) + + setup_ssl(ignore_ssl, ssl_cert_path) + + return server_url, username, password, site_id, ignore_ssl + + +def setup_ssl(ignore_ssl, ssl_cert_path): if not ignore_ssl and ssl_cert_path: if not os.path.isfile(ssl_cert_path): raise ValueError('SSL certificate file {} does not exist'.format(ssl_cert_path)) @@ -118,5 +154,3 @@ def get_tableau_server_connection(config): # default variables handled by python requests to validate cert (used by underlying tableauserverclient) os.environ['REQUESTS_CA_BUNDLE'] = ssl_cert_path os.environ['CURL_CA_BUNDLE'] = ssl_cert_path - - return server_url, username, password, site_id, ignore_ssl From 4b342624ec1fd4bf40457fed74cb4b16317c35f0 Mon Sep 17 00:00:00 2001 From: Alexandre Bourret Date: Mon, 21 Nov 2022 10:20:17 +0100 Subject: [PATCH 2/6] Renaming secure preset to personal preset --- .../parameter-set.json | 6 +++--- python-exporters/tableau-hyper_upload/exporter.json | 8 ++++---- python-lib/tableau_server_utils.py | 8 ++++---- 3 files changed, 11 insertions(+), 11 deletions(-) rename parameter-sets/{secure-connection => tableau-personal-connection}/parameter-set.json (90%) diff --git a/parameter-sets/secure-connection/parameter-set.json b/parameter-sets/tableau-personal-connection/parameter-set.json similarity index 90% rename from parameter-sets/secure-connection/parameter-set.json rename to parameter-sets/tableau-personal-connection/parameter-set.json index d0952bf..51cfd03 100644 --- a/parameter-sets/secure-connection/parameter-set.json +++ b/parameter-sets/tableau-personal-connection/parameter-set.json @@ -1,7 +1,7 @@ { "meta" : { - "label": "Tableau Server Secure Preset", - "description": "Tableau Server Secure Preset", + "label": "Tableau Server Personal Preset", + "description": "Tableau Server Personal Preset", "icon": "icon-tableau" }, "defaultDefinableInline": false, @@ -40,7 +40,7 @@ "visibilityCondition": "model.ignore_ssl == false" }, { - "name": "tableau_secure_basic", + "name": "tableau_personal_basic", "type": "CREDENTIAL_REQUEST", "label": "Tableau basic login", "credentialRequestSettings": { diff --git a/python-exporters/tableau-hyper_upload/exporter.json b/python-exporters/tableau-hyper_upload/exporter.json index 031ce84..c44bb58 100644 --- a/python-exporters/tableau-hyper_upload/exporter.json +++ b/python-exporters/tableau-hyper_upload/exporter.json @@ -24,7 +24,7 @@ "selectChoices": [ { "value": "basic-oauth", - "label": "Secure preset" + "label": "Personal preset" }, { "value": "legacy-preset", @@ -45,10 +45,10 @@ "visibilityCondition": false }, { - "name": "tableau_server_secure_connection", - "label": "Tableau Server Secure Preset", + "name": "tableau_server_personal_connection", + "label": "Tableau Server Personal Preset", "type": "PRESET", - "parameterSetId": "secure-connection", + "parameterSetId": "tableau-personal-connection", "visibilityCondition": "model.auth_type == 'basic-oauth'" }, { diff --git a/python-lib/tableau_server_utils.py b/python-lib/tableau_server_utils.py index c2c2c26..c1592c9 100644 --- a/python-lib/tableau_server_utils.py +++ b/python-lib/tableau_server_utils.py @@ -125,10 +125,10 @@ def get_tableau_server_connection(config): elif auth_type == "legacy-preset": configuration = config.get('tableau_server_connection', {}) elif auth_type == "basic-oauth": - configuration = config.get('tableau_server_secure_connection', {}) - tableau_secure_basic = configuration.get("tableau_secure_basic", {}) - username = tableau_secure_basic.get("user") - password = tableau_secure_basic.get("password") + configuration = config.get('tableau_server_personal_connection', {}) + tableau_personal_basic = configuration.get("tableau_personal_basic", {}) + username = tableau_personal_basic.get("user") + password = tableau_personal_basic.get("password") else: # the auth_type selector was never used, so old conf file, use legacy mode server_url, username, password, site_id, ignore_ssl = get_legacy_tableau_server_connection(config) return server_url, username, password, site_id, ignore_ssl From 2dad81725e3fa6d3f70c6d810b89090d4555d450 Mon Sep 17 00:00:00 2001 From: Alexandre Bourret Date: Thu, 29 Dec 2022 14:33:56 +0100 Subject: [PATCH 3/6] Old settings made visible if exists and auth selector on None --- .../tableau-hyper_upload/exporter.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/python-exporters/tableau-hyper_upload/exporter.json b/python-exporters/tableau-hyper_upload/exporter.json index c44bb58..1b78251 100644 --- a/python-exporters/tableau-hyper_upload/exporter.json +++ b/python-exporters/tableau-hyper_upload/exporter.json @@ -56,28 +56,28 @@ "label": "Tableau Server Preset", "type": "PRESET", "parameterSetId": "tableau-server-connection", - "visibilityCondition": "model.auth_type == 'legacy-preset'" + "visibilityCondition": "model.auth_type == 'legacy-preset' || (model.auth_type == null && model.usePreset == true)" }, { "name": "server_url", "label":"URL", "type": "STRING", "mandatory": false, - "visibilityCondition": "model.auth_type == 'legacy-login'" + "visibilityCondition": "model.auth_type == 'legacy-login' || (model.auth_type == null && model.usePreset == false && model.server_url != null)" }, { "name": "username", "label":"Username", "type": "STRING", "mandatory": false, - "visibilityCondition": "model.auth_type == 'legacy-login'" + "visibilityCondition": "model.auth_type == 'legacy-login' || (model.auth_type == null && model.usePreset == false && model.server_url != null)" }, { "name": "password", "label":"Password", "type": "PASSWORD", "mandatory": false, - "visibilityCondition": "model.auth_type == 'legacy-login'" + "visibilityCondition": "model.auth_type == 'legacy-login' || (model.auth_type == null && model.usePreset == false && model.server_url != null)" }, { "name": "ignore_ssl", @@ -86,7 +86,7 @@ "mandatory": false, "description": "Ignore SSL", "defaultValue": false, - "visibilityCondition": "model.auth_type == 'legacy-login'" + "visibilityCondition": "model.auth_type == 'legacy-login' || (model.auth_type == null && model.server_url != null)" }, { "name": "ssl_cert_path", @@ -94,7 +94,7 @@ "type": "STRING", "description": "(optional) Full path to your tableau SSL certificate", "mandatory": false, - "visibilityCondition": "(model.auth_type == 'legacy-login') && model.ignore_ssl == false" + "visibilityCondition": "(model.auth_type == 'legacy-login' || (model.auth_type == null && model.server_url != null)) && model.ignore_ssl == false" }, { "label":"Destination", @@ -138,7 +138,7 @@ "type": "STRING", "description": "The site_id is the subpath of your full site URL. (See the README.md for details)", "mandatory": false, - "visibilityCondition": "model.auth_type == 'legacy-login'" + "visibilityCondition": "model.auth_type == 'legacy-login' || model.auth_type == null" } ] } \ No newline at end of file From a84dcba37d128c580d0d4ca81a348545cbac08a7 Mon Sep 17 00:00:00 2001 From: Alexandre Bourret Date: Thu, 5 Jan 2023 09:11:37 +0100 Subject: [PATCH 4/6] changing basic-oauth to basic-preset --- python-exporters/tableau-hyper_upload/exporter.json | 4 ++-- python-lib/tableau_server_utils.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/python-exporters/tableau-hyper_upload/exporter.json b/python-exporters/tableau-hyper_upload/exporter.json index 1b78251..a7471df 100644 --- a/python-exporters/tableau-hyper_upload/exporter.json +++ b/python-exporters/tableau-hyper_upload/exporter.json @@ -23,7 +23,7 @@ "type": "SELECT", "selectChoices": [ { - "value": "basic-oauth", + "value": "basic-preset", "label": "Personal preset" }, { @@ -49,7 +49,7 @@ "label": "Tableau Server Personal Preset", "type": "PRESET", "parameterSetId": "tableau-personal-connection", - "visibilityCondition": "model.auth_type == 'basic-oauth'" + "visibilityCondition": "model.auth_type == 'basic-preset'" }, { "name": "tableau_server_connection", diff --git a/python-lib/tableau_server_utils.py b/python-lib/tableau_server_utils.py index c1592c9..7671665 100644 --- a/python-lib/tableau_server_utils.py +++ b/python-lib/tableau_server_utils.py @@ -124,7 +124,7 @@ def get_tableau_server_connection(config): configuration = config elif auth_type == "legacy-preset": configuration = config.get('tableau_server_connection', {}) - elif auth_type == "basic-oauth": + elif auth_type == "basic-preset": configuration = config.get('tableau_server_personal_connection', {}) tableau_personal_basic = configuration.get("tableau_personal_basic", {}) username = tableau_personal_basic.get("user") From f3f174f39700781eaade021e9d5dcd0b613778ff Mon Sep 17 00:00:00 2001 From: Alexandre Bourret Date: Wed, 21 Jun 2023 10:57:32 +0200 Subject: [PATCH 5/6] Version 0.1.7 --- plugin.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin.json b/plugin.json index 8233808..0e78896 100644 --- a/plugin.json +++ b/plugin.json @@ -1,6 +1,6 @@ { "id": "tableau-hyper-export", - "version": "0.1.6", + "version": "0.1.7", "meta": { "label": "Tableau Hyper format", "description": "Export datasets to Tableau .hyper format.", From f866c2099e210576da5135d520d3eb361953de5a Mon Sep 17 00:00:00 2001 From: Alexandre Bourret Date: Wed, 21 Jun 2023 10:57:41 +0200 Subject: [PATCH 6/6] Changelog update --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 74feec4..ce59fd7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## [Version 0.1.7](https://github.com/dataiku/dss-plugin-tableau-hyper/releases/tag/v0.1.7) - Feature release - 2023-06-21 + +- Feature: add secure personal preset + ## [Version 0.1.6](https://github.com/dataiku/dss-plugin-tableau-hyper/releases/tag/v0.1.6) - Feature release - 2022-11-14 - Feature: add a project selector