Google Apps Scriptでアクセスしてるユーザを元に処理をする方法【GAS】
問い合わせの中で「Google Apps ScriptのWebAppからのSession.getActiveUser().getEmail()が有効に働かないというお話を頂き、検証してみたところ、2020年にセキュリティ上の制約が加わり、Google Workspaceのドメイン内ユーザでは問題は無いものの、Gmailアカウントなど外部のドメイン外ユーザの場合は、メアドが取得できなくなったようです。
ということで、そこまで含めて取得した上で、そのメアドを元に処理を分岐する仕組みを作ってみました。
目次
今回使用するスプレッドシート等
どちらのスプレッドシートも、外部ユーザにわざわざ公開する必要はありません。あくまでもウェブアプリケーションを使わせるだけなので。但し、管理者権限だとメアドが取れない為、2つに分けてurlfetchappで通信するようにするためにこのような仕様にしています。
認証窓口側からのメアド問い合わせで、ユーザ認証側にメアドが存在すればOKを返し、無ければNGを返すという単純な仕様です。さらに個別に独自の認証キーでも用意しておけば、メアドのみで認証よりマシになるかもしれません。
これまでのスクリプト
事前準備
以下のスクリプトは、Google Apps for Work 〜 G Suiteで利用できていたスクリプトなのですが、Google Workspaceになるとともに、ドメイン内ユーザ以外ではこのメソッドは利用が出来なくなっているようです。しかし、引き続きドメイン内ユーザであれば取得が可能なので、社内アプリで使う上では有効な手段です。
使うための事前準備は以下の通り。
- スプレッドシートにメアドを記録した承認リストシートを用意します。ここに記述されてるメアドの人のみにアクセス可とします。
- フォームのメイン部分は全体を<div id=”mainform”style=”display:none”></div>などで括ってしまいます。最初はフォームを表示しないようにします。
- GAS側にGetUser()という関数を用意。アクセスしてきてるユーザのメアドを取得し判定するコードを記述します。メアドが承認リストある場合にはOKを、ない場合にはNGを返します。
- HTML側に最初に実行する関数として、google.script.run.withSuccessHandler(onSuccess).GetUser();を記述しておき、onSuccessはGetUserからの返り値を受取り、NGの場合には2.のdivにinnerHTMLで「アクセス不可」の文字を入れてしまう。OKのときはdocument.getElementById(“mainform”).style.display = “block”で表示してあげるなどのロジックを組む。
- ウェブアプリケーションは組織内公開とする(実行権限はオーナー権限で動かす)
こんな具合です。これで特定の人のみウェブアプリケーションを使えるようになります。承認リストの管理が必要になりますけれどね。具体的なコード例は以下の通り。
GAS側コード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
function GetUser(){ //スクリプトプロパティからシートのIDを取得 var Properties = PropertiesService.getScriptProperties(); var ssid = Properties.getProperty("sheetid"); //アクセス中のユーザのメアドを取得 var nowuser = Session.getActiveUser(); //リターン値の初期値を格納 var retman = "NG"; //承認リストの値を取得 var ss = SpreadsheetApp.openById(ssid).getSheetByName("承認リスト").getRange("A2:A").getValues(); var length = ss.length; //承認リストにメアドがあるか?探索 var array = []; for(var i = 0;i<length;i++){ //メアドが空白のものはスルーする if(ss[i][0] == ""){ continue; } //User情報を照合し、あったらOKを返す if(nowuser == ss[i][0]){ retman = "OK"; } } //値を返す return JSON.stringify([nowuser,retman]); } |
- Session.getActiveUser().getEmail()にて、アクセスしてきてるユーザのメアドを取得し、スプレッドシート上のデータと照合する
- 但し、Google Workspaceのドメイン内ユーザでしか利用できない
HTML側コード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
//最初に実行する google.script.run.withSuccessHandler(onSuccess).GetUser(); //GetUserの実行結果を受け取る function onSuccess(data){ //返り値を配列に戻す var json = JSON.parse(data); //判定結果を取得 var result = json[1]; //ノードを取得 var node = document.getElementById("mainform"); //判定結果に基いて処理 if(result == "NG"){ //OKなのでフォームを表示する node.style.display = "block"; //アラートを出してみる alert(json[0] + "さん、こんにちは!!") }else{ //NGなのでメッセージをnodeに叩き込む node.innerHTML = json[0] + "さんは、アクセス不可ですよ"; node.style.display = "block"; } } |
これで、承認リストにはないユーザにはアプリケーションを表示せず、リストに乗ってる人はアプリケーションを利用できるようになります。
今回作成したスクリプト
さて問題は、セキュリティ上の制約が加わった事で、Google WorkspaceユーザであってもノーマルなGMailアカウントであっても「外部ユーザに対して使わせる」という意味では、承認リストを公開するわけには行きません(見れるのも編集できるのもよろしくない)。しかし、肝心のメソッドが「作成者の権限で動かした場合には、メアドが取得できず空になる」というのでは、どうにもならない。
という事で、これを回避しつつこれまでと同様の効果を得るためには、更なる一工夫が必要になります。以下のエントリーも参考にしてください。
事前準備
ユーザ認証用シート
ユーザ認証用シートは、様々なメアドを格納しておく為のシートで、外部に公開しません。よって、このシートにアクセスできるのは「管理者」である作成者のみです。こちら側はスクリプトを開き、gasheet変数にこのスプレッドシートのIDを書き込んでおきます。こちらは、管理者権限で動かす為、UI側からの情報を受け取って後ろで作業をする部分を担当します。
そしてウェブアプリとしてデプロイしますが、
- 実行権限は「自分」で設定する
- アクセスできるユーザは「全員」とする
ここでのデプロイしたURLは次の項目で利用するのでコピーしておきます。
※アクセスできるユーザが組織内ではなく「全員」としている理由は、そもそも2つにファイルを分けている関係で、別のファイルから別のファイルへのアクセスをさせると、GASのWebAppは出力結果はリダイレクトが生じる為、followRedirectsをtrueにしないと返り値を取得できない。しかし、認証有りとすると出力結果を得られず、認証窓口側で表示していた画面がリダイレクト先に飛ばされてしまい、元のWebApp側で出力結果を得られない為。
図:こんな感じでデプロイする
認証窓口となるスプレッドシート
こちらは、外部のユーザがアクセスしてくる為のスプレッドシートで主にウェブアプリのUIを担当します。よって、こちら側は見せるロジック以外は特に装備もしませんし、スプレッドシート側には情報も記載しません。
スクリプトエディタを開いて
- このスプレッドシートのIDをssid変数に記載する
- authurl変数にはユーザ認証用シートでコピーしておいたデプロイしたURL +exec?param1=という形で、入れておく。
そしてウェブアプリとしてデプロイしますが、
- 実行権限は「ウェブアプリケーションにアクセスしてるユーザ」で設定する
- アクセスできるユーザは「Googleアカウントを持つ全員」とする
結果として、ウェブアプリアクセス時に最初の1回目にスコープの認証と安全でないウェブアプリへのアクセスが必要になってしまいますが、仕方ない。コレばかりは(そもそも、Google Apps Scriptのウェブアプリは外部の人間まで含めて実行を許容する仕組みになっていない為)
図:こちらはユーザ権限で実行するようにする
認証作業
認証窓口側のURLを外部ユーザがアクセスすると最初の1回目の認証は必ず行わければなりません。この手順は以下の通り
- URLにアクセスする
- Permissionの認可をするため、Review Permissionをクリックする
- 次の画面でアクセスユーザが自分のアカウントでログインする
- 左下の詳細を表示をクリック
- 安全ではないページに移動をクリックする
- すると、ウェブアプリにアクセスできて、urlfetchappにてユーザ認証用URLにアクセスして結果が表示される
少々手間が増えますがこの認証作業は基本最初の一回だけなので、以降ユーザは再度この為にサインインは不要です(予めログインしていないとアプリにはアクセス出来ませんが)
図:パーミッションの許諾をする画面
図:安全ではないアクセスをする必要がある
GAS側コード
ユーザ認証用シート
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
//リスト用スプレッドシート var gasheet = "このスプレッドシートのID"; //パラメータを受け取って判定する function doGet(e) { //受け取るパラメータを用意する var mail = e.parameter.param1; //スプレッドシートのリストと照合 var ss = SpreadsheetApp.openById(gasheet).getSheetByName("users").getRange("A2:A").getValues(); var result = "NG"; for(var i = 0;i<ss.length;i++){ //照合してあったらOKを返す console.log(ss[i][0]) if(ss[i][0] == mail){ result = "OK"; break; } } //照合結果を返す return ContentService.createTextOutput(result); } |
- こちらはdoGetでウェブAPIとしてリリースされていますので、GETもしくはPOSTで受け取ります
- 返り値はOKかNGかだけを返しますが、OKだったら、何か処理をしてから返すのでも良いでしょう
- 管理者権限で動かしているので、ユーザの権限の及ばない処理を後ろで行わせる事が可能です。
- 今回はメアドだけなので、param1のみ取得していますが、param2などを追加してURLに加えてリクエストしてもらうようにすれば、拡張する事が可能です。
認証窓口となるスプレッドシート
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
var ssid = "このスプレッドシートのID"; var authurl = "https://script.google.com/macros/s/ここに認証用URL側のID/exec?param1=" function GetUser() { var objUser = Session.getActiveUser(); var email = objUser.getUserLoginId(); //承認用URLに投げる var url = authurl + email; console.log(url) //Access Tokenを取得 const token = ScriptApp.getOAuthToken(); //リクエストオプション var param = { method : "get", followRedirects : true, muteHttpExceptions:true, validateHttpsCertificates : false, headers : {"Authorization": "Bearer " + token}, //401エラーが出る場合はここをコメントアウト }; //リクエスト結果を取得 var resp = UrlFetchApp.fetch(url,param); var word = resp.getContentText(); //HTML側へ返す return word; } |
- こちら側はユーザ権限で実行します。この場合は、Session.getActiveUser().getUserLoginId()にてメアドを取得可能
- 取得したメアドを持ってして、UrlfetchAppにて、ユーザ認証側シートで公開したURLに対してリクエストする
- この時、followRedirectsをtrueにしておかないとリダイレクトが発生する為、値が取れなくなるので注意
- 相手側で処理が終わるとOKが返ってくるので、これをgetContentTextで取得し、結果をユーザに返します。
- 401エラーが出る場合には、「headers : {"Authorization": "Bearer " + token},」の部分をコメントアウトして再度デプロイしてみてください(当方だと、コメントアウトせずともアクセス出来ていますが)
図:このエラーが出る場合は対処が必要
HTML側コード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<!DOCTYPE html> <html> <head> <base target="_top"> <script> //自動実行 google.script.run.withSuccessHandler(onSuccess).GetUser(); //返り値を反映 function onSuccess(data){ console.log(data); document.getElementById("test").innerHTML = data; } </script> </head> <body> <div id="test"></div> </body> </html> |
- こちらは単純にGAS側のメソッドを呼んで、結果を受け取ったらdivに出力するだけです。
- この処理は認証窓口となるスプレッドシートのみに装備しています
- ユーザ認証側では特にUIは持っていないので、HTML側コードはありません。
注意点
ユーザリストにあるかないか?のチェック後に、ウェブアプリの挙動として、別に「管理者権限で動かしたい」処理がある場合、今回のケースだとちょっと工夫が足りません(ユーザの可否しか相手側のdoGetの処理が対応していない為)。ユーザ認証後、何かのボタンをクリックして、例えば非公開のファイルを管理者権限でコピーし、ユーザ可否でOKだった場合にはアクセス権限を付与するといったような処理をしたい場合には以下の3パターンで処理を追加実装が必要です。
- doGetのパラメータにてparam3として処理タスクの分類を追加し、これに応じてdoGet内で処理を分岐させる(この場合、ユーザ可否についても処理は加えておいたほうが良い。でないと、不可の人間がURL直叩きで管理者権限での挙動をUIを飛ばして実行出来てしまう為)
- doGetは一つのプロジェクトに一個しか用意出来ないので、以下のエントリーを参考にして、1つのスプレッドシートに複数のプロジェクトを作成し、そちら側で認証とは別の処理を行うdoGetを用意する(別途デプロイが必要ですし、URLも違うものになるので若干管理が煩雑になります)
- doGetを使わず、GCPのApps Script APIを利用してREST APIを構築し、OAuth2.0認証で構築するほうが手間は掛かりますが、王道とも言えます。
個人的には、1.がオススメです。parameterでただのユーザ認証か?ユーザ認証をしつつ処理をするか?の2つを実装して、ユーザ可否でOKだった場合にだけ、ユーザ認証側で処理を続行するようにしています。ただ、自分自身がもし実装するなら、3.で実装すると思う。
Google Pickerを使いたい
例として管理者側にある「非公開のファイル」をユーザ可否でOKになった者の場合、ユーザ側で指定したフォルダにそのファイルをコピーしたい、というケースがあります。しかし旧方式の場合の問題点として
- 管理者側はユーザが指定したフォルダにはアクセス権限が無いので、管理者権限で動かすとコピーしてあげられない
- また、その場合ユーザがフォルダを指定したくても、Pickerは管理者権限で動くので、自身のフォルダではなく管理者側のフォルダが表示されてしまい、指定が出来ない
- 逆にユーザ権限で動かした場合、Pickerでユーザ側のフォルダ指定は可能ですが、管理者側のファイルは非公開なのでアクセスが出来ないのでコピーが出来ない
という問題にぶつかります。Pickerを諦めてコピーはユーザのドライブのroot直下にするしかない為、痛し痒しの状態になります。しかし、今回の分離方式の場合では
- 認証窓口となる外部ユーザ用のスプレッドシートはユーザ権限で動かしてるので、Pickerでフォルダの指定が可能
- この時、指定したフォルダに対して管理者側のメアドをアクセス権限で追加する処理を入れておきます。
- リクエスト先のユーザ認証側は管理者権限で動かしてるので、メアドとフォルダのIDをparamで送ってあげれば、管理者側からフォルダが見える状態なので、そのフォルダに対して非公開ファイルをコピー可能
- できればコピー後にコピーしたファイルのオーナー権限をユーザ側に変更(ユーザ認証側ファイル)、指定したフォルダに対する管理者側のアクセス権限削除(認証窓口側ファイル)などの処理を双方のスクリプトに追加しておくと尚丁寧です。
といった事が可能になるため、非公開ファイルを許可した特定の人にだけユーザ指定のフォルダにコピーという離れ業が実現可能です。なお、Pickerの実装については以下のエントリーを参考にしてください。