Google Apps ScriptでBox APIを叩いて権限変更をする【GAS】
Google Driveの場合は、Google Apps ScriptもしくはDrive APIが充実しているため、割と簡単に権限変更が可能です。しかし、Enterpriseで採用されてるBoxとなると、Box APIやNode.js用のライブラリなどが用意されているのですが、リファレンス資料が充実していない為、結構厄介です。
今回、Google Apps ScriptでBox Content Pickerを使ってフォルダを選択、そのフォルダ内のコラボレータ(権限保有者)の一覧を取得、コラボレータの権限を変更を実践してみたいと思います。当然、Node.js用ライブラリなどは使えないのと、Node.jsであってもrequestモジュールのように手動でBox APIを叩けるようにしておくのは重要なので、その辺りを取り組んでみます。
目次
今回使用するファイル等
- Box APIで権限変更スプレッドシート
- Box Content Picker
- OAuth2 library for Google Apps Script
- Box API - フォルダを更新
- Box API - コラボレーションを取得
- Box API - フォルダコラボレーションのリストを取得
サンプル使用時は、Client IDとClient Secretの2つの書き換えを忘れずに。
事前準備
リダイレクトURL
リダイレクトURLとは、認証を完了しAccess Tokenを取得したら戻るべきURLを指定するものです。これは、スクリプトIDをもとに作られているので、スクリプトIDを取得して組み立てます。
- スクリプトエディタのサイドバーより、プロジェクトの設定を開く
- 情報の中にある「スクリプトID」を控えておく。
- https://script.google.com/macros/d/スクリプトID/usercallback として組み立てる。これがリダイレクトURLとなる。
図:スクリプトIDを取得してURLを組み立てておく
Box APIの準備
Box APIを使うために必要なクライアントIDとシークレットを生成します。以下の手順で作成します。
- Box Developer Consoleにて新規アプリをカスタムアプリで作る。ユーザ認証はOAuth2.0を選び、名前をつけて、アプリの作成をクリック
- すでにクライアントIDとシークレットが生成されてるので、コピーする
- リダイレクトURLに前項で作ったURLを入力
- スコープでは読み書きと、ユーザ・グループの管理にチェックを入れる
- CORSドメイン設定をしないと呼び出せないので、入力欄に「https://*.googleusercontent.com」を入力する
- 最後に変更を保存をクリック
図:クライアント情報とリダイレクトURL
図:スコープを設定
OAuth2.0認証
取得したクライアントID、シークレットを元にGoogle Apps Scriptにこれらを記述する。これで認証の準備が完了したので、
- スプレッドシートメニューの「OAuth認証」→「認証の実行」をクリック。
- ダイアログの下部にある「アクセス承認」をクリック
- Boxログイン画面が出るので、ログインし、Boxへのアクセス許可をクリック
- これで、アクセストークンが取得出来ました。
- スプレッドシートメニューの「OAuth認証」→「プロパティ」から格納情報を確認出来ます。oauth2.boxに一連の情報がJSON形式で格納されています。
- これで、コードからBox APIを叩く事ができるようになります。
図:コードに情報を追記する
図:認証画面
ソースコード
今回は権限変更を実装していますが、共有期限変更なども可能です。しかし、共有期限の変更は無料アカウントでは出来ないので、403エラーとなります。以下主要な部分のソースコードを記載します。
GAS側コード
//メンバーリスト取得用エンドポイント var colab = "https://api.box.com/2.0/folders/"; //コラボレーション変更用エンドポイント var colabchange = "https://api.box.com/2.0/collaborations/"; //対象フォルダのBoxメンバー一覧を取得する function getBoxMember(folderid){ //エンドポイントを構築 var endpoint = colab + folderid + "/collaborations/"; //認証済みかチェックする var service = checkOAuth(); //Access Tokenをプロパティから取り出す var token = service.getAccessToken(); //リクエストを作成 let header = { 'Authorization': 'Bearer ' + token, "Content-type": "application/json", } if(service.hasAccess()) { //HTTP通信 var response = UrlFetchApp.fetch(endpoint, { headers: header, method: "GET" }); //ステータスコードを取得 var status = response.getResponseCode(); //成功したら返り値を取得 if(status == 201 || status == 200){ //返り値を取得する var json = JSON.parse(response.getContentText()); //メンバー数を取得 var length = json.total_count; //メンバー情報を構築 var members = []; var cnt = 1; for(var i = 0;i<length;i++){ //レコードを1つ取り出す var rec = json.entries[i]; //メンバー情報を取り出す var tempinfo = rec.accessible_by; var role = rec.role; var tempexpire = rec.expires_at; var colabid = rec.id; //これがユーザの場合のcollaboration_idとなるようだ var expire = ""; var domain = ""; //login情報が無い場合はスルーする if(tempinfo.login == undefined || tempinfo.login == null){ //カウンターを回す cnt = cnt + 1; continue; }else{ //expire日付を修正 if(tempexpire == null){ //期限が無い場合はexpireは空で処理 expire = ""; }else{ var dateman = new Date(tempexpire.substr(0,10)); //1日加算する dateman.setDate(dateman.getDate() + 1); //yyyy/mm/ddに変換する let fullyear = dateman.getFullYear(); let monthman = paddingZero(dateman.getMonth() + 1); let datekun = paddingZero(dateman.getDate()); expire = fullyear + "/" + monthman + "/" + datekun; } //ドメインを切り出す var mail = tempinfo.login var tempdomain = mail.split('@'); //一時配列を用意 var temparr = { id: cnt, uid: tempinfo.id, name: tempinfo.name, login : tempinfo.login, role : role, expire : expire, domain : tempdomain[1], colabid : colabid } //メンバー情報を配列に作成 members.push(temparr) //カウンターを回す cnt = cnt + 1 } } return JSON.stringify(members); }else{ return status + "エラー"; } }else{ //エラーを返す(認証が実行されていない場合) return "error"; } } //共有期限を変更する function changeLimit(temparr){ //引数を分解 var temp = temparr; //引数を取得 //var changedate = temp.changedate; var colabid = temp.colabid; var role = temp.role; //expires_atを構成(例:2019-08-29T23:59:00-07:00) //var expiredate = changedate.replace(/\//g, '-'); //スラッシュをハイフンに変換 //expiredate = expiredate + "T23:59:00+09:00" //UTCに9時間プラスした値で日付を指定 //認証済みかチェックする var service = checkOAuth(); //Access Tokenをプロパティから取り出す var token = service.getAccessToken(); //エンドポイントを構成 var endpoint = colabchange + colabid + "/"; //リクエストボディ var json = { "role": role, //"expires_at" : expiredate //有償アカウントの場合、共有期限変更が可能 } //リクエストを作成 var header = { 'Authorization': 'Bearer ' + token, } //Box APIを叩く if(service.hasAccess()) { //HTTP通信 var response = UrlFetchApp.fetch(endpoint, { method: 'PUT', headers: header, contentType: "application/json", payload: JSON.stringify(json), muteHttpExceptions:true }); //ステータスコードを取得 var status = response.getResponseCode(); //成功したら返り値を取得 if(status == 201 || status == 200){ //返り値を取得する var json = JSON.parse(response.getContentText()); console.log(json.id); //値を返す return "OK"; }else{ console.log(response.getContentText()) return status + "エラー"; } }else{ //エラーを返す(認証が実行されていない場合) return "error"; } } //Access Tokenを取得して返す function getAccessToken(){ //認証をチェック var service = checkOAuth(); if (!service.hasAccess()) { //認証されていないので何もしない return; }else{ //Access Tokenをプロパティから取り出す var token = service.getAccessToken(); return token; } }
- getAccessTokenは、HTML側のBox Content Pickerで表示する際のアクセストークンとして必要なコードを返す為の関数です。
- getBoxMemberは、指定したフォルダのコラボレーターのリストを取得して返します。
- このとき、コラボレータのメンバーの詳細な情報が返ってきますが、重要なのはroleとcolabid。roleは権限です。
- 問題はcolabidですが、Boxのリファレンスにはcollaboration_idとして記載があるものの、何がそれに該当するのか一切記載がありません。これは、メンバー固有のIDでもフォルダのIDでもなく、コラボレータとして追加された時に付与されるIDで、rec.idにあるように、返り値のすぐ最初にあるid値がそれに該当します。
- また、フォルダにもcollaboration_idに該当するものが存在し、これをもってして、ユーザやフォルダの権限変更や共有期限の変更を行います。Boxのリファレンス資料は実に不親切。
- changeLimitでは、前述のcollaboration_idに該当するcolabidの値をもって、roleを変更します。
- 変更時のメソッドはPUTにてUrlfetchAppでリクエストを行います。
- HTTPステータスコードはresponse.getResponseCodeで取得可能
- 今回とくに、返り値自体は不要なので、そのままHTML側で完了の処理に移ります。
HTML側コード
今回はインターフェースはVue + Vuetifyで構築しています。全体のコードはサンプルをご覧ください。ここでは、Box Content Pickerの表示部分だけを表示しています。
//BoxPickerを初期化 var folderPicker = ""; var folderId = "0"; //Pickerの開始フォルダ var targetid = ""; //メンバー取得対象のフォルダID //Box Pickerをロード function initBoxPicker(){ folderPicker = new Box.FolderPicker(); //全イベントを削除 folderPicker.removeAllListeners(); //キャンセル時イベントを追加 folderPicker.addListener('cancel', function() { //Pickerを非表示にする document.getElementById("boxman").style.display = "none"; //入力欄を表示する document.getElementById("folderinfo").style.display = "block"; }); //選択時イベント folderPicker.addListener('choose', function(items) { //ピックアップしたフォルダの情報 var info = JSON.stringify(items, null, 2) info = JSON.parse(info); //非表示にする document.getElementById("boxman").style.display = "none"; //入力欄を表示する document.getElementById("folderinfo").style.display = "block"; //共有期限日がある場合は日付を取得する let limitdate = ""; let templimit = info[0].shared_link.unshared_at; if(templimit == null){ //何もしない }else{ //日付部分だけ切り出し var dateman = new Date(templimit.substr(0,10)); //1日加算する dateman.setDate(dateman.getDate() + 1); //yyyy/mm/ddに変換する let fullyear = dateman.getFullYear(); let monthman = paddingZero(dateman.getMonth() + 1); let datekun = paddingZero(dateman.getDate()); limitdate = fullyear + "/" + monthman + "/" + datekun; } //vm.folinfoに情報を入れる var temparr = { foldername: info[0].name, folderid : info[0].id, limit : limitdate, } vm.folinfo = temparr; //folderIdを取得する targetid = info[0].id; //現在のフォルダのアクセス可能メンバー一覧を取得する google.script.run.withSuccessHandler(onMember).getBoxMember(targetid); }); } //Tokenを取得したあとの処理 function onSuccess(token){ //Pickerを表示 folderPicker.show(folderId, token, { container: '.containerman', logoUrl: 'box', maxSelectable: 1, canUpload: false, canCreateNewFolder: false, }); //Pickerエリアを表示し入力欄を非表示にする document.getElementById("boxman").style.display = "block"; document.getElementById("folderinfo").style.display = "none"; //入力データをクリアする vm.folinfo = {}; }
- HTML側の多くのコードはVuetifyのインターフェース操作用コマンドとHTMLが殆どです。
- HTML表示時の初期化で、上記のコードを実行し、Box Content Pickerの初期化を行っています。
- フォルダを指定をクリックすると、GAS側からAccess Tokenを取得
- 取得後に、onSuccessのコードが実行されて、Pickerが表示されます。
- 今回はフォルダピッカーとして作りましたが、ファイルピッカーとしても使えます。
フォルダの共有期限変更
今回は行いませんでしたが、expires_atにてユーザの共有期限変更が、roleの変更時に行えます(というよりも、expires_atのみではエラーになるので、roleの指定は必ず必要です)。
フォルダ自体の共有期限もユーザ同様にcollaboration_idとなるid値が割り振られているので、それをもってして共有期限変更もできるのですが、フォルダの場合には、これとは別に違うエンドポイントとunshare_atを変更することでも、共有期限の変更が可能です。こちらはcollaboration_idではなくfolderidを使うので、難しいことはありません。
//メンバーリスト取得用エンドポイント var colab = "https://api.box.com/2.0/folders/"; //エンドポイントを構築 var endpoint = colab + folderid + "/"; //リクエストボディ var json = { "shared_link": { "unshared_at":expiredate } }
エンドポイントとリクエストボディの値を上記に置き換え、適切なexpiredateの形式を指定すればフォルダは共有期限変更が可能です。ユーザだけが、オカシナスタイルになっています(普通にフォルダのIDとユーザのIDの2つをパラメータとして指定するようなAPIにすれば良いにも関わらず、説明のないcollaboration_idという何者なのかがわからないものを利用してるのはいかがなものか)。
実行結果
Box Content Pickerの画面
スプレッドシートメニューのOAuth認証→「Box管理」を開くと、ダイアログが開かれます。フォルダの指定ボタンをクリックすると、Box Content Pickerが起動し、初期化時のfolderIdの値(ルート直下は0)に基づいて開かれます。フォルダを選択肢、右下の選択ボタンをクリックすると、そのフォルダに対するコラボレータの一覧が取得し、表示される仕組みになっています。
※設定を変更すれば、フォルダではなくファイルピッカーにもなります。
図:Pickerでフォルダを選択できる
図:コラボレータ一覧取得後
コラボレータの共有期限変更
コラボレータ一覧の各レコードのアクションにあるアイコンをクリックするとダイアログが表示され、権限を変更し「変更」をクリックすると、Box APIが叩かれて、権限が変更されます。この権限変更メソッドは同時にexpires_atに値を指定することで、共有期限の変更も可能です(有償アカウントのみ)。
図:共有期限変更ダイアログ