Automatizar solicitudes de eliminación

*Este contenido se traduce usando la IA (Beta) y puede contener errores. Para ver esta página en inglés, haz clic en aquí.

El Reglamento General de Protección de Datos (RGDP) es un reglamento europeo sobre protección de datos y privacidad. Otorga a los individuos el derecho a solicitar la eliminación de sus datos personales, conocido como el derecho a la eliminación. Si almacena cualquier Información Personalmente Identificable (Información de Identificación Personal (IIP)) de sus usuarios, como sus ID de usuario, debe cum

En lugar de manejar solicitudes manualmente, puede configurar un webhook y usar un bot dentro de una aplicación de mensajería de terceros para automatizar el proceso. Como datos se almacenan siendo la forma más común de almacenar datos PII, este tutorial proporciona un ejemplo de cómo crear un bot dentro de Guilded o Discord que usa la API de Open Cloud para almacenar

Flujo de trabajo

Al completar este tutorial, debería poder crear un programa personalizado que se ejecuta localmente para automatizar el manejo de solicitudes de eliminación de derechos de los usuarios. El flujo de trabajo para este proceso es el siguiente:

  1. El soporte de Roblox recibe una solicitud de eliminación de un usuario.
  2. El webhook de Roblox se activa, que contiene el ID de usuario y una lista de ID de lugar de inicio para las experiencias que se han unido en el payload.
  3. Tu bot escucha estas notificaciones de webhook, verifica su autenticidad y utiliza la API de Open Cloud para almacenar datos para eliminar los datos almacenados en los almacenes de datos.
  4. El bot responde a la solicitud de webhook en Discord o Guilded con el estado de eliminación.

Configurando un Webhook con integración de terceros

Antes de crear un bot, configura un servidor con integración de webhook en la aplicación de mensajería de terceros. Luego usa el servidor para configurar un webhook en el Panel de Creator.

Configurando un servidor

Las siguientes etapas muestran cómo configurar el servidor utilizando Guilded o Discord.

  1. Crea un nuevo servidor Guilded. Si no estás familiarizado con el proceso, see Soporte Guilded.
  2. Bajo las configuraciones de privacidad , establece el servidor como servidor privado. El servidor crea automáticamente un canal privado de # general como canal predeterminado.
  3. Cree una integración de webhook con el nuevo servidor y le dé un nombre que pueda entender fácilmente, como GDPR Hook. Si no estás familiarizado con el proceso, see Soporte de Gremio .
  4. Copia la URL del webhook y almacénala en un lugar seguro. Solo permite que los miembros de tu equipo de confianza accedan a ella, ya que filtrar la URL puede permitir a los actores maliciosos enviar mensajes falsos y potencialmente eliminar tus datos de usuario.

Configurando un Webhook en Roblox

Después de obtener la URL del tercer servidor, úsela para configurar un webhook en el Panel de Creator. asegúrese de realizar las siguientes configuraciones:

  • Agregue la URL del servidor de Guilded o Discord como el URL del Webhook .
  • Incluya un secreto personalizado. Aunque un secreto es opcional para completar la configuración, debe incluir uno para evitar que los actores maliciosos se hagan pasar por Roblox y eliminen sus datos. Para obtener más información sobre el uso de un secreto, consulte Verificando la seguridad del webhook.
  • Seleccione Solicitud de borrado derecho debajo de Activadores .

Puedes probar el webhook usando el botón Respuesta de prueba para ver si recibes una notificación en tu canal # general de Roblox. Si no recibes la notificaciones, intenta de nuevo o revisa tus configuraciones del servidor para solucionar el error.

Example notification on Guilded

Configurando un Bot

Después de agregar el webhook, úselo para configurar el bot con los siguientes pasos:

  1. Abre la lista de Todos los servidores haciendo clic en su icono o usando el atajo:

    • CtrlS en Windows.
    • S en Mac.
  2. Seleccione su servidor para recibir notificaciones de derecho a eliminar.

  3. Expand the list under Inicio del servidor and select Administrar Bots .

  4. El servidor crea automáticamente un canal privado de # general como tu canal por defecto.

  5. Haga clic en el botón Crear un bot y agregue un nombre de bot. Guilded lo redirige a la página de configuración del bot.

  6. Seleccione la sección API en la página de configuración del bot.

  7. En la sección Fichas, haz clic en el botón Generar ficha.

  8. Guarde y almacene el token generado en un lugar seguro.

Crear una llave de Clave APIOpen Cloud

Para permitir que su tercer bot de terceros acceda a sus almacenes de datos para almacenar datos Información de Identificación Personal (IIP)de los usuarios, crea una llave de API de Open Cloud que puede acceder a sus experiencias y agregar el permiso de almacenamiento de datos de Eliminar entrada para la eliminación de datos. Si usa almacenes de datos de terceros para almacenar

Obtener identificadores de experiencias y lugares

Para que el bot localice los datos PII solicitados por los usuarios para eliminar, obtenga los siguientes identificadores de todas las experiencias que tiene la intención de usar el bot:

  • El ID del Universo , el identificador único de tu experiencia.
  • El ID del Lugar de Inicio , el identificador único del lugar de inicio de una experiencia.

Para obtener estos identificadores, abra la página Creaciones en Panel del Creador . Luego seleccione una experiencia y copie el ID del Universo y 2> Lugar de inicio de la ID2> .

