1
+ from tableau_tools import *
2
+ #import time
3
+
4
+ # This script shows two example generic functions which utilize the RestTokensManager class
5
+ # It is an example of how you can create a wrapper REST API which exposes some of the
6
+ # Tableau REST API functionality but not all of it, while using the impersonation feature
7
+ # so that each request is performed for the User, without needing their credentials (only admin credentials)
8
+ # Taken from a Django project, but Flask code would work similarly
9
+
10
+ # You would probably store your admin credentials securely in an settings or ENV file
11
+ server = settings .TABLEAU_SERVER
12
+ admin_username = settings .TABLEAU_ADMIN_USERNAME
13
+ admin_password = settings .TABLEAU_ADMIN_PASSWORD
14
+ # This is most likely just 'default' but you might have some reason not to bootstrap from there
15
+ default_site_content_url = settings .TABLEAU_DEFAULT_SITE_CONTENT_URL
16
+
17
+ tableau_tools_logger = Logger ('tableau_tools.log' )
18
+
19
+ # Connect to the default site to bootstrap the process
20
+
21
+ # In a running app server, the same process is always running so it needs a Master login PER site
22
+ # But also, REST API sessions do timeout, so we need to check for that possibility and remove
23
+ # Sessions once they have timed out
24
+
25
+ # You must use an Admin Username and Password, as PAT does not have Impersonation at this time (2020.2)
26
+ d = TableauServerRest32 (server = server , username = admin_username , password = admin_password ,
27
+ site_content_url = default_site_content_url )
28
+ # alternatively could use the older TableauRestApiConnection objects if you had code built on those objects
29
+
30
+ # If you are using a self-signed cert or need to pass in a CERT chain, this pass directly to
31
+ # the requests library https://requests.readthedocs.io/en/master/user/quickstart/ to do whatever SSL option you need:
32
+ # d.verify_ssl_cert = False
33
+
34
+ d .enable_logging (tableau_tools_logger )
35
+ # Other options you might turn off for deeper logging:
36
+ # tableau_tools_logger.enable_request_logging()
37
+ # tableau_tools_logger.enable_response_logging()
38
+ # tableau_tools_logger.enable_debug_level()
39
+
40
+
41
+ # This manages all the connection tokens here on out
42
+ connections = RestTokensManager ()
43
+
44
+ #
45
+ # RestTokensManager methods are all functional -- you pass in a TableauServerRest or TableauRestApiConnection object
46
+ # and then it perhaps actions on that object, such as logging in as a different user or switching to
47
+ # an already logged in user.
48
+ # Internally it maintains a data structure with the Admin tokens for any site that has been signed into
49
+ # And the individual User Tokens for any User / Site combination that has been signed into
50
+ # It does not RUN any REST API commands other than sign-in: You run those commands on the
51
+ # connection object once it has been returned
52
+
53
+ # For example, once this is run, the connection object 'd' will have been signed in, and you can
54
+ # do any REST API command against 'd', and it will be done as the master on the default site
55
+ # This is just bootstrapping at the very beginning to make sure we've connected successfully
56
+ # with the admin credentials. If there are errors at this point, something is likely wrong
57
+ # with the configuration/credentials or the Tableau Server
58
+ default_token = connections .sign_in_connection_object (d )
59
+
60
+ # Next is a generic_request function (based on Django pattern), that utilizes the connections object
61
+
62
+ # Every one of our REST methods follows basically this pattern
63
+ # So it has been made generic
64
+ # You pass the callback function to do whatever you want with
65
+ # the REST API object and whatever keyword arguments it needs
66
+ # Callback returns a valid type of HttpResponse object and we're all good
67
+ def generic_request (request , site , callback_function , ** kwargs ):
68
+ # Generic response to start. This will be returned if no other condition overwrites it
69
+ response = HttpResponseServerError ()
70
+
71
+ # If request is none, then it is an admin level function
72
+ if request is not None :
73
+ # Check user, if non, response is Http Forbidden
74
+ # This function represents whatever your application needs to do to tell you the user who has logged in securely
75
+ username = check_user_session (request )
76
+ if username is None :
77
+ response = HttpResponseForbidden ()
78
+ return response
79
+ else :
80
+ # If username is none, the request is run as the Site Admin
81
+ username = None
82
+
83
+ # Create Connection Object for Given User
84
+ # Just create, but don't sign in. Will use swap via the TokenManager
85
+ t = TableauServerRest32 (server = server , username = admin_username , password = admin_password ,
86
+ site_content_url = default_site_content_url )
87
+
88
+ # Again, you might need to pass in certain arguments to requests library if using a self-signed cert
89
+ #t.verify_ssl_cert = False
90
+ t .enable_logging (tableau_tools_logger )
91
+
92
+ # Check for connection, attempt to reestablish if possible
93
+ if connections .connection_signed_in is False :
94
+ tableau_tools_logger .log ("Signing back in to the master user" )
95
+ # If the reconnection fails, return Server Error response
96
+ if connections .sign_in_connection_object (rest_connection = t ) is False :
97
+ # This is a Django error response, take it as whatever HTTP error you'd like to throw
98
+ response = HttpResponseServerError ()
99
+ # If connection is already confirmed, just swap to the user token for the site
100
+ else :
101
+ # Site Admin level request
102
+ if username is None :
103
+ tableau_tools_logger .log ("Swapping to Site Admin " )
104
+ connections .switch_to_site_master (rest_connection = t , site_content_url = site )
105
+ tableau_tools_logger .log ("Token is now {}" .format (t .token ))
106
+ # Request as a particular username
107
+ else :
108
+ tableau_tools_logger .log ("Swapping in existing user token for user {}" .format (username ))
109
+ connections .switch_user_and_site (rest_connection = t , username = username , site_content_url = site )
110
+ tableau_tools_logger .log ("Token is now {}" .format (t .token ))
111
+
112
+ # Do action with connection
113
+ # Whatever callback function was specified will be called with RestApiConnection / TableauServerRest object as first argument
114
+ # then any other kwargs in the order they were passed.
115
+ # The callback function must return a Django HttpResponse (or related) object
116
+ # But within the callback, 't' is the TableauServerRest or TableauRestApiConnection object with the token for the
117
+ # particular user you want
118
+ try :
119
+ response = callback_function (t , ** kwargs )
120
+
121
+ except NotSignedInException as e :
122
+ if username is None :
123
+ tableau_tools_logger .log ("Master REST API session on site {} has timed out" .format (site ))
124
+ del connections .site_master_tokens [site ]
125
+ # Rerun the connection
126
+ tableau_tools_logger .log ("Creating new user token for site master" )
127
+ connections .switch_to_site_master (rest_connection = t , site_content_url = site )
128
+ tableau_tools_logger .log ("Token is now {}" .format (t .token ))
129
+ else :
130
+ tableau_tools_logger .log ("User {} REST API session on vertical {} has timed out" .format (username , site ))
131
+ del connections .site_user_tokens [site ][username ]
132
+ # Rerun the connection
133
+ tableau_tools_logger .log ("Creating new user token for username {} on vertical {}" .format (username , site ))
134
+ connections .switch_user_and_site (rest_connection = t , username = username , site_content_url = site )
135
+ tableau_tools_logger .log ("Token is now {}" .format (t .token ))
136
+ # Rerun the orginal callback command
137
+ tableau_tools_logger .log ("Doing callback function again now that new token exists" )
138
+ response = callback_function (t , ** kwargs )
139
+ # Originally, the code looked at the following two exceptions. This is been replaced by looking at NotSignedInException
140
+ # RecoverableHTTPException is an exception from tableau_tools, when it is known what the error represents
141
+ # HTTPError is a Requests library exception, which might happen if tableau_tools doesn't wrap the particular error.
142
+ # except (RecoverableHTTPException, HTTPError) as e:
143
+ # if e.http_code == 401:
144
+ except Exception as e :
145
+ raise e
146
+ # Destroy REST API Connection Object, which is just used within this code block
147
+ del t
148
+ # Return Response
149
+ return response
150
+
151
+ # There were originally separate functions but they shared enough code to be merged together
152
+ def admin_request (request , site , callback_function , ** kwargs ):
153
+ # We don't pass the 'request' here, because it would have the end user's username attached via the session
154
+ # The point is that username ends up None in the generic_request call, forcing it to use the admin
155
+ return generic_request (None , site , callback_function , ** kwargs )
156
+
157
+
158
+ #
159
+ # Here is an example of an actual exposed endpoint
160
+ #
161
+
162
+ # This is what is passed in as the callback function - so rest_connection is the 't' object passed in by generic_request
163
+ # Returns all of the Projects a user can see content in, alphabetically sorted
164
+ def query_projects (rest_connection : TableauServerRest32 ):
165
+ p_sort = Sort ('name' , 'asc' )
166
+ p = rest_connection .query_projects_json (sorts = [p_sort , ])
167
+ return JsonResponse (p )
168
+
169
+ # An exposed endpoint linked to an actual URL
170
+ def projects (request , site ):
171
+ #log("Starting to request all workbooks")
172
+ # Note we are just wrapping the generic request (this one doesn't take keyword arguments, but anything after
173
+ # 'query_projects' would be passed as an argument into the query_projects function (if it took arguments)
174
+ response = generic_request (request , site , query_projects )
175
+ return response
0 commit comments