3 Replies Latest reply on Aug 27, 2018 4:58 PM by Jeff Bryant

    Viewing published workbooks via C# and REST API

    Jeff Bryant

      We've had an application in place that allows computers running in a Kiosk fashion to automatically launch a Tableau workbook when the computer starts.  This happens w/no human interaction and has been working quite well until the recent update of our server from 2018.1 to 2018.2 (for the record this has been working on versions prior to 2018.1 as well). 

       

      The application is a pretty basic C# app that isn't really rocket science. It simply calls the REST API sign-in and uses the returned token in the navigation to a workbook on an internal web server.  Since the upgrade to 2018.2 I'm seeing a failure that indicates the token is invalid or expired.  I've verified that we're using version 3.1 of the api (I believe this is the version associated with 2018.2).  I've gone through the basic debugging steps, checked the logs I'm seeing the request and then a 403 error.

       

      I've put together a paired down version of what we're doing.  If anyone sees something obviously out of whack, I'd really appreciate it.

       

              /// <summary>

              /// A Basic test of authentication and navigation using Tableau REST API and C#

              /// </summary>

              public static void TestSignInAndNavigate()

              {

                  StringBuilder sbPayload = new StringBuilder();

                  byte[] data = null;

                  /* Credentials obfuscated for security */

                  string server = "*****";

                  string user = "*****";

                  string pwd = "****";

                  string site = "";

                  string chunk = string.Empty;

                  string response = string.Empty;

                  string signInURL = String.Format("http://{0}/api/3.1/auth/signin", server);

                  string authToken = string.Empty;

       

                  // Create Sign In payload (parameters obfuscated for demo purposes)

                  signInURL = String.Format("http://{0}/api/3.1/auth/signin", server);

                  sbPayload.AppendFormat("<tsRequest><credentials name='{0}' password='{1}'><site contentUrl='{2}'/></credentials></tsRequest>",

                      user,

                      pwd,

                      site);

       

                  data = Encoding.UTF8.GetBytes(sbPayload.ToString());

       

                  HttpWebRequest httpWReq = (HttpWebRequest)WebRequest.Create(signInURL);

                  httpWReq.Method = "POST";

                  httpWReq.ContentType = "text/xml";

                  httpWReq.ContentLength = data.Length;

                 

                  // POST request to Tableau Server

                  httpWReq.GetRequestStream().Write(data, 0, data.Length);

       

                  // Receive response

                  HttpWebResponse httpResponse = (HttpWebResponse)httpWReq.GetResponse();

                  using (StreamReader reader = new StreamReader(httpResponse.GetResponseStream()))

                  {

                      while ((chunk = reader.ReadLine()) != null)

                          response += chunk;

                  }

       

                  // Typical response looks like this (formatted for clarity):

                  /*

                      <?xml version='1.0' encoding='UTF-8'?>

                      <tsResponse xmlns=\"http://tableau.com/api\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://tableau.com/api http://tableau.com/api/ts-api-3.1.xsd\">

                      <credentials token=\"uA20W78QTumQFsADuwtq0w|TsAopY5joElQEDFBoK2WrCCnPZ0q7DhC\">

                      <site id=\"a946d998-2ead-4894-bb50-1054a91dcab3\" contentUrl=\"\"/>

                      <user id=\"7bf9acf2-16cd-4cab-b32e-9e54cc0bac73\"/>

                      </credentials>

                      </tsResponse>             

                   */

                  // Parse response

                  XmlDocument doc = new XmlDocument();

                  doc.LoadXml(response);

                  XmlNodeList nodes = doc.GetElementsByTagName("tsResponse");

       

                  foreach (XmlNode credentials in nodes)

                  {

                      foreach (XmlNode credential in credentials)

                      {

                          // extract the token attribute

                          XmlAttribute attr = (XmlAttribute)credential.Attributes.GetNamedItem("token");

                          if (attr != null)

                              authToken = attr.InnerText;

                      }

                  }

       

                  // Now attempt to navigate to a known workbook/view

                  string uri = String.Format("http://{0)/trusted/{1}/views/Fresno-SqueezeLine/ProcessControl", server, authToken);

       

                  // Using the above referenced token the URI  would be:

                  // http://<server name>/trusted/uA20W78QTumQFsADuwtq0w|TsAopY5joElQEDFBoK2WrCCnPZ0q7DhC/views/Fresno-SqueezeLine/ProcessControl

                  Process.Start(uri);

       

                  // Since the upgrade to 2018.2 The results returned the browser always appear as the following  -

                  /*

                   An error occurred on the server. The details of the error are:

                      Could not locate unexpired trusted ticket

                  Click the Refresh button in your web browser and try again.

                  If you continue to receive this error please contact your Tableau Server Administrator.

                  */

              }

        • 1. Re: Viewing published workbooks via C# and REST API
          Carisa Chang

          Hi Jeff,

           

          The code above is conflating two different authentication methods for different purposes, I've got no clue how this was working in earlier versions.

           

          The REST API signin method returns an authtoken that can be used for REST API calls only.

           

          To redeem a trusted ticket as the second half of the code is attempting, requires you to request a trusted ticket using the methods here:

          Trusted Authentication

          Get a Ticket from Tableau Server

           

          A REST API authtoken is not interchangeable with a trusted ticket. Hope this helps sort out the code issue!

          1 of 1 people found this helpful
          • 2. Re: Viewing published workbooks via C# and REST API
            Jeff Bryant

            Yeah, after reviewing the code I wondered how this was indeed ever working, that said, it was.  I'm actually happy that it's not working now since it's causing me to have to dig into the REST API deeper and as a result giving me some additional understanding about how things *should* work.

             

            I'm going to work with the methods outlined in the references you provided to see if I can get this working.  I'll post an update once I do.

             

             

            Thanks in for the response.  I was starting to wonder if anyone out there was watching the thread.

            • 3. Re: Viewing published workbooks via C# and REST API
              Jeff Bryant

              Carisa,

               

              OK, I got it working.  Once I changed the code to use an actual authorization ticket as opposed to the sign-in ticket things began to flow a little better.  Here's the updated code for anyone else struggling with something similar:

               

                      /// <summary>

                      /// A Basic test of authentication and navigation using Tableau REST API and C#

                      /// </summary>

                      public static void TestSignInAndNavigate()

                      {

                          byte[] data = null;

                          string server = "*******";

                          string user = "******";
                          string chunk = string.Empty;

                          string response = string.Empty;

                          string authToken = string.Empty;

                          string postData = string.Empty;

               

                          // Get Authorization Ticket

                          var request = (HttpWebRequest)WebRequest.Create(String.Format("http://{0}/trusted", server);

               

                          var encoding = new UTF8Encoding();

                          postData = String.Format("username={0}", user);

                          data = encoding.GetBytes(postData);

               

                          // Create POST

                          request.Method = "POST";

                          request.ContentType = "application/x-www-form-urlencoded";

                          request.ContentLength = data.Length;

                          using (var stream = request.GetRequestStream())

                          {

                              stream.Write(data, 0, data.Length);

                          }

               

                          // Receive response

                          HttpWebResponse httpResponse = (HttpWebResponse)request.GetResponse();

                          using (StreamReader reader = new StreamReader(httpResponse.GetResponseStream()))

                          {

                              while ((chunk = reader.ReadLine()) != null)

                                  authToken += chunk;

                          }

               

                          // Navigate to a known workbook/view

                          string uri = String.Format("http:/{0}//trusted/{1}/views/Fresno-SqueezeLine/ProcessControl", server, authToken);

                          Process.Start(uri);

                      }

               

              Note:

              I also had to "Trust" the IP addresses of any machine utilizing this. It would be really nice if you could specify a range of addresses, but I can live with it for now.

               

              Thanks again for your help.