Using multiple service accounts to work across projects and lock down permissions with Apps Script and Goa

Apps Script / Oauth2 (Intermediate level) posted on 6th December 2018

I use Cloud storage quite extensively as it's a great way to share data across platforms and projects. Apps Script projects can share data with Node apps for example. There are quite a few articles about this topic on this site of related topics such as
There are a number of ways to get access tokens to get to cloud storage such as editing the manifest file (the simplest), or using Goa (or some other library) to create an interactive OAUTH2 flow or service account flow. The choice all depends on who owns the data (the user or the app), and whether you want to share across platforms or projects. Generally though, I want to share data belonging to an App across platforms and potentially across cloud projects too. This makes the service account the simplest and easiest choice for me, so for Apps Script I use these libraries.

  • for Oauth2, Goa -  1v_l4xN3ICa0lAW315NQEzAHPSoNiFdWHsMEwj2qA5t9cgZ5VWci2Qxv2
  • for accessing cloud storage, GcsStore - 1w0dgijlIMA_o5p63ajzcaa_LJeUMYnrrSgfOzLKHesKZJqDCzw36qorl

Setting up

Like all Goa projects
  • create your service account in cloud storage and download the JSON file.
  • create and run one-off function to set up the Goa service in your property service referencing that file  - something like this. You can delete it after you've run it. Neither the function nor the file are required again.
function oneOffgcs() {

  // used by all using this script
  var propertyStore = PropertiesService.getScriptProperties();
 // DriveApp.createFile(blob).. comment forces Drive scope
 cGoa.GoaApp.setPackage (propertyStore ,
    cGoa.GoaApp.createServiceAccount (DriveApp , {
    packageName: 'gcs_artifacts',
    fileId:'xxx-the json file id-xxx',
    scopes : cGoa.GoaApp.scopesGoogleExpand (['cloud-platform']),
    service:'google_service'
  }));

}
  •  Now you can use goa to get tokens whenever they are required. To simplify things, let's use a namespace to handle all that, which will set everything up whenever the script is invoked.
var gcs = (function (ns) {
  
  function getStore  (account , bucket , folderKey) {
    const goa = cGoa.make (account,PropertiesService.getScriptProperties());

    return new cGcsStore.GcsStore()
      .setAccessToken(goa.getToken())
      .setBucket(bucket)
      .setExpiryLog (false)
      .setFolderKey (folderKey);
  };
  
  ns.init = function () {
    ns.artifacts = getStore ('gcs_artifacts','project-artifacts',"" ); 
    return ns;
  };
  
  return ns;
})({});
  • It's now easy to read and write to storage with a pattern like this, where all the token management is happening behind the scenes.
  var myob = gcs.artifacts.get (somekey);


Using multiple service accounts.

That's all fine, but sometimes you want to read in one part of the app, and write or delete or upload in another. It's always best to give the least possible permissions, so you can set up multiple service accounts with different permissions, and further tailor the scopes when you initialise your Goa. This needs  to be able to read and write to cloud storage, and occasionally delete and upload. Rather than using a single service account, and a single scope, you can use different accounts for different operations. Another usage would be to access data from multiple projects or different buckets, each of which could need a different service account.
  • Create multiple service accounts with appropriate, different permissions according to what access is required.
  • Modify and run the one off function to maintain multiple service accounts.
function oneOffgcs() { 

  // used by all using this script
  var propertyStore = PropertiesService.getScriptProperties();
 // DriveApp.createFile(blob).. comment forces Drive scope
 cGoa.GoaApp.setPackage (propertyStore , 
    cGoa.GoaApp.createServiceAccount (DriveApp , {
    packageName: 'gcs_artifacts',
    fileId:'xxx-the json file id-xxx',
    scopes : cGoa.GoaApp.scopesGoogleExpand (['cloud-platform']),
    service:'google_service'
  }));


 cGoa.GoaApp.setPackage (propertyStore , 
    cGoa.GoaApp.createServiceAccount (DriveApp , {
    packageName: 'gcs_artifacts_delete',
    fileId:'xxx-the other json file id-xxx',
    scopes : cGoa.GoaApp.scopesGoogleExpand (['devstorage.full_control']),
    service:'google_service'
  }));

 cGoa.GoaApp.setPackage (propertyStore , 
    cGoa.GoaApp.createServiceAccount (DriveApp , {
    packageName: 'gcs_another_project',
    fileId:'xxx-the other project json file id-xxx',
    scopes : cGoa.GoaApp.scopesGoogleExpand (['
cloud-platform']),
    service:'google_service'
  }));
}

  • add more handles to the gcs namespace
var gcs = (function (ns) {
  
  function getStore  (account , bucket , folderKey) {
    const goa = cGoa.make (account,PropertiesService.getScriptProperties());

    return new cGcsStore.GcsStore()
      .setAccessToken(goa.getToken())
      .setBucket(bucket)
      .setExpiryLog (false)
      .setFolderKey (folderKey);
  };
  
  ns.init = function () {
    ns.artifacts = getStore ('gcs_artifacts','bucket-artifacts',"" );
    ns.artifactsDelete = getStore ('gcs_artifacts_delete','bucket-artifacts',"" );
    ns.anotherProject = getStore ('gcs_another_project','bucket-from-another-project',"" );
    return ns;
  };
  
  return ns;
})({});
    
  • Now you can use multiple handles depending on the operation - this minimises the risk of accidentally deleting objects (using the wrong handle wouldn't have the permission to do so), or seamlessly working across projects.
var myob = gcs.artifacts.get (somekey);
var result = gcs.artifactsDelete.remove (somekey);
var otherob = gcs.anotherProject.get (somekey);

For more like this, see Google Apps Scripts snippets. Why not join our forumfollow the blog or follow me on twitter to ensure you get updates when they are available. 

You want to learn Google Apps Script?

Learning Apps Script, (and transitioning from VBA) are covered comprehensively in my my book, Going Gas - from VBA to Apps script, available All formats are available now from O'Reilly,Amazon and all good bookshops. You can also read a preview on O'Reilly

If you prefer Video style learning I also have two courses available. also published by O'Reilly.
Google Apps Script for Developers and Google Apps Script for Beginners.



For more like this, see Google Apps Scripts snippets. Why not join our forumfollow the blog or follow me on twitter to ensure you get updates when they are available. 





Comments