GASを使った認可パターン

Wada Gaku
11 min readDec 7, 2018

--

この記事は Google Cloud Platform その1 Advent Calendar 2018 の8日目の記事です。

GCPには以下のような分析ツールが用意されておりますが、実際にBigQuery+DatastudioやBigQuery+Spreadsheetなど様々な組み合わせで様々な分析をされていることでしょう。

  • BigQuery : データ分析、DWH
  • Datalab: データ分析、機械学習
  • Data Studio: データの可視化、共有
  • Spreadsheet: データ分析、可視化、共有

BigQueryに貯めてあるデータをSpreadSheetやDatastudioで可視化するというのもとても便利ですが、最近社内でCloud Datastoreに貯めてあるデータをSpreadsheetで共有したいというニーズがあり、その際にGoogle Apps Script(GAS)を使ってとても簡単に実現出来たのでその方法・手順についてご紹介しようと思います。

事前にご準備いただくもの

  • GCPプロジェクト
  • Cloud Datastore及び抽出用のデータ
  • サービスアカウントキー(使わない方法もご紹介します)

TL;DR

  • GASからGoogle APIを叩く際の認可の方法として、Oauthとサービスアカウントの2種類ある
  • Oauthが簡単で楽
  • Datastore APIに限らずGoogleの提供する他のAPIでも適用可能

GASで使える認可のパターン

GASからGoogle APIを実行する場合、以下の2つの認可のパターンが考えられます。

  • Oauthによる認可
  • サービスアカウントを利用したJWTによる認可

通常のアプリケーションからAPIを叩く場合にはどちらか都合の良い方を選べばいいと思いますが、GASから叩く場合にはOauthを使うのが簡単です。

Oauthを使った認可パターン

大まかな流れは以下の通りです。

  1. マニフェストファイル(appsscript.js)でScopeを設定する
  2. ScriptApp.getOAuthToken()でアクセストークンを取得しAPIリクエストを送信する

1.マニフェストファイル(appsscript.js)でScopeを設定する

[表示]→[マニフェストファイルを表示]を選択すると左側のファイルビューでappsscript.jsが表示されると思います。そこで今回使用するScopeを設定します。

{
"timeZone": "Asia/Tokyo",
"dependencies": {
"libraries": [{
"userSymbol": "OAuth2",
"libraryId": {YOUR_LIBRARY_ID},
"version": "31",
"developmentMode": true
}]
},
"webapp": {
"access": "MYSELF",
"executeAs": "USER_ACCESSING"
},
"exceptionLogging": "STACKDRIVER",
"oauthScopes": [
"
https://www.googleapis.com/auth/script.external_request",
"
https://www.googleapis.com/auth/datastore"
]

}

今回必要なのは”oauthScopes”の項目です。今回はDatastoreのAPIを実行するので”https://www.googleapis.com/auth/datastore”を追加します。

“https://www.googleapis.com/auth/script.external_request"は外部のAPIを叩くときに必要なScopeです。

2.マニフェストファイル(appsscript.js)でScopeを設定する

設定はこれだけでOKです。あとはコードを書いていきます。

function main() {
var result = query("select * from `{YOUR_DATA_KIND}`");
Logger.log(result);
}
function query(queryString) {
var url = "https://datastore.googleapis.com/v1/projects/" +
{YOUR_PROJECT_ID}+ ":runQuery";

var response = sendRequest(url, createRequestOption("POST", {
"gqlQuery": {
"queryString": queryString
},
}));
return response;
}
function sendRequest(url, fetchOptions) {
var res = UrlFetchApp.fetch(url, fetchOptions);
return JSON.parse(res);
}
function createRequestOption(method, payload) {
return { "method": method,
"muteHttpExceptions": true,
"contentType": "application/json",
"payload": JSON.stringify(payload),
"headers": {
Authorization: "Bearer " + ScriptApp.getOAuthToken()
} };
}

