Closed41

Box API Java SDK でサービスアカウント → ユーザーにフォルダー共有する

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

このスクラップについて

Box 連携するアプリとユーザーの間でフォルダーを共有したい。

ユーザー認証(OAuth 2.0)を使えばユーザーの代理アクセスができるがユーザーによる設定が必要となる。

具体的にはアプリを承認(Box では authorization / 認可を「承認」と呼んでいる)してもらう必要がある。

サーバー認証を使えば管理者がユーザーの代わりにアプリを認可できる。

as-user ヘッダーを使うことでユーザーの代理アクセスもできる。

しかし、アプリの権限がかなり強大になるので安全性に懸念がある。

もう少しライトな代替手段としてフォルダー共有(Box では「コラボレーション」と呼ばれる)がある。

フォルダー共有はユーザーとアプリ(厳密にはサービスアカウント)の間でも可能であり、過去にユーザー → アプリへのフォルダー共有には成功した経験がある。

しかし、アプリ → ユーザーへのフォルダー共有はまだやったことがない。

このスクラップでは Kotlin から Box API Java SDK を使ってアプリ → ユーザーへのフォルダー共有ができるかどうかを試してみようと思う。

アプリ → ユーザーにフォルダー共有する場合、ユーザーにとっては招待メールに含まれるリンクにアクセスするだけなので設定が楽になりそう。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

カスタムアプリ作成から始める

前に作成したカスタムアプリを流用することもできるが設定手順の記憶があやふやになっていきているので復習を兼ねてカスタムアプリ作成から始めようと思う。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

カスタムアプリ作成

開発者コンソールのマイアプリページで「アプリの新規作成」ボタンを押す。

「カスタムアプリ」を選ぶ。

アプリ名(例:SA2User)と目的(例:自動化)を入力してから「次へ」ボタンを押す。

認証方法として「サーバー認証(クライアント資格情報許可)」を選んでから「アプリの作成」ボタンを押す。

無事にアプリが作成された。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

OAuth 2.0 資格情報の取得

カスタムアプリ構成タブのOAuth 2.0 資格情報セクションから取得する。

「クライアントシークレットを取得」ボタンを押すと 2 段階認証ページが表示される。

認証コードを入力してから「送信」ボタンを押すと元のページに戻るので再度「クライアントシークレットを取得」ボタンを押す。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

jdkVersion の修正

build.gradle.kts を開いて kotlin > jvmToolchain の jdkVersion を 11 → 8 に変更する。

build.gradle.kts
kotlin {
    jvmToolchain(8)
}
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Box API Java SDK の追加

build.gradle.kts を開いて dependencies セクションに依存関係を追加する。

build.gradle.kts
dependencies {
    testImplementation(kotlin("test"))
    implementation("com.box:box-java-sdk:4.1.0")
}

依存関係を追加したら Gradle ツールウィンドウの左上のリロード 🔃 ボタンを押す。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

環境変数の追加

Run / Debug 構成を編集して下記 3 点の環境変数を追加する。

  • BOX_CLIENT_ID
  • BOX_CLIENT_SECRET
  • BOX_ENTERPRISE_ID

Enterprise ID はカスタムアプリ一般設定タブで確認できる。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

コーディング

フォルダーを作成してみる。

src/main/kotlin/Main.kt
import com.box.sdk.BoxCCGAPIConnection
import com.box.sdk.BoxFolder

fun main(args: Array<String>) {
    val clientID = System.getenv("BOX_CLIENT_ID")
    val clientSecret = System.getenv("BOX_CLIENT_SECRET")
    val enterpriseID = System.getenv("BOX_ENTERPRISE_ID")
    val api = BoxCCGAPIConnection.applicationServiceAccountConnection(
        clientID,
        clientSecret,
        enterpriseID,
    )

    val rootFolder = BoxFolder.getRootFolder(api)
    rootFolder.createFolder("Folder to be shared")
}
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

