Box APIでBox Shieldの分類の操作をしてみる



## やりたいこと


カスタムアプリからAPIを通してBoxを利用する場合、Box Sheld（のスマートアクセス機能）って、APIから使えるの？どうやってセキュリティ分類をファイルにつけるの？を調査しました。


## TL;DR


Box APIからもセキュリティ分類を操作して、Box Shield スマートアクセスを利用可能です。

node.jsでのやりかたはこんな感じです。

```js
const boxSDK = require("box-node-sdk");
const config = require("./config"); // config.json

const main = async () => {
  const sdk = boxSDK.getPreconfiguredInstance(config);
  const saClient = await sdk.getAppAuthClient("enterprise");

  // 社内ユーザー1として実行
  saClient.asUser("12510354596");

  // テストファイル2 = 656467312576
  await saClient.files.addMetadata(
    "656467312576", // ファイルID
    saClient.metadata.scopes.ENTERPRISE,
    "securityClassification-6VMVochwUWo", // 分類つけるときの定数
    { Box__Security__Classification__Key: "機密情報" } // この部分を変える
  );
};

main();
```




## そもそもBox Shieldってどういうもの？



Box Shieldとは、Boxのアドオンで、Boxの標準のセキュリティを更に拡張するオプションです。
Box Shieldをざっと俯瞰で説明すると、大きくは2つの（全然ちがう）以下の機能があります。

- スマートアクセス
    - 不注意、または悪意の操作による情報漏えいを予防
    - 分類されたフォルダ・ファイルに誤ってアクセス権を付与してもアクセス不可
- 異常検知
    - ユーザーの操作をAIで分析、悪意のある操作を検出する
    - 不審な場所（警戒地域からのアクセス）、不審なセッション（特定IDに対して複数の地域からの同タイミングのアクセス）、異常なダウンロード（通常発生しないような大量ダウンロード）を検出

今回対象とするのは、スマートアクセスの方です。


#### スマートアクセス（アクセスポリシー）の使いかた


基本的なスマートアクセスの使いかたは以下の3ステップ。簡単です。

1. 分類を作る
2. アクセスポリシーを作る
3. ファイルやフォルダに分類を適用する

参考： https://support.box.com/hc/en-us/articles/360044196353-Using-Smart-Access





画面でみていきましょう。



#### 1. 分類を作る



まず分類を作ります。分類はメタデータの一種です。分類自体はただのメタデータなので、単体では何もできません。アクセスポリシーと一緒につかって初めて価値が出ます。分類はGovernanceでリテンションのためにもつかわれたりします。

Box 管理コンソール > 分類 > 新規作成ボタンを押し、以下の項目を入力し保存ボタンを押します。


![screencapture-trial-entgov-app-box-com-master-classification-create-1587521524090.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/45767/1ee13a2e-f305-65d9-95c9-240e0f1a674c.png)




**分類名**：わかりやすい名前をつけます。ここでは「機密情報」にしてみます。

**ラベルカラー**：視認性を上げるために、ここでは赤を選んでみます。下にプレビューの例でどのように見えるかがわかります。

**分類の定義**：ここに書いた文章が、各コンテンツについた分類にマウスオーバーした時やコンテツの詳細ウィンドウに表示されます。わかりやすい文章を入れておきます。





#### 2. アクセスポリシーを作る

次にアクセスポリシーを作ります。

Box 管理コンソール > Shield >アクセスポリシー > ポリシーの作成ボタンを押し、以下の項目を入力します。

（Box Shieldのアドオンを有効にしていないテナントではShieldが表示されません）

![screencapture-trial-entgov-app-box-com-master-shield-access-policies-43281157-edit-1587527157106.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/45767/670eb0ed-f629-9c7c-5503-08342fd57305.png)





**ポリシー名**：ここでは「機密情報ポリシー」としておきます。

**説明**：わかりやすいポリシーの説明を入れます。

**コンテンツの種類**：ここで、1.分類を作るで作った分類を選びます。

分類を選ぶ他に、分類がついていないコンテンツ全てに適用するアクセスポリシーとすることも可能です。

**セキュリティコントロール**：ここで実際にどのような制御をしたいのかを決めます。

セキュリティコントロールでは、以下の5種類のコントロールを組み合わせて設定可能です。



* 1.外部コラボレーションの制限 

外部コラボレーションに関する制限です。
Boxの画面上で共有を押した時の上半分の「ユーザーを招待」に関しての制限です。
以下から選択します。
(1)すべての外部コラボレーションをブロック、(2) 選択した組織以外の全員がコラボレーション可能、（3）しょうにんされた組織のみがコラボレーション可能
このサンプルでは、(1)すべての外部コラボレーションをブロック、を選択しておきます。



* 2.共有リンクの制限

リンクの共有に関する制限です。
Boxの画面上で共有を押した時の下半分の「リンクを共有」に関しての制限です。
以下から選択します。
(1) 公開、(2)会社およびコラボレーターにのみ、(3)コラボレータのみ
この記事では、(3)コラボレータのみとしておきます。

