Automatiser les demandes de droit à l'effacement

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

Les réglementations mondiales sur la protection des données et la vie privée accordent aux individus le droit de contrôler leurs données, y compris le droit de demander leur suppression (souvent appelé « droit à l'effacement » ou « droit à la suppression »). Si vous stockez des données personnelles ou des Informations Personnellement Identifiables (PII), telles que des identifiants d'utilisateur, vous êtes responsable de la conformité aux réglementations de confidentialité applicables en supprimant définitivement ces informations dès réception d'une demande d'utilisateur. Plus d'informations peuvent être trouvées dans RTBF et créateurs.

Au lieu de gérer les demandes manuellement, vous pouvez configurer un webhook et utiliser un bot au sein d'une application de messagerie tierce pour automatiser le processus. Étant donné que les magasins de données sont le moyen le plus courant de stocker des données PII, ce tutoriel fournit un exemple sur la façon de créer un bot au sein de Discord qui utilise l'API Open Cloud pour les magasins de données pour supprimer des données PII comme solution d'automatisation.

Flux de travail

Une fois ce tutoriel terminé, vous devriez être en mesure de créer un programme personnalisé s'exécutant localement qui automatise le traitement des demandes de droit à l'effacement des utilisateurs. Le flux de travail pour ce processus est le suivant :

  1. Le support Roblox reçoit une demande de droit à l'effacement d'un utilisateur.
  2. Le webhook Roblox est déclenché, contenant l'identifiant de l'utilisateur et une liste d'identifiants de lieu de départ pour les jeux auxquels ils ont participé dans la charge utile.
  3. Votre bot écoute ces notifications de webhook, vérifie leur authenticité et utilise l'API Open Cloud 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 du webhook sur Discord avec le statut de suppression.

Configurer un webhook avec une intégration tierce

Avant de créer un bot, configurez un serveur avec intégration webhook sur l'application de messagerie tierce. Ensuite, utilisez le serveur pour configurer un webhook sur le Creator Dashboard.

Configurer un serveur

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

  1. Créez un nouveau serveur Discord. Si vous n'êtes pas familiarisé avec le processus, consultez Support Discord.

  2. Le serveur crée automatiquement un canal #general comme canal par défaut. Cliquez sur l'icône Modifier le canal du canal #general.

  3. Sous Permissions, définissez le canal comme privé.

  4. Créez une intégration webhook avec le nouveau serveur, nommez-la RTBF Hook. Si vous n'êtes pas familier avec le processus, consultez Support Discord.

  5. Copiez l'URL du webhook et conservez-la en lieu sûr. Ne permettez l'accès qu'aux membres de l'équipe de confiance, car la fuite de l'URL peut permettre à des acteurs malveillants d'envoyer de faux messages et potentiellement de supprimer vos données utilisateur.

Configurer un webhook sur Roblox

Après avoir obtenu l'URL du serveur tiers, utilisez-la pour configurer un webhook sur le Creator Dashboard. assurez-vous d'effectuer les réglages suivants :

  • Ajoutez l'URL du serveur Discord en tant que Webhook URL.
  • Incluez un Secret personnalisé. Bien qu'un secret soit facultatif pour compléter la configuration, vous devriez 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, consultez Vérifier la sécurité du webhook.
  • Sélectionnez Demande de droit à l'effacement sous Déclencheurs.

Vous pouvez tester le webhook en utilisant le bouton Test Response pour voir si vous recevez une notification dans le canal #general de votre serveur de la part de Roblox. Si vous ne recevez pas la notification, réessayez ou vérifiez les paramètres de votre serveur pour dépanner l'erreur.

Configurer un bot

Après avoir ajouté le webhook, utilisez-le pour configurer le bot avec les étapes suivantes. Pour plus d'informations, consultez la documentation Discord.

  1. Accédez à la page des applications.
  2. Créez une nouvelle application et nommez-la RTBF Bot.
  3. Le système vous redirige vers les paramètres Informations générales du bot. Copiez et enregistrez son ID d'application dans un endroit sûr.
  4. Dans le menu des paramètres, sélectionnez OAuth2.
  5. Accédez aux paramètres OAuth2
    1. Activez le champ bot, une liste d'Autorisation de Bot supplémentaires s'affiche.
    2. Ajoutez l'autorisation Administrateur. Enregistrez vos modifications.
    3. Enregistrez l'URL générée.
  6. Copiez l'URL générée. Ne fermez pas la page de paramètres de l'application.
  7. Accédez à l'URL générée. Sélectionnez le serveur cible. Cliquez sur le bouton Continuer puis sur le bouton Autoriser.
  8. Revenez à la page de paramètres de l'application et accédez aux paramètres du Bot.
  9. Sous la section Intentions de passerelle privilégiées, activez Intent de contenu des messages.
  10. Dans les paramètres du Bot > section Build-A-Bot, enregistrez le token du bot dans un endroit sûr pour les étapes ultérieures. Si vous ne voyez pas le token, cliquez sur le bouton Réinitialiser le token pour en générer un nouveau.

Créer une clé API Open Cloud

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

Obtenir les identifiants des jeux et des lieux

Pour que le bot puisse localiser les données PII demandées par les utilisateurs pour la suppression, obtenez les identifiants suivants de tous les jeux pour lesquels vous avez l'intention d'utiliser le bot :

  • L'ID de l'univers, l'identifiant unique de votre jeu.
  • L'ID de lieu de départ, l'identifiant unique du lieu de départ d'un jeu.

Pour obtenir ces identifiants :

  1. Accédez au Creator Dashboard.

  2. Survolez la miniature d'un jeu, cliquez sur le bouton et sélectionnez Copier l'ID de l'univers et Copier l'ID de lieu de départ, respectivement.

Ajouter des scripts

Après avoir terminé la configuration du webhook, du bot et de la clé API pour les magasins de données, ajoutez-les aux scripts qui mettent en œuvre 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 discord
    pip3 install requests
    pip3 install urllib3==1.26.6
  2. Copiez et enregistrez les scripts suivants correspondant aux différentes parties de la logique du bot dans le même répertoire :

    bot_config.py
    BOT_TOKEN = ""
    OPEN_CLOUD_API_KEY = ""
    ROBLOX_WEBHOOK_SECRET = ""
    # Dictionnaire de l'ID de lieu de départ à
    # (ID d'univers, liste de (nom des magasins de données, portée, et clé d'entrée)) pour
    # Magasins 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 d'univers
    222222222,
    [
    ("StandardDataStore1", "Scope1", "Key1_{user_id}"),
    ("StandardDataStore1", "Scope1", "Key2_{user_id}"),
    ("StandardDataStore2", "Scope1", "Key3_{user_id}")
    ]
    ),
    33333333: (
    444444444,
    [
    ("StandardDataStore3", "Scope1", "Key1_{user_id}")
    ]
    )
    }
    # Dictionnaire de l'ID de lieu de départ à
    # (ID d'univers, liste de (nom des magasins de données, portée, et clé d'entrée)) pour
    # Magasins de données ordonnés
    # 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
    """
    Appelle l'API Open Cloud des magasins de données pour supprimer toutes les entrées pour un user_id configuré dans
    STANDARD_DATA_STORE_ENTRIES. Renvoie une liste de suppressions réussies et d'échecs de suppression.
    """
    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
    """
    Appelle l'API Open Cloud des magasins de données ordonnés pour supprimer toutes les entrées pour un user_id configuré dans
    ORDERED_DATA_STORE_ENTRIES. Renvoie une liste de suppressions réussies et d'échecs de suppression.
    """
    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
    """
    Analyse le message reçu pour la signature et l'horodatage de Roblox, le pied de page n'est défini que si vous
    configurez un secret de webhook
    """
    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
    """
    Vérifie la signature Roblox avec le secret configuré pour vérifier sa validité
    """
    def validate_signature(message, signature, timestamp):
    if not message or not signature or not timestamp:
    return False
    # Empêche les attaques de rejeu dans une 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
    """
    Analyse un message de webhook reçu sur Discord. Extrait l'identifiant de l'utilisateur, empêche les attaques de rejeu
    basées sur l'horodatage reçu et vérifie la signature Roblox avec le secret configuré pour vérifier la validité.
    """
    def parse_message(message):
    # Analyse le message reçu pour l'ID utilisateur et l'ID du jeu
    if len(message.embeds) != 1 or \
    not message.embeds[0].description:
    return "", []
    description_match = re.match(
    r"Vous avez reçu une nouvelle notification pour le Droit à l'Effacement pour l'ID utilisateur : (.*) dans " +
    r"le(s) jeu(x) avec ID : (.*)",
    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 "", []
    discord_bot.py
    import discord
    import bot_config
    import data_stores_api
    import message_parser
    def run():
    intents = discord.Intents.default()
    intents.message_content = True
    client = discord.Client(intents=intents)
    @client.event
    async def on_ready():
    print(f"{client.user} écoute les messages de Droit à l'Effacement")
    """
    Gestionnaire pour les messages de webhook de Roblox
    """
    @client.event
    async def on_message(message):
    # Analyse 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 utilisateurs 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"Données supprimées pour les magasins de données standard pour " +
    f"l'ID utilisateur : {user_id}, données : {dict(successes)}")
    if failures:
    await message.reply(f"Échec de la suppression des données des magasins de données standard pour " +
    f"l'ID utilisateur : {user_id}, données : {dict(failures)}")
    # Supprime les données des utilisateurs des magasins de données ordonnés
    [successes, failures] = data_stores_api.delete_ordered_data_stores(user_id, start_place_ids)
    if successes:
    await message.reply(f"Données supprimées pour les magasins de données ordonnés pour " +
    f"l'ID utilisateur : {user_id}, données : {dict(successes)}")
    if failures:
    await message.reply(f"Échec de la suppression des données des magasins de données ordonnés pour " +
    f"l'ID utilisateur : {user_id}, données : {dict(failures)}")
    client.run(bot_config.BOT_TOKEN)
    if __name__ == "__main__":
    run()
  3. Dans le fichier bot_config.py pour la configuration principale du bot :

    1. Définissez BOT_TOKEN sur le token généré par votre bot.
    2. Définissez OPEN_CLOUD_API_KEY comme la 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 Creator Dashboard.
    4. Dans les dictionnaires STANDARD_DATA_STORE_ENTRIES et ORDERED_DATA_STORE_ENTRIES pour localiser le magasin de données de chaque enregistrement à supprimer :
      1. Ajoutez vos IDs de lieu de départ copiés en tant que clés.
      2. Ajoutez les IDs d'univers comme premier élément du tuple de valeur.
      3. Remplacez le deuxième élément du tuple par le nom, la portée, le nom de la clé d'entrée et l'ID 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 Discord
    python3 discord_bot.py
  5. Le bot commence alors à écouter et à vérifier les webhooks Roblox pour les demandes de droit à l'effacement et appelle l'endpoint Open Cloud pour supprimer le magasin de données correspondant.