この時点で実行してみる

まだアプリの承認すらしていないので下記の例外が発生した。

Exception in thread "main" com.box.sdk.BoxAPIResponseException: The API returned an error code [400] invalid_grant - Grant credentials are invalid

承認していない場合は 400 Bad Request エラーになるようだ。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

カスタムアプリ承認

管理コンソールのカスタムアプリマネージャページで「アプリの追加」ボタンを押す。

クライアント ID を入力してから「次へ」ボタンを押す。

情報を確認してから「承認」ボタンを押す。

カスタムアプリが承認された。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

この状態で実行する。

例外が変化した、権限設定を行なっていないのでこれは期待通り。

Exception in thread "main" com.box.sdk.BoxAPIResponseException: The API returned an error code [403 | .06e4ae6ed24930073215d89d53503a9cf]

403 であることから権限まわりのエラーであることが推測される。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

アプリケーションスコープ追加

開発者コンソールのカスタムアプリ構成タブでアプリケーションスコープセクションに含まれる「Boxに格納されているすべてのファイルとフォルダへの書き込み」にチェックを入れてから「変更を保存」ボタンを押す。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

カスタムアプリ再承認

スコープを追加した後は管理コンソールから再承認が必要になる。

カスタムアプリマネージャでメニューボタンを押して「アプリを再承認」を押す。

この際、アプリケーションスコープが反映されていないことがあるので注意。

一度「キャンセル」ボタンを押してアプリの詳細を表示する。

カスタムアプリマネージャに戻って同じ操作を行い、反映されたことを確認してから「再承認」ボタンを押す。

無事に再承認された。

不安な場合はアプリの詳細を表示して確認する。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

この状態で実行してみる

特にエラーが発生せずに実行を完了できたのでフォルダーが作成されたようだ。

不安な場合は再実行するとフォルダー名の重複で例外が発生することで確認できる。

Exception in thread "main" com.box.sdk.BoxAPIResponseException: The API returned an error code [409 | smugmshdgc272ag3.0f6c1a187f12b4ddd36b8a1b270b18a58] item_name_in_use - Item with the same name already exists

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

コラボレーション追加

https://github.com/box/box-java-sdk/blob/v4.1.0/doc/collaborations.md#add-a-collaboration

フォルダー ID とユーザー ID が必要になる。

コード例
BoxCollaborator user = new BoxUser(api, "user-id")
BoxFolder folder = new BoxFolder(api, "folder-id");
folder.collaborate(user, BoxCollaboration.Role.EDITOR);

ユーザー ID の代わりにメールを指定することもできる。

コード例
BoxFolder folder = new BoxFile(api, "id");
folder.collaborate("gcurtis@box.com", BoxCollaboration.Role.EDITOR);
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

フォルダー ID 取得

src/main/kotlin/Main.kt
import com.box.sdk.BoxCCGAPIConnection
import com.box.sdk.BoxFolder

fun main(args: Array<String>) {
    val clientID = System.getenv("BOX_CLIENT_ID")
    val clientSecret = System.getenv("BOX_CLIENT_SECRET")
    val enterpriseID = System.getenv("BOX_ENTERPRISE_ID")
    val api = BoxCCGAPIConnection.applicationServiceAccountConnection(
        clientID,
        clientSecret,
        enterpriseID,
    )

    val rootFolder = BoxFolder.getRootFolder(api)

    for (info in rootFolder) {
        println("%s %s".format(info.id, info.name))
    }
}
実行結果(例)
123456789012 Folder to be shared

フォルダー ID は環境変数 BOX_FOLDER_ID として登録しておく。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

環境変数の追加

ついでに BOX_USER_EMAIL という名前でコラボレーション先のユーザーのメールアドレスを登録しておく。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

コーディング

src/main/kotlin/Main.kt
import com.box.sdk.BoxCCGAPIConnection
import com.box.sdk.BoxCollaboration
import com.box.sdk.BoxFolder

