Automatiser les demandes de suppression des droits

*Ce contenu est traduit en utilisant l'IA (Beta) et peut contenir des erreurs. Pour consulter cette page en anglais, clique ici.

Le règlement général sur la protection des données (GDPR) est un règlement européen sur la protection des données et de la confidentialité.Il accorde aux individus le droit de demander la suppression de leurs données personnelles, appelée le droit à l'oubli.Si vous stockez toute information personnellement identifiable (IPI) de vos utilisateurs, telle que leurs identifiants d'utilisateur, vous devez respecter les exigences du RGPD en supprimant cette information lorsque vous recevez la demande d'un utilisateur.

Au lieu de gérer les demandes manuellement, vous pouvez configurer un webhook et utiliser un bot dans une application de messagerie tiers pour automatiser le processus.Comme les magasins de données sont les plus courants pour stocker les données PII, ce tutoriel fournit un exemple sur la façon de créer un bot dans Guilded ou Discord qui utilise la Open Cloud API pour les magasins de données pour supprimer les données PII en tant que solution d'automatisation.

Flux de travail

Une fois ce tutoriel terminé, vous devriez être en mesure de créer un programme personnalisé localement exécutable qui automatise la gestion des demandes de suppression des utilisateurs.Le flux de travail pour ce processus est comme suit :

  1. Le support de Roblox reçoit une demande de suppression d'un utilisateur.
  2. Le webhook Roblox est déclenché, contenant l'ID utilisateur et une liste d'ID de lieu de départ pour les expériences auxquelles ils ont rejoint dans le payload.
  3. Votre bot écoute ces notifications de webhook, vérifie leur authenticité et utilise la Open Cloud API pour les magasins de données pour supprimer les données PII stockées dans les magasins de données.
  4. Le bot répond au message Webhook dans Discord ou Guilded avec le statut de suppression.

Configurer un webhook avec une intégration tiers

Avant de créer un bot, configurez un serveur avec une intégration de webhook sur l'application de messagerie tiers.Ensuite, utilisez le serveur pour configurer un webhook sur le tableau de bord du créateur.

Configurer un serveur

Les étapes suivantes montrent comment configurer le serveur en utilisant Guilded ou Discord.

  1. Créez un nouveau serveur Guilded. Si vous ne connaissez pas le processus, voir soutien Guilded.
  2. Dans les paramètres de confidentialité , définissez le serveur en privé.Le serveur crée automatiquement un canal privé # général comme chat (chat privé)par défaut.
  3. Créez une intégration de webhook avec le nouveau serveur et donnez-lui un nom que vous pouvez facilement comprendre, comme GDPR Hook .Si vous n'êtes pas familier avec le processus, voir soutien guidé.
  4. Copiez l'URL du webhook et stockez-la dans un emplacementsécurisé.Ne permettez d'y accéder qu'aux membres de l'équipe de confiance, car fuiter l'URL peut permettre aux acteurs malveillants d'envoyer des messages faux et de potentiellement supprimer les données de votre utilisateur.

Configurer un webhook sur Roblox

Après avoir obtenu l'URL du serveur tiers, utilisez-la pour configurer un webhook sur le tableau de bord du créateur.assurez-vous d'effectuer les paramètres suivants :

  • Ajoutez l'URL du serveur Guilded ou Discord comme URL de Webhook .
  • Incluez un secret personnalisé secret .Bien que le secret soit facultatif pour terminer la configuration, vous devez en inclure un pour empêcher les acteurs malveillants de se faire passer pour Roblox et de supprimer vos données.Pour plus d'informations sur l'utilisation d'un secret, voir Vérifier la sécurité du webhook.
  • Sélectionnez Droit à la demande d'effacement sous déclencheurs .

Vous pouvez tester le webhook en utilisant le bouton Réponse de test pour voir si vous recevez une notification dans le canal #général de votre serveur de Roblox.Si vous ne recevez pas la notification, réessayez ou vérifiez les paramètres de votre serveur pour résoudre l'erreur.

Example notification on Guilded

Configurer un bot

Après avoir ajouté le webhook, utilisez-le pour configurer le bot avec les étapes suivantes :

  1. Ouvrez la liste de tous les serveurs en cliquant sur son icône ou utilisez le raccourci :

    • CtrlS sur Windows.
    • S sur Mac.
  2. Sélectionnez votre serveur pour recevoir les notifications de droit à l'effacement.

  3. Étendez la liste sous Accueil du serveur et sélectionnez Gérer les bots .

  4. Le serveur crée automatiquement un canal privé # général comme chat (chat privé)par défaut.

  5. Cliquez sur le bouton Créer un bot et ajoutez un nom de bot. Guilded vous redirige vers la page de configuration du bot.

  6. Sélectionnez la section API sur la page de configuration du bot.

  7. Dans la section jetons , cliquez sur le bouton générer un jeton .

  8. Enregistrez et stockez le jeton généré dans un emplacementsûr.

Créer une clé Open Cloud API

Pour permettre à votre bot tiers d'accéder à vos magasins de données pour stocker les données PII des utilisateurs, créez une clé Open Cloud API qui peut accéder à vos expériences et ajoutez la permission Supprimer l'entrée de magasins de données pour la suppression des données.Si vous utilisez des magasins de données ordonnés pour stocker des IPI, vous devez également ajouter l'autorisation Écrire des magasins de données ordonnés.Une fois la finition terminée, copiez et sauvegardez la clé API dans un emplacement sécurisé pour l'utiliser dans des étapes ultérieures.

