GAS『Insufficient Permissions』の原因と対処法

GAS『Insufficient Permissions』の原因と対処法

Google Apps Scriptで「Insufficient Permissions(権限が不足しています)」が出るのは、実行主体に対象リソースのアクセス権がない、または要求スコープが不足/未認可だから。Drive/Sheets/Gmail/カレンダー等のサービス、共有ドライブや他ユーザー所有のファイル、ウェブアプリやトリガーの実行主体ずれ、Advanced ServicesやCloud API未有効化、組織ポリシーの制限など、発生箇所ごとの直し方をまとめる。

エラーの意味と発生条件(まず把握)

・対象リソース(スプレッドシート/ファイル/カレンダー/メール)に実行主体がアクセス権を持たない
・スクリプトが要求するOAuthスコープが不足(認可していない/マニフェスト未明示/権限を増やした)
・共有ドライブ(旧Team Drive)で、実行主体に閲覧/編集権限がない or ドメイン制限
・ウェブアプリ/トリガーの「実行するユーザー」と実際の権限が食い違う
・Advanced Google services や対応するCloud APIが未有効化
・組織の「信頼できるアプリ」やスコープ制御ポリシーでブロック

誰の権限で動いているかを確認(実行主体の可視化)

// 実行主体(effectiveUser)とUI操作ユーザー(activeUser)をログ
function whoAmI() {
  console.log({
    effectiveUser: Session.getEffectiveUser().getEmail(),
    activeUser: Session.getActiveUser().getEmail()
  });
}
// ウェブアプリ/トリガー/ライブラリ経由で主体が変わることがあるので必ず確認

スプレッドシート/Driveでの権限不足(ID・共有・ごみ箱・共有ドライブ)

// 典型NG:権限なし or ごみ箱 or 共有ドライブのアクセスなし
function openSheet(sheetId) {
  // ここでInsufficient Permissions/アクセス拒否
  const ss = SpreadsheetApp.openById(sheetId);
  return ss.getName();
}

// 対処:IDの妥当性と共有状態を確認し、実行主体に権限を付与
function assertDriveFile(id) {
  const f = DriveApp.getFileById(id); // 取得できなければ権限不足
  console.log({name: f.getName(), trashed: f.isTrashed()});
  // 必要に応じて編集者を付与(所有者権限がある場合のみ)
  // f.addEditor('user@example.com');
}

チェックポイント
□ URLではなく“IDのみ”をopenByIdに渡しているか
□ ファイルがごみ箱/共有ドライブに移動されていないか
□ 実行主体(whoAmIで確認)に閲覧/編集権があるか(共有設定)
□ 共有ドライブでは「コンテンツ管理者/編集者」以上か

Sheets/Gmail/Calendar等:要求スコープ不足(再認可/明示スコープ)

// 初回/権限追加後の“認可促し”関数(必要サービスを一度ずつ呼ぶ)
function authorizeOnce() {
  const ss = SpreadsheetApp.getActive();
  ss.getSheets()[0].getRange(1,1).setValue(new Date()); // spreadsheets スコープ
  DriveApp.getFiles();                                   // drive スコープ
  // GmailApp.getAliases();                              // 必要なときだけ
  console.log('authorized');
}

// appsscript.json に必要最小限のスコープを明示(例)
/*
{
  "oauthScopes": [
    "https://www.googleapis.com/auth/spreadsheets",
    "https://www.googleapis.com/auth/drive",
    "https://www.googleapis.com/auth/script.external_request"
  ]
}
*/
チェックポイント
□ エディタで authorizeOnce() を実行して同意ダイアログに承認したか
□ プロジェクト設定→スコープに想定のスコープが並んでいるか
□ スコープを増やしたのに再デプロイ/再認可していない、が起きていないか

ウェブアプリの実行主体/アクセス設定の不整合

// doGet/doPost は定義済みか
function doGet(e) {
  return HtmlService.createHtmlOutput('OK');
}
/*
デプロイ時に確認:
・実行するユーザー:自分(推奨。自分にリソース権限があるなら確実)
・アクセスできるユーザー:自分のみ/全員/ドメイン内など要件に合わせる
・変更後は「新バージョンとしてデプロイ」必須(古い版のままだと権限食い違い)
*/

典型NG→OK
× 実行主体を「アクセスするユーザー」にしており、そのユーザーにDrive権限がない
○ 「自分として実行」に変更し、所有者が対象ファイルの編集権を保持

シンプルトリガーではなく“インストール型トリガー”に(権限の壁を回避)