この他、以下のような制限も可能となりますが、ここでは割愛します。

* 3.ダウンロードと印刷の制限
* 4.アプリケーションの制限
* 5.FTPの制限



#### 3. ファイルやフォルダに分類を適用する



フォルダ／ファイルに対して、分類として、上記で作成した機密情報の分類をセットするだけです。



確認のため以下のシナリオを用意しました。


**テストのための状況**

* 「テストフォルダ」の所有者が社内ユーザー1
* 「テストフォルダ」には、「テストファイル1」と「テストファイル2」が入っている。
* 「テストフォルダ」に社内ユーザー2と社外ユーザーAが、編集者として招待されている
* 「テストフォルダ」への共有リンクが、リンクを知っている全員＋表示およびダウンロード可能になっている

わかりずづらいので関係を図にします。

![テストの関係.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/45767/ad9ed4b5-c717-d229-d38d-cc4df3fa994b.png)


テストファイル１、テストファイル２ともに、全員アクセス出来る状況です。
社内ユーザー1は所有者として、社内ユーザー2と社外ユーザーAはコラボレーターとして。
公開共有リンクを経由した場合、これ以外のユーザー例えば社内ユーザー3や社外ユーザーBもテストフォルダ配下にアクセス可能です。



**分類を付けてみる**

では、ここで先程作成した分類：機密情報を、テストファイル1につけてみます。

社内ユーザー１と社内ユーザー２にとって変化はありません。
社外ユーザーAからみると、テストファイル1とテストファイル2は以前のように一覧はできています。
ただし、テストファイル１の横にバージョン番号が表示されなくなりました。
公開共有リンクを経由して参照している社外ユーザーBも社外ユーザーAと同じ状態です。


<img width="1069" alt="スクリーンショット 2020-04-22 14.45.53.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/45767/cd44f193-d8fb-12e4-c298-14d3de3bcc5f.png">


ここで、社外ユーザーAが、テストファイル１を開いてみると、以下のような画面になります。
これは公開共有リンク経由で開いている社外ユーザーBも同じ挙動になります。

<img width="1066" alt="スクリーンショット 2020-04-22 14.46.10.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/45767/2a8b2a85-f262-f637-adb8-41b43cd9f42b.png">

分類が表示されており、どういう理由でブロックされているのかが明確です。


以上が、基本的なBox Shieldのスマートアクセスの使いかたです。
社内ユーザーが誤った共有操作をしても、分類をつけておくことで（あるいはつけない場合のアクセスポリシーを設定することで）予防的・明示的にアクセス制限ができますね。



## APIからの使いかた



さて、ここまで長々とBox Shieldについて書いてきましたが、これをAPIから操作できるのか？というのが今回のお題です。



分類を作るところからできそうですが、今回は、テストファイル1についている分類「機密情報」を取り出して、テストファイル2につけてみることを今回のゴールとしてやってみましょう。


### とりあえずユーザーIDとファイルIDを用意

事前準備として、テストファイル1にアクセスできる社内ユーザー1のユーザーIDを取り出します。

※ 管理対象ユーザーの情報を抜き出すには、開発者コンソール > 当該アプリ > 構成 > アプリケーションアクセスを、"Enterprise"にセットするひつようがあります。合わせて、高度な機能で、ユーザーとして操作を実行もONにしておきます。

さらに、操作対象のファイルIDも用意します。
このあたりは苦労しない部分だとおもうのでコードは割愛します。
IDが以下の通りだとわかりました。


社内ユーザー1のid: "12510354596"
テストファイル1のid: "656474520915"
テストファイル2のid: "656467312576"

### テストファイル1のメタデータを取り出してみる

```js
const boxSDK = require("box-node-sdk");
const config = require("./config"); // config.json

const main = async () => {
  const sdk = boxSDK.getPreconfiguredInstance(config);
  const saClient = await sdk.getAppAuthClient("enterprise");

  // 社内ユーザー1として実行 (Service Accountではテストファイル1が見れない）
  saClient.asUser("12510354596"); 
  // テストファイル1の全てのメタデータを取り出してみる
  const metadatas = await saClient.files.getAllMetadata("656474520915");

  console.log(metadatas.entries);
};

main();

```



console.logの出力内容がこちらです。

```js

[
  {
    '$type': 'securityClassification-6VMVochwUWo-43a8a5c1-c0ed-4ee5-bab4-1cd373af5054',
    '$parent': 'file_656474520915',
    '$id': 'ecca948a-23a0-4bf9-9719-77d965320888',
    '$version': 1,
    '$typeVersion': 4,
    Box__Security__Classification__Key: '機密情報',
    '$template': 'securityClassification-6VMVochwUWo',
    '$scope': 'enterprise_324763110',
    '$canEdit': true
  }
]

```



`Box__Security__Classification__Key`の部分だけプロパティ名が$からはじまっていませんが、このKey：Valueだけが、通常のメタデータでいうところのデータのようです。

`'$template': 'securityClassification-6VMVochwUWo'` この長々しい名前が固定で分類のメタデータ・テンプレートの名前になるようです。



