Setting Permissions in the Tableau with the REST API

This post in conjunction with the release of tableau_rest_api 1.1 at PyPI and on my GitHub repository. It contains much improved functionality for working with permissions — most of what is explained here is now built into the library.

Update: Please use at least version 1.1.4 where the update_permissions_by_gcap_obj_list method now only removes permissions that are going to be updated, rather than all as occurred in 1.1.3 and before.

Permissions are way easier to visualize and test in Tableau 9.0, as anyone who has seen the new Permissions views in Tableau Server can attest. The basic model has not changed much, but there is no “inherit” concept any more. Permissions are made of ‘capabilities’, and each capability can have one of three ‘modes’ — (1) Allow (2) Deny (3) Unspecified. If a user ever has a Deny set on a capability, either individually or in a Group they belong to, that capability will be denied.

Permissions can be set on Projects, Workbooks and Datasources. They are set either at a User or Group level. It is always best practice to assign permissions on groups and then add users to the appropriate groups. This way you can easily give additional users the correct permissions simply by adding them to the appropriate group.

The permissions on a Project become the defaults for content that is published into that project, but they don’t automatically apply down to the content within the project when changes are made, and the publisher can change from the default permissions in Desktop at publish time.

Replicating permissions

In most cases, when you are ‘assigning’ permissions, what you really want to do is clear all of the previously set permissions on a piece of content and then add the new permissions set. As previously mentioned, to clear permissions you have to know what permissions exist and then delete those out individually. This is what the delete_all_permissions_by_luids method is for. It follows these steps:

  1. Requests the permissions for the content object
  2. Converts the XML into a list of GranteeCapabilities objects via convert_capabilities_xml_into_obj_list
  3. Loops through each individual capability and deletes them on the object via delete_permissions_by_luids

Using this order of actions gives you the least number of wasted DELETE calls, keeping you from having an excessive number of 4XX errors to wade through.

With the delete_all_permissions_by_luids method in place to clear the way, it is simple to create methods that grab an existing permissions set and apply them to other content.

sync_project_permissions_to_contents(project_name_or_luid) completely replicates the ‘Assign Permissions to Contents’ button from the Tableau Server UI. You could run this on a schedule if you do not want publishers to ever be able to assign permissions that vary from the default permissions on the projects they are publishing to.

replicate_content_permissions is the method that underlies sync_project_permissions_to_contents; it lets you flexibly copy the permissions from any object to a set of LUIDS of the same type (project, workbook, or datasource).

Here’s an example to trigger ‘Assign Permissions to Contents’ for every project


from tableau_rest_api.tableau_rest_api import *
import urllib2

username = 'bhowell'
password = 'q1w2e3r4t5!'
server = 'http://127.0.0.1'

tab_srv = TableauRestApi(server, username, password, 'default')

logger = Logger('publish.log')
tab_srv.enable_logging(logger)

tab_srv.signin()

projects = tab_srv.query_projects()
project_dict = tab_srv.convert_xml_list_to_name_id_dict(projects)
for project_name in project_dict:
print 'Assigning Permissions to Contents for Project: {}'.format(project_name)
tab_srv.sync_project_permissions_to_contents(project_name)

Capability Names In the REST API

Per the REST API documentation , the capability names in the REST API don’t match up necessarily to how they are presented in the Tableau UI. If you follow the link, there is a useful table showing the translations between the two. To make this easy to remember, there are some translation functions, and the GranteeCapabilities class (more on this later) will handle either type of capability name.

Adding and deleting capabilities

You cannot update a capability directly via the REST API. You must first remove the previous capability, and then add the capability with the new mode. When deleting a capability, you must specify all aspects, including the mode it was set to:

DELETE /api/api-version/sites/site-id/workbooks/workbook-id/permissions/groups/group-id/capability-name/capability-mode

DELETE /api/api-version/sites/site-id/workbooks/workbook-id/permissions/users/user-id/capability-name/capability-mode

You’ll also notice there is a different URI depending on whether you are assigning permissions to a group or a user. For this reason, tableau_rest_api library methods that can assign the same permissions can only run for lists of groups or lists of users, but not both at once, unless there is a list of GranteeCapabilities objects which can specify.

One last note — if you try to DELETE a capability that does not exist, or ADD a capability where it has already been set, you will get back a 40X HTTP error, which often will raise an exception on your library of choice for handling HTTP requests. In most cases, you can keep going and just log the error message. The tableau_rest_api library handles this by looking at the error and the verb and raising its own RecoverableHTTPException, to be handled if the methods are working with permissions.

tableau_error = xml.xpath('//t:error', namespaces=self.__ns_map)
error_code = tableau_error[0].get('code')
self.log('Tableau REST API error code is: {}'.format(error_code))
if e.code in [400, 401, 402, 403, 404]:
    # If 'not exists' for a delete, recover and log
    if self.__http_verb == 'delete':
        self.log('Delete action attempted on non-exists, keep going')
        raise RecoverableHTTPException(e.code, error_code)

Permissions XML And GranteeCapabilities class

You can request permissions on a project, workbook or datasource, and it will come through like the following


