t.toda
t.toda

Reputation: 83

Getting 404 when trying to get an object from GCS using service account

I am writing a Google Apps Script code to download an object from Google Cloud Storage and return Blob. However, running getGcsObjectBlobTest() function in the following call gives me 404 response code with "Not Found" body.

I am using a service account with secret JSON key for the authentication. The service account has Storage Object Viewer role for the project and Storage Object Viewer Role/Creator roles for the bucket. I have not set any ACLs believing the official documentation which states "a user only needs permission from either Cloud IAM or an ACL to access a bucket or object".

The funny thing is if I switch the URL to "Link #2" in the code which I got from object details page on Google Cloud Console, I get 200 response with html body. So I guess my IAM is partially working at the least.

Can anyone tell me what is going on?

function getGcsObjectBlobTest(){
  var bucketName = 'vision_api_head_count';
  var objectName = 'Sample/Sample_01.jpg';

  getGcsObjectBlob(bucketName, objectName);
}


/*
 * Get Blob for a GCS object
 *
 * @param {string} bucketName - bucket name for the object
 * @param {string} objectName - name for the object
 * @returns Blob
 */
function getGcsObjectBlob(bucketName, objectName){
  var googleAppCredentials = getGoogleCredentials();
  var scope = 'https://www.googleapis.com/auth/devstorage.read_only';
  var token = getOAuth2TokenForGCP('annotatePrivateGcsImage', googleAppCredentials, scope);

  // Link #1: This URL is compliant with the official documentation. (https://cloud.google.com/storage/docs/json_api/v1/objects/get)
  var url = "https://storage.googleapis.com/storage/v1/b/" + bucketName + "/o/" + objectName + "?alt=media";

  // Link #2: This is "Link URL" from Google Cloud Console.
  // var url = "https://storage.cloud.google.com/" + bucketName + "/" + objectName + "?alt=media&orgonly=true&supportedpurview=organizationId";

  console.log(url);

  var options = {
    'method' : 'GET',
    'headers' : {
      'Authorization': 'Bearer ' + token,
    }
  }

  try{
    var blob = urlfetch(url, options, true).getBlob();
    console.log(blob.getName());
    console.log(blob.getContentType());
    console.log(blob.getBytes().length + " Bytes");

    return blob;
  }catch(e){
    throw new Error(e);
  }
}


/* Get OAuth2 token for specified credentials and scope
 * 
 * @requires {@link https://github.com/gsuitedevs/apps-script-oauth2|OAuth2 for Apps Script}
 * @param {string} serviceNamePrefix - prefix for the service name
 * @param {string} googleAppCredentials - JSON secret key for an account
 * @param {string} scope - {@link https://developers.google.com/identity/protocols/googlescopes|OAuth 2.0 Scopes for Google APIs}
 * @returns {string} OAuth 2.0 access token
 */
function getOAuth2TokenForGCP(serviceNamePrefix, googleAppCredentials, scope){
  var creds = JSON.parse(googleAppCredentials);

  var service = OAuth2.createService(serviceNamePrefix + "_" + creds.client_email)
      .setTokenUrl(creds.token_uri)
      .setPrivateKey(creds.private_key)
      .setIssuer(creds.client_email)
      .setSubject(creds.client_email)
      .setPropertyStore(PropertiesService.getScriptProperties())
      .setCache(CacheService.getScriptCache())
      .setLock(LockService.getScriptLock())
      .setScope(scope);

  if(service.hasAccess()){
    return service.getAccessToken();
  }else{
    console.error(service.getLastError());
    throw new Error('Could not get OAuth 2.0 access token!!');
  }
}

Upvotes: 1

Views: 9427

Answers (2)

t.toda
t.toda

Reputation: 83

I have just solved the problem by applying encodeURIComponent() to the object name which has "/" in it. They should mention this in their document

var url = "https://storage.googleapis.com/storage/v1/b/" + bucketName + "/o/" + encodeURIComponent(objectName) + "?alt=media";

edit: typo

Upvotes: 3

John Hanley
John Hanley

Reputation: 81454

You are using the wrong endpoint that is why you are getting a 404 (NOT FOUND ERROR). You are using the method called "Authenticated Browser Downloads" but the endpoint you are calling is for the JSON API.

Look up the URL in the Google Cloud Console.

The URL looks like this but there are several variations:

https://storage.cloud.google.com/BUCKET_NAME/OBJECT_NAME

Google Cloud Storage Request Endpoints

Authenticated Browser Downloads

Upvotes: -1

Related Questions