### テストファイル2に分類を適用してみる

それでは、取り出した分類の情報をもとに、別ファイル（テストファイル２）に分類を適用してみます。
以下のコードで実現できました。

```js
const boxSDK = require("box-node-sdk");
const config = require("./config"); // config.json

const main = async () => {
  const sdk = boxSDK.getPreconfiguredInstance(config);
  const saClient = await sdk.getAppAuthClient("enterprise");

  // 社内ユーザー1として実行
  saClient.asUser("12510354596");

  // テストファイル2 = 656467312576
  await saClient.files.addMetadata(
    "656467312576", // ファイルID
    saClient.metadata.scopes.ENTERPRISE,
    "securityClassification-6VMVochwUWo", // 分類つけるときの定数
    { Box__Security__Classification__Key: "機密情報" } // この部分を変える
  );
};
main();
```

ポイントは、`securityClassification-6VMVochwUWo` という決められた固定のテンプレート名を渡すことと、`{ Box__Security__Classification__Key: "機密情報" }` という可変部分のデータを渡すことです。

※ ちなみにaddMetadataを使うと重複して分類が付く場合エラーになりなるので注意してください。

## スマートアクセスがAPI経由でも有効なことを確認


APIをつかって分類を付与できることはわかりました。次に、APIでファイル情報をとりだすときに分類をつけたことでアクセス制御ができているのか確認してみます。
実験のため、分類がついていないテストファイル3を追加します。
このような状態です。

![テストの関係2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/45767/c8b1761f-bfe0-88c8-e93d-0b1cdbb1c0a8.png)

期待値としては、以下のようになります。
社内ユーザー１と社内ユーザー２からは、テストファイル１〜３が全て見れる。
社外ユーザーAからは、テストファイル３だけが見れる。


### 社外ユーザーAで、標準BOX画面を見てみる

確認として、社外ユーザーAからはAPI経由でファイルが見れるのかを実験してみます。
APIでどのように見えるかを確認する前に、社外ユーザーAの標準画面がどうなっているかを軽くみておきます。
テストファイル３にだけバージョン番号が出ていることで分かるとおり、テストファイル１とテストファイル２は、スマートアクセスが効いています。

![スクリーンショット 2020-04-23 7.20.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/45767/a0471cdf-8fd7-8ca4-efdf-4c9ad707d367.png)


### 社外ユーザーAで、APIからファイルを確認してみる

社外ユーザーが所属するBOXテナントで、アプリケーションの設定を行い、JWT認証のConfigファイルを作成します。
このコードで確認します。ext_configは、外部ユーザーが所属するBoxテナントで生成したconfigファイルです。


```js
const boxSDK = require("box-node-sdk");
const config = require("./ext_config"); // 外部ユーザーの会社のもの
const fs = require("fs");

const main = async () => {
  const sdk = boxSDK.getPreconfiguredInstance(config);

  // const saClient = await sdk.getAppAuthClient("enterprise");

  // 【罠】外部共有のフォルダ・ファイルは、asUserでは見れない！！！！
  // saClient.asUser("12539232009");

  // 社外ユーザーAとしてClientを作る
  const client = sdk.getAppAuthClient("user", "12539232009");

  //　テストフォルダの中を一覧し、ファイルをダウンロードしてみる。
  const items = await client.folders.getItems("111123885335");
  for (const i of items.entries) {
    console.log("ファイル情報:", i.id, i.name);
    try {
      // ダウンロード
      await client.files.getReadStream(i.id, null, (error, stream) => {
        if (error) {
          return;
        }
        const out = fs.createWriteStream(`./${i.name}`);
        stream.pipe(out);
        console.log("ダウンロード成功", out.path);
      });
    } catch (e) {
      console.log("ダウンロード失敗", e.message);
    }
  }
};
main();

```

ハマりポイントとして、ServiceAccountからのAsUserではなぜか外部共有のフォルダ・ファイルがAPIからは見えないというところです。回避するために、社外ユーザーAとしてｃｌｉｅｎｔを作成します。
実行結果がこちらです。

```txt:実行結果
ファイル情報: 656474520915 テストファイル1.docx
ダウンロード失敗 Unexpected API Response [403 Forbidden | 1w93cyge8mvx6mun.0a219bf9256709fa67e670a5c6b0d68b7] forbidden_by_policy - Access denied - Blocked by Shield Access Policy
ファイル情報: 656467312576 テストファイル2.docx
ダウンロード失敗 Unexpected API Response [403 Forbidden | wxjwpoge8mvxgctw.0fa22daf9cadb1fc20e20da8858918282] forbidden_by_policy - Access denied - Blocked by Shield Access Policy
ファイル情報: 656747679283 テストファイル3.docx
ダウンロード成功 ./テストファイル3.docx

```

テストファイル１と２は一覧はできるものの、コンテンツをダウンロードしようとするとエラーになります。
エラーメッセージも、Blocked by Shield Access Policyとあり、Shieldによりブロックされたことがわかります。
以上、APIでも、スマートアクセスが効いていることが確認できました。