説明の都合上実際のコードから多少端折った箇所もありますが大体こんな感じです。これだけでOauth認証を介してDatastore APIを実行することが出来ます。通常、アプリケーションからOauth認証でGoogle APIを実行する場合はClientIdとClientSecretが必要となりますが、GASの場合はGoogle承認済みのクライアントのため?、それらも必要ありません。

  • YOUR_DATA_KIND: Datastoreから取得する対象のKind
  • YOUR_PROJECT_ID: Datastoreの存在するプロジェクトのID

を適宜自分の環境に置き換えてお読みください。

先にも書いた通り、GASからGoogle APIを叩く場合は上記の方法がおすすめです。ただ、どうしても個人のアカウントに依存してしまうOauthでは要件を満たせない場合、少し複雑にはなりますがサービスアカウントを利用できますので、ご紹介します。

2.サービスアカウントを利用したJWTによる認可

サービスアカウントのキーはGCPコンソールから発行できます。

[IAM&admin]→[Service accounts]からAppengineのDefault Service Accountの[Create Key]を選択することでjsonのキーを発行出来ます。

そしてGASではそのファイル全てを使うわけではなく、中身に含まれているPrivate Keyのみを利用します。

{"type": "service_account","project_id": "{YOUR_PROJECT_ID}","private_key_id": "efcbbcc2e880e8b917712b4a18a0ebf655f0b12d","private_key": "{YOUR_PRIVATE_KEY}","client_email": "{YOUR_CLIENT_EMAIL}","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": "https://www.googleapis.com/robot/v1/metadata/x509/{YOUR_PROJECT_ID}%40appspot.gserviceaccount.com"}

JSONファイルの中身は上記のような内容になっているかと思います。

上記の{YOUR_PRIVATE_KEY}と{YOUR_CLIENT_EMAIL}にあたる文字列を控えておいてください。

では実際にGASのコードを見ていきます。

function main() {
var result = query("select * from `{YOUR_DATA_KIND}`");
Logger.log(result);
}
function query(queryString) {
var url = "https://datastore.googleapis.com/v1/projects/" +
{YOUR_PROJECT_ID}+ ":runQuery";

var response = sendRequest(url, createRequestOption("POST", {
"gqlQuery": {
"queryString": queryString
},
}));
return response;
}
function sendRequest(url, fetchOptions) {
var res = UrlFetchApp.fetch(url, fetchOptions);
return JSON.parse(res);
}
function createRequestOption(method, payload) {
var service = getService();
if (service.hasAccess()) {
return { "method": method,
"muteHttpExceptions": true,
"contentType": "application/json",
"payload": JSON.stringify(payload),
"headers": { Authorization: "Bearer " + service.getAccessToken() } };
} else {
Logger.log(service.getLastError());
}
}
function getService() {
return OAuth2.createService("Datastore:" + {YOUR_CLIENT_EMAIL})
.setTokenUrl("https://accounts.google.com/o/oauth2/token")
.setPrivateKey("YOUR_PRIVATE_KEY")
.setIssuer({YOUR_CLIENT_EMAIL})
.setPropertyStore(PropertiesService.getScriptProperties())
.setScope("https://www.googleapis.com/auth/datastore");
}

本来Private_keyからJWTを生成する部分を自前で実装しなくてはならないのですが、そのあたりの面倒をみてくれるライブラリをGoogleが用意してくれています。

このライブラリを利用することでJWTの生成、トークンの取得あたりの処理を自前で実装する必要がなくなります。

いずれの方式もほんの十数行のコードを書くだけでDatastoreからデータを抽出することができることが分かりました。今回はご紹介できませんでしたがSpreadsheetにも書き出すことも簡単にできます。

もちろん大規模なデータを扱う場合であったり、機械学習系のデータを扱う場合にはBigQueryであったりDatalabといった他の選択肢を選ぶ方が好ましいでしょう。ただ、もっと気軽にちょっとした分析を行う際の選択肢としてGAS(+Spreadsheet)の組み合わせも検討して頂き、本記事がその際の実装のお役に立てれば幸いです。

それでは良いGASライフを〜

--

--