How to remove Tableau Server users who own content | by Elliott Stam | Towards Data Science

This is also covered in our YouTube channel (Devyx): check it out.
Breakups are hard. Tableau users come and go, and lucky for us we have some tools at our fingertips that can help us make a clean break to avoid the unpleasant nuances of dealing with orphaned content.
This article explores one approach to making the removal of Tableau Server users a painless process. Whether your team adds and removes users manually or employs a bit of automation, the topics covered in this tutorial can help simplify the process by making sure the users being removed no longer own any content on the server, preventing users from landing in the ‘unlicensed’ graveyard and ensuring that no content on your server belongs to an unlicensed user.
This tutorial walks through using the Python tableau-api-lib package and is part of a series on how to tap Tableau Server like a keg, giving you control over Tableau Server’s REST API.
These tutorials assume you have Python 3 installed already. If you do not have Python 3 yet, this will get you started: guide to install Python.
When you remove a Tableau user who does not own any content, it’s a piece of cake. No errors pop up, and all you see is a pleasant confirmation message that the user has been removed successfully.
However, if you attempt to remove a Tableau user who owns content, you will get an error message informing you that the user cannot be removed because they are listed as an owner of content on the server.
When we try to remove a user who owns content, that user instead becomes a sort of Tableau zombie who is banished to the ‘unlicensed’ user role but still holds ownership of their content. Tableau doesn’t let any content become an orphan, so the owner’s spirit lingers around on Tableau Server until its unfinished business (content ownership) has been taken care of and the user can be removed completely.
So what can we do about that? In this tutorial, we demonstrate using the REST API to do the following:
Identify if the user being removed owns any contentIf the user owns content, designate a different user as the new ownerRemove the user
Identify if the user being removed owns any content
If the user owns content, designate a different user as the new owner
Remove the user
Let’s also highlight what objects a user can own in Tableau Server:
WorkbooksDatasourcesFlowsProjects
Workbooks
Datasources
Flows
Projects
Currently, the Tableau Server REST API does not support changing owners of projects. If your user owns a project, you will need to change that project ownership manually before removing the user (sorry, please don’t kill the messenger).
If you’re like me and want to be able to use the REST API to change project owners, please get out and upvote the idea on the Tableau forums.
Even if you’re a pro at these tutorials, do yourself a favor and pull the latest version of the library.
pip install --upgrade tableau-api-lib
New to this Python stuff? Don’t sweat it, you’ll catch on quick. Follow this getting started tutorial. That tutorial walks you through getting connected to Tableau Server using tableau-api-lib.
Use the code below as a template for getting connected to your server. In future steps, we will build upon this boiler plate with one-off lines of code. At the end of the article, you’ll find a consolidated block of code you can copy / paste for your convenience.
from tableau_api_lib import TableauServerConnectionfrom tableau_api_lib.utils.querying import queryingtableau_server_config = {        'my_env': {                'server': 'https://YourTableauServer.com',                'api_version': '<YOUR_API_VERSION>',                'username': '<YOUR_USERNAME>',                'password': '<YOUR_PASSWORD>',                'site_name': '<YOUR_SITE_NAME>',                'site_url': '<YOUR_SITE_CONTENT_URL>'        }}conn = TableauServerConnection(tableau_server_config, env='my_env')conn.sign_in()
Fun fact: you can also use personal access tokens, assuming you are on Tableau Server 2019.4 or newer. If you’re all about the access tokens, check out my article for details on how to use them.
The first piece of this puzzle is building ourselves a list of every content owner on the server. Once we have this, we can do a simple check to see if the user we plan on removing appears as an owner of any content on the server.
To build this comprehensive list of content owners, we need to:
get all project ownersget all workbook ownersget all datasource ownersget all flow ownersconsolidate the relevant information from each of the items above
get all project owners
get all workbook owners
get all datasource owners
get all flow owners
consolidate the relevant information from each of the items above
The full code is provided as a GitHub gist at the end of this article, so I won’t detail every step here, but let’s highlight how the tableau-api-lib comes in handy here.
Let’s use workbooks as an example. Here’s a pattern we can use to pull information for all our workbooks, which also allows us to extract information about the workbook owners.
workbooks_df = querying.get_workbooks_dataframe(conn)
The resulting workbooks_df will look like this:
Now, zooming in on that ‘owner’ column, there’s a nugget of information in there that we’d like to extract. That nugget is the ‘id’ value for the user who owns the content. Let’s extract that nested dict / JSON value by using tableau-api-lib’s flatten_dict_column util function.
workbooks_df = flatten_dict_column(workbooks_df, keys=['id'], col_name='owner')
If you take a look at the complete code at the end of the article, you’ll notice that we also add a ‘content_type’ column to each DataFrame we create.
workbooks_df['content_type'] = 'workbook'
This is simply there so that when we iterate over all the content owned by the user, we can identify the appropriate REST API endpoint to use. There are different endpoints for updating workbooks, datasources, and flows, and we need to call the correct tableau-api-lib method depending on the content type being updated.
If we follow this pattern for our workbooks, datasources, flows, and projects, then we can easily combine the resulting data (please see the code at the end of the article for an example of doing this).
Since we are changing ownership of content in order to remove a user, we need to designate another Tableau user to take ownership of the content. There are several valid approaches to this process.
You could randomly select any of your server administrator users and assign the content to one of them, you could select a user by name, or you could cherry pick user ID values and select whichever one tickles your fancy.
For this example, my test environment had a grand total of... two users. So I just called out my other user by name, like so:
new_owner_id = list(users_df[users_df['name'] == 'estam']['id']).pop()
The user whose username is ‘estam’ will be the new owner of any content that was previously owned by the user who is being removed.
Speaking of the user being removed, I define that in my workflow as well:
user_id_to_remove = list(users_df[users_df['name'] == 'estam2']['id']).pop()
Again, there are many valid ways to define these values. All that matters is that you have a user ID for the new owner, and a user ID for the user being removed.
The implementation I ran with iterates over all content owned by the user who is being removed. For each piece of content owned by that user, a function named change_content_owner is called.
For the exact details of how that function is defined, please see the full code at the end of this article.
In summary, the function contains some if statements which execute different REST API update calls depending on the type of content being processed. If a workbook needs an ownership change, then the ‘Update Workbook’ REST API endpoint is invoked. If a flow needs an ownership, then the ‘Update Flow’ REST API endpoint is invoked. And so on, and so on.
Now, given that projects cannot (yet) be updated via REST API calls, that is a bit inconvenient for us. Until this ability is added to the realm of REST API possibilities, we will need to change that ownership by hand. If you’re really into automation, you can modify the function I’ve defined here to trigger a Slack message or email if a user is being removed who owns a project. That message could alert your server administrator team that this user will need some manual attention to be successfully removed from the server.
Here’s some sample output from my process running. In this demonstration, the user being removed owned a workbook and a datasource.
Before we finish here, let’s recap on the limitations of the process and where those limitations come from.
You cannot remove users who own contentYou must change ownership of all content owned by a user before you can successfully remove the userYou cannot change ownership of projects via REST API calls (hopefully this functionality will be added soon; go vote on it!)Attempting to remove users who own content will result in the user’s role being modified to ‘unlicensed’ but they will still own the content.When you change who owns a workbook or datasource, any embedded credentials are invalidated.
You cannot remove users who own content
You must change ownership of all content owned by a user before you can successfully remove the user
You cannot change ownership of projects via REST API calls (hopefully this functionality will be added soon; go vote on it!)
Attempting to remove users who own content will result in the user’s role being modified to ‘unlicensed’ but they will still own the content.
When you change who owns a workbook or datasource, any embedded credentials are invalidated.
Want to know more? Check out Tableau’s documentation on changing content ownership.
That’s all for this tutorial on removing Tableau users who own content. It all boils down to not leaving any loose ends in terms of content ownership.
If your users don’t own any content, it’s a piece of cake!
If your users do own content, change ownership for your workbooks, datasources, and flows. If your users own projects, you’ll need to manually change the owner before being able to successfully remove the user from the server.
Thanks for tuning in! Reach out if your team needs help with Tableau Server REST API automation.
Use this GitHub gist as a template for integrating the concepts from this tutorial into your own workflow.