Obtenir les identifiants des expériences et des lieux

Pour que le bot localise les données PII demandées par les utilisateurs pour suppression, obtenez les identifiants suivants de toutes les expériences auxquelles vous prévoyez d'utiliser le bot :

  • L'ID Univers , l'identifiant unique de votre expérience.
  • L'ID de lieu de départ Start Place ID , l'identifiant unique du lieu de départ d'une expérience.

Pour obtenir ces identifiants :

  1. Passez la souris sur une vignette d'expérience, cliquez sur le bouton et sélectionnez Copier l'ID de l'univers et Copier l'ID du lieu de départ , respectivement.

Ajouter des scripts

Après avoir configuré le webhook, le bot et la clé API pour les magasins de données, ajoutez-les aux scripts qui implémentent la logique d'automatisation du bot.L'exemple suivant utilise Python 3 :

  1. Installez les bibliothèques Python en utilisant les commandes suivantes :

    Installer les bibliothèques

    pip3 install guilded.py==1.8.0
    pip3 install requests
    pip3 install urllib3==1.26.6
  2. Copiez et enregistrez les scripts suivants correspondant à différentes parties de la logique du bot dans le même dossier :

    bot_config.py

    BOT_TOKEN = ""
    OPEN_CLOUD_API_KEY = ""
    ROBLOX_WEBHOOK_SECRET = ""
    # Dictiction de l'ID de lieu de départ à
    # (ID de l'univers, liste des (nom des magasins de données, de la portée et de la clé d'entrée)) pour
    # Stockage de données standard
    # Les données utilisateur stockées sous ces entrées seront supprimées
    STANDARD_DATA_STORE_ENTRIES = {
    # ID de lieu de départ
    111111111: (
    # ID de l'univers
    222222222,
    [
    ("StandardDataStore1", "Scope1", "Key1_{user_id}"),
    ("StandardDataStore1", "Scope1", "Key2_{user_id}"),
    ("StandardDataStore2", "Scope1", "Key3_{user_id}")
    ]
    ),
    33333333: (
    444444444,
    [
    ("StandardDataStore3", "Scope1", "Key1_{user_id}")
    ]
    )
    }
    # Dictiction de l'ID de lieu de départ à
    # (ID de l'univers, liste des (nom des magasins de données, de la portée et de la clé d'entrée)) pour
    # Stockage de données ordonné
    # Les données utilisateur stockées sous ces entrées seront supprimées
    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
    # Empêche l'attaque de relecture dans la fenêtre de 300 secondes
    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
    # Vérifie la signature
    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
    # Signature valide
    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):
    # Parses reçoit le message pour l'ID de l'utilisateur et l'ID du jeu
    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):
    # Parse et valide le message
    user_id, start_place_ids = message_parser.parse_message(message)
    if not user_id or not start_place_ids:
    return
    # Supprime les données des magasins de données standard
    [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)}")
    # Supprime les données des magasins de données commandées de l'utilisateur
    [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. Sur le fichier bot_config.py pour la configuration principale du bot :

    1. Définissez BOT_TOKEN à la valeur générée par votre bot.
    2. Définissez OPEN_CLOUD_API_KEY comme clé API que vous avez créée.
    3. Définissez ROBLOX_WEBHOOK_SECRET comme le secret que vous avez défini lors de la configuration du webhook sur le tableau de bord de Creator.
    4. Dans STANDARD_DATA_STORE_ENTRIES et ORDERED_DATA_STORE_ENTRIES dictionnaires pour localiser le magasin de données de chaque enregistrement à supprimer :
      1. Ajoutez vos ID de lieu de départ copiés en tant que clés.
      2. Ajoutez les ID d'univers comme premier élément de la valeur de tuple.
      3. Remplacez le deuxième élément de la tuple par le nom, la portée, le nom de la clé d'entrée et l'ID d'utilisateur associé de vos magasins de données.Si vous utilisez un schéma de données différent, modifiez-le pour correspondre à votre propre schéma de données en conséquence.
  4. Exécutez la commande suivante pour exécuter le bot :

    Exécuter le bot de guilde

    python3 guilded_bot.py
  5. Le bot commence ensuite à écouter et à vérifier les webhooks Roblox pour les demandes de suppression et appelle l'extrémité du cloud ouverte pour supprimer le boutiquede données correspondant.

Testez

Vous pouvez créer et exécuter un message de test pour vérifier que votre programme personnalisé peut gérer correctement les demandes de suppression et supprimer les données PII :

  1. Envoyez une demande HTTP POST à votre serveur webhook Guilded ou Discord avec le corps de requête suivant :

    Exemple de demande

    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 vous avez un secret webhook :

    1. Générez un Roblox-Signature en appliquant l'encodage HMAC-SHA256 à la clé secrète de votre webhook.
    2. Définissez le temps actuel en utilisant l'horodatage UTC en secondes comme Timestamp .
  3. Assemblez le description dans le format suivant :

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

    Par exemple :

    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

Votre programme devrait être en mesure d'identifier que votre message provient de la source Roblox officielle puisque vous avez codé le message avec votre secret.Il devrait ensuite supprimer les données PII associées à votre demande.

Exemple de corps

{
"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://créer.roblox.com/dashboard/assets/webhooks/roblox_logo_metal.png",
"text": "Roblox-Signature: UIe6GJ78MHCmU/zUKBYP3LV0lAqwWRFR6UEfPt1xBFw=, Timestamp: 1683927229"
}
}
]
}