消去リクエストを自動化する

*このコンテンツは、ベータ版のAI(人工知能)を使用して翻訳されており、エラーが含まれている可能性があります。このページを英語で表示するには、 こちら をクリックしてください。

一般データ保護規則 (GDPR) は、データ保護とプライバシーに関する欧州規制です。それは個人に、知られる個人データの削除をリクエストする権利、「消去権」と呼ばれるものを付与します。ユーザーのユーザーIDなどの個人識別情報を保存する場合、ユーザーのリクエストを受け取ったときにこの情報を削除して、GDPR要件に従わなければなりません。

リクエストを手動で処理するのではなく、ウェブホックを設定し、サードパーティのメッセージアプリ内のボットを使用してプロセスを自動化できます。As データストア が PII データを保存する最も一般的な方法であるため、このチュートリアルでは、オープンクラウド API for データストア を使用して、自動化ソリューションとして PII データを削除するボットを Guilded または Discord 内で作成する方法の例を提供します。

ワークフロー

このチュートリアルを完了すると、ユーザーの消去リクエストの処理を自動化するローカル実行のカスタムプログラムを作成できるはずです。このプロセスのワークフローは次のとおりです:

  1. Roblox サポートは、ユーザから消去リクエストを受け取ります。
  2. Roblox ウェブホックがトリガーされ、ユーザー ID と、ペイロードに参加したエクスペリエンスのスタートプレース ID のリストが含まれています。
  3. ボットはこれらの Webhook 通知を聞き、その正確性を確認し、データストアに保存された PII データを削除するために オープンクラウドAPI for data stores を使用します。
  4. ボットは、Discord または Guilded の Webhook メッセージに削除ステータスで応答します。

サードパーティ統合で Webhook を構成する

ボットを作成する前に、サードパーティのメッセージアプリケーションで Webhook 統合を持つサーバーを設定します。次に、サーバーを使用してクリエイターダッシュボード上の Webhook を構成します。

サーバーを設定する

次のステップでは、Guilded または Discord を使用してサーバーを設定する方法を示します。

  1. 新しい Guilded サーバーを作成します。プロセスに精通していない場合は、Guilded サポート を参照してください。
  2. プライバシー 設定の下で、サーバーをプライベートに設定します。サーバーはデフォルトのチャネルとして、プライベートの #一般 チャンネルを自動的に作成します。
  3. 新しいサーバーと Webhook 統合を作成し、GDPR Hook など、簡単に理解できる名前を付けます。プロセスに精通していない場合は、ギルデッドサポート を参照してください。
  4. Webhook URLをコピーして、安全な場プレースに保存するURLを漏洩すると、悪意のあるアクターが偽のメッセージを送信し、ユーザーデータを削除する可能性があるため、信頼できるチームメンバーのみがアクセスできるようにすること。

Roblox で Roblox(ロブロックス)ebhook を構成する

サードパーティのサーバー URL を取得した後、クリエイターダッシュボードで ウェブホックを構成する を使用します。次の設クリエーターダッシュボードを実行したことを確認してください:

  • Guilded または Discord サーバーの URL を Webhook URL として追加します。
  • カスタムの 秘密 を含めます。設定を完了するためには秘密はオプションですが、悪意のあるアクターが Roblox を偽装してデータを削除するのを防ぐためには、1つ含める必要があります。秘密の使用に関する詳細情報は、ウェブホックのセキュリティを確認する を参照してください。
  • Select 消去リクエストの右を選択 under トリガー .

Webhook を使用して テスト応答 ボタンを使用して、Roblox からサーバーの #一般 チャネルで通知を受け取るかどうかをテストできます。通知を受け取らない場合は、再試行したり、サーバー設定をチェックしてエラーを解決します。

Example notification on Guilded

ボットを構成する

Webhook を追加した後、次の手順でボットを構成します:

  1. アイコンをクリックして すべてのサーバー リストを開き、またはショートカットを使用します:

    • Ctrl S に Windows 上。
    • S に Mac 上で。
  2. 消去通知を受信するサーバーを選択します。

  3. Expand the list under サーバーホーム and select ボットの管理 .

  4. サーバーはデフォルトのチャネルとして、プライベートの #一般 チャンネルを自動的に作成します。

  5. クリックする ボットを作成する ボタンとボット名を追加し、ギルデッドはボット構成ページにリダイレクトします。

  6. ボット構成ページの API セクションを選択します。

  7. トークン セクションで、 トークンを生成 ボタンをクリックします。

  8. 生成されたトークンを安全な場プレースに保存して保存する。

