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
52 changes: 52 additions & 0 deletions samples/login.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
####
# This script demonstrates how to log in to Tableau Server Client.
#
# To run the script, you must have installed Python 2.7.9 or later.
####

import argparse
import getpass
import logging

import tableauserverclient as TSC


def main():
parser = argparse.ArgumentParser(description='Logs in to the server.')

parser.add_argument('--logging-level', '-l', choices=['debug', 'info', 'error'], default='error',
help='desired logging level (set to error by default)')

parser.add_argument('--server', '-s', required=True, help='server address')

group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('--username', '-u', help='username to sign into the server')
group.add_argument('--token-name', '-n', help='name of the personal access token used to sign into the server')

args = parser.parse_args()

# Set logging level based on user input, or error by default.
logging_level = getattr(logging, args.logging_level.upper())
logging.basicConfig(level=logging_level)

# Make sure we use an updated version of the rest apis.
server = TSC.Server(args.server, use_server_version=True)

if args.username:
# Trying to authenticate using username and password.
password = getpass.getpass("Password: ")
tableau_auth = TSC.TableauAuth(args.username, password)
with server.auth.sign_in(tableau_auth):
print('Logged in successfully')

else:
# Trying to authenticate using personal access tokens.
personal_access_token = getpass.getpass("Personal Access Token: ")
tableau_auth = TSC.PersonalAccessTokenAuth(token_name=args.token_name,
personal_access_token=personal_access_token)
with server.auth.sign_in_with_personal_access_token(tableau_auth):
print('Logged in successfully')


if __name__ == '__main__':
main()
2 changes: 1 addition & 1 deletion tableauserverclient/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from .namespace import NEW_NAMESPACE as DEFAULT_NAMESPACE
from .models import ConnectionCredentials, ConnectionItem, DatasourceItem,\
GroupItem, JobItem, BackgroundJobItem, PaginationItem, ProjectItem, ScheduleItem, \
SiteItem, TableauAuth, UserItem, ViewItem, WorkbookItem, UnpopulatedPropertyError, \
SiteItem, TableauAuth, PersonalAccessTokenAuth, UserItem, ViewItem, WorkbookItem, UnpopulatedPropertyError, \
HourlyInterval, DailyInterval, WeeklyInterval, MonthlyInterval, IntervalItem, TaskItem, \
SubscriptionItem, Target
from .server import RequestOptions, CSVRequestOptions, ImageRequestOptions, PDFRequestOptions, Filter, Sort, \
Expand Down
1 change: 1 addition & 0 deletions tableauserverclient/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from .server_info_item import ServerInfoItem
from .site_item import SiteItem
from .tableau_auth import TableauAuth
from .personal_access_token_auth import PersonalAccessTokenAuth
from .target import Target
from .task_item import TaskItem
from .user_item import UserItem
Expand Down
11 changes: 11 additions & 0 deletions tableauserverclient/models/personal_access_token_auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class PersonalAccessTokenAuth(object):
def __init__(self, token_name, personal_access_token, site_id=''):
self.token_name = token_name
self.personal_access_token = personal_access_token
self.site_id = site_id
# Personal Access Tokens doesn't support impersonation.
self.user_id_to_impersonate = None

@property
def credentials(self):
return {'clientId': self.token_name, 'personalAccessToken': self.personal_access_token}
4 changes: 4 additions & 0 deletions tableauserverclient/models/tableau_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,7 @@ def site(self, value):
warnings.warn('TableauAuth.site is deprecated, use TableauAuth.site_id instead.',
DeprecationWarning)
self.site_id = value

@property
def credentials(self):
return {'name': self.username, 'password': self.password}
7 changes: 6 additions & 1 deletion tableauserverclient/server/endpoint/auth_endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,14 @@ def sign_in(self, auth_req):
user_id = parsed_response.find('.//t:user', namespaces=self.parent_srv.namespace).get('id', None)
auth_token = parsed_response.find('t:credentials', namespaces=self.parent_srv.namespace).get('token', None)
self.parent_srv._set_auth(site_id, user_id, auth_token)
logger.info('Signed into {0} as {1}'.format(self.parent_srv.server_address, auth_req.username))
logger.info('Signed into {0} as user with id {1}'.format(self.parent_srv.server_address, user_id))
return Auth.contextmgr(self.sign_out)

@api(version="3.6")
def sign_in_with_personal_access_token(self, auth_req):
# We use the same request that username/password login uses.
return self.sign_in(auth_req)

@api(version="2.0")
def sign_out(self):
url = "{0}/{1}".format(self.baseurl, 'signout')
Expand Down
7 changes: 5 additions & 2 deletions tableauserverclient/server/request_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,14 @@ def _add_credentials_element(parent_element, connection_credentials):
class AuthRequest(object):
def signin_req(self, auth_item):
xml_request = ET.Element('tsRequest')

credentials_element = ET.SubElement(xml_request, 'credentials')
credentials_element.attrib['name'] = auth_item.username
credentials_element.attrib['password'] = auth_item.password
for attribute_name, attribute_value in auth_item.credentials.items():
credentials_element.attrib[attribute_name] = attribute_value

site_element = ET.SubElement(credentials_element, 'site')
site_element.attrib['contentUrl'] = auth_item.site_id

if auth_item.user_id_to_impersonate:
user_element = ET.SubElement(credentials_element, 'user')
user_element.attrib['id'] = auth_item.user_id_to_impersonate
Expand Down
21 changes: 21 additions & 0 deletions test/test_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,19 @@ def test_sign_in(self):
self.assertEqual('6b7179ba-b82b-4f0f-91ed-812074ac5da6', self.server.site_id)
self.assertEqual('1a96d216-e9b8-497b-a82a-0b899a965e01', self.server.user_id)

def test_sign_in_with_personal_access_tokens(self):
with open(SIGN_IN_XML, 'rb') as f:
response_xml = f.read().decode('utf-8')
with requests_mock.mock() as m:
m.post(self.baseurl + '/signin', text=response_xml)
tableau_auth = TSC.PersonalAccessTokenAuth(token_name='mytoken',
personal_access_token='Random123Generated', site_id='Samples')
self.server.auth.sign_in(tableau_auth)

self.assertEqual('eIX6mvFsqyansa4KqEI1UwOpS8ggRs2l', self.server.auth_token)
self.assertEqual('6b7179ba-b82b-4f0f-91ed-812074ac5da6', self.server.site_id)
self.assertEqual('1a96d216-e9b8-497b-a82a-0b899a965e01', self.server.user_id)

def test_sign_in_impersonate(self):
with open(SIGN_IN_IMPERSONATE_XML, 'rb') as f:
response_xml = f.read().decode('utf-8')
Expand All @@ -48,6 +61,14 @@ def test_sign_in_error(self):
tableau_auth = TSC.TableauAuth('testuser', 'wrongpassword')
self.assertRaises(TSC.ServerResponseError, self.server.auth.sign_in, tableau_auth)

def test_sign_in_invalid_token(self):
with open(SIGN_IN_ERROR_XML, 'rb') as f:
response_xml = f.read().decode('utf-8')
with requests_mock.mock() as m:
m.post(self.baseurl + '/signin', text=response_xml, status_code=401)
tableau_auth = TSC.PersonalAccessTokenAuth(token_name='mytoken', personal_access_token='invalid')
self.assertRaises(TSC.ServerResponseError, self.server.auth.sign_in, tableau_auth)

def test_sign_in_without_auth(self):
with open(SIGN_IN_ERROR_XML, 'rb') as f:
response_xml = f.read().decode('utf-8')
Expand Down