LANG SELRCT

Apps Script Reference  (Create: Create new Spreadsheet | Create new Apps Script

Wednesday, October 20, 2021

GASでWebアプリを作ってローカルファイルをGoogleドライブにアップしたい(アプリを開くユーザーとファイルをアップロードするユーザーを分けたい)



GASでWebアプリを作ってローカルファイルをGoogleドライブにアップしたい

以下の条件を満たすもの

条件
  1. アップロード先はGoogleドライブの共有フォルダ
  2. そのフォルダには権限が設定されている
  3. Webアプリを開いたユーザーはそのフォルダにアクセスする権限がない
  4. アップロード後、3のユーザにアップロードしたファイルだけ閲覧権限を付与する

というニッチな条件を満たすために考えました。


条件3をどうするか
「Webアプリを開いたユーザーはそのフォルダにアクセスする権限がない」

権限がないので、ファイルをアップロードする直前に権限を付与する?
  • 閲覧権限だけだとアップロードできないので編集権限も必要
  • フォルダへの編集権限を付与すると、フォルダ内にある他のファイルも閲覧、編集できる
  • そうなると4を満たせない


そこで考えた方法
「フォルダの編集権限を持ったユーザーが代わりにファイルをアップロードする」

Webアプリを開いたユーザーとは別のAdminユーザにやってもらう。
もう一つGASのプロジェクトを作るとできそう。

どうやるか考えてみる。
  1. ファイルをアップロードする処理は別のアプリを作って実行する
  2. それをライブラリとして扱う
  3. ライブラリ側アプリの実行ユーザーはアップロード先フォルダの編集権限を持っている
ライブラリでは実行ユーザーを指定できないため、doPostで考えてみる。(2022/01/03 追記)

  1. ファイルアップロードするdoPostの処理を別のプロジェクトで作る
  2. doPost側でpayloadを受け取ってドライブにアップロードする
  3. doPost側のアプリの実行者は自分(アップロード先フォルダの編集権限を持っている)

これでできそう。


そして書いたコード


画面を操作する側のGASプロジェクト


コード.gs
function doGet() {
  return HtmlService.createHtmlOutputFromFile("index");
}

function upload_file_gs(reader_result, file_name) {
  var email = Session.getActiveUser().getEmail();// ファイルの閲覧権限を追加したいユーザー(この例では実行したユーザーを指定)
  var payload = {
    "reader_result": reader_result,
    "file_name": file_name, 
    "email": email
  }

  const options = {
    "method": "post",
    "payload": JSON.stringify(payload),
    "contentType": "application/json"
  }
  var url = "doPost側のWebアプリのURL";
  const response = UrlFetchApp.fetch(url, options).getContentText();
  return response;
}

ウェブアプリケーションのデプロイ設定



※グレーのコードはライブラリで考えたときのコードの備忘録(不完全)

コード.gs
function doGet() {
  return HtmlService.createHtmlOutputFromFile("index");
}

function upload_file_gs(reader_result, file_name) {
  var email = "追加したいメールアドレス";
  var file_url = FileUpload.run(reader_result, file_name, email)
  return file_url;
}



index.html
<!DOCTYPE html>
<html>
<body>
  <form>
    <div>
      <input type="file" id="my_files" multiple>
    </div>
  </form>
  <label id="upload_label"></label>
  <div id="upload_div"></div>
  <script>
    document.getElementById("my_files").addEventListener("change", upload_files);
    var files_len = 0;
    var uploaded_len = 0;

    function upload_files() {
      var files = document.getElementById("my_files").files;
      files_len = files.length;
      for (var i = 0; i < files.length; i++) {
        uploadFile(files[i]);
        document.getElementById("upload_label").textContent = "アップロードしています...";
      }
    }

    function uploadFile(file) {
      var reader = new FileReader();
      reader.onload = function() {
        var reader_result = reader.result;
        var file_name = file.name
        google.script.run
          .withSuccessHandler(uploaded)
          .upload_file_gs(reader_result, file_name);
      }
      reader.readAsDataURL(file);
    }

    function uploaded(file_url) {
      create_link(file_url);
      uploaded_len++;
      if (uploaded_len == files_len) {
        document.getElementById("upload_label").textContent = "アップロードしました";
      }
    }

    function create_link(file_url) {
      var div = document.getElementById("upload_div");
      var br = document.createElement("br");
      var link = document.createElement("a");
      link.textContent = file_url;
      link.setAttribute("href", file_url);
      link.setAttribute("target", "_blank");
      div.appendChild(link);
      div.appendChild(br);
    }
  </script>
</body>
</html>



doPost側のGASプロジェクト


コード.gs
/************************************
payloadはeventObject.postData.contentsで取得できる
取得してGoogleドライブに保存する
************************************/
function doPost(eventObject) {
  const contents = JSON.parse(eventObject.postData.contents);
  const created = createFile(contents);  
  return ContentService.createTextOutput(created).setMimeType(ContentService.MimeType.JSON);
}

const FOLDER_ID = "GoogleドライブのフォルダID";
function createFile(contents) {
  var reader_result = contents["reader_result"];
  var file_name = contents["file_name"];
  var email = contents["email"];
  var result_split = reader_result.split(',');
  var content_type = result_split[0].split(';')[0].replace('data:', '');
  var row_data = result_split[1];
  var data = Utilities.base64Decode(row_data);
  
  var file = Utilities.newBlob(data, content_type, file_name);
  var folder = DriveApp.getFolderById(FOLDER_ID);
  var drive_file = folder.createFile(file);
  var file_url = drive_file.getUrl();
  drive_file.addViewer(email);
  return file_url;
}

ウェブアプリケーションのデプロイ設定



※グレーのコードはライブラリで考えたときのコードの備忘録(不完全)

コード.gs
var FOLDER_ID = "保存先フォルダのID";
function run(reader_result, file_name, email) {
  var result_split = reader_result.split(',');
  var content_type = result_split[0].split(';')[0].replace('data:', '');
  var row_data = result_split[1];
  var data = Utilities.base64Decode(row_data);
  
  var file = Utilities.newBlob(data, content_type, file_name);
  var folder = DriveApp.getFolderById(FOLDER_ID);
  var drive_file = folder.createFile(file);
  var file_url = drive_file.getUrl();
  drive_file.addViewer(email);
  return file_url;
}



実行手順

操作する側のWebアプリを開いて、以下のようにファイルをアップロードします。


「ファイル選択」をクリックして出てくるモーダルでローカルファイルを開きます。



「アップロードしています...」表示になります。



アップロードされたファイルへのリンクが表示されます。


関連記事


index.htmlはこれと同じです。
Google Driveに複数ファイルをアップロードしたい(submitボタン使わない)


GASでAPIを作ってPOSTでpayloadを渡したい


GASのライブラリの扱いは別途書き残しました。
Google Apps Scriptでライブラリを作って使う

Latest post

Extracting data from Google Sheets with regular expressions

Introduction Regular expressions are a powerful tool that can be used to extract data from text.  In Google Sheets, regular expressions ca...