オープンクラウド API キーを作成する

ユーザーの PII データを保存するために、サードパーティのボットがデータストアにアクセスできるようにするには、オープンクラウド API キーを作成して、エクスペリエンスにアクセスし、データ削除のためのデータストアの 削除権限 を追加します。IIを保存するには、注文されたデータストアを使用する場合、注文されたデータストアの 書き込み 権限も追加する必要があります。完了後、API キーを安全な場所にコピーして保存して、後のステップで使用します。

エクスペリエンスと場所の識別子を取得する

ボットがユーザーが削除をリクエストした PII データを見つけるために、ボットを使用する予定のすべてのエクスペリエンスの次の識別子を取得します:

  • ユニバースID 、あなたの経験の唯一の識別子。
  • スタートプレースID 、エクスペリエンスのスタートプレースの唯一の識別子。

これらの識別子を取得するには:

  1. ナビゲート to the クリエイターダッシュボード.

  2. エクスペリエンスのサムネイルの上にマウスポインタを置き、 ボタンをクリックし、それぞれ ユニバースIDをコピーするスタート場所IDをコピーする を選択します。

スクリプトを追加

Webhook、ボット、およびデータストアの設定を完了した後、ボットの自動化ロジックを実装するスクリプトに追加します。次の例では、Python 3 を使用します:

  1. 次のコマンドを使用して Python ライブラリをインストールします:

    ライブラリをインストール

    pip3 install guilded.py==1.8.0
    pip3 install requests
    pip3 install urllib3==1.26.6
  2. 同じディレクトリにあるボットロジックの異なる部分に対応する次のスクリプトをコピーして保存する:

    bot_config.py

    BOT_TOKEN = ""
    OPEN_CLOUD_API_KEY = ""
    ROBLOX_WEBHOOK_SECRET = ""
    # スタート地点IDの辞書を
    # (ユニバースID、(データストア名、スコープ、およびエントリキー)のリスト) について
    # 標準データストア
    # これらのエントリに保存されたユーザーデータは削除されます
    STANDARD_DATA_STORE_ENTRIES = {
    # スタート地点ID
    111111111: (
    # ユニバースID
    222222222,
    [
    ("StandardDataStore1", "Scope1", "Key1_{user_id}"),
    ("StandardDataStore1", "Scope1", "Key2_{user_id}"),
    ("StandardDataStore2", "Scope1", "Key3_{user_id}")
    ]
    ),
    33333333: (
    444444444,
    [
    ("StandardDataStore3", "Scope1", "Key1_{user_id}")
    ]
    )
    }
    # スタート地点IDの辞書を
    # (ユニバースID、(データストア名、スコープ、およびエントリキー)のリスト) について
    # 整理されたデータストア
    # これらのエントリに保存されたユーザーデータは削除されます
    ORDERED_DATA_STORE_ENTRIES = {
    111111111: (
    222222222,
    [
    ("OrderedDataStore1", "Scope2", "Key4_{user_id}")
    ]
    )
    }
    データ_ストア_api.py

    import requests
    import bot_config
    from collections import defaultdict
    """
    Calls Data Stores Open Cloud API to delete all entries for a user_id configured in
    STANDARD_DATA_STORE_ENTRIES. Returns a list of successful deletions and failures to delete.
    """
    def delete_standard_data_stores(user_id, start_place_ids):
    successes = defaultdict(list)
    failures = defaultdict(list)
    for owned_start_place_id in bot_config.STANDARD_DATA_STORE_ENTRIES:
    if owned_start_place_id not in start_place_ids:
    continue
    universe_id, universe_entries = bot_config.STANDARD_DATA_STORE_ENTRIES[owned_start_place_id]
    for (data_store_name, scope, entry_key) in universe_entries:
    entry_key = entry_key.replace("{user_id}", user_id)
    response = requests.delete(
    f"https://apis.roblox.com/datastores/v1/universes/{universe_id}/standard-datastores/datastore/entries/entry",
    headers={"x-api-key": bot_config.OPEN_CLOUD_API_KEY},
    params={
    "datastoreName": data_store_name,
    "scope": scope,
    "entryKey": entry_key
    }
    )
    if response.status_code in [200, 204]:
    successes[owned_start_place_id].append((data_store_name, scope, entry_key))
    else:
    failures[owned_start_place_id].append((data_store_name, scope, entry_key))
    return successes, failures
    """
    Calls Ordered Data Stores Open Cloud API to delete all entries for a user_id configured in
    ORDERED_DATA_STORE_ENTRIES. Returns a list of successful deletions and failures to delete.
    """
    def delete_ordered_data_stores(user_id, start_place_ids):
    successes = defaultdict(list)
    failures = defaultdict(list)
    for owned_start_place_id in bot_config.ORDERED_DATA_STORE_ENTRIES:
    if owned_start_place_id not in start_place_ids:
    continue
    universe_id, universe_entries = bot_config.ORDERED_DATA_STORE_ENTRIES[owned_start_place_id]
    for (data_store_name, scope, entry_key) in universe_entries:
    entry_key = entry_key.replace("{user_id}", user_id)
    response = requests.delete(
    f"https://apis.roblox.com/ordered-data-stores/v1/universes/{universe_id}/orderedDatastores/{data_store_name}/scopes/{scope}/entries/{entry_key}",
    headers={"x-api-key": bot_config.OPEN_CLOUD_API_KEY}
    )
    if response.status_code in [200, 204, 404]:
    successes[owned_start_place_id].append((data_store_name, scope, entry_key))
    else:
    failures[owned_start_place_id].append((data_store_name, scope, entry_key))
    return successes, failures
    message_parser.py

    import time
    import hmac
    import hashlib
    import re
    import base64
    import bot_config
    """
    Parses received message for Roblox signature and timestamp, the footer is only set if you
    configured webhook secret
    """
    def parse_footer(message):
    if not message.embeds[0].footer or \
    not message.embeds[0].footer.text:
    return "", 0
    footer_match = re.match(
    r"Roblox-Signature: (.*), Timestamp: (.*)",
    message.embeds[0].footer.text
    )
    if not footer_match:
    return "", 0
    else:
    signature = footer_match.group(1)
    timestamp = int(footer_match.group(2))
    return signature, timestamp
    """
    Verifies Roblox signature with configured secret to check for validity
    """
    def validate_signature(message, signature, timestamp):
    if not message or not signature or not timestamp:
    return False
    # 300秒以内のリプレイ攻撃を防ぐ
    request_timestamp_ms = timestamp * 1000
    window_time_ms = 300 * 1000
    oldest_timestamp_allowed = round(time.time() * 1000) - window_time_ms
    if request_timestamp_ms < oldest_timestamp_allowed:
    return False
    # 署名を検証する
    timestamp_message = "{}.{}".format(timestamp, message.embeds[0].description)
    digest = hmac.new(
    bot_config.ROBLOX_WEBHOOK_SECRET.encode(),
    msg=timestamp_message.encode(),
    digestmod=hashlib.sha256
    ).digest()
    validated_signature = base64.b64encode(digest).decode()
    if signature != validated_signature:
    return False
    # 有効な署名
    return True
    """
    Parses a received webhook messaged on Discord or Guilded. Extracts user ID, prevents replay attack
    based on timestamp received, and verifies Roblox signature with configured secret to check for
    validity.
    """
    def parse_message(message):
    # ユーザーIDとゲームIDの受信メッセージをパース
    if len(message.embeds) != 1 or \
    not message.embeds[0].description:
    return "", []
    description_match = re.match(
    r"You have received a new notification for Right to Erasure for the User Id: (.*) in " +
    r"the game\(s\) with Ids: (.*)",
    message.embeds[0].description
    )
    if not description_match:
    return "", []
    user_id = description_match.group(1)
    start_place_ids = set(int(item.strip()) for item in description_match.group(2).split(","))
    signature, timestamp = parse_footer(message)
    if validate_signature(message, signature, timestamp):
    return user_id, start_place_ids
    else:
    return "", []
    ギルデッド_ボット.py

    import guilded
    import json
    import bot_config
    import data_stores_api
    import message_parser
    def run():
    client = guilded.Client()
    @client.event
    async def on_ready():
    print(f"{client.user} is listening to Right to Erasure messages")
    """
    Handler for webhook messages from Roblox
    """
    @client.event
    async def on_message(message):
    # メッセージを解析して有効化する
    user_id, start_place_ids = message_parser.parse_message(message)
    if not user_id or not start_place_ids:
    return
    # 標準のデータストアユーザーデータを削除する
    [successes, failures] = data_stores_api.delete_standard_data_stores(user_id, start_place_ids)
    if successes:
    await message.reply(f"Deleted standard data stores data for " +
    f"user ID: {user_id}, data: {dict(successes)}")
    if failures:
    await message.reply(f"Failed to delete standard data stores data for " +
    f"user ID: {user_id}, data: {dict(failures)}")
    # 注文されたデータストアユーザーデータを削除する
    [successes, failures] = data_stores_api.delete_ordered_data_stores(user_id, start_place_ids)
    if successes:
    await message.reply(f"Deleted ordered data stores data for " +
    f"user ID: {user_id}, data: {dict(successes)}")
    if failures:
    await message.reply(f"Failed to delete ordered data stores data for " +
    f"user ID: {user_id}, data: {dict(failures)}")
    client.run(bot_config.BOT_TOKEN)
    if __name__ == "__main__":
    run()
  3. ボットのメイン構成のファイル bot_config.py において:

    1. BOT_TOKEN をボットによって生成されたトークンに設定します。
    2. Set OPEN_CLOUD_API_KEY を作成した API キーとして設定します。
    3. クリエイターダッシュボードで Webhook を設定するときに設定したクリエーターダッシュボード密として ROBLOX_WEBHOOK_SECRET を設定します。
    4. 削除する各レコードのデータストアを見つけるための STANDARD_DATA_STORE_ENTRIES および ORDERED_DATA_STORE_ENTRIES 辞書で:
      1. コピーしたスタート地点IDをキーとして追加します。
      2. ユニバースIDをタプル値の最初の要素として追加する。
      3. トークンの 2番目の要素をデータストアの名前、スコープ、エントリキー名、および関連するユーザーIDに置き換えます。異なるデータスキームを使用する場合は、自分のデータスキームに合わせて変更します。
  4. 次のコマンドを実行してボットを実行します:

    ギルデッドボットを実行

    python3 guilded_bot.py
  5. ボットは次に、消去リクエストと呼び出しのための Roblox ウェブホックを聞き始め、検証し、対応するデータストアを削除するためのオープンクラウドエンドポイン保管を呼び出します。

