How to merge two users' content ownership and permissions in Jira
Platform notice: Server and Data Center only. This article only applies to Atlassian products on the Server and Data Center platforms.
Support for Server* products ended on February 15th 2024. If you are running a Server product, you can visit the Atlassian Server end of support announcement to review your migration options.
*Except Fisheye and Crucible
Where a workaround involves running SQL scripts directly on your database, we strongly recommend you do this first on a non-production, staging or test environment before running the scripts on your production database. We also strongly recommend you take a backup of your data before making any modifications via SQL scripts.
Problem
When migrating users from one LDAP / Active Directory to another (such as during a company merge or domain migration), content ownership will not be migrated to the new usernames if the new user key and Unique Identifier do not match the existing unique user key and Unique Identifier. When the Unique Identifier for each user changes across directories, Jira has no idea how to map the old usernames to the new usernames.
Disclaimer
Atlassian support does not recommend following these steps unless absolutely necessary (i.e. the second directory has already been added and users have content under both usernames), as they require a lot of manual database manipulation with a lot of opportunity for error.
Instead, we recommend renaming the users to their new usernames in the existing LDAP/AD directory, performing directory sync in Jira, then adding the second directory to Jira as in Migrate to LDAP directory using different username formats for Jira server. This will effectively preserve content ownership and permissions for each user in the new LDAP/AD directory.
Diagnosis
These steps are intended to fix Jira content ownership after an environmental change to LDAP, wherein the original directory is no longer used and the new directory will be used going forward. This only applies when migrating from one external directory to another, where the usernames change and both directories have already been added to Jira (meaning users have created content under both usernames).
For example:
Old | New | |
---|---|---|
Username | pedro.souza@atlassian.com | psouza@newdomain.com |
Unique Identifier / objectGUID (Pre Jira 8.4) | 123456a | 555666b |
Unique Identifier / objectGUID (Jira 8.4+) | JIRAUSER10000 | JIRAUSER90000 |
The Unique Identifier is defined as this setting (objectGUID is the attribute in this example):
This does not apply if the existing directory is modified and the usernames are updated, keeping the same Unique Identifiers. That type of change does not require these steps and after the next directory sync, Jira will see the updated usernames, assuming they still have the original unique guid/identifier.
Resolution
Always back up your data before performing any modifications to the database. If possible, test any alter, insert, update, or delete SQL commands on a staging server first.
When renaming users and their keys, there are a few important fields that need to be updated. You will also need a temp table to map the old usernames to the new usernames.
cwd_user
table: user_name and lower_user_name columnsapp_user
table: user_key and lower_username columns
This article assumes that the renaming of the lower_user_name fields has already occurred. Please Ensure that all "lower" username columns are indeed all lower-case entries when entering the new usernames.
The queries below are written for PostgreSQL and may need syntax changes for other database flavors.
Step 1: Stop Jira
As we are manipulating data, the instance must be stopped before proceeding. This will prevent any new records from being created or modified during the update process below.
Step 2: Create a temp table to map old and new user keys
CREATE TABLE tmp_app_user_migration
(
old_user_key varchar(255),
new_user_key varchar(255)
);
If your new and old users have the same e-mail address, the following script may help mapping them more easily
Step 3: Update app_user table
Obtain the old & new user keys:
select user_key, lower_user_name from app_user
You will need to find a way to manually keep track of which usernames are old vs new here, and their keys.
In some cases, you may see user_keys reflecting the lower_user_name, and in others you might see users using a unique identifier ID, such as JIRAUSER10000.
This is expected since the GDPR changes that were implemented with Jira 8.4. Users that have the user_key reflecting the lower_user_name were created prior to this change.
Next, insert the old_user_key and new_user_key into the temp table, for all the users being merged:
INSERT INTO tmp_app_user_migration (old_user_key, new_user_key)
VALUES
('<olduserkey1>', '<newuserkey1>'),
('<olduserkey2>', '<newuserkey2>'),
('<olduserkey3>', '<newuserkey3>'),
...
('<olduserkeyN>', '<newuserkeyN>');
Example:
INSERT INTO tmp_app_user_migration (old_user_key, new_user_key) VALUES ('123456a', '555666b'), ('JIRAUSER10000', 'JIRAUSER90000');
Step 4: Migrate the content ownership for the new user_key:
Run the following queries to leave all the content associated to one of the user accounts:
These updates should work for PostgreSQL, MySQL, and MSSQL.
update audit_item set object_id = tmp_app_user_migration.new_user_key from tmp_app_user_migration where object_id = tmp_app_user_migration.old_user_key;
update audit_item set object_name = tmp_app_user_migration.new_user_key from tmp_app_user_migration where object_name = tmp_app_user_migration.old_user_key;
update audit_log set author_key= tmp_app_user_migration.new_user_key from tmp_app_user_migration where author_key= tmp_app_user_migration.old_user_key;
update audit_log set object_id = tmp_app_user_migration.new_user_key from tmp_app_user_migration where object_id = tmp_app_user_migration.old_user_key;
update audit_log set object_name = tmp_app_user_migration.new_user_key from tmp_app_user_migration where object_name = tmp_app_user_migration.old_user_key;
update changegroup set author = tmp_app_user_migration.new_user_key from tmp_app_user_migration where author = tmp_app_user_migration.old_user_key;
update changeitem set newvalue = tmp_app_user_migration.new_user_key from tmp_app_user_migration where newvalue = tmp_app_user_migration.old_user_key;
update changeitem set oldvalue = tmp_app_user_migration.new_user_key from tmp_app_user_migration where oldvalue = tmp_app_user_migration.old_user_key;
update component set lead = tmp_app_user_migration.new_user_key from tmp_app_user_migration where lead = tmp_app_user_migration.old_user_key;
update customfieldvalue set stringvalue = tmp_app_user_migration.new_user_key from tmp_app_user_migration where stringvalue = tmp_app_user_migration.old_user_key;
update favouriteassociations set username = tmp_app_user_migration.new_user_key from tmp_app_user_migration where username = tmp_app_user_migration.old_user_key;
update fileattachment set author = tmp_app_user_migration.new_user_key from tmp_app_user_migration where author = tmp_app_user_migration.old_user_key;
update filtersubscription set username = tmp_app_user_migration.new_user_key from tmp_app_user_migration where username = tmp_app_user_migration.old_user_key;
update jiraaction set author = tmp_app_user_migration.new_user_key from tmp_app_user_migration where author = tmp_app_user_migration.old_user_key;
update jiraaction set updateauthor = tmp_app_user_migration.new_user_key from tmp_app_user_migration where updateauthor = tmp_app_user_migration.old_user_key;
update jiraissue set reporter = tmp_app_user_migration.new_user_key from tmp_app_user_migration where reporter = tmp_app_user_migration.old_user_key;
update jiraissue set assignee = tmp_app_user_migration.new_user_key from tmp_app_user_migration where assignee = tmp_app_user_migration.old_user_key;
update jiraissue set creator = tmp_app_user_migration.new_user_key from tmp_app_user_migration where creator = tmp_app_user_migration.old_user_key;
update os_historystep set caller = tmp_app_user_migration.new_user_key from tmp_app_user_migration where caller = tmp_app_user_migration.old_user_key;
update portalpage set username = tmp_app_user_migration.new_user_key from tmp_app_user_migration where username = tmp_app_user_migration.old_user_key;
update project set lead = tmp_app_user_migration.new_user_key from tmp_app_user_migration where lead = tmp_app_user_migration.old_user_key;
update projectroleactor set roletypeparameter = tmp_app_user_migration.new_user_key from tmp_app_user_migration where roletypeparameter = tmp_app_user_migration.old_user_key;
update schemepermissions set perm_parameter = tmp_app_user_migration.new_user_key from tmp_app_user_migration where perm_parameter = tmp_app_user_migration.old_user_key and perm_type = 'user';
update searchrequest set authorname = tmp_app_user_migration.new_user_key from tmp_app_user_migration where authorname = tmp_app_user_migration.old_user_key;
update searchrequest set username = tmp_app_user_migration.new_user_key from tmp_app_user_migration where username = tmp_app_user_migration.old_user_key;
update worklog set author = tmp_app_user_migration.new_user_key from tmp_app_user_migration where author = tmp_app_user_migration.old_user_key;
update worklog set updateauthor = tmp_app_user_migration.new_user_key from tmp_app_user_migration where updateauthor = tmp_app_user_migration.old_user_key;
update favouriteassociations set username = tmp_app_user_migration.new_user_key from tmp_app_user_migration where username = tmp_app_user_migration.old_user_key
and entityid not in (select U1.entityid from favouriteassociations U1, favouriteassociations U2
where U1.username = tmp_app_user_migration.old_user_key and U2.username = tmp_app_user_migration.new_user_key
and U2.entityid = U1.entityid
and U2.entitytype = U1.entitytype
and U1.entitytype = 'PortalPage')
and entitytype = 'PortalPage';
update favouriteassociations set username = tmp_app_user_migration.new_user_key from tmp_app_user_migration where username = tmp_app_user_migration.old_user_key
and entityid not in (select U1.entityid from favouriteassociations U1, favouriteassociations U2
where U1.username = tmp_app_user_migration.old_user_key and U2.username = tmp_app_user_migration.new_user_key
and U2.entityid = U1.entityid
and U2.entitytype = U1.entitytype
and U1.entitytype = 'SearchRequest')
and entitytype = 'SearchRequest';
update sharepermissions set param1 = tmp_app_user_migration.new_user_key from tmp_app_user_migration where param1 = tmp_app_user_migration.old_user_key
and entityid not in (select U1.entityid from sharepermissions U1, sharepermissions U2
where U1.param1 = tmp_app_user_migration.old_user_key and U2.param1 = tmp_app_user_migration.new_user_key
and U2.entityid = U1.entityid
and U2.entitytype = U1.entitytype
and U1.entitytype = 'PortalPage'
and U1.sharetype = 'user')
and entitytype = 'PortalPage'
and sharetype = 'user';
update sharepermissions set param1 = tmp_app_user_migration.new_user_key from tmp_app_user_migration where param1 = tmp_app_user_migration.old_user_key
and entityid not in (select U1.entityid from sharepermissions U1, sharepermissions U2
where U1.param1 = tmp_app_user_migration.old_user_key and U2.param1 = tmp_app_user_migration.new_user_key
and U2.entityid = U1.entityid
and U2.entitytype = U1.entitytype
and U1.entitytype = 'SearchRequest'
and U1.sharetype = 'user')
and entitytype = 'SearchRequest'
and sharetype = 'user';
update userassociation set source_name = tmp_app_user_migration.new_user_key from tmp_app_user_migration where source_name = tmp_app_user_migration.old_user_key
and sink_node_id not in (select U1.sink_node_id from userassociation U1, userassociation U2
where U1.source_name = tmp_app_user_migration.old_user_key and U2.source_name = tmp_app_user_migration.new_user_key
and U2.sink_node_id = U1.sink_node_id);
update userhistoryitem set username = tmp_app_user_migration.new_user_key from tmp_app_user_migration where username = tmp_app_user_migration.old_user_key
and entityid not in (select U1.entityid from userhistoryitem U1, userhistoryitem U2
where U1.username = tmp_app_user_migration.old_user_key and U2.username = tmp_app_user_migration.new_user_key
and U2.entityid = U1.entityid
and U2.entitytype = U1.entitytype);
For MySQL, the last six queries from above should be changed to the ones below:
update favouriteassociations as FA, tmp_app_user_migration TA
set FA.username = TA.new_user_key
where FA.username = TA.old_user_key
and FA.entityid not in (select entity_id from (select U1.entityid as entity_id from favouriteassociations U1, favouriteassociations U2, tmp_app_user_migration AS TA
where U1.username = TA.old_user_key
and U2.username = TA.new_user_key
and U2.entityid = U1.entityid
and U2.entitytype = U1.entitytype
and U1.entitytype = 'PortalPage') as entity)
and FA.entitytype = 'PortalPage';
update favouriteassociations as FA, tmp_app_user_migration TA
set FA.username = TA.new_user_key
where FA.username = TA.old_user_key
and FA.entityid not in (select entity_id from (select U1.entityid as entity_id from favouriteassociations U1, favouriteassociations U2, tmp_app_user_migration AS TA
where U1.username = TA.old_user_key
and U2.username = TA.new_user_key
and U2.entityid = U1.entityid
and U2.entitytype = U1.entitytype
and U1.entitytype = 'SearchRequest') as entity)
and FA.entitytype = 'SearchRequest';
update sharepermissions as SP, tmp_app_user_migration TA
set SP.param1 = TA.new_user_key
where SP.param1 = TA.old_user_key
and SP.entityid not in (select entity_id from (select U1.entityid as entity_id from sharepermissions U1, sharepermissions U2, tmp_app_user_migration AS TA
where U1.param1 = TA.old_user_key
and U2.param1 = TA.new_user_key
and U2.entityid = U1.entityid
and U2.entitytype = U1.entitytype
and U1.entitytype = 'PortalPage'
and U1.sharetype = 'user') as entity)
and SP.entitytype = 'PortalPage'
and SP.sharetype = 'user';
update sharepermissions as SP, tmp_app_user_migration TA
set SP.param1 = TA.new_user_key
where SP.param1 = TA.old_user_key
and SP.entityid not in (select entity_id from (select U1.entityid as entity_id from sharepermissions U1, sharepermissions U2, tmp_app_user_migration AS TA
where U1.param1 = TA.old_user_key
and U2.param1 = TA.new_user_key
and U2.entityid = U1.entityid
and U2.entitytype = U1.entitytype
and U1.entitytype = 'SearchRequest'
and U1.sharetype = 'user') as entity)
and SP.entitytype = 'SearchRequest'
and SP.sharetype = 'user';
update userassociation as UA, tmp_app_user_migration AS TA
set UA.source_name = TA.new_user_key
where UA.source_name = TA.old_user_key
and sink_node_id not in (select sink_id from (select U1.sink_node_id as sink_id from userassociation U1, userassociation U2, tmp_app_user_migration AS TA
where U1.source_name = TA.old_user_key
and U2.source_name = TA.new_user_key
and U2.sink_node_id = U1.sink_node_id) as sink);
update userhistoryitem as UA, tmp_app_user_migration AS TA
set UA.username = TA.new_user_key
where UA.username = TA.old_user_key
and entityid not in (select entity_id from (select U1.entityid as entity_id from userhistoryitem U1, userhistoryitem U2, tmp_app_user_migration AS TA
where U1.username = TA.old_user_key and U2.username = TA.new_user_key
and U2.entityid = U1.entityid
and U2.entitytype = U1.entitytype)as entity);
Disclaimer
You may have AO_ tables provided by Plugins that may associate data directly in the user_key.
We recommend checking in the AO tables of your JIRA database and follow the same UPDATE model to adjust those tables.
Below you can find a list of the most common AO tables which are known to reference the user_key directly, you may include these tables in the migration process in case you have these tables in your instance database:
Step 5: Remove leftovers
The last 2 updates may leave left over users when we already have users watching issues with both the new and old user keys so we need to remove the rows associated to the old user keys.
delete from userassociation where source_name in (select user_key in tmp_app_user_migration old_user_key);
delete from userhistoryitem where username in (select user_key in tmp_app_user_migration old_user_key);
Step 6: Start JIRA
Start Jira and rebuild the index, so it can take effect.
- We recommend leaving the temp tables in place, as they may be useful in the future.
Optional:
In case this procedure has left empty leftover users that are no longer required and you would like to completely remove these empty user profiles, eg: Internal Users that are no longer needed after merging the profiles.
You can follow the additional procedure below to completely delete users from the database: Delete users from the database