Skip to content
Merged
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
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ dependencies = [
]
[project.optional-dependencies]
test = [
"black",
"black>=22,<23",
"mock",
"mypy",
"pytest>=7.0",
Expand Down
5 changes: 2 additions & 3 deletions tabcmd/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@
try:
from tabcmd.tabcmd import main
except ImportError as e:
print(sys.stderr, e)
print(sys.stderr, "Tabcmd needs to be run as a module, it cannot be run as a script")
print(sys.stderr, "Try running python -m tabcmd")
print("Exception thrown running program: `{}`, `{}`".format(e, e.__context__), file=sys.stderr)
print("[Possible cause: Tabcmd needs to be run as a module, try running `python -m tabcmd`]", file=sys.stderr)
sys.exit(1)

if __name__ == "__main__":
Expand Down
86 changes: 62 additions & 24 deletions tabcmd/commands/auth/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def __init__(self):
self.no_prompt = False
self.certificate = None
self.no_certcheck = False
self.no_proxy = True
self.no_proxy = False
self.proxy = None
self.timeout = None

Expand Down Expand Up @@ -73,10 +73,10 @@ def _update_session_data(self, args):
self.token_name = args.token_name or self.token_name
self.token_value = args.token_value or self.token_value

self.no_prompt = args.no_prompt or self.no_prompt
self.no_prompt = args.no_prompt # have to set this on every call?
self.certificate = args.certificate or self.certificate
self.no_certcheck = args.no_certcheck or self.no_certcheck
self.no_proxy = args.no_proxy or self.no_proxy
self.no_certcheck = args.no_certcheck # have to set this on every call?
self.no_proxy = args.no_proxy # have to set this on every call?
self.proxy = args.proxy or self.proxy
self.timeout = self.timeout_as_integer(self.logger, args.timeout, self.timeout)

Expand Down Expand Up @@ -150,28 +150,45 @@ def _create_new_token_credential(self):
else:
Errors.exit_with_error(self.logger, _("session.errors.missing_arguments").format("token name"))

def _set_connection_options(self) -> TSC.Server:
def _open_connection_with_opts(self) -> TSC.Server:
self.logger.debug("Setting up request options")
# args still to be handled here:
# proxy, --no-proxy,
# cert
http_options: Dict[str, Any] = {"headers": {"User-Agent": "Tabcmd/{}".format(version)}}

if self.no_certcheck:
http_options["verify"] = False
urllib3.disable_warnings(category=InsecureRequestWarning)

"""
Do we want to do the same format check as old tabcmd?
For now I think we can trust requests to handle a bad proxy
Pattern pattern = Pattern.compile("([^:]*):([0-9]*)");
if not matches:
throw new ReportableException(m_i18n.getString("sessionoptions.errors.bad_proxy_format", proxyArg));
"""
if self.proxy:
# do we catch this error? "sessionoptions.errors.bad_proxy_format"
self.logger.debug("Setting proxy: ", self.proxy)
self.logger.debug("Setting http proxy: {}".format(self.proxy))
proxies = {"http": self.proxy}
http_options["proxies"] = proxies
if self.no_proxy:
# override any proxy that was set
http_options["proxies"] = None

if self.timeout:
http_options["timeout"] = self.timeout

if self.certificate:
http_options["cert"] = self.certificate

try:
self.logger.debug(http_options)
# this is the only place we open a connection to the server
# so the request options are all set for the session now
tableau_server = TSC.Server(self.server_url, http_options=http_options)

except Exception as e:
self.logger.debug(
"Connection args: server {}, site {}, proxy {}, cert {}".format(
self.server_url, self.site_name, self.proxy, self.certificate
"Connection args: server {}, site {}, proxy {}/no-proxy {}, cert {}".format(
self.server_url, self.site_name, self.proxy, self.no_proxy, self.certificate
)
)
Errors.exit_with_error(self.logger, "Failed to connect to server", e)
Expand All @@ -196,11 +213,10 @@ def _verify_server_connection_unauthed(self):
Errors.exit_with_error(self.logger, exception=e)

def _create_new_connection(self) -> TSC.Server:
self.logger.info(_("session.new_session"))
self._print_server_info()
self.logger.info(_("session.connecting"))
try:
self.tableau_server = self._set_connection_options()
self.tableau_server = self._open_connection_with_opts()
except Exception as e:
Errors.exit_with_error(self.logger, "Failed to connect to server", e)
return self.tableau_server
Expand All @@ -211,22 +227,33 @@ def _read_existing_state(self):

def _print_server_info(self):
self.logger.info("===== Server: {}".format(self.server_url))
if self.proxy:
self.logger.info("===== Proxy: {}".format(self.proxy))
if self.username:
self.logger.info("===== Username: {}".format(self.username))
if self.certificate:
self.logger.info("===== Certificate: {}".format(self.certificate))
else:
self.logger.info("===== Token Name: {}".format(self.token_name))
site_display_name = self.site_name or "Default Site"
self.logger.info(_("dataconnections.classes.tableau_server_site") + ": {}".format(site_display_name))

# side-effect: sets self.username
def _validate_existing_signin(self):
self.logger.info(_("session.continuing_session"))
# when do these two messages show up? self.logger.info(_("session.auto_site_login"))
try:
if self.tableau_server and self.tableau_server.is_signed_in():
response = self.tableau_server.users.get_by_id(self.user_id)
self.logger.debug(response)
if response.status_code.startswith("200"):
return self.tableau_server
server_user = self.tableau_server.users.get_by_id(self.user_id).name
if not self.username:
self.username = server_user
if not self.username == server_user:
Errors.exit_with_error(
self.logger,
message="Local username `{}` does not match server username `{}`".format(
self.username, server_user
),
)
return self.tableau_server
except TSC.ServerResponseError as e:
self.logger.info(_("publish.errors.unexpected_server_response"), e)
except Exception as e:
Expand All @@ -245,12 +272,14 @@ def _sign_in(self, tableau_auth) -> TSC.Server:
self.site_id = self.tableau_server.site_id
self.user_id = self.tableau_server.user_id
self.auth_token = self.tableau_server._auth_token
if not self.username:
self.username = self.tableau_server.users.get_by_id(self.user_id).name
self.logger.info(_("common.output.succeeded"))
success = self._validate_existing_signin()
except Exception as e:
Errors.exit_with_error(self.logger, exception=e)
self.logger.debug("Signed into {0}{1} as {2}".format(self.server_url, self.site_name, self.username))
if success:
self.logger.info(_("common.output.succeeded"))
else:
Errors.exit_with_error(self.logger, message="Sign in failed")

return self.tableau_server

def _get_saved_credentials(self):
Expand Down Expand Up @@ -290,14 +319,15 @@ def create_session(self, args, logger):
else: # no login arguments given - look for saved info
# maybe we're already signed in!
if self.tableau_server:
self.logger.info(_("session.continuing_session"))
signed_in_object = self._validate_existing_signin()
self.logger.debug(signed_in_object)
# or maybe we at least have the credentials saved
if not signed_in_object:
credentials = self._get_saved_credentials()

if credentials and not signed_in_object:
# logging in, not using an existing session
self.logger.debug("We are not logged in yet but we have credentials to log in with")
self.tableau_server = self._create_new_connection()
self._verify_server_connection_unauthed()
signed_in_object = self._sign_in(credentials)
Expand Down Expand Up @@ -358,7 +388,12 @@ def _read_from_json(self):
try:
with open(str(file_path), "r") as file_contents:
data = json.load(file_contents)
if data is None or data == {}:
return
content = data["tableau_auth"]
if content is None:
return
self._save_data_from_json(content)
except json.JSONDecodeError as e:
self._wipe_bad_json(e, "Error reading data from session file")
except IOError as e:
Expand All @@ -368,8 +403,11 @@ def _read_from_json(self):
except Exception as e:
self._wipe_bad_json(e, "Unexpected error reading session details from file")

def _save_data_from_json(self, content):
try:
auth = content[0]
if auth is None:
self._wipe_bad_json(ValueError(), "Empty session file")
self.auth_token = auth["auth_token"]
self.server_url = auth["server"]
self.site_name = auth["site_name"]
Expand Down
2 changes: 1 addition & 1 deletion tabcmd/execution/parent_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ def parent_parser_with_global_options():
)
proxy_group.add_argument(
"--no-proxy",
action="store_false",
action="store_true", # default to false
help=_("session.options.no-proxy"),
)

Expand Down
Loading