Hi everyone!
I’m writing here in the developer community to provide details on our proposed solution to AC-1528 - Add-ons break after customer data imports. We’ve had lots of feedback from customers, partners and account managers that this problem is causing pain on a regular basis, especially as the frequency of server to cloud migrations increases.
We’ve been able to implement a fix for this problem that also requires a change to the Connect app. In this post, I’ll explain the problem in a bit of detail and then describe the solution and what change is required.
Why do site imports break apps?
Firstly we need to distinguish a site import from a site restore from backup. Restore from backup is an operation at the database level within a single site. When a site is restored, its clientKey does not change. Import, on the other hand, is the process of taking the content of a site, potentially a different site, and bringing it into an existing site. It will result in a new clientKey value.
When an import occurs, installed Connect apps are uninstalled in the target site. Any Connect apps installed in the source site are also not included. This is done so that any install information from the original site is not included, so that two distinct sites do not end up with colliding installation records from the app’s perspective.
Although the site URL stays the same, e,g foo.atlassian.net, an import effectively creates a new logical site. For this reason the clientKey of the site changes. This change of clientKey indicates that this is effectively a new site. The old version of the site, including any app install records associated with it, is removed at this point in time. Due to some underlying architectural limitations, apps are not notified when this occurs.
Loss of secret synchronization
The import process causes a loss of app install state and secret synchronization. This happens in the following scenario:
- The app is installed in a site, with the original clientKey.
- The site undergoes an import. App install records are removed and the site’s clientKey changes.
- Re-installation of the app is attempted in the site after the import is complete. As Connect has no install records, the install request is unsigned.
- The app sees an installation request for the site but already has an existing installation record for a different clientKey. The app (correctly) rejects the install as unauthorized (401), as it is expecting the request to be signed with the original clientKey.
Until recently, we could only resolve this desynchronisation manually, in co-ordination with the app developer (eg. via a customer support request).
Our solution to the problem.
When Atlassian encounters a HTTP 401 (Unauthorized) response to an install request, we will retry the installation by sending a follow-up installation request; this time signed with the app’s current secret.
How should apps handle this situation?
Your app should continue to validate that requests from Atlassian are validly signed. Your app should continue to reject un-signed installation requests as normal (excepting the first, unsigned install for a site).
When you receive a signed install request for a site that has an existing installation record under a different clientKey, your app should associate the site with the new clientKey. Older clientKeys for that site should no longer be used for signing outbound requests to the site.
Orphaned installation records should be retained for 30 days before being cleaned up.
Recommendations for atlassian-connect-express apps
atlassian-connect-express
always uses the clientKey as the unique identifier for an installation. So, an install following an import will automatically behave as a new install. The existing install with the old clientKey remains, but is effectively inactive. If your app ever searches for an installation record by its base URL, you should ensure you are finding the latest clientKey entry associated with the baseURL in the database.
Recommendations for atlassian-connect-spring-boot apps
atlassian-connect-spring-boot
will also treat an install following an import as a new install and create a new entry keyed on the new clientKey. For most operations, atlassian-connect-spring-boot
uses the request context to determine the clientKey to use in making any API calls.
atlassian-connect-spring-boot
provides a method to look-up an installation based on the base URL. This can be used to initiate an API call when there is no incoming request context. Until recently, the returned record (and therefore its underlying clientKey) was non-deterministic (see ACSPRING-117: Host lookup by base URL is not deterministic. This is fixed in release 2.1.0 of atlassian-connect-spring-boot
so that this method always returns the most recent clientKey for the site.
Recommendations for custom apps
If your app is a custom development or uses a separate framework from the above, you should observe the following guidelines:
Installs which use an existing clientKey
If Atlassian has no record we will send an unsigned install request. This must be rejected with a HTTP 401 response. After failing an unsigned install, Atlassian will fall back to an install request signed with the app’s standard key. If this is valid you may proceed to update the install record. If the install request is not signed your app must never replace an existing install.
Installs which use a new clientKey.
- If your app only makes API calls in the context of an incoming request, e.g. on receiving a webhook call-back, you may simply create a new record for the new clientKey. Any existing install records for the same site will become inactive.
- If your app does host base URL look ups and your app has an existing install for this base URL, you should reject the unsigned install request. Connect will retry with a signed install. If this is valid you should create a new record. Any base URL lookups should use the latest received install information.
- If your app iterates across all install records, you should operate as though you perform base URL lookups (described above) and you should ensure that you only accept signed requests to add an entry for an existing site. Also, the iteration should only use records which are active - i.e. the latest received for a given site.
In all cases, please observe the golden rule:
Your app must never overwrite or remove an installation unless the installation request has a valid signature from Atlassian.