はじめに
Box上のファイルが更新されたら、即座に検知し特定アクションを行いたい場合があります。
Webhookの仕組みを用いて、Boxにイベントトリガーを設定し、イベント通知してみました。
構成図
Webhook(ウェブフック)とは
- Webアプリケーションで特定のイベントが発生したら別のWebアプリケーションに通知を発行する仕組み
- Web開発でカスタムコールバックを用いてWebアプリケーションの動作を追加または変更するための方法
- リクエストは、HTTP POSTリクエストとして行われる
- フォーマットには通常JSONが利用される
Box Webhook V2とは
イベントトリガーが発動されたら、指定URLにHTTP POST
リクエストするための
Webhook API
が用意されています。
- Webhookの作成
- Webhookの更新
- Webhookの削除
- Webhook署名検証
- Boxから送信されたWebhookペイロードが改ざんされていないことを確認する
- 署名により、中間者攻撃またはリプレイ攻撃による影響が大幅に軽減される
Webhookを使用するためのBoxの設定
認証方法の設定
- Boxアプリケーション設定画面で、
OAuth 2.0 with JSON Web Tokens (Server Authentication)
を選択
- Boxアプリケーション設定画面で、アプリ設定Jsonファイルをダウンロード
Manage Webhooks
スコープを設定
- Boxアプリケーション設定画面で、
Application Scopes
セクションのManage Webhooks
にチェックを入れ、設定を保存
- Boxアプリケーション承認画面で、
Review and Submit
ボタンをクリックし承認申請を行う
- Box管理者が、カスタムアプリ管理画面で、アプリケーションの承認申請を許可(管理者のみ実施可能)
Webhook操作用のPythonサンプルプログラム
"""BOX Webhook操作プログラム"""
# 前提としてboxsdkのインストールが必要
# pip install boxsdk
from boxsdk import Client, JWTAuth
# Boxからダウンロードしたアプリ設定Jsonデータ
CONNECTION_INFO = {
"boxAppSettings": {
"clientID": "xxx",
"clientSecret": "xxx",
"appAuth": {
"publicKeyID": "xxx",
"privateKey": "xxx",
"passphrase": "xxx",
},
},
"enterpriseID": "xxx",
}
class WebhookOper:
"""Webhookの操作"""
def __init__(self, connection_info: dict):
config = JWTAuth.from_settings_dictionary(connection_info)
self.client = Client(config)
def list_webhookids(self):
"""全webhook_idリストを取得"""
webhooks = self.client.get_webhooks()
for webhook in webhooks:
print(f"The webhook ID is {webhook.id} and the address is {webhook}")
def get_folder_id(self, webhook_id: str):
"""webhook_idに紐づくfolder_idを取得"""
webhook = self.client.webhook(webhook_id=str(webhook_id)).get()
print(webhook.target)
def create_webhook(self, folder_id: str, notify_url: str):
"""フォルダにwebhook作成(ファイル操作イベントトリガーを指定)"""
folder = self.client.folder(folder_id=folder_id)
webhook = self.client.create_webhook(
folder,
[
"FILE.UPLOADED",
"FILE.DELETED",
"FILE.TRASHED",
"FILE.RESTORED",
"FILE.COPIED",
"FILE.MOVED",
"FILE.RENAMED",
],
notify_url,
)
print(f"Webhook ID is {webhook.id} and the address is {webhook.address}")
def delete_webhook(self, webhook_id: str):
"""webhookを削除"""
self.client.webhook(webhook_id=webhook_id).delete()
print(f"webhook_id={webhook_id} has been deleted")
if __name__ == '__main__':
# Webhook操作
who = WebhookOper(CONNECTION_INFO)
folder_id = 'xxx' # Webhook設定対象のフォルダID
notify_url = 'https://xxx' # Webhook通知先URL
# Webhookをfolder_idに作成
who.create_webhook(folder_id, notify_url)
# Webhook一覧を表示
who.list_webhookids()
Webhookイベント通知のサンプルデータ
Boxフォルダ移動により通知されるFOLDER.MOVED
イベントのリクエストボディサンプルです。
{
"type": "webhook_event",
"id": "xxx",
"created_at": "2022-12-30T05:28:48-07:00",
"trigger": "FOLDER.MOVED",
"webhook": {
"id": "770111431",
"type": "webhook"
},
"created_by": {
"type": "user",
"id": "20188107977",
"name": "name1",
"login": "name1@ari-jp.com"
},
"source": {
"id": "169775002763",
"type": "folder",
"sequence_id": "3",
"etag": "3",
"name": "test2",
"created_at": "2022-12-30T01:17:18-07:00",
"modified_at": "2022-12-30T05:01:55-07:00",
"description": "",
"size": 78666,
"path_collection": {
"total_count": 3,
"entries": [
{
"type": "folder",
"id": "0",
"sequence_id": null,
"etag": null,
"name": "All Files"
},
{
"type": "folder",
"id": "169370964125",
"sequence_id": "0",
"etag": "0",
"name": "私のBox Notes"
},
{
"type": "folder",
"id": "169780883554",
"sequence_id": "1",
"etag": "1",
"name": "test3"
}
]
},
"created_by": {
"type": "user",
"id": "20188107977",
"name": "name1",
"login": "name1@ari-jp.com"
},
"modified_by": {
"type": "user",
"id": "20188107977",
"name": "name1",
"login": "name1@ari-jp.com"
},
"trashed_at": null,
"purged_at": null,
"content_created_at": "2022-12-30T01:17:18-07:00",
"content_modified_at": "2022-12-30T05:01:55-07:00",
"owned_by": {
"type": "user",
"id": "20188107977",
"name": "name1",
"login": "name1@ari-jp.com"
},
"shared_link": null,
"folder_upload_email": null,
"parent": {
"type": "folder",
"id": "169780883554",
"sequence_id": "1",
"etag": "1",
"name": "test3"
},
"item_status": "active"
},
"additional_info": {
"after": {
"id": "169780883554",
"type": "folder"
},
"before": {
"id": "169370964125",
"type": "folder"
}
}
}
(余談)Webhookのイベント通知で、HTTPリクエストのヘッダーとボディの長さに制限がないか問い合わせた結果
GitHubプロジェクトbox-python-sdk
にissueを投げてみました。
https://github.com/box/box-python-sdk/issues/774
- 質問
Is there a limitation on length of HTTP Header and Body
included in a notification from V2 webhooks?
- 回答
boxsdk API側では制限を設けておらず、HTTP仕様に準拠するとのことでした。
I've asked service owners but they have not specified any limit.
So this response follow HTTP specification.
- ちなみに、
RFC 7230
の仕様では、HTTPヘッダーのフィールド長に制限は設けられていないようです。
3.2.5. Field Limits
HTTP does not place a predefined limit on the length of each header
field or on the length of the header section as a whole, as described
in Section 2.5.
おわりに
Box上のファイル操作を、Webhookを使ってイベント通知してみました。
受信イベントは、ビジネスプロセス(分析や監視)に活用できそうです。