White Box技術部

WEB開発のあれこれ(と何か)

Google Apps Script(GAS)からBox APIを叩くためのすべて

四苦八苦してしまって本当に辛かったので、他の人が同じような苦労をしないように・・・

個人アカウントのOAuth認証で良い場合はこのサイトの手順が参考になります。
How to Use the Box API with Google Apps Script - Digital Inspiration

ただ今回は、AppSheetからの呼び出しで使いたかったので、これだと用途に合わないんですよね。

必要なこと

ざっくりですが、以下の作業が必要です。

  • Boxの開発アプリをCustom AppのServer Authenticationで作成
  • Box Adminでアプリを承認
  • BoxアプリのService Account IDを操作したいBoxフォルダの共有に追加
  • GASの実行権限設定
  • Box APIからアクセストークンの取得

Box側

1. 開発アプリの作成

Boxの開発アプリをCustom AppのServer Authenticationで作成します。

※途中で設定を日本語にしたので、図ではサーバー認証(クライアント資格情報許可)です

f:id:seri_wb:20210810010958p:plain:w400

構成での作業

  • クライアントシークレットの発行
  • アプリケーションスコープを設定
  • CROSドメインにGASのドメインを追加
    • https://*.googleusercontent.com

2. アプリのEnterprise承認をする

アプリの設定が終わったら承認リクエストを飛ばします。
そうするとBox Adminの権限があるメンバーにアプリ承認のリクエストが飛ぶので、承認してください。

GAS側

GASのコードはclaspを使って作成しています。
ここは特に真似る必要はありませんが、TypeScriptでのdoPost関数の実装例として見て貰えればと。

結局nodeライブラリは使わなかったのですが、claspの使い方はこのサイトを参考にさせて貰いました。

appsscript.jsonの中身

{
  "timeZone": "Asia/Tokyo",
  "dependencies": {},
  "exceptionLogging": "STACKDRIVER",
  "oauthScopes": [
    "https://www.googleapis.com/auth/drive.readonly",
    "https://www.googleapis.com/auth/drive",
    "https://www.googleapis.com/auth/script.external_request"
  ],
  "runtimeVersion": "V8"
}

main.tsの全量

重要なのはアクセストークンを取得しているgetAccessTokenで、他は各自の処理に合わせて貰えればOKです。

const BOX_CLIENT_ID = 'クライアントID';
const BOX_CLIENT_SECRET = 'クライアントシークレット';
const BOX_ENTERPRISE_ID = '組織ID';
const BOX_TEMPLATE_FOLDER_ID = 'コピー元フォルダのID';
const BOX_PARENT_FOLDER_ID = 'コピー先(親)フォルダのID';

// https://developers.google.com/apps-script/guides/web#request_parameters
export function doPost(e: GoogleAppsScript.Events.DoPost): GoogleAppsScript.Content.TextOutput {
  const contents = JSON.parse(e.postData.contents);
  const orderDate = contents["orderDate"];
  const customerName = contents["customerName"];

  const createFolderId = copyFolder(orderDate, customerName);

  const resultParams = {folderId: createFolderId};
  const result = JSON.stringify(resultParams);
  return ContentService.createTextOutput(result);
}

const getAccessToken = (): string => {
  const endpoint = 'https://api.box.com/oauth2/token';
  const payload = {
    client_id: BOX_CLIENT_ID,
    client_secret: BOX_CLIENT_SECRET,
    grant_type: 'client_credentials',
    box_subject_type: 'enterprise',
    box_subject_id: BOX_ENTERPRISE_ID
  };
  const headers = { 'Content-Type': 'application/x-www-form-urlencoded' };
  const response = UrlFetchApp.fetch(endpoint, {
    headers: headers,
    method: 'post',
    payload: payload
  });
  const json = JSON.parse(response.getContentText());

  return json.access_token;
};


const copyFolder = (orderDate: string, customerName: string): string => {
  const endpoint = `https://api.box.com/2.0/folders/${BOX_TEMPLATE_FOLDER_ID}/copy`;

  const access_token = getAccessToken();
  const headers = {
    'Authorization': 'Bearer ' + access_token,
    "Content-type": "application/json",
  };
  const payload = {
    name: `${orderDate}_${customerName}`,
    parent: {
      id: BOX_PARENT_FOLDER_ID
    }
  };
  const response = UrlFetchApp.fetch(endpoint, {
    headers: headers,
    method: 'post',
    payload: JSON.stringify(payload)
  });
  const json = JSON.parse(response.getContentText());
  Logger.log(json.id);
  return json.id;
};

以上です。


落ち穂拾い

Client Credentials Grantの日本語ページがない

今回のやり方のベースになっている、以下のページに該当する日本語のページがありません。

Boxの開発ドキュメントは日本語のものが用意されており、 そちらが最初に表示されたので、別段気にしていなかったのですが、 クライアントクレデンシャルでのアクセストークン取得方法のページはなぜかガッツリ抜け落ちていました。

英語

f:id:seri_wb:20210810012220p:plain:w500

日本語

f:id:seri_wb:20210810012251p:plain:w500

Boxで最初に企業ドメインアカウントで開発者登録をすると、その人が管理者になる

ADMINは、他のアカウントへのスイッチもできるので、適当なタイミングで適切な人に権限を渡しましょう。

https://support.box.com/hc/ja/articles/360044194953/

AppSheetはPOSTのレスポンスを使えない

本記事の例ではdoPostでレスポンスを返していますが、これを呼び出すAppSheetは現状レスポンスの中身を利用できないので、 あの部分はなくても問題ありません。

GASでデプロイ後はGCP連携していないとログが表示されない

GASをデプロイし、外部からGASのスクリプトcurlなどで叩いた場合、GCP連携していないとコード中に組み込んだログは表示されません。