テスト

カスタムプログラムが消去リクエストと PII データを適切に処理できるかどうかを確認するためのテストメッセージを作成して実行できます:

  1. 次のリクエストボディで Guilded または Discord の Webhook サーバーに HTTP リクエストを送信します:

    例のリクエスト

    curl -X POST {serverUrl}
    -H 'Content-Type: application/json'
    -d '{
    "embeds":[{
    "title":"RightToErasureRequest",
    "description":"You have received a new notification for Right to Erasure for the User Id: {userIds} in the game(s) with Ids: {gameIds}",
    "footer":{
    "icon_url":"https://create.roblox.com/dashboard/assets/webhooks/roblox_logo_metal.png",
    "text":"Roblox-Signature: {robloxSignature}, Timestamp: {timestamp}"
    }
    }]
    }'
  2. Webhook の秘密がある場合:

    1. Webhook の秘密キーに HMAC-SHA256 エンコードを適用して Roblox-Signature を生成します。
    2. 秒単位の UTC タイムスタンプを使用して、現在の時間を Timestamp と設定します。
  3. 次の形式で description を組み合わせてください:

    Description Field Format

    {Timestamp}. You have received a new notification for Right to Erasure for the User Id: {userId} in the game(s) with Ids: {gameIds}`.

    例えば:

    Example Description Field

    1683927229. You have received a new notification for Right to Erasure for the User Id: 2425654247 in the game(s) with Ids: 10539205763, 13260950955

プログラムは、メッセージが秘密でエンコードされたため、メッセージが公式の Roblox ソースから来ていることを識別できるはずです。その後、リクエストに関連する PII データを削除する必要があります。

例の体

{
"embeds": [
{
"title": "RightToErasureRequest",
"description": "You have received a new notification for Right to Erasure for the User Id: 2425654247 in the game(s) with Ids: 10539205763, 13260950955",
"footer": {
"icon_url": "https://create.roblox.com/dashboards/assets/webhooks/roblox_logo_metal.png"
"text": "Roblox-Signature: UIe6GJ78MHCmU/zUKBYP3LV0lAqwWRFR6UEfPt1xBFw=, Timestamp: 1683927229"
}
}
]
}