Tester

Vous pouvez créer et exécuter un message de test pour vérifier que votre programme personnalisé peut traiter correctement les demandes de droit à l'effacement et supprimer les données PII :

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

    Exemple de requête
    curl -X POST {serverUrl}
    -H 'Content-Type: application/json'
    -d '{
    "embeds":[{
    "title":"RightToErasureRequest",
    "description":"Vous avez reçu une nouvelle notification pour le Droit à l'Effacement pour l'ID utilisateur : {userIds} dans le(s) jeu(x) avec ID : {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 de webhook :

    1. Générez une Roblox-Signature en appliquant l'encodage HMAC-SHA256 à votre clé secrète webhook.
    2. Définissez l'heure actuelle en utilisant le timestamp UTC en secondes comme Timestamp.
  3. Rassemblez la description au format suivant :

    Format du champ Description
    {Timestamp}. Vous avez reçu une nouvelle notification pour le Droit à l'Effacement pour l'ID utilisateur : {userId} dans le(s) jeu(x) avec ID : {gameIds}`.

    Par exemple :

    Exemple de champ Description
    1683927229. Vous avez reçu une nouvelle notification pour le Droit à l'Effacement pour l'ID utilisateur : 2425654247 dans le(s) jeu(x) avec ID : 10539205763, 13260950955

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

Exemple de corps
{
"embeds": [
{
"title": "RightToErasureRequest",
"description": "Vous avez reçu une nouvelle notification pour le Droit à l'Effacement pour l'ID utilisateur : 2425654247 dans le(s) jeu(x) avec ID : 10539205763, 13260950955",
"footer": {
"icon_url": "https://create.roblox.com/dashboard/assets/webhooks/roblox_logo_metal.png",
"text": "Roblox-Signature: UIe6GJ78MHCmU/zUKBYP3LV0lAqwWRFR6UEfPt1xBFw=, Timestamp: 1683927229"
}
}
]
}
©2026 Société Roblox. Roblox, le logo Roblox et Powering Imagination font partie de nos marques déposées aux États-Unis et dans d'autres pays.