Most of us are bored with the tabcmd tool provided by Tableau. It only uses the 20% of the Tableau API and whenever you want to do something from your software you have to call it thru a command line interface. Not a nice solution in the age of webservices and restful APIs. The good news that you actually can use Tableau web services without tabcmd. It’s undocumented, but who cares if you know where to search for services.
Let’s check how the web services invocation works. In our example I would like to know the members of a give group without connecting the underlying database. Tabcmd does not have any option to return with user and group data (including object ownership, etc.), however, on Tableau Server the http://server_url/users.xml contains everything what I need. But how to grab it? The solution is to write a small class which
- Initializes a session inside Tableau Server
- Logs on with credentials using RSA assymmetric crypting (without sending clear text passwords on non-clear text channels)
- Invokes /users.xml URL
OK, how it looks like in details:
In order to log on to tableau securely, we should obtain an authenticity_token (acts like a cryptographic session identifier), and an RSA key (modulus+exponent). We can get by simply calling the http://server/auth.xml url. Most of the wgserver URLs accepts format parameter or .xml suffix, which indicates that the result should be returned as XML.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
def login(user, pass) key = OpenSSL::PKey::RSA.new # invoke /auth.xml on server response = @http_client.get( tableau_url_with '/auth.xml' ) # parse returned XML doc = REXML::Document.new( response.body ) # read RSA key + authenticity token authinfo = doc.elements[1, 'authinfo'] modulus = authinfo.elements[1, 'modulus'] exponent = authinfo.elements[1, 'exponent'] authenticity_token = authinfo.elements[1, 'authenticity_token'] # fill RSA key information key.n = modulus.text.to_i(16) key.e = exponent.text.to_i(16) |
The second step is to call /auth/login.xml URL with our username, authenticity_token and encrypted password. The RSA public key returned with step 1 allows us to securely encrypt our password (single-way, assymetric encryption), thus no one except the server could decrypt it.
1 2 3 4 5 6 7 |
# logon to server with encrypted password response = @http_client.post( tableau_url_with('/auth/login.xml'), { 'authenticity_token' => authenticity_token.text, 'crypted' => assymmetric_encrypt(pass,key), 'username' => user } ) |
The function for encrypting passwords:
1 2 3 4 5 |
# Encrypt test with RSA public key and pack as %.0x hex numbers def assymmetric_encrypt(val, public_key) crypt_binary = public_key.public_encrypt(val) crypt_binary.unpack("H*") end |
The third step is to call /users.xml (with persistent cookie information) and obtain the results:
1 2 3 4 5 6 |
tableau = TableauLDAPSync::Tableau.new( url ) tableau.login( user, pass ) users = tableau.get( "/users.xml") doc = REXML::Document.new( users.body ) doc.elements.each("users/user/name") { |element| puts element.text } |
And bang, we have the results. Also check the results from the step 1 (http response body), it mentions quite a lot undocumented API URLs — it’s a goldmine for the information hungry ones.
Full source code is here: https://gist.github.com/tfoldi/5450418
This post was originally published on the Tableau Developer Community Forums.
There is also an alternate method available, you can check it out here.
Looking for the JAVA approach? Click here.
- Tableau Extensions Addons Introduction: Synchronized Scrollbars - December 2, 2019
- Tableau External Services API: Adding Haskell Expressions as Calculations - November 20, 2019
- Scaling out Tableau Extracts – Building a distributed, multi-node MPP Hyper Cluster - August 11, 2019