Search code examples
google-apps-scriptgoogle-cloud-platformgoogle-oauthadd-ongoogle-apps-marketplace

Getting an error when trying to query the installations of add-on


Goal:

I have a public Google Workspace Marketplace editor add-on for Google Forms. I would like to query the installations and uninstallations via the Google Workspace Marketplace API within Google Apps Script.

Approach:

  • I have created a new Google Apps Script and linked it to the same GCP project as the add-on is hosted
  • My understanding is that when making the request to the Google Workspace Marketplace API from the script with my own user, it is going to use the OAuth consent screen and the corresponding scopes of the add-on. Thus resulting in an error. For this reason I need to use a service account to make the API request and to use separate scopes.
  • I am using the Compute Engine default service account. I have already enabled domain-wide delegation and the Google Workspace Marketplace OAuth Client for this service account.
  • Last but not least I have created a service account key file in JSON format and copy/pasted the content into my script.

Script:

const serviceAccount = {
  "type": "service_account",
  "project_id": "<<MY PROJECT ID>>",
  "private_key_id": "<<MY PRIVATE KEY>>",
  "private_key": "<<MY PRIVATE KEY>>",
  "client_email": "<<MY CLIENT EMAIL>>",
  "client_id": "<<MY CLIENT ID>>",
  "auth_uri": "https://accounts.google.com/o/oauth2/auth",
  "token_uri": "https://oauth2.googleapis.com/token",
  "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
  "client_x509_cert_url": "<<MY CERT URL>>"
};

const { private_key, client_email, project_id } = serviceAccount;

const createService = (name, serviceAccount, scopes, userToImpersonate) => {
  return OAuth2.createService(name)
    .setSubject(userToImpersonate)
    .setTokenUrl('https://accounts.google.com/o/oauth2/token')
    .setPrivateKey(serviceAccount.private_key)
    .setIssuer(serviceAccount.client_email)
    .setPropertyStore(PropertiesService.getScriptProperties())
    .setCache(CacheService.getUserCache())
    .setLock(LockService.getUserLock())
    .setScope(scopes);
};

const sendRequest = (url, oauthParams) => {
  const { serviceName, serviceAccount, scopes, userToImpersonate } =
    oauthParams;

  const oauthService = createService(
    serviceName,
    serviceAccount,
    scopes,
    userToImpersonate
  );

  if (!oauthService.hasAccess()) {
    console.log('ERROR IS ' + oauthService.getLastError());
    return;
  }

  const headers = {
    Authorization: `Bearer ${oauthService.getAccessToken()}`,
  };

  const options = {
    method: 'get',
    headers,
    muteHttpExceptions: true,
  };

  return UrlFetchApp.fetch(url, options).getContentText();
};

const getLicenseNotifications = () => {
  const url = 'https://appsmarket.googleapis.com/appsmarket/v2/licenseNotification/<<MY APP ID>>?maxResults=2';
  const oauthParams = {
    serviceName: 'GWMservice',
    serviceAccount,
    scopes: ['https://www.googleapis.com/auth/appsmarketplace.license'],
    userToImpersonate: '<<MY USER>>@<<MY DOMAIN>>.com',
  };

  console.log(sendRequest(url, oauthParams));
};

Google Apps Script manifest:

{
  "timeZone": "Europe/Berlin",
  "dependencies": {
    "libraries": [
      {
        "userSymbol": "OAuth2",
        "version": "43",
        "libraryId": "1B7FSrk5Zi6L1rSxxTDgDEUsPzlukDsi4KGuTMorsTQHhGBzBkMun4iDF"
      }
    ]
  },
  "exceptionLogging": "STACKDRIVER",
  "runtimeVersion": "V8",
  "oauthScopes": [
    "https://www.googleapis.com/auth/appsmarketplace.license",
    "https://www.googleapis.com/auth/script.external_request"
  ]
}

Error:

I am receiving the following error:

{
  "error": {
    "code": 503,
    "message": "Temporary error. Please try later.",
    "errors": [
      {
        "message": "Temporary error. Please try later.",
        "domain": "global",
        "reason": "backendError"
      }
    ]
  }
}

Solution

  • Unfortunately this is a case of the documentation being wrong (as of April 8th, 2023). The maxResults parameter is actually expressed as max-results and the same applies to start-token. Once you change the query parameter retrieval (and pagination) should work.