Search code examples
google-apps-scriptoauth-2.0google-apigoogle-oauthgoogle-cloud-sql

"Authorisation is required to perform that action" message, even after clicking "Allow"


I've recently run into an issue authorising a new Google App Script project, specifically one using the Cloud SQL admin API.

The same code exists in previously authorised GAS projects and works fine, but if I take a copy of the GAS project and try to run a function for the first time I'm unable to complete the authorisation process. The screens I'm going through are listed below:

  1. Authorisation Required. - clicked "Review Permissions"
  2. Choose an account to authorise the Google project. - clicked my account
  3. This app isn't verified! - clicked "Go to project (unsafe)"
  4. Google project wants access to this list of scopes.- clicked "Allow"
  5. Authorisation is required to perform that action.

The warning screen (3) is a recent addition to the process. I don't rememeber encountering it when I've created and run new projects earlier this year. I'm wondering if Google has made any changes to their security implementation of OAuth2.0 recently.

Also, this issue only seems to affect REST calls to the Cloud SQL admin API. In the same project mentioned above I am able to run functions which write data to BigQuery tables in the same Google project which is also hosting the Cloud SQL instances. Clearly some scopes and code can be made to work.

The "https://www.googleapis.com/auth/sqlservice.admin" scope is included in the list of those I requested and approve. I even tried manually editing the URL to add more scopes being requested and it still doesn't get me passed the "Authorisation is required to perform that action" screen.

Does anyone have any idea's?

EDIT:

The code in question which is triggering the authentication.

// Function to get the ip address of a given CloudSQL instance
function _getInstanceIpAddress_(projectId, sqlInstance) {

  var token = _getAuthenticationToken_();

  // Create the header authorisation  
  var headers = {
    "Authorization": "Bearer " + token
  };

  // Create the Cloud SQL instances get parameters
  var parameters = {
    "method": "get",    
    "headers": headers,
    "instance": sqlInstance,
    "project": projectId,
    "muteHttpExceptions": true
  };

  // Create the url of the sql instances get API    
  var api = "https://www.googleapis.com/sql/v1beta4/projects/" + projectId + "/instances/" + sqlInstance + "?fields=ipAddresses";   

  try {
    // Use the url fetch service to issue the https request and capture the response
    var response = UrlFetchApp.fetch(api, parameters);    

    // Extract the ip address of the instance from the response
    var content = JSON.parse(response.getContentText());

    return content.ipAddresses[0].ipAddress; 
  } catch(err) {
    _log_('ERROR', 'Getting ' + sqlInstance + ' instance ip address failed: ' + err);
    return null;
  }
}

function _getAuthenticationToken_() {
  // Check we have access to the service
  var service = getService();
  if (!service.hasAccess()) {
    var authorizationUrl = service.getAuthorizationUrl();
    _log_('INFO', 'Open the following URL and re-run the script: ' + authorizationUrl);
    return;
  }

  Logger.log('Passed Authentication');

  //Get the Access Token
  return service.getAccessToken();

  function getService() {
    // Create a new service with the given name. The name will be used when
    // persisting the authorized token, so ensure it is unique within the
    // scope of the property store.
    return OAuth2.createService('companyName-dev-service')

    // Set the endpoint URLs, which are the same for all Google services.
    .setAuthorizationBaseUrl('https://accounts.google.com/o/oauth2/auth')
    .setTokenUrl('https://accounts.google.com/o/oauth2/token')

    // Set the client ID and secret, from the Google Developers Console.
    .setClientId(CLIENT_ID)
    .setClientSecret(CLIENT_SECRET)

    // Set the name of the callback function in the script referenced
    // above that should be invoked to complete the OAuth flow.
    .setCallbackFunction('authCallback')

    // Set the property store where authorized tokens should be persisted.
    .setPropertyStore(PropertiesService.getUserProperties())

    // Set the scopes to request (space-separated for Google services).
    // this is admin access for the sqlservice and access to the cloud-platform:
    .setScope(
      'https://www.googleapis.com/auth/sqlservice.admin ' + 
      'https://www.googleapis.com/auth/cloud-platform')

    //Removed because this Should be covered by cloud-platform
    //'https://www.googleapis.com/auth/devstorage.read_write ' 

    // Below are Google-specific OAuth2 parameters.

    // Sets the login hint, which will prevent the account chooser screen
    // from being shown to users logged in with multiple accounts.
    .setParam('login_hint', Session.getActiveUser().getEmail())

    // Requests offline access.
    .setParam('access_type', 'offline')

    // Forces the approval prompt every time. This is useful for testing,
    // but not desirable in a production application.
    .setParam('approval_prompt', 'force');
  }

  function authCallback(request) {
    var cloudSQLService = getService();
    var isAuthorized = cloudSQLService.handleCallback(request);

    if (isAuthorized) {
      _log_('INFO', 'Access Approved');
      return HtmlService.createHtmlOutput('Success! You can close this tab.');
    } else {
      _log_('INFO', 'Access Denied');
      return HtmlService.createHtmlOutput('Denied. You can close this tab');
    }
  }
}

Solution

  • We had a similar issue with the Google Compute Engine API. Setting the scopes explicitly in the appsscript.json file as per this article solved it for us:

    "oauthScopes": [
        "https://www.googleapis.com/auth/spreadsheets.readonly",
        "https://www.googleapis.com/auth/userinfo.email",
        "https://www.googleapis.com/auth/script.container.ui",
        "https://www.googleapis.com/auth/script.external_request",
        "https://www.googleapis.com/auth/spreadsheets",
        "https://www.googleapis.com/auth/compute",
        "https://www.googleapis.com/auth/cloud-platform"
      ],
    

    enter image description here