<tsResponse xmlns="http://tableausoftware.com/api" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://tableausoftware.com/api http://tableausoftware.com/api/ts-api-2.0.xsd">
  <permissions>
    <project id="7f3480f4-f7c7-11e3-a8c1-abe0260841f0" name="Finance - Production">
      <owner id="b74884e9-783e-4ea2-9b1c-833f78648b47"/>
    </project>
    <granteeCapabilities>
      <group id="7f609a04-f7c7-11e3-b4c1-53bc24f2f2b1"/>
      <capabilities>
        <capability name="Read" mode="Allow"/>
        <capability name="WebAuthoring" mode="Allow"/>
        <capability name="Connect" mode="Allow"/>
        <capability name="ExportXml" mode="Allow"/>
        <capability name="Filter" mode="Allow"/>
        <capability name="AddComment" mode="Allow"/>
        <capability name="ViewComments" mode="Allow"/>
        <capability name="ExportData" mode="Allow"/>
        <capability name="ViewUnderlyingData" mode="Allow"/>
        <capability name="ExportImage" mode="Allow"/>
        <capability name="ShareView" mode="Allow"/>
        <capability name="Write" mode="Deny"/>
        <capability name="Delete" mode="Deny"/>
        <capability name="ChangeHierarchy" mode="Deny"/>
        <capability name="ChangePermissions" mode="Deny"/>
        <capability name="ProjectLeader" mode="Deny"/>
      </capabilities>
    </granteeCapabilities>
    <granteeCapabilities>
      <group id="7f609a04-f7c7-11e3-b622-ebb16bfa7f22"/>
      <capabilities>
        <capability name="Read" mode="Allow"/>
        <capability name="WebAuthoring" mode="Allow"/>
        <capability name="Connect" mode="Allow"/>
        <capability name="Write" mode="Allow"/>
        <capability name="ExportXml" mode="Allow"/>
        <capability name="Delete" mode="Allow"/>
        <capability name="Filter" mode="Allow"/>
        <capability name="AddComment" mode="Allow"/>
        <capability name="ViewComments" mode="Allow"/>
        <capability name="ExportData" mode="Allow"/>
        <capability name="ViewUnderlyingData" mode="Allow"/>
        <capability name="ExportImage" mode="Allow"/>
        <capability name="ShareView" mode="Allow"/>
        <capability name="ChangeHierarchy" mode="Allow"/>
        <capability name="ChangePermissions" mode="Allow"/>
        <capability name="ProjectLeader" mode="Allow"/>
      </capabilities>
    </granteeCapabilities>
    <granteeCapabilities>
      <user id="e6a07423-d264-49cf-b046-f58a97d898e8"/>
      <capabilities>
        <capability name="ShareView" mode="Deny"/>
        <capability name="WebAuthoring" mode="Deny"/>
        <capability name="ExportImage" mode="Deny"/>
        <capability name="Read" mode="Deny"/>
        <capability name="AddComment" mode="Deny"/>
        <capability name="Delete" mode="Deny"/>
        <capability name="Filter" mode="Deny"/>
        <capability name="ExportData" mode="Deny"/>
        <capability name="ExportXml" mode="Deny"/>
        <capability name="ChangeHierarchy" mode="Deny"/>
        <capability name="ViewUnderlyingData" mode="Deny"/>
        <capability name="ChangePermissions" mode="Deny"/>
        <capability name="Write" mode="Deny"/>
        <capability name="ViewComments" mode="Deny"/>
        <capability name="Connect" mode="Deny"/>
      </capabilities>
    </granteeCapabilities>
    <granteeCapabilities>
      <user id="a7c19dcf-f90e-4f82-8af7-4e66803baea2"/>
      <capabilities>
        <capability name="WebAuthoring" mode="Allow"/>
        <capability name="ViewComments" mode="Allow"/>
        <capability name="Read" mode="Allow"/>
        <capability name="ShareView" mode="Allow"/>
        <capability name="ExportData" mode="Allow"/>
        <capability name="ViewUnderlyingData" mode="Allow"/>
        <capability name="Filter" mode="Allow"/>
        <capability name="ExportImage" mode="Allow"/>
        <capability name="AddComment" mode="Allow"/>
      </capabilities>
    </granteeCapabilities>
  </permissions>
</tsResponse>

The set of permissions allowed to each ‘principal’ (group or user) comes enclosed in the granteeCapabilities tags. The solution in the tableau_rest_api is to create an object of the GranteeCapabilities class, and then make a list of these objects to represent the full set of permissions on a given piece of content. The GranteeCapabilities class has all of the allowable permissions built in, initializes with all capabilities set to None and will accept either the Tableau Server UI or the REST API names for Capabilities if you are using the set_capabilities method or set_capabilities_to_unspecified.

The add_permissions_by_gcap_obj_list(obj_type, obj_luid_s, gcap_obj_list) lets you pass a list of GranteeCapabilities objects to have the permissions set described therein applied to a list of objects of one type (project, workbook, or datasource). This is the method used from version 1.1.1 onward to replicate permissions from one object to another using a single call. It’s complementary method is update_permissions_by_gcap_obj_list which works similarly but first uses the delete_all_permissions_by_luids method. You can only use the add method after a delete, so in most situations you’ll want to use the update method.

There are older methods called add_permissions_by_luids / update_permissions_by_luids which will take a ‘capabilities_dict’, a simple way of representing the desired capabilities of form { capability_name : capability_mode , } , but you are better off creating a GranteeCapabilities object and assigning capabilities that way for all the error protection and ability to copy it provides.

CONclusion

There is a tremendous amount of power available in the REST API for administrating permissions, but understanding the model is essential or you’ll end up with tons of errors. This is why the tableau_rest_api library exists, to give a working model for anyone attempting to implement the full functionality.

Advertisements

One comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s