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
3 changes: 3 additions & 0 deletions _includes/docs_menu.html
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@
<li>
<a href="{{ site.baseurl }}/docs/trex_sandbox_test.html">Create and Test Sandboxed Extensions</a>
</li>
<li>
<a href="{{ site.baseurl }}/docs/trex_oauth.html">Add OAuth to Dashboard Extensions</a>
</li>
<li class="nav-header">Debugging and Troubleshooting</li>
<li>
<a href="{{ site.baseurl }}/docs/trex_debugging.html">Debug Extensions in Tableau Desktop</a>
Expand Down
7 changes: 4 additions & 3 deletions docs/trex_debugging.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ Tableau Desktop version | Chromium version | Chrome version
|----|----|----|
2018.2, 2018.3 | 47.0.2526.0 | Not available
2019.1 and later | 79.0.3945.0 | Chrome version 79 or earlier.
Latest maintenance release of 2020.2.7+, 2020.3.6+, 2020.4.2+ | 87.0.4280 | Chrome version 80 or later.
2021.1 and later | 87.0.4280 | Chrome version 80 or later.



**Chromium downloads for debugging Tableau 2018.2, 2018.3**
Expand All @@ -41,9 +44,7 @@ Tableau Desktop version | Chromium version | Chrome version

* [Chromium for macOS (`chrome-mac.zip`) (79.0.3945.0)](https://commondatastorage.googleapis.com/chromium-browser-snapshots/index.html?prefix=Mac/706915/)


<div class="alert alert-info"><b>Note </b> If you are using Tableau 2019.1 or later, you can debug extensions in Tableau Desktop using certain versions of Chrome (versions prior to 80). Currently, you can't use Chrome version 80 (or later) for debugging your extension.</div>

<div class="alert alert-info"><b>Note </b> If you are using Tableau 2021.1, or the latest maintenance releases of Tableau 2020.2.2.7+, 2020.3.3.6+, and 2020.4.2+, you can use Chrome version 80 (or later) for debugging your extension.</div>

---

Expand Down
20 changes: 18 additions & 2 deletions docs/trex_known_issues.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ layout: docs

The following section describes some issues in the current release of the Extensions API where the API or the platform does not behave as expected.

For information about what is new or has changed in each release, see the [Release Notes for the Tableau Extensions API]({{ site.baseurl }}/docs/trex_release-notes.html)
For information about what is new or has changed in each release, see the [Release Notes for the Tableau Extensions API]({{site.baseurl}}/docs/trex_release-notes.html).

**In this section**

Expand All @@ -17,6 +17,22 @@ For information about what is new or has changed in each release, see the [Relea
Because of incompatibilities between Chrome and the internal Chromium-based browser used in Tableau, you can't use Chrome version 80 or later to debug your extensions. If you are using Tableau Desktop 2019.1 or later, you can debug extensions using Chrome version 79 or Chromium version 79. If you are using Tableau Desktop versions 2018.2 or 2018.3, you can use Chromium version 47. For more information about debugging extensions and using the Chromium browser, see [Debug Extensions in Tableau Desktop]({{site.baseurl}}/docs/trex_debugging.html) and [Download the Chromium Browser]({{site.baseurl}}/docs/trex_debugging.html#download-the-chromium-browser).


### Unable to run dashboard extension using self-signed certificates

Tableau now uses Qt WebEngine 5.15, which is based on Chromium version 87.0.4280, with additional security fixes from newer versions of Chromium. Because of this update, dashboard extensions hosted on web servers that use self-signed certificates (SSL) might not work in Tableau 2021.1, or in the most recent Tableau maintenance releases: 2020.2.7+, 2020.3.6+, and 2020.4.2+.
You might see one of the following errors:

* `Failed to load resource: net::ERR_CERT_COMMON_NAME_INVALID`

* `Error: Subject Alternative Name Missing`

* `Your connection is not private`

You can avoid these errors if you specify the `subjectAlternativeName` (SAN) in the extended certificate parameters when you sign your certificate.

For more information, see [Google Chromium Enterprise Known Issues - Error "Subject Alternative Name Missing"](https://support.google.com/chrome/a/answer/9813310?hl=en#zippy=%2Cerror-subject-alternative-name-missing-or-neterr-cert-common-name-invalid-or-your-connection-is-not-private){:target="_blank"} and the following discussion on Stack Overflow: [Invalid self signed SSL cert - “Subject Alternative Name Missing” on StackOverflow](https://stackoverflow.com/questions/43665243/invalid-self-signed-ssl-cert-subject-alternative-name-missing){:target="_blank"}.


### Time zone not persisted when updating date parameter

When you update a date or date-time parameter using `changeValueAsync()`, the time zone information is not kept. The date/time is still correct, however, it is just that the data/time is converted to UTC.
Expand All @@ -39,7 +55,7 @@ If your extension uses the `getDataSourcesAsync()` method, calling this method m

### Full data access and permission errors

When an extension needs full data access and the user does not have full data permission on the workbook, Tableau currently allows the extension to run. However, Tableau will throw a console error when the extension calls `getUnderlyingData()` method. See [Handle full data access and permission errors]({{ site.baseurl }}/docs/trex_getdata.html#handle-full-data-access-and-permission-errors).
When an extension needs full data access and the user does not have full data permission on the workbook, Tableau currently allows the extension to run. However, Tableau will throw a console error when the extension calls `getUnderlyingData()` method. See [Handle full data access and permission errors]({{site.baseurl}}/docs/trex_getdata.html#handle-full-data-access-and-permission-errors).


### Tableau Extensions API library version 1.0.0
Expand Down
182 changes: 182 additions & 0 deletions docs/trex_oauth.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
---
title: Add OAuth to Dashboard Extensions
layout: docs
---

If you want your dashboard extension to use OAuth for authentication, you need to be aware of some important changes that were introduced along with sandboxed extensions in Tableau 2019.4. All dashboard extensions are now wrapped in an `iframe`. Because most OAuth sign-in pages enforce the `X-Frame-Options` settings, which helps prevent [clickjacking](https://tools.ietf.org/html/draft-ietf-oauth-v2-23#section-10.13) attacks, you can't load an OAuth sign-in page within the dashboard, or open a dashboard extension dialog window to use for OAuth sign in.

As a best practice, dashboard extensions should open an external browser window when you need to request end-user authorization. In Tableau Server and Tableau Online, you can do this by using methods like `window.open()` and `window.postMessage()`. However, the `window` methods alone will not work in Tableau Desktop, as the external browser window you open for OAuth sign in has no direct connection to the instance of the dashboard extension. This adds a layer of security and requires extra steps to properly route the access token back to your dashboard extension. The following section describes one possible way of coordinating the OAuth interaction using web sockets for communication and the authorization code grant. For security, the client secret should be stored on the server and not in the client code. This is the only safe way to ensure that someone can't use the client credentials to impersonate your application. To simplify your code, your extension should use the same OAuth flow for Tableau Server and Tableau Desktop.


---
**In this section**

* TOC
{:toc}

---

## Summary of the OAuth flow

The following outlines some of the basic steps in using a secondary window for OAuth sign in. For more guidance on setting up OAuth 2.0 securely, see [OAuth 2.0 Security Best Current Practice](https://tools.ietf.org/html/draft-ietf-oauth-security-topics). The following summarizes the steps you'd take if you were using the authorization code grant type. This OAuth flow is recommended as it ensures that the access token is not returned in the URL and that the client secret is safely stored on the server.

* Open a channel for communication between the server hosting the extension (the server) and the instance of the extension (the client). Create a unique identifier (or session id) for the extension when the extension loads.

* Create a request (URL) that directs the user to the OAuth provider sign-in page. The request URL includes the client ID and the extension's unique identifier (session id), and provides a callback URL. Open a new dialog window using `window.open()` directed to the URL. Prompt the user to sign in to the OAuth provider (the provider).

* The user signs in to the OAuth provider and allows (or denies) the extension access to data.

* When authorization has been granted, the OAuth authorization code is returned to the callback URL (on the server). The server processes the response and extracts the authorization code and the session id. The server creates a new request for the OAuth access token, using the authorization code grant type, and maps the client ID, client secret, and authorization code in the body of the request.

* When the response from the OAuth provider is received, the server signals an event that returns the OAuth access token, through the web socket, back to the calling extension.

* In response to the signal, the extension retrieves and stores the OAuth token in local storage on the client's computer. The extension can then use the access token to send subsequent requests for services from the OAuth provider.

---

## An example of the OAuth code path using Socket.IO

One way to manage OAuth sign in for your dashboard extension is to employee web sockets for identifying and coordinating the OAuth connection. For example, the OAuth sample ([datadev-oauth-sign-in](https://glitch.com/~datadev-oauth-sign-in){:target="_blank"}) on Glitch makes use of the [Socket.IO](https://socket.io){:target="_blank"} library. Socket.IO provides bi-directional event-based communication between the browser (client) and server. This example uses Node.js and Express to setup a web server to host an extension that connects to Spotify. The extension uses sockets to identify the extension and to route communication with the OAuth sign-in window.


### Initialize the socket (session ID)

In the OAuth sample, when the extension loads in the dashboard, a unique socket session ID is generated. The socket instance is assigned a random 20-character identifier (`socket.id`). This code is on the client-side, the extension's web page (`index.js`). The code also initializes the sign-in button on the extension's home page.

```javascript

const socket = io();

// Wait to make sure you've made the socket connection so when you press the button there is a socket ID available to send with the request
socket.on("connect", () => {
$("#submit").prop("disabled", false);
});


```

On the Express server (`server.js`), in addition to serving the web pages, the server initializes `socket.io`.

```javascript

const express = require("express");
const app = express();
const server = require("http").createServer(app);
const io = require("socket.io")(server);
const fetch = require("node-fetch");


```



### Sign in and get the authorization code

When a user clicks the sign-in button, it opens a new window or tab in the user's native browser (`window.open`) passing with it the client ID and the socket session ID (`socket.id`). In this example, the URL goes to the authorization endpoint on Spotify and specifies the authorization code response type. Note that this sample uses the client ID for the OAuth sample project on Glitch. You need to change this to match your client ID for your dashboard extension. The method also provides a redirect URL back to the server, where the response will return the authorization code.

```javascript

// Open a new window to manage the OAuth process outside of Desktop, passing the socket ID so the broker knows where to return the token
function openSignInWindow() {
const scopes = "user-top-read";
const redirect_uri = "https://datadev-oauth-sign-in.glitch.me/complete";
const url =
"https://accounts.spotify.com/authorize?response_type=code&client_id=" +
"2029812009c54aef866bd660f612b603" +
"&scope=" +
encodeURIComponent(scopes) +
"&redirect_uri=" +
encodeURIComponent(redirect_uri) +
"&state=" +
socket.id;
window.open(url, "_blank");
}


```

### Request the access token (server-side)

The redirect URL sends the response to the server, where the server extracts the authorization code. The server then sends a new request for the OAuth access token, providing the client ID, the client secret, and authorization code. Making this request from the server ensures that the client secret is kept protected and out of the client-side browser code. In this example, the request is made as part of an `async` function call and the sample makes use of the node-fetch module. There is a lot going on here. After sending the request, the server waits for the response from Spotify that returns the OAuth access token. When the access token is received, the server emits a `signedin` event that signals the instance of extension that initiated the sign in. The server redirects the client browser to the complete page and sends the client the access token, so that the extension can make the authorized requests from Spotify.

```javascript
app.get("/complete", async (req, res) => {
const code = req.query.code;
const sessionid = req.query.state;

const url = "https://accounts.spotify.com/api/token";
const parameters = {
grant_type: "authorization_code",
client_id: process.env.SPOTIFY_CLIENT_ID,
client_secret: process.env.SPOTIFY_CLIENT_SECRET,
code,
redirect_uri: "https://datadev-oauth-sign-in.glitch.me/complete"
};
const body = Object.keys(parameters)
.map(key => `${key}=${encodeURIComponent(parameters[key])}`)
.join("&");
const options = {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body
};

const response = await fetch(url, options);
const data = await response.json();

io.to(sessionid).emit("signedin", data.access_token);
res.sendFile(__dirname + "/views/complete.html");
});



```

### Pass the access token to the client and make requests

In response to the `signedin` event, the client checks to see if the OAuth access token was received. If the token is present, the client stores it locally and then uses the access token to make a new request to retrieve the user's top artist from Spotify. In case of error, the access token is removed from local storage and the user is asked to sign in again.

```javascript

socket.on("signedin", function(token) {
if (token) {
localStorage.setItem("spotifyAuth", token);
getTopArtist(token);
} else {
console.error("Token response was empty!");
}
});

// Get the top artist information with the token, if the request fails, prompt again for auth
async function getTopArtist(token) {
const url = "https://api.spotify.com/v1/me/top/artists";
const options = {
headers: {
Authorization: "Bearer " + token
}
};

try {
const response = await fetch(url, options);
const data = await response.json();
updateTopArtist(data);
} catch (error) {
localStorage.removeItem("spotifyAuth");
openSignInWindow();
console.error(error);
}
}

// Updated the text in the dashboard extension
function updateTopArtist(data) {
$("#signIn").hide();
$("#name").html("<b>" + data.items[0].name + "</b> is your favorite artist!");
}

```

### Next steps

* Use the sample code ([datadev-oauth-sign-in](https://glitch.com/~datadev-oauth-sign-in){:target="_blank"}) on Glitch as a starting point to build your own OAuth solution.

* Review the latest [OAuth 2.0 Security Best Current Practice](https://tools.ietf.org/html/draft-ietf-oauth-security-topics) draft documentation.
10 changes: 9 additions & 1 deletion docs/trex_release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,18 @@ See also: [Known Issues]({{site.baseurl}}/docs/trex_known_issues.html)

----

### Tableau 2021.1 Updates
*March 2021*

* You can now use Chrome version 80 and later to debug your dashboard extension in Tableau Desktop. If you are using Tableau 2021.1, or the latest maintenance releases of Tableau 2020.2, 2020.3, or 2020.4, you no longer need to use Chromium (version 79 or earlier) for debugging. For more information, see [Debug Extensions in Tableau Desktop]({{site.baseurl}}/docs/trex_debugging.html) and [Download the Chromium Browser]({{site.baseurl}}/docs/trex_debugging.html#download-the-chromium-browser).

* If you plan to implement OAuth in your dashboard extension, you'll want to check out [Add OAuth to Dashboard Extensions]({{site.baseurl}}/docs/trex_oauth.html), and the OAuth sample ([datadev-oauth-sign-in](https://glitch.com/~datadev-oauth-sign-in){:target="_blank"}) on Glitch.

* Because of browser changes in Tableau, dashboard extensions running with self-signed certificates (SSL) might not work in Tableau 2021.1, or in the most recent Tableau maintenance releases: 2020.2.7+, 2020.3.6+, and 2020.4.2+. For more information, see [Known Issues]({{site.baseurl}}/docs/trex_known_issues.html#unable-to-run-dashboard-extension-on-localhost-or-use-self-signed-certificates).

### Tableau Dashboard Extensions API version 1.4
*May 2020*


* Tableau Dashboard Extensions API library: `tableau.extensions.1.4.0.js` <br>(download or clone the Extensions API repository on [GitHub](https://github.com/tableau/extensions-api){:target="_blank"}). <br/>

About this release:
Expand Down