Google Apps Scriptで一部だけをユーザ権限で動かしたい時は?【GAS】
Google Apps Scriptでウェブアプリケーション等を動かそうとした場合に必ずぶつかる問題が「実行権限とアクセス権限、そしてアクセストークン」の処理です。とりわけ、Google Picker APIを動かす場合や、カレンダーを操作するなんて場合になかなか厄介な問題点として立ちはだかります。通常のウェブアプリを作ってる分には困らないのですが、この問題点を解消する為の手段を検証してみたいと思います。
過去にもトリッキーな方法でこれを実現しようとしたパターンがありました。が、結構複雑になりがちなので、今回の手法で解決できるか?挑戦してみます。このテクニックはかなり広範囲且つ、これまでのGASでは難しかった問題点を突破できるテクニックになります。
今回利用するスプレッドシート等
- サービスアカウントを使ったトークン取得 - Google Spreadsheet
- OAuth2 library for Google Apps Script
今回の手法は、GCP側に用意したサービスアカウントを用いて間接的にアクセスしてきてるユーザのアクセストークンを取得し、そのトークンを持ってしてGASではなくGoogleのAPIを叩いて操作するという手法になります。
GCP側での準備やライブラリの追加が必要になります。今回のサンプルでは、GAS作成者の権限で実行する形でウェブアプリをデプロイしていただき、別のユーザでアクセスさせてGoogle Pickerの画面がユーザ毎の画面になるかどうか?がポイントになります。Google Pickerについては以下のエントリーを参考にしてみて下さい。
問題点の内容
Google Apps Scriptでウェブアプリケーションを動かしたい場合に遭遇する問題が「誰の権限で動かし、どのファイルにアクセスするか?」といった問題点があります。これを簡単にここにまとめてみたいと思います。
デプロイした時の実行権限
ウェブアプリケーションをデプロイする場合、2パターンの実行権限付与を求められます。
- GAS作成者の権限で動作させる
- ユーザ自身の権限で動作させる
通常は1.の権限で動かすのが良いのですが、ケースによってはユーザの権限で動かしたい場合があります。前者の場合、特定のAdmin権限を要求するようなメソッドをユーザがGAS作成者の権限を通して利用が出来るというメリットがあり、またユーザに触らせたくないファイルへも部分的にGASで許容した範囲で処理が出来ます(ファイルを完全非公開でも処理の過程でアクセスが可能になる)
両方のいいとこ取りが実現出来るのがこのコードです。
権限と課題
例えば、前述で1.の作成者の権限で動かした場合に問題となるのが
- Google Pickerなどを使って見えるファイルが、GAS作成者の権限で見える範囲となってしまうので、見せたくないものが見えたり、本来ユーザのファイルを選ばせたくても、共有されていなければ見えない
- APIの実行についても、GAS作成者の権限で動かしてるので、カレンダーの予定などを追加するものを作ると、GAS作成者のカレンダーに追加されてしまう(本来はアクセスしてるユーザのカレンダーに追加したいのに)
- かといって、ユーザ権限で動かすとなると、見せたくない管理ファイルなどが自由にアクセス可能になってしまう(アクセス可能にしておかないとGASが実行できない)
- また、同じくユーザ権限の場合、Admin SDKなどの管理者権限を要求するメソッドは動かすことが出来なくなってしまう。
ジレンマというか痛し痒しというか、この問題点にぶつかり、如何ともしがたい状態になってしまいます。結局妥協して運用で誤魔化すといったような事が必要になり、旨味が激減してしまうのです。
解決策
通常のGASのメソッドを動かす場合は、デプロイした時の権限で動作する為、例えばScriptApp.getOAuthToken()でアクセストークンを取得した場合、作成者の権限で動かしていると、誰がアクセスしても、作成者のAccess Tokenが取得されます。同様に各種GASのメソッドを実行する場合も同じです。
よって、これを解決するには以下の要件を満たす必要があります。
- デプロイし実行する権限は作成者の権限で動かしたい
- 但し、一部の作業はユーザの権限で動かしたい(Google Pickerの表示内容や実際のカレンダーの操作等)
これらの要件を満たすには
- 何らかの手法を用いて、作成者の権限で動かしてるスクリプト中で、アクセスしてきてるユーザのAccess Tokenを取得する
- そのTokenを用いて、GASのメソッドではなくDrive APIやCalendar APIを用いて処理を実装する
- 出来れば、実行時にOAuth2.0認証などの画面が出ないままスムーズに実行できると尚良い(トークン管理が必要になってしまうため)
今回の課題は、上記の1.の何らかの手法を用いて、作成者権限で動かしてる中でユーザのAccess Tokenを取得するという、なんともトリッキーに見える手法です。
注意点
この手法はGCPのサービスアカウントを利用してアクセストークンを取得する方法ですが、同一ドメイン内のユーザであればIAMにユーザ登録等の作業をせずともデプロイしたURLにアクセスすれば利用することが可能です。しかし、社外のメンバーや特に普通のGoogleアカウントの場合は利用することができません(というより、もともとGASのアプリは大概的に利用出来るように作られていない)。
社外用として利用する場合は、GASではなく通常のHTMLにて記述したものをWebサーバで公開し、GASのApps Script APIを使って叩くなどの別の仕組みが必要になります。
また、一部をユーザ権限で動かすといっても例えばDriveやCalendarの操作で使う標準のGASのメソッドは常時デプロイしたユーザの権限で動いてしまうので使えません。Access Tokenを利用してDrive APIやCalendar APIなどをUrlfetchAppでREST APIを叩く必要があります。
事前準備
サービスアカウントを用意する
直接的にユーザのアクセストークンを自分の権限で実行中のスクリプトから取得するのは出来ません。そこで利用するのが「サービスアカウント」。以下のエントリーのサービスアカウントを作成するにて作成し、
- APIアクセス用のPrivate Keyの値
- サービスアカウントのメールアドレス
- クライアントID
- アクセスしてきてるユーザのメールアドレス
の4点を用意する必要があります。エントリーを参考にサービスアカウントを作成し、鍵を追加、JSON形式でダウンロードすると上記の内のユーザのメールアドレス以外が全て記述されているので、その内容を他のユーザがアクセスできない形でロードするようにします(GASで非公開のJSONファイルにアクセスするのでも良いし、ユーザが直接アクセスできないスクリプトプロパティに格納するのも良し)
今回は便宜的にコード内に記述しますが、安全に値が取れるように配慮が必要ですし漏洩しないように最大限の注意が必要です。
※GCP側のIAMに全ユーザを登録する必要はありません。あくまでもGAS作成者のアカウントがあれば問題ないです。
図:サービスアカウントのJSONデータの取得が必要
Picker APIのキーを取得する
今回のサンプルでは、Google Picker APIでファイルを選択する画面を利用しています。その為、このAPIで利用する為のAPI Keyを取得しておく必要があります。取得したらコード内に記述してロード時に利用します。前述で紹介のエントリーの「APIを有効にする」の項目で取得手順が掲載されています。
API keyは漏洩したり第三者から見えるような場所には配置しないようにしましょう。また、利用するに当たってはGoogle Picker APIに限定しておくことも忘れずに。
図:Google Picker APIを有効化して認証キーを作成
管理コンソールでの作業
Google Workspaceの管理コンソールでも作業が必要になります。
- 管理コンソールの「セキュリティ」⇒「アクセスとデータ管理」⇒「APIの制御」⇒「ドメイン全体の委任」を開きます
- APIクライアントの新規追加をクリック
- クライアントIDに、前述のJSONファイル内にあるclient_idを追加する
- Scopeには、「https://www.googleapis.com/auth/drive」を追加する
これで、Google Workspace内で利用が可能になります。これを行っておかないと、「Error: Access not granted or expired. at Service_.getAccessToken」というエラーが出て、サービスアカウントの初回認証が失敗します。
図:管理コンソールでAPI利用の許可をする
ライブラリを追加する
GASからサービスアカウントにアクセスして処理をする為の認証用ライブラリとして、以下の手順でOAuth2 library for Google Apps Scriptライブラリを追加しましょう。
- スクリプトエディタを開きます。
- サイドバーよりより「ライブラリ」の+ボタンをクリック
- ライブラリを追加欄に「1B7FSrk5Zi6L1rSxxTDgDEUsPzlukDsi4KGuTMorsTQHhGBzBkMun4iDF」を追加します。
- 今回はバージョンは43を選択してみます。
- 保存ボタンを押して完了
これで、OAuth2.0認証にまつわる様々な関数を手軽に利用できるようになります。
図:ライブラリを追加した様子
ウェブアプリケーションのデプロイ
ここまで準備が整いましたら、最期にウェブアプリケーションとしてデプロイを行います。ただし、1つ注意点もあります。以下の手順でまずはウェブアプリケーションとしてデプロイを行いましょう。デプロイする前に適当に関数を手動実行して認証し、使えるようにしておきましょう。
スプレッドシートはあくまでも管理者が使う為だけのもので、ユーザが利用するのはGASで生成したウェブアプリの側を利用します。ウェブアプリケーションをデプロイをします。
- スクリプトエディタを開く
- 右上のデプロイをクリック
- 新しいデプロイをクリック
- 種類の選択ではウェブアプリを選択し、「次のユーザとして実行は自分」にしておきます(管理者権限で動作しますが、スプレッドシートを公開する必要はありません)
- アクセスできるユーザは、社内公開する必要があるので「ドメイン内組織内全員」としておきます
- 末尾がexecで終わるURLが発行される。これがウェブアプリケーションのページとなります。
- 次回以降コードを編集して再デプロイ時はデプロイを管理から同じURLにて、新しいバージョンを指定して発行することが出来ます。
GASのウェブアプリですので、複数アカウントでログインした状態の場合、403エラーで表示されないこともあるので、基本は1プロファイル1アカウントでログインしての運用が必要です。
図:組織内の全員が使えるようにしておきます。
ソースコード
GAS側コード
メインのコード
//サービスアカウント情報
var json = {
private_key: "ここにサービスアカウントのPrivate_keyを入れる",
client_email: 'ここにサービスアカウントのメアドを入れる',
client_id: 'ここにクライアントIDを入れる',
}
//Picker APIのキー
var apikey = "ここにPicker API用";
//現在のユーザのアドレスを取得
function GetUser() {
var objUser = Session.getActiveUser();
return objUser.getEmail();
}
//ユーザのアクセストークンを取得する
function getUserToken() {
//ロックを開始
let lock = LockService.getScriptLock()
try{
//30秒間のロックを取得する
lock.waitLock(30000);
//スクリプトプロパティをリセット
reset();
//Access Tokenを取得する
var service = getOAuthService();
var accessToken = service.getAccessToken();
//ロック解除
lock.releaseLock();
//Tokenを返す
return accessToken;
}catch(e){
var checkword = "ロックのタイムアウト: 別のプロセスがロックを保持している時間が長すぎました。";
var flag = "";
//通常のエラーとロックエラーを区別する
if(e.message == checkword){
//ロックエラーの場合
flag = 1;
}else{
//ソレ以外のエラーの場合
flag = 0;
}
//ロック解除
lock.releaseLock();
return flag
}
}
//サービスアカウントからトークンを取得
function getOAuthService() {
return OAuth2.createService("Service Account")
.setTokenUrl('https://accounts.google.com/o/oauth2/token')
.setPrivateKey(json.private_key)
.setIssuer(json.client_email)
.setSubject(GetUser())
.setPropertyStore(PropertiesService.getScriptProperties())
.setParam('access_type', 'offline')
.setScope('https://www.googleapis.com/auth/drive');
}
//サービスリセット(スクリプトプロパティを空にする)
function reset() {
var service = getOAuthService();
service.reset();
}
- 冒頭はサービスアカウント生成時に取得したJSONファイル内のPrivate keyやClient ID、サービスアカウントのメアドなどを入れる
- private_keyは「BEGIN PRIVATE KEY」といった文字列まで含めて全部入れておいて良いです。
- またPicker API用のAPI Keyのコードも入れておく
- GetUser関数で現在アクセスしてきてるユーザのメアドを取得可能です。
- GetUserToken関数がメインの処理を行う関数で、複数名同時にアクセスしてくる可能性があるため、排他制御を入れている。
- ユーザがアクセスするとまず、reset関数でスクリプトプロパティの認証情報をクリアしてから、再度サービスアカウントを持ってAccess Tokenを取り直している(ここがとても重要で、resetしないと前のユーザの認証情報が出てきてしまう。できればトークン取得直後にresetを入れた方が良い)
- getOAuthService関数がサービスアカウントを叩いて、ユーザ単位のアクセストークンを取得して返す関数です。
- setSubjectにアクセスしてきてるユーザのメアドを入れることで、その人のAccess Tokenが取得できるようになります。
- GASの各種メソッド自体はあくまでもデプロイした人間の権限で動いてしまうので、SpreadsheetやCalendarを操作したい場合は、UrlfetchAppを使ってGoogle APIを叩きに行くようにしましょう。
この処理によって、ウェブアプリケーションはGAS作成者の権限でデプロイしつつ、特定のAPIを叩く時にだけユーザ単位のAccess Tokenを取得して個別のGoogle Picker APIのファイル選択画面を出すことが可能です。もちろん、同じトークンを使ってユーザレベルでGoogle Drive APIを叩いたりといったことも可能です。
ウェブアプリ用コード
//ウェブアプリケーション
function doGet(e){
var html = HtmlService.createTemplateFromFile("index").evaluate()
html = html.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
return html;
}
//APIキーとTokenを取得する
function apikeyget(){
//返却用の値のJSONを作成する
//ユーザのトークンで表示する
let temparr = {
"inst": apikey,
"token" : getUserToken(),
"origin":"https://script.google.com"
}
//値を返却する
return JSON.stringify(temparr);
}
こちらは、ウェブアプリケーションの出力と、ウェブアプリ側へTokenやAPIキーなどを返却するだけのコードです。
HTML側コード
<html>
<head>
<!-- Picker API用 -->
<script async defer src="https://apis.google.com/js/api.js" onload="onApiLoad()"></script>
<script type="text/javascript">
var DIALOG_DIMENSIONS = {width: 755, height: 465};
var pickerApiLoaded = false;
var DEVELOPER_KEY;
var oauthtoken = "";
var origin = ""
//Picker APIを初期化
function onApiLoad() {
gapi.load('picker', onPickerApiLoad);
}
function onPickerApiLoad() {
pickerApiLoaded = true;
//GAS側から必要なデータを取得する
google.script.run.withSuccessHandler(onSuccess).apikeyget();
}
//token内容を変数に保存する
function onSuccess(token){
let json = JSON.parse(token);
DEVELOPER_KEY = json.inst;
oauthtoken = json.token;
origin = json.origin;
//Pickerを表示する
createPicker();
}
//エラー表示用
function showError(message) {
alert(message);
}
//Pickerを表示する
function createPicker(){
if (oauthtoken) {
var docsView = new google.picker.DocsView()
.setIncludeFolders(true)
.setSelectFolderEnabled(false);
//Pickerに値をセットする
var picker = new google.picker.PickerBuilder()
.addView(docsView)
.enableFeature(google.picker.Feature.NAV_HIDDEN)
.hideTitleBar()
.setLocale('ja')
.setOAuthToken(oauthtoken)
.setOrigin(origin)
.setDeveloperKey(DEVELOPER_KEY)
.setCallback(pickerCallback)
.setSize(DIALOG_DIMENSIONS.width - 50,
DIALOG_DIMENSIONS.height - 50)
.build();
//Pickerを表示する
picker.setVisible(true);
} else {
showError("Tokenが取得できていません。");
}
}
//Callbackデータを受け取る
function pickerCallback(data) {
//レスポンスを取得する
var action = data[google.picker.Response.ACTION];
//選択時の処理
if (action == google.picker.Action.PICKED) {
//ドキュメントの詳細な情報を取得する
var doc = data[google.picker.Response.DOCUMENTS][0];
var id = doc[google.picker.Document.ID];
var url = doc[google.picker.Document.URL];
var title = doc[google.picker.Document.NAME];
//ファイルのIDを表示する
alert(id + "のファイルが選択されました。")
} else if (action == google.picker.Action.CANCEL) {
showError("ファイル選択はキャンセルされました");
}
}
</script>
</head>
<body>
</body>
</html>
こちらは単純にGAS側へAPIキーやユーザのアクセストークンをリクエストして、Pickerの画面を出力するだけです。但し、GAS作成者の権限でデプロイしていても、各ユーザのアクセス可能な範囲でのPicker表示が実現できています。
図:自分のファイルしか見えないよ
応用事例
カレンダー情報を操作
前述の事例はGoogle Picker APIを叩く際のTokenとして今回の手法を使ってユーザのピッカー画面を出していました。もう一つ身近な事例として、カレンダーに本日の勤務場所を登録するみたいな場合に於いて、CalendarAppではなくUrlfetchAppでCalendar API v3を叩き、ユーザのカレンダーの勤務場所に設定するといったものを作ってみました。
そもそも勤務場所の読み書き用API自体が比較的新しくリリースされたばかりのものなのですが、ばっちり動作しました。これまではこの処理を通常のCalendarAppで行ってしまうと、デプロイしたユーザのカレンダーに追加されてしまっていたので見事に両立できています。
※setScopeやドメイン全体の委任でのスコープはカレンダーなので、「https://www.googleapis.com/auth/calendar」を指定する必要があります。
//ユーザのカレンダーに勤務場所を登録する
function createWorkingLocationEvent(place) {
//アクセスしてるユーザのメアドを取得
const calendarId = GetUser();
//ユーザのアクセストークンを取得
const usertoken = getUserToken()
//時刻を取得する
const startdate = workdatenow(true);
const enddate = workdatenow(false);
//イベントデータを生成
const requiredArgs = {
start: {
dateTime: startdate,
timeZone: 'Asia/Tokyo'
},
end: {
dateTime: enddate,
timeZone: 'Asia/Tokyo'
},
eventType: "workingLocation",
visibility: "public",
transparency: "transparent",
workingLocationProperties: {
type: "customLocation",
customLocation: { label: place },
}
}
//endpoint
var endpoint = "https://www.googleapis.com/calendar/v3/calendars/" + calendarId + "/events"
//リクエストヘッダ
let headers = {
"Authorization":"Bearer " + usertoken,
"Content-Type": "application/json"
}
//リクエストオプション
let options = {
method: "POST",
headers: headers,
payload: JSON.stringify(requiredArgs),
muteHttpExceptions: true
}
//リクエスト実行
const response = UrlFetchApp.fetch(endpoint, options);
//リクエスト結果を取得する
const result = JSON.parse(response.getContentText());
}
//今日の日付を取得
function workdatenow(flg){
var dateman = new Date()
//日付を取得
var fullyear = dateman.getFullYear()
var monthman = paddingZero(dateman.getMonth() + 1);
var datekun = paddingZero(dateman.getDate())
//開始時刻を取得
var hourman = paddingZero(dateman.getHours())
var minman = paddingZero(dateman.getMinutes())
var secondman = paddingZero(dateman.getSeconds())
//日付を形成する
if(flg){
//開始時刻を返す
var dateformat = fullyear + "-" + monthman + "-" + datekun + "T" + hourman + ":" + minman + ":" + secondman + "+09:00";
}else{
//終業時刻を返す
var dateformat = fullyear + "-" + monthman + "-" + datekun + "T" + "17:30:00" + "+09:00"
}
//時刻を返す
return dateformat;
}
- UrlfetchAppでのBearer Tokenでユーザのトークンを呼び出す処理に変更しています。
- CalendarIdはGetUserでユーザのメアドを取得し、それをもってエンドポイントURLとしています。
- イベントデータとして勤務場所をカレンダーに記録するプロパティをセットしています。
- 日付形式はyyyy-MM-ddThh:mm:ss+09:00の形式で指定が必要なので専用の関数を用意しています。
Google Driveを操作
Drive API v3を利用してユーザの権限でアクセスできるGoogle Driveの範疇に絞って共有ドライブのリストを返したり、そのメンバー情報を取得してみようと思います。通常はデプロイした人の権限なので管理者だと全部返してしまうのですが、この手法を使う事でユーザ単位で異なるリストを返すことが可能になります。
※setScopeやドメイン全体の委任でのスコープはドライブなので、「https://www.googleapis.com/auth/drive」を指定する必要があります。
共有ドライブの一覧を取得
//Drive API v3エンドポイント
var drivepoint = "https://www.googleapis.com/drive/v3/";
//共有ドライブ一覧を取得する(ユーザレベルでアクセスできるものだけ)
function getshdriveuser(){
//変数を宣言
let pageToken = "";
let array = [];
//ユーザのアクセストークンを取得
const usertoken = getUserToken();
//ヘッダ情報
const header = {
Authorization: "Bearer " + usertoken
}
//リクエストオプション
const options = {
headers: header,
method: "GET",
contentType: "application/json",
muteHttpExceptions: true
}
//drivepointに対してGETでリクエスト
//https://developers.google.com/drive/api/reference/rest/v3/drives/list?hl=ja
do{
//エンドポイントを構築
let endpoint = drivepoint + "drives?pageSize=100&pageToken=" + pageToken + "&useDomainAdminAccess=false"
//URLリクエスト
let response = UrlFetchApp.fetch(endpoint,options);
//レスポンス内容を取得
let sharedDrives = JSON.parse(response.getContentText());
//ドライブの情報を取得する
let sharedDrivesItems = sharedDrives.drives;
//ドライブのID,名前だけ取得する
for(var i = 0;i<sharedDrivesItems.length;i++){
//レコードを一個取り出す
let rec = sharedDrivesItems[i];
//ドライブの名称を取得する
let drivename = rec.name
//ドライブのIDを取得する
let driveid = rec.id
let temparr = [
drivename,
driveid,
]
array.push(temparr)
}
//ページトークンを取得する
pageToken = sharedDrives.nextPageToken;
//ウェイトを入れる
Utilities.sleep(500)
}while(pageToken)
return JSON.stringify(array);
}
- getUserTokenにてユーザ単位のアクセストークンを取得します。
- サービスではなくREST APIのDrive APIを利用する必要があるので、UrlfetchAppにてGETにてリクエストをします。
- エンドポイントURLにクエリパラメータとしてpageSize, pageToken, useDomainAdminAccessの3つを指定してリクエストを行います。
- その後の処理についてはサービスのDrive APIとほぼ変わらないですが、レスポンスデータがv3ではちょっと異なる点がある(レスポンスを取得する部分が、sharedDrives.drivesで取得する必要がある。Drive API v2だとitemsとなってる)
- またレスポンスデータはJSON.parseする必要があるので注意。
共有ドライブのメンバーを取得
//Drive API v3エンドポイント
var drivepoint = "https://www.googleapis.com/drive/v3/";
//特定共有ドライブの権限者一覧を取得する
function getshddrivepermission(driveid){
//変数を宣言
let pageToken = "";
let array = [];
//ユーザのアクセストークンを取得
const usertoken = getUserToken();
//ヘッダ情報
const header = {
Authorization: "Bearer " + usertoken
}
//リクエストオプション
const options = {
headers: header,
method: "GET",
contentType: "application/json",
muteHttpExceptions: true
}
//drivepointに対してGETでリクエスト
//https://developers.google.com/drive/api/reference/rest/v3/permissions/list?hl=ja
do{
//エンドポイントを指定(全フィールド指定)
let endpoint = drivepoint + "files/" + driveid + "/permissions?pageSize=100&supportsAllDrives=true&useDomainAdminAccess=false&fields=*";
//URLリクエスト
let response = UrlFetchApp.fetch(endpoint,options);
//レスポンス内容を取得
let pList = JSON.parse(response.getContentText());
let pmember = pList.permissions;
//メンバー一覧のアドレスとロールを取得する
for(var j = 0;j<pmember.length;j++){
//レコードを取り出す
let member = pmember[j];
//ロールを取り出す
let role = member.role;
//メンバーアドレスを取得する
let maddress = member.emailAddress
let temparr = [
drivename,
driveid,
maddress,
role
]
array.push(temparr)
}
//ページトークンを取得する
pageToken = pList.nextPageToken;
//ウェイトを入れる
Utilities.sleep(1000)
}while(pageToken)
return JSON.stringify(array);
}
- リクエストするスタイルは前述のドライブリスト一覧とほぼ同じ
- エンドポイントはhttps://www.googleapis.com/drive/v3/files/{fileId}/permissionsのスタイルにGETリクエストする必要がある
- エンドポイントURLにクエリパラメータとしてpageSize, pageToken, useDomainAdminAccess, supportsAllDrives, fieldsの5つを指定してリクエストを行います。
- 公式ドキュメントに記載が無いのですが、クエリパラメータでfileds=*を指定しないと、emailAddressなどの他の要素がDrive API v3では出てきません。必ずつけるようにしましょう。
- その後の処理についてはサービスのDrive APIとほぼ変わらないですが、レスポンスデータがv3ではちょっと異なる点がある(レスポンスを取得する部分が、pList.permissionsで取得する必要がある。Drive API v2だとitemsとなってる)
- またレスポンスデータはJSON.parseする必要があるので注意。
関連リンク
- Access token for the list of effective user in google apps script
- How to Use Google Service Accounts with Google Apps Script
- Google Apps Scriptでサービスアカウントを使いGoogleカレンダーにイベントを登録する
- 【図解説明】GASの実行権限や共有権限の仕組みについて解説
- GASでサービスアカウントとしてデータ操作する方法
- Drive API : Method: files.
get - サーバー間アプリケーションに OAuth 2.0 を使用する
- Access not granted or expired with Service Account in Google Apps Script
- Calendar API :Events: insert
- Google カレンダー ユーザーの勤務場所の管理
- GoogleドライブAPIを使用した権限リストにメールアドレスがありません