Copy Universe ID and Copy Start Place ID options from Creator Dashboard

Agregar Scripts

Después de configurar el webhook, el bot y la llave de API para almacenar datos, agrégelos a los scripts que implementan la lógica de automatización del bot. El siguiente ejemplo usa Python 3:

  1. Instala bibliotecas de Python usando los siguientes comandos:

    Instala bibliotecas

    pip3 install discord
    pip3 install guilded.py==1.8.0
    pip3 install requests
    pip3 install urllib3==1.26.6
  2. Copia y guarda los siguientes scripts que correspondan a diferentes partes de la lógica del bot en el mismo directorio:

    bot_config.py

    DISCORD_BOT_TOKEN = ""
    GUILDED_BOT_TOKEN = ""
    OPEN_CLOUD_API_KEY = ""
    ROBLOX_WEBHOOK_SECRET = ""
    # Diccionario del ID del Lugar de Salida para
    # (ID del universo, lista de (nombres de almacenes de datos, rango y clave de entrada)) para
    # Almacenes de datos estándar
    # Los datos del usuario almacenados debajo de estas entradas se eliminarán
    STANDARD_DATA_STORE_ENTRIES = {
    # ID de lugar de inicio
    111111111: (
    # ID del Universo
    222222222,
    [
    ("StandardDataStore1", "Scope1", "Key1_{user_id}"),
    ("StandardDataStore1", "Scope1", "Key2_{user_id}"),
    ("StandardDataStore2", "Scope1", "Key3_{user_id}")
    ]
    ),
    33333333: (
    444444444,
    [
    ("StandardDataStore3", "Scope1", "Key1_{user_id}")
    ]
    )
    }
    # Diccionario del ID del Lugar de Salida para
    # (ID del universo, lista de (nombres de almacenes de datos, rango y clave de entrada)) para
    # Almacenamiento de datos ordenado
    # Los datos del usuario almacenados debajo de estas entradas se eliminarán
    ORDERED_DATA_STORE_ENTRIES = {
    111111111: (
    222222222,
    [
    ("OrderedDataStore1", "Scope2", "Key4_{user_id}")
    ]
    )
    }
    data_stores_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
    # Evita que se repita el ataque dentro de la ventana de 300 segundos
    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
    # Valida la firma
    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
    # Firma válida
    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):
    # Parsley recibe un mensaje por el ID del usuario y el ID del juego
    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 "", []
    guilded_bot.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):
    # Parsa y valida el mensaje
    user_id, start_place_ids = message_parser.parse_message(message)
    if not user_id or not start_place_ids:
    return
    # Elimina los almacenes de datos estándar que almacenan datos del usuario
    [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)}")
    # Elimina los datos almacenados de los usuarios
    [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.GUILDED_BOT_TOKEN)
    if __name__ == "__main__":
    run()
  3. En el archivo bot_config.py para la configuración principal del bot:

    1. Establece DISCORD_BOT_TOKEN o GUILDED_BOT_TOKEN en el token generado por tu bot.
    2. Establece OPEN_CLOUD_API_KEY como la llave API que creaste.
    3. Establece ROBLOX_WEBHOOK_SECRET como el secreto que estableces al configurar el webhook en el Panel de control del creador.
    4. En STANDARD_DATA_STORE_ENTRIES y ORDERED_DATA_STORE_ENTRIES diccionarios para localizar el almacén de datos de cada registro para eliminar:
      1. Agregue sus ID de lugar de inicio copiados como claves.
      2. Añadir los ID del Universo como el primer elemento del valor de tu lista.
      3. Reemplace el segundo elemento del tutorial con el nombre, rango, nombre de la llave de entrada y el ID de usuario asociado de sus almacenes de datos. Si usa un diferente esquema de datos, modifique para que coincida con su propio esquema de datos de acuerdo.
  4. Ejecuta el siguiente comando para ejecutar el bot:

    Ejecutar Guilded Bot

    python3 guilded_bot.py
  5. El botón entonces comienza a escuchar y verificar las llamadas de API de Roblox para tener derecho a las solicitudes de eliminación y llama al punto de interconexión abierto para eliminar el tiendade datos correspondiente.

Testeo

Puede crear y ejecutar un mensaje de prueba para verificar que su programa personalizado puede manejar correctamente las solicitudes de eliminación y eliminar datos PII:

  1. Envía una solicitud HTTP POST a tu servidor web de Guilded o Discord con el cuerpo de solicitud siguiente:

    Solicitud de Ejemplo

    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. Si tienes un secreto de webhook:

    1. Genera un Roblox-Signature al aplicar la codificación HMAC-SHA256 a tu clave de secreto webhook.
    2. Establezca el tiempo actual utilizando UTC timestamp en segundos como Timestamp .
  3. Pon juntos el description en el siguiente formato:

    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}`.

    Por ejemplo:

    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

Su programa debería ser capaz de identificar que su mensaje es de la fuente oficial de Roblox ya que ha codificado el mensaje con su secreto. Luego debería eliminar los datos PII asociados con su solicitud.

Cuerpo de Ejemplo

{
"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://crear.roblox.com/dash/artefactos/webhook/roblox_logo_metal.png",
"text": "Roblox-Signature: UIe6GJ78MHCmU/zUKBYP3LV0lAqwWRFR6UEfPt1xBFw=, Timestamp: 1683927229"
}
}
]
}