fun main(args: Array<String>) {
    val clientID = System.getenv("BOX_CLIENT_ID")
    val clientSecret = System.getenv("BOX_CLIENT_SECRET")
    val enterpriseID = System.getenv("BOX_ENTERPRISE_ID")
    val folderID = System.getenv("BOX_FOLDER_ID")
    val userEmail = System.getenv("BOX_USER_EMAIL")
    val api = BoxCCGAPIConnection.applicationServiceAccountConnection(
        clientID,
        clientSecret,
        enterpriseID,
    )

    val folderToBeShared = BoxFolder(api, folderID)
    folderToBeShared.collaborate(userEmail, BoxCollaboration.Role.EDITOR)
}
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

この時点で実行する

下記の例外が発生した。

Exception in thread "main" com.box.sdk.BoxAPIResponseException: The API returned an error code [400] invalid_client - The client credentials are invalid

もう少し強い権限設定が必要なのかな?

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

この状態で実行してみる

結果は変わらなかった。

Exception in thread "main" com.box.sdk.BoxAPIResponseException: The API returned an error code [400] invalid_client - The client credentials are invalid

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

この状態で実行する

結果は変わらなかった。

Exception in thread "main" com.box.sdk.BoxAPIResponseException: The API returned an error code [400] invalid_client - The client credentials are invalid

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

原因はしょうもないことだった

環境変数を入力するときに誤操作があったのか BOX_CLIENT_ID が BOX_CLIENT_IDs という名前になっていた。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

共有フォルダーにファイルを追加してみる

src/main/kotlin/Main.kt
import com.box.sdk.BoxCCGAPIConnection
import com.box.sdk.BoxFolder

fun main(args: Array<String>) {
    val clientID = System.getenv("BOX_CLIENT_ID")
    val clientSecret = System.getenv("BOX_CLIENT_SECRET")
    val enterpriseID = System.getenv("BOX_ENTERPRISE_ID")
    val folderID = System.getenv("BOX_FOLDER_ID")
    val api = BoxCCGAPIConnection.applicationServiceAccountConnection(
        clientID,
        clientSecret,
        enterpriseID,
    )

    val sharedFolder = BoxFolder(api, folderID)
    val fileContent = "It works!"
    val inputStream = fileContent.byteInputStream(Charsets.UTF_8)
    val fileName = "File to be shared"
    sharedFolder.uploadFile(inputStream, fileName);
}
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

先ほどは言及しなかったが

コラボレーションを作成した時にユーザーの方では特に何もすることなくフォルダー共有が行われていた。

ユーザーの方でメール記載のリンクをクリックして承認とか必要なのかと思っていたがそれすらないようだ。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

無事に検証できた

サービスアカウント → ユーザーへのフォルダー共有を確認することができた。

必要となる権限も少ないのでユーザー代理アクセスよりもおすすめかも知れない。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

まとめ

  • フォルダー共有するにはフォルダー ID とユーザーのメールアドレスが必要になる。
  • 未検証だがユーザーのメールアドレスはユーザー ID で代替できるようだ。
  • 必要なアプリアクセスレベルは「アプリアクセスのみ」で大丈夫。
  • 必要なアプリケーションスコープは「書き込み」で大丈夫。
  • フォルダー共有を行うと即座に承認される、ユーザーの方で承認操作は必要ない。
  • ユーザーへの通知はメールと Web UI の両方から来る。
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

おわりに

先ほども言及したがユーザーとファイルをやり取りするために必要となる権限がユーザー代理アクセスよりもはるかに弱いので安全性が高い方法だと感じた。

Box 連携アプリを開発する場合はこちらの方法を使う方がベターだと感じた。

必要となるのもユーザーのメールアドレスだけなのでユーザー ID を調べなくて良いのはとてもありがたい。

このスクラップは2023/05/01にクローズされました