// シンプルトリガー(onEdit/onOpen)の制限でInsufficient Permissionsになりがち
// → インストール型トリガーに切り替え、作成者が認可
function job(e) {
  // 要認可の処理(例:Drive/Calendar/Gmail)
  const ss = SpreadsheetApp.getActive();
  ss.getSheets()[0].appendRow([new Date(), 'trigger']);
}

function installOnEdit() {
  ScriptApp.newTrigger('job').forSpreadsheet(SpreadsheetApp.getActive()).onEdit().create();
}

チェックポイント
□ どのトリガーが失敗しているか(実行ログ)と、作成者が認可済みか
□ トリガー対象の関数名が最新か(改名後はトリガー再作成)

Advanced Google services / Cloud API 未有効化

[code]
/*
Sheets API / Drive API / Admin SDK などをコードから呼ぶ場合:
1) スクリプトエディタの「サービス」から対象サービスをON
2) 関連するCloud APIが有効化される(必要に応じCloud Console側を確認)
3) 初回呼び出しで認可ダイアログに同意
*/
function writeWithSheetsApi(sheetId) {
const body = { values: [[‘A’,’B’],[‘C’,’D’]] };
Sheets.Spreadsheets.Values.update(body, sheetId, ‘A1’, { valueInputOption: ‘USER_ENTERED’ });
}
[/code]

[code]
典型NG→OK
× ServicesをOFFのまま Sheets.Spreadsheets… を呼ぶ → Insufficient Permissions/Service not found
○ ServicesをON→初回実行で認可→成功
[/code]

組織ポリシー(信頼できるアプリ/スコープ制限)でブロック

[code]
/*
・Google Workspace 管理コンソールで第三者アプリ制限が有効だと、
ユーザーの同意だけではスコープを付与できない
対処:
・管理者に依頼して、該当デプロイのOAuthクライアントIDを「信頼できる」へ追加
・必要スコープ(spreadsheets/drive/gmail等)を許可リストへ
*/
[/code]

リソース単位の共有漏れを自動検査(ファイル・シート名・範囲)

[code]
// ファイル共有の検査(閲覧だけ/編集可 などの確認に利用)
function listEditors(fileId) {
const f = DriveApp.getFileById(fileId);
console.log({
name: f.getName(),
editors: f.getEditors().map(u => u.getEmail()),
viewers: f.getViewers().map(u => u.getEmail())
});
}

// シート存在チェック(見つからずnull→別のTypeErrorに化ける前に止める)
function getSheetSafe(id, name) {
const ss = SpreadsheetApp.openById(id);
const sh = ss.getSheetByName(name);
if (!sh) throw new Error(シートが見つかりません: ${name});
return sh;
}
[/code]

よくあるNG→OK(クイック修正集)

[code]
× 共有ドライブのスプレッドシートを、権限のない実行主体で編集
○ 実行主体に共有ドライブの編集権を付与 or ウェブアプリを「自分として実行」

× スコープ追加後に再デプロイ/再認可をしない
○ authorizeOnce() を実行→新バージョンとしてデプロイ

× シンプルトリガーからGmailApp/DriveAppを呼ぶ
○ インストール型トリガーに切替え、作成者が承認

× Advanced ServicesをOFFのまま利用
○ エディタのサービスON→Cloud API有効化→初回認可

× 別ドメイン/ゲストのアカウントで組織ポリシーにブロック
○ 管理者に「信頼できるアプリ」登録とスコープ許可を依頼
[/code]

失敗点を特定するための最小再現テンプレ

[code]
function diag() {
try {
const id = ‘YOUR_SHEET_ID’;
console.log(‘who’, Session.getEffectiveUser().getEmail());
const ss = SpreadsheetApp.openById(id); // Sheets/Drive権限
console.log(‘sheet’, ss.getName());
const files = DriveApp.getFiles(); // Drive権限確認
console.log(‘drive’, files.hasNext());
} catch (e) {
console.error(‘diag failed’, {msg: e.message, stack: e.stack});
throw e;
}
}
[/code]

チェックリスト(上から順に潰す)

[code]
□ whoAmI() で実行主体を確認(期待ユーザーか)
□ 対象ファイル/フォルダ/共有ドライブに実行主体の閲覧/編集権がある
□ スクリプトのスコープが十分(プロジェクト設定/マニフェスト)で、再認可済み
□ ウェブアプリは最新デプロイで、実行主体設定が要件と一致
□ シンプルトリガーで要認可APIを呼んでいない(インストール型へ)
□ Advanced Services と対応Cloud APIを有効化済み
□ 組織ポリシー(信頼できるアプリ/スコープ制御)に引っかかっていない
[/code]