2

My main goal is to programatically download an app that is being distributed to the testers from firebase app distribution. The effect should be the same as pressing "Download" button on specified release from app distribution here:enter image description here

I've tried using Firebase REST API but there only seems to be to be the endpoint for listing the releases for specified app and uploading a release, there is no download option.

I've also checked firebase which is supporting firebase storage, however you can add apps releases without having a storage so I assume it's not where they're being stored.

The download URL generated by "Download" button follows the pattern https://firebaseappdistribution.googleapis.com/app-binary-downloads/firebase-app-distro/app-binaries/[PROJECT_ID]/[APP_ID]/e4faf167-0e3d-4b1a-82dd-44811c0b5e43.apk?token=[TOKEN]. The part after [APP_ID] identifies the app but I wasn't able to find where does it come from.

Is it even possible to download an app in such fashion? Are there any other ways to download the app?

Ciszko
  • 175
  • 1
  • 13

2 Answers2

5

After days of struggles I've finally found a solution! It's definitely not nice and is using Firebase internal API which isn't really available externally. My solution uses two sessions.

First one is an authorized session from google.auth.transport.requests.AuthorizedSession, credentials are generated as in the article: https://developers.google.com/docs/api/quickstart/python and then passed to AuthorizedSession. This session is working with documented firebase REST API, for example to fetch all released versions of the app.

The second one is used for internal Firebase API and requires cookies from browser session. In order to get the cookies I had to login to firebase and navigate to app distribution to download the app. I opened dev console (F12) and observed "Network" tab. Firebase is making 2 requests to download the app. First one is to get the download URL and the second one is just calling the URL returned by the previous request. First of two requests has the address like https://firebaseappdistribution-pa.clients6.google.com/v1/{app_name}:getLatestBinary?alt=json&key={api_key}. app_name is from authenticated session when getting all app releases. api_key can be copied from the browser URL or from the first request parameters. Sending a GET request with these parameters won't work, it also requires cookies and headers.

Sufficient headers are:

{
    'origin': 'https://console.firebase.google.com',
    'referer': 'https://console.firebase.google.com/',
    'x-goog-authuser': '<has to be copied from the first request from browser>'
}

When it comes to the cookies, things are slightly more complicated, the request requires following cookies: "APISID", "HSID", "SAPISID", "SID" and "SSID". I copied them from the first request. Then all that is missing is one additional cookie: "SAPISIDHASH", thanks to this answer, I've created my implementation in python:

def calculate_sapisid_hash(cookies):
    """Calculates SAPISIDHASH based on cookies. Required in authorization to download apk from firebase"""
    epoch = int(time())
    sha = sha1(' '.join([str(int(epoch)), cookies['SAPISID'], 'https://console.firebase.google.com']).encode())
    return f'SAPISIDHASH {int(epoch)}_{sha.hexdigest()}'

Finally by adding "SAPISIDHASH" to the cookies, we can send GET request on the first URL and get a valid response with "fileURL" and "fileSize" in it. Having "fileURL" we can save the final file with requests.get / wget / curl as there is no additional authorization needed

Ciszko
  • 175
  • 1
  • 13
4

The public REST API can (now) be used to get the download URL. See binaryDownloadUri in the Release resource.

You could list all releases for a given app_id (or pick the latest release using orderBy: createTime desc and pageSize: 1 to get the latest release).

Kai Bolay
  • 56
  • 1