Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add packager support for null OAuth config #1169

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions connector-packager/connector_packager/jar_jdk_packager.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,12 @@ def get_min_support_version(file_list: List[ConnectorFile], cur_min_version_tabl
oauthConfigs = manifestRoot.findall('.//oauth-config')
if (oauthConfigs is not None and len(oauthConfigs) > 1 and 2023.1 > float(min_version_tableau)):
min_version_tableau = "2023.1"
reasons.append("Support for multiple OAuth configs was added in the 2023.1 release")
elif (oauthConfigs is not None and len(oauthConfigs) == 1):
firstConfig = oauthConfigs[0]
if firstConfig.attrib['file'] == "null_config" and 2024.1 > float(min_version_tableau):
min_version_tableau = "2024.1"
reasons.append("Connector uses Null OAuth Config, which was added in the 2024.1 release")

if version.parse(cur_min_version_tableau) > version.parse(min_version_tableau):
reasons.append("min-tableau-version set to " + cur_min_version_tableau + ", since that is higher than calculated version of " + min_version_tableau)
Expand Down
81 changes: 51 additions & 30 deletions connector-packager/connector_packager/xml_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ def __init__(self, path_to_folder: Path):
self.class_name = None # Get this from the class name in the manifest file
self.file_list = [] # list of files to package
self.loc_strings = [] # list of loc strings so we can make sure they are covered in the resource files.
self.null_oauth_config_found = False # whether or not we found a null oauth config
self.num_oauth_configs_found = 0 # number of oauth configs found, so we can fail the connector if there are non-null cconfigs and a null config

def generate_file_list(self) -> Optional[List[ConnectorFile]]:
"""
Expand Down Expand Up @@ -157,46 +159,65 @@ def parse_file(self, file_to_parse: ConnectorFile) -> bool:
file_to_parse.file_name)
return False

# If oauth-config attribute, keep track of how many we find. Enforce that if null oauth config only one config is defined
if child.tag == "oauth-config":
if self.null_oauth_config_found:
logger.error("Error: cannot declare a null OAuth config in connector with non-null configs")
return False

if 'file' in child.attrib and child.attrib['file'] == "null_config":

# If we already found oauth configs and then found a null config, reject the connector
if self.num_oauth_configs_found > 0:
logger.error("Error: cannot declare a null OAuth config in connector with non-null configs")
return False
else:
logger.debug("Null OAuth config found")
self.null_oauth_config_found = True

self.num_oauth_configs_found+=1



# Check the attributes
# If xml element has file attribute, add it to the file list. If it's not a script, parse that file too.
if 'file' in child.attrib:
if 'file' in child.attrib and not (child.tag == "oauth-config" and child.attrib['file'] == "null_config"):
# Check to make sure the file actually exists
new_file_path = str(self.path_to_folder / child.attrib['file'])

# Check to make sure the file actually exists
new_file_path = str(self.path_to_folder / child.attrib['file'])
if not os.path.isfile(new_file_path):
logger.error("Error: " + new_file_path + " does not exist but is referenced in " +
str(file_to_parse.file_name))
return False

if not os.path.isfile(new_file_path):
logger.debug("Error: " + new_file_path + " does not exist but is referenced in " +
str(file_to_parse.file_name))
return False
# Make new connector file object
logging.debug("Adding file to list (name = " + child.attrib['file'] + ", type = " + child.tag + ")")
new_file = ConnectorFile(child.attrib['file'], child.tag)

# Make new connector file object
logging.debug("Adding file to list (name = " + child.attrib['file'] + ", type = " + child.tag + ")")
new_file = ConnectorFile(child.attrib['file'], child.tag)
# figure out if new file is in the list
already_in_list = new_file in self.file_list

# figure out if new file is in the list
already_in_list = new_file in self.file_list
# add new file to list
self.file_list.append(new_file)

# add new file to list
self.file_list.append(new_file)
# If connection-metadata, make sure that connection-fields file exists
if child.tag == 'connection-metadata':
connection_fields_exists = False

# If connection-metadata, make sure that connection-fields file exists
if child.tag == 'connection-metadata':
connection_fields_exists = False
for xml_file in self.file_list:
if xml_file.file_type == 'connection-fields':
connection_fields_exists = True
break

for xml_file in self.file_list:
if xml_file.file_type == 'connection-fields':
connection_fields_exists = True
break
if not connection_fields_exists:
logger.debug("Error: connection-metadata file requires a connection-fields file")
return False

if not connection_fields_exists:
logger.debug("Error: connection-metadata file requires a connection-fields file")
return False

# If not a script and not in list, parse the file for more files to include
if child.tag != 'script' and not already_in_list:
children_valid = self.parse_file(new_file)
if not children_valid:
return False
# If not a script and not in list, parse the file for more files to include
if child.tag != 'script' and not already_in_list:
children_valid = self.parse_file(new_file)
if not children_valid:
return False

if 'url' in child.attrib:

Expand Down
31 changes: 31 additions & 0 deletions connector-packager/tests/test_jar_packager.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
VERSION_2021_4 = "2021.4"
VERSION_2023_1 = "2023.1"
VERSION_2023_2 = "2023.2"
VERSION_2024_1 = "2024.1"
VERSION_FUTURE = "2525.1"
VERSION_THREE_PART = "2021.1.3"

Expand Down Expand Up @@ -393,3 +394,33 @@ def test_jdk_create_jar_oauth_with_config_label(self):

if dest_dir.exists():
shutil.rmtree(dest_dir)

def test_jdk_create_jar_with_null_oauth_config(self):
files_list = [
ConnectorFile("manifest.xml", "manifest"),
ConnectorFile("connectionFields.xml", "connection-fields"),
ConnectorFile("connectionBuilder.js", "script"),
ConnectorFile("dialect.xml", "dialect"),
ConnectorFile("connectionResolver.xml", "connection-resolver")]
source_dir = TEST_FOLDER / Path("null_oauth_config")
dest_dir = TEST_FOLDER / Path("packaged-connector-by-jdk/")
package_name = "null_oauth_config.taco"

jdk_create_jar(source_dir, files_list, package_name, dest_dir)

path_to_test_file = dest_dir / Path(package_name)
self.assertTrue(os.path.isfile(path_to_test_file), "taco file doesn't exist")

# test min support tableau version is stamped
args = ["jar", "xf", package_name, MANIFEST_FILE_NAME]
p = subprocess.Popen(args, cwd=os.path.abspath(dest_dir))
self.assertEqual(p.wait(), 0, "can not extract manfifest file from taco")
path_to_extracted_manifest = dest_dir / MANIFEST_FILE_NAME
self.assertTrue(os.path.isfile(path_to_extracted_manifest), "extracted manifest file doesn't exist")

manifest = ET.parse(path_to_extracted_manifest)
self.assertEqual(manifest.getroot().get("min-version-tableau"),
VERSION_2024_1, "wrong min-version-tableau attr or doesn't exist")

if dest_dir.exists():
shutil.rmtree(dest_dir)
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
(function dsbuilder(attr)
{
var params = {};
var authAttrValue = attr[connectionHelper.attributeAuthentication];

params["SERVER"] = attr[connectionHelper.attributeServer];
params["UID"] = attr[connectionHelper.attributeUsername];
if(authAttrValue =="auth-user-pass")
{
params["PWD"] = attr[connectionHelper.attributePassword];
}
else if(authAttrValue == "oauth")
{
params["AUTHENTICATOR"] = "OAUTH";
params["TOKEN"] = attr["ACCESSTOKEN"];
}

var formattedParams = [];

formattedParams.push(connectionHelper.formatKeyValuePair(driverLocator.keywordDriver, driverLocator.locateDriver(attr)));

for (var key in params)
{
formattedParams.push(connectionHelper.formatKeyValuePair(key, params[key]));
}

return formattedParams;
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>

<connection-fields>
<field name="server" label="Server" value-type="string" category="endpoint" >
<validation-rule reg-exp="^(([a-zA-Z]|[a-zA-Z][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z]|[A-Za-z][A-Za-z0-9\-]*[A-Za-z0-9])$"/>
</field>

<field name="authentication" label="Authentication" category="authentication" value-type="selection" default-value="auth-user-pass" >
<selection-group>
<option value="auth-user-pass" label="Username and Password"/>
<option value="oauth" label="OAuth"/>
</selection-group>
</field>

<field name="username" label="Username" category="authentication" value-type="string">
<conditions>
<condition field="authentication" value="auth-user-pass"/>
</conditions>
</field>

<field name="password" label="Password" category="authentication" value-type="string" secure="true">
<conditions>
<condition field="authentication" value="auth-user-pass"/>
</conditions>
</field>

<field name="instanceurl" label="OAuth Instance Url" category="authentication" value-type="string">
<conditions>
<condition field="authentication" value="oauth" />
</conditions>
<validation-rule reg-exp="^https:\/\/(.+\.)?(snowflakecomputing\.(com|us|cn|de))(.*)"/>
</field>


</connection-fields>
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?xml version='1.0' encoding='utf-8' ?>
<tdr class='null_oauth_config'>
<connection-resolver>
<connection-builder>
<script file='connectionBuilder.js'/>
</connection-builder>
<connection-normalizer>
<required-attributes>
<attribute-list>
<attr>class</attr>
<attr>server</attr>
<attr>port</attr>
<attr>dbname</attr>
<attr>username</attr>
<attr>one-time-sql</attr>
<attr>service</attr>
<attr>authentication</attr>
<attr>password</attr>
</attribute-list>
</required-attributes>
</connection-normalizer>
</connection-resolver>
<driver-resolver>
<driver-match >
<driver-name type='regex'>SnowflakeDSIIDriver*</driver-name>
</driver-match>
</driver-resolver>
</tdr>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<dialect name='SimpleSnowflake'
class='null_oauth_config'
base='SnowflakeDialect'
version='18.1'>
</dialect>

Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?xml version='1.0' encoding='utf-8' ?>

<connector-plugin class='null_oauth_config' superclass='odbc' plugin-version='0.0.0' name='Null OAuth Config' version='18.1'>
<vendor-information>
<company name="Sample Company"/>
<support-link url = "https://example.com"/>
</vendor-information>
<connection-customization class="null_oauth_config" enabled="true" version="10.0">
<vendor name="vendor"/>
<driver name="driver"/>
<customizations>
<customization name="CAP_QUERY_HAVING_REQUIRES_GROUP_BY" value="yes"/>
<customization name="CAP_QUERY_INITIAL_SQL_SPLIT_STATEMENTS" value="yes"/>
</customizations>
</connection-customization>
<connection-fields file='connectionFields.xml'/>
<connection-resolver file="connectionResolver.xml"/>
<dialect file='dialect.xml'/>
<oauth-config file='null_config'/>
</connector